@spinajs/log-common 2.0.480 → 2.0.482

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +125 -4
  2. package/lib/cjs/BatchQueue.d.ts +127 -0
  3. package/lib/cjs/BatchQueue.d.ts.map +1 -0
  4. package/lib/cjs/BatchQueue.js +126 -0
  5. package/lib/cjs/BatchQueue.js.map +1 -0
  6. package/lib/cjs/filters/filter.d.ts +27 -0
  7. package/lib/cjs/filters/filter.d.ts.map +1 -0
  8. package/lib/cjs/filters/filter.js +20 -0
  9. package/lib/cjs/filters/filter.js.map +1 -0
  10. package/lib/cjs/filters/whenRepeated.d.ts +52 -0
  11. package/lib/cjs/filters/whenRepeated.d.ts.map +1 -0
  12. package/lib/cjs/filters/whenRepeated.js +98 -0
  13. package/lib/cjs/filters/whenRepeated.js.map +1 -0
  14. package/lib/cjs/format.d.ts +20 -0
  15. package/lib/cjs/format.d.ts.map +1 -0
  16. package/lib/cjs/format.js +75 -0
  17. package/lib/cjs/format.js.map +1 -0
  18. package/lib/cjs/index.d.ts +260 -21
  19. package/lib/cjs/index.d.ts.map +1 -1
  20. package/lib/cjs/index.js +207 -33
  21. package/lib/cjs/index.js.map +1 -1
  22. package/lib/cjs/perf.d.ts +109 -0
  23. package/lib/cjs/perf.d.ts.map +1 -0
  24. package/lib/cjs/perf.js +162 -0
  25. package/lib/cjs/perf.js.map +1 -0
  26. package/lib/cjs/persistence.d.ts +19 -0
  27. package/lib/cjs/persistence.d.ts.map +1 -0
  28. package/lib/cjs/persistence.js +137 -0
  29. package/lib/cjs/persistence.js.map +1 -0
  30. package/lib/cjs/serializers.d.ts +77 -0
  31. package/lib/cjs/serializers.d.ts.map +1 -0
  32. package/lib/cjs/serializers.js +215 -0
  33. package/lib/cjs/serializers.js.map +1 -0
  34. package/lib/mjs/BatchQueue.d.ts +127 -0
  35. package/lib/mjs/BatchQueue.d.ts.map +1 -0
  36. package/lib/mjs/BatchQueue.js +122 -0
  37. package/lib/mjs/BatchQueue.js.map +1 -0
  38. package/lib/mjs/filters/filter.d.ts +27 -0
  39. package/lib/mjs/filters/filter.d.ts.map +1 -0
  40. package/lib/mjs/filters/filter.js +16 -0
  41. package/lib/mjs/filters/filter.js.map +1 -0
  42. package/lib/mjs/filters/whenRepeated.d.ts +52 -0
  43. package/lib/mjs/filters/whenRepeated.d.ts.map +1 -0
  44. package/lib/mjs/filters/whenRepeated.js +95 -0
  45. package/lib/mjs/filters/whenRepeated.js.map +1 -0
  46. package/lib/mjs/format.d.ts +20 -0
  47. package/lib/mjs/format.d.ts.map +1 -0
  48. package/lib/mjs/format.js +72 -0
  49. package/lib/mjs/format.js.map +1 -0
  50. package/lib/mjs/index.d.ts +260 -21
  51. package/lib/mjs/index.d.ts.map +1 -1
  52. package/lib/mjs/index.js +201 -10
  53. package/lib/mjs/index.js.map +1 -1
  54. package/lib/mjs/perf.d.ts +109 -0
  55. package/lib/mjs/perf.d.ts.map +1 -0
  56. package/lib/mjs/perf.js +157 -0
  57. package/lib/mjs/perf.js.map +1 -0
  58. package/lib/mjs/persistence.d.ts +19 -0
  59. package/lib/mjs/persistence.d.ts.map +1 -0
  60. package/lib/mjs/persistence.js +132 -0
  61. package/lib/mjs/persistence.js.map +1 -0
  62. package/lib/mjs/serializers.d.ts +77 -0
  63. package/lib/mjs/serializers.d.ts.map +1 -0
  64. package/lib/mjs/serializers.js +208 -0
  65. package/lib/mjs/serializers.js.map +1 -0
  66. package/lib/tsconfig.cjs.tsbuildinfo +1 -1
  67. package/lib/tsconfig.mjs.tsbuildinfo +1 -1
  68. package/package.json +8 -9
@@ -0,0 +1,122 @@
1
+ export class BatchQueue {
2
+ constructor(options) {
3
+ this.options = options;
4
+ this.buffer = [];
5
+ this.stopped = false;
6
+ /**
7
+ * The currently running flush, or null when idle. Used both as the
8
+ * re-entrancy guard ( coalesce concurrent flush() calls ) and to let
9
+ * forceFlush()/shutdown() wait for an in-flight flush before draining.
10
+ */
11
+ this.inFlight = null;
12
+ this.timer = setInterval(() => void this.flush(), this.options.flushIntervalMs);
13
+ // Do not keep the event loop / process alive just for the flush tick.
14
+ this.timer.unref?.();
15
+ }
16
+ /** Current number of buffered items. */
17
+ get size() {
18
+ return this.buffer.length;
19
+ }
20
+ /**
21
+ * Add an item to the buffer.
22
+ *
23
+ * If the queue has been shut down this is a no-op that resolves immediately.
24
+ * When the buffer reaches `maxBatch` a flush is triggered and its promise is
25
+ * returned, so a caller MAY await a size-triggered flush; otherwise resolves
26
+ * immediately.
27
+ */
28
+ enqueue(item) {
29
+ if (this.stopped) {
30
+ return Promise.resolve();
31
+ }
32
+ this.buffer.push(item);
33
+ this.enforceCap();
34
+ if (this.size >= this.options.maxBatch) {
35
+ return this.flush();
36
+ }
37
+ return Promise.resolve();
38
+ }
39
+ /**
40
+ * Put items back at the FRONT of the buffer ( then enforce the cap ). Owners
41
+ * use this to re-queue a batch whose `onFlush` failed, without dropping it.
42
+ */
43
+ requeueFront(items) {
44
+ this.buffer = [...items, ...this.buffer];
45
+ this.enforceCap();
46
+ }
47
+ /**
48
+ * Enforce the hard `maxQueue` cap by dropping the OLDEST items. Fires
49
+ * `onOverflow(droppedItems)` with the spliced-out items when anything is dropped.
50
+ */
51
+ enforceCap() {
52
+ if (this.buffer.length > this.options.maxQueue) {
53
+ const dropCount = this.buffer.length - this.options.maxQueue;
54
+ const droppedItems = this.buffer.splice(0, dropCount);
55
+ this.options.onOverflow?.(droppedItems);
56
+ }
57
+ }
58
+ /**
59
+ * Flush the entire buffer in a single `onFlush` call.
60
+ *
61
+ * Re-entrancy: if a flush is already in flight, that same promise is returned
62
+ * ( no second `onFlush` ). If the buffer is empty, resolves immediately.
63
+ * Otherwise the buffer is snapshotted and cleared synchronously, `onFlush`
64
+ * runs ( optionally raced against `exportTimeoutMs` ), and the in-flight guard
65
+ * is cleared in `finally` regardless of success or failure. The returned
66
+ * promise settles with `onFlush`'s outcome so an awaiting owner can catch a
67
+ * rejection and requeue.
68
+ */
69
+ flush() {
70
+ if (this.inFlight) {
71
+ return this.inFlight;
72
+ }
73
+ if (this.buffer.length === 0) {
74
+ return Promise.resolve();
75
+ }
76
+ const batch = this.buffer;
77
+ this.buffer = [];
78
+ const run = this.options.exportTimeoutMs !== undefined ? this.withTimeout(this.options.onFlush(batch), this.options.exportTimeoutMs) : this.options.onFlush(batch);
79
+ // Store as the guard and clear it once settled ( success OR failure ) so the
80
+ // next flush can proceed. The promise is still returned to the caller so the
81
+ // rejection propagates for owner-side handling.
82
+ this.inFlight = run.finally(() => {
83
+ this.inFlight = null;
84
+ });
85
+ return this.inFlight;
86
+ }
87
+ /**
88
+ * Race a flush promise against a timeout that rejects after `ms`. The timeout
89
+ * timer is cleared ( and unref()'d ) so it neither leaks nor keeps the loop alive.
90
+ */
91
+ withTimeout(promise, ms) {
92
+ let timeout;
93
+ const timer = new Promise((_resolve, reject) => {
94
+ timeout = setTimeout(() => reject(new Error(`BatchQueue flush timed out after ${ms}ms`)), ms);
95
+ timeout.unref?.();
96
+ });
97
+ return Promise.race([promise, timer]).finally(() => {
98
+ clearTimeout(timeout);
99
+ });
100
+ }
101
+ /**
102
+ * Flush any buffered items and await completion. If a flush is already in
103
+ * flight, wait for it first, then flush any remainder that accumulated. Since
104
+ * a single `flush()` drains the ENTIRE buffer, one follow-up flush suffices.
105
+ */
106
+ async forceFlush() {
107
+ if (this.inFlight) {
108
+ await this.inFlight;
109
+ }
110
+ await this.flush();
111
+ }
112
+ /**
113
+ * Stop the periodic timer and perform a best-effort final flush of buffered
114
+ * items. After shutdown, `enqueue` is a no-op that resolves.
115
+ */
116
+ async shutdown() {
117
+ this.stopped = true;
118
+ clearInterval(this.timer);
119
+ await this.forceFlush();
120
+ }
121
+ }
122
+ //# sourceMappingURL=BatchQueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BatchQueue.js","sourceRoot":"","sources":["../../src/BatchQueue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { ILogEntry } from "../index.js";
2
+ /**
3
+ * Common shape of a configured filter entry. `type` is the DI string name the
4
+ * filter is registered under ( eg. "LevelFilter" ); all other keys are the
5
+ * filter's own free-form options and are read by the concrete filter.
6
+ */
7
+ export interface ILogFilterOptions {
8
+ type: string;
9
+ [k: string]: unknown;
10
+ }
11
+ /**
12
+ * Base class for a composable log filter. Filters are DI-registered by string
13
+ * name ( just like log targets ) via `@Injectable("<Name>")` and resolved per
14
+ * logger from `logger.filters` config. They run IN ORDER inside the logger's
15
+ * `write()`: each filter may pass the entry through ( optionally mutating it ),
16
+ * or return `null` to DROP it entirely.
17
+ *
18
+ * Kept a plain, dependency-light abstract class ( no SyncService etc. ) so the
19
+ * contract stays browser-safe and cheap to instantiate.
20
+ */
21
+ export declare abstract class LogFilter {
22
+ protected options?: ILogFilterOptions | undefined;
23
+ constructor(options?: ILogFilterOptions | undefined);
24
+ /** Return the ( possibly modified ) entry to keep, or null to DROP it. */
25
+ abstract apply(entry: ILogEntry): ILogEntry | null;
26
+ }
27
+ //# sourceMappingURL=filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../../src/filters/filter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,8BAAsB,SAAS;IACjB,SAAS,CAAC,OAAO,CAAC,EAAE,iBAAiB;gBAA3B,OAAO,CAAC,EAAE,iBAAiB,YAAA;IAEjD,0EAA0E;aAC1D,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;CAC1D"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Base class for a composable log filter. Filters are DI-registered by string
3
+ * name ( just like log targets ) via `@Injectable("<Name>")` and resolved per
4
+ * logger from `logger.filters` config. They run IN ORDER inside the logger's
5
+ * `write()`: each filter may pass the entry through ( optionally mutating it ),
6
+ * or return `null` to DROP it entirely.
7
+ *
8
+ * Kept a plain, dependency-light abstract class ( no SyncService etc. ) so the
9
+ * contract stays browser-safe and cheap to instantiate.
10
+ */
11
+ export class LogFilter {
12
+ constructor(options) {
13
+ this.options = options;
14
+ }
15
+ }
16
+ //# sourceMappingURL=filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.js","sourceRoot":"","sources":["../../../src/filters/filter.ts"],"names":[],"mappings":"AAeA;;;;;;;;;GASG;AACH,MAAM,OAAgB,SAAS;IAC7B,YAAsB,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;CAItD"}
@@ -0,0 +1,52 @@
1
+ import type { ILogEntry } from "../index.js";
2
+ import { LogFilter, ILogFilterOptions } from "./filter.js";
3
+ export interface IWhenRepeatedOptions extends Partial<ILogFilterOptions> {
4
+ /**
5
+ * Suppression window in SECONDS. Identical entries logged within this window
6
+ * ( from the first occurrence ) are collapsed into one. Default is 10.
7
+ */
8
+ timeout?: number;
9
+ /**
10
+ * Hard cap on the number of tracked keys. The internal map is self-pruning:
11
+ * expired records are dropped first, then the oldest, so memory stays bounded
12
+ * even under many distinct messages. Default is 1024.
13
+ */
14
+ maxKeys?: number;
15
+ }
16
+ interface IRepeatRecord {
17
+ count: number;
18
+ windowStart: number;
19
+ level: number;
20
+ }
21
+ /**
22
+ * Port of NLog's WhenRepeatedFilter. Collapses identical, repeated log entries
23
+ * within a timeout window into a single emitted entry, appending a ` (xN)`
24
+ * suppressed-count when logging of that key resumes. This stops a hot loop or a
25
+ * flapping error from flooding console / file / Loki and burying real signal.
26
+ *
27
+ * Known tradeoff: there is no background timer. A residual suppressed count is
28
+ * flushed on the NEXT matching entry ( window expired or higher severity ), NOT
29
+ * when the window simply elapses with no further activity. This keeps the filter
30
+ * dependency-free; the full filter pipeline in a later phase can layer a timed
31
+ * flush on top.
32
+ */
33
+ export declare class WhenRepeatedFilter extends LogFilter {
34
+ protected now: () => number;
35
+ protected timeoutMs: number;
36
+ protected maxKeys: number;
37
+ protected records: Map<string, IRepeatRecord>;
38
+ constructor(options?: IWhenRepeatedOptions, now?: () => number);
39
+ protected keyOf(entry: ILogEntry): string;
40
+ /**
41
+ * Returns the entry to EMIT ( possibly with an injected suppressed-count ) or
42
+ * `null` to SUPPRESS.
43
+ */
44
+ apply(entry: ILogEntry): ILogEntry | null;
45
+ /**
46
+ * Keeps the tracked-key map bounded. Drops expired records first, then the
47
+ * oldest by `windowStart` until at/under the cap.
48
+ */
49
+ protected prune(now: number): void;
50
+ }
51
+ export {};
52
+ //# sourceMappingURL=whenRepeated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whenRepeated.d.ts","sourceRoot":"","sources":["../../../src/filters/whenRepeated.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,oBAAqB,SAAQ,OAAO,CAAC,iBAAiB,CAAC;IACtE;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,qBAEa,kBAAmB,SAAQ,SAAS;IAKH,SAAS,CAAC,GAAG,EAAE,MAAM,MAAM;IAJvE,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAa;gBAE9C,OAAO,CAAC,EAAE,oBAAoB,EAAY,GAAG,GAAE,MAAM,MAAyB;IAM1F,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM;IAIzC;;;OAGG;IACI,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;IA8BhD;;;OAGG;IACH,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAuBnC"}
@@ -0,0 +1,95 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Injectable, NewInstance } from "@spinajs/di";
11
+ import { LogFilter } from "./filter.js";
12
+ /**
13
+ * Port of NLog's WhenRepeatedFilter. Collapses identical, repeated log entries
14
+ * within a timeout window into a single emitted entry, appending a ` (xN)`
15
+ * suppressed-count when logging of that key resumes. This stops a hot loop or a
16
+ * flapping error from flooding console / file / Loki and burying real signal.
17
+ *
18
+ * Known tradeoff: there is no background timer. A residual suppressed count is
19
+ * flushed on the NEXT matching entry ( window expired or higher severity ), NOT
20
+ * when the window simply elapses with no further activity. This keeps the filter
21
+ * dependency-free; the full filter pipeline in a later phase can layer a timed
22
+ * flush on top.
23
+ */
24
+ let WhenRepeatedFilter = class WhenRepeatedFilter extends LogFilter {
25
+ constructor(options, now = () => Date.now()) {
26
+ super(options);
27
+ this.now = now;
28
+ this.records = new Map();
29
+ this.timeoutMs = (options?.timeout ?? 10) * 1000;
30
+ this.maxKeys = options?.maxKeys ?? 1024;
31
+ }
32
+ keyOf(entry) {
33
+ return `${entry.Level}|${entry.Variables.logger}|${entry.Variables.message}`;
34
+ }
35
+ /**
36
+ * Returns the entry to EMIT ( possibly with an injected suppressed-count ) or
37
+ * `null` to SUPPRESS.
38
+ */
39
+ apply(entry) {
40
+ const key = this.keyOf(entry);
41
+ const now = this.now();
42
+ const record = this.records.get(key);
43
+ if (!record) {
44
+ // first occurrence of this key - emit and start tracking
45
+ this.records.set(key, { count: 0, windowStart: now, level: entry.Level });
46
+ this.prune(now);
47
+ return entry;
48
+ }
49
+ if (now - record.windowStart < this.timeoutMs && entry.Level <= record.level) {
50
+ // still inside the window and not a higher severity - suppress
51
+ record.count++;
52
+ return null;
53
+ }
54
+ // window expired OR a higher-severity entry arrived - emit and reset. If we
55
+ // suppressed anything, inject the count into the ( freshly built per call,
56
+ // so safe to mutate ) message string.
57
+ if (record.count > 0) {
58
+ entry.Variables.message = `${entry.Variables.message} (x${record.count})`;
59
+ }
60
+ this.records.set(key, { count: 0, windowStart: now, level: entry.Level });
61
+ this.prune(now);
62
+ return entry;
63
+ }
64
+ /**
65
+ * Keeps the tracked-key map bounded. Drops expired records first, then the
66
+ * oldest by `windowStart` until at/under the cap.
67
+ */
68
+ prune(now) {
69
+ if (this.records.size <= this.maxKeys) {
70
+ return;
71
+ }
72
+ for (const [k, r] of this.records) {
73
+ if (now - r.windowStart >= this.timeoutMs) {
74
+ this.records.delete(k);
75
+ }
76
+ }
77
+ if (this.records.size <= this.maxKeys) {
78
+ return;
79
+ }
80
+ const byAge = [...this.records.entries()].sort((a, b) => a[1].windowStart - b[1].windowStart);
81
+ for (const [k] of byAge) {
82
+ if (this.records.size <= this.maxKeys) {
83
+ break;
84
+ }
85
+ this.records.delete(k);
86
+ }
87
+ }
88
+ };
89
+ WhenRepeatedFilter = __decorate([
90
+ Injectable("WhenRepeatedFilter"),
91
+ NewInstance(),
92
+ __metadata("design:paramtypes", [Object, Function])
93
+ ], WhenRepeatedFilter);
94
+ export { WhenRepeatedFilter };
95
+ //# sourceMappingURL=whenRepeated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whenRepeated.js","sourceRoot":"","sources":["../../../src/filters/whenRepeated.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal, platform-free clone of Node's `util.format`.
3
+ *
4
+ * Supports the format specifiers the framework actually uses:
5
+ * %s - String
6
+ * %d - Number ( integer )
7
+ * %i - parseInt
8
+ * %f - parseFloat
9
+ * %j - JSON
10
+ * %o - Object ( JSON.stringify )
11
+ * %O - Object ( JSON.stringify )
12
+ * %% - literal percent sign ( consumes no argument )
13
+ *
14
+ * Any arguments left over after the specifiers are consumed are appended,
15
+ * space-separated. Objects are rendered via JSON.stringify ( circular refs
16
+ * fall back to String() ). This avoids pulling in Node's `util` module so the
17
+ * logger is usable in the browser.
18
+ */
19
+ export declare function format(f?: any, ...args: any[]): string;
20
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/format.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,CA0CtD"}
@@ -0,0 +1,72 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /**
3
+ * Minimal, platform-free clone of Node's `util.format`.
4
+ *
5
+ * Supports the format specifiers the framework actually uses:
6
+ * %s - String
7
+ * %d - Number ( integer )
8
+ * %i - parseInt
9
+ * %f - parseFloat
10
+ * %j - JSON
11
+ * %o - Object ( JSON.stringify )
12
+ * %O - Object ( JSON.stringify )
13
+ * %% - literal percent sign ( consumes no argument )
14
+ *
15
+ * Any arguments left over after the specifiers are consumed are appended,
16
+ * space-separated. Objects are rendered via JSON.stringify ( circular refs
17
+ * fall back to String() ). This avoids pulling in Node's `util` module so the
18
+ * logger is usable in the browser.
19
+ */
20
+ export function format(f, ...args) {
21
+ if (typeof f !== 'string') {
22
+ // no format string: join everything space-separated
23
+ return [f, ...args].map((a) => inspect(a)).join(' ');
24
+ }
25
+ let argIndex = 0;
26
+ const str = f.replace(/%[sdifjoO%]/g, (match) => {
27
+ if (match === '%%') {
28
+ return '%';
29
+ }
30
+ if (argIndex >= args.length) {
31
+ return match;
32
+ }
33
+ const arg = args[argIndex++];
34
+ switch (match) {
35
+ case '%s':
36
+ return typeof arg === 'object' && arg !== null ? inspect(arg) : String(arg);
37
+ case '%d':
38
+ return String(Number(arg));
39
+ case '%i':
40
+ return String(parseInt(arg, 10));
41
+ case '%f':
42
+ return String(parseFloat(arg));
43
+ case '%j':
44
+ case '%o':
45
+ case '%O':
46
+ return inspect(arg);
47
+ default:
48
+ return match;
49
+ }
50
+ });
51
+ if (argIndex >= args.length) {
52
+ return str;
53
+ }
54
+ // append remaining args space-separated
55
+ const rest = args.slice(argIndex).map((a) => (typeof a === 'string' ? a : inspect(a)));
56
+ return [str, ...rest].join(' ');
57
+ }
58
+ function inspect(value) {
59
+ if (typeof value === 'string') {
60
+ return value;
61
+ }
62
+ if (typeof value === 'object' && value !== null) {
63
+ try {
64
+ return JSON.stringify(value);
65
+ }
66
+ catch {
67
+ return String(value);
68
+ }
69
+ }
70
+ return String(value);
71
+ }
72
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/format.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,MAAM,CAAC,CAAO,EAAE,GAAG,IAAW;IAC5C,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1B,oDAAoD;QACpD,OAAO,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,KAAa,EAAU,EAAE;QAC9D,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,GAAG,CAAC;QACb,CAAC;QAED,IAAI,QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC7B,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,IAAI;gBACP,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC9E,KAAK,IAAI;gBACP,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7B,KAAK,IAAI;gBACP,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAU,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1C,KAAK,IAAI;gBACP,OAAO,MAAM,CAAC,UAAU,CAAC,GAAU,CAAC,CAAC,CAAC;YACxC,KAAK,IAAI,CAAC;YACV,KAAK,IAAI,CAAC;YACV,KAAK,IAAI;gBACP,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;YACtB;gBACE,OAAO,KAAK,CAAC;QACjB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,wCAAwC;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,OAAO,CAAC,KAAU;IACzB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}