Logging
Structured log levels & scoped channels
Nucleus ships with a structured logger that every internal subsystem writes through. The logging block lets you tune two independent dials: how severe a message must be to appear (level), and which feature areas are allowed to speak at all (scopes).
Severity filtering is the familiar threshold model. Scope filtering is the powerful part — because each log line is tagged with a dotted scope like authentication.login or entity.create, you can silence entire subsystems or zoom in on exactly one while debugging, without touching code.
Severity level#
A single threshold. Any message below the configured level is dropped before it reaches a transport. Levels are ordered debug < info < warn < error < fatal, so level: "warn" keeps warnings, errors and fatals while discarding debug and info chatter.
level'debug' | 'info' | 'warn' | 'error' | 'fatal'OptionalThe minimum severity that will be emitted. In development the logger also colorizes and pretty-prints; in production (NODE_ENV=production) it switches to compact output and drops caller info for performance.
debug— Everything, including verbose internal tracing. Ideal locally, far too noisy for production.info— Lifecycle and notable events. The default and a sensible production baseline.warn— Recoverable problems and suspicious conditions only.error— Failed operations only.fatal— Process-threatening failures only.
"info"Scope filtering#
Scopes are dotted identifiers — category.subcategory — attached to log lines across the framework. The scopes array is an allow-list: only messages whose scope matches an entry are shown. This is orthogonal to level, so you combine them (e.g. only warnings, and only from authentication).
1{2 "logging": {3 "level": "debug",4 "scopes": ["authentication.*", "middleware.auth"]5 }6}scopesstring[]OptionalAllow-list of scopes to emit. Use "*" to enable everything (the default). Use a category wildcard like "authentication.*" to keep one subsystem. Provide exact scopes like "tenant.provision" for surgical focus. An empty array [] silences all scoped logs, leaving only unscoped framework messages.
Example: ["authentication.*", "tenant.provision", "entity.create"]
["*"]Under the hood — the Logger#
A single Logger instance (exported as logger, also Logger.getInstance()) backs every subsystem. It is a structured logger with pluggable transports, request correlation and built-in audit — the two config dials above are the tip of it.
scoped(scope)ScopedLoggerOptionalHow the scope tags above are produced: a subsystem calls logger.scoped('authentication.login') and every line it emits carries that scope. If the scope isn't in your allow-list the calls become genuine no-ops — filtering happens at the source, not just at output, so disabled scopes cost almost nothing.
child(context) / withCorrelationId(id)LoggerOptionalPer-request child loggers carry a correlation id (shown as the first 8 chars in pretty output) and a merged context object, so every line from one request is traceable. Context is passed through redactSensitiveData first, so configured secret keys never reach a transport.
transportsConsoleTransport · BufferedTransport · DatabaseAuditTransportOptionalConsole output is pretty + colorised in development and compact JSON in production (NODE_ENV-driven), with caller file:line attached only in dev. BufferedTransport batches entries (flush at 100 or every 1s). Audit is part of the logger: a DatabaseAuditTransport persists audit entries to your audit table — addTransport/addAuditTransport let you attach more sinks.
request() / db() / time() / timeAsync()helpersOptionalConvenience emitters used across the framework: request() logs method/path/status and auto-selects level by status (5xx→error, 4xx→warn, else info); db() logs a query with table + duration + row count; time()/timeAsync() measure and log an operation's duration.
audit() / trace()audit bridgeOptionalThe same logger writes the immutable audit trail (see the Audit section). trace() can both log and conditionally write an audit entry in one call, gated by auditEnabled — which is why audit and logging share this one component.
Related sections