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/README.md +73 -31
- package/dist/context.d.mts +91 -0
- package/dist/context.d.mts.map +1 -0
- package/dist/context.mjs +145 -0
- package/dist/context.mjs.map +1 -0
- package/dist/core-DAFH-huv.d.mts +199 -0
- package/dist/core-DAFH-huv.d.mts.map +1 -0
- package/dist/core-Du3sIje6.mjs +900 -0
- package/dist/core-Du3sIje6.mjs.map +1 -0
- package/dist/index-Co4jC3mx.d.mts +98 -0
- package/dist/index-Co4jC3mx.d.mts.map +1 -0
- package/dist/index.d.mts +3 -333
- package/dist/index.mjs +2 -734
- package/dist/metrics.d.mts +48 -0
- package/dist/metrics.d.mts.map +1 -0
- package/dist/metrics.mjs +130 -0
- package/dist/metrics.mjs.map +1 -0
- package/dist/worker.d.mts +173 -0
- package/dist/worker.d.mts.map +1 -0
- package/dist/worker.mjs +471 -0
- package/dist/worker.mjs.map +1 -0
- package/package.json +25 -8
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,734 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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 };
|