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