@loggerjs/processors 0.0.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,31 +1,44 @@
1
1
  # @loggerjs/processors
2
2
 
3
- Compatibility processor package for common synchronous middleware behavior.
3
+ > The composable middleware and processor toolbox for LoggerJS — redact, sample, dedupe, rate-limit, fingerprint, enrich, route, and buffer.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@loggerjs/processors.svg)](https://www.npmjs.com/package/@loggerjs/processors)
6
+ [![license](https://img.shields.io/npm/l/@loggerjs/processors)](../../LICENSE)
7
+
8
+ Synchronous, error-isolated steps that run inside the [LoggerJS](../../README.md) pipeline before delivery. **Middleware** run on the raw `LogRecord` (before id/message/error work); **processors** run on the projected `LogEvent`. Both are sandboxed — a throwing step is reported to logger meta and never corrupts other records or blocks delivery.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @loggerjs/processors
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ```ts
19
+ import { createLogger } from "@loggerjs/node";
20
+ import { redactProcessor, sampleProcessor, tagsMiddleware } from "@loggerjs/processors";
21
+
22
+ const logger = createLogger({
23
+ category: ["api"],
24
+ middleware: [tagsMiddleware({ service: "checkout" })],
25
+ processors: [
26
+ redactProcessor({ keys: ["password", "token", /secret/i] }),
27
+ sampleProcessor({ rates: { debug: 0.1, info: 1, warn: 1, error: 1, fatal: 1 } }),
28
+ ],
29
+ });
30
+ ```
31
+
32
+ <details>
33
+ <summary>Full example — middleware and processors side by side</summary>
4
34
 
5
35
  ```ts
6
36
  import {
7
- contextMiddleware,
8
- enrichMiddleware,
9
- tagsMiddleware,
10
- traceContextMiddleware,
11
- typeMiddleware,
12
- breadcrumbBufferProcessor,
13
- dedupeProcessor,
14
- dynamicSamplerProcessor,
15
- enrichProcessor,
16
- filterProcessor,
17
- fingerprintProcessor,
18
- fingersCrossedProcessor,
19
- levelOverrideProcessor,
20
- normalizeErrorProcessor,
21
- privacyGuardProcessor,
22
- rateLimitProcessor,
23
- redactProcessor,
24
- routeProcessor,
25
- sampleProcessor,
26
- schemaDevCheckProcessor,
27
- stackParserProcessor,
28
- tagsProcessor,
37
+ contextMiddleware, enrichMiddleware, tagsMiddleware, traceContextMiddleware, typeMiddleware,
38
+ breadcrumbBufferProcessor, dedupeProcessor, dynamicSamplerProcessor, enrichProcessor,
39
+ filterProcessor, fingerprintProcessor, fingersCrossedProcessor, levelOverrideProcessor,
40
+ normalizeErrorProcessor, privacyGuardProcessor, rateLimitProcessor, redactProcessor,
41
+ routeProcessor, sampleProcessor, schemaDevCheckProcessor, stackParserProcessor, tagsProcessor,
29
42
  } from "@loggerjs/processors";
30
43
 
31
44
  const middleware = [
@@ -37,7 +50,7 @@ const middleware = [
37
50
  ];
38
51
 
39
52
  const processors = [
40
- redactProcessor({ keys: ["password", "token", /secret/i] }),
53
+ redactProcessor({ keys: ["password", "token", /secret/i], censor: "[hidden]" }),
41
54
  privacyGuardProcessor({ maxStringLength: 8192, allowKeys: ["publicToken"] }),
42
55
  schemaDevCheckProcessor({
43
56
  validators: { "order.created": (data) => (typeof data === "object" && data ? true : "bad payload") },
@@ -59,9 +72,34 @@ const processors = [
59
72
  ];
60
73
  ```
61
74
 
62
- Prefer record middleware for metadata and enrichment that can run before event projection. Legacy
63
- processors remain available for compatibility and for event-only behavior such as routing, schema
64
- checks, sampling, buffering, and filtering. Expensive I/O and serialization belong in transports.
65
- `fingersCrossedProcessor` can receive a `flushTo` transport or sink when buffered pre-trigger events
66
- must be replayed. `routeProcessor` targets transport names, so configure named transports when using
67
- per-event routing.
75
+ </details>
76
+
77
+ ## The toolbox
78
+
79
+ | Group | Steps |
80
+ | --- | --- |
81
+ | **Redaction & privacy** | `redactProcessor`, `privacyGuardProcessor` |
82
+ | **Sampling & volume** | `sampleProcessor`, `dynamicSamplerProcessor`, `rateLimitProcessor`, `dedupeProcessor`, `coalesceProcessor` |
83
+ | **Enrichment & tagging** | `enrichProcessor` / `enrichMiddleware`, `tagsProcessor` / `tagsMiddleware`, `typeProcessor` / `typeMiddleware`, `contextProcessor` / `contextMiddleware`, `traceContextProcessor` / `traceContextMiddleware` |
84
+ | **Errors** | `normalizeErrorProcessor`, `fingerprintProcessor`, `stackParserProcessor`, `symbolicateStackProcessor` |
85
+ | **Routing & control** | `routeProcessor`, `filterProcessor`, `levelOverrideProcessor` |
86
+ | **Buffering** | `fingersCrossedProcessor`, `breadcrumbBufferProcessor` |
87
+ | **Development** | `schemaDevCheckProcessor` |
88
+
89
+ ## Ordering & cost
90
+
91
+ - **Prefer middleware** for metadata and enrichment that can run before event projection — it's the cheapest place to drop or annotate a record.
92
+ - **Use processors** for event-only behavior: routing, schema checks, sampling, buffering, and filtering on the resolved event shape.
93
+ - **Configuring any processor disables the record fast path** for that logger, because every log must then be projected to an event. That is the correct trade when you need event-level behavior — see [PROCESSORS.md](../../docs/PROCESSORS.md) and [PERFORMANCE.md](../../docs/PERFORMANCE.md).
94
+ - `redactProcessor()` supports exact `keys`/`paths`, regex/custom matchers, the Pino-compatible `censor` alias, and `remove: true` for omitting matched object fields. It does not compile user paths with `eval` or `new Function`.
95
+ - `fingersCrossedProcessor` accepts a `flushTo` transport or sink to replay buffered pre-trigger events. `routeProcessor` targets **named** transports, so name the transports you route to. Expensive I/O and serialization belong in transports, not processors.
96
+
97
+ ## Documentation
98
+
99
+ - [Processors](../../docs/PROCESSORS.md) — the full reference and ordering guidance
100
+ - [Operations](../../docs/OPERATIONS.md) — privacy defaults and what to redact
101
+ - [Concepts](../../docs/CONCEPTS.md) · [LoggerJS root README](../../README.md)
102
+
103
+ ## License
104
+
105
+ [MIT](../../LICENSE) © JS Kits
@@ -0,0 +1,10 @@
1
+ import { type LogEvent, type Processor } from "@loggerjs/core";
2
+ export interface CoalesceOptions {
3
+ windowMs?: number;
4
+ maxEntries?: number;
5
+ key?: (event: LogEvent) => string;
6
+ field?: string;
7
+ updateMessage?: boolean;
8
+ }
9
+ export declare function coalesceProcessor(options?: CoalesceOptions): Processor;
10
+ //# sourceMappingURL=coalesce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coalesce.d.ts","sourceRoot":"","sources":["../src/coalesce.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,MAAM,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAgCD,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,eAAoB,GAAG,SAAS,CAqC1E"}
package/dist/index.cjs CHANGED
@@ -4,7 +4,12 @@ let _loggerjs_core = require("@loggerjs/core");
4
4
  function matchesKey(key, path, value, matchers) {
5
5
  return matchers.some((matcher) => {
6
6
  if (typeof matcher === "string") return matcher.toLowerCase() === key.toLowerCase();
7
- if (matcher instanceof RegExp) return matcher.test(key) || matcher.test(path);
7
+ if (matcher instanceof RegExp) {
8
+ matcher.lastIndex = 0;
9
+ const keyMatches = matcher.test(key);
10
+ matcher.lastIndex = 0;
11
+ return keyMatches || matcher.test(path);
12
+ }
8
13
  return matcher(key, path, value);
9
14
  });
10
15
  }
@@ -29,8 +34,10 @@ function redactValue(value, options, path = "", depth = 0, seen = /* @__PURE__ *
29
34
  seen.set(value, out);
30
35
  for (const [key, child] of Object.entries(input)) {
31
36
  const childPath = path ? `${path}.${key}` : key;
32
- if (matchesKey(key, childPath, child, options.keys ?? []) || pathMatches(childPath, options.paths ?? [])) out[key] = options.replacement;
33
- else out[key] = redactValue(child, options, childPath, depth + 1, seen);
37
+ if (matchesKey(key, childPath, child, options.keys ?? []) || pathMatches(childPath, options.paths ?? [])) {
38
+ if (options.remove) continue;
39
+ out[key] = options.replacement;
40
+ } else out[key] = redactValue(child, options, childPath, depth + 1, seen);
34
41
  }
35
42
  return out;
36
43
  }
@@ -48,7 +55,8 @@ function redactProcessor(options = {}) {
48
55
  "api_key"
49
56
  ],
50
57
  paths: options.paths ?? [],
51
- replacement: options.replacement ?? "[REDACTED]",
58
+ replacement: options.replacement ?? options.censor ?? "[REDACTED]",
59
+ remove: options.remove ?? false,
52
60
  maxDepth: options.maxDepth ?? 8
53
61
  };
54
62
  return (event) => ({
@@ -127,6 +135,58 @@ function contextProcessor(context) {
127
135
  });
128
136
  }
129
137
  //#endregion
138
+ //#region src/coalesce.ts
139
+ function attachCoalesced(event, field, key, state, updateMessage) {
140
+ if (state.count <= 1) return event;
141
+ const payload = {
142
+ key,
143
+ count: state.count,
144
+ firstSeen: state.firstSeen,
145
+ lastSeen: state.lastSeen
146
+ };
147
+ return {
148
+ ...event,
149
+ message: updateMessage ? `${event.message} (x${state.count})` : event.message,
150
+ data: event.data && typeof event.data === "object" && !Array.isArray(event.data) ? {
151
+ ...event.data,
152
+ [field]: payload
153
+ } : {
154
+ value: event.data,
155
+ [field]: payload
156
+ }
157
+ };
158
+ }
159
+ function coalesceProcessor(options = {}) {
160
+ const windowMs = options.windowMs ?? 1e3;
161
+ const maxEntries = options.maxEntries ?? 1e3;
162
+ const field = options.field ?? "coalesced";
163
+ const updateMessage = options.updateMessage ?? true;
164
+ const keyFor = options.key ?? ((event) => `${event.levelName}:${event.message}:${event.error?.message ?? ""}`);
165
+ const seen = /* @__PURE__ */ new Map();
166
+ return (event, context) => {
167
+ const now = context.now();
168
+ const key = keyFor(event);
169
+ const previous = seen.get(key);
170
+ if (previous && now - previous.lastSeen < windowMs) {
171
+ previous.count += 1;
172
+ previous.lastSeen = now;
173
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("processor.coalesce.suppressed");
174
+ return false;
175
+ }
176
+ const next = {
177
+ firstSeen: now,
178
+ lastSeen: now,
179
+ count: 1
180
+ };
181
+ seen.set(key, next);
182
+ if (seen.size > maxEntries) {
183
+ const cutoff = now - windowMs;
184
+ for (const [entryKey, state] of seen) if (state.lastSeen < cutoff || seen.size > maxEntries) seen.delete(entryKey);
185
+ }
186
+ return previous ? attachCoalesced(event, field, key, previous, updateMessage) : event;
187
+ };
188
+ }
189
+ //#endregion
130
190
  //#region src/dedupe.ts
131
191
  function dedupeProcessor(options = {}) {
132
192
  const windowMs = options.windowMs ?? 1e3;
@@ -825,7 +885,7 @@ function normalizeFrames(frames, options) {
825
885
  }
826
886
  return out;
827
887
  }
828
- function writeFrames(event, options, frames) {
888
+ function writeFrames$1(event, options, frames) {
829
889
  if (options.target === "context") return {
830
890
  ...event,
831
891
  context: {
@@ -858,7 +918,60 @@ function stackParserProcessor(options = {}) {
858
918
  const stack = event.error?.stack;
859
919
  if (!stack || normalized.maxFrames === 0) return event;
860
920
  const frames = normalizeFrames(normalized.parser(stack), normalized);
861
- return frames.length > 0 ? writeFrames(event, normalized, frames) : event;
921
+ return frames.length > 0 ? writeFrames$1(event, normalized, frames) : event;
922
+ };
923
+ }
924
+ //#endregion
925
+ //#region src/symbolicate-stack.ts
926
+ function frameListFromEvent(event, sourceKey) {
927
+ const existing = event.error?.[sourceKey];
928
+ if (Array.isArray(existing)) return existing;
929
+ const stack = event.error?.stack;
930
+ return stack ? parseStack(stack) : [];
931
+ }
932
+ function applySymbolication(frame, event, options) {
933
+ const original = options.symbolicate(frame, event);
934
+ if (!original) return frame;
935
+ if (options.mode === "replace") return {
936
+ ...original,
937
+ raw: frame.raw
938
+ };
939
+ return {
940
+ ...frame,
941
+ original
942
+ };
943
+ }
944
+ function writeFrames(event, target, key, frames) {
945
+ if (target === "context") return {
946
+ ...event,
947
+ context: {
948
+ ...event.context,
949
+ [key]: frames
950
+ }
951
+ };
952
+ return {
953
+ ...event,
954
+ error: {
955
+ message: event.error?.message ?? event.message,
956
+ ...event.error,
957
+ [key]: frames
958
+ }
959
+ };
960
+ }
961
+ function symbolicateStackProcessor(options) {
962
+ const maxFrames = Math.max(0, Math.floor(options.maxFrames ?? 20));
963
+ const sourceKey = options.sourceKey ?? "frames";
964
+ const target = options.target ?? "error";
965
+ const key = options.key ?? "symbolicatedFrames";
966
+ const mode = options.mode ?? "annotate";
967
+ return (event) => {
968
+ if (maxFrames === 0) return event;
969
+ const frames = frameListFromEvent(event, sourceKey).slice(0, maxFrames);
970
+ if (frames.length === 0) return event;
971
+ return writeFrames(event, target, key, frames.map((frame) => applySymbolication(frame, event, {
972
+ mode,
973
+ symbolicate: options.symbolicate
974
+ })));
862
975
  };
863
976
  }
864
977
  //#endregion
@@ -1326,6 +1439,8 @@ function breadcrumbBufferProcessor(options = {}) {
1326
1439
  //#endregion
1327
1440
  exports.breadcrumbBuffer = breadcrumbBufferProcessor;
1328
1441
  exports.breadcrumbBufferProcessor = breadcrumbBufferProcessor;
1442
+ exports.coalesce = coalesceProcessor;
1443
+ exports.coalesceProcessor = coalesceProcessor;
1329
1444
  exports.context = contextProcessor;
1330
1445
  exports.contextMiddleware = contextMiddleware;
1331
1446
  exports.contextMw = contextMiddleware;
@@ -1365,6 +1480,8 @@ exports.schemaDevCheck = schemaDevCheckProcessor;
1365
1480
  exports.schemaDevCheckProcessor = schemaDevCheckProcessor;
1366
1481
  exports.stackParser = stackParserProcessor;
1367
1482
  exports.stackParserProcessor = stackParserProcessor;
1483
+ exports.symbolicateStack = symbolicateStackProcessor;
1484
+ exports.symbolicateStackProcessor = symbolicateStackProcessor;
1368
1485
  exports.tags = tagsProcessor;
1369
1486
  exports.tagsMiddleware = tagsMiddleware;
1370
1487
  exports.tagsMw = tagsMiddleware;