loggily 0.7.0 → 0.10.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,900 +0,0 @@
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/file-writer.ts
23
- /**
24
- * File writer for loggily — Node.js/Bun only.
25
- *
26
- * Separated from core logger to allow tree-shaking in browser bundles.
27
- * Uses dynamic import("node:fs") to avoid static dependency on Node APIs.
28
- */
29
- /**
30
- * Create an async buffered file writer for log output.
31
- * Buffers writes and flushes on size threshold or interval.
32
- * Registers a process.on('exit') handler to flush remaining buffer.
33
- *
34
- * **Node.js/Bun only** — not available in browser environments.
35
- *
36
- * @param filePath - Path to the log file (opened in append mode)
37
- * @param options - Buffer size and flush interval configuration
38
- * @returns FileWriter with write, flush, and close methods
39
- *
40
- * @example
41
- * const writer = createFileWriter('/tmp/app.log')
42
- * const unsubscribe = addWriter((formatted) => writer.write(formatted))
43
- *
44
- * // On shutdown:
45
- * unsubscribe()
46
- * writer.close()
47
- */
48
- function createFileWriter(filePath, options = {}) {
49
- const bufferSize = options.bufferSize ?? 4096;
50
- const flushInterval = options.flushInterval ?? 100;
51
- let buffer = "";
52
- let fd = null;
53
- let timer = null;
54
- let closed = false;
55
- fd = openSync(filePath, "a");
56
- /** Flush buffer contents to disk synchronously */
57
- function flush() {
58
- if (buffer.length === 0 || fd === null) return;
59
- writeSync(fd, buffer);
60
- buffer = "";
61
- }
62
- timer = setInterval(flush, flushInterval);
63
- if (timer && typeof timer === "object" && "unref" in timer) timer.unref();
64
- const exitHandler = () => flush();
65
- process.on("exit", exitHandler);
66
- return {
67
- write(line) {
68
- if (closed) return;
69
- buffer += line + "\n";
70
- if (buffer.length >= bufferSize) flush();
71
- },
72
- flush,
73
- close() {
74
- if (closed) return;
75
- closed = true;
76
- if (timer !== null) {
77
- clearInterval(timer);
78
- timer = null;
79
- }
80
- try {
81
- flush();
82
- } catch {} finally {
83
- if (fd !== null) {
84
- closeSync(fd);
85
- fd = null;
86
- }
87
- process.removeListener("exit", exitHandler);
88
- }
89
- }
90
- };
91
- }
92
- //#endregion
93
- //#region src/pipeline.ts
94
- const LOG_LEVEL_PRIORITY = {
95
- trace: 0,
96
- debug: 1,
97
- info: 2,
98
- warn: 3,
99
- error: 4,
100
- silent: 5
101
- };
102
- const _process = typeof process !== "undefined" ? process : void 0;
103
- function getEnv(key) {
104
- return _process?.env?.[key];
105
- }
106
- function writeStderr(text) {
107
- if (_process?.stderr?.write) _process.stderr.write(text + "\n");
108
- else console.error(text);
109
- }
110
- function safeStringify(value) {
111
- const seen = /* @__PURE__ */ new WeakSet();
112
- return JSON.stringify(value, (_key, val) => {
113
- if (typeof val === "bigint") return val.toString();
114
- if (typeof val === "symbol") return val.toString();
115
- if (val instanceof Error) return {
116
- message: val.message,
117
- stack: val.stack,
118
- name: val.name
119
- };
120
- if (typeof val === "object" && val !== null) {
121
- if (seen.has(val)) return "[Circular]";
122
- seen.add(val);
123
- }
124
- return val;
125
- });
126
- }
127
- function formatConsoleEvent(event) {
128
- const time = colors.dim(new Date(event.time).toISOString().split("T")[1]?.split(".")[0] || "");
129
- const ns = colors.cyan(event.namespace);
130
- if (event.kind === "span") {
131
- const message = `(${event.duration}ms)`;
132
- let output = `${time} ${colors.magenta("SPAN")} ${ns} ${message}`;
133
- if (event.props && Object.keys(event.props).length > 0) output += ` ${colors.dim(safeStringify(event.props))}`;
134
- return output;
135
- }
136
- let levelStr;
137
- switch (event.level) {
138
- case "trace":
139
- levelStr = colors.dim("TRACE");
140
- break;
141
- case "debug":
142
- levelStr = colors.dim("DEBUG");
143
- break;
144
- case "info":
145
- levelStr = colors.blue("INFO");
146
- break;
147
- case "warn":
148
- levelStr = colors.yellow("WARN");
149
- break;
150
- case "error":
151
- levelStr = colors.red("ERROR");
152
- break;
153
- }
154
- let output = `${time} ${levelStr} ${ns} ${event.message}`;
155
- if (event.props && Object.keys(event.props).length > 0) output += ` ${colors.dim(safeStringify(event.props))}`;
156
- return output;
157
- }
158
- function formatJSONEvent(event) {
159
- if (event.kind === "span") return safeStringify({
160
- time: new Date(event.time).toISOString(),
161
- level: "span",
162
- name: event.namespace,
163
- msg: `(${event.duration}ms)`,
164
- duration: event.duration,
165
- span_id: event.spanId,
166
- trace_id: event.traceId,
167
- parent_id: event.parentId,
168
- ...event.props
169
- });
170
- return safeStringify({
171
- time: new Date(event.time).toISOString(),
172
- level: event.level,
173
- name: event.namespace,
174
- msg: event.message,
175
- ...event.props
176
- });
177
- }
178
- function matchesPattern(namespace, pattern) {
179
- if (pattern === "*") return true;
180
- return namespace === pattern || namespace.startsWith(pattern + ":");
181
- }
182
- function parseNsFilter(ns) {
183
- const patterns = typeof ns === "string" ? ns.split(",").map((s) => s.trim()) : ns;
184
- const includes = [];
185
- const excludes = [];
186
- for (const p of patterns) if (p.startsWith("-")) excludes.push(p.slice(1));
187
- else includes.push(p);
188
- return (namespace) => {
189
- for (const exc of excludes) if (matchesPattern(namespace, exc)) return false;
190
- if (includes.length > 0) {
191
- for (const inc of includes) if (matchesPattern(namespace, inc)) return true;
192
- return false;
193
- }
194
- return true;
195
- };
196
- }
197
- function createConsoleSink(format) {
198
- const formatter = format === "json" ? formatJSONEvent : formatConsoleEvent;
199
- return (event) => {
200
- const text = formatter(event);
201
- if (event.kind === "span") {
202
- writeStderr(text);
203
- return;
204
- }
205
- switch (event.level) {
206
- case "trace":
207
- case "debug":
208
- console.debug(text);
209
- break;
210
- case "info":
211
- console.info(text);
212
- break;
213
- case "warn":
214
- console.warn(text);
215
- break;
216
- case "error":
217
- console.error(text);
218
- break;
219
- }
220
- };
221
- }
222
- function createFileSink(path, format) {
223
- const writer = createFileWriter(path);
224
- const formatter = format === "json" ? formatJSONEvent : formatConsoleEvent;
225
- return {
226
- write: (event) => writer.write(formatter(event)),
227
- dispose: () => writer.close()
228
- };
229
- }
230
- function createWritableSink(writable, format) {
231
- const formatter = format === "json" ? formatJSONEvent : formatConsoleEvent;
232
- return (event) => writable.write(formatter(event) + "\n");
233
- }
234
- const VALID_CONFIG_KEYS = new Set([
235
- "level",
236
- "ns",
237
- "format"
238
- ]);
239
- const SINK_KEYS = new Set(["file", "otel"]);
240
- function isPojo(obj) {
241
- if (typeof obj !== "object" || obj === null) return false;
242
- const proto = Object.getPrototypeOf(obj);
243
- return proto === Object.prototype || proto === null;
244
- }
245
- function isWritable(obj) {
246
- return typeof obj === "object" && obj !== null && "write" in obj && typeof obj.write === "function" && !isPojo(obj);
247
- }
248
- function isValidLogLevel(val) {
249
- return typeof val === "string" && val in LOG_LEVEL_PRIORITY;
250
- }
251
- function buildPipeline(elements, parentConfig) {
252
- const config = {
253
- level: parentConfig?.level ?? readEnvLevel(),
254
- ns: parentConfig?.ns ?? readEnvNs(),
255
- format: parentConfig?.format ?? readEnvFormat()
256
- };
257
- const stages = [];
258
- const outputs = [];
259
- const branches = [];
260
- const disposables = [];
261
- for (const element of elements) {
262
- if (Array.isArray(element)) {
263
- const branch = buildPipeline(element, { ...config });
264
- branches.push(branch);
265
- disposables.push(() => branch.dispose());
266
- continue;
267
- }
268
- if (typeof element === "function" && element !== console) {
269
- stages.push(element);
270
- continue;
271
- }
272
- if (element === console) {
273
- outputs.push({
274
- levelPriority: LOG_LEVEL_PRIORITY[config.level],
275
- nsFilter: config.ns,
276
- write: createConsoleSink(config.format)
277
- });
278
- continue;
279
- }
280
- if (isWritable(element)) {
281
- outputs.push({
282
- levelPriority: LOG_LEVEL_PRIORITY[config.level],
283
- nsFilter: config.ns,
284
- write: createWritableSink(element, config.format)
285
- });
286
- continue;
287
- }
288
- if (isPojo(element)) {
289
- const obj = element;
290
- const keys = Object.keys(obj);
291
- const hasSinkKey = keys.some((k) => SINK_KEYS.has(k));
292
- if (keys.some((k) => !VALID_CONFIG_KEYS.has(k) && !SINK_KEYS.has(k))) {
293
- const unknown = keys.find((k) => !VALID_CONFIG_KEYS.has(k) && !SINK_KEYS.has(k));
294
- throw new Error(`loggily: unknown config key "${unknown}" in config object. Valid keys: ${[...VALID_CONFIG_KEYS, ...SINK_KEYS].join(", ")}`);
295
- }
296
- if (hasSinkKey) {
297
- if (typeof obj.file === "string") {
298
- const outputLevel = isValidLogLevel(obj.level) ? obj.level : config.level;
299
- const outputNs = obj.ns ? parseNsFilter(obj.ns) : config.ns;
300
- const outputFormat = obj.format ?? config.format;
301
- const sink = createFileSink(obj.file, outputFormat);
302
- disposables.push(sink.dispose);
303
- outputs.push({
304
- levelPriority: LOG_LEVEL_PRIORITY[outputLevel],
305
- nsFilter: outputNs,
306
- write: sink.write,
307
- dispose: sink.dispose
308
- });
309
- }
310
- continue;
311
- }
312
- if (isValidLogLevel(obj.level)) config.level = obj.level;
313
- if (obj.ns !== void 0) config.ns = parseNsFilter(obj.ns);
314
- if (obj.format === "console" || obj.format === "json") config.format = obj.format;
315
- continue;
316
- }
317
- throw new Error(`loggily: unsupported config element of type "${typeof element}". Config arrays accept: objects (config), arrays (branches), functions (stages), console, or writables ({ write }).`);
318
- }
319
- const dispatch = (event) => {
320
- let e = event;
321
- for (const stage of stages) {
322
- const result = stage(e);
323
- if (result === null) return;
324
- if (result !== void 0) e = result;
325
- }
326
- for (const output of outputs) {
327
- if (e.kind === "log" && LOG_LEVEL_PRIORITY[e.level] < output.levelPriority) continue;
328
- if (output.nsFilter && !output.nsFilter(e.namespace)) continue;
329
- output.write(e);
330
- }
331
- for (const branch of branches) branch.dispatch(e);
332
- };
333
- return {
334
- dispatch,
335
- level: config.level,
336
- dispose: () => {
337
- for (const d of disposables) d();
338
- }
339
- };
340
- }
341
- const runtimeState = {
342
- suppressConsole: false,
343
- writers: []
344
- };
345
- function defaultPipeline() {
346
- const rt = runtimeState;
347
- const disposables = [];
348
- let fileSink = null;
349
- const logFile = getEnv("LOG_FILE");
350
- if (logFile) {
351
- const sink = createFileSink(logFile, "json");
352
- fileSink = sink.write;
353
- disposables.push(sink.dispose);
354
- }
355
- const dispatch = (event) => {
356
- const currentLevel = readEnvLevel();
357
- if (event.kind === "log") {
358
- if (LOG_LEVEL_PRIORITY[event.level] < LOG_LEVEL_PRIORITY[currentLevel]) return;
359
- } else if (event.kind === "span") {
360
- const trace = readEnvTrace();
361
- if (!trace.enabled) return;
362
- if (trace.filter && !trace.filter(event.namespace)) return;
363
- }
364
- const currentNs = readEnvNs();
365
- if (currentNs && !currentNs(event.namespace)) return;
366
- const formatter = readEnvFormat() === "json" || getEnv("NODE_ENV") === "production" || getEnv("TRACE_FORMAT") === "json" ? formatJSONEvent : formatConsoleEvent;
367
- if (rt.writers.length > 0) {
368
- const formatted = formatter(event);
369
- const lvl = event.kind === "log" ? event.level : "span";
370
- for (const w of rt.writers) w(formatted, lvl);
371
- }
372
- if (rt.suppressConsole) {
373
- fileSink?.(event);
374
- return;
375
- }
376
- const text = formatter(event);
377
- if (event.kind === "span") writeStderr(text);
378
- else switch (event.level) {
379
- case "trace":
380
- case "debug":
381
- console.debug(text);
382
- break;
383
- case "info":
384
- console.info(text);
385
- break;
386
- case "warn":
387
- console.warn(text);
388
- break;
389
- case "error":
390
- console.error(text);
391
- break;
392
- }
393
- fileSink?.(event);
394
- };
395
- return {
396
- dispatch,
397
- get level() {
398
- return readEnvLevel();
399
- },
400
- dispose: () => {
401
- for (const d of disposables) d();
402
- }
403
- };
404
- }
405
- function readEnvLevel() {
406
- const env = getEnv("LOG_LEVEL")?.toLowerCase();
407
- let level = env === "trace" || env === "debug" || env === "info" || env === "warn" || env === "error" || env === "silent" ? env : "info";
408
- if (getEnv("DEBUG") && LOG_LEVEL_PRIORITY[level] > LOG_LEVEL_PRIORITY.debug) level = "debug";
409
- return level;
410
- }
411
- function readEnvNs() {
412
- const debugEnv = getEnv("DEBUG");
413
- if (!debugEnv) return null;
414
- return parseNsFilter(debugEnv.split(",").map((s) => s.trim()));
415
- }
416
- function readEnvFormat() {
417
- const envFormat = getEnv("LOG_FORMAT")?.toLowerCase();
418
- if (envFormat === "json") return "json";
419
- if (envFormat === "console") return "console";
420
- if (getEnv("TRACE_FORMAT") === "json") return "json";
421
- if (getEnv("NODE_ENV") === "production") return "json";
422
- return "console";
423
- }
424
- function readEnvTrace() {
425
- const traceEnv = getEnv("TRACE");
426
- if (!traceEnv) return {
427
- enabled: false,
428
- filter: null
429
- };
430
- if (traceEnv === "1" || traceEnv === "true") return {
431
- enabled: true,
432
- filter: null
433
- };
434
- const prefixes = traceEnv.split(",").map((s) => s.trim());
435
- return {
436
- enabled: true,
437
- filter: (namespace) => {
438
- for (const prefix of prefixes) if (matchesPattern(namespace, prefix)) return true;
439
- return false;
440
- }
441
- };
442
- }
443
- //#endregion
444
- //#region src/tracing.ts
445
- let currentIdFormat = "simple";
446
- /**
447
- * Set the ID format for new spans and traces.
448
- * - "simple": sp_1, sp_2, tr_1, tr_2 (default, lightweight)
449
- * - "w3c": 32-char hex trace ID, 16-char hex span ID (W3C Trace Context compatible)
450
- */
451
- function setIdFormat(format) {
452
- currentIdFormat = format;
453
- }
454
- /** Get the current ID format */
455
- function getIdFormat() {
456
- return currentIdFormat;
457
- }
458
- let simpleSpanCounter = 0;
459
- let simpleTraceCounter = 0;
460
- /** Generate a hex string of the given byte length using crypto.randomUUID */
461
- function randomHex(bytes) {
462
- return crypto.randomUUID().replace(/-/g, "").slice(0, bytes * 2);
463
- }
464
- /** Generate a span ID according to the current format */
465
- function generateSpanId() {
466
- if (currentIdFormat === "w3c") return randomHex(8);
467
- return `sp_${(++simpleSpanCounter).toString(36)}`;
468
- }
469
- /** Generate a trace ID according to the current format */
470
- function generateTraceId() {
471
- if (currentIdFormat === "w3c") return randomHex(16);
472
- return `tr_${(++simpleTraceCounter).toString(36)}`;
473
- }
474
- /** Reset ID counters (for testing) */
475
- function resetIdCounters() {
476
- simpleSpanCounter = 0;
477
- simpleTraceCounter = 0;
478
- }
479
- /**
480
- * Format a W3C traceparent header from span data.
481
- *
482
- * Format: `{version}-{trace-id}-{span-id}-{trace-flags}`
483
- * - version: "00" (current W3C spec version)
484
- * - trace-id: 32 hex chars (128 bits)
485
- * - span-id: 16 hex chars (64 bits)
486
- * - trace-flags: "01" (sampled) or "00" (not sampled)
487
- *
488
- * Works with both simple and W3C ID formats. Simple IDs are zero-padded to spec length.
489
- *
490
- * @param spanData - Span data with id and traceId
491
- * @param options - Optional settings (sampled flag). Defaults to sampled=true.
492
- * @returns W3C traceparent header string
493
- *
494
- * @example
495
- * ```typescript
496
- * const span = log.span("http-request")
497
- * const header = traceparent(span.spanData)
498
- * // → "00-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6-1a2b3c4d5e6f7a8b-01"
499
- * fetch(url, { headers: { traceparent: header } })
500
- * ```
501
- */
502
- function traceparent(spanData, options) {
503
- return `00-${padHex(spanData.traceId, 32)}-${padHex(spanData.id, 16)}-${options?.sampled ?? true ? "01" : "00"}`;
504
- }
505
- /** Pad or hash an ID to the specified hex length */
506
- function padHex(id, length) {
507
- if (id.length === length && /^[0-9a-f]+$/.test(id)) return id;
508
- let hex = "";
509
- for (let i = 0; i < id.length; i++) hex += id.charCodeAt(i).toString(16).padStart(2, "0");
510
- return hex.padStart(length, "0").slice(-length);
511
- }
512
- let sampleRate = 1;
513
- /**
514
- * Set the head-based sampling rate for new traces.
515
- * Applied at trace creation — all spans within a sampled trace are kept.
516
- *
517
- * @param rate - Sampling rate from 0.0 (sample nothing) to 1.0 (sample everything, default)
518
- */
519
- function setSampleRate(rate) {
520
- if (rate < 0 || rate > 1) throw new Error(`Sample rate must be between 0.0 and 1.0, got ${rate}`);
521
- sampleRate = rate;
522
- }
523
- /** Get the current sampling rate */
524
- function getSampleRate() {
525
- return sampleRate;
526
- }
527
- /**
528
- * Determine whether a new trace should be sampled.
529
- * Called at trace creation time (head-based sampling).
530
- */
531
- function shouldSample() {
532
- if (sampleRate >= 1) return true;
533
- if (sampleRate <= 0) return false;
534
- return Math.random() < sampleRate;
535
- }
536
- //#endregion
537
- //#region src/core.ts
538
- /**
539
- * loggily v2 — Structured logging with spans
540
- *
541
- * One import. Objects configure. Arrays branch. Values write.
542
- *
543
- * @example
544
- * const log = createLogger('myapp')
545
- * log.info?.('starting')
546
- *
547
- * @example
548
- * const log = createLogger('myapp', [
549
- * { level: 'debug', ns: '-sql' },
550
- * console,
551
- * { file: '/tmp/app.log', level: 'info', format: 'json' },
552
- * ])
553
- * log.info?.('server started', { port: 3000 })
554
- */
555
- /** @internal */
556
- let _ambientRecorder = null;
557
- function _setAmbientRecorder(recorder) {
558
- _ambientRecorder = recorder;
559
- }
560
- function resetIds() {
561
- resetIdCounters();
562
- }
563
- let _getContextTags = null;
564
- let _getContextParent = null;
565
- let _enterContext = null;
566
- let _exitContext = null;
567
- /** @internal */
568
- function _setContextHooks(hooks) {
569
- _getContextTags = hooks.getContextTags;
570
- _getContextParent = hooks.getContextParent;
571
- _enterContext = hooks.enterContext;
572
- _exitContext = hooks.exitContext;
573
- }
574
- /** @internal */
575
- function _clearContextHooks() {
576
- _getContextTags = null;
577
- _getContextParent = null;
578
- _enterContext = null;
579
- _exitContext = null;
580
- }
581
- function createSpanDataProxy(getFields, attrs) {
582
- const READONLY_KEYS = new Set([
583
- "id",
584
- "traceId",
585
- "parentId",
586
- "startTime",
587
- "endTime",
588
- "duration"
589
- ]);
590
- return new Proxy(attrs, {
591
- get(_target, prop) {
592
- if (READONLY_KEYS.has(prop)) return getFields()[prop];
593
- return attrs[prop];
594
- },
595
- set(_target, prop, value) {
596
- if (READONLY_KEYS.has(prop)) return false;
597
- attrs[prop] = value;
598
- return true;
599
- }
600
- });
601
- }
602
- const collectedSpans = [];
603
- let collectSpans = false;
604
- function startCollecting() {
605
- collectSpans = true;
606
- collectedSpans.length = 0;
607
- }
608
- function stopCollecting() {
609
- collectSpans = false;
610
- return [...collectedSpans];
611
- }
612
- function getCollectedSpans() {
613
- return [...collectedSpans];
614
- }
615
- function clearCollectedSpans() {
616
- collectedSpans.length = 0;
617
- }
618
- function resolveMessage(msg) {
619
- return typeof msg === "function" ? msg() : msg;
620
- }
621
- function createLoggerImpl(name, props, pipeline, spanMeta, parentSpanId, traceId, traceSampled = true) {
622
- const emitLog = (level, msgOrError, dataOrMsg, extraData) => {
623
- let message;
624
- let data;
625
- if (msgOrError instanceof Error) {
626
- const err = msgOrError;
627
- if (typeof dataOrMsg === "string") {
628
- message = dataOrMsg;
629
- data = {
630
- ...props,
631
- ...extraData,
632
- error_type: err.name,
633
- error_message: err.message,
634
- error_stack: err.stack,
635
- error_code: err.code
636
- };
637
- } else {
638
- message = err.message;
639
- data = {
640
- ...props,
641
- ...dataOrMsg,
642
- error_type: err.name,
643
- error_stack: err.stack,
644
- error_code: err.code
645
- };
646
- }
647
- } else {
648
- message = resolveMessage(msgOrError);
649
- const contextTags = _getContextTags?.();
650
- data = contextTags && Object.keys(contextTags).length > 0 ? {
651
- ...contextTags,
652
- ...props,
653
- ...dataOrMsg
654
- } : Object.keys(props).length > 0 || dataOrMsg ? {
655
- ...props,
656
- ...dataOrMsg
657
- } : void 0;
658
- }
659
- const event = {
660
- kind: "log",
661
- time: Date.now(),
662
- namespace: name,
663
- level,
664
- message,
665
- props: data
666
- };
667
- pipeline.dispatch(event);
668
- };
669
- return {
670
- name,
671
- props: Object.freeze({ ...props }),
672
- get spanData() {
673
- if (!spanMeta) return null;
674
- return createSpanDataProxy(() => ({
675
- id: spanMeta.id,
676
- traceId: spanMeta.traceId,
677
- parentId: spanMeta.parentId,
678
- startTime: spanMeta.startTime,
679
- endTime: spanMeta.endTime,
680
- duration: spanMeta.endTime !== null ? spanMeta.endTime - spanMeta.startTime : Date.now() - spanMeta.startTime
681
- }), spanMeta.attrs);
682
- },
683
- trace: (msg, data) => emitLog("trace", msg, data),
684
- debug: (msg, data) => emitLog("debug", msg, data),
685
- info: (msg, data) => emitLog("info", msg, data),
686
- warn: (msg, data) => emitLog("warn", msg, data),
687
- error: (msgOrError, dataOrMsg, extraData) => emitLog("error", msgOrError, dataOrMsg, extraData),
688
- logger(namespace, childProps) {
689
- return wrapConditional(createLoggerImpl(namespace ? `${name}:${namespace}` : name, {
690
- ...props,
691
- ...childProps
692
- }, pipeline, null, parentSpanId, traceId, traceSampled), () => pipeline.level);
693
- },
694
- span(namespace, childProps) {
695
- const childName = namespace ? `${name}:${namespace}` : name;
696
- const resolvedChildProps = typeof childProps === "function" ? childProps() : childProps;
697
- const mergedProps = {
698
- ...props,
699
- ...resolvedChildProps
700
- };
701
- const newSpanId = generateSpanId();
702
- let resolvedParentId = parentSpanId;
703
- let resolvedTraceId = traceId;
704
- if (!resolvedParentId && _getContextParent) {
705
- const ctxParent = _getContextParent();
706
- if (ctxParent) {
707
- resolvedParentId = ctxParent.spanId;
708
- resolvedTraceId = resolvedTraceId || ctxParent.traceId;
709
- }
710
- }
711
- const isNewTrace = !resolvedTraceId;
712
- const finalTraceId = resolvedTraceId || generateTraceId();
713
- const sampled = isNewTrace ? shouldSample() : traceSampled;
714
- const newSpanData = {
715
- id: newSpanId,
716
- traceId: finalTraceId,
717
- parentId: resolvedParentId,
718
- startTime: Date.now(),
719
- endTime: null,
720
- duration: null,
721
- attrs: {}
722
- };
723
- const spanLogger = createLoggerImpl(childName, mergedProps, pipeline, newSpanData, newSpanId, finalTraceId, sampled);
724
- _enterContext?.(newSpanId, finalTraceId, resolvedParentId);
725
- spanLogger[Symbol.dispose] = () => {
726
- if (newSpanData.endTime !== null) return;
727
- newSpanData.endTime = Date.now();
728
- newSpanData.duration = newSpanData.endTime - newSpanData.startTime;
729
- if (collectSpans) collectedSpans.push(createSpanDataProxy(() => ({
730
- id: newSpanData.id,
731
- traceId: newSpanData.traceId,
732
- parentId: newSpanData.parentId,
733
- startTime: newSpanData.startTime,
734
- endTime: newSpanData.endTime,
735
- duration: newSpanData.duration
736
- }), { ...newSpanData.attrs }));
737
- _exitContext?.(newSpanId);
738
- _ambientRecorder?.recordSpan({
739
- name: childName,
740
- durationMs: newSpanData.duration
741
- });
742
- if (sampled) {
743
- const spanEvent = {
744
- kind: "span",
745
- time: newSpanData.endTime,
746
- namespace: childName,
747
- name: childName,
748
- duration: newSpanData.duration,
749
- props: {
750
- ...mergedProps,
751
- ...newSpanData.attrs
752
- },
753
- spanId: newSpanData.id,
754
- traceId: newSpanData.traceId,
755
- parentId: newSpanData.parentId
756
- };
757
- pipeline.dispatch(spanEvent);
758
- }
759
- };
760
- return spanLogger;
761
- },
762
- child(context) {
763
- if (typeof context === "string") return this.logger(context);
764
- return wrapConditional(createLoggerImpl(name, {
765
- ...props,
766
- ...context
767
- }, pipeline, null, parentSpanId, traceId, traceSampled), () => pipeline.level);
768
- },
769
- end() {
770
- if (spanMeta?.endTime === null) this[Symbol.dispose]?.();
771
- }
772
- };
773
- }
774
- function wrapConditional(logger, getLevel) {
775
- return new Proxy(logger, { get(target, prop) {
776
- if (prop in LOG_LEVEL_PRIORITY && prop !== "silent") {
777
- if (LOG_LEVEL_PRIORITY[prop] < LOG_LEVEL_PRIORITY[getLevel()]) return;
778
- }
779
- return target[prop];
780
- } });
781
- }
782
- /**
783
- * Create a logger.
784
- *
785
- * @param name - Logger namespace (e.g., 'myapp', 'myapp:db')
786
- * @param config - Optional config array. Objects configure, arrays branch, values write.
787
- *
788
- * @example
789
- * // Zero config (reads LOG_LEVEL, DEBUG, LOG_FORMAT from env)
790
- * const log = createLogger('myapp')
791
- *
792
- * @example
793
- * // Configured pipeline
794
- * const log = createLogger('myapp', [
795
- * { level: 'debug', ns: '-sql' },
796
- * console,
797
- * { file: '/tmp/app.log', level: 'info', format: 'json' },
798
- * ])
799
- */
800
- function createLogger(name, config) {
801
- const pipeline = config ? buildPipeline(config) : defaultPipeline();
802
- return wrapConditional(createLoggerImpl(name, {}, pipeline, null, null, null), () => pipeline.level);
803
- }
804
- /**
805
- * Compose a custom createLogger with plugins.
806
- *
807
- * @example
808
- * import { createLogger as base, compose } from "loggily"
809
- * import withSentry from "@sentry/loggily"
810
- *
811
- * const createLogger = compose(base, withSentry({ dsn: "..." }))
812
- * const log = createLogger("myapp")
813
- */
814
- function compose(base, ...plugins) {
815
- return plugins.reduce((factory, plugin) => plugin(factory), base);
816
- }
817
- const _env = (typeof process !== "undefined" ? process : void 0)?.env ?? {};
818
- /** @deprecated Use createLogger config array: createLogger("x", [{ level }, console]) */
819
- function setLogLevel(level) {
820
- _env.LOG_LEVEL = level;
821
- }
822
- /** @deprecated Level is per-logger in v2 */
823
- function getLogLevel() {
824
- return _env.LOG_LEVEL ?? "info";
825
- }
826
- /** @deprecated Use TRACE=1 env var */
827
- function enableSpans() {
828
- _env.TRACE = "1";
829
- }
830
- /** @deprecated */
831
- function disableSpans() {
832
- delete _env.TRACE;
833
- }
834
- /** @deprecated */
835
- function spansAreEnabled() {
836
- return !!_env.TRACE;
837
- }
838
- /** @deprecated Use TRACE=namespace env var */
839
- function setTraceFilter(namespaces) {
840
- if (!namespaces || namespaces.length === 0) delete _env.TRACE;
841
- else _env.TRACE = namespaces.join(",");
842
- }
843
- /** @deprecated */
844
- function getTraceFilter() {
845
- return _env.TRACE ? _env.TRACE.split(",") : null;
846
- }
847
- /** @deprecated Use DEBUG=namespace env var or { ns } in config array */
848
- function setDebugFilter(namespaces) {
849
- if (!namespaces || namespaces.length === 0) delete _env.DEBUG;
850
- else _env.DEBUG = namespaces.join(",");
851
- }
852
- /** @deprecated */
853
- function getDebugFilter() {
854
- return _env.DEBUG ? _env.DEBUG.split(",") : null;
855
- }
856
- /** @deprecated Use { format } in config array */
857
- function setLogFormat(format) {
858
- _env.LOG_FORMAT = format;
859
- }
860
- /** @deprecated */
861
- function getLogFormat() {
862
- return _env.LOG_FORMAT ?? "console";
863
- }
864
- /** @deprecated Omit console from config array instead */
865
- function setSuppressConsole(value) {
866
- runtimeState.suppressConsole = value;
867
- }
868
- /** @deprecated Use config array */
869
- function setOutputMode(_mode) {}
870
- /** @deprecated */
871
- function getOutputMode() {
872
- return "console";
873
- }
874
- /** @deprecated Pass writers in config array instead */
875
- function addWriter(writer) {
876
- runtimeState.writers.push(writer);
877
- return () => {
878
- const idx = runtimeState.writers.indexOf(writer);
879
- if (idx !== -1) runtimeState.writers.splice(idx, 1);
880
- };
881
- }
882
- /** @deprecated Spans dispatch through the pipeline automatically */
883
- function writeSpan(namespace, duration, attrs) {
884
- const event = {
885
- kind: "span",
886
- time: Date.now(),
887
- namespace,
888
- name: namespace,
889
- duration,
890
- props: attrs,
891
- spanId: attrs.span_id ?? "",
892
- traceId: attrs.trace_id ?? "",
893
- parentId: attrs.parent_id ?? null
894
- };
895
- defaultPipeline().dispatch(event);
896
- }
897
- //#endregion
898
- export { getSampleRate as A, setSuppressConsole as C, stopCollecting as D, startCollecting as E, buildPipeline as F, defaultPipeline as I, safeStringify as L, setSampleRate as M, traceparent as N, writeSpan as O, LOG_LEVEL_PRIORITY as P, createFileWriter as R, setOutputMode as S, spansAreEnabled as T, getTraceFilter as _, addWriter as a, setLogFormat as b, createLogger as c, enableSpans as d, getCollectedSpans as f, getOutputMode as g, getLogLevel as h, _setContextHooks as i, setIdFormat as j, getIdFormat as k, createSpanDataProxy as l, getLogFormat as m, _clearContextHooks as n, clearCollectedSpans as o, getDebugFilter as p, _setAmbientRecorder as r, compose as s, _ambientRecorder as t, disableSpans as u, resetIds as v, setTraceFilter as w, setLogLevel as x, setDebugFilter as y };
899
-
900
- //# sourceMappingURL=core-Du3sIje6.mjs.map