exisjs/logger
The exisjs/logger module provides structured logging powered by Pino. It includes a single-line terminal formatter for development, structured NDJSON output for production environments, automatic credential redaction, request-scoped loggers, and distributed tracing integration.
Module Exports
import { logger, configureLogger, setLogger, createLogger, createSilentLogger, isLogger, formatBytes, formatLatency, formatMethod, formatStatus, formatUrl, formatIp, extractValidationSummary, extractErrorMessage,} from 'exisjs/logger'
import type { Logger, LoggerConfig } from 'exisjs/logger'Global Logger Singleton (logger)
logger is a global proxy satisfying the Logger interface. It can be imported and executed anywhere across controllers, services, repositories, or background tasks.
import { logger } from 'exisjs/logger'
// Level logging methodslogger.info('Payment service initialized')logger.warn({ ip: req.ip, attempts: 3 }, 'Multiple failed login attempts')logger.error({ err, orderId: 'ord_123' }, 'Stripe webhook processing failed')logger.debug({ payload }, 'Parsed RPC frame')logger.trace('Entering database connection pool checkout')logger.fatal({ err }, 'Unrecoverable process state')
// Contextual child loggersconst workerLogger = logger.child({ workerId: 'worker-1', queue: 'emails' })workerLogger.info('Processing email batch')Logger Interface
export interface LogFn { (msg: string, ...args: unknown[]): void (obj: Record<string, unknown>, msg?: string, ...args: unknown[]): void}
export interface Logger { fatal: LogFn error: LogFn warn: LogFn info: LogFn debug: LogFn trace: LogFn silent: LogFn child(bindings: Record<string, unknown>): Logger level: string}Configuration (configureLogger)
Configures global logger settings, default log levels, terminal pretty printing, and sensitive field redaction.
function configureLogger( config: LoggerConfig & { replaceRedact?: boolean }): voidOptions Reference (LoggerConfig)
| Property | Type | Default | Description |
|---|---|---|---|
level | 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent' | 'info' | Minimum severity level to emit. |
pretty | boolean | NODE_ENV !== 'production' | Whether to use formatted single-line terminal output or raw NDJSON. |
redact | string[] | See Redaction section | Array of object property paths to censor. |
replaceRedact | boolean | false | When true, overrides the default redact list instead of merging with it. |
Example
import { configureLogger } from 'exisjs/logger'
configureLogger({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', pretty: process.env.NODE_ENV !== 'production', redact: ['*.password', '*.token', '*.secret', 'req.headers.cookie'],})Custom Logger Replacement (setLogger)
Replaces the underlying global logger instance with a custom Pino instance or third-party logger implementing the Logger interface.
function setLogger(custom: Logger): voidExample: Custom Transport with pino-loki
import { setLogger } from 'exisjs/logger'import pino from 'pino'
const customPino = pino({ level: 'info', transport: { target: 'pino-loki', options: { host: 'http://localhost:3100' }, },})
setLogger(customPino)Sensitive Data Redaction
By default, the logger censors sensitive fields in logged objects, replacing values with "[Redacted]" before serialization:
const DEFAULT_REDACT = [ 'req.headers.authorization', 'req.headers.cookie', 'req.body.password', 'req.body.token', 'req.body.secret', '*.password', '*.token', '*.secret', 'password', 'token', 'secret',]Request-Scoped Logging (req.log)
Every incoming ExisRequest provides a lazy child logger bound to req.log containing contextual request metadata:
requestId: Unique request identifier (req.requestId).method: HTTP method (e.g.GET,POST).url: Request path.
import { controller, route } from 'exisjs/router'
export default controller({ create: route.post('/', { async handle({ req, body }) { req.log.info({ orderId: body.id }, 'Creating order record') return { success: true } }, }),})Distributed Tracing & OpenTelemetry
When @opentelemetry/api is present in the application runtime, log entries automatically include active distributed tracing identifiers:
{ "level": 30, "time": 1774286390000, "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7", "msg": "Order created successfully"}Formatting Utilities
The module exports helper utilities for formatting terminal outputs:
| Function | Signature | Output Example |
|---|---|---|
formatBytes | (bytes?: number) => string | 1420 -> '1.4 KB' |
formatLatency | (ms?: number) => string | 14.5 -> '\x1b[32m14.5ms\x1b[0m' |
formatMethod | (method?: string) => string | 'GET' -> '\x1b[1;36mGET \x1b[0m' |
formatStatus | (status: number) => string | 200 -> '\x1b[1;32m200 OK\x1b[0m' |
formatIp | (ip?: string) => string | '192.168.1.10' -> formatted IP string |
createSilentLogger | () => Logger | Returns a no-op logger instance |
isLogger | (obj: unknown) => obj is Logger | Type guard checking Logger compliance |