typescriptbeginner

Structured JSON Logger

Build a structured logger with log levels, context, child loggers, and JSON output for Node.js services.

typescript
type LogLevel = 'debug' | 'info' | 'warn' | 'error';

const LEVELS: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };

interface LogEntry {
  level: LogLevel;
  message: string;
  timestamp: string;
  [key: string]: unknown;
}

class Logger {
  private context: Record<string, unknown>;
  private minLevel: number;

  constructor(
    private level: LogLevel = 'info',
    context: Record<string, unknown> = {},
  ) {
    this.minLevel = LEVELS[level];
    this.context = context;
  }

  private log(level: LogLevel, message: string, data?: Record<string, unknown>) {
    if (LEVELS[level] < this.minLevel) return;

    const entry: LogEntry = {
      level,
      message,
      timestamp: new Date().toISOString(),
      ...this.context,
      ...data,
    };

    const output = JSON.stringify(entry);
    if (level === 'error') console.error(output);
    else if (level === 'warn') console.warn(output);
    else console.log(output);
  }

  debug(message: string, data?: Record<string, unknown>) { this.log('debug', message, data); }
  info(message: string, data?: Record<string, unknown>) { this.log('info', message, data); }
  warn(message: string, data?: Record<string, unknown>) { this.log('warn', message, data); }
  error(message: string, data?: Record<string, unknown>) { this.log('error', message, data); }

  // Child logger inherits context
  child(context: Record<string, unknown>): Logger {
    return new Logger(this.level, { ...this.context, ...context });
  }

  // Timer utility
  time(label: string) {
    const start = performance.now();
    return {
      end: (data?: Record<string, unknown>) => {
        const duration = Math.round(performance.now() - start);
        this.info(`${label} completed`, { ...data, durationMs: duration });
      },
    };
  }
}

// Usage
const logger = new Logger('debug', { service: 'api' });
logger.info('Server starting', { port: 3000 });

const reqLogger = logger.child({ requestId: 'req-123', method: 'GET', path: '/users' });
reqLogger.info('Request received');

const timer = reqLogger.time('db-query');
// ... do work
timer.end({ rows: 42 });

reqLogger.warn('Slow response', { latencyMs: 1500 });
reqLogger.error('Failed', { error: 'Connection refused' });

Use Cases

  • Application logging for production
  • Request tracing with context
  • Performance measurement

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.