@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +58 -12
  3. package/dist/final/neutral/browser.d.mts +60 -0
  4. package/dist/final/neutral/browser.mjs +1 -0
  5. package/dist/final/neutral/index.d.mts +366 -572
  6. package/dist/final/neutral/index.mjs +2 -3
  7. package/dist/final/neutral/indexed-db-hsIfv7Cv.mjs +2 -0
  8. package/dist/final/neutral/types-BkkBXgY3.d.mts +76 -0
  9. package/dist/final/node/file-CRGb1hDK.mjs +1 -0
  10. package/dist/final/node/index.d.mts +366 -572
  11. package/dist/final/node/index.mjs +3 -3
  12. package/dist/final/node/node.d.mts +103 -0
  13. package/dist/final/node/node.mjs +1 -0
  14. package/dist/final/node/types-BkkBXgY3.d.mts +76 -0
  15. package/package.json +19 -5
  16. package/src/artifact-platform-split.unit.test.ts +140 -0
  17. package/src/browser.ts +14 -0
  18. package/src/create-logger.ts +249 -151
  19. package/src/create-logger.unit.test.ts +527 -75
  20. package/src/default-sinks.neutral.ts +34 -0
  21. package/src/default-sinks.node.ts +32 -0
  22. package/src/error-format.ts +23 -23
  23. package/src/index.ts +2 -0
  24. package/src/logger.ts +23 -50
  25. package/src/node.ts +23 -0
  26. package/src/sink/console-control-chars.ts +64 -64
  27. package/src/sink/console-control-chars.unit.test.ts +14 -14
  28. package/src/sink/console.ts +194 -194
  29. package/src/sink/console.unit.test.ts +18 -18
  30. package/src/sink/file.ts +136 -140
  31. package/src/sink/file.unit.test.ts +19 -26
  32. package/src/sink/index.ts +4 -7
  33. package/src/sink/indexed-db-util.ts +42 -42
  34. package/src/sink/indexed-db.browser.test.ts +7 -7
  35. package/src/sink/indexed-db.ts +109 -109
  36. package/src/sink/indexed-db.unit.test.ts +5 -13
  37. package/src/sink/local-storage-key.ts +73 -73
  38. package/src/sink/local-storage-key.unit.test.ts +8 -8
  39. package/src/sink/local-storage-quota.ts +37 -37
  40. package/src/sink/local-storage-quota.unit.test.ts +8 -8
  41. package/src/sink/local-storage-store.ts +113 -113
  42. package/src/sink/local-storage-store.unit.test.ts +35 -35
  43. package/src/sink/local-storage.ts +72 -72
  44. package/src/sink/local-storage.unit.test.ts +27 -27
  45. package/src/sink/noop.ts +20 -20
  46. package/src/sink/noop.unit.test.ts +1 -1
  47. package/src/sink/opfs.browser.test.ts +7 -7
  48. package/src/sink/opfs.ts +62 -62
  49. package/src/sink/opfs.unit.test.ts +5 -13
  50. package/src/sink/record-buffer.ts +84 -84
  51. package/src/sink/record-buffer.unit.test.ts +20 -20
  52. package/src/sink/session-storage-quota.ts +34 -34
  53. package/src/sink/session-storage-quota.unit.test.ts +8 -8
  54. package/src/sink/session-storage-store.ts +72 -72
  55. package/src/sink/session-storage.ts +48 -48
  56. package/src/sink/session-storage.unit.test.ts +39 -39
  57. package/src/sink/web-storage-quota-error.ts +22 -22
  58. package/src/sink/web-storage-quota-error.unit.test.ts +2 -2
  59. package/src/sink/web-storage-runtime.ts +24 -24
  60. package/src/startup.unit.test.ts +18 -18
  61. package/src/tagged.ts +35 -35
  62. package/src/tagged.unit.test.ts +8 -8
  63. package/src/types.ts +39 -39
@@ -9,20 +9,41 @@ import type {
9
9
  } from './types.ts';
10
10
 
11
11
  /**
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}.
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
- * Sink paired with its current availability inside one logger instance.
23
- * Availability starts `false` (not-yet-verified reads as unavailable,
24
- * identical to a verified failure) and flips `true` once the sink's own
25
- * `verify` confirms its backend.
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
- * Awaits one sink write so `flush()` can observe its settling. A rejected
34
- * write is swallowed here via {@link reportLoggerInternalError} (the sink
35
- * owns its own write-error handling) and does not disable the sink: one
36
- * transient failure must not retire a backend for the rest of the run.
37
- *
38
- * @param writePromise - Promise returned by the sink write call.
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
- * Builds a multi-sink logger over the supplied sink adapters. All
56
- * orchestration (per-sink availability, startup buffering and replay,
57
- * in-flight write tracking, and flush) lives here; the exported default
58
- * `logger` is just this factory applied to the default sink set, and tests
59
- * apply it to fake sinks to exercise the orchestration directly.
60
- *
61
- * Verification runs eagerly at construction and never blocks callers:
62
- * records emitted while an async sink is still verifying buffer internally
63
- * and replay to that sink the moment it verifies. A sink whose `verify`
64
- * resolves `false` or throws is dropped and receives no records. A rejected
65
- * `write` is the sink's own concern and does not disable the backend.
66
- *
67
- * `flush()` always resolves: one deadline (`flushDeadlineMs`, default
68
- * {@link DEFAULT_FLUSH_DEADLINE_MS}) wraps startup verification, the
69
- * in-flight write drain, and every sink flush hook together. When it elapses
70
- * the logger reports one breadcrumb, abandons the tracked writes from its
71
- * view (the sinks expose no cancellation, so the underlying work continues),
72
- * and resolves, so a wedged backend cannot hang a shutdown.
73
- *
74
- * @param sinks - Sink adapters to fan each record out to, in priority order.
75
- *
76
- * @param flushDeadlineMs - Milliseconds one `flush()` may take before it
77
- * resolves anyway; raise it for slow but working backends such as network
78
- * filesystems.
79
- *
80
- * @returns Logger plus its eager `initPromise`; callers need not await
81
- * `initPromise` before logging, since startup records replay on verify.
82
- *
83
- * @example
84
- * ```ts
85
- * const { logger } = createLogger({ sinks: [createConsoleSink()] });
86
- * logger.info('ready');
87
- * await logger.flush();
88
- * ```
89
- *
90
- * @example
91
- * ```ts
92
- * const { logger } = createLogger({
93
- * sinks: [createFileSink()],
94
- * flushDeadlineMs: 30_000,
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
- * Per-sink availability for this logger instance, indexed by startup order.
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
- * Log records emitted before every sink has completed startup verification.
122
- * Records stay here only during initialization; each sink that later
123
- * verifies as available receives a replay, while already-available sinks
124
- * still receive writes immediately.
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
- * Sink writes currently in flight. Logger-level `flush()` drains these so
130
- * sinks without their own flush hook, such as file writes, still settle
131
- * before the flush resolves.
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
- * Instance-local aggregate flags. `initialized` flips true once the eager
137
- * `initialize()` settles; `hasAvailableSink` reflects whether any sink
138
- * survives verification and is recomputed by `recomputeAvailability`.
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
- * Recomputes aggregate availability after a sink entry's flag flips.
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
- * Reads a sink entry by startup-order index.
159
- *
160
- * @param entryIndex - Sink entry index from the construction-time order.
161
- *
162
- * @returns Sink entry at that index.
163
- *
164
- * @throws Error when index no longer maps to a sink entry.
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
- * Sink entry read from startup-order storage; undefined means the caller supplied an invalid index.
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
- * Marks a sink unavailable after its verification fails or throws. Write
179
- * failures never reach here: a rejected write is the sink's own concern and
180
- * leaves the backend available, so one transient hiccup does not retire it.
181
- *
182
- * @param entryIndex - Sink entry index whose backend failed verification.
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
- * Mutable sink entry being disabled.
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
- * Removes a tracked sink write from {@link pendingWrites} once it settles.
195
- *
196
- * @param trackedWrite - Promise returned by {@link trackWrite}.
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
- * Sends a record to one sink, recording the write so `flush()` can await it.
207
- *
208
- * @param entryIndex - Available sink entry index to receive the record.
209
- *
210
- * @param record - Log record to deliver.
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
- * Sink entry receiving the record.
273
+ Sink entry receiving the record.
224
274
  */
225
275
  const entry = getSinkEntry({ entryIndex, },);
226
276
  /**
227
- * Monitored sink write; resolves even when the underlying write rejects, because {@link trackWrite} swallows rejection.
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
- * Replays buffered startup records to a sink that just became available.
246
- *
247
- * @param entryIndex - Newly available sink entry index.
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
- * Applies a verification result to a sink and replays startup records on
260
- * success.
261
- *
262
- * @param entryIndex - Sink entry index whose verification completed.
263
- *
264
- * @param available - Whether the backend verified successfully.
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
- * Sink entry whose availability is changing.
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
- * Runs one sink's verification and records the result, replaying buffered
287
- * startup records to it on success. A rejected verification (or a
288
- * synchronous throw from the verifier) drops the sink.
289
- *
290
- * @param entryIndex - Sink entry index to verify.
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
- * Sink entry whose verifier is about to run.
347
+ Sink entry whose verifier is about to run.
296
348
  */
297
349
  const entry = getSinkEntry({ entryIndex, },);
298
350
  setEntryAvailability({
299
- available: await entry.sink
300
- .verify(),
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
- * Initializes all sink backends by verifying their availability. Runs once
315
- * at construction. Verification order does not affect correctness: every
316
- * sink that verifies available replays the full startup buffer, so no sink
317
- * depends on another verifying first. The sequential await keeps startup in
318
- * a single deterministic order rather than racing all verifiers at once.
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
- for (const [entryIndex,] of entries.entries())
325
- // oxlint-disable-next-line no-await-in-loop -- Verify sinks in priority order; order does not affect replay correctness.
326
- await verifyAndApply({ entryIndex, },);
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
- * Eager readiness promise. Consumers do not need to await this before
334
- * logging; `flush()` awaits it internally, and startup records replay to
335
- * async sinks as they become available.
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
- * Drains every currently tracked sink write.
438
+ Drains every currently tracked sink write.
341
439
  */
342
440
  async function drainPendingWrites(): Promise<void> {
343
441
  /**
344
- * Snapshot of tracked writes at flush time.
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
- * Creates a logging method for the specified severity level.
352
- *
353
- * @param level - Log severity level for messages from this method.
354
- *
355
- * @returns Logging function for the given level.
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
- * Shared LogRecord forwarded to every available sink; built once per call so all sinks see the same timestamp.
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
- startupRecords.push(record,);
471
+ bufferStartupRecord({ record, },);
374
472
 
375
473
  /**
376
- * Indices of sinks that survived verification; recomputed per call so a sink dropped at verify time is excluded next time.
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
- * Runs every available sink's own `flush` hook. A rejecting hook marks that
401
- * sink unavailable and does not fail the aggregate.
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
- * Optional sink-supplied flush hook; absent when the sink writes synchronously and needs no draining.
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
- * Drains startup verification, in-flight sink writes (via
432
- * {@link drainPendingWrites}), and every sink flush hook (via
433
- * {@link runSinkFlushHooks}) in order. Never rejects on its own: every
434
- * inner failure is already reported and swallowed, so the only way this
435
- * stays pending is a verify, write, or hook that never settles.
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
- * Drops every tracked write from the logger's view after the flush deadline
445
- * elapsed. The tracked wrappers keep their own rejection handling, so a
446
- * late settlement can neither surface as an unhandled rejection nor stall
447
- * the next `flush()`. The underlying sink work is not cancelled: sinks
448
- * expose no cancellation signal.
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
- * Runs {@link drainEverything} under the flush deadline. Resolves once all
456
- * tracked writes and hooks have settled, or once `flushDeadlineMs` elapses,
457
- * whichever comes first; a deadline hit reports one breadcrumb and abandons
458
- * the tracked writes so shutdown proceeds.
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
- * Multi-sink logger that writes to all available backends.
576
+ Multi-sink logger that writes to all available backends.
479
577
  */
480
578
  const logger: Logger = {
481
579
  debug: createMethod('debug',),