@geekmidas/logger 10.0.0-alpha.14 → 10.0.0-alpha.141

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.
@@ -1 +1 @@
1
- {"version":3,"file":"console.d.cts","names":[],"sources":["../src/console.ts"],"mappings":";;qBAgBa,yBAAyB;WAU3B;mBATO;;;;;;;EASP,YAAA,eACT,QAAO;;;;UAQA;;;;;;;;;UAeA;;EA+BR,OAAO;;EAEP,OAAO;;EAEP,MAAM;;EAEN,MAAM;;EAEN,OAAO;;EAEP,OAAO;;;;;;;;;;;;;;;;EAiBP,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;qBAoCR;;;;;;;;;;;;;;;;;wBAkBG,aAAa,UAAS,sBAA2B"}
1
+ {"version":3,"file":"console.d.cts","names":[],"sources":["../src/console.ts"],"mappings":";;qBAgBa,yBAAyB;WAU3B;mBATO;;;;;;;EAQjB,YACU,eACT,QAAO;;;;UAQA;;;;;;;;;UAeA;;EA+BR,OAAO;;EAEP,OAAO;;EAEP,MAAM;;EAEN,MAAM;;EAEN,OAAO;;EAEP,OAAO;;;;;;;;;;;;;;;;EAiBP,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;qBAoCR;;;;;;;;;;;;;;;;;wBAkBG,aAAa,UAAS,sBAA2B"}
@@ -1 +1 @@
1
- {"version":3,"file":"console.d.mts","names":[],"sources":["../src/console.ts"],"mappings":";;qBAgBa,yBAAyB;WAU3B;mBATO;;;;;;;EASP,YAAA,eACT,QAAO;;;;UAQA;;;;;;;;;UAeA;;EA+BR,OAAO;;EAEP,OAAO;;EAEP,MAAM;;EAEN,MAAM;;EAEN,OAAO;;EAEP,OAAO;;;;;;;;;;;;;;;;EAiBP,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;qBAoCR;;;;;;;;;;;;;;;;;wBAkBG,aAAa,UAAS,sBAA2B"}
1
+ {"version":3,"file":"console.d.mts","names":[],"sources":["../src/console.ts"],"mappings":";;qBAgBa,yBAAyB;WAU3B;mBATO;;;;;;;EAQjB,YACU,eACT,QAAO;;;;UAQA;;;;;;;;;UAeA;;EA+BR,OAAO;;EAEP,OAAO;;EAEP,MAAM;;EAEN,MAAM;;EAEN,OAAO;;EAEP,OAAO;;;;;;;;;;;;;;;;EAiBP,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;qBAoCR;;;;;;;;;;;;;;;;;wBAkBG,aAAa,UAAS,sBAA2B"}
package/dist/otel.cjs ADDED
@@ -0,0 +1,113 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _opentelemetry_api_logs = require("@opentelemetry/api-logs");
3
+ //#region src/otel.ts
4
+ /**
5
+ * The bridge from pino to OpenTelemetry logs.
6
+ *
7
+ * A pino record is also emitted through `@opentelemetry/api-logs` when a
8
+ * `LoggerProvider` is registered globally — as the generated telemetry setup
9
+ * of a production server does. Nothing hooks module loading, so it works the
10
+ * same in a single bundled file as from `node_modules`.
11
+ *
12
+ * `@opentelemetry/api-logs` keeps its provider on `globalThis` under a
13
+ * `Symbol.for` key, so the copy imported here and the copy the SDK registered
14
+ * through share one provider even when a bundle holds both. That is why the
15
+ * provider is looked up per record rather than once: a logger obtained before
16
+ * the SDK registered — or through another copy's proxy — would stay a no-op.
17
+ *
18
+ * @module
19
+ */
20
+ /** The instrumentation scope every bridged record carries. */
21
+ const OTEL_SCOPE = "@geekmidas/logger";
22
+ /** Pino's level labels, upper-cased as the logger prints them. */
23
+ const SEVERITY = {
24
+ TRACE: _opentelemetry_api_logs.SeverityNumber.TRACE,
25
+ DEBUG: _opentelemetry_api_logs.SeverityNumber.DEBUG,
26
+ INFO: _opentelemetry_api_logs.SeverityNumber.INFO,
27
+ WARN: _opentelemetry_api_logs.SeverityNumber.WARN,
28
+ ERROR: _opentelemetry_api_logs.SeverityNumber.ERROR,
29
+ FATAL: _opentelemetry_api_logs.SeverityNumber.FATAL
30
+ };
31
+ /** Pino's numeric levels, for a record whose level was not formatted. */
32
+ function severityFromNumber(level) {
33
+ if (level >= 60) return _opentelemetry_api_logs.SeverityNumber.FATAL;
34
+ if (level >= 50) return _opentelemetry_api_logs.SeverityNumber.ERROR;
35
+ if (level >= 40) return _opentelemetry_api_logs.SeverityNumber.WARN;
36
+ if (level >= 30) return _opentelemetry_api_logs.SeverityNumber.INFO;
37
+ if (level >= 20) return _opentelemetry_api_logs.SeverityNumber.DEBUG;
38
+ return _opentelemetry_api_logs.SeverityNumber.TRACE;
39
+ }
40
+ const SEVERITY_TEXT = {
41
+ [_opentelemetry_api_logs.SeverityNumber.TRACE]: "TRACE",
42
+ [_opentelemetry_api_logs.SeverityNumber.DEBUG]: "DEBUG",
43
+ [_opentelemetry_api_logs.SeverityNumber.INFO]: "INFO",
44
+ [_opentelemetry_api_logs.SeverityNumber.WARN]: "WARN",
45
+ [_opentelemetry_api_logs.SeverityNumber.ERROR]: "ERROR",
46
+ [_opentelemetry_api_logs.SeverityNumber.FATAL]: "FATAL"
47
+ };
48
+ let cached;
49
+ /** The bridge's logger from whichever provider is registered now. */
50
+ function otelLogger() {
51
+ const provider = _opentelemetry_api_logs.logs.getLoggerProvider();
52
+ if (cached?.provider !== provider) cached = {
53
+ provider,
54
+ logger: provider.getLogger(OTEL_SCOPE)
55
+ };
56
+ return cached.logger;
57
+ }
58
+ /**
59
+ * Emit one serialized pino line through OpenTelemetry, when a provider is
60
+ * registered that wants it.
61
+ *
62
+ * The line is what pino is about to write — after path redaction and the
63
+ * URL-credential redaction — so the exported copy is exactly as redacted as
64
+ * stdout. It runs synchronously inside the log call, so the active context —
65
+ * and with it the trace and span ids — is the caller's.
66
+ *
67
+ * Without a registered provider the logger is a no-op whose `enabled()` is
68
+ * false, and the line is not parsed.
69
+ */
70
+ function emitToOtel(line) {
71
+ const logger = otelLogger();
72
+ if (!logger.enabled()) return;
73
+ let record;
74
+ try {
75
+ record = JSON.parse(line);
76
+ } catch {
77
+ return;
78
+ }
79
+ const { level, time, msg, ...attributes } = record;
80
+ const severityNumber = typeof level === "number" ? severityFromNumber(level) : SEVERITY[String(level).toUpperCase()] ?? _opentelemetry_api_logs.SeverityNumber.UNSPECIFIED;
81
+ if (!logger.enabled({ severityNumber })) return;
82
+ logger.emit({
83
+ severityNumber,
84
+ severityText: SEVERITY_TEXT[severityNumber] ?? (level === void 0 ? void 0 : String(level)),
85
+ ...msg !== void 0 && { body: String(msg) },
86
+ ...typeof time === "number" && { timestamp: time },
87
+ attributes
88
+ });
89
+ }
90
+ /**
91
+ * The bridge as a pino `streamWrite` hook, for a logger made with `pino()`
92
+ * directly rather than `createLogger` — which installs it already.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * import pino from 'pino';
97
+ * import { otelStreamWrite } from '@geekmidas/logger/otel';
98
+ *
99
+ * const logger = pino({ hooks: { streamWrite: otelStreamWrite } });
100
+ * ```
101
+ */
102
+ function otelStreamWrite(line) {
103
+ try {
104
+ emitToOtel(line);
105
+ } catch {}
106
+ return line;
107
+ }
108
+ //#endregion
109
+ exports.OTEL_SCOPE = OTEL_SCOPE;
110
+ exports.emitToOtel = emitToOtel;
111
+ exports.otelStreamWrite = otelStreamWrite;
112
+
113
+ //# sourceMappingURL=otel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.cjs","names":["SeverityNumber","logs"],"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,OAAOA,wBAAAA,eAAe;CACtB,OAAOA,wBAAAA,eAAe;CACtB,MAAMA,wBAAAA,eAAe;CACrB,MAAMA,wBAAAA,eAAe;CACrB,OAAOA,wBAAAA,eAAe;CACtB,OAAOA,wBAAAA,eAAe;AACvB;;AAGA,SAAS,mBAAmB,OAA+B;CAC1D,IAAI,SAAS,IAAI,OAAOA,wBAAAA,eAAe;CACvC,IAAI,SAAS,IAAI,OAAOA,wBAAAA,eAAe;CACvC,IAAI,SAAS,IAAI,OAAOA,wBAAAA,eAAe;CACvC,IAAI,SAAS,IAAI,OAAOA,wBAAAA,eAAe;CACvC,IAAI,SAAS,IAAI,OAAOA,wBAAAA,eAAe;CACvC,OAAOA,wBAAAA,eAAe;AACvB;AAEA,MAAM,gBAAyD;EAC7DA,wBAAAA,eAAe,QAAQ;EACvBA,wBAAAA,eAAe,QAAQ;EACvBA,wBAAAA,eAAe,OAAO;EACtBA,wBAAAA,eAAe,OAAO;EACtBA,wBAAAA,eAAe,QAAQ;EACvBA,wBAAAA,eAAe,QAAQ;AACzB;AAEA,IAAI;;AAGJ,SAAS,aAAqB;CAC7B,MAAM,WAAWC,wBAAAA,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,MAAMD,wBAAAA,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"}
@@ -0,0 +1,31 @@
1
+ //#region src/otel.d.ts
2
+ /** The instrumentation scope every bridged record carries. */
3
+ export declare const OTEL_SCOPE = "@geekmidas/logger";
4
+ /**
5
+ * Emit one serialized pino line through OpenTelemetry, when a provider is
6
+ * registered that wants it.
7
+ *
8
+ * The line is what pino is about to write — after path redaction and the
9
+ * URL-credential redaction — so the exported copy is exactly as redacted as
10
+ * stdout. It runs synchronously inside the log call, so the active context —
11
+ * and with it the trace and span ids — is the caller's.
12
+ *
13
+ * Without a registered provider the logger is a no-op whose `enabled()` is
14
+ * false, and the line is not parsed.
15
+ */
16
+ export declare function emitToOtel(line: string): void;
17
+ /**
18
+ * The bridge as a pino `streamWrite` hook, for a logger made with `pino()`
19
+ * directly rather than `createLogger` — which installs it already.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import pino from 'pino';
24
+ * import { otelStreamWrite } from '@geekmidas/logger/otel';
25
+ *
26
+ * const logger = pino({ hooks: { streamWrite: otelStreamWrite } });
27
+ * ```
28
+ */
29
+ export declare function otelStreamWrite(line: string): string;
30
+ //#endregion
31
+ //# sourceMappingURL=otel.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.d.cts","names":[],"sources":["../src/otel.ts"],"mappings":";;qBAyBa;;;;;;;;;;;;;wBAsDG,WAAW;;;;;;;;;;;;;wBA0CX,gBAAgB"}
@@ -0,0 +1,31 @@
1
+ //#region src/otel.d.ts
2
+ /** The instrumentation scope every bridged record carries. */
3
+ export declare const OTEL_SCOPE = "@geekmidas/logger";
4
+ /**
5
+ * Emit one serialized pino line through OpenTelemetry, when a provider is
6
+ * registered that wants it.
7
+ *
8
+ * The line is what pino is about to write — after path redaction and the
9
+ * URL-credential redaction — so the exported copy is exactly as redacted as
10
+ * stdout. It runs synchronously inside the log call, so the active context —
11
+ * and with it the trace and span ids — is the caller's.
12
+ *
13
+ * Without a registered provider the logger is a no-op whose `enabled()` is
14
+ * false, and the line is not parsed.
15
+ */
16
+ export declare function emitToOtel(line: string): void;
17
+ /**
18
+ * The bridge as a pino `streamWrite` hook, for a logger made with `pino()`
19
+ * directly rather than `createLogger` — which installs it already.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import pino from 'pino';
24
+ * import { otelStreamWrite } from '@geekmidas/logger/otel';
25
+ *
26
+ * const logger = pino({ hooks: { streamWrite: otelStreamWrite } });
27
+ * ```
28
+ */
29
+ export declare function otelStreamWrite(line: string): string;
30
+ //#endregion
31
+ //# sourceMappingURL=otel.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.d.mts","names":[],"sources":["../src/otel.ts"],"mappings":";;qBAyBa;;;;;;;;;;;;;wBAsDG,WAAW;;;;;;;;;;;;;wBA0CX,gBAAgB"}
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,16 +1,20 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_otel = require("./otel.cjs");
2
3
  const require_redact_paths = require("./redact-paths.cjs");
3
4
  let pino = require("pino");
4
5
  //#region src/pino.ts
5
6
  /**
6
7
  * Pino logger with built-in redaction support for sensitive data.
7
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
+ *
8
12
  * @example
9
13
  * ```typescript
10
14
  * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';
11
15
  *
12
- * // Enable redaction with sensible defaults
13
- * const logger = createLogger({ redact: true });
16
+ * // Redaction with sensible defaults — the same as `redact: true`
17
+ * const logger = createLogger();
14
18
  *
15
19
  * // Sensitive data is automatically masked
16
20
  * logger.info({ password: 'secret123', user: 'john' }, 'Login');
@@ -32,14 +36,16 @@ let pino = require("pino");
32
36
  */
33
37
  /**
34
38
  * Resolves redaction configuration from options.
35
- * 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.
36
42
  *
37
43
  * By default (resolution: 'merge'), custom paths are merged with DEFAULT_REDACT_PATHS.
38
44
  * With resolution: 'override', only the custom paths are used.
39
45
  */
40
46
  function resolveRedactConfig(redact) {
41
- if (redact === void 0 || redact === false) return;
42
- 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;
43
49
  if (Array.isArray(redact)) return [...require_redact_paths.DEFAULT_REDACT_PATHS, ...redact];
44
50
  const { resolution = "merge", paths, censor, remove } = redact;
45
51
  const config = { paths: resolution === "override" ? paths : [...require_redact_paths.DEFAULT_REDACT_PATHS, ...paths] };
@@ -55,27 +61,45 @@ function resolveRedactConfig(redact) {
55
61
  *
56
62
  * @example
57
63
  * ```typescript
58
- * // Basic logger
64
+ * // Basic logger — sensitive paths are redacted by default
59
65
  * const logger = createLogger({ level: 'debug' });
60
66
  *
61
- * // With redaction enabled
62
- * const secureLogger = createLogger({ redact: true });
67
+ * // Opt out of redaction
68
+ * const rawLogger = createLogger({ redact: false });
63
69
  *
64
70
  * // Pretty printing in development
65
- * const devLogger = createLogger({ pretty: true, redact: true });
71
+ * const devLogger = createLogger({ pretty: true });
66
72
  * ```
67
73
  */
68
74
  function createLogger(options = {}) {
69
- const baseOptions = options?.pretty && process.NODE_ENV !== "production" ? { transport: {
75
+ const pretty = options?.pretty && process.env.NODE_ENV !== "production";
76
+ const baseOptions = pretty ? { transport: {
70
77
  target: "pino-pretty",
71
78
  options: { colorize: true }
72
79
  } } : {};
73
80
  const redact = resolveRedactConfig(options.redact);
74
- 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 = {
75
87
  ...baseOptions,
76
88
  ...options.level && { level: options.level },
77
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
+ },
78
101
  formatters: {
102
+ ...redact && { log: (object) => require_redact_paths.redactUrlCredentialsIn(object) },
79
103
  bindings() {
80
104
  return { nodeVersion: process.version };
81
105
  },
@@ -83,7 +107,8 @@ function createLogger(options = {}) {
83
107
  return { level: label.toUpperCase() };
84
108
  }
85
109
  }
86
- });
110
+ };
111
+ return options.destination && !pretty ? (0, pino.pino)(pinoOptions, options.destination) : (0, pino.pino)(pinoOptions);
87
112
  }
88
113
  //#endregion
89
114
  exports.DEFAULT_REDACT_PATHS = require_redact_paths.DEFAULT_REDACT_PATHS;
package/dist/pino.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pino.cjs","names":["DEFAULT_REDACT_PATHS"],"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,oBACR,QAC+B;CAC/B,IAAI,WAAW,KAAA,KAAa,WAAW,OACtC;CAGD,IAAI,WAAW,MACd,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,cADS,SAAS,UAAU,QAAQ,aAAa,eAEpD,EACA,WAAW;EACV,QAAQ;EACR,SAAS,EAAE,UAAU,KAAK;CAC3B,EACD,IACC,CAAC;CAEJ,MAAM,SAAS,oBAAoB,QAAQ,MAAM;CAEjD,QAAA,GAAO,KAAA,KAAA,CAAK;EACX,GAAG;EACH,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM;EAC5C,GAAI,UAAU,EAAE,OAAO;EACvB,YAAY;GACX,WAAW;IACV,OAAO,EAAE,aAAa,QAAQ,QAAQ;GACvC;GACA,QAAQ,UAAU;IACjB,OAAO,EAAE,OAAO,MAAM,YAAY,EAAE;GACrC;EACD;CACD,CAAC;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,5 +1,6 @@
1
1
  import { CreateLoggerOptions } from "./types.cjs";
2
2
  import { DEFAULT_REDACT_PATHS } from "./redact-paths.cjs";
3
+ import { Logger } from "pino";
3
4
  //#region src/pino.d.ts
4
5
  /**
5
6
  * Creates a pino logger instance with optional redaction support.
@@ -9,17 +10,17 @@ import { DEFAULT_REDACT_PATHS } from "./redact-paths.cjs";
9
10
  *
10
11
  * @example
11
12
  * ```typescript
12
- * // Basic logger
13
+ * // Basic logger — sensitive paths are redacted by default
13
14
  * const logger = createLogger({ level: 'debug' });
14
15
  *
15
- * // With redaction enabled
16
- * const secureLogger = createLogger({ redact: true });
16
+ * // Opt out of redaction
17
+ * const rawLogger = createLogger({ redact: false });
17
18
  *
18
19
  * // Pretty printing in development
19
- * const devLogger = createLogger({ pretty: true, redact: true });
20
+ * const devLogger = createLogger({ pretty: true });
20
21
  * ```
21
22
  */
22
- export declare function createLogger(options?: CreateLoggerOptions): import("pino").Logger<never, boolean>;
23
+ export declare function createLogger(options?: CreateLoggerOptions): Logger<never, boolean>;
23
24
  //#endregion
24
25
  export { DEFAULT_REDACT_PATHS };
25
26
  //# sourceMappingURL=pino.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pino.d.cts","names":[],"sources":["../src/pino.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;wBAqGgB,aAAa,UAAS,qCAAwB"}
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,5 +1,6 @@
1
1
  import { CreateLoggerOptions } from "./types.mjs";
2
2
  import { DEFAULT_REDACT_PATHS } from "./redact-paths.mjs";
3
+ import { Logger } from "pino";
3
4
  //#region src/pino.d.ts
4
5
  /**
5
6
  * Creates a pino logger instance with optional redaction support.
@@ -9,17 +10,17 @@ import { DEFAULT_REDACT_PATHS } from "./redact-paths.mjs";
9
10
  *
10
11
  * @example
11
12
  * ```typescript
12
- * // Basic logger
13
+ * // Basic logger — sensitive paths are redacted by default
13
14
  * const logger = createLogger({ level: 'debug' });
14
15
  *
15
- * // With redaction enabled
16
- * const secureLogger = createLogger({ redact: true });
16
+ * // Opt out of redaction
17
+ * const rawLogger = createLogger({ redact: false });
17
18
  *
18
19
  * // Pretty printing in development
19
- * const devLogger = createLogger({ pretty: true, redact: true });
20
+ * const devLogger = createLogger({ pretty: true });
20
21
  * ```
21
22
  */
22
- export declare function createLogger(options?: CreateLoggerOptions): import("pino").Logger<never, boolean>;
23
+ export declare function createLogger(options?: CreateLoggerOptions): Logger<never, boolean>;
23
24
  //#endregion
24
25
  export { DEFAULT_REDACT_PATHS };
25
26
  //# sourceMappingURL=pino.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pino.d.mts","names":[],"sources":["../src/pino.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;wBAqGgB,aAAa,UAAS,qCAAwB"}
1
+ {"version":3,"file":"pino.d.mts","names":[],"sources":["../src/pino.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;wBA+GgB,aAAa,UAAS,sBAAwB"}
package/dist/pino.mjs CHANGED
@@ -1,15 +1,19 @@
1
- import { DEFAULT_REDACT_PATHS } from "./redact-paths.mjs";
2
- import { pino } from "pino";
1
+ import { otelStreamWrite } from "./otel.mjs";
2
+ import { DEFAULT_REDACT_PATHS, redactUrlCredentials, redactUrlCredentialsIn } from "./redact-paths.mjs";
3
+ import { pino, stdSerializers } from "pino";
3
4
  //#region src/pino.ts
4
5
  /**
5
6
  * Pino logger with built-in redaction support for sensitive data.
6
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
+ *
7
11
  * @example
8
12
  * ```typescript
9
13
  * import { createLogger, DEFAULT_REDACT_PATHS } from '@geekmidas/logger/pino';
10
14
  *
11
- * // Enable redaction with sensible defaults
12
- * const logger = createLogger({ redact: true });
15
+ * // Redaction with sensible defaults — the same as `redact: true`
16
+ * const logger = createLogger();
13
17
  *
14
18
  * // Sensitive data is automatically masked
15
19
  * logger.info({ password: 'secret123', user: 'john' }, 'Login');
@@ -31,14 +35,16 @@ import { pino } from "pino";
31
35
  */
32
36
  /**
33
37
  * Resolves redaction configuration from options.
34
- * 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.
35
41
  *
36
42
  * By default (resolution: 'merge'), custom paths are merged with DEFAULT_REDACT_PATHS.
37
43
  * With resolution: 'override', only the custom paths are used.
38
44
  */
39
45
  function resolveRedactConfig(redact) {
40
- if (redact === void 0 || redact === false) return;
41
- if (redact === true) return DEFAULT_REDACT_PATHS;
46
+ if (redact === false) return;
47
+ if (redact === void 0 || redact === true) return DEFAULT_REDACT_PATHS;
42
48
  if (Array.isArray(redact)) return [...DEFAULT_REDACT_PATHS, ...redact];
43
49
  const { resolution = "merge", paths, censor, remove } = redact;
44
50
  const config = { paths: resolution === "override" ? paths : [...DEFAULT_REDACT_PATHS, ...paths] };
@@ -54,27 +60,45 @@ function resolveRedactConfig(redact) {
54
60
  *
55
61
  * @example
56
62
  * ```typescript
57
- * // Basic logger
63
+ * // Basic logger — sensitive paths are redacted by default
58
64
  * const logger = createLogger({ level: 'debug' });
59
65
  *
60
- * // With redaction enabled
61
- * const secureLogger = createLogger({ redact: true });
66
+ * // Opt out of redaction
67
+ * const rawLogger = createLogger({ redact: false });
62
68
  *
63
69
  * // Pretty printing in development
64
- * const devLogger = createLogger({ pretty: true, redact: true });
70
+ * const devLogger = createLogger({ pretty: true });
65
71
  * ```
66
72
  */
67
73
  function createLogger(options = {}) {
68
- const baseOptions = options?.pretty && process.NODE_ENV !== "production" ? { transport: {
74
+ const pretty = options?.pretty && process.env.NODE_ENV !== "production";
75
+ const baseOptions = pretty ? { transport: {
69
76
  target: "pino-pretty",
70
77
  options: { colorize: true }
71
78
  } } : {};
72
79
  const redact = resolveRedactConfig(options.redact);
73
- 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 = {
74
86
  ...baseOptions,
75
87
  ...options.level && { level: options.level },
76
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
+ },
77
100
  formatters: {
101
+ ...redact && { log: (object) => redactUrlCredentialsIn(object) },
78
102
  bindings() {
79
103
  return { nodeVersion: process.version };
80
104
  },
@@ -82,7 +106,8 @@ function createLogger(options = {}) {
82
106
  return { level: label.toUpperCase() };
83
107
  }
84
108
  }
85
- });
109
+ };
110
+ return options.destination && !pretty ? pino(pinoOptions, options.destination) : pino(pinoOptions);
86
111
  }
87
112
  //#endregion
88
113
  export { DEFAULT_REDACT_PATHS, createLogger };
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 * @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,oBACR,QAC+B;CAC/B,IAAI,WAAW,KAAA,KAAa,WAAW,OACtC;CAGD,IAAI,WAAW,MACd,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,cADS,SAAS,UAAU,QAAQ,aAAa,eAEpD,EACA,WAAW;EACV,QAAQ;EACR,SAAS,EAAE,UAAU,KAAK;CAC3B,EACD,IACC,CAAC;CAEJ,MAAM,SAAS,oBAAoB,QAAQ,MAAM;CAEjD,OAAO,KAAK;EACX,GAAG;EACH,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM;EAC5C,GAAI,UAAU,EAAE,OAAO;EACvB,YAAY;GACX,WAAW;IACV,OAAO,EAAE,aAAa,QAAQ,QAAQ;GACvC;GACA,QAAQ,UAAU;IACjB,OAAO,EAAE,OAAO,MAAM,YAAY,EAAE;GACrC;EACD;CACD,CAAC;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"}
@@ -62,7 +62,56 @@ const DEFAULT_REDACT_PATHS = [
62
62
  "databaseUrl",
63
63
  "database_url"
64
64
  ];
65
+ /**
66
+ * `scheme://user:password@` — the userinfo of a URL that carries a secret.
67
+ * A user with no password (`ssh://git@host`) names nobody's secret and is left
68
+ * alone.
69
+ */
70
+ const URL_CREDENTIALS = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/?#@:]*:[^\s/?#@]*@/gi;
71
+ /**
72
+ * Replace the credentials of every URL in `text` with `REDACTED`, so
73
+ * `s3://KEY:SECRET@uploads` is logged as `s3://REDACTED@uploads`.
74
+ *
75
+ * Path redaction cannot catch these: a URL turns up under any field name —
76
+ * `url`, `origin`, `endpoint` — or inside a message, and blanking every such
77
+ * field would hide the half of it that is useful.
78
+ */
79
+ function redactUrlCredentials(text) {
80
+ return text.replace(URL_CREDENTIALS, "$1REDACTED@");
81
+ }
82
+ /** Deep enough for a log object; past it, values are logged as they are. */
83
+ const MAX_DEPTH = 6;
84
+ /**
85
+ * A copy of `value` with {@link redactUrlCredentials} applied to every string
86
+ * in it. Objects and arrays without such a string are returned as they were.
87
+ */
88
+ function redactUrlCredentialsIn(value, depth = 0) {
89
+ if (typeof value === "string") return redactUrlCredentials(value);
90
+ if (depth >= MAX_DEPTH || value === null || typeof value !== "object") return value;
91
+ if (Array.isArray(value)) {
92
+ let changed = false;
93
+ const next = value.map((item) => {
94
+ const redacted = redactUrlCredentialsIn(item, depth + 1);
95
+ if (redacted !== item) changed = true;
96
+ return redacted;
97
+ });
98
+ return changed ? next : value;
99
+ }
100
+ const proto = Object.getPrototypeOf(value);
101
+ if (proto !== Object.prototype && proto !== null) return value;
102
+ let next;
103
+ for (const [key, item] of Object.entries(value)) {
104
+ const redacted = redactUrlCredentialsIn(item, depth + 1);
105
+ if (redacted !== item) {
106
+ next ??= { ...value };
107
+ next[key] = redacted;
108
+ }
109
+ }
110
+ return next ?? value;
111
+ }
65
112
  //#endregion
66
113
  exports.DEFAULT_REDACT_PATHS = DEFAULT_REDACT_PATHS;
114
+ exports.redactUrlCredentials = redactUrlCredentials;
115
+ exports.redactUrlCredentialsIn = redactUrlCredentialsIn;
67
116
 
68
117
  //# sourceMappingURL=redact-paths.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"redact-paths.cjs","names":[],"sources":["../src/redact-paths.ts"],"sourcesContent":["/**\n * Default sensitive field paths for redaction.\n *\n * These paths are automatically used when `redact: true` is set,\n * and merged with custom paths unless `resolution: 'override'` is specified.\n *\n * Includes:\n * - Authentication: password, token, apiKey, authorization, credentials\n * - Headers: authorization, cookie, x-api-key, x-auth-token\n * - Personal data: ssn, creditCard, cvv, pin\n * - Secrets: secret, connectionString, databaseUrl\n * - Wildcards: *.password, *.secret, *.token (catches nested fields)\n */\nexport const DEFAULT_REDACT_PATHS: string[] = [\n\t// Authentication & authorization\n\t'password',\n\t'pass',\n\t'passwd',\n\t'secret',\n\t'token',\n\t'accessToken',\n\t'refreshToken',\n\t'idToken',\n\t'apiKey',\n\t'api_key',\n\t'apikey',\n\t'auth',\n\t'authorization',\n\t'credential',\n\t'credentials',\n\n\t// Common nested patterns (headers, body, etc.)\n\t'*.password',\n\t'*.secret',\n\t'*.token',\n\t'*.apiKey',\n\t'*.api_key',\n\t'*.authorization',\n\t'*.accessToken',\n\t'*.refreshToken',\n\n\t// HTTP headers (case variations)\n\t'headers.authorization',\n\t'headers.Authorization',\n\t'headers[\"authorization\"]',\n\t'headers[\"Authorization\"]',\n\t'headers.cookie',\n\t'headers.Cookie',\n\t'headers[\"x-api-key\"]',\n\t'headers[\"X-Api-Key\"]',\n\t'headers[\"x-auth-token\"]',\n\t'headers[\"X-Auth-Token\"]',\n\n\t// Common sensitive data fields\n\t'ssn',\n\t'socialSecurityNumber',\n\t'social_security_number',\n\t'creditCard',\n\t'credit_card',\n\t'cardNumber',\n\t'card_number',\n\t'cvv',\n\t'cvc',\n\t'pin',\n\n\t// Database & connection strings\n\t'connectionString',\n\t'connection_string',\n\t'databaseUrl',\n\t'database_url',\n];\n"],"mappings":";;;;;;;;;;;;;;;AAaA,MAAa,uBAAiC;CAE7C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;AACD"}
1
+ {"version":3,"file":"redact-paths.cjs","names":[],"sources":["../src/redact-paths.ts"],"sourcesContent":["/**\n * Default sensitive field paths for redaction.\n *\n * These paths are automatically used when `redact: true` is set,\n * and merged with custom paths unless `resolution: 'override'` is specified.\n *\n * Includes:\n * - Authentication: password, token, apiKey, authorization, credentials\n * - Headers: authorization, cookie, x-api-key, x-auth-token\n * - Personal data: ssn, creditCard, cvv, pin\n * - Secrets: secret, connectionString, databaseUrl\n * - Wildcards: *.password, *.secret, *.token (catches nested fields)\n */\nexport const DEFAULT_REDACT_PATHS: string[] = [\n\t// Authentication & authorization\n\t'password',\n\t'pass',\n\t'passwd',\n\t'secret',\n\t'token',\n\t'accessToken',\n\t'refreshToken',\n\t'idToken',\n\t'apiKey',\n\t'api_key',\n\t'apikey',\n\t'auth',\n\t'authorization',\n\t'credential',\n\t'credentials',\n\n\t// Common nested patterns (headers, body, etc.)\n\t'*.password',\n\t'*.secret',\n\t'*.token',\n\t'*.apiKey',\n\t'*.api_key',\n\t'*.authorization',\n\t'*.accessToken',\n\t'*.refreshToken',\n\n\t// HTTP headers (case variations)\n\t'headers.authorization',\n\t'headers.Authorization',\n\t'headers[\"authorization\"]',\n\t'headers[\"Authorization\"]',\n\t'headers.cookie',\n\t'headers.Cookie',\n\t'headers[\"x-api-key\"]',\n\t'headers[\"X-Api-Key\"]',\n\t'headers[\"x-auth-token\"]',\n\t'headers[\"X-Auth-Token\"]',\n\n\t// Common sensitive data fields\n\t'ssn',\n\t'socialSecurityNumber',\n\t'social_security_number',\n\t'creditCard',\n\t'credit_card',\n\t'cardNumber',\n\t'card_number',\n\t'cvv',\n\t'cvc',\n\t'pin',\n\n\t// Database & connection strings\n\t'connectionString',\n\t'connection_string',\n\t'databaseUrl',\n\t'database_url',\n];\n\n/**\n * `scheme://user:password@` — the userinfo of a URL that carries a secret.\n * A user with no password (`ssh://git@host`) names nobody's secret and is left\n * alone.\n */\nconst URL_CREDENTIALS = /\\b([a-z][a-z0-9+.-]*:\\/\\/)[^\\s/?#@:]*:[^\\s/?#@]*@/gi;\n\n/**\n * Replace the credentials of every URL in `text` with `REDACTED`, so\n * `s3://KEY:SECRET@uploads` is logged as `s3://REDACTED@uploads`.\n *\n * Path redaction cannot catch these: a URL turns up under any field name —\n * `url`, `origin`, `endpoint` — or inside a message, and blanking every such\n * field would hide the half of it that is useful.\n */\nexport function redactUrlCredentials(text: string): string {\n\treturn text.replace(URL_CREDENTIALS, '$1REDACTED@');\n}\n\n/** Deep enough for a log object; past it, values are logged as they are. */\nconst MAX_DEPTH = 6;\n\n/**\n * A copy of `value` with {@link redactUrlCredentials} applied to every string\n * in it. Objects and arrays without such a string are returned as they were.\n */\nexport function redactUrlCredentialsIn<T>(value: T, depth = 0): T {\n\tif (typeof value === 'string') {\n\t\treturn redactUrlCredentials(value) as T;\n\t}\n\tif (depth >= MAX_DEPTH || value === null || typeof value !== 'object') {\n\t\treturn value;\n\t}\n\tif (Array.isArray(value)) {\n\t\tlet changed = false;\n\t\tconst next = value.map((item) => {\n\t\t\tconst redacted = redactUrlCredentialsIn(item, depth + 1);\n\t\t\tif (redacted !== item) changed = true;\n\t\t\treturn redacted;\n\t\t});\n\t\treturn (changed ? next : value) as T;\n\t}\n\tconst proto = Object.getPrototypeOf(value);\n\tif (proto !== Object.prototype && proto !== null) return value;\n\n\tlet next: Record<string, unknown> | undefined;\n\tfor (const [key, item] of Object.entries(value)) {\n\t\tconst redacted = redactUrlCredentialsIn(item, depth + 1);\n\t\tif (redacted !== item) {\n\t\t\tnext ??= { ...(value as Record<string, unknown>) };\n\t\t\tnext[key] = redacted;\n\t\t}\n\t}\n\treturn (next ?? value) as T;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAaA,MAAa,uBAAiC;CAE7C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;AACD;;;;;;AAOA,MAAM,kBAAkB;;;;;;;;;AAUxB,SAAgB,qBAAqB,MAAsB;CAC1D,OAAO,KAAK,QAAQ,iBAAiB,aAAa;AACnD;;AAGA,MAAM,YAAY;;;;;AAMlB,SAAgB,uBAA0B,OAAU,QAAQ,GAAM;CACjE,IAAI,OAAO,UAAU,UACpB,OAAO,qBAAqB,KAAK;CAElC,IAAI,SAAS,aAAa,UAAU,QAAQ,OAAO,UAAU,UAC5D,OAAO;CAER,IAAI,MAAM,QAAQ,KAAK,GAAG;EACzB,IAAI,UAAU;EACd,MAAM,OAAO,MAAM,KAAK,SAAS;GAChC,MAAM,WAAW,uBAAuB,MAAM,QAAQ,CAAC;GACvD,IAAI,aAAa,MAAM,UAAU;GACjC,OAAO;EACR,CAAC;EACD,OAAQ,UAAU,OAAO;CAC1B;CACA,MAAM,QAAQ,OAAO,eAAe,KAAK;CACzC,IAAI,UAAU,OAAO,aAAa,UAAU,MAAM,OAAO;CAEzD,IAAI;CACJ,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,KAAK,GAAG;EAChD,MAAM,WAAW,uBAAuB,MAAM,QAAQ,CAAC;EACvD,IAAI,aAAa,MAAM;GACtB,SAAS,EAAE,GAAI,MAAkC;GACjD,KAAK,OAAO;EACb;CACD;CACA,OAAQ,QAAQ;AACjB"}
@@ -13,5 +13,19 @@
13
13
  * - Wildcards: *.password, *.secret, *.token (catches nested fields)
14
14
  */
15
15
  export declare const DEFAULT_REDACT_PATHS: string[];
16
+ /**
17
+ * Replace the credentials of every URL in `text` with `REDACTED`, so
18
+ * `s3://KEY:SECRET@uploads` is logged as `s3://REDACTED@uploads`.
19
+ *
20
+ * Path redaction cannot catch these: a URL turns up under any field name —
21
+ * `url`, `origin`, `endpoint` — or inside a message, and blanking every such
22
+ * field would hide the half of it that is useful.
23
+ */
24
+ export declare function redactUrlCredentials(text: string): string;
25
+ /**
26
+ * A copy of `value` with {@link redactUrlCredentials} applied to every string
27
+ * in it. Objects and arrays without such a string are returned as they were.
28
+ */
29
+ export declare function redactUrlCredentialsIn<T>(value: T, depth?: number): T;
16
30
  //#endregion
17
31
  //# sourceMappingURL=redact-paths.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redact-paths.d.cts","names":[],"sources":["../src/redact-paths.ts"],"mappings":";;;;;;;;;;;;;;qBAaa"}
1
+ {"version":3,"file":"redact-paths.d.cts","names":[],"sources":["../src/redact-paths.ts"],"mappings":";;;;;;;;;;;;;;qBAaa;;;;;;;;;wBA0EG,qBAAqB;;;;;wBAWrB,uBAAuB,GAAG,OAAO,GAAG,iBAAY"}
@@ -13,5 +13,19 @@
13
13
  * - Wildcards: *.password, *.secret, *.token (catches nested fields)
14
14
  */
15
15
  export declare const DEFAULT_REDACT_PATHS: string[];
16
+ /**
17
+ * Replace the credentials of every URL in `text` with `REDACTED`, so
18
+ * `s3://KEY:SECRET@uploads` is logged as `s3://REDACTED@uploads`.
19
+ *
20
+ * Path redaction cannot catch these: a URL turns up under any field name —
21
+ * `url`, `origin`, `endpoint` — or inside a message, and blanking every such
22
+ * field would hide the half of it that is useful.
23
+ */
24
+ export declare function redactUrlCredentials(text: string): string;
25
+ /**
26
+ * A copy of `value` with {@link redactUrlCredentials} applied to every string
27
+ * in it. Objects and arrays without such a string are returned as they were.
28
+ */
29
+ export declare function redactUrlCredentialsIn<T>(value: T, depth?: number): T;
16
30
  //#endregion
17
31
  //# sourceMappingURL=redact-paths.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redact-paths.d.mts","names":[],"sources":["../src/redact-paths.ts"],"mappings":";;;;;;;;;;;;;;qBAaa"}
1
+ {"version":3,"file":"redact-paths.d.mts","names":[],"sources":["../src/redact-paths.ts"],"mappings":";;;;;;;;;;;;;;qBAaa;;;;;;;;;wBA0EG,qBAAqB;;;;;wBAWrB,uBAAuB,GAAG,OAAO,GAAG,iBAAY"}
@@ -61,7 +61,54 @@ const DEFAULT_REDACT_PATHS = [
61
61
  "databaseUrl",
62
62
  "database_url"
63
63
  ];
64
+ /**
65
+ * `scheme://user:password@` — the userinfo of a URL that carries a secret.
66
+ * A user with no password (`ssh://git@host`) names nobody's secret and is left
67
+ * alone.
68
+ */
69
+ const URL_CREDENTIALS = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/?#@:]*:[^\s/?#@]*@/gi;
70
+ /**
71
+ * Replace the credentials of every URL in `text` with `REDACTED`, so
72
+ * `s3://KEY:SECRET@uploads` is logged as `s3://REDACTED@uploads`.
73
+ *
74
+ * Path redaction cannot catch these: a URL turns up under any field name —
75
+ * `url`, `origin`, `endpoint` — or inside a message, and blanking every such
76
+ * field would hide the half of it that is useful.
77
+ */
78
+ function redactUrlCredentials(text) {
79
+ return text.replace(URL_CREDENTIALS, "$1REDACTED@");
80
+ }
81
+ /** Deep enough for a log object; past it, values are logged as they are. */
82
+ const MAX_DEPTH = 6;
83
+ /**
84
+ * A copy of `value` with {@link redactUrlCredentials} applied to every string
85
+ * in it. Objects and arrays without such a string are returned as they were.
86
+ */
87
+ function redactUrlCredentialsIn(value, depth = 0) {
88
+ if (typeof value === "string") return redactUrlCredentials(value);
89
+ if (depth >= MAX_DEPTH || value === null || typeof value !== "object") return value;
90
+ if (Array.isArray(value)) {
91
+ let changed = false;
92
+ const next = value.map((item) => {
93
+ const redacted = redactUrlCredentialsIn(item, depth + 1);
94
+ if (redacted !== item) changed = true;
95
+ return redacted;
96
+ });
97
+ return changed ? next : value;
98
+ }
99
+ const proto = Object.getPrototypeOf(value);
100
+ if (proto !== Object.prototype && proto !== null) return value;
101
+ let next;
102
+ for (const [key, item] of Object.entries(value)) {
103
+ const redacted = redactUrlCredentialsIn(item, depth + 1);
104
+ if (redacted !== item) {
105
+ next ??= { ...value };
106
+ next[key] = redacted;
107
+ }
108
+ }
109
+ return next ?? value;
110
+ }
64
111
  //#endregion
65
- export { DEFAULT_REDACT_PATHS };
112
+ export { DEFAULT_REDACT_PATHS, redactUrlCredentials, redactUrlCredentialsIn };
66
113
 
67
114
  //# sourceMappingURL=redact-paths.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"redact-paths.mjs","names":[],"sources":["../src/redact-paths.ts"],"sourcesContent":["/**\n * Default sensitive field paths for redaction.\n *\n * These paths are automatically used when `redact: true` is set,\n * and merged with custom paths unless `resolution: 'override'` is specified.\n *\n * Includes:\n * - Authentication: password, token, apiKey, authorization, credentials\n * - Headers: authorization, cookie, x-api-key, x-auth-token\n * - Personal data: ssn, creditCard, cvv, pin\n * - Secrets: secret, connectionString, databaseUrl\n * - Wildcards: *.password, *.secret, *.token (catches nested fields)\n */\nexport const DEFAULT_REDACT_PATHS: string[] = [\n\t// Authentication & authorization\n\t'password',\n\t'pass',\n\t'passwd',\n\t'secret',\n\t'token',\n\t'accessToken',\n\t'refreshToken',\n\t'idToken',\n\t'apiKey',\n\t'api_key',\n\t'apikey',\n\t'auth',\n\t'authorization',\n\t'credential',\n\t'credentials',\n\n\t// Common nested patterns (headers, body, etc.)\n\t'*.password',\n\t'*.secret',\n\t'*.token',\n\t'*.apiKey',\n\t'*.api_key',\n\t'*.authorization',\n\t'*.accessToken',\n\t'*.refreshToken',\n\n\t// HTTP headers (case variations)\n\t'headers.authorization',\n\t'headers.Authorization',\n\t'headers[\"authorization\"]',\n\t'headers[\"Authorization\"]',\n\t'headers.cookie',\n\t'headers.Cookie',\n\t'headers[\"x-api-key\"]',\n\t'headers[\"X-Api-Key\"]',\n\t'headers[\"x-auth-token\"]',\n\t'headers[\"X-Auth-Token\"]',\n\n\t// Common sensitive data fields\n\t'ssn',\n\t'socialSecurityNumber',\n\t'social_security_number',\n\t'creditCard',\n\t'credit_card',\n\t'cardNumber',\n\t'card_number',\n\t'cvv',\n\t'cvc',\n\t'pin',\n\n\t// Database & connection strings\n\t'connectionString',\n\t'connection_string',\n\t'databaseUrl',\n\t'database_url',\n];\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAa,uBAAiC;CAE7C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;AACD"}
1
+ {"version":3,"file":"redact-paths.mjs","names":[],"sources":["../src/redact-paths.ts"],"sourcesContent":["/**\n * Default sensitive field paths for redaction.\n *\n * These paths are automatically used when `redact: true` is set,\n * and merged with custom paths unless `resolution: 'override'` is specified.\n *\n * Includes:\n * - Authentication: password, token, apiKey, authorization, credentials\n * - Headers: authorization, cookie, x-api-key, x-auth-token\n * - Personal data: ssn, creditCard, cvv, pin\n * - Secrets: secret, connectionString, databaseUrl\n * - Wildcards: *.password, *.secret, *.token (catches nested fields)\n */\nexport const DEFAULT_REDACT_PATHS: string[] = [\n\t// Authentication & authorization\n\t'password',\n\t'pass',\n\t'passwd',\n\t'secret',\n\t'token',\n\t'accessToken',\n\t'refreshToken',\n\t'idToken',\n\t'apiKey',\n\t'api_key',\n\t'apikey',\n\t'auth',\n\t'authorization',\n\t'credential',\n\t'credentials',\n\n\t// Common nested patterns (headers, body, etc.)\n\t'*.password',\n\t'*.secret',\n\t'*.token',\n\t'*.apiKey',\n\t'*.api_key',\n\t'*.authorization',\n\t'*.accessToken',\n\t'*.refreshToken',\n\n\t// HTTP headers (case variations)\n\t'headers.authorization',\n\t'headers.Authorization',\n\t'headers[\"authorization\"]',\n\t'headers[\"Authorization\"]',\n\t'headers.cookie',\n\t'headers.Cookie',\n\t'headers[\"x-api-key\"]',\n\t'headers[\"X-Api-Key\"]',\n\t'headers[\"x-auth-token\"]',\n\t'headers[\"X-Auth-Token\"]',\n\n\t// Common sensitive data fields\n\t'ssn',\n\t'socialSecurityNumber',\n\t'social_security_number',\n\t'creditCard',\n\t'credit_card',\n\t'cardNumber',\n\t'card_number',\n\t'cvv',\n\t'cvc',\n\t'pin',\n\n\t// Database & connection strings\n\t'connectionString',\n\t'connection_string',\n\t'databaseUrl',\n\t'database_url',\n];\n\n/**\n * `scheme://user:password@` — the userinfo of a URL that carries a secret.\n * A user with no password (`ssh://git@host`) names nobody's secret and is left\n * alone.\n */\nconst URL_CREDENTIALS = /\\b([a-z][a-z0-9+.-]*:\\/\\/)[^\\s/?#@:]*:[^\\s/?#@]*@/gi;\n\n/**\n * Replace the credentials of every URL in `text` with `REDACTED`, so\n * `s3://KEY:SECRET@uploads` is logged as `s3://REDACTED@uploads`.\n *\n * Path redaction cannot catch these: a URL turns up under any field name —\n * `url`, `origin`, `endpoint` — or inside a message, and blanking every such\n * field would hide the half of it that is useful.\n */\nexport function redactUrlCredentials(text: string): string {\n\treturn text.replace(URL_CREDENTIALS, '$1REDACTED@');\n}\n\n/** Deep enough for a log object; past it, values are logged as they are. */\nconst MAX_DEPTH = 6;\n\n/**\n * A copy of `value` with {@link redactUrlCredentials} applied to every string\n * in it. Objects and arrays without such a string are returned as they were.\n */\nexport function redactUrlCredentialsIn<T>(value: T, depth = 0): T {\n\tif (typeof value === 'string') {\n\t\treturn redactUrlCredentials(value) as T;\n\t}\n\tif (depth >= MAX_DEPTH || value === null || typeof value !== 'object') {\n\t\treturn value;\n\t}\n\tif (Array.isArray(value)) {\n\t\tlet changed = false;\n\t\tconst next = value.map((item) => {\n\t\t\tconst redacted = redactUrlCredentialsIn(item, depth + 1);\n\t\t\tif (redacted !== item) changed = true;\n\t\t\treturn redacted;\n\t\t});\n\t\treturn (changed ? next : value) as T;\n\t}\n\tconst proto = Object.getPrototypeOf(value);\n\tif (proto !== Object.prototype && proto !== null) return value;\n\n\tlet next: Record<string, unknown> | undefined;\n\tfor (const [key, item] of Object.entries(value)) {\n\t\tconst redacted = redactUrlCredentialsIn(item, depth + 1);\n\t\tif (redacted !== item) {\n\t\t\tnext ??= { ...(value as Record<string, unknown>) };\n\t\t\tnext[key] = redacted;\n\t\t}\n\t}\n\treturn (next ?? value) as T;\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAa,uBAAiC;CAE7C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;AACD;;;;;;AAOA,MAAM,kBAAkB;;;;;;;;;AAUxB,SAAgB,qBAAqB,MAAsB;CAC1D,OAAO,KAAK,QAAQ,iBAAiB,aAAa;AACnD;;AAGA,MAAM,YAAY;;;;;AAMlB,SAAgB,uBAA0B,OAAU,QAAQ,GAAM;CACjE,IAAI,OAAO,UAAU,UACpB,OAAO,qBAAqB,KAAK;CAElC,IAAI,SAAS,aAAa,UAAU,QAAQ,OAAO,UAAU,UAC5D,OAAO;CAER,IAAI,MAAM,QAAQ,KAAK,GAAG;EACzB,IAAI,UAAU;EACd,MAAM,OAAO,MAAM,KAAK,SAAS;GAChC,MAAM,WAAW,uBAAuB,MAAM,QAAQ,CAAC;GACvD,IAAI,aAAa,MAAM,UAAU;GACjC,OAAO;EACR,CAAC;EACD,OAAQ,UAAU,OAAO;CAC1B;CACA,MAAM,QAAQ,OAAO,eAAe,KAAK;CACzC,IAAI,UAAU,OAAO,aAAa,UAAU,MAAM,OAAO;CAEzD,IAAI;CACJ,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,KAAK,GAAG;EAChD,MAAM,WAAW,uBAAuB,MAAM,QAAQ,CAAC;EACvD,IAAI,aAAa,MAAM;GACtB,SAAS,EAAE,GAAI,MAAkC;GACjD,KAAK,OAAO;EACb;CACD;CACA,OAAQ,QAAQ;AACjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Logging function type that supports both structured and simple logging.\n * Can be called with an object for structured logging or just a message string.\n *\n * @example\n * ```typescript\n * // Structured logging with context object\n * logger.info({ userId: 123, action: 'login' }, 'User logged in');\n *\n * // Simple string logging\n * logger.info('Application started');\n * ```\n */\nexport type LogFn = {\n\t/** Structured logging with context object, optional message, and additional arguments */\n\t<T extends object>(obj: T, msg?: string, ...args: any[]): void;\n\t/** Simple string logging */\n\t(msg: string): void;\n};\n\n/**\n * Standard logger interface with multiple log levels and child logger support.\n * Follows common logging patterns with structured logging capabilities.\n *\n * @interface Logger\n */\nexport interface Logger {\n\t/** Debug level logging - verbose information for debugging */\n\tdebug: LogFn;\n\t/** Info level logging - general informational messages */\n\tinfo: LogFn;\n\t/** Warning level logging - potentially harmful situations */\n\twarn: LogFn;\n\t/** Error level logging - error events that might still allow the application to continue */\n\terror: LogFn;\n\t/** Fatal level logging - severe errors that will likely cause the application to abort */\n\tfatal: LogFn;\n\t/** Trace level logging - most detailed information */\n\ttrace: LogFn;\n\t/**\n\t * Creates a child logger with additional context.\n\t * Child loggers inherit parent context and add their own.\n\t *\n\t * @param obj - Additional context to include in all child logger calls\n\t * @returns A new Logger instance with merged context\n\t */\n\tchild: (obj: object) => Logger;\n}\n\n/**\n * Console-based logger implementation that outputs to standard console methods.\n * Supports structured logging with automatic timestamp injection and context inheritance.\n *\n * @implements {Logger}\n *\n * @example\n * ```typescript\n * const logger = new ConsoleLogger({ app: 'myApp', version: '1.0.0' });\n * logger.info({ userId: 123 }, 'User action performed');\n * // Output: { app: 'myApp', version: '1.0.0', userId: 123, ts: 1234567890 } User action performed\n *\n * const childLogger = logger.child({ module: 'auth' });\n * childLogger.debug({ action: 'validate' }, 'Validating token');\n * // Output: { app: 'myApp', version: '1.0.0', module: 'auth', action: 'validate', ts: 1234567891 } Validating token\n * ```\n */\nexport enum LogLevel {\n\tTrace = 'trace',\n\tDebug = 'debug',\n\tInfo = 'info',\n\tWarn = 'warn',\n\tError = 'error',\n\tFatal = 'fatal',\n\tSilent = 'silent',\n}\n\n/**\n * Redaction configuration for masking sensitive data in logs.\n * Uses pino's fast-redact library under the hood.\n *\n * By default, custom paths are merged with the default sensitive paths.\n * Use `resolution: 'override'` to use only your custom paths.\n *\n * @example\n * ```typescript\n * // Simple path array (merges with defaults)\n * redact: ['user.ssn', 'custom.field']\n *\n * // Override defaults completely\n * redact: {\n * paths: ['only.these.paths'],\n * resolution: 'override',\n * }\n *\n * // With custom censor\n * redact: {\n * paths: ['extra.secret'],\n * censor: '***',\n * }\n *\n * // Remove fields entirely\n * redact: {\n * paths: ['temporary.data'],\n * remove: true,\n * }\n * ```\n */\nexport type RedactOptions =\n\t| string[]\n\t| {\n\t\t\t/** Paths to redact using dot notation or bracket notation for special chars */\n\t\t\tpaths: string[];\n\t\t\t/** Custom replacement text (default: '[REDACTED]') */\n\t\t\tcensor?: string | ((value: unknown, path: string[]) => unknown);\n\t\t\t/** Remove the field entirely instead of replacing (default: false) */\n\t\t\tremove?: boolean;\n\t\t\t/**\n\t\t\t * How to combine custom paths with default sensitive paths.\n\t\t\t * - 'merge': Custom paths are added to default paths (default)\n\t\t\t * - 'override': Only custom paths are used, defaults are ignored\n\t\t\t */\n\t\t\tresolution?: 'merge' | 'override';\n\t };\n\nexport type CreateLoggerOptions = {\n\t/** Enable pretty printing with colors (disabled in production) */\n\tpretty?: boolean;\n\t/** Minimum log level to output */\n\tlevel?: LogLevel;\n\t/**\n\t * Redaction configuration for masking sensitive data.\n\t *\n\t * - `true`: Uses default sensitive paths (password, token, secret, etc.)\n\t * - `false` or `undefined`: No redaction applied\n\t * - `string[]`: Custom paths merged with defaults\n\t * - `object`: Advanced config with paths, censor, remove, and resolution options\n\t *\n\t * By default, custom paths are **merged** with the default sensitive paths.\n\t * Use `resolution: 'override'` to disable defaults and use only your paths.\n\t *\n\t * @example\n\t * ```typescript\n\t * // Use defaults only\n\t * createLogger({ redact: true });\n\t *\n\t * // Add custom paths (merged with defaults)\n\t * createLogger({ redact: ['user.ssn', 'custom.field'] });\n\t *\n\t * // Override defaults completely\n\t * createLogger({\n\t * redact: {\n\t * paths: ['only.these.paths'],\n\t * resolution: 'override',\n\t * }\n\t * });\n\t *\n\t * // Merge with custom censor\n\t * createLogger({\n\t * redact: {\n\t * paths: ['extra.secret'],\n\t * censor: '***',\n\t * }\n\t * });\n\t * ```\n\t */\n\tredact?: boolean | RedactOptions;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkEA,IAAY,WAAL,yBAAA,UAAA;CACN,SAAA,WAAA;CACA,SAAA,WAAA;CACA,SAAA,UAAA;CACA,SAAA,UAAA;CACA,SAAA,WAAA;CACA,SAAA,WAAA;CACA,SAAA,YAAA;;AACD,EAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"types.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Logging function type that supports both structured and simple logging.\n * Can be called with an object for structured logging or just a message string.\n *\n * @example\n * ```typescript\n * // Structured logging with context object\n * logger.info({ userId: 123, action: 'login' }, 'User logged in');\n *\n * // Simple string logging\n * logger.info('Application started');\n * ```\n */\nexport type LogFn = {\n\t/** Structured logging with context object, optional message, and additional arguments */\n\t<T extends object>(obj: T, msg?: string, ...args: any[]): void;\n\t/** Simple string logging */\n\t(msg: string): void;\n};\n\n/**\n * Standard logger interface with multiple log levels and child logger support.\n * Follows common logging patterns with structured logging capabilities.\n *\n * @interface Logger\n */\nexport interface Logger {\n\t/** Debug level logging - verbose information for debugging */\n\tdebug: LogFn;\n\t/** Info level logging - general informational messages */\n\tinfo: LogFn;\n\t/** Warning level logging - potentially harmful situations */\n\twarn: LogFn;\n\t/** Error level logging - error events that might still allow the application to continue */\n\terror: LogFn;\n\t/** Fatal level logging - severe errors that will likely cause the application to abort */\n\tfatal: LogFn;\n\t/** Trace level logging - most detailed information */\n\ttrace: LogFn;\n\t/**\n\t * Creates a child logger with additional context.\n\t * Child loggers inherit parent context and add their own.\n\t *\n\t * @param obj - Additional context to include in all child logger calls\n\t * @returns A new Logger instance with merged context\n\t */\n\tchild: (obj: object) => Logger;\n}\n\n/**\n * Console-based logger implementation that outputs to standard console methods.\n * Supports structured logging with automatic timestamp injection and context inheritance.\n *\n * @implements {Logger}\n *\n * @example\n * ```typescript\n * const logger = new ConsoleLogger({ app: 'myApp', version: '1.0.0' });\n * logger.info({ userId: 123 }, 'User action performed');\n * // Output: { app: 'myApp', version: '1.0.0', userId: 123, ts: 1234567890 } User action performed\n *\n * const childLogger = logger.child({ module: 'auth' });\n * childLogger.debug({ action: 'validate' }, 'Validating token');\n * // Output: { app: 'myApp', version: '1.0.0', module: 'auth', action: 'validate', ts: 1234567891 } Validating token\n * ```\n */\nexport enum LogLevel {\n\tTrace = 'trace',\n\tDebug = 'debug',\n\tInfo = 'info',\n\tWarn = 'warn',\n\tError = 'error',\n\tFatal = 'fatal',\n\tSilent = 'silent',\n}\n\n/**\n * Redaction configuration for masking sensitive data in logs.\n * Uses pino's fast-redact library under the hood.\n *\n * By default, custom paths are merged with the default sensitive paths.\n * Use `resolution: 'override'` to use only your custom paths.\n *\n * @example\n * ```typescript\n * // Simple path array (merges with defaults)\n * redact: ['user.ssn', 'custom.field']\n *\n * // Override defaults completely\n * redact: {\n * paths: ['only.these.paths'],\n * resolution: 'override',\n * }\n *\n * // With custom censor\n * redact: {\n * paths: ['extra.secret'],\n * censor: '***',\n * }\n *\n * // Remove fields entirely\n * redact: {\n * paths: ['temporary.data'],\n * remove: true,\n * }\n * ```\n */\nexport type RedactOptions =\n\t| string[]\n\t| {\n\t\t\t/** Paths to redact using dot notation or bracket notation for special chars */\n\t\t\tpaths: string[];\n\t\t\t/** Custom replacement text (default: '[REDACTED]') */\n\t\t\tcensor?: string | ((value: unknown, path: string[]) => unknown);\n\t\t\t/** Remove the field entirely instead of replacing (default: false) */\n\t\t\tremove?: boolean;\n\t\t\t/**\n\t\t\t * How to combine custom paths with default sensitive paths.\n\t\t\t * - 'merge': Custom paths are added to default paths (default)\n\t\t\t * - 'override': Only custom paths are used, defaults are ignored\n\t\t\t */\n\t\t\tresolution?: 'merge' | 'override';\n\t };\n\nexport type CreateLoggerOptions = {\n\t/** Enable pretty printing with colors (disabled in production) */\n\tpretty?: boolean;\n\t/** Minimum log level to output */\n\tlevel?: LogLevel;\n\t/**\n\t * Redaction configuration for masking sensitive data.\n\t *\n\t * - `true` or `undefined` (the default): Uses default sensitive paths\n\t * (password, token, secret, etc.)\n\t * - `false`: No redaction applied — the explicit opt-out\n\t * - `string[]`: Custom paths merged with defaults\n\t * - `object`: Advanced config with paths, censor, remove, and resolution options\n\t *\n\t * By default, custom paths are **merged** with the default sensitive paths.\n\t * Use `resolution: 'override'` to disable defaults and use only your paths.\n\t *\n\t * @example\n\t * ```typescript\n\t * // Use defaults only (the same as leaving `redact` out)\n\t * createLogger({ redact: true });\n\t *\n\t * // Opt out\n\t * createLogger({ redact: false });\n\t *\n\t * // Add custom paths (merged with defaults)\n\t * createLogger({ redact: ['user.ssn', 'custom.field'] });\n\t *\n\t * // Override defaults completely\n\t * createLogger({\n\t * redact: {\n\t * paths: ['only.these.paths'],\n\t * resolution: 'override',\n\t * }\n\t * });\n\t *\n\t * // Merge with custom censor\n\t * createLogger({\n\t * redact: {\n\t * paths: ['extra.secret'],\n\t * censor: '***',\n\t * }\n\t * });\n\t * ```\n\t */\n\tredact?: boolean | RedactOptions;\n\t/**\n\t * Where log lines are written (pino logger only). Defaults to stdout.\n\t * Ignored when `pretty` is on, since the pretty transport owns its output.\n\t */\n\tdestination?: { write(line: string): unknown };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkEA,IAAY,WAAL,yBAAA,UAAA;CACN,SAAA,WAAA;CACA,SAAA,WAAA;CACA,SAAA,UAAA;CACA,SAAA,UAAA;CACA,SAAA,WAAA;CACA,SAAA,WAAA;CACA,SAAA,YAAA;;AACD,EAAA,CAAA,CAAA"}
package/dist/types.d.cts CHANGED
@@ -125,8 +125,9 @@ export type CreateLoggerOptions = {
125
125
  /**
126
126
  * Redaction configuration for masking sensitive data.
127
127
  *
128
- * - `true`: Uses default sensitive paths (password, token, secret, etc.)
129
- * - `false` or `undefined`: No redaction applied
128
+ * - `true` or `undefined` (the default): Uses default sensitive paths
129
+ * (password, token, secret, etc.)
130
+ * - `false`: No redaction applied — the explicit opt-out
130
131
  * - `string[]`: Custom paths merged with defaults
131
132
  * - `object`: Advanced config with paths, censor, remove, and resolution options
132
133
  *
@@ -135,9 +136,12 @@ export type CreateLoggerOptions = {
135
136
  *
136
137
  * @example
137
138
  * ```typescript
138
- * // Use defaults only
139
+ * // Use defaults only (the same as leaving `redact` out)
139
140
  * createLogger({ redact: true });
140
141
  *
142
+ * // Opt out
143
+ * createLogger({ redact: false });
144
+ *
141
145
  * // Add custom paths (merged with defaults)
142
146
  * createLogger({ redact: ['user.ssn', 'custom.field'] });
143
147
  *
@@ -159,6 +163,13 @@ export type CreateLoggerOptions = {
159
163
  * ```
160
164
  */
161
165
  redact?: boolean | RedactOptions;
166
+ /**
167
+ * Where log lines are written (pino logger only). Defaults to stdout.
168
+ * Ignored when `pretty` is on, since the pretty transport owns its output.
169
+ */
170
+ destination?: {
171
+ write(line: string): unknown;
172
+ };
162
173
  };
163
174
  //#endregion
164
175
  //# sourceMappingURL=types.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;YAaY;;GAEV,kBAAkB,KAAK,GAAG,iBAAiB;;GAE3C;;;;;;;;iBASe;;EAEhB,OAAO;;EAEP,MAAM;;EAEN,MAAM;;EAEN,OAAO;;EAEP,OAAO;;EAEP,OAAO;;;;;;;;EAQP,QAAQ,gBAAgB;;;;;;;;;;;;;;;;;;;oBAoBb;EACX;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkCW;;EAIT;;EAEA,oBAAoB,gBAAgB;;EAEpC;;;;;;EAMA;;YAGS;;EAEX;;EAEA,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqCR,mBAAmB"}
1
+ {"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;YAaY;;GAEV,kBAAkB,KAAK,GAAG,iBAAiB;;GAE3C;;;;;;;;iBASe;;EAEhB,OAAO;;EAEP,MAAM;;EAEN,MAAM;;EAEN,OAAO;;EAEP,OAAO;;EAEP,OAAO;;;;;;;;EAQP,QAAQ,gBAAgB;;;;;;;;;;;;;;;;;;;oBAoBb;EACX;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkCW;;EAIT;;EAEA,oBAAoB,gBAAgB;;EAEpC;;;;;;EAMA;;YAGS;;EAEX;;EAEA,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyCR,mBAAmB;;;;;EAKnB;IAAgB,MAAM"}
package/dist/types.d.mts CHANGED
@@ -125,8 +125,9 @@ export type CreateLoggerOptions = {
125
125
  /**
126
126
  * Redaction configuration for masking sensitive data.
127
127
  *
128
- * - `true`: Uses default sensitive paths (password, token, secret, etc.)
129
- * - `false` or `undefined`: No redaction applied
128
+ * - `true` or `undefined` (the default): Uses default sensitive paths
129
+ * (password, token, secret, etc.)
130
+ * - `false`: No redaction applied — the explicit opt-out
130
131
  * - `string[]`: Custom paths merged with defaults
131
132
  * - `object`: Advanced config with paths, censor, remove, and resolution options
132
133
  *
@@ -135,9 +136,12 @@ export type CreateLoggerOptions = {
135
136
  *
136
137
  * @example
137
138
  * ```typescript
138
- * // Use defaults only
139
+ * // Use defaults only (the same as leaving `redact` out)
139
140
  * createLogger({ redact: true });
140
141
  *
142
+ * // Opt out
143
+ * createLogger({ redact: false });
144
+ *
141
145
  * // Add custom paths (merged with defaults)
142
146
  * createLogger({ redact: ['user.ssn', 'custom.field'] });
143
147
  *
@@ -159,6 +163,13 @@ export type CreateLoggerOptions = {
159
163
  * ```
160
164
  */
161
165
  redact?: boolean | RedactOptions;
166
+ /**
167
+ * Where log lines are written (pino logger only). Defaults to stdout.
168
+ * Ignored when `pretty` is on, since the pretty transport owns its output.
169
+ */
170
+ destination?: {
171
+ write(line: string): unknown;
172
+ };
162
173
  };
163
174
  //#endregion
164
175
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;YAaY;;GAEV,kBAAkB,KAAK,GAAG,iBAAiB;;GAE3C;;;;;;;;iBASe;;EAEhB,OAAO;;EAEP,MAAM;;EAEN,MAAM;;EAEN,OAAO;;EAEP,OAAO;;EAEP,OAAO;;;;;;;;EAQP,QAAQ,gBAAgB;;;;;;;;;;;;;;;;;;;oBAoBb;EACX;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkCW;;EAIT;;EAEA,oBAAoB,gBAAgB;;EAEpC;;;;;;EAMA;;YAGS;;EAEX;;EAEA,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqCR,mBAAmB"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;YAaY;;GAEV,kBAAkB,KAAK,GAAG,iBAAiB;;GAE3C;;;;;;;;iBASe;;EAEhB,OAAO;;EAEP,MAAM;;EAEN,MAAM;;EAEN,OAAO;;EAEP,OAAO;;EAEP,OAAO;;;;;;;;EAQP,QAAQ,gBAAgB;;;;;;;;;;;;;;;;;;;oBAoBb;EACX;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkCW;;EAIT;;EAEA,oBAAoB,gBAAgB;;EAEpC;;;;;;EAMA;;YAGS;;EAEX;;EAEA,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyCR,mBAAmB;;;;;EAKnB;IAAgB,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Logging function type that supports both structured and simple logging.\n * Can be called with an object for structured logging or just a message string.\n *\n * @example\n * ```typescript\n * // Structured logging with context object\n * logger.info({ userId: 123, action: 'login' }, 'User logged in');\n *\n * // Simple string logging\n * logger.info('Application started');\n * ```\n */\nexport type LogFn = {\n\t/** Structured logging with context object, optional message, and additional arguments */\n\t<T extends object>(obj: T, msg?: string, ...args: any[]): void;\n\t/** Simple string logging */\n\t(msg: string): void;\n};\n\n/**\n * Standard logger interface with multiple log levels and child logger support.\n * Follows common logging patterns with structured logging capabilities.\n *\n * @interface Logger\n */\nexport interface Logger {\n\t/** Debug level logging - verbose information for debugging */\n\tdebug: LogFn;\n\t/** Info level logging - general informational messages */\n\tinfo: LogFn;\n\t/** Warning level logging - potentially harmful situations */\n\twarn: LogFn;\n\t/** Error level logging - error events that might still allow the application to continue */\n\terror: LogFn;\n\t/** Fatal level logging - severe errors that will likely cause the application to abort */\n\tfatal: LogFn;\n\t/** Trace level logging - most detailed information */\n\ttrace: LogFn;\n\t/**\n\t * Creates a child logger with additional context.\n\t * Child loggers inherit parent context and add their own.\n\t *\n\t * @param obj - Additional context to include in all child logger calls\n\t * @returns A new Logger instance with merged context\n\t */\n\tchild: (obj: object) => Logger;\n}\n\n/**\n * Console-based logger implementation that outputs to standard console methods.\n * Supports structured logging with automatic timestamp injection and context inheritance.\n *\n * @implements {Logger}\n *\n * @example\n * ```typescript\n * const logger = new ConsoleLogger({ app: 'myApp', version: '1.0.0' });\n * logger.info({ userId: 123 }, 'User action performed');\n * // Output: { app: 'myApp', version: '1.0.0', userId: 123, ts: 1234567890 } User action performed\n *\n * const childLogger = logger.child({ module: 'auth' });\n * childLogger.debug({ action: 'validate' }, 'Validating token');\n * // Output: { app: 'myApp', version: '1.0.0', module: 'auth', action: 'validate', ts: 1234567891 } Validating token\n * ```\n */\nexport enum LogLevel {\n\tTrace = 'trace',\n\tDebug = 'debug',\n\tInfo = 'info',\n\tWarn = 'warn',\n\tError = 'error',\n\tFatal = 'fatal',\n\tSilent = 'silent',\n}\n\n/**\n * Redaction configuration for masking sensitive data in logs.\n * Uses pino's fast-redact library under the hood.\n *\n * By default, custom paths are merged with the default sensitive paths.\n * Use `resolution: 'override'` to use only your custom paths.\n *\n * @example\n * ```typescript\n * // Simple path array (merges with defaults)\n * redact: ['user.ssn', 'custom.field']\n *\n * // Override defaults completely\n * redact: {\n * paths: ['only.these.paths'],\n * resolution: 'override',\n * }\n *\n * // With custom censor\n * redact: {\n * paths: ['extra.secret'],\n * censor: '***',\n * }\n *\n * // Remove fields entirely\n * redact: {\n * paths: ['temporary.data'],\n * remove: true,\n * }\n * ```\n */\nexport type RedactOptions =\n\t| string[]\n\t| {\n\t\t\t/** Paths to redact using dot notation or bracket notation for special chars */\n\t\t\tpaths: string[];\n\t\t\t/** Custom replacement text (default: '[REDACTED]') */\n\t\t\tcensor?: string | ((value: unknown, path: string[]) => unknown);\n\t\t\t/** Remove the field entirely instead of replacing (default: false) */\n\t\t\tremove?: boolean;\n\t\t\t/**\n\t\t\t * How to combine custom paths with default sensitive paths.\n\t\t\t * - 'merge': Custom paths are added to default paths (default)\n\t\t\t * - 'override': Only custom paths are used, defaults are ignored\n\t\t\t */\n\t\t\tresolution?: 'merge' | 'override';\n\t };\n\nexport type CreateLoggerOptions = {\n\t/** Enable pretty printing with colors (disabled in production) */\n\tpretty?: boolean;\n\t/** Minimum log level to output */\n\tlevel?: LogLevel;\n\t/**\n\t * Redaction configuration for masking sensitive data.\n\t *\n\t * - `true`: Uses default sensitive paths (password, token, secret, etc.)\n\t * - `false` or `undefined`: No redaction applied\n\t * - `string[]`: Custom paths merged with defaults\n\t * - `object`: Advanced config with paths, censor, remove, and resolution options\n\t *\n\t * By default, custom paths are **merged** with the default sensitive paths.\n\t * Use `resolution: 'override'` to disable defaults and use only your paths.\n\t *\n\t * @example\n\t * ```typescript\n\t * // Use defaults only\n\t * createLogger({ redact: true });\n\t *\n\t * // Add custom paths (merged with defaults)\n\t * createLogger({ redact: ['user.ssn', 'custom.field'] });\n\t *\n\t * // Override defaults completely\n\t * createLogger({\n\t * redact: {\n\t * paths: ['only.these.paths'],\n\t * resolution: 'override',\n\t * }\n\t * });\n\t *\n\t * // Merge with custom censor\n\t * createLogger({\n\t * redact: {\n\t * paths: ['extra.secret'],\n\t * censor: '***',\n\t * }\n\t * });\n\t * ```\n\t */\n\tredact?: boolean | RedactOptions;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkEA,IAAY,WAAL,yBAAA,UAAA;CACN,SAAA,WAAA;CACA,SAAA,WAAA;CACA,SAAA,UAAA;CACA,SAAA,UAAA;CACA,SAAA,WAAA;CACA,SAAA,WAAA;CACA,SAAA,YAAA;;AACD,EAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Logging function type that supports both structured and simple logging.\n * Can be called with an object for structured logging or just a message string.\n *\n * @example\n * ```typescript\n * // Structured logging with context object\n * logger.info({ userId: 123, action: 'login' }, 'User logged in');\n *\n * // Simple string logging\n * logger.info('Application started');\n * ```\n */\nexport type LogFn = {\n\t/** Structured logging with context object, optional message, and additional arguments */\n\t<T extends object>(obj: T, msg?: string, ...args: any[]): void;\n\t/** Simple string logging */\n\t(msg: string): void;\n};\n\n/**\n * Standard logger interface with multiple log levels and child logger support.\n * Follows common logging patterns with structured logging capabilities.\n *\n * @interface Logger\n */\nexport interface Logger {\n\t/** Debug level logging - verbose information for debugging */\n\tdebug: LogFn;\n\t/** Info level logging - general informational messages */\n\tinfo: LogFn;\n\t/** Warning level logging - potentially harmful situations */\n\twarn: LogFn;\n\t/** Error level logging - error events that might still allow the application to continue */\n\terror: LogFn;\n\t/** Fatal level logging - severe errors that will likely cause the application to abort */\n\tfatal: LogFn;\n\t/** Trace level logging - most detailed information */\n\ttrace: LogFn;\n\t/**\n\t * Creates a child logger with additional context.\n\t * Child loggers inherit parent context and add their own.\n\t *\n\t * @param obj - Additional context to include in all child logger calls\n\t * @returns A new Logger instance with merged context\n\t */\n\tchild: (obj: object) => Logger;\n}\n\n/**\n * Console-based logger implementation that outputs to standard console methods.\n * Supports structured logging with automatic timestamp injection and context inheritance.\n *\n * @implements {Logger}\n *\n * @example\n * ```typescript\n * const logger = new ConsoleLogger({ app: 'myApp', version: '1.0.0' });\n * logger.info({ userId: 123 }, 'User action performed');\n * // Output: { app: 'myApp', version: '1.0.0', userId: 123, ts: 1234567890 } User action performed\n *\n * const childLogger = logger.child({ module: 'auth' });\n * childLogger.debug({ action: 'validate' }, 'Validating token');\n * // Output: { app: 'myApp', version: '1.0.0', module: 'auth', action: 'validate', ts: 1234567891 } Validating token\n * ```\n */\nexport enum LogLevel {\n\tTrace = 'trace',\n\tDebug = 'debug',\n\tInfo = 'info',\n\tWarn = 'warn',\n\tError = 'error',\n\tFatal = 'fatal',\n\tSilent = 'silent',\n}\n\n/**\n * Redaction configuration for masking sensitive data in logs.\n * Uses pino's fast-redact library under the hood.\n *\n * By default, custom paths are merged with the default sensitive paths.\n * Use `resolution: 'override'` to use only your custom paths.\n *\n * @example\n * ```typescript\n * // Simple path array (merges with defaults)\n * redact: ['user.ssn', 'custom.field']\n *\n * // Override defaults completely\n * redact: {\n * paths: ['only.these.paths'],\n * resolution: 'override',\n * }\n *\n * // With custom censor\n * redact: {\n * paths: ['extra.secret'],\n * censor: '***',\n * }\n *\n * // Remove fields entirely\n * redact: {\n * paths: ['temporary.data'],\n * remove: true,\n * }\n * ```\n */\nexport type RedactOptions =\n\t| string[]\n\t| {\n\t\t\t/** Paths to redact using dot notation or bracket notation for special chars */\n\t\t\tpaths: string[];\n\t\t\t/** Custom replacement text (default: '[REDACTED]') */\n\t\t\tcensor?: string | ((value: unknown, path: string[]) => unknown);\n\t\t\t/** Remove the field entirely instead of replacing (default: false) */\n\t\t\tremove?: boolean;\n\t\t\t/**\n\t\t\t * How to combine custom paths with default sensitive paths.\n\t\t\t * - 'merge': Custom paths are added to default paths (default)\n\t\t\t * - 'override': Only custom paths are used, defaults are ignored\n\t\t\t */\n\t\t\tresolution?: 'merge' | 'override';\n\t };\n\nexport type CreateLoggerOptions = {\n\t/** Enable pretty printing with colors (disabled in production) */\n\tpretty?: boolean;\n\t/** Minimum log level to output */\n\tlevel?: LogLevel;\n\t/**\n\t * Redaction configuration for masking sensitive data.\n\t *\n\t * - `true` or `undefined` (the default): Uses default sensitive paths\n\t * (password, token, secret, etc.)\n\t * - `false`: No redaction applied — the explicit opt-out\n\t * - `string[]`: Custom paths merged with defaults\n\t * - `object`: Advanced config with paths, censor, remove, and resolution options\n\t *\n\t * By default, custom paths are **merged** with the default sensitive paths.\n\t * Use `resolution: 'override'` to disable defaults and use only your paths.\n\t *\n\t * @example\n\t * ```typescript\n\t * // Use defaults only (the same as leaving `redact` out)\n\t * createLogger({ redact: true });\n\t *\n\t * // Opt out\n\t * createLogger({ redact: false });\n\t *\n\t * // Add custom paths (merged with defaults)\n\t * createLogger({ redact: ['user.ssn', 'custom.field'] });\n\t *\n\t * // Override defaults completely\n\t * createLogger({\n\t * redact: {\n\t * paths: ['only.these.paths'],\n\t * resolution: 'override',\n\t * }\n\t * });\n\t *\n\t * // Merge with custom censor\n\t * createLogger({\n\t * redact: {\n\t * paths: ['extra.secret'],\n\t * censor: '***',\n\t * }\n\t * });\n\t * ```\n\t */\n\tredact?: boolean | RedactOptions;\n\t/**\n\t * Where log lines are written (pino logger only). Defaults to stdout.\n\t * Ignored when `pretty` is on, since the pretty transport owns its output.\n\t */\n\tdestination?: { write(line: string): unknown };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkEA,IAAY,WAAL,yBAAA,UAAA;CACN,SAAA,WAAA;CACA,SAAA,WAAA;CACA,SAAA,UAAA;CACA,SAAA,UAAA;CACA,SAAA,WAAA;CACA,SAAA,WAAA;CACA,SAAA,YAAA;;AACD,EAAA,CAAA,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/logger",
3
- "version": "10.0.0-alpha.14",
3
+ "version": "10.0.0-alpha.141",
4
4
  "description": "A simple and flexible logging library for Node.js and the browser",
5
5
  "private": false,
6
6
  "type": "module",
@@ -35,6 +35,16 @@
35
35
  "default": "./dist/console.cjs"
36
36
  }
37
37
  },
38
+ "./otel": {
39
+ "import": {
40
+ "types": "./dist/otel.d.mts",
41
+ "default": "./dist/otel.mjs"
42
+ },
43
+ "require": {
44
+ "types": "./dist/otel.d.cts",
45
+ "default": "./dist/otel.cjs"
46
+ }
47
+ },
38
48
  "./redact": {
39
49
  "import": {
40
50
  "types": "./dist/redact-paths.d.mts",
@@ -57,8 +67,14 @@
57
67
  "registry": "https://registry.npmjs.org/",
58
68
  "access": "public"
59
69
  },
60
- "dependencies": {},
61
- "devDependencies": {},
70
+ "dependencies": {
71
+ "@opentelemetry/api-logs": "~0.222.0"
72
+ },
73
+ "devDependencies": {
74
+ "@opentelemetry/api": "~1.9.1",
75
+ "@opentelemetry/sdk-logs": "~0.222.0",
76
+ "@opentelemetry/sdk-trace-node": "~2.11.0"
77
+ },
62
78
  "peerDependencies": {
63
79
  "pino-pretty": ">=13.1.3",
64
80
  "pino": ">=10.3.1"
@@ -70,5 +86,8 @@
70
86
  "pino-pretty": {
71
87
  "optional": true
72
88
  }
89
+ },
90
+ "scripts": {
91
+ "build": "tsdown --config ../../tsdown.config.ts --filter ."
73
92
  }
74
93
  }