UNPKG

autotel

Version:
349 lines (343 loc) 11.4 kB
export { Logger as PinoLogger } from 'pino'; /** * Zero-dependency structured logger for autotel * * This logger provides: * - Structured JSON logging (production) or pretty print (development) * - Auto trace context injection (traceId, spanId, correlationId) * - Dynamic log level control (per-request via OTel context) * - Level support (debug, info, warn, error, none) * - Zero additional dependencies (uses @opentelemetry/api, already a dep) * * Uses Pino-compatible signature supporting both patterns: * - `log.info('simple message')` - string-only * - `log.info({ userId: '123' }, 'message')` - object first with optional message * * Used as the default fallback when users don't provide Pino/Bunyan. * Can also be used directly: import { createBuiltinLogger } from 'autotel/logger' * * @example * ```typescript * import { createBuiltinLogger, runWithLogLevel } from 'autotel/logger'; * * const log = createBuiltinLogger('my-service'); * * // Simple message (no metadata) * log.info('Server started'); * * // With metadata (Pino-style: object first, message second) * log.info({ userId: '123' }, 'User created'); * // Output: {"level":"info","service":"my-service","msg":"User created","userId":"123","traceId":"..."} * * // Dynamic log level per-request * runWithLogLevel('debug', () => { * log.debug('This will log even if default level is "info"'); * }); * ``` */ type BuiltinLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none'; /** * Get the active log level from context (if set) * Falls back to undefined if no log level is set in context */ declare function getActiveLogLevel(): BuiltinLogLevel | undefined; /** * Run a function with a specific log level * The log level is stored in OpenTelemetry context and applies to all logger calls within the callback * * @example * ```typescript * // Enable debug logging for a specific request * runWithLogLevel('debug', () => { * log.debug('This will be logged'); * processRequest(); * }); * * // Disable logging temporarily * runWithLogLevel('none', () => { * log.info('This will NOT be logged'); * }); * ``` */ declare function runWithLogLevel<T>(level: BuiltinLogLevel, callback: () => T): T; /** * Helper to get trace context (useful for BYOL - Bring Your Own Logger) * * @example * ```typescript * import bunyan from 'bunyan'; * import { getTraceContext } from 'autotel/logger'; * * const bunyanLogger = bunyan.createLogger({ name: 'myapp' }); * const ctx = getTraceContext(); * bunyanLogger.info({ ...ctx, email: 'test@example.com' }, 'Creating user'); * ``` */ declare function getTraceContext(): { traceId: string; spanId: string; correlationId: string; } | null; interface BuiltinLoggerOptions { /** Minimum log level. Default: 'info' */ level?: BuiltinLogLevel; /** Pretty print for development. Default: false (JSON output) */ pretty?: boolean; } /** * Create a lightweight structured logger * * @param service - Service name for logging * @param options - Optional configuration * * @example * ```typescript * const log = createBuiltinLogger('user-service'); * * log.info('Creating user', { email: 'test@example.com' }); * // Output: {"level":"info","service":"user-service","msg":"Creating user", * // "email":"test@example.com","traceId":"...","spanId":"..."} * * // Dynamic log level control per-request * runWithLogLevel('debug', () => { * log.debug('This will be logged even if logger was created with level: "info"'); * }); * ``` */ declare function createBuiltinLogger(service: string, options?: BuiltinLoggerOptions): Logger; /** * Pino-like factory function for creating an autotel logger * * @example * ```typescript * import { autotelLogger } from 'autotel/logger'; * * const log = autotelLogger(); * * // Simple message * log.info('Server started'); * * // With metadata (Pino-style: object first, message second) * log.info({ userId: '123' }, 'User created'); * * // With options * const devLog = autotelLogger({ service: 'my-app', level: 'debug', pretty: true }); * ``` */ declare function autotelLogger(options?: { service?: string; level?: BuiltinLogLevel; pretty?: boolean; }): Logger; /** * Logger types and utilities for autotel * * **Zero-Config Option:** Don't provide a logger to `init()` and autotel uses * a built-in structured JSON logger with automatic trace context injection. * * **BYOL (Bring Your Own Logger):** Pass Pino or Bunyan to `init()` for * automatic instrumentation with trace context and OTLP log export. * * ## Logger Signature * * Autotel v2.10+ uses **Pino's signature**: `logger.info({ metadata }, 'message')`. * * ### Backward Compatibility * * The built-in logger auto-detects legacy Winston-style calls and swaps arguments: * ```typescript * // Legacy (auto-detected and handled) * logger.info('User created', { userId: '123' }); * // → Internally treated as: logger.info({ userId: '123' }, 'User created') * // → Logs warning in development, works silently in production * ``` * * ### Recommended Usage * * ```typescript * // ✅ Pino-style (preferred) * logger.info({ userId: '123' }, 'User created'); * * // ✅ Simple message (no metadata) * logger.info('Server started'); * ``` * * **Note:** If you BYOL (bring your own logger), it must use Pino signature. * Winston and other `(message, meta)` loggers are NOT compatible. * For Winston, use `@opentelemetry/instrumentation-winston` instead. * * @example Zero-config (uses built-in logger) * ```typescript * import { init } from 'autotel'; * * init({ service: 'my-app' }); * // Internal logs: {"level":"info","service":"my-app","msg":"...","traceId":"..."} * ``` * * @example Using built-in logger directly * ```typescript * import { createBuiltinLogger, runWithLogLevel } from 'autotel/logger'; * * const log = createBuiltinLogger('my-service'); * * // Simple message (no metadata) * log.info('Server started'); * * // With metadata (Pino-style: object first, message second) * log.info({ userId: '123' }, 'User created'); * // Output: {"level":"info","service":"my-service","msg":"User created","userId":"123","traceId":"..."} * * // Dynamic log level per-request * runWithLogLevel('debug', () => { * log.debug('Debug info for this request only'); * }); * ``` * * @example Using Pino (recommended for production, auto-instrumented) * ```typescript * import pino from 'pino'; // npm install pino * import { init } from 'autotel'; * * const logger = pino({ level: 'info' }); * init({ service: 'my-app', logger }); * * // Logs automatically include traceId/spanId and export via OTLP! * logger.info({ userId: '123' }, 'User created'); * ``` * * @example Using Bunyan (auto-instrumented, same signature as Pino) * ```typescript * import bunyan from 'bunyan'; // npm install bunyan @opentelemetry/instrumentation-bunyan * import { init } from 'autotel'; * import { BunyanInstrumentation } from '@opentelemetry/instrumentation-bunyan'; * * const logger = bunyan.createLogger({ name: 'my-app' }); * init({ * service: 'my-app', * logger, * instrumentations: [new BunyanInstrumentation()] * }); * ``` * * @example Custom logger (MUST use Pino-compatible signature) * ```typescript * // ⚠️ Your custom logger MUST accept (object, message?) signature * const logger = { * info: (extra, msg) => console.log(msg || '', extra), * warn: (extra, msg) => console.warn(msg || '', extra), * error: (extra, msg) => console.error(msg || '', extra), * debug: (extra, msg) => console.debug(msg || '', extra), * }; * init({ service: 'my-app', logger }); * ``` * * @example BYOL helper: inject trace context into any logger * ```typescript * import bunyan from 'bunyan'; * import { getTraceContext } from 'autotel/logger'; * * const bunyanLogger = bunyan.createLogger({ name: 'myapp' }); * const ctx = getTraceContext(); * bunyanLogger.info({ ...ctx, userId: '123' }, 'Creating user'); * ``` */ /** * Log level constants */ declare const LOG_LEVEL: { readonly DEBUG: "debug"; readonly INFO: "info"; readonly WARN: "warn"; readonly ERROR: "error"; }; type LogLevel = (typeof LOG_LEVEL)[keyof typeof LOG_LEVEL]; /** * Logger configuration (for reference - not needed with BYOL approach) */ interface LoggerConfig { service: string; level?: LogLevel; pretty?: boolean; redact?: string[] | false; } /** * Pino-compatible log function signature * * Matches Pino's actual LogFn type which supports: * - `(msg: string)` - simple string message * - `(obj: object, msg?: string)` - object first with optional message * * @example * ```typescript * logger.info('User logged in'); * logger.info({ userId: '123' }, 'User created'); * logger.error({ err: error }, 'Operation failed'); * ``` */ interface LogFn { (msg: string): void; (obj: Record<string, unknown>, msg?: string): void; } /** * Simple logger interface - Pino/Bunyan-compatible * * Uses Pino's LogFn signature which supports both: * - `logger.info('message')` - simple string message * - `logger.info({ extra }, 'message')` - object first with optional message * * This is compatible with Pino, Bunyan, and any logger following this pattern. * * @example Using Pino (just works!) * ```typescript * import pino from 'pino'; * const logger = pino({ level: 'info' }); * init({ service: 'my-app', logger }); * ``` * * @example Direct usage * ```typescript * logger.info('Simple message'); * logger.info({ userId: '123' }, 'User created'); * logger.error({ err: error }, 'Operation failed'); * ``` */ interface Logger { info: LogFn; warn: LogFn; error: LogFn; debug: LogFn; } /** * Alias for Logger interface (backwards compatibility) * @deprecated Use Logger instead */ type ILogger = Logger; interface LoggedOperationOptions { /** Operation name for tracing (e.g., 'user.createUser') */ operationName: string; } /** * TS5+ Standard Decorator for logging and tracing operations * Uses TC39 Stage 3 decorator syntax * * This is the traditional per-method decorator approach. * For zero-boilerplate solution, see @Instrumented class decorator. * * @example * // Simple usage (Pino-style: object first, message second) * class OrderService { * constructor(private readonly deps: { log: Logger }) {} * * @LoggedOperation('order.create') * async createOrder(data: CreateOrderData) { * // ✅ Correct Pino-style logging * this.deps.log.info({ orderId: data.id }, 'Creating order'); * } * } * * // Advanced usage (future-proof for options) * @LoggedOperation({ operationName: 'order.create' }) * async createOrder(data: CreateOrderData) { } */ declare function LoggedOperation(operationNameOrOptions: string | LoggedOperationOptions): <This, Args extends unknown[], Return>(originalMethod: (this: This, ...args: Args) => Promise<Return>, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Promise<Return>>) => (this: This, ...args: Args) => Promise<Return>; export { type BuiltinLogLevel, type BuiltinLoggerOptions, type ILogger, LOG_LEVEL, type LogFn, type LogLevel, LoggedOperation, type LoggedOperationOptions, type Logger, type LoggerConfig, autotelLogger, createBuiltinLogger, getActiveLogLevel, getTraceContext, runWithLogLevel };