@likec4/log 1.49.0 → 1.51.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.
@@ -0,0 +1,1314 @@
1
+ import { t as __exportAll } from "../../rolldown-runtime.mjs";
2
+ /**
3
+ * Converts a {@link FilterLike} value to an actual {@link Filter}.
4
+ *
5
+ * @param filter The filter-like value to convert.
6
+ * @returns The actual filter.
7
+ */
8
+ function toFilter(filter) {
9
+ if (typeof filter === "function") return filter;
10
+ return getLevelFilter(filter);
11
+ }
12
+ /**
13
+ * Returns a filter that accepts log records with the specified level.
14
+ *
15
+ * @param level The level to filter by. If `null`, the filter will reject all
16
+ * records.
17
+ * @returns The filter.
18
+ */
19
+ function getLevelFilter(level) {
20
+ if (level == null) return () => false;
21
+ if (level === "fatal") return (record) => record.level === "fatal";
22
+ else if (level === "error") return (record) => record.level === "fatal" || record.level === "error";
23
+ else if (level === "warning") return (record) => record.level === "fatal" || record.level === "error" || record.level === "warning";
24
+ else if (level === "info") return (record) => record.level === "fatal" || record.level === "error" || record.level === "warning" || record.level === "info";
25
+ else if (level === "debug") return (record) => record.level === "fatal" || record.level === "error" || record.level === "warning" || record.level === "info" || record.level === "debug";
26
+ else if (level === "trace") return () => true;
27
+ throw new TypeError(`Invalid log level: ${level}.`);
28
+ }
29
+ const logLevels = [
30
+ "trace",
31
+ "debug",
32
+ "info",
33
+ "warning",
34
+ "error",
35
+ "fatal"
36
+ ];
37
+ /**
38
+ * Compares two log levels.
39
+ * @param a The first log level.
40
+ * @param b The second log level.
41
+ * @returns A negative number if `a` is less than `b`, a positive number if `a`
42
+ * is greater than `b`, or zero if they are equal.
43
+ * @since 0.8.0
44
+ */
45
+ function compareLogLevel(a, b) {
46
+ const aIndex = logLevels.indexOf(a);
47
+ if (aIndex < 0) throw new TypeError(`Invalid log level: ${JSON.stringify(a)}.`);
48
+ const bIndex = logLevels.indexOf(b);
49
+ if (bIndex < 0) throw new TypeError(`Invalid log level: ${JSON.stringify(b)}.`);
50
+ return aIndex - bIndex;
51
+ }
52
+ /**
53
+ * Get a logger with the given category.
54
+ *
55
+ * ```typescript
56
+ * const logger = getLogger(["my-app"]);
57
+ * ```
58
+ *
59
+ * @param category The category of the logger. It can be a string or an array
60
+ * of strings. If it is a string, it is equivalent to an array
61
+ * with a single element.
62
+ * @returns The logger.
63
+ */
64
+ function getLogger(category = []) {
65
+ return LoggerImpl.getLogger(category);
66
+ }
67
+ /**
68
+ * The symbol for the global root logger.
69
+ */
70
+ const globalRootLoggerSymbol = Symbol.for("logtape.rootLogger");
71
+ /**
72
+ * A logger implementation. Do not use this directly; use {@link getLogger}
73
+ * instead. This class is exported for testing purposes.
74
+ */
75
+ var LoggerImpl = class LoggerImpl {
76
+ parent;
77
+ children;
78
+ category;
79
+ sinks;
80
+ parentSinks = "inherit";
81
+ filters;
82
+ lowestLevel = "trace";
83
+ contextLocalStorage;
84
+ static getLogger(category = []) {
85
+ let rootLogger = globalRootLoggerSymbol in globalThis ? globalThis[globalRootLoggerSymbol] ?? null : null;
86
+ if (rootLogger == null) {
87
+ rootLogger = new LoggerImpl(null, []);
88
+ globalThis[globalRootLoggerSymbol] = rootLogger;
89
+ }
90
+ if (typeof category === "string") return rootLogger.getChild(category);
91
+ if (category.length === 0) return rootLogger;
92
+ return rootLogger.getChild(category);
93
+ }
94
+ constructor(parent, category) {
95
+ this.parent = parent;
96
+ this.children = {};
97
+ this.category = category;
98
+ this.sinks = [];
99
+ this.filters = [];
100
+ }
101
+ getChild(subcategory) {
102
+ const name = typeof subcategory === "string" ? subcategory : subcategory[0];
103
+ const childRef = this.children[name];
104
+ let child = childRef instanceof LoggerImpl ? childRef : childRef?.deref();
105
+ if (child == null) {
106
+ child = new LoggerImpl(this, [...this.category, name]);
107
+ this.children[name] = "WeakRef" in globalThis ? new WeakRef(child) : child;
108
+ }
109
+ if (typeof subcategory === "string" || subcategory.length === 1) return child;
110
+ return child.getChild(subcategory.slice(1));
111
+ }
112
+ /**
113
+ * Reset the logger. This removes all sinks and filters from the logger.
114
+ */
115
+ reset() {
116
+ while (this.sinks.length > 0) this.sinks.shift();
117
+ this.parentSinks = "inherit";
118
+ while (this.filters.length > 0) this.filters.shift();
119
+ this.lowestLevel = "trace";
120
+ }
121
+ /**
122
+ * Reset the logger and all its descendants. This removes all sinks and
123
+ * filters from the logger and all its descendants.
124
+ */
125
+ resetDescendants() {
126
+ for (const child of Object.values(this.children)) {
127
+ const logger = child instanceof LoggerImpl ? child : child.deref();
128
+ if (logger != null) logger.resetDescendants();
129
+ }
130
+ this.reset();
131
+ }
132
+ with(properties) {
133
+ return new LoggerCtx(this, { ...properties });
134
+ }
135
+ filter(record) {
136
+ for (const filter of this.filters) if (!filter(record)) return false;
137
+ if (this.filters.length < 1) return this.parent?.filter(record) ?? true;
138
+ return true;
139
+ }
140
+ *getSinks(level) {
141
+ if (this.lowestLevel === null || compareLogLevel(level, this.lowestLevel) < 0) return;
142
+ if (this.parent != null && this.parentSinks === "inherit") for (const sink of this.parent.getSinks(level)) yield sink;
143
+ for (const sink of this.sinks) yield sink;
144
+ }
145
+ emit(record, bypassSinks) {
146
+ const categoryPrefix = getCategoryPrefix();
147
+ const baseCategory = "category" in record ? record.category : this.category;
148
+ const fullCategory = categoryPrefix.length > 0 ? [...categoryPrefix, ...baseCategory] : baseCategory;
149
+ const descriptors = Object.getOwnPropertyDescriptors(record);
150
+ descriptors.category = {
151
+ value: fullCategory,
152
+ enumerable: true,
153
+ configurable: true
154
+ };
155
+ const fullRecord = Object.defineProperties({}, descriptors);
156
+ if (this.lowestLevel === null || compareLogLevel(fullRecord.level, this.lowestLevel) < 0 || !this.filter(fullRecord)) return;
157
+ for (const sink of this.getSinks(fullRecord.level)) {
158
+ if (bypassSinks?.has(sink)) continue;
159
+ try {
160
+ sink(fullRecord);
161
+ } catch (error) {
162
+ const bypassSinks2 = new Set(bypassSinks);
163
+ bypassSinks2.add(sink);
164
+ metaLogger.log("fatal", "Failed to emit a log record to sink {sink}: {error}", {
165
+ sink,
166
+ error,
167
+ record: fullRecord
168
+ }, bypassSinks2);
169
+ }
170
+ }
171
+ }
172
+ log(level, rawMessage, properties, bypassSinks) {
173
+ const implicitContext = getImplicitContext();
174
+ let cachedProps = void 0;
175
+ const record = typeof properties === "function" ? {
176
+ category: this.category,
177
+ level,
178
+ timestamp: Date.now(),
179
+ get message() {
180
+ return parseMessageTemplate(rawMessage, this.properties);
181
+ },
182
+ rawMessage,
183
+ get properties() {
184
+ if (cachedProps == null) cachedProps = {
185
+ ...implicitContext,
186
+ ...properties()
187
+ };
188
+ return cachedProps;
189
+ }
190
+ } : {
191
+ category: this.category,
192
+ level,
193
+ timestamp: Date.now(),
194
+ message: parseMessageTemplate(rawMessage, {
195
+ ...implicitContext,
196
+ ...properties
197
+ }),
198
+ rawMessage,
199
+ properties: {
200
+ ...implicitContext,
201
+ ...properties
202
+ }
203
+ };
204
+ this.emit(record, bypassSinks);
205
+ }
206
+ logLazily(level, callback, properties = {}) {
207
+ const implicitContext = getImplicitContext();
208
+ let rawMessage = void 0;
209
+ let msg = void 0;
210
+ function realizeMessage() {
211
+ if (msg == null || rawMessage == null) {
212
+ msg = callback((tpl, ...values) => {
213
+ rawMessage = tpl;
214
+ return renderMessage(tpl, values);
215
+ });
216
+ if (rawMessage == null) throw new TypeError("No log record was made.");
217
+ }
218
+ return [msg, rawMessage];
219
+ }
220
+ this.emit({
221
+ category: this.category,
222
+ level,
223
+ get message() {
224
+ return realizeMessage()[0];
225
+ },
226
+ get rawMessage() {
227
+ return realizeMessage()[1];
228
+ },
229
+ timestamp: Date.now(),
230
+ properties: {
231
+ ...implicitContext,
232
+ ...properties
233
+ }
234
+ });
235
+ }
236
+ logTemplate(level, messageTemplate, values, properties = {}) {
237
+ const implicitContext = getImplicitContext();
238
+ this.emit({
239
+ category: this.category,
240
+ level,
241
+ message: renderMessage(messageTemplate, values),
242
+ rawMessage: messageTemplate,
243
+ timestamp: Date.now(),
244
+ properties: {
245
+ ...implicitContext,
246
+ ...properties
247
+ }
248
+ });
249
+ }
250
+ trace(message, ...values) {
251
+ if (typeof message === "string") this.log("trace", message, values[0] ?? {});
252
+ else if (typeof message === "function") this.logLazily("trace", message);
253
+ else if (!Array.isArray(message)) this.log("trace", "{*}", message);
254
+ else this.logTemplate("trace", message, values);
255
+ }
256
+ debug(message, ...values) {
257
+ if (typeof message === "string") this.log("debug", message, values[0] ?? {});
258
+ else if (typeof message === "function") this.logLazily("debug", message);
259
+ else if (!Array.isArray(message)) this.log("debug", "{*}", message);
260
+ else this.logTemplate("debug", message, values);
261
+ }
262
+ info(message, ...values) {
263
+ if (typeof message === "string") this.log("info", message, values[0] ?? {});
264
+ else if (typeof message === "function") this.logLazily("info", message);
265
+ else if (!Array.isArray(message)) this.log("info", "{*}", message);
266
+ else this.logTemplate("info", message, values);
267
+ }
268
+ warn(message, ...values) {
269
+ if (typeof message === "string") this.log("warning", message, values[0] ?? {});
270
+ else if (typeof message === "function") this.logLazily("warning", message);
271
+ else if (!Array.isArray(message)) this.log("warning", "{*}", message);
272
+ else this.logTemplate("warning", message, values);
273
+ }
274
+ warning(message, ...values) {
275
+ this.warn(message, ...values);
276
+ }
277
+ error(message, ...values) {
278
+ if (typeof message === "string") this.log("error", message, values[0] ?? {});
279
+ else if (typeof message === "function") this.logLazily("error", message);
280
+ else if (!Array.isArray(message)) this.log("error", "{*}", message);
281
+ else this.logTemplate("error", message, values);
282
+ }
283
+ fatal(message, ...values) {
284
+ if (typeof message === "string") this.log("fatal", message, values[0] ?? {});
285
+ else if (typeof message === "function") this.logLazily("fatal", message);
286
+ else if (!Array.isArray(message)) this.log("fatal", "{*}", message);
287
+ else this.logTemplate("fatal", message, values);
288
+ }
289
+ };
290
+ /**
291
+ * A logger implementation with contextual properties. Do not use this
292
+ * directly; use {@link Logger.with} instead. This class is exported
293
+ * for testing purposes.
294
+ */
295
+ var LoggerCtx = class LoggerCtx {
296
+ logger;
297
+ properties;
298
+ constructor(logger, properties) {
299
+ this.logger = logger;
300
+ this.properties = properties;
301
+ }
302
+ get category() {
303
+ return this.logger.category;
304
+ }
305
+ get parent() {
306
+ return this.logger.parent;
307
+ }
308
+ getChild(subcategory) {
309
+ return this.logger.getChild(subcategory).with(this.properties);
310
+ }
311
+ with(properties) {
312
+ return new LoggerCtx(this.logger, {
313
+ ...this.properties,
314
+ ...properties
315
+ });
316
+ }
317
+ log(level, message, properties, bypassSinks) {
318
+ this.logger.log(level, message, typeof properties === "function" ? () => ({
319
+ ...this.properties,
320
+ ...properties()
321
+ }) : {
322
+ ...this.properties,
323
+ ...properties
324
+ }, bypassSinks);
325
+ }
326
+ logLazily(level, callback) {
327
+ this.logger.logLazily(level, callback, this.properties);
328
+ }
329
+ logTemplate(level, messageTemplate, values) {
330
+ this.logger.logTemplate(level, messageTemplate, values, this.properties);
331
+ }
332
+ emit(record) {
333
+ const recordWithContext = {
334
+ ...record,
335
+ properties: {
336
+ ...this.properties,
337
+ ...record.properties
338
+ }
339
+ };
340
+ this.logger.emit(recordWithContext);
341
+ }
342
+ trace(message, ...values) {
343
+ if (typeof message === "string") this.log("trace", message, values[0] ?? {});
344
+ else if (typeof message === "function") this.logLazily("trace", message);
345
+ else if (!Array.isArray(message)) this.log("trace", "{*}", message);
346
+ else this.logTemplate("trace", message, values);
347
+ }
348
+ debug(message, ...values) {
349
+ if (typeof message === "string") this.log("debug", message, values[0] ?? {});
350
+ else if (typeof message === "function") this.logLazily("debug", message);
351
+ else if (!Array.isArray(message)) this.log("debug", "{*}", message);
352
+ else this.logTemplate("debug", message, values);
353
+ }
354
+ info(message, ...values) {
355
+ if (typeof message === "string") this.log("info", message, values[0] ?? {});
356
+ else if (typeof message === "function") this.logLazily("info", message);
357
+ else if (!Array.isArray(message)) this.log("info", "{*}", message);
358
+ else this.logTemplate("info", message, values);
359
+ }
360
+ warn(message, ...values) {
361
+ if (typeof message === "string") this.log("warning", message, values[0] ?? {});
362
+ else if (typeof message === "function") this.logLazily("warning", message);
363
+ else if (!Array.isArray(message)) this.log("warning", "{*}", message);
364
+ else this.logTemplate("warning", message, values);
365
+ }
366
+ warning(message, ...values) {
367
+ this.warn(message, ...values);
368
+ }
369
+ error(message, ...values) {
370
+ if (typeof message === "string") this.log("error", message, values[0] ?? {});
371
+ else if (typeof message === "function") this.logLazily("error", message);
372
+ else if (!Array.isArray(message)) this.log("error", "{*}", message);
373
+ else this.logTemplate("error", message, values);
374
+ }
375
+ fatal(message, ...values) {
376
+ if (typeof message === "string") this.log("fatal", message, values[0] ?? {});
377
+ else if (typeof message === "function") this.logLazily("fatal", message);
378
+ else if (!Array.isArray(message)) this.log("fatal", "{*}", message);
379
+ else this.logTemplate("fatal", message, values);
380
+ }
381
+ };
382
+ /**
383
+ * The meta logger. It is a logger with the category `["logtape", "meta"]`.
384
+ */
385
+ const metaLogger = LoggerImpl.getLogger(["logtape", "meta"]);
386
+ /**
387
+ * Check if a property access key contains nested access patterns.
388
+ * @param key The property key to check.
389
+ * @returns True if the key contains nested access patterns.
390
+ */
391
+ function isNestedAccess(key) {
392
+ return key.includes(".") || key.includes("[") || key.includes("?.");
393
+ }
394
+ /**
395
+ * Safely access an own property from an object, blocking prototype pollution.
396
+ *
397
+ * @param obj The object to access the property from.
398
+ * @param key The property key to access.
399
+ * @returns The property value or undefined if not accessible.
400
+ */
401
+ function getOwnProperty(obj, key) {
402
+ if (key === "__proto__" || key === "prototype" || key === "constructor") return void 0;
403
+ if ((typeof obj === "object" || typeof obj === "function") && obj !== null) return Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : void 0;
404
+ }
405
+ /**
406
+ * Parse the next segment from a property path string.
407
+ *
408
+ * @param path The full property path string.
409
+ * @param fromIndex The index to start parsing from.
410
+ * @returns The parsed segment and next index, or null if parsing fails.
411
+ */
412
+ function parseNextSegment(path, fromIndex) {
413
+ const len = path.length;
414
+ let i = fromIndex;
415
+ if (i >= len) return null;
416
+ let segment;
417
+ if (path[i] === "[") {
418
+ i++;
419
+ if (i >= len) return null;
420
+ if (path[i] === "\"" || path[i] === "'") {
421
+ const quote = path[i];
422
+ i++;
423
+ let segmentStr = "";
424
+ while (i < len && path[i] !== quote) if (path[i] === "\\") {
425
+ i++;
426
+ if (i < len) {
427
+ const escapeChar = path[i];
428
+ switch (escapeChar) {
429
+ case "n":
430
+ segmentStr += "\n";
431
+ break;
432
+ case "t":
433
+ segmentStr += " ";
434
+ break;
435
+ case "r":
436
+ segmentStr += "\r";
437
+ break;
438
+ case "b":
439
+ segmentStr += "\b";
440
+ break;
441
+ case "f":
442
+ segmentStr += "\f";
443
+ break;
444
+ case "v":
445
+ segmentStr += "\v";
446
+ break;
447
+ case "0":
448
+ segmentStr += "\0";
449
+ break;
450
+ case "\\":
451
+ segmentStr += "\\";
452
+ break;
453
+ case "\"":
454
+ segmentStr += "\"";
455
+ break;
456
+ case "'":
457
+ segmentStr += "'";
458
+ break;
459
+ case "u":
460
+ if (i + 4 < len) {
461
+ const hex = path.slice(i + 1, i + 5);
462
+ const codePoint = Number.parseInt(hex, 16);
463
+ if (!Number.isNaN(codePoint)) {
464
+ segmentStr += String.fromCharCode(codePoint);
465
+ i += 4;
466
+ } else segmentStr += escapeChar;
467
+ } else segmentStr += escapeChar;
468
+ break;
469
+ default: segmentStr += escapeChar;
470
+ }
471
+ i++;
472
+ }
473
+ } else {
474
+ segmentStr += path[i];
475
+ i++;
476
+ }
477
+ if (i >= len) return null;
478
+ segment = segmentStr;
479
+ i++;
480
+ } else {
481
+ const startIndex = i;
482
+ while (i < len && path[i] !== "]" && path[i] !== "'" && path[i] !== "\"") i++;
483
+ if (i >= len) return null;
484
+ const indexStr = path.slice(startIndex, i);
485
+ if (indexStr.length === 0) return null;
486
+ const indexNum = Number(indexStr);
487
+ segment = Number.isNaN(indexNum) ? indexStr : indexNum;
488
+ }
489
+ while (i < len && path[i] !== "]") i++;
490
+ if (i < len) i++;
491
+ } else {
492
+ const startIndex = i;
493
+ while (i < len && path[i] !== "." && path[i] !== "[" && path[i] !== "?" && path[i] !== "]") i++;
494
+ segment = path.slice(startIndex, i);
495
+ if (segment.length === 0) return null;
496
+ }
497
+ if (i < len && path[i] === ".") i++;
498
+ return {
499
+ segment,
500
+ nextIndex: i
501
+ };
502
+ }
503
+ /**
504
+ * Access a property or index on an object or array.
505
+ *
506
+ * @param obj The object or array to access.
507
+ * @param segment The property key or array index.
508
+ * @returns The accessed value or undefined if not accessible.
509
+ */
510
+ function accessProperty(obj, segment) {
511
+ if (typeof segment === "string") return getOwnProperty(obj, segment);
512
+ if (Array.isArray(obj) && segment >= 0 && segment < obj.length) return obj[segment];
513
+ }
514
+ /**
515
+ * Resolve a nested property path from an object.
516
+ *
517
+ * There are two types of property access patterns:
518
+ * 1. Array/index access: [0] or ["prop"]
519
+ * 2. Property access: prop or prop?.next
520
+ *
521
+ * @param obj The object to traverse.
522
+ * @param path The property path (e.g., "user.name", "users[0].email", "user['full-name']").
523
+ * @returns The resolved value or undefined if path doesn't exist.
524
+ */
525
+ function resolvePropertyPath(obj, path) {
526
+ if (obj == null) return void 0;
527
+ if (path.length === 0 || path.endsWith(".")) return void 0;
528
+ let current = obj;
529
+ let i = 0;
530
+ const len = path.length;
531
+ while (i < len) {
532
+ if (path.slice(i, i + 2) === "?.") {
533
+ i += 2;
534
+ if (current == null) return void 0;
535
+ } else if (current == null) return void 0;
536
+ const result = parseNextSegment(path, i);
537
+ if (result === null) return void 0;
538
+ const { segment, nextIndex } = result;
539
+ i = nextIndex;
540
+ current = accessProperty(current, segment);
541
+ if (current === void 0) return void 0;
542
+ }
543
+ return current;
544
+ }
545
+ /**
546
+ * Parse a message template into a message template array and a values array.
547
+ *
548
+ * Placeholders to be replaced with `values` are indicated by keys in curly braces
549
+ * (e.g., `{value}`). The system supports both simple property access and nested
550
+ * property access patterns:
551
+ *
552
+ * **Simple property access:**
553
+ * ```ts
554
+ * parseMessageTemplate("Hello, {user}!", { user: "foo" })
555
+ * // Returns: ["Hello, ", "foo", "!"]
556
+ * ```
557
+ *
558
+ * **Nested property access (dot notation):**
559
+ * ```ts
560
+ * parseMessageTemplate("Hello, {user.name}!", {
561
+ * user: { name: "foo", email: "foo@example.com" }
562
+ * })
563
+ * // Returns: ["Hello, ", "foo", "!"]
564
+ * ```
565
+ *
566
+ * **Array indexing:**
567
+ * ```ts
568
+ * parseMessageTemplate("First: {users[0]}", {
569
+ * users: ["foo", "bar", "baz"]
570
+ * })
571
+ * // Returns: ["First: ", "foo", ""]
572
+ * ```
573
+ *
574
+ * **Bracket notation for special property names:**
575
+ * ```ts
576
+ * parseMessageTemplate("Name: {user[\"full-name\"]}", {
577
+ * user: { "full-name": "foo bar" }
578
+ * })
579
+ * // Returns: ["Name: ", "foo bar", ""]
580
+ * ```
581
+ *
582
+ * **Optional chaining for safe navigation:**
583
+ * ```ts
584
+ * parseMessageTemplate("Email: {user?.profile?.email}", {
585
+ * user: { name: "foo" }
586
+ * })
587
+ * // Returns: ["Email: ", undefined, ""]
588
+ * ```
589
+ *
590
+ * **Wildcard patterns:**
591
+ * - `{*}` - Replaced with the entire properties object
592
+ * - `{ key-with-whitespace }` - Whitespace is trimmed when looking up keys
593
+ *
594
+ * **Escaping:**
595
+ * - `{{` and `}}` are escaped literal braces
596
+ *
597
+ * **Error handling:**
598
+ * - Non-existent paths return `undefined`
599
+ * - Malformed expressions resolve to `undefined` without throwing errors
600
+ * - Out of bounds array access returns `undefined`
601
+ *
602
+ * @param template The message template string containing placeholders.
603
+ * @param properties The values to replace placeholders with.
604
+ * @returns The message template array with values interleaved between text segments.
605
+ */
606
+ function parseMessageTemplate(template, properties) {
607
+ const length = template.length;
608
+ if (length === 0) return [""];
609
+ if (!template.includes("{")) return [template];
610
+ const message = [];
611
+ let startIndex = 0;
612
+ for (let i = 0; i < length; i++) {
613
+ const char = template[i];
614
+ if (char === "{") {
615
+ if ((i + 1 < length ? template[i + 1] : "") === "{") {
616
+ i++;
617
+ continue;
618
+ }
619
+ const closeIndex = template.indexOf("}", i + 1);
620
+ if (closeIndex === -1) continue;
621
+ const beforeText = template.slice(startIndex, i);
622
+ message.push(beforeText.replace(/{{/g, "{").replace(/}}/g, "}"));
623
+ const key = template.slice(i + 1, closeIndex);
624
+ let prop;
625
+ const trimmedKey = key.trim();
626
+ if (trimmedKey === "*") prop = key in properties ? properties[key] : "*" in properties ? properties["*"] : properties;
627
+ else {
628
+ if (key !== trimmedKey) prop = key in properties ? properties[key] : properties[trimmedKey];
629
+ else prop = properties[key];
630
+ if (prop === void 0 && isNestedAccess(trimmedKey)) prop = resolvePropertyPath(properties, trimmedKey);
631
+ }
632
+ message.push(prop);
633
+ i = closeIndex;
634
+ startIndex = i + 1;
635
+ } else if (char === "}" && i + 1 < length && template[i + 1] === "}") i++;
636
+ }
637
+ const remainingText = template.slice(startIndex);
638
+ message.push(remainingText.replace(/{{/g, "{").replace(/}}/g, "}"));
639
+ return message;
640
+ }
641
+ /**
642
+ * Render a message template with values.
643
+ * @param template The message template.
644
+ * @param values The message template values.
645
+ * @returns The message template values interleaved between the substitution
646
+ * values.
647
+ */
648
+ function renderMessage(template, values) {
649
+ const args = [];
650
+ for (let i = 0; i < template.length; i++) {
651
+ args.push(template[i]);
652
+ if (i < values.length) args.push(values[i]);
653
+ }
654
+ return args;
655
+ }
656
+ /**
657
+ * Internal symbol for storing category prefix in context.
658
+ */
659
+ const categoryPrefixSymbol = Symbol.for("logtape.categoryPrefix");
660
+ /**
661
+ * Gets the current category prefix from context local storage.
662
+ * @returns The current category prefix, or an empty array if not set.
663
+ * @since 1.3.0
664
+ */
665
+ function getCategoryPrefix() {
666
+ const store = LoggerImpl.getLogger().contextLocalStorage?.getStore();
667
+ if (store == null) return [];
668
+ const prefix = store[categoryPrefixSymbol];
669
+ return Array.isArray(prefix) ? prefix : [];
670
+ }
671
+ /**
672
+ * Gets the current implicit context from context local storage, excluding
673
+ * internal symbol keys (like category prefix).
674
+ * @returns The current implicit context without internal symbol keys.
675
+ * @since 1.3.0
676
+ */
677
+ function getImplicitContext() {
678
+ const store = LoggerImpl.getLogger().contextLocalStorage?.getStore();
679
+ if (store == null) return {};
680
+ const result = {};
681
+ for (const key of Object.keys(store)) result[key] = store[key];
682
+ return result;
683
+ }
684
+ var util_exports = /* @__PURE__ */ __exportAll({ inspect: () => inspect$1 });
685
+ function inspect$1(obj, options) {
686
+ const indent = options?.compact === true ? void 0 : 2;
687
+ return JSON.stringify(obj, null, indent);
688
+ }
689
+ /**
690
+ * The severity level abbreviations.
691
+ */
692
+ const levelAbbreviations = {
693
+ "trace": "TRC",
694
+ "debug": "DBG",
695
+ "info": "INF",
696
+ "warning": "WRN",
697
+ "error": "ERR",
698
+ "fatal": "FTL"
699
+ };
700
+ /**
701
+ * A platform-specific inspect function. In Deno, this is {@link Deno.inspect},
702
+ * and in Node.js/Bun it is `util.inspect()`. If neither is available, it
703
+ * falls back to {@link JSON.stringify}.
704
+ *
705
+ * @param value The value to inspect.
706
+ * @param options The options for inspecting the value.
707
+ * If `colors` is `true`, the output will be ANSI-colored.
708
+ * @returns The string representation of the value.
709
+ */
710
+ const inspect = typeof document !== "undefined" || typeof navigator !== "undefined" && navigator.product === "ReactNative" ? (v) => JSON.stringify(v) : "Deno" in globalThis && "inspect" in globalThis.Deno && typeof globalThis.Deno.inspect === "function" ? (v, opts) => globalThis.Deno.inspect(v, {
711
+ strAbbreviateSize: Infinity,
712
+ iterableLimit: Infinity,
713
+ ...opts
714
+ }) : util_exports != null && "inspect" in util_exports && typeof inspect$1 === "function" ? (v, opts) => inspect$1(v, {
715
+ maxArrayLength: Infinity,
716
+ maxStringLength: Infinity,
717
+ ...opts
718
+ }) : (v) => JSON.stringify(v);
719
+ function padZero(num) {
720
+ return num < 10 ? `0${num}` : `${num}`;
721
+ }
722
+ function padThree(num) {
723
+ return num < 10 ? `00${num}` : num < 100 ? `0${num}` : `${num}`;
724
+ }
725
+ const timestampFormatters = {
726
+ "date-time-timezone": (ts) => {
727
+ const d = new Date(ts);
728
+ return `${d.getUTCFullYear()}-${padZero(d.getUTCMonth() + 1)}-${padZero(d.getUTCDate())} ${padZero(d.getUTCHours())}:${padZero(d.getUTCMinutes())}:${padZero(d.getUTCSeconds())}.${padThree(d.getUTCMilliseconds())} +00:00`;
729
+ },
730
+ "date-time-tz": (ts) => {
731
+ const d = new Date(ts);
732
+ return `${d.getUTCFullYear()}-${padZero(d.getUTCMonth() + 1)}-${padZero(d.getUTCDate())} ${padZero(d.getUTCHours())}:${padZero(d.getUTCMinutes())}:${padZero(d.getUTCSeconds())}.${padThree(d.getUTCMilliseconds())} +00`;
733
+ },
734
+ "date-time": (ts) => {
735
+ const d = new Date(ts);
736
+ return `${d.getUTCFullYear()}-${padZero(d.getUTCMonth() + 1)}-${padZero(d.getUTCDate())} ${padZero(d.getUTCHours())}:${padZero(d.getUTCMinutes())}:${padZero(d.getUTCSeconds())}.${padThree(d.getUTCMilliseconds())}`;
737
+ },
738
+ "time-timezone": (ts) => {
739
+ const d = new Date(ts);
740
+ return `${padZero(d.getUTCHours())}:${padZero(d.getUTCMinutes())}:${padZero(d.getUTCSeconds())}.${padThree(d.getUTCMilliseconds())} +00:00`;
741
+ },
742
+ "time-tz": (ts) => {
743
+ const d = new Date(ts);
744
+ return `${padZero(d.getUTCHours())}:${padZero(d.getUTCMinutes())}:${padZero(d.getUTCSeconds())}.${padThree(d.getUTCMilliseconds())} +00`;
745
+ },
746
+ "time": (ts) => {
747
+ const d = new Date(ts);
748
+ return `${padZero(d.getUTCHours())}:${padZero(d.getUTCMinutes())}:${padZero(d.getUTCSeconds())}.${padThree(d.getUTCMilliseconds())}`;
749
+ },
750
+ "date": (ts) => {
751
+ const d = new Date(ts);
752
+ return `${d.getUTCFullYear()}-${padZero(d.getUTCMonth() + 1)}-${padZero(d.getUTCDate())}`;
753
+ },
754
+ "rfc3339": (ts) => new Date(ts).toISOString(),
755
+ "none": () => null
756
+ };
757
+ const levelRenderersCache = {
758
+ ABBR: levelAbbreviations,
759
+ abbr: {
760
+ trace: "trc",
761
+ debug: "dbg",
762
+ info: "inf",
763
+ warning: "wrn",
764
+ error: "err",
765
+ fatal: "ftl"
766
+ },
767
+ FULL: {
768
+ trace: "TRACE",
769
+ debug: "DEBUG",
770
+ info: "INFO",
771
+ warning: "WARNING",
772
+ error: "ERROR",
773
+ fatal: "FATAL"
774
+ },
775
+ full: {
776
+ trace: "trace",
777
+ debug: "debug",
778
+ info: "info",
779
+ warning: "warning",
780
+ error: "error",
781
+ fatal: "fatal"
782
+ },
783
+ L: {
784
+ trace: "T",
785
+ debug: "D",
786
+ info: "I",
787
+ warning: "W",
788
+ error: "E",
789
+ fatal: "F"
790
+ },
791
+ l: {
792
+ trace: "t",
793
+ debug: "d",
794
+ info: "i",
795
+ warning: "w",
796
+ error: "e",
797
+ fatal: "f"
798
+ }
799
+ };
800
+ /**
801
+ * Get a text formatter with the specified options. Although it's flexible
802
+ * enough to create a custom formatter, if you want more control, you can
803
+ * create a custom formatter that satisfies the {@link TextFormatter} type
804
+ * instead.
805
+ *
806
+ * For more information on the options, see {@link TextFormatterOptions}.
807
+ *
808
+ * By default, the formatter formats log records as follows:
809
+ *
810
+ * ```
811
+ * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world!
812
+ * ```
813
+ * @param options The options for the text formatter.
814
+ * @returns The text formatter.
815
+ * @since 0.6.0
816
+ */
817
+ function getTextFormatter(options = {}) {
818
+ const timestampRenderer = (() => {
819
+ const tsOption = options.timestamp;
820
+ if (tsOption == null) return timestampFormatters["date-time-timezone"];
821
+ else if (tsOption === "disabled") return timestampFormatters["none"];
822
+ else if (typeof tsOption === "string" && tsOption in timestampFormatters) return timestampFormatters[tsOption];
823
+ else return tsOption;
824
+ })();
825
+ const categorySeparator = options.category ?? "·";
826
+ const valueRenderer = options.value ? (v) => options.value(v, inspect) : inspect;
827
+ const levelRenderer = (() => {
828
+ const levelOption = options.level;
829
+ if (levelOption == null || levelOption === "ABBR") return (level) => levelRenderersCache.ABBR[level];
830
+ else if (levelOption === "abbr") return (level) => levelRenderersCache.abbr[level];
831
+ else if (levelOption === "FULL") return (level) => levelRenderersCache.FULL[level];
832
+ else if (levelOption === "full") return (level) => levelRenderersCache.full[level];
833
+ else if (levelOption === "L") return (level) => levelRenderersCache.L[level];
834
+ else if (levelOption === "l") return (level) => levelRenderersCache.l[level];
835
+ else return levelOption;
836
+ })();
837
+ const formatter = options.format ?? (({ timestamp, level, category, message }) => `${timestamp ? `${timestamp} ` : ""}[${level}] ${category}: ${message}`);
838
+ return (record) => {
839
+ const msgParts = record.message;
840
+ const msgLen = msgParts.length;
841
+ let message;
842
+ if (msgLen === 1) message = msgParts[0];
843
+ else if (msgLen <= 6) {
844
+ message = "";
845
+ for (let i = 0; i < msgLen; i++) message += i % 2 === 0 ? msgParts[i] : valueRenderer(msgParts[i]);
846
+ } else {
847
+ const parts = new Array(msgLen);
848
+ for (let i = 0; i < msgLen; i++) parts[i] = i % 2 === 0 ? msgParts[i] : valueRenderer(msgParts[i]);
849
+ message = parts.join("");
850
+ }
851
+ return `${formatter({
852
+ timestamp: timestampRenderer(record.timestamp),
853
+ level: levelRenderer(record.level),
854
+ category: typeof categorySeparator === "function" ? categorySeparator(record.category) : record.category.join(categorySeparator),
855
+ message,
856
+ record
857
+ })}\n`;
858
+ };
859
+ }
860
+ getTextFormatter();
861
+ const RESET = "\x1B[0m";
862
+ const ansiColors = {
863
+ black: "\x1B[30m",
864
+ red: "\x1B[31m",
865
+ green: "\x1B[32m",
866
+ yellow: "\x1B[33m",
867
+ blue: "\x1B[34m",
868
+ magenta: "\x1B[35m",
869
+ cyan: "\x1B[36m",
870
+ white: "\x1B[37m"
871
+ };
872
+ const ansiStyles = {
873
+ bold: "\x1B[1m",
874
+ dim: "\x1B[2m",
875
+ italic: "\x1B[3m",
876
+ underline: "\x1B[4m",
877
+ strikethrough: "\x1B[9m"
878
+ };
879
+ const defaultLevelColors = {
880
+ trace: null,
881
+ debug: "blue",
882
+ info: "green",
883
+ warning: "yellow",
884
+ error: "red",
885
+ fatal: "magenta"
886
+ };
887
+ /**
888
+ * Get an ANSI color formatter with the specified options.
889
+ *
890
+ * ![A preview of an ANSI color formatter.](https://i.imgur.com/I8LlBUf.png)
891
+ * @param option The options for the ANSI color formatter.
892
+ * @returns The ANSI color formatter.
893
+ * @since 0.6.0
894
+ */
895
+ function getAnsiColorFormatter(options = {}) {
896
+ const format = options.format;
897
+ const timestampStyle = typeof options.timestampStyle === "undefined" ? "dim" : options.timestampStyle;
898
+ const timestampColor = options.timestampColor ?? null;
899
+ const timestampPrefix = `${timestampStyle == null ? "" : ansiStyles[timestampStyle]}${timestampColor == null ? "" : ansiColors[timestampColor]}`;
900
+ const timestampSuffix = timestampStyle == null && timestampColor == null ? "" : RESET;
901
+ const levelStyle = typeof options.levelStyle === "undefined" ? "bold" : options.levelStyle;
902
+ const levelColors = options.levelColors ?? defaultLevelColors;
903
+ const categoryStyle = typeof options.categoryStyle === "undefined" ? "dim" : options.categoryStyle;
904
+ const categoryColor = options.categoryColor ?? null;
905
+ const categoryPrefix = `${categoryStyle == null ? "" : ansiStyles[categoryStyle]}${categoryColor == null ? "" : ansiColors[categoryColor]}`;
906
+ const categorySuffix = categoryStyle == null && categoryColor == null ? "" : RESET;
907
+ return getTextFormatter({
908
+ timestamp: "date-time-tz",
909
+ value(value, fallbackInspect) {
910
+ return fallbackInspect(value, { colors: true });
911
+ },
912
+ ...options,
913
+ format({ timestamp, level, category, message, record }) {
914
+ const levelColor = levelColors[record.level];
915
+ timestamp = `${timestampPrefix}${timestamp}${timestampSuffix}`;
916
+ level = `${levelStyle == null ? "" : ansiStyles[levelStyle]}${levelColor == null ? "" : ansiColors[levelColor]}${level}${levelStyle == null && levelColor == null ? "" : RESET}`;
917
+ return format == null ? `${timestamp} ${level} ${categoryPrefix}${category}:${categorySuffix} ${message}` : format({
918
+ timestamp,
919
+ level,
920
+ category: `${categoryPrefix}${category}${categorySuffix}`,
921
+ message,
922
+ record
923
+ });
924
+ }
925
+ });
926
+ }
927
+ getAnsiColorFormatter();
928
+ /**
929
+ * Get a [JSON Lines] formatter with the specified options. The log records
930
+ * will be rendered as JSON objects, one per line, which is a common format
931
+ * for log files. This format is also known as Newline-Delimited JSON (NDJSON).
932
+ * It looks like this:
933
+ *
934
+ * ```json
935
+ * {"@timestamp":"2023-11-14T22:13:20.000Z","level":"INFO","message":"Hello, world!","logger":"my.logger","properties":{"key":"value"}}
936
+ * ```
937
+ *
938
+ * [JSON Lines]: https://jsonlines.org/
939
+ * @param options The options for the JSON Lines formatter.
940
+ * @returns The JSON Lines formatter.
941
+ * @since 0.11.0
942
+ */
943
+ function getJsonLinesFormatter(options = {}) {
944
+ if (!options.categorySeparator && !options.message && !options.properties) return (record) => {
945
+ if (record.message.length === 3) return JSON.stringify({
946
+ "@timestamp": new Date(record.timestamp).toISOString(),
947
+ level: record.level === "warning" ? "WARN" : record.level.toUpperCase(),
948
+ message: record.message[0] + JSON.stringify(record.message[1]) + record.message[2],
949
+ logger: record.category.join("."),
950
+ properties: record.properties
951
+ }) + "\n";
952
+ if (record.message.length === 1) return JSON.stringify({
953
+ "@timestamp": new Date(record.timestamp).toISOString(),
954
+ level: record.level === "warning" ? "WARN" : record.level.toUpperCase(),
955
+ message: record.message[0],
956
+ logger: record.category.join("."),
957
+ properties: record.properties
958
+ }) + "\n";
959
+ let msg = record.message[0];
960
+ for (let i = 1; i < record.message.length; i++) msg += i & 1 ? JSON.stringify(record.message[i]) : record.message[i];
961
+ return JSON.stringify({
962
+ "@timestamp": new Date(record.timestamp).toISOString(),
963
+ level: record.level === "warning" ? "WARN" : record.level.toUpperCase(),
964
+ message: msg,
965
+ logger: record.category.join("."),
966
+ properties: record.properties
967
+ }) + "\n";
968
+ };
969
+ const isTemplateMessage = options.message === "template";
970
+ const propertiesOption = options.properties ?? "nest:properties";
971
+ let joinCategory;
972
+ if (typeof options.categorySeparator === "function") joinCategory = options.categorySeparator;
973
+ else {
974
+ const separator = options.categorySeparator ?? ".";
975
+ joinCategory = (category) => category.join(separator);
976
+ }
977
+ let getProperties;
978
+ if (propertiesOption === "flatten") getProperties = (properties) => properties;
979
+ else if (propertiesOption.startsWith("prepend:")) {
980
+ const prefix = propertiesOption.substring(8);
981
+ if (prefix === "") throw new TypeError(`Invalid properties option: ${JSON.stringify(propertiesOption)}. It must be of the form "prepend:<prefix>" where <prefix> is a non-empty string.`);
982
+ getProperties = (properties) => {
983
+ const result = {};
984
+ for (const key in properties) result[`${prefix}${key}`] = properties[key];
985
+ return result;
986
+ };
987
+ } else if (propertiesOption.startsWith("nest:")) {
988
+ const key = propertiesOption.substring(5);
989
+ getProperties = (properties) => ({ [key]: properties });
990
+ } else throw new TypeError(`Invalid properties option: ${JSON.stringify(propertiesOption)}. It must be "flatten", "prepend:<prefix>", or "nest:<key>".`);
991
+ let getMessage;
992
+ if (isTemplateMessage) getMessage = (record) => {
993
+ if (typeof record.rawMessage === "string") return record.rawMessage;
994
+ let msg = "";
995
+ for (let i = 0; i < record.rawMessage.length; i++) msg += i % 2 < 1 ? record.rawMessage[i] : "{}";
996
+ return msg;
997
+ };
998
+ else getMessage = (record) => {
999
+ const msgLen = record.message.length;
1000
+ if (msgLen === 1) return record.message[0];
1001
+ let msg = "";
1002
+ for (let i = 0; i < msgLen; i++) msg += i % 2 < 1 ? record.message[i] : JSON.stringify(record.message[i]);
1003
+ return msg;
1004
+ };
1005
+ return (record) => {
1006
+ return JSON.stringify({
1007
+ "@timestamp": new Date(record.timestamp).toISOString(),
1008
+ level: record.level === "warning" ? "WARN" : record.level.toUpperCase(),
1009
+ message: getMessage(record),
1010
+ logger: joinCategory(record.category),
1011
+ ...getProperties(record.properties)
1012
+ }) + "\n";
1013
+ };
1014
+ }
1015
+ getJsonLinesFormatter();
1016
+ /**
1017
+ * The styles for the log level in the console.
1018
+ */
1019
+ const logLevelStyles = {
1020
+ "trace": "background-color: gray; color: white;",
1021
+ "debug": "background-color: gray; color: white;",
1022
+ "info": "background-color: white; color: black;",
1023
+ "warning": "background-color: orange; color: black;",
1024
+ "error": "background-color: red; color: white;",
1025
+ "fatal": "background-color: maroon; color: white;"
1026
+ };
1027
+ /**
1028
+ * The default console formatter.
1029
+ *
1030
+ * @param record The log record to format.
1031
+ * @returns The formatted log record, as an array of arguments for
1032
+ * {@link console.log}.
1033
+ */
1034
+ function defaultConsoleFormatter(record) {
1035
+ let msg = "";
1036
+ const values = [];
1037
+ for (let i = 0; i < record.message.length; i++) if (i % 2 === 0) msg += record.message[i];
1038
+ else {
1039
+ msg += "%o";
1040
+ values.push(record.message[i]);
1041
+ }
1042
+ const date = new Date(record.timestamp);
1043
+ return [
1044
+ `%c${`${date.getUTCHours().toString().padStart(2, "0")}:${date.getUTCMinutes().toString().padStart(2, "0")}:${date.getUTCSeconds().toString().padStart(2, "0")}.${date.getUTCMilliseconds().toString().padStart(3, "0")}`} %c${levelAbbreviations[record.level]}%c %c${record.category.join("·")} %c${msg}`,
1045
+ "color: gray;",
1046
+ logLevelStyles[record.level],
1047
+ "background-color: default;",
1048
+ "color: gray;",
1049
+ "color: default;",
1050
+ ...values
1051
+ ];
1052
+ }
1053
+ /**
1054
+ * Turns a sink into a filtered sink. The returned sink only logs records that
1055
+ * pass the filter.
1056
+ *
1057
+ * @example Filter a console sink to only log records with the info level
1058
+ * ```typescript
1059
+ * const sink = withFilter(getConsoleSink(), "info");
1060
+ * ```
1061
+ *
1062
+ * @param sink A sink to be filtered.
1063
+ * @param filter A filter to apply to the sink. It can be either a filter
1064
+ * function or a {@link LogLevel} string.
1065
+ * @returns A sink that only logs records that pass the filter.
1066
+ */
1067
+ function withFilter(sink, filter) {
1068
+ const filterFunc = toFilter(filter);
1069
+ return (record) => {
1070
+ if (filterFunc(record)) sink(record);
1071
+ };
1072
+ }
1073
+ /**
1074
+ * A console sink factory that returns a sink that logs to the console.
1075
+ *
1076
+ * @param options The options for the sink.
1077
+ * @returns A sink that logs to the console. If `nonBlocking` is enabled,
1078
+ * returns a sink that also implements {@link Disposable}.
1079
+ */
1080
+ function getConsoleSink(options = {}) {
1081
+ const formatter = options.formatter ?? defaultConsoleFormatter;
1082
+ const levelMap = {
1083
+ trace: "debug",
1084
+ debug: "debug",
1085
+ info: "info",
1086
+ warning: "warn",
1087
+ error: "error",
1088
+ fatal: "error",
1089
+ ...options.levelMap ?? {}
1090
+ };
1091
+ const console = options.console ?? globalThis.console;
1092
+ const baseSink = (record) => {
1093
+ const args = formatter(record);
1094
+ const method = levelMap[record.level];
1095
+ if (method === void 0) throw new TypeError(`Invalid log level: ${record.level}.`);
1096
+ if (typeof args === "string") {
1097
+ const msg = args.replace(/\r?\n$/, "");
1098
+ console[method](msg);
1099
+ } else console[method](...args);
1100
+ };
1101
+ if (!options.nonBlocking) return baseSink;
1102
+ const nonBlockingConfig = options.nonBlocking === true ? {} : options.nonBlocking;
1103
+ const bufferSize = nonBlockingConfig.bufferSize ?? 100;
1104
+ const flushInterval = nonBlockingConfig.flushInterval ?? 100;
1105
+ const buffer = [];
1106
+ let flushTimer = null;
1107
+ let disposed = false;
1108
+ let flushScheduled = false;
1109
+ const maxBufferSize = bufferSize * 2;
1110
+ function flush() {
1111
+ if (buffer.length === 0) return;
1112
+ const records = buffer.splice(0);
1113
+ for (const record of records) try {
1114
+ baseSink(record);
1115
+ } catch {}
1116
+ }
1117
+ function scheduleFlush() {
1118
+ if (flushScheduled) return;
1119
+ flushScheduled = true;
1120
+ setTimeout(() => {
1121
+ flushScheduled = false;
1122
+ flush();
1123
+ }, 0);
1124
+ }
1125
+ function startFlushTimer() {
1126
+ if (flushTimer !== null || disposed) return;
1127
+ flushTimer = setInterval(() => {
1128
+ flush();
1129
+ }, flushInterval);
1130
+ }
1131
+ const nonBlockingSink = (record) => {
1132
+ if (disposed) return;
1133
+ if (buffer.length >= maxBufferSize) buffer.shift();
1134
+ buffer.push(record);
1135
+ if (buffer.length >= bufferSize) scheduleFlush();
1136
+ else if (flushTimer === null) startFlushTimer();
1137
+ };
1138
+ nonBlockingSink[Symbol.dispose] = () => {
1139
+ disposed = true;
1140
+ if (flushTimer !== null) {
1141
+ clearInterval(flushTimer);
1142
+ flushTimer = null;
1143
+ }
1144
+ flush();
1145
+ };
1146
+ return nonBlockingSink;
1147
+ }
1148
+ /**
1149
+ * The current configuration, if any. Otherwise, `null`.
1150
+ */
1151
+ let currentConfig = null;
1152
+ /**
1153
+ * Strong references to the loggers.
1154
+ * This is to prevent the loggers from being garbage collected so that their
1155
+ * sinks and filters are not removed.
1156
+ */
1157
+ const strongRefs = /* @__PURE__ */ new Set();
1158
+ /**
1159
+ * Disposables to dispose when resetting the configuration.
1160
+ */
1161
+ const disposables = /* @__PURE__ */ new Set();
1162
+ /**
1163
+ * Async disposables to dispose when resetting the configuration.
1164
+ */
1165
+ const asyncDisposables = /* @__PURE__ */ new Set();
1166
+ /**
1167
+ * Check if a config is for the meta logger.
1168
+ */
1169
+ function isLoggerConfigMeta(cfg) {
1170
+ return cfg.category.length === 0 || cfg.category.length === 1 && cfg.category[0] === "logtape" || cfg.category.length === 2 && cfg.category[0] === "logtape" && cfg.category[1] === "meta";
1171
+ }
1172
+ /**
1173
+ * Configure sync loggers with the specified configuration.
1174
+ *
1175
+ * Note that if the given sinks or filters are disposable, they will be
1176
+ * disposed when the configuration is reset, or when the process exits.
1177
+ *
1178
+ * Also note that passing async sinks or filters will throw. If
1179
+ * necessary use {@link resetSync} or {@link disposeSync}.
1180
+ *
1181
+ * @example
1182
+ * ```typescript
1183
+ * configureSync({
1184
+ * sinks: {
1185
+ * console: getConsoleSink(),
1186
+ * },
1187
+ * loggers: [
1188
+ * {
1189
+ * category: "my-app",
1190
+ * sinks: ["console"],
1191
+ * lowestLevel: "info",
1192
+ * },
1193
+ * {
1194
+ * category: "logtape",
1195
+ * sinks: ["console"],
1196
+ * lowestLevel: "error",
1197
+ * },
1198
+ * ],
1199
+ * });
1200
+ * ```
1201
+ *
1202
+ * @param config The configuration.
1203
+ * @since 0.9.0
1204
+ */
1205
+ function configureSync(config) {
1206
+ if (currentConfig != null && !config.reset) throw new ConfigError("Already configured; if you want to reset, turn on the reset flag.");
1207
+ if (asyncDisposables.size > 0) throw new ConfigError("Previously configured async disposables are still active. Use configure() instead or explicitly dispose them using dispose().");
1208
+ resetSync();
1209
+ try {
1210
+ configureInternal(config, false);
1211
+ } catch (e) {
1212
+ if (e instanceof ConfigError) resetSync();
1213
+ throw e;
1214
+ }
1215
+ }
1216
+ function configureInternal(config, allowAsync) {
1217
+ currentConfig = config;
1218
+ let metaConfigured = false;
1219
+ const configuredCategories = /* @__PURE__ */ new Set();
1220
+ for (const cfg of config.loggers) {
1221
+ if (isLoggerConfigMeta(cfg)) metaConfigured = true;
1222
+ const categoryKey = Array.isArray(cfg.category) ? JSON.stringify(cfg.category) : JSON.stringify([cfg.category]);
1223
+ if (configuredCategories.has(categoryKey)) throw new ConfigError(`Duplicate logger configuration for category: ${categoryKey}. Each category can only be configured once.`);
1224
+ configuredCategories.add(categoryKey);
1225
+ const logger = LoggerImpl.getLogger(cfg.category);
1226
+ for (const sinkId of cfg.sinks ?? []) {
1227
+ const sink = config.sinks[sinkId];
1228
+ if (!sink) throw new ConfigError(`Sink not found: ${sinkId}.`);
1229
+ logger.sinks.push(sink);
1230
+ }
1231
+ logger.parentSinks = cfg.parentSinks ?? "inherit";
1232
+ if (cfg.lowestLevel !== void 0) logger.lowestLevel = cfg.lowestLevel;
1233
+ for (const filterId of cfg.filters ?? []) {
1234
+ const filter = config.filters?.[filterId];
1235
+ if (filter === void 0) throw new ConfigError(`Filter not found: ${filterId}.`);
1236
+ logger.filters.push(toFilter(filter));
1237
+ }
1238
+ strongRefs.add(logger);
1239
+ }
1240
+ LoggerImpl.getLogger().contextLocalStorage = config.contextLocalStorage;
1241
+ for (const sink of Object.values(config.sinks)) {
1242
+ if (Symbol.asyncDispose in sink) if (allowAsync) asyncDisposables.add(sink);
1243
+ else throw new ConfigError("Async disposables cannot be used with configureSync().");
1244
+ if (Symbol.dispose in sink) disposables.add(sink);
1245
+ }
1246
+ for (const filter of Object.values(config.filters ?? {})) {
1247
+ if (filter == null || typeof filter === "string") continue;
1248
+ if (Symbol.asyncDispose in filter) if (allowAsync) asyncDisposables.add(filter);
1249
+ else throw new ConfigError("Async disposables cannot be used with configureSync().");
1250
+ if (Symbol.dispose in filter) disposables.add(filter);
1251
+ }
1252
+ if (typeof globalThis.EdgeRuntime !== "string" && "process" in globalThis && !("Deno" in globalThis)) {
1253
+ const proc = globalThis.process;
1254
+ const onMethod = proc?.["on"];
1255
+ if (typeof onMethod === "function") onMethod.call(proc, "exit", allowAsync ? dispose : disposeSync);
1256
+ } else addEventListener("unload", allowAsync ? dispose : disposeSync);
1257
+ const meta = LoggerImpl.getLogger(["logtape", "meta"]);
1258
+ if (!metaConfigured) meta.sinks.push(getConsoleSink());
1259
+ meta.info("LogTape loggers are configured. Note that LogTape itself uses the meta logger, which has category {metaLoggerCategory}. The meta logger purposes to log internal errors such as sink exceptions. If you are seeing this message, the meta logger is automatically configured. It's recommended to configure the meta logger with a separate sink so that you can easily notice if logging itself fails or is misconfigured. To turn off this message, configure the meta logger with higher log levels than {dismissLevel}. See also <https://logtape.org/manual/categories#meta-logger>.", {
1260
+ metaLoggerCategory: ["logtape", "meta"],
1261
+ dismissLevel: "info"
1262
+ });
1263
+ }
1264
+ /**
1265
+ * Reset the configuration. Mostly for testing purposes. Will not clear async
1266
+ * sinks, only use with sync sinks. Use {@link reset} if you have async sinks.
1267
+ * @since 0.9.0
1268
+ */
1269
+ function resetSync() {
1270
+ disposeSync();
1271
+ resetInternal();
1272
+ }
1273
+ function resetInternal() {
1274
+ const rootLogger = LoggerImpl.getLogger([]);
1275
+ rootLogger.resetDescendants();
1276
+ delete rootLogger.contextLocalStorage;
1277
+ strongRefs.clear();
1278
+ currentConfig = null;
1279
+ }
1280
+ /**
1281
+ * Dispose of the disposables.
1282
+ */
1283
+ async function dispose() {
1284
+ disposeSync();
1285
+ const promises = [];
1286
+ for (const disposable of asyncDisposables) {
1287
+ promises.push(disposable[Symbol.asyncDispose]());
1288
+ asyncDisposables.delete(disposable);
1289
+ }
1290
+ await Promise.all(promises);
1291
+ }
1292
+ /**
1293
+ * Dispose of the sync disposables. Async disposables will be untouched,
1294
+ * use {@link dispose} if you have async sinks.
1295
+ * @since 0.9.0
1296
+ */
1297
+ function disposeSync() {
1298
+ for (const disposable of disposables) disposable[Symbol.dispose]();
1299
+ disposables.clear();
1300
+ }
1301
+ /**
1302
+ * A configuration error.
1303
+ */
1304
+ var ConfigError = class extends Error {
1305
+ /**
1306
+ * Constructs a new configuration error.
1307
+ * @param message The error message.
1308
+ */
1309
+ constructor(message) {
1310
+ super(message);
1311
+ this.name = "ConfigureError";
1312
+ }
1313
+ };
1314
+ export { getTextFormatter as a, getAnsiColorFormatter as i, getConsoleSink as n, getLogger as o, withFilter as r, configureSync as t };