UNPKG

autotel

Version:
238 lines (236 loc) 7.22 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); //#region src/logger.ts /** * 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 */ const LOG_LEVEL = { DEBUG: "debug", INFO: "info", WARN: "warn", ERROR: "error" }; /** * 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) { } */ function LoggedOperation(operationNameOrOptions) { const operationName = typeof operationNameOrOptions === "string" ? operationNameOrOptions : operationNameOrOptions.operationName; return function(originalMethod, context) { const methodName = String(context.name); return async function(...args) { const log = this.deps?.log; const startTime = performance.now(); return getConfig().tracer.startActiveSpan(operationName, async (span) => { try { log?.info({ operation: operationName, method: methodName, args }, "Operation started"); const result = await originalMethod.apply(this, args); const duration = performance.now() - startTime; log?.info({ operation: operationName, method: methodName, duration }, "Operation completed"); span.setStatus({ code: SpanStatusCode.OK }); span.setAttributes({ "operation.name": operationName, "operation.method": methodName, "operation.duration": duration, "operation.success": true }); return result; } catch (error) { const duration = performance.now() - startTime; log?.error({ err: error instanceof Error ? error : void 0, operation: operationName, method: methodName, duration }, "Operation failed"); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : "Unknown error" }); span.setAttributes({ "operation.name": operationName, "operation.method": methodName, "operation.duration": duration, "operation.success": false, "error.type": error instanceof Error ? error.constructor.name : "Unknown" }); throw error; } finally { span.end(); } }); }; }; } //#endregion exports.LOG_LEVEL = LOG_LEVEL; exports.LoggedOperation = LoggedOperation; Object.defineProperty(exports, 'autotelLogger', { enumerable: true, get: function () { return autotelLogger; } }); Object.defineProperty(exports, 'createBuiltinLogger', { enumerable: true, get: function () { return createBuiltinLogger; } }); Object.defineProperty(exports, 'getActiveLogLevel', { enumerable: true, get: function () { return getActiveLogLevel; } }); Object.defineProperty(exports, 'getTraceContext', { enumerable: true, get: function () { return getTraceContext; } }); Object.defineProperty(exports, 'runWithLogLevel', { enumerable: true, get: function () { return runWithLogLevel; } }); //# sourceMappingURL=logger.cjs.map