/** * Unified Logger - Consolidated logging for all postgres.do packages * * This module provides a unified logging interface that replaces multiple * logger implementations across the codebase with a single, consistent API. * * ## Replaced Implementations * * - `CDCLogger` from `packages/postgres.do/src/cdc/logger.ts` * - `Logger` interface from `packages/postgres/src/storage/tiered-orchestrator.ts` * - `TenantRouterLogger` from `packages/postgres/src/routing/tenant-router.ts` * * ## Features * * - Multiple log levels (DEBUG, INFO, WARN, ERROR, SILENT) * - Structured logging with arbitrary metadata * - Child loggers with scoped context * - Log filtering by level * - Custom handlers/formatters (Console, JSON, or custom) * - Event capture for debugging with circular buffer * - Cloudflare Workers compatible (no Node.js dependencies) * * ## Quick Start * * ```typescript * import { createLogger, LogLevel } from '@dotdo/postgres-shared' * * // Create a basic logger * const logger = createLogger() * logger.info('Hello world') * * // Create a logger with configuration * const debugLogger = createLogger({ * level: LogLevel.DEBUG, * prefix: '[MyService]', * timestamps: true, * }) * * // Log with context * logger.info('User action', { userId: '123', action: 'login' }) * * // Create a child logger with scoped context * const requestLogger = logger.child({ requestId: 'abc-123' }) * requestLogger.info('Processing request') // includes requestId in context * ``` * * ## Migration Guide * * ### From CDCLogger * * ```typescript * // Before (CDCLogger) * const logger = new CDCLogger({ level: LogLevel.DEBUG, prefix: '[CDC]' }) * logger.info('Event', { subscriptionId: 'sub-123', data: { key: 'value' } }) * const subLogger = logger.withSubscription('sub-123') * * // After (Unified Logger) * const logger = createLogger({ level: LogLevel.DEBUG, prefix: '[CDC]' }) * logger.info('Event', { subscriptionId: 'sub-123', key: 'value' }) * const subLogger = logger.child({ subscriptionId: 'sub-123' }) * ``` * * ### From TieredOrchestrator Logger interface * * The unified logger is directly compatible with the simple Logger interface: * * ```typescript * // The simple interface from tiered-orchestrator: * // interface Logger { * // debug: (msg: string) => void * // info: (msg: string) => void * // warn: (msg: string) => void * // error: (msg: string) => void * // } * * // Just use createLogger() - it implements the same interface! * const logger = createLogger() * orchestrator.setLogger(logger) // Works directly! * ``` * * ### From TenantRouterLogger * * ```typescript * // Before (TenantRouterLogger) * logger.log({ level: 'info', message: 'Request', tenantId: 'acme' }) * * // After (Unified Logger) * logger.log({ level: LogLevel.INFO, message: 'Request' }) * // Or use the simpler API: * logger.info('Request', { tenantId: 'acme' }) * ``` * * @module logger */ /** * Log levels in order of verbosity (lower = more verbose) * * @example * ```typescript * // Set logger to only show warnings and errors * logger.setLevel(LogLevel.WARN) * * // Disable all logging * logger.setLevel(LogLevel.SILENT) * ``` */ export declare enum LogLevel { /** Detailed debugging information */ DEBUG = 0, /** General informational messages */ INFO = 1, /** Warning messages for potentially problematic situations */ WARN = 2, /** Error messages for serious problems */ ERROR = 3, /** No logging at all */ SILENT = 4 } /** * Log entry structure - unified from all implementations */ export interface LogEntry { /** Log level */ level: LogLevel; /** Human-readable level name */ levelName: string; /** Log message */ message: string; /** ISO timestamp (empty string if timestamps disabled) */ timestamp: string; /** Additional context data */ context?: Record; /** Error if this is an error log */ error?: Error; } /** * Custom log handler function type * * Implement this to create custom log destinations (e.g., remote logging, * file logging, etc.) * * @example * ```typescript * const remoteHandler: LogHandler = (entry) => { * fetch('/logs', { * method: 'POST', * body: JSON.stringify(entry), * }) * } * ``` */ export type LogHandler = (entry: LogEntry) => void; /** * Extended handler interface with additional methods * Used by MultiHandler and BatchHandler to expose management APIs */ export interface MultiLogHandler extends LogHandler { addHandler(handler: LogHandler): void; removeHandler(handler: LogHandler): void; } export interface BatchLogHandler extends LogHandler { flush(): void; } /** * Logger configuration */ export interface LoggerConfig { /** Minimum level to log (default: INFO) */ level?: LogLevel; /** Custom log handler (default: console) */ handler?: LogHandler; /** Whether to include timestamps (default: true) */ timestamps?: boolean; /** Prefix for all log messages */ prefix?: string; /** Whether to capture logs in memory for debugging (default: false) */ captureEvents?: boolean; /** Maximum number of events to capture in memory (default: 100) */ maxCapturedEvents?: number; } /** * Log context - arbitrary key-value pairs for structured logging * * Common context keys: * - `tenantId` - For multi-tenant applications * - `subscriptionId` - For CDC subscriptions * - `correlationId` - For distributed tracing * - `requestId` - For request tracking * - `error` - Error object (will be extracted automatically) */ export type LogContext = Record; /** * Logger interface - the unified logger contract */ export interface ILogger { debug(message: string, context?: LogContext): void; info(message: string, context?: LogContext): void; warn(message: string, context?: LogContext): void; error(message: string, context?: LogContext): void; log(entry: Partial): void; child(context: LogContext): ILogger; getLevel(): LogLevel; setLevel(level: LogLevel): void; setHandler(handler: LogHandler): void; enableCapture(maxEvents?: number): void; disableCapture(): void; getCapturedLogs(): LogEntry[]; clearCapturedLogs(): void; } /** * Console handler class with configurable color support * @deprecated Use ConsoleHandler factory function instead for better compatibility */ export declare class ConsoleHandlerClass { private handler; constructor(options?: { colors?: boolean | 'auto'; }); /** * Make the instance callable as a LogHandler */ call(entry: LogEntry): void; /** * Get the underlying handler function */ toHandler(): LogHandler; } /** * Console handler factory - creates a LogHandler for console output * * @param options.colors - Color mode: true (always), false (never), 'auto' (detect TTY) * @returns LogHandler function * * @example * ```typescript * // With colors * const logger = createLogger({ * handler: new ConsoleHandler({ colors: true }) * }) * * // Auto-detect (colors in terminal, plain in Workers) * const logger = createLogger({ * handler: new ConsoleHandler({ colors: 'auto' }) * }) * ``` */ export declare const ConsoleHandler: new (options?: { colors?: boolean | "auto"; }) => LogHandler; /** * JSON handler class for structured output */ export declare class JsonHandlerClass { private outputFn; constructor(options?: { output?: (line: string) => void; }); /** * Handle a log entry by outputting it as JSON */ call(entry: LogEntry): void; /** * Get the underlying handler function */ toHandler(): LogHandler; } /** * JSON handler factory - creates a LogHandler for JSON-formatted output * * Useful for structured logging to log aggregation services. * * @param options.output - Custom output function (default: console.log) * @returns LogHandler function * * @example * ```typescript * // Default (outputs to console.log) * const logger = createLogger({ * handler: new JsonHandler() * }) * * // Custom output (e.g., to a buffer) * const logs: string[] = [] * const logger = createLogger({ * handler: new JsonHandler({ output: (line) => logs.push(line) }) * }) * ``` */ export declare const JsonHandler: new (options?: { output?: (line: string) => void; }) => LogHandler; /** * Unified Logger class */ export declare class Logger implements ILogger { private level; private handler; private timestamps; private prefix; private capturedEvents; private frozenCapture; private baseContext; constructor(config?: LoggerConfig, baseContext?: LogContext); /** * Set the log level */ setLevel(level: LogLevel): void; /** * Get the current log level */ getLevel(): LogLevel; /** * Set a new log handler at runtime * * Use this to swap logging destinations without creating a new logger. * * @example * ```typescript * const logger = createLogger({ handler: consoleHandler }) * * // Later, switch to JSON output * logger.setHandler(new JsonHandler()) * ``` */ setHandler(handler: LogHandler): void; /** * Enable event capture */ enableCapture(maxEvents?: number): void; /** * Disable event capture * Note: Captured logs are retained but no new logs will be captured */ disableCapture(): void; /** * Get captured log entries */ getCapturedLogs(): LogEntry[]; /** * Clear captured logs */ clearCapturedLogs(): void; /** * Core log method - internal * Optimized to avoid unnecessary allocations when context is empty */ private logInternal; /** * Log a raw entry (for TenantRouterLogger compatibility) */ log(entry: Partial): void; /** * Log debug message */ debug(message: string, context?: LogContext): void; /** * Log info message */ info(message: string, context?: LogContext): void; /** * Log warning message */ warn(message: string, context?: LogContext): void; /** * Log error message */ error(message: string, context?: LogContext): void; /** * Create a child logger with scoped context * This is the unified approach that replaces: * - CDCLogger.withSubscription() * - TenantRouterLogger context */ child(context: LogContext): ILogger; } /** * Create a new logger instance * * This is the recommended way to create loggers. * * @param config - Logger configuration options * @returns ILogger instance * * @example * ```typescript * // Basic logger (INFO level, console output) * const logger = createLogger() * * // Debug logger with prefix * const logger = createLogger({ * level: LogLevel.DEBUG, * prefix: '[MyService]', * }) * * // JSON output logger * const logger = createLogger({ * handler: new JsonHandler(), * }) * * // Logger with event capture * const logger = createLogger({ * captureEvents: true, * maxCapturedEvents: 50, * }) * ``` */ export declare function createLogger(config?: LoggerConfig): ILogger; /** * Simple logger interface for backward compatibility with tiered-orchestrator * The unified logger implements this interface automatically. */ export type SimpleLogger = Pick; /** * MultiHandler - sends logs to multiple destinations simultaneously * * Use this when you need to log to multiple destinations (e.g., console + analytics) * * @example * ```typescript * const multiHandler = new MultiHandler([ * new ConsoleHandler({ colors: true }), * new JsonHandler({ output: sendToRemote }), * ]) * * const logger = createLogger({ handler: multiHandler }) * logger.info('Logged to both destinations') * ``` */ export declare const MultiHandler: new (handlers: LogHandler[]) => MultiLogHandler; /** * FilterHandler - filters logs based on a custom predicate * * Use this to conditionally pass logs to another handler based on * log level, context fields, or any custom logic. * * @example * ```typescript * // Only log errors to analytics * const errorOnlyHandler = new FilterHandler( * analyticsHandler, * (entry) => entry.level >= LogLevel.ERROR * ) * * // Only log entries from specific tenant * const tenantHandler = new FilterHandler( * handler, * (entry) => entry.context?.tenantId === 'acme' * ) * ``` */ export declare const FilterHandler: new (handler: LogHandler, predicate: (entry: LogEntry) => boolean) => LogHandler; /** * Options for BatchHandler configuration */ export interface BatchHandlerOptions { /** Number of entries to batch before flushing */ batchSize: number; /** Optional flush interval in milliseconds */ flushIntervalMs?: number; /** Callback when batch is ready to be sent */ onBatch: (entries: LogEntry[]) => void; } /** * BatchHandler - batches logs for performance * * Useful for reducing network requests when sending logs to a remote service. * Logs are accumulated and sent in batches when the batch size is reached * or when the flush interval expires. * * @example * ```typescript * const batchHandler = new BatchHandler({ * batchSize: 100, * flushIntervalMs: 5000, * onBatch: async (entries) => { * await fetch('/logs', { * method: 'POST', * body: JSON.stringify(entries), * }) * }, * }) * * const logger = createLogger({ handler: batchHandler }) * * // Don't forget to flush before worker terminates * batchHandler.flush() * ``` */ export declare const BatchHandler: new (options: BatchHandlerOptions) => BatchLogHandler; /** * Cloudflare Analytics Engine binding interface */ export interface AnalyticsEngineDataset { writeDataPoint(data: { blobs?: string[]; doubles?: number[]; indexes?: string[]; }): void; } /** * Options for CloudflareAnalyticsHandler */ export interface CloudflareAnalyticsHandlerOptions { /** The Analytics Engine binding from the worker */ analyticsEngine: AnalyticsEngineDataset; /** Optional function to extract custom indexes from log entries */ extractIndexes?: (entry: LogEntry) => string[]; /** Optional function to extract custom doubles (metrics) from log entries */ extractDoubles?: (entry: LogEntry) => number[]; } /** * CloudflareAnalyticsHandler - sends logs to Cloudflare Analytics Engine * * This handler formats log entries for Cloudflare Workers Analytics Engine, * which provides SQL-queryable logging with up to 90 days retention. * * @example * ```typescript * // In your worker * export default { * async fetch(request, env) { * const handler = new CloudflareAnalyticsHandler({ * analyticsEngine: env.LOGS, * extractIndexes: (entry) => [ * entry.context?.tenantId as string || '', * ], * }) * * const logger = createLogger({ handler }) * logger.info('Request received', { tenantId: 'acme' }) * * return new Response('OK') * } * } * ``` * * Analytics Engine data format: * - blobs[0]: Log level name (e.g., "INFO") * - blobs[1]: Log message * - blobs[2]: JSON-encoded context (if present) * - blobs[3]: Timestamp * - indexes: Custom indexes from extractIndexes (for filtering/grouping) * - doubles: Custom metrics from extractDoubles */ export declare const CloudflareAnalyticsHandler: new (options: CloudflareAnalyticsHandlerOptions) => LogHandler; //# sourceMappingURL=logger.d.ts.map