@opentelemetry/browser-instrumentation 0.6.0 → 0.8.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 +108 -1
- package/dist/fetch/index.d.ts +3 -0
- package/dist/fetch/index.js +2 -0
- package/dist/fetch/instrumentation.d.ts +36 -0
- package/dist/fetch/instrumentation.js +223 -0
- package/dist/fetch/instrumentation.js.map +1 -0
- package/dist/fetch/semconv.js +13 -0
- package/dist/fetch/semconv.js.map +1 -0
- package/dist/fetch/types.d.ts +52 -0
- package/dist/package.js +1 -1
- package/dist/resource-timing/instrumentation.js +12 -11
- package/dist/resource-timing/instrumentation.js.map +1 -1
- package/dist/user-action/instrumentation.d.ts +1 -0
- package/dist/user-action/instrumentation.js +11 -3
- package/dist/user-action/instrumentation.js.map +1 -1
- package/dist/user-action/types.d.ts +5 -1
- package/dist/utils/ContextRegistry.js +77 -0
- package/dist/utils/ContextRegistry.js.map +1 -0
- package/dist/utils/NetworkContextRegistry.js +23 -0
- package/dist/utils/NetworkContextRegistry.js.map +1 -0
- package/dist/utils/getElementCSSSelector.js.map +1 -1
- package/dist/utils/request.js +131 -0
- package/dist/utils/request.js.map +1 -0
- package/dist/utils/url.js +36 -0
- package/dist/utils/url.js.map +1 -0
- package/dist/xhr/index.d.ts +3 -0
- package/dist/xhr/index.js +2 -0
- package/dist/xhr/instrumentation.d.ts +39 -0
- package/dist/xhr/instrumentation.js +187 -0
- package/dist/xhr/instrumentation.js.map +1 -0
- package/dist/xhr/semconv.js +13 -0
- package/dist/xhr/semconv.js.map +1 -0
- package/dist/xhr/types.d.ts +23 -0
- package/package.json +15 -8
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { version } from "../package.js";
|
|
2
|
+
import { getNetworkContextRegistry } from "../utils/NetworkContextRegistry.js";
|
|
3
|
+
import { getXHRBodyLength, normalizeHttpRequestMethod } from "../utils/request.js";
|
|
4
|
+
import { matchesUrl, parseUrl, serverPortFromUrl } from "../utils/url.js";
|
|
5
|
+
import { ATTR_HTTP_REQUEST_BODY_SIZE } from "./semconv.js";
|
|
6
|
+
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
7
|
+
import { InstrumentationBase, safeExecuteInTheMiddle } from "@opentelemetry/instrumentation";
|
|
8
|
+
import { ATTR_ERROR_TYPE, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_REQUEST_METHOD_ORIGINAL, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
9
|
+
//#region src/xhr/instrumentation.ts
|
|
10
|
+
var XhrInstrumentation = class extends InstrumentationBase {
|
|
11
|
+
_xhrSpanMap = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
super("@opentelemetry/browser-instrumentation/xhr", version, config);
|
|
14
|
+
}
|
|
15
|
+
init() {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
enable() {
|
|
19
|
+
if (this._isEnabled) return;
|
|
20
|
+
if (this._isXhrPatched) {
|
|
21
|
+
this._diag.debug("XMLHttpRequest prototype already patched");
|
|
22
|
+
this._isEnabled = true;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
this._wrap(XMLHttpRequest.prototype, "open", this._patchOpen());
|
|
27
|
+
} catch (err) {
|
|
28
|
+
this._diag.warn("Failed to patch XMLHttpRequest.prototype.open; instrumentation will not be enabled. Another script may have locked XMLHttpRequest.prototype.open via Object.defineProperty.", err);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
this._wrap(XMLHttpRequest.prototype, "send", this._patchSend());
|
|
33
|
+
this._isXhrPatched = true;
|
|
34
|
+
this._isEnabled = true;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
this._unwrap(XMLHttpRequest.prototype, "open");
|
|
37
|
+
this._diag.warn("Failed to patch XMLHttpRequest.prototype.send; instrumentation will not be enabled. Another script may have locked XMLHttpRequest.prototype.send via Object.defineProperty.", err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
disable() {
|
|
41
|
+
if (!this._isEnabled) return;
|
|
42
|
+
this._isEnabled = false;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Patches the "open" method of XmlHttpRequest
|
|
46
|
+
*/
|
|
47
|
+
_patchOpen() {
|
|
48
|
+
return (original) => {
|
|
49
|
+
const instrumentation = this;
|
|
50
|
+
return function patchedOpen(...args) {
|
|
51
|
+
if (!instrumentation._isEnabled) return original.apply(this, args);
|
|
52
|
+
try {
|
|
53
|
+
const method = args[0];
|
|
54
|
+
const urlArgument = typeof args[1] === "string" ? args[1] : args[1].toString();
|
|
55
|
+
const url = parseUrl(urlArgument);
|
|
56
|
+
if (matchesUrl(url.href, instrumentation.getConfig().ignoreUrls)) return original.apply(this, args);
|
|
57
|
+
instrumentation._xhrSpanMap.set(this, {
|
|
58
|
+
method,
|
|
59
|
+
url
|
|
60
|
+
});
|
|
61
|
+
} catch (e) {
|
|
62
|
+
instrumentation._diag.error("Failed to instrument XmlHttpRequest.open", e);
|
|
63
|
+
}
|
|
64
|
+
return original.apply(this, args);
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Patches the "open" method of XmlHttpRequest
|
|
70
|
+
*/
|
|
71
|
+
_patchSend() {
|
|
72
|
+
return (original) => {
|
|
73
|
+
const instrumentation = this;
|
|
74
|
+
return function patchedSend(...args) {
|
|
75
|
+
if (!instrumentation._isEnabled) return original.apply(this, args);
|
|
76
|
+
const xhrRecord = instrumentation._xhrSpanMap.get(this);
|
|
77
|
+
if (xhrRecord) try {
|
|
78
|
+
const { method, url } = xhrRecord;
|
|
79
|
+
const span = instrumentation._createSpan(url, method);
|
|
80
|
+
xhrRecord.span = span;
|
|
81
|
+
xhrRecord.start = performance.now();
|
|
82
|
+
if (instrumentation.getConfig().measureRequestSize && args?.[0]) {
|
|
83
|
+
const bodyLength = getXHRBodyLength(args[0]);
|
|
84
|
+
if (bodyLength) span.setAttribute(ATTR_HTTP_REQUEST_BODY_SIZE, bodyLength);
|
|
85
|
+
}
|
|
86
|
+
const onAbort = () => instrumentation._endSpan(this, "abort");
|
|
87
|
+
const onError = () => instrumentation._endSpan(this, "error");
|
|
88
|
+
const onTimeout = () => instrumentation._endSpan(this, "timeout");
|
|
89
|
+
const onLoad = () => instrumentation._endSpan(this, "load");
|
|
90
|
+
xhrRecord.listeners = {
|
|
91
|
+
abort: onAbort,
|
|
92
|
+
error: onError,
|
|
93
|
+
timeout: onTimeout,
|
|
94
|
+
load: onLoad
|
|
95
|
+
};
|
|
96
|
+
const xhrContext = trace.setSpan(context.active(), span);
|
|
97
|
+
context.with(xhrContext, () => {
|
|
98
|
+
this.addEventListener("abort", onAbort);
|
|
99
|
+
this.addEventListener("error", onError);
|
|
100
|
+
this.addEventListener("timeout", onTimeout);
|
|
101
|
+
this.addEventListener("load", onLoad);
|
|
102
|
+
instrumentation._addHeaders(this, url, xhrContext);
|
|
103
|
+
});
|
|
104
|
+
} catch (e) {
|
|
105
|
+
instrumentation._diag.error("Failed to instrument fetch request", e);
|
|
106
|
+
instrumentation._xhrSpanMap.delete(this);
|
|
107
|
+
}
|
|
108
|
+
return original.apply(this, args);
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates a new span when method "send" is called
|
|
114
|
+
*/
|
|
115
|
+
_createSpan(url, method) {
|
|
116
|
+
const origMethod = method;
|
|
117
|
+
const normMethod = normalizeHttpRequestMethod(method);
|
|
118
|
+
const attributes = {};
|
|
119
|
+
const { sanitizeUrl } = this.getConfig();
|
|
120
|
+
attributes[ATTR_HTTP_REQUEST_METHOD] = normMethod;
|
|
121
|
+
if (normMethod !== origMethod) attributes[ATTR_HTTP_REQUEST_METHOD_ORIGINAL] = origMethod;
|
|
122
|
+
attributes[ATTR_URL_FULL] = sanitizeUrl ? sanitizeUrl(url.href) : url.href;
|
|
123
|
+
attributes[ATTR_SERVER_ADDRESS] = url.hostname;
|
|
124
|
+
const serverPort = serverPortFromUrl(url);
|
|
125
|
+
if (serverPort) attributes[ATTR_SERVER_PORT] = serverPort;
|
|
126
|
+
return this.tracer.startSpan(normMethod, {
|
|
127
|
+
kind: SpanKind.CLIENT,
|
|
128
|
+
attributes
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Finish span, add attributes, network events etc.
|
|
133
|
+
*/
|
|
134
|
+
_endSpan(xhr, eventName) {
|
|
135
|
+
const xhrRecord = this._xhrSpanMap.get(xhr);
|
|
136
|
+
if (xhrRecord?.listeners) for (const k of Object.keys(xhrRecord.listeners)) xhr.removeEventListener(k, xhrRecord.listeners[k]);
|
|
137
|
+
if (xhrRecord?.span) {
|
|
138
|
+
const { span, url, start } = xhrRecord;
|
|
139
|
+
if (eventName === "load") span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, xhr.status);
|
|
140
|
+
if (eventName !== "abort") {
|
|
141
|
+
if (xhr.status >= 400 || eventName === "error" || eventName === "timeout") {
|
|
142
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
143
|
+
const errorType = xhr.status >= 400 ? String(xhr.status) : eventName === "error" ? "Error" : "TimeoutError";
|
|
144
|
+
span.setAttribute(ATTR_ERROR_TYPE, errorType);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
this._xhrSpanMap.delete(xhr);
|
|
148
|
+
this._applyAttributesAfterSend(span, xhr);
|
|
149
|
+
span.end();
|
|
150
|
+
getNetworkContextRegistry().register(span, {
|
|
151
|
+
key: url.href,
|
|
152
|
+
startPerfNow: Number(start),
|
|
153
|
+
endPerfNow: performance.now()
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Runs the user provided `applyCustomAttributesOnSpan` function
|
|
159
|
+
*/
|
|
160
|
+
_applyAttributesAfterSend(span, xhr) {
|
|
161
|
+
const applyCustomAttributesOnSpan = this.getConfig().applyCustomAttributesOnSpan;
|
|
162
|
+
if (applyCustomAttributesOnSpan) safeExecuteInTheMiddle(() => applyCustomAttributesOnSpan(span, xhr), (error) => {
|
|
163
|
+
if (!error) return;
|
|
164
|
+
this._diag.error("applyCustomAttributesOnSpan", error);
|
|
165
|
+
}, true);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Adds custom headers to XMLHttpRequest
|
|
169
|
+
*/
|
|
170
|
+
_addHeaders(xhr, url, ctx) {
|
|
171
|
+
const urlsToPropagate = this.getConfig().propagateTraceHeaderCorsUrls;
|
|
172
|
+
if (location.origin === url.origin || matchesUrl(url.href, urlsToPropagate)) propagation.inject(ctx, xhr, { set: (x, k, v) => {
|
|
173
|
+
x.setRequestHeader(k, typeof v === "string" ? v : String(v));
|
|
174
|
+
} });
|
|
175
|
+
else {
|
|
176
|
+
const headers = {};
|
|
177
|
+
propagation.inject(ctx, headers, { set: (h, k, v) => {
|
|
178
|
+
h[k] = v;
|
|
179
|
+
} });
|
|
180
|
+
if (Object.keys(headers).length > 0) this._diag.debug("headers inject skipped due to CORS policy");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
//#endregion
|
|
185
|
+
export { XhrInstrumentation };
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=instrumentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/xhr/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Attributes, Context, Span } from '@opentelemetry/api';\nimport {\n context,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace,\n} from '@opentelemetry/api';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport {\n ATTR_ERROR_TYPE,\n ATTR_HTTP_REQUEST_METHOD,\n ATTR_HTTP_REQUEST_METHOD_ORIGINAL,\n ATTR_HTTP_RESPONSE_STATUS_CODE,\n ATTR_SERVER_ADDRESS,\n ATTR_SERVER_PORT,\n ATTR_URL_FULL,\n} from '@opentelemetry/semantic-conventions';\nimport { version } from '../../package.json' with { type: 'json' };\nimport { getNetworkContextRegistry } from '../utils/NetworkContextRegistry.ts';\nimport {\n getXHRBodyLength,\n normalizeHttpRequestMethod,\n} from '../utils/request.ts';\nimport { matchesUrl, parseUrl, serverPortFromUrl } from '../utils/url.ts';\nimport { ATTR_HTTP_REQUEST_BODY_SIZE } from './semconv.ts';\nimport type { XhrInstrumentationConfig } from './types.ts';\n\ntype XhrOpenFunction = typeof XMLHttpRequest.prototype.open;\ntype XhrSendFunction = typeof XMLHttpRequest.prototype.send;\ntype XhrEventName = 'abort' | 'timeout' | 'error' | 'load';\ntype XhrRecord = {\n method: string;\n url: URL;\n start?: number;\n span?: Span;\n listeners?: Record<XhrEventName, (...args: unknown[]) => unknown>;\n};\n\nexport class XhrInstrumentation extends InstrumentationBase<XhrInstrumentationConfig> {\n // Note: Intentionally *not* using `_enabled` as the field name to avoid\n // any possible confusion with the `_enabled` field used on the *Node.js*\n // InstrumentationBase class.\n // Also not initializing the fields to `false` because the base class\n // constructor already call `enable` modifying their values and it will\n // set the instrumentaitons in a base state (enabled, patched but with flags set to false)\n private declare _isEnabled: boolean;\n private declare _isXhrPatched: boolean;\n\n // To keep references to span/xhr tuples across XHR events and stores\n // init data like URL and method\n private _xhrSpanMap: WeakMap<XMLHttpRequest, XhrRecord> = new WeakMap();\n\n constructor(config: XhrInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/xhr', version, config);\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n\n if (this._isXhrPatched) {\n this._diag.debug('XMLHttpRequest prototype already patched');\n this._isEnabled = true;\n return;\n }\n\n // `_wrap` throws if a third-party script has locked the target methods via\n // Object.defineProperty(XMLHttpRequest.prototype, 'open', { writable: false, ... }).\n try {\n this._wrap(XMLHttpRequest.prototype, 'open', this._patchOpen());\n } catch (err) {\n this._diag.warn(\n 'Failed to patch XMLHttpRequest.prototype.open; instrumentation will not be enabled. ' +\n 'Another script may have locked XMLHttpRequest.prototype.open via Object.defineProperty.',\n err,\n );\n return;\n }\n\n // If 1st patch has succeded try the second. Unpatch `open` if error\n // here to avoid having multiple patches of `open`\n try {\n this._wrap(XMLHttpRequest.prototype, 'send', this._patchSend());\n this._isXhrPatched = true;\n this._isEnabled = true;\n } catch (err) {\n this._unwrap(XMLHttpRequest.prototype, 'open');\n this._diag.warn(\n 'Failed to patch XMLHttpRequest.prototype.send; instrumentation will not be enabled. ' +\n 'Another script may have locked XMLHttpRequest.prototype.send via Object.defineProperty.',\n err,\n );\n }\n }\n\n override disable(): void {\n if (!this._isEnabled) {\n return;\n }\n this._isEnabled = false;\n }\n\n /**\n * Patches the \"open\" method of XmlHttpRequest\n */\n private _patchOpen(): (original: XhrOpenFunction) => XhrOpenFunction {\n return (original) => {\n const instrumentation = this;\n\n return function patchedOpen(\n this: XMLHttpRequest,\n ...args: Parameters<XhrOpenFunction>\n ): ReturnType<XhrOpenFunction> {\n if (!instrumentation._isEnabled) {\n return original.apply(this, args);\n }\n try {\n const method = args[0];\n const urlArgument =\n typeof args[1] === 'string' ? args[1] : args[1].toString();\n const url = parseUrl(urlArgument);\n const shouldIgnoreUrl = matchesUrl(\n url.href,\n instrumentation.getConfig().ignoreUrls,\n );\n if (shouldIgnoreUrl) {\n return original.apply(this, args);\n }\n instrumentation._xhrSpanMap.set(this, { method, url });\n } catch (e: unknown) {\n instrumentation._diag.error(\n 'Failed to instrument XmlHttpRequest.open',\n e,\n );\n }\n return original.apply(this, args);\n } as XhrOpenFunction;\n };\n }\n\n /**\n * Patches the \"open\" method of XmlHttpRequest\n */\n private _patchSend(): (original: XhrSendFunction) => XhrSendFunction {\n return (original) => {\n const instrumentation = this;\n\n return function patchedSend(\n this: XMLHttpRequest,\n ...args: Parameters<XhrSendFunction>\n ): ReturnType<XhrSendFunction> {\n if (!instrumentation._isEnabled) {\n return original.apply(this, args);\n }\n\n // Instrument the method if the XHR was previously picked in `open`\n const xhrRecord = instrumentation._xhrSpanMap.get(this);\n if (xhrRecord) {\n try {\n const { method, url } = xhrRecord;\n const span = instrumentation._createSpan(url, method);\n xhrRecord.span = span;\n xhrRecord.start = performance.now();\n\n if (instrumentation.getConfig().measureRequestSize && args?.[0]) {\n const bodyLength = getXHRBodyLength(args[0]);\n if (bodyLength) {\n span.setAttribute(ATTR_HTTP_REQUEST_BODY_SIZE, bodyLength);\n }\n }\n\n const onAbort = () => instrumentation._endSpan(this, 'abort');\n const onError = () => instrumentation._endSpan(this, 'error');\n const onTimeout = () => instrumentation._endSpan(this, 'timeout');\n const onLoad = () => instrumentation._endSpan(this, 'load');\n xhrRecord.listeners = {\n abort: onAbort,\n error: onError,\n timeout: onTimeout,\n load: onLoad,\n };\n\n const xhrContext = trace.setSpan(context.active(), span);\n context.with(xhrContext, () => {\n this.addEventListener('abort', onAbort);\n this.addEventListener('error', onError);\n this.addEventListener('timeout', onTimeout);\n this.addEventListener('load', onLoad);\n instrumentation._addHeaders(this, url, xhrContext);\n });\n } catch (e: unknown) {\n // failed to instrument request, remove span\n instrumentation._diag.error(\n 'Failed to instrument fetch request',\n e,\n );\n instrumentation._xhrSpanMap.delete(this);\n }\n }\n return original.apply(this, args);\n } as XhrSendFunction;\n };\n }\n\n /**\n * Creates a new span when method \"send\" is called\n */\n private _createSpan(url: URL, method: string): Span {\n const origMethod = method;\n const normMethod = normalizeHttpRequestMethod(method);\n const attributes = {} as Attributes;\n const { sanitizeUrl } = this.getConfig();\n\n attributes[ATTR_HTTP_REQUEST_METHOD] = normMethod;\n if (normMethod !== origMethod) {\n attributes[ATTR_HTTP_REQUEST_METHOD_ORIGINAL] = origMethod;\n }\n attributes[ATTR_URL_FULL] = sanitizeUrl ? sanitizeUrl(url.href) : url.href;\n attributes[ATTR_SERVER_ADDRESS] = url.hostname;\n const serverPort = serverPortFromUrl(url);\n if (serverPort) {\n attributes[ATTR_SERVER_PORT] = serverPort;\n }\n\n return this.tracer.startSpan(normMethod, {\n kind: SpanKind.CLIENT,\n attributes,\n });\n }\n\n /**\n * Finish span, add attributes, network events etc.\n */\n private _endSpan(xhr: XMLHttpRequest, eventName: XhrEventName) {\n const xhrRecord = this._xhrSpanMap.get(xhr);\n\n if (xhrRecord?.listeners) {\n for (const k of Object.keys(xhrRecord.listeners)) {\n xhr.removeEventListener(\n k as XhrEventName,\n xhrRecord.listeners[k as XhrEventName],\n );\n }\n }\n\n if (xhrRecord?.span) {\n const { span, url, start } = xhrRecord;\n\n // Status code only has a meaningful value if request got a response\n if (eventName === 'load') {\n span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, xhr.status);\n }\n\n if (eventName !== 'abort') {\n // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#status\n // 1xx/2xx/3xx MUST be left unset UNLESS there was another error (e.g. a\n // network error receiving the response body)\n const isErrorStatus =\n xhr.status >= 400 || eventName === 'error' || eventName === 'timeout';\n\n if (isErrorStatus) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n const errorType =\n xhr.status >= 400\n ? String(xhr.status)\n : eventName === 'error'\n ? 'Error'\n : 'TimeoutError';\n span.setAttribute(ATTR_ERROR_TYPE, errorType);\n }\n }\n\n this._xhrSpanMap.delete(xhr);\n this._applyAttributesAfterSend(span, xhr);\n span.end();\n\n getNetworkContextRegistry().register(span, {\n key: url.href,\n startPerfNow: Number(start),\n endPerfNow: performance.now(),\n });\n }\n }\n\n /**\n * Runs the user provided `applyCustomAttributesOnSpan` function\n */\n private _applyAttributesAfterSend(span: Span, xhr: XMLHttpRequest) {\n const applyCustomAttributesOnSpan =\n this.getConfig().applyCustomAttributesOnSpan;\n if (applyCustomAttributesOnSpan) {\n safeExecuteInTheMiddle(\n () => applyCustomAttributesOnSpan(span, xhr),\n (error) => {\n if (!error) {\n return;\n }\n this._diag.error('applyCustomAttributesOnSpan', error);\n },\n true,\n );\n }\n }\n\n /**\n * Adds custom headers to XMLHttpRequest\n */\n private _addHeaders(xhr: XMLHttpRequest, url: URL, ctx: Context) {\n // Propagate only if in request goes to same origin or is in the allow list\n const urlsToPropagate = this.getConfig().propagateTraceHeaderCorsUrls;\n const sameOrigin = location.origin === url.origin;\n const shouldPropagate = sameOrigin || matchesUrl(url.href, urlsToPropagate);\n\n if (shouldPropagate) {\n propagation.inject(ctx, xhr, {\n set: (x, k, v) => {\n x.setRequestHeader(k, typeof v === 'string' ? v : String(v));\n },\n });\n } else {\n const headers: Partial<Record<string, unknown>> = {};\n propagation.inject(ctx, headers, {\n set: (h, k, v) => {\n h[k] = v;\n },\n });\n if (Object.keys(headers).length > 0) {\n this._diag.debug('headers inject skipped due to CORS policy');\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/xhr/semconv.ts
|
|
2
|
+
/**
|
|
3
|
+
* The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) header. For requests using transport encoding, this should be the compressed size.
|
|
4
|
+
*
|
|
5
|
+
* @example 3495
|
|
6
|
+
*
|
|
7
|
+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
|
|
8
|
+
*/
|
|
9
|
+
const ATTR_HTTP_REQUEST_BODY_SIZE = "http.request.body.size";
|
|
10
|
+
//#endregion
|
|
11
|
+
export { ATTR_HTTP_REQUEST_BODY_SIZE };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=semconv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semconv.js","names":[],"sources":["../../src/xhr/semconv.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/*\n * This file contains a copy of unstable semantic convention definitions\n * used by this package.\n * @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv\n */\n\n/**\n * The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) header. For requests using transport encoding, this should be the compressed size.\n *\n * @example 3495\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_HTTP_REQUEST_BODY_SIZE = 'http.request.body.size' as const;\n"],"mappings":";;;;;;;;AAkBA,MAAa,8BAA8B"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Span } from "@opentelemetry/api";
|
|
2
|
+
import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
3
|
+
//#region src/xhr/types.d.ts
|
|
4
|
+
type XhrCustomAttributeFunction = (span: Span, xhr: XMLHttpRequest) => void;
|
|
5
|
+
interface XhrInstrumentationConfig extends InstrumentationConfig {
|
|
6
|
+
/** URLs which should include trace headers when origin doesn't match */
|
|
7
|
+
propagateTraceHeaderCorsUrls?: Array<string | RegExp>;
|
|
8
|
+
/**
|
|
9
|
+
* URLs that partially match any regex in ignoreUrls will not be traced.
|
|
10
|
+
* In addition, URLs that are _exact matches_ of strings in ignoreUrls will
|
|
11
|
+
* also not be traced.
|
|
12
|
+
*/
|
|
13
|
+
ignoreUrls?: Array<string | RegExp>;
|
|
14
|
+
/** Function for adding custom attributes on the span */
|
|
15
|
+
applyCustomAttributesOnSpan?: XhrCustomAttributeFunction;
|
|
16
|
+
/** Measure outgoing request size */
|
|
17
|
+
measureRequestSize?: boolean;
|
|
18
|
+
/** Custom function to sanitize URLs before adding to log records. */
|
|
19
|
+
sanitizeUrl?: (url: string) => string;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { XhrInstrumentationConfig };
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentelemetry/browser-instrumentation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "OpenTelemetry browser instrumentations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opentelemetry",
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"navigation-timing",
|
|
14
14
|
"resource-timing",
|
|
15
15
|
"user-action",
|
|
16
|
-
"web-vitals"
|
|
16
|
+
"web-vitals",
|
|
17
|
+
"fetch",
|
|
18
|
+
"xhr"
|
|
17
19
|
],
|
|
18
20
|
"homepage": "https://github.com/open-telemetry/opentelemetry-browser",
|
|
19
21
|
"bugs": "https://github.com/open-telemetry/opentelemetry-browser/issues",
|
|
@@ -36,7 +38,9 @@
|
|
|
36
38
|
"./experimental/navigation-timing": "./dist/navigation-timing/index.js",
|
|
37
39
|
"./experimental/resource-timing": "./dist/resource-timing/index.js",
|
|
38
40
|
"./experimental/user-action": "./dist/user-action/index.js",
|
|
39
|
-
"./experimental/web-vitals": "./dist/web-vitals/index.js"
|
|
41
|
+
"./experimental/web-vitals": "./dist/web-vitals/index.js",
|
|
42
|
+
"./experimental/fetch": "./dist/fetch/index.js",
|
|
43
|
+
"./experimental/xhr": "./dist/xhr/index.js"
|
|
40
44
|
},
|
|
41
45
|
"sideEffects": false,
|
|
42
46
|
"files": [
|
|
@@ -51,17 +55,20 @@
|
|
|
51
55
|
"test:coverage": "vitest --coverage"
|
|
52
56
|
},
|
|
53
57
|
"dependencies": {
|
|
54
|
-
"@opentelemetry/api-logs": "^0.
|
|
55
|
-
"@opentelemetry/
|
|
56
|
-
"@opentelemetry/instrumentation": "^0.220.0",
|
|
58
|
+
"@opentelemetry/api-logs": "^0.221.0",
|
|
59
|
+
"@opentelemetry/instrumentation": "^0.221.0",
|
|
57
60
|
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
58
|
-
"web-vitals": "^
|
|
61
|
+
"web-vitals": "^6.1.1"
|
|
59
62
|
},
|
|
60
63
|
"peerDependencies": {
|
|
61
64
|
"@opentelemetry/api": "^1.9.1"
|
|
62
65
|
},
|
|
63
66
|
"devDependencies": {
|
|
64
|
-
"@opentelemetry/
|
|
67
|
+
"@opentelemetry/core": "2.10.0",
|
|
68
|
+
"@opentelemetry/propagator-b3": "2.10.0",
|
|
69
|
+
"@opentelemetry/sdk-logs": "0.221.0",
|
|
70
|
+
"@opentelemetry/sdk-trace-base": "2.10.0",
|
|
71
|
+
"msw": "2.15.0"
|
|
65
72
|
},
|
|
66
73
|
"publishConfig": {
|
|
67
74
|
"access": "public"
|