@spinajs/log-common 2.0.480 → 2.0.482
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 +125 -4
- package/lib/cjs/BatchQueue.d.ts +127 -0
- package/lib/cjs/BatchQueue.d.ts.map +1 -0
- package/lib/cjs/BatchQueue.js +126 -0
- package/lib/cjs/BatchQueue.js.map +1 -0
- package/lib/cjs/filters/filter.d.ts +27 -0
- package/lib/cjs/filters/filter.d.ts.map +1 -0
- package/lib/cjs/filters/filter.js +20 -0
- package/lib/cjs/filters/filter.js.map +1 -0
- package/lib/cjs/filters/whenRepeated.d.ts +52 -0
- package/lib/cjs/filters/whenRepeated.d.ts.map +1 -0
- package/lib/cjs/filters/whenRepeated.js +98 -0
- package/lib/cjs/filters/whenRepeated.js.map +1 -0
- package/lib/cjs/format.d.ts +20 -0
- package/lib/cjs/format.d.ts.map +1 -0
- package/lib/cjs/format.js +75 -0
- package/lib/cjs/format.js.map +1 -0
- package/lib/cjs/index.d.ts +260 -21
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +207 -33
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/perf.d.ts +109 -0
- package/lib/cjs/perf.d.ts.map +1 -0
- package/lib/cjs/perf.js +162 -0
- package/lib/cjs/perf.js.map +1 -0
- package/lib/cjs/persistence.d.ts +19 -0
- package/lib/cjs/persistence.d.ts.map +1 -0
- package/lib/cjs/persistence.js +137 -0
- package/lib/cjs/persistence.js.map +1 -0
- package/lib/cjs/serializers.d.ts +77 -0
- package/lib/cjs/serializers.d.ts.map +1 -0
- package/lib/cjs/serializers.js +215 -0
- package/lib/cjs/serializers.js.map +1 -0
- package/lib/mjs/BatchQueue.d.ts +127 -0
- package/lib/mjs/BatchQueue.d.ts.map +1 -0
- package/lib/mjs/BatchQueue.js +122 -0
- package/lib/mjs/BatchQueue.js.map +1 -0
- package/lib/mjs/filters/filter.d.ts +27 -0
- package/lib/mjs/filters/filter.d.ts.map +1 -0
- package/lib/mjs/filters/filter.js +16 -0
- package/lib/mjs/filters/filter.js.map +1 -0
- package/lib/mjs/filters/whenRepeated.d.ts +52 -0
- package/lib/mjs/filters/whenRepeated.d.ts.map +1 -0
- package/lib/mjs/filters/whenRepeated.js +95 -0
- package/lib/mjs/filters/whenRepeated.js.map +1 -0
- package/lib/mjs/format.d.ts +20 -0
- package/lib/mjs/format.d.ts.map +1 -0
- package/lib/mjs/format.js +72 -0
- package/lib/mjs/format.js.map +1 -0
- package/lib/mjs/index.d.ts +260 -21
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +201 -10
- package/lib/mjs/index.js.map +1 -1
- package/lib/mjs/perf.d.ts +109 -0
- package/lib/mjs/perf.d.ts.map +1 -0
- package/lib/mjs/perf.js +157 -0
- package/lib/mjs/perf.js.map +1 -0
- package/lib/mjs/persistence.d.ts +19 -0
- package/lib/mjs/persistence.d.ts.map +1 -0
- package/lib/mjs/persistence.js +132 -0
- package/lib/mjs/persistence.js.map +1 -0
- package/lib/mjs/serializers.d.ts +77 -0
- package/lib/mjs/serializers.d.ts.map +1 -0
- package/lib/mjs/serializers.js +208 -0
- package/lib/mjs/serializers.js.map +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -1,11 +1,132 @@
|
|
|
1
1
|
# `@spinajs/log-common`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared, dependency-light contracts for the SpinaJS logging stack. This package
|
|
4
|
+
holds no logging implementation of its own; it defines the abstract types that
|
|
5
|
+
`@spinajs/log`, `@spinajs/internal-logger` and the various log-source packages
|
|
6
|
+
implement. Depend on it when you write a **custom log target** or a **custom
|
|
7
|
+
`Log` implementation**, and want to avoid a circular dependency on `@spinajs/log`.
|
|
4
8
|
|
|
5
|
-
##
|
|
9
|
+
## What lives here
|
|
6
10
|
|
|
11
|
+
| Export | Purpose |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `Log` (abstract) | The logger contract: `trace/debug/info/warn/error/fatal/security/success`, `child`, `write`, `addVariable`, `timeStart/timeEnd`. |
|
|
14
|
+
| `LogTarget<T>` (abstract) | A sink that receives `ILogEntry` objects and writes them somewhere ( console, file, HTTP, ... ). |
|
|
15
|
+
| `createLogMessageObject` | Builds a normalized `ILogEntry` from the many call shapes the `Log` methods accept. |
|
|
16
|
+
| `LogLevel` / `StrToLogLevel` / `LogLevelStrings` | Level enum and string<->enum maps. |
|
|
17
|
+
| `ILogEntry`, `ILogRule`, `ICommonTargetOptions`, `IFileTargetOptions`, `ILogOptions` | Config and payload shapes. |
|
|
18
|
+
|
|
19
|
+
## The `LogTarget` contract
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
abstract class LogTarget<T extends ICommonTargetOptions> extends SyncService {
|
|
23
|
+
public HasError: boolean; // true after a failed write, cleared on the next success
|
|
24
|
+
public Error: Error | null | unknown; // the last write error, or null
|
|
25
|
+
public Options: T; // merged options ( includes the default layout )
|
|
26
|
+
public abstract write(data: ILogEntry): void;
|
|
27
|
+
}
|
|
7
28
|
```
|
|
8
|
-
const logCommon = require('@spinajs/log-common');
|
|
9
29
|
|
|
10
|
-
|
|
30
|
+
A target receives fully-formed `ILogEntry` objects. It is responsible for:
|
|
31
|
+
|
|
32
|
+
- honouring `Options.enabled` ( skip when `false` ),
|
|
33
|
+
- rendering `data.Variables` through its `Options.layout`,
|
|
34
|
+
- setting `HasError = true` / `Error = err` when a write fails and clearing them
|
|
35
|
+
( `HasError = false`, `Error = null` ) on the next successful write. Consumers
|
|
36
|
+
can poll these fields to detect a degraded sink.
|
|
37
|
+
|
|
38
|
+
## `createLogMessageObject`
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
createLogMessageObject(
|
|
42
|
+
err: Error | string,
|
|
43
|
+
message: string | any[],
|
|
44
|
+
level: LogLevel,
|
|
45
|
+
logger: string,
|
|
46
|
+
variables: any,
|
|
47
|
+
...args: any[]
|
|
48
|
+
): ILogEntry
|
|
11
49
|
```
|
|
50
|
+
|
|
51
|
+
It normalizes the two calling conventions the `Log` methods accept:
|
|
52
|
+
|
|
53
|
+
- **Message overload** — `err` is the format string ( or `undefined` ) and
|
|
54
|
+
`message` carries the format arguments, e.g. `log.info("hello %s", "world")`.
|
|
55
|
+
- **Error overload** — `err` is an `Error` and `message` is the human message,
|
|
56
|
+
e.g. `log.error(err, "could not connect")`.
|
|
57
|
+
|
|
58
|
+
Rules it applies:
|
|
59
|
+
|
|
60
|
+
- `sMsg = (err instanceof Error || !err) ? message : err` — an `err` string is
|
|
61
|
+
itself the message body, so a bare `log.info("hi")` works.
|
|
62
|
+
- `tMsg = args.length ? format(sMsg, ...args) : sMsg` — runs `util.format`-style
|
|
63
|
+
substitution ( see `format.ts`; `%s %d %i %f %j %o %O %%` ) only when there are
|
|
64
|
+
args.
|
|
65
|
+
- `Variables.error` is set only for the `Error` overload.
|
|
66
|
+
- `Variables.logger = logger ?? message` and `Variables.level` is the upper-cased
|
|
67
|
+
level string.
|
|
68
|
+
|
|
69
|
+
The returned entry is:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
{
|
|
73
|
+
Level: LogLevel,
|
|
74
|
+
Variables: {
|
|
75
|
+
error, // Error | undefined
|
|
76
|
+
level, // "INFO" | "ERROR" | ...
|
|
77
|
+
logger, // logger name
|
|
78
|
+
message, // formatted message
|
|
79
|
+
...variables
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Layout variables
|
|
85
|
+
|
|
86
|
+
A target's `layout` is a template string resolved by `@spinajs/configuration`'s
|
|
87
|
+
`format(variables, layout)`. Every key on `ILogEntry.Variables` is available as
|
|
88
|
+
`${key}`. The always-present variables are:
|
|
89
|
+
|
|
90
|
+
| Variable | Value |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `${datetime}` | timestamp of the entry |
|
|
93
|
+
| `${level}` | upper-cased level, e.g. `INFO` |
|
|
94
|
+
| `${message}` | the formatted message |
|
|
95
|
+
| `${error}` | the `Error` ( use `${error:message}` for its message ) |
|
|
96
|
+
| `${logger}` | the logger name |
|
|
97
|
+
|
|
98
|
+
Conditional blocks are supported, e.g. `${?error} ... ${/error}` only renders
|
|
99
|
+
when `error` is set. Any custom variable you attach via `log.addVariable(name, value)`
|
|
100
|
+
or the config `variables` array is also available as `${name}`.
|
|
101
|
+
|
|
102
|
+
The default layout ( applied to every target unless overridden ) is:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
${datetime} ${level} ${message}${?error} Exception: ${error:message}${/error} (${logger})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Writing a custom target
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { Injectable } from "@spinajs/di";
|
|
112
|
+
import { format } from "@spinajs/configuration";
|
|
113
|
+
import { LogTarget, ILogEntry, ICommonTargetOptions } from "@spinajs/log-common";
|
|
114
|
+
|
|
115
|
+
@Injectable("MyTarget") // referenced by `type: "MyTarget"` in config
|
|
116
|
+
export class MyTarget extends LogTarget<ICommonTargetOptions> {
|
|
117
|
+
public write(data: ILogEntry): void {
|
|
118
|
+
if (!this.Options.enabled) return;
|
|
119
|
+
try {
|
|
120
|
+
// render with the configured layout and send it somewhere
|
|
121
|
+
send(format(data.Variables, this.Options.layout));
|
|
122
|
+
this.HasError = false;
|
|
123
|
+
this.Error = null;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
this.HasError = true;
|
|
126
|
+
this.Error = err;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
See `@spinajs/log` for the concrete targets and the rules/targets configuration.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small, dependency-free, browser-safe buffered-batch queue.
|
|
3
|
+
*
|
|
4
|
+
* `BatchQueue<T>` owns ACCUMULATION and FLUSH TRIGGERING only; it delegates the
|
|
5
|
+
* actual I/O to the owner via `onFlush`. Ported from the essence of
|
|
6
|
+
* OpenTelemetry's BatchLogRecordProcessor.
|
|
7
|
+
*
|
|
8
|
+
* Triggers a flush when either:
|
|
9
|
+
* - the buffer reaches `maxBatch` items ( size trigger ), or
|
|
10
|
+
* - the periodic timer fires every `flushIntervalMs` ( interval trigger ).
|
|
11
|
+
*
|
|
12
|
+
* ## Failure model
|
|
13
|
+
*
|
|
14
|
+
* There are two, deliberately distinct, ways items can leave the queue:
|
|
15
|
+
*
|
|
16
|
+
* 1. **Overflow ( the ONLY silent drop ).** The buffer is hard-capped at
|
|
17
|
+
* `maxQueue`. When it grows past the cap the OLDEST items are dropped so
|
|
18
|
+
* memory stays bounded, and `onOverflow(droppedItems)` fires. This is the
|
|
19
|
+
* back-pressure release valve for a stuck/slow sink.
|
|
20
|
+
*
|
|
21
|
+
* 2. **Flush failure ( never a silent drop ).** `onFlush` may throw / reject.
|
|
22
|
+
* `BatchQueue` does NOT retry, requeue, or swallow the batch on its own —
|
|
23
|
+
* that is the owner's decision. The rejection PROPAGATES to any caller that
|
|
24
|
+
* awaits `flush()` / `forceFlush()`, so the owner can `catch` it and call
|
|
25
|
+
* `requeueFront(batch)` to put the batch back at the front ( never-drop
|
|
26
|
+
* semantics ) or drop it deliberately. The periodic timer intentionally
|
|
27
|
+
* ignores the rejection ( `void this.flush()` ) so a failing sink never
|
|
28
|
+
* kills the flush loop; the in-flight guard is always cleared in `finally`
|
|
29
|
+
* so the next flush can proceed regardless of outcome.
|
|
30
|
+
*
|
|
31
|
+
* ## Re-entrancy
|
|
32
|
+
*
|
|
33
|
+
* At most one `onFlush` runs at a time. If a flush is already in flight, calling
|
|
34
|
+
* `flush()` returns the SAME in-flight promise ( coalesced ) rather than
|
|
35
|
+
* starting a second `onFlush`. A flush snapshots and clears the buffer
|
|
36
|
+
* synchronously before awaiting the I/O, so items enqueued during a flush land
|
|
37
|
+
* in a fresh buffer and are picked up by the next flush.
|
|
38
|
+
*
|
|
39
|
+
* ## Runtime
|
|
40
|
+
*
|
|
41
|
+
* Uses only `setInterval` / `setTimeout` ( available in both Node and browsers ).
|
|
42
|
+
* The periodic timer ( and the optional per-flush timeout timer ) are `unref()`'d
|
|
43
|
+
* when that method exists, so they never keep a Node process alive; the `?.`
|
|
44
|
+
* guard makes the calls harmless in browsers.
|
|
45
|
+
*/
|
|
46
|
+
export interface IBatchQueueOptions<T> {
|
|
47
|
+
/** Flush automatically once the buffer reaches this many items. */
|
|
48
|
+
maxBatch: number;
|
|
49
|
+
/** Hard cap on buffered items. Beyond it the OLDEST are dropped ( onOverflow fires ). */
|
|
50
|
+
maxQueue: number;
|
|
51
|
+
/** Periodic flush tick in ms. The timer is unref()'d so it never blocks process exit. */
|
|
52
|
+
flushIntervalMs: number;
|
|
53
|
+
/** Optional per-flush timeout in ms. If onFlush does not settle in time, the flush rejects. */
|
|
54
|
+
exportTimeoutMs?: number;
|
|
55
|
+
/** Does the actual I/O for a batch. May throw/reject; the owner handles retry/requeue/drop. */
|
|
56
|
+
onFlush: (items: T[]) => Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Called when the queue cap is exceeded and the oldest items are dropped.
|
|
59
|
+
* Receives the DROPPED ITEMS themselves ( oldest first ) so an owner can route
|
|
60
|
+
* exactly what was dropped to a fallback ( see LogTarget.OnDropped ). Use
|
|
61
|
+
* `droppedItems.length` when only the count matters.
|
|
62
|
+
*/
|
|
63
|
+
onOverflow?: (droppedItems: T[]) => void;
|
|
64
|
+
}
|
|
65
|
+
export declare class BatchQueue<T> {
|
|
66
|
+
private options;
|
|
67
|
+
private buffer;
|
|
68
|
+
private timer;
|
|
69
|
+
private stopped;
|
|
70
|
+
/**
|
|
71
|
+
* The currently running flush, or null when idle. Used both as the
|
|
72
|
+
* re-entrancy guard ( coalesce concurrent flush() calls ) and to let
|
|
73
|
+
* forceFlush()/shutdown() wait for an in-flight flush before draining.
|
|
74
|
+
*/
|
|
75
|
+
private inFlight;
|
|
76
|
+
constructor(options: IBatchQueueOptions<T>);
|
|
77
|
+
/** Current number of buffered items. */
|
|
78
|
+
get size(): number;
|
|
79
|
+
/**
|
|
80
|
+
* Add an item to the buffer.
|
|
81
|
+
*
|
|
82
|
+
* If the queue has been shut down this is a no-op that resolves immediately.
|
|
83
|
+
* When the buffer reaches `maxBatch` a flush is triggered and its promise is
|
|
84
|
+
* returned, so a caller MAY await a size-triggered flush; otherwise resolves
|
|
85
|
+
* immediately.
|
|
86
|
+
*/
|
|
87
|
+
enqueue(item: T): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Put items back at the FRONT of the buffer ( then enforce the cap ). Owners
|
|
90
|
+
* use this to re-queue a batch whose `onFlush` failed, without dropping it.
|
|
91
|
+
*/
|
|
92
|
+
requeueFront(items: T[]): void;
|
|
93
|
+
/**
|
|
94
|
+
* Enforce the hard `maxQueue` cap by dropping the OLDEST items. Fires
|
|
95
|
+
* `onOverflow(droppedItems)` with the spliced-out items when anything is dropped.
|
|
96
|
+
*/
|
|
97
|
+
private enforceCap;
|
|
98
|
+
/**
|
|
99
|
+
* Flush the entire buffer in a single `onFlush` call.
|
|
100
|
+
*
|
|
101
|
+
* Re-entrancy: if a flush is already in flight, that same promise is returned
|
|
102
|
+
* ( no second `onFlush` ). If the buffer is empty, resolves immediately.
|
|
103
|
+
* Otherwise the buffer is snapshotted and cleared synchronously, `onFlush`
|
|
104
|
+
* runs ( optionally raced against `exportTimeoutMs` ), and the in-flight guard
|
|
105
|
+
* is cleared in `finally` regardless of success or failure. The returned
|
|
106
|
+
* promise settles with `onFlush`'s outcome so an awaiting owner can catch a
|
|
107
|
+
* rejection and requeue.
|
|
108
|
+
*/
|
|
109
|
+
flush(): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Race a flush promise against a timeout that rejects after `ms`. The timeout
|
|
112
|
+
* timer is cleared ( and unref()'d ) so it neither leaks nor keeps the loop alive.
|
|
113
|
+
*/
|
|
114
|
+
private withTimeout;
|
|
115
|
+
/**
|
|
116
|
+
* Flush any buffered items and await completion. If a flush is already in
|
|
117
|
+
* flight, wait for it first, then flush any remainder that accumulated. Since
|
|
118
|
+
* a single `flush()` drains the ENTIRE buffer, one follow-up flush suffices.
|
|
119
|
+
*/
|
|
120
|
+
forceFlush(): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Stop the periodic timer and perform a best-effort final flush of buffered
|
|
123
|
+
* items. After shutdown, `enqueue` is a no-op that resolves.
|
|
124
|
+
*/
|
|
125
|
+
shutdown(): Promise<void>;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=BatchQueue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BatchQueue.d.ts","sourceRoot":"","sources":["../../src/BatchQueue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,eAAe,EAAE,MAAM,CAAC;IACxB,+FAA+F;IAC/F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+FAA+F;IAC/F,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;CAC1C;AAED,qBAAa,UAAU,CAAC,CAAC;IAcX,OAAO,CAAC,OAAO;IAb3B,OAAO,CAAC,MAAM,CAAW;IAEzB,OAAO,CAAC,KAAK,CAAiC;IAE9C,OAAO,CAAC,OAAO,CAAS;IAExB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAA8B;gBAE1B,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAMlD,wCAAwC;IACxC,IAAW,IAAI,IAAI,MAAM,CAExB;IAED;;;;;;;OAOG;IACI,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAetC;;;OAGG;IACI,YAAY,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI;IAKrC;;;OAGG;IACH,OAAO,CAAC,UAAU;IAQlB;;;;;;;;;;OAUG;IACI,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB7B;;;OAGG;IACH,OAAO,CAAC,WAAW;IAanB;;;;OAIG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQxC;;;OAGG;IACU,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAKvC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BatchQueue = void 0;
|
|
4
|
+
class BatchQueue {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
this.buffer = [];
|
|
8
|
+
this.stopped = false;
|
|
9
|
+
/**
|
|
10
|
+
* The currently running flush, or null when idle. Used both as the
|
|
11
|
+
* re-entrancy guard ( coalesce concurrent flush() calls ) and to let
|
|
12
|
+
* forceFlush()/shutdown() wait for an in-flight flush before draining.
|
|
13
|
+
*/
|
|
14
|
+
this.inFlight = null;
|
|
15
|
+
this.timer = setInterval(() => void this.flush(), this.options.flushIntervalMs);
|
|
16
|
+
// Do not keep the event loop / process alive just for the flush tick.
|
|
17
|
+
this.timer.unref?.();
|
|
18
|
+
}
|
|
19
|
+
/** Current number of buffered items. */
|
|
20
|
+
get size() {
|
|
21
|
+
return this.buffer.length;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Add an item to the buffer.
|
|
25
|
+
*
|
|
26
|
+
* If the queue has been shut down this is a no-op that resolves immediately.
|
|
27
|
+
* When the buffer reaches `maxBatch` a flush is triggered and its promise is
|
|
28
|
+
* returned, so a caller MAY await a size-triggered flush; otherwise resolves
|
|
29
|
+
* immediately.
|
|
30
|
+
*/
|
|
31
|
+
enqueue(item) {
|
|
32
|
+
if (this.stopped) {
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}
|
|
35
|
+
this.buffer.push(item);
|
|
36
|
+
this.enforceCap();
|
|
37
|
+
if (this.size >= this.options.maxBatch) {
|
|
38
|
+
return this.flush();
|
|
39
|
+
}
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Put items back at the FRONT of the buffer ( then enforce the cap ). Owners
|
|
44
|
+
* use this to re-queue a batch whose `onFlush` failed, without dropping it.
|
|
45
|
+
*/
|
|
46
|
+
requeueFront(items) {
|
|
47
|
+
this.buffer = [...items, ...this.buffer];
|
|
48
|
+
this.enforceCap();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Enforce the hard `maxQueue` cap by dropping the OLDEST items. Fires
|
|
52
|
+
* `onOverflow(droppedItems)` with the spliced-out items when anything is dropped.
|
|
53
|
+
*/
|
|
54
|
+
enforceCap() {
|
|
55
|
+
if (this.buffer.length > this.options.maxQueue) {
|
|
56
|
+
const dropCount = this.buffer.length - this.options.maxQueue;
|
|
57
|
+
const droppedItems = this.buffer.splice(0, dropCount);
|
|
58
|
+
this.options.onOverflow?.(droppedItems);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Flush the entire buffer in a single `onFlush` call.
|
|
63
|
+
*
|
|
64
|
+
* Re-entrancy: if a flush is already in flight, that same promise is returned
|
|
65
|
+
* ( no second `onFlush` ). If the buffer is empty, resolves immediately.
|
|
66
|
+
* Otherwise the buffer is snapshotted and cleared synchronously, `onFlush`
|
|
67
|
+
* runs ( optionally raced against `exportTimeoutMs` ), and the in-flight guard
|
|
68
|
+
* is cleared in `finally` regardless of success or failure. The returned
|
|
69
|
+
* promise settles with `onFlush`'s outcome so an awaiting owner can catch a
|
|
70
|
+
* rejection and requeue.
|
|
71
|
+
*/
|
|
72
|
+
flush() {
|
|
73
|
+
if (this.inFlight) {
|
|
74
|
+
return this.inFlight;
|
|
75
|
+
}
|
|
76
|
+
if (this.buffer.length === 0) {
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
}
|
|
79
|
+
const batch = this.buffer;
|
|
80
|
+
this.buffer = [];
|
|
81
|
+
const run = this.options.exportTimeoutMs !== undefined ? this.withTimeout(this.options.onFlush(batch), this.options.exportTimeoutMs) : this.options.onFlush(batch);
|
|
82
|
+
// Store as the guard and clear it once settled ( success OR failure ) so the
|
|
83
|
+
// next flush can proceed. The promise is still returned to the caller so the
|
|
84
|
+
// rejection propagates for owner-side handling.
|
|
85
|
+
this.inFlight = run.finally(() => {
|
|
86
|
+
this.inFlight = null;
|
|
87
|
+
});
|
|
88
|
+
return this.inFlight;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Race a flush promise against a timeout that rejects after `ms`. The timeout
|
|
92
|
+
* timer is cleared ( and unref()'d ) so it neither leaks nor keeps the loop alive.
|
|
93
|
+
*/
|
|
94
|
+
withTimeout(promise, ms) {
|
|
95
|
+
let timeout;
|
|
96
|
+
const timer = new Promise((_resolve, reject) => {
|
|
97
|
+
timeout = setTimeout(() => reject(new Error(`BatchQueue flush timed out after ${ms}ms`)), ms);
|
|
98
|
+
timeout.unref?.();
|
|
99
|
+
});
|
|
100
|
+
return Promise.race([promise, timer]).finally(() => {
|
|
101
|
+
clearTimeout(timeout);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Flush any buffered items and await completion. If a flush is already in
|
|
106
|
+
* flight, wait for it first, then flush any remainder that accumulated. Since
|
|
107
|
+
* a single `flush()` drains the ENTIRE buffer, one follow-up flush suffices.
|
|
108
|
+
*/
|
|
109
|
+
async forceFlush() {
|
|
110
|
+
if (this.inFlight) {
|
|
111
|
+
await this.inFlight;
|
|
112
|
+
}
|
|
113
|
+
await this.flush();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Stop the periodic timer and perform a best-effort final flush of buffered
|
|
117
|
+
* items. After shutdown, `enqueue` is a no-op that resolves.
|
|
118
|
+
*/
|
|
119
|
+
async shutdown() {
|
|
120
|
+
this.stopped = true;
|
|
121
|
+
clearInterval(this.timer);
|
|
122
|
+
await this.forceFlush();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.BatchQueue = BatchQueue;
|
|
126
|
+
//# sourceMappingURL=BatchQueue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BatchQueue.js","sourceRoot":"","sources":["../../src/BatchQueue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ILogEntry } from "../index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Common shape of a configured filter entry. `type` is the DI string name the
|
|
4
|
+
* filter is registered under ( eg. "LevelFilter" ); all other keys are the
|
|
5
|
+
* filter's own free-form options and are read by the concrete filter.
|
|
6
|
+
*/
|
|
7
|
+
export interface ILogFilterOptions {
|
|
8
|
+
type: string;
|
|
9
|
+
[k: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Base class for a composable log filter. Filters are DI-registered by string
|
|
13
|
+
* name ( just like log targets ) via `@Injectable("<Name>")` and resolved per
|
|
14
|
+
* logger from `logger.filters` config. They run IN ORDER inside the logger's
|
|
15
|
+
* `write()`: each filter may pass the entry through ( optionally mutating it ),
|
|
16
|
+
* or return `null` to DROP it entirely.
|
|
17
|
+
*
|
|
18
|
+
* Kept a plain, dependency-light abstract class ( no SyncService etc. ) so the
|
|
19
|
+
* contract stays browser-safe and cheap to instantiate.
|
|
20
|
+
*/
|
|
21
|
+
export declare abstract class LogFilter {
|
|
22
|
+
protected options?: ILogFilterOptions | undefined;
|
|
23
|
+
constructor(options?: ILogFilterOptions | undefined);
|
|
24
|
+
/** Return the ( possibly modified ) entry to keep, or null to DROP it. */
|
|
25
|
+
abstract apply(entry: ILogEntry): ILogEntry | null;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../../src/filters/filter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,8BAAsB,SAAS;IACjB,SAAS,CAAC,OAAO,CAAC,EAAE,iBAAiB;gBAA3B,OAAO,CAAC,EAAE,iBAAiB,YAAA;IAEjD,0EAA0E;aAC1D,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;CAC1D"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogFilter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base class for a composable log filter. Filters are DI-registered by string
|
|
6
|
+
* name ( just like log targets ) via `@Injectable("<Name>")` and resolved per
|
|
7
|
+
* logger from `logger.filters` config. They run IN ORDER inside the logger's
|
|
8
|
+
* `write()`: each filter may pass the entry through ( optionally mutating it ),
|
|
9
|
+
* or return `null` to DROP it entirely.
|
|
10
|
+
*
|
|
11
|
+
* Kept a plain, dependency-light abstract class ( no SyncService etc. ) so the
|
|
12
|
+
* contract stays browser-safe and cheap to instantiate.
|
|
13
|
+
*/
|
|
14
|
+
class LogFilter {
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.LogFilter = LogFilter;
|
|
20
|
+
//# sourceMappingURL=filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../../../src/filters/filter.ts"],"names":[],"mappings":";;;AAeA;;;;;;;;;GASG;AACH,MAAsB,SAAS;IAC7B,YAAsB,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;CAItD;AALD,8BAKC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ILogEntry } from "../index.js";
|
|
2
|
+
import { LogFilter, ILogFilterOptions } from "./filter.js";
|
|
3
|
+
export interface IWhenRepeatedOptions extends Partial<ILogFilterOptions> {
|
|
4
|
+
/**
|
|
5
|
+
* Suppression window in SECONDS. Identical entries logged within this window
|
|
6
|
+
* ( from the first occurrence ) are collapsed into one. Default is 10.
|
|
7
|
+
*/
|
|
8
|
+
timeout?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Hard cap on the number of tracked keys. The internal map is self-pruning:
|
|
11
|
+
* expired records are dropped first, then the oldest, so memory stays bounded
|
|
12
|
+
* even under many distinct messages. Default is 1024.
|
|
13
|
+
*/
|
|
14
|
+
maxKeys?: number;
|
|
15
|
+
}
|
|
16
|
+
interface IRepeatRecord {
|
|
17
|
+
count: number;
|
|
18
|
+
windowStart: number;
|
|
19
|
+
level: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Port of NLog's WhenRepeatedFilter. Collapses identical, repeated log entries
|
|
23
|
+
* within a timeout window into a single emitted entry, appending a ` (xN)`
|
|
24
|
+
* suppressed-count when logging of that key resumes. This stops a hot loop or a
|
|
25
|
+
* flapping error from flooding console / file / Loki and burying real signal.
|
|
26
|
+
*
|
|
27
|
+
* Known tradeoff: there is no background timer. A residual suppressed count is
|
|
28
|
+
* flushed on the NEXT matching entry ( window expired or higher severity ), NOT
|
|
29
|
+
* when the window simply elapses with no further activity. This keeps the filter
|
|
30
|
+
* dependency-free; the full filter pipeline in a later phase can layer a timed
|
|
31
|
+
* flush on top.
|
|
32
|
+
*/
|
|
33
|
+
export declare class WhenRepeatedFilter extends LogFilter {
|
|
34
|
+
protected now: () => number;
|
|
35
|
+
protected timeoutMs: number;
|
|
36
|
+
protected maxKeys: number;
|
|
37
|
+
protected records: Map<string, IRepeatRecord>;
|
|
38
|
+
constructor(options?: IWhenRepeatedOptions, now?: () => number);
|
|
39
|
+
protected keyOf(entry: ILogEntry): string;
|
|
40
|
+
/**
|
|
41
|
+
* Returns the entry to EMIT ( possibly with an injected suppressed-count ) or
|
|
42
|
+
* `null` to SUPPRESS.
|
|
43
|
+
*/
|
|
44
|
+
apply(entry: ILogEntry): ILogEntry | null;
|
|
45
|
+
/**
|
|
46
|
+
* Keeps the tracked-key map bounded. Drops expired records first, then the
|
|
47
|
+
* oldest by `windowStart` until at/under the cap.
|
|
48
|
+
*/
|
|
49
|
+
protected prune(now: number): void;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=whenRepeated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whenRepeated.d.ts","sourceRoot":"","sources":["../../../src/filters/whenRepeated.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,oBAAqB,SAAQ,OAAO,CAAC,iBAAiB,CAAC;IACtE;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,qBAEa,kBAAmB,SAAQ,SAAS;IAKH,SAAS,CAAC,GAAG,EAAE,MAAM,MAAM;IAJvE,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAa;gBAE9C,OAAO,CAAC,EAAE,oBAAoB,EAAY,GAAG,GAAE,MAAM,MAAyB;IAM1F,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM;IAIzC;;;OAGG;IACI,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;IA8BhD;;;OAGG;IACH,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAuBnC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.WhenRepeatedFilter = void 0;
|
|
13
|
+
const di_1 = require("@spinajs/di");
|
|
14
|
+
const filter_js_1 = require("./filter.js");
|
|
15
|
+
/**
|
|
16
|
+
* Port of NLog's WhenRepeatedFilter. Collapses identical, repeated log entries
|
|
17
|
+
* within a timeout window into a single emitted entry, appending a ` (xN)`
|
|
18
|
+
* suppressed-count when logging of that key resumes. This stops a hot loop or a
|
|
19
|
+
* flapping error from flooding console / file / Loki and burying real signal.
|
|
20
|
+
*
|
|
21
|
+
* Known tradeoff: there is no background timer. A residual suppressed count is
|
|
22
|
+
* flushed on the NEXT matching entry ( window expired or higher severity ), NOT
|
|
23
|
+
* when the window simply elapses with no further activity. This keeps the filter
|
|
24
|
+
* dependency-free; the full filter pipeline in a later phase can layer a timed
|
|
25
|
+
* flush on top.
|
|
26
|
+
*/
|
|
27
|
+
let WhenRepeatedFilter = class WhenRepeatedFilter extends filter_js_1.LogFilter {
|
|
28
|
+
constructor(options, now = () => Date.now()) {
|
|
29
|
+
super(options);
|
|
30
|
+
this.now = now;
|
|
31
|
+
this.records = new Map();
|
|
32
|
+
this.timeoutMs = (options?.timeout ?? 10) * 1000;
|
|
33
|
+
this.maxKeys = options?.maxKeys ?? 1024;
|
|
34
|
+
}
|
|
35
|
+
keyOf(entry) {
|
|
36
|
+
return `${entry.Level}|${entry.Variables.logger}|${entry.Variables.message}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns the entry to EMIT ( possibly with an injected suppressed-count ) or
|
|
40
|
+
* `null` to SUPPRESS.
|
|
41
|
+
*/
|
|
42
|
+
apply(entry) {
|
|
43
|
+
const key = this.keyOf(entry);
|
|
44
|
+
const now = this.now();
|
|
45
|
+
const record = this.records.get(key);
|
|
46
|
+
if (!record) {
|
|
47
|
+
// first occurrence of this key - emit and start tracking
|
|
48
|
+
this.records.set(key, { count: 0, windowStart: now, level: entry.Level });
|
|
49
|
+
this.prune(now);
|
|
50
|
+
return entry;
|
|
51
|
+
}
|
|
52
|
+
if (now - record.windowStart < this.timeoutMs && entry.Level <= record.level) {
|
|
53
|
+
// still inside the window and not a higher severity - suppress
|
|
54
|
+
record.count++;
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
// window expired OR a higher-severity entry arrived - emit and reset. If we
|
|
58
|
+
// suppressed anything, inject the count into the ( freshly built per call,
|
|
59
|
+
// so safe to mutate ) message string.
|
|
60
|
+
if (record.count > 0) {
|
|
61
|
+
entry.Variables.message = `${entry.Variables.message} (x${record.count})`;
|
|
62
|
+
}
|
|
63
|
+
this.records.set(key, { count: 0, windowStart: now, level: entry.Level });
|
|
64
|
+
this.prune(now);
|
|
65
|
+
return entry;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Keeps the tracked-key map bounded. Drops expired records first, then the
|
|
69
|
+
* oldest by `windowStart` until at/under the cap.
|
|
70
|
+
*/
|
|
71
|
+
prune(now) {
|
|
72
|
+
if (this.records.size <= this.maxKeys) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
for (const [k, r] of this.records) {
|
|
76
|
+
if (now - r.windowStart >= this.timeoutMs) {
|
|
77
|
+
this.records.delete(k);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (this.records.size <= this.maxKeys) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const byAge = [...this.records.entries()].sort((a, b) => a[1].windowStart - b[1].windowStart);
|
|
84
|
+
for (const [k] of byAge) {
|
|
85
|
+
if (this.records.size <= this.maxKeys) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
this.records.delete(k);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
exports.WhenRepeatedFilter = WhenRepeatedFilter;
|
|
93
|
+
exports.WhenRepeatedFilter = WhenRepeatedFilter = __decorate([
|
|
94
|
+
(0, di_1.Injectable)("WhenRepeatedFilter"),
|
|
95
|
+
(0, di_1.NewInstance)(),
|
|
96
|
+
__metadata("design:paramtypes", [Object, Function])
|
|
97
|
+
], WhenRepeatedFilter);
|
|
98
|
+
//# sourceMappingURL=whenRepeated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whenRepeated.js","sourceRoot":"","sources":["../../../src/filters/whenRepeated.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, platform-free clone of Node's `util.format`.
|
|
3
|
+
*
|
|
4
|
+
* Supports the format specifiers the framework actually uses:
|
|
5
|
+
* %s - String
|
|
6
|
+
* %d - Number ( integer )
|
|
7
|
+
* %i - parseInt
|
|
8
|
+
* %f - parseFloat
|
|
9
|
+
* %j - JSON
|
|
10
|
+
* %o - Object ( JSON.stringify )
|
|
11
|
+
* %O - Object ( JSON.stringify )
|
|
12
|
+
* %% - literal percent sign ( consumes no argument )
|
|
13
|
+
*
|
|
14
|
+
* Any arguments left over after the specifiers are consumed are appended,
|
|
15
|
+
* space-separated. Objects are rendered via JSON.stringify ( circular refs
|
|
16
|
+
* fall back to String() ). This avoids pulling in Node's `util` module so the
|
|
17
|
+
* logger is usable in the browser.
|
|
18
|
+
*/
|
|
19
|
+
export declare function format(f?: any, ...args: any[]): string;
|
|
20
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/format.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,CA0CtD"}
|