@obtrace/browser 2.1.0 → 2.2.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.
@@ -1,3 +1,4 @@
1
+ import { SpanStatusCode } from "@opentelemetry/api";
1
2
  import { addBreadcrumb } from "./breadcrumbs";
2
3
  const LEVEL_MAP = {
3
4
  debug: "debug",
@@ -31,8 +32,23 @@ export function installConsoleCapture(tracer, sessionId) {
31
32
  return String(v);
32
33
  }
33
34
  };
34
- if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && !Array.isArray(args[0])) {
35
- const obj = args[0];
35
+ const firstArg = args[0];
36
+ const isErrorObj = firstArg instanceof Error;
37
+ if (isErrorObj) {
38
+ const err = firstArg;
39
+ message = `${err.name}: ${err.message}`;
40
+ if (err.stack)
41
+ attrs["error.stack"] = err.stack.slice(0, 4096);
42
+ attrs["error.type"] = err.name;
43
+ for (let i = 1; i < args.length; i++) {
44
+ const extra = args[i];
45
+ if (typeof extra === "string" && extra.includes("\n at ")) {
46
+ attrs["error.component_stack"] = extra.slice(0, 4096);
47
+ }
48
+ }
49
+ }
50
+ else if (args.length === 1 && typeof firstArg === "object" && firstArg !== null && !Array.isArray(firstArg)) {
51
+ const obj = firstArg;
36
52
  message = String(obj.msg || obj.message || safeStringify(obj));
37
53
  for (const [k, v] of Object.entries(obj)) {
38
54
  if (k === "msg" || k === "message")
@@ -44,9 +60,18 @@ export function installConsoleCapture(tracer, sessionId) {
44
60
  }
45
61
  else {
46
62
  message = args.map(a => typeof a === "string" ? a : safeStringify(a)).join(" ");
63
+ if (method === "error") {
64
+ for (const a of args) {
65
+ if (typeof a === "string" && a.includes("\n at ")) {
66
+ attrs["error.stack"] = a.slice(0, 4096);
67
+ break;
68
+ }
69
+ }
70
+ }
47
71
  }
48
72
  addBreadcrumb({ timestamp: Date.now(), category: `console.${method}`, message, level, data: attrs });
49
- const span = tracer.startSpan("browser.console", {
73
+ const spanName = (method === "error" && (isErrorObj || attrs["error.stack"])) ? "browser.error" : "browser.console";
74
+ const span = tracer.startSpan(spanName, {
50
75
  attributes: {
51
76
  "log.severity": level.toUpperCase(),
52
77
  "log.message": message.slice(0, 1024),
@@ -54,6 +79,11 @@ export function installConsoleCapture(tracer, sessionId) {
54
79
  ...attrs,
55
80
  },
56
81
  });
82
+ if (method === "error") {
83
+ span.setStatus({ code: SpanStatusCode.ERROR, message: message.slice(0, 1024) });
84
+ if (isErrorObj)
85
+ span.recordException(firstArg);
86
+ }
57
87
  span.end();
58
88
  }
59
89
  catch { }
@@ -5,21 +5,25 @@ export function installBrowserErrorHooks(tracer, sessionId) {
5
5
  return () => undefined;
6
6
  }
7
7
  const onError = (ev) => {
8
- addBreadcrumb({ timestamp: Date.now(), category: "error", message: ev.message || "window.error", level: "error" });
8
+ const message = ev.message || "window.error";
9
+ addBreadcrumb({ timestamp: Date.now(), category: "error", message, level: "error" });
9
10
  try {
10
11
  const breadcrumbs = getBreadcrumbs();
12
+ const stack = ev.error instanceof Error ? ev.error.stack || "" : "";
11
13
  const span = tracer.startSpan("browser.error", {
12
14
  attributes: {
13
- "error.message": ev.message || "window.error",
15
+ "error.message": message,
14
16
  "error.file": ev.filename || "",
15
17
  "error.line": ev.lineno || 0,
16
18
  "error.column": ev.colno || 0,
19
+ "error.stack": stack.slice(0, 4096),
20
+ "error.type": ev.error?.constructor?.name || "Error",
17
21
  "breadcrumbs.count": breadcrumbs.length,
18
22
  "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
19
23
  ...(sessionId ? { "session.id": sessionId } : {}),
20
24
  },
21
25
  });
22
- span.setStatus({ code: SpanStatusCode.ERROR, message: ev.message });
26
+ span.setStatus({ code: SpanStatusCode.ERROR, message });
23
27
  if (ev.error instanceof Error) {
24
28
  span.recordException(ev.error);
25
29
  }
@@ -28,13 +32,23 @@ export function installBrowserErrorHooks(tracer, sessionId) {
28
32
  catch { }
29
33
  };
30
34
  const onRejection = (ev) => {
31
- const reason = typeof ev.reason === "string" ? ev.reason : JSON.stringify(ev.reason ?? {});
35
+ let reason;
36
+ let stack = "";
37
+ if (ev.reason instanceof Error) {
38
+ reason = `${ev.reason.name}: ${ev.reason.message}`;
39
+ stack = ev.reason.stack || "";
40
+ }
41
+ else {
42
+ reason = typeof ev.reason === "string" ? ev.reason : JSON.stringify(ev.reason ?? {});
43
+ }
32
44
  addBreadcrumb({ timestamp: Date.now(), category: "error", message: reason, level: "error" });
33
45
  try {
34
46
  const breadcrumbs = getBreadcrumbs();
35
47
  const span = tracer.startSpan("browser.unhandledrejection", {
36
48
  attributes: {
37
49
  "error.message": reason,
50
+ "error.stack": stack.slice(0, 4096),
51
+ "error.type": ev.reason?.constructor?.name || "UnhandledRejection",
38
52
  "breadcrumbs.count": breadcrumbs.length,
39
53
  "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
40
54
  ...(sessionId ? { "session.id": sessionId } : {}),
@@ -157,7 +157,12 @@ export function initBrowserSDK(config) {
157
157
  const sampleRate = config.tracesSampleRate ?? 1;
158
158
  const replaySampleRate = config.replaySampleRate ?? 1;
159
159
  const shouldReplay = Math.random() < replaySampleRate;
160
- const otel = setupOtelWeb({ ...config, tracesSampleRate: sampleRate });
160
+ const replay = new BrowserReplayBuffer({
161
+ maxChunkBytes: config.replay?.maxChunkBytes ?? 480_000,
162
+ flushIntervalMs: config.replay?.flushIntervalMs ?? 5000,
163
+ sessionStorageKey: config.replay?.sessionStorageKey ?? "obtrace_session_id",
164
+ });
165
+ const otel = setupOtelWeb({ ...config, tracesSampleRate: sampleRate, sessionId: replay.sessionId });
161
166
  const tracer = otel.tracer;
162
167
  const meter = otel.meter;
163
168
  const client = new ObtraceClient({
@@ -173,11 +178,6 @@ export function initBrowserSDK(config) {
173
178
  vitals: { enabled: true, reportAllChanges: false, ...config.vitals },
174
179
  propagation: { enabled: true, ...config.propagation },
175
180
  });
176
- const replay = new BrowserReplayBuffer({
177
- maxChunkBytes: config.replay?.maxChunkBytes ?? 480_000,
178
- flushIntervalMs: config.replay?.flushIntervalMs ?? 5000,
179
- sessionStorageKey: config.replay?.sessionStorageKey ?? "obtrace_session_id",
180
- });
181
181
  const recipeSteps = [];
182
182
  const cleanups = [];
183
183
  const entry = { client, sessionId: replay.sessionId, replay, config, recipeSteps, otel };
@@ -27784,6 +27784,7 @@ function setupOtelWeb(config) {
27784
27784
  ...config.projectId ? { "obtrace.project_id": config.projectId } : {},
27785
27785
  ...config.appId ? { "obtrace.app_id": config.appId } : {},
27786
27786
  ...config.env ? { "obtrace.env": config.env } : {},
27787
+ ...config.sessionId ? { "session.id": config.sessionId } : {},
27787
27788
  "runtime.name": "browser"
27788
27789
  });
27789
27790
  const traceExporter = new OTLPTraceExporter({
@@ -27904,21 +27905,25 @@ function installBrowserErrorHooks(tracer, sessionId) {
27904
27905
  return () => void 0;
27905
27906
  }
27906
27907
  const onError = (ev) => {
27907
- addBreadcrumb({ timestamp: Date.now(), category: "error", message: ev.message || "window.error", level: "error" });
27908
+ const message = ev.message || "window.error";
27909
+ addBreadcrumb({ timestamp: Date.now(), category: "error", message, level: "error" });
27908
27910
  try {
27909
27911
  const breadcrumbs = getBreadcrumbs();
27912
+ const stack = ev.error instanceof Error ? ev.error.stack || "" : "";
27910
27913
  const span = tracer.startSpan("browser.error", {
27911
27914
  attributes: {
27912
- "error.message": ev.message || "window.error",
27915
+ "error.message": message,
27913
27916
  "error.file": ev.filename || "",
27914
27917
  "error.line": ev.lineno || 0,
27915
27918
  "error.column": ev.colno || 0,
27919
+ "error.stack": stack.slice(0, 4096),
27920
+ "error.type": ev.error?.constructor?.name || "Error",
27916
27921
  "breadcrumbs.count": breadcrumbs.length,
27917
27922
  "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
27918
27923
  ...sessionId ? { "session.id": sessionId } : {}
27919
27924
  }
27920
27925
  });
27921
- span.setStatus({ code: SpanStatusCode.ERROR, message: ev.message });
27926
+ span.setStatus({ code: SpanStatusCode.ERROR, message });
27922
27927
  if (ev.error instanceof Error) {
27923
27928
  span.recordException(ev.error);
27924
27929
  }
@@ -27927,13 +27932,22 @@ function installBrowserErrorHooks(tracer, sessionId) {
27927
27932
  }
27928
27933
  };
27929
27934
  const onRejection = (ev) => {
27930
- const reason = typeof ev.reason === "string" ? ev.reason : JSON.stringify(ev.reason ?? {});
27935
+ let reason;
27936
+ let stack = "";
27937
+ if (ev.reason instanceof Error) {
27938
+ reason = `${ev.reason.name}: ${ev.reason.message}`;
27939
+ stack = ev.reason.stack || "";
27940
+ } else {
27941
+ reason = typeof ev.reason === "string" ? ev.reason : JSON.stringify(ev.reason ?? {});
27942
+ }
27931
27943
  addBreadcrumb({ timestamp: Date.now(), category: "error", message: reason, level: "error" });
27932
27944
  try {
27933
27945
  const breadcrumbs = getBreadcrumbs();
27934
27946
  const span = tracer.startSpan("browser.unhandledrejection", {
27935
27947
  attributes: {
27936
27948
  "error.message": reason,
27949
+ "error.stack": stack.slice(0, 4096),
27950
+ "error.type": ev.reason?.constructor?.name || "UnhandledRejection",
27937
27951
  "breadcrumbs.count": breadcrumbs.length,
27938
27952
  "breadcrumbs.json": JSON.stringify(breadcrumbs.slice(-20)),
27939
27953
  ...sessionId ? { "session.id": sessionId } : {}
@@ -28245,8 +28259,21 @@ function installConsoleCapture(tracer, sessionId) {
28245
28259
  return String(v);
28246
28260
  }
28247
28261
  };
28248
- if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && !Array.isArray(args[0])) {
28249
- const obj = args[0];
28262
+ const firstArg = args[0];
28263
+ const isErrorObj = firstArg instanceof Error;
28264
+ if (isErrorObj) {
28265
+ const err = firstArg;
28266
+ message = `${err.name}: ${err.message}`;
28267
+ if (err.stack) attrs["error.stack"] = err.stack.slice(0, 4096);
28268
+ attrs["error.type"] = err.name;
28269
+ for (let i = 1; i < args.length; i++) {
28270
+ const extra = args[i];
28271
+ if (typeof extra === "string" && extra.includes("\n at ")) {
28272
+ attrs["error.component_stack"] = extra.slice(0, 4096);
28273
+ }
28274
+ }
28275
+ } else if (args.length === 1 && typeof firstArg === "object" && firstArg !== null && !Array.isArray(firstArg)) {
28276
+ const obj = firstArg;
28250
28277
  message = String(obj.msg || obj.message || safeStringify(obj));
28251
28278
  for (const [k, v] of Object.entries(obj)) {
28252
28279
  if (k === "msg" || k === "message") continue;
@@ -28256,9 +28283,18 @@ function installConsoleCapture(tracer, sessionId) {
28256
28283
  }
28257
28284
  } else {
28258
28285
  message = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ");
28286
+ if (method === "error") {
28287
+ for (const a of args) {
28288
+ if (typeof a === "string" && a.includes("\n at ")) {
28289
+ attrs["error.stack"] = a.slice(0, 4096);
28290
+ break;
28291
+ }
28292
+ }
28293
+ }
28259
28294
  }
28260
28295
  addBreadcrumb({ timestamp: Date.now(), category: `console.${method}`, message, level, data: attrs });
28261
- const span = tracer.startSpan("browser.console", {
28296
+ const spanName = method === "error" && (isErrorObj || attrs["error.stack"]) ? "browser.error" : "browser.console";
28297
+ const span = tracer.startSpan(spanName, {
28262
28298
  attributes: {
28263
28299
  "log.severity": level.toUpperCase(),
28264
28300
  "log.message": message.slice(0, 1024),
@@ -28266,6 +28302,10 @@ function installConsoleCapture(tracer, sessionId) {
28266
28302
  ...attrs
28267
28303
  }
28268
28304
  });
28305
+ if (method === "error") {
28306
+ span.setStatus({ code: SpanStatusCode.ERROR, message: message.slice(0, 1024) });
28307
+ if (isErrorObj) span.recordException(firstArg);
28308
+ }
28269
28309
  span.end();
28270
28310
  } catch {
28271
28311
  }
@@ -28552,7 +28592,12 @@ function initBrowserSDK(config) {
28552
28592
  const sampleRate = config.tracesSampleRate ?? 1;
28553
28593
  const replaySampleRate = config.replaySampleRate ?? 1;
28554
28594
  const shouldReplay = Math.random() < replaySampleRate;
28555
- const otel = setupOtelWeb({ ...config, tracesSampleRate: sampleRate });
28595
+ const replay = new BrowserReplayBuffer({
28596
+ maxChunkBytes: config.replay?.maxChunkBytes ?? 48e4,
28597
+ flushIntervalMs: config.replay?.flushIntervalMs ?? 5e3,
28598
+ sessionStorageKey: config.replay?.sessionStorageKey ?? "obtrace_session_id"
28599
+ });
28600
+ const otel = setupOtelWeb({ ...config, tracesSampleRate: sampleRate, sessionId: replay.sessionId });
28556
28601
  const tracer = otel.tracer;
28557
28602
  const meter = otel.meter;
28558
28603
  const client = new ObtraceClient({
@@ -28568,11 +28613,6 @@ function initBrowserSDK(config) {
28568
28613
  vitals: { enabled: true, reportAllChanges: false, ...config.vitals },
28569
28614
  propagation: { enabled: true, ...config.propagation }
28570
28615
  });
28571
- const replay = new BrowserReplayBuffer({
28572
- maxChunkBytes: config.replay?.maxChunkBytes ?? 48e4,
28573
- flushIntervalMs: config.replay?.flushIntervalMs ?? 5e3,
28574
- sessionStorageKey: config.replay?.sessionStorageKey ?? "obtrace_session_id"
28575
- });
28576
28616
  const recipeSteps = [];
28577
28617
  const cleanups = [];
28578
28618
  const entry = { client, sessionId: replay.sessionId, replay, config, recipeSteps, otel };