@geekmidas/logger 10.0.0-alpha.70 → 10.0.0-alpha.71

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/dist/pino.cjs CHANGED
@@ -78,10 +78,19 @@ function createLogger(options = {}) {
78
78
  options: { colorize: true }
79
79
  } } : {};
80
80
  const redact = resolveRedactConfig(options.redact);
81
+ const serializeError = (value) => {
82
+ const serialized = pino.stdSerializers.err(value);
83
+ if (!redact || serialized === value) return serialized;
84
+ return require_redact_paths.redactUrlCredentialsIn({ ...serialized });
85
+ };
81
86
  const pinoOptions = {
82
87
  ...baseOptions,
83
88
  ...options.level && { level: options.level },
84
89
  ...redact && { redact },
90
+ serializers: {
91
+ err: serializeError,
92
+ error: serializeError
93
+ },
85
94
  hooks: {
86
95
  ...redact && { logMethod(args, method) {
87
96
  const redacted = args.map((arg) => typeof arg === "string" ? require_redact_paths.redactUrlCredentials(arg) : arg);
package/dist/pino.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pino.cjs","names":["DEFAULT_REDACT_PATHS","redactUrlCredentials","otelStreamWrite","redactUrlCredentialsIn"],"sources":["../src/pino.ts"],"sourcesContent":["/**\n * Pino logger with built-in redaction support for sensitive data.\n *\n * Redaction is on by default: a logger that was never told about passwords\n * and tokens still masks them. `redact: false` is the explicit opt-out.\n *\n * @example\n * ```typescript\n * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';\n *\n * // Redaction with sensible defaults — the same as `redact: true`\n * const logger = createLogger();\n *\n * // Sensitive data is automatically masked\n * logger.info({ password: 'secret123', user: 'john' }, 'Login');\n * // Output: { password: '[Redacted]', user: 'john' } Login\n *\n * // Add custom paths (merged with defaults)\n * const logger2 = createLogger({ redact: ['user.ssn'] });\n *\n * // Override defaults for full control\n * const logger3 = createLogger({\n * redact: {\n * paths: ['onlyThis'],\n * resolution: 'override',\n * }\n * });\n * ```\n *\n * @module\n */\nimport { type LogFn, type Logger, pino } from 'pino';\nimport { otelStreamWrite } from './otel';\nimport {\n\tDEFAULT_REDACT_PATHS,\n\tredactUrlCredentials,\n\tredactUrlCredentialsIn,\n} from './redact-paths';\nimport type { CreateLoggerOptions, RedactOptions } from './types';\n\n// Re-export for backwards compatibility\nexport { DEFAULT_REDACT_PATHS } from './redact-paths';\n\n/**\n * Type for the resolved pino redact config (without our custom resolution field).\n */\ntype PinoRedactConfig =\n\t| string[]\n\t| {\n\t\t\tpaths: string[];\n\t\t\tcensor?: string | ((value: unknown, path: string[]) => unknown);\n\t\t\tremove?: boolean;\n\t };\n\n/**\n * Resolves redaction configuration from options.\n * Returns undefined only when redaction is turned off with `false`; leaving it\n * unset is the same as `true`, so sensitive paths are masked unless someone\n * decides they should not be.\n *\n * By default (resolution: 'merge'), custom paths are merged with DEFAULT_REDACT_PATHS.\n * With resolution: 'override', only the custom paths are used.\n */\nfunction resolveRedactConfig(\n\tredact: boolean | RedactOptions | undefined,\n): PinoRedactConfig | undefined {\n\tif (redact === false) {\n\t\treturn undefined;\n\t}\n\n\tif (redact === undefined || redact === true) {\n\t\treturn DEFAULT_REDACT_PATHS;\n\t}\n\n\t// Array syntax - merge with defaults\n\tif (Array.isArray(redact)) {\n\t\treturn [...DEFAULT_REDACT_PATHS, ...redact];\n\t}\n\n\t// Object syntax - check resolution mode\n\tconst { resolution = 'merge', paths, censor, remove } = redact;\n\n\tconst resolvedPaths =\n\t\tresolution === 'override' ? paths : [...DEFAULT_REDACT_PATHS, ...paths];\n\n\t// Return clean pino config without our resolution field\n\tconst config: PinoRedactConfig = { paths: resolvedPaths };\n\tif (censor !== undefined) config.censor = censor;\n\tif (remove !== undefined) config.remove = remove;\n\n\treturn config;\n}\n\n/**\n * Creates a pino logger instance with optional redaction support.\n *\n * @param options - Logger configuration options\n * @returns A configured pino logger instance\n *\n * @example\n * ```typescript\n * // Basic logger — sensitive paths are redacted by default\n * const logger = createLogger({ level: 'debug' });\n *\n * // Opt out of redaction\n * const rawLogger = createLogger({ redact: false });\n *\n * // Pretty printing in development\n * const devLogger = createLogger({ pretty: true });\n * ```\n */\nexport function createLogger(options: CreateLoggerOptions = {}) {\n\t// Pretty printing spawns a transport worker and writes for humans; in\n\t// production the output is for a log pipeline, so `pretty` is ignored there.\n\tconst pretty = options?.pretty && process.env.NODE_ENV !== 'production';\n\tconst baseOptions = pretty\n\t\t? {\n\t\t\t\ttransport: {\n\t\t\t\t\ttarget: 'pino-pretty',\n\t\t\t\t\toptions: { colorize: true },\n\t\t\t\t},\n\t\t\t}\n\t\t: {};\n\n\tconst redact = resolveRedactConfig(options.redact);\n\n\tconst pinoOptions = {\n\t\t...baseOptions,\n\t\t...(options.level && { level: options.level }),\n\t\t...(redact && { redact }),\n\t\thooks: {\n\t\t\t// Redaction also masks the credentials of any URL, in any field or\n\t\t\t// in the message: `s3://KEY:SECRET@uploads` is logged with its\n\t\t\t// secret under a name no path list can predict.\n\t\t\t...(redact && {\n\t\t\t\tlogMethod(this: Logger, args: Parameters<LogFn>, method: LogFn) {\n\t\t\t\t\tconst redacted = args.map((arg) =>\n\t\t\t\t\t\ttypeof arg === 'string' ? redactUrlCredentials(arg) : arg,\n\t\t\t\t\t) as Parameters<LogFn>;\n\t\t\t\t\treturn method.apply(this, redacted);\n\t\t\t\t},\n\t\t\t}),\n\t\t\t// Each line, once redacted and serialized, also goes to\n\t\t\t// OpenTelemetry when a LoggerProvider is registered. On the calling\n\t\t\t// thread, before any transport, so the active span is the caller's.\n\t\t\tstreamWrite: otelStreamWrite,\n\t\t},\n\t\tformatters: {\n\t\t\t...(redact && {\n\t\t\t\tlog: (object: Record<string, unknown>) =>\n\t\t\t\t\tredactUrlCredentialsIn(object),\n\t\t\t}),\n\t\t\tbindings() {\n\t\t\t\treturn { nodeVersion: process.version };\n\t\t\t},\n\t\t\tlevel: (label: string) => {\n\t\t\t\treturn { level: label.toUpperCase() };\n\t\t\t},\n\t\t},\n\t};\n\n\t// A transport writes on its own worker, so a destination only applies when\n\t// the logger is not pretty-printing.\n\treturn options.destination && !pretty\n\t\t? pino(pinoOptions, options.destination)\n\t\t: pino(pinoOptions);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAS,oBACR,QAC+B;CAC/B,IAAI,WAAW,OACd;CAGD,IAAI,WAAW,KAAA,KAAa,WAAW,MACtC,OAAOA,qBAAAA;CAIR,IAAI,MAAM,QAAQ,MAAM,GACvB,OAAO,CAAC,GAAGA,qBAAAA,sBAAsB,GAAG,MAAM;CAI3C,MAAM,EAAE,aAAa,SAAS,OAAO,QAAQ,WAAW;CAMxD,MAAM,SAA2B,EAAE,OAHlC,eAAe,aAAa,QAAQ,CAAC,GAAGA,qBAAAA,sBAAsB,GAAG,KAAK,EAGf;CACxD,IAAI,WAAW,KAAA,GAAW,OAAO,SAAS;CAC1C,IAAI,WAAW,KAAA,GAAW,OAAO,SAAS;CAE1C,OAAO;AACR;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,aAAa,UAA+B,CAAC,GAAG;CAG/D,MAAM,SAAS,SAAS,UAAU,QAAQ,IAAI,aAAa;CAC3D,MAAM,cAAc,SACjB,EACA,WAAW;EACV,QAAQ;EACR,SAAS,EAAE,UAAU,KAAK;CAC3B,EACD,IACC,CAAC;CAEJ,MAAM,SAAS,oBAAoB,QAAQ,MAAM;CAEjD,MAAM,cAAc;EACnB,GAAG;EACH,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM;EAC5C,GAAI,UAAU,EAAE,OAAO;EACvB,OAAO;GAIN,GAAI,UAAU,EACb,UAAwB,MAAyB,QAAe;IAC/D,MAAM,WAAW,KAAK,KAAK,QAC1B,OAAO,QAAQ,WAAWC,qBAAAA,qBAAqB,GAAG,IAAI,GACvD;IACA,OAAO,OAAO,MAAM,MAAM,QAAQ;GACnC,EACD;GAIA,aAAaC,aAAAA;EACd;EACA,YAAY;GACX,GAAI,UAAU,EACb,MAAM,WACLC,qBAAAA,uBAAuB,MAAM,EAC/B;GACA,WAAW;IACV,OAAO,EAAE,aAAa,QAAQ,QAAQ;GACvC;GACA,QAAQ,UAAkB;IACzB,OAAO,EAAE,OAAO,MAAM,YAAY,EAAE;GACrC;EACD;CACD;CAIA,OAAO,QAAQ,eAAe,CAAC,UAAA,GAC5B,KAAA,KAAA,CAAK,aAAa,QAAQ,WAAW,KAAA,GACrC,KAAA,KAAA,CAAK,WAAW;AACpB"}
1
+ {"version":3,"file":"pino.cjs","names":["DEFAULT_REDACT_PATHS","stdSerializers","redactUrlCredentialsIn","redactUrlCredentials","otelStreamWrite"],"sources":["../src/pino.ts"],"sourcesContent":["/**\n * Pino logger with built-in redaction support for sensitive data.\n *\n * Redaction is on by default: a logger that was never told about passwords\n * and tokens still masks them. `redact: false` is the explicit opt-out.\n *\n * @example\n * ```typescript\n * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';\n *\n * // Redaction with sensible defaults — the same as `redact: true`\n * const logger = createLogger();\n *\n * // Sensitive data is automatically masked\n * logger.info({ password: 'secret123', user: 'john' }, 'Login');\n * // Output: { password: '[Redacted]', user: 'john' } Login\n *\n * // Add custom paths (merged with defaults)\n * const logger2 = createLogger({ redact: ['user.ssn'] });\n *\n * // Override defaults for full control\n * const logger3 = createLogger({\n * redact: {\n * paths: ['onlyThis'],\n * resolution: 'override',\n * }\n * });\n * ```\n *\n * @module\n */\nimport { type LogFn, type Logger, pino, stdSerializers } from 'pino';\nimport { otelStreamWrite } from './otel';\nimport {\n\tDEFAULT_REDACT_PATHS,\n\tredactUrlCredentials,\n\tredactUrlCredentialsIn,\n} from './redact-paths';\nimport type { CreateLoggerOptions, RedactOptions } from './types';\n\n// Re-export for backwards compatibility\nexport { DEFAULT_REDACT_PATHS } from './redact-paths';\n\n/**\n * Type for the resolved pino redact config (without our custom resolution field).\n */\ntype PinoRedactConfig =\n\t| string[]\n\t| {\n\t\t\tpaths: string[];\n\t\t\tcensor?: string | ((value: unknown, path: string[]) => unknown);\n\t\t\tremove?: boolean;\n\t };\n\n/**\n * Resolves redaction configuration from options.\n * Returns undefined only when redaction is turned off with `false`; leaving it\n * unset is the same as `true`, so sensitive paths are masked unless someone\n * decides they should not be.\n *\n * By default (resolution: 'merge'), custom paths are merged with DEFAULT_REDACT_PATHS.\n * With resolution: 'override', only the custom paths are used.\n */\nfunction resolveRedactConfig(\n\tredact: boolean | RedactOptions | undefined,\n): PinoRedactConfig | undefined {\n\tif (redact === false) {\n\t\treturn undefined;\n\t}\n\n\tif (redact === undefined || redact === true) {\n\t\treturn DEFAULT_REDACT_PATHS;\n\t}\n\n\t// Array syntax - merge with defaults\n\tif (Array.isArray(redact)) {\n\t\treturn [...DEFAULT_REDACT_PATHS, ...redact];\n\t}\n\n\t// Object syntax - check resolution mode\n\tconst { resolution = 'merge', paths, censor, remove } = redact;\n\n\tconst resolvedPaths =\n\t\tresolution === 'override' ? paths : [...DEFAULT_REDACT_PATHS, ...paths];\n\n\t// Return clean pino config without our resolution field\n\tconst config: PinoRedactConfig = { paths: resolvedPaths };\n\tif (censor !== undefined) config.censor = censor;\n\tif (remove !== undefined) config.remove = remove;\n\n\treturn config;\n}\n\n/**\n * Creates a pino logger instance with optional redaction support.\n *\n * @param options - Logger configuration options\n * @returns A configured pino logger instance\n *\n * @example\n * ```typescript\n * // Basic logger — sensitive paths are redacted by default\n * const logger = createLogger({ level: 'debug' });\n *\n * // Opt out of redaction\n * const rawLogger = createLogger({ redact: false });\n *\n * // Pretty printing in development\n * const devLogger = createLogger({ pretty: true });\n * ```\n */\nexport function createLogger(options: CreateLoggerOptions = {}) {\n\t// Pretty printing spawns a transport worker and writes for humans; in\n\t// production the output is for a log pipeline, so `pretty` is ignored there.\n\tconst pretty = options?.pretty && process.env.NODE_ENV !== 'production';\n\tconst baseOptions = pretty\n\t\t? {\n\t\t\t\ttransport: {\n\t\t\t\t\ttarget: 'pino-pretty',\n\t\t\t\t\toptions: { colorize: true },\n\t\t\t\t},\n\t\t\t}\n\t\t: {};\n\n\tconst redact = resolveRedactConfig(options.redact);\n\n\t// An Error is serialized only under a key pino has a serializer for — `err`\n\t// by default — and anything else is JSON.stringify'd, which an Error survives\n\t// as `{}`: no message, no stack. `logger.error({ error }, …)` is how most\n\t// code writes it, so `error` gets the same serializer as `err`.\n\t//\n\t// The serialized copy is a plain object, so URL credentials in the message\n\t// or the stack are masked like any other field; path redaction runs after\n\t// serializers, so `err.message` and `error.stack` are paths it can reach.\n\tconst serializeError = (value: unknown) => {\n\t\tconst serialized = stdSerializers.err(value as Error);\n\t\tif (!redact || serialized === value) return serialized;\n\t\treturn redactUrlCredentialsIn({ ...serialized });\n\t};\n\n\tconst pinoOptions = {\n\t\t...baseOptions,\n\t\t...(options.level && { level: options.level }),\n\t\t...(redact && { redact }),\n\t\tserializers: { err: serializeError, error: serializeError },\n\t\thooks: {\n\t\t\t// Redaction also masks the credentials of any URL, in any field or\n\t\t\t// in the message: `s3://KEY:SECRET@uploads` is logged with its\n\t\t\t// secret under a name no path list can predict.\n\t\t\t...(redact && {\n\t\t\t\tlogMethod(this: Logger, args: Parameters<LogFn>, method: LogFn) {\n\t\t\t\t\tconst redacted = args.map((arg) =>\n\t\t\t\t\t\ttypeof arg === 'string' ? redactUrlCredentials(arg) : arg,\n\t\t\t\t\t) as Parameters<LogFn>;\n\t\t\t\t\treturn method.apply(this, redacted);\n\t\t\t\t},\n\t\t\t}),\n\t\t\t// Each line, once redacted and serialized, also goes to\n\t\t\t// OpenTelemetry when a LoggerProvider is registered. On the calling\n\t\t\t// thread, before any transport, so the active span is the caller's.\n\t\t\tstreamWrite: otelStreamWrite,\n\t\t},\n\t\tformatters: {\n\t\t\t...(redact && {\n\t\t\t\tlog: (object: Record<string, unknown>) =>\n\t\t\t\t\tredactUrlCredentialsIn(object),\n\t\t\t}),\n\t\t\tbindings() {\n\t\t\t\treturn { nodeVersion: process.version };\n\t\t\t},\n\t\t\tlevel: (label: string) => {\n\t\t\t\treturn { level: label.toUpperCase() };\n\t\t\t},\n\t\t},\n\t};\n\n\t// A transport writes on its own worker, so a destination only applies when\n\t// the logger is not pretty-printing.\n\treturn options.destination && !pretty\n\t\t? pino(pinoOptions, options.destination)\n\t\t: pino(pinoOptions);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAS,oBACR,QAC+B;CAC/B,IAAI,WAAW,OACd;CAGD,IAAI,WAAW,KAAA,KAAa,WAAW,MACtC,OAAOA,qBAAAA;CAIR,IAAI,MAAM,QAAQ,MAAM,GACvB,OAAO,CAAC,GAAGA,qBAAAA,sBAAsB,GAAG,MAAM;CAI3C,MAAM,EAAE,aAAa,SAAS,OAAO,QAAQ,WAAW;CAMxD,MAAM,SAA2B,EAAE,OAHlC,eAAe,aAAa,QAAQ,CAAC,GAAGA,qBAAAA,sBAAsB,GAAG,KAAK,EAGf;CACxD,IAAI,WAAW,KAAA,GAAW,OAAO,SAAS;CAC1C,IAAI,WAAW,KAAA,GAAW,OAAO,SAAS;CAE1C,OAAO;AACR;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,aAAa,UAA+B,CAAC,GAAG;CAG/D,MAAM,SAAS,SAAS,UAAU,QAAQ,IAAI,aAAa;CAC3D,MAAM,cAAc,SACjB,EACA,WAAW;EACV,QAAQ;EACR,SAAS,EAAE,UAAU,KAAK;CAC3B,EACD,IACC,CAAC;CAEJ,MAAM,SAAS,oBAAoB,QAAQ,MAAM;CAUjD,MAAM,kBAAkB,UAAmB;EAC1C,MAAM,aAAaC,KAAAA,eAAe,IAAI,KAAc;EACpD,IAAI,CAAC,UAAU,eAAe,OAAO,OAAO;EAC5C,OAAOC,qBAAAA,uBAAuB,EAAE,GAAG,WAAW,CAAC;CAChD;CAEA,MAAM,cAAc;EACnB,GAAG;EACH,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM;EAC5C,GAAI,UAAU,EAAE,OAAO;EACvB,aAAa;GAAE,KAAK;GAAgB,OAAO;EAAe;EAC1D,OAAO;GAIN,GAAI,UAAU,EACb,UAAwB,MAAyB,QAAe;IAC/D,MAAM,WAAW,KAAK,KAAK,QAC1B,OAAO,QAAQ,WAAWC,qBAAAA,qBAAqB,GAAG,IAAI,GACvD;IACA,OAAO,OAAO,MAAM,MAAM,QAAQ;GACnC,EACD;GAIA,aAAaC,aAAAA;EACd;EACA,YAAY;GACX,GAAI,UAAU,EACb,MAAM,WACLF,qBAAAA,uBAAuB,MAAM,EAC/B;GACA,WAAW;IACV,OAAO,EAAE,aAAa,QAAQ,QAAQ;GACvC;GACA,QAAQ,UAAkB;IACzB,OAAO,EAAE,OAAO,MAAM,YAAY,EAAE;GACrC;EACD;CACD;CAIA,OAAO,QAAQ,eAAe,CAAC,UAAA,GAC5B,KAAA,KAAA,CAAK,aAAa,QAAQ,WAAW,KAAA,GACrC,KAAA,KAAA,CAAK,WAAW;AACpB"}
package/dist/pino.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { otelStreamWrite } from "./otel.mjs";
2
2
  import { DEFAULT_REDACT_PATHS, redactUrlCredentials, redactUrlCredentialsIn } from "./redact-paths.mjs";
3
- import { pino } from "pino";
3
+ import { pino, stdSerializers } from "pino";
4
4
  //#region src/pino.ts
5
5
  /**
6
6
  * Pino logger with built-in redaction support for sensitive data.
@@ -77,10 +77,19 @@ function createLogger(options = {}) {
77
77
  options: { colorize: true }
78
78
  } } : {};
79
79
  const redact = resolveRedactConfig(options.redact);
80
+ const serializeError = (value) => {
81
+ const serialized = stdSerializers.err(value);
82
+ if (!redact || serialized === value) return serialized;
83
+ return redactUrlCredentialsIn({ ...serialized });
84
+ };
80
85
  const pinoOptions = {
81
86
  ...baseOptions,
82
87
  ...options.level && { level: options.level },
83
88
  ...redact && { redact },
89
+ serializers: {
90
+ err: serializeError,
91
+ error: serializeError
92
+ },
84
93
  hooks: {
85
94
  ...redact && { logMethod(args, method) {
86
95
  const redacted = args.map((arg) => typeof arg === "string" ? redactUrlCredentials(arg) : arg);
package/dist/pino.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pino.mjs","names":[],"sources":["../src/pino.ts"],"sourcesContent":["/**\n * Pino logger with built-in redaction support for sensitive data.\n *\n * Redaction is on by default: a logger that was never told about passwords\n * and tokens still masks them. `redact: false` is the explicit opt-out.\n *\n * @example\n * ```typescript\n * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';\n *\n * // Redaction with sensible defaults — the same as `redact: true`\n * const logger = createLogger();\n *\n * // Sensitive data is automatically masked\n * logger.info({ password: 'secret123', user: 'john' }, 'Login');\n * // Output: { password: '[Redacted]', user: 'john' } Login\n *\n * // Add custom paths (merged with defaults)\n * const logger2 = createLogger({ redact: ['user.ssn'] });\n *\n * // Override defaults for full control\n * const logger3 = createLogger({\n * redact: {\n * paths: ['onlyThis'],\n * resolution: 'override',\n * }\n * });\n * ```\n *\n * @module\n */\nimport { type LogFn, type Logger, pino } from 'pino';\nimport { otelStreamWrite } from './otel';\nimport {\n\tDEFAULT_REDACT_PATHS,\n\tredactUrlCredentials,\n\tredactUrlCredentialsIn,\n} from './redact-paths';\nimport type { CreateLoggerOptions, RedactOptions } from './types';\n\n// Re-export for backwards compatibility\nexport { DEFAULT_REDACT_PATHS } from './redact-paths';\n\n/**\n * Type for the resolved pino redact config (without our custom resolution field).\n */\ntype PinoRedactConfig =\n\t| string[]\n\t| {\n\t\t\tpaths: string[];\n\t\t\tcensor?: string | ((value: unknown, path: string[]) => unknown);\n\t\t\tremove?: boolean;\n\t };\n\n/**\n * Resolves redaction configuration from options.\n * Returns undefined only when redaction is turned off with `false`; leaving it\n * unset is the same as `true`, so sensitive paths are masked unless someone\n * decides they should not be.\n *\n * By default (resolution: 'merge'), custom paths are merged with DEFAULT_REDACT_PATHS.\n * With resolution: 'override', only the custom paths are used.\n */\nfunction resolveRedactConfig(\n\tredact: boolean | RedactOptions | undefined,\n): PinoRedactConfig | undefined {\n\tif (redact === false) {\n\t\treturn undefined;\n\t}\n\n\tif (redact === undefined || redact === true) {\n\t\treturn DEFAULT_REDACT_PATHS;\n\t}\n\n\t// Array syntax - merge with defaults\n\tif (Array.isArray(redact)) {\n\t\treturn [...DEFAULT_REDACT_PATHS, ...redact];\n\t}\n\n\t// Object syntax - check resolution mode\n\tconst { resolution = 'merge', paths, censor, remove } = redact;\n\n\tconst resolvedPaths =\n\t\tresolution === 'override' ? paths : [...DEFAULT_REDACT_PATHS, ...paths];\n\n\t// Return clean pino config without our resolution field\n\tconst config: PinoRedactConfig = { paths: resolvedPaths };\n\tif (censor !== undefined) config.censor = censor;\n\tif (remove !== undefined) config.remove = remove;\n\n\treturn config;\n}\n\n/**\n * Creates a pino logger instance with optional redaction support.\n *\n * @param options - Logger configuration options\n * @returns A configured pino logger instance\n *\n * @example\n * ```typescript\n * // Basic logger — sensitive paths are redacted by default\n * const logger = createLogger({ level: 'debug' });\n *\n * // Opt out of redaction\n * const rawLogger = createLogger({ redact: false });\n *\n * // Pretty printing in development\n * const devLogger = createLogger({ pretty: true });\n * ```\n */\nexport function createLogger(options: CreateLoggerOptions = {}) {\n\t// Pretty printing spawns a transport worker and writes for humans; in\n\t// production the output is for a log pipeline, so `pretty` is ignored there.\n\tconst pretty = options?.pretty && process.env.NODE_ENV !== 'production';\n\tconst baseOptions = pretty\n\t\t? {\n\t\t\t\ttransport: {\n\t\t\t\t\ttarget: 'pino-pretty',\n\t\t\t\t\toptions: { colorize: true },\n\t\t\t\t},\n\t\t\t}\n\t\t: {};\n\n\tconst redact = resolveRedactConfig(options.redact);\n\n\tconst pinoOptions = {\n\t\t...baseOptions,\n\t\t...(options.level && { level: options.level }),\n\t\t...(redact && { redact }),\n\t\thooks: {\n\t\t\t// Redaction also masks the credentials of any URL, in any field or\n\t\t\t// in the message: `s3://KEY:SECRET@uploads` is logged with its\n\t\t\t// secret under a name no path list can predict.\n\t\t\t...(redact && {\n\t\t\t\tlogMethod(this: Logger, args: Parameters<LogFn>, method: LogFn) {\n\t\t\t\t\tconst redacted = args.map((arg) =>\n\t\t\t\t\t\ttypeof arg === 'string' ? redactUrlCredentials(arg) : arg,\n\t\t\t\t\t) as Parameters<LogFn>;\n\t\t\t\t\treturn method.apply(this, redacted);\n\t\t\t\t},\n\t\t\t}),\n\t\t\t// Each line, once redacted and serialized, also goes to\n\t\t\t// OpenTelemetry when a LoggerProvider is registered. On the calling\n\t\t\t// thread, before any transport, so the active span is the caller's.\n\t\t\tstreamWrite: otelStreamWrite,\n\t\t},\n\t\tformatters: {\n\t\t\t...(redact && {\n\t\t\t\tlog: (object: Record<string, unknown>) =>\n\t\t\t\t\tredactUrlCredentialsIn(object),\n\t\t\t}),\n\t\t\tbindings() {\n\t\t\t\treturn { nodeVersion: process.version };\n\t\t\t},\n\t\t\tlevel: (label: string) => {\n\t\t\t\treturn { level: label.toUpperCase() };\n\t\t\t},\n\t\t},\n\t};\n\n\t// A transport writes on its own worker, so a destination only applies when\n\t// the logger is not pretty-printing.\n\treturn options.destination && !pretty\n\t\t? pino(pinoOptions, options.destination)\n\t\t: pino(pinoOptions);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAS,oBACR,QAC+B;CAC/B,IAAI,WAAW,OACd;CAGD,IAAI,WAAW,KAAA,KAAa,WAAW,MACtC,OAAO;CAIR,IAAI,MAAM,QAAQ,MAAM,GACvB,OAAO,CAAC,GAAG,sBAAsB,GAAG,MAAM;CAI3C,MAAM,EAAE,aAAa,SAAS,OAAO,QAAQ,WAAW;CAMxD,MAAM,SAA2B,EAAE,OAHlC,eAAe,aAAa,QAAQ,CAAC,GAAG,sBAAsB,GAAG,KAAK,EAGf;CACxD,IAAI,WAAW,KAAA,GAAW,OAAO,SAAS;CAC1C,IAAI,WAAW,KAAA,GAAW,OAAO,SAAS;CAE1C,OAAO;AACR;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,aAAa,UAA+B,CAAC,GAAG;CAG/D,MAAM,SAAS,SAAS,UAAU,QAAQ,IAAI,aAAa;CAC3D,MAAM,cAAc,SACjB,EACA,WAAW;EACV,QAAQ;EACR,SAAS,EAAE,UAAU,KAAK;CAC3B,EACD,IACC,CAAC;CAEJ,MAAM,SAAS,oBAAoB,QAAQ,MAAM;CAEjD,MAAM,cAAc;EACnB,GAAG;EACH,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM;EAC5C,GAAI,UAAU,EAAE,OAAO;EACvB,OAAO;GAIN,GAAI,UAAU,EACb,UAAwB,MAAyB,QAAe;IAC/D,MAAM,WAAW,KAAK,KAAK,QAC1B,OAAO,QAAQ,WAAW,qBAAqB,GAAG,IAAI,GACvD;IACA,OAAO,OAAO,MAAM,MAAM,QAAQ;GACnC,EACD;GAIA,aAAa;EACd;EACA,YAAY;GACX,GAAI,UAAU,EACb,MAAM,WACL,uBAAuB,MAAM,EAC/B;GACA,WAAW;IACV,OAAO,EAAE,aAAa,QAAQ,QAAQ;GACvC;GACA,QAAQ,UAAkB;IACzB,OAAO,EAAE,OAAO,MAAM,YAAY,EAAE;GACrC;EACD;CACD;CAIA,OAAO,QAAQ,eAAe,CAAC,SAC5B,KAAK,aAAa,QAAQ,WAAW,IACrC,KAAK,WAAW;AACpB"}
1
+ {"version":3,"file":"pino.mjs","names":[],"sources":["../src/pino.ts"],"sourcesContent":["/**\n * Pino logger with built-in redaction support for sensitive data.\n *\n * Redaction is on by default: a logger that was never told about passwords\n * and tokens still masks them. `redact: false` is the explicit opt-out.\n *\n * @example\n * ```typescript\n * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';\n *\n * // Redaction with sensible defaults — the same as `redact: true`\n * const logger = createLogger();\n *\n * // Sensitive data is automatically masked\n * logger.info({ password: 'secret123', user: 'john' }, 'Login');\n * // Output: { password: '[Redacted]', user: 'john' } Login\n *\n * // Add custom paths (merged with defaults)\n * const logger2 = createLogger({ redact: ['user.ssn'] });\n *\n * // Override defaults for full control\n * const logger3 = createLogger({\n * redact: {\n * paths: ['onlyThis'],\n * resolution: 'override',\n * }\n * });\n * ```\n *\n * @module\n */\nimport { type LogFn, type Logger, pino, stdSerializers } from 'pino';\nimport { otelStreamWrite } from './otel';\nimport {\n\tDEFAULT_REDACT_PATHS,\n\tredactUrlCredentials,\n\tredactUrlCredentialsIn,\n} from './redact-paths';\nimport type { CreateLoggerOptions, RedactOptions } from './types';\n\n// Re-export for backwards compatibility\nexport { DEFAULT_REDACT_PATHS } from './redact-paths';\n\n/**\n * Type for the resolved pino redact config (without our custom resolution field).\n */\ntype PinoRedactConfig =\n\t| string[]\n\t| {\n\t\t\tpaths: string[];\n\t\t\tcensor?: string | ((value: unknown, path: string[]) => unknown);\n\t\t\tremove?: boolean;\n\t };\n\n/**\n * Resolves redaction configuration from options.\n * Returns undefined only when redaction is turned off with `false`; leaving it\n * unset is the same as `true`, so sensitive paths are masked unless someone\n * decides they should not be.\n *\n * By default (resolution: 'merge'), custom paths are merged with DEFAULT_REDACT_PATHS.\n * With resolution: 'override', only the custom paths are used.\n */\nfunction resolveRedactConfig(\n\tredact: boolean | RedactOptions | undefined,\n): PinoRedactConfig | undefined {\n\tif (redact === false) {\n\t\treturn undefined;\n\t}\n\n\tif (redact === undefined || redact === true) {\n\t\treturn DEFAULT_REDACT_PATHS;\n\t}\n\n\t// Array syntax - merge with defaults\n\tif (Array.isArray(redact)) {\n\t\treturn [...DEFAULT_REDACT_PATHS, ...redact];\n\t}\n\n\t// Object syntax - check resolution mode\n\tconst { resolution = 'merge', paths, censor, remove } = redact;\n\n\tconst resolvedPaths =\n\t\tresolution === 'override' ? paths : [...DEFAULT_REDACT_PATHS, ...paths];\n\n\t// Return clean pino config without our resolution field\n\tconst config: PinoRedactConfig = { paths: resolvedPaths };\n\tif (censor !== undefined) config.censor = censor;\n\tif (remove !== undefined) config.remove = remove;\n\n\treturn config;\n}\n\n/**\n * Creates a pino logger instance with optional redaction support.\n *\n * @param options - Logger configuration options\n * @returns A configured pino logger instance\n *\n * @example\n * ```typescript\n * // Basic logger — sensitive paths are redacted by default\n * const logger = createLogger({ level: 'debug' });\n *\n * // Opt out of redaction\n * const rawLogger = createLogger({ redact: false });\n *\n * // Pretty printing in development\n * const devLogger = createLogger({ pretty: true });\n * ```\n */\nexport function createLogger(options: CreateLoggerOptions = {}) {\n\t// Pretty printing spawns a transport worker and writes for humans; in\n\t// production the output is for a log pipeline, so `pretty` is ignored there.\n\tconst pretty = options?.pretty && process.env.NODE_ENV !== 'production';\n\tconst baseOptions = pretty\n\t\t? {\n\t\t\t\ttransport: {\n\t\t\t\t\ttarget: 'pino-pretty',\n\t\t\t\t\toptions: { colorize: true },\n\t\t\t\t},\n\t\t\t}\n\t\t: {};\n\n\tconst redact = resolveRedactConfig(options.redact);\n\n\t// An Error is serialized only under a key pino has a serializer for — `err`\n\t// by default — and anything else is JSON.stringify'd, which an Error survives\n\t// as `{}`: no message, no stack. `logger.error({ error }, …)` is how most\n\t// code writes it, so `error` gets the same serializer as `err`.\n\t//\n\t// The serialized copy is a plain object, so URL credentials in the message\n\t// or the stack are masked like any other field; path redaction runs after\n\t// serializers, so `err.message` and `error.stack` are paths it can reach.\n\tconst serializeError = (value: unknown) => {\n\t\tconst serialized = stdSerializers.err(value as Error);\n\t\tif (!redact || serialized === value) return serialized;\n\t\treturn redactUrlCredentialsIn({ ...serialized });\n\t};\n\n\tconst pinoOptions = {\n\t\t...baseOptions,\n\t\t...(options.level && { level: options.level }),\n\t\t...(redact && { redact }),\n\t\tserializers: { err: serializeError, error: serializeError },\n\t\thooks: {\n\t\t\t// Redaction also masks the credentials of any URL, in any field or\n\t\t\t// in the message: `s3://KEY:SECRET@uploads` is logged with its\n\t\t\t// secret under a name no path list can predict.\n\t\t\t...(redact && {\n\t\t\t\tlogMethod(this: Logger, args: Parameters<LogFn>, method: LogFn) {\n\t\t\t\t\tconst redacted = args.map((arg) =>\n\t\t\t\t\t\ttypeof arg === 'string' ? redactUrlCredentials(arg) : arg,\n\t\t\t\t\t) as Parameters<LogFn>;\n\t\t\t\t\treturn method.apply(this, redacted);\n\t\t\t\t},\n\t\t\t}),\n\t\t\t// Each line, once redacted and serialized, also goes to\n\t\t\t// OpenTelemetry when a LoggerProvider is registered. On the calling\n\t\t\t// thread, before any transport, so the active span is the caller's.\n\t\t\tstreamWrite: otelStreamWrite,\n\t\t},\n\t\tformatters: {\n\t\t\t...(redact && {\n\t\t\t\tlog: (object: Record<string, unknown>) =>\n\t\t\t\t\tredactUrlCredentialsIn(object),\n\t\t\t}),\n\t\t\tbindings() {\n\t\t\t\treturn { nodeVersion: process.version };\n\t\t\t},\n\t\t\tlevel: (label: string) => {\n\t\t\t\treturn { level: label.toUpperCase() };\n\t\t\t},\n\t\t},\n\t};\n\n\t// A transport writes on its own worker, so a destination only applies when\n\t// the logger is not pretty-printing.\n\treturn options.destination && !pretty\n\t\t? pino(pinoOptions, options.destination)\n\t\t: pino(pinoOptions);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAS,oBACR,QAC+B;CAC/B,IAAI,WAAW,OACd;CAGD,IAAI,WAAW,KAAA,KAAa,WAAW,MACtC,OAAO;CAIR,IAAI,MAAM,QAAQ,MAAM,GACvB,OAAO,CAAC,GAAG,sBAAsB,GAAG,MAAM;CAI3C,MAAM,EAAE,aAAa,SAAS,OAAO,QAAQ,WAAW;CAMxD,MAAM,SAA2B,EAAE,OAHlC,eAAe,aAAa,QAAQ,CAAC,GAAG,sBAAsB,GAAG,KAAK,EAGf;CACxD,IAAI,WAAW,KAAA,GAAW,OAAO,SAAS;CAC1C,IAAI,WAAW,KAAA,GAAW,OAAO,SAAS;CAE1C,OAAO;AACR;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,aAAa,UAA+B,CAAC,GAAG;CAG/D,MAAM,SAAS,SAAS,UAAU,QAAQ,IAAI,aAAa;CAC3D,MAAM,cAAc,SACjB,EACA,WAAW;EACV,QAAQ;EACR,SAAS,EAAE,UAAU,KAAK;CAC3B,EACD,IACC,CAAC;CAEJ,MAAM,SAAS,oBAAoB,QAAQ,MAAM;CAUjD,MAAM,kBAAkB,UAAmB;EAC1C,MAAM,aAAa,eAAe,IAAI,KAAc;EACpD,IAAI,CAAC,UAAU,eAAe,OAAO,OAAO;EAC5C,OAAO,uBAAuB,EAAE,GAAG,WAAW,CAAC;CAChD;CAEA,MAAM,cAAc;EACnB,GAAG;EACH,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM;EAC5C,GAAI,UAAU,EAAE,OAAO;EACvB,aAAa;GAAE,KAAK;GAAgB,OAAO;EAAe;EAC1D,OAAO;GAIN,GAAI,UAAU,EACb,UAAwB,MAAyB,QAAe;IAC/D,MAAM,WAAW,KAAK,KAAK,QAC1B,OAAO,QAAQ,WAAW,qBAAqB,GAAG,IAAI,GACvD;IACA,OAAO,OAAO,MAAM,MAAM,QAAQ;GACnC,EACD;GAIA,aAAa;EACd;EACA,YAAY;GACX,GAAI,UAAU,EACb,MAAM,WACL,uBAAuB,MAAM,EAC/B;GACA,WAAW;IACV,OAAO,EAAE,aAAa,QAAQ,QAAQ;GACvC;GACA,QAAQ,UAAkB;IACzB,OAAO,EAAE,OAAO,MAAM,YAAY,EAAE;GACrC;EACD;CACD;CAIA,OAAO,QAAQ,eAAe,CAAC,SAC5B,KAAK,aAAa,QAAQ,WAAW,IACrC,KAAK,WAAW;AACpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/logger",
3
- "version": "10.0.0-alpha.70",
3
+ "version": "10.0.0-alpha.71",
4
4
  "description": "A simple and flexible logging library for Node.js and the browser",
5
5
  "private": false,
6
6
  "type": "module",