@dash0/sdk-web 0.16.0 → 0.16.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/README.md +32 -583
- package/dist/dash0.iife.js +1 -1
- package/dist/dash0.iife.js.map +1 -1
- package/dist/dash0.js +1 -1
- package/dist/dash0.js.map +1 -1
- package/dist/dash0.umd.cjs +1 -1
- package/dist/dash0.umd.cjs.map +1 -1
- package/dist/modules/api/report-error.js +1 -1
- package/dist/modules/instrumentations/errors/event-handlers.js +2 -2
- package/dist/modules/instrumentations/errors/unhandled-error.js +1 -1
- package/dist/modules/instrumentations/errors/unhandled-promise-rejection.js +4 -4
- package/dist/modules/instrumentations/http/fetch.js +83 -23
- package/dist/modules/transport/index.js +2 -0
- package/dist/modules/utils/index.js +1 -0
- package/dist/modules/utils/otel/span.js +9 -0
- package/dist/modules/utils/performance.js +16 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/instrumentations/errors/unhandled-error.d.ts +1 -1
- package/dist/types/transport/index.d.ts +1 -1
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/otel/span.d.ts +5 -1
- package/dist/types/utils/performance.d.ts +5 -1
- package/package.json +1 -1
- package/src/api/report-error.ts +1 -1
- package/src/instrumentations/errors/event-handlers.ts +2 -2
- package/src/instrumentations/errors/unhandled-error.ts +1 -1
- package/src/instrumentations/errors/unhandled-promise-rejection.ts +4 -4
- package/src/instrumentations/http/fetch.ts +95 -24
- package/src/transport/index.ts +3 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/otel/span.ts +16 -1
- package/src/utils/performance.ts +21 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { reportUnhandledError as reportErrorInternal } from "../instrumentations/errors/unhandled-error";
|
|
2
2
|
export function reportError(error, opts) {
|
|
3
3
|
reportErrorInternal(error, opts);
|
|
4
4
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { vars } from "../../vars";
|
|
2
2
|
import { win } from "../../utils";
|
|
3
3
|
import { addWrappedDomEventListener, popWrappedDomEventListener, } from "./async-function-wrapping";
|
|
4
|
-
import { ignoreNextOnErrorEvent } from "./unhandled-error";
|
|
4
|
+
import { ignoreNextOnErrorEvent, reportUnhandledError } from "./unhandled-error";
|
|
5
5
|
export function startEventHandlerInstrumentation() {
|
|
6
6
|
if (vars.wrapEventHandlers) {
|
|
7
7
|
wrapEventTarget(win?.EventTarget);
|
|
@@ -29,7 +29,7 @@ function wrapEventTarget(EventTarget) {
|
|
|
29
29
|
return fn.apply(this, arguments);
|
|
30
30
|
}
|
|
31
31
|
catch (e) {
|
|
32
|
-
|
|
32
|
+
reportUnhandledError(e);
|
|
33
33
|
ignoreNextOnErrorEvent();
|
|
34
34
|
throw e;
|
|
35
35
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { win } from "../../utils";
|
|
2
|
-
import {
|
|
2
|
+
import { reportUnhandledError } from "./unhandled-error";
|
|
3
3
|
const MESSAGE_PREFIX = "Unhandled promise rejection: ";
|
|
4
4
|
const STACK_UNAVAILABLE_MESSAGE = "<unavailable because Promise wasn't rejected with an Error object>";
|
|
5
5
|
export function startUnhandledRejectionInstrumentation() {
|
|
@@ -9,19 +9,19 @@ export function startUnhandledRejectionInstrumentation() {
|
|
|
9
9
|
}
|
|
10
10
|
export function onUnhandledRejection(event) {
|
|
11
11
|
if (event.reason == null) {
|
|
12
|
-
|
|
12
|
+
reportUnhandledError({
|
|
13
13
|
message: MESSAGE_PREFIX + "<no reason defined>",
|
|
14
14
|
stack: STACK_UNAVAILABLE_MESSAGE,
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
else if (typeof event.reason.message === "string") {
|
|
18
|
-
|
|
18
|
+
reportUnhandledError({
|
|
19
19
|
message: MESSAGE_PREFIX + event.reason.message,
|
|
20
20
|
stack: typeof event.reason.stack === "string" ? event.reason.stack : STACK_UNAVAILABLE_MESSAGE,
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
else if (typeof event.reason !== "object") {
|
|
24
|
-
|
|
24
|
+
reportUnhandledError({
|
|
25
25
|
message: MESSAGE_PREFIX + event.reason,
|
|
26
26
|
stack: STACK_UNAVAILABLE_MESSAGE,
|
|
27
27
|
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { debug, observeResourcePerformance, win } from "../../utils";
|
|
1
|
+
import { debug, observeResourcePerformance, perf, win, setTimeout, isSameOrigin, wrap, parseUrl } from "../../utils";
|
|
2
2
|
import { isUrlIgnored, matchesAny } from "../../utils/ignore-rules";
|
|
3
3
|
import { addAttribute, setSpanStatus, addW3CTraceContextHttpHeaders, addXRayTraceContextHttpHeaders, endSpan, errorToSpanStatus, recordException, startSpan, } from "../../utils/otel";
|
|
4
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
|
-
import { isSameOrigin, wrap, parseUrl } from "../../utils";
|
|
6
5
|
import { vars } from "../../vars";
|
|
7
6
|
import { httpRequestHeaderKey, httpResponseHeaderKey } from "../../utils/otel/http";
|
|
8
7
|
import { sendSpan } from "../../transport";
|
|
@@ -83,16 +82,12 @@ function wrapFetch(original) {
|
|
|
83
82
|
});
|
|
84
83
|
performanceObserver.start();
|
|
85
84
|
try {
|
|
86
|
-
const
|
|
87
|
-
addResponseData(span,
|
|
88
|
-
|
|
89
|
-
waitForFullResponse(response)
|
|
90
|
-
.then(() => performanceObserver.end())
|
|
91
|
-
.catch((e) => {
|
|
85
|
+
const origResponse = await original(input instanceof Request ? request : input, copyOfInit);
|
|
86
|
+
addResponseData(span, origResponse);
|
|
87
|
+
return wrapResponse(origResponse, vars.maxToleranceForResourceTimingsMillis, () => performanceObserver.end(), (e) => {
|
|
92
88
|
performanceObserver.cancel();
|
|
93
89
|
endSpanOnError(span, e);
|
|
94
90
|
});
|
|
95
|
-
return response;
|
|
96
91
|
}
|
|
97
92
|
catch (e) {
|
|
98
93
|
performanceObserver.cancel();
|
|
@@ -146,20 +141,81 @@ function addResponseData(span, response) {
|
|
|
146
141
|
addAttribute(span.attributes, HTTP_RESPONSE_STATUS_CODE, String(status));
|
|
147
142
|
tryCaptureHttpHeaders(response.headers, span, (k) => httpResponseHeaderKey(k));
|
|
148
143
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Wraps the response to be able to detect when it is fully read
|
|
146
|
+
* @param originalResponse
|
|
147
|
+
* @param readTimeoutMs Timeout applied between reading of response chunks, if exceeded the response is considered abandoned and onDone is called.
|
|
148
|
+
* @param onDone Called when the response is completely read or with "fallbackEndTs" when reading timed out.
|
|
149
|
+
* @param onError
|
|
150
|
+
*/
|
|
151
|
+
function wrapResponse(originalResponse, readTimeoutMs, onDone, onError) {
|
|
152
|
+
// When the response was wrapped (i.e. first available to js) we use this to replace or find the actual end timestamp
|
|
153
|
+
// in case the response body is never read by js
|
|
154
|
+
let fallbackTs = perf.now();
|
|
155
|
+
const body = originalResponse.body;
|
|
156
|
+
// For some reason browsers return a response body on responses that can't be constructed with one. In that case we can't wrap the body stream
|
|
157
|
+
if (!body || !responseCanHaveBody(originalResponse)) {
|
|
158
|
+
onDone();
|
|
159
|
+
return originalResponse;
|
|
160
|
+
}
|
|
161
|
+
let cbCalled = false;
|
|
162
|
+
const handleDone = (fallbackEndTs) => {
|
|
163
|
+
if (cbCalled)
|
|
164
|
+
return;
|
|
165
|
+
onDone(fallbackEndTs);
|
|
166
|
+
cbCalled = true;
|
|
167
|
+
};
|
|
168
|
+
const handleError = (e) => {
|
|
169
|
+
if (cbCalled)
|
|
170
|
+
return;
|
|
171
|
+
onError(e);
|
|
172
|
+
cbCalled = true;
|
|
173
|
+
};
|
|
174
|
+
let bodyNeverCompletelyReadTimeout = setTimeout(() => handleDone(fallbackTs), readTimeoutMs);
|
|
175
|
+
const reader = body.getReader();
|
|
176
|
+
const stream = new ReadableStream({
|
|
177
|
+
async pull(controller) {
|
|
178
|
+
try {
|
|
179
|
+
clearTimeout(bodyNeverCompletelyReadTimeout);
|
|
180
|
+
const { value, done } = await reader.read();
|
|
181
|
+
if (done) {
|
|
182
|
+
reader.releaseLock();
|
|
183
|
+
controller.close();
|
|
184
|
+
handleDone();
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
fallbackTs = perf.now();
|
|
188
|
+
bodyNeverCompletelyReadTimeout = setTimeout(() => handleDone(fallbackTs), readTimeoutMs);
|
|
189
|
+
controller.enqueue(value);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
handleError(e);
|
|
194
|
+
controller.error(e);
|
|
195
|
+
try {
|
|
196
|
+
reader.releaseLock();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Spec reference:
|
|
200
|
+
// https://streams.spec.whatwg.org/#default-reader-release-lock
|
|
201
|
+
//
|
|
202
|
+
// releaseLock() only throws if called on an invalid reader
|
|
203
|
+
// (i.e. reader.[[stream]] is undefined, meaning the lock is already released
|
|
204
|
+
// or the reader was never associated). In normal use this cannot happen.
|
|
205
|
+
// This catch is defensive only.
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
cancel(reason) {
|
|
210
|
+
clearTimeout(bodyNeverCompletelyReadTimeout);
|
|
211
|
+
handleDone();
|
|
212
|
+
return reader.cancel(reason);
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
return new Response(stream, {
|
|
216
|
+
status: originalResponse.status,
|
|
217
|
+
statusText: originalResponse.statusText,
|
|
218
|
+
headers: originalResponse.headers,
|
|
163
219
|
});
|
|
164
220
|
}
|
|
165
221
|
function endSpanOnError(span, error) {
|
|
@@ -207,3 +263,7 @@ function addTraceContextHttpHeaders(fn, ctx, span, types) {
|
|
|
207
263
|
}
|
|
208
264
|
}
|
|
209
265
|
}
|
|
266
|
+
function responseCanHaveBody(response) {
|
|
267
|
+
const status = response.status;
|
|
268
|
+
return status >= 200 && status != 204 && status != 205 && status != 304;
|
|
269
|
+
}
|
|
@@ -4,6 +4,7 @@ import { sessionId } from "../../api/session";
|
|
|
4
4
|
import { generateTraceId } from "../trace-id";
|
|
5
5
|
import { generateSpanId } from "../span-id";
|
|
6
6
|
import { addAttribute } from "./attributes";
|
|
7
|
+
import { debug } from "../debug";
|
|
7
8
|
export function startSpan(name) {
|
|
8
9
|
const traceId = generateTraceId(sessionId);
|
|
9
10
|
const spanId = generateSpanId(traceId);
|
|
@@ -23,9 +24,17 @@ export function startSpan(name) {
|
|
|
23
24
|
status: { code: SPAN_STATUS_UNSET },
|
|
24
25
|
};
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Ends the span by setting status and endTimeUnixNano on it. If the spand was already previously ended, this returns undefined
|
|
29
|
+
* to prevent the span from being ended multiple times on accident.
|
|
30
|
+
*/
|
|
26
31
|
export function endSpan(span, status, durationNano) {
|
|
27
32
|
// We cast here to avoid having to instantiate a copy of the span
|
|
28
33
|
const s = span;
|
|
34
|
+
if (s.endTimeUnixNano) {
|
|
35
|
+
debug("Attempting to end already ended span. Dropping...", s);
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
29
38
|
if (status) {
|
|
30
39
|
s.status = status;
|
|
31
40
|
}
|
|
@@ -6,6 +6,7 @@ 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
8
|
const OBSERVER_WAIT_TIME_MS = 300;
|
|
9
|
+
const MAX_RESOURCES_ENTRIES = 100;
|
|
9
10
|
export const isResourceTimingAvailable = !!(perf && perf.getEntriesByType);
|
|
10
11
|
export const isPerformanceObserverAvailable = perf && typeof win["PerformanceObserver"] === "function" && typeof perf["now"] === "function";
|
|
11
12
|
export const PerformanceTimingNames = Object.freeze({
|
|
@@ -69,8 +70,11 @@ export function observeResourcePerformance(opts) {
|
|
|
69
70
|
}
|
|
70
71
|
fallbackEndNeverCalledTimerHandle = setTimeout(disposeGlobalResources, TEN_MINUTES_IN_MILLIS);
|
|
71
72
|
}
|
|
72
|
-
function onEnd() {
|
|
73
|
-
endTime
|
|
73
|
+
function onEnd(endTs) {
|
|
74
|
+
// If endTime is already set, we are already terminating and should not do so again
|
|
75
|
+
if (endTime)
|
|
76
|
+
return;
|
|
77
|
+
endTime = endTs ?? perf.now();
|
|
74
78
|
cancelFallbackEndNeverCalledTimer();
|
|
75
79
|
if (!isWaitingAcceptable()) {
|
|
76
80
|
return end();
|
|
@@ -101,7 +105,16 @@ export function observeResourcePerformance(opts) {
|
|
|
101
105
|
const entry = e;
|
|
102
106
|
return entry.startTime >= startTime && opts.resourceMatcher(entry);
|
|
103
107
|
})
|
|
104
|
-
.forEach((entry) =>
|
|
108
|
+
.forEach((entry) => {
|
|
109
|
+
// Limit array size to prevent memory leaks on high-traffic pages
|
|
110
|
+
if (resources.length >= MAX_RESOURCES_ENTRIES) {
|
|
111
|
+
// Remove oldest entry (FIFO)
|
|
112
|
+
// Timings are only reported once the resource is fully recorded (i.e. the request has ended), so timings ending
|
|
113
|
+
// way before the `onEnd` is called are less likely to be good matches and we can ignore them.
|
|
114
|
+
resources.shift();
|
|
115
|
+
}
|
|
116
|
+
resources.push(entry);
|
|
117
|
+
});
|
|
105
118
|
}
|
|
106
119
|
function onVisibilityChanged() {
|
|
107
120
|
if (!isWaitingAcceptable())
|