loggily 0.7.0 → 0.8.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.
@@ -1,48 +1,2 @@
1
- import { d as SpanRecorder, t as ConditionalLogger, u as SpanRecord } from "./core-DAFH-huv.mjs";
2
-
3
- //#region src/metrics.d.ts
4
- interface SpanStats {
5
- count: number;
6
- min: number;
7
- max: number;
8
- mean: number;
9
- p50: number;
10
- p95: number;
11
- p99: number;
12
- total: number;
13
- }
14
- interface MetricsCollector extends SpanRecorder {
15
- /** Get stats for a specific span namespace */
16
- stats(name: string): SpanStats | undefined;
17
- /** Get stats for all recorded namespaces */
18
- all(): Map<string, SpanStats>;
19
- /** Format a human-readable summary */
20
- summary(): string;
21
- /** Reset all collected data */
22
- reset(): void;
23
- }
24
- declare function createMetricsCollector(maxEntries?: number): MetricsCollector;
25
- /** Get aggregated span stats (from ambient collector). */
26
- declare function spanStats(): Map<string, SpanStats>;
27
- /** Get the ambient collector's formatted summary. */
28
- declare function spanSummary(): string;
29
- /** Reset the ambient collector. */
30
- declare function resetSpanStats(): void;
31
- /**
32
- * Compose a logger with a metrics collector.
33
- * Returns a curried wrapper: `withMetrics(collector?)(logger)`
34
- *
35
- * - No arg: uses the built-in ambient collector
36
- * - Custom collector: records to your collector
37
- * - Stackable: `withMetrics(a)(withMetrics(b)(logger))` fans out to both
38
- *
39
- * @example
40
- * ```typescript
41
- * const log = withMetrics()(createLogger("myapp"))
42
- * const log = withMetrics(myCollector)(createLogger("myapp"))
43
- * ```
44
- */
45
- declare function withMetrics(collector?: SpanRecorder): (logger: ConditionalLogger) => ConditionalLogger;
46
- //#endregion
47
- export { MetricsCollector, type SpanRecord, type SpanRecorder, SpanStats, createMetricsCollector, resetSpanStats, spanStats, spanSummary, withMetrics };
48
- //# sourceMappingURL=metrics.d.mts.map
1
+ import { G as SpanStats, K as createMetricsCollector, W as MetricsCollector, d as SpanRecord, f as SpanRecorder, q as withMetrics } from "./core-Dm2PQUoS.mjs";
2
+ export { MetricsCollector, SpanRecord, SpanRecorder, SpanStats, createMetricsCollector, withMetrics };
package/dist/metrics.mjs CHANGED
@@ -1,23 +1,4 @@
1
- import { r as _setAmbientRecorder } from "./core-Du3sIje6.mjs";
2
1
  //#region src/metrics.ts
3
- /**
4
- * Metrics collection for loggily spans.
5
- *
6
- * Two modes:
7
- * - **Ambient**: import this module → spans auto-record when TRACE is active
8
- * - **Explicit**: `withMetrics(collector?)(logger)` for custom collection
9
- *
10
- * @example
11
- * ```typescript
12
- * import { spanStats } from "loggily/metrics"
13
- * // TRACE=myapp bun run app
14
- * // → on exit: spanStats() returns aggregated p50/p95/p99
15
- *
16
- * // Custom collector:
17
- * import { withMetrics, createMetricsCollector } from "loggily/metrics"
18
- * const log = withMetrics()(createLogger("myapp"))
19
- * ```
20
- */
21
2
  function percentile(sorted, p) {
22
3
  if (sorted.length === 0) return 0;
23
4
  return sorted[Math.min(Math.floor(sorted.length * p), sorted.length - 1)];
@@ -68,38 +49,23 @@ function createMetricsCollector(maxEntries = 1e3) {
68
49
  }
69
50
  };
70
51
  }
71
- const _ambient = createMetricsCollector();
72
- _setAmbientRecorder(_ambient);
73
- /** Get aggregated span stats (from ambient collector). */
74
- function spanStats() {
75
- return _ambient.all();
76
- }
77
- /** Get the ambient collector's formatted summary. */
78
- function spanSummary() {
79
- return _ambient.summary();
80
- }
81
- /** Reset the ambient collector. */
82
- function resetSpanStats() {
83
- _ambient.reset();
84
- }
85
52
  /**
86
53
  * Compose a logger with a metrics collector.
87
- * Returns a curried wrapper: `withMetrics(collector?)(logger)`
54
+ * Returns a curried wrapper: `withMetrics(collector)(logger)`
88
55
  *
89
- * - No arg: uses the built-in ambient collector
90
- * - Custom collector: records to your collector
91
- * - Stackable: `withMetrics(a)(withMetrics(b)(logger))` fans out to both
56
+ * Records span duration to the provided collector on span disposal.
57
+ * Stackable: `withMetrics(a)(withMetrics(b)(logger))` fans out to both.
92
58
  *
93
59
  * @example
94
60
  * ```typescript
95
- * const log = withMetrics()(createLogger("myapp"))
96
- * const log = withMetrics(myCollector)(createLogger("myapp"))
61
+ * const collector = createMetricsCollector()
62
+ * const log = withMetrics(collector)(createLogger("myapp"))
97
63
  * ```
98
64
  */
99
65
  function withMetrics(collector) {
100
- const recorder = collector ?? _ambient;
101
66
  return (logger) => {
102
67
  return new Proxy(logger, { get(target, prop) {
68
+ if (prop === "metrics") return collector;
103
69
  if (prop === "span") {
104
70
  const originalSpan = target.span;
105
71
  if (!originalSpan) return void 0;
@@ -108,7 +74,7 @@ function withMetrics(collector) {
108
74
  const originalDispose = span[Symbol.dispose];
109
75
  span[Symbol.dispose] = () => {
110
76
  originalDispose.call(span);
111
- if (span.spanData?.duration != null) recorder.recordSpan({
77
+ if (span.spanData?.duration != null) collector.recordSpan({
112
78
  name: span.name,
113
79
  durationMs: span.spanData.duration
114
80
  });
@@ -116,15 +82,19 @@ function withMetrics(collector) {
116
82
  return span;
117
83
  };
118
84
  }
85
+ if (prop === "child") return (namespaceOrContext, childProps) => {
86
+ const child = target.child(namespaceOrContext, childProps);
87
+ return withMetrics(collector)(child);
88
+ };
119
89
  if (prop === "logger") return (namespace, childProps) => {
120
90
  const child = target.logger(namespace, childProps);
121
- return withMetrics(recorder)(child);
91
+ return withMetrics(collector)(child);
122
92
  };
123
93
  return target[prop];
124
94
  } });
125
95
  };
126
96
  }
127
97
  //#endregion
128
- export { createMetricsCollector, resetSpanStats, spanStats, spanSummary, withMetrics };
98
+ export { createMetricsCollector, withMetrics };
129
99
 
130
100
  //# sourceMappingURL=metrics.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.mjs","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["/**\n * Metrics collection for loggily spans.\n *\n * Two modes:\n * - **Ambient**: import this module → spans auto-record when TRACE is active\n * - **Explicit**: `withMetrics(collector?)(logger)` for custom collection\n *\n * @example\n * ```typescript\n * import { spanStats } from \"loggily/metrics\"\n * // TRACE=myapp bun run app\n * // → on exit: spanStats() returns aggregated p50/p95/p99\n *\n * // Custom collector:\n * import { withMetrics, createMetricsCollector } from \"loggily/metrics\"\n * const log = withMetrics()(createLogger(\"myapp\"))\n * ```\n */\n\nimport {\n type SpanRecorder,\n type SpanRecord,\n type ConditionalLogger,\n type Logger,\n type LazyProps,\n type SpanLogger,\n _setAmbientRecorder,\n spansAreEnabled,\n} from \"./core.js\"\n\nexport type { SpanRecorder, SpanRecord }\n\n// ============ Stats ============\n\nexport interface SpanStats {\n count: number\n min: number\n max: number\n mean: number\n p50: number\n p95: number\n p99: number\n total: number\n}\n\nfunction percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0\n const idx = Math.min(Math.floor(sorted.length * p), sorted.length - 1)\n return sorted[idx]!\n}\n\nfunction computeStats(durations: number[]): SpanStats {\n const sorted = [...durations].sort((a, b) => a - b)\n const total = sorted.reduce((sum, d) => sum + d, 0)\n return {\n count: sorted.length,\n min: sorted[0] ?? 0,\n max: sorted[sorted.length - 1] ?? 0,\n mean: sorted.length > 0 ? total / sorted.length : 0,\n p50: percentile(sorted, 0.5),\n p95: percentile(sorted, 0.95),\n p99: percentile(sorted, 0.99),\n total,\n }\n}\n\n// ============ Collector ============\n\nexport interface MetricsCollector extends SpanRecorder {\n /** Get stats for a specific span namespace */\n stats(name: string): SpanStats | undefined\n /** Get stats for all recorded namespaces */\n all(): Map<string, SpanStats>\n /** Format a human-readable summary */\n summary(): string\n /** Reset all collected data */\n reset(): void\n}\n\nexport function createMetricsCollector(maxEntries = 1000): MetricsCollector {\n const store = new Map<string, number[]>()\n\n return {\n recordSpan(data: SpanRecord): void {\n let arr = store.get(data.name)\n if (!arr) {\n arr = []\n store.set(data.name, arr)\n }\n arr.push(data.durationMs)\n // Bound memory: keep last N entries per namespace\n if (arr.length > maxEntries) arr.shift()\n },\n\n stats(name: string): SpanStats | undefined {\n const arr = store.get(name)\n if (!arr || arr.length === 0) return undefined\n return computeStats(arr)\n },\n\n all(): Map<string, SpanStats> {\n const result = new Map<string, SpanStats>()\n for (const [name, durations] of store) {\n if (durations.length > 0) result.set(name, computeStats(durations))\n }\n return result\n },\n\n summary(): string {\n const entries = [...this.all().entries()]\n if (entries.length === 0) return \"(no span data)\"\n const lines = entries.map(\n ([name, s]) =>\n `${name}: ${s.count} spans, mean=${s.mean.toFixed(1)}ms, p50=${s.p50.toFixed(1)}ms, p95=${s.p95.toFixed(1)}ms, p99=${s.p99.toFixed(1)}ms`,\n )\n return lines.join(\"\\n\")\n },\n\n reset(): void {\n store.clear()\n },\n }\n}\n\n// ============ Ambient Collector ============\n\nconst _ambient = createMetricsCollector()\n\n// Auto-activate ambient recording.\n// Always set — the cost is one ?.recordSpan() call per span (negligible).\n// The TRACE gate already controls whether spans are *created* at all.\n_setAmbientRecorder(_ambient)\n\n/** Get aggregated span stats (from ambient collector). */\nexport function spanStats(): Map<string, SpanStats> {\n return _ambient.all()\n}\n\n/** Get the ambient collector's formatted summary. */\nexport function spanSummary(): string {\n return _ambient.summary()\n}\n\n/** Reset the ambient collector. */\nexport function resetSpanStats(): void {\n _ambient.reset()\n}\n\n// ============ withMetrics ============\n\n/**\n * Compose a logger with a metrics collector.\n * Returns a curried wrapper: `withMetrics(collector?)(logger)`\n *\n * - No arg: uses the built-in ambient collector\n * - Custom collector: records to your collector\n * - Stackable: `withMetrics(a)(withMetrics(b)(logger))` fans out to both\n *\n * @example\n * ```typescript\n * const log = withMetrics()(createLogger(\"myapp\"))\n * const log = withMetrics(myCollector)(createLogger(\"myapp\"))\n * ```\n */\nexport function withMetrics(collector?: SpanRecorder): (logger: ConditionalLogger) => ConditionalLogger {\n const recorder = collector ?? _ambient\n\n return (logger: ConditionalLogger): ConditionalLogger => {\n // Wrap the logger's span method to intercept disposal\n return new Proxy(logger, {\n get(target, prop: string | symbol) {\n if (prop === \"span\") {\n const originalSpan = target.span\n if (!originalSpan) return undefined // TRACE off — preserve ?. behavior\n return (namespace?: string, props?: LazyProps): SpanLogger => {\n const span = originalSpan.call(target, namespace, props)\n // Wrap disposal to record to our collector\n const originalDispose = (span as unknown as { [Symbol.dispose]: () => void })[Symbol.dispose]\n ;(span as unknown as { [Symbol.dispose]: () => void })[Symbol.dispose] = () => {\n originalDispose.call(span)\n // After original disposal computed duration, record it\n if (span.spanData?.duration != null) {\n recorder.recordSpan({ name: span.name, durationMs: span.spanData.duration })\n }\n }\n return span\n }\n }\n if (prop === \"logger\") {\n // Child loggers inherit the metrics wrapper\n return (namespace?: string, childProps?: Record<string, unknown>): Logger => {\n const child = target.logger(namespace, childProps)\n // Re-wrap the child — withMetrics(recorder) applied recursively\n return withMetrics(recorder)(child as unknown as ConditionalLogger) as unknown as Logger\n }\n }\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n })\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA6CA,SAAS,WAAW,QAAkB,GAAmB;AACvD,KAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAO,OADK,KAAK,IAAI,KAAK,MAAM,OAAO,SAAS,EAAE,EAAE,OAAO,SAAS,EAAE;;AAIxE,SAAS,aAAa,WAAgC;CACpD,MAAM,SAAS,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE;CACnD,MAAM,QAAQ,OAAO,QAAQ,KAAK,MAAM,MAAM,GAAG,EAAE;AACnD,QAAO;EACL,OAAO,OAAO;EACd,KAAK,OAAO,MAAM;EAClB,KAAK,OAAO,OAAO,SAAS,MAAM;EAClC,MAAM,OAAO,SAAS,IAAI,QAAQ,OAAO,SAAS;EAClD,KAAK,WAAW,QAAQ,GAAI;EAC5B,KAAK,WAAW,QAAQ,IAAK;EAC7B,KAAK,WAAW,QAAQ,IAAK;EAC7B;EACD;;AAgBH,SAAgB,uBAAuB,aAAa,KAAwB;CAC1E,MAAM,wBAAQ,IAAI,KAAuB;AAEzC,QAAO;EACL,WAAW,MAAwB;GACjC,IAAI,MAAM,MAAM,IAAI,KAAK,KAAK;AAC9B,OAAI,CAAC,KAAK;AACR,UAAM,EAAE;AACR,UAAM,IAAI,KAAK,MAAM,IAAI;;AAE3B,OAAI,KAAK,KAAK,WAAW;AAEzB,OAAI,IAAI,SAAS,WAAY,KAAI,OAAO;;EAG1C,MAAM,MAAqC;GACzC,MAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,OAAI,CAAC,OAAO,IAAI,WAAW,EAAG,QAAO,KAAA;AACrC,UAAO,aAAa,IAAI;;EAG1B,MAA8B;GAC5B,MAAM,yBAAS,IAAI,KAAwB;AAC3C,QAAK,MAAM,CAAC,MAAM,cAAc,MAC9B,KAAI,UAAU,SAAS,EAAG,QAAO,IAAI,MAAM,aAAa,UAAU,CAAC;AAErE,UAAO;;EAGT,UAAkB;GAChB,MAAM,UAAU,CAAC,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;AACzC,OAAI,QAAQ,WAAW,EAAG,QAAO;AAKjC,UAJc,QAAQ,KACnB,CAAC,MAAM,OACN,GAAG,KAAK,IAAI,EAAE,MAAM,eAAe,EAAE,KAAK,QAAQ,EAAE,CAAC,UAAU,EAAE,IAAI,QAAQ,EAAE,CAAC,UAAU,EAAE,IAAI,QAAQ,EAAE,CAAC,UAAU,EAAE,IAAI,QAAQ,EAAE,CAAC,IACzI,CACY,KAAK,KAAK;;EAGzB,QAAc;AACZ,SAAM,OAAO;;EAEhB;;AAKH,MAAM,WAAW,wBAAwB;AAKzC,oBAAoB,SAAS;;AAG7B,SAAgB,YAAoC;AAClD,QAAO,SAAS,KAAK;;;AAIvB,SAAgB,cAAsB;AACpC,QAAO,SAAS,SAAS;;;AAI3B,SAAgB,iBAAuB;AACrC,UAAS,OAAO;;;;;;;;;;;;;;;;AAmBlB,SAAgB,YAAY,WAA4E;CACtG,MAAM,WAAW,aAAa;AAE9B,SAAQ,WAAiD;AAEvD,SAAO,IAAI,MAAM,QAAQ,EACvB,IAAI,QAAQ,MAAuB;AACjC,OAAI,SAAS,QAAQ;IACnB,MAAM,eAAe,OAAO;AAC5B,QAAI,CAAC,aAAc,QAAO,KAAA;AAC1B,YAAQ,WAAoB,UAAkC;KAC5D,MAAM,OAAO,aAAa,KAAK,QAAQ,WAAW,MAAM;KAExD,MAAM,kBAAmB,KAAqD,OAAO;AACnF,UAAqD,OAAO,iBAAiB;AAC7E,sBAAgB,KAAK,KAAK;AAE1B,UAAI,KAAK,UAAU,YAAY,KAC7B,UAAS,WAAW;OAAE,MAAM,KAAK;OAAM,YAAY,KAAK,SAAS;OAAU,CAAC;;AAGhF,YAAO;;;AAGX,OAAI,SAAS,SAEX,SAAQ,WAAoB,eAAiD;IAC3E,MAAM,QAAQ,OAAO,OAAO,WAAW,WAAW;AAElD,WAAO,YAAY,SAAS,CAAC,MAAsC;;AAGvE,UAAQ,OAAuD;KAElE,CAAC"}
1
+ {"version":3,"file":"metrics.mjs","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["/**\n * Metrics collection for loggily spans.\n *\n * Explicit only: `withMetrics(collector)(logger)` for custom collection.\n *\n * @example\n * ```typescript\n * import { withMetrics, createMetricsCollector } from \"loggily/metrics\"\n * const collector = createMetricsCollector()\n * const log = withMetrics(collector)(createLogger(\"myapp\"))\n * ```\n */\n\nimport {\n type SpanRecorder,\n type SpanRecord,\n type ConditionalLogger,\n type Logger,\n type LazyProps,\n type SpanLogger,\n} from \"./core.js\"\n\nexport type { SpanRecorder, SpanRecord }\n\n// ============ Stats ============\n\nexport interface SpanStats {\n count: number\n min: number\n max: number\n mean: number\n p50: number\n p95: number\n p99: number\n total: number\n}\n\nfunction percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0\n const idx = Math.min(Math.floor(sorted.length * p), sorted.length - 1)\n return sorted[idx]!\n}\n\nfunction computeStats(durations: number[]): SpanStats {\n const sorted = [...durations].sort((a, b) => a - b)\n const total = sorted.reduce((sum, d) => sum + d, 0)\n return {\n count: sorted.length,\n min: sorted[0] ?? 0,\n max: sorted[sorted.length - 1] ?? 0,\n mean: sorted.length > 0 ? total / sorted.length : 0,\n p50: percentile(sorted, 0.5),\n p95: percentile(sorted, 0.95),\n p99: percentile(sorted, 0.99),\n total,\n }\n}\n\n// ============ Collector ============\n\nexport interface MetricsCollector extends SpanRecorder {\n /** Get stats for a specific span namespace */\n stats(name: string): SpanStats | undefined\n /** Get stats for all recorded namespaces */\n all(): Map<string, SpanStats>\n /** Format a human-readable summary */\n summary(): string\n /** Reset all collected data */\n reset(): void\n}\n\nexport function createMetricsCollector(maxEntries = 1000): MetricsCollector {\n const store = new Map<string, number[]>()\n\n return {\n recordSpan(data: SpanRecord): void {\n let arr = store.get(data.name)\n if (!arr) {\n arr = []\n store.set(data.name, arr)\n }\n arr.push(data.durationMs)\n // Bound memory: keep last N entries per namespace\n if (arr.length > maxEntries) arr.shift()\n },\n\n stats(name: string): SpanStats | undefined {\n const arr = store.get(name)\n if (!arr || arr.length === 0) return undefined\n return computeStats(arr)\n },\n\n all(): Map<string, SpanStats> {\n const result = new Map<string, SpanStats>()\n for (const [name, durations] of store) {\n if (durations.length > 0) result.set(name, computeStats(durations))\n }\n return result\n },\n\n summary(): string {\n const entries = [...this.all().entries()]\n if (entries.length === 0) return \"(no span data)\"\n const lines = entries.map(\n ([name, s]) =>\n `${name}: ${s.count} spans, mean=${s.mean.toFixed(1)}ms, p50=${s.p50.toFixed(1)}ms, p95=${s.p95.toFixed(1)}ms, p99=${s.p99.toFixed(1)}ms`,\n )\n return lines.join(\"\\n\")\n },\n\n reset(): void {\n store.clear()\n },\n }\n}\n\n// ============ withMetrics ============\n\n/**\n * Compose a logger with a metrics collector.\n * Returns a curried wrapper: `withMetrics(collector)(logger)`\n *\n * Records span duration to the provided collector on span disposal.\n * Stackable: `withMetrics(a)(withMetrics(b)(logger))` fans out to both.\n *\n * @example\n * ```typescript\n * const collector = createMetricsCollector()\n * const log = withMetrics(collector)(createLogger(\"myapp\"))\n * ```\n */\nexport function withMetrics(collector: SpanRecorder): (logger: ConditionalLogger) => ConditionalLogger {\n return (logger: ConditionalLogger): ConditionalLogger => {\n // Wrap the logger's span method to intercept disposal\n return new Proxy(logger, {\n get(target, prop: string | symbol) {\n if (prop === \"metrics\") {\n return collector\n }\n if (prop === \"span\") {\n const originalSpan = target.span\n if (!originalSpan) return undefined // TRACE off — preserve ?. behavior\n return (namespace?: string, props?: LazyProps): SpanLogger => {\n const span = originalSpan.call(target, namespace, props)\n // Wrap disposal to record to our collector\n const originalDispose = (span as unknown as { [Symbol.dispose]: () => void })[Symbol.dispose]\n ;(span as unknown as { [Symbol.dispose]: () => void })[Symbol.dispose] = () => {\n originalDispose.call(span)\n // After original disposal computed duration, record it\n if (span.spanData?.duration != null) {\n collector.recordSpan({ name: span.name, durationMs: span.spanData.duration })\n }\n }\n return span\n }\n }\n if (prop === \"child\") {\n return (\n namespaceOrContext?: string | Record<string, unknown>,\n childProps?: Record<string, unknown>,\n ): ConditionalLogger => {\n const child = target.child(namespaceOrContext as string, childProps)\n return withMetrics(collector)(child)\n }\n }\n if (prop === \"logger\") {\n // Child loggers inherit the metrics wrapper\n return (namespace?: string, childProps?: Record<string, unknown>): Logger => {\n const child = target.logger(namespace, childProps)\n // Re-wrap the child — withMetrics(collector) applied recursively\n return withMetrics(collector)(child as unknown as ConditionalLogger) as unknown as Logger\n }\n }\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n })\n }\n}\n"],"mappings":";AAqCA,SAAS,WAAW,QAAkB,GAAmB;AACvD,KAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAO,OADK,KAAK,IAAI,KAAK,MAAM,OAAO,SAAS,EAAE,EAAE,OAAO,SAAS,EAAE;;AAIxE,SAAS,aAAa,WAAgC;CACpD,MAAM,SAAS,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE;CACnD,MAAM,QAAQ,OAAO,QAAQ,KAAK,MAAM,MAAM,GAAG,EAAE;AACnD,QAAO;EACL,OAAO,OAAO;EACd,KAAK,OAAO,MAAM;EAClB,KAAK,OAAO,OAAO,SAAS,MAAM;EAClC,MAAM,OAAO,SAAS,IAAI,QAAQ,OAAO,SAAS;EAClD,KAAK,WAAW,QAAQ,GAAI;EAC5B,KAAK,WAAW,QAAQ,IAAK;EAC7B,KAAK,WAAW,QAAQ,IAAK;EAC7B;EACD;;AAgBH,SAAgB,uBAAuB,aAAa,KAAwB;CAC1E,MAAM,wBAAQ,IAAI,KAAuB;AAEzC,QAAO;EACL,WAAW,MAAwB;GACjC,IAAI,MAAM,MAAM,IAAI,KAAK,KAAK;AAC9B,OAAI,CAAC,KAAK;AACR,UAAM,EAAE;AACR,UAAM,IAAI,KAAK,MAAM,IAAI;;AAE3B,OAAI,KAAK,KAAK,WAAW;AAEzB,OAAI,IAAI,SAAS,WAAY,KAAI,OAAO;;EAG1C,MAAM,MAAqC;GACzC,MAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,OAAI,CAAC,OAAO,IAAI,WAAW,EAAG,QAAO,KAAA;AACrC,UAAO,aAAa,IAAI;;EAG1B,MAA8B;GAC5B,MAAM,yBAAS,IAAI,KAAwB;AAC3C,QAAK,MAAM,CAAC,MAAM,cAAc,MAC9B,KAAI,UAAU,SAAS,EAAG,QAAO,IAAI,MAAM,aAAa,UAAU,CAAC;AAErE,UAAO;;EAGT,UAAkB;GAChB,MAAM,UAAU,CAAC,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;AACzC,OAAI,QAAQ,WAAW,EAAG,QAAO;AAKjC,UAJc,QAAQ,KACnB,CAAC,MAAM,OACN,GAAG,KAAK,IAAI,EAAE,MAAM,eAAe,EAAE,KAAK,QAAQ,EAAE,CAAC,UAAU,EAAE,IAAI,QAAQ,EAAE,CAAC,UAAU,EAAE,IAAI,QAAQ,EAAE,CAAC,UAAU,EAAE,IAAI,QAAQ,EAAE,CAAC,IACzI,CACY,KAAK,KAAK;;EAGzB,QAAc;AACZ,SAAM,OAAO;;EAEhB;;;;;;;;;;;;;;;AAkBH,SAAgB,YAAY,WAA2E;AACrG,SAAQ,WAAiD;AAEvD,SAAO,IAAI,MAAM,QAAQ,EACvB,IAAI,QAAQ,MAAuB;AACjC,OAAI,SAAS,UACX,QAAO;AAET,OAAI,SAAS,QAAQ;IACnB,MAAM,eAAe,OAAO;AAC5B,QAAI,CAAC,aAAc,QAAO,KAAA;AAC1B,YAAQ,WAAoB,UAAkC;KAC5D,MAAM,OAAO,aAAa,KAAK,QAAQ,WAAW,MAAM;KAExD,MAAM,kBAAmB,KAAqD,OAAO;AACnF,UAAqD,OAAO,iBAAiB;AAC7E,sBAAgB,KAAK,KAAK;AAE1B,UAAI,KAAK,UAAU,YAAY,KAC7B,WAAU,WAAW;OAAE,MAAM,KAAK;OAAM,YAAY,KAAK,SAAS;OAAU,CAAC;;AAGjF,YAAO;;;AAGX,OAAI,SAAS,QACX,SACE,oBACA,eACsB;IACtB,MAAM,QAAQ,OAAO,MAAM,oBAA8B,WAAW;AACpE,WAAO,YAAY,UAAU,CAAC,MAAM;;AAGxC,OAAI,SAAS,SAEX,SAAQ,WAAoB,eAAiD;IAC3E,MAAM,QAAQ,OAAO,OAAO,WAAW,WAAW;AAElD,WAAO,YAAY,UAAU,CAAC,MAAsC;;AAGxE,UAAQ,OAAuD;KAElE,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { f as Stage } from "./pipeline-Cl9-wCmt.mjs";
2
+
3
+ //#region src/otel.d.ts
4
+ interface OtelApi {
5
+ logs?: {
6
+ getLoggerProvider(): {
7
+ getLogger(name: string): {
8
+ emit(record: {
9
+ severityNumber: number;
10
+ severityText: string;
11
+ body: string;
12
+ attributes: Record<string, unknown>;
13
+ timestamp: number[];
14
+ }): void;
15
+ };
16
+ };
17
+ };
18
+ trace?: {
19
+ getTracerProvider(): {
20
+ getTracer(name: string): {
21
+ startSpan(name: string, options?: {
22
+ startTime: number[];
23
+ attributes: Record<string, unknown>;
24
+ }): {
25
+ end(endTime?: number[]): void;
26
+ setAttribute(key: string, value: unknown): void;
27
+ setStatus(status: {
28
+ code: number;
29
+ message?: string;
30
+ }): void;
31
+ };
32
+ };
33
+ };
34
+ };
35
+ }
36
+ interface OtelBridgeOptions {
37
+ /** The @opentelemetry/api module. Pass the import to avoid require() in ESM. */
38
+ api?: OtelApi;
39
+ /** Logger name for OTLP LogRecords (default: "loggily") */
40
+ loggerName?: string;
41
+ /** Tracer name for OTLP Spans (default: "loggily") */
42
+ tracerName?: string;
43
+ /** Whether to forward log events (default: true) */
44
+ logs?: boolean;
45
+ /** Whether to forward span events (default: true) */
46
+ spans?: boolean;
47
+ }
48
+ /**
49
+ * Create a Stage that forwards loggily events to OpenTelemetry.
50
+ *
51
+ * Pass the @opentelemetry/api module via options.api:
52
+ *
53
+ * ```ts
54
+ * import * as otelApi from "@opentelemetry/api"
55
+ * const log = createLogger("myapp", [toOtel({ api: otelApi }), console])
56
+ * ```
57
+ *
58
+ * Events pass through unchanged (the stage is transparent).
59
+ */
60
+ declare function toOtel(options?: OtelBridgeOptions): Stage;
61
+ //#endregion
62
+ export { OtelBridgeOptions, toOtel };
63
+ //# sourceMappingURL=otel.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.d.mts","names":[],"sources":["../src/otel.ts"],"mappings":";;;UAeU,OAAA;EACR,IAAA;IACE,iBAAA;MACE,SAAA,CAAU,IAAA;QACR,IAAA,CAAK,MAAA;UACH,cAAA;UACA,YAAA;UACA,IAAA;UACA,UAAA,EAAY,MAAA;UACZ,SAAA;QAAA;MAAA;IAAA;EAAA;EAKR,KAAA;IACE,iBAAA;MACE,SAAA,CAAU,IAAA;QACR,SAAA,CACE,IAAA,UACA,OAAA;UACE,SAAA;UACA,UAAA,EAAY,MAAA;QAAA;UAGd,GAAA,CAAI,OAAA;UACJ,YAAA,CAAa,GAAA,UAAa,KAAA;UAC1B,SAAA,CAAU,MAAA;YAAU,IAAA;YAAc,OAAA;UAAA;QAAA;MAAA;IAAA;EAAA;AAAA;AAAA,UAqB3B,iBAAA;EAET;EAAN,GAAA,GAAM,OAAA;EAIN;EAFA,UAAA;EAMA;EAJA,UAAA;EAIK;EAFL,IAAA;EAiBoB;EAfpB,KAAA;AAAA;;;;;;;;;;;;;iBAec,MAAA,CAAO,OAAA,GAAS,iBAAA,GAAyB,KAAA"}
package/dist/otel.mjs ADDED
@@ -0,0 +1,82 @@
1
+ //#region src/otel.ts
2
+ const SEVERITY_MAP = {
3
+ trace: {
4
+ number: 1,
5
+ text: "TRACE"
6
+ },
7
+ debug: {
8
+ number: 5,
9
+ text: "DEBUG"
10
+ },
11
+ info: {
12
+ number: 9,
13
+ text: "INFO"
14
+ },
15
+ warn: {
16
+ number: 13,
17
+ text: "WARN"
18
+ },
19
+ error: {
20
+ number: 17,
21
+ text: "ERROR"
22
+ }
23
+ };
24
+ function msToHrTime(ms) {
25
+ return [Math.floor(ms / 1e3), ms % 1e3 * 1e6];
26
+ }
27
+ /**
28
+ * Create a Stage that forwards loggily events to OpenTelemetry.
29
+ *
30
+ * Pass the @opentelemetry/api module via options.api:
31
+ *
32
+ * ```ts
33
+ * import * as otelApi from "@opentelemetry/api"
34
+ * const log = createLogger("myapp", [toOtel({ api: otelApi }), console])
35
+ * ```
36
+ *
37
+ * Events pass through unchanged (the stage is transparent).
38
+ */
39
+ function toOtel(options = {}) {
40
+ const { api: otelApi = null, loggerName = "loggily", tracerName = "loggily", logs: forwardLogs = true, spans: forwardSpans = true } = options;
41
+ function forwardLog(event) {
42
+ const logsApi = otelApi?.logs;
43
+ if (!logsApi) return;
44
+ const severity = SEVERITY_MAP[event.level] ?? SEVERITY_MAP.info;
45
+ logsApi.getLoggerProvider().getLogger(loggerName).emit({
46
+ severityNumber: severity.number,
47
+ severityText: severity.text,
48
+ body: event.message,
49
+ attributes: {
50
+ "loggily.namespace": event.namespace,
51
+ ...event.props
52
+ },
53
+ timestamp: msToHrTime(event.time)
54
+ });
55
+ }
56
+ function forwardSpan(event) {
57
+ const traceApi = otelApi?.trace;
58
+ if (!traceApi) return;
59
+ const tracer = traceApi.getTracerProvider().getTracer(tracerName);
60
+ const startTime = msToHrTime(event.time - event.duration);
61
+ const span = tracer.startSpan(event.name, {
62
+ startTime,
63
+ attributes: {
64
+ "loggily.namespace": event.namespace,
65
+ "loggily.span_id": event.spanId,
66
+ "loggily.trace_id": event.traceId,
67
+ ...event.props
68
+ }
69
+ });
70
+ if (event.parentId) span.setAttribute("loggily.parent_id", event.parentId);
71
+ span.end(msToHrTime(event.time));
72
+ }
73
+ return (event) => {
74
+ if (forwardLogs && event.kind === "log") forwardLog(event);
75
+ if (forwardSpans && event.kind === "span") forwardSpan(event);
76
+ return event;
77
+ };
78
+ }
79
+ //#endregion
80
+ export { toOtel };
81
+
82
+ //# sourceMappingURL=otel.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.mjs","names":[],"sources":["../src/otel.ts"],"sourcesContent":["/**\n * loggily/otel — OpenTelemetry bridge\n *\n * Maps loggily LogEvent/SpanEvent to OpenTelemetry SDK calls.\n * Requires @opentelemetry/api as a peer dependency.\n *\n * Usage:\n * import * as otelApi from \"@opentelemetry/api\"\n * import { toOtel } from \"loggily/otel\"\n * const log = createLogger(\"myapp\", [toOtel({ api: otelApi }), console])\n */\n\nimport type { Event, LogEvent, SpanEvent, Stage } from \"./pipeline.js\"\n\n// OpenTelemetry API types (peer dep — imported at runtime)\ninterface OtelApi {\n logs?: {\n getLoggerProvider(): {\n getLogger(name: string): {\n emit(record: {\n severityNumber: number\n severityText: string\n body: string\n attributes: Record<string, unknown>\n timestamp: number[]\n }): void\n }\n }\n }\n trace?: {\n getTracerProvider(): {\n getTracer(name: string): {\n startSpan(\n name: string,\n options?: {\n startTime: number[]\n attributes: Record<string, unknown>\n },\n ): {\n end(endTime?: number[]): void\n setAttribute(key: string, value: unknown): void\n setStatus(status: { code: number; message?: string }): void\n }\n }\n }\n }\n}\n\nconst SEVERITY_MAP: Record<string, { number: number; text: string }> = {\n trace: { number: 1, text: \"TRACE\" },\n debug: { number: 5, text: \"DEBUG\" },\n info: { number: 9, text: \"INFO\" },\n warn: { number: 13, text: \"WARN\" },\n error: { number: 17, text: \"ERROR\" },\n}\n\nfunction msToHrTime(ms: number): [number, number] {\n const seconds = Math.floor(ms / 1000)\n const nanos = (ms % 1000) * 1_000_000\n return [seconds, nanos]\n}\n\nexport interface OtelBridgeOptions {\n /** The @opentelemetry/api module. Pass the import to avoid require() in ESM. */\n api?: OtelApi\n /** Logger name for OTLP LogRecords (default: \"loggily\") */\n loggerName?: string\n /** Tracer name for OTLP Spans (default: \"loggily\") */\n tracerName?: string\n /** Whether to forward log events (default: true) */\n logs?: boolean\n /** Whether to forward span events (default: true) */\n spans?: boolean\n}\n\n/**\n * Create a Stage that forwards loggily events to OpenTelemetry.\n *\n * Pass the @opentelemetry/api module via options.api:\n *\n * ```ts\n * import * as otelApi from \"@opentelemetry/api\"\n * const log = createLogger(\"myapp\", [toOtel({ api: otelApi }), console])\n * ```\n *\n * Events pass through unchanged (the stage is transparent).\n */\nexport function toOtel(options: OtelBridgeOptions = {}): Stage {\n const {\n api: otelApi = null,\n loggerName = \"loggily\",\n tracerName = \"loggily\",\n logs: forwardLogs = true,\n spans: forwardSpans = true,\n } = options\n\n function forwardLog(event: LogEvent): void {\n const logsApi = otelApi?.logs\n if (!logsApi) return\n\n const severity = SEVERITY_MAP[event.level] ?? SEVERITY_MAP.info!\n const logger = logsApi.getLoggerProvider().getLogger(loggerName)\n logger.emit({\n severityNumber: severity.number,\n severityText: severity.text,\n body: event.message,\n attributes: {\n \"loggily.namespace\": event.namespace,\n ...event.props,\n },\n timestamp: msToHrTime(event.time),\n })\n }\n\n function forwardSpan(event: SpanEvent): void {\n const traceApi = otelApi?.trace\n if (!traceApi) return\n\n const tracer = traceApi.getTracerProvider().getTracer(tracerName)\n const startTime = msToHrTime(event.time - event.duration)\n const span = tracer.startSpan(event.name, {\n startTime,\n attributes: {\n \"loggily.namespace\": event.namespace,\n \"loggily.span_id\": event.spanId,\n \"loggily.trace_id\": event.traceId,\n ...event.props,\n },\n })\n if (event.parentId) {\n span.setAttribute(\"loggily.parent_id\", event.parentId)\n }\n span.end(msToHrTime(event.time))\n }\n\n return (event: Event): Event => {\n if (forwardLogs && event.kind === \"log\") forwardLog(event)\n if (forwardSpans && event.kind === \"span\") forwardSpan(event)\n return event // transparent — pass through\n }\n}\n"],"mappings":";AAgDA,MAAM,eAAiE;CACrE,OAAO;EAAE,QAAQ;EAAG,MAAM;EAAS;CACnC,OAAO;EAAE,QAAQ;EAAG,MAAM;EAAS;CACnC,MAAM;EAAE,QAAQ;EAAG,MAAM;EAAQ;CACjC,MAAM;EAAE,QAAQ;EAAI,MAAM;EAAQ;CAClC,OAAO;EAAE,QAAQ;EAAI,MAAM;EAAS;CACrC;AAED,SAAS,WAAW,IAA8B;AAGhD,QAAO,CAFS,KAAK,MAAM,KAAK,IAAK,EACtB,KAAK,MAAQ,IACL;;;;;;;;;;;;;;AA4BzB,SAAgB,OAAO,UAA6B,EAAE,EAAS;CAC7D,MAAM,EACJ,KAAK,UAAU,MACf,aAAa,WACb,aAAa,WACb,MAAM,cAAc,MACpB,OAAO,eAAe,SACpB;CAEJ,SAAS,WAAW,OAAuB;EACzC,MAAM,UAAU,SAAS;AACzB,MAAI,CAAC,QAAS;EAEd,MAAM,WAAW,aAAa,MAAM,UAAU,aAAa;AAC5C,UAAQ,mBAAmB,CAAC,UAAU,WAAW,CACzD,KAAK;GACV,gBAAgB,SAAS;GACzB,cAAc,SAAS;GACvB,MAAM,MAAM;GACZ,YAAY;IACV,qBAAqB,MAAM;IAC3B,GAAG,MAAM;IACV;GACD,WAAW,WAAW,MAAM,KAAK;GAClC,CAAC;;CAGJ,SAAS,YAAY,OAAwB;EAC3C,MAAM,WAAW,SAAS;AAC1B,MAAI,CAAC,SAAU;EAEf,MAAM,SAAS,SAAS,mBAAmB,CAAC,UAAU,WAAW;EACjE,MAAM,YAAY,WAAW,MAAM,OAAO,MAAM,SAAS;EACzD,MAAM,OAAO,OAAO,UAAU,MAAM,MAAM;GACxC;GACA,YAAY;IACV,qBAAqB,MAAM;IAC3B,mBAAmB,MAAM;IACzB,oBAAoB,MAAM;IAC1B,GAAG,MAAM;IACV;GACF,CAAC;AACF,MAAI,MAAM,SACR,MAAK,aAAa,qBAAqB,MAAM,SAAS;AAExD,OAAK,IAAI,WAAW,MAAM,KAAK,CAAC;;AAGlC,SAAQ,UAAwB;AAC9B,MAAI,eAAe,MAAM,SAAS,MAAO,YAAW,MAAM;AAC1D,MAAI,gBAAgB,MAAM,SAAS,OAAQ,aAAY,MAAM;AAC7D,SAAO"}
@@ -0,0 +1,73 @@
1
+ //#region src/pipeline.d.ts
2
+ type OutputLogLevel = "trace" | "debug" | "info" | "warn" | "error";
3
+ type LogLevel = OutputLogLevel | "silent";
4
+ type LogFormat = "console" | "json";
5
+ type LogEvent = {
6
+ kind: "log";
7
+ time: number;
8
+ namespace: string;
9
+ level: OutputLogLevel;
10
+ message: string;
11
+ props?: Record<string, unknown>;
12
+ };
13
+ type SpanEvent = {
14
+ kind: "span";
15
+ time: number;
16
+ namespace: string;
17
+ name: string;
18
+ duration: number;
19
+ props?: Record<string, unknown>;
20
+ spanId: string;
21
+ traceId: string;
22
+ parentId: string | null;
23
+ };
24
+ type Event = LogEvent | SpanEvent;
25
+ type Stage = (event: Event) => Event | null | void;
26
+ declare const LOG_LEVEL_PRIORITY: Record<LogLevel, number>;
27
+ /** Serialize Error.cause chains up to a max depth */
28
+ declare function serializeCause(cause: unknown, maxDepth?: number): unknown;
29
+ declare function safeStringify(value: unknown): string;
30
+ type NsFilter = (namespace: string) => boolean;
31
+ interface Pipeline {
32
+ dispatch: (event: Event) => void;
33
+ level: LogLevel;
34
+ dispose: () => void;
35
+ }
36
+ /** A writable sink — any object with a write method. Receives raw Event objects by default. */
37
+ interface Writable {
38
+ write: (data: any) => any;
39
+ /** Set to false to receive formatted strings instead of raw Event objects (default: true) */
40
+ objectMode?: boolean;
41
+ }
42
+ /** Config keys that set scope for subsequent siblings */
43
+ interface ConfigObject {
44
+ level?: LogLevel;
45
+ ns?: string | string[];
46
+ format?: LogFormat;
47
+ spans?: boolean;
48
+ /** Enable per-logger metrics collection. Creates a MetricsCollector accessible via `log.metrics`. */
49
+ metrics?: boolean;
50
+ /** ID format for trace/span IDs: "simple" (default) or "w3c" (W3C Trace Context) */
51
+ idFormat?: "simple" | "w3c";
52
+ /** Head-based sampling rate for new traces: 0.0 (none) to 1.0 (all, default) */
53
+ sampleRate?: number;
54
+ }
55
+ /** File output descriptor */
56
+ interface FileDescriptor extends ConfigObject {
57
+ file: string;
58
+ }
59
+ /** OTEL output descriptor (Phase 4) */
60
+ interface OtelDescriptor extends ConfigObject {
61
+ otel: Record<string, unknown>;
62
+ }
63
+ /** A single element in a createLogger config array */
64
+ type ConfigElement = ConfigObject | FileDescriptor | OtelDescriptor | Console | "console" | "stderr" | Stage | Writable | ConfigElement[];
65
+ interface ScopeConfig {
66
+ level: LogLevel;
67
+ ns: NsFilter | null;
68
+ format: LogFormat;
69
+ }
70
+ declare function buildPipeline(elements: ConfigElement[], parentConfig?: Partial<ScopeConfig>): Pipeline;
71
+ //#endregion
72
+ export { LOG_LEVEL_PRIORITY as a, LogLevel as c, SpanEvent as d, Stage as f, serializeCause as g, safeStringify as h, FileDescriptor as i, OutputLogLevel as l, buildPipeline as m, ConfigObject as n, LogEvent as o, Writable as p, Event as r, LogFormat as s, ConfigElement as t, Pipeline as u };
73
+ //# sourceMappingURL=pipeline-Cl9-wCmt.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline-Cl9-wCmt.d.mts","names":[],"sources":["../src/pipeline.ts"],"mappings":";KAOY,cAAA;AAAA,KACA,QAAA,GAAW,cAAA;AAAA,KACX,SAAA;AAAA,KAEA,QAAA;EACV,IAAA;EACA,IAAA;EACA,SAAA;EACA,KAAA,EAAO,cAAA;EACP,OAAA;EACA,KAAA,GAAQ,MAAA;AAAA;AAAA,KAGE,SAAA;EACV,IAAA;EACA,IAAA;EACA,SAAA;EACA,IAAA;EACA,QAAA;EACA,KAAA,GAAQ,MAAA;EACR,MAAA;EACA,OAAA;EACA,QAAA;AAAA;AAAA,KAGU,KAAA,GAAQ,QAAA,GAAW,SAAA;AAAA,KACnB,KAAA,IAAS,KAAA,EAAO,KAAA,KAAU,KAAA;AAAA,cAIzB,kBAAA,EAAoB,MAAA,CAAO,QAAA;;iBA4BxB,cAAA,CAAe,KAAA,WAAgB,QAAA;AAAA,iBAkB/B,aAAA,CAAc,KAAA;AAAA,KAoFlB,QAAA,IAAY,SAAA;AAAA,UA2GP,QAAA;EACf,QAAA,GAAW,KAAA,EAAO,KAAA;EAClB,KAAA,EAAO,QAAA;EACP,OAAA;AAAA;;UAqCe,QAAA;EAEf,KAAA,GAAQ,IAAA;EA5R8B;EA8RtC,UAAA;AAAA;;UAIe,YAAA;EACf,KAAA,GAAQ,QAAA;EACR,EAAA;EACA,MAAA,GAAS,SAAA;EACT,KAAA;EArSyC;EAuSzC,OAAA;EA5RD;EA8RC,QAAA;EArS+B;EAuS/B,UAAA;AAAA;;UAIe,cAAA,SAAuB,YAAA;EACtC,IAAA;AAAA;AA9PF;AAAA,UAkQiB,cAAA,SAAuB,YAAA;EACtC,IAAA,EAAM,MAAA;AAAA;;KAII,aAAA,GACR,YAAA,GACA,cAAA,GACA,cAAA,GACA,OAAA,0BAGA,KAAA,GACA,QAAA,GACA,aAAA;AAAA,UAIM,WAAA;EACR,KAAA,EAAO,QAAA;EACP,EAAA,EAAI,QAAA;EACJ,MAAA,EAAQ,SAAA;AAAA;AAAA,iBAGM,aAAA,CAAc,QAAA,EAAU,aAAA,IAAiB,YAAA,GAAe,OAAA,CAAQ,WAAA,IAAe,QAAA"}
package/dist/worker.d.mts CHANGED
@@ -1,4 +1,5 @@
1
- import { i as Logger } from "./core-DAFH-huv.mjs";
1
+ import { t as ConditionalLogger } from "./core-Dm2PQUoS.mjs";
2
+ import { d as SpanEvent, f as Stage, o as LogEvent, r as Event } from "./pipeline-Cl9-wCmt.mjs";
2
3
 
3
4
  //#region src/worker.d.ts
4
5
  /** Message sent from worker to main thread for console output */
@@ -9,59 +10,29 @@ interface WorkerConsoleMessage {
9
10
  args: unknown[];
10
11
  timestamp: number;
11
12
  }
12
- /** Message sent from worker to main thread for structured log output */
13
- interface WorkerLogMessage {
14
- type: "log";
15
- level: "trace" | "debug" | "info" | "warn" | "error";
16
- namespace: string;
17
- message: string;
18
- data?: Record<string, unknown>;
19
- timestamp: number;
20
- }
21
- /** Message sent from worker to main thread for span events */
22
- interface WorkerSpanMessage {
23
- type: "span";
24
- event: "start" | "end";
25
- namespace: string;
26
- spanId: string;
27
- traceId: string;
28
- parentId: string | null;
29
- startTime: number;
30
- endTime?: number;
31
- duration?: number;
32
- props: Record<string, unknown>;
33
- spanData: Record<string, unknown>;
34
- timestamp: number;
35
- }
36
- /** Union type for all worker messages */
37
- type WorkerMessage = WorkerConsoleMessage | WorkerLogMessage | WorkerSpanMessage;
13
+ /** Type guard for LogEvent (structured log from worker) */
14
+ declare function isWorkerLogEvent(msg: unknown): msg is LogEvent;
15
+ /** Type guard for SpanEvent (span from worker) */
16
+ declare function isWorkerSpanEvent(msg: unknown): msg is SpanEvent;
17
+ /** Type guard for any pipeline Event (log or span) */
18
+ declare function isWorkerEvent(msg: unknown): msg is Event;
38
19
  /** Type guard for WorkerConsoleMessage */
39
20
  declare function isWorkerConsoleMessage(msg: unknown): msg is WorkerConsoleMessage;
40
- /** Type guard for WorkerLogMessage */
41
- declare function isWorkerLogMessage(msg: unknown): msg is WorkerLogMessage;
42
- /** Type guard for WorkerSpanMessage */
43
- declare function isWorkerSpanMessage(msg: unknown): msg is WorkerSpanMessage;
44
- /** Type guard for any worker message */
45
- declare function isWorkerMessage(msg: unknown): msg is WorkerMessage;
21
+ /** Type guard for any worker message (console or pipeline event) */
22
+ declare function isWorkerMessage(msg: unknown): msg is WorkerConsoleMessage | Event;
46
23
  type PostMessageFn = (message: WorkerConsoleMessage) => void;
47
24
  /**
48
- * Forward console.* calls from worker to main thread.
49
- *
50
- * Monkey-patches console methods to send messages via postMessage.
51
- * Call this at the start of your worker script.
25
+ * Forward console.* calls from worker to main thread via postMessage.
52
26
  *
53
- * @param postMessage - The worker's postMessage function
54
- * @param namespace - Optional namespace for log messages (e.g., "km:worker:parse")
27
+ * Monkey-patches console methods. postMessage uses structuredClone,
28
+ * which handles most values natively. If cloning fails (functions,
29
+ * symbols), falls back to original console.
55
30
  *
56
31
  * @example
57
32
  * ```typescript
58
- * // At top of worker file:
59
33
  * import { forwardConsole } from "loggily/worker"
60
34
  * forwardConsole(postMessage, "km:worker:parse")
61
- *
62
- * // Now all console.* calls are forwarded:
63
35
  * console.log("processing", { file: "test.md" })
64
- * console.error(new Error("failed"))
65
36
  * ```
66
37
  */
67
38
  declare function forwardConsole(postMessage: PostMessageFn, namespace?: string): void;
@@ -70,25 +41,24 @@ declare function forwardConsole(postMessage: PostMessageFn, namespace?: string):
70
41
  * Call this if you need to disable console forwarding.
71
42
  */
72
43
  declare function restoreConsole(): void;
73
- type PostMessageAnyFn = (message: WorkerMessage) => void;
74
- /** Reset worker ID counters (for testing) */
75
- declare function resetWorkerIds(): void;
76
- interface WorkerLoggerOptions {
77
- /** Parent span ID for nested spans */
78
- parentSpanId?: string | null;
79
- /** Trace ID for distributed tracing */
80
- traceId?: string | null;
81
- }
82
44
  /**
83
- * Create a logger instance for use in a worker thread.
45
+ * Create a pipeline stage that forwards events via postMessage.
46
+ *
47
+ * Events are plain JSON objects that survive structuredClone natively.
48
+ * The stage consumes events (returns null) so nothing is output locally.
49
+ * The main thread uses handleWorkerEvents() or createWorkerLogHandler()
50
+ * to dispatch them through a local logger pipeline.
51
+ */
52
+ declare function workerTransportStage(postMessage: (msg: unknown) => void): Stage;
53
+ /**
54
+ * Create a logger for use in a worker thread.
84
55
  *
85
- * All log calls and span events are forwarded to the main thread via postMessage.
86
- * The main thread should use createWorkerLogHandler to process these messages.
56
+ * All log and span events are forwarded to the main thread via postMessage.
57
+ * The main thread should use createWorkerLogHandler() to process these messages.
87
58
  *
88
59
  * @param postMessage - The worker's postMessage function
89
60
  * @param namespace - Logger namespace (e.g., "km:worker:parse")
90
61
  * @param props - Optional initial props
91
- * @param options - Optional configuration
92
62
  *
93
63
  * @example
94
64
  * ```typescript
@@ -96,22 +66,53 @@ interface WorkerLoggerOptions {
96
66
  *
97
67
  * const log = createWorkerLogger(postMessage, "km:worker:parse")
98
68
  *
99
- * log.info("starting parse", { file: "test.md" })
69
+ * log.info?.("starting parse", { file: "test.md" })
100
70
  *
101
71
  * {
102
- * using span = log.span("process")
103
- * span.info("processing...")
104
- * span.spanData.lineCount = 100
72
+ * using span = log.span?.("process")
73
+ * span.info?.("processing...")
74
+ * span.spanData.count = 100
105
75
  * }
106
76
  * // Span end event automatically sent to main thread
107
77
  * ```
108
78
  */
109
- declare function createWorkerLogger(postMessage: PostMessageAnyFn, namespace: string, props?: Record<string, unknown>, options?: WorkerLoggerOptions): Logger;
79
+ declare function createWorkerLogger(postMessage: (msg: unknown) => void, namespace: string, props?: Record<string, unknown>): ConditionalLogger;
80
+ /**
81
+ * Create a handler that dispatches worker events to a target logger.
82
+ *
83
+ * Use this when you have a specific logger to dispatch through.
84
+ *
85
+ * @param target - Logger or object with dispatch method
86
+ * @returns Handler function to call with worker messages
87
+ */
88
+ declare function handleWorkerEvents(target: ConditionalLogger | {
89
+ dispatch(event: Event): void;
90
+ }): (msg: unknown) => void;
91
+ /**
92
+ * Create a zero-config handler for worker logger messages.
93
+ *
94
+ * Automatically creates loggers per-namespace. For console messages,
95
+ * formats args and dispatches through a logger.
96
+ *
97
+ * @returns Handler function to call with any worker message
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * import { createWorkerLogHandler } from "loggily/worker"
102
+ *
103
+ * const handleLog = createWorkerLogHandler()
104
+ * worker.onmessage = (e) => handleLog(e.data)
105
+ * ```
106
+ */
107
+ declare function createWorkerLogHandler(): (message: unknown) => void;
110
108
  interface WorkerConsoleHandlerOptions {
111
109
  /** Default namespace if message doesn't include one */
112
110
  defaultNamespace?: string;
113
111
  /** Custom logger to use (defaults to creating one with the namespace) */
114
- logger?: Logger;
112
+ logger?: {
113
+ name: string;
114
+ dispatch(event: Event): void;
115
+ };
115
116
  }
116
117
  /**
117
118
  * Create a handler for worker console messages.
@@ -132,42 +133,11 @@ interface WorkerConsoleHandlerOptions {
132
133
  * worker.onmessage = (e) => {
133
134
  * if (e.data.type === "console") {
134
135
  * handleConsole(e.data)
135
- * } else {
136
- * // Handle other message types
137
136
  * }
138
137
  * }
139
138
  * ```
140
139
  */
141
140
  declare function createWorkerConsoleHandler(options?: WorkerConsoleHandlerOptions): (message: WorkerConsoleMessage) => void;
142
- interface WorkerLogHandlerOptions {
143
- /** @deprecated Span output is now controlled by TRACE env var */
144
- enableSpans?: boolean;
145
- }
146
- /**
147
- * Create a handler for worker logger messages (logs and spans).
148
- *
149
- * Use this on the main thread to receive and output messages from workers
150
- * that use createWorkerLogger.
151
- *
152
- * @param options - Handler options
153
- * @returns Handler function to call with worker messages
154
- *
155
- * @example
156
- * ```typescript
157
- * import { createWorkerLogHandler, isWorkerMessage } from "loggily/worker"
158
- *
159
- * const handleLog = createWorkerLogHandler()
160
- *
161
- * worker.onmessage = (e) => {
162
- * if (isWorkerMessage(e.data)) {
163
- * handleLog(e.data)
164
- * } else {
165
- * // Handle other message types
166
- * }
167
- * }
168
- * ```
169
- */
170
- declare function createWorkerLogHandler(options?: WorkerLogHandlerOptions): (message: WorkerMessage) => void;
171
141
  //#endregion
172
- export { WorkerConsoleHandlerOptions, WorkerConsoleMessage, WorkerLogHandlerOptions, WorkerLogMessage, WorkerMessage, WorkerSpanMessage, createWorkerConsoleHandler, createWorkerLogHandler, createWorkerLogger, forwardConsole, isWorkerConsoleMessage, isWorkerLogMessage, isWorkerMessage, isWorkerSpanMessage, resetWorkerIds, restoreConsole };
142
+ export { WorkerConsoleHandlerOptions, WorkerConsoleMessage, createWorkerConsoleHandler, createWorkerLogHandler, createWorkerLogger, forwardConsole, handleWorkerEvents, isWorkerConsoleMessage, isWorkerEvent, isWorkerLogEvent, isWorkerMessage, isWorkerSpanEvent, restoreConsole, workerTransportStage };
173
143
  //# sourceMappingURL=worker.d.mts.map