@nextrush/loggerLogger
Request logging middleware with correlation IDs and structured output.
HTTP requests leave no trace by default. When a production issue hits, you need structured logs with correlation IDs to track requests across services and find the failure point.
The logger middleware solves this: structured request/response logging, automatic correlation ID tracking, and a request-scoped logger attached to every context as ctx.log.
Source & internals
Default Behavior
With default options, logger():
- Logs request completion with method, path, status code, and duration
- Generates a UUID correlation ID per request (via
crypto.randomUUID()) - Sets the correlation ID in the
x-request-idresponse header - Attaches a scoped logger to
ctx.logwith the correlation ID - Logs request start in development only (based on
isProductionBuild()) - Uses
infolevel for 2xx/3xx,warnfor 4xx,errorfor 5xx responses
Installation
$ pnpm add @nextrush/logger
This package wraps @nextrush/log and re-exports all its functionality.
Minimal Usage
Register the logger middleware before route handlers. It must run first to attach ctx.log and
track request timing.
import { createApp } from '@nextrush/core';
import { logger } from '@nextrush/logger';
const app = createApp();
app.use(logger());
app.use(async (ctx) => {
ctx.log.info('Processing request');
ctx.json({ ok: true });
});The middleware attaches ctx.log — a request-scoped logger that includes the correlation ID in every log entry.
Configuration Options
LoggerMiddlewareOptions controls the middleware behavior. It extends LoggerOptions from @nextrush/log.
Middleware Options
| Property | Type | Description |
|---|---|---|
skip? | (ctx: Context) => boolean | Skip logging for matching requests |
formatMessage? | (ctx: Context, duration: number) => string= `${method} ${path}` | Custom log message formatter |
successLevel? | LogLevel= "info" | Log level for 2xx/3xx responses |
clientErrorLevel? | LogLevel= "warn" | Log level for 4xx responses |
serverErrorLevel? | LogLevel= "error" | Log level for 5xx responses |
logRequestStart? | boolean= true in dev, false in production | Log when request starts |
correlationIdHeader? | string= "x-request-id" | Header name for correlation ID |
generateCorrelationId? | boolean= true | Generate ID if not present in headers |
context? | string= "nextrush" | Logger namespace prefix |
Inherited from LoggerOptions
| Property | Type | Description |
|---|---|---|
minLevel? | LogLevel | Minimum log level to output |
silent? | boolean= false | Suppress console output (transports still fire) |
pretty? | boolean | Force pretty printing regardless of environment |
colors? | boolean | Enable/disable terminal colors |
redact? | boolean= true in production | Redact sensitive keys in log output |
sensitiveKeys? | string[] | Additional keys to redact (merged with defaults) |
transports? | LogTransport[] | Custom log transports |
timestamps? | boolean | Include timestamps in output |
metadata? | LogContext | Additional metadata for all log entries |
samplingRate? | number | Sampling rate for debug logs in production (0–1) |
env? | 'development' | 'test' | 'production' | Environment preset |
app.use(
logger({
minLevel: 'info',
skip: (ctx) => ctx.path === '/health',
logRequestStart: false,
correlationIdHeader: 'x-request-id',
context: 'api',
})
);Programmatic API (re-exported from @nextrush/log)
Beyond the middleware surface above, this package re-exports its entire
underlying @nextrush/log library — useful for application code that needs
structured logging outside a request (startup, background jobs, CLI
scripts).
Configuration
| Property | Type | Description |
|---|---|---|
configure | (config: Partial<GlobalLoggerConfig>) => void | Set global logger defaults (level, transports, redaction) for every logger created afterward. |
configureFromEnv | () => void | Populate global config from environment variables (e.g. LOG_LEVEL). |
getGlobalConfig | () => GlobalLoggerConfig | Read the current global configuration. |
resetGlobalConfig | () => void | Reset global configuration to defaults — mainly for test isolation. |
setGlobalLevel | (level: LogLevel) => void | Set the minimum log level globally. |
onConfigChange | (cb: (config: GlobalLoggerConfig) => void) => () => void | Subscribe to global config changes; returns an unsubscribe function. |
Transports
| Property | Type | Description |
|---|---|---|
createConsoleTransport | (options?) => LogTransport | The default transport — writes to stdout/stderr. |
createBatchTransport | (transport: LogTransport, options?) => LogTransport | Batches entries before forwarding, to reduce I/O overhead under high log volume. |
createFilteredTransport | (transport: LogTransport, predicate) => LogTransport | Wraps a transport to only forward entries matching a predicate. |
createPredicateTransport | (predicate, transport: LogTransport) => LogTransport | Alias form of createFilteredTransport. |
createRateLimitedTransport | (transport: LogTransport, options: RateLimitOptions) => LogTransport | Caps entries-per-window forwarded to the underlying transport. |
createNamespaceRateLimitedTransport | (transport: LogTransport, limits: NamespaceRateLimits) => LogTransport | Per-namespace variant of the rate-limited transport. |
addGlobalTransport | (transport: LogTransport) => void | Register an additional transport for every logger. |
clearGlobalTransports | () => void | Remove all globally registered transports. |
Formatters
| Property | Type | Description |
|---|---|---|
formatJSON | (entry: LogEntry) => string | Newline-delimited JSON — the default in production. |
formatPrettyJSON | (entry: LogEntry) => string | Indented, human-readable JSON. |
formatPrettyTerminal | (entry: LogEntry) => string | Colorized terminal output — the default in development. |
formatPrettyTimestamp | (date: Date) => string | Formats a timestamp for the pretty-terminal formatter's header. |
formatTimestamp | (date: Date) => string | Formats a timestamp for the JSON formatters (ISO 8601). |
Context (AsyncLocalStorage)
| Property | Type | Description |
|---|---|---|
runWithContext | <T>(context: LogContext, fn: () => T) => T | Runs fn with LogContext available to any logger call inside it, without explicit passing. |
getAsyncContext | () => LogContext | undefined | Reads the current AsyncLocalStorage-backed context, if any. |
getContextCorrelationId | () => string | undefined | Convenience accessor for the correlation ID on the current context. |
getContextMetadata | () => Record<string, unknown> | undefined | Reads arbitrary metadata attached to the current context. |
isAsyncContextAvailable | () => boolean | Whether AsyncLocalStorage is supported in the current runtime. |
createContextMiddleware | () => Middleware | A NextRush middleware that seeds runWithContext for the request lifecycle. |
Redaction & serialization
| Property | Type | Description |
|---|---|---|
DEFAULT_SENSITIVE_KEYS | readonly string[] | The built-in list of keys redacted by default (password, token, authorization, etc.). |
mergeSensitiveKeys | (...lists: string[][]) => string[] | Combines the default list with custom keys. |
shouldRedact | (key: string, sensitiveKeys: string[]) => boolean | Whether a given key matches the redaction list. |
containsSensitivePattern | (value: string) => boolean | Heuristic check for values that look sensitive even under an unlisted key (e.g. a JWT-shaped string). |
redactSensitiveValues | (data: unknown, sensitiveKeys: string[]) => unknown | Deep-redacts matching keys in an object before logging. |
sanitizeContext | (context: LogContext, sensitiveKeys: string[]) => LogContext | Applies redaction to a LogContext object specifically. |
safeSerialize | (value: unknown) => unknown | Serializes a value defensively — handles circular references and non-JSON-safe types. |
serializeError | (error: Error) => SerializedError | Converts an Error into a plain, loggable object (message, stack, cause). |
Levels & runtime
| Property | Type | Description |
|---|---|---|
LOG_LEVELS | readonly LogLevel[] | Every level in ascending severity: ['trace', 'debug', 'info', 'warn', 'error', 'fatal']. |
LOG_LEVEL_PRIORITY | Record<LogLevel, number> | Numeric priority per level, used for minLevel comparisons. |
compareLevels | (a: LogLevel, b: LogLevel) => number | Compares two levels by priority. |
isValidLogLevel | (value: unknown) => value is LogLevel | Type guard for a LogLevel string. |
parseLogLevel | (value: string, fallback: LogLevel) => LogLevel | Parses an environment/config string into a LogLevel, falling back if invalid. |
shouldLog | (level: LogLevel, minLevel: LogLevel) => boolean | Whether an entry at level clears the minLevel threshold. |
isProductionBuild | () => boolean | Whether the current build/environment is production (drives several defaults). |
isError | (value: unknown) => value is Error | Type guard for Error-like values. |
detectRuntime | () => RuntimeEnvironment | Detects Node.js/Bun/Deno/browser/edge at the logging layer (independent of `@nextrush/runtime`). |
getRuntime | () => RuntimeEnvironment | Cached accessor for the detected runtime. |
getRuntimeInfo | () => RuntimeInfo | Extended runtime detail (name + version) for log metadata. |
getEnvVar | (name: string) => string | undefined | Cross-runtime environment variable read (Node, Deno, Bun). |
getProcessId | () => number | undefined | Process ID for log metadata, where available. |
getTime | () => number | High-resolution timestamp source used for duration calculations. |
isNamespaceEnabled | (namespace: string) => boolean | Whether a given logger namespace currently passes the enable/disable filters below. |
enableNamespaces | (...patterns: string[]) => void | Enables logging for namespaces matching the given patterns. |
disableNamespaces | (...patterns: string[]) => void | Disables logging for namespaces matching the given patterns. |
enableLogging | () => void | Globally re-enables logging after disableLogging(). |
disableLogging | () => void | Globally suppresses all log output — mainly for tests. |
log | (level: LogLevel, message: string, ...args) => void | The lowest-level log call, bypassing a named Logger instance. |
defaultLogger | ILogger | The library's own default logger instance (aliased from @nextrush/log's `logger`). |
scopedLogger | (namespace: string) => ILogger | Alias for creating a namespace-scoped logger, equivalent to createLogger(namespace). |
Integration Example
Correlation IDs Across Services
Track requests through microservices using the correlation ID:
import { logger } from '@nextrush/logger';
import type { LoggerContext } from '@nextrush/logger';
app.use(logger());
app.use(async (ctx) => {
const correlationId = (ctx as LoggerContext).log.getCorrelationId();
const response = await fetch('https://api.internal/data', {
headers: { 'x-request-id': correlationId ?? '' },
});
ctx.json(await response.json());
});Do not use ctx.get('x-request-id') to retrieve a generated correlation ID. The middleware sets
it on the response via ctx.set(). Use ctx.log.getCorrelationId() instead.
Logger Without Request Logging
Use attachLogger() when you need ctx.log but not automatic request/response logging:
import { attachLogger } from '@nextrush/logger';
app.use(attachLogger({ context: 'api' }));
app.use(async (ctx) => {
ctx.log.info('Handler called');
ctx.json({ ok: true });
});Type Guards
import { hasLogger, getLogger } from '@nextrush/logger';
app.use(async (ctx) => {
if (hasLogger(ctx)) {
ctx.log.info('Logger available');
}
// Get logger with automatic fallback
const log = getLogger(ctx, 'fallback-context');
log.info('Works with or without middleware');
});Standalone Logger
Create loggers outside of middleware context:
import { createLogger } from '@nextrush/logger';
const log = createLogger('MyService');
log.info('Server starting');
log.error('Connection failed', new Error('timeout'));Compatibility
| Runtime | Supported | Notes |
|---|---|---|
| Node.js >=22 | Yes | ESM-only |
| Bun / Deno | Yes | @nextrush/log detects these runtimes explicitly (detectRuntime()) and uses AsyncLocalStorage-based context where available |
| Edge / Cloudflare Workers / browsers | Partial | @nextrush/log falls back to a non-AsyncLocalStorage context path per its own runtime detection; correlation-ID propagation through this package's middleware still works, since it does not depend on AsyncLocalStorage itself |
@nextrush/core is an optional peer dependency — only needed for the Middleware/Context
types it references at compile time; the middleware itself only needs a NextRush-shaped Context.
Common Mistakes
Placing logger middleware after route handlers. The middleware must run before handlers to attach ctx.log. Register it early in the middleware chain.
Skipping health checks without the skip option. Health check endpoints generate high-volume logs with no diagnostic value. Use skip: (ctx) => ctx.path === '/health'.
Using redactKeys instead of sensitiveKeys. The option inherited from @nextrush/log is sensitiveKeys, not redactKeys.
Troubleshooting
ctx.log is undefined — The logger middleware did not run before your handler. Ensure app.use(logger()) is registered before route handlers. If using skip, verify your route is not being skipped.
Correlation ID not appearing in logs — Set generateCorrelationId: true (the default). If using a custom header name, ensure correlationIdHeader matches the header your upstream service sends.
Too many logs in production — Set minLevel: 'info' and logRequestStart: false to reduce volume. Use skip to exclude health checks and static asset paths.
Related
- Reference — All packages
- @nextrush/request-id — Request ID middleware
- @nextrush/timer — Response timing