@deeptracer/core 0.3.1 → 0.4.2

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/index.cjs CHANGED
@@ -62,21 +62,51 @@ var Batcher = class {
62
62
  };
63
63
 
64
64
  // src/version.ts
65
- var SDK_VERSION = "0.3.1";
65
+ var SDK_VERSION = "0.4.2";
66
66
  var SDK_NAME = "core";
67
67
 
68
68
  // src/transport.ts
69
69
  var Transport = class {
70
70
  constructor(config) {
71
71
  this.config = config;
72
+ const hasKey = !!(config.secretKey || config.publicKey);
73
+ const hasEndpoint = !!config.endpoint;
74
+ this.disabled = !hasKey || !hasEndpoint;
75
+ if (config.secretKey?.startsWith("dt_secret_") && typeof globalThis.window !== "undefined") {
76
+ console.error(
77
+ "[@deeptracer/core] WARNING: `secretKey` (dt_secret_...) detected in a browser bundle. This exposes your server key to end users. Use `publicKey` (dt_public_...) for client-side code."
78
+ );
79
+ }
72
80
  }
73
81
  inFlightRequests = /* @__PURE__ */ new Set();
82
+ /**
83
+ * When true, all send methods become silent no-ops.
84
+ * Set automatically when no auth key or no endpoint is configured.
85
+ * This prevents pointless network requests and console noise during
86
+ * local development without API keys.
87
+ */
88
+ disabled;
89
+ /**
90
+ * Tracks which send types (logs, error, trace, LLM usage) have already
91
+ * logged a failure warning. After the first failure for a given type,
92
+ * subsequent failures are silently dropped to prevent console spam
93
+ * (e.g., when the ingestion endpoint is unreachable during development).
94
+ */
95
+ warnedLabels = /* @__PURE__ */ new Set();
96
+ /** Resolve the auth key: prefer secretKey (server), fall back to publicKey (client). */
97
+ get authKey() {
98
+ return this.config.secretKey ?? this.config.publicKey ?? "";
99
+ }
74
100
  /**
75
101
  * Send a request with automatic retry and exponential backoff.
76
102
  * Retries up to `maxRetries` times on network errors and 5xx responses.
77
103
  * Does NOT retry on 4xx (client errors — bad payload, auth failure, etc.).
104
+ *
105
+ * After the first total failure for a given label, subsequent failures
106
+ * are silently dropped (no more console warnings).
78
107
  */
79
108
  async sendWithRetry(url, body, label, maxRetries = 3) {
109
+ if (this.disabled) return;
80
110
  const baseDelays = [1e3, 2e3, 4e3];
81
111
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
82
112
  try {
@@ -84,33 +114,45 @@ var Transport = class {
84
114
  method: "POST",
85
115
  headers: {
86
116
  "Content-Type": "application/json",
87
- Authorization: `Bearer ${this.config.apiKey}`,
117
+ Authorization: `Bearer ${this.authKey}`,
88
118
  "x-deeptracer-sdk": `${SDK_NAME}/${SDK_VERSION}`
89
119
  },
90
120
  body: JSON.stringify(body)
91
121
  });
92
- if (res.ok) return;
122
+ if (res.ok) {
123
+ this.warnedLabels.delete(label);
124
+ return;
125
+ }
93
126
  if (res.status >= 400 && res.status < 500) {
94
- console.warn(
95
- `[@deeptracer/core] Failed to send ${label}: ${res.status} ${res.statusText}`
96
- );
127
+ if (!this.warnedLabels.has(label)) {
128
+ this.warnedLabels.add(label);
129
+ console.warn(
130
+ `[@deeptracer/core] Failed to send ${label}: ${res.status} ${res.statusText}`
131
+ );
132
+ }
97
133
  return;
98
134
  }
99
135
  if (attempt < maxRetries) {
100
136
  await this.sleep(this.jitter(baseDelays[attempt]));
101
137
  continue;
102
138
  }
103
- console.warn(
104
- `[@deeptracer/core] Failed to send ${label}: ${res.status} ${res.statusText} (exhausted ${maxRetries} retries)`
105
- );
139
+ if (!this.warnedLabels.has(label)) {
140
+ this.warnedLabels.add(label);
141
+ console.warn(
142
+ `[@deeptracer/core] Failed to send ${label}: ${res.status} ${res.statusText} (exhausted ${maxRetries} retries). Suppressing further warnings.`
143
+ );
144
+ }
106
145
  } catch {
107
146
  if (attempt < maxRetries) {
108
147
  await this.sleep(this.jitter(baseDelays[attempt]));
109
148
  continue;
110
149
  }
111
- console.warn(
112
- `[@deeptracer/core] Failed to send ${label} (exhausted ${maxRetries} retries)`
113
- );
150
+ if (!this.warnedLabels.has(label)) {
151
+ this.warnedLabels.add(label);
152
+ console.warn(
153
+ `[@deeptracer/core] Failed to send ${label} (exhausted ${maxRetries} retries). Suppressing further warnings.`
154
+ );
155
+ }
114
156
  }
115
157
  }
116
158
  }
@@ -131,7 +173,6 @@ var Transport = class {
131
173
  const p = this.sendWithRetry(
132
174
  `${this.config.endpoint}/ingest/logs`,
133
175
  {
134
- product: this.config.product,
135
176
  service: this.config.service,
136
177
  environment: this.config.environment,
137
178
  logs
@@ -146,7 +187,6 @@ var Transport = class {
146
187
  `${this.config.endpoint}/ingest/errors`,
147
188
  {
148
189
  ...error,
149
- product: this.config.product,
150
190
  service: this.config.service,
151
191
  environment: this.config.environment
152
192
  },
@@ -160,7 +200,6 @@ var Transport = class {
160
200
  `${this.config.endpoint}/ingest/traces`,
161
201
  {
162
202
  ...span,
163
- product: this.config.product,
164
203
  service: this.config.service,
165
204
  environment: this.config.environment
166
205
  },
@@ -174,7 +213,6 @@ var Transport = class {
174
213
  `${this.config.endpoint}/ingest/llm`,
175
214
  {
176
215
  ...report,
177
- product: this.config.product,
178
216
  service: this.config.service,
179
217
  environment: this.config.environment
180
218
  },
@@ -208,6 +246,15 @@ function createLoggerState(maxBreadcrumbs) {
208
246
  maxBreadcrumbs
209
247
  };
210
248
  }
249
+ function cloneState(state) {
250
+ return {
251
+ user: state.user ? { ...state.user } : null,
252
+ tags: { ...state.tags },
253
+ contexts: Object.fromEntries(Object.entries(state.contexts).map(([k, v]) => [k, { ...v }])),
254
+ breadcrumbs: [...state.breadcrumbs],
255
+ maxBreadcrumbs: state.maxBreadcrumbs
256
+ };
257
+ }
211
258
  function addBreadcrumb(state, breadcrumb) {
212
259
  state.breadcrumbs.push(breadcrumb);
213
260
  if (state.breadcrumbs.length > state.maxBreadcrumbs) {
@@ -216,11 +263,33 @@ function addBreadcrumb(state, breadcrumb) {
216
263
  }
217
264
 
218
265
  // src/logger.ts
266
+ var LOG_LEVEL_VALUES = {
267
+ debug: 0,
268
+ info: 1,
269
+ warn: 2,
270
+ error: 3
271
+ };
219
272
  function generateId() {
220
273
  const bytes = new Uint8Array(8);
221
274
  crypto.getRandomValues(bytes);
222
275
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
223
276
  }
277
+ function generateTraceId() {
278
+ const bytes = new Uint8Array(16);
279
+ crypto.getRandomValues(bytes);
280
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
281
+ }
282
+ function parseTraceparent(header) {
283
+ const parts = header.trim().split("-");
284
+ if (parts.length !== 4) return null;
285
+ const [version, traceId, parentId, flags] = parts;
286
+ if (version !== "00") return null;
287
+ if (traceId.length !== 32 || !/^[0-9a-f]{32}$/.test(traceId)) return null;
288
+ if (parentId.length !== 16 || !/^[0-9a-f]{16}$/.test(parentId)) return null;
289
+ if (flags.length !== 2 || !/^[0-9a-f]{2}$/.test(flags)) return null;
290
+ if (/^0+$/.test(traceId) || /^0+$/.test(parentId)) return null;
291
+ return { traceId, parentId, flags };
292
+ }
224
293
  var _originalConsole = {
225
294
  log: console.log,
226
295
  info: console.info,
@@ -231,6 +300,7 @@ var _originalConsole = {
231
300
  var Logger = class _Logger {
232
301
  batcher;
233
302
  transport;
303
+ effectiveLevel;
234
304
  contextName;
235
305
  config;
236
306
  state;
@@ -240,6 +310,20 @@ var Logger = class _Logger {
240
310
  this.contextName = contextName;
241
311
  this.requestMeta = requestMeta;
242
312
  this.state = state ?? createLoggerState(config.maxBreadcrumbs ?? 20);
313
+ const hasKey = !!(config.secretKey || config.publicKey);
314
+ const hasEndpoint = !!config.endpoint;
315
+ if (!hasKey && !hasEndpoint) {
316
+ _originalConsole.warn(
317
+ "[@deeptracer/core] No API key or endpoint configured. Running in local-only mode (logging methods work, but events are not sent). Set DEEPTRACER_SECRET_KEY and DEEPTRACER_ENDPOINT to enable."
318
+ );
319
+ } else if (!hasKey) {
320
+ _originalConsole.warn(
321
+ "[@deeptracer/core] No `secretKey` or `publicKey` provided. Events will not be sent."
322
+ );
323
+ } else if (!hasEndpoint) {
324
+ _originalConsole.warn("[@deeptracer/core] No `endpoint` provided. Events will not be sent.");
325
+ }
326
+ this.effectiveLevel = LOG_LEVEL_VALUES[config.level ?? (config.environment === "production" ? "info" : "debug")];
243
327
  this.transport = new Transport(config);
244
328
  this.batcher = new Batcher(
245
329
  { batchSize: config.batchSize, flushIntervalMs: config.flushIntervalMs },
@@ -253,7 +337,8 @@ var Logger = class _Logger {
253
337
  // ---------------------------------------------------------------------------
254
338
  /**
255
339
  * Set the current user context. Attached to all subsequent logs, errors, spans, and LLM reports.
256
- * Shared across all child loggers (withContext, forRequest).
340
+ * Only affects this logger instance — child loggers created via `withContext()` or `forRequest()`
341
+ * have their own independent state.
257
342
  *
258
343
  * @example
259
344
  * ```ts
@@ -375,26 +460,13 @@ var Logger = class _Logger {
375
460
  };
376
461
  }
377
462
  metadata = this.mergeStateMetadata(metadata);
378
- const entry = {
379
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
380
- level,
381
- message,
382
- metadata,
383
- context: this.contextName,
384
- trace_id: this.requestMeta?.trace_id,
385
- span_id: this.requestMeta?.span_id,
386
- request_id: this.requestMeta?.request_id,
387
- vercel_id: this.requestMeta?.vercel_id
388
- };
389
- const hookResult = this.applyBeforeSend({ type: "log", data: entry });
390
- if (hookResult === null) return;
391
- const finalEntry = hookResult.data;
463
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
392
464
  if (this.config.debug) {
393
465
  const prefix = this.contextName ? `[${this.contextName}]` : "";
394
466
  const lvl = level.toUpperCase().padEnd(5);
395
467
  const consoleFn = level === "error" ? _originalConsole.error : level === "warn" ? _originalConsole.warn : level === "debug" ? _originalConsole.debug : _originalConsole.log;
396
- if (finalEntry.metadata) {
397
- consoleFn(`${lvl} ${prefix} ${message}`, finalEntry.metadata);
468
+ if (metadata) {
469
+ consoleFn(`${lvl} ${prefix} ${message}`, metadata);
398
470
  } else {
399
471
  consoleFn(`${lvl} ${prefix} ${message}`);
400
472
  }
@@ -402,9 +474,23 @@ var Logger = class _Logger {
402
474
  addBreadcrumb(this.state, {
403
475
  type: "log",
404
476
  message: `[${level}] ${message}`,
405
- timestamp: entry.timestamp
477
+ timestamp
406
478
  });
407
- this.batcher.add(finalEntry);
479
+ if (LOG_LEVEL_VALUES[level] < this.effectiveLevel) return;
480
+ const entry = {
481
+ timestamp,
482
+ level,
483
+ message,
484
+ metadata,
485
+ context: this.contextName,
486
+ trace_id: this.requestMeta?.trace_id,
487
+ span_id: this.requestMeta?.span_id,
488
+ request_id: this.requestMeta?.request_id,
489
+ vercel_id: this.requestMeta?.vercel_id
490
+ };
491
+ const hookResult = this.applyBeforeSend({ type: "log", data: entry });
492
+ if (hookResult === null) return;
493
+ this.batcher.add(hookResult.data);
408
494
  }
409
495
  /** Log a debug message. */
410
496
  debug(message, dataOrError, error) {
@@ -425,22 +511,37 @@ var Logger = class _Logger {
425
511
  // ---------------------------------------------------------------------------
426
512
  // Child loggers
427
513
  // ---------------------------------------------------------------------------
428
- /** Create a context-scoped logger. All logs include the context name. Shares state with parent. */
514
+ /** Create a context-scoped logger. All logs include the context name. Gets an independent copy of state. */
429
515
  withContext(name) {
430
- return new _Logger(this.config, name, this.requestMeta, this.state);
516
+ return new _Logger(this.config, name, this.requestMeta, cloneState(this.state));
431
517
  }
432
- /** Create a request-scoped logger that extracts trace context from headers. Shares state with parent. */
518
+ /** Create a request-scoped logger that extracts trace context from headers. Gets an independent copy of state. */
433
519
  forRequest(request) {
434
- const vercelId = request.headers.get("x-vercel-id") || void 0;
520
+ let traceId;
521
+ let spanId;
522
+ const traceparent = request.headers.get("traceparent");
523
+ if (traceparent) {
524
+ const parsed = parseTraceparent(traceparent);
525
+ if (parsed) {
526
+ traceId = parsed.traceId;
527
+ spanId = parsed.parentId;
528
+ }
529
+ }
530
+ traceId = traceId || request.headers.get("x-trace-id") || void 0;
531
+ spanId = spanId || request.headers.get("x-span-id") || void 0;
435
532
  const requestId = request.headers.get("x-request-id") || void 0;
436
- const traceId = request.headers.get("x-trace-id") || void 0;
437
- const spanId = request.headers.get("x-span-id") || void 0;
438
- return new _Logger(this.config, this.contextName, {
439
- trace_id: traceId,
440
- span_id: spanId,
441
- request_id: requestId || (vercelId ? vercelId.split("::").pop() : void 0),
442
- vercel_id: vercelId
443
- }, this.state);
533
+ const vercelId = request.headers.get("x-vercel-id") || void 0;
534
+ return new _Logger(
535
+ this.config,
536
+ this.contextName,
537
+ {
538
+ trace_id: traceId,
539
+ span_id: spanId,
540
+ request_id: requestId || (vercelId ? vercelId.split("::").pop() : void 0),
541
+ vercel_id: vercelId
542
+ },
543
+ cloneState(this.state)
544
+ );
444
545
  }
445
546
  // ---------------------------------------------------------------------------
446
547
  // Error capture
@@ -459,7 +560,8 @@ var Logger = class _Logger {
459
560
  const enrichedContext = { ...context?.context };
460
561
  if (this.state.user) enrichedContext.user = this.state.user;
461
562
  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 };
563
+ if (Object.keys(this.state.contexts).length > 0)
564
+ enrichedContext._contexts = { ...this.state.contexts };
463
565
  const report = {
464
566
  error_message: err.message,
465
567
  stack_trace: err.stack || "",
@@ -540,7 +642,7 @@ var Logger = class _Logger {
540
642
  }
541
643
  /** Start a span with manual lifecycle. You must call span.end(). */
542
644
  startInactiveSpan(operation) {
543
- const traceId = this.requestMeta?.trace_id || generateId();
645
+ const traceId = this.requestMeta?.trace_id || generateTraceId();
544
646
  const parentSpanId = this.requestMeta?.span_id || "";
545
647
  const spanId = generateId();
546
648
  const startTime = (/* @__PURE__ */ new Date()).toISOString();
@@ -580,10 +682,16 @@ var Logger = class _Logger {
580
682
  const childLogger = new _Logger(this.config, this.contextName, childMeta, this.state);
581
683
  return childLogger.startInactiveSpan(childOp);
582
684
  },
583
- getHeaders: () => ({
584
- "x-trace-id": traceId,
585
- "x-span-id": spanId
586
- })
685
+ getHeaders: () => {
686
+ const headers = {
687
+ "x-trace-id": traceId,
688
+ "x-span-id": spanId
689
+ };
690
+ if (/^[0-9a-f]{32}$/.test(traceId)) {
691
+ headers.traceparent = `00-${traceId}-${spanId}-01`;
692
+ }
693
+ return headers;
694
+ }
587
695
  };
588
696
  return span;
589
697
  }
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- export { B as BeforeSendEvent, a as Breadcrumb, E as ErrorReport, I as InactiveSpan, L as LLMUsageReport, b as LogEntry, c as LogLevel, d as Logger, e as LoggerConfig, M as MiddlewareOptions, S as Span, f as SpanData, U as User, g as createLogger } from './internal-DdKQRgCs.cjs';
1
+ export { B as BeforeSendEvent, a as Breadcrumb, E as ErrorReport, I as InactiveSpan, L as LLMUsageReport, b as LogEntry, c as LogLevel, d as Logger, e as LoggerConfig, M as MiddlewareOptions, S as Span, f as SpanData, U as User, g as createLogger } from './logger-BDTEt7Gi.cjs';
2
2
 
3
3
  /** SDK version. Update on each release. */
4
- declare const SDK_VERSION = "0.3.1";
4
+ declare const SDK_VERSION = "0.4.2";
5
5
  declare const SDK_NAME = "core";
6
6
 
7
7
  export { SDK_NAME, SDK_VERSION };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { B as BeforeSendEvent, a as Breadcrumb, E as ErrorReport, I as InactiveSpan, L as LLMUsageReport, b as LogEntry, c as LogLevel, d as Logger, e as LoggerConfig, M as MiddlewareOptions, S as Span, f as SpanData, U as User, g as createLogger } from './internal-DdKQRgCs.js';
1
+ export { B as BeforeSendEvent, a as Breadcrumb, E as ErrorReport, I as InactiveSpan, L as LLMUsageReport, b as LogEntry, c as LogLevel, d as Logger, e as LoggerConfig, M as MiddlewareOptions, S as Span, f as SpanData, U as User, g as createLogger } from './logger-BDTEt7Gi.js';
2
2
 
3
3
  /** SDK version. Update on each release. */
4
- declare const SDK_VERSION = "0.3.1";
4
+ declare const SDK_VERSION = "0.4.2";
5
5
  declare const SDK_NAME = "core";
6
6
 
7
7
  export { SDK_NAME, SDK_VERSION };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  SDK_NAME,
4
4
  SDK_VERSION,
5
5
  createLogger
6
- } from "./chunk-XVDM75HZ.js";
6
+ } from "./chunk-IWPXOBZV.js";
7
7
  export {
8
8
  Logger,
9
9
  SDK_NAME,