ExisJS

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 }): void

Options Reference (LoggerConfig)

PropertyTypeDefaultDescription
level'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent''info'Minimum severity level to emit.
prettybooleanNODE_ENV !== 'production'Whether to use formatted single-line terminal output or raw NDJSON.
redactstring[]See Redaction sectionArray of object property paths to censor.
replaceRedactbooleanfalseWhen true, overrides the default redact list instead of merging with it.

Example

src/http/server.ts
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): void

Example: Custom Transport with pino-loki

src/http/server.ts
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.
src/http/orders/route.ts
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:

FunctionSignatureOutput Example
formatBytes(bytes?: number) => string1420 -> '1.4 KB'
formatLatency(ms?: number) => string14.5 -> '\x1b[32m14.5ms\x1b[0m'
formatMethod(method?: string) => string'GET' -> '\x1b[1;36mGET \x1b[0m'
formatStatus(status: number) => string200 -> '\x1b[1;32m200 OK\x1b[0m'
formatIp(ip?: string) => string'192.168.1.10' -> formatted IP string
createSilentLogger() => LoggerReturns a no-op logger instance
isLogger(obj: unknown) => obj is LoggerType guard checking Logger compliance