@opentelemetry/browser-instrumentation 0.8.0 → 0.8.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/dist/console/instrumentation.d.ts +1 -2
- package/dist/console/instrumentation.js.map +1 -1
- package/dist/console/types.d.ts +2 -3
- package/dist/errors/instrumentation.d.ts +1 -2
- package/dist/errors/instrumentation.js.map +1 -1
- package/dist/errors/types.d.ts +2 -3
- package/dist/fetch/instrumentation.d.ts +1 -2
- package/dist/fetch/instrumentation.js.map +1 -1
- package/dist/fetch/types.d.ts +5 -6
- package/dist/navigation/instrumentation.d.ts +1 -2
- package/dist/navigation/instrumentation.js.map +1 -1
- package/dist/navigation/types.d.ts +4 -5
- package/dist/navigation-timing/instrumentation.d.ts +1 -2
- package/dist/navigation-timing/instrumentation.js.map +1 -1
- package/dist/navigation-timing/types.d.ts +1 -2
- package/dist/package.js +1 -1
- package/dist/resource-timing/instrumentation.d.ts +1 -2
- package/dist/resource-timing/instrumentation.js.map +1 -1
- package/dist/resource-timing/types.d.ts +1 -2
- package/dist/user-action/instrumentation.d.ts +1 -2
- package/dist/user-action/instrumentation.js.map +1 -1
- package/dist/user-action/types.d.ts +3 -4
- package/dist/web-vitals/instrumentation.d.ts +1 -2
- package/dist/web-vitals/instrumentation.js.map +1 -1
- package/dist/web-vitals/types.d.ts +1 -2
- package/dist/xhr/instrumentation.d.ts +1 -2
- package/dist/xhr/instrumentation.js.map +1 -1
- package/dist/xhr/types.d.ts +1 -2
- package/package.json +14 -8
|
@@ -4,7 +4,7 @@ import { InstrumentationBase } from "@opentelemetry/instrumentation";
|
|
|
4
4
|
/**
|
|
5
5
|
* OpenTelemetry instrumentation that captures console calls and emits them as OpenTelemetry logs.
|
|
6
6
|
*/
|
|
7
|
-
declare class ConsoleInstrumentation extends InstrumentationBase<ConsoleInstrumentationConfig> {
|
|
7
|
+
export declare class ConsoleInstrumentation extends InstrumentationBase<ConsoleInstrumentationConfig> {
|
|
8
8
|
private _isPatched;
|
|
9
9
|
private _active;
|
|
10
10
|
constructor(config?: ConsoleInstrumentationConfig);
|
|
@@ -16,5 +16,4 @@ declare class ConsoleInstrumentation extends InstrumentationBase<ConsoleInstrume
|
|
|
16
16
|
disable(): void;
|
|
17
17
|
}
|
|
18
18
|
//#endregion
|
|
19
|
-
export { ConsoleInstrumentation };
|
|
20
19
|
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/console/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { context } from '@opentelemetry/api';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts';\nimport type { ConsoleInstrumentationConfig, ConsoleMethod } from './types.ts';\n\nconst DEFAULT_LOG_METHODS: ConsoleMethod[] = [\n 'log',\n 'warn',\n 'error',\n 'info',\n 'debug',\n];\n\nconst SEVERITY_MAP: Record<ConsoleMethod, SeverityNumber> = {\n debug: SeverityNumber.DEBUG,\n log: SeverityNumber.INFO,\n info: SeverityNumber.INFO,\n warn: SeverityNumber.WARN,\n error: SeverityNumber.ERROR,\n};\n\n/**\n * Default serializer for console arguments.\n * Joins arguments as strings.\n */\nfunction defaultMessageSerializer(args: unknown[]): string {\n return args\n .map((arg) => {\n if (typeof arg === 'object' && arg !== null) {\n try {\n return JSON.stringify(arg);\n } catch {\n // Circular reference or other error, fallback to String\n return String(arg);\n }\n }\n return String(arg);\n })\n .join(' ');\n}\n\n/**\n * OpenTelemetry instrumentation that captures console calls and emits them as OpenTelemetry logs.\n */\nexport class ConsoleInstrumentation extends InstrumentationBase<ConsoleInstrumentationConfig> {\n private
|
|
1
|
+
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/console/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { context } from '@opentelemetry/api';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts';\nimport type { ConsoleInstrumentationConfig, ConsoleMethod } from './types.ts';\n\nconst DEFAULT_LOG_METHODS: ConsoleMethod[] = [\n 'log',\n 'warn',\n 'error',\n 'info',\n 'debug',\n];\n\nconst SEVERITY_MAP: Record<ConsoleMethod, SeverityNumber> = {\n debug: SeverityNumber.DEBUG,\n log: SeverityNumber.INFO,\n info: SeverityNumber.INFO,\n warn: SeverityNumber.WARN,\n error: SeverityNumber.ERROR,\n};\n\n/**\n * Default serializer for console arguments.\n * Joins arguments as strings.\n */\nfunction defaultMessageSerializer(args: unknown[]): string {\n return args\n .map((arg) => {\n if (typeof arg === 'object' && arg !== null) {\n try {\n return JSON.stringify(arg);\n } catch {\n // Circular reference or other error, fallback to String\n return String(arg);\n }\n }\n return String(arg);\n })\n .join(' ');\n}\n\n/**\n * OpenTelemetry instrumentation that captures console calls and emits them as OpenTelemetry logs.\n */\nexport class ConsoleInstrumentation extends InstrumentationBase<ConsoleInstrumentationConfig> {\n declare private _isPatched: boolean;\n declare private _active: boolean;\n\n constructor(config: ConsoleInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/console', version, config);\n }\n\n protected override init() {\n return [];\n }\n\n private _getMessageSerializer(): (args: unknown[]) => string {\n return this._config.messageSerializer ?? defaultMessageSerializer;\n }\n\n private _getLogMethods(): ConsoleMethod[] {\n return this._config.logMethods ?? DEFAULT_LOG_METHODS;\n }\n\n private _patchConsoleMethod(\n method: ConsoleMethod,\n ): (original: Console[ConsoleMethod]) => Console[ConsoleMethod] {\n const instrumentation = this;\n\n return function patchConsoleMethod(original: Console[ConsoleMethod]) {\n return function (this: Console, ...args: unknown[]) {\n if (\n instrumentation._active &&\n instrumentation._getLogMethods().includes(method)\n ) {\n const logContext = context.active();\n const body = instrumentation._getMessageSerializer()(args);\n\n instrumentation.logger.emit({\n body,\n eventName: CONSOLE_LOG_EVENT_NAME,\n severityNumber: SEVERITY_MAP[method],\n severityText: method,\n context: logContext,\n attributes: {\n [ATTR_CONSOLE_METHOD]: method,\n },\n });\n }\n\n return original.apply(this, args);\n } as Console[ConsoleMethod];\n };\n }\n\n override enable(): void {\n this._active = true;\n if (this._isPatched) {\n return;\n }\n this._isPatched = true;\n for (const method of DEFAULT_LOG_METHODS) {\n if (typeof console[method] === 'function') {\n this._wrap(console, method, this._patchConsoleMethod(method));\n }\n }\n }\n\n override disable(): void {\n this._active = false;\n }\n}\n"],"mappings":";;;;;;AAYA,MAAM,sBAAuC;CAC3C;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,eAAsD;CAC1D,OAAO,eAAe;CACtB,KAAK,eAAe;CACpB,MAAM,eAAe;CACrB,MAAM,eAAe;CACrB,OAAO,eAAe;AACxB;;;;;AAMA,SAAS,yBAAyB,MAAyB;CACzD,OAAO,KACJ,KAAK,QAAQ;EACZ,IAAI,OAAO,QAAQ,YAAY,QAAQ,MACrC,IAAI;GACF,OAAO,KAAK,UAAU,GAAG;EAC3B,QAAQ;GAEN,OAAO,OAAO,GAAG;EACnB;EAEF,OAAO,OAAO,GAAG;CACnB,CAAC,CAAC,CACD,KAAK,GAAG;AACb;;;;AAKA,IAAa,yBAAb,cAA4C,oBAAkD;CAI5F,YAAY,SAAuC,CAAC,GAAG;EACrD,MAAM,kDAAkD,SAAS,MAAM;CACzE;CAEA,OAA0B;EACxB,OAAO,CAAC;CACV;CAEA,wBAA6D;EAC3D,OAAO,KAAK,QAAQ,qBAAqB;CAC3C;CAEA,iBAA0C;EACxC,OAAO,KAAK,QAAQ,cAAc;CACpC;CAEA,oBACE,QAC8D;EAC9D,MAAM,kBAAkB;EAExB,OAAO,SAAS,mBAAmB,UAAkC;GACnE,OAAO,SAAyB,GAAG,MAAiB;IAClD,IACE,gBAAgB,WAChB,gBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,GAChD;KACA,MAAM,aAAa,QAAQ,OAAO;KAClC,MAAM,OAAO,gBAAgB,sBAAsB,CAAC,CAAC,IAAI;KAEzD,gBAAgB,OAAO,KAAK;MAC1B;MACA,WAAW;MACX,gBAAgB,aAAa;MAC7B,cAAc;MACd,SAAS;MACT,YAAY,GACT,sBAAsB,OACzB;KACF,CAAC;IACH;IAEA,OAAO,SAAS,MAAM,MAAM,IAAI;GAClC;EACF;CACF;CAEA,SAAwB;EACtB,KAAK,UAAU;EACf,IAAI,KAAK,YACP;EAEF,KAAK,aAAa;EAClB,KAAK,MAAM,UAAU,qBACnB,IAAI,OAAO,QAAQ,YAAY,YAC7B,KAAK,MAAM,SAAS,QAAQ,KAAK,oBAAoB,MAAM,CAAC;CAGlE;CAEA,UAAyB;EACvB,KAAK,UAAU;CACjB;AACF"}
|
package/dist/console/types.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
|
3
3
|
/**
|
|
4
4
|
* Console methods that can be instrumented.
|
|
5
5
|
*/
|
|
6
|
-
type ConsoleMethod = 'log' | 'warn' | 'error' | 'info' | 'debug';
|
|
6
|
+
export type ConsoleMethod = 'log' | 'warn' | 'error' | 'info' | 'debug';
|
|
7
7
|
/**
|
|
8
8
|
* ConsoleInstrumentation Configuration
|
|
9
9
|
*/
|
|
10
|
-
interface ConsoleInstrumentationConfig extends InstrumentationConfig {
|
|
10
|
+
export interface ConsoleInstrumentationConfig extends InstrumentationConfig {
|
|
11
11
|
/**
|
|
12
12
|
* Console methods to instrument.
|
|
13
13
|
* @default ['log', 'warn', 'error', 'info', 'debug']
|
|
@@ -20,5 +20,4 @@ interface ConsoleInstrumentationConfig extends InstrumentationConfig {
|
|
|
20
20
|
messageSerializer?: (args: unknown[]) => string;
|
|
21
21
|
}
|
|
22
22
|
//#endregion
|
|
23
|
-
export { ConsoleInstrumentationConfig, ConsoleMethod };
|
|
24
23
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ErrorsInstrumentationConfig } from "./types.js";
|
|
2
2
|
import { InstrumentationBase } from "@opentelemetry/instrumentation";
|
|
3
3
|
//#region src/errors/instrumentation.d.ts
|
|
4
|
-
declare class ErrorsInstrumentation extends InstrumentationBase<ErrorsInstrumentationConfig> {
|
|
4
|
+
export declare class ErrorsInstrumentation extends InstrumentationBase<ErrorsInstrumentationConfig> {
|
|
5
5
|
private _isEnabled;
|
|
6
6
|
private _onErrorHandler?;
|
|
7
7
|
constructor(config?: ErrorsInstrumentationConfig);
|
|
@@ -12,5 +12,4 @@ declare class ErrorsInstrumentation extends InstrumentationBase<ErrorsInstrument
|
|
|
12
12
|
private _applyCustomAttributes;
|
|
13
13
|
}
|
|
14
14
|
//#endregion
|
|
15
|
-
export { ErrorsInstrumentation };
|
|
16
15
|
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/errors/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Attributes } from '@opentelemetry/api';\nimport type { AnyValueMap, LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport {\n ATTR_EXCEPTION_MESSAGE,\n ATTR_EXCEPTION_STACKTRACE,\n ATTR_EXCEPTION_TYPE,\n} from '@opentelemetry/semantic-conventions';\nimport { version } from '../../package.json' with { type: 'json' };\nimport type { ErrorsInstrumentationConfig } from './types.ts';\n\nconst EXCEPTION_EVENT_NAME = 'exception';\n\nexport class ErrorsInstrumentation extends InstrumentationBase<ErrorsInstrumentationConfig> {\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n private
|
|
1
|
+
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/errors/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Attributes } from '@opentelemetry/api';\nimport type { AnyValueMap, LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport {\n ATTR_EXCEPTION_MESSAGE,\n ATTR_EXCEPTION_STACKTRACE,\n ATTR_EXCEPTION_TYPE,\n} from '@opentelemetry/semantic-conventions';\nimport { version } from '../../package.json' with { type: 'json' };\nimport type { ErrorsInstrumentationConfig } from './types.ts';\n\nconst EXCEPTION_EVENT_NAME = 'exception';\n\nexport class ErrorsInstrumentation extends InstrumentationBase<ErrorsInstrumentationConfig> {\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n declare private _isEnabled: boolean;\n declare private _onErrorHandler?: (\n event: ErrorEvent | PromiseRejectionEvent,\n ) => void;\n\n constructor(config: ErrorsInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/errors', version, config);\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n this._isEnabled = true;\n\n this._onErrorHandler = (event) => this._onError(event);\n window.addEventListener('error', this._onErrorHandler);\n window.addEventListener('unhandledrejection', this._onErrorHandler);\n }\n\n override disable(): void {\n if (!this._isEnabled) {\n return;\n }\n this._isEnabled = false;\n\n if (this._onErrorHandler) {\n window.removeEventListener('error', this._onErrorHandler);\n window.removeEventListener('unhandledrejection', this._onErrorHandler);\n this._onErrorHandler = undefined;\n }\n }\n\n private _onError(event: ErrorEvent | PromiseRejectionEvent): void {\n const isRejection = 'reason' in event;\n let error: Error | string | null | undefined = isRejection\n ? event.reason\n : event.error;\n\n // Cross-origin scripts deliver an ErrorEvent with `event.error` null but\n // a populated `event.message` (the canonical \"Script error.\" sanitized\n // message). Treat that message as a string error so these events still\n // surface as an exception log instead of being silently dropped.\n // PromiseRejectionEvent has no analogous message field, so it stays a\n // no-op.\n if (\n error == null &&\n !isRejection &&\n typeof event.message === 'string' &&\n event.message.length > 0\n ) {\n error = event.message;\n }\n\n if (error == null) {\n // Emit a diag debug message so the drop is observable. Anything that\n // lands here had no usable signal (ErrorEvent with no error and no\n // message, or PromiseRejectionEvent rejected with null/undefined). Pass\n // the raw value too so the reader can tell null from undefined when\n // investigating a missing capture.\n this._diag.debug(\n isRejection\n ? 'ignored unhandledrejection event with no reason'\n : 'ignored error event with no error and no message',\n error,\n );\n return;\n }\n\n // Capture as a const so the type narrowing survives into the closure below.\n const capturedError = error;\n\n // This runs inside a global error listener, so an escaping throw would\n // re-trigger the listener and loop. Both attribute extraction (a rejection\n // can carry a value whose `stack` getter throws) and emit (a broken\n // LogRecordProcessor) can throw, so contain the whole path.\n safeExecuteInTheMiddle(\n () => {\n let errorAttributes: AnyValueMap;\n if (typeof capturedError === 'string') {\n errorAttributes = { [ATTR_EXCEPTION_MESSAGE]: capturedError };\n } else {\n errorAttributes = {\n [ATTR_EXCEPTION_TYPE]: capturedError.name,\n [ATTR_EXCEPTION_MESSAGE]: capturedError.message,\n [ATTR_EXCEPTION_STACKTRACE]: capturedError.stack,\n };\n }\n\n const customAttributes = this._applyCustomAttributes(capturedError);\n\n const logRecord: LogRecord = {\n eventName: EXCEPTION_EVENT_NAME,\n severityNumber: SeverityNumber.ERROR,\n attributes: { ...errorAttributes, ...customAttributes },\n };\n\n this.logger.emit(logRecord);\n },\n (err) => {\n if (err) {\n this._diag.error('failed to record exception', err);\n }\n },\n true,\n );\n }\n\n private _applyCustomAttributes(error: Error | string): Attributes {\n const hook = this.getConfig().applyCustomAttributes;\n if (!hook) {\n return {};\n }\n let result: Attributes = {};\n safeExecuteInTheMiddle(\n () => {\n result = hook(error);\n },\n (err) => {\n if (err) {\n this._diag.error('applyCustomAttributes hook failed', err);\n }\n },\n true,\n );\n return result;\n }\n}\n"],"mappings":";;;;;AAoBA,MAAM,uBAAuB;AAE7B,IAAa,wBAAb,cAA2C,oBAAiD;CAS1F,YAAY,SAAsC,CAAC,GAAG;EACpD,MAAM,iDAAiD,SAAS,MAAM;CACxE;CAEA,OAA0B;EACxB,OAAO,CAAC;CACV;CAEA,SAAwB;EACtB,IAAI,KAAK,YACP;EAEF,KAAK,aAAa;EAElB,KAAK,mBAAmB,UAAU,KAAK,SAAS,KAAK;EACrD,OAAO,iBAAiB,SAAS,KAAK,eAAe;EACrD,OAAO,iBAAiB,sBAAsB,KAAK,eAAe;CACpE;CAEA,UAAyB;EACvB,IAAI,CAAC,KAAK,YACR;EAEF,KAAK,aAAa;EAElB,IAAI,KAAK,iBAAiB;GACxB,OAAO,oBAAoB,SAAS,KAAK,eAAe;GACxD,OAAO,oBAAoB,sBAAsB,KAAK,eAAe;GACrE,KAAK,kBAAkB,KAAA;EACzB;CACF;CAEA,SAAiB,OAAiD;EAChE,MAAM,cAAc,YAAY;EAChC,IAAI,QAA2C,cAC3C,MAAM,SACN,MAAM;EAQV,IACE,SAAS,QACT,CAAC,eACD,OAAO,MAAM,YAAY,YACzB,MAAM,QAAQ,SAAS,GAEvB,QAAQ,MAAM;EAGhB,IAAI,SAAS,MAAM;GAMjB,KAAK,MAAM,MACT,cACI,oDACA,oDACJ,KACF;GACA;EACF;EAGA,MAAM,gBAAgB;EAMtB,6BACQ;GACJ,IAAI;GACJ,IAAI,OAAO,kBAAkB,UAC3B,kBAAkB,GAAG,yBAAyB,cAAc;QAE5D,kBAAkB;KACf,sBAAsB,cAAc;KACpC,yBAAyB,cAAc;KACvC,4BAA4B,cAAc;GAC7C;GAGF,MAAM,mBAAmB,KAAK,uBAAuB,aAAa;GAElE,MAAM,YAAuB;IAC3B,WAAW;IACX,gBAAgB,eAAe;IAC/B,YAAY;KAAE,GAAG;KAAiB,GAAG;IAAiB;GACxD;GAEA,KAAK,OAAO,KAAK,SAAS;EAC5B,IACC,QAAQ;GACP,IAAI,KACF,KAAK,MAAM,MAAM,8BAA8B,GAAG;EAEtD,GACA,IACF;CACF;CAEA,uBAA+B,OAAmC;EAChE,MAAM,OAAO,KAAK,UAAU,CAAC,CAAC;EAC9B,IAAI,CAAC,MACH,OAAO,CAAC;EAEV,IAAI,SAAqB,CAAC;EAC1B,6BACQ;GACJ,SAAS,KAAK,KAAK;EACrB,IACC,QAAQ;GACP,IAAI,KACF,KAAK,MAAM,MAAM,qCAAqC,GAAG;EAE7D,GACA,IACF;EACA,OAAO;CACT;AACF"}
|
package/dist/errors/types.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Attributes } from "@opentelemetry/api";
|
|
2
2
|
import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
3
3
|
//#region src/errors/types.d.ts
|
|
4
|
-
type ApplyCustomAttributesFunction = (error: Error | string) => Attributes;
|
|
4
|
+
export type ApplyCustomAttributesFunction = (error: Error | string) => Attributes;
|
|
5
5
|
/**
|
|
6
6
|
* ErrorsInstrumentation Configuration
|
|
7
7
|
*/
|
|
8
|
-
interface ErrorsInstrumentationConfig extends InstrumentationConfig {
|
|
8
|
+
export interface ErrorsInstrumentationConfig extends InstrumentationConfig {
|
|
9
9
|
/**
|
|
10
10
|
* Optional callback invoked for each captured error or unhandled rejection.
|
|
11
11
|
* Returned attributes are merged onto the emitted log record (after the
|
|
@@ -14,5 +14,4 @@ interface ErrorsInstrumentationConfig extends InstrumentationConfig {
|
|
|
14
14
|
applyCustomAttributes?: ApplyCustomAttributesFunction;
|
|
15
15
|
}
|
|
16
16
|
//#endregion
|
|
17
|
-
export { ApplyCustomAttributesFunction, ErrorsInstrumentationConfig };
|
|
18
17
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FetchInstrumentationConfig } from "./types.js";
|
|
2
2
|
import { InstrumentationBase } from "@opentelemetry/instrumentation";
|
|
3
3
|
//#region src/fetch/instrumentation.d.ts
|
|
4
|
-
declare class FetchInstrumentation extends InstrumentationBase<FetchInstrumentationConfig> {
|
|
4
|
+
export declare class FetchInstrumentation extends InstrumentationBase<FetchInstrumentationConfig> {
|
|
5
5
|
private _isEnabled;
|
|
6
6
|
private _isFetchPatched;
|
|
7
7
|
private _spanResources;
|
|
@@ -32,5 +32,4 @@ declare class FetchInstrumentation extends InstrumentationBase<FetchInstrumentat
|
|
|
32
32
|
private _addHeaders;
|
|
33
33
|
}
|
|
34
34
|
//#endregion
|
|
35
|
-
export { FetchInstrumentation };
|
|
36
35
|
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/fetch/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 getFetchBodyLength,\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 {\n FetchError,\n FetchInstrumentationConfig,\n FetchResponse,\n} from './types.ts';\n\nconst hasBrowserPerformanceAPI = typeof PerformanceObserver !== 'undefined';\n\nexport class FetchInstrumentation extends InstrumentationBase<FetchInstrumentationConfig> {\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 instrumentations in a base state (enabled, patched but with flags set to false)\n private declare _isEnabled: boolean;\n private declare _isFetchPatched: boolean;\n\n // To keep track of the resources\n private _spanResources: Map<Span, PerformanceResourceTiming> = new Map();\n\n constructor(config: FetchInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/fetch', version, config);\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (!hasBrowserPerformanceAPI) {\n this._diag.warn(\n 'this instrumentation is intended for web usage only, it does not instrument server-side fetch()',\n );\n return;\n }\n\n if (this._isEnabled) {\n return;\n }\n\n if (this._isFetchPatched) {\n this._diag.debug('fetch constructor already patched');\n this._isEnabled = true;\n return;\n }\n\n try {\n // `_wrap` throws if a third-party script has locked globalThis.fetch via\n // Object.defineProperty(window, 'fetch', { writable: false, ... }).\n this._wrap(globalThis, 'fetch', this._patchConstructor());\n this._isFetchPatched = true;\n this._isEnabled = true;\n } catch (err) {\n this._diag.warn(\n 'Failed to patch globalThis.fetch; instrumentation will not be enabled. ' +\n 'Another script may have locked globalThis.fetch via Object.defineProperty.',\n err,\n );\n }\n }\n\n override disable(): void {\n if (!hasBrowserPerformanceAPI) {\n return;\n }\n\n if (!this._isEnabled) {\n return;\n }\n this._isEnabled = false;\n }\n\n /**\n * Patches the constructor of fetch\n */\n private _patchConstructor(): (original: typeof fetch) => typeof fetch {\n return (original) => {\n const instrumentation = this;\n\n return function patchConstructor(\n this: typeof globalThis,\n ...args: Parameters<typeof fetch>\n ): Promise<Response> {\n if (!instrumentation._isEnabled) {\n return original.apply(this, args);\n }\n\n // Reference to the Span in case there is an exception after creating it (wil not end)\n // It will be used to keep the Map only with the Spans that will be ended\n let trucatedSpan: Span | undefined;\n try {\n const instrConfig = instrumentation.getConfig();\n const url = parseUrl(\n args[0] instanceof Request ? args[0].url : String(args[0]),\n ).href;\n\n const shouldIgnoreUrl = matchesUrl(url, instrConfig.ignoreUrls);\n if (shouldIgnoreUrl) {\n return original.apply(this, args);\n }\n\n // Per the Fetch spec, when fetch() is called with a Request object\n // and a separate init object, the init properties override the\n // Request's properties. Merge them into a new Request so that\n // downstream consumers (hooks, header injection, the actual fetch\n // call) see the correct final values.\n // See: https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#parameters\n let options: Request | RequestInit;\n if (args[0] instanceof Request) {\n options = args[1] != null ? new Request(args[0], args[1]) : args[0];\n } else {\n options = args[1] || {};\n }\n\n const createdSpan = instrumentation._createSpan(url, options);\n trucatedSpan = createdSpan;\n if (instrConfig.measureRequestSize) {\n getFetchBodyLength(...args)\n .then((bodyLength) => {\n if (typeof bodyLength === 'number') {\n createdSpan.setAttribute(\n ATTR_HTTP_REQUEST_BODY_SIZE,\n bodyLength,\n );\n }\n })\n .catch((error) => {\n instrumentation._diag.warn('getFetchBodyLength', error);\n });\n }\n\n function endSpanOnError(span: Span, error: FetchError) {\n instrumentation._applyAttributesAfterFetch(span, options, error);\n instrumentation._endSpan(span, {\n status: error.status,\n error: error.name || error.message,\n aborted: error.name === 'AbortError',\n });\n }\n\n function endSpanOnSuccess(span: Span, response: Response) {\n instrumentation._applyAttributesAfterFetch(span, options, response);\n instrumentation._endSpan(span, response);\n }\n\n function onSuccess(span: Span, response: Response): Response {\n try {\n // Clone the response and eagerly consume the clone to detect\n // when the body transfer completes. The original response is\n // returned to the caller untouched so that it passes internal\n // brand-checks required by APIs such as\n // WebAssembly.compileStreaming.\n // It consumes the entire body even if the user cancels reading it\n // from the original response. But it does work with `AbortController.abort()`\n // because it aborts the underlying fetch cancelling the original and clone streams\n // ref: https://github.com/open-telemetry/opentelemetry-js/pull/6521\n const resClone = response.clone();\n const body = resClone.body;\n if (body) {\n const reader = body.getReader();\n\n const read = (): void => {\n reader.read().then(\n ({ done }) => {\n try {\n if (done) {\n endSpanOnSuccess(span, response);\n } else {\n read();\n }\n } catch (e) {\n instrumentation._diag.error(\n 'Failed to finalize span after body read',\n e,\n );\n instrumentation._endSpan(span, {\n status: response.status,\n });\n }\n },\n (error) => {\n try {\n endSpanOnError(span, {\n name: error?.name,\n message: error?.message,\n status: response.status,\n });\n } catch (e) {\n instrumentation._diag.error(\n 'Failed to end span after body read rejected',\n e,\n );\n instrumentation._endSpan(span, {\n status: response.status,\n });\n }\n },\n );\n };\n read();\n } else {\n // some older browsers don't have .body implemented\n endSpanOnSuccess(span, response);\n }\n } catch (error) {\n instrumentation._diag.error(\n 'Failed to read fetch response body',\n error,\n );\n instrumentation._endSpan(span, { status: response.status });\n }\n return response;\n }\n\n function onError(span: Span, error: FetchError): never {\n try {\n endSpanOnError(span, error);\n } catch (e: unknown) {\n // endSpanOnError failed — fall back to ending the span directly.\n // No `status`: no response was ever received for this request.\n instrumentation._diag.error(\n 'Failed to end span on fetch error',\n e,\n );\n instrumentation._endSpan(span, {});\n }\n throw error;\n }\n\n const fetchContext = trace.setSpan(context.active(), createdSpan);\n return context.with(fetchContext, () => {\n instrumentation._spanResources.set(createdSpan, {\n name: url,\n fetchStart: performance.now(),\n } as PerformanceResourceTiming);\n\n // Call request hook before injection so hooks cannot tamper with propagation headers.\n // Also, this means the hook will see `options.headers` in the same type as passed in,\n // rather than as a `Headers` instance set by `_addHeaders()`.\n instrumentation._callRequestHook(createdSpan, options);\n instrumentation._addHeaders(options, url, fetchContext);\n\n return original\n .apply(\n globalThis,\n options instanceof Request ? [options] : [url, options],\n )\n .then(\n onSuccess.bind(globalThis, createdSpan),\n onError.bind(globalThis, createdSpan),\n );\n });\n } catch (e: unknown) {\n instrumentation._diag.error('Failed to instrument fetch request', e);\n }\n // Remove the span if it was created and stored before the exception\n if (trucatedSpan) {\n instrumentation._spanResources.delete(trucatedSpan);\n }\n return original.apply(this, args);\n };\n };\n }\n\n /**\n * Creates a new span\n */\n private _createSpan(\n url: string,\n options: Partial<Request | RequestInit> = {},\n ): Span {\n const attributes = {} as Attributes;\n const parsedUrl = parseUrl(url);\n const origMethod = options.method;\n const normMethod = normalizeHttpRequestMethod(options.method || 'GET');\n const serverPort = serverPortFromUrl(parsedUrl);\n const name = normMethod;\n const { sanitizeUrl } = this.getConfig();\n\n attributes[ATTR_HTTP_REQUEST_METHOD] = normMethod;\n attributes[ATTR_URL_FULL] = sanitizeUrl ? sanitizeUrl(url) : url;\n attributes[ATTR_SERVER_ADDRESS] = parsedUrl.hostname;\n if (serverPort) {\n attributes[ATTR_SERVER_PORT] = serverPort;\n }\n if (origMethod && origMethod !== normMethod) {\n attributes[ATTR_HTTP_REQUEST_METHOD_ORIGINAL] = origMethod;\n }\n\n return this.tracer.startSpan(name, {\n kind: SpanKind.CLIENT,\n attributes,\n });\n }\n\n /**\n * Finish span, add attributes, etc.\n */\n private _endSpan(span: Span, response: FetchResponse) {\n if (response.status !== undefined) {\n span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);\n }\n\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 if (!response.aborted) {\n const isErrorStatus =\n response.status === undefined ||\n response.status >= 400 ||\n response.error !== undefined;\n if (isErrorStatus) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n const errorType =\n response.error ??\n (response.status !== undefined ? String(response.status) : undefined);\n if (typeof errorType === 'string') {\n span.setAttribute(ATTR_ERROR_TYPE, errorType);\n }\n }\n }\n\n span.end();\n\n // Register the resource context for other instrumentations\n const resource = this._spanResources.get(span);\n if (resource) {\n this._spanResources.delete(span);\n getNetworkContextRegistry().register(span, {\n key: resource.name,\n startPerfNow: resource.fetchStart,\n endPerfNow: performance.now(),\n });\n }\n }\n\n private _applyAttributesAfterFetch(\n span: Span,\n request: Request | RequestInit,\n result: Response | FetchError,\n ) {\n const applyCustomAttributesOnSpan =\n this.getConfig().applyCustomAttributesOnSpan;\n if (applyCustomAttributesOnSpan) {\n safeExecuteInTheMiddle(\n () => applyCustomAttributesOnSpan(span, request, result),\n (error) => {\n if (!error) {\n return;\n }\n this._diag.error('applyCustomAttributesOnSpan', error);\n },\n true,\n );\n }\n }\n\n /**\n * Calls the request hook if defined\n */\n private _callRequestHook(span: Span, request: Request | RequestInit) {\n const requestHook = this.getConfig().requestHook;\n\n if (requestHook) {\n safeExecuteInTheMiddle(\n () => requestHook(span, request),\n (error) => {\n if (error) {\n this._diag.error('requestHook', error);\n }\n },\n true,\n );\n }\n }\n\n /**\n * Add headers for the request and the given context if apply\n */\n private _addHeaders(\n options: Request | RequestInit,\n url: string,\n ctx: Context,\n ): void {\n // Propagate only if in request goes to same origin or is in the allow list\n const urlsToPropagate = this.getConfig().propagateTraceHeaderCorsUrls;\n const urlOrigin = parseUrl(url).origin;\n const sameOrigin = location.origin === urlOrigin;\n const shouldPropagate = sameOrigin || matchesUrl(url, urlsToPropagate);\n\n if (shouldPropagate) {\n if (options instanceof Request) {\n // This mutates `Request.headers` in-place, because it is read-only\n // (per https://developer.mozilla.org/en-US/docs/Web/API/Request/headers),\n // so we cannot (easily) replace it.\n propagation.inject(ctx, options.headers, {\n set: (h, k, v) => h.set(k, typeof v === 'string' ? v : String(v)),\n });\n } else {\n // Otherwise, create a new Headers to avoid mutating the caller's\n // possibly re-used headers.\n const headers = new Headers(options.headers);\n propagation.inject(ctx, headers, {\n set: (h, k, v) => h.set(k, typeof v === 'string' ? v : String(v)),\n });\n options.headers = headers;\n }\n } else {\n const headers: Partial<Record<string, unknown>> = {};\n propagation.inject(ctx, headers);\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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"}
|
|
1
|
+
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/fetch/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 getFetchBodyLength,\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 {\n FetchError,\n FetchInstrumentationConfig,\n FetchResponse,\n} from './types.ts';\n\nconst hasBrowserPerformanceAPI = typeof PerformanceObserver !== 'undefined';\n\nexport class FetchInstrumentation extends InstrumentationBase<FetchInstrumentationConfig> {\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 instrumentations in a base state (enabled, patched but with flags set to false)\n declare private _isEnabled: boolean;\n declare private _isFetchPatched: boolean;\n\n // To keep track of the resources\n private _spanResources: Map<Span, PerformanceResourceTiming> = new Map();\n\n constructor(config: FetchInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/fetch', version, config);\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (!hasBrowserPerformanceAPI) {\n this._diag.warn(\n 'this instrumentation is intended for web usage only, it does not instrument server-side fetch()',\n );\n return;\n }\n\n if (this._isEnabled) {\n return;\n }\n\n if (this._isFetchPatched) {\n this._diag.debug('fetch constructor already patched');\n this._isEnabled = true;\n return;\n }\n\n try {\n // `_wrap` throws if a third-party script has locked globalThis.fetch via\n // Object.defineProperty(window, 'fetch', { writable: false, ... }).\n this._wrap(globalThis, 'fetch', this._patchConstructor());\n this._isFetchPatched = true;\n this._isEnabled = true;\n } catch (err) {\n this._diag.warn(\n 'Failed to patch globalThis.fetch; instrumentation will not be enabled. ' +\n 'Another script may have locked globalThis.fetch via Object.defineProperty.',\n err,\n );\n }\n }\n\n override disable(): void {\n if (!hasBrowserPerformanceAPI) {\n return;\n }\n\n if (!this._isEnabled) {\n return;\n }\n this._isEnabled = false;\n }\n\n /**\n * Patches the constructor of fetch\n */\n private _patchConstructor(): (original: typeof fetch) => typeof fetch {\n return (original) => {\n const instrumentation = this;\n\n return function patchConstructor(\n this: typeof globalThis,\n ...args: Parameters<typeof fetch>\n ): Promise<Response> {\n if (!instrumentation._isEnabled) {\n return original.apply(this, args);\n }\n\n // Reference to the Span in case there is an exception after creating it (wil not end)\n // It will be used to keep the Map only with the Spans that will be ended\n let trucatedSpan: Span | undefined;\n try {\n const instrConfig = instrumentation.getConfig();\n const url = parseUrl(\n args[0] instanceof Request ? args[0].url : String(args[0]),\n ).href;\n\n const shouldIgnoreUrl = matchesUrl(url, instrConfig.ignoreUrls);\n if (shouldIgnoreUrl) {\n return original.apply(this, args);\n }\n\n // Per the Fetch spec, when fetch() is called with a Request object\n // and a separate init object, the init properties override the\n // Request's properties. Merge them into a new Request so that\n // downstream consumers (hooks, header injection, the actual fetch\n // call) see the correct final values.\n // See: https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#parameters\n let options: Request | RequestInit;\n if (args[0] instanceof Request) {\n options = args[1] != null ? new Request(args[0], args[1]) : args[0];\n } else {\n options = args[1] || {};\n }\n\n const createdSpan = instrumentation._createSpan(url, options);\n trucatedSpan = createdSpan;\n if (instrConfig.measureRequestSize) {\n getFetchBodyLength(...args)\n .then((bodyLength) => {\n if (typeof bodyLength === 'number') {\n createdSpan.setAttribute(\n ATTR_HTTP_REQUEST_BODY_SIZE,\n bodyLength,\n );\n }\n })\n .catch((error) => {\n instrumentation._diag.warn('getFetchBodyLength', error);\n });\n }\n\n function endSpanOnError(span: Span, error: FetchError) {\n instrumentation._applyAttributesAfterFetch(span, options, error);\n instrumentation._endSpan(span, {\n status: error.status,\n error: error.name || error.message,\n aborted: error.name === 'AbortError',\n });\n }\n\n function endSpanOnSuccess(span: Span, response: Response) {\n instrumentation._applyAttributesAfterFetch(span, options, response);\n instrumentation._endSpan(span, response);\n }\n\n function onSuccess(span: Span, response: Response): Response {\n try {\n // Clone the response and eagerly consume the clone to detect\n // when the body transfer completes. The original response is\n // returned to the caller untouched so that it passes internal\n // brand-checks required by APIs such as\n // WebAssembly.compileStreaming.\n // It consumes the entire body even if the user cancels reading it\n // from the original response. But it does work with `AbortController.abort()`\n // because it aborts the underlying fetch cancelling the original and clone streams\n // ref: https://github.com/open-telemetry/opentelemetry-js/pull/6521\n const resClone = response.clone();\n const body = resClone.body;\n if (body) {\n const reader = body.getReader();\n\n const read = (): void => {\n reader.read().then(\n ({ done }) => {\n try {\n if (done) {\n endSpanOnSuccess(span, response);\n } else {\n read();\n }\n } catch (e) {\n instrumentation._diag.error(\n 'Failed to finalize span after body read',\n e,\n );\n instrumentation._endSpan(span, {\n status: response.status,\n });\n }\n },\n (error) => {\n try {\n endSpanOnError(span, {\n name: error?.name,\n message: error?.message,\n status: response.status,\n });\n } catch (e) {\n instrumentation._diag.error(\n 'Failed to end span after body read rejected',\n e,\n );\n instrumentation._endSpan(span, {\n status: response.status,\n });\n }\n },\n );\n };\n read();\n } else {\n // some older browsers don't have .body implemented\n endSpanOnSuccess(span, response);\n }\n } catch (error) {\n instrumentation._diag.error(\n 'Failed to read fetch response body',\n error,\n );\n instrumentation._endSpan(span, { status: response.status });\n }\n return response;\n }\n\n function onError(span: Span, error: FetchError): never {\n try {\n endSpanOnError(span, error);\n } catch (e: unknown) {\n // endSpanOnError failed — fall back to ending the span directly.\n // No `status`: no response was ever received for this request.\n instrumentation._diag.error(\n 'Failed to end span on fetch error',\n e,\n );\n instrumentation._endSpan(span, {});\n }\n throw error;\n }\n\n const fetchContext = trace.setSpan(context.active(), createdSpan);\n return context.with(fetchContext, () => {\n instrumentation._spanResources.set(createdSpan, {\n name: url,\n fetchStart: performance.now(),\n } as PerformanceResourceTiming);\n\n // Call request hook before injection so hooks cannot tamper with propagation headers.\n // Also, this means the hook will see `options.headers` in the same type as passed in,\n // rather than as a `Headers` instance set by `_addHeaders()`.\n instrumentation._callRequestHook(createdSpan, options);\n instrumentation._addHeaders(options, url, fetchContext);\n\n return original\n .apply(\n globalThis,\n options instanceof Request ? [options] : [url, options],\n )\n .then(\n onSuccess.bind(globalThis, createdSpan),\n onError.bind(globalThis, createdSpan),\n );\n });\n } catch (e: unknown) {\n instrumentation._diag.error('Failed to instrument fetch request', e);\n }\n // Remove the span if it was created and stored before the exception\n if (trucatedSpan) {\n instrumentation._spanResources.delete(trucatedSpan);\n }\n return original.apply(this, args);\n };\n };\n }\n\n /**\n * Creates a new span\n */\n private _createSpan(\n url: string,\n options: Partial<Request | RequestInit> = {},\n ): Span {\n const attributes = {} as Attributes;\n const parsedUrl = parseUrl(url);\n const origMethod = options.method;\n const normMethod = normalizeHttpRequestMethod(options.method || 'GET');\n const serverPort = serverPortFromUrl(parsedUrl);\n const name = normMethod;\n const { sanitizeUrl } = this.getConfig();\n\n attributes[ATTR_HTTP_REQUEST_METHOD] = normMethod;\n attributes[ATTR_URL_FULL] = sanitizeUrl ? sanitizeUrl(url) : url;\n attributes[ATTR_SERVER_ADDRESS] = parsedUrl.hostname;\n if (serverPort) {\n attributes[ATTR_SERVER_PORT] = serverPort;\n }\n if (origMethod && origMethod !== normMethod) {\n attributes[ATTR_HTTP_REQUEST_METHOD_ORIGINAL] = origMethod;\n }\n\n return this.tracer.startSpan(name, {\n kind: SpanKind.CLIENT,\n attributes,\n });\n }\n\n /**\n * Finish span, add attributes, etc.\n */\n private _endSpan(span: Span, response: FetchResponse) {\n if (response.status !== undefined) {\n span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);\n }\n\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 if (!response.aborted) {\n const isErrorStatus =\n response.status === undefined ||\n response.status >= 400 ||\n response.error !== undefined;\n if (isErrorStatus) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n const errorType =\n response.error ??\n (response.status !== undefined ? String(response.status) : undefined);\n if (typeof errorType === 'string') {\n span.setAttribute(ATTR_ERROR_TYPE, errorType);\n }\n }\n }\n\n span.end();\n\n // Register the resource context for other instrumentations\n const resource = this._spanResources.get(span);\n if (resource) {\n this._spanResources.delete(span);\n getNetworkContextRegistry().register(span, {\n key: resource.name,\n startPerfNow: resource.fetchStart,\n endPerfNow: performance.now(),\n });\n }\n }\n\n private _applyAttributesAfterFetch(\n span: Span,\n request: Request | RequestInit,\n result: Response | FetchError,\n ) {\n const applyCustomAttributesOnSpan =\n this.getConfig().applyCustomAttributesOnSpan;\n if (applyCustomAttributesOnSpan) {\n safeExecuteInTheMiddle(\n () => applyCustomAttributesOnSpan(span, request, result),\n (error) => {\n if (!error) {\n return;\n }\n this._diag.error('applyCustomAttributesOnSpan', error);\n },\n true,\n );\n }\n }\n\n /**\n * Calls the request hook if defined\n */\n private _callRequestHook(span: Span, request: Request | RequestInit) {\n const requestHook = this.getConfig().requestHook;\n\n if (requestHook) {\n safeExecuteInTheMiddle(\n () => requestHook(span, request),\n (error) => {\n if (error) {\n this._diag.error('requestHook', error);\n }\n },\n true,\n );\n }\n }\n\n /**\n * Add headers for the request and the given context if apply\n */\n private _addHeaders(\n options: Request | RequestInit,\n url: string,\n ctx: Context,\n ): void {\n // Propagate only if in request goes to same origin or is in the allow list\n const urlsToPropagate = this.getConfig().propagateTraceHeaderCorsUrls;\n const urlOrigin = parseUrl(url).origin;\n const sameOrigin = location.origin === urlOrigin;\n const shouldPropagate = sameOrigin || matchesUrl(url, urlsToPropagate);\n\n if (shouldPropagate) {\n if (options instanceof Request) {\n // This mutates `Request.headers` in-place, because it is read-only\n // (per https://developer.mozilla.org/en-US/docs/Web/API/Request/headers),\n // so we cannot (easily) replace it.\n propagation.inject(ctx, options.headers, {\n set: (h, k, v) => h.set(k, typeof v === 'string' ? v : String(v)),\n });\n } else {\n // Otherwise, create a new Headers to avoid mutating the caller's\n // possibly re-used headers.\n const headers = new Headers(options.headers);\n propagation.inject(ctx, headers, {\n set: (h, k, v) => h.set(k, typeof v === 'string' ? v : String(v)),\n });\n options.headers = headers;\n }\n } else {\n const headers: Partial<Record<string, unknown>> = {};\n propagation.inject(ctx, headers);\n if (Object.keys(headers).length > 0) {\n this._diag.debug('headers inject skipped due to CORS policy');\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;AAwCA,MAAM,2BAA2B,OAAO,wBAAwB;AAEhE,IAAa,uBAAb,cAA0C,oBAAgD;CAWxF,iCAA+D,IAAI,IAAI;CAEvE,YAAY,SAAqC,CAAC,GAAG;EACnD,MAAM,gDAAgD,SAAS,MAAM;CACvE;CAEA,OAA0B;EACxB,OAAO,CAAC;CACV;CAEA,SAAwB;EACtB,IAAI,CAAC,0BAA0B;GAC7B,KAAK,MAAM,KACT,iGACF;GACA;EACF;EAEA,IAAI,KAAK,YACP;EAGF,IAAI,KAAK,iBAAiB;GACxB,KAAK,MAAM,MAAM,mCAAmC;GACpD,KAAK,aAAa;GAClB;EACF;EAEA,IAAI;GAGF,KAAK,MAAM,YAAY,SAAS,KAAK,kBAAkB,CAAC;GACxD,KAAK,kBAAkB;GACvB,KAAK,aAAa;EACpB,SAAS,KAAK;GACZ,KAAK,MAAM,KACT,qJAEA,GACF;EACF;CACF;CAEA,UAAyB;EACvB,IAAI,CAAC,0BACH;EAGF,IAAI,CAAC,KAAK,YACR;EAEF,KAAK,aAAa;CACpB;;;;CAKA,oBAAsE;EACpE,QAAQ,aAAa;GACnB,MAAM,kBAAkB;GAExB,OAAO,SAAS,iBAEd,GAAG,MACgB;IACnB,IAAI,CAAC,gBAAgB,YACnB,OAAO,SAAS,MAAM,MAAM,IAAI;IAKlC,IAAI;IACJ,IAAI;KACF,MAAM,cAAc,gBAAgB,UAAU;KAC9C,MAAM,MAAM,SACV,KAAK,cAAc,UAAU,KAAK,EAAE,CAAC,MAAM,OAAO,KAAK,EAAE,CAC3D,CAAC,CAAC;KAGF,IADwB,WAAW,KAAK,YAAY,UAClC,GAChB,OAAO,SAAS,MAAM,MAAM,IAAI;KASlC,IAAI;KACJ,IAAI,KAAK,cAAc,SACrB,UAAU,KAAK,MAAM,OAAO,IAAI,QAAQ,KAAK,IAAI,KAAK,EAAE,IAAI,KAAK;UAEjE,UAAU,KAAK,MAAM,CAAC;KAGxB,MAAM,cAAc,gBAAgB,YAAY,KAAK,OAAO;KAC5D,eAAe;KACf,IAAI,YAAY,oBACd,mBAAmB,GAAG,IAAI,CAAC,CACxB,MAAM,eAAe;MACpB,IAAI,OAAO,eAAe,UACxB,YAAY,aACV,6BACA,UACF;KAEJ,CAAC,CAAC,CACD,OAAO,UAAU;MAChB,gBAAgB,MAAM,KAAK,sBAAsB,KAAK;KACxD,CAAC;KAGL,SAAS,eAAe,MAAY,OAAmB;MACrD,gBAAgB,2BAA2B,MAAM,SAAS,KAAK;MAC/D,gBAAgB,SAAS,MAAM;OAC7B,QAAQ,MAAM;OACd,OAAO,MAAM,QAAQ,MAAM;OAC3B,SAAS,MAAM,SAAS;MAC1B,CAAC;KACH;KAEA,SAAS,iBAAiB,MAAY,UAAoB;MACxD,gBAAgB,2BAA2B,MAAM,SAAS,QAAQ;MAClE,gBAAgB,SAAS,MAAM,QAAQ;KACzC;KAEA,SAAS,UAAU,MAAY,UAA8B;MAC3D,IAAI;OAWF,MAAM,OADW,SAAS,MACN,CAAC,CAAC;OACtB,IAAI,MAAM;QACR,MAAM,SAAS,KAAK,UAAU;QAE9B,MAAM,aAAmB;SACvB,OAAO,KAAK,CAAC,CAAC,MACX,EAAE,WAAW;UACZ,IAAI;WACF,IAAI,MACF,iBAAiB,MAAM,QAAQ;gBAE/B,KAAK;UAET,SAAS,GAAG;WACV,gBAAgB,MAAM,MACpB,2CACA,CACF;WACA,gBAAgB,SAAS,MAAM,EAC7B,QAAQ,SAAS,OACnB,CAAC;UACH;SACF,IACC,UAAU;UACT,IAAI;WACF,eAAe,MAAM;YACnB,MAAM,OAAO;YACb,SAAS,OAAO;YAChB,QAAQ,SAAS;WACnB,CAAC;UACH,SAAS,GAAG;WACV,gBAAgB,MAAM,MACpB,+CACA,CACF;WACA,gBAAgB,SAAS,MAAM,EAC7B,QAAQ,SAAS,OACnB,CAAC;UACH;SACF,CACF;QACF;QACA,KAAK;OACP,OAEE,iBAAiB,MAAM,QAAQ;MAEnC,SAAS,OAAO;OACd,gBAAgB,MAAM,MACpB,sCACA,KACF;OACA,gBAAgB,SAAS,MAAM,EAAE,QAAQ,SAAS,OAAO,CAAC;MAC5D;MACA,OAAO;KACT;KAEA,SAAS,QAAQ,MAAY,OAA0B;MACrD,IAAI;OACF,eAAe,MAAM,KAAK;MAC5B,SAAS,GAAY;OAGnB,gBAAgB,MAAM,MACpB,qCACA,CACF;OACA,gBAAgB,SAAS,MAAM,CAAC,CAAC;MACnC;MACA,MAAM;KACR;KAEA,MAAM,eAAe,MAAM,QAAQ,QAAQ,OAAO,GAAG,WAAW;KAChE,OAAO,QAAQ,KAAK,oBAAoB;MACtC,gBAAgB,eAAe,IAAI,aAAa;OAC9C,MAAM;OACN,YAAY,YAAY,IAAI;MAC9B,CAA8B;MAK9B,gBAAgB,iBAAiB,aAAa,OAAO;MACrD,gBAAgB,YAAY,SAAS,KAAK,YAAY;MAEtD,OAAO,SACJ,MACC,YACA,mBAAmB,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,OAAO,CACxD,CAAC,CACA,KACC,UAAU,KAAK,YAAY,WAAW,GACtC,QAAQ,KAAK,YAAY,WAAW,CACtC;KACJ,CAAC;IACH,SAAS,GAAY;KACnB,gBAAgB,MAAM,MAAM,sCAAsC,CAAC;IACrE;IAEA,IAAI,cACF,gBAAgB,eAAe,OAAO,YAAY;IAEpD,OAAO,SAAS,MAAM,MAAM,IAAI;GAClC;EACF;CACF;;;;CAKA,YACE,KACA,UAA0C,CAAC,GACrC;EACN,MAAM,aAAa,CAAC;EACpB,MAAM,YAAY,SAAS,GAAG;EAC9B,MAAM,aAAa,QAAQ;EAC3B,MAAM,aAAa,2BAA2B,QAAQ,UAAU,KAAK;EACrE,MAAM,aAAa,kBAAkB,SAAS;EAC9C,MAAM,OAAO;EACb,MAAM,EAAE,gBAAgB,KAAK,UAAU;EAEvC,WAAW,4BAA4B;EACvC,WAAW,iBAAiB,cAAc,YAAY,GAAG,IAAI;EAC7D,WAAW,uBAAuB,UAAU;EAC5C,IAAI,YACF,WAAW,oBAAoB;EAEjC,IAAI,cAAc,eAAe,YAC/B,WAAW,qCAAqC;EAGlD,OAAO,KAAK,OAAO,UAAU,MAAM;GACjC,MAAM,SAAS;GACf;EACF,CAAC;CACH;;;;CAKA,SAAiB,MAAY,UAAyB;EACpD,IAAI,SAAS,WAAW,KAAA,GACtB,KAAK,aAAa,gCAAgC,SAAS,MAAM;EAMnE,IAAI,CAAC,SAAS,SAEV;OAAA,SAAS,WAAW,KAAA,KACpB,SAAS,UAAU,OACnB,SAAS,UAAU,KAAA,GACF;IACjB,KAAK,UAAU,EAAE,MAAM,eAAe,MAAM,CAAC;IAC7C,MAAM,YACJ,SAAS,UACR,SAAS,WAAW,KAAA,IAAY,OAAO,SAAS,MAAM,IAAI,KAAA;IAC7D,IAAI,OAAO,cAAc,UACvB,KAAK,aAAa,iBAAiB,SAAS;GAEhD;;EAGF,KAAK,IAAI;EAGT,MAAM,WAAW,KAAK,eAAe,IAAI,IAAI;EAC7C,IAAI,UAAU;GACZ,KAAK,eAAe,OAAO,IAAI;GAC/B,0BAA0B,CAAC,CAAC,SAAS,MAAM;IACzC,KAAK,SAAS;IACd,cAAc,SAAS;IACvB,YAAY,YAAY,IAAI;GAC9B,CAAC;EACH;CACF;CAEA,2BACE,MACA,SACA,QACA;EACA,MAAM,8BACJ,KAAK,UAAU,CAAC,CAAC;EACnB,IAAI,6BACF,6BACQ,4BAA4B,MAAM,SAAS,MAAM,IACtD,UAAU;GACT,IAAI,CAAC,OACH;GAEF,KAAK,MAAM,MAAM,+BAA+B,KAAK;EACvD,GACA,IACF;CAEJ;;;;CAKA,iBAAyB,MAAY,SAAgC;EACnE,MAAM,cAAc,KAAK,UAAU,CAAC,CAAC;EAErC,IAAI,aACF,6BACQ,YAAY,MAAM,OAAO,IAC9B,UAAU;GACT,IAAI,OACF,KAAK,MAAM,MAAM,eAAe,KAAK;EAEzC,GACA,IACF;CAEJ;;;;CAKA,YACE,SACA,KACA,KACM;EAEN,MAAM,kBAAkB,KAAK,UAAU,CAAC,CAAC;EACzC,MAAM,YAAY,SAAS,GAAG,CAAC,CAAC;EAIhC,IAHmB,SAAS,WAAW,aACD,WAAW,KAAK,eAAe,GAEhD;GACnB,IAAI,mBAAmB,SAIrB,YAAY,OAAO,KAAK,QAAQ,SAAS,EACvC,MAAM,GAAG,GAAG,MAAM,EAAE,IAAI,GAAG,OAAO,MAAM,WAAW,IAAI,OAAO,CAAC,CAAC,EAClE,CAAC;QACI;IAGL,MAAM,UAAU,IAAI,QAAQ,QAAQ,OAAO;IAC3C,YAAY,OAAO,KAAK,SAAS,EAC/B,MAAM,GAAG,GAAG,MAAM,EAAE,IAAI,GAAG,OAAO,MAAM,WAAW,IAAI,OAAO,CAAC,CAAC,EAClE,CAAC;IACD,QAAQ,UAAU;GACpB;EACF,OAAO;GACL,MAAM,UAA4C,CAAC;GACnD,YAAY,OAAO,KAAK,OAAO;GAC/B,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAChC,KAAK,MAAM,MAAM,2CAA2C;EAEhE;CACF;AACF"}
|
package/dist/fetch/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
|
7
7
|
* network error), per
|
|
8
8
|
* https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#status
|
|
9
9
|
*/
|
|
10
|
-
interface FetchResponse {
|
|
10
|
+
export interface FetchResponse {
|
|
11
11
|
status?: number;
|
|
12
12
|
/**
|
|
13
13
|
* Error identifier used as `error.type` (e.g. the exception's `name`).
|
|
@@ -21,15 +21,15 @@ interface FetchResponse {
|
|
|
21
21
|
/**
|
|
22
22
|
* Interface used to provide information to finish span on fetch error
|
|
23
23
|
*/
|
|
24
|
-
interface FetchError {
|
|
24
|
+
export interface FetchError {
|
|
25
25
|
status?: number;
|
|
26
26
|
message: string;
|
|
27
27
|
/** The error's exception type (e.g. `TypeError`, `AbortError`), if available. */
|
|
28
28
|
name?: string;
|
|
29
29
|
}
|
|
30
|
-
type FetchCustomAttributeFunction = (span: Span, request: Request | RequestInit, result: Response | FetchError) => void;
|
|
31
|
-
type FetchRequestHookFunction = (span: Span, request: Request | RequestInit) => void;
|
|
32
|
-
interface FetchInstrumentationConfig extends InstrumentationConfig {
|
|
30
|
+
export type FetchCustomAttributeFunction = (span: Span, request: Request | RequestInit, result: Response | FetchError) => void;
|
|
31
|
+
export type FetchRequestHookFunction = (span: Span, request: Request | RequestInit) => void;
|
|
32
|
+
export interface FetchInstrumentationConfig extends InstrumentationConfig {
|
|
33
33
|
/** urls which should include trace headers when origin doesn't match */
|
|
34
34
|
propagateTraceHeaderCorsUrls?: Array<string | RegExp>;
|
|
35
35
|
/**
|
|
@@ -48,5 +48,4 @@ interface FetchInstrumentationConfig extends InstrumentationConfig {
|
|
|
48
48
|
sanitizeUrl?: (url: string) => string;
|
|
49
49
|
}
|
|
50
50
|
//#endregion
|
|
51
|
-
export { FetchCustomAttributeFunction, FetchError, FetchInstrumentationConfig, FetchRequestHookFunction, FetchResponse };
|
|
52
51
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NavigationInstrumentationConfig } from "./types.js";
|
|
2
2
|
import { InstrumentationBase } from "@opentelemetry/instrumentation";
|
|
3
3
|
//#region src/navigation/instrumentation.d.ts
|
|
4
|
-
declare class NavigationInstrumentation extends InstrumentationBase<NavigationInstrumentationConfig> {
|
|
4
|
+
export declare class NavigationInstrumentation extends InstrumentationBase<NavigationInstrumentationConfig> {
|
|
5
5
|
private _isEnabled;
|
|
6
6
|
private _isHistoryPatched;
|
|
7
7
|
private _hasProcessedInitialLoad;
|
|
@@ -24,5 +24,4 @@ declare class NavigationInstrumentation extends InstrumentationBase<NavigationIn
|
|
|
24
24
|
private _mapChangeStateToType;
|
|
25
25
|
}
|
|
26
26
|
//#endregion
|
|
27
|
-
export { NavigationInstrumentation };
|
|
28
27
|
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/navigation/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_BROWSER_NAVIGATION_HASH_CHANGE,\n ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT,\n ATTR_BROWSER_NAVIGATION_TYPE,\n ATTR_URL_FULL,\n BROWSER_NAVIGATION_EVENT_NAME,\n} from './semconv.ts';\nimport type {\n NavigationInstrumentationConfig,\n NavigationType,\n} from './types.ts';\nimport { isHashChange } from './utils.ts';\n\ntype ChangeState =\n | 'pushState'\n | 'replaceState'\n | 'popstate'\n | 'currententrychange';\n\ninterface NavigationApiEntry {\n url?: string;\n}\n\ninterface NavigationApiTarget {\n currentEntry?: NavigationApiEntry;\n}\n\ninterface NavigationApiEvent extends Event {\n navigationType?: NavigationType;\n target: NavigationApiTarget & EventTarget;\n}\n\ninterface NavigationApi {\n addEventListener(\n type: 'currententrychange',\n listener: (event: NavigationApiEvent) => void,\n ): void;\n removeEventListener(\n type: 'currententrychange',\n listener: (event: NavigationApiEvent) => void,\n ): void;\n}\n\nexport class NavigationInstrumentation extends InstrumentationBase<NavigationInstrumentationConfig> {\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n private declare _isEnabled: boolean;\n private declare _isHistoryPatched: boolean;\n private declare _hasProcessedInitialLoad: boolean;\n private declare _lastUrl: string;\n private declare _onDOMContentLoaded?: () => void;\n private declare _onPopState?: (event: PopStateEvent) => void;\n private declare _onCurrentEntryChange?: (event: NavigationApiEvent) => void;\n\n constructor(config: NavigationInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/navigation', version, config);\n this._lastUrl = location.href;\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n this._isEnabled = true;\n\n const navigationApi = this._getNavigationApi();\n\n // Only patch history API if Navigation API is not being used.\n if (!navigationApi && !this._isHistoryPatched) {\n this._patchHistoryApi();\n this._isHistoryPatched = true;\n }\n\n this._waitForPageLoad();\n\n if (navigationApi) {\n this._onCurrentEntryChange = (event) => {\n this._onSoftNavigation('currententrychange', event);\n };\n navigationApi.addEventListener(\n 'currententrychange',\n this._onCurrentEntryChange,\n );\n } else {\n this._onPopState = () => {\n this._onSoftNavigation('popstate');\n };\n window.addEventListener('popstate', this._onPopState);\n }\n }\n\n override disable(): void {\n if (!this._isEnabled) {\n return;\n }\n this._isEnabled = false;\n\n if (this._onDOMContentLoaded) {\n document.removeEventListener(\n 'DOMContentLoaded',\n this._onDOMContentLoaded,\n );\n this._onDOMContentLoaded = undefined;\n }\n if (this._onPopState) {\n window.removeEventListener('popstate', this._onPopState);\n this._onPopState = undefined;\n }\n if (this._onCurrentEntryChange) {\n const navigationApi = this._getNavigationApi();\n navigationApi?.removeEventListener(\n 'currententrychange',\n this._onCurrentEntryChange,\n );\n this._onCurrentEntryChange = undefined;\n }\n // Reset the initial-load flag so it can be processed again if re-enabled.\n this._hasProcessedInitialLoad = false;\n }\n\n private _getNavigationApi(): NavigationApi | undefined {\n const cfg = this.getConfig();\n if (!cfg.useNavigationApiIfAvailable) {\n return undefined;\n }\n return (window as unknown as { navigation?: NavigationApi }).navigation;\n }\n\n private _onHardNavigation(): void {\n const cfg = this.getConfig();\n const logRecord: LogRecord = {\n eventName: BROWSER_NAVIGATION_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_URL_FULL]: cfg.sanitizeUrl\n ? cfg.sanitizeUrl(document.documentURI)\n : document.documentURI,\n [ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT]: false,\n [ATTR_BROWSER_NAVIGATION_HASH_CHANGE]: false,\n },\n };\n this._applyCustomLogRecordData(logRecord);\n this.logger.emit(logRecord);\n }\n\n private _onSoftNavigation(\n changeState: ChangeState,\n navigationEvent?: NavigationApiEvent,\n ): void {\n const referrerUrl = this._lastUrl;\n const currentUrl =\n changeState === 'currententrychange' &&\n navigationEvent?.target?.currentEntry?.url\n ? navigationEvent.target.currentEntry.url\n : location.href;\n\n if (referrerUrl === currentUrl) {\n return;\n }\n\n const navType = this._mapChangeStateToType(changeState, navigationEvent);\n const sameDocument = this._determineSameDocument(referrerUrl, currentUrl);\n const hashChange = isHashChange(referrerUrl, currentUrl);\n const cfg = this.getConfig();\n\n const logRecord: LogRecord = {\n eventName: BROWSER_NAVIGATION_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_URL_FULL]: cfg.sanitizeUrl\n ? cfg.sanitizeUrl(currentUrl)\n : currentUrl,\n [ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT]: sameDocument,\n [ATTR_BROWSER_NAVIGATION_HASH_CHANGE]: hashChange,\n ...(navType ? { [ATTR_BROWSER_NAVIGATION_TYPE]: navType } : {}),\n },\n };\n this._applyCustomLogRecordData(logRecord);\n this.logger.emit(logRecord);\n\n this._lastUrl = currentUrl;\n }\n\n private _waitForPageLoad(): void {\n if (document.readyState === 'complete' && !this._hasProcessedInitialLoad) {\n this._hasProcessedInitialLoad = true;\n this._onHardNavigation();\n return;\n }\n\n this._onDOMContentLoaded = () => {\n if (!this._hasProcessedInitialLoad) {\n this._hasProcessedInitialLoad = true;\n this._onHardNavigation();\n }\n };\n document.addEventListener('DOMContentLoaded', this._onDOMContentLoaded);\n }\n\n private _patchHistoryApi(): void {\n this._wrap(\n history,\n 'replaceState',\n this._patchHistoryMethod('replaceState'),\n );\n this._wrap(history, 'pushState', this._patchHistoryMethod('pushState'));\n }\n\n private _patchHistoryMethod(changeState: 'pushState' | 'replaceState') {\n const plugin = this;\n return (original: History['pushState' | 'replaceState']) => {\n return function patchedHistoryMethod(\n this: History,\n ...args: Parameters<History['pushState' | 'replaceState']>\n ) {\n if (!plugin._isEnabled) {\n return original.apply(this, args);\n }\n const result = original.apply(this, args);\n if (location.href !== plugin._lastUrl) {\n plugin._onSoftNavigation(changeState);\n }\n return result;\n };\n };\n }\n\n private _applyCustomLogRecordData(logRecord: LogRecord): void {\n const cfg = this.getConfig();\n const hook = cfg.applyCustomLogRecordData;\n if (!hook) {\n return;\n }\n safeExecuteInTheMiddle(\n () => hook(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n\n private _determineSameDocument(fromUrl: string, toUrl: string): boolean {\n try {\n const fromURL = new URL(fromUrl);\n const toURL = new URL(toUrl);\n return fromURL.origin === toURL.origin;\n } catch {\n // Fallback: assume same document for relative URLs or parsing errors.\n // In SPAs, route changes via pushState/replaceState are same-document.\n return true;\n }\n }\n\n private _mapChangeStateToType(\n changeState: ChangeState,\n navigationEvent?: NavigationApiEvent,\n ): NavigationType | undefined {\n if (changeState === 'currententrychange') {\n const navType = navigationEvent?.navigationType;\n switch (navType) {\n case 'traverse':\n return 'traverse';\n case 'replace':\n return 'replace';\n case 'reload':\n return 'reload';\n default:\n // Default to 'push' for programmatic navigations (history.pushState,\n // link clicks) when no explicit type info is available.\n return 'push';\n }\n }\n\n switch (changeState) {\n case 'pushState':\n return 'push';\n case 'replaceState':\n return 'replace';\n case 'popstate':\n return 'traverse';\n default:\n return undefined;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/navigation/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_BROWSER_NAVIGATION_HASH_CHANGE,\n ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT,\n ATTR_BROWSER_NAVIGATION_TYPE,\n ATTR_URL_FULL,\n BROWSER_NAVIGATION_EVENT_NAME,\n} from './semconv.ts';\nimport type {\n NavigationInstrumentationConfig,\n NavigationType,\n} from './types.ts';\nimport { isHashChange } from './utils.ts';\n\ntype ChangeState =\n | 'pushState'\n | 'replaceState'\n | 'popstate'\n | 'currententrychange';\n\ninterface NavigationApiEntry {\n url?: string;\n}\n\ninterface NavigationApiTarget {\n currentEntry?: NavigationApiEntry;\n}\n\ninterface NavigationApiEvent extends Event {\n navigationType?: NavigationType;\n target: NavigationApiTarget & EventTarget;\n}\n\ninterface NavigationApi {\n addEventListener(\n type: 'currententrychange',\n listener: (event: NavigationApiEvent) => void,\n ): void;\n removeEventListener(\n type: 'currententrychange',\n listener: (event: NavigationApiEvent) => void,\n ): void;\n}\n\nexport class NavigationInstrumentation extends InstrumentationBase<NavigationInstrumentationConfig> {\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n declare private _isEnabled: boolean;\n declare private _isHistoryPatched: boolean;\n declare private _hasProcessedInitialLoad: boolean;\n declare private _lastUrl: string;\n declare private _onDOMContentLoaded?: () => void;\n declare private _onPopState?: (event: PopStateEvent) => void;\n declare private _onCurrentEntryChange?: (event: NavigationApiEvent) => void;\n\n constructor(config: NavigationInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/navigation', version, config);\n this._lastUrl = location.href;\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n this._isEnabled = true;\n\n const navigationApi = this._getNavigationApi();\n\n // Only patch history API if Navigation API is not being used.\n if (!navigationApi && !this._isHistoryPatched) {\n this._patchHistoryApi();\n this._isHistoryPatched = true;\n }\n\n this._waitForPageLoad();\n\n if (navigationApi) {\n this._onCurrentEntryChange = (event) => {\n this._onSoftNavigation('currententrychange', event);\n };\n navigationApi.addEventListener(\n 'currententrychange',\n this._onCurrentEntryChange,\n );\n } else {\n this._onPopState = () => {\n this._onSoftNavigation('popstate');\n };\n window.addEventListener('popstate', this._onPopState);\n }\n }\n\n override disable(): void {\n if (!this._isEnabled) {\n return;\n }\n this._isEnabled = false;\n\n if (this._onDOMContentLoaded) {\n document.removeEventListener(\n 'DOMContentLoaded',\n this._onDOMContentLoaded,\n );\n this._onDOMContentLoaded = undefined;\n }\n if (this._onPopState) {\n window.removeEventListener('popstate', this._onPopState);\n this._onPopState = undefined;\n }\n if (this._onCurrentEntryChange) {\n const navigationApi = this._getNavigationApi();\n navigationApi?.removeEventListener(\n 'currententrychange',\n this._onCurrentEntryChange,\n );\n this._onCurrentEntryChange = undefined;\n }\n // Reset the initial-load flag so it can be processed again if re-enabled.\n this._hasProcessedInitialLoad = false;\n }\n\n private _getNavigationApi(): NavigationApi | undefined {\n const cfg = this.getConfig();\n if (!cfg.useNavigationApiIfAvailable) {\n return undefined;\n }\n return (window as unknown as { navigation?: NavigationApi }).navigation;\n }\n\n private _onHardNavigation(): void {\n const cfg = this.getConfig();\n const logRecord: LogRecord = {\n eventName: BROWSER_NAVIGATION_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_URL_FULL]: cfg.sanitizeUrl\n ? cfg.sanitizeUrl(document.documentURI)\n : document.documentURI,\n [ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT]: false,\n [ATTR_BROWSER_NAVIGATION_HASH_CHANGE]: false,\n },\n };\n this._applyCustomLogRecordData(logRecord);\n this.logger.emit(logRecord);\n }\n\n private _onSoftNavigation(\n changeState: ChangeState,\n navigationEvent?: NavigationApiEvent,\n ): void {\n const referrerUrl = this._lastUrl;\n const currentUrl =\n changeState === 'currententrychange' &&\n navigationEvent?.target?.currentEntry?.url\n ? navigationEvent.target.currentEntry.url\n : location.href;\n\n if (referrerUrl === currentUrl) {\n return;\n }\n\n const navType = this._mapChangeStateToType(changeState, navigationEvent);\n const sameDocument = this._determineSameDocument(referrerUrl, currentUrl);\n const hashChange = isHashChange(referrerUrl, currentUrl);\n const cfg = this.getConfig();\n\n const logRecord: LogRecord = {\n eventName: BROWSER_NAVIGATION_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_URL_FULL]: cfg.sanitizeUrl\n ? cfg.sanitizeUrl(currentUrl)\n : currentUrl,\n [ATTR_BROWSER_NAVIGATION_SAME_DOCUMENT]: sameDocument,\n [ATTR_BROWSER_NAVIGATION_HASH_CHANGE]: hashChange,\n ...(navType ? { [ATTR_BROWSER_NAVIGATION_TYPE]: navType } : {}),\n },\n };\n this._applyCustomLogRecordData(logRecord);\n this.logger.emit(logRecord);\n\n this._lastUrl = currentUrl;\n }\n\n private _waitForPageLoad(): void {\n if (document.readyState === 'complete' && !this._hasProcessedInitialLoad) {\n this._hasProcessedInitialLoad = true;\n this._onHardNavigation();\n return;\n }\n\n this._onDOMContentLoaded = () => {\n if (!this._hasProcessedInitialLoad) {\n this._hasProcessedInitialLoad = true;\n this._onHardNavigation();\n }\n };\n document.addEventListener('DOMContentLoaded', this._onDOMContentLoaded);\n }\n\n private _patchHistoryApi(): void {\n this._wrap(\n history,\n 'replaceState',\n this._patchHistoryMethod('replaceState'),\n );\n this._wrap(history, 'pushState', this._patchHistoryMethod('pushState'));\n }\n\n private _patchHistoryMethod(changeState: 'pushState' | 'replaceState') {\n const plugin = this;\n return (original: History['pushState' | 'replaceState']) => {\n return function patchedHistoryMethod(\n this: History,\n ...args: Parameters<History['pushState' | 'replaceState']>\n ) {\n if (!plugin._isEnabled) {\n return original.apply(this, args);\n }\n const result = original.apply(this, args);\n if (location.href !== plugin._lastUrl) {\n plugin._onSoftNavigation(changeState);\n }\n return result;\n };\n };\n }\n\n private _applyCustomLogRecordData(logRecord: LogRecord): void {\n const cfg = this.getConfig();\n const hook = cfg.applyCustomLogRecordData;\n if (!hook) {\n return;\n }\n safeExecuteInTheMiddle(\n () => hook(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n\n private _determineSameDocument(fromUrl: string, toUrl: string): boolean {\n try {\n const fromURL = new URL(fromUrl);\n const toURL = new URL(toUrl);\n return fromURL.origin === toURL.origin;\n } catch {\n // Fallback: assume same document for relative URLs or parsing errors.\n // In SPAs, route changes via pushState/replaceState are same-document.\n return true;\n }\n }\n\n private _mapChangeStateToType(\n changeState: ChangeState,\n navigationEvent?: NavigationApiEvent,\n ): NavigationType | undefined {\n if (changeState === 'currententrychange') {\n const navType = navigationEvent?.navigationType;\n switch (navType) {\n case 'traverse':\n return 'traverse';\n case 'replace':\n return 'replace';\n case 'reload':\n return 'reload';\n default:\n // Default to 'push' for programmatic navigations (history.pushState,\n // link clicks) when no explicit type info is available.\n return 'push';\n }\n }\n\n switch (changeState) {\n case 'pushState':\n return 'push';\n case 'replaceState':\n return 'replace';\n case 'popstate':\n return 'traverse';\n default:\n return undefined;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { LogRecord } from "@opentelemetry/api-logs";
|
|
2
2
|
import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
3
3
|
//#region src/navigation/types.d.ts
|
|
4
|
-
type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
|
|
5
|
-
type SanitizeUrlFunction = (url: string) => string;
|
|
6
|
-
type NavigationType = 'push' | 'replace' | 'reload' | 'traverse';
|
|
4
|
+
export type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
|
|
5
|
+
export type SanitizeUrlFunction = (url: string) => string;
|
|
6
|
+
export type NavigationType = 'push' | 'replace' | 'reload' | 'traverse';
|
|
7
7
|
/**
|
|
8
8
|
* NavigationInstrumentation Configuration
|
|
9
9
|
*/
|
|
10
|
-
interface NavigationInstrumentationConfig extends InstrumentationConfig {
|
|
10
|
+
export interface NavigationInstrumentationConfig extends InstrumentationConfig {
|
|
11
11
|
/** Hook to modify log records before they are emitted. */
|
|
12
12
|
applyCustomLogRecordData?: ApplyCustomLogRecordDataFunction;
|
|
13
13
|
/** Use the Navigation API `currententrychange` event if available (experimental). Defaults to false. */
|
|
@@ -16,5 +16,4 @@ interface NavigationInstrumentationConfig extends InstrumentationConfig {
|
|
|
16
16
|
sanitizeUrl?: SanitizeUrlFunction;
|
|
17
17
|
}
|
|
18
18
|
//#endregion
|
|
19
|
-
export { ApplyCustomLogRecordDataFunction, NavigationInstrumentationConfig, NavigationType, SanitizeUrlFunction };
|
|
20
19
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -4,7 +4,7 @@ import { InstrumentationBase } from "@opentelemetry/instrumentation";
|
|
|
4
4
|
/**
|
|
5
5
|
* This class automatically instruments navigation timing within the browser.
|
|
6
6
|
*/
|
|
7
|
-
declare class NavigationTimingInstrumentation extends InstrumentationBase<NavigationTimingInstrumentationConfig> {
|
|
7
|
+
export declare class NavigationTimingInstrumentation extends InstrumentationBase<NavigationTimingInstrumentationConfig> {
|
|
8
8
|
private _lastEntry?;
|
|
9
9
|
private _completeDelayTimeoutId?;
|
|
10
10
|
private _retryCount;
|
|
@@ -36,5 +36,4 @@ declare class NavigationTimingInstrumentation extends InstrumentationBase<Naviga
|
|
|
36
36
|
private _unsubscribeAll;
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
39
|
-
export { NavigationTimingInstrumentation };
|
|
40
39
|
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/navigation-timing/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_NAVIGATION_CONNECT_END,\n ATTR_NAVIGATION_CONNECT_START,\n ATTR_NAVIGATION_DECODED_BODY_SIZE,\n ATTR_NAVIGATION_DOM_COMPLETE,\n ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_END,\n ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_START,\n ATTR_NAVIGATION_DOM_INTERACTIVE,\n ATTR_NAVIGATION_DOMAIN_LOOKUP_END,\n ATTR_NAVIGATION_DOMAIN_LOOKUP_START,\n ATTR_NAVIGATION_DURATION,\n ATTR_NAVIGATION_ENCODED_BODY_SIZE,\n ATTR_NAVIGATION_FETCH_START,\n ATTR_NAVIGATION_LOAD_EVENT_END,\n ATTR_NAVIGATION_LOAD_EVENT_START,\n ATTR_NAVIGATION_REDIRECT_COUNT,\n ATTR_NAVIGATION_REQUEST_START,\n ATTR_NAVIGATION_RESPONSE_END,\n ATTR_NAVIGATION_RESPONSE_START,\n ATTR_NAVIGATION_SECURE_CONNECTION_START,\n ATTR_NAVIGATION_TRANSFER_SIZE,\n ATTR_NAVIGATION_TYPE,\n ATTR_NAVIGATION_UNLOAD_EVENT_END,\n ATTR_NAVIGATION_UNLOAD_EVENT_START,\n ATTR_NAVIGATION_URL,\n NAVIGATION_TIMING_EVENT_NAME,\n} from './semconv.ts';\nimport type { NavigationTimingInstrumentationConfig } from './types.ts';\n\nconst BASE_DELAY_MS = 50;\nconst MAX_RETRIES = 5;\n\n/**\n * This class automatically instruments navigation timing within the browser.\n */\nexport class NavigationTimingInstrumentation extends InstrumentationBase<NavigationTimingInstrumentationConfig> {\n private _lastEntry?: PerformanceNavigationTiming;\n private _completeDelayTimeoutId?: number;\n private _retryCount = 0;\n private _didEmit = false;\n\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n private declare _isEnabled: boolean;\n private declare _onLoad: () => void;\n private declare _onPageHide: () => void;\n\n constructor(config: NavigationTimingInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/navigation-timing',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n this._isEnabled = true;\n this._onLoad = () => this._tryEmitOrSchedule();\n this._onPageHide = () => this._handleUnload();\n\n // Try emitting immediately (e.g. when enabled after load),\n // otherwise schedule for `load` or fall back to unload.\n this._tryEmitOrSchedule();\n if (this._didEmit) {\n return;\n }\n\n window.addEventListener('pagehide', this._onPageHide);\n }\n\n override disable(): void {\n this._isEnabled = false;\n this._unsubscribeAll();\n this._lastEntry = undefined;\n this._didEmit = false;\n this._retryCount = 0;\n }\n\n private _getLatestNavigationEntry(): PerformanceNavigationTiming | undefined {\n const entries = performance?.getEntriesByType?.('navigation') as\n | PerformanceNavigationTiming[]\n | undefined;\n if (!entries || entries.length === 0) {\n return;\n }\n\n return entries[entries.length - 1];\n }\n\n private _calculateBackoffDelay(): number {\n return this._retryCount * BASE_DELAY_MS;\n }\n\n /**\n * Attempts to emit the navigation timing event.\n *\n * - Emits immediately if a complete `PerformanceNavigationTiming` entry is available.\n * - If the page is still loading, waits for `window.load` and retries.\n * - If the page is already loaded but the entry is not finalized yet, schedules one\n * deferred re-check (to allow the browser to populate the timing fields).\n *\n * This method can be called multiple times (from `enable()`, the load handler, or the\n * deferred timeout), so it must be safe to re-enter.\n */\n private _tryEmitOrSchedule(): void {\n if (this._didEmit) {\n return;\n }\n\n const entry = this._getLatestNavigationEntry();\n if (entry) {\n this._lastEntry = entry;\n }\n\n // Prefer emitting a \"complete\" navigation entry.\n if (entry && entry.loadEventEnd > 0) {\n this._emitAndCleanup(entry);\n return;\n }\n\n // If the document is still loading, wait for `load` and try again.\n if (document.readyState !== 'complete') {\n window.addEventListener('load', this._onLoad, { once: true });\n return;\n }\n\n // If the document is already complete but navigation timings are not finalized yet,\n // schedule a deferred re-check with linear backoff to allow the browser to finish\n // populating the entry.\n if (this._completeDelayTimeoutId !== undefined) {\n return;\n }\n if (this._retryCount > MAX_RETRIES) {\n if (this._lastEntry) {\n this._diag.warn(\n 'Navigation timing: retries exhausted, emitting incomplete entry',\n );\n this._emitAndCleanup(this._lastEntry);\n } else {\n this._diag.warn(\n 'Navigation timing: retries exhausted with no entry available',\n );\n this._unsubscribeAll();\n }\n return;\n }\n\n const delay = this._calculateBackoffDelay();\n this._retryCount++;\n\n this._completeDelayTimeoutId = window.setTimeout(() => {\n this._completeDelayTimeoutId = undefined;\n this._tryEmitOrSchedule();\n }, delay);\n }\n\n private _handleUnload(): void {\n if (this._didEmit) {\n return;\n }\n\n const entry = this._getLatestNavigationEntry() ?? this._lastEntry;\n if (entry) {\n this._emitAndCleanup(entry);\n } else {\n this._diag.warn('Navigation timing: no entry available at unload');\n this._unsubscribeAll();\n }\n }\n\n private _emitAndCleanup(entry: PerformanceNavigationTiming): void {\n if (this._didEmit) {\n return;\n }\n\n this._didEmit = true;\n\n this._emitNavigationTiming(entry);\n this._lastEntry = undefined;\n this._unsubscribeAll();\n }\n\n private _emitNavigationTiming(entry: PerformanceNavigationTiming) {\n if (!entry) {\n return;\n }\n\n this.logger.emit({\n eventName: NAVIGATION_TIMING_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_NAVIGATION_TYPE]: entry.type,\n [ATTR_NAVIGATION_URL]: entry.name,\n [ATTR_NAVIGATION_DURATION]: entry.duration,\n [ATTR_NAVIGATION_DOM_COMPLETE]: entry.domComplete,\n [ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_END]:\n entry.domContentLoadedEventEnd,\n [ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_START]:\n entry.domContentLoadedEventStart,\n [ATTR_NAVIGATION_DOM_INTERACTIVE]: entry.domInteractive,\n [ATTR_NAVIGATION_LOAD_EVENT_END]: entry.loadEventEnd,\n [ATTR_NAVIGATION_LOAD_EVENT_START]: entry.loadEventStart,\n [ATTR_NAVIGATION_REDIRECT_COUNT]: entry.redirectCount,\n [ATTR_NAVIGATION_UNLOAD_EVENT_END]: entry.unloadEventEnd,\n [ATTR_NAVIGATION_UNLOAD_EVENT_START]: entry.unloadEventStart,\n [ATTR_NAVIGATION_FETCH_START]: entry.fetchStart,\n [ATTR_NAVIGATION_DOMAIN_LOOKUP_START]: entry.domainLookupStart,\n [ATTR_NAVIGATION_DOMAIN_LOOKUP_END]: entry.domainLookupEnd,\n [ATTR_NAVIGATION_CONNECT_START]: entry.connectStart,\n [ATTR_NAVIGATION_CONNECT_END]: entry.connectEnd,\n [ATTR_NAVIGATION_SECURE_CONNECTION_START]: entry.secureConnectionStart,\n [ATTR_NAVIGATION_REQUEST_START]: entry.requestStart,\n [ATTR_NAVIGATION_RESPONSE_START]: entry.responseStart,\n [ATTR_NAVIGATION_RESPONSE_END]: entry.responseEnd,\n [ATTR_NAVIGATION_TRANSFER_SIZE]: entry.transferSize,\n [ATTR_NAVIGATION_ENCODED_BODY_SIZE]: entry.encodedBodySize,\n [ATTR_NAVIGATION_DECODED_BODY_SIZE]: entry.decodedBodySize,\n },\n });\n }\n\n private _unsubscribeAll(): void {\n if (this._completeDelayTimeoutId) {\n clearTimeout(this._completeDelayTimeoutId);\n this._completeDelayTimeoutId = undefined;\n }\n\n if (this._onLoad) {\n window.removeEventListener('load', this._onLoad);\n }\n if (this._onPageHide) {\n window.removeEventListener('pagehide', this._onPageHide);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/navigation-timing/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_NAVIGATION_CONNECT_END,\n ATTR_NAVIGATION_CONNECT_START,\n ATTR_NAVIGATION_DECODED_BODY_SIZE,\n ATTR_NAVIGATION_DOM_COMPLETE,\n ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_END,\n ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_START,\n ATTR_NAVIGATION_DOM_INTERACTIVE,\n ATTR_NAVIGATION_DOMAIN_LOOKUP_END,\n ATTR_NAVIGATION_DOMAIN_LOOKUP_START,\n ATTR_NAVIGATION_DURATION,\n ATTR_NAVIGATION_ENCODED_BODY_SIZE,\n ATTR_NAVIGATION_FETCH_START,\n ATTR_NAVIGATION_LOAD_EVENT_END,\n ATTR_NAVIGATION_LOAD_EVENT_START,\n ATTR_NAVIGATION_REDIRECT_COUNT,\n ATTR_NAVIGATION_REQUEST_START,\n ATTR_NAVIGATION_RESPONSE_END,\n ATTR_NAVIGATION_RESPONSE_START,\n ATTR_NAVIGATION_SECURE_CONNECTION_START,\n ATTR_NAVIGATION_TRANSFER_SIZE,\n ATTR_NAVIGATION_TYPE,\n ATTR_NAVIGATION_UNLOAD_EVENT_END,\n ATTR_NAVIGATION_UNLOAD_EVENT_START,\n ATTR_NAVIGATION_URL,\n NAVIGATION_TIMING_EVENT_NAME,\n} from './semconv.ts';\nimport type { NavigationTimingInstrumentationConfig } from './types.ts';\n\nconst BASE_DELAY_MS = 50;\nconst MAX_RETRIES = 5;\n\n/**\n * This class automatically instruments navigation timing within the browser.\n */\nexport class NavigationTimingInstrumentation extends InstrumentationBase<NavigationTimingInstrumentationConfig> {\n private _lastEntry?: PerformanceNavigationTiming;\n private _completeDelayTimeoutId?: number;\n private _retryCount = 0;\n private _didEmit = false;\n\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n declare private _isEnabled: boolean;\n declare private _onLoad: () => void;\n declare private _onPageHide: () => void;\n\n constructor(config: NavigationTimingInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/navigation-timing',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n this._isEnabled = true;\n this._onLoad = () => this._tryEmitOrSchedule();\n this._onPageHide = () => this._handleUnload();\n\n // Try emitting immediately (e.g. when enabled after load),\n // otherwise schedule for `load` or fall back to unload.\n this._tryEmitOrSchedule();\n if (this._didEmit) {\n return;\n }\n\n window.addEventListener('pagehide', this._onPageHide);\n }\n\n override disable(): void {\n this._isEnabled = false;\n this._unsubscribeAll();\n this._lastEntry = undefined;\n this._didEmit = false;\n this._retryCount = 0;\n }\n\n private _getLatestNavigationEntry(): PerformanceNavigationTiming | undefined {\n const entries = performance?.getEntriesByType?.('navigation') as\n | PerformanceNavigationTiming[]\n | undefined;\n if (!entries || entries.length === 0) {\n return;\n }\n\n return entries[entries.length - 1];\n }\n\n private _calculateBackoffDelay(): number {\n return this._retryCount * BASE_DELAY_MS;\n }\n\n /**\n * Attempts to emit the navigation timing event.\n *\n * - Emits immediately if a complete `PerformanceNavigationTiming` entry is available.\n * - If the page is still loading, waits for `window.load` and retries.\n * - If the page is already loaded but the entry is not finalized yet, schedules one\n * deferred re-check (to allow the browser to populate the timing fields).\n *\n * This method can be called multiple times (from `enable()`, the load handler, or the\n * deferred timeout), so it must be safe to re-enter.\n */\n private _tryEmitOrSchedule(): void {\n if (this._didEmit) {\n return;\n }\n\n const entry = this._getLatestNavigationEntry();\n if (entry) {\n this._lastEntry = entry;\n }\n\n // Prefer emitting a \"complete\" navigation entry.\n if (entry && entry.loadEventEnd > 0) {\n this._emitAndCleanup(entry);\n return;\n }\n\n // If the document is still loading, wait for `load` and try again.\n if (document.readyState !== 'complete') {\n window.addEventListener('load', this._onLoad, { once: true });\n return;\n }\n\n // If the document is already complete but navigation timings are not finalized yet,\n // schedule a deferred re-check with linear backoff to allow the browser to finish\n // populating the entry.\n if (this._completeDelayTimeoutId !== undefined) {\n return;\n }\n if (this._retryCount > MAX_RETRIES) {\n if (this._lastEntry) {\n this._diag.warn(\n 'Navigation timing: retries exhausted, emitting incomplete entry',\n );\n this._emitAndCleanup(this._lastEntry);\n } else {\n this._diag.warn(\n 'Navigation timing: retries exhausted with no entry available',\n );\n this._unsubscribeAll();\n }\n return;\n }\n\n const delay = this._calculateBackoffDelay();\n this._retryCount++;\n\n this._completeDelayTimeoutId = window.setTimeout(() => {\n this._completeDelayTimeoutId = undefined;\n this._tryEmitOrSchedule();\n }, delay);\n }\n\n private _handleUnload(): void {\n if (this._didEmit) {\n return;\n }\n\n const entry = this._getLatestNavigationEntry() ?? this._lastEntry;\n if (entry) {\n this._emitAndCleanup(entry);\n } else {\n this._diag.warn('Navigation timing: no entry available at unload');\n this._unsubscribeAll();\n }\n }\n\n private _emitAndCleanup(entry: PerformanceNavigationTiming): void {\n if (this._didEmit) {\n return;\n }\n\n this._didEmit = true;\n\n this._emitNavigationTiming(entry);\n this._lastEntry = undefined;\n this._unsubscribeAll();\n }\n\n private _emitNavigationTiming(entry: PerformanceNavigationTiming) {\n if (!entry) {\n return;\n }\n\n this.logger.emit({\n eventName: NAVIGATION_TIMING_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_NAVIGATION_TYPE]: entry.type,\n [ATTR_NAVIGATION_URL]: entry.name,\n [ATTR_NAVIGATION_DURATION]: entry.duration,\n [ATTR_NAVIGATION_DOM_COMPLETE]: entry.domComplete,\n [ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_END]:\n entry.domContentLoadedEventEnd,\n [ATTR_NAVIGATION_DOM_CONTENT_LOADED_EVENT_START]:\n entry.domContentLoadedEventStart,\n [ATTR_NAVIGATION_DOM_INTERACTIVE]: entry.domInteractive,\n [ATTR_NAVIGATION_LOAD_EVENT_END]: entry.loadEventEnd,\n [ATTR_NAVIGATION_LOAD_EVENT_START]: entry.loadEventStart,\n [ATTR_NAVIGATION_REDIRECT_COUNT]: entry.redirectCount,\n [ATTR_NAVIGATION_UNLOAD_EVENT_END]: entry.unloadEventEnd,\n [ATTR_NAVIGATION_UNLOAD_EVENT_START]: entry.unloadEventStart,\n [ATTR_NAVIGATION_FETCH_START]: entry.fetchStart,\n [ATTR_NAVIGATION_DOMAIN_LOOKUP_START]: entry.domainLookupStart,\n [ATTR_NAVIGATION_DOMAIN_LOOKUP_END]: entry.domainLookupEnd,\n [ATTR_NAVIGATION_CONNECT_START]: entry.connectStart,\n [ATTR_NAVIGATION_CONNECT_END]: entry.connectEnd,\n [ATTR_NAVIGATION_SECURE_CONNECTION_START]: entry.secureConnectionStart,\n [ATTR_NAVIGATION_REQUEST_START]: entry.requestStart,\n [ATTR_NAVIGATION_RESPONSE_START]: entry.responseStart,\n [ATTR_NAVIGATION_RESPONSE_END]: entry.responseEnd,\n [ATTR_NAVIGATION_TRANSFER_SIZE]: entry.transferSize,\n [ATTR_NAVIGATION_ENCODED_BODY_SIZE]: entry.encodedBodySize,\n [ATTR_NAVIGATION_DECODED_BODY_SIZE]: entry.decodedBodySize,\n },\n });\n }\n\n private _unsubscribeAll(): void {\n if (this._completeDelayTimeoutId) {\n clearTimeout(this._completeDelayTimeoutId);\n this._completeDelayTimeoutId = undefined;\n }\n\n if (this._onLoad) {\n window.removeEventListener('load', this._onLoad);\n }\n if (this._onPageHide) {\n window.removeEventListener('pagehide', this._onPageHide);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,6 @@ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
|
3
3
|
/**
|
|
4
4
|
* NavigationTimingInstrumentation Configuration
|
|
5
5
|
*/
|
|
6
|
-
interface NavigationTimingInstrumentationConfig extends InstrumentationConfig {}
|
|
6
|
+
export interface NavigationTimingInstrumentationConfig extends InstrumentationConfig {}
|
|
7
7
|
//#endregion
|
|
8
|
-
export { NavigationTimingInstrumentationConfig };
|
|
9
8
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/package.js
CHANGED
|
@@ -9,7 +9,7 @@ import { InstrumentationBase } from "@opentelemetry/instrumentation";
|
|
|
9
9
|
* requestIdleCallback when available (with fallback for Safari) to ensure
|
|
10
10
|
* processing happens during idle periods.
|
|
11
11
|
*/
|
|
12
|
-
declare class ResourceTimingInstrumentation extends InstrumentationBase<ResourceTimingInstrumentationConfig> {
|
|
12
|
+
export declare class ResourceTimingInstrumentation extends InstrumentationBase<ResourceTimingInstrumentationConfig> {
|
|
13
13
|
private _observer?;
|
|
14
14
|
private _pendingEntries;
|
|
15
15
|
private _idleHandle?;
|
|
@@ -28,5 +28,4 @@ declare class ResourceTimingInstrumentation extends InstrumentationBase<Resource
|
|
|
28
28
|
private _cancelScheduledProcessing;
|
|
29
29
|
}
|
|
30
30
|
//#endregion
|
|
31
|
-
export { ResourceTimingInstrumentation };
|
|
32
31
|
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/resource-timing/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { getNetworkContextRegistry } from '#utils';\nimport { version } from '../../package.json' with { type: 'json' };\nimport { matchesUrl } from '../utils/url.ts';\nimport type { IdleCallbackHandle } from './idle-callback-shim.ts';\nimport {\n cancelIdleCallbackShim,\n requestIdleCallbackShim,\n} from './idle-callback-shim.ts';\nimport {\n ATTR_RESOURCE_CONNECT_END,\n ATTR_RESOURCE_CONNECT_START,\n ATTR_RESOURCE_DECODED_BODY_SIZE,\n ATTR_RESOURCE_DOMAIN_LOOKUP_END,\n ATTR_RESOURCE_DOMAIN_LOOKUP_START,\n ATTR_RESOURCE_DURATION,\n ATTR_RESOURCE_ENCODED_BODY_SIZE,\n ATTR_RESOURCE_FETCH_START,\n ATTR_RESOURCE_INITIATOR_TYPE,\n ATTR_RESOURCE_NEXT_HOP_PROTOCOL,\n ATTR_RESOURCE_REDIRECT_END,\n ATTR_RESOURCE_REDIRECT_START,\n ATTR_RESOURCE_RENDER_BLOCKING_STATUS,\n ATTR_RESOURCE_REQUEST_START,\n ATTR_RESOURCE_RESPONSE_END,\n ATTR_RESOURCE_RESPONSE_START,\n ATTR_RESOURCE_SECURE_CONNECTION_START,\n ATTR_RESOURCE_TRANSFER_SIZE,\n ATTR_RESOURCE_URL,\n ATTR_RESOURCE_WORKER_START,\n RESOURCE_TIMING_EVENT_NAME,\n} from './semconv.ts';\nimport type { ResourceTimingInstrumentationConfig } from './types.ts';\n\nconst DEFAULT_BATCH_SIZE = 50;\nconst DEFAULT_FORCE_PROCESSING_AFTER = 1000;\nconst DEFAULT_MAX_PROCESSING_TIME = 50;\nconst DEFAULT_MAX_QUEUE_SIZE = 1000;\n\nconst MIN_BATCH_SIZE = 1;\nconst MIN_FORCE_PROCESSING_AFTER = 0;\nconst MIN_PROCESSING_TIME = 0;\nconst MIN_QUEUE_SIZE = 1;\n\n/**\n * OpenTelemetry instrumentation for resource timing for browser applications.\n *\n * This instrumentation captures resource timing data using PerformanceObserver\n * and batches emissions to avoid overwhelming the main thread. It uses\n * requestIdleCallback when available (with fallback for Safari) to ensure\n * processing happens during idle periods.\n */\nexport class ResourceTimingInstrumentation extends InstrumentationBase<ResourceTimingInstrumentationConfig> {\n private _observer?: PerformanceObserver;\n private _pendingEntries: PerformanceResourceTiming[] = [];\n private _idleHandle?: IdleCallbackHandle;\n\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n private declare _isEnabled: boolean;\n private declare _loadHandler: (() => void) | undefined;\n private declare _visibilityChangeHandler: (() => void) | undefined;\n\n constructor(config: ResourceTimingInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/resource-timing',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n\n if (!('PerformanceObserver' in window)) {\n this._diag.debug(\n 'PerformanceObserver is not supported, resource timings will not be collected',\n );\n return;\n }\n\n this._isEnabled = true;\n\n if (document.readyState === 'complete') {\n this._setupObserver();\n } else {\n this._loadHandler = () => this._setupObserver();\n window.addEventListener('load', this._loadHandler, { once: true });\n }\n\n this._visibilityChangeHandler = () => {\n if (document.hidden) {\n this._flush();\n }\n };\n document.addEventListener(\n 'visibilitychange',\n this._visibilityChangeHandler,\n );\n }\n\n override disable(): void {\n this._isEnabled = false;\n this._flush();\n this._observer?.disconnect();\n this._observer = undefined;\n if (this._loadHandler) {\n window.removeEventListener('load', this._loadHandler);\n this._loadHandler = undefined;\n }\n if (this._visibilityChangeHandler) {\n document.removeEventListener(\n 'visibilitychange',\n this._visibilityChangeHandler,\n );\n this._visibilityChangeHandler = undefined;\n }\n }\n\n private _setupObserver(): void {\n if (!this._isEnabled) {\n return;\n }\n\n const observer = new PerformanceObserver((list) => {\n if (!this._isEnabled) {\n return;\n }\n\n const maxQueueSize = Math.max(\n this._config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,\n MIN_QUEUE_SIZE,\n );\n const entries = list.getEntries() as PerformanceResourceTiming[];\n const initiatorTypes = this._config.initiatorTypes;\n\n for (const entry of entries) {\n if (\n initiatorTypes !== undefined &&\n !initiatorTypes.includes(entry.initiatorType)\n ) {\n continue;\n }\n\n const shouldIgnoreUrl = matchesUrl(entry.name, this._config.ignoreUrls);\n if (shouldIgnoreUrl) {\n continue;\n }\n\n if (this._pendingEntries.length >= maxQueueSize) {\n this._flush();\n }\n this._pendingEntries.push(entry);\n }\n\n if (this._pendingEntries.length > 0) {\n this._scheduleProcessing();\n }\n });\n\n this._observer = observer;\n\n try {\n observer.observe({ type: 'resource', buffered: true });\n } catch (e) {\n this._diag.error('Failed to start resource PerformanceObserver', e);\n this._observer = undefined;\n }\n }\n\n private _scheduleProcessing(): void {\n if (this._idleHandle !== undefined) {\n return;\n }\n\n const timeout = Math.max(\n this._config.forceProcessingAfter ?? DEFAULT_FORCE_PROCESSING_AFTER,\n MIN_FORCE_PROCESSING_AFTER,\n );\n this._idleHandle = requestIdleCallbackShim(\n (deadline) => this._processChunk(deadline),\n { timeout },\n );\n }\n\n private _processChunk(deadline: IdleDeadline): void {\n this._idleHandle = undefined;\n if (!this._isEnabled || this._pendingEntries.length === 0) {\n return;\n }\n\n const maxTime = Math.max(\n this._config.maxProcessingTime ?? DEFAULT_MAX_PROCESSING_TIME,\n MIN_PROCESSING_TIME,\n );\n const batchSize = Math.max(\n this._config.batchSize ?? DEFAULT_BATCH_SIZE,\n MIN_BATCH_SIZE,\n );\n const startTime = performance.now();\n\n let cursor = 0;\n try {\n for (let i = 0; i < batchSize; i++) {\n const entry = this._pendingEntries[cursor];\n if (entry === undefined) {\n break;\n }\n\n const elapsed = performance.now() - startTime;\n // timeRemaining() is not Baseline widely available (no Safari support),\n // compatibility is handled by idle-callback-shim.ts.\n // eslint-disable-next-line baseline-js/use-baseline\n if (elapsed >= maxTime || deadline.timeRemaining() < 1) {\n break;\n }\n\n cursor++;\n this._emitResource(entry);\n }\n } finally {\n this._pendingEntries.splice(0, cursor);\n if (this._pendingEntries.length > 0) {\n this._scheduleProcessing();\n }\n }\n }\n\n private _emitResource(entry: PerformanceResourceTiming): void {\n try {\n const record: LogRecord = {\n eventName: RESOURCE_TIMING_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_RESOURCE_URL]: entry.name,\n [ATTR_RESOURCE_INITIATOR_TYPE]: entry.initiatorType,\n [ATTR_RESOURCE_DURATION]: entry.duration,\n [ATTR_RESOURCE_FETCH_START]: entry.fetchStart,\n [ATTR_RESOURCE_DOMAIN_LOOKUP_START]: entry.domainLookupStart,\n [ATTR_RESOURCE_DOMAIN_LOOKUP_END]: entry.domainLookupEnd,\n [ATTR_RESOURCE_CONNECT_START]: entry.connectStart,\n [ATTR_RESOURCE_CONNECT_END]: entry.connectEnd,\n [ATTR_RESOURCE_SECURE_CONNECTION_START]: entry.secureConnectionStart,\n [ATTR_RESOURCE_REQUEST_START]: entry.requestStart,\n [ATTR_RESOURCE_RESPONSE_START]: entry.responseStart,\n [ATTR_RESOURCE_RESPONSE_END]: entry.responseEnd,\n [ATTR_RESOURCE_TRANSFER_SIZE]: entry.transferSize,\n [ATTR_RESOURCE_ENCODED_BODY_SIZE]: entry.encodedBodySize,\n [ATTR_RESOURCE_DECODED_BODY_SIZE]: entry.decodedBodySize,\n [ATTR_RESOURCE_REDIRECT_START]: entry.redirectStart,\n [ATTR_RESOURCE_REDIRECT_END]: entry.redirectEnd,\n [ATTR_RESOURCE_WORKER_START]: entry.workerStart,\n [ATTR_RESOURCE_NEXT_HOP_PROTOCOL]: entry.nextHopProtocol,\n // @ts-expect-error renderBlockingStatus is only available in Chromium as of March 2026\n [ATTR_RESOURCE_RENDER_BLOCKING_STATUS]: entry.renderBlockingStatus,\n },\n };\n const registry = getNetworkContextRegistry();\n const ctx = registry.getContext(entry);\n // Attach the context to the LogRecord and remove it from the registry\n if (ctx) {\n record.context = ctx;\n registry.unregister(entry);\n }\n this.logger.emit(record);\n } catch (error) {\n this._diag.error(\n `Failed to emit resource timing entry for \"${entry.name}\"`,\n error,\n );\n }\n }\n\n private _flush(): void {\n this._cancelScheduledProcessing();\n\n for (const entry of this._pendingEntries) {\n this._emitResource(entry);\n }\n this._pendingEntries = [];\n }\n\n private _cancelScheduledProcessing(): void {\n if (this._idleHandle !== undefined) {\n cancelIdleCallbackShim(this._idleHandle);\n this._idleHandle = undefined;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/resource-timing/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { getNetworkContextRegistry } from '#utils';\nimport { version } from '../../package.json' with { type: 'json' };\nimport { matchesUrl } from '../utils/url.ts';\nimport type { IdleCallbackHandle } from './idle-callback-shim.ts';\nimport {\n cancelIdleCallbackShim,\n requestIdleCallbackShim,\n} from './idle-callback-shim.ts';\nimport {\n ATTR_RESOURCE_CONNECT_END,\n ATTR_RESOURCE_CONNECT_START,\n ATTR_RESOURCE_DECODED_BODY_SIZE,\n ATTR_RESOURCE_DOMAIN_LOOKUP_END,\n ATTR_RESOURCE_DOMAIN_LOOKUP_START,\n ATTR_RESOURCE_DURATION,\n ATTR_RESOURCE_ENCODED_BODY_SIZE,\n ATTR_RESOURCE_FETCH_START,\n ATTR_RESOURCE_INITIATOR_TYPE,\n ATTR_RESOURCE_NEXT_HOP_PROTOCOL,\n ATTR_RESOURCE_REDIRECT_END,\n ATTR_RESOURCE_REDIRECT_START,\n ATTR_RESOURCE_RENDER_BLOCKING_STATUS,\n ATTR_RESOURCE_REQUEST_START,\n ATTR_RESOURCE_RESPONSE_END,\n ATTR_RESOURCE_RESPONSE_START,\n ATTR_RESOURCE_SECURE_CONNECTION_START,\n ATTR_RESOURCE_TRANSFER_SIZE,\n ATTR_RESOURCE_URL,\n ATTR_RESOURCE_WORKER_START,\n RESOURCE_TIMING_EVENT_NAME,\n} from './semconv.ts';\nimport type { ResourceTimingInstrumentationConfig } from './types.ts';\n\nconst DEFAULT_BATCH_SIZE = 50;\nconst DEFAULT_FORCE_PROCESSING_AFTER = 1000;\nconst DEFAULT_MAX_PROCESSING_TIME = 50;\nconst DEFAULT_MAX_QUEUE_SIZE = 1000;\n\nconst MIN_BATCH_SIZE = 1;\nconst MIN_FORCE_PROCESSING_AFTER = 0;\nconst MIN_PROCESSING_TIME = 0;\nconst MIN_QUEUE_SIZE = 1;\n\n/**\n * OpenTelemetry instrumentation for resource timing for browser applications.\n *\n * This instrumentation captures resource timing data using PerformanceObserver\n * and batches emissions to avoid overwhelming the main thread. It uses\n * requestIdleCallback when available (with fallback for Safari) to ensure\n * processing happens during idle periods.\n */\nexport class ResourceTimingInstrumentation extends InstrumentationBase<ResourceTimingInstrumentationConfig> {\n private _observer?: PerformanceObserver;\n private _pendingEntries: PerformanceResourceTiming[] = [];\n private _idleHandle?: IdleCallbackHandle;\n\n // Use `declare` to prevent JS class field initializers from running after\n // super(), which would reset values set by the enable() call that\n // InstrumentationBase makes during its constructor.\n declare private _isEnabled: boolean;\n declare private _loadHandler: (() => void) | undefined;\n declare private _visibilityChangeHandler: (() => void) | undefined;\n\n constructor(config: ResourceTimingInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/resource-timing',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n\n if (!('PerformanceObserver' in window)) {\n this._diag.debug(\n 'PerformanceObserver is not supported, resource timings will not be collected',\n );\n return;\n }\n\n this._isEnabled = true;\n\n if (document.readyState === 'complete') {\n this._setupObserver();\n } else {\n this._loadHandler = () => this._setupObserver();\n window.addEventListener('load', this._loadHandler, { once: true });\n }\n\n this._visibilityChangeHandler = () => {\n if (document.hidden) {\n this._flush();\n }\n };\n document.addEventListener(\n 'visibilitychange',\n this._visibilityChangeHandler,\n );\n }\n\n override disable(): void {\n this._isEnabled = false;\n this._flush();\n this._observer?.disconnect();\n this._observer = undefined;\n if (this._loadHandler) {\n window.removeEventListener('load', this._loadHandler);\n this._loadHandler = undefined;\n }\n if (this._visibilityChangeHandler) {\n document.removeEventListener(\n 'visibilitychange',\n this._visibilityChangeHandler,\n );\n this._visibilityChangeHandler = undefined;\n }\n }\n\n private _setupObserver(): void {\n if (!this._isEnabled) {\n return;\n }\n\n const observer = new PerformanceObserver((list) => {\n if (!this._isEnabled) {\n return;\n }\n\n const maxQueueSize = Math.max(\n this._config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,\n MIN_QUEUE_SIZE,\n );\n const entries = list.getEntries() as PerformanceResourceTiming[];\n const initiatorTypes = this._config.initiatorTypes;\n\n for (const entry of entries) {\n if (\n initiatorTypes !== undefined &&\n !initiatorTypes.includes(entry.initiatorType)\n ) {\n continue;\n }\n\n const shouldIgnoreUrl = matchesUrl(entry.name, this._config.ignoreUrls);\n if (shouldIgnoreUrl) {\n continue;\n }\n\n if (this._pendingEntries.length >= maxQueueSize) {\n this._flush();\n }\n this._pendingEntries.push(entry);\n }\n\n if (this._pendingEntries.length > 0) {\n this._scheduleProcessing();\n }\n });\n\n this._observer = observer;\n\n try {\n observer.observe({ type: 'resource', buffered: true });\n } catch (e) {\n this._diag.error('Failed to start resource PerformanceObserver', e);\n this._observer = undefined;\n }\n }\n\n private _scheduleProcessing(): void {\n if (this._idleHandle !== undefined) {\n return;\n }\n\n const timeout = Math.max(\n this._config.forceProcessingAfter ?? DEFAULT_FORCE_PROCESSING_AFTER,\n MIN_FORCE_PROCESSING_AFTER,\n );\n this._idleHandle = requestIdleCallbackShim(\n (deadline) => this._processChunk(deadline),\n { timeout },\n );\n }\n\n private _processChunk(deadline: IdleDeadline): void {\n this._idleHandle = undefined;\n if (!this._isEnabled || this._pendingEntries.length === 0) {\n return;\n }\n\n const maxTime = Math.max(\n this._config.maxProcessingTime ?? DEFAULT_MAX_PROCESSING_TIME,\n MIN_PROCESSING_TIME,\n );\n const batchSize = Math.max(\n this._config.batchSize ?? DEFAULT_BATCH_SIZE,\n MIN_BATCH_SIZE,\n );\n const startTime = performance.now();\n\n let cursor = 0;\n try {\n for (let i = 0; i < batchSize; i++) {\n const entry = this._pendingEntries[cursor];\n if (entry === undefined) {\n break;\n }\n\n const elapsed = performance.now() - startTime;\n // timeRemaining() is not Baseline widely available (no Safari support),\n // compatibility is handled by idle-callback-shim.ts.\n // eslint-disable-next-line baseline-js/use-baseline\n if (elapsed >= maxTime || deadline.timeRemaining() < 1) {\n break;\n }\n\n cursor++;\n this._emitResource(entry);\n }\n } finally {\n this._pendingEntries.splice(0, cursor);\n if (this._pendingEntries.length > 0) {\n this._scheduleProcessing();\n }\n }\n }\n\n private _emitResource(entry: PerformanceResourceTiming): void {\n try {\n const record: LogRecord = {\n eventName: RESOURCE_TIMING_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [ATTR_RESOURCE_URL]: entry.name,\n [ATTR_RESOURCE_INITIATOR_TYPE]: entry.initiatorType,\n [ATTR_RESOURCE_DURATION]: entry.duration,\n [ATTR_RESOURCE_FETCH_START]: entry.fetchStart,\n [ATTR_RESOURCE_DOMAIN_LOOKUP_START]: entry.domainLookupStart,\n [ATTR_RESOURCE_DOMAIN_LOOKUP_END]: entry.domainLookupEnd,\n [ATTR_RESOURCE_CONNECT_START]: entry.connectStart,\n [ATTR_RESOURCE_CONNECT_END]: entry.connectEnd,\n [ATTR_RESOURCE_SECURE_CONNECTION_START]: entry.secureConnectionStart,\n [ATTR_RESOURCE_REQUEST_START]: entry.requestStart,\n [ATTR_RESOURCE_RESPONSE_START]: entry.responseStart,\n [ATTR_RESOURCE_RESPONSE_END]: entry.responseEnd,\n [ATTR_RESOURCE_TRANSFER_SIZE]: entry.transferSize,\n [ATTR_RESOURCE_ENCODED_BODY_SIZE]: entry.encodedBodySize,\n [ATTR_RESOURCE_DECODED_BODY_SIZE]: entry.decodedBodySize,\n [ATTR_RESOURCE_REDIRECT_START]: entry.redirectStart,\n [ATTR_RESOURCE_REDIRECT_END]: entry.redirectEnd,\n [ATTR_RESOURCE_WORKER_START]: entry.workerStart,\n [ATTR_RESOURCE_NEXT_HOP_PROTOCOL]: entry.nextHopProtocol,\n // @ts-expect-error renderBlockingStatus is only available in Chromium as of March 2026\n [ATTR_RESOURCE_RENDER_BLOCKING_STATUS]: entry.renderBlockingStatus,\n },\n };\n const registry = getNetworkContextRegistry();\n const ctx = registry.getContext(entry);\n // Attach the context to the LogRecord and remove it from the registry\n if (ctx) {\n record.context = ctx;\n registry.unregister(entry);\n }\n this.logger.emit(record);\n } catch (error) {\n this._diag.error(\n `Failed to emit resource timing entry for \"${entry.name}\"`,\n error,\n );\n }\n }\n\n private _flush(): void {\n this._cancelScheduledProcessing();\n\n for (const entry of this._pendingEntries) {\n this._emitResource(entry);\n }\n this._pendingEntries = [];\n }\n\n private _cancelScheduledProcessing(): void {\n if (this._idleHandle !== undefined) {\n cancelIdleCallbackShim(this._idleHandle);\n this._idleHandle = undefined;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
|
3
3
|
/**
|
|
4
4
|
* ResourceTimingInstrumentation Configuration
|
|
5
5
|
*/
|
|
6
|
-
interface ResourceTimingInstrumentationConfig extends InstrumentationConfig {
|
|
6
|
+
export interface ResourceTimingInstrumentationConfig extends InstrumentationConfig {
|
|
7
7
|
/**
|
|
8
8
|
* Number of resources to process per batch.
|
|
9
9
|
* Default: 50
|
|
@@ -43,5 +43,4 @@ interface ResourceTimingInstrumentationConfig extends InstrumentationConfig {
|
|
|
43
43
|
ignoreUrls?: (string | RegExp)[];
|
|
44
44
|
}
|
|
45
45
|
//#endregion
|
|
46
|
-
export { ResourceTimingInstrumentationConfig };
|
|
47
46
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -4,7 +4,7 @@ import { InstrumentationBase } from "@opentelemetry/instrumentation";
|
|
|
4
4
|
/**
|
|
5
5
|
* This class automatically instruments different User Actions within the browser.
|
|
6
6
|
*/
|
|
7
|
-
declare class UserActionInstrumentation extends InstrumentationBase<UserActionInstrumentationConfig> {
|
|
7
|
+
export declare class UserActionInstrumentation extends InstrumentationBase<UserActionInstrumentationConfig> {
|
|
8
8
|
private _onClickHandler?;
|
|
9
9
|
constructor(config?: UserActionInstrumentationConfig);
|
|
10
10
|
protected init(): never[];
|
|
@@ -15,5 +15,4 @@ declare class UserActionInstrumentation extends InstrumentationBase<UserActionIn
|
|
|
15
15
|
disable(): void;
|
|
16
16
|
}
|
|
17
17
|
//#endregion
|
|
18
|
-
export { UserActionInstrumentation };
|
|
19
18
|
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/user-action/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport { getElementCSSSelector } from '#utils';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_CSS_SELECTOR,\n ATTR_MOUSE_EVENT_BUTTON,\n ATTR_PAGE_X,\n ATTR_PAGE_Y,\n ATTR_TAG_NAME,\n ATTR_TAGS,\n CLICK_EVENT_NAME,\n} from './semconv.ts';\nimport type {\n AutoCapturedUserAction,\n MouseButton,\n UserActionInstrumentationConfig,\n} from './types.ts';\n\nconst DEFAULT_AUTO_CAPTURED_ACTIONS: AutoCapturedUserAction[] = ['click'];\nconst OTEL_ELEMENT_ATTRIBUTE_PREFIX = 'data-otel-';\n\n/**\n * This class automatically instruments different User Actions within the browser.\n */\nexport class UserActionInstrumentation extends InstrumentationBase<UserActionInstrumentationConfig> {\n private
|
|
1
|
+
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/user-action/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport { getElementCSSSelector } from '#utils';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_CSS_SELECTOR,\n ATTR_MOUSE_EVENT_BUTTON,\n ATTR_PAGE_X,\n ATTR_PAGE_Y,\n ATTR_TAG_NAME,\n ATTR_TAGS,\n CLICK_EVENT_NAME,\n} from './semconv.ts';\nimport type {\n AutoCapturedUserAction,\n MouseButton,\n UserActionInstrumentationConfig,\n} from './types.ts';\n\nconst DEFAULT_AUTO_CAPTURED_ACTIONS: AutoCapturedUserAction[] = ['click'];\nconst OTEL_ELEMENT_ATTRIBUTE_PREFIX = 'data-otel-';\n\n/**\n * This class automatically instruments different User Actions within the browser.\n */\nexport class UserActionInstrumentation extends InstrumentationBase<UserActionInstrumentationConfig> {\n declare private _onClickHandler?: (event: MouseEvent) => void;\n\n constructor(config: UserActionInstrumentationConfig = {}) {\n super(\n '@opentelemetry/browser-instrumentation/user-action',\n version,\n config,\n );\n }\n\n protected override init() {\n return [];\n }\n\n private _getMouseButtonFromMouseEvent(event: MouseEvent): MouseButton {\n switch (event.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n default:\n return 'left';\n }\n }\n\n private onClick(event: MouseEvent) {\n const element = event.target;\n\n if (!(element instanceof HTMLElement)) {\n return;\n }\n\n if (element.hasAttribute('disabled')) {\n return;\n }\n\n const cssSelector = getElementCSSSelector(element, {\n useIdForTargetElement: true,\n useIdForAncestors: true,\n });\n const otelPrefixedAttributes: Record<string, string> = {};\n\n // Grab all the attributes in the element that start with data-otel-*\n for (const attr of element.attributes) {\n if (attr.name.startsWith(OTEL_ELEMENT_ATTRIBUTE_PREFIX)) {\n otelPrefixedAttributes[\n attr.name.slice(OTEL_ELEMENT_ATTRIBUTE_PREFIX.length)\n ] = attr.value;\n }\n }\n\n const logRecord: LogRecord = {\n severityNumber: SeverityNumber.INFO,\n eventName: CLICK_EVENT_NAME,\n attributes: {\n [ATTR_PAGE_X]: event.pageX,\n [ATTR_PAGE_Y]: event.pageY,\n [ATTR_TAG_NAME]: element.tagName,\n [ATTR_TAGS]: otelPrefixedAttributes,\n [ATTR_MOUSE_EVENT_BUTTON]: this._getMouseButtonFromMouseEvent(event),\n [ATTR_CSS_SELECTOR]: cssSelector,\n },\n };\n\n this._applyCustomLogRecordData(logRecord);\n\n this.logger.emit(logRecord);\n }\n\n private _applyCustomLogRecordData(logRecord: LogRecord) {\n const applyCustomLogRecordData = this.getConfig().applyCustomLogRecordData;\n if (applyCustomLogRecordData) {\n safeExecuteInTheMiddle(\n () => applyCustomLogRecordData(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n }\n\n override enable(): void {\n const autoCapturedActions =\n this._config.autoCapturedActions ?? DEFAULT_AUTO_CAPTURED_ACTIONS;\n if (!this._onClickHandler) {\n this._onClickHandler = this.onClick.bind(this);\n }\n\n if (autoCapturedActions.includes('click')) {\n document.addEventListener('click', this._onClickHandler, true);\n }\n }\n\n override disable(): void {\n if (this._onClickHandler) {\n document.removeEventListener('click', this._onClickHandler, true);\n }\n }\n}\n"],"mappings":";;;;;;AA2BA,MAAM,gCAA0D,CAAC,OAAO;AACxE,MAAM,gCAAgC;;;;AAKtC,IAAa,4BAAb,cAA+C,oBAAqD;CAGlG,YAAY,SAA0C,CAAC,GAAG;EACxD,MACE,sDACA,SACA,MACF;CACF;CAEA,OAA0B;EACxB,OAAO,CAAC;CACV;CAEA,8BAAsC,OAAgC;EACpE,QAAQ,MAAM,QAAd;GACE,KAAK,GACH,OAAO;GACT,KAAK,GACH,OAAO;GACT,KAAK,GACH,OAAO;GACT,SACE,OAAO;EACX;CACF;CAEA,QAAgB,OAAmB;EACjC,MAAM,UAAU,MAAM;EAEtB,IAAI,EAAE,mBAAmB,cACvB;EAGF,IAAI,QAAQ,aAAa,UAAU,GACjC;EAGF,MAAM,cAAc,sBAAsB,SAAS;GACjD,uBAAuB;GACvB,mBAAmB;EACrB,CAAC;EACD,MAAM,yBAAiD,CAAC;EAGxD,KAAK,MAAM,QAAQ,QAAQ,YACzB,IAAI,KAAK,KAAK,WAAW,6BAA6B,GACpD,uBACE,KAAK,KAAK,MAAM,EAAoC,KAClD,KAAK;EAIb,MAAM,YAAuB;GAC3B,gBAAgB,eAAe;GAC/B,WAAW;GACX,YAAY;KACT,cAAc,MAAM;KACpB,cAAc,MAAM;KACpB,gBAAgB,QAAQ;KACxB,YAAY;KACZ,0BAA0B,KAAK,8BAA8B,KAAK;KAClE,oBAAoB;GACvB;EACF;EAEA,KAAK,0BAA0B,SAAS;EAExC,KAAK,OAAO,KAAK,SAAS;CAC5B;CAEA,0BAAkC,WAAsB;EACtD,MAAM,2BAA2B,KAAK,UAAU,CAAC,CAAC;EAClD,IAAI,0BACF,6BACQ,yBAAyB,SAAS,IACvC,UAAU;GACT,IAAI,OACF,KAAK,MAAM,MAAM,wCAAwC,KAAK;EAElE,GACA,IACF;CAEJ;CAEA,SAAwB;EACtB,MAAM,sBACJ,KAAK,QAAQ,uBAAuB;EACtC,IAAI,CAAC,KAAK,iBACR,KAAK,kBAAkB,KAAK,QAAQ,KAAK,IAAI;EAG/C,IAAI,oBAAoB,SAAS,OAAO,GACtC,SAAS,iBAAiB,SAAS,KAAK,iBAAiB,IAAI;CAEjE;CAEA,UAAyB;EACvB,IAAI,KAAK,iBACP,SAAS,oBAAoB,SAAS,KAAK,iBAAiB,IAAI;CAEpE;AACF"}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { LogRecord } from "@opentelemetry/api-logs";
|
|
2
2
|
import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
3
3
|
//#region src/user-action/types.d.ts
|
|
4
|
-
type AutoCapturedUserAction = 'click';
|
|
5
|
-
type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
|
|
4
|
+
export type AutoCapturedUserAction = 'click';
|
|
5
|
+
export type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
|
|
6
6
|
/**
|
|
7
7
|
* UserActionInstrumentation Configuration
|
|
8
8
|
*/
|
|
9
|
-
interface UserActionInstrumentationConfig extends InstrumentationConfig {
|
|
9
|
+
export interface UserActionInstrumentationConfig extends InstrumentationConfig {
|
|
10
10
|
autoCapturedActions?: AutoCapturedUserAction[];
|
|
11
11
|
/** Hook to modify log records before they are emitted. */
|
|
12
12
|
applyCustomLogRecordData?: ApplyCustomLogRecordDataFunction;
|
|
13
13
|
}
|
|
14
14
|
//#endregion
|
|
15
|
-
export { ApplyCustomLogRecordDataFunction, AutoCapturedUserAction, UserActionInstrumentationConfig };
|
|
16
15
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -9,7 +9,7 @@ import { InstrumentationBase } from "@opentelemetry/instrumentation";
|
|
|
9
9
|
* registered. Calling `disable()` will stop emitting logs, but the underlying
|
|
10
10
|
* listeners remain active. Calling `enable()` again will resume emission.
|
|
11
11
|
*/
|
|
12
|
-
declare class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInstrumentationConfig> {
|
|
12
|
+
export declare class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInstrumentationConfig> {
|
|
13
13
|
private _isEnabled;
|
|
14
14
|
private _listenersRegistered;
|
|
15
15
|
private _applyCustomLogRecordData?;
|
|
@@ -35,5 +35,4 @@ declare class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInst
|
|
|
35
35
|
private _emitWebVital;
|
|
36
36
|
}
|
|
37
37
|
//#endregion
|
|
38
|
-
export { WebVitalsInstrumentation };
|
|
39
38
|
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/web-vitals/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Attributes } from '@opentelemetry/api';\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport type {\n CLSMetricWithAttribution,\n INPMetricWithAttribution,\n MetricWithAttribution,\n} from 'web-vitals/attribution';\nimport { onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals/attribution';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_WEB_VITAL_DELTA,\n ATTR_WEB_VITAL_ID,\n ATTR_WEB_VITAL_NAME,\n ATTR_WEB_VITAL_NAVIGATION_TYPE,\n ATTR_WEB_VITAL_RATING,\n ATTR_WEB_VITAL_VALUE,\n WEB_VITAL_EVENT_NAME,\n} from './semconv.ts';\nimport type { WebVitalsInstrumentationConfig } from './types.ts';\n\n/**\n * Instrumentation for Core Web Vitals using the `web-vitals` library.\n * https://github.com/GoogleChrome/web-vitals\n *\n * Note: The `web-vitals` library does not support removing listeners once\n * registered. Calling `disable()` will stop emitting logs, but the underlying\n * listeners remain active. Calling `enable()` again will resume emission.\n */\nexport class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInstrumentationConfig> {\n // Using `declare` is required here: InstrumentationBase calls enable() during\n // construction, and standard field initialization would reset this flag after\n // super() returns, breaking the duplicate-registration guard.\n private
|
|
1
|
+
{"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/web-vitals/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Attributes } from '@opentelemetry/api';\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport type {\n CLSMetricWithAttribution,\n INPMetricWithAttribution,\n MetricWithAttribution,\n} from 'web-vitals/attribution';\nimport { onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals/attribution';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_WEB_VITAL_DELTA,\n ATTR_WEB_VITAL_ID,\n ATTR_WEB_VITAL_NAME,\n ATTR_WEB_VITAL_NAVIGATION_TYPE,\n ATTR_WEB_VITAL_RATING,\n ATTR_WEB_VITAL_VALUE,\n WEB_VITAL_EVENT_NAME,\n} from './semconv.ts';\nimport type { WebVitalsInstrumentationConfig } from './types.ts';\n\n/**\n * Instrumentation for Core Web Vitals using the `web-vitals` library.\n * https://github.com/GoogleChrome/web-vitals\n *\n * Note: The `web-vitals` library does not support removing listeners once\n * registered. Calling `disable()` will stop emitting logs, but the underlying\n * listeners remain active. Calling `enable()` again will resume emission.\n */\nexport class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInstrumentationConfig> {\n // Using `declare` is required here: InstrumentationBase calls enable() during\n // construction, and standard field initialization would reset this flag after\n // super() returns, breaking the duplicate-registration guard.\n declare private _isEnabled: boolean;\n declare private _listenersRegistered: boolean;\n private _applyCustomLogRecordData?: (logRecord: LogRecord) => void;\n private _includeRawAttribution: boolean;\n\n constructor(config: WebVitalsInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/web-vitals', version, config);\n this._applyCustomLogRecordData = config.applyCustomLogRecordData;\n this._includeRawAttribution = config.includeRawAttribution ?? false;\n }\n\n protected override init() {\n return [];\n }\n\n /**\n * Enables the instrumentation and registers web-vitals listeners.\n * Listeners are registered only once. If disabled, subsequent calls resume emission.\n */\n override enable(): void {\n if (typeof PerformanceObserver === 'undefined') {\n this._diag.debug(\n 'PerformanceObserver not supported, web vitals will not be collected',\n );\n return;\n }\n\n this._isEnabled = true;\n\n if (this._listenersRegistered) {\n this._diag.debug('Listeners already registered, resuming emission');\n return;\n }\n\n this._listenersRegistered = true;\n this._diag.debug(`Registering listeners`);\n // CLS is only supported in Chromium. See:\n // https://github.com/GoogleChrome/web-vitals?tab=readme-ov-file#browser-support\n onCLS((metric) => this._emitWebVital(metric));\n onINP((metric) => this._emitWebVital(metric));\n onLCP((metric) => this._emitWebVital(metric));\n onFCP((metric) => this._emitWebVital(metric));\n onTTFB((metric) => this._emitWebVital(metric));\n }\n\n /**\n * Disables the instrumentation, pausing log emission.\n * Listeners remain active due to web-vitals library limitations.\n */\n override disable(): void {\n this._isEnabled = false;\n this._diag.debug('Instrumentation disabled, pausing emission');\n }\n\n /**\n * Gets the timestamp for a metric based on attribution timing.\n * Returns undefined to let OTel use the current time for metrics without\n * specific timing information.\n */\n private _getTimestampForMetric(\n metric: MetricWithAttribution,\n ): number | undefined {\n if (metric.name === 'CLS') {\n const { attribution } = metric as CLSMetricWithAttribution;\n if (attribution.largestShiftTime !== undefined) {\n return attribution.largestShiftTime;\n }\n return undefined;\n }\n if (metric.name === 'INP') {\n const { attribution } = metric as INPMetricWithAttribution;\n return attribution.interactionTime;\n }\n // FCP, LCP, TTFB: metric.value is already DOMHighResTimeStamp of the event\n return metric.value;\n }\n\n private _emitWebVital(metric: MetricWithAttribution): void {\n if (!this._isEnabled) {\n return;\n }\n const attributes: Attributes = {\n [ATTR_WEB_VITAL_NAME]: metric.name.toLowerCase(),\n [ATTR_WEB_VITAL_VALUE]: metric.value,\n // `delta` equals `value` on the first emission; subsequent emissions report only the change\n [ATTR_WEB_VITAL_DELTA]: metric.delta,\n [ATTR_WEB_VITAL_RATING]: metric.rating,\n [ATTR_WEB_VITAL_ID]: metric.id,\n [ATTR_WEB_VITAL_NAVIGATION_TYPE]: metric.navigationType,\n };\n\n const timestamp = this._getTimestampForMetric(metric);\n\n const logRecord: LogRecord = {\n eventName: WEB_VITAL_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes,\n ...(this._includeRawAttribution\n ? { body: JSON.stringify(metric.attribution) }\n : {}),\n ...(timestamp !== undefined ? { timestamp } : {}),\n };\n\n if (this._applyCustomLogRecordData) {\n safeExecuteInTheMiddle(\n () => this._applyCustomLogRecordData?.(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n\n this.logger.emit(logRecord);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAsCA,IAAa,2BAAb,cAA8C,oBAAoD;CAMhG;CACA;CAEA,YAAY,SAAyC,CAAC,GAAG;EACvD,MAAM,qDAAqD,SAAS,MAAM;EAC1E,KAAK,4BAA4B,OAAO;EACxC,KAAK,yBAAyB,OAAO,yBAAyB;CAChE;CAEA,OAA0B;EACxB,OAAO,CAAC;CACV;;;;;CAMA,SAAwB;EACtB,IAAI,OAAO,wBAAwB,aAAa;GAC9C,KAAK,MAAM,MACT,qEACF;GACA;EACF;EAEA,KAAK,aAAa;EAElB,IAAI,KAAK,sBAAsB;GAC7B,KAAK,MAAM,MAAM,iDAAiD;GAClE;EACF;EAEA,KAAK,uBAAuB;EAC5B,KAAK,MAAM,MAAM,uBAAuB;EAGxC,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,QAAQ,WAAW,KAAK,cAAc,MAAM,CAAC;CAC/C;;;;;CAMA,UAAyB;EACvB,KAAK,aAAa;EAClB,KAAK,MAAM,MAAM,4CAA4C;CAC/D;;;;;;CAOA,uBACE,QACoB;EACpB,IAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;GACxB,IAAI,YAAY,qBAAqB,KAAA,GACnC,OAAO,YAAY;GAErB;EACF;EACA,IAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;GACxB,OAAO,YAAY;EACrB;EAEA,OAAO,OAAO;CAChB;CAEA,cAAsB,QAAqC;EACzD,IAAI,CAAC,KAAK,YACR;EAEF,MAAM,aAAyB;IAC5B,sBAAsB,OAAO,KAAK,YAAY;IAC9C,uBAAuB,OAAO;IAE9B,uBAAuB,OAAO;IAC9B,wBAAwB,OAAO;IAC/B,oBAAoB,OAAO;IAC3B,iCAAiC,OAAO;EAC3C;EAEA,MAAM,YAAY,KAAK,uBAAuB,MAAM;EAEpD,MAAM,YAAuB;GAC3B,WAAW;GACX,gBAAgB,eAAe;GAC/B;GACA,GAAI,KAAK,yBACL,EAAE,MAAM,KAAK,UAAU,OAAO,WAAW,EAAE,IAC3C,CAAC;GACL,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;EACjD;EAEA,IAAI,KAAK,2BACP,6BACQ,KAAK,4BAA4B,SAAS,IAC/C,UAAU;GACT,IAAI,OACF,KAAK,MAAM,MAAM,wCAAwC,KAAK;EAElE,GACA,IACF;EAGF,KAAK,OAAO,KAAK,SAAS;CAC5B;AACF"}
|
|
@@ -4,7 +4,7 @@ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
|
4
4
|
/**
|
|
5
5
|
* WebVitalsInstrumentation Configuration
|
|
6
6
|
*/
|
|
7
|
-
interface WebVitalsInstrumentationConfig extends InstrumentationConfig {
|
|
7
|
+
export interface WebVitalsInstrumentationConfig extends InstrumentationConfig {
|
|
8
8
|
/**
|
|
9
9
|
* @experimental
|
|
10
10
|
* When true, sets the log record body to the JSON-stringified
|
|
@@ -21,5 +21,4 @@ interface WebVitalsInstrumentationConfig extends InstrumentationConfig {
|
|
|
21
21
|
applyCustomLogRecordData?: (logRecord: LogRecord) => void;
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
|
-
export { WebVitalsInstrumentationConfig };
|
|
25
24
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { XhrInstrumentationConfig } from "./types.js";
|
|
2
2
|
import { InstrumentationBase } from "@opentelemetry/instrumentation";
|
|
3
3
|
//#region src/xhr/instrumentation.d.ts
|
|
4
|
-
declare class XhrInstrumentation extends InstrumentationBase<XhrInstrumentationConfig> {
|
|
4
|
+
export declare class XhrInstrumentation extends InstrumentationBase<XhrInstrumentationConfig> {
|
|
5
5
|
private _isEnabled;
|
|
6
6
|
private _isXhrPatched;
|
|
7
7
|
private _xhrSpanMap;
|
|
@@ -35,5 +35,4 @@ declare class XhrInstrumentation extends InstrumentationBase<XhrInstrumentationC
|
|
|
35
35
|
private _addHeaders;
|
|
36
36
|
}
|
|
37
37
|
//#endregion
|
|
38
|
-
export { XhrInstrumentation };
|
|
39
38
|
//# sourceMappingURL=instrumentation.d.ts.map
|
|
@@ -1 +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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"}
|
|
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 declare private _isEnabled: boolean;\n declare private _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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"}
|
package/dist/xhr/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Span } from "@opentelemetry/api";
|
|
|
2
2
|
import { InstrumentationConfig } from "@opentelemetry/instrumentation";
|
|
3
3
|
//#region src/xhr/types.d.ts
|
|
4
4
|
type XhrCustomAttributeFunction = (span: Span, xhr: XMLHttpRequest) => void;
|
|
5
|
-
interface XhrInstrumentationConfig extends InstrumentationConfig {
|
|
5
|
+
export interface XhrInstrumentationConfig extends InstrumentationConfig {
|
|
6
6
|
/** URLs which should include trace headers when origin doesn't match */
|
|
7
7
|
propagateTraceHeaderCorsUrls?: Array<string | RegExp>;
|
|
8
8
|
/**
|
|
@@ -19,5 +19,4 @@ interface XhrInstrumentationConfig extends InstrumentationConfig {
|
|
|
19
19
|
sanitizeUrl?: (url: string) => string;
|
|
20
20
|
}
|
|
21
21
|
//#endregion
|
|
22
|
-
export { XhrInstrumentationConfig };
|
|
23
22
|
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentelemetry/browser-instrumentation",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "OpenTelemetry browser instrumentations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opentelemetry",
|
|
@@ -50,27 +50,33 @@
|
|
|
50
50
|
"build": "tsdown",
|
|
51
51
|
"watch": "tsdown --watch --no-clean",
|
|
52
52
|
"check-types": "tsc",
|
|
53
|
+
"msw:init": "msw init public --save",
|
|
53
54
|
"test": "vitest run",
|
|
54
55
|
"test:watch": "vitest",
|
|
55
56
|
"test:coverage": "vitest --coverage"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
|
-
"@opentelemetry/api-logs": "^0.
|
|
59
|
-
"@opentelemetry/instrumentation": "^0.
|
|
59
|
+
"@opentelemetry/api-logs": "^0.222.0",
|
|
60
|
+
"@opentelemetry/instrumentation": "^0.222.0",
|
|
60
61
|
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
61
|
-
"web-vitals": "^6.
|
|
62
|
+
"web-vitals": "^6.2.1"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|
|
64
65
|
"@opentelemetry/api": "^1.9.1"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
|
-
"@opentelemetry/core": "2.
|
|
68
|
-
"@opentelemetry/propagator-b3": "2.
|
|
69
|
-
"@opentelemetry/sdk-logs": "0.
|
|
70
|
-
"@opentelemetry/sdk-trace-base": "2.
|
|
68
|
+
"@opentelemetry/core": "2.11.0",
|
|
69
|
+
"@opentelemetry/propagator-b3": "2.11.0",
|
|
70
|
+
"@opentelemetry/sdk-logs": "0.222.0",
|
|
71
|
+
"@opentelemetry/sdk-trace-base": "2.11.0",
|
|
71
72
|
"msw": "2.15.0"
|
|
72
73
|
},
|
|
73
74
|
"publishConfig": {
|
|
74
75
|
"access": "public"
|
|
76
|
+
},
|
|
77
|
+
"msw": {
|
|
78
|
+
"workerDirectory": [
|
|
79
|
+
"public"
|
|
80
|
+
]
|
|
75
81
|
}
|
|
76
82
|
}
|