traza-log 0.1.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 +135 -0
- package/dist/index.cjs +735 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
// src/runtime.ts
|
|
6
|
+
function detectRuntime() {
|
|
7
|
+
if (typeof Bun !== "undefined") return "bun";
|
|
8
|
+
if (typeof Deno !== "undefined") return "deno";
|
|
9
|
+
if (typeof EdgeRuntime !== "undefined") return "edge";
|
|
10
|
+
if (typeof process !== "undefined" && process.versions?.node) return "node";
|
|
11
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") return "browser";
|
|
12
|
+
return "unknown";
|
|
13
|
+
}
|
|
14
|
+
var RUNTIME = detectRuntime();
|
|
15
|
+
var IS_NODE_LIKE = RUNTIME === "node" || RUNTIME === "bun" || RUNTIME === "deno";
|
|
16
|
+
|
|
17
|
+
// src/context.ts
|
|
18
|
+
var FallbackContextStore = class {
|
|
19
|
+
current;
|
|
20
|
+
getActive() {
|
|
21
|
+
return this.current;
|
|
22
|
+
}
|
|
23
|
+
run(ctx, fn) {
|
|
24
|
+
const prev = this.current;
|
|
25
|
+
this.current = ctx;
|
|
26
|
+
try {
|
|
27
|
+
const result = fn();
|
|
28
|
+
if (result instanceof Promise) {
|
|
29
|
+
return result.finally(() => {
|
|
30
|
+
this.current = prev;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
this.current = prev;
|
|
34
|
+
return result;
|
|
35
|
+
} catch (e) {
|
|
36
|
+
this.current = prev;
|
|
37
|
+
throw e;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var AlsContextStore = class {
|
|
42
|
+
als;
|
|
43
|
+
constructor(Als) {
|
|
44
|
+
this.als = new Als();
|
|
45
|
+
}
|
|
46
|
+
getActive() {
|
|
47
|
+
return this.als.getStore();
|
|
48
|
+
}
|
|
49
|
+
run(ctx, fn) {
|
|
50
|
+
return this.als.run(ctx, fn);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var storeInstance;
|
|
54
|
+
function getStore() {
|
|
55
|
+
if (storeInstance) return storeInstance;
|
|
56
|
+
if (IS_NODE_LIKE) {
|
|
57
|
+
try {
|
|
58
|
+
const req = Function("return require")();
|
|
59
|
+
const mod = req ? req("node:async_hooks") : void 0;
|
|
60
|
+
if (mod?.AsyncLocalStorage) {
|
|
61
|
+
storeInstance = new AlsContextStore(mod.AsyncLocalStorage);
|
|
62
|
+
return storeInstance;
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
storeInstance = new FallbackContextStore();
|
|
68
|
+
return storeInstance;
|
|
69
|
+
}
|
|
70
|
+
function getActiveContext() {
|
|
71
|
+
return getStore().getActive();
|
|
72
|
+
}
|
|
73
|
+
function runWithContext(ctx, fn) {
|
|
74
|
+
return getStore().run(ctx, fn);
|
|
75
|
+
}
|
|
76
|
+
function mergeContext(base, patch) {
|
|
77
|
+
return {
|
|
78
|
+
traceId: patch.traceId ?? base?.traceId,
|
|
79
|
+
spanId: patch.spanId ?? base?.spanId,
|
|
80
|
+
parentSpanId: patch.parentSpanId ?? base?.parentSpanId,
|
|
81
|
+
attributes: { ...base?.attributes ?? {}, ...patch.attributes ?? {} }
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/utils.ts
|
|
86
|
+
var HEX = "0123456789abcdef";
|
|
87
|
+
function randomHex(bytes) {
|
|
88
|
+
const buf = new Uint8Array(bytes);
|
|
89
|
+
const c = globalThis.crypto;
|
|
90
|
+
if (c?.getRandomValues) {
|
|
91
|
+
c.getRandomValues(buf);
|
|
92
|
+
} else {
|
|
93
|
+
for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);
|
|
94
|
+
}
|
|
95
|
+
let out = "";
|
|
96
|
+
for (let i = 0; i < bytes; i++) {
|
|
97
|
+
const v = buf[i];
|
|
98
|
+
out += HEX[v >> 4] + HEX[v & 15];
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
function newTraceId() {
|
|
103
|
+
return randomHex(16);
|
|
104
|
+
}
|
|
105
|
+
function newSpanId() {
|
|
106
|
+
return randomHex(8);
|
|
107
|
+
}
|
|
108
|
+
function newEventId() {
|
|
109
|
+
const c = globalThis.crypto;
|
|
110
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
111
|
+
const b = new Uint8Array(16);
|
|
112
|
+
if (c?.getRandomValues) c.getRandomValues(b);
|
|
113
|
+
else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);
|
|
114
|
+
b[6] = b[6] & 15 | 64;
|
|
115
|
+
b[8] = b[8] & 63 | 128;
|
|
116
|
+
const h = (n) => HEX[n >> 4 & 15] + HEX[n & 15];
|
|
117
|
+
return h(b[0]) + h(b[1]) + h(b[2]) + h(b[3]) + "-" + h(b[4]) + h(b[5]) + "-" + h(b[6]) + h(b[7]) + "-" + h(b[8]) + h(b[9]) + "-" + h(b[10]) + h(b[11]) + h(b[12]) + h(b[13]) + h(b[14]) + h(b[15]);
|
|
118
|
+
}
|
|
119
|
+
function now() {
|
|
120
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
121
|
+
return performance.timeOrigin + performance.now();
|
|
122
|
+
}
|
|
123
|
+
return Date.now();
|
|
124
|
+
}
|
|
125
|
+
function safeStringify(value) {
|
|
126
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
127
|
+
try {
|
|
128
|
+
return JSON.stringify(value, (_k, v) => {
|
|
129
|
+
if (typeof v === "bigint") return v.toString();
|
|
130
|
+
if (typeof v === "object" && v !== null) {
|
|
131
|
+
if (seen.has(v)) return "[Circular]";
|
|
132
|
+
seen.add(v);
|
|
133
|
+
}
|
|
134
|
+
if (v instanceof Error) {
|
|
135
|
+
return { name: v.name, message: v.message, stack: v.stack };
|
|
136
|
+
}
|
|
137
|
+
return v;
|
|
138
|
+
});
|
|
139
|
+
} catch {
|
|
140
|
+
return String(value);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function isPlainObject(v) {
|
|
144
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
145
|
+
}
|
|
146
|
+
function normalizeError(err) {
|
|
147
|
+
if (err instanceof Error) {
|
|
148
|
+
return { name: err.name, message: err.message, stack: err.stack };
|
|
149
|
+
}
|
|
150
|
+
if (typeof err === "string") return { message: err };
|
|
151
|
+
if (isPlainObject(err)) {
|
|
152
|
+
return {
|
|
153
|
+
name: typeof err.name === "string" ? err.name : void 0,
|
|
154
|
+
message: typeof err.message === "string" ? err.message : safeStringify(err),
|
|
155
|
+
stack: typeof err.stack === "string" ? err.stack : void 0
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return { message: safeStringify(err) };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/middleware.ts
|
|
162
|
+
function createHttpMiddleware(client) {
|
|
163
|
+
return function trazaMiddleware(req, res, next) {
|
|
164
|
+
const method = req.method ?? "GET";
|
|
165
|
+
const url = req.originalUrl ?? req.url ?? "/";
|
|
166
|
+
const traceparent = pickHeader(req.headers, "traceparent");
|
|
167
|
+
const parent = parseTraceparent(traceparent);
|
|
168
|
+
const route = stripQuery(url);
|
|
169
|
+
const span = client.startSpan(`HTTP ${method}`, {
|
|
170
|
+
attributes: {
|
|
171
|
+
"http.method": method,
|
|
172
|
+
"http.url": url,
|
|
173
|
+
"http.route": route,
|
|
174
|
+
"http.user_agent": pickHeader(req.headers, "user-agent")
|
|
175
|
+
},
|
|
176
|
+
parent
|
|
177
|
+
});
|
|
178
|
+
client.runWithContext(
|
|
179
|
+
{
|
|
180
|
+
traceId: span.traceId,
|
|
181
|
+
spanId: span.id,
|
|
182
|
+
attributes: { "http.method": method, "http.route": route }
|
|
183
|
+
},
|
|
184
|
+
() => {
|
|
185
|
+
let finished = false;
|
|
186
|
+
const finish = (err) => {
|
|
187
|
+
if (finished) return;
|
|
188
|
+
finished = true;
|
|
189
|
+
const status = res.statusCode ?? 0;
|
|
190
|
+
span.setAttribute("http.status_code", status);
|
|
191
|
+
if (err) span.recordError(err);
|
|
192
|
+
span.end({
|
|
193
|
+
status: err || status >= 500 ? "error" : "ok",
|
|
194
|
+
error: err
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
try {
|
|
198
|
+
res.on?.("finish", () => finish());
|
|
199
|
+
res.on?.("close", () => finish());
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
next();
|
|
204
|
+
} catch (e) {
|
|
205
|
+
finish(e);
|
|
206
|
+
throw e;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function wrapH3Handler(client, handler, name) {
|
|
213
|
+
return (async (...args) => {
|
|
214
|
+
const event = args[0];
|
|
215
|
+
const req = event?.node?.req;
|
|
216
|
+
const res = event?.node?.res;
|
|
217
|
+
const method = event?.method ?? req?.method ?? "GET";
|
|
218
|
+
const url = event?.path ?? req?.url ?? "/";
|
|
219
|
+
const parent = parseTraceparent(pickHeader(req?.headers, "traceparent"));
|
|
220
|
+
const span = client.startSpan(name ?? `HTTP ${method} ${stripQuery(url)}`, {
|
|
221
|
+
attributes: { "http.method": method, "http.url": url, "http.route": stripQuery(url) },
|
|
222
|
+
parent
|
|
223
|
+
});
|
|
224
|
+
try {
|
|
225
|
+
return await client.runWithContext(
|
|
226
|
+
{ traceId: span.traceId, spanId: span.id, attributes: {} },
|
|
227
|
+
() => handler(...args)
|
|
228
|
+
);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
span.recordError(err);
|
|
231
|
+
throw err;
|
|
232
|
+
} finally {
|
|
233
|
+
span.setAttribute("http.status_code", res?.statusCode ?? 0);
|
|
234
|
+
span.end({ status: (res?.statusCode ?? 0) >= 500 ? "error" : "ok" });
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function pickHeader(headers, name) {
|
|
239
|
+
if (!headers) return void 0;
|
|
240
|
+
const v = headers[name] ?? headers[name.toLowerCase()];
|
|
241
|
+
if (Array.isArray(v)) return v[0];
|
|
242
|
+
return v;
|
|
243
|
+
}
|
|
244
|
+
function stripQuery(url) {
|
|
245
|
+
const i = url.indexOf("?");
|
|
246
|
+
return i === -1 ? url : url.slice(0, i);
|
|
247
|
+
}
|
|
248
|
+
function parseTraceparent(value) {
|
|
249
|
+
if (!value) return void 0;
|
|
250
|
+
const parts = value.split("-");
|
|
251
|
+
if (parts.length < 4) return void 0;
|
|
252
|
+
const traceId = parts[1];
|
|
253
|
+
const spanId = parts[2];
|
|
254
|
+
if (!traceId || traceId.length !== 32 || !spanId || spanId.length !== 16) return void 0;
|
|
255
|
+
return { traceId, spanId };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/queue.ts
|
|
259
|
+
var BoundedQueue = class {
|
|
260
|
+
constructor(opts) {
|
|
261
|
+
this.opts = opts;
|
|
262
|
+
}
|
|
263
|
+
opts;
|
|
264
|
+
buffer = [];
|
|
265
|
+
get size() {
|
|
266
|
+
return this.buffer.length;
|
|
267
|
+
}
|
|
268
|
+
enqueue(event) {
|
|
269
|
+
if (this.buffer.length >= this.opts.maxSize) {
|
|
270
|
+
const dropped = this.buffer.shift();
|
|
271
|
+
if (dropped && this.opts.onDrop) {
|
|
272
|
+
try {
|
|
273
|
+
this.opts.onDrop(dropped);
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
this.buffer.push(event);
|
|
279
|
+
}
|
|
280
|
+
drain(max) {
|
|
281
|
+
if (max <= 0 || this.buffer.length === 0) return [];
|
|
282
|
+
if (max >= this.buffer.length) {
|
|
283
|
+
const all = this.buffer;
|
|
284
|
+
this.buffer = [];
|
|
285
|
+
return all;
|
|
286
|
+
}
|
|
287
|
+
return this.buffer.splice(0, max);
|
|
288
|
+
}
|
|
289
|
+
prepend(events) {
|
|
290
|
+
if (events.length === 0) return;
|
|
291
|
+
const overflow = this.buffer.length + events.length - this.opts.maxSize;
|
|
292
|
+
if (overflow > 0) {
|
|
293
|
+
const drop = this.buffer.splice(this.buffer.length - overflow, overflow);
|
|
294
|
+
if (this.opts.onDrop) drop.forEach((d) => this.opts.onDrop(d));
|
|
295
|
+
}
|
|
296
|
+
this.buffer = events.concat(this.buffer);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// src/transport.ts
|
|
301
|
+
var SDK_NAME = "traza";
|
|
302
|
+
var SDK_VERSION = "0.1.0";
|
|
303
|
+
var DEFAULTS = {
|
|
304
|
+
flushIntervalMs: 3e3,
|
|
305
|
+
maxBatchSize: 100,
|
|
306
|
+
maxQueueSize: 1e4,
|
|
307
|
+
requestTimeoutMs: 1e4,
|
|
308
|
+
maxRetries: 3
|
|
309
|
+
};
|
|
310
|
+
var BatchTransport = class {
|
|
311
|
+
constructor(deps) {
|
|
312
|
+
this.deps = deps;
|
|
313
|
+
const max = deps.config.maxQueueSize ?? DEFAULTS.maxQueueSize;
|
|
314
|
+
this.queue = new BoundedQueue({ maxSize: max });
|
|
315
|
+
this.endpoint = normalizeEndpoint(deps.config.endpoint);
|
|
316
|
+
const f = deps.config.fetch ?? globalThis.fetch;
|
|
317
|
+
if (!f) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
"[traza] No fetch implementation found. Pass `fetch` in config or upgrade to Node 18+."
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
this.fetchImpl = f.bind(globalThis);
|
|
323
|
+
this.startTimer();
|
|
324
|
+
this.attachShutdownHooks();
|
|
325
|
+
}
|
|
326
|
+
deps;
|
|
327
|
+
queue;
|
|
328
|
+
timer = null;
|
|
329
|
+
flushing = false;
|
|
330
|
+
stopped = false;
|
|
331
|
+
endpoint;
|
|
332
|
+
fetchImpl;
|
|
333
|
+
push(event) {
|
|
334
|
+
if (this.stopped) return;
|
|
335
|
+
this.queue.enqueue(event);
|
|
336
|
+
const max = this.deps.config.maxBatchSize ?? DEFAULTS.maxBatchSize;
|
|
337
|
+
if (this.queue.size >= max) {
|
|
338
|
+
void this.flush();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async flush() {
|
|
342
|
+
if (this.flushing) return;
|
|
343
|
+
this.flushing = true;
|
|
344
|
+
try {
|
|
345
|
+
const batchSize = this.deps.config.maxBatchSize ?? DEFAULTS.maxBatchSize;
|
|
346
|
+
let batch = this.queue.drain(batchSize);
|
|
347
|
+
while (batch.length > 0) {
|
|
348
|
+
const ok = await this.send(batch);
|
|
349
|
+
if (!ok) {
|
|
350
|
+
this.queue.prepend(batch);
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
batch = this.queue.drain(batchSize);
|
|
354
|
+
}
|
|
355
|
+
} catch (e) {
|
|
356
|
+
this.deps.onError(e);
|
|
357
|
+
} finally {
|
|
358
|
+
this.flushing = false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async shutdown() {
|
|
362
|
+
if (this.stopped) return;
|
|
363
|
+
this.stopped = true;
|
|
364
|
+
if (this.timer) {
|
|
365
|
+
clearInterval(this.timer);
|
|
366
|
+
this.timer = null;
|
|
367
|
+
}
|
|
368
|
+
await this.flush();
|
|
369
|
+
}
|
|
370
|
+
async send(events) {
|
|
371
|
+
const payload = {
|
|
372
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION, runtime: RUNTIME },
|
|
373
|
+
sentAt: Date.now(),
|
|
374
|
+
events
|
|
375
|
+
};
|
|
376
|
+
const body = JSON.stringify(payload);
|
|
377
|
+
const timeoutMs = this.deps.config.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs;
|
|
378
|
+
for (let attempt = 0; attempt <= DEFAULTS.maxRetries; attempt++) {
|
|
379
|
+
const controller = new AbortController();
|
|
380
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
381
|
+
try {
|
|
382
|
+
const res = await this.fetchImpl(`${this.endpoint}/api/ingest`, {
|
|
383
|
+
method: "POST",
|
|
384
|
+
headers: {
|
|
385
|
+
"content-type": "application/json",
|
|
386
|
+
authorization: `Bearer ${this.deps.config.token}`,
|
|
387
|
+
"x-traza-sdk": `${SDK_NAME}/${SDK_VERSION}`,
|
|
388
|
+
"x-traza-runtime": RUNTIME
|
|
389
|
+
},
|
|
390
|
+
body,
|
|
391
|
+
signal: controller.signal,
|
|
392
|
+
keepalive: events.length < 50
|
|
393
|
+
// browsers permiten keepalive en payloads pequeños
|
|
394
|
+
});
|
|
395
|
+
clearTimeout(timer);
|
|
396
|
+
if (res.ok) return true;
|
|
397
|
+
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
|
|
398
|
+
this.deps.onError(new Error(`[traza] ingest rejected: ${res.status} ${res.statusText}`));
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
} catch (e) {
|
|
402
|
+
clearTimeout(timer);
|
|
403
|
+
if (attempt === DEFAULTS.maxRetries) {
|
|
404
|
+
this.deps.onError(e);
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
await delay(backoffMs(attempt));
|
|
409
|
+
}
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
startTimer() {
|
|
413
|
+
const interval = this.deps.config.flushIntervalMs ?? DEFAULTS.flushIntervalMs;
|
|
414
|
+
this.timer = setInterval(() => {
|
|
415
|
+
void this.flush();
|
|
416
|
+
}, interval);
|
|
417
|
+
const t = this.timer;
|
|
418
|
+
if (typeof t.unref === "function") t.unref();
|
|
419
|
+
}
|
|
420
|
+
attachShutdownHooks() {
|
|
421
|
+
if (typeof process !== "undefined" && typeof process.on === "function") {
|
|
422
|
+
const handler = () => {
|
|
423
|
+
void this.flush();
|
|
424
|
+
};
|
|
425
|
+
try {
|
|
426
|
+
process.on("beforeExit", handler);
|
|
427
|
+
} catch {
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
431
|
+
const handler = () => {
|
|
432
|
+
void this.flush();
|
|
433
|
+
};
|
|
434
|
+
try {
|
|
435
|
+
window.addEventListener("pagehide", handler);
|
|
436
|
+
document.addEventListener("visibilitychange", () => {
|
|
437
|
+
if (document.visibilityState === "hidden") handler();
|
|
438
|
+
});
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
function backoffMs(attempt) {
|
|
445
|
+
const base = 200 * Math.pow(2, attempt);
|
|
446
|
+
const jitter = Math.random() * base * 0.3;
|
|
447
|
+
return Math.min(base + jitter, 5e3);
|
|
448
|
+
}
|
|
449
|
+
function delay(ms) {
|
|
450
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
451
|
+
}
|
|
452
|
+
function normalizeEndpoint(url) {
|
|
453
|
+
return url.replace(/\/+$/, "");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/span.ts
|
|
457
|
+
var SpanImpl = class {
|
|
458
|
+
constructor(input, deps) {
|
|
459
|
+
this.deps = deps;
|
|
460
|
+
this.name = input.name;
|
|
461
|
+
this.attributes = { ...input.attributes ?? {} };
|
|
462
|
+
this.contextSnapshot = input.parentContext?.attributes;
|
|
463
|
+
if (input.parentOverride) {
|
|
464
|
+
this.traceId = input.parentOverride.traceId;
|
|
465
|
+
this.parentSpanId = input.parentOverride.spanId;
|
|
466
|
+
} else if (input.parentContext?.traceId) {
|
|
467
|
+
this.traceId = input.parentContext.traceId;
|
|
468
|
+
this.parentSpanId = input.parentContext.spanId;
|
|
469
|
+
} else {
|
|
470
|
+
this.traceId = newTraceId();
|
|
471
|
+
}
|
|
472
|
+
this.id = newSpanId();
|
|
473
|
+
this.startTime = now();
|
|
474
|
+
}
|
|
475
|
+
deps;
|
|
476
|
+
id;
|
|
477
|
+
traceId;
|
|
478
|
+
parentSpanId;
|
|
479
|
+
name;
|
|
480
|
+
startTime;
|
|
481
|
+
attributes;
|
|
482
|
+
contextSnapshot;
|
|
483
|
+
status = "ok";
|
|
484
|
+
errorInfo;
|
|
485
|
+
endedFlag = false;
|
|
486
|
+
get ended() {
|
|
487
|
+
return this.endedFlag;
|
|
488
|
+
}
|
|
489
|
+
setAttribute(key, value) {
|
|
490
|
+
this.attributes[key] = value;
|
|
491
|
+
return this;
|
|
492
|
+
}
|
|
493
|
+
setAttributes(attributes) {
|
|
494
|
+
Object.assign(this.attributes, attributes);
|
|
495
|
+
return this;
|
|
496
|
+
}
|
|
497
|
+
recordError(error) {
|
|
498
|
+
this.status = "error";
|
|
499
|
+
this.errorInfo = normalizeError(error);
|
|
500
|
+
return this;
|
|
501
|
+
}
|
|
502
|
+
end(options = {}) {
|
|
503
|
+
if (this.endedFlag) return;
|
|
504
|
+
this.endedFlag = true;
|
|
505
|
+
if (options.attributes) Object.assign(this.attributes, options.attributes);
|
|
506
|
+
if (options.error) {
|
|
507
|
+
this.status = options.status ?? "error";
|
|
508
|
+
this.errorInfo = normalizeError(options.error);
|
|
509
|
+
} else if (options.status) {
|
|
510
|
+
this.status = options.status;
|
|
511
|
+
}
|
|
512
|
+
const endTime = now();
|
|
513
|
+
const event = {
|
|
514
|
+
type: "span",
|
|
515
|
+
id: this.id,
|
|
516
|
+
traceId: this.traceId,
|
|
517
|
+
parentSpanId: this.parentSpanId,
|
|
518
|
+
name: this.name,
|
|
519
|
+
status: this.status,
|
|
520
|
+
startTime: this.startTime,
|
|
521
|
+
endTime,
|
|
522
|
+
durationMs: Math.max(0, endTime - this.startTime),
|
|
523
|
+
attributes: { ...this.deps.defaultAttributes ?? {}, ...this.attributes },
|
|
524
|
+
error: this.errorInfo,
|
|
525
|
+
service: this.deps.service,
|
|
526
|
+
environment: this.deps.environment,
|
|
527
|
+
release: this.deps.release,
|
|
528
|
+
context: this.contextSnapshot
|
|
529
|
+
};
|
|
530
|
+
this.deps.onEnd(event);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/client.ts
|
|
535
|
+
var noop = () => {
|
|
536
|
+
};
|
|
537
|
+
var CONSOLE_METHOD = {
|
|
538
|
+
debug: "debug",
|
|
539
|
+
info: "info",
|
|
540
|
+
log: "log",
|
|
541
|
+
warn: "warn",
|
|
542
|
+
error: "error"
|
|
543
|
+
};
|
|
544
|
+
var TrazaClient = class {
|
|
545
|
+
transport = null;
|
|
546
|
+
config = null;
|
|
547
|
+
configure(config) {
|
|
548
|
+
if (!config.token) throw new Error("[traza] config.token is required");
|
|
549
|
+
if (!config.endpoint) throw new Error("[traza] config.endpoint is required");
|
|
550
|
+
if (this.transport) {
|
|
551
|
+
void this.transport.shutdown();
|
|
552
|
+
}
|
|
553
|
+
this.config = config;
|
|
554
|
+
const onError = config.onError ?? (config.debug ? console.error.bind(console) : noop);
|
|
555
|
+
this.transport = new BatchTransport({ config, onError });
|
|
556
|
+
return this;
|
|
557
|
+
}
|
|
558
|
+
isConfigured() {
|
|
559
|
+
return this.transport !== null;
|
|
560
|
+
}
|
|
561
|
+
/** Cierra el transport y vacía la cola. Útil en tests y al apagar el proceso. */
|
|
562
|
+
async shutdown() {
|
|
563
|
+
if (this.transport) {
|
|
564
|
+
await this.transport.shutdown();
|
|
565
|
+
this.transport = null;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/** Fuerza el envío inmediato del lote en cola. */
|
|
569
|
+
async flush() {
|
|
570
|
+
await this.transport?.flush();
|
|
571
|
+
}
|
|
572
|
+
// --- Logs ---
|
|
573
|
+
debug(message, attributes) {
|
|
574
|
+
this.emitLog("debug", message, attributes);
|
|
575
|
+
}
|
|
576
|
+
info(message, attributes) {
|
|
577
|
+
this.emitLog("info", message, attributes);
|
|
578
|
+
}
|
|
579
|
+
log(message, attributes) {
|
|
580
|
+
this.emitLog("log", message, attributes);
|
|
581
|
+
}
|
|
582
|
+
warn(message, attributes) {
|
|
583
|
+
this.emitLog("warn", message, attributes);
|
|
584
|
+
}
|
|
585
|
+
error(message, attributes) {
|
|
586
|
+
this.emitLog("error", message, attributes);
|
|
587
|
+
}
|
|
588
|
+
// --- Spans ---
|
|
589
|
+
startSpan(name, options = {}) {
|
|
590
|
+
const ctx = options.detached ? void 0 : getActiveContext();
|
|
591
|
+
return new SpanImpl(
|
|
592
|
+
{
|
|
593
|
+
name,
|
|
594
|
+
attributes: options.attributes,
|
|
595
|
+
parentContext: ctx,
|
|
596
|
+
parentOverride: options.parent
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
service: this.config?.service,
|
|
600
|
+
environment: this.config?.environment,
|
|
601
|
+
release: this.config?.release,
|
|
602
|
+
defaultAttributes: this.config?.defaultAttributes,
|
|
603
|
+
onEnd: (event) => this.push(event)
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
/** Mide la duración de una función async y la reporta como span. */
|
|
608
|
+
async time(name, fn, attributes) {
|
|
609
|
+
const span = this.startSpan(name, { attributes });
|
|
610
|
+
try {
|
|
611
|
+
const result = await this.runWithContext(
|
|
612
|
+
{ traceId: span.traceId, spanId: span.id, attributes: {} },
|
|
613
|
+
() => Promise.resolve(fn())
|
|
614
|
+
);
|
|
615
|
+
span.end({ status: "ok" });
|
|
616
|
+
return result;
|
|
617
|
+
} catch (err) {
|
|
618
|
+
span.recordError(err);
|
|
619
|
+
span.end({ status: "error", error: err });
|
|
620
|
+
throw err;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/** Envuelve una función para que cada llamada quede instrumentada. */
|
|
624
|
+
instrument(name, fn, attributesFactory) {
|
|
625
|
+
return async (...args) => {
|
|
626
|
+
const attrs = attributesFactory ? attributesFactory(...args) : void 0;
|
|
627
|
+
return this.time(name, () => fn(...args), attrs);
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
// --- Contexto ---
|
|
631
|
+
/** Ejecuta `fn` dentro de un contexto activo. Logs/spans hijos lo heredan. */
|
|
632
|
+
runWithContext(patch, fn) {
|
|
633
|
+
const merged = mergeContext(getActiveContext(), patch);
|
|
634
|
+
return runWithContext(merged, fn);
|
|
635
|
+
}
|
|
636
|
+
/** Devuelve una copia del contexto actual (útil para propagación manual). */
|
|
637
|
+
getContext() {
|
|
638
|
+
return getActiveContext();
|
|
639
|
+
}
|
|
640
|
+
// --- Integraciones ---
|
|
641
|
+
middleware() {
|
|
642
|
+
return createHttpMiddleware(this);
|
|
643
|
+
}
|
|
644
|
+
h3(handler, name) {
|
|
645
|
+
return wrapH3Handler(this, handler, name);
|
|
646
|
+
}
|
|
647
|
+
// --- Privados ---
|
|
648
|
+
emitLog(level, message, attributes) {
|
|
649
|
+
const ctx = getActiveContext();
|
|
650
|
+
const event = {
|
|
651
|
+
type: "log",
|
|
652
|
+
id: newEventId(),
|
|
653
|
+
level,
|
|
654
|
+
message,
|
|
655
|
+
attributes: this.mergeAttributes(attributes, level === "error" ? this.errorFromAttrs(attributes) : void 0),
|
|
656
|
+
timestamp: now(),
|
|
657
|
+
service: this.config?.service,
|
|
658
|
+
environment: this.config?.environment,
|
|
659
|
+
release: this.config?.release,
|
|
660
|
+
traceId: ctx?.traceId,
|
|
661
|
+
spanId: ctx?.spanId,
|
|
662
|
+
parentSpanId: ctx?.parentSpanId,
|
|
663
|
+
context: ctx?.attributes
|
|
664
|
+
};
|
|
665
|
+
if (this.config?.forwardToConsole !== false) {
|
|
666
|
+
this.forwardConsole(level, message, attributes);
|
|
667
|
+
}
|
|
668
|
+
this.push(event);
|
|
669
|
+
}
|
|
670
|
+
errorFromAttrs(attrs) {
|
|
671
|
+
if (!attrs) return void 0;
|
|
672
|
+
const err = attrs.err ?? attrs.error;
|
|
673
|
+
if (err instanceof Error || typeof err === "object" && err !== null && "message" in err) {
|
|
674
|
+
const norm = normalizeError(err);
|
|
675
|
+
return {
|
|
676
|
+
"error.name": norm.name ?? null,
|
|
677
|
+
"error.message": norm.message ?? null,
|
|
678
|
+
"error.stack": norm.stack ?? null
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
return void 0;
|
|
682
|
+
}
|
|
683
|
+
mergeAttributes(...parts) {
|
|
684
|
+
const out = { ...this.config?.defaultAttributes ?? {} };
|
|
685
|
+
let has = Object.keys(out).length > 0;
|
|
686
|
+
for (const p of parts) {
|
|
687
|
+
if (!p) continue;
|
|
688
|
+
for (const [k, v] of Object.entries(p)) {
|
|
689
|
+
out[k] = v;
|
|
690
|
+
has = true;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return has ? out : void 0;
|
|
694
|
+
}
|
|
695
|
+
forwardConsole(level, message, attributes) {
|
|
696
|
+
if (typeof console === "undefined") return;
|
|
697
|
+
const method = CONSOLE_METHOD[level];
|
|
698
|
+
const fn = console[method];
|
|
699
|
+
if (typeof fn !== "function") return;
|
|
700
|
+
if (attributes && Object.keys(attributes).length > 0) {
|
|
701
|
+
fn.call(console, `[traza] ${message}`, attributes);
|
|
702
|
+
} else {
|
|
703
|
+
fn.call(console, `[traza] ${message}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
push(event) {
|
|
707
|
+
if (!this.transport) {
|
|
708
|
+
if (this.config?.debug) {
|
|
709
|
+
console.warn("[traza] no configurado; evento descartado");
|
|
710
|
+
}
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const rate = this.config?.sampleRate ?? 1;
|
|
714
|
+
if (rate < 1 && Math.random() > rate) return;
|
|
715
|
+
this.transport.push(event);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// src/index.ts
|
|
720
|
+
var traza = new TrazaClient();
|
|
721
|
+
function createTraza() {
|
|
722
|
+
return new TrazaClient();
|
|
723
|
+
}
|
|
724
|
+
var index_default = traza;
|
|
725
|
+
|
|
726
|
+
exports.RUNTIME = RUNTIME;
|
|
727
|
+
exports.TrazaClient = TrazaClient;
|
|
728
|
+
exports.createTraza = createTraza;
|
|
729
|
+
exports.default = index_default;
|
|
730
|
+
exports.detectRuntime = detectRuntime;
|
|
731
|
+
exports.getActiveContext = getActiveContext;
|
|
732
|
+
exports.runWithContext = runWithContext;
|
|
733
|
+
exports.traza = traza;
|
|
734
|
+
//# sourceMappingURL=index.cjs.map
|
|
735
|
+
//# sourceMappingURL=index.cjs.map
|