@dash0/sdk-web 0.13.1 → 0.13.3

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.
@@ -0,0 +1 @@
1
+ export { setActiveLogLevel } from "../utils/debug";
@@ -4,6 +4,7 @@ export * from "../api/identify";
4
4
  export * from "../api/debug";
5
5
  export * from "../api/attributes";
6
6
  export * from "../api/events";
7
+ export * from "../api/log-level";
7
8
  export { terminateSession } from "../api/session";
8
9
  export { reportError } from "../api/report-error";
9
10
  export function init(opts) {
@@ -6,6 +6,7 @@ import { init as initApi } from "../api/init";
6
6
  import { terminateSession } from "../api/session";
7
7
  import { addSignalAttribute, removeSignalAttribute } from "../api/attributes";
8
8
  import { sendEvent } from "../api/events";
9
+ import { setActiveLogLevel } from "../api/log-level";
9
10
  /**
10
11
  * All the APIs exposed through the script tag via `dash0('{{api name}}')`
11
12
  */
@@ -17,6 +18,7 @@ const scriptApis = {
17
18
  reportError,
18
19
  addSignalAttribute,
19
20
  removeSignalAttribute,
21
+ setActiveLogLevel,
20
22
  sendEvent,
21
23
  };
22
24
  init();
@@ -1,7 +1,7 @@
1
1
  import { debug, observeResourcePerformance, win } from "../../utils";
2
2
  import { isUrlIgnored, matchesAny } from "../../utils/ignore-rules";
3
3
  import { addAttribute, setSpanStatus, addTraceContextHttpHeaders, endSpan, errorToSpanStatus, recordException, startSpan, } from "../../utils/otel";
4
- import { HTTP_REQUEST_METHOD, HTTP_REQUEST_METHOD_ORIGINAL, HTTP_RESPONSE_STATUS_CODE, SPAN_STATUS_ERROR, SPAN_STATUS_UNSET, } from "../../semantic-conventions";
4
+ import { ERROR_TYPE, HTTP_REQUEST_METHOD, HTTP_REQUEST_METHOD_ORIGINAL, HTTP_RESPONSE_STATUS_CODE, SPAN_STATUS_ERROR, SPAN_STATUS_UNSET, } from "../../semantic-conventions";
5
5
  import { isSameOrigin, wrap, parseUrl } from "../../utils";
6
6
  import { vars } from "../../vars";
7
7
  import { httpRequestHeaderKey, httpResponseHeaderKey } from "../../utils/otel/http";
@@ -138,7 +138,10 @@ function tryCaptureHttpHeaders(headers, span, getAttributeKey) {
138
138
  }
139
139
  function addResponseData(span, response) {
140
140
  const status = response.status;
141
- setSpanStatus(span, status >= 200 && status < 400 ? SPAN_STATUS_UNSET : SPAN_STATUS_ERROR, response.statusText);
141
+ setSpanStatus(span, status >= 200 && status < 400 ? SPAN_STATUS_UNSET : SPAN_STATUS_ERROR);
142
+ if (status === 0) {
143
+ addAttribute(span.attributes, ERROR_TYPE, response.type);
144
+ }
142
145
  addAttribute(span.attributes, HTTP_RESPONSE_STATUS_CODE, String(status));
143
146
  tryCaptureHttpHeaders(response.headers, span, (k) => httpResponseHeaderKey(k));
144
147
  }
@@ -27,6 +27,8 @@ export const USER_ROLES = "user.roles";
27
27
  export const EXCEPTION_MESSAGE = "exception.message";
28
28
  export const EXCEPTION_TYPE = "exception.type";
29
29
  export const EXCEPTION_STACKTRACE = "exception.stacktrace";
30
+ // Error Attribute Keys
31
+ export const ERROR_TYPE = "error.type";
30
32
  // URL Attribute Keys
31
33
  export const URL_DOMAIN = "url.domain";
32
34
  export const URL_FRAGMENT = "url.fragment";
@@ -1,7 +1,8 @@
1
1
  import { vars } from "../vars";
2
- import { noop, warn, fetch } from "../utils";
2
+ import { noop, warn, fetch, debug } from "../utils";
3
3
  const BEACON_BODY_SIZE_LIMIT = 60000;
4
4
  export async function send(path, body) {
5
+ debug("Transmitting telemetry to endpoints", body);
5
6
  const jsonString = JSON.stringify(body);
6
7
  let requestBody = jsonString;
7
8
  let byteLength = jsonString.length;
@@ -1,19 +1,29 @@
1
1
  import { noop } from "./fn";
2
- export const log = createLogger("log");
2
+ const logLevels = {
3
+ debug: 0,
4
+ info: 1,
5
+ warn: 2,
6
+ error: 3,
7
+ };
8
+ let activeLogLevel = logLevels.warn;
9
+ /**
10
+ * Changes the logging verbosity of Dash0's web SDK. By default, only warnings and errors are logged.
11
+ */
12
+ export function setActiveLogLevel(level) {
13
+ activeLogLevel = logLevels[level] ?? logLevels.warn;
14
+ }
3
15
  export const info = createLogger("info");
4
16
  export const warn = createLogger("warn");
5
17
  export const error = createLogger("error");
6
18
  export const debug = createLogger("debug");
7
- function createLogger(method) {
8
- if (typeof console === "undefined" || typeof console.log !== "function" || typeof console.log.apply !== "function") {
9
- return noop;
10
- }
11
- if (console[method] && typeof console[method].apply === "function") {
19
+ function createLogger(logLevel) {
20
+ if (typeof console !== "undefined" && console[logLevel] && typeof console[logLevel].apply === "function") {
21
+ const numericLogLevel = logLevels[logLevel];
12
22
  return function () {
13
- console[method].apply(console, arguments);
23
+ if (numericLogLevel >= activeLogLevel) {
24
+ console[logLevel].apply(console, arguments);
25
+ }
14
26
  };
15
27
  }
16
- return function () {
17
- console.log.apply(console, arguments);
18
- };
28
+ return noop;
19
29
  }
@@ -5,6 +5,7 @@ import { setTimeout } from "./timers";
5
5
  import { addEventListener, removeEventListener } from "./listeners";
6
6
  const TEN_MINUTES_IN_MILLIS = 1000 * 60 * 10;
7
7
  const ONE_DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
8
+ const OBSERVER_WAIT_TIME_MS = 300;
8
9
  export const isResourceTimingAvailable = !!(perf && perf.getEntriesByType);
9
10
  export const isPerformanceObserverAvailable = perf && typeof win["PerformanceObserver"] === "function" && typeof perf["now"] === "function";
10
11
  export const PerformanceTimingNames = Object.freeze({
@@ -32,13 +33,14 @@ export const PerformanceTimingNames = Object.freeze({
32
33
  UNLOAD_EVENT_END: "unloadEventEnd",
33
34
  UNLOAD_EVENT_START: "unloadEventStart",
34
35
  });
36
+ const usedResources = new WeakSet();
35
37
  export function observeResourcePerformance(opts) {
36
38
  if (!isPerformanceObserverAvailable)
37
39
  return observeWithoutPerformanceObserverSupport(opts.onEnd);
38
40
  // Used to calculate the duration when no resource was found.
39
41
  let startTime;
40
42
  let endTime;
41
- let resource;
43
+ const resources = [];
42
44
  // Global resources that will need to be disposed
43
45
  let observer;
44
46
  let fallbackNoResourceFoundTimerHandle;
@@ -70,40 +72,36 @@ export function observeResourcePerformance(opts) {
70
72
  function onEnd() {
71
73
  endTime = perf.now();
72
74
  cancelFallbackEndNeverCalledTimer();
73
- if (resource || !isWaitingAcceptable()) {
74
- end();
75
- }
76
- else {
77
- addEventListener(doc, "visibilitychange", onVisibilityChanged);
78
- fallbackNoResourceFoundTimerHandle = setTimeout(end, opts.maxWaitForResourceMillis);
75
+ if (!isWaitingAcceptable()) {
76
+ return end();
79
77
  }
78
+ setTimeout(() => end(), Math.min(OBSERVER_WAIT_TIME_MS, opts.maxWaitForResourceMillis));
79
+ addEventListener(doc, "visibilitychange", onVisibilityChanged);
80
+ fallbackNoResourceFoundTimerHandle = setTimeout(end, opts.maxWaitForResourceMillis);
80
81
  }
81
82
  function end() {
82
83
  disposeGlobalResources();
84
+ const resource = findBestMatchingResource();
83
85
  // In some old web browsers, e.g. Chrome 31, the value provided as the duration
84
86
  // can be very wrong. We have seen cases where this value is measured in years.
85
87
  // If this does seem be the case, then we will ignore the duration property and
86
88
  // instead prefer our approximation.
87
89
  if (resource?.duration && resource.duration < ONE_DAY_IN_MILLIS) {
88
- opts.onEnd({ resource, duration: Math.round(resource.duration) });
90
+ opts.onEnd({ resource, duration: resource.duration });
89
91
  }
90
92
  else {
91
- opts.onEnd({ resource, duration: Math.round(endTime - startTime) });
93
+ opts.onEnd({ resource, duration: endTime - startTime });
92
94
  }
93
95
  }
94
96
  function onResourceFound(list) {
95
- const r = list.getEntriesByType("resource").find((e) => {
97
+ list
98
+ .getEntriesByType("resource")
99
+ .filter((e) => {
96
100
  // This polymorphism is not properly represented in the api types. The cast is safe since we're only accessing the resource timings.
97
101
  const entry = e;
98
- return (entry.startTime >= startTime &&
99
- (!endTime || endTime + opts.maxToleranceForResourceTimingsMillis >= entry.responseEnd) &&
100
- opts.resourceMatcher(entry));
101
- });
102
- if (!r)
103
- return;
104
- resource = r;
105
- if (endTime)
106
- end();
102
+ return entry.startTime >= startTime && opts.resourceMatcher(entry);
103
+ })
104
+ .forEach((entry) => resources.push(entry));
107
105
  }
108
106
  function onVisibilityChanged() {
109
107
  if (!isWaitingAcceptable())
@@ -143,6 +141,32 @@ export function observeResourcePerformance(opts) {
143
141
  return;
144
142
  removeEventListener(doc, "visibilitychange", onVisibilityChanged);
145
143
  }
144
+ function findBestMatchingResource() {
145
+ if (!resources.length)
146
+ return undefined;
147
+ let bestMatch;
148
+ const matchingResources = resources.filter((res) => res.responseEnd <= endTime + opts.maxToleranceForResourceTimingsMillis && !usedResources.has(res));
149
+ if (!matchingResources.length) {
150
+ return undefined;
151
+ }
152
+ if (matchingResources.length === 1) {
153
+ bestMatch = matchingResources[0];
154
+ }
155
+ let bestScore;
156
+ if (!bestMatch) {
157
+ for (const res of matchingResources) {
158
+ const score = Math.abs(endTime - startTime - res.duration) + Math.abs(res.responseEnd - endTime);
159
+ if (bestScore === undefined || score < bestScore) {
160
+ bestScore = score;
161
+ bestMatch = res;
162
+ }
163
+ }
164
+ }
165
+ if (!bestMatch)
166
+ return;
167
+ usedResources.add(bestMatch);
168
+ return bestMatch;
169
+ }
146
170
  }
147
171
  // We may only wait for resource data to arrive as long as the document is visible or in the process
148
172
  // of becoming visible. In all other cases we might lose data when waiting, e.g. when the document
@@ -16,7 +16,7 @@ export const vars = {
16
16
  wrapTimers: true,
17
17
  propagateTraceHeadersCorsURLs: [],
18
18
  maxWaitForResourceTimingsMillis: 10000,
19
- maxToleranceForResourceTimingsMillis: 3000,
19
+ maxToleranceForResourceTimingsMillis: 50,
20
20
  headersToCapture: [],
21
21
  pageViewInstrumentation: {
22
22
  trackVirtualPageViews: true,