Skip to content

lib: implement node:logger - #65840

Open
jasnell wants to merge 1 commit into
nodejs:mainfrom
jasnell:jasnell/node-logger
Open

lib: implement node:logger#65840
jasnell wants to merge 1 commit into
nodejs:mainfrom
jasnell:jasnell/node-logger

Conversation

@jasnell

@jasnell jasnell commented Sep 6, 2026

Copy link
Copy Markdown
Member

A simpler attempt to add a structured logging API.

Uses a provider model similar to VFS.

This implements four providers out of the box:

  • ConsoleProvider -- emits to stderr or stdout using Utf8Stream
  • EventProvider -- implements EventEmitter, emits logs as events
  • DiagnosticsProvider -- emits to diagnostics channels
  • AggregateProvider -- fans out to multiple providers
const { create } = require('node:logger');
const logger = create(); // default logger to console
logger.info('foo');

// ...
const als = new AsyncLocalStorage();
const logger2 = create(new ConsoleProvider({ pid: true }), {
  name: 'foo',
  bindings: {
    'abc': 'included in every log line',
    'xyz': als,  // current als.getStore() included in
                 // every log line
  }
});
logger2.info('foo', { baz: 1 });

The Logger itself keeps things as simple as possible, leaving actual handling of the log events to the providers, which can be fully customized. Easy to adapt to other loggers like pino or extend capabilities without directly touching the facade.

The current json output produced by the ConsoleProvider is:

{"attributes":{},"bindings":{},"level":{"name":"info","value":30},"message":"test","name":"foo","timestamp":1788677600214,"pid":670179}

But there are options to produce a "flattened" version

We should bikeshed the format a bit before this lands.


Updated... I've tweaked things just a bit more.

The module itself exports the create function directly, making it possible to do:

const create = require('node:logger');
const logger = create();

It still has all the exports off of it.

There's also a singleton default provider. When create() is called without a provider option, then the default singleton ConsoleProvider is created lazily. An application can override the default using setDefaultProvider...

const logger = require('node:logger');
logger.setDefaultProvider(new logger.EventProvider());

const log = logger();

log.warn('...');  // Uses the singleton EventProvider

This way, modules can just create a logger and dispatch to the application's default configured provider without worrying about where those will go.

Also includes a fix to fs.Utf8Stream's async and sync flush handling that I noticed wasn't working quite right when testing.

@jasnell
jasnell requested a review from mcollina September 6, 2026 06:51
@jasnell jasnell added the experimental Issues and PRs related to experimental features. label Sep 6, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/loaders
  • @nodejs/startup

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run. labels Sep 6, 2026
@TheOneTheOnlyJJ

Copy link
Copy Markdown
Contributor

@mertcanaltin, your experience from #60468 may be valuable here.

@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.10327% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.19%. Comparing base (1e0ebef) to head (efff647).
⚠️ Report is 26 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/streams/fast-utf8-stream.js 87.69% 16 Missing ⚠️
lib/logger.js 98.94% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65840      +/-   ##
==========================================
+ Coverage   90.17%   90.19%   +0.01%     
==========================================
  Files         771      772       +1     
  Lines      265097   265854     +757     
  Branches    50362    50559     +197     
==========================================
+ Hits       239054   239786     +732     
- Misses      17004    17013       +9     
- Partials     9039     9055      +16     
Files with missing lines Coverage Δ
lib/internal/bootstrap/realm.js 96.98% <100.00%> (+<0.01%) ⬆️
lib/logger.js 98.94% <98.94%> (ø)
lib/internal/streams/fast-utf8-stream.js 84.00% <87.69%> (+3.13%) ⬆️

... and 39 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mertcanaltin

Copy link
Copy Markdown
Member

The Logger keeps things as simple as possible, leaving actual handling of the log events to the providers, which can be fully customized. Easy to adapt to other loggers like pino or extend capabilities without directly touching the facade.

I think this step is very good for review. We started with a big step before, but it was very costly for reviewers.

I want to share some topics we had in #60468. I hope they help this PR.

Do custom levels need to be part of the public contract in the first version? I'm not sure. Level names and numeric ordering are a long-term cost for both users and providers.

My suggestion is to limit the first version to the built-in levels, so the API stays smaller. Providers can do their own mapping internally when necessary.

This comment is not a blocker for this PR.

@jasnell

jasnell commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Do custom levels need to be part of the public contract in the first version

I think they ought to be, yes, at least to an extent. I absolutely don't think we should do the automatic custom level method installation like what pino does (e.g. logger.foo(...)) but the generic logger.log(level, ... gives enough coverage to minimally meet the need.

@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

This comment was marked as outdated.

@jasnell
jasnell force-pushed the jasnell/node-logger branch 2 times, most recently from 1a8be55 to b1e0970 Compare September 7, 2026 17:08
Comment thread doc/api/logger.md Outdated
@jasnell
jasnell force-pushed the jasnell/node-logger branch 2 times, most recently from b1f2cc0 to 10db06e Compare September 7, 2026 20:33
A simpler attempt to add a structured logging API.

Uses a provider model similar to VFS.

This implements two providers out of the box,
ConsoleProvider and ReadableProvider. ConsoleProvider
is the default and uses Utf8Stream to emit to either
stdout or stderr.

```js
const { create } = require('node:logger');
const logger = create(); // default logger to console
logger.info('foo');

// ...
const als = new AsyncLocalStorage();
const logger2 = create(new ConsoleProvider({ pid: true }), {
  name: 'foo',
  bindings: {
    'abc': 'included in every log line',
    'xyz': als,  // current als.getStore() included in
                 // every log line
  }
});
logger2.info('foo', { baz: 1 });
```

The `Logger` keeps things as simple as possible, leaving
actual handling of the log events to the providers, which
can be fully customized. Easy to adapt to other loggers
like pino or extend capabilities without directly touching
the facade.

Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode
@jasnell
jasnell force-pushed the jasnell/node-logger branch from 10db06e to efff647 Compare September 7, 2026 23:17
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

experimental Issues and PRs related to experimental features. lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants