Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"examples/cpu-count",
"examples/gzip-stream",
"examples/hello-world",
"examples/logging",
"examples/tokio-fetch",
]

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ All examples are for [`napi-backend`][napi-migration]. For examples using `legac
| [`cpu-count`][cpu-count] | Return the number of CPUs |
| [`gzip-stream`][gzip-stream] | Asynchronously compress a stream of data |
| [`hello-world`][hello-world] | Return a JS String with a greeting message |
| [`logging`][logging] | Connects Rust logging to Node.js logging |
| [`tokio-fetch`][tokio-fetch] | Asynchronously fetch a node release date |

[async-sqlite]: examples/async-sqlite
[cpu-count]: examples/cpu-count
[gzip-stream]: examples/gzip-stream
[hello-world]: examples/hello-world
[logging]: examples/logging
[tokio-fetch]: examples/tokio-fetch

## Contributing
Expand Down
18 changes: 18 additions & 0 deletions examples/logging/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "logging"
version = "0.1.0"
description = "Neon Logging Example"
license = "MIT"
edition = "2018"
exclude = ["index.node"]

[lib]
crate-type = ["cdylib"]

[dependencies]
log = "0.4"

[dependencies.neon]
version = "0.10.0-alpha.3"
default-features = false
features = ["napi-6", "channel-api"]
62 changes: 62 additions & 0 deletions examples/logging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Logging

The logging example connects the Rust [`log`][log] crate to the Node [`debug`][debug] module. Neon modules can write logs directly to `stdout` with typical crates like [`env_logger`][env-logger], but connecting logging to [`debug`][debug] more seamlessly integrates with the Node ecosystem.

## Usage

```sh
# Only `INFO` logs on the top level crate
DEBUG="INFO:logging" npm start

# All logs on the top level crate
DEBUG="*:logging" npm start

# All `INFO` logs on any Rust crate
DEBUG="INFO:*" npm start

# All `WARN` and higher logs in our crate
DEBUG="WARN:logging,ERROR:logging"
```

## Libraries

### [`log`][log] crate

The [`log`][log] crate provides a logging facade used throughout the Rust ecosystem. It provides convenient macros for logging, but does not provide any facility to write or display logs.

### [`debug`][debug] module

the [`debug`][debug] node module provides a decorated version of `console.error` and is used throughout the Node library ecosystem, including in the [`express`][express] HTTP framework. It allows configurable log filtering with the `DEBUG` environment variable.

## Design

Rust code uses the typical logging facilities, but in order for it to be used, the [`Log`][log-trait] must be implemented. This example provides a simple [`Log`][log-trait] implementation that delegates to the [`debug`][debug] node module.

### Initialization

At initialization, the module calls `global.require("debug")` to get a copy of the function used to create logger instances. This function, as well as `enabled` and a [`Channel`][channel] are used to create a `Logger` instance and initialize the `log` crate.

### Loggers

The `Logger` struct maintains a map of logger names to logger instances that are lazily created as needed. Each logger is in an `Option` with `None` representing the disabled state. If an entry is missing, it is assumed to be in the `enabled` state until it can be further evaluated.

## Limitations

### Levels

The provided implementation does not understand logger levels. Each level needs to be enabled individually. As an improvement, the logger level could be determined by checking `debug.enabled(...)` for each level from lowest to highest.

### Multiple Contexts

The `log` crate only supports a single global logger instance in a process. If the module is initialized multiple times with Web Workers, all logs will be sent to the instance that initialized.

### Runtime level changes

The `debug` module supports enabling and disabling logging at runtime, but for efficiency, our `Logging` implementation assumes that the result of `debug.enabled(..)` never changes.

[log]: https://crates.io/crates/log
[log-trait]: https://docs.rs/log/latest/log/trait.Log.html
[debug]: https://www.npmjs.com/package/debug
[env-logger]: https://crates.io/crates/env-logger
[express]: https://www.npmjs.com/package/express
[channel]: https://docs.rs/neon/latest/neon/event/struct.Channel.html
71 changes: 71 additions & 0 deletions examples/logging/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions examples/logging/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "logging",
"version": "0.1.0",
"description": "Neon Logging Example",
"main": "index.node",
"scripts": {
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
"install": "npm run build",
"start": "node run",
"test": "cargo test"
},
"license": "MIT",
"devDependencies": {
"cargo-cp-artifact": "^0.1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/neon-bindings/examples.git"
},
"keywords": [
"Neon",
"Examples"
],
"bugs": {
"url": "https://github.com/neon-bindings/examples/issues"
},
"homepage": "https://github.com/neon-bindings/examples#readme",
"dependencies": {
"debug": "^4.3.3"
}
}
13 changes: 13 additions & 0 deletions examples/logging/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use strict";

const debug = require("debug");
const { hello, init } = require(".");

// Must be called to initialized logging
init(debug);

// Call an example function
hello();

// Give logs a chance to flush
setTimeout(() => {}, 500);
35 changes: 35 additions & 0 deletions examples/logging/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use neon::prelude::*;

use crate::logger::Logger;

mod logger;

// `init(debug)` must be called before using any other functionality.
//
// An exported initialization function is a common pattern in Neon. Since
// Node-API does not expose `require`, a JavaScript wrapper requires the
// `debug` module and passes it to `init` where logging is initialized.
fn init(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let debug = cx.argument::<JsFunction>(0)?;

Logger::init(&mut cx, debug)?;

log::info!("Module initialized");

Ok(cx.undefined())
}

// Example function with logging
fn hello(mut cx: FunctionContext) -> JsResult<JsString> {
log::trace!("Called `hello` function");

Ok(cx.string("hello node"))
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("init", init)?;
cx.export_function("hello", hello)?;

Ok(())
}
Loading