@dash0/sdk-web 0.16.0 → 0.16.1
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 +78 -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 +89 -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 { 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 {
|
|
4
4
|
addAttribute,
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
SPAN_STATUS_ERROR,
|
|
21
21
|
SPAN_STATUS_UNSET,
|
|
22
22
|
} from "../../semantic-conventions";
|
|
23
|
-
import { isSameOrigin, wrap, parseUrl } from "../../utils";
|
|
24
23
|
import { vars, PropagatorType } from "../../vars";
|
|
25
24
|
import { httpRequestHeaderKey, httpResponseHeaderKey } from "../../utils/otel/http";
|
|
26
25
|
import { sendSpan } from "../../transport";
|
|
@@ -111,18 +110,18 @@ function wrapFetch(original: typeof fetch) {
|
|
|
111
110
|
|
|
112
111
|
performanceObserver.start();
|
|
113
112
|
try {
|
|
114
|
-
const
|
|
115
|
-
addResponseData(span,
|
|
113
|
+
const origResponse = await original(input instanceof Request ? request : input, copyOfInit);
|
|
114
|
+
addResponseData(span, origResponse);
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.
|
|
120
|
-
|
|
116
|
+
return wrapResponse(
|
|
117
|
+
origResponse,
|
|
118
|
+
vars.maxToleranceForResourceTimingsMillis,
|
|
119
|
+
() => performanceObserver.end(),
|
|
120
|
+
(e) => {
|
|
121
121
|
performanceObserver.cancel();
|
|
122
|
-
endSpanOnError(span, e
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return response;
|
|
122
|
+
endSpanOnError(span, e);
|
|
123
|
+
}
|
|
124
|
+
);
|
|
126
125
|
} catch (e) {
|
|
127
126
|
performanceObserver.cancel();
|
|
128
127
|
endSpanOnError(span, e as Exception);
|
|
@@ -177,20 +176,86 @@ function addResponseData(span: InProgressSpan, response: Response) {
|
|
|
177
176
|
tryCaptureHttpHeaders(response.headers, span, (k) => httpResponseHeaderKey(k));
|
|
178
177
|
}
|
|
179
178
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Wraps the response to be able to detect when it is fully read
|
|
181
|
+
* @param originalResponse
|
|
182
|
+
* @param readTimeoutMs Timeout applied between reading of response chunks, if exceeded the response is considered abandoned and onDone is called.
|
|
183
|
+
* @param onDone Called when the response is completely read or with "fallbackEndTs" when reading timed out.
|
|
184
|
+
* @param onError
|
|
185
|
+
*/
|
|
186
|
+
function wrapResponse(
|
|
187
|
+
originalResponse: Response,
|
|
188
|
+
readTimeoutMs: number,
|
|
189
|
+
onDone: (fallbackEndTs?: number) => void,
|
|
190
|
+
onError: (e: Exception) => void
|
|
191
|
+
): Response {
|
|
192
|
+
// When the response was wrapped (i.e. first available to js) we use this to replace or find the actual end timestamp
|
|
193
|
+
// in case the response body is never read by js
|
|
194
|
+
let fallbackTs: number = perf.now();
|
|
195
|
+
const body = originalResponse.body;
|
|
196
|
+
|
|
197
|
+
if (!body) {
|
|
198
|
+
onDone();
|
|
199
|
+
return originalResponse;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let cbCalled: boolean = false;
|
|
203
|
+
const handleDone = (fallbackEndTs?: number) => {
|
|
204
|
+
if (cbCalled) return;
|
|
205
|
+
onDone(fallbackEndTs);
|
|
206
|
+
cbCalled = true;
|
|
207
|
+
};
|
|
208
|
+
const handleError = (e: Exception) => {
|
|
209
|
+
if (cbCalled) return;
|
|
210
|
+
onError(e);
|
|
211
|
+
cbCalled = true;
|
|
212
|
+
};
|
|
184
213
|
|
|
185
|
-
|
|
214
|
+
let bodyNeverCompletelyReadTimeout = setTimeout(() => handleDone(fallbackTs), readTimeoutMs);
|
|
215
|
+
|
|
216
|
+
const reader = body.getReader();
|
|
217
|
+
const stream = new ReadableStream({
|
|
218
|
+
async pull(controller) {
|
|
219
|
+
try {
|
|
220
|
+
clearTimeout(bodyNeverCompletelyReadTimeout);
|
|
221
|
+
const { value, done } = await reader.read();
|
|
222
|
+
if (done) {
|
|
223
|
+
reader.releaseLock();
|
|
224
|
+
controller.close();
|
|
225
|
+
handleDone();
|
|
226
|
+
} else {
|
|
227
|
+
fallbackTs = perf.now();
|
|
228
|
+
bodyNeverCompletelyReadTimeout = setTimeout(() => handleDone(fallbackTs), readTimeoutMs);
|
|
229
|
+
controller.enqueue(value);
|
|
230
|
+
}
|
|
231
|
+
} catch (e) {
|
|
232
|
+
handleError(e as Exception);
|
|
233
|
+
controller.error(e);
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
reader.releaseLock();
|
|
237
|
+
} catch {
|
|
238
|
+
// Spec reference:
|
|
239
|
+
// https://streams.spec.whatwg.org/#default-reader-release-lock
|
|
240
|
+
//
|
|
241
|
+
// releaseLock() only throws if called on an invalid reader
|
|
242
|
+
// (i.e. reader.[[stream]] is undefined, meaning the lock is already released
|
|
243
|
+
// or the reader was never associated). In normal use this cannot happen.
|
|
244
|
+
// This catch is defensive only.
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
cancel(reason) {
|
|
249
|
+
clearTimeout(bodyNeverCompletelyReadTimeout);
|
|
250
|
+
handleDone();
|
|
251
|
+
return reader.cancel(reason);
|
|
252
|
+
},
|
|
253
|
+
});
|
|
186
254
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return read();
|
|
192
|
-
};
|
|
193
|
-
return read();
|
|
255
|
+
return new Response(stream, {
|
|
256
|
+
status: originalResponse.status,
|
|
257
|
+
statusText: originalResponse.statusText,
|
|
258
|
+
headers: originalResponse.headers,
|
|
194
259
|
});
|
|
195
260
|
}
|
|
196
261
|
|
package/src/transport/index.ts
CHANGED
|
@@ -47,7 +47,9 @@ function sendLogs(logs: LogRecord[]): void {
|
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
export function sendSpan(span: Span): void {
|
|
50
|
+
export function sendSpan(span: Span | undefined): void {
|
|
51
|
+
if (!span) return;
|
|
52
|
+
|
|
51
53
|
if (isRateLimited()) {
|
|
52
54
|
debug("Transport rate limit. Will not send item.", span);
|
|
53
55
|
return;
|
package/src/utils/index.ts
CHANGED
package/src/utils/otel/span.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { generateTraceId } from "../trace-id";
|
|
|
5
5
|
import { generateSpanId } from "../span-id";
|
|
6
6
|
import { addAttribute } from "./attributes";
|
|
7
7
|
import { KeyValue, Span, SpanStatus } from "../../types/otlp";
|
|
8
|
+
import { debug } from "../debug";
|
|
8
9
|
|
|
9
10
|
export type InProgressSpan = Omit<Span, "endTimeUnixNano">;
|
|
10
11
|
|
|
@@ -30,9 +31,23 @@ export function startSpan(name: string): InProgressSpan {
|
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Ends the span by setting status and endTimeUnixNano on it. If the spand was already previously ended, this returns undefined
|
|
36
|
+
* to prevent the span from being ended multiple times on accident.
|
|
37
|
+
*/
|
|
38
|
+
export function endSpan(
|
|
39
|
+
span: InProgressSpan,
|
|
40
|
+
status: SpanStatus | undefined,
|
|
41
|
+
durationNano: number | undefined
|
|
42
|
+
): Span | undefined {
|
|
34
43
|
// We cast here to avoid having to instantiate a copy of the span
|
|
35
44
|
const s = span as Span;
|
|
45
|
+
|
|
46
|
+
if (s.endTimeUnixNano) {
|
|
47
|
+
debug("Attempting to end already ended span. Dropping...", s);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
if (status) {
|
|
37
52
|
s.status = status;
|
|
38
53
|
}
|
package/src/utils/performance.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { addEventListener, removeEventListener } from "./listeners";
|
|
|
7
7
|
const TEN_MINUTES_IN_MILLIS = 1000 * 60 * 10;
|
|
8
8
|
const ONE_DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
|
|
9
9
|
const OBSERVER_WAIT_TIME_MS = 300;
|
|
10
|
+
const MAX_RESOURCES_ENTRIES = 100;
|
|
10
11
|
|
|
11
12
|
export const isResourceTimingAvailable = !!(perf && perf.getEntriesByType);
|
|
12
13
|
export const isPerformanceObserverAvailable =
|
|
@@ -54,7 +55,11 @@ type ObserveResourcePerformanceOptions = {
|
|
|
54
55
|
|
|
55
56
|
type PerformanceObservationContoller = {
|
|
56
57
|
start: () => void;
|
|
57
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Ends performance observation and starts matching the observed resources.
|
|
60
|
+
* @param endTs End performance timing timestamp to use for the end of the resource, instead of the current time when end is called. Useful when calling end from a timeout handler
|
|
61
|
+
*/
|
|
62
|
+
end: (endTs?: number) => void;
|
|
58
63
|
cancel: () => void;
|
|
59
64
|
};
|
|
60
65
|
|
|
@@ -99,8 +104,11 @@ export function observeResourcePerformance(opts: ObserveResourcePerformanceOptio
|
|
|
99
104
|
fallbackEndNeverCalledTimerHandle = setTimeout(disposeGlobalResources, TEN_MINUTES_IN_MILLIS);
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
function onEnd() {
|
|
103
|
-
endTime
|
|
107
|
+
function onEnd(endTs?: number) {
|
|
108
|
+
// If endTime is already set, we are already terminating and should not do so again
|
|
109
|
+
if (endTime) return;
|
|
110
|
+
|
|
111
|
+
endTime = endTs ?? perf.now();
|
|
104
112
|
cancelFallbackEndNeverCalledTimer();
|
|
105
113
|
|
|
106
114
|
if (!isWaitingAcceptable()) {
|
|
@@ -136,7 +144,16 @@ export function observeResourcePerformance(opts: ObserveResourcePerformanceOptio
|
|
|
136
144
|
const entry = e as PerformanceResourceTiming;
|
|
137
145
|
return entry.startTime >= startTime && opts.resourceMatcher(entry);
|
|
138
146
|
})
|
|
139
|
-
.forEach((entry) =>
|
|
147
|
+
.forEach((entry) => {
|
|
148
|
+
// Limit array size to prevent memory leaks on high-traffic pages
|
|
149
|
+
if (resources.length >= MAX_RESOURCES_ENTRIES) {
|
|
150
|
+
// Remove oldest entry (FIFO)
|
|
151
|
+
// Timings are only reported once the resource is fully recorded (i.e. the request has ended), so timings ending
|
|
152
|
+
// way before the `onEnd` is called are less likely to be good matches and we can ignore them.
|
|
153
|
+
resources.shift();
|
|
154
|
+
}
|
|
155
|
+
resources.push(entry as PerformanceResourceTiming);
|
|
156
|
+
});
|
|
140
157
|
}
|
|
141
158
|
|
|
142
159
|
function onVisibilityChanged() {
|