@monochromatic-dev/module-logger 0.1.0 → 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/CHANGELOG.md +27 -0
- package/README.md +58 -12
- package/dist/final/neutral/browser.d.mts +60 -0
- package/dist/final/neutral/browser.mjs +1 -0
- package/dist/final/neutral/index.d.mts +366 -572
- package/dist/final/neutral/index.mjs +2 -3
- package/dist/final/neutral/indexed-db-hsIfv7Cv.mjs +2 -0
- package/dist/final/neutral/types-BkkBXgY3.d.mts +76 -0
- package/dist/final/node/file-CRGb1hDK.mjs +1 -0
- package/dist/final/node/index.d.mts +366 -572
- package/dist/final/node/index.mjs +3 -3
- package/dist/final/node/node.d.mts +103 -0
- package/dist/final/node/node.mjs +1 -0
- package/dist/final/node/types-BkkBXgY3.d.mts +76 -0
- package/package.json +19 -5
- package/src/artifact-platform-split.unit.test.ts +140 -0
- package/src/browser.ts +14 -0
- package/src/create-logger.ts +249 -151
- package/src/create-logger.unit.test.ts +527 -75
- package/src/default-sinks.neutral.ts +34 -0
- package/src/default-sinks.node.ts +32 -0
- package/src/error-format.ts +23 -23
- package/src/index.ts +2 -0
- package/src/logger.ts +23 -50
- package/src/node.ts +23 -0
- package/src/sink/console-control-chars.ts +64 -64
- package/src/sink/console-control-chars.unit.test.ts +14 -14
- package/src/sink/console.ts +194 -194
- package/src/sink/console.unit.test.ts +18 -18
- package/src/sink/file.ts +136 -140
- package/src/sink/file.unit.test.ts +19 -26
- package/src/sink/index.ts +4 -7
- package/src/sink/indexed-db-util.ts +42 -42
- package/src/sink/indexed-db.browser.test.ts +7 -7
- package/src/sink/indexed-db.ts +109 -109
- package/src/sink/indexed-db.unit.test.ts +5 -13
- package/src/sink/local-storage-key.ts +73 -73
- package/src/sink/local-storage-key.unit.test.ts +8 -8
- package/src/sink/local-storage-quota.ts +37 -37
- package/src/sink/local-storage-quota.unit.test.ts +8 -8
- package/src/sink/local-storage-store.ts +113 -113
- package/src/sink/local-storage-store.unit.test.ts +35 -35
- package/src/sink/local-storage.ts +72 -72
- package/src/sink/local-storage.unit.test.ts +27 -27
- package/src/sink/noop.ts +20 -20
- package/src/sink/noop.unit.test.ts +1 -1
- package/src/sink/opfs.browser.test.ts +7 -7
- package/src/sink/opfs.ts +62 -62
- package/src/sink/opfs.unit.test.ts +5 -13
- package/src/sink/record-buffer.ts +84 -84
- package/src/sink/record-buffer.unit.test.ts +20 -20
- package/src/sink/session-storage-quota.ts +34 -34
- package/src/sink/session-storage-quota.unit.test.ts +8 -8
- package/src/sink/session-storage-store.ts +72 -72
- package/src/sink/session-storage.ts +48 -48
- package/src/sink/session-storage.unit.test.ts +39 -39
- package/src/sink/web-storage-quota-error.ts +22 -22
- package/src/sink/web-storage-quota-error.unit.test.ts +2 -2
- package/src/sink/web-storage-runtime.ts +24 -24
- package/src/startup.unit.test.ts +18 -18
- package/src/tagged.ts +35 -35
- package/src/tagged.unit.test.ts +8 -8
- package/src/types.ts +39 -39
package/src/create-logger.ts
CHANGED
|
@@ -9,20 +9,41 @@ import type {
|
|
|
9
9
|
} from './types.ts';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
Default `flush()` deadline in milliseconds. Measured on 2026-09-06: a
|
|
13
|
+
default logger flushing 100 records through the console and file sinks
|
|
14
|
+
settles in about 2 ms locally, so this leaves three orders of magnitude for
|
|
15
|
+
a slow but working backend while still bounding shutdown on a wedged one.
|
|
16
|
+
Override per logger through the `flushDeadlineMs` option of
|
|
17
|
+
{@link createLogger}.
|
|
18
18
|
*/
|
|
19
19
|
export const DEFAULT_FLUSH_DEADLINE_MS = 5_000;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
Default per-sink `verify()` time limit in milliseconds. Measured on
|
|
23
|
+
2026-09-06: the default logger's five shipped verifies complete together in
|
|
24
|
+
about 2.4 ms locally, so this leaves three orders of magnitude for a slow
|
|
25
|
+
but working backend probe (a network filesystem, a busy IndexedDB) while a
|
|
26
|
+
verify that never answers (a hung mount, an IndexedDB open blocked by
|
|
27
|
+
another tab) can no longer stall startup. Override per logger through the
|
|
28
|
+
`verifyTimeoutMs` option of {@link createLogger}.
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_VERIFY_TIMEOUT_MS = 5_000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
Most records the logger buffers before its sinks have verified. Startup
|
|
34
|
+
lasts at most {@link DEFAULT_VERIFY_TIMEOUT_MS}, so this bounds the memory a
|
|
35
|
+
burst during that window can claim; on overflow the oldest buffered record
|
|
36
|
+
is dropped so the newest (usually most diagnostic) context survives, and
|
|
37
|
+
one synthetic `warn` record naming the dropped count is written to every
|
|
38
|
+
available sink once initialization completes.
|
|
39
|
+
*/
|
|
40
|
+
export const STARTUP_BUFFER_CAP = 10_000;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
Sink paired with its current availability inside one logger instance.
|
|
44
|
+
Availability starts `false` (not-yet-verified reads as unavailable,
|
|
45
|
+
identical to a verified failure) and flips `true` once the sink's own
|
|
46
|
+
`verify` confirms its backend.
|
|
26
47
|
*/
|
|
27
48
|
type SinkEntry = {
|
|
28
49
|
available: boolean;
|
|
@@ -30,12 +51,12 @@ type SinkEntry = {
|
|
|
30
51
|
};
|
|
31
52
|
|
|
32
53
|
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
Awaits one sink write so `flush()` can observe its settling. A rejected
|
|
55
|
+
write is swallowed here via {@link reportLoggerInternalError} (the sink
|
|
56
|
+
owns its own write-error handling) and does not disable the sink: one
|
|
57
|
+
transient failure must not retire a backend for the rest of the run.
|
|
58
|
+
|
|
59
|
+
@param writePromise - Promise returned by the sink write call.
|
|
39
60
|
*/
|
|
40
61
|
async function trackWrite(
|
|
41
62
|
{ writePromise, }: { readonly writePromise: Promise<void>; },
|
|
@@ -52,63 +73,73 @@ async function trackWrite(
|
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
76
|
+
Builds a multi-sink logger over the supplied sink adapters. All
|
|
77
|
+
orchestration (per-sink availability, startup buffering and replay,
|
|
78
|
+
in-flight write tracking, and flush) lives here; the exported default
|
|
79
|
+
`logger` is just this factory applied to the default sink set, and tests
|
|
80
|
+
apply it to fake sinks to exercise the orchestration directly.
|
|
81
|
+
|
|
82
|
+
Verification runs eagerly at construction and never blocks callers:
|
|
83
|
+
records emitted while an async sink is still verifying buffer internally
|
|
84
|
+
and replay to that sink the moment it verifies. Every sink verifies
|
|
85
|
+
concurrently under its own time limit (`verifyTimeoutMs`, default
|
|
86
|
+
{@link DEFAULT_VERIFY_TIMEOUT_MS}), so one backend that never answers
|
|
87
|
+
cannot starve the others or keep the logger from initializing. A sink
|
|
88
|
+
whose `verify` resolves `false`, throws, or runs past the limit is dropped
|
|
89
|
+
and receives no records; an answer that arrives after the limit is
|
|
90
|
+
ignored. A rejected `write` is the sink's own concern and does not disable
|
|
91
|
+
the backend.
|
|
92
|
+
|
|
93
|
+
`flush()` always resolves: one deadline (`flushDeadlineMs`, default
|
|
94
|
+
{@link DEFAULT_FLUSH_DEADLINE_MS}) wraps startup verification, the
|
|
95
|
+
in-flight write drain, and every sink flush hook together. When it elapses
|
|
96
|
+
the logger reports one breadcrumb, abandons the tracked writes from its
|
|
97
|
+
view (the sinks expose no cancellation, so the underlying work continues),
|
|
98
|
+
and resolves, so a wedged backend cannot hang a shutdown.
|
|
99
|
+
|
|
100
|
+
@param sinks - Sink adapters to fan each record out to, in priority order.
|
|
101
|
+
|
|
102
|
+
@param flushDeadlineMs - Milliseconds one `flush()` may take before it
|
|
103
|
+
resolves anyway; raise it for slow but working backends such as network
|
|
104
|
+
filesystems.
|
|
105
|
+
|
|
106
|
+
@param verifyTimeoutMs - Milliseconds one sink's `verify()` may take before
|
|
107
|
+
the sink counts as unavailable; raise it for a slow but working probe.
|
|
108
|
+
|
|
109
|
+
@returns Logger plus its eager `initPromise`; callers need not await
|
|
110
|
+
`initPromise` before logging, since startup records replay on verify.
|
|
111
|
+
|
|
112
|
+
@example
|
|
113
|
+
```ts
|
|
114
|
+
const { logger } = createLogger({ sinks: [createConsoleSink()] });
|
|
115
|
+
logger.info('ready');
|
|
116
|
+
await logger.flush();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
@example
|
|
120
|
+
```ts
|
|
121
|
+
const { logger } = createLogger({
|
|
122
|
+
sinks: [createFileSink()],
|
|
123
|
+
flushDeadlineMs: 30_000,
|
|
124
|
+
});
|
|
125
|
+
```
|
|
97
126
|
*/
|
|
98
127
|
export function createLogger(
|
|
99
128
|
{
|
|
100
129
|
sinks,
|
|
101
130
|
flushDeadlineMs = DEFAULT_FLUSH_DEADLINE_MS,
|
|
131
|
+
verifyTimeoutMs = DEFAULT_VERIFY_TIMEOUT_MS,
|
|
102
132
|
}: {
|
|
103
133
|
readonly sinks: readonly Sink[];
|
|
104
134
|
readonly flushDeadlineMs?: number;
|
|
135
|
+
readonly verifyTimeoutMs?: number;
|
|
105
136
|
},
|
|
106
137
|
): {
|
|
107
138
|
readonly initPromise: Promise<void>;
|
|
108
139
|
readonly logger: Logger;
|
|
109
140
|
} {
|
|
110
141
|
/**
|
|
111
|
-
|
|
142
|
+
Per-sink availability for this logger instance, indexed by startup order.
|
|
112
143
|
*/
|
|
113
144
|
const entries: SinkEntry[] = sinks.map(function toEntry(sink,): SinkEntry {
|
|
114
145
|
return {
|
|
@@ -118,35 +149,54 @@ export function createLogger(
|
|
|
118
149
|
},);
|
|
119
150
|
|
|
120
151
|
/**
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
152
|
+
Log records emitted before every sink has completed startup verification.
|
|
153
|
+
Records stay here only during initialization; each sink that later
|
|
154
|
+
verifies as available receives a replay, while already-available sinks
|
|
155
|
+
still receive writes immediately.
|
|
125
156
|
*/
|
|
126
157
|
const startupRecords: LogRecord[] = [];
|
|
127
158
|
|
|
128
159
|
/**
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
160
|
+
Sink writes currently in flight. Logger-level `flush()` drains these so
|
|
161
|
+
sinks without their own flush hook, such as file writes, still settle
|
|
162
|
+
before the flush resolves.
|
|
132
163
|
*/
|
|
133
164
|
const pendingWrites = new Set<Promise<void>>();
|
|
134
165
|
|
|
135
166
|
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
167
|
+
Instance-local aggregate flags. `initialized` flips true once the eager
|
|
168
|
+
`initialize()` settles; `hasAvailableSink` reflects whether any sink
|
|
169
|
+
survives verification and is recomputed by `recomputeAvailability`;
|
|
170
|
+
`droppedStartupRecords` counts startup-buffer overflow drops for the
|
|
171
|
+
post-initialization marker record.
|
|
139
172
|
*/
|
|
140
173
|
const state: {
|
|
174
|
+
droppedStartupRecords: number;
|
|
141
175
|
hasAvailableSink: boolean;
|
|
142
176
|
initialized: boolean;
|
|
143
177
|
} = {
|
|
178
|
+
droppedStartupRecords: 0,
|
|
144
179
|
hasAvailableSink: false,
|
|
145
180
|
initialized: false,
|
|
146
181
|
};
|
|
147
182
|
|
|
148
183
|
/**
|
|
149
|
-
|
|
184
|
+
Buffers a pre-initialization record under {@link STARTUP_BUFFER_CAP},
|
|
185
|
+
dropping the oldest buffered record (and counting the drop) when the cap
|
|
186
|
+
is reached.
|
|
187
|
+
|
|
188
|
+
@param record - Record logged before every sink has verified.
|
|
189
|
+
*/
|
|
190
|
+
function bufferStartupRecord({ record, }: { readonly record: LogRecord; },): void {
|
|
191
|
+
if (startupRecords.length >= STARTUP_BUFFER_CAP) {
|
|
192
|
+
startupRecords.shift();
|
|
193
|
+
state.droppedStartupRecords += 1;
|
|
194
|
+
}
|
|
195
|
+
startupRecords.push(record,);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
Recomputes aggregate availability after a sink entry's flag flips.
|
|
150
200
|
*/
|
|
151
201
|
function recomputeAvailability(): void {
|
|
152
202
|
state.hasAvailableSink = entries.some(function isAvailable(entry,) {
|
|
@@ -155,17 +205,17 @@ export function createLogger(
|
|
|
155
205
|
}
|
|
156
206
|
|
|
157
207
|
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
208
|
+
Reads a sink entry by startup-order index.
|
|
209
|
+
|
|
210
|
+
@param entryIndex - Sink entry index from the construction-time order.
|
|
211
|
+
|
|
212
|
+
@returns Sink entry at that index.
|
|
213
|
+
|
|
214
|
+
@throws Error when index no longer maps to a sink entry.
|
|
165
215
|
*/
|
|
166
216
|
function getSinkEntry({ entryIndex, }: { readonly entryIndex: number; },): SinkEntry {
|
|
167
217
|
/**
|
|
168
|
-
|
|
218
|
+
Sink entry read from startup-order storage; undefined means the caller supplied an invalid index.
|
|
169
219
|
*/
|
|
170
220
|
const entry = entries[entryIndex];
|
|
171
221
|
if (entry === undefined)
|
|
@@ -175,15 +225,15 @@ export function createLogger(
|
|
|
175
225
|
}
|
|
176
226
|
|
|
177
227
|
/**
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
228
|
+
Marks a sink unavailable after its verification fails or throws. Write
|
|
229
|
+
failures never reach here: a rejected write is the sink's own concern and
|
|
230
|
+
leaves the backend available, so one transient hiccup does not retire it.
|
|
231
|
+
|
|
232
|
+
@param entryIndex - Sink entry index whose backend failed verification.
|
|
183
233
|
*/
|
|
184
234
|
function markEntryUnavailable({ entryIndex, }: { readonly entryIndex: number; },): void {
|
|
185
235
|
/**
|
|
186
|
-
|
|
236
|
+
Mutable sink entry being disabled.
|
|
187
237
|
*/
|
|
188
238
|
const entry = getSinkEntry({ entryIndex, },);
|
|
189
239
|
entry.available = false;
|
|
@@ -191,9 +241,9 @@ export function createLogger(
|
|
|
191
241
|
}
|
|
192
242
|
|
|
193
243
|
/**
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
244
|
+
Removes a tracked sink write from {@link pendingWrites} once it settles.
|
|
245
|
+
|
|
246
|
+
@param trackedWrite - Promise returned by {@link trackWrite}.
|
|
197
247
|
*/
|
|
198
248
|
async function removePendingWriteWhenSettled(
|
|
199
249
|
{ trackedWrite, }: { readonly trackedWrite: Promise<void>; },
|
|
@@ -203,11 +253,11 @@ export function createLogger(
|
|
|
203
253
|
}
|
|
204
254
|
|
|
205
255
|
/**
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
256
|
+
Sends a record to one sink, recording the write so `flush()` can await it.
|
|
257
|
+
|
|
258
|
+
@param entryIndex - Available sink entry index to receive the record.
|
|
259
|
+
|
|
260
|
+
@param record - Log record to deliver.
|
|
211
261
|
*/
|
|
212
262
|
function writeRecordToEntry(
|
|
213
263
|
{
|
|
@@ -220,11 +270,11 @@ export function createLogger(
|
|
|
220
270
|
): void {
|
|
221
271
|
try {
|
|
222
272
|
/**
|
|
223
|
-
|
|
273
|
+
Sink entry receiving the record.
|
|
224
274
|
*/
|
|
225
275
|
const entry = getSinkEntry({ entryIndex, },);
|
|
226
276
|
/**
|
|
227
|
-
|
|
277
|
+
Monitored sink write; resolves even when the underlying write rejects, because {@link trackWrite} swallows rejection.
|
|
228
278
|
*/
|
|
229
279
|
const trackedWrite = trackWrite({
|
|
230
280
|
writePromise: entry.sink
|
|
@@ -242,9 +292,9 @@ export function createLogger(
|
|
|
242
292
|
}
|
|
243
293
|
|
|
244
294
|
/**
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
295
|
+
Replays buffered startup records to a sink that just became available.
|
|
296
|
+
|
|
297
|
+
@param entryIndex - Newly available sink entry index.
|
|
248
298
|
*/
|
|
249
299
|
function replayStartupRecordsToEntry({ entryIndex, }: { readonly entryIndex: number; },): void {
|
|
250
300
|
startupRecords.forEach(function replayStartupRecord(record,) {
|
|
@@ -256,12 +306,12 @@ export function createLogger(
|
|
|
256
306
|
}
|
|
257
307
|
|
|
258
308
|
/**
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
309
|
+
Applies a verification result to a sink and replays startup records on
|
|
310
|
+
success.
|
|
311
|
+
|
|
312
|
+
@param entryIndex - Sink entry index whose verification completed.
|
|
313
|
+
|
|
314
|
+
@param available - Whether the backend verified successfully.
|
|
265
315
|
*/
|
|
266
316
|
function setEntryAvailability(
|
|
267
317
|
{
|
|
@@ -273,7 +323,7 @@ export function createLogger(
|
|
|
273
323
|
},
|
|
274
324
|
): void {
|
|
275
325
|
/**
|
|
276
|
-
|
|
326
|
+
Sink entry whose availability is changing.
|
|
277
327
|
*/
|
|
278
328
|
const entry = getSinkEntry({ entryIndex, },);
|
|
279
329
|
entry.available = available;
|
|
@@ -283,21 +333,27 @@ export function createLogger(
|
|
|
283
333
|
}
|
|
284
334
|
|
|
285
335
|
/**
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
336
|
+
Runs one sink's verification under the verify time limit and records the
|
|
337
|
+
result, replaying buffered startup records to it on success. A rejected
|
|
338
|
+
verification, a synchronous throw from the verifier, or a verify that
|
|
339
|
+
runs past `verifyTimeoutMs` drops the sink; a late answer after the limit
|
|
340
|
+
is never observed, so it cannot flip availability afterwards.
|
|
341
|
+
|
|
342
|
+
@param entryIndex - Sink entry index to verify.
|
|
291
343
|
*/
|
|
292
344
|
async function verifyAndApply({ entryIndex, }: { readonly entryIndex: number; },): Promise<void> {
|
|
293
345
|
try {
|
|
294
346
|
/**
|
|
295
|
-
|
|
347
|
+
Sink entry whose verifier is about to run.
|
|
296
348
|
*/
|
|
297
349
|
const entry = getSinkEntry({ entryIndex, },);
|
|
298
350
|
setEntryAvailability({
|
|
299
|
-
available: await
|
|
300
|
-
|
|
351
|
+
available: await withTimeout({
|
|
352
|
+
label: `sink ${entryIndex} verify`,
|
|
353
|
+
ms: verifyTimeoutMs,
|
|
354
|
+
promise: entry.sink
|
|
355
|
+
.verify(),
|
|
356
|
+
},),
|
|
301
357
|
entryIndex,
|
|
302
358
|
},);
|
|
303
359
|
}
|
|
@@ -311,48 +367,90 @@ export function createLogger(
|
|
|
311
367
|
}
|
|
312
368
|
|
|
313
369
|
/**
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
370
|
+
Writes one synthetic `warn` record to every available sink when the
|
|
371
|
+
startup buffer overflowed, so the loss is never silent. Runs once, after
|
|
372
|
+
initialization, when the dropped count is known and final.
|
|
373
|
+
*/
|
|
374
|
+
function emitDroppedStartupMarker(): void {
|
|
375
|
+
/**
|
|
376
|
+
Records dropped from the startup buffer; zero means nothing to report.
|
|
377
|
+
*/
|
|
378
|
+
const dropped = state.droppedStartupRecords;
|
|
379
|
+
if (dropped === 0)
|
|
380
|
+
return;
|
|
381
|
+
/**
|
|
382
|
+
Marker record naming the loss; the noun agrees with the count.
|
|
383
|
+
*/
|
|
384
|
+
const marker: LogRecord = {
|
|
385
|
+
level: 'warn',
|
|
386
|
+
message: `${dropped} startup record${(dropped === 1) ? '' : 's'} dropped before a backend verified (buffer cap ${STARTUP_BUFFER_CAP})`,
|
|
387
|
+
timestamp: Date.now(),
|
|
388
|
+
};
|
|
389
|
+
entries.forEach(function writeMarker(
|
|
390
|
+
entry,
|
|
391
|
+
entryIndex,
|
|
392
|
+
) {
|
|
393
|
+
if (entry.available) {
|
|
394
|
+
writeRecordToEntry({
|
|
395
|
+
entryIndex,
|
|
396
|
+
record: marker,
|
|
397
|
+
},);
|
|
398
|
+
}
|
|
399
|
+
},);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
Initializes all sink backends by verifying their availability. Runs once
|
|
404
|
+
at construction. Every verifier runs concurrently, each under the verify
|
|
405
|
+
time limit, so one backend that never answers cannot starve the rest or
|
|
406
|
+
keep `initialized` from flipping. Completion order does not affect
|
|
407
|
+
correctness: a record's immediate-write set (sinks already available when
|
|
408
|
+
it was logged) and its replay set (sinks that become available later) are
|
|
409
|
+
disjoint, so each available sink receives each record exactly once
|
|
410
|
+
whichever verify settles first.
|
|
319
411
|
*/
|
|
320
412
|
async function initialize(): Promise<void> {
|
|
321
413
|
if (state.initialized)
|
|
322
414
|
return;
|
|
323
415
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
416
|
+
await Promise.all(
|
|
417
|
+
entries.map(function verifyEntry(
|
|
418
|
+
_entry,
|
|
419
|
+
entryIndex,
|
|
420
|
+
) {
|
|
421
|
+
return verifyAndApply({ entryIndex, },);
|
|
422
|
+
},),
|
|
423
|
+
);
|
|
327
424
|
|
|
328
425
|
state.initialized = true;
|
|
329
426
|
startupRecords.length = 0;
|
|
427
|
+
emitDroppedStartupMarker();
|
|
330
428
|
}
|
|
331
429
|
|
|
332
430
|
/**
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
431
|
+
Eager readiness promise. Consumers do not need to await this before
|
|
432
|
+
logging; `flush()` awaits it internally, and startup records replay to
|
|
433
|
+
async sinks as they become available.
|
|
336
434
|
*/
|
|
337
435
|
const initPromise: Promise<void> = initialize();
|
|
338
436
|
|
|
339
437
|
/**
|
|
340
|
-
|
|
438
|
+
Drains every currently tracked sink write.
|
|
341
439
|
*/
|
|
342
440
|
async function drainPendingWrites(): Promise<void> {
|
|
343
441
|
/**
|
|
344
|
-
|
|
442
|
+
Snapshot of tracked writes at flush time.
|
|
345
443
|
*/
|
|
346
444
|
const writes = [...pendingWrites,];
|
|
347
445
|
await Promise.all(writes,);
|
|
348
446
|
}
|
|
349
447
|
|
|
350
448
|
/**
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
449
|
+
Creates a logging method for the specified severity level.
|
|
450
|
+
|
|
451
|
+
@param level - Log severity level for messages from this method.
|
|
452
|
+
|
|
453
|
+
@returns Logging function for the given level.
|
|
356
454
|
*/
|
|
357
455
|
function createMethod(level: Level,): (message: string,) => void {
|
|
358
456
|
return function logAtLevel(message: string,): void {
|
|
@@ -361,7 +459,7 @@ export function createLogger(
|
|
|
361
459
|
throw new Error('No logging backends available',);
|
|
362
460
|
|
|
363
461
|
/**
|
|
364
|
-
|
|
462
|
+
Shared LogRecord forwarded to every available sink; built once per call so all sinks see the same timestamp.
|
|
365
463
|
*/
|
|
366
464
|
const record: LogRecord = {
|
|
367
465
|
level,
|
|
@@ -370,10 +468,10 @@ export function createLogger(
|
|
|
370
468
|
};
|
|
371
469
|
|
|
372
470
|
if (!state.initialized)
|
|
373
|
-
|
|
471
|
+
bufferStartupRecord({ record, },);
|
|
374
472
|
|
|
375
473
|
/**
|
|
376
|
-
|
|
474
|
+
Indices of sinks that survived verification; recomputed per call so a sink dropped at verify time is excluded next time.
|
|
377
475
|
*/
|
|
378
476
|
const availableIndices = entries
|
|
379
477
|
.map(function indexEntry(
|
|
@@ -397,8 +495,8 @@ export function createLogger(
|
|
|
397
495
|
}
|
|
398
496
|
|
|
399
497
|
/**
|
|
400
|
-
|
|
401
|
-
|
|
498
|
+
Runs every available sink's own `flush` hook. A rejecting hook marks that
|
|
499
|
+
sink unavailable and does not fail the aggregate.
|
|
402
500
|
*/
|
|
403
501
|
async function runSinkFlushHooks(): Promise<void> {
|
|
404
502
|
await Promise.all(
|
|
@@ -407,7 +505,7 @@ export function createLogger(
|
|
|
407
505
|
entryIndex,
|
|
408
506
|
) {
|
|
409
507
|
/**
|
|
410
|
-
|
|
508
|
+
Optional sink-supplied flush hook; absent when the sink writes synchronously and needs no draining.
|
|
411
509
|
*/
|
|
412
510
|
const sinkFlush = entry.sink
|
|
413
511
|
.flush;
|
|
@@ -428,11 +526,11 @@ export function createLogger(
|
|
|
428
526
|
}
|
|
429
527
|
|
|
430
528
|
/**
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
529
|
+
Drains startup verification, in-flight sink writes (via
|
|
530
|
+
{@link drainPendingWrites}), and every sink flush hook (via
|
|
531
|
+
{@link runSinkFlushHooks}) in order. Never rejects on its own: every
|
|
532
|
+
inner failure is already reported and swallowed, so the only way this
|
|
533
|
+
stays pending is a verify, write, or hook that never settles.
|
|
436
534
|
*/
|
|
437
535
|
async function drainEverything(): Promise<void> {
|
|
438
536
|
await initPromise;
|
|
@@ -441,21 +539,21 @@ export function createLogger(
|
|
|
441
539
|
}
|
|
442
540
|
|
|
443
541
|
/**
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
542
|
+
Drops every tracked write from the logger's view after the flush deadline
|
|
543
|
+
elapsed. The tracked wrappers keep their own rejection handling, so a
|
|
544
|
+
late settlement can neither surface as an unhandled rejection nor stall
|
|
545
|
+
the next `flush()`. The underlying sink work is not cancelled: sinks
|
|
546
|
+
expose no cancellation signal.
|
|
449
547
|
*/
|
|
450
548
|
function abandonPendingWrites(): void {
|
|
451
549
|
pendingWrites.clear();
|
|
452
550
|
}
|
|
453
551
|
|
|
454
552
|
/**
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
553
|
+
Runs {@link drainEverything} under the flush deadline. Resolves once all
|
|
554
|
+
tracked writes and hooks have settled, or once `flushDeadlineMs` elapses,
|
|
555
|
+
whichever comes first; a deadline hit reports one breadcrumb and abandons
|
|
556
|
+
the tracked writes so shutdown proceeds.
|
|
459
557
|
*/
|
|
460
558
|
async function flushAll(): Promise<void> {
|
|
461
559
|
try {
|
|
@@ -475,7 +573,7 @@ export function createLogger(
|
|
|
475
573
|
}
|
|
476
574
|
|
|
477
575
|
/**
|
|
478
|
-
|
|
576
|
+
Multi-sink logger that writes to all available backends.
|
|
479
577
|
*/
|
|
480
578
|
const logger: Logger = {
|
|
481
579
|
debug: createMethod('debug',),
|