Skip to content

Commit 4e07435

Browse files
committed
Logging example
1 parent 7a3698c commit 4e07435

File tree

10 files changed

+423
-9
lines changed

10 files changed

+423
-9
lines changed

Cargo.lock

Lines changed: 31 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"examples/cpu-count",
55
"examples/gzip-stream",
66
"examples/hello-world",
7+
"examples/logging",
78
]
89

910
[profile.release]

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ All examples are for [`napi-backend`][napi-migration]. For examples using `legac
1818
| [`cpu-count`][cpu-count] | Return the number of CPUs |
1919
| [`gzip-stream`][gzip-stream] | Asynchronously compress a stream of data |
2020
| [`hello-world`][hello-world] | Return a JS String with a greeting message |
21+
| [`logging`][logging] | Connects Rust logging to Node.js logging |
2122

2223
[async-sqlite]: examples/async-sqlite
2324
[cpu-count]: examples/cpu-count
2425
[gzip-stream]: examples/gzip-stream
2526
[hello-world]: examples/hello-world
27+
[logging]: examples/logging
2628

2729
## Contributing
2830

examples/logging/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "logging"
3+
version = "0.1.0"
4+
description = "Neon Logging Example"
5+
license = "MIT"
6+
edition = "2018"
7+
exclude = ["index.node"]
8+
9+
[lib]
10+
crate-type = ["cdylib"]
11+
12+
[dependencies]
13+
log = "0.4"
14+
15+
[dependencies.neon]
16+
version = "0.10.0-alpha.3"
17+
default-features = false
18+
features = ["napi-6", "channel-api"]

examples/logging/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Logging
2+
3+
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.
4+
5+
## Usage
6+
7+
```sh
8+
# Only `INFO` logs on the top level crate
9+
DEBUG="INFO:logging" npm start
10+
11+
# All logs on the top level crate
12+
DEBUG="*:logging" npm start
13+
14+
# All `INFO` logs on any Rust crate
15+
DEBUG="INFO:*" npm start
16+
17+
# All `WARN` and higher logs in our crate
18+
DEBUG="WARN:logging,ERROR:logging"
19+
```
20+
21+
## Libraries
22+
23+
### [`log`][log] crate
24+
25+
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.
26+
27+
### [`debug`][debug] module
28+
29+
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.
30+
31+
## Design
32+
33+
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.
34+
35+
### Initialization
36+
37+
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.
38+
39+
### Loggers
40+
41+
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.
42+
43+
## Limitations
44+
45+
### Levels
46+
47+
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.
48+
49+
### Multiple Contexts
50+
51+
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.
52+
53+
### Runtime level changes
54+
55+
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.
56+
57+
[log]: https://crates.io/crates/log
58+
[log-trait]: https://docs.rs/log/latest/log/trait.Log.html
59+
[debug]: https://www.npmjs.com/package/debug
60+
[env-logger]: https://crates.io/crates/env-logger
61+
[express]: https://www.npmjs.com/package/express
62+
[channel]: https://docs.rs/neon/latest/neon/event/struct.Channel.html

examples/logging/package-lock.json

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/logging/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "logging",
3+
"version": "0.1.0",
4+
"description": "Neon Logging Example",
5+
"main": "index.node",
6+
"scripts": {
7+
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
8+
"install": "npm run build",
9+
"start": "node run",
10+
"test": "cargo test"
11+
},
12+
"license": "MIT",
13+
"devDependencies": {
14+
"cargo-cp-artifact": "^0.1"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "git+https://github.com/neon-bindings/examples.git"
19+
},
20+
"keywords": [
21+
"Neon",
22+
"Examples"
23+
],
24+
"bugs": {
25+
"url": "https://github.com/neon-bindings/examples/issues"
26+
},
27+
"homepage": "https://github.com/neon-bindings/examples#readme",
28+
"dependencies": {
29+
"debug": "^4.3.3"
30+
}
31+
}

examples/logging/run.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"use strict";
2+
3+
const debug = require("debug");
4+
const { hello, init } = require(".");
5+
6+
// Must be called to initialized logging
7+
init(debug);
8+
9+
// Call an example function
10+
hello();
11+
12+
// Give logs a chance to flush
13+
setTimeout(() => {}, 500);

examples/logging/src/lib.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use neon::prelude::*;
2+
3+
use crate::logger::Logger;
4+
5+
mod logger;
6+
7+
// `init(debug)` must be called before using any other functionality.
8+
//
9+
// An exported initialization function is a common pattern in Neon. Since
10+
// Node-API does not expose `require`, a JavaScript wrapper requires the
11+
// `debug` module and passes it to `init` where logging is initialized.
12+
fn init(mut cx: FunctionContext) -> JsResult<JsUndefined> {
13+
let debug = cx.argument::<JsFunction>(0)?;
14+
15+
Logger::init(&mut cx, debug)?;
16+
17+
log::info!("Module initialized");
18+
19+
Ok(cx.undefined())
20+
}
21+
22+
// Example function with logging
23+
fn hello(mut cx: FunctionContext) -> JsResult<JsString> {
24+
log::trace!("Called `hello` function");
25+
26+
Ok(cx.string("hello node"))
27+
}
28+
29+
#[neon::main]
30+
fn main(mut cx: ModuleContext) -> NeonResult<()> {
31+
cx.export_function("init", init)?;
32+
cx.export_function("hello", hello)?;
33+
34+
Ok(())
35+
}

0 commit comments

Comments
 (0)