loggily 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,734 +1,2 @@
1
- import { closeSync, openSync, writeSync } from "node:fs";
2
- //#region src/colors.ts
3
- /**
4
- * Vendored ANSI color functions — replaces picocolors dependency.
5
- * Supports NO_COLOR, FORCE_COLOR, and TTY detection.
6
- */
7
- const _process$1 = typeof process !== "undefined" ? process : void 0;
8
- const enabled = _process$1?.env?.["FORCE_COLOR"] !== void 0 && _process$1?.env?.["FORCE_COLOR"] !== "0" ? true : _process$1?.env?.["NO_COLOR"] !== void 0 ? false : _process$1?.stdout?.isTTY ?? false;
9
- function wrap(open, close) {
10
- if (!enabled) return (str) => str;
11
- return (str) => open + str + close;
12
- }
13
- const colors = {
14
- dim: wrap("\x1B[2m", "\x1B[22m"),
15
- blue: wrap("\x1B[34m", "\x1B[39m"),
16
- yellow: wrap("\x1B[33m", "\x1B[39m"),
17
- red: wrap("\x1B[31m", "\x1B[39m"),
18
- magenta: wrap("\x1B[35m", "\x1B[39m"),
19
- cyan: wrap("\x1B[36m", "\x1B[39m")
20
- };
21
- //#endregion
22
- //#region src/tracing.ts
23
- let currentIdFormat = "simple";
24
- /**
25
- * Set the ID format for new spans and traces.
26
- * - "simple": sp_1, sp_2, tr_1, tr_2 (default, lightweight)
27
- * - "w3c": 32-char hex trace ID, 16-char hex span ID (W3C Trace Context compatible)
28
- */
29
- function setIdFormat(format) {
30
- currentIdFormat = format;
31
- }
32
- /** Get the current ID format */
33
- function getIdFormat() {
34
- return currentIdFormat;
35
- }
36
- let simpleSpanCounter = 0;
37
- let simpleTraceCounter = 0;
38
- /** Generate a hex string of the given byte length using crypto.randomUUID */
39
- function randomHex(bytes) {
40
- return crypto.randomUUID().replace(/-/g, "").slice(0, bytes * 2);
41
- }
42
- /** Generate a span ID according to the current format */
43
- function generateSpanId() {
44
- if (currentIdFormat === "w3c") return randomHex(8);
45
- return `sp_${(++simpleSpanCounter).toString(36)}`;
46
- }
47
- /** Generate a trace ID according to the current format */
48
- function generateTraceId() {
49
- if (currentIdFormat === "w3c") return randomHex(16);
50
- return `tr_${(++simpleTraceCounter).toString(36)}`;
51
- }
52
- /** Reset ID counters (for testing) */
53
- function resetIdCounters() {
54
- simpleSpanCounter = 0;
55
- simpleTraceCounter = 0;
56
- }
57
- /**
58
- * Format a W3C traceparent header from span data.
59
- *
60
- * Format: `{version}-{trace-id}-{span-id}-{trace-flags}`
61
- * - version: "00" (current W3C spec version)
62
- * - trace-id: 32 hex chars (128 bits)
63
- * - span-id: 16 hex chars (64 bits)
64
- * - trace-flags: "01" (sampled) or "00" (not sampled)
65
- *
66
- * Works with both simple and W3C ID formats. Simple IDs are zero-padded to spec length.
67
- *
68
- * @param spanData - Span data with id and traceId
69
- * @param options - Optional settings (sampled flag). Defaults to sampled=true.
70
- * @returns W3C traceparent header string
71
- *
72
- * @example
73
- * ```typescript
74
- * const span = log.span("http-request")
75
- * const header = traceparent(span.spanData)
76
- * // → "00-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6-1a2b3c4d5e6f7a8b-01"
77
- * fetch(url, { headers: { traceparent: header } })
78
- * ```
79
- */
80
- function traceparent(spanData, options) {
81
- return `00-${padHex(spanData.traceId, 32)}-${padHex(spanData.id, 16)}-${options?.sampled ?? true ? "01" : "00"}`;
82
- }
83
- /** Pad or hash an ID to the specified hex length */
84
- function padHex(id, length) {
85
- if (id.length === length && /^[0-9a-f]+$/.test(id)) return id;
86
- let hex = "";
87
- for (let i = 0; i < id.length; i++) hex += id.charCodeAt(i).toString(16).padStart(2, "0");
88
- return hex.padStart(length, "0").slice(-length);
89
- }
90
- let sampleRate = 1;
91
- /**
92
- * Set the head-based sampling rate for new traces.
93
- * Applied at trace creation — all spans within a sampled trace are kept.
94
- *
95
- * @param rate - Sampling rate from 0.0 (sample nothing) to 1.0 (sample everything, default)
96
- */
97
- function setSampleRate(rate) {
98
- if (rate < 0 || rate > 1) throw new Error(`Sample rate must be between 0.0 and 1.0, got ${rate}`);
99
- sampleRate = rate;
100
- }
101
- /** Get the current sampling rate */
102
- function getSampleRate() {
103
- return sampleRate;
104
- }
105
- /**
106
- * Determine whether a new trace should be sampled.
107
- * Called at trace creation time (head-based sampling).
108
- */
109
- function shouldSample() {
110
- if (sampleRate >= 1) return true;
111
- if (sampleRate <= 0) return false;
112
- return Math.random() < sampleRate;
113
- }
114
- //#endregion
115
- //#region src/core.ts
116
- /**
117
- * loggily - Structured logging with spans
118
- *
119
- * Logger-first architecture: Span = Logger + Duration
120
- *
121
- * @example
122
- * const log = createLogger('myapp')
123
- *
124
- * // Simple logging
125
- * log.info('starting')
126
- *
127
- * // Lazy messages (function not called when level is disabled)
128
- * log.debug?.(() => `expensive: ${computeState()}`)
129
- *
130
- * // Child loggers with context fields
131
- * const reqLog = log.child({ requestId: 'abc' })
132
- * reqLog.info('handling request') // includes requestId in every message
133
- *
134
- * // With timing (span)
135
- * {
136
- * using task = log.span('import', { file: 'data.csv' })
137
- * task.info('importing')
138
- * task.spanData.count = 42 // Set span attributes
139
- * // Auto-disposal on block exit → SPAN myapp:import (15ms)
140
- * }
141
- */
142
- /**
143
- * Ambient span recorder — auto-records when TRACE is active.
144
- * Set by metrics.ts on import; can be replaced for testing.
145
- * @internal
146
- */
147
- let _ambientRecorder = null;
148
- function _setAmbientRecorder(recorder) {
149
- _ambientRecorder = recorder;
150
- }
151
- /** Cached process reference — undefined in browser/edge runtimes */
152
- const _process = typeof process !== "undefined" ? process : void 0;
153
- /** Read an environment variable, returning undefined in non-Node runtimes */
154
- function getEnv(key) {
155
- return _process?.env?.[key];
156
- }
157
- /** Write to stderr with console.error fallback for non-Node runtimes */
158
- function writeStderr(text) {
159
- if (_process?.stderr?.write) _process.stderr.write(text + "\n");
160
- else console.error(text);
161
- }
162
- const writers = [];
163
- /** Add a writer that receives all formatted log output. Returns unsubscribe. */
164
- function addWriter(writer) {
165
- writers.push(writer);
166
- return () => {
167
- const idx = writers.indexOf(writer);
168
- if (idx !== -1) writers.splice(idx, 1);
169
- };
170
- }
171
- let suppressConsole = false;
172
- /** Suppress console output from the logger (writers still receive output). */
173
- function setSuppressConsole(value) {
174
- suppressConsole = value;
175
- }
176
- let outputMode = "console";
177
- /** Set output mode for log messages (not spans — spans always use stderr). */
178
- function setOutputMode(mode) {
179
- outputMode = mode;
180
- }
181
- /** Get current output mode */
182
- function getOutputMode() {
183
- return outputMode;
184
- }
185
- const LOG_LEVEL_PRIORITY = {
186
- trace: 0,
187
- debug: 1,
188
- info: 2,
189
- warn: 3,
190
- error: 4,
191
- silent: 5
192
- };
193
- const envLogLevel = getEnv("LOG_LEVEL")?.toLowerCase();
194
- let currentLogLevel = envLogLevel === "trace" || envLogLevel === "debug" || envLogLevel === "info" || envLogLevel === "warn" || envLogLevel === "error" || envLogLevel === "silent" ? envLogLevel : "info";
195
- const traceEnv = getEnv("TRACE");
196
- let spansEnabled = traceEnv === "1" || traceEnv === "true";
197
- let traceFilter = null;
198
- if (traceEnv && traceEnv !== "1" && traceEnv !== "true") {
199
- traceFilter = new Set(traceEnv.split(",").map((s) => s.trim()));
200
- spansEnabled = true;
201
- }
202
- /** Parse a comma-separated namespace filter into include/exclude sets */
203
- function parseNamespaceFilter(input) {
204
- const includeList = [];
205
- const excludeList = [];
206
- for (const part of input) if (part.startsWith("-")) excludeList.push(part.slice(1));
207
- else includeList.push(part);
208
- return {
209
- includes: includeList.length > 0 ? new Set(includeList) : null,
210
- excludes: excludeList.length > 0 ? new Set(excludeList) : null
211
- };
212
- }
213
- const debugEnv = getEnv("DEBUG");
214
- let debugIncludes = null;
215
- let debugExcludes = null;
216
- if (debugEnv) {
217
- const parsed = parseNamespaceFilter(debugEnv.split(",").map((s) => s.trim()));
218
- debugIncludes = parsed.includes;
219
- if (debugIncludes && [...debugIncludes].some((p) => p === "*" || p === "1" || p === "true")) debugIncludes = new Set(["*"]);
220
- debugExcludes = parsed.excludes;
221
- if (LOG_LEVEL_PRIORITY[currentLogLevel] > LOG_LEVEL_PRIORITY.debug) currentLogLevel = "debug";
222
- }
223
- /** Set minimum log level */
224
- function setLogLevel(level) {
225
- currentLogLevel = level;
226
- }
227
- /** Get current log level */
228
- function getLogLevel() {
229
- return currentLogLevel;
230
- }
231
- /** Enable span output */
232
- function enableSpans() {
233
- spansEnabled = true;
234
- }
235
- /** Disable span output */
236
- function disableSpans() {
237
- spansEnabled = false;
238
- }
239
- /** Check if spans are enabled */
240
- function spansAreEnabled() {
241
- return spansEnabled;
242
- }
243
- /**
244
- * Set trace filter for namespace-based span output control.
245
- * Only spans matching these namespace prefixes will be output.
246
- * @param namespaces - Array of namespace prefixes, or null to disable filtering
247
- */
248
- function setTraceFilter(namespaces) {
249
- if (namespaces === null || namespaces.length === 0) traceFilter = null;
250
- else {
251
- traceFilter = new Set(namespaces);
252
- spansEnabled = true;
253
- }
254
- }
255
- /** Get current trace filter (null means no filtering) */
256
- function getTraceFilter() {
257
- return traceFilter ? [...traceFilter] : null;
258
- }
259
- /**
260
- * Set debug namespace filter (like the `debug` npm package).
261
- * When set, only loggers matching these namespace prefixes produce output.
262
- * Supports negative patterns with `-` prefix (e.g., ["-km:noisy"]).
263
- * Also ensures log level is at least `debug`.
264
- * @param namespaces - Array of namespace prefixes (prefix with `-` to exclude), or null to disable
265
- */
266
- function setDebugFilter(namespaces) {
267
- if (namespaces === null || namespaces.length === 0) {
268
- debugIncludes = null;
269
- debugExcludes = null;
270
- } else {
271
- const parsed = parseNamespaceFilter(namespaces);
272
- debugIncludes = parsed.includes;
273
- debugExcludes = parsed.excludes;
274
- if (LOG_LEVEL_PRIORITY[currentLogLevel] > LOG_LEVEL_PRIORITY.debug) currentLogLevel = "debug";
275
- }
276
- }
277
- /** Get current debug namespace filter (null means no filtering) */
278
- function getDebugFilter() {
279
- if (!debugIncludes && !debugExcludes) return null;
280
- const result = [];
281
- if (debugIncludes) result.push(...debugIncludes);
282
- if (debugExcludes) result.push(...[...debugExcludes].map((e) => `-${e}`));
283
- return result;
284
- }
285
- const envLogFormat = getEnv("LOG_FORMAT")?.toLowerCase();
286
- let currentLogFormat = envLogFormat === "json" ? "json" : envLogFormat === "console" ? "console" : "console";
287
- /** Set log output format */
288
- function setLogFormat(format) {
289
- currentLogFormat = format;
290
- }
291
- /** Get current log output format */
292
- function getLogFormat() {
293
- return currentLogFormat;
294
- }
295
- /** Determine whether to use JSON formatting for the current call */
296
- function useJsonFormat() {
297
- return currentLogFormat === "json" || getEnv("NODE_ENV") === "production" || getEnv("TRACE_FORMAT") === "json";
298
- }
299
- function resetIds() {
300
- resetIdCounters();
301
- }
302
- /** Hook to get current span context tags (trace_id, span_id) for auto-tagging logs */
303
- let _getContextTags = null;
304
- /** Hook to get parent span info from async context */
305
- let _getContextParent = null;
306
- /** Hook to enter a span context (sets AsyncLocalStorage for the current async scope) */
307
- let _enterContext = null;
308
- /** Hook to exit a span context (restores previous context snapshot) */
309
- let _exitContext = null;
310
- /**
311
- * Register context propagation hooks (called by context.ts).
312
- * @internal
313
- */
314
- function _setContextHooks(hooks) {
315
- _getContextTags = hooks.getContextTags;
316
- _getContextParent = hooks.getContextParent;
317
- _enterContext = hooks.enterContext;
318
- _exitContext = hooks.exitContext;
319
- }
320
- /**
321
- * Clear context propagation hooks (called by disableContextPropagation).
322
- * @internal
323
- */
324
- function _clearContextHooks() {
325
- _getContextTags = null;
326
- _getContextParent = null;
327
- _enterContext = null;
328
- _exitContext = null;
329
- }
330
- function shouldLog(level) {
331
- return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[currentLogLevel];
332
- }
333
- function shouldTraceNamespace(namespace) {
334
- if (!spansEnabled) return false;
335
- if (!traceFilter) return true;
336
- return matchesNamespaceSet(namespace, traceFilter);
337
- }
338
- /**
339
- * Safe JSON.stringify that handles bigint, circular refs, symbols, and Error objects.
340
- * Prevents crashes from non-serializable values in log data.
341
- */
342
- function safeStringify(value) {
343
- const seen = /* @__PURE__ */ new WeakSet();
344
- return JSON.stringify(value, (_key, val) => {
345
- if (typeof val === "bigint") return val.toString();
346
- if (typeof val === "symbol") return val.toString();
347
- if (val instanceof Error) return {
348
- message: val.message,
349
- stack: val.stack,
350
- name: val.name
351
- };
352
- if (typeof val === "object" && val !== null) {
353
- if (seen.has(val)) return "[Circular]";
354
- seen.add(val);
355
- }
356
- return val;
357
- });
358
- }
359
- function formatConsole(namespace, level, message, data) {
360
- const time = colors.dim((/* @__PURE__ */ new Date()).toISOString().split("T")[1]?.split(".")[0] || "");
361
- let levelStr = "";
362
- switch (level) {
363
- case "trace":
364
- levelStr = colors.dim("TRACE");
365
- break;
366
- case "debug":
367
- levelStr = colors.dim("DEBUG");
368
- break;
369
- case "info":
370
- levelStr = colors.blue("INFO");
371
- break;
372
- case "warn":
373
- levelStr = colors.yellow("WARN");
374
- break;
375
- case "error":
376
- levelStr = colors.red("ERROR");
377
- break;
378
- case "span":
379
- levelStr = colors.magenta("SPAN");
380
- break;
381
- }
382
- const ns = colors.cyan(namespace);
383
- let output = `${time} ${levelStr} ${ns} ${message}`;
384
- if (data && Object.keys(data).length > 0) output += ` ${colors.dim(safeStringify(data))}`;
385
- return output;
386
- }
387
- function formatJSON(namespace, level, message, data) {
388
- return safeStringify({
389
- time: (/* @__PURE__ */ new Date()).toISOString(),
390
- level,
391
- name: namespace,
392
- msg: message,
393
- ...data
394
- });
395
- }
396
- function matchesNamespaceSet(namespace, set) {
397
- if (set.has("*")) return true;
398
- for (const filter of set) if (namespace === filter || namespace.startsWith(filter + ":")) return true;
399
- return false;
400
- }
401
- function shouldDebugNamespace(namespace) {
402
- if (!debugIncludes && !debugExcludes) return true;
403
- if (debugExcludes && matchesNamespaceSet(namespace, debugExcludes)) return false;
404
- if (debugIncludes) return matchesNamespaceSet(namespace, debugIncludes);
405
- return true;
406
- }
407
- /** Resolve a lazy message: if it's a function, call it; otherwise return the string */
408
- function resolveMessage(msg) {
409
- return typeof msg === "function" ? msg() : msg;
410
- }
411
- function writeLog(namespace, level, message, data) {
412
- if (!shouldLog(level)) return;
413
- if (!shouldDebugNamespace(namespace)) return;
414
- const resolved = resolveMessage(message);
415
- const contextTags = _getContextTags?.();
416
- const mergedData = contextTags && Object.keys(contextTags).length > 0 ? {
417
- ...contextTags,
418
- ...data
419
- } : data;
420
- const formatted = useJsonFormat() ? formatJSON(namespace, level, resolved, mergedData) : formatConsole(namespace, level, resolved, mergedData);
421
- for (const w of writers) w(formatted, level);
422
- if (suppressConsole || outputMode === "writers-only") return;
423
- if (outputMode === "stderr") {
424
- writeStderr(formatted);
425
- return;
426
- }
427
- switch (level) {
428
- case "trace":
429
- case "debug":
430
- console.debug(formatted);
431
- break;
432
- case "info":
433
- console.info(formatted);
434
- break;
435
- case "warn":
436
- console.warn(formatted);
437
- break;
438
- case "error":
439
- console.error(formatted);
440
- break;
441
- }
442
- }
443
- function writeSpan(namespace, duration, attrs) {
444
- if (!shouldTraceNamespace(namespace)) return;
445
- if (!shouldDebugNamespace(namespace)) return;
446
- const message = `(${duration}ms)`;
447
- const formatted = useJsonFormat() ? formatJSON(namespace, "span", message, {
448
- duration,
449
- ...attrs
450
- }) : formatConsole(namespace, "span", message, {
451
- duration,
452
- ...attrs
453
- });
454
- for (const w of writers) w(formatted, "span");
455
- if (!suppressConsole) writeStderr(formatted);
456
- }
457
- /**
458
- * Create a proxy that exposes span metadata as readonly and custom attributes as writable.
459
- * Shared between core logger spans and worker logger spans.
460
- */
461
- function createSpanDataProxy(getFields, attrs) {
462
- const READONLY_KEYS = new Set([
463
- "id",
464
- "traceId",
465
- "parentId",
466
- "startTime",
467
- "endTime",
468
- "duration"
469
- ]);
470
- return new Proxy(attrs, {
471
- get(_target, prop) {
472
- if (READONLY_KEYS.has(prop)) return getFields()[prop];
473
- return attrs[prop];
474
- },
475
- set(_target, prop, value) {
476
- if (READONLY_KEYS.has(prop)) return false;
477
- attrs[prop] = value;
478
- return true;
479
- }
480
- });
481
- }
482
- function createLoggerImpl(name, props, spanMeta, parentSpanId, traceId, traceSampled = true) {
483
- const log = (level, msgOrError, data) => {
484
- if (msgOrError instanceof Error) {
485
- const err = msgOrError;
486
- writeLog(name, level, err.message, {
487
- ...props,
488
- ...data,
489
- error_type: err.name,
490
- error_stack: err.stack,
491
- error_code: err.code
492
- });
493
- } else writeLog(name, level, msgOrError, {
494
- ...props,
495
- ...data
496
- });
497
- };
498
- return {
499
- name,
500
- props: Object.freeze({ ...props }),
501
- get spanData() {
502
- if (!spanMeta) return null;
503
- return createSpanDataProxy(() => ({
504
- id: spanMeta.id,
505
- traceId: spanMeta.traceId,
506
- parentId: spanMeta.parentId,
507
- startTime: spanMeta.startTime,
508
- endTime: spanMeta.endTime,
509
- duration: spanMeta.endTime !== null ? spanMeta.endTime - spanMeta.startTime : Date.now() - spanMeta.startTime
510
- }), spanMeta.attrs);
511
- },
512
- trace: (msg, data) => log("trace", msg, data),
513
- debug: (msg, data) => log("debug", msg, data),
514
- info: (msg, data) => log("info", msg, data),
515
- warn: (msg, data) => log("warn", msg, data),
516
- error: (msgOrError, data) => log("error", msgOrError, data),
517
- logger(namespace, childProps) {
518
- return createLoggerImpl(namespace ? `${name}:${namespace}` : name, {
519
- ...props,
520
- ...childProps
521
- }, null, parentSpanId, traceId, traceSampled);
522
- },
523
- span(namespace, childProps) {
524
- const childName = namespace ? `${name}:${namespace}` : name;
525
- const resolvedChildProps = typeof childProps === "function" ? childProps() : childProps;
526
- const mergedProps = {
527
- ...props,
528
- ...resolvedChildProps
529
- };
530
- const newSpanId = generateSpanId();
531
- let resolvedParentId = parentSpanId;
532
- let resolvedTraceId = traceId;
533
- if (!resolvedParentId && _getContextParent) {
534
- const ctxParent = _getContextParent();
535
- if (ctxParent) {
536
- resolvedParentId = ctxParent.spanId;
537
- resolvedTraceId = resolvedTraceId || ctxParent.traceId;
538
- }
539
- }
540
- const isNewTrace = !resolvedTraceId;
541
- const finalTraceId = resolvedTraceId || generateTraceId();
542
- const sampled = isNewTrace ? shouldSample() : traceSampled;
543
- const newSpanData = {
544
- id: newSpanId,
545
- traceId: finalTraceId,
546
- parentId: resolvedParentId,
547
- startTime: Date.now(),
548
- endTime: null,
549
- duration: null,
550
- attrs: {}
551
- };
552
- const spanLogger = createLoggerImpl(childName, mergedProps, newSpanData, newSpanId, finalTraceId, sampled);
553
- _enterContext?.(newSpanId, finalTraceId, resolvedParentId);
554
- spanLogger[Symbol.dispose] = () => {
555
- if (newSpanData.endTime !== null) return;
556
- newSpanData.endTime = Date.now();
557
- newSpanData.duration = newSpanData.endTime - newSpanData.startTime;
558
- if (collectSpans) collectedSpans.push(createSpanDataProxy(() => ({
559
- id: newSpanData.id,
560
- traceId: newSpanData.traceId,
561
- parentId: newSpanData.parentId,
562
- startTime: newSpanData.startTime,
563
- endTime: newSpanData.endTime,
564
- duration: newSpanData.duration
565
- }), { ...newSpanData.attrs }));
566
- _exitContext?.(newSpanId);
567
- _ambientRecorder?.recordSpan({
568
- name: childName,
569
- durationMs: newSpanData.duration
570
- });
571
- if (sampled) writeSpan(childName, newSpanData.duration, {
572
- span_id: newSpanData.id,
573
- trace_id: newSpanData.traceId,
574
- parent_id: newSpanData.parentId,
575
- ...mergedProps,
576
- ...newSpanData.attrs
577
- });
578
- };
579
- return spanLogger;
580
- },
581
- child(context) {
582
- if (typeof context === "string") return this.logger(context);
583
- return createLoggerImpl(name, {
584
- ...props,
585
- ...context
586
- }, null, parentSpanId, traceId, traceSampled);
587
- },
588
- end() {
589
- if (spanMeta?.endTime === null) this[Symbol.dispose]?.();
590
- }
591
- };
592
- }
593
- /**
594
- * Create a plain logger for a component (internal use).
595
- * For application code, use createLogger() instead which returns undefined for disabled levels.
596
- */
597
- function createPlainLogger(name, props) {
598
- return createLoggerImpl(name, props || {}, null, null, null);
599
- }
600
- const collectedSpans = [];
601
- let collectSpans = false;
602
- /** Enable span collection for analysis */
603
- function startCollecting() {
604
- collectSpans = true;
605
- collectedSpans.length = 0;
606
- }
607
- /** Stop collecting and return collected spans */
608
- function stopCollecting() {
609
- collectSpans = false;
610
- return [...collectedSpans];
611
- }
612
- /** Get collected spans */
613
- function getCollectedSpans() {
614
- return [...collectedSpans];
615
- }
616
- /** Clear collected spans */
617
- function clearCollectedSpans() {
618
- collectedSpans.length = 0;
619
- }
620
- /**
621
- * Create a logger for a component.
622
- * Returns undefined for disabled levels - use with optional chaining for zero overhead.
623
- *
624
- * Log levels (most → least verbose): trace < debug < info < warn < error < silent
625
- * Default level: info (trace and debug disabled)
626
- *
627
- * @example
628
- * const log = createLogger('myapp')
629
- *
630
- * // All methods support ?. for zero-overhead when disabled
631
- * log.trace?.(`very verbose: ${expensiveDebug()}`) // Skipped at info level
632
- * log.debug?.(`debug: ${getState()}`) // Skipped at info level
633
- * log.info?.('starting') // Enabled at info level
634
- * log.warn?.('deprecated') // Enabled at info level
635
- * log.error?.('failed') // Enabled at info level
636
- *
637
- * // With -q flag or LOG_LEVEL=warn:
638
- * log.info?.('starting') // Now skipped - info < warn
639
- *
640
- * // With initial props
641
- * const log = createLogger('myapp', { version: '1.0' })
642
- *
643
- * // Create spans
644
- * {
645
- * using task = log.span('import', { file: 'data.csv' })
646
- * task.info?.('importing')
647
- * task.spanData.count = 42
648
- * }
649
- */
650
- function createLogger(name, props) {
651
- const baseLog = createPlainLogger(name, props);
652
- return new Proxy(baseLog, { get(target, prop) {
653
- if (prop in LOG_LEVEL_PRIORITY && prop !== "silent") {
654
- const current = LOG_LEVEL_PRIORITY[currentLogLevel];
655
- if (LOG_LEVEL_PRIORITY[prop] < current) return;
656
- }
657
- return target[prop];
658
- } });
659
- }
660
- //#endregion
661
- //#region src/file-writer.ts
662
- /**
663
- * File writer for loggily — Node.js/Bun only.
664
- *
665
- * Separated from core logger to allow tree-shaking in browser bundles.
666
- * Uses dynamic import("node:fs") to avoid static dependency on Node APIs.
667
- */
668
- /**
669
- * Create an async buffered file writer for log output.
670
- * Buffers writes and flushes on size threshold or interval.
671
- * Registers a process.on('exit') handler to flush remaining buffer.
672
- *
673
- * **Node.js/Bun only** — not available in browser environments.
674
- *
675
- * @param filePath - Path to the log file (opened in append mode)
676
- * @param options - Buffer size and flush interval configuration
677
- * @returns FileWriter with write, flush, and close methods
678
- *
679
- * @example
680
- * const writer = createFileWriter('/tmp/app.log')
681
- * const unsubscribe = addWriter((formatted) => writer.write(formatted))
682
- *
683
- * // On shutdown:
684
- * unsubscribe()
685
- * writer.close()
686
- */
687
- function createFileWriter(filePath, options = {}) {
688
- const bufferSize = options.bufferSize ?? 4096;
689
- const flushInterval = options.flushInterval ?? 100;
690
- let buffer = "";
691
- let fd = null;
692
- let timer = null;
693
- let closed = false;
694
- fd = openSync(filePath, "a");
695
- /** Flush buffer contents to disk synchronously */
696
- function flush() {
697
- if (buffer.length === 0 || fd === null) return;
698
- writeSync(fd, buffer);
699
- buffer = "";
700
- }
701
- timer = setInterval(flush, flushInterval);
702
- if (timer && typeof timer === "object" && "unref" in timer) timer.unref();
703
- const exitHandler = () => flush();
704
- process.on("exit", exitHandler);
705
- return {
706
- write(line) {
707
- if (closed) return;
708
- buffer += line + "\n";
709
- if (buffer.length >= bufferSize) flush();
710
- },
711
- flush,
712
- close() {
713
- if (closed) return;
714
- closed = true;
715
- if (timer !== null) {
716
- clearInterval(timer);
717
- timer = null;
718
- }
719
- try {
720
- flush();
721
- } catch {} finally {
722
- if (fd !== null) {
723
- closeSync(fd);
724
- fd = null;
725
- }
726
- process.removeListener("exit", exitHandler);
727
- }
728
- }
729
- };
730
- }
731
- //#endregion
732
- export { _ambientRecorder, _clearContextHooks, _setAmbientRecorder, _setContextHooks, addWriter, clearCollectedSpans, createFileWriter, createLogger, createSpanDataProxy, disableSpans, enableSpans, getCollectedSpans, getDebugFilter, getIdFormat, getLogFormat, getLogLevel, getOutputMode, getSampleRate, getTraceFilter, resetIds, setDebugFilter, setIdFormat, setLogFormat, setLogLevel, setOutputMode, setSampleRate, setSuppressConsole, setTraceFilter, spansAreEnabled, startCollecting, stopCollecting, traceparent, writeSpan };
733
-
734
- //# sourceMappingURL=index.mjs.map
1
+ import { A as getSampleRate, C as setSuppressConsole, D as stopCollecting, E as startCollecting, F as buildPipeline, I as defaultPipeline, L as safeStringify, M as setSampleRate, N as traceparent, O as writeSpan, P as LOG_LEVEL_PRIORITY, R as createFileWriter, S as setOutputMode, T as spansAreEnabled, _ as getTraceFilter, a as addWriter, b as setLogFormat, c as createLogger, d as enableSpans, f as getCollectedSpans, g as getOutputMode, h as getLogLevel, i as _setContextHooks, j as setIdFormat, k as getIdFormat, l as createSpanDataProxy, m as getLogFormat, n as _clearContextHooks, o as clearCollectedSpans, p as getDebugFilter, r as _setAmbientRecorder, s as compose, t as _ambientRecorder, u as disableSpans, v as resetIds, w as setTraceFilter, x as setLogLevel, y as setDebugFilter } from "./core-Du3sIje6.mjs";
2
+ export { LOG_LEVEL_PRIORITY, _ambientRecorder, _clearContextHooks, _setAmbientRecorder, _setContextHooks, addWriter, buildPipeline, clearCollectedSpans, compose, createFileWriter, createLogger, createSpanDataProxy, defaultPipeline, disableSpans, enableSpans, getCollectedSpans, getDebugFilter, getIdFormat, getLogFormat, getLogLevel, getOutputMode, getSampleRate, getTraceFilter, resetIds, safeStringify, setDebugFilter, setIdFormat, setLogFormat, setLogLevel, setOutputMode, setSampleRate, setSuppressConsole, setTraceFilter, spansAreEnabled, startCollecting, stopCollecting, traceparent, writeSpan };