import pino from 'pino'; import type { LogForwarder, LogEntry } from '../services/log-forwarder.js'; let _logForwarder: LogForwarder | null = null; /** * Register a LogForwarder instance. * Once registered, all loggers created via `createLogger` will * also forward their output to the cloud backend. */ export function setLogForwarder(forwarder: LogForwarder): void { _logForwarder = forwarder; } export function createLogger(name: string, level = 'info') { const logger = pino({ name, level, transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty', options: { colorize: true } } : undefined, }); // Wrap the logger to also forward logs when a forwarder is set. // We hook into Pino's internal write by using `onChild` isn't available, // so we use a proxy on the logging methods. return new Proxy(logger, { get(target, prop, receiver) { const value = Reflect.get(target, prop, receiver); // Intercept logging methods: trace, debug, info, warn, error, fatal if ( _logForwarder && typeof prop === 'string' && ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].includes(prop) && typeof value === 'function' ) { return (...args: unknown[]) => { // Call original (value as (...a: unknown[]) => void).apply(target, args); // Forward to the cloud try { const levelNum = target.levels.values[prop] ?? 30; let msg = ''; let extra: Record = {}; if (typeof args[0] === 'string') { msg = args[0]; } else if (typeof args[0] === 'object' && args[0] !== null) { extra = args[0] as Record; if (typeof args[1] === 'string') { msg = args[1]; } } const entry: LogEntry = { level: levelNum, time: Date.now(), msg, name, ...extra, }; _logForwarder!.ingest(entry); } catch { // Never let forwarding break the app } }; } return value; }, }); } export type Logger = pino.Logger;