@loggerjs/processors 0.0.2 → 0.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/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 = [
@@ -59,9 +72,33 @@ 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
+ - `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.
95
+
96
+ ## Documentation
97
+
98
+ - [Processors](../../docs/PROCESSORS.md) — the full reference and ordering guidance
99
+ - [Operations](../../docs/OPERATIONS.md) — privacy defaults and what to redact
100
+ - [Concepts](../../docs/CONCEPTS.md) · [LoggerJS root README](../../README.md)
101
+
102
+ ## License
103
+
104
+ [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
@@ -127,6 +127,58 @@ function contextProcessor(context) {
127
127
  });
128
128
  }
129
129
  //#endregion
130
+ //#region src/coalesce.ts
131
+ function attachCoalesced(event, field, key, state, updateMessage) {
132
+ if (state.count <= 1) return event;
133
+ const payload = {
134
+ key,
135
+ count: state.count,
136
+ firstSeen: state.firstSeen,
137
+ lastSeen: state.lastSeen
138
+ };
139
+ return {
140
+ ...event,
141
+ message: updateMessage ? `${event.message} (x${state.count})` : event.message,
142
+ data: event.data && typeof event.data === "object" && !Array.isArray(event.data) ? {
143
+ ...event.data,
144
+ [field]: payload
145
+ } : {
146
+ value: event.data,
147
+ [field]: payload
148
+ }
149
+ };
150
+ }
151
+ function coalesceProcessor(options = {}) {
152
+ const windowMs = options.windowMs ?? 1e3;
153
+ const maxEntries = options.maxEntries ?? 1e3;
154
+ const field = options.field ?? "coalesced";
155
+ const updateMessage = options.updateMessage ?? true;
156
+ const keyFor = options.key ?? ((event) => `${event.levelName}:${event.message}:${event.error?.message ?? ""}`);
157
+ const seen = /* @__PURE__ */ new Map();
158
+ return (event, context) => {
159
+ const now = context.now();
160
+ const key = keyFor(event);
161
+ const previous = seen.get(key);
162
+ if (previous && now - previous.lastSeen < windowMs) {
163
+ previous.count += 1;
164
+ previous.lastSeen = now;
165
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("processor.coalesce.suppressed");
166
+ return false;
167
+ }
168
+ const next = {
169
+ firstSeen: now,
170
+ lastSeen: now,
171
+ count: 1
172
+ };
173
+ seen.set(key, next);
174
+ if (seen.size > maxEntries) {
175
+ const cutoff = now - windowMs;
176
+ for (const [entryKey, state] of seen) if (state.lastSeen < cutoff || seen.size > maxEntries) seen.delete(entryKey);
177
+ }
178
+ return previous ? attachCoalesced(event, field, key, previous, updateMessage) : event;
179
+ };
180
+ }
181
+ //#endregion
130
182
  //#region src/dedupe.ts
131
183
  function dedupeProcessor(options = {}) {
132
184
  const windowMs = options.windowMs ?? 1e3;
@@ -825,7 +877,7 @@ function normalizeFrames(frames, options) {
825
877
  }
826
878
  return out;
827
879
  }
828
- function writeFrames(event, options, frames) {
880
+ function writeFrames$1(event, options, frames) {
829
881
  if (options.target === "context") return {
830
882
  ...event,
831
883
  context: {
@@ -858,7 +910,60 @@ function stackParserProcessor(options = {}) {
858
910
  const stack = event.error?.stack;
859
911
  if (!stack || normalized.maxFrames === 0) return event;
860
912
  const frames = normalizeFrames(normalized.parser(stack), normalized);
861
- return frames.length > 0 ? writeFrames(event, normalized, frames) : event;
913
+ return frames.length > 0 ? writeFrames$1(event, normalized, frames) : event;
914
+ };
915
+ }
916
+ //#endregion
917
+ //#region src/symbolicate-stack.ts
918
+ function frameListFromEvent(event, sourceKey) {
919
+ const existing = event.error?.[sourceKey];
920
+ if (Array.isArray(existing)) return existing;
921
+ const stack = event.error?.stack;
922
+ return stack ? parseStack(stack) : [];
923
+ }
924
+ function applySymbolication(frame, event, options) {
925
+ const original = options.symbolicate(frame, event);
926
+ if (!original) return frame;
927
+ if (options.mode === "replace") return {
928
+ ...original,
929
+ raw: frame.raw
930
+ };
931
+ return {
932
+ ...frame,
933
+ original
934
+ };
935
+ }
936
+ function writeFrames(event, target, key, frames) {
937
+ if (target === "context") return {
938
+ ...event,
939
+ context: {
940
+ ...event.context,
941
+ [key]: frames
942
+ }
943
+ };
944
+ return {
945
+ ...event,
946
+ error: {
947
+ message: event.error?.message ?? event.message,
948
+ ...event.error,
949
+ [key]: frames
950
+ }
951
+ };
952
+ }
953
+ function symbolicateStackProcessor(options) {
954
+ const maxFrames = Math.max(0, Math.floor(options.maxFrames ?? 20));
955
+ const sourceKey = options.sourceKey ?? "frames";
956
+ const target = options.target ?? "error";
957
+ const key = options.key ?? "symbolicatedFrames";
958
+ const mode = options.mode ?? "annotate";
959
+ return (event) => {
960
+ if (maxFrames === 0) return event;
961
+ const frames = frameListFromEvent(event, sourceKey).slice(0, maxFrames);
962
+ if (frames.length === 0) return event;
963
+ return writeFrames(event, target, key, frames.map((frame) => applySymbolication(frame, event, {
964
+ mode,
965
+ symbolicate: options.symbolicate
966
+ })));
862
967
  };
863
968
  }
864
969
  //#endregion
@@ -1326,6 +1431,8 @@ function breadcrumbBufferProcessor(options = {}) {
1326
1431
  //#endregion
1327
1432
  exports.breadcrumbBuffer = breadcrumbBufferProcessor;
1328
1433
  exports.breadcrumbBufferProcessor = breadcrumbBufferProcessor;
1434
+ exports.coalesce = coalesceProcessor;
1435
+ exports.coalesceProcessor = coalesceProcessor;
1329
1436
  exports.context = contextProcessor;
1330
1437
  exports.contextMiddleware = contextMiddleware;
1331
1438
  exports.contextMw = contextMiddleware;
@@ -1365,6 +1472,8 @@ exports.schemaDevCheck = schemaDevCheckProcessor;
1365
1472
  exports.schemaDevCheckProcessor = schemaDevCheckProcessor;
1366
1473
  exports.stackParser = stackParserProcessor;
1367
1474
  exports.stackParserProcessor = stackParserProcessor;
1475
+ exports.symbolicateStack = symbolicateStackProcessor;
1476
+ exports.symbolicateStackProcessor = symbolicateStackProcessor;
1368
1477
  exports.tags = tagsProcessor;
1369
1478
  exports.tagsMiddleware = tagsMiddleware;
1370
1479
  exports.tagsMw = tagsMiddleware;