@kamranbiglari/pino-logger 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,17 @@ logger.fatal({ err }, 'cannot continue'); // logs then exits with code 1
33
33
  All messages are prefixed with the log level: `[INFO] user logged in`, `[ERROR] request failed`, etc.
34
34
  This enables log parser engines to alert on specific levels via message pattern matching.
35
35
 
36
+ ## Excluding Base Fields
37
+
38
+ By default every log line includes `service`, `env`, and `version`. Exclude any of them:
39
+
40
+ ```typescript
41
+ const logger = createLogger({
42
+ service: 'my-service',
43
+ exclude: ['env', 'version'], // only service remains in output
44
+ });
45
+ ```
46
+
36
47
  ## Log Levels
37
48
 
38
49
  | Level | Numeric | Description |
package/dist/index.d.mts CHANGED
@@ -15,7 +15,11 @@ interface BaseLogFields {
15
15
  env?: string;
16
16
  }
17
17
  /**
18
- * Options for createLogger extends BaseLogFields with sampling config.
18
+ * Base field keys that can be excluded from log output.
19
+ */
20
+ type BaseFieldKey = 'service' | 'env' | 'version';
21
+ /**
22
+ * Options for createLogger — extends BaseLogFields with sampling and exclusion config.
19
23
  */
20
24
  interface CreateLoggerOptions extends BaseLogFields {
21
25
  /**
@@ -25,6 +29,16 @@ interface CreateLoggerOptions extends BaseLogFields {
25
29
  * Levels not listed here (and warn/error/fatal/alert) always emit every log.
26
30
  */
27
31
  sample?: Partial<Record<LogLevel, number>>;
32
+ /**
33
+ * Base fields to exclude from every log line.
34
+ * By default all base fields (service, env, version) are included.
35
+ * Pass field names to remove them from output.
36
+ *
37
+ * @example
38
+ * // Only include service, exclude env and version:
39
+ * createLogger({ service: 'my-api', exclude: ['env', 'version'] });
40
+ */
41
+ exclude?: BaseFieldKey[];
28
42
  }
29
43
  /**
30
44
  * Fields to include when logging an HTTP request context.
@@ -225,4 +239,4 @@ declare function withContext<T>(context: Record<string, unknown>, fn: () => T):
225
239
  */
226
240
  declare function getContext(): Record<string, unknown> | undefined;
227
241
 
228
- export { type BaseLogFields, type CreateLoggerOptions, type ErrorContext, LOG_LEVELS, type LogLevel, type MetricFields, PinoLogger, type RequestContext, type Timer, createLogger, getContext, withContext };
242
+ export { type BaseFieldKey, type BaseLogFields, type CreateLoggerOptions, type ErrorContext, LOG_LEVELS, type LogLevel, type MetricFields, PinoLogger, type RequestContext, type Timer, createLogger, getContext, withContext };
package/dist/index.d.ts CHANGED
@@ -15,7 +15,11 @@ interface BaseLogFields {
15
15
  env?: string;
16
16
  }
17
17
  /**
18
- * Options for createLogger extends BaseLogFields with sampling config.
18
+ * Base field keys that can be excluded from log output.
19
+ */
20
+ type BaseFieldKey = 'service' | 'env' | 'version';
21
+ /**
22
+ * Options for createLogger — extends BaseLogFields with sampling and exclusion config.
19
23
  */
20
24
  interface CreateLoggerOptions extends BaseLogFields {
21
25
  /**
@@ -25,6 +29,16 @@ interface CreateLoggerOptions extends BaseLogFields {
25
29
  * Levels not listed here (and warn/error/fatal/alert) always emit every log.
26
30
  */
27
31
  sample?: Partial<Record<LogLevel, number>>;
32
+ /**
33
+ * Base fields to exclude from every log line.
34
+ * By default all base fields (service, env, version) are included.
35
+ * Pass field names to remove them from output.
36
+ *
37
+ * @example
38
+ * // Only include service, exclude env and version:
39
+ * createLogger({ service: 'my-api', exclude: ['env', 'version'] });
40
+ */
41
+ exclude?: BaseFieldKey[];
28
42
  }
29
43
  /**
30
44
  * Fields to include when logging an HTTP request context.
@@ -225,4 +239,4 @@ declare function withContext<T>(context: Record<string, unknown>, fn: () => T):
225
239
  */
226
240
  declare function getContext(): Record<string, unknown> | undefined;
227
241
 
228
- export { type BaseLogFields, type CreateLoggerOptions, type ErrorContext, LOG_LEVELS, type LogLevel, type MetricFields, PinoLogger, type RequestContext, type Timer, createLogger, getContext, withContext };
242
+ export { type BaseFieldKey, type BaseLogFields, type CreateLoggerOptions, type ErrorContext, LOG_LEVELS, type LogLevel, type MetricFields, PinoLogger, type RequestContext, type Timer, createLogger, getContext, withContext };
package/dist/index.js CHANGED
@@ -282,16 +282,17 @@ var PinoLogger = class _PinoLogger {
282
282
  }
283
283
  };
284
284
  function createLogger(fields) {
285
+ const excluded = new Set(fields.exclude);
286
+ const base = {};
287
+ if (!excluded.has("service")) base.service = fields.service;
288
+ if (!excluded.has("env")) base.env = fields.env ?? process.env.NODE_ENV ?? "production";
289
+ if (!excluded.has("version")) base.version = fields.version ?? process.env.npm_package_version ?? "unknown";
285
290
  const pinoInstance = (0, import_pino.default)({
286
291
  level: resolveLogLevel(),
287
292
  // Register custom "alert" level above fatal (70 > 60)
288
293
  customLevels: { [ALERT_LEVEL_NAME]: ALERT_LEVEL_NUM },
289
- // Merged into every log line
290
- base: {
291
- service: fields.service,
292
- env: fields.env ?? process.env.NODE_ENV ?? "production",
293
- version: fields.version ?? process.env.npm_package_version ?? "unknown"
294
- },
294
+ // Merged into every log line (respects exclude list)
295
+ base,
295
296
  // ISO timestamp on every line
296
297
  timestamp: import_pino.default.stdTimeFunctions.isoTime,
297
298
  // Use string level labels (info/warn/error) not numeric codes (30/40/50)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/types.ts","../src/resolve-level.ts","../src/context.ts"],"sourcesContent":["// Public API — everything a consumer needs\nexport { createLogger, PinoLogger } from './logger.js';\nexport { withContext, getContext } from './context.js';\nexport { LOG_LEVELS } from './types.js';\nexport type {\n LogLevel,\n BaseLogFields,\n CreateLoggerOptions,\n RequestContext,\n ErrorContext,\n MetricFields,\n Timer,\n} from './types.js';\n","import pino, { LoggerOptions, LogFn } from 'pino';\nimport { CreateLoggerOptions, LOG_LEVELS, LogLevel, MetricFields, Timer } from './types.js';\nimport { resolveLogLevel } from './resolve-level.js';\nimport { getContext } from './context.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n// Pino logger with custom 'alert' level added\ntype PinoInstance = pino.Logger<'alert'>;\n\n// A function that can log — covers all Pino level methods including custom ones\ntype PinoLogFn = LogFn;\n\n// ─── Pre-allocated constants ──────────────────────────────────────────────────\n\n// Pre-allocate level label objects to avoid creating a new object on every log call.\n// This eliminates per-call GC pressure from the formatters.level function.\nconst LEVEL_OBJECTS: Record<string, { level: string }> = Object.fromEntries(\n LOG_LEVELS.map((l) => [l, { level: l }]),\n);\n\n// Pre-allocate level prefix strings: \"[INFO] \", \"[ERROR] \", etc.\nconst LEVEL_PREFIXES: Record<string, string> = Object.fromEntries(\n LOG_LEVELS.map((l) => [l, `[${l.toUpperCase()}] `]),\n);\n\n// Pino's built-in numeric levels max at fatal=60. Alert is above fatal.\nconst ALERT_LEVEL_NUM = 70;\nconst ALERT_LEVEL_NAME = 'alert';\n\n// ─── Sample state ─────────────────────────────────────────────────────────────\n\ninterface SampleState {\n since_last_emit: number;\n total: number;\n}\n\n// ─── Transport ────────────────────────────────────────────────────────────────\n\nfunction buildTransport(): LoggerOptions['transport'] {\n if (process.env.NODE_ENV !== 'development') return undefined;\n\n return {\n target: 'pino-pretty',\n options: {\n colorize: true,\n translateTime: 'HH:MM:ss.l',\n ignore: 'pid,hostname',\n messageFormat: '{msg}',\n },\n };\n}\n\n// ─── PinoLogger ───────────────────────────────────────────────────────────────\n\n/**\n * Opinionated Pino wrapper with:\n * - LOG_LEVEL / LOG_VERBOSE env var control\n * - Level prefix in messages: \"[INFO] msg\" for log parser alerting\n * - Full Error serialisation (message, stack, code) via `err` field\n * - alert() — highest severity level (above fatal), does NOT kill process\n * - fatal() kills the process with exit code 1\n * - child() returns PinoLogger (not raw Pino instance)\n * - AsyncLocalStorage context propagation (withContext)\n * - Request duration tracking (startTimer)\n * - Structured metric logging (metric)\n * - Log sampling with counting (no data loss)\n * - Graceful async shutdown\n * - pino-pretty in development (NODE_ENV=development)\n * - Sensitive field redaction\n */\nexport class PinoLogger {\n private readonly _pino: PinoInstance;\n private readonly _sample: Partial<Record<LogLevel, number>> | undefined;\n // Shared across parent + child loggers so sampling counters are global\n private readonly _sampleState: Map<string, SampleState>;\n\n constructor(\n pinoInstance: PinoInstance,\n sample?: Partial<Record<LogLevel, number>>,\n sampleState?: Map<string, SampleState>,\n ) {\n this._pino = pinoInstance;\n this._sample = sample;\n this._sampleState = sampleState ?? new Map();\n }\n\n // ── core log dispatch ────────────────────────────────────────────────────\n\n /**\n * Internal log dispatch. Handles sampling, async context merge,\n * and level prefix injection. Optimised for the common fast path\n * (no context, no sampling) to avoid object allocation.\n */\n private _log(level: LogLevel, objOrMsg: Record<string, unknown> | string, msg?: string): void {\n // ── Sampling gate ──\n const rate = this._sample?.[level];\n let sampleFields: { sampled_count: number; sampled_total: number } | null = null;\n if (rate && rate > 1) {\n let state = this._sampleState.get(level);\n if (!state) {\n state = { since_last_emit: 0, total: 0 };\n this._sampleState.set(level, state);\n }\n state.total++;\n state.since_last_emit++;\n if (state.since_last_emit < rate) return; // skip but counted\n sampleFields = { sampled_count: state.since_last_emit, sampled_total: state.total };\n state.since_last_emit = 0;\n }\n\n // ── Async context ──\n const ctx = getContext();\n const hasExtra = ctx !== undefined || sampleFields !== null;\n\n // ── Resolve the Pino log function for this level ──\n const logFn: PinoLogFn = (this._pino[level] as PinoLogFn).bind(this._pino);\n\n // ── Emit with level prefix in message ──\n const prefix = LEVEL_PREFIXES[level] ?? `[${level.toUpperCase()}] `;\n\n if (typeof objOrMsg === 'string') {\n const message = prefix + objOrMsg;\n if (hasExtra) {\n const merged = sampleFields\n ? ctx ? { ...ctx, ...sampleFields } : sampleFields\n : ctx!;\n logFn(merged, message);\n } else {\n logFn(message);\n }\n } else {\n const message = prefix + msg!;\n if (hasExtra) {\n const merged = { ...ctx, ...sampleFields, ...objOrMsg };\n logFn(merged, message);\n } else {\n logFn(objOrMsg, message);\n }\n }\n }\n\n // ── trace ────────────────────────────────────────────────────────────────\n\n trace(obj: Record<string, unknown>, msg: string): void;\n trace(msg: string): void;\n trace(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('trace', objOrMsg, msg);\n }\n\n // ── debug ────────────────────────────────────────────────────────────────\n\n debug(obj: Record<string, unknown>, msg: string): void;\n debug(msg: string): void;\n debug(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('debug', objOrMsg, msg);\n }\n\n // ── info ─────────────────────────────────────────────────────────────────\n\n info(obj: Record<string, unknown>, msg: string): void;\n info(msg: string): void;\n info(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('info', objOrMsg, msg);\n }\n\n // ── warn ─────────────────────────────────────────────────────────────────\n\n warn(obj: Record<string, unknown>, msg: string): void;\n warn(msg: string): void;\n warn(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('warn', objOrMsg, msg);\n }\n\n // ── error ────────────────────────────────────────────────────────────────\n\n error(obj: Record<string, unknown>, msg: string): void;\n error(msg: string): void;\n error(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('error', objOrMsg, msg);\n }\n\n // ── fatal ────────────────────────────────────────────────────────────────\n\n /**\n * Logs at FATAL level then kills the process with exit code 1.\n * Flushes sample counters and log buffer before exiting.\n */\n fatal(obj: Record<string, unknown>, msg: string): never;\n fatal(msg: string): never;\n fatal(objOrMsg: Record<string, unknown> | string, msg?: string): never {\n this._log('fatal', objOrMsg, msg);\n this.flushSampleCounts();\n try { this._pino.flush?.(); } catch { /* exiting anyway */ }\n process.exit(1);\n }\n\n // ── alert ────────────────────────────────────────────────────────────────\n\n /**\n * Logs at ALERT level — highest severity, above fatal.\n * Use for conditions requiring immediate operator attention:\n * - Security breaches detected\n * - Data corruption detected\n * - Critical SLA violations\n *\n * Unlike fatal(), alert() does NOT kill the process.\n * The service continues running so it can handle other requests.\n *\n * @example\n * logger.alert({ breach_type: 'unauthorized_access', ip }, 'security breach detected');\n */\n alert(obj: Record<string, unknown>, msg: string): void;\n alert(msg: string): void;\n alert(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('alert', objOrMsg, msg);\n }\n\n // ── child ────────────────────────────────────────────────────────────────\n\n /**\n * Creates a child logger with additional bound fields.\n * Shares sample counters with the parent for consistent global sampling.\n */\n child(bindings: Record<string, unknown>): PinoLogger {\n return new PinoLogger(this._pino.child(bindings), this._sample, this._sampleState);\n }\n\n // ── startTimer ───────────────────────────────────────────────────────────\n\n /**\n * Starts a high-resolution timer. Returns a Timer object with:\n * - elapsed(): returns ms elapsed so far\n * - done(msg) / done(obj, msg): logs at info level with duration_ms\n *\n * @example\n * const timer = logger.startTimer();\n * await processRequest();\n * timer.done({ req_id }, 'request processed'); // includes duration_ms\n */\n startTimer(): Timer {\n const start = process.hrtime.bigint();\n const self = this;\n return {\n elapsed(): number {\n return Number(process.hrtime.bigint() - start) / 1_000_000;\n },\n done(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n const duration_ms = Math.round(Number(process.hrtime.bigint() - start) * 100 / 1_000_000) / 100;\n if (typeof objOrMsg === 'string') {\n self._log('info', { duration_ms }, objOrMsg);\n } else {\n self._log('info', { ...objOrMsg, duration_ms }, msg!);\n }\n },\n } as Timer;\n }\n\n // ── metric ───────────────────────────────────────────────────────────────\n\n /**\n * Emits a structured metric log at info level.\n * All metric logs include `metric_type: \"metric\"` for easy filtering\n * in your log parser / aggregator.\n *\n * @example\n * logger.metric({ metric_name: 'http_request_duration', metric_value: 42, metric_unit: 'ms' });\n * logger.metric({ metric_name: 'queue_depth', metric_value: 150, metric_unit: 'count', queue: 'orders' });\n */\n metric(fields: MetricFields): void {\n const { metric_name, ...rest } = fields;\n this._log('info', { metric_type: 'metric', metric_name, ...rest }, `metric: ${metric_name}`);\n }\n\n // ── sampling ─────────────────────────────────────────────────────────────\n\n /**\n * Flushes any remaining sample counters as summary log lines.\n * Called automatically by fatal() and shutdown().\n * Call manually if you need to ensure all counts are emitted.\n */\n flushSampleCounts(): void {\n if (!this._sample) return;\n for (const [level, state] of this._sampleState.entries()) {\n if (state.since_last_emit > 0) {\n const prefix = LEVEL_PREFIXES[level] ?? `[${level.toUpperCase()}] `;\n const logFn: PinoLogFn = (this._pino[level as keyof PinoInstance] as PinoLogFn).bind(this._pino);\n logFn(\n { sampled_count: state.since_last_emit, sampled_total: state.total },\n prefix + 'sampled log flush',\n );\n state.since_last_emit = 0;\n }\n }\n }\n\n // ── shutdown ─────────────────────────────────────────────────────────────\n\n /**\n * Gracefully shuts down the logger:\n * 1. Flushes any remaining sample counters\n * 2. Flushes the Pino write buffer (important for async transports)\n *\n * Call this on SIGTERM / SIGINT before process exit.\n *\n * @example\n * process.on('SIGTERM', async () => {\n * logger.info('shutting down');\n * await logger.shutdown();\n * process.exit(0);\n * });\n */\n async shutdown(): Promise<void> {\n this.flushSampleCounts();\n return new Promise<void>((resolve, reject) => {\n if (typeof this._pino.flush === 'function') {\n this._pino.flush((err?: Error) => {\n if (err) reject(err);\n else resolve();\n });\n } else {\n resolve();\n }\n });\n }\n\n // ── isLevelEnabled ───────────────────────────────────────────────────────\n\n isLevelEnabled(level: LogLevel): boolean {\n return this._pino.isLevelEnabled(level);\n }\n\n // ── instance ─────────────────────────────────────────────────────────────\n\n /**\n * Exposes the raw Pino Logger instance.\n * Use for integrations that require it directly (e.g. pino-http).\n */\n get instance(): PinoInstance {\n return this._pino;\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Creates a configured PinoLogger instance.\n *\n * Reads from environment:\n * LOG_LEVEL — trace|debug|info|warn|error|fatal|alert (default: info)\n * LOG_VERBOSE — true|false forces trace level (default: false)\n * NODE_ENV — development enables pino-pretty (default: production)\n *\n * @example\n * const logger = createLogger({\n * service: 'windy-gateway',\n * version: process.env.npm_package_version,\n * env: process.env.NODE_ENV,\n * sample: { trace: 100, debug: 10 }, // emit every 100th trace, every 10th debug\n * });\n */\nexport function createLogger(fields: CreateLoggerOptions): PinoLogger {\n const pinoInstance = pino({\n level: resolveLogLevel(),\n\n // Register custom \"alert\" level above fatal (70 > 60)\n customLevels: { [ALERT_LEVEL_NAME]: ALERT_LEVEL_NUM },\n\n // Merged into every log line\n base: {\n service: fields.service,\n env: fields.env ?? process.env.NODE_ENV ?? 'production',\n version: fields.version ?? process.env.npm_package_version ?? 'unknown',\n },\n\n // ISO timestamp on every line\n timestamp: pino.stdTimeFunctions.isoTime,\n\n // Use string level labels (info/warn/error) not numeric codes (30/40/50)\n // Uses pre-allocated objects to avoid per-call allocation + GC pressure.\n formatters: {\n level(label) {\n return LEVEL_OBJECTS[label] ?? { level: label };\n },\n },\n\n // Serialise Error objects: message, stack, code, type\n serializers: {\n err: pino.stdSerializers.err,\n error: pino.stdSerializers.err,\n },\n\n // Redact sensitive fields before they reach stdout\n // NOTE: Avoid wildcard paths (*.field) — they force Pino to walk\n // the entire object tree on every log call, which is expensive.\n // Use explicit paths for predictable O(1) redaction.\n redact: {\n paths: [\n 'req.headers.authorization',\n 'req.headers.cookie',\n 'body.password',\n 'body.api_key',\n 'body.token',\n 'body.secret',\n 'headers.authorization',\n 'headers.cookie',\n 'password',\n 'apiKey',\n 'api_key',\n 'secret',\n ],\n censor: '[REDACTED]',\n },\n\n transport: buildTransport(),\n });\n\n return new PinoLogger(pinoInstance, fields.sample);\n}\n","export const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'alert'] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\n/**\n * Fields merged into every log line emitted by this service.\n * All fields follow snake_case naming convention.\n */\nexport interface BaseLogFields {\n /** Service identifier in kebab-case. e.g. 'windy-gateway', 'fx-api' */\n service: string;\n /** App version — recommended: process.env.npm_package_version */\n version?: string;\n /** Runtime environment — recommended: process.env.NODE_ENV */\n env?: string;\n}\n\n/**\n * Options for createLogger — extends BaseLogFields with sampling config.\n */\nexport interface CreateLoggerOptions extends BaseLogFields {\n /**\n * Sample rates per level. e.g. { trace: 100 } emits every 100th trace log.\n * Skipped logs are counted — the emitted log includes `sampled_count` and\n * `sampled_total` fields so no data is lost.\n * Levels not listed here (and warn/error/fatal/alert) always emit every log.\n */\n sample?: Partial<Record<LogLevel, number>>;\n}\n\n/**\n * Fields to include when logging an HTTP request context.\n * Bind these via logger.child() at request start.\n */\nexport interface RequestContext {\n req_id: string;\n method?: string;\n path?: string;\n user_id?: string;\n}\n\n/**\n * Fields to include when logging an error.\n * Always pass the raw Error object as `err` — Pino serialises it automatically.\n */\nexport interface ErrorContext {\n err: Error | unknown;\n req_id?: string;\n [key: string]: unknown;\n}\n\n/**\n * Fields for structured metric logging.\n */\nexport interface MetricFields {\n /** Metric identifier in snake_case. e.g. 'http_request_duration_ms' */\n metric_name: string;\n /** Numeric value of the metric */\n metric_value: number;\n /** Unit of measurement. e.g. 'ms', 'bytes', 'count' */\n metric_unit?: string;\n [key: string]: unknown;\n}\n\n/**\n * Timer returned by startTimer().\n */\nexport interface Timer {\n /** Returns elapsed time in milliseconds */\n elapsed(): number;\n /** Logs at info level with duration_ms field */\n done(msg: string): void;\n done(obj: Record<string, unknown>, msg: string): void;\n}\n","import { LOG_LEVELS, LogLevel } from './types.js';\n\n/**\n * Resolves the active log level from environment variables.\n *\n * Priority:\n * 1. LOG_VERBOSE=true → forces 'trace' (verbose mode)\n * 2. LOG_LEVEL → uses the specified level\n * 3. default → 'info'\n *\n * Invalid LOG_LEVEL values warn to stderr and fall back to 'info'.\n * This prevents silent log blackouts from typos in config.\n */\nexport function resolveLogLevel(): LogLevel {\n if (process.env.LOG_VERBOSE === 'true') {\n return 'trace';\n }\n\n const raw = process.env.LOG_LEVEL?.toLowerCase().trim();\n\n if (!raw) return 'info';\n\n if ((LOG_LEVELS as readonly string[]).includes(raw)) {\n return raw as LogLevel;\n }\n\n process.stderr.write(\n `[pino-logger] Invalid LOG_LEVEL=\"${raw}\". ` +\n `Valid values: ${LOG_LEVELS.join(', ')}. Falling back to \"info\".\\n`\n );\n\n return 'info';\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\n\nconst storage = new AsyncLocalStorage<Record<string, unknown>>();\n\n/**\n * Runs a function with context fields that are automatically merged\n * into every log call within that async scope.\n *\n * Contexts nest — inner calls inherit and can override outer fields.\n *\n * @example\n * // In middleware:\n * app.use((req, res, next) => {\n * withContext({ req_id: req.id, user_id: req.userId }, next);\n * });\n *\n * // Anywhere in the call stack — no need to pass logger around:\n * logger.info('processing'); // automatically includes req_id, user_id\n */\nexport function withContext<T>(context: Record<string, unknown>, fn: () => T): T {\n const existing = storage.getStore();\n const merged = existing ? { ...existing, ...context } : context;\n return storage.run(merged, fn);\n}\n\n/**\n * Returns the current async context, or undefined if none is active.\n * Used internally by PinoLogger to auto-merge context into log calls.\n */\nexport function getContext(): Record<string, unknown> | undefined {\n return storage.getStore();\n}\n"],"mappings":"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","names":["pino"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/types.ts","../src/resolve-level.ts","../src/context.ts"],"sourcesContent":["// Public API — everything a consumer needs\nexport { createLogger, PinoLogger } from './logger.js';\nexport { withContext, getContext } from './context.js';\nexport { LOG_LEVELS } from './types.js';\nexport type {\n LogLevel,\n BaseFieldKey,\n BaseLogFields,\n CreateLoggerOptions,\n RequestContext,\n ErrorContext,\n MetricFields,\n Timer,\n} from './types.js';\n","import pino, { LoggerOptions, LogFn } from 'pino';\nimport { BaseFieldKey, CreateLoggerOptions, LOG_LEVELS, LogLevel, MetricFields, Timer } from './types.js';\nimport { resolveLogLevel } from './resolve-level.js';\nimport { getContext } from './context.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n// Pino logger with custom 'alert' level added\ntype PinoInstance = pino.Logger<'alert'>;\n\n// A function that can log — covers all Pino level methods including custom ones\ntype PinoLogFn = LogFn;\n\n// ─── Pre-allocated constants ──────────────────────────────────────────────────\n\n// Pre-allocate level label objects to avoid creating a new object on every log call.\n// This eliminates per-call GC pressure from the formatters.level function.\nconst LEVEL_OBJECTS: Record<string, { level: string }> = Object.fromEntries(\n LOG_LEVELS.map((l) => [l, { level: l }]),\n);\n\n// Pre-allocate level prefix strings: \"[INFO] \", \"[ERROR] \", etc.\nconst LEVEL_PREFIXES: Record<string, string> = Object.fromEntries(\n LOG_LEVELS.map((l) => [l, `[${l.toUpperCase()}] `]),\n);\n\n// Pino's built-in numeric levels max at fatal=60. Alert is above fatal.\nconst ALERT_LEVEL_NUM = 70;\nconst ALERT_LEVEL_NAME = 'alert';\n\n// ─── Sample state ─────────────────────────────────────────────────────────────\n\ninterface SampleState {\n since_last_emit: number;\n total: number;\n}\n\n// ─── Transport ────────────────────────────────────────────────────────────────\n\nfunction buildTransport(): LoggerOptions['transport'] {\n if (process.env.NODE_ENV !== 'development') return undefined;\n\n return {\n target: 'pino-pretty',\n options: {\n colorize: true,\n translateTime: 'HH:MM:ss.l',\n ignore: 'pid,hostname',\n messageFormat: '{msg}',\n },\n };\n}\n\n// ─── PinoLogger ───────────────────────────────────────────────────────────────\n\n/**\n * Opinionated Pino wrapper with:\n * - LOG_LEVEL / LOG_VERBOSE env var control\n * - Level prefix in messages: \"[INFO] msg\" for log parser alerting\n * - Full Error serialisation (message, stack, code) via `err` field\n * - alert() — highest severity level (above fatal), does NOT kill process\n * - fatal() kills the process with exit code 1\n * - child() returns PinoLogger (not raw Pino instance)\n * - AsyncLocalStorage context propagation (withContext)\n * - Request duration tracking (startTimer)\n * - Structured metric logging (metric)\n * - Log sampling with counting (no data loss)\n * - Graceful async shutdown\n * - pino-pretty in development (NODE_ENV=development)\n * - Sensitive field redaction\n */\nexport class PinoLogger {\n private readonly _pino: PinoInstance;\n private readonly _sample: Partial<Record<LogLevel, number>> | undefined;\n // Shared across parent + child loggers so sampling counters are global\n private readonly _sampleState: Map<string, SampleState>;\n\n constructor(\n pinoInstance: PinoInstance,\n sample?: Partial<Record<LogLevel, number>>,\n sampleState?: Map<string, SampleState>,\n ) {\n this._pino = pinoInstance;\n this._sample = sample;\n this._sampleState = sampleState ?? new Map();\n }\n\n // ── core log dispatch ────────────────────────────────────────────────────\n\n /**\n * Internal log dispatch. Handles sampling, async context merge,\n * and level prefix injection. Optimised for the common fast path\n * (no context, no sampling) to avoid object allocation.\n */\n private _log(level: LogLevel, objOrMsg: Record<string, unknown> | string, msg?: string): void {\n // ── Sampling gate ──\n const rate = this._sample?.[level];\n let sampleFields: { sampled_count: number; sampled_total: number } | null = null;\n if (rate && rate > 1) {\n let state = this._sampleState.get(level);\n if (!state) {\n state = { since_last_emit: 0, total: 0 };\n this._sampleState.set(level, state);\n }\n state.total++;\n state.since_last_emit++;\n if (state.since_last_emit < rate) return; // skip but counted\n sampleFields = { sampled_count: state.since_last_emit, sampled_total: state.total };\n state.since_last_emit = 0;\n }\n\n // ── Async context ──\n const ctx = getContext();\n const hasExtra = ctx !== undefined || sampleFields !== null;\n\n // ── Resolve the Pino log function for this level ──\n const logFn: PinoLogFn = (this._pino[level] as PinoLogFn).bind(this._pino);\n\n // ── Emit with level prefix in message ──\n const prefix = LEVEL_PREFIXES[level] ?? `[${level.toUpperCase()}] `;\n\n if (typeof objOrMsg === 'string') {\n const message = prefix + objOrMsg;\n if (hasExtra) {\n const merged = sampleFields\n ? ctx ? { ...ctx, ...sampleFields } : sampleFields\n : ctx!;\n logFn(merged, message);\n } else {\n logFn(message);\n }\n } else {\n const message = prefix + msg!;\n if (hasExtra) {\n const merged = { ...ctx, ...sampleFields, ...objOrMsg };\n logFn(merged, message);\n } else {\n logFn(objOrMsg, message);\n }\n }\n }\n\n // ── trace ────────────────────────────────────────────────────────────────\n\n trace(obj: Record<string, unknown>, msg: string): void;\n trace(msg: string): void;\n trace(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('trace', objOrMsg, msg);\n }\n\n // ── debug ────────────────────────────────────────────────────────────────\n\n debug(obj: Record<string, unknown>, msg: string): void;\n debug(msg: string): void;\n debug(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('debug', objOrMsg, msg);\n }\n\n // ── info ─────────────────────────────────────────────────────────────────\n\n info(obj: Record<string, unknown>, msg: string): void;\n info(msg: string): void;\n info(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('info', objOrMsg, msg);\n }\n\n // ── warn ─────────────────────────────────────────────────────────────────\n\n warn(obj: Record<string, unknown>, msg: string): void;\n warn(msg: string): void;\n warn(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('warn', objOrMsg, msg);\n }\n\n // ── error ────────────────────────────────────────────────────────────────\n\n error(obj: Record<string, unknown>, msg: string): void;\n error(msg: string): void;\n error(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('error', objOrMsg, msg);\n }\n\n // ── fatal ────────────────────────────────────────────────────────────────\n\n /**\n * Logs at FATAL level then kills the process with exit code 1.\n * Flushes sample counters and log buffer before exiting.\n */\n fatal(obj: Record<string, unknown>, msg: string): never;\n fatal(msg: string): never;\n fatal(objOrMsg: Record<string, unknown> | string, msg?: string): never {\n this._log('fatal', objOrMsg, msg);\n this.flushSampleCounts();\n try { this._pino.flush?.(); } catch { /* exiting anyway */ }\n process.exit(1);\n }\n\n // ── alert ────────────────────────────────────────────────────────────────\n\n /**\n * Logs at ALERT level — highest severity, above fatal.\n * Use for conditions requiring immediate operator attention:\n * - Security breaches detected\n * - Data corruption detected\n * - Critical SLA violations\n *\n * Unlike fatal(), alert() does NOT kill the process.\n * The service continues running so it can handle other requests.\n *\n * @example\n * logger.alert({ breach_type: 'unauthorized_access', ip }, 'security breach detected');\n */\n alert(obj: Record<string, unknown>, msg: string): void;\n alert(msg: string): void;\n alert(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('alert', objOrMsg, msg);\n }\n\n // ── child ────────────────────────────────────────────────────────────────\n\n /**\n * Creates a child logger with additional bound fields.\n * Shares sample counters with the parent for consistent global sampling.\n */\n child(bindings: Record<string, unknown>): PinoLogger {\n return new PinoLogger(this._pino.child(bindings), this._sample, this._sampleState);\n }\n\n // ── startTimer ───────────────────────────────────────────────────────────\n\n /**\n * Starts a high-resolution timer. Returns a Timer object with:\n * - elapsed(): returns ms elapsed so far\n * - done(msg) / done(obj, msg): logs at info level with duration_ms\n *\n * @example\n * const timer = logger.startTimer();\n * await processRequest();\n * timer.done({ req_id }, 'request processed'); // includes duration_ms\n */\n startTimer(): Timer {\n const start = process.hrtime.bigint();\n const self = this;\n return {\n elapsed(): number {\n return Number(process.hrtime.bigint() - start) / 1_000_000;\n },\n done(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n const duration_ms = Math.round(Number(process.hrtime.bigint() - start) * 100 / 1_000_000) / 100;\n if (typeof objOrMsg === 'string') {\n self._log('info', { duration_ms }, objOrMsg);\n } else {\n self._log('info', { ...objOrMsg, duration_ms }, msg!);\n }\n },\n } as Timer;\n }\n\n // ── metric ───────────────────────────────────────────────────────────────\n\n /**\n * Emits a structured metric log at info level.\n * All metric logs include `metric_type: \"metric\"` for easy filtering\n * in your log parser / aggregator.\n *\n * @example\n * logger.metric({ metric_name: 'http_request_duration', metric_value: 42, metric_unit: 'ms' });\n * logger.metric({ metric_name: 'queue_depth', metric_value: 150, metric_unit: 'count', queue: 'orders' });\n */\n metric(fields: MetricFields): void {\n const { metric_name, ...rest } = fields;\n this._log('info', { metric_type: 'metric', metric_name, ...rest }, `metric: ${metric_name}`);\n }\n\n // ── sampling ─────────────────────────────────────────────────────────────\n\n /**\n * Flushes any remaining sample counters as summary log lines.\n * Called automatically by fatal() and shutdown().\n * Call manually if you need to ensure all counts are emitted.\n */\n flushSampleCounts(): void {\n if (!this._sample) return;\n for (const [level, state] of this._sampleState.entries()) {\n if (state.since_last_emit > 0) {\n const prefix = LEVEL_PREFIXES[level] ?? `[${level.toUpperCase()}] `;\n const logFn: PinoLogFn = (this._pino[level as keyof PinoInstance] as PinoLogFn).bind(this._pino);\n logFn(\n { sampled_count: state.since_last_emit, sampled_total: state.total },\n prefix + 'sampled log flush',\n );\n state.since_last_emit = 0;\n }\n }\n }\n\n // ── shutdown ─────────────────────────────────────────────────────────────\n\n /**\n * Gracefully shuts down the logger:\n * 1. Flushes any remaining sample counters\n * 2. Flushes the Pino write buffer (important for async transports)\n *\n * Call this on SIGTERM / SIGINT before process exit.\n *\n * @example\n * process.on('SIGTERM', async () => {\n * logger.info('shutting down');\n * await logger.shutdown();\n * process.exit(0);\n * });\n */\n async shutdown(): Promise<void> {\n this.flushSampleCounts();\n return new Promise<void>((resolve, reject) => {\n if (typeof this._pino.flush === 'function') {\n this._pino.flush((err?: Error) => {\n if (err) reject(err);\n else resolve();\n });\n } else {\n resolve();\n }\n });\n }\n\n // ── isLevelEnabled ───────────────────────────────────────────────────────\n\n isLevelEnabled(level: LogLevel): boolean {\n return this._pino.isLevelEnabled(level);\n }\n\n // ── instance ─────────────────────────────────────────────────────────────\n\n /**\n * Exposes the raw Pino Logger instance.\n * Use for integrations that require it directly (e.g. pino-http).\n */\n get instance(): PinoInstance {\n return this._pino;\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Creates a configured PinoLogger instance.\n *\n * Reads from environment:\n * LOG_LEVEL — trace|debug|info|warn|error|fatal|alert (default: info)\n * LOG_VERBOSE — true|false forces trace level (default: false)\n * NODE_ENV — development enables pino-pretty (default: production)\n *\n * @example\n * const logger = createLogger({\n * service: 'windy-gateway',\n * version: process.env.npm_package_version,\n * env: process.env.NODE_ENV,\n * sample: { trace: 100, debug: 10 }, // emit every 100th trace, every 10th debug\n * });\n */\nexport function createLogger(fields: CreateLoggerOptions): PinoLogger {\n // Build base fields, then remove any that are excluded\n const excluded = new Set<BaseFieldKey>(fields.exclude);\n const base: Record<string, string> = {};\n if (!excluded.has('service')) base.service = fields.service;\n if (!excluded.has('env')) base.env = fields.env ?? process.env.NODE_ENV ?? 'production';\n if (!excluded.has('version')) base.version = fields.version ?? process.env.npm_package_version ?? 'unknown';\n\n const pinoInstance = pino({\n level: resolveLogLevel(),\n\n // Register custom \"alert\" level above fatal (70 > 60)\n customLevels: { [ALERT_LEVEL_NAME]: ALERT_LEVEL_NUM },\n\n // Merged into every log line (respects exclude list)\n base,\n\n // ISO timestamp on every line\n timestamp: pino.stdTimeFunctions.isoTime,\n\n // Use string level labels (info/warn/error) not numeric codes (30/40/50)\n // Uses pre-allocated objects to avoid per-call allocation + GC pressure.\n formatters: {\n level(label) {\n return LEVEL_OBJECTS[label] ?? { level: label };\n },\n },\n\n // Serialise Error objects: message, stack, code, type\n serializers: {\n err: pino.stdSerializers.err,\n error: pino.stdSerializers.err,\n },\n\n // Redact sensitive fields before they reach stdout\n // NOTE: Avoid wildcard paths (*.field) — they force Pino to walk\n // the entire object tree on every log call, which is expensive.\n // Use explicit paths for predictable O(1) redaction.\n redact: {\n paths: [\n 'req.headers.authorization',\n 'req.headers.cookie',\n 'body.password',\n 'body.api_key',\n 'body.token',\n 'body.secret',\n 'headers.authorization',\n 'headers.cookie',\n 'password',\n 'apiKey',\n 'api_key',\n 'secret',\n ],\n censor: '[REDACTED]',\n },\n\n transport: buildTransport(),\n });\n\n return new PinoLogger(pinoInstance, fields.sample);\n}\n","export const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'alert'] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\n/**\n * Fields merged into every log line emitted by this service.\n * All fields follow snake_case naming convention.\n */\nexport interface BaseLogFields {\n /** Service identifier in kebab-case. e.g. 'windy-gateway', 'fx-api' */\n service: string;\n /** App version — recommended: process.env.npm_package_version */\n version?: string;\n /** Runtime environment — recommended: process.env.NODE_ENV */\n env?: string;\n}\n\n/**\n * Base field keys that can be excluded from log output.\n */\nexport type BaseFieldKey = 'service' | 'env' | 'version';\n\n/**\n * Options for createLogger — extends BaseLogFields with sampling and exclusion config.\n */\nexport interface CreateLoggerOptions extends BaseLogFields {\n /**\n * Sample rates per level. e.g. { trace: 100 } emits every 100th trace log.\n * Skipped logs are counted — the emitted log includes `sampled_count` and\n * `sampled_total` fields so no data is lost.\n * Levels not listed here (and warn/error/fatal/alert) always emit every log.\n */\n sample?: Partial<Record<LogLevel, number>>;\n\n /**\n * Base fields to exclude from every log line.\n * By default all base fields (service, env, version) are included.\n * Pass field names to remove them from output.\n *\n * @example\n * // Only include service, exclude env and version:\n * createLogger({ service: 'my-api', exclude: ['env', 'version'] });\n */\n exclude?: BaseFieldKey[];\n}\n\n/**\n * Fields to include when logging an HTTP request context.\n * Bind these via logger.child() at request start.\n */\nexport interface RequestContext {\n req_id: string;\n method?: string;\n path?: string;\n user_id?: string;\n}\n\n/**\n * Fields to include when logging an error.\n * Always pass the raw Error object as `err` — Pino serialises it automatically.\n */\nexport interface ErrorContext {\n err: Error | unknown;\n req_id?: string;\n [key: string]: unknown;\n}\n\n/**\n * Fields for structured metric logging.\n */\nexport interface MetricFields {\n /** Metric identifier in snake_case. e.g. 'http_request_duration_ms' */\n metric_name: string;\n /** Numeric value of the metric */\n metric_value: number;\n /** Unit of measurement. e.g. 'ms', 'bytes', 'count' */\n metric_unit?: string;\n [key: string]: unknown;\n}\n\n/**\n * Timer returned by startTimer().\n */\nexport interface Timer {\n /** Returns elapsed time in milliseconds */\n elapsed(): number;\n /** Logs at info level with duration_ms field */\n done(msg: string): void;\n done(obj: Record<string, unknown>, msg: string): void;\n}\n","import { LOG_LEVELS, LogLevel } from './types.js';\n\n/**\n * Resolves the active log level from environment variables.\n *\n * Priority:\n * 1. LOG_VERBOSE=true → forces 'trace' (verbose mode)\n * 2. LOG_LEVEL → uses the specified level\n * 3. default → 'info'\n *\n * Invalid LOG_LEVEL values warn to stderr and fall back to 'info'.\n * This prevents silent log blackouts from typos in config.\n */\nexport function resolveLogLevel(): LogLevel {\n if (process.env.LOG_VERBOSE === 'true') {\n return 'trace';\n }\n\n const raw = process.env.LOG_LEVEL?.toLowerCase().trim();\n\n if (!raw) return 'info';\n\n if ((LOG_LEVELS as readonly string[]).includes(raw)) {\n return raw as LogLevel;\n }\n\n process.stderr.write(\n `[pino-logger] Invalid LOG_LEVEL=\"${raw}\". ` +\n `Valid values: ${LOG_LEVELS.join(', ')}. Falling back to \"info\".\\n`\n );\n\n return 'info';\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\n\nconst storage = new AsyncLocalStorage<Record<string, unknown>>();\n\n/**\n * Runs a function with context fields that are automatically merged\n * into every log call within that async scope.\n *\n * Contexts nest — inner calls inherit and can override outer fields.\n *\n * @example\n * // In middleware:\n * app.use((req, res, next) => {\n * withContext({ req_id: req.id, user_id: req.userId }, next);\n * });\n *\n * // Anywhere in the call stack — no need to pass logger around:\n * logger.info('processing'); // automatically includes req_id, user_id\n */\nexport function withContext<T>(context: Record<string, unknown>, fn: () => T): T {\n const existing = storage.getStore();\n const merged = existing ? { ...existing, ...context } : context;\n return storage.run(merged, fn);\n}\n\n/**\n * Returns the current async context, or undefined if none is active.\n * Used internally by PinoLogger to auto-merge context into log calls.\n */\nexport function getContext(): Record<string, unknown> | undefined {\n return storage.getStore();\n}\n"],"mappings":"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","names":["pino"]}
package/dist/index.mjs CHANGED
@@ -242,16 +242,17 @@ var PinoLogger = class _PinoLogger {
242
242
  }
243
243
  };
244
244
  function createLogger(fields) {
245
+ const excluded = new Set(fields.exclude);
246
+ const base = {};
247
+ if (!excluded.has("service")) base.service = fields.service;
248
+ if (!excluded.has("env")) base.env = fields.env ?? process.env.NODE_ENV ?? "production";
249
+ if (!excluded.has("version")) base.version = fields.version ?? process.env.npm_package_version ?? "unknown";
245
250
  const pinoInstance = pino({
246
251
  level: resolveLogLevel(),
247
252
  // Register custom "alert" level above fatal (70 > 60)
248
253
  customLevels: { [ALERT_LEVEL_NAME]: ALERT_LEVEL_NUM },
249
- // Merged into every log line
250
- base: {
251
- service: fields.service,
252
- env: fields.env ?? process.env.NODE_ENV ?? "production",
253
- version: fields.version ?? process.env.npm_package_version ?? "unknown"
254
- },
254
+ // Merged into every log line (respects exclude list)
255
+ base,
255
256
  // ISO timestamp on every line
256
257
  timestamp: pino.stdTimeFunctions.isoTime,
257
258
  // Use string level labels (info/warn/error) not numeric codes (30/40/50)
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/logger.ts","../src/types.ts","../src/resolve-level.ts","../src/context.ts"],"sourcesContent":["import pino, { LoggerOptions, LogFn } from 'pino';\nimport { CreateLoggerOptions, LOG_LEVELS, LogLevel, MetricFields, Timer } from './types.js';\nimport { resolveLogLevel } from './resolve-level.js';\nimport { getContext } from './context.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n// Pino logger with custom 'alert' level added\ntype PinoInstance = pino.Logger<'alert'>;\n\n// A function that can log — covers all Pino level methods including custom ones\ntype PinoLogFn = LogFn;\n\n// ─── Pre-allocated constants ──────────────────────────────────────────────────\n\n// Pre-allocate level label objects to avoid creating a new object on every log call.\n// This eliminates per-call GC pressure from the formatters.level function.\nconst LEVEL_OBJECTS: Record<string, { level: string }> = Object.fromEntries(\n LOG_LEVELS.map((l) => [l, { level: l }]),\n);\n\n// Pre-allocate level prefix strings: \"[INFO] \", \"[ERROR] \", etc.\nconst LEVEL_PREFIXES: Record<string, string> = Object.fromEntries(\n LOG_LEVELS.map((l) => [l, `[${l.toUpperCase()}] `]),\n);\n\n// Pino's built-in numeric levels max at fatal=60. Alert is above fatal.\nconst ALERT_LEVEL_NUM = 70;\nconst ALERT_LEVEL_NAME = 'alert';\n\n// ─── Sample state ─────────────────────────────────────────────────────────────\n\ninterface SampleState {\n since_last_emit: number;\n total: number;\n}\n\n// ─── Transport ────────────────────────────────────────────────────────────────\n\nfunction buildTransport(): LoggerOptions['transport'] {\n if (process.env.NODE_ENV !== 'development') return undefined;\n\n return {\n target: 'pino-pretty',\n options: {\n colorize: true,\n translateTime: 'HH:MM:ss.l',\n ignore: 'pid,hostname',\n messageFormat: '{msg}',\n },\n };\n}\n\n// ─── PinoLogger ───────────────────────────────────────────────────────────────\n\n/**\n * Opinionated Pino wrapper with:\n * - LOG_LEVEL / LOG_VERBOSE env var control\n * - Level prefix in messages: \"[INFO] msg\" for log parser alerting\n * - Full Error serialisation (message, stack, code) via `err` field\n * - alert() — highest severity level (above fatal), does NOT kill process\n * - fatal() kills the process with exit code 1\n * - child() returns PinoLogger (not raw Pino instance)\n * - AsyncLocalStorage context propagation (withContext)\n * - Request duration tracking (startTimer)\n * - Structured metric logging (metric)\n * - Log sampling with counting (no data loss)\n * - Graceful async shutdown\n * - pino-pretty in development (NODE_ENV=development)\n * - Sensitive field redaction\n */\nexport class PinoLogger {\n private readonly _pino: PinoInstance;\n private readonly _sample: Partial<Record<LogLevel, number>> | undefined;\n // Shared across parent + child loggers so sampling counters are global\n private readonly _sampleState: Map<string, SampleState>;\n\n constructor(\n pinoInstance: PinoInstance,\n sample?: Partial<Record<LogLevel, number>>,\n sampleState?: Map<string, SampleState>,\n ) {\n this._pino = pinoInstance;\n this._sample = sample;\n this._sampleState = sampleState ?? new Map();\n }\n\n // ── core log dispatch ────────────────────────────────────────────────────\n\n /**\n * Internal log dispatch. Handles sampling, async context merge,\n * and level prefix injection. Optimised for the common fast path\n * (no context, no sampling) to avoid object allocation.\n */\n private _log(level: LogLevel, objOrMsg: Record<string, unknown> | string, msg?: string): void {\n // ── Sampling gate ──\n const rate = this._sample?.[level];\n let sampleFields: { sampled_count: number; sampled_total: number } | null = null;\n if (rate && rate > 1) {\n let state = this._sampleState.get(level);\n if (!state) {\n state = { since_last_emit: 0, total: 0 };\n this._sampleState.set(level, state);\n }\n state.total++;\n state.since_last_emit++;\n if (state.since_last_emit < rate) return; // skip but counted\n sampleFields = { sampled_count: state.since_last_emit, sampled_total: state.total };\n state.since_last_emit = 0;\n }\n\n // ── Async context ──\n const ctx = getContext();\n const hasExtra = ctx !== undefined || sampleFields !== null;\n\n // ── Resolve the Pino log function for this level ──\n const logFn: PinoLogFn = (this._pino[level] as PinoLogFn).bind(this._pino);\n\n // ── Emit with level prefix in message ──\n const prefix = LEVEL_PREFIXES[level] ?? `[${level.toUpperCase()}] `;\n\n if (typeof objOrMsg === 'string') {\n const message = prefix + objOrMsg;\n if (hasExtra) {\n const merged = sampleFields\n ? ctx ? { ...ctx, ...sampleFields } : sampleFields\n : ctx!;\n logFn(merged, message);\n } else {\n logFn(message);\n }\n } else {\n const message = prefix + msg!;\n if (hasExtra) {\n const merged = { ...ctx, ...sampleFields, ...objOrMsg };\n logFn(merged, message);\n } else {\n logFn(objOrMsg, message);\n }\n }\n }\n\n // ── trace ────────────────────────────────────────────────────────────────\n\n trace(obj: Record<string, unknown>, msg: string): void;\n trace(msg: string): void;\n trace(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('trace', objOrMsg, msg);\n }\n\n // ── debug ────────────────────────────────────────────────────────────────\n\n debug(obj: Record<string, unknown>, msg: string): void;\n debug(msg: string): void;\n debug(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('debug', objOrMsg, msg);\n }\n\n // ── info ─────────────────────────────────────────────────────────────────\n\n info(obj: Record<string, unknown>, msg: string): void;\n info(msg: string): void;\n info(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('info', objOrMsg, msg);\n }\n\n // ── warn ─────────────────────────────────────────────────────────────────\n\n warn(obj: Record<string, unknown>, msg: string): void;\n warn(msg: string): void;\n warn(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('warn', objOrMsg, msg);\n }\n\n // ── error ────────────────────────────────────────────────────────────────\n\n error(obj: Record<string, unknown>, msg: string): void;\n error(msg: string): void;\n error(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('error', objOrMsg, msg);\n }\n\n // ── fatal ────────────────────────────────────────────────────────────────\n\n /**\n * Logs at FATAL level then kills the process with exit code 1.\n * Flushes sample counters and log buffer before exiting.\n */\n fatal(obj: Record<string, unknown>, msg: string): never;\n fatal(msg: string): never;\n fatal(objOrMsg: Record<string, unknown> | string, msg?: string): never {\n this._log('fatal', objOrMsg, msg);\n this.flushSampleCounts();\n try { this._pino.flush?.(); } catch { /* exiting anyway */ }\n process.exit(1);\n }\n\n // ── alert ────────────────────────────────────────────────────────────────\n\n /**\n * Logs at ALERT level — highest severity, above fatal.\n * Use for conditions requiring immediate operator attention:\n * - Security breaches detected\n * - Data corruption detected\n * - Critical SLA violations\n *\n * Unlike fatal(), alert() does NOT kill the process.\n * The service continues running so it can handle other requests.\n *\n * @example\n * logger.alert({ breach_type: 'unauthorized_access', ip }, 'security breach detected');\n */\n alert(obj: Record<string, unknown>, msg: string): void;\n alert(msg: string): void;\n alert(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('alert', objOrMsg, msg);\n }\n\n // ── child ────────────────────────────────────────────────────────────────\n\n /**\n * Creates a child logger with additional bound fields.\n * Shares sample counters with the parent for consistent global sampling.\n */\n child(bindings: Record<string, unknown>): PinoLogger {\n return new PinoLogger(this._pino.child(bindings), this._sample, this._sampleState);\n }\n\n // ── startTimer ───────────────────────────────────────────────────────────\n\n /**\n * Starts a high-resolution timer. Returns a Timer object with:\n * - elapsed(): returns ms elapsed so far\n * - done(msg) / done(obj, msg): logs at info level with duration_ms\n *\n * @example\n * const timer = logger.startTimer();\n * await processRequest();\n * timer.done({ req_id }, 'request processed'); // includes duration_ms\n */\n startTimer(): Timer {\n const start = process.hrtime.bigint();\n const self = this;\n return {\n elapsed(): number {\n return Number(process.hrtime.bigint() - start) / 1_000_000;\n },\n done(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n const duration_ms = Math.round(Number(process.hrtime.bigint() - start) * 100 / 1_000_000) / 100;\n if (typeof objOrMsg === 'string') {\n self._log('info', { duration_ms }, objOrMsg);\n } else {\n self._log('info', { ...objOrMsg, duration_ms }, msg!);\n }\n },\n } as Timer;\n }\n\n // ── metric ───────────────────────────────────────────────────────────────\n\n /**\n * Emits a structured metric log at info level.\n * All metric logs include `metric_type: \"metric\"` for easy filtering\n * in your log parser / aggregator.\n *\n * @example\n * logger.metric({ metric_name: 'http_request_duration', metric_value: 42, metric_unit: 'ms' });\n * logger.metric({ metric_name: 'queue_depth', metric_value: 150, metric_unit: 'count', queue: 'orders' });\n */\n metric(fields: MetricFields): void {\n const { metric_name, ...rest } = fields;\n this._log('info', { metric_type: 'metric', metric_name, ...rest }, `metric: ${metric_name}`);\n }\n\n // ── sampling ─────────────────────────────────────────────────────────────\n\n /**\n * Flushes any remaining sample counters as summary log lines.\n * Called automatically by fatal() and shutdown().\n * Call manually if you need to ensure all counts are emitted.\n */\n flushSampleCounts(): void {\n if (!this._sample) return;\n for (const [level, state] of this._sampleState.entries()) {\n if (state.since_last_emit > 0) {\n const prefix = LEVEL_PREFIXES[level] ?? `[${level.toUpperCase()}] `;\n const logFn: PinoLogFn = (this._pino[level as keyof PinoInstance] as PinoLogFn).bind(this._pino);\n logFn(\n { sampled_count: state.since_last_emit, sampled_total: state.total },\n prefix + 'sampled log flush',\n );\n state.since_last_emit = 0;\n }\n }\n }\n\n // ── shutdown ─────────────────────────────────────────────────────────────\n\n /**\n * Gracefully shuts down the logger:\n * 1. Flushes any remaining sample counters\n * 2. Flushes the Pino write buffer (important for async transports)\n *\n * Call this on SIGTERM / SIGINT before process exit.\n *\n * @example\n * process.on('SIGTERM', async () => {\n * logger.info('shutting down');\n * await logger.shutdown();\n * process.exit(0);\n * });\n */\n async shutdown(): Promise<void> {\n this.flushSampleCounts();\n return new Promise<void>((resolve, reject) => {\n if (typeof this._pino.flush === 'function') {\n this._pino.flush((err?: Error) => {\n if (err) reject(err);\n else resolve();\n });\n } else {\n resolve();\n }\n });\n }\n\n // ── isLevelEnabled ───────────────────────────────────────────────────────\n\n isLevelEnabled(level: LogLevel): boolean {\n return this._pino.isLevelEnabled(level);\n }\n\n // ── instance ─────────────────────────────────────────────────────────────\n\n /**\n * Exposes the raw Pino Logger instance.\n * Use for integrations that require it directly (e.g. pino-http).\n */\n get instance(): PinoInstance {\n return this._pino;\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Creates a configured PinoLogger instance.\n *\n * Reads from environment:\n * LOG_LEVEL — trace|debug|info|warn|error|fatal|alert (default: info)\n * LOG_VERBOSE — true|false forces trace level (default: false)\n * NODE_ENV — development enables pino-pretty (default: production)\n *\n * @example\n * const logger = createLogger({\n * service: 'windy-gateway',\n * version: process.env.npm_package_version,\n * env: process.env.NODE_ENV,\n * sample: { trace: 100, debug: 10 }, // emit every 100th trace, every 10th debug\n * });\n */\nexport function createLogger(fields: CreateLoggerOptions): PinoLogger {\n const pinoInstance = pino({\n level: resolveLogLevel(),\n\n // Register custom \"alert\" level above fatal (70 > 60)\n customLevels: { [ALERT_LEVEL_NAME]: ALERT_LEVEL_NUM },\n\n // Merged into every log line\n base: {\n service: fields.service,\n env: fields.env ?? process.env.NODE_ENV ?? 'production',\n version: fields.version ?? process.env.npm_package_version ?? 'unknown',\n },\n\n // ISO timestamp on every line\n timestamp: pino.stdTimeFunctions.isoTime,\n\n // Use string level labels (info/warn/error) not numeric codes (30/40/50)\n // Uses pre-allocated objects to avoid per-call allocation + GC pressure.\n formatters: {\n level(label) {\n return LEVEL_OBJECTS[label] ?? { level: label };\n },\n },\n\n // Serialise Error objects: message, stack, code, type\n serializers: {\n err: pino.stdSerializers.err,\n error: pino.stdSerializers.err,\n },\n\n // Redact sensitive fields before they reach stdout\n // NOTE: Avoid wildcard paths (*.field) — they force Pino to walk\n // the entire object tree on every log call, which is expensive.\n // Use explicit paths for predictable O(1) redaction.\n redact: {\n paths: [\n 'req.headers.authorization',\n 'req.headers.cookie',\n 'body.password',\n 'body.api_key',\n 'body.token',\n 'body.secret',\n 'headers.authorization',\n 'headers.cookie',\n 'password',\n 'apiKey',\n 'api_key',\n 'secret',\n ],\n censor: '[REDACTED]',\n },\n\n transport: buildTransport(),\n });\n\n return new PinoLogger(pinoInstance, fields.sample);\n}\n","export const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'alert'] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\n/**\n * Fields merged into every log line emitted by this service.\n * All fields follow snake_case naming convention.\n */\nexport interface BaseLogFields {\n /** Service identifier in kebab-case. e.g. 'windy-gateway', 'fx-api' */\n service: string;\n /** App version — recommended: process.env.npm_package_version */\n version?: string;\n /** Runtime environment — recommended: process.env.NODE_ENV */\n env?: string;\n}\n\n/**\n * Options for createLogger — extends BaseLogFields with sampling config.\n */\nexport interface CreateLoggerOptions extends BaseLogFields {\n /**\n * Sample rates per level. e.g. { trace: 100 } emits every 100th trace log.\n * Skipped logs are counted — the emitted log includes `sampled_count` and\n * `sampled_total` fields so no data is lost.\n * Levels not listed here (and warn/error/fatal/alert) always emit every log.\n */\n sample?: Partial<Record<LogLevel, number>>;\n}\n\n/**\n * Fields to include when logging an HTTP request context.\n * Bind these via logger.child() at request start.\n */\nexport interface RequestContext {\n req_id: string;\n method?: string;\n path?: string;\n user_id?: string;\n}\n\n/**\n * Fields to include when logging an error.\n * Always pass the raw Error object as `err` — Pino serialises it automatically.\n */\nexport interface ErrorContext {\n err: Error | unknown;\n req_id?: string;\n [key: string]: unknown;\n}\n\n/**\n * Fields for structured metric logging.\n */\nexport interface MetricFields {\n /** Metric identifier in snake_case. e.g. 'http_request_duration_ms' */\n metric_name: string;\n /** Numeric value of the metric */\n metric_value: number;\n /** Unit of measurement. e.g. 'ms', 'bytes', 'count' */\n metric_unit?: string;\n [key: string]: unknown;\n}\n\n/**\n * Timer returned by startTimer().\n */\nexport interface Timer {\n /** Returns elapsed time in milliseconds */\n elapsed(): number;\n /** Logs at info level with duration_ms field */\n done(msg: string): void;\n done(obj: Record<string, unknown>, msg: string): void;\n}\n","import { LOG_LEVELS, LogLevel } from './types.js';\n\n/**\n * Resolves the active log level from environment variables.\n *\n * Priority:\n * 1. LOG_VERBOSE=true → forces 'trace' (verbose mode)\n * 2. LOG_LEVEL → uses the specified level\n * 3. default → 'info'\n *\n * Invalid LOG_LEVEL values warn to stderr and fall back to 'info'.\n * This prevents silent log blackouts from typos in config.\n */\nexport function resolveLogLevel(): LogLevel {\n if (process.env.LOG_VERBOSE === 'true') {\n return 'trace';\n }\n\n const raw = process.env.LOG_LEVEL?.toLowerCase().trim();\n\n if (!raw) return 'info';\n\n if ((LOG_LEVELS as readonly string[]).includes(raw)) {\n return raw as LogLevel;\n }\n\n process.stderr.write(\n `[pino-logger] Invalid LOG_LEVEL=\"${raw}\". ` +\n `Valid values: ${LOG_LEVELS.join(', ')}. Falling back to \"info\".\\n`\n );\n\n return 'info';\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\n\nconst storage = new AsyncLocalStorage<Record<string, unknown>>();\n\n/**\n * Runs a function with context fields that are automatically merged\n * into every log call within that async scope.\n *\n * Contexts nest — inner calls inherit and can override outer fields.\n *\n * @example\n * // In middleware:\n * app.use((req, res, next) => {\n * withContext({ req_id: req.id, user_id: req.userId }, next);\n * });\n *\n * // Anywhere in the call stack — no need to pass logger around:\n * logger.info('processing'); // automatically includes req_id, user_id\n */\nexport function withContext<T>(context: Record<string, unknown>, fn: () => T): T {\n const existing = storage.getStore();\n const merged = existing ? { ...existing, ...context } : context;\n return storage.run(merged, fn);\n}\n\n/**\n * Returns the current async context, or undefined if none is active.\n * Used internally by PinoLogger to auto-merge context into log calls.\n */\nexport function getContext(): Record<string, unknown> | undefined {\n return storage.getStore();\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/logger.ts","../src/types.ts","../src/resolve-level.ts","../src/context.ts"],"sourcesContent":["import pino, { LoggerOptions, LogFn } from 'pino';\nimport { BaseFieldKey, CreateLoggerOptions, LOG_LEVELS, LogLevel, MetricFields, Timer } from './types.js';\nimport { resolveLogLevel } from './resolve-level.js';\nimport { getContext } from './context.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n// Pino logger with custom 'alert' level added\ntype PinoInstance = pino.Logger<'alert'>;\n\n// A function that can log — covers all Pino level methods including custom ones\ntype PinoLogFn = LogFn;\n\n// ─── Pre-allocated constants ──────────────────────────────────────────────────\n\n// Pre-allocate level label objects to avoid creating a new object on every log call.\n// This eliminates per-call GC pressure from the formatters.level function.\nconst LEVEL_OBJECTS: Record<string, { level: string }> = Object.fromEntries(\n LOG_LEVELS.map((l) => [l, { level: l }]),\n);\n\n// Pre-allocate level prefix strings: \"[INFO] \", \"[ERROR] \", etc.\nconst LEVEL_PREFIXES: Record<string, string> = Object.fromEntries(\n LOG_LEVELS.map((l) => [l, `[${l.toUpperCase()}] `]),\n);\n\n// Pino's built-in numeric levels max at fatal=60. Alert is above fatal.\nconst ALERT_LEVEL_NUM = 70;\nconst ALERT_LEVEL_NAME = 'alert';\n\n// ─── Sample state ─────────────────────────────────────────────────────────────\n\ninterface SampleState {\n since_last_emit: number;\n total: number;\n}\n\n// ─── Transport ────────────────────────────────────────────────────────────────\n\nfunction buildTransport(): LoggerOptions['transport'] {\n if (process.env.NODE_ENV !== 'development') return undefined;\n\n return {\n target: 'pino-pretty',\n options: {\n colorize: true,\n translateTime: 'HH:MM:ss.l',\n ignore: 'pid,hostname',\n messageFormat: '{msg}',\n },\n };\n}\n\n// ─── PinoLogger ───────────────────────────────────────────────────────────────\n\n/**\n * Opinionated Pino wrapper with:\n * - LOG_LEVEL / LOG_VERBOSE env var control\n * - Level prefix in messages: \"[INFO] msg\" for log parser alerting\n * - Full Error serialisation (message, stack, code) via `err` field\n * - alert() — highest severity level (above fatal), does NOT kill process\n * - fatal() kills the process with exit code 1\n * - child() returns PinoLogger (not raw Pino instance)\n * - AsyncLocalStorage context propagation (withContext)\n * - Request duration tracking (startTimer)\n * - Structured metric logging (metric)\n * - Log sampling with counting (no data loss)\n * - Graceful async shutdown\n * - pino-pretty in development (NODE_ENV=development)\n * - Sensitive field redaction\n */\nexport class PinoLogger {\n private readonly _pino: PinoInstance;\n private readonly _sample: Partial<Record<LogLevel, number>> | undefined;\n // Shared across parent + child loggers so sampling counters are global\n private readonly _sampleState: Map<string, SampleState>;\n\n constructor(\n pinoInstance: PinoInstance,\n sample?: Partial<Record<LogLevel, number>>,\n sampleState?: Map<string, SampleState>,\n ) {\n this._pino = pinoInstance;\n this._sample = sample;\n this._sampleState = sampleState ?? new Map();\n }\n\n // ── core log dispatch ────────────────────────────────────────────────────\n\n /**\n * Internal log dispatch. Handles sampling, async context merge,\n * and level prefix injection. Optimised for the common fast path\n * (no context, no sampling) to avoid object allocation.\n */\n private _log(level: LogLevel, objOrMsg: Record<string, unknown> | string, msg?: string): void {\n // ── Sampling gate ──\n const rate = this._sample?.[level];\n let sampleFields: { sampled_count: number; sampled_total: number } | null = null;\n if (rate && rate > 1) {\n let state = this._sampleState.get(level);\n if (!state) {\n state = { since_last_emit: 0, total: 0 };\n this._sampleState.set(level, state);\n }\n state.total++;\n state.since_last_emit++;\n if (state.since_last_emit < rate) return; // skip but counted\n sampleFields = { sampled_count: state.since_last_emit, sampled_total: state.total };\n state.since_last_emit = 0;\n }\n\n // ── Async context ──\n const ctx = getContext();\n const hasExtra = ctx !== undefined || sampleFields !== null;\n\n // ── Resolve the Pino log function for this level ──\n const logFn: PinoLogFn = (this._pino[level] as PinoLogFn).bind(this._pino);\n\n // ── Emit with level prefix in message ──\n const prefix = LEVEL_PREFIXES[level] ?? `[${level.toUpperCase()}] `;\n\n if (typeof objOrMsg === 'string') {\n const message = prefix + objOrMsg;\n if (hasExtra) {\n const merged = sampleFields\n ? ctx ? { ...ctx, ...sampleFields } : sampleFields\n : ctx!;\n logFn(merged, message);\n } else {\n logFn(message);\n }\n } else {\n const message = prefix + msg!;\n if (hasExtra) {\n const merged = { ...ctx, ...sampleFields, ...objOrMsg };\n logFn(merged, message);\n } else {\n logFn(objOrMsg, message);\n }\n }\n }\n\n // ── trace ────────────────────────────────────────────────────────────────\n\n trace(obj: Record<string, unknown>, msg: string): void;\n trace(msg: string): void;\n trace(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('trace', objOrMsg, msg);\n }\n\n // ── debug ────────────────────────────────────────────────────────────────\n\n debug(obj: Record<string, unknown>, msg: string): void;\n debug(msg: string): void;\n debug(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('debug', objOrMsg, msg);\n }\n\n // ── info ─────────────────────────────────────────────────────────────────\n\n info(obj: Record<string, unknown>, msg: string): void;\n info(msg: string): void;\n info(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('info', objOrMsg, msg);\n }\n\n // ── warn ─────────────────────────────────────────────────────────────────\n\n warn(obj: Record<string, unknown>, msg: string): void;\n warn(msg: string): void;\n warn(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('warn', objOrMsg, msg);\n }\n\n // ── error ────────────────────────────────────────────────────────────────\n\n error(obj: Record<string, unknown>, msg: string): void;\n error(msg: string): void;\n error(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('error', objOrMsg, msg);\n }\n\n // ── fatal ────────────────────────────────────────────────────────────────\n\n /**\n * Logs at FATAL level then kills the process with exit code 1.\n * Flushes sample counters and log buffer before exiting.\n */\n fatal(obj: Record<string, unknown>, msg: string): never;\n fatal(msg: string): never;\n fatal(objOrMsg: Record<string, unknown> | string, msg?: string): never {\n this._log('fatal', objOrMsg, msg);\n this.flushSampleCounts();\n try { this._pino.flush?.(); } catch { /* exiting anyway */ }\n process.exit(1);\n }\n\n // ── alert ────────────────────────────────────────────────────────────────\n\n /**\n * Logs at ALERT level — highest severity, above fatal.\n * Use for conditions requiring immediate operator attention:\n * - Security breaches detected\n * - Data corruption detected\n * - Critical SLA violations\n *\n * Unlike fatal(), alert() does NOT kill the process.\n * The service continues running so it can handle other requests.\n *\n * @example\n * logger.alert({ breach_type: 'unauthorized_access', ip }, 'security breach detected');\n */\n alert(obj: Record<string, unknown>, msg: string): void;\n alert(msg: string): void;\n alert(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n this._log('alert', objOrMsg, msg);\n }\n\n // ── child ────────────────────────────────────────────────────────────────\n\n /**\n * Creates a child logger with additional bound fields.\n * Shares sample counters with the parent for consistent global sampling.\n */\n child(bindings: Record<string, unknown>): PinoLogger {\n return new PinoLogger(this._pino.child(bindings), this._sample, this._sampleState);\n }\n\n // ── startTimer ───────────────────────────────────────────────────────────\n\n /**\n * Starts a high-resolution timer. Returns a Timer object with:\n * - elapsed(): returns ms elapsed so far\n * - done(msg) / done(obj, msg): logs at info level with duration_ms\n *\n * @example\n * const timer = logger.startTimer();\n * await processRequest();\n * timer.done({ req_id }, 'request processed'); // includes duration_ms\n */\n startTimer(): Timer {\n const start = process.hrtime.bigint();\n const self = this;\n return {\n elapsed(): number {\n return Number(process.hrtime.bigint() - start) / 1_000_000;\n },\n done(objOrMsg: Record<string, unknown> | string, msg?: string): void {\n const duration_ms = Math.round(Number(process.hrtime.bigint() - start) * 100 / 1_000_000) / 100;\n if (typeof objOrMsg === 'string') {\n self._log('info', { duration_ms }, objOrMsg);\n } else {\n self._log('info', { ...objOrMsg, duration_ms }, msg!);\n }\n },\n } as Timer;\n }\n\n // ── metric ───────────────────────────────────────────────────────────────\n\n /**\n * Emits a structured metric log at info level.\n * All metric logs include `metric_type: \"metric\"` for easy filtering\n * in your log parser / aggregator.\n *\n * @example\n * logger.metric({ metric_name: 'http_request_duration', metric_value: 42, metric_unit: 'ms' });\n * logger.metric({ metric_name: 'queue_depth', metric_value: 150, metric_unit: 'count', queue: 'orders' });\n */\n metric(fields: MetricFields): void {\n const { metric_name, ...rest } = fields;\n this._log('info', { metric_type: 'metric', metric_name, ...rest }, `metric: ${metric_name}`);\n }\n\n // ── sampling ─────────────────────────────────────────────────────────────\n\n /**\n * Flushes any remaining sample counters as summary log lines.\n * Called automatically by fatal() and shutdown().\n * Call manually if you need to ensure all counts are emitted.\n */\n flushSampleCounts(): void {\n if (!this._sample) return;\n for (const [level, state] of this._sampleState.entries()) {\n if (state.since_last_emit > 0) {\n const prefix = LEVEL_PREFIXES[level] ?? `[${level.toUpperCase()}] `;\n const logFn: PinoLogFn = (this._pino[level as keyof PinoInstance] as PinoLogFn).bind(this._pino);\n logFn(\n { sampled_count: state.since_last_emit, sampled_total: state.total },\n prefix + 'sampled log flush',\n );\n state.since_last_emit = 0;\n }\n }\n }\n\n // ── shutdown ─────────────────────────────────────────────────────────────\n\n /**\n * Gracefully shuts down the logger:\n * 1. Flushes any remaining sample counters\n * 2. Flushes the Pino write buffer (important for async transports)\n *\n * Call this on SIGTERM / SIGINT before process exit.\n *\n * @example\n * process.on('SIGTERM', async () => {\n * logger.info('shutting down');\n * await logger.shutdown();\n * process.exit(0);\n * });\n */\n async shutdown(): Promise<void> {\n this.flushSampleCounts();\n return new Promise<void>((resolve, reject) => {\n if (typeof this._pino.flush === 'function') {\n this._pino.flush((err?: Error) => {\n if (err) reject(err);\n else resolve();\n });\n } else {\n resolve();\n }\n });\n }\n\n // ── isLevelEnabled ───────────────────────────────────────────────────────\n\n isLevelEnabled(level: LogLevel): boolean {\n return this._pino.isLevelEnabled(level);\n }\n\n // ── instance ─────────────────────────────────────────────────────────────\n\n /**\n * Exposes the raw Pino Logger instance.\n * Use for integrations that require it directly (e.g. pino-http).\n */\n get instance(): PinoInstance {\n return this._pino;\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Creates a configured PinoLogger instance.\n *\n * Reads from environment:\n * LOG_LEVEL — trace|debug|info|warn|error|fatal|alert (default: info)\n * LOG_VERBOSE — true|false forces trace level (default: false)\n * NODE_ENV — development enables pino-pretty (default: production)\n *\n * @example\n * const logger = createLogger({\n * service: 'windy-gateway',\n * version: process.env.npm_package_version,\n * env: process.env.NODE_ENV,\n * sample: { trace: 100, debug: 10 }, // emit every 100th trace, every 10th debug\n * });\n */\nexport function createLogger(fields: CreateLoggerOptions): PinoLogger {\n // Build base fields, then remove any that are excluded\n const excluded = new Set<BaseFieldKey>(fields.exclude);\n const base: Record<string, string> = {};\n if (!excluded.has('service')) base.service = fields.service;\n if (!excluded.has('env')) base.env = fields.env ?? process.env.NODE_ENV ?? 'production';\n if (!excluded.has('version')) base.version = fields.version ?? process.env.npm_package_version ?? 'unknown';\n\n const pinoInstance = pino({\n level: resolveLogLevel(),\n\n // Register custom \"alert\" level above fatal (70 > 60)\n customLevels: { [ALERT_LEVEL_NAME]: ALERT_LEVEL_NUM },\n\n // Merged into every log line (respects exclude list)\n base,\n\n // ISO timestamp on every line\n timestamp: pino.stdTimeFunctions.isoTime,\n\n // Use string level labels (info/warn/error) not numeric codes (30/40/50)\n // Uses pre-allocated objects to avoid per-call allocation + GC pressure.\n formatters: {\n level(label) {\n return LEVEL_OBJECTS[label] ?? { level: label };\n },\n },\n\n // Serialise Error objects: message, stack, code, type\n serializers: {\n err: pino.stdSerializers.err,\n error: pino.stdSerializers.err,\n },\n\n // Redact sensitive fields before they reach stdout\n // NOTE: Avoid wildcard paths (*.field) — they force Pino to walk\n // the entire object tree on every log call, which is expensive.\n // Use explicit paths for predictable O(1) redaction.\n redact: {\n paths: [\n 'req.headers.authorization',\n 'req.headers.cookie',\n 'body.password',\n 'body.api_key',\n 'body.token',\n 'body.secret',\n 'headers.authorization',\n 'headers.cookie',\n 'password',\n 'apiKey',\n 'api_key',\n 'secret',\n ],\n censor: '[REDACTED]',\n },\n\n transport: buildTransport(),\n });\n\n return new PinoLogger(pinoInstance, fields.sample);\n}\n","export const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'alert'] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\n/**\n * Fields merged into every log line emitted by this service.\n * All fields follow snake_case naming convention.\n */\nexport interface BaseLogFields {\n /** Service identifier in kebab-case. e.g. 'windy-gateway', 'fx-api' */\n service: string;\n /** App version — recommended: process.env.npm_package_version */\n version?: string;\n /** Runtime environment — recommended: process.env.NODE_ENV */\n env?: string;\n}\n\n/**\n * Base field keys that can be excluded from log output.\n */\nexport type BaseFieldKey = 'service' | 'env' | 'version';\n\n/**\n * Options for createLogger — extends BaseLogFields with sampling and exclusion config.\n */\nexport interface CreateLoggerOptions extends BaseLogFields {\n /**\n * Sample rates per level. e.g. { trace: 100 } emits every 100th trace log.\n * Skipped logs are counted — the emitted log includes `sampled_count` and\n * `sampled_total` fields so no data is lost.\n * Levels not listed here (and warn/error/fatal/alert) always emit every log.\n */\n sample?: Partial<Record<LogLevel, number>>;\n\n /**\n * Base fields to exclude from every log line.\n * By default all base fields (service, env, version) are included.\n * Pass field names to remove them from output.\n *\n * @example\n * // Only include service, exclude env and version:\n * createLogger({ service: 'my-api', exclude: ['env', 'version'] });\n */\n exclude?: BaseFieldKey[];\n}\n\n/**\n * Fields to include when logging an HTTP request context.\n * Bind these via logger.child() at request start.\n */\nexport interface RequestContext {\n req_id: string;\n method?: string;\n path?: string;\n user_id?: string;\n}\n\n/**\n * Fields to include when logging an error.\n * Always pass the raw Error object as `err` — Pino serialises it automatically.\n */\nexport interface ErrorContext {\n err: Error | unknown;\n req_id?: string;\n [key: string]: unknown;\n}\n\n/**\n * Fields for structured metric logging.\n */\nexport interface MetricFields {\n /** Metric identifier in snake_case. e.g. 'http_request_duration_ms' */\n metric_name: string;\n /** Numeric value of the metric */\n metric_value: number;\n /** Unit of measurement. e.g. 'ms', 'bytes', 'count' */\n metric_unit?: string;\n [key: string]: unknown;\n}\n\n/**\n * Timer returned by startTimer().\n */\nexport interface Timer {\n /** Returns elapsed time in milliseconds */\n elapsed(): number;\n /** Logs at info level with duration_ms field */\n done(msg: string): void;\n done(obj: Record<string, unknown>, msg: string): void;\n}\n","import { LOG_LEVELS, LogLevel } from './types.js';\n\n/**\n * Resolves the active log level from environment variables.\n *\n * Priority:\n * 1. LOG_VERBOSE=true → forces 'trace' (verbose mode)\n * 2. LOG_LEVEL → uses the specified level\n * 3. default → 'info'\n *\n * Invalid LOG_LEVEL values warn to stderr and fall back to 'info'.\n * This prevents silent log blackouts from typos in config.\n */\nexport function resolveLogLevel(): LogLevel {\n if (process.env.LOG_VERBOSE === 'true') {\n return 'trace';\n }\n\n const raw = process.env.LOG_LEVEL?.toLowerCase().trim();\n\n if (!raw) return 'info';\n\n if ((LOG_LEVELS as readonly string[]).includes(raw)) {\n return raw as LogLevel;\n }\n\n process.stderr.write(\n `[pino-logger] Invalid LOG_LEVEL=\"${raw}\". ` +\n `Valid values: ${LOG_LEVELS.join(', ')}. Falling back to \"info\".\\n`\n );\n\n return 'info';\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\n\nconst storage = new AsyncLocalStorage<Record<string, unknown>>();\n\n/**\n * Runs a function with context fields that are automatically merged\n * into every log call within that async scope.\n *\n * Contexts nest — inner calls inherit and can override outer fields.\n *\n * @example\n * // In middleware:\n * app.use((req, res, next) => {\n * withContext({ req_id: req.id, user_id: req.userId }, next);\n * });\n *\n * // Anywhere in the call stack — no need to pass logger around:\n * logger.info('processing'); // automatically includes req_id, user_id\n */\nexport function withContext<T>(context: Record<string, unknown>, fn: () => T): T {\n const existing = storage.getStore();\n const merged = existing ? { ...existing, ...context } : context;\n return storage.run(merged, fn);\n}\n\n/**\n * Returns the current async context, or undefined if none is active.\n * Used internally by PinoLogger to auto-merge context into log calls.\n */\nexport function getContext(): Record<string, unknown> | undefined {\n return storage.getStore();\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kamranbiglari/pino-logger",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Opinionated Pino wrapper for structured logging across Node.js services",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",