data-sanitization-log-providers 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Mark Jubenville
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # data-sanitization-log-providers
2
+
3
+ Pre-built log provider adapters for [`data-sanitization`](https://github.com/ioncache/data-sanitization).
4
+ Each adapter wires `sanitizeData` into the logger's native hook or transport API so you don't
5
+ have to write the glue yourself.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install data-sanitization data-sanitization-log-providers
11
+ ```
12
+
13
+ npm is used as an example — yarn, pnpm, and bun work the same way.
14
+
15
+ Install only the peer dependency for the logger you use:
16
+
17
+ ```bash
18
+ npm install pino # for /pino-hook
19
+ npm install pino pino-abstract-transport # for /pino-transport
20
+ npm install winston # for /winston
21
+ ```
22
+
23
+ ## Pino — stream write hook (`/pino-hook`)
24
+
25
+ Runs synchronously in the main thread. Sanitizes each log line and optionally prepends a
26
+ structured warning entry when sensitive fields are found.
27
+
28
+ ```typescript
29
+ import pino from 'pino';
30
+ import { createSanitizeLogLine } from 'data-sanitization-log-providers/pino-hook';
31
+
32
+ const logger = pino({
33
+ hooks: {
34
+ streamWrite: createSanitizeLogLine({
35
+ // Any DataSanitizationReplacerOptions:
36
+ customPatterns: ['email', 'health_card'],
37
+ // Fields to carry into the warning entry (default: ['time', 'pid', 'hostname']):
38
+ allowedFields: ['time', 'pid', 'hostname'],
39
+ }),
40
+ },
41
+ });
42
+ ```
43
+
44
+ The hook function signature is `(s: string) => string`, matching pino's `hooks.streamWrite`
45
+ contract. When a field is sanitized, a `level: 40` warning line is prepended:
46
+
47
+ ```json
48
+ {"time":1716667200000,"pid":1234,"hostname":"api-1","level":40,"msg":"sensitive data found in log entry","fields":["password"]}
49
+ {"time":1716667200000,"pid":1234,"hostname":"api-1","level":30,"msg":"user login","password":"**********"}
50
+ ```
51
+
52
+ ### Error handling
53
+
54
+ When `sanitizeData` throws, the default behaviour is to emit an error-level (50) JSON
55
+ placeholder preserving `time`, `pid`, and `hostname` — the original line is not emitted.
56
+ Override this with `onError`:
57
+
58
+ ```typescript
59
+ createSanitizeLogLine({
60
+ onError: (err, original) => {
61
+ myErrorTracker.capture(err);
62
+ return '{"level":50,"msg":"sanitization failed"}\n';
63
+ },
64
+ });
65
+ ```
66
+
67
+ ## Pino — worker thread transport (`/pino-transport`)
68
+
69
+ Runs in a `pino.transport()` worker thread via `pino-abstract-transport`. Use this when you
70
+ want process isolation or need to fan out to multiple destinations.
71
+
72
+ ```typescript
73
+ import pino from 'pino';
74
+
75
+ const logger = pino({
76
+ transport: {
77
+ target: 'data-sanitization-log-providers/pino-transport',
78
+ options: {
79
+ customPatterns: ['email'],
80
+ allowedFields: ['time', 'pid', 'hostname'],
81
+ },
82
+ },
83
+ });
84
+ ```
85
+
86
+ **Note:** `customMatchers` (custom matcher functions) is not supported via the transport
87
+ because functions cannot be serialized across the worker-thread boundary. Use the
88
+ `/pino-hook` adapter when custom matcher functions are required.
89
+
90
+ ## Winston (`/winston`)
91
+
92
+ A `TransportStream` subclass that sanitizes each log entry before writing. Compose it with
93
+ `winston.format.json()` (or equivalent) so that `info[Symbol.for('message')]` is populated
94
+ before the transport runs.
95
+
96
+ ```typescript
97
+ import winston from 'winston';
98
+ import { createSanitizingTransport } from 'data-sanitization-log-providers/winston';
99
+
100
+ const logger = winston.createLogger({
101
+ format: winston.format.json(),
102
+ transports: [
103
+ createSanitizingTransport({
104
+ sanitize: { customPatterns: ['email'] },
105
+ }),
106
+ ],
107
+ });
108
+ ```
109
+
110
+ ### Warning lines (`emitWarning`)
111
+
112
+ The `emitWarning` option is disabled by default. When enabled, a structured warning entry is
113
+ written to the output stream immediately before the sanitized line:
114
+
115
+ ```typescript
116
+ createSanitizingTransport({
117
+ emitWarning: true,
118
+ allowedFields: ['timestamp', 'service'],
119
+ });
120
+ ```
121
+
122
+ **Why `emitWarning` is opt-in:** Winston formats are 1-to-1 — one `info` object produces one
123
+ serialized `MESSAGE` string. There is no built-in way to emit a second log line from a format
124
+ transform. This transport subclass owns the write path directly, which makes the two-line
125
+ pattern possible. However, structured log aggregators that expect exactly one JSON object per
126
+ write call may not handle the extra line correctly. Enable this option only when writing to a
127
+ file or stream where embedded newlines are safe.
128
+
129
+ ### Error handling (`onError`)
130
+
131
+ When `sanitizeData` throws, an error-level placeholder is written in place of the original
132
+ line. Provide `onError` to be notified:
133
+
134
+ ```typescript
135
+ createSanitizingTransport({
136
+ onError: (err) => myErrorTracker.capture(err),
137
+ });
138
+ ```
@@ -0,0 +1,23 @@
1
+ import type { PinoHookOptions } from './types.js';
2
+ /**
3
+ * Creates a sanitizing function for pino's `hooks.streamWrite` option.
4
+ *
5
+ * The returned function sanitizes each log line synchronously in the main
6
+ * thread. When sensitive fields are found, a structured warning entry is
7
+ * prepended so the sanitization event is visible in log aggregators. On
8
+ * error, the default handler emits an error-level (50) JSON placeholder
9
+ * preserving `time`, `pid`, and `hostname` for traceability.
10
+ *
11
+ * @param options - Sanitization and hook options.
12
+ * @returns A `(s: string) => string` function for `pino({ hooks: { streamWrite: fn } })`.
13
+ *
14
+ * @example
15
+ * import pino from 'pino';
16
+ * import { createSanitizeLogLine } from 'data-sanitization-log-providers/pino-hook';
17
+ *
18
+ * const logger = pino({
19
+ * hooks: { streamWrite: createSanitizeLogLine({ customPatterns: ['email'] }) },
20
+ * });
21
+ */
22
+ declare function createSanitizeLogLine(options?: PinoHookOptions): (s: string) => string;
23
+ export { createSanitizeLogLine };
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSanitizeLogLine = createSanitizeLogLine;
4
+ const shared_js_1 = require("./shared.js");
5
+ /**
6
+ * Creates a sanitizing function for pino's `hooks.streamWrite` option.
7
+ *
8
+ * The returned function sanitizes each log line synchronously in the main
9
+ * thread. When sensitive fields are found, a structured warning entry is
10
+ * prepended so the sanitization event is visible in log aggregators. On
11
+ * error, the default handler emits an error-level (50) JSON placeholder
12
+ * preserving `time`, `pid`, and `hostname` for traceability.
13
+ *
14
+ * @param options - Sanitization and hook options.
15
+ * @returns A `(s: string) => string` function for `pino({ hooks: { streamWrite: fn } })`.
16
+ *
17
+ * @example
18
+ * import pino from 'pino';
19
+ * import { createSanitizeLogLine } from 'data-sanitization-log-providers/pino-hook';
20
+ *
21
+ * const logger = pino({
22
+ * hooks: { streamWrite: createSanitizeLogLine({ customPatterns: ['email'] }) },
23
+ * });
24
+ */
25
+ function createSanitizeLogLine(options = {}) {
26
+ const { onError, allowedFields = shared_js_1.PINO_DEFAULT_ALLOWED_FIELDS, ...sanitizeOptions } = options;
27
+ return function sanitizeLogLine(s) {
28
+ let result;
29
+ try {
30
+ result = (0, shared_js_1.sanitizeLine)(s, sanitizeOptions, allowedFields);
31
+ }
32
+ catch (err) {
33
+ if (onError) {
34
+ return onError(err, s);
35
+ }
36
+ return (0, shared_js_1.buildErrorPlaceholder)(s) + '\n';
37
+ }
38
+ const { sanitized, warning } = result;
39
+ if (warning) {
40
+ return warning + '\n' + sanitized;
41
+ }
42
+ return sanitized;
43
+ };
44
+ }
45
+ //# sourceMappingURL=pino-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pino-hook.js","sourceRoot":"","sources":["../src/pino-hook.ts"],"names":[],"mappings":";;AAuDS,sDAAqB;AAvD9B,2CAIqB;AAGrB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAS,qBAAqB,CAC5B,UAA2B,EAAE;IAE7B,MAAM,EACJ,OAAO,EACP,aAAa,GAAG,uCAA2B,EAC3C,GAAG,eAAe,EACnB,GAAG,OAAO,CAAC;IAEZ,OAAO,SAAS,eAAe,CAAC,CAAS;QACvC,IAAI,MAAuC,CAAC;QAC5C,IAAI,CAAC;YACH,MAAM,GAAG,IAAA,wBAAY,EAAC,CAAC,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACzB,CAAC;YACD,OAAO,IAAA,iCAAqB,EAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACzC,CAAC;QAED,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QACtC,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;QACpC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { PinoTransportOptions } from './types.js';
2
+ /**
3
+ * Pino `pino-abstract-transport` worker module.
4
+ *
5
+ * Use as the `target` for `pino.transport()`. Sanitizes each log line and
6
+ * writes the result to `process.stdout`. When sensitive fields are found, a
7
+ * structured warning entry is written first so the sanitization event is
8
+ * visible in log aggregators. On error, an error-level (50) JSON placeholder
9
+ * is written in place of the original line.
10
+ *
11
+ * Note: `customMatchers` is not supported because functions cannot be
12
+ * serialized across the worker-thread boundary. Use the `/pino-hook` adapter
13
+ * when custom matcher functions are required.
14
+ *
15
+ * @param opts - Serializable sanitization options passed via `pino.transport({ options: ... })`.
16
+ *
17
+ * @example
18
+ * import pino from 'pino';
19
+ *
20
+ * const logger = pino({
21
+ * transport: {
22
+ * target: 'data-sanitization-log-providers/pino-transport',
23
+ * options: { customPatterns: ['email'] },
24
+ * },
25
+ * });
26
+ */
27
+ export default function pinoTransport(opts?: PinoTransportOptions): Promise<unknown>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = pinoTransport;
7
+ const node_events_1 = require("node:events");
8
+ const pino_abstract_transport_1 = __importDefault(require("pino-abstract-transport"));
9
+ const writeLine = async (line) => {
10
+ if (!process.stdout.write(line + '\n')) {
11
+ await (0, node_events_1.once)(process.stdout, 'drain');
12
+ }
13
+ };
14
+ const shared_js_1 = require("./shared.js");
15
+ /**
16
+ * Pino `pino-abstract-transport` worker module.
17
+ *
18
+ * Use as the `target` for `pino.transport()`. Sanitizes each log line and
19
+ * writes the result to `process.stdout`. When sensitive fields are found, a
20
+ * structured warning entry is written first so the sanitization event is
21
+ * visible in log aggregators. On error, an error-level (50) JSON placeholder
22
+ * is written in place of the original line.
23
+ *
24
+ * Note: `customMatchers` is not supported because functions cannot be
25
+ * serialized across the worker-thread boundary. Use the `/pino-hook` adapter
26
+ * when custom matcher functions are required.
27
+ *
28
+ * @param opts - Serializable sanitization options passed via `pino.transport({ options: ... })`.
29
+ *
30
+ * @example
31
+ * import pino from 'pino';
32
+ *
33
+ * const logger = pino({
34
+ * transport: {
35
+ * target: 'data-sanitization-log-providers/pino-transport',
36
+ * options: { customPatterns: ['email'] },
37
+ * },
38
+ * });
39
+ */
40
+ async function pinoTransport(opts = {}) {
41
+ const { allowedFields = shared_js_1.PINO_DEFAULT_ALLOWED_FIELDS, ...sanitizeOptions } = opts;
42
+ return (0, pino_abstract_transport_1.default)(async function (source) {
43
+ for await (const line of source) {
44
+ try {
45
+ const { sanitized, warning } = (0, shared_js_1.sanitizeLine)(line, sanitizeOptions, allowedFields);
46
+ if (warning) {
47
+ await writeLine(warning);
48
+ }
49
+ await writeLine(sanitized);
50
+ }
51
+ catch {
52
+ await writeLine((0, shared_js_1.buildErrorPlaceholder)(line));
53
+ }
54
+ }
55
+ }, { parse: 'lines' });
56
+ }
57
+ //# sourceMappingURL=pino-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pino-transport.js","sourceRoot":"","sources":["../src/pino-transport.ts"],"names":[],"mappings":";;;;;AAwCA,gCA0BC;AAlED,6CAAmC;AACnC,sFAA4C;AAE5C,MAAM,SAAS,GAAG,KAAK,EAAE,IAAY,EAAiB,EAAE;IACtD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,IAAA,kBAAI,EAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;AACH,CAAC,CAAC;AACF,2CAIqB;AAGrB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACY,KAAK,UAAU,aAAa,CACzC,OAA6B,EAAE;IAE/B,MAAM,EAAE,aAAa,GAAG,uCAA2B,EAAE,GAAG,eAAe,EAAE,GACvE,IAAI,CAAC;IAEP,OAAO,IAAA,iCAAK,EACV,KAAK,WAAW,MAA6B;QAC3C,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAChC,IAAI,CAAC;gBACH,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,IAAA,wBAAY,EACzC,IAAI,EACJ,eAAe,EACf,aAAa,CACd,CAAC;gBACF,IAAI,OAAO,EAAE,CAAC;oBACZ,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;gBAC3B,CAAC;gBACD,MAAM,SAAS,CAAC,SAAS,CAAC,CAAC;YAC7B,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,SAAS,CAAC,IAAA,iCAAqB,EAAC,IAAI,CAAC,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;IACH,CAAC,EACD,EAAE,KAAK,EAAE,OAAO,EAAE,CACnB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { type DataSanitizationReplacerOptions } from 'data-sanitization';
2
+ declare const PINO_DEFAULT_ALLOWED_FIELDS: readonly ["time", "pid", "hostname"];
3
+ interface SanitizeLineResult {
4
+ sanitized: string;
5
+ warning: string | null;
6
+ }
7
+ /**
8
+ * Sanitizes a JSON log line and returns the sanitized string plus an optional
9
+ * warning line.
10
+ *
11
+ * @param line - Raw log line to sanitize (with or without trailing newline).
12
+ * @param sanitizeOptions - Options forwarded to `sanitizeData`.
13
+ * @param allowedFields - Fields to carry into the warning entry.
14
+ * @returns Object with `sanitized` and `warning` (`null` when no fields changed).
15
+ */
16
+ declare function sanitizeLine(line: string, sanitizeOptions: DataSanitizationReplacerOptions, allowedFields: readonly string[]): SanitizeLineResult;
17
+ /**
18
+ * Builds a JSON error-level placeholder from a raw log line, preserving
19
+ * `time`, `pid`, and `hostname` for traceability without re-emitting
20
+ * potentially unsanitized content.
21
+ *
22
+ * @param original - Original log line before sanitization.
23
+ * @returns A JSON string at level 50 indicating sanitization failure.
24
+ */
25
+ declare function buildErrorPlaceholder(original: string): string;
26
+ export { sanitizeLine, buildErrorPlaceholder, PINO_DEFAULT_ALLOWED_FIELDS };
27
+ export type { SanitizeLineResult };
package/dist/shared.js ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PINO_DEFAULT_ALLOWED_FIELDS = void 0;
4
+ exports.sanitizeLine = sanitizeLine;
5
+ exports.buildErrorPlaceholder = buildErrorPlaceholder;
6
+ const data_sanitization_1 = require("data-sanitization");
7
+ const utils_1 = require("data-sanitization/utils");
8
+ const ERROR_LEVEL = 50;
9
+ const ERROR_MSG = 'log entry dropped: sanitization failed';
10
+ const PINO_DEFAULT_ALLOWED_FIELDS = Object.freeze([
11
+ 'time',
12
+ 'pid',
13
+ 'hostname',
14
+ ]);
15
+ exports.PINO_DEFAULT_ALLOWED_FIELDS = PINO_DEFAULT_ALLOWED_FIELDS;
16
+ /**
17
+ * Sanitizes a JSON log line and returns the sanitized string plus an optional
18
+ * warning line.
19
+ *
20
+ * @param line - Raw log line to sanitize (with or without trailing newline).
21
+ * @param sanitizeOptions - Options forwarded to `sanitizeData`.
22
+ * @param allowedFields - Fields to carry into the warning entry.
23
+ * @returns Object with `sanitized` and `warning` (`null` when no fields changed).
24
+ */
25
+ function sanitizeLine(line, sanitizeOptions, allowedFields) {
26
+ const sanitized = (0, data_sanitization_1.sanitizeData)(line, sanitizeOptions);
27
+ const warning = sanitized !== line
28
+ ? (0, utils_1.buildSanitizedWarning)(line, sanitized, { allowedFields })
29
+ : null;
30
+ return { sanitized, warning };
31
+ }
32
+ /**
33
+ * Builds a JSON error-level placeholder from a raw log line, preserving
34
+ * `time`, `pid`, and `hostname` for traceability without re-emitting
35
+ * potentially unsanitized content.
36
+ *
37
+ * @param original - Original log line before sanitization.
38
+ * @returns A JSON string at level 50 indicating sanitization failure.
39
+ */
40
+ function buildErrorPlaceholder(original) {
41
+ try {
42
+ const parsed = JSON.parse(original);
43
+ const { time, pid, hostname } = parsed;
44
+ return JSON.stringify({
45
+ level: ERROR_LEVEL,
46
+ ...(time !== undefined && { time }),
47
+ ...(pid !== undefined && { pid }),
48
+ ...(hostname !== undefined && { hostname }),
49
+ msg: ERROR_MSG,
50
+ });
51
+ }
52
+ catch {
53
+ /* istanbul ignore next -- only reached when original is not valid JSON */
54
+ return JSON.stringify({ level: ERROR_LEVEL, msg: ERROR_MSG });
55
+ }
56
+ }
57
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":";;;AAkES,oCAAY;AAAE,sDAAqB;AAlE5C,yDAG2B;AAC3B,mDAAgE;AAEhE,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,SAAS,GAAG,wCAAwC,CAAC;AAC3D,MAAM,2BAA2B,GAAG,MAAM,CAAC,MAAM,CAAC;IAChD,MAAM;IACN,KAAK;IACL,UAAU;CACF,CAAC,CAAC;AAsDkC,kEAA2B;AA/CzE;;;;;;;;GAQG;AACH,SAAS,YAAY,CACnB,IAAY,EACZ,eAAgD,EAChD,aAAgC;IAEhC,MAAM,SAAS,GAAG,IAAA,gCAAY,EAAC,IAAI,EAAE,eAAe,CAAW,CAAC;IAChE,MAAM,OAAO,GACX,SAAS,KAAK,IAAI;QAChB,CAAC,CAAC,IAAA,6BAAqB,EAAC,IAAI,EAAE,SAAS,EAAE,EAAE,aAAa,EAAE,CAAC;QAC3D,CAAC,CAAC,IAAI,CAAC;IACX,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAChC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,qBAAqB,CAAC,QAAgB;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAA4B,CAAC;QAC/D,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;QACvC,OAAO,IAAI,CAAC,SAAS,CAAC;YACpB,KAAK,EAAE,WAAW;YAClB,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;YACnC,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,GAAG,EAAE,CAAC;YACjC,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,CAAC;YAC3C,GAAG,EAAE,SAAS;SACf,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC"}
@@ -0,0 +1,79 @@
1
+ import type { DataSanitizationReplacerOptions } from 'data-sanitization';
2
+ /**
3
+ * Options for {@link createSanitizeLogLine}.
4
+ */
5
+ interface PinoHookOptions extends DataSanitizationReplacerOptions {
6
+ /**
7
+ * Fields from the sanitized log object to carry into the warning entry.
8
+ *
9
+ * Default: `['time', 'pid', 'hostname']`.
10
+ */
11
+ allowedFields?: readonly string[];
12
+ /**
13
+ * Called when `sanitizeData` throws. Must return a string to write in place
14
+ * of the original log line. Default: emits an error-level (50) JSON
15
+ * placeholder preserving `time`, `pid`, and `hostname` from the original.
16
+ */
17
+ onError?: (err: unknown, original: string) => string;
18
+ }
19
+ /**
20
+ * Options for the pino transport default export.
21
+ *
22
+ * Extends {@link DataSanitizationReplacerOptions} except for `customMatchers`,
23
+ * which cannot be serialized across the worker-thread boundary. Use the
24
+ * `/pino-hook` adapter when custom matcher functions are required.
25
+ */
26
+ type PinoTransportOptions = Omit<DataSanitizationReplacerOptions, 'customMatchers'> & {
27
+ /**
28
+ * Fields from the sanitized log object to carry into the warning entry.
29
+ *
30
+ * Default: `['time', 'pid', 'hostname']`.
31
+ */
32
+ allowedFields?: readonly string[];
33
+ };
34
+ /**
35
+ * Options for {@link createSanitizingTransport}.
36
+ */
37
+ interface WinstonSanitizationOptions {
38
+ /** Standard winston transport options (level, silent, handleExceptions). */
39
+ level?: string;
40
+ silent?: boolean;
41
+ handleExceptions?: boolean;
42
+ handleRejections?: boolean;
43
+ /**
44
+ * Sanitization options forwarded to `sanitizeData`.
45
+ */
46
+ sanitize?: DataSanitizationReplacerOptions;
47
+ /**
48
+ * Fields from the sanitized log object to carry into the warning entry.
49
+ *
50
+ * Default: `[]` — no fields carry through unless explicitly listed. Winston
51
+ * has no standardized correlation-field convention, so callers opt in
52
+ * explicitly (e.g. `['timestamp', 'service']`).
53
+ */
54
+ allowedFields?: readonly string[];
55
+ /**
56
+ * When `true`, a structured warning line is written to the output stream
57
+ * immediately before the sanitized line as a separate `stream.write()` call.
58
+ *
59
+ * Winston formats are 1-to-1 (one `info` object → one serialized `MESSAGE`
60
+ * string), so there is no built-in way to emit a second log line from a
61
+ * format transform. This transport subclass owns the write path, which makes
62
+ * the two-line pattern possible. Enable only when writing to a file or
63
+ * stream — structured aggregators that expect exactly one JSON object per
64
+ * write call may not handle the extra line correctly.
65
+ *
66
+ * Default: `false`.
67
+ */
68
+ emitWarning?: boolean;
69
+ /**
70
+ * Output stream. Default: `process.stdout`.
71
+ */
72
+ stream?: NodeJS.WritableStream;
73
+ /**
74
+ * Called when `sanitizeData` throws. Default: emits an error-level
75
+ * placeholder to the output stream and continues processing.
76
+ */
77
+ onError?: (err: unknown) => void;
78
+ }
79
+ export type { PinoHookOptions, PinoTransportOptions, WinstonSanitizationOptions, };
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ import TransportStream from 'winston-transport';
2
+ import type { WinstonSanitizationOptions } from './types.js';
3
+ /**
4
+ * A winston `TransportStream` that sanitizes log entries before writing them.
5
+ *
6
+ * Reads `info[Symbol.for('message')]` (the final serialized string produced by
7
+ * upstream formats such as `format.json()`), sanitizes it, and writes the
8
+ * result to the configured output stream. When `emitWarning` is enabled, a
9
+ * structured warning line is written first as a separate `stream.write()` call.
10
+ *
11
+ * Add this transport after a serializing format in the format chain so that
12
+ * `info[MESSAGE]` is already a JSON string when `log()` is called.
13
+ */
14
+ declare class SanitizingTransport extends TransportStream {
15
+ #private;
16
+ constructor(opts?: WinstonSanitizationOptions);
17
+ log(info: any, callback: () => void): void;
18
+ }
19
+ /**
20
+ * Creates a sanitizing winston transport.
21
+ *
22
+ * Sanitizes each log entry's serialized `MESSAGE` string before writing it to
23
+ * the output stream. Compose with `winston.format.json()` (or equivalent) so
24
+ * that the `MESSAGE` symbol is populated before sanitization runs.
25
+ *
26
+ * @param options - Transport and sanitization options.
27
+ * @returns A configured {@link SanitizingTransport} instance.
28
+ *
29
+ * @example
30
+ * import winston from 'winston';
31
+ * import { createSanitizingTransport } from 'data-sanitization-log-providers/winston';
32
+ *
33
+ * const logger = winston.createLogger({
34
+ * format: winston.format.json(),
35
+ * transports: [
36
+ * createSanitizingTransport({
37
+ * sanitize: { customPatterns: ['email'] },
38
+ * allowedFields: ['timestamp', 'service'],
39
+ * emitWarning: true,
40
+ * }),
41
+ * ],
42
+ * });
43
+ */
44
+ declare function createSanitizingTransport(options?: WinstonSanitizationOptions): SanitizingTransport;
45
+ export { createSanitizingTransport, SanitizingTransport };
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SanitizingTransport = void 0;
7
+ exports.createSanitizingTransport = createSanitizingTransport;
8
+ const winston_transport_1 = __importDefault(require("winston-transport"));
9
+ const shared_js_1 = require("./shared.js");
10
+ const MESSAGE = Symbol.for('message');
11
+ /**
12
+ * A winston `TransportStream` that sanitizes log entries before writing them.
13
+ *
14
+ * Reads `info[Symbol.for('message')]` (the final serialized string produced by
15
+ * upstream formats such as `format.json()`), sanitizes it, and writes the
16
+ * result to the configured output stream. When `emitWarning` is enabled, a
17
+ * structured warning line is written first as a separate `stream.write()` call.
18
+ *
19
+ * Add this transport after a serializing format in the format chain so that
20
+ * `info[MESSAGE]` is already a JSON string when `log()` is called.
21
+ */
22
+ class SanitizingTransport extends winston_transport_1.default {
23
+ #sanitizeOptions;
24
+ #allowedFields;
25
+ #emitWarning;
26
+ #dest;
27
+ #onError;
28
+ constructor(opts = {}) {
29
+ const { sanitize = {}, allowedFields = [], emitWarning = false, stream: dest = process.stdout, onError, ...rest } = opts;
30
+ super(rest);
31
+ this.#sanitizeOptions = sanitize;
32
+ this.#allowedFields = allowedFields;
33
+ this.#emitWarning = emitWarning;
34
+ this.#dest = dest;
35
+ this.#onError = onError ?? (() => undefined);
36
+ }
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ log(info, callback) {
39
+ const raw = info[MESSAGE];
40
+ let sanitized;
41
+ let warning = null;
42
+ try {
43
+ const input = typeof raw === 'string' ? raw : JSON.stringify(info);
44
+ ({ sanitized, warning } = (0, shared_js_1.sanitizeLine)(input, this.#sanitizeOptions, this.#allowedFields));
45
+ }
46
+ catch (err) {
47
+ this.#onError(err);
48
+ this.#dest.write((0, shared_js_1.buildErrorPlaceholder)(raw ?? '') + '\n');
49
+ this.emit('logged', info);
50
+ callback();
51
+ return;
52
+ }
53
+ if (this.#emitWarning && warning) {
54
+ this.#dest.write(warning + '\n');
55
+ }
56
+ this.#dest.write(sanitized + '\n');
57
+ this.emit('logged', info);
58
+ callback();
59
+ }
60
+ }
61
+ exports.SanitizingTransport = SanitizingTransport;
62
+ /**
63
+ * Creates a sanitizing winston transport.
64
+ *
65
+ * Sanitizes each log entry's serialized `MESSAGE` string before writing it to
66
+ * the output stream. Compose with `winston.format.json()` (or equivalent) so
67
+ * that the `MESSAGE` symbol is populated before sanitization runs.
68
+ *
69
+ * @param options - Transport and sanitization options.
70
+ * @returns A configured {@link SanitizingTransport} instance.
71
+ *
72
+ * @example
73
+ * import winston from 'winston';
74
+ * import { createSanitizingTransport } from 'data-sanitization-log-providers/winston';
75
+ *
76
+ * const logger = winston.createLogger({
77
+ * format: winston.format.json(),
78
+ * transports: [
79
+ * createSanitizingTransport({
80
+ * sanitize: { customPatterns: ['email'] },
81
+ * allowedFields: ['timestamp', 'service'],
82
+ * emitWarning: true,
83
+ * }),
84
+ * ],
85
+ * });
86
+ */
87
+ function createSanitizingTransport(options = {}) {
88
+ return new SanitizingTransport(options);
89
+ }
90
+ //# sourceMappingURL=winston.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"winston.js","sourceRoot":"","sources":["../src/winston.ts"],"names":[],"mappings":";;;;;;AA0GS,8DAAyB;AA1GlC,0EAAgD;AAChD,2CAAkE;AAIlE,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAEtC;;;;;;;;;;GAUG;AACH,MAAM,mBAAoB,SAAQ,2BAAe;IACtC,gBAAgB,CAAkC;IAClD,cAAc,CAAoB;IAClC,YAAY,CAAU;IACtB,KAAK,CAAwB;IAC7B,QAAQ,CAAyB;IAE1C,YAAY,OAAmC,EAAE;QAC/C,MAAM,EACJ,QAAQ,GAAG,EAAE,EACb,aAAa,GAAG,EAAE,EAClB,WAAW,GAAG,KAAK,EACnB,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAC7B,OAAO,EACP,GAAG,IAAI,EACR,GAAG,IAAI,CAAC;QACT,KAAK,CAAC,IAAwD,CAAC,CAAC;QAChE,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAC;QACjC,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;QACpC,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,QAAQ,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/C,CAAC;IAED,8DAA8D;IACrD,GAAG,CAAC,IAAS,EAAE,QAAoB;QAC1C,MAAM,GAAG,GAAI,IAAgC,CAAC,OAAO,CAExC,CAAC;QAEd,IAAI,SAAiB,CAAC;QACtB,IAAI,OAAO,GAAkB,IAAI,CAAC;QAElC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACnE,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,IAAA,wBAAY,EACpC,KAAK,EACL,IAAI,CAAC,gBAAgB,EACrB,IAAI,CAAC,cAAc,CACpB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACnB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAA,iCAAqB,EAAC,GAAG,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YAC1D,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC1B,QAAQ,EAAE,CAAC;YACX,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,IAAI,OAAO,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC1B,QAAQ,EAAE,CAAC;IACb,CAAC;CACF;AAiCmC,kDAAmB;AA/BvD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,SAAS,yBAAyB,CAChC,UAAsC,EAAE;IAExC,OAAO,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "data-sanitization-log-providers",
3
+ "version": "1.1.0",
4
+ "description": "Pre-built log provider adapters for data-sanitization (pino, winston).",
5
+ "keywords": [
6
+ "data-sanitization",
7
+ "logging",
8
+ "pino",
9
+ "redact",
10
+ "sanitization",
11
+ "sanitize",
12
+ "sensitive-data",
13
+ "typescript",
14
+ "winston"
15
+ ],
16
+ "homepage": "https://github.com/ioncache/data-sanitization/tree/main/packages/data-sanitization-log-providers",
17
+ "bugs": {
18
+ "url": "https://github.com/ioncache/data-sanitization/issues"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Mark Jubenville <ioncache@gmail.com>",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/ioncache/data-sanitization.git"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "exports": {
32
+ "./pino-hook": {
33
+ "types": "./dist/pino-hook.d.ts",
34
+ "default": "./dist/pino-hook.js"
35
+ },
36
+ "./pino-transport": {
37
+ "types": "./dist/pino-transport.d.ts",
38
+ "default": "./dist/pino-transport.js"
39
+ },
40
+ "./winston": {
41
+ "types": "./dist/winston.d.ts",
42
+ "default": "./dist/winston.js"
43
+ }
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "scripts": {
49
+ "build": "yarn clean && tsc -p tsconfig.build.json",
50
+ "clean": "rm -rf dist coverage && find . -maxdepth 1 -name '*.tsbuildinfo' -delete",
51
+ "format": "oxfmt",
52
+ "format:check": "oxfmt --check",
53
+ "lint": "oxlint",
54
+ "lint:fix": "oxlint --fix",
55
+ "prepack": "yarn build",
56
+ "test": "vitest run",
57
+ "test:coverage": "vitest run --coverage",
58
+ "test:watch": "vitest"
59
+ },
60
+ "devDependencies": {
61
+ "@types/node": "^25.9.0",
62
+ "@vitest/coverage-v8": "^4.1.6",
63
+ "data-sanitization": "1.5.0",
64
+ "oxfmt": "^0.51.0",
65
+ "oxlint": "^1.66.0",
66
+ "pino": "^9.0.0",
67
+ "pino-abstract-transport": "^2.0.0",
68
+ "typescript": "^6.0.3",
69
+ "vitest": "^4.1.6",
70
+ "winston": "^3.19.0",
71
+ "winston-transport": "^4.9.0"
72
+ },
73
+ "peerDependencies": {
74
+ "data-sanitization": ">=1.5.0",
75
+ "pino": ">=9.0.0",
76
+ "pino-abstract-transport": ">=2.0.0",
77
+ "winston": ">=3.0.0"
78
+ },
79
+ "peerDependenciesMeta": {
80
+ "pino": {
81
+ "optional": true
82
+ },
83
+ "pino-abstract-transport": {
84
+ "optional": true
85
+ },
86
+ "winston": {
87
+ "optional": true
88
+ }
89
+ },
90
+ "engines": {
91
+ "node": ">=22.22.1"
92
+ },
93
+ "volta": {
94
+ "node": "22.22.3",
95
+ "yarn": "4.15.0"
96
+ }
97
+ }