@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 +66 -29
- package/dist/coalesce.d.ts +10 -0
- package/dist/coalesce.d.ts.map +1 -0
- package/dist/index.cjs +111 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +108 -3
- package/dist/index.js.map +1 -1
- package/dist/symbolicate-stack.d.ts +15 -0
- package/dist/symbolicate-stack.d.ts.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,31 +1,44 @@
|
|
|
1
1
|
# @loggerjs/processors
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> The composable middleware and processor toolbox for LoggerJS — redact, sample, dedupe, rate-limit, fingerprint, enrich, route, and buffer.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@loggerjs/processors)
|
|
6
|
+
[](../../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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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;
|