Concepts

Levels

Six levels, minimums, per-source overrides, runtime switches, per-sink restrictions.

The six levels#

logitSerilogpinoMeaning
verboseVerbosetrace (10)Everything, for the one afternoon you need it
debugDebugdebug (20)Internal flow, for developers
infoInformationinfo (30)What the system did — the default minimum
warnWarningwarn (40)Something is off and was handled
errorErrorerror (50)An operation failed
fatalFatalfatal (60)The process cannot go on

One method per level on every logger: log.verbose() … log.fatal(). log.write(level, …) takes the level as a value.

Wherever a level is read from text — configuration, LOGIT_LEVEL, parseLevel() — the other spellings are accepted too: information, warning, trace, critical, the three-letter codes, pino's numbers. parseLevel('Warning') is 'warn'; an unknown name throws, so a typo fails at startup instead of silencing a logger.

Minimum level#

Events below the minimum are dropped before anything is parsed or captured. The default is info, or LOGIT_LEVEL from the environment when set.

configure({ minimumLevel: 'debug' });

logger.isEnabled('debug') is the cheap check to put before expensive argument building:

if (log.isEnabled('verbose')) log.verbose('State: {@State}', buildExpensiveSnapshot());

Overrides by source#

Serilog's MinimumLevel.Override. Keys are SourceContext prefixes on dot boundaries; the longest match wins; a source with no match uses the minimum.

configure({
  minimumLevel: 'info',
  overrides: {
    next: 'warn',         // 'next' and 'next.anything' — the framework's own noise
    'app.db': 'verbose',  // 'app.db', 'app.db.query', … — but not 'app.dbx'
  },
});

log.forSource('app.db.query').verbose('…'); // written
log.forSource('next.router').info('…');     // dropped

LOGIT_OVERRIDES="next=warn,app.db=verbose" sets the same thing from the environment; explicit overrides win over it key by key.

Runtime switches#

A LevelSwitch is a level that can change while the application runs — Serilog's LoggingLevelSwitch. Use the same instance as the minimum, as an override, or on a sink, and flip it from an admin route or a signal handler:

import { LevelSwitch, configure } from '@unhingged/logit';

export const levelSwitch = new LevelSwitch('info');
configure({ minimumLevel: levelSwitch, overrides: { 'app.db': levelSwitch } });

// later, from an admin route:
levelSwitch.level = 'debug';

levelSwitch.onChange(fn) notifies listeners; logger.minimumLevel reports the current effective level for that logger's source.

Per-sink minimum#

Every built-in sink takes restrictedToMinimumLevel, and restricted(sink, level) wraps any other. It can only raise the bar — a sink never sees what the logger dropped:

configure({
  minimumLevel: 'debug',
  sinks: [
    consoleSink(),                                         // everything from debug
    seqSink({ serverUrl: '…', restrictedToMinimumLevel: 'info' }), // info and above
  ],
});

A sub-logger used as a sink has its own minimum and overrides as well — see Sinks.

Where the level shows up#

  • The pretty console prints the three-letter code — VRB DBG INF WRN ERR FTL — in a colour per level.
  • The JSON formatter writes "level":"info" by default; levelFormat: 'number' gives pino's 30, 'serilog' gives Information, 'upper' gives INFO.
  • CLEF writes Serilog's name in @l and omits it for info, as Serilog does.
  • Output templates: {Level} is Information, {Level:u3} is INF, {Level:w3} is inf, {Level:u} is INFORMATION.
  • OTLP: severity numbers 1, 5, 9, 13, 17, 21 with the standard texts.