@geekmidas/logger 10.0.0-alpha.9 → 10.0.0-alpha.90

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.
Files changed (61) hide show
  1. package/dist/console.cjs +24 -25
  2. package/dist/console.cjs.map +1 -1
  3. package/dist/console.d.cts +4 -7
  4. package/dist/console.d.cts.map +1 -1
  5. package/dist/console.d.mts +4 -7
  6. package/dist/console.d.mts.map +1 -1
  7. package/dist/console.mjs +23 -25
  8. package/dist/console.mjs.map +1 -1
  9. package/dist/index.cjs +3 -3
  10. package/dist/index.d.cts +2 -2
  11. package/dist/index.d.mts +2 -2
  12. package/dist/index.mjs +2 -3
  13. package/dist/otel.cjs +113 -0
  14. package/dist/otel.cjs.map +1 -0
  15. package/dist/otel.d.cts +31 -0
  16. package/dist/otel.d.cts.map +1 -0
  17. package/dist/otel.d.mts +31 -0
  18. package/dist/otel.d.mts.map +1 -0
  19. package/dist/otel.mjs +110 -0
  20. package/dist/otel.mjs.map +1 -0
  21. package/dist/pino.cjs +67 -39
  22. package/dist/pino.cjs.map +1 -1
  23. package/dist/pino.d.cts +9 -12
  24. package/dist/pino.d.cts.map +1 -1
  25. package/dist/pino.d.mts +9 -12
  26. package/dist/pino.d.mts.map +1 -1
  27. package/dist/pino.mjs +66 -16
  28. package/dist/pino.mjs.map +1 -1
  29. package/dist/redact-paths.cjs +116 -2
  30. package/dist/redact-paths.cjs.map +1 -0
  31. package/dist/redact-paths.d.cts +31 -2
  32. package/dist/redact-paths.d.cts.map +1 -0
  33. package/dist/redact-paths.d.mts +31 -2
  34. package/dist/redact-paths.d.mts.map +1 -0
  35. package/dist/redact-paths.mjs +113 -2
  36. package/dist/redact-paths.mjs.map +1 -0
  37. package/dist/types.cjs +32 -2
  38. package/dist/types.cjs.map +1 -0
  39. package/dist/types.d.cts +175 -2
  40. package/dist/types.d.cts.map +1 -0
  41. package/dist/types.d.mts +175 -2
  42. package/dist/types.d.mts.map +1 -0
  43. package/dist/types.mjs +31 -2
  44. package/dist/types.mjs.map +1 -0
  45. package/package.json +22 -3
  46. package/dist/redact-paths-CsK0_uz-.d.cts +0 -19
  47. package/dist/redact-paths-CsK0_uz-.d.cts.map +0 -1
  48. package/dist/redact-paths-D07eTMlH.d.mts +0 -19
  49. package/dist/redact-paths-D07eTMlH.d.mts.map +0 -1
  50. package/dist/redact-paths-D0m0DIuQ.cjs +0 -73
  51. package/dist/redact-paths-D0m0DIuQ.cjs.map +0 -1
  52. package/dist/redact-paths-DQoIXhkS.mjs +0 -67
  53. package/dist/redact-paths-DQoIXhkS.mjs.map +0 -1
  54. package/dist/types-BDdpcrpy.d.mts +0 -166
  55. package/dist/types-BDdpcrpy.d.mts.map +0 -1
  56. package/dist/types-Dk_k2-f_.d.cts +0 -166
  57. package/dist/types-Dk_k2-f_.d.cts.map +0 -1
  58. package/dist/types-ag_0Cvbg.cjs +0 -38
  59. package/dist/types-ag_0Cvbg.cjs.map +0 -1
  60. package/dist/types-yQ6XOihF.mjs +0 -32
  61. package/dist/types-yQ6XOihF.mjs.map +0 -1
package/dist/otel.mjs ADDED
@@ -0,0 +1,110 @@
1
+ import { SeverityNumber, logs } from "@opentelemetry/api-logs";
2
+ //#region src/otel.ts
3
+ /**
4
+ * The bridge from pino to OpenTelemetry logs.
5
+ *
6
+ * A pino record is also emitted through `@opentelemetry/api-logs` when a
7
+ * `LoggerProvider` is registered globally — as the generated telemetry setup
8
+ * of a production server does. Nothing hooks module loading, so it works the
9
+ * same in a single bundled file as from `node_modules`.
10
+ *
11
+ * `@opentelemetry/api-logs` keeps its provider on `globalThis` under a
12
+ * `Symbol.for` key, so the copy imported here and the copy the SDK registered
13
+ * through share one provider even when a bundle holds both. That is why the
14
+ * provider is looked up per record rather than once: a logger obtained before
15
+ * the SDK registered — or through another copy's proxy — would stay a no-op.
16
+ *
17
+ * @module
18
+ */
19
+ /** The instrumentation scope every bridged record carries. */
20
+ const OTEL_SCOPE = "@geekmidas/logger";
21
+ /** Pino's level labels, upper-cased as the logger prints them. */
22
+ const SEVERITY = {
23
+ TRACE: SeverityNumber.TRACE,
24
+ DEBUG: SeverityNumber.DEBUG,
25
+ INFO: SeverityNumber.INFO,
26
+ WARN: SeverityNumber.WARN,
27
+ ERROR: SeverityNumber.ERROR,
28
+ FATAL: SeverityNumber.FATAL
29
+ };
30
+ /** Pino's numeric levels, for a record whose level was not formatted. */
31
+ function severityFromNumber(level) {
32
+ if (level >= 60) return SeverityNumber.FATAL;
33
+ if (level >= 50) return SeverityNumber.ERROR;
34
+ if (level >= 40) return SeverityNumber.WARN;
35
+ if (level >= 30) return SeverityNumber.INFO;
36
+ if (level >= 20) return SeverityNumber.DEBUG;
37
+ return SeverityNumber.TRACE;
38
+ }
39
+ const SEVERITY_TEXT = {
40
+ [SeverityNumber.TRACE]: "TRACE",
41
+ [SeverityNumber.DEBUG]: "DEBUG",
42
+ [SeverityNumber.INFO]: "INFO",
43
+ [SeverityNumber.WARN]: "WARN",
44
+ [SeverityNumber.ERROR]: "ERROR",
45
+ [SeverityNumber.FATAL]: "FATAL"
46
+ };
47
+ let cached;
48
+ /** The bridge's logger from whichever provider is registered now. */
49
+ function otelLogger() {
50
+ const provider = logs.getLoggerProvider();
51
+ if (cached?.provider !== provider) cached = {
52
+ provider,
53
+ logger: provider.getLogger(OTEL_SCOPE)
54
+ };
55
+ return cached.logger;
56
+ }
57
+ /**
58
+ * Emit one serialized pino line through OpenTelemetry, when a provider is
59
+ * registered that wants it.
60
+ *
61
+ * The line is what pino is about to write — after path redaction and the
62
+ * URL-credential redaction — so the exported copy is exactly as redacted as
63
+ * stdout. It runs synchronously inside the log call, so the active context —
64
+ * and with it the trace and span ids — is the caller's.
65
+ *
66
+ * Without a registered provider the logger is a no-op whose `enabled()` is
67
+ * false, and the line is not parsed.
68
+ */
69
+ function emitToOtel(line) {
70
+ const logger = otelLogger();
71
+ if (!logger.enabled()) return;
72
+ let record;
73
+ try {
74
+ record = JSON.parse(line);
75
+ } catch {
76
+ return;
77
+ }
78
+ const { level, time, msg, ...attributes } = record;
79
+ const severityNumber = typeof level === "number" ? severityFromNumber(level) : SEVERITY[String(level).toUpperCase()] ?? SeverityNumber.UNSPECIFIED;
80
+ if (!logger.enabled({ severityNumber })) return;
81
+ logger.emit({
82
+ severityNumber,
83
+ severityText: SEVERITY_TEXT[severityNumber] ?? (level === void 0 ? void 0 : String(level)),
84
+ ...msg !== void 0 && { body: String(msg) },
85
+ ...typeof time === "number" && { timestamp: time },
86
+ attributes
87
+ });
88
+ }
89
+ /**
90
+ * The bridge as a pino `streamWrite` hook, for a logger made with `pino()`
91
+ * directly rather than `createLogger` — which installs it already.
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * import pino from 'pino';
96
+ * import { otelStreamWrite } from '@geekmidas/logger/otel';
97
+ *
98
+ * const logger = pino({ hooks: { streamWrite: otelStreamWrite } });
99
+ * ```
100
+ */
101
+ function otelStreamWrite(line) {
102
+ try {
103
+ emitToOtel(line);
104
+ } catch {}
105
+ return line;
106
+ }
107
+ //#endregion
108
+ export { OTEL_SCOPE, emitToOtel, otelStreamWrite };
109
+
110
+ //# sourceMappingURL=otel.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.mjs","names":[],"sources":["../src/otel.ts"],"sourcesContent":["/**\n * The bridge from pino to OpenTelemetry logs.\n *\n * A pino record is also emitted through `@opentelemetry/api-logs` when a\n * `LoggerProvider` is registered globally — as the generated telemetry setup\n * of a production server does. Nothing hooks module loading, so it works the\n * same in a single bundled file as from `node_modules`.\n *\n * `@opentelemetry/api-logs` keeps its provider on `globalThis` under a\n * `Symbol.for` key, so the copy imported here and the copy the SDK registered\n * through share one provider even when a bundle holds both. That is why the\n * provider is looked up per record rather than once: a logger obtained before\n * the SDK registered — or through another copy's proxy — would stay a no-op.\n *\n * @module\n */\nimport {\n\ttype AnyValueMap,\n\ttype Logger,\n\ttype LoggerProvider,\n\tlogs,\n\tSeverityNumber,\n} from '@opentelemetry/api-logs';\n\n/** The instrumentation scope every bridged record carries. */\nexport const OTEL_SCOPE = '@geekmidas/logger';\n\n/** Pino's level labels, upper-cased as the logger prints them. */\nconst SEVERITY: Record<string, SeverityNumber> = {\n\tTRACE: SeverityNumber.TRACE,\n\tDEBUG: SeverityNumber.DEBUG,\n\tINFO: SeverityNumber.INFO,\n\tWARN: SeverityNumber.WARN,\n\tERROR: SeverityNumber.ERROR,\n\tFATAL: SeverityNumber.FATAL,\n};\n\n/** Pino's numeric levels, for a record whose level was not formatted. */\nfunction severityFromNumber(level: number): SeverityNumber {\n\tif (level >= 60) return SeverityNumber.FATAL;\n\tif (level >= 50) return SeverityNumber.ERROR;\n\tif (level >= 40) return SeverityNumber.WARN;\n\tif (level >= 30) return SeverityNumber.INFO;\n\tif (level >= 20) return SeverityNumber.DEBUG;\n\treturn SeverityNumber.TRACE;\n}\n\nconst SEVERITY_TEXT: Partial<Record<SeverityNumber, string>> = {\n\t[SeverityNumber.TRACE]: 'TRACE',\n\t[SeverityNumber.DEBUG]: 'DEBUG',\n\t[SeverityNumber.INFO]: 'INFO',\n\t[SeverityNumber.WARN]: 'WARN',\n\t[SeverityNumber.ERROR]: 'ERROR',\n\t[SeverityNumber.FATAL]: 'FATAL',\n};\n\nlet cached: { provider: LoggerProvider; logger: Logger } | undefined;\n\n/** The bridge's logger from whichever provider is registered now. */\nfunction otelLogger(): Logger {\n\tconst provider = logs.getLoggerProvider();\n\tif (cached?.provider !== provider) {\n\t\tcached = { provider, logger: provider.getLogger(OTEL_SCOPE) };\n\t}\n\treturn cached.logger;\n}\n\n/**\n * Emit one serialized pino line through OpenTelemetry, when a provider is\n * registered that wants it.\n *\n * The line is what pino is about to write — after path redaction and the\n * URL-credential redaction — so the exported copy is exactly as redacted as\n * stdout. It runs synchronously inside the log call, so the active context —\n * and with it the trace and span ids — is the caller's.\n *\n * Without a registered provider the logger is a no-op whose `enabled()` is\n * false, and the line is not parsed.\n */\nexport function emitToOtel(line: string): void {\n\tconst logger = otelLogger();\n\tif (!logger.enabled()) return;\n\n\tlet record: Record<string, unknown>;\n\ttry {\n\t\trecord = JSON.parse(line);\n\t} catch {\n\t\treturn;\n\t}\n\n\tconst { level, time, msg, ...attributes } = record;\n\tconst severityNumber =\n\t\ttypeof level === 'number'\n\t\t\t? severityFromNumber(level)\n\t\t\t: (SEVERITY[String(level).toUpperCase()] ?? SeverityNumber.UNSPECIFIED);\n\n\tif (!logger.enabled({ severityNumber })) return;\n\n\tlogger.emit({\n\t\tseverityNumber,\n\t\tseverityText:\n\t\t\tSEVERITY_TEXT[severityNumber] ??\n\t\t\t(level === undefined ? undefined : String(level)),\n\t\t...(msg !== undefined && { body: String(msg) }),\n\t\t...(typeof time === 'number' && { timestamp: time }),\n\t\tattributes: attributes as AnyValueMap,\n\t});\n}\n\n/**\n * The bridge as a pino `streamWrite` hook, for a logger made with `pino()`\n * directly rather than `createLogger` — which installs it already.\n *\n * @example\n * ```typescript\n * import pino from 'pino';\n * import { otelStreamWrite } from '@geekmidas/logger/otel';\n *\n * const logger = pino({ hooks: { streamWrite: otelStreamWrite } });\n * ```\n */\nexport function otelStreamWrite(line: string): string {\n\ttry {\n\t\temitToOtel(line);\n\t} catch {\n\t\t// Exporting a log is never a reason to lose it from stdout.\n\t}\n\treturn line;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAyBA,MAAa,aAAa;;AAG1B,MAAM,WAA2C;CAChD,OAAO,eAAe;CACtB,OAAO,eAAe;CACtB,MAAM,eAAe;CACrB,MAAM,eAAe;CACrB,OAAO,eAAe;CACtB,OAAO,eAAe;AACvB;;AAGA,SAAS,mBAAmB,OAA+B;CAC1D,IAAI,SAAS,IAAI,OAAO,eAAe;CACvC,IAAI,SAAS,IAAI,OAAO,eAAe;CACvC,IAAI,SAAS,IAAI,OAAO,eAAe;CACvC,IAAI,SAAS,IAAI,OAAO,eAAe;CACvC,IAAI,SAAS,IAAI,OAAO,eAAe;CACvC,OAAO,eAAe;AACvB;AAEA,MAAM,gBAAyD;EAC7D,eAAe,QAAQ;EACvB,eAAe,QAAQ;EACvB,eAAe,OAAO;EACtB,eAAe,OAAO;EACtB,eAAe,QAAQ;EACvB,eAAe,QAAQ;AACzB;AAEA,IAAI;;AAGJ,SAAS,aAAqB;CAC7B,MAAM,WAAW,KAAK,kBAAkB;CACxC,IAAI,QAAQ,aAAa,UACxB,SAAS;EAAE;EAAU,QAAQ,SAAS,UAAU,UAAU;CAAE;CAE7D,OAAO,OAAO;AACf;;;;;;;;;;;;;AAcA,SAAgB,WAAW,MAAoB;CAC9C,MAAM,SAAS,WAAW;CAC1B,IAAI,CAAC,OAAO,QAAQ,GAAG;CAEvB,IAAI;CACJ,IAAI;EACH,SAAS,KAAK,MAAM,IAAI;CACzB,QAAQ;EACP;CACD;CAEA,MAAM,EAAE,OAAO,MAAM,KAAK,GAAG,eAAe;CAC5C,MAAM,iBACL,OAAO,UAAU,WACd,mBAAmB,KAAK,IACvB,SAAS,OAAO,KAAK,CAAC,CAAC,YAAY,MAAM,eAAe;CAE7D,IAAI,CAAC,OAAO,QAAQ,EAAE,eAAe,CAAC,GAAG;CAEzC,OAAO,KAAK;EACX;EACA,cACC,cAAc,oBACb,UAAU,KAAA,IAAY,KAAA,IAAY,OAAO,KAAK;EAChD,GAAI,QAAQ,KAAA,KAAa,EAAE,MAAM,OAAO,GAAG,EAAE;EAC7C,GAAI,OAAO,SAAS,YAAY,EAAE,WAAW,KAAK;EACtC;CACb,CAAC;AACF;;;;;;;;;;;;;AAcA,SAAgB,gBAAgB,MAAsB;CACrD,IAAI;EACH,WAAW,IAAI;CAChB,QAAQ,CAER;CACA,OAAO;AACR"}
package/dist/pino.cjs CHANGED
@@ -1,44 +1,54 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
-
23
- //#endregion
24
- const require_redact_paths = require('./redact-paths-D0m0DIuQ.cjs');
25
- const pino = __toESM(require("pino"));
26
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_otel = require("./otel.cjs");
3
+ const require_redact_paths = require("./redact-paths.cjs");
4
+ let pino = require("pino");
27
5
  //#region src/pino.ts
28
6
  /**
7
+ * Pino logger with built-in redaction support for sensitive data.
8
+ *
9
+ * Redaction is on by default: a logger that was never told about passwords
10
+ * and tokens still masks them. `redact: false` is the explicit opt-out.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';
15
+ *
16
+ * // Redaction with sensible defaults — the same as `redact: true`
17
+ * const logger = createLogger();
18
+ *
19
+ * // Sensitive data is automatically masked
20
+ * logger.info({ password: 'secret123', user: 'john' }, 'Login');
21
+ * // Output: { password: '[Redacted]', user: 'john' } Login
22
+ *
23
+ * // Add custom paths (merged with defaults)
24
+ * const logger2 = createLogger({ redact: ['user.ssn'] });
25
+ *
26
+ * // Override defaults for full control
27
+ * const logger3 = createLogger({
28
+ * redact: {
29
+ * paths: ['onlyThis'],
30
+ * resolution: 'override',
31
+ * }
32
+ * });
33
+ * ```
34
+ *
35
+ * @module
36
+ */
37
+ /**
29
38
  * Resolves redaction configuration from options.
30
- * Returns undefined if redaction is disabled, or a pino-compatible redact config.
39
+ * Returns undefined only when redaction is turned off with `false`; leaving it
40
+ * unset is the same as `true`, so sensitive paths are masked unless someone
41
+ * decides they should not be.
31
42
  *
32
43
  * By default (resolution: 'merge'), custom paths are merged with DEFAULT_REDACT_PATHS.
33
44
  * With resolution: 'override', only the custom paths are used.
34
45
  */
35
46
  function resolveRedactConfig(redact) {
36
- if (redact === void 0 || redact === false) return void 0;
37
- if (redact === true) return require_redact_paths.DEFAULT_REDACT_PATHS;
47
+ if (redact === false) return;
48
+ if (redact === void 0 || redact === true) return require_redact_paths.DEFAULT_REDACT_PATHS;
38
49
  if (Array.isArray(redact)) return [...require_redact_paths.DEFAULT_REDACT_PATHS, ...redact];
39
50
  const { resolution = "merge", paths, censor, remove } = redact;
40
- const resolvedPaths = resolution === "override" ? paths : [...require_redact_paths.DEFAULT_REDACT_PATHS, ...paths];
41
- const config = { paths: resolvedPaths };
51
+ const config = { paths: resolution === "override" ? paths : [...require_redact_paths.DEFAULT_REDACT_PATHS, ...paths] };
42
52
  if (censor !== void 0) config.censor = censor;
43
53
  if (remove !== void 0) config.remove = remove;
44
54
  return config;
@@ -51,28 +61,45 @@ function resolveRedactConfig(redact) {
51
61
  *
52
62
  * @example
53
63
  * ```typescript
54
- * // Basic logger
64
+ * // Basic logger — sensitive paths are redacted by default
55
65
  * const logger = createLogger({ level: 'debug' });
56
66
  *
57
- * // With redaction enabled
58
- * const secureLogger = createLogger({ redact: true });
67
+ * // Opt out of redaction
68
+ * const rawLogger = createLogger({ redact: false });
59
69
  *
60
70
  * // Pretty printing in development
61
- * const devLogger = createLogger({ pretty: true, redact: true });
71
+ * const devLogger = createLogger({ pretty: true });
62
72
  * ```
63
73
  */
64
74
  function createLogger(options = {}) {
65
- const pretty = options?.pretty && process.NODE_ENV !== "production";
75
+ const pretty = options?.pretty && process.env.NODE_ENV !== "production";
66
76
  const baseOptions = pretty ? { transport: {
67
77
  target: "pino-pretty",
68
78
  options: { colorize: true }
69
79
  } } : {};
70
80
  const redact = resolveRedactConfig(options.redact);
71
- return (0, pino.pino)({
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
+ };
86
+ const pinoOptions = {
72
87
  ...baseOptions,
73
88
  ...options.level && { level: options.level },
74
89
  ...redact && { redact },
90
+ serializers: {
91
+ err: serializeError,
92
+ error: serializeError
93
+ },
94
+ hooks: {
95
+ ...redact && { logMethod(args, method) {
96
+ const redacted = args.map((arg) => typeof arg === "string" ? require_redact_paths.redactUrlCredentials(arg) : arg);
97
+ return method.apply(this, redacted);
98
+ } },
99
+ streamWrite: require_otel.otelStreamWrite
100
+ },
75
101
  formatters: {
102
+ ...redact && { log: (object) => require_redact_paths.redactUrlCredentialsIn(object) },
76
103
  bindings() {
77
104
  return { nodeVersion: process.version };
78
105
  },
@@ -80,10 +107,11 @@ function createLogger(options = {}) {
80
107
  return { level: label.toUpperCase() };
81
108
  }
82
109
  }
83
- });
110
+ };
111
+ return options.destination && !pretty ? (0, pino.pino)(pinoOptions, options.destination) : (0, pino.pino)(pinoOptions);
84
112
  }
85
-
86
113
  //#endregion
87
114
  exports.DEFAULT_REDACT_PATHS = require_redact_paths.DEFAULT_REDACT_PATHS;
88
115
  exports.createLogger = createLogger;
116
+
89
117
  //# sourceMappingURL=pino.cjs.map
package/dist/pino.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pino.cjs","names":["redact: boolean | RedactOptions | undefined","DEFAULT_REDACT_PATHS","config: PinoRedactConfig","options: CreateLoggerOptions"],"sources":["../src/pino.ts"],"sourcesContent":["/**\n * Pino logger with built-in redaction support for sensitive data.\n *\n * @example\n * ```typescript\n * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';\n *\n * // Enable redaction with sensible defaults\n * const logger = createLogger({ redact: true });\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 { pino } from 'pino';\nimport { DEFAULT_REDACT_PATHS } 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 if redaction is disabled, or a pino-compatible redact config.\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 === undefined || redact === false) {\n\t\treturn undefined;\n\t}\n\n\tif (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\n * const logger = createLogger({ level: 'debug' });\n *\n * // With redaction enabled\n * const secureLogger = createLogger({ redact: true });\n *\n * // Pretty printing in development\n * const devLogger = createLogger({ pretty: true, redact: true });\n * ```\n */\nexport function createLogger(options: CreateLoggerOptions = {}) {\n\t// @ts-expect-error\n\tconst pretty = options?.pretty && process.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\treturn pino({\n\t\t...baseOptions,\n\t\t...(options.level && { level: options.level }),\n\t\t...(redact && { redact }),\n\t\tformatters: {\n\t\t\tbindings() {\n\t\t\t\treturn { nodeVersion: process.version };\n\t\t\t},\n\t\t\tlevel: (label) => {\n\t\t\t\treturn { level: label.toUpperCase() };\n\t\t\t},\n\t\t},\n\t});\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAS,oBACRA,QAC+B;AAC/B,KAAI,qBAAwB,WAAW,MACtC;AAGD,KAAI,WAAW,KACd,QAAOC;AAIR,KAAI,MAAM,QAAQ,OAAO,CACxB,QAAO,CAAC,GAAGA,2CAAsB,GAAG,MAAO;CAI5C,MAAM,EAAE,aAAa,SAAS,OAAO,QAAQ,QAAQ,GAAG;CAExD,MAAM,gBACL,eAAe,aAAa,QAAQ,CAAC,GAAGA,2CAAsB,GAAG,KAAM;CAGxE,MAAMC,SAA2B,EAAE,OAAO,cAAe;AACzD,KAAI,kBAAsB,QAAO,SAAS;AAC1C,KAAI,kBAAsB,QAAO,SAAS;AAE1C,QAAO;AACP;;;;;;;;;;;;;;;;;;;AAoBD,SAAgB,aAAaC,UAA+B,CAAE,GAAE;CAE/D,MAAM,SAAS,SAAS,UAAU,QAAQ,aAAa;CACvD,MAAM,cAAc,SACjB,EACA,WAAW;EACV,QAAQ;EACR,SAAS,EAAE,UAAU,KAAM;CAC3B,EACD,IACA,CAAE;CAEL,MAAM,SAAS,oBAAoB,QAAQ,OAAO;AAElD,QAAO,eAAK;EACX,GAAG;EACH,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAO;EAC7C,GAAI,UAAU,EAAE,OAAQ;EACxB,YAAY;GACX,WAAW;AACV,WAAO,EAAE,aAAa,QAAQ,QAAS;GACvC;GACD,OAAO,CAAC,UAAU;AACjB,WAAO,EAAE,OAAO,MAAM,aAAa,CAAE;GACrC;EACD;CACD,EAAC;AACF"}
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.d.cts CHANGED
@@ -1,9 +1,7 @@
1
- import { CreateLoggerOptions } from "./types-Dk_k2-f_.cjs";
2
- import { DEFAULT_REDACT_PATHS } from "./redact-paths-CsK0_uz-.cjs";
3
- import * as pino0 from "pino";
4
-
1
+ import { CreateLoggerOptions } from "./types.cjs";
2
+ import { DEFAULT_REDACT_PATHS } from "./redact-paths.cjs";
3
+ import { Logger } from "pino";
5
4
  //#region src/pino.d.ts
6
-
7
5
  /**
8
6
  * Creates a pino logger instance with optional redaction support.
9
7
  *
@@ -12,18 +10,17 @@ import * as pino0 from "pino";
12
10
  *
13
11
  * @example
14
12
  * ```typescript
15
- * // Basic logger
13
+ * // Basic logger — sensitive paths are redacted by default
16
14
  * const logger = createLogger({ level: 'debug' });
17
15
  *
18
- * // With redaction enabled
19
- * const secureLogger = createLogger({ redact: true });
16
+ * // Opt out of redaction
17
+ * const rawLogger = createLogger({ redact: false });
20
18
  *
21
19
  * // Pretty printing in development
22
- * const devLogger = createLogger({ pretty: true, redact: true });
20
+ * const devLogger = createLogger({ pretty: true });
23
21
  * ```
24
22
  */
25
- declare function createLogger(options?: CreateLoggerOptions): pino0.Logger<never, boolean>;
26
- //# sourceMappingURL=pino.d.ts.map
23
+ export declare function createLogger(options?: CreateLoggerOptions): Logger<never, boolean>;
27
24
  //#endregion
28
- export { DEFAULT_REDACT_PATHS, createLogger };
25
+ export { DEFAULT_REDACT_PATHS };
29
26
  //# sourceMappingURL=pino.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pino.d.cts","names":[],"sources":["../src/pino.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAqGA;;;;AAA8D;;;;;;;;;;;iBAA9C,YAAA,WAAsB,sBAAwB,KAAA,CAAA"}
1
+ {"version":3,"file":"pino.d.cts","names":[],"sources":["../src/pino.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;wBA+GgB,aAAa,UAAS,sBAAwB"}
package/dist/pino.d.mts CHANGED
@@ -1,9 +1,7 @@
1
- import { CreateLoggerOptions } from "./types-BDdpcrpy.mjs";
2
- import { DEFAULT_REDACT_PATHS } from "./redact-paths-D07eTMlH.mjs";
3
- import * as pino0 from "pino";
4
-
1
+ import { CreateLoggerOptions } from "./types.mjs";
2
+ import { DEFAULT_REDACT_PATHS } from "./redact-paths.mjs";
3
+ import { Logger } from "pino";
5
4
  //#region src/pino.d.ts
6
-
7
5
  /**
8
6
  * Creates a pino logger instance with optional redaction support.
9
7
  *
@@ -12,18 +10,17 @@ import * as pino0 from "pino";
12
10
  *
13
11
  * @example
14
12
  * ```typescript
15
- * // Basic logger
13
+ * // Basic logger — sensitive paths are redacted by default
16
14
  * const logger = createLogger({ level: 'debug' });
17
15
  *
18
- * // With redaction enabled
19
- * const secureLogger = createLogger({ redact: true });
16
+ * // Opt out of redaction
17
+ * const rawLogger = createLogger({ redact: false });
20
18
  *
21
19
  * // Pretty printing in development
22
- * const devLogger = createLogger({ pretty: true, redact: true });
20
+ * const devLogger = createLogger({ pretty: true });
23
21
  * ```
24
22
  */
25
- declare function createLogger(options?: CreateLoggerOptions): pino0.Logger<never, boolean>;
26
- //# sourceMappingURL=pino.d.ts.map
23
+ export declare function createLogger(options?: CreateLoggerOptions): Logger<never, boolean>;
27
24
  //#endregion
28
- export { DEFAULT_REDACT_PATHS, createLogger };
25
+ export { DEFAULT_REDACT_PATHS };
29
26
  //# sourceMappingURL=pino.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pino.d.mts","names":[],"sources":["../src/pino.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAqGA;;;;AAA8D;;;;;;;;;;;iBAA9C,YAAA,WAAsB,sBAAwB,KAAA,CAAA"}
1
+ {"version":3,"file":"pino.d.mts","names":[],"sources":["../src/pino.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;wBA+GgB,aAAa,UAAS,sBAAwB"}
package/dist/pino.mjs CHANGED
@@ -1,21 +1,53 @@
1
- import { DEFAULT_REDACT_PATHS } from "./redact-paths-DQoIXhkS.mjs";
2
- import { pino } from "pino";
3
-
1
+ import { otelStreamWrite } from "./otel.mjs";
2
+ import { DEFAULT_REDACT_PATHS, redactUrlCredentials, redactUrlCredentialsIn } from "./redact-paths.mjs";
3
+ import { pino, stdSerializers } from "pino";
4
4
  //#region src/pino.ts
5
5
  /**
6
+ * Pino logger with built-in redaction support for sensitive data.
7
+ *
8
+ * Redaction is on by default: a logger that was never told about passwords
9
+ * and tokens still masks them. `redact: false` is the explicit opt-out.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';
14
+ *
15
+ * // Redaction with sensible defaults — the same as `redact: true`
16
+ * const logger = createLogger();
17
+ *
18
+ * // Sensitive data is automatically masked
19
+ * logger.info({ password: 'secret123', user: 'john' }, 'Login');
20
+ * // Output: { password: '[Redacted]', user: 'john' } Login
21
+ *
22
+ * // Add custom paths (merged with defaults)
23
+ * const logger2 = createLogger({ redact: ['user.ssn'] });
24
+ *
25
+ * // Override defaults for full control
26
+ * const logger3 = createLogger({
27
+ * redact: {
28
+ * paths: ['onlyThis'],
29
+ * resolution: 'override',
30
+ * }
31
+ * });
32
+ * ```
33
+ *
34
+ * @module
35
+ */
36
+ /**
6
37
  * Resolves redaction configuration from options.
7
- * Returns undefined if redaction is disabled, or a pino-compatible redact config.
38
+ * Returns undefined only when redaction is turned off with `false`; leaving it
39
+ * unset is the same as `true`, so sensitive paths are masked unless someone
40
+ * decides they should not be.
8
41
  *
9
42
  * By default (resolution: 'merge'), custom paths are merged with DEFAULT_REDACT_PATHS.
10
43
  * With resolution: 'override', only the custom paths are used.
11
44
  */
12
45
  function resolveRedactConfig(redact) {
13
- if (redact === void 0 || redact === false) return void 0;
14
- if (redact === true) return DEFAULT_REDACT_PATHS;
46
+ if (redact === false) return;
47
+ if (redact === void 0 || redact === true) return DEFAULT_REDACT_PATHS;
15
48
  if (Array.isArray(redact)) return [...DEFAULT_REDACT_PATHS, ...redact];
16
49
  const { resolution = "merge", paths, censor, remove } = redact;
17
- const resolvedPaths = resolution === "override" ? paths : [...DEFAULT_REDACT_PATHS, ...paths];
18
- const config = { paths: resolvedPaths };
50
+ const config = { paths: resolution === "override" ? paths : [...DEFAULT_REDACT_PATHS, ...paths] };
19
51
  if (censor !== void 0) config.censor = censor;
20
52
  if (remove !== void 0) config.remove = remove;
21
53
  return config;
@@ -28,28 +60,45 @@ function resolveRedactConfig(redact) {
28
60
  *
29
61
  * @example
30
62
  * ```typescript
31
- * // Basic logger
63
+ * // Basic logger — sensitive paths are redacted by default
32
64
  * const logger = createLogger({ level: 'debug' });
33
65
  *
34
- * // With redaction enabled
35
- * const secureLogger = createLogger({ redact: true });
66
+ * // Opt out of redaction
67
+ * const rawLogger = createLogger({ redact: false });
36
68
  *
37
69
  * // Pretty printing in development
38
- * const devLogger = createLogger({ pretty: true, redact: true });
70
+ * const devLogger = createLogger({ pretty: true });
39
71
  * ```
40
72
  */
41
73
  function createLogger(options = {}) {
42
- const pretty = options?.pretty && process.NODE_ENV !== "production";
74
+ const pretty = options?.pretty && process.env.NODE_ENV !== "production";
43
75
  const baseOptions = pretty ? { transport: {
44
76
  target: "pino-pretty",
45
77
  options: { colorize: true }
46
78
  } } : {};
47
79
  const redact = resolveRedactConfig(options.redact);
48
- return pino({
80
+ const serializeError = (value) => {
81
+ const serialized = stdSerializers.err(value);
82
+ if (!redact || serialized === value) return serialized;
83
+ return redactUrlCredentialsIn({ ...serialized });
84
+ };
85
+ const pinoOptions = {
49
86
  ...baseOptions,
50
87
  ...options.level && { level: options.level },
51
88
  ...redact && { redact },
89
+ serializers: {
90
+ err: serializeError,
91
+ error: serializeError
92
+ },
93
+ hooks: {
94
+ ...redact && { logMethod(args, method) {
95
+ const redacted = args.map((arg) => typeof arg === "string" ? redactUrlCredentials(arg) : arg);
96
+ return method.apply(this, redacted);
97
+ } },
98
+ streamWrite: otelStreamWrite
99
+ },
52
100
  formatters: {
101
+ ...redact && { log: (object) => redactUrlCredentialsIn(object) },
53
102
  bindings() {
54
103
  return { nodeVersion: process.version };
55
104
  },
@@ -57,9 +106,10 @@ function createLogger(options = {}) {
57
106
  return { level: label.toUpperCase() };
58
107
  }
59
108
  }
60
- });
109
+ };
110
+ return options.destination && !pretty ? pino(pinoOptions, options.destination) : pino(pinoOptions);
61
111
  }
62
-
63
112
  //#endregion
64
113
  export { DEFAULT_REDACT_PATHS, createLogger };
114
+
65
115
  //# sourceMappingURL=pino.mjs.map
package/dist/pino.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pino.mjs","names":["redact: boolean | RedactOptions | undefined","config: PinoRedactConfig","options: CreateLoggerOptions"],"sources":["../src/pino.ts"],"sourcesContent":["/**\n * Pino logger with built-in redaction support for sensitive data.\n *\n * @example\n * ```typescript\n * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';\n *\n * // Enable redaction with sensible defaults\n * const logger = createLogger({ redact: true });\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 { pino } from 'pino';\nimport { DEFAULT_REDACT_PATHS } 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 if redaction is disabled, or a pino-compatible redact config.\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 === undefined || redact === false) {\n\t\treturn undefined;\n\t}\n\n\tif (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\n * const logger = createLogger({ level: 'debug' });\n *\n * // With redaction enabled\n * const secureLogger = createLogger({ redact: true });\n *\n * // Pretty printing in development\n * const devLogger = createLogger({ pretty: true, redact: true });\n * ```\n */\nexport function createLogger(options: CreateLoggerOptions = {}) {\n\t// @ts-expect-error\n\tconst pretty = options?.pretty && process.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\treturn pino({\n\t\t...baseOptions,\n\t\t...(options.level && { level: options.level }),\n\t\t...(redact && { redact }),\n\t\tformatters: {\n\t\t\tbindings() {\n\t\t\t\treturn { nodeVersion: process.version };\n\t\t\t},\n\t\t\tlevel: (label) => {\n\t\t\t\treturn { level: label.toUpperCase() };\n\t\t\t},\n\t\t},\n\t});\n}\n"],"mappings":";;;;;;;;;;;AAqDA,SAAS,oBACRA,QAC+B;AAC/B,KAAI,qBAAwB,WAAW,MACtC;AAGD,KAAI,WAAW,KACd,QAAO;AAIR,KAAI,MAAM,QAAQ,OAAO,CACxB,QAAO,CAAC,GAAG,sBAAsB,GAAG,MAAO;CAI5C,MAAM,EAAE,aAAa,SAAS,OAAO,QAAQ,QAAQ,GAAG;CAExD,MAAM,gBACL,eAAe,aAAa,QAAQ,CAAC,GAAG,sBAAsB,GAAG,KAAM;CAGxE,MAAMC,SAA2B,EAAE,OAAO,cAAe;AACzD,KAAI,kBAAsB,QAAO,SAAS;AAC1C,KAAI,kBAAsB,QAAO,SAAS;AAE1C,QAAO;AACP;;;;;;;;;;;;;;;;;;;AAoBD,SAAgB,aAAaC,UAA+B,CAAE,GAAE;CAE/D,MAAM,SAAS,SAAS,UAAU,QAAQ,aAAa;CACvD,MAAM,cAAc,SACjB,EACA,WAAW;EACV,QAAQ;EACR,SAAS,EAAE,UAAU,KAAM;CAC3B,EACD,IACA,CAAE;CAEL,MAAM,SAAS,oBAAoB,QAAQ,OAAO;AAElD,QAAO,KAAK;EACX,GAAG;EACH,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAO;EAC7C,GAAI,UAAU,EAAE,OAAQ;EACxB,YAAY;GACX,WAAW;AACV,WAAO,EAAE,aAAa,QAAQ,QAAS;GACvC;GACD,OAAO,CAAC,UAAU;AACjB,WAAO,EAAE,OAAO,MAAM,aAAa,CAAE;GACrC;EACD;CACD,EAAC;AACF"}
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"}