@obtrace/browser 2.0.1 → 2.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 CHANGED
@@ -14,17 +14,19 @@ bun add @obtrace/sdk-browser
14
14
 
15
15
  ### Simplified setup
16
16
 
17
- The API key resolves `tenant_id`, `project_id`, `app_id`, and `env` automatically on the server side, so only three fields are needed:
17
+ The API key resolves `tenant_id`, `project_id`, `app_id`, and `env` automatically on the server side. `serviceName` must match the connected app name in the project, or an explicit alias configured for that app.
18
18
 
19
19
  ```ts
20
20
  import { initBrowserSDK } from "@obtrace/sdk-browser/browser";
21
21
 
22
22
  const sdk = initBrowserSDK({
23
23
  apiKey: "obt_live_...",
24
- serviceName: "web-app",
24
+ serviceName: "core",
25
25
  });
26
26
  ```
27
27
 
28
+ If the project app is `core` and the dashboard defines `web` as an alias, `serviceName: "web"` is accepted and normalized to `core` on ingest. Arbitrary names are rejected.
29
+
28
30
  ### Full configuration
29
31
 
30
32
  For advanced use cases you can override the resolved values explicitly:
@@ -34,10 +36,10 @@ import { initBrowserSDK, SemanticMetrics } from "@obtrace/sdk-browser/browser";
34
36
 
35
37
  const sdk = initBrowserSDK({
36
38
  apiKey: "<API_KEY>",
37
- serviceName: "web-app",
39
+ serviceName: "core",
38
40
  tenantId: "tenant-prod",
39
41
  projectId: "project-prod",
40
- appId: "web",
42
+ appId: "core",
41
43
  env: "prod"
42
44
  });
43
45
 
@@ -20,32 +20,43 @@ export function installConsoleCapture(tracer, sessionId) {
20
20
  const level = LEVEL_MAP[method];
21
21
  console[method] = (...args) => {
22
22
  original(...args);
23
- let message;
24
- let attrs = {};
25
- if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && !Array.isArray(args[0])) {
26
- const obj = args[0];
27
- message = String(obj.msg || obj.message || JSON.stringify(obj));
28
- for (const [k, v] of Object.entries(obj)) {
29
- if (k === "msg" || k === "message")
30
- continue;
31
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
32
- attrs[k] = v;
23
+ try {
24
+ let message;
25
+ let attrs = {};
26
+ const safeStringify = (v) => {
27
+ try {
28
+ return JSON.stringify(v);
29
+ }
30
+ catch {
31
+ return String(v);
32
+ }
33
+ };
34
+ if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && !Array.isArray(args[0])) {
35
+ const obj = args[0];
36
+ message = String(obj.msg || obj.message || safeStringify(obj));
37
+ for (const [k, v] of Object.entries(obj)) {
38
+ if (k === "msg" || k === "message")
39
+ continue;
40
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
41
+ attrs[k] = v;
42
+ }
33
43
  }
34
44
  }
45
+ else {
46
+ message = args.map(a => typeof a === "string" ? a : safeStringify(a)).join(" ");
47
+ }
48
+ addBreadcrumb({ timestamp: Date.now(), category: `console.${method}`, message, level, data: attrs });
49
+ const span = tracer.startSpan("browser.console", {
50
+ attributes: {
51
+ "log.severity": level.toUpperCase(),
52
+ "log.message": message.slice(0, 1024),
53
+ "session.id": sessionId,
54
+ ...attrs,
55
+ },
56
+ });
57
+ span.end();
35
58
  }
36
- else {
37
- message = args.map(a => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
38
- }
39
- addBreadcrumb({ timestamp: Date.now(), category: `console.${method}`, message, level, data: attrs });
40
- const span = tracer.startSpan("browser.console", {
41
- attributes: {
42
- "log.severity": level.toUpperCase(),
43
- "log.message": message.slice(0, 1024),
44
- "session.id": sessionId,
45
- ...attrs,
46
- },
47
- });
48
- span.end();
59
+ catch { }
49
60
  };
50
61
  }
51
62
  return () => {
@@ -6,41 +6,47 @@ export function installBrowserErrorHooks(tracer, sessionId) {
6
6
  }
7
7
  const onError = (ev) => {
8
8
  addBreadcrumb({ timestamp: Date.now(), category: "error", message: ev.message || "window.error", level: "error" });
9
- const breadcrumbs = getBreadcrumbs();
10
- const span = tracer.startSpan("browser.error", {
11
- attributes: {
12
- "error.message": ev.message || "window.error",
13
- "error.file": ev.filename || "",
14
- "error.line": ev.lineno || 0,
15
- "error.column": ev.colno || 0,
16
- "breadcrumbs.count": breadcrumbs.length,
17
- "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
18
- ...(sessionId ? { "session.id": sessionId } : {}),
19
- },
20
- });
21
- span.setStatus({ code: SpanStatusCode.ERROR, message: ev.message });
22
- if (ev.error instanceof Error) {
23
- span.recordException(ev.error);
9
+ try {
10
+ const breadcrumbs = getBreadcrumbs();
11
+ const span = tracer.startSpan("browser.error", {
12
+ attributes: {
13
+ "error.message": ev.message || "window.error",
14
+ "error.file": ev.filename || "",
15
+ "error.line": ev.lineno || 0,
16
+ "error.column": ev.colno || 0,
17
+ "breadcrumbs.count": breadcrumbs.length,
18
+ "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
19
+ ...(sessionId ? { "session.id": sessionId } : {}),
20
+ },
21
+ });
22
+ span.setStatus({ code: SpanStatusCode.ERROR, message: ev.message });
23
+ if (ev.error instanceof Error) {
24
+ span.recordException(ev.error);
25
+ }
26
+ span.end();
24
27
  }
25
- span.end();
28
+ catch { }
26
29
  };
27
30
  const onRejection = (ev) => {
28
31
  const reason = typeof ev.reason === "string" ? ev.reason : JSON.stringify(ev.reason ?? {});
29
32
  addBreadcrumb({ timestamp: Date.now(), category: "error", message: reason, level: "error" });
30
- const breadcrumbs = getBreadcrumbs();
31
- const span = tracer.startSpan("browser.unhandledrejection", {
32
- attributes: {
33
- "error.message": reason,
34
- "breadcrumbs.count": breadcrumbs.length,
35
- "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
36
- ...(sessionId ? { "session.id": sessionId } : {}),
37
- },
38
- });
39
- span.setStatus({ code: SpanStatusCode.ERROR, message: reason });
40
- if (ev.reason instanceof Error) {
41
- span.recordException(ev.reason);
33
+ try {
34
+ const breadcrumbs = getBreadcrumbs();
35
+ const span = tracer.startSpan("browser.unhandledrejection", {
36
+ attributes: {
37
+ "error.message": reason,
38
+ "breadcrumbs.count": breadcrumbs.length,
39
+ "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
40
+ ...(sessionId ? { "session.id": sessionId } : {}),
41
+ },
42
+ });
43
+ span.setStatus({ code: SpanStatusCode.ERROR, message: reason });
44
+ if (ev.reason instanceof Error) {
45
+ span.recordException(ev.reason);
46
+ }
47
+ span.end();
42
48
  }
43
- span.end();
49
+ catch { }
44
50
  };
45
51
  window.addEventListener("error", onError);
46
52
  window.addEventListener("unhandledrejection", onRejection);
@@ -245,9 +245,16 @@ export function initBrowserSDK(config) {
245
245
  pendingBeaconBlob = null;
246
246
  }
247
247
  };
248
- const onVisibility = () => { if (document.visibilityState === "hidden")
249
- flushReplay(); };
250
- const onBeforeUnload = () => sendViaBeacon();
248
+ const onVisibility = () => {
249
+ if (document.visibilityState === "hidden") {
250
+ otel.forceFlush();
251
+ flushReplay();
252
+ }
253
+ };
254
+ const onBeforeUnload = () => {
255
+ otel.forceFlush();
256
+ sendViaBeacon();
257
+ };
251
258
  if (typeof document !== "undefined") {
252
259
  document.addEventListener("visibilitychange", onVisibility);
253
260
  cleanups.push(() => document.removeEventListener("visibilitychange", onVisibility));
@@ -27840,13 +27840,24 @@ function setupOtelWeb(config) {
27840
27840
  tracerProvider,
27841
27841
  instrumentations
27842
27842
  });
27843
- const tracer = trace.getTracer("@obtrace/sdk-browser", "1.1.0");
27844
- const meter = metrics.getMeter("@obtrace/sdk-browser", "1.1.0");
27843
+ const tracer = trace.getTracer("@obtrace/sdk-browser", "2.1.0");
27844
+ const meter = metrics.getMeter("@obtrace/sdk-browser", "2.1.0");
27845
+ const forceFlush = async () => {
27846
+ try {
27847
+ await tracerProvider.forceFlush();
27848
+ } catch {
27849
+ }
27850
+ try {
27851
+ await meterProvider.forceFlush();
27852
+ } catch {
27853
+ }
27854
+ };
27845
27855
  const shutdown = async () => {
27856
+ await forceFlush();
27846
27857
  await tracerProvider.shutdown();
27847
27858
  await meterProvider.shutdown();
27848
27859
  };
27849
- return { tracer, meter, shutdown };
27860
+ return { tracer, meter, shutdown, forceFlush };
27850
27861
  }
27851
27862
 
27852
27863
  // src/browser/breadcrumbs.ts
@@ -27894,41 +27905,47 @@ function installBrowserErrorHooks(tracer, sessionId) {
27894
27905
  }
27895
27906
  const onError = (ev) => {
27896
27907
  addBreadcrumb({ timestamp: Date.now(), category: "error", message: ev.message || "window.error", level: "error" });
27897
- const breadcrumbs = getBreadcrumbs();
27898
- const span = tracer.startSpan("browser.error", {
27899
- attributes: {
27900
- "error.message": ev.message || "window.error",
27901
- "error.file": ev.filename || "",
27902
- "error.line": ev.lineno || 0,
27903
- "error.column": ev.colno || 0,
27904
- "breadcrumbs.count": breadcrumbs.length,
27905
- "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
27906
- ...sessionId ? { "session.id": sessionId } : {}
27908
+ try {
27909
+ const breadcrumbs = getBreadcrumbs();
27910
+ const span = tracer.startSpan("browser.error", {
27911
+ attributes: {
27912
+ "error.message": ev.message || "window.error",
27913
+ "error.file": ev.filename || "",
27914
+ "error.line": ev.lineno || 0,
27915
+ "error.column": ev.colno || 0,
27916
+ "breadcrumbs.count": breadcrumbs.length,
27917
+ "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
27918
+ ...sessionId ? { "session.id": sessionId } : {}
27919
+ }
27920
+ });
27921
+ span.setStatus({ code: SpanStatusCode.ERROR, message: ev.message });
27922
+ if (ev.error instanceof Error) {
27923
+ span.recordException(ev.error);
27907
27924
  }
27908
- });
27909
- span.setStatus({ code: SpanStatusCode.ERROR, message: ev.message });
27910
- if (ev.error instanceof Error) {
27911
- span.recordException(ev.error);
27925
+ span.end();
27926
+ } catch {
27912
27927
  }
27913
- span.end();
27914
27928
  };
27915
27929
  const onRejection = (ev) => {
27916
27930
  const reason = typeof ev.reason === "string" ? ev.reason : JSON.stringify(ev.reason ?? {});
27917
27931
  addBreadcrumb({ timestamp: Date.now(), category: "error", message: reason, level: "error" });
27918
- const breadcrumbs = getBreadcrumbs();
27919
- const span = tracer.startSpan("browser.unhandledrejection", {
27920
- attributes: {
27921
- "error.message": reason,
27922
- "breadcrumbs.count": breadcrumbs.length,
27923
- "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
27924
- ...sessionId ? { "session.id": sessionId } : {}
27932
+ try {
27933
+ const breadcrumbs = getBreadcrumbs();
27934
+ const span = tracer.startSpan("browser.unhandledrejection", {
27935
+ attributes: {
27936
+ "error.message": reason,
27937
+ "breadcrumbs.count": breadcrumbs.length,
27938
+ "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
27939
+ ...sessionId ? { "session.id": sessionId } : {}
27940
+ }
27941
+ });
27942
+ span.setStatus({ code: SpanStatusCode.ERROR, message: reason });
27943
+ if (ev.reason instanceof Error) {
27944
+ span.recordException(ev.reason);
27925
27945
  }
27926
- });
27927
- span.setStatus({ code: SpanStatusCode.ERROR, message: reason });
27928
- if (ev.reason instanceof Error) {
27929
- span.recordException(ev.reason);
27946
+ span.end();
27947
+ } catch {
27930
27948
  }
27931
- span.end();
27932
27949
  };
27933
27950
  window.addEventListener("error", onError);
27934
27951
  window.addEventListener("unhandledrejection", onRejection);
@@ -28218,30 +28235,40 @@ function installConsoleCapture(tracer, sessionId) {
28218
28235
  const level = LEVEL_MAP[method];
28219
28236
  console[method] = (...args) => {
28220
28237
  original(...args);
28221
- let message;
28222
- let attrs = {};
28223
- if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && !Array.isArray(args[0])) {
28224
- const obj = args[0];
28225
- message = String(obj.msg || obj.message || JSON.stringify(obj));
28226
- for (const [k, v] of Object.entries(obj)) {
28227
- if (k === "msg" || k === "message") continue;
28228
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
28229
- attrs[k] = v;
28238
+ try {
28239
+ let message;
28240
+ let attrs = {};
28241
+ const safeStringify = (v) => {
28242
+ try {
28243
+ return JSON.stringify(v);
28244
+ } catch {
28245
+ return String(v);
28246
+ }
28247
+ };
28248
+ if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && !Array.isArray(args[0])) {
28249
+ const obj = args[0];
28250
+ message = String(obj.msg || obj.message || safeStringify(obj));
28251
+ for (const [k, v] of Object.entries(obj)) {
28252
+ if (k === "msg" || k === "message") continue;
28253
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
28254
+ attrs[k] = v;
28255
+ }
28230
28256
  }
28257
+ } else {
28258
+ message = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ");
28231
28259
  }
28232
- } else {
28233
- message = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
28260
+ addBreadcrumb({ timestamp: Date.now(), category: `console.${method}`, message, level, data: attrs });
28261
+ const span = tracer.startSpan("browser.console", {
28262
+ attributes: {
28263
+ "log.severity": level.toUpperCase(),
28264
+ "log.message": message.slice(0, 1024),
28265
+ "session.id": sessionId,
28266
+ ...attrs
28267
+ }
28268
+ });
28269
+ span.end();
28270
+ } catch {
28234
28271
  }
28235
- addBreadcrumb({ timestamp: Date.now(), category: `console.${method}`, message, level, data: attrs });
28236
- const span = tracer.startSpan("browser.console", {
28237
- attributes: {
28238
- "log.severity": level.toUpperCase(),
28239
- "log.message": message.slice(0, 1024),
28240
- "session.id": sessionId,
28241
- ...attrs
28242
- }
28243
- });
28244
- span.end();
28245
28272
  };
28246
28273
  }
28247
28274
  return () => {
@@ -28610,9 +28637,15 @@ function initBrowserSDK(config) {
28610
28637
  }
28611
28638
  };
28612
28639
  const onVisibility = () => {
28613
- if (document.visibilityState === "hidden") flushReplay();
28640
+ if (document.visibilityState === "hidden") {
28641
+ otel.forceFlush();
28642
+ flushReplay();
28643
+ }
28644
+ };
28645
+ const onBeforeUnload = () => {
28646
+ otel.forceFlush();
28647
+ sendViaBeacon();
28614
28648
  };
28615
- const onBeforeUnload = () => sendViaBeacon();
28616
28649
  if (typeof document !== "undefined") {
28617
28650
  document.addEventListener("visibilitychange", onVisibility);
28618
28651
  cleanups.push(() => document.removeEventListener("visibilitychange", onVisibility));