@deeptracer/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-BRUVRV5O.js +601 -0
- package/dist/index.cjs +333 -112
- package/dist/index.d.cts +5 -219
- package/dist/index.d.ts +5 -219
- package/dist/index.js +5 -1
- package/dist/internal-DdKQRgCs.d.cts +375 -0
- package/dist/internal-DdKQRgCs.d.ts +375 -0
- package/dist/internal.cjs +329 -112
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LSMMIS4A.js +0 -382
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
// src/version.ts
|
|
2
|
+
var SDK_VERSION = "0.3.0";
|
|
3
|
+
var SDK_NAME = "core";
|
|
4
|
+
|
|
5
|
+
// src/batcher.ts
|
|
6
|
+
var Batcher = class {
|
|
7
|
+
constructor(config, onFlush) {
|
|
8
|
+
this.onFlush = onFlush;
|
|
9
|
+
this.batchSize = config.batchSize ?? 50;
|
|
10
|
+
this.flushIntervalMs = config.flushIntervalMs ?? 5e3;
|
|
11
|
+
this.startTimer();
|
|
12
|
+
}
|
|
13
|
+
buffer = [];
|
|
14
|
+
timer = null;
|
|
15
|
+
batchSize;
|
|
16
|
+
flushIntervalMs;
|
|
17
|
+
add(entry) {
|
|
18
|
+
this.buffer.push(entry);
|
|
19
|
+
if (this.buffer.length >= this.batchSize) {
|
|
20
|
+
this.flush();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
flush() {
|
|
24
|
+
if (this.buffer.length === 0) return;
|
|
25
|
+
const entries = [...this.buffer];
|
|
26
|
+
this.buffer = [];
|
|
27
|
+
this.onFlush(entries);
|
|
28
|
+
}
|
|
29
|
+
startTimer() {
|
|
30
|
+
this.timer = setInterval(() => this.flush(), this.flushIntervalMs);
|
|
31
|
+
}
|
|
32
|
+
async destroy() {
|
|
33
|
+
if (this.timer) clearInterval(this.timer);
|
|
34
|
+
this.timer = null;
|
|
35
|
+
this.flush();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/transport.ts
|
|
40
|
+
var Transport = class {
|
|
41
|
+
constructor(config) {
|
|
42
|
+
this.config = config;
|
|
43
|
+
}
|
|
44
|
+
inFlightRequests = /* @__PURE__ */ new Set();
|
|
45
|
+
/**
|
|
46
|
+
* Send a request with automatic retry and exponential backoff.
|
|
47
|
+
* Retries up to `maxRetries` times on network errors and 5xx responses.
|
|
48
|
+
* Does NOT retry on 4xx (client errors — bad payload, auth failure, etc.).
|
|
49
|
+
*/
|
|
50
|
+
async sendWithRetry(url, body, label, maxRetries = 3) {
|
|
51
|
+
const baseDelays = [1e3, 2e3, 4e3];
|
|
52
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(url, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
59
|
+
"x-deeptracer-sdk": `${SDK_NAME}/${SDK_VERSION}`
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify(body)
|
|
62
|
+
});
|
|
63
|
+
if (res.ok) return;
|
|
64
|
+
if (res.status >= 400 && res.status < 500) {
|
|
65
|
+
console.warn(
|
|
66
|
+
`[@deeptracer/core] Failed to send ${label}: ${res.status} ${res.statusText}`
|
|
67
|
+
);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (attempt < maxRetries) {
|
|
71
|
+
await this.sleep(this.jitter(baseDelays[attempt]));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
console.warn(
|
|
75
|
+
`[@deeptracer/core] Failed to send ${label}: ${res.status} ${res.statusText} (exhausted ${maxRetries} retries)`
|
|
76
|
+
);
|
|
77
|
+
} catch {
|
|
78
|
+
if (attempt < maxRetries) {
|
|
79
|
+
await this.sleep(this.jitter(baseDelays[attempt]));
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
console.warn(
|
|
83
|
+
`[@deeptracer/core] Failed to send ${label} (exhausted ${maxRetries} retries)`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Add +/- 20% jitter to a delay to prevent thundering herd. */
|
|
89
|
+
jitter(ms) {
|
|
90
|
+
const factor = 0.8 + Math.random() * 0.4;
|
|
91
|
+
return Math.round(ms * factor);
|
|
92
|
+
}
|
|
93
|
+
sleep(ms) {
|
|
94
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
|
+
}
|
|
96
|
+
/** Track an in-flight request and remove it when done. */
|
|
97
|
+
track(promise) {
|
|
98
|
+
this.inFlightRequests.add(promise);
|
|
99
|
+
promise.finally(() => this.inFlightRequests.delete(promise));
|
|
100
|
+
}
|
|
101
|
+
async sendLogs(logs) {
|
|
102
|
+
const p = this.sendWithRetry(
|
|
103
|
+
`${this.config.endpoint}/ingest/logs`,
|
|
104
|
+
{
|
|
105
|
+
product: this.config.product,
|
|
106
|
+
service: this.config.service,
|
|
107
|
+
environment: this.config.environment,
|
|
108
|
+
logs
|
|
109
|
+
},
|
|
110
|
+
"logs"
|
|
111
|
+
);
|
|
112
|
+
this.track(p);
|
|
113
|
+
return p;
|
|
114
|
+
}
|
|
115
|
+
async sendError(error) {
|
|
116
|
+
const p = this.sendWithRetry(
|
|
117
|
+
`${this.config.endpoint}/ingest/errors`,
|
|
118
|
+
{
|
|
119
|
+
...error,
|
|
120
|
+
product: this.config.product,
|
|
121
|
+
service: this.config.service,
|
|
122
|
+
environment: this.config.environment
|
|
123
|
+
},
|
|
124
|
+
"error"
|
|
125
|
+
);
|
|
126
|
+
this.track(p);
|
|
127
|
+
return p;
|
|
128
|
+
}
|
|
129
|
+
async sendTrace(span) {
|
|
130
|
+
const p = this.sendWithRetry(
|
|
131
|
+
`${this.config.endpoint}/ingest/traces`,
|
|
132
|
+
{
|
|
133
|
+
...span,
|
|
134
|
+
product: this.config.product,
|
|
135
|
+
service: this.config.service,
|
|
136
|
+
environment: this.config.environment
|
|
137
|
+
},
|
|
138
|
+
"trace"
|
|
139
|
+
);
|
|
140
|
+
this.track(p);
|
|
141
|
+
return p;
|
|
142
|
+
}
|
|
143
|
+
async sendLLMUsage(report) {
|
|
144
|
+
const p = this.sendWithRetry(
|
|
145
|
+
`${this.config.endpoint}/ingest/llm`,
|
|
146
|
+
{
|
|
147
|
+
...report,
|
|
148
|
+
product: this.config.product,
|
|
149
|
+
service: this.config.service,
|
|
150
|
+
environment: this.config.environment
|
|
151
|
+
},
|
|
152
|
+
"LLM usage"
|
|
153
|
+
);
|
|
154
|
+
this.track(p);
|
|
155
|
+
return p;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Wait for all in-flight requests to complete, with a timeout.
|
|
159
|
+
* Used by `logger.destroy()` to ensure data is sent before process exit.
|
|
160
|
+
*
|
|
161
|
+
* @param timeoutMs - Maximum time to wait (default: 2000ms)
|
|
162
|
+
*/
|
|
163
|
+
async drain(timeoutMs = 2e3) {
|
|
164
|
+
if (this.inFlightRequests.size === 0) return;
|
|
165
|
+
const allDone = Promise.all(this.inFlightRequests).then(() => {
|
|
166
|
+
});
|
|
167
|
+
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
168
|
+
await Promise.race([allDone, timeout]);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// src/state.ts
|
|
173
|
+
function createLoggerState(maxBreadcrumbs) {
|
|
174
|
+
return {
|
|
175
|
+
user: null,
|
|
176
|
+
tags: {},
|
|
177
|
+
contexts: {},
|
|
178
|
+
breadcrumbs: [],
|
|
179
|
+
maxBreadcrumbs
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function addBreadcrumb(state, breadcrumb) {
|
|
183
|
+
state.breadcrumbs.push(breadcrumb);
|
|
184
|
+
if (state.breadcrumbs.length > state.maxBreadcrumbs) {
|
|
185
|
+
state.breadcrumbs.shift();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/logger.ts
|
|
190
|
+
function generateId() {
|
|
191
|
+
const bytes = new Uint8Array(8);
|
|
192
|
+
crypto.getRandomValues(bytes);
|
|
193
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
194
|
+
}
|
|
195
|
+
var _originalConsole = {
|
|
196
|
+
log: console.log,
|
|
197
|
+
info: console.info,
|
|
198
|
+
warn: console.warn,
|
|
199
|
+
error: console.error,
|
|
200
|
+
debug: console.debug
|
|
201
|
+
};
|
|
202
|
+
var Logger = class _Logger {
|
|
203
|
+
batcher;
|
|
204
|
+
transport;
|
|
205
|
+
contextName;
|
|
206
|
+
config;
|
|
207
|
+
state;
|
|
208
|
+
requestMeta;
|
|
209
|
+
constructor(config, contextName, requestMeta, state) {
|
|
210
|
+
this.config = config;
|
|
211
|
+
this.contextName = contextName;
|
|
212
|
+
this.requestMeta = requestMeta;
|
|
213
|
+
this.state = state ?? createLoggerState(config.maxBreadcrumbs ?? 20);
|
|
214
|
+
this.transport = new Transport(config);
|
|
215
|
+
this.batcher = new Batcher(
|
|
216
|
+
{ batchSize: config.batchSize, flushIntervalMs: config.flushIntervalMs },
|
|
217
|
+
(entries) => {
|
|
218
|
+
this.transport.sendLogs(entries);
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// User context
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
/**
|
|
226
|
+
* Set the current user context. Attached to all subsequent logs, errors, spans, and LLM reports.
|
|
227
|
+
* Shared across all child loggers (withContext, forRequest).
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* logger.setUser({ id: "u_123", email: "user@example.com", plan: "pro" })
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
setUser(user) {
|
|
235
|
+
this.state.user = user;
|
|
236
|
+
}
|
|
237
|
+
/** Clear the current user context. */
|
|
238
|
+
clearUser() {
|
|
239
|
+
this.state.user = null;
|
|
240
|
+
}
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Tags & Context
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
/**
|
|
245
|
+
* Set global tags (flat string key-values). Merged into all events' metadata as `_tags`.
|
|
246
|
+
* Tags are indexed and searchable on the dashboard.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* logger.setTags({ release: "1.2.3", region: "us-east-1" })
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
setTags(tags) {
|
|
254
|
+
Object.assign(this.state.tags, tags);
|
|
255
|
+
}
|
|
256
|
+
/** Clear all global tags. */
|
|
257
|
+
clearTags() {
|
|
258
|
+
this.state.tags = {};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Set a named context block. Merged into metadata as `_contexts.{name}`.
|
|
262
|
+
* Contexts are structured objects attached for reference (not necessarily indexed).
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* logger.setContext("server", { hostname: "web-3", memory: "4gb" })
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
setContext(name, data) {
|
|
270
|
+
this.state.contexts[name] = data;
|
|
271
|
+
}
|
|
272
|
+
/** Clear a specific context block, or all contexts if no name is given. */
|
|
273
|
+
clearContext(name) {
|
|
274
|
+
if (name) {
|
|
275
|
+
delete this.state.contexts[name];
|
|
276
|
+
} else {
|
|
277
|
+
this.state.contexts = {};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Breadcrumbs
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
/**
|
|
284
|
+
* Manually add a breadcrumb to the trail.
|
|
285
|
+
* Breadcrumbs are also recorded automatically for every log, span, and error.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* logger.addBreadcrumb({ type: "http", message: "POST /api/checkout" })
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
addBreadcrumb(breadcrumb) {
|
|
293
|
+
addBreadcrumb(this.state, {
|
|
294
|
+
type: breadcrumb.type,
|
|
295
|
+
message: breadcrumb.message,
|
|
296
|
+
timestamp: breadcrumb.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// Internal helpers
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
/** Merge user, tags, and contexts from shared state into event metadata. */
|
|
303
|
+
mergeStateMetadata(metadata) {
|
|
304
|
+
const { user, tags, contexts } = this.state;
|
|
305
|
+
const hasUser = user !== null;
|
|
306
|
+
const hasTags = Object.keys(tags).length > 0;
|
|
307
|
+
const hasContexts = Object.keys(contexts).length > 0;
|
|
308
|
+
if (!hasUser && !hasTags && !hasContexts && !metadata) return void 0;
|
|
309
|
+
const result = { ...metadata };
|
|
310
|
+
if (hasUser) result.user = user;
|
|
311
|
+
if (hasTags) result._tags = { ...tags };
|
|
312
|
+
if (hasContexts) result._contexts = { ...contexts };
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
/** Run the beforeSend hook. If the hook throws, pass the event through. */
|
|
316
|
+
applyBeforeSend(event) {
|
|
317
|
+
if (!this.config.beforeSend) return event;
|
|
318
|
+
try {
|
|
319
|
+
return this.config.beforeSend(event);
|
|
320
|
+
} catch {
|
|
321
|
+
return event;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
// Logging
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
log(level, message, dataOrError, maybeError) {
|
|
328
|
+
let metadata;
|
|
329
|
+
let error;
|
|
330
|
+
if (dataOrError instanceof Error) {
|
|
331
|
+
error = dataOrError;
|
|
332
|
+
} else if (dataOrError && typeof dataOrError === "object" && !Array.isArray(dataOrError)) {
|
|
333
|
+
metadata = dataOrError;
|
|
334
|
+
error = maybeError;
|
|
335
|
+
} else if (dataOrError !== void 0) {
|
|
336
|
+
error = dataOrError;
|
|
337
|
+
}
|
|
338
|
+
if (error instanceof Error) {
|
|
339
|
+
metadata = {
|
|
340
|
+
...metadata,
|
|
341
|
+
error: {
|
|
342
|
+
message: error.message,
|
|
343
|
+
name: error.name,
|
|
344
|
+
stack: error.stack
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
metadata = this.mergeStateMetadata(metadata);
|
|
349
|
+
const entry = {
|
|
350
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
351
|
+
level,
|
|
352
|
+
message,
|
|
353
|
+
metadata,
|
|
354
|
+
context: this.contextName,
|
|
355
|
+
trace_id: this.requestMeta?.trace_id,
|
|
356
|
+
span_id: this.requestMeta?.span_id,
|
|
357
|
+
request_id: this.requestMeta?.request_id,
|
|
358
|
+
vercel_id: this.requestMeta?.vercel_id
|
|
359
|
+
};
|
|
360
|
+
const hookResult = this.applyBeforeSend({ type: "log", data: entry });
|
|
361
|
+
if (hookResult === null) return;
|
|
362
|
+
const finalEntry = hookResult.data;
|
|
363
|
+
if (this.config.debug) {
|
|
364
|
+
const prefix = this.contextName ? `[${this.contextName}]` : "";
|
|
365
|
+
const lvl = level.toUpperCase().padEnd(5);
|
|
366
|
+
const consoleFn = level === "error" ? _originalConsole.error : level === "warn" ? _originalConsole.warn : level === "debug" ? _originalConsole.debug : _originalConsole.log;
|
|
367
|
+
if (finalEntry.metadata) {
|
|
368
|
+
consoleFn(`${lvl} ${prefix} ${message}`, finalEntry.metadata);
|
|
369
|
+
} else {
|
|
370
|
+
consoleFn(`${lvl} ${prefix} ${message}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
addBreadcrumb(this.state, {
|
|
374
|
+
type: "log",
|
|
375
|
+
message: `[${level}] ${message}`,
|
|
376
|
+
timestamp: entry.timestamp
|
|
377
|
+
});
|
|
378
|
+
this.batcher.add(finalEntry);
|
|
379
|
+
}
|
|
380
|
+
/** Log a debug message. */
|
|
381
|
+
debug(message, dataOrError, error) {
|
|
382
|
+
this.log("debug", message, dataOrError, error);
|
|
383
|
+
}
|
|
384
|
+
/** Log an informational message. */
|
|
385
|
+
info(message, dataOrError, error) {
|
|
386
|
+
this.log("info", message, dataOrError, error);
|
|
387
|
+
}
|
|
388
|
+
/** Log a warning. */
|
|
389
|
+
warn(message, dataOrError, error) {
|
|
390
|
+
this.log("warn", message, dataOrError, error);
|
|
391
|
+
}
|
|
392
|
+
/** Log an error. */
|
|
393
|
+
error(message, dataOrError, error) {
|
|
394
|
+
this.log("error", message, dataOrError, error);
|
|
395
|
+
}
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// Child loggers
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
/** Create a context-scoped logger. All logs include the context name. Shares state with parent. */
|
|
400
|
+
withContext(name) {
|
|
401
|
+
return new _Logger(this.config, name, this.requestMeta, this.state);
|
|
402
|
+
}
|
|
403
|
+
/** Create a request-scoped logger that extracts trace context from headers. Shares state with parent. */
|
|
404
|
+
forRequest(request) {
|
|
405
|
+
const vercelId = request.headers.get("x-vercel-id") || void 0;
|
|
406
|
+
const requestId = request.headers.get("x-request-id") || void 0;
|
|
407
|
+
const traceId = request.headers.get("x-trace-id") || void 0;
|
|
408
|
+
const spanId = request.headers.get("x-span-id") || void 0;
|
|
409
|
+
return new _Logger(this.config, this.contextName, {
|
|
410
|
+
trace_id: traceId,
|
|
411
|
+
span_id: spanId,
|
|
412
|
+
request_id: requestId || (vercelId ? vercelId.split("::").pop() : void 0),
|
|
413
|
+
vercel_id: vercelId
|
|
414
|
+
}, this.state);
|
|
415
|
+
}
|
|
416
|
+
// ---------------------------------------------------------------------------
|
|
417
|
+
// Error capture
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
419
|
+
/**
|
|
420
|
+
* Capture and report an error immediately (not batched).
|
|
421
|
+
* Automatically attaches breadcrumbs from the buffer and user context.
|
|
422
|
+
*/
|
|
423
|
+
captureError(error, context) {
|
|
424
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
425
|
+
addBreadcrumb(this.state, {
|
|
426
|
+
type: "error",
|
|
427
|
+
message: err.message,
|
|
428
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
429
|
+
});
|
|
430
|
+
const enrichedContext = { ...context?.context };
|
|
431
|
+
if (this.state.user) enrichedContext.user = this.state.user;
|
|
432
|
+
if (Object.keys(this.state.tags).length > 0) enrichedContext._tags = { ...this.state.tags };
|
|
433
|
+
if (Object.keys(this.state.contexts).length > 0) enrichedContext._contexts = { ...this.state.contexts };
|
|
434
|
+
const report = {
|
|
435
|
+
error_message: err.message,
|
|
436
|
+
stack_trace: err.stack || "",
|
|
437
|
+
severity: context?.severity || "medium",
|
|
438
|
+
context: Object.keys(enrichedContext).length > 0 ? enrichedContext : void 0,
|
|
439
|
+
trace_id: this.requestMeta?.trace_id,
|
|
440
|
+
user_id: context?.userId || this.state.user?.id,
|
|
441
|
+
breadcrumbs: context?.breadcrumbs || [...this.state.breadcrumbs]
|
|
442
|
+
};
|
|
443
|
+
const hookResult = this.applyBeforeSend({ type: "error", data: report });
|
|
444
|
+
if (hookResult === null) return;
|
|
445
|
+
this.transport.sendError(hookResult.data);
|
|
446
|
+
}
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
// LLM usage
|
|
449
|
+
// ---------------------------------------------------------------------------
|
|
450
|
+
/** Track LLM usage. Sends to /ingest/llm and logs for visibility. */
|
|
451
|
+
llmUsage(report) {
|
|
452
|
+
const metadata = this.mergeStateMetadata(report.metadata);
|
|
453
|
+
const payload = {
|
|
454
|
+
model: report.model,
|
|
455
|
+
provider: report.provider,
|
|
456
|
+
operation: report.operation,
|
|
457
|
+
input_tokens: report.inputTokens,
|
|
458
|
+
output_tokens: report.outputTokens,
|
|
459
|
+
cost_usd: report.costUsd || 0,
|
|
460
|
+
latency_ms: report.latencyMs,
|
|
461
|
+
metadata
|
|
462
|
+
};
|
|
463
|
+
const hookResult = this.applyBeforeSend({ type: "llm", data: report });
|
|
464
|
+
if (hookResult === null) return;
|
|
465
|
+
this.transport.sendLLMUsage(payload);
|
|
466
|
+
this.log("info", `LLM call: ${report.model} (${report.operation})`, {
|
|
467
|
+
llm_usage: {
|
|
468
|
+
model: report.model,
|
|
469
|
+
provider: report.provider,
|
|
470
|
+
operation: report.operation,
|
|
471
|
+
input_tokens: report.inputTokens,
|
|
472
|
+
output_tokens: report.outputTokens,
|
|
473
|
+
latency_ms: report.latencyMs
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
// Tracing
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
/** Start a span with automatic lifecycle (callback-based, recommended). */
|
|
481
|
+
startSpan(operation, fn) {
|
|
482
|
+
const inactive = this.startInactiveSpan(operation);
|
|
483
|
+
const span = {
|
|
484
|
+
traceId: inactive.traceId,
|
|
485
|
+
spanId: inactive.spanId,
|
|
486
|
+
parentSpanId: inactive.parentSpanId,
|
|
487
|
+
operation: inactive.operation,
|
|
488
|
+
getHeaders: () => inactive.getHeaders()
|
|
489
|
+
};
|
|
490
|
+
let result;
|
|
491
|
+
try {
|
|
492
|
+
result = fn(span);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
inactive.end({ status: "error" });
|
|
495
|
+
throw err;
|
|
496
|
+
}
|
|
497
|
+
if (result instanceof Promise) {
|
|
498
|
+
return result.then(
|
|
499
|
+
(value) => {
|
|
500
|
+
inactive.end({ status: "ok" });
|
|
501
|
+
return value;
|
|
502
|
+
},
|
|
503
|
+
(err) => {
|
|
504
|
+
inactive.end({ status: "error" });
|
|
505
|
+
throw err;
|
|
506
|
+
}
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
inactive.end({ status: "ok" });
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
/** Start a span with manual lifecycle. You must call span.end(). */
|
|
513
|
+
startInactiveSpan(operation) {
|
|
514
|
+
const traceId = this.requestMeta?.trace_id || generateId();
|
|
515
|
+
const parentSpanId = this.requestMeta?.span_id || "";
|
|
516
|
+
const spanId = generateId();
|
|
517
|
+
const startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
518
|
+
const startMs = Date.now();
|
|
519
|
+
const childMeta = { ...this.requestMeta, trace_id: traceId, span_id: spanId };
|
|
520
|
+
addBreadcrumb(this.state, {
|
|
521
|
+
type: "function",
|
|
522
|
+
message: operation,
|
|
523
|
+
timestamp: startTime
|
|
524
|
+
});
|
|
525
|
+
const span = {
|
|
526
|
+
traceId,
|
|
527
|
+
spanId,
|
|
528
|
+
parentSpanId,
|
|
529
|
+
operation,
|
|
530
|
+
end: (options) => {
|
|
531
|
+
const durationMs = Date.now() - startMs;
|
|
532
|
+
const spanData = {
|
|
533
|
+
trace_id: traceId,
|
|
534
|
+
span_id: spanId,
|
|
535
|
+
parent_span_id: parentSpanId,
|
|
536
|
+
operation,
|
|
537
|
+
start_time: startTime,
|
|
538
|
+
duration_ms: durationMs,
|
|
539
|
+
status: options?.status || "ok",
|
|
540
|
+
metadata: this.mergeStateMetadata(options?.metadata)
|
|
541
|
+
};
|
|
542
|
+
const hookResult = this.applyBeforeSend({ type: "trace", data: spanData });
|
|
543
|
+
if (hookResult === null) return;
|
|
544
|
+
this.transport.sendTrace(hookResult.data);
|
|
545
|
+
},
|
|
546
|
+
startSpan: (childOp, fn) => {
|
|
547
|
+
const childLogger = new _Logger(this.config, this.contextName, childMeta, this.state);
|
|
548
|
+
return childLogger.startSpan(childOp, fn);
|
|
549
|
+
},
|
|
550
|
+
startInactiveSpan: (childOp) => {
|
|
551
|
+
const childLogger = new _Logger(this.config, this.contextName, childMeta, this.state);
|
|
552
|
+
return childLogger.startInactiveSpan(childOp);
|
|
553
|
+
},
|
|
554
|
+
getHeaders: () => ({
|
|
555
|
+
"x-trace-id": traceId,
|
|
556
|
+
"x-span-id": spanId
|
|
557
|
+
})
|
|
558
|
+
};
|
|
559
|
+
return span;
|
|
560
|
+
}
|
|
561
|
+
/** Wrap a function with automatic tracing and error capture. */
|
|
562
|
+
wrap(operation, fn) {
|
|
563
|
+
return (...args) => {
|
|
564
|
+
return this.startSpan(operation, () => fn(...args));
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
568
|
+
// Lifecycle
|
|
569
|
+
// ---------------------------------------------------------------------------
|
|
570
|
+
/** Immediately flush all batched log entries. */
|
|
571
|
+
flush() {
|
|
572
|
+
this.batcher.flush();
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Stop the batch timer, flush remaining logs, and wait for in-flight requests.
|
|
576
|
+
*
|
|
577
|
+
* @param timeoutMs - Max time to wait for in-flight requests (default: 2000ms)
|
|
578
|
+
* @returns Promise that resolves when all data is sent or timeout is reached
|
|
579
|
+
*
|
|
580
|
+
* @example
|
|
581
|
+
* ```ts
|
|
582
|
+
* await logger.destroy()
|
|
583
|
+
* process.exit(0) // safe — data is confirmed sent
|
|
584
|
+
* ```
|
|
585
|
+
*/
|
|
586
|
+
async destroy(timeoutMs) {
|
|
587
|
+
await this.batcher.destroy();
|
|
588
|
+
await this.transport.drain(timeoutMs);
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
function createLogger(config) {
|
|
592
|
+
return new Logger(config);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export {
|
|
596
|
+
SDK_VERSION,
|
|
597
|
+
SDK_NAME,
|
|
598
|
+
_originalConsole,
|
|
599
|
+
Logger,
|
|
600
|
+
createLogger
|
|
601
|
+
};
|