@opentelemetry/browser-instrumentation 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -194,10 +194,24 @@ By default the instrumentation captures `click` events. You can configure which
194
194
 
195
195
  ```typescript
196
196
  new UserActionInstrumentation({
197
- autoCapturedActions: [], // default is ['click']
197
+ // Array of actions to automatically capture. Default: ['click'].
198
+ autoCapturedActions: [],
199
+
200
+ // Mutate the log record before it is emitted (e.g. attach custom attributes).
201
+ applyCustomLogRecordData: (logRecord) => {
202
+ logRecord.attributes = {
203
+ ...logRecord.attributes,
204
+ 'app.user.role': 'admin',
205
+ };
206
+ },
198
207
  });
199
208
  ```
200
209
 
210
+ | Option | Type | Default | Description |
211
+ |--------|------|---------|-------------|
212
+ | `autoCapturedActions` | `AutoCapturedUserAction[]` | `['click']` | Array of actions to automatically capture. |
213
+ | `applyCustomLogRecordData` | `(logRecord: LogRecord) => void` | — | Hook to modify log records before they are emitted. Errors thrown from this hook are caught and logged via the instrumentation diag logger. |
214
+
201
215
  #### Additional Attributes
202
216
 
203
217
  Data attributes with the prefix `data-otel-` on the target element will be added as additional attributes to the generated log record. For example:
@@ -1,6 +1,5 @@
1
1
  import { ConsoleInstrumentationConfig } from "./types.js";
2
2
  import { InstrumentationBase } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/console/instrumentation.d.ts
5
4
  /**
6
5
  * OpenTelemetry instrumentation that captures console calls and emits them as OpenTelemetry logs.
@@ -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 declare _isPatched: boolean;\n private declare _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,EACA,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,EAAE,SAAS,MAAM,GAChD;KACA,MAAM,aAAa,QAAQ,OAAO;KAClC,MAAM,OAAO,gBAAgB,sBAAsB,EAAE,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"}
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 declare _isPatched: boolean;\n private declare _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"}
@@ -1,5 +1,4 @@
1
1
  import { InstrumentationConfig } from "@opentelemetry/instrumentation";
2
-
3
2
  //#region src/console/types.d.ts
4
3
  /**
5
4
  * Console methods that can be instrumented.
@@ -1,6 +1,5 @@
1
1
  import { ErrorsInstrumentationConfig } from "./types.js";
2
2
  import { InstrumentationBase } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/errors/instrumentation.d.ts
5
4
  declare class ErrorsInstrumentation extends InstrumentationBase<ErrorsInstrumentationConfig> {
6
5
  private _isEnabled;
@@ -28,25 +28,35 @@ var ErrorsInstrumentation = class extends InstrumentationBase {
28
28
  }
29
29
  }
30
30
  _onError(event) {
31
- const error = "reason" in event ? event.reason : event.error;
32
- if (error == null) return;
33
- let errorAttributes;
34
- if (typeof error === "string") errorAttributes = { [ATTR_EXCEPTION_MESSAGE]: error };
35
- else errorAttributes = {
36
- [ATTR_EXCEPTION_TYPE]: error.name,
37
- [ATTR_EXCEPTION_MESSAGE]: error.message,
38
- [ATTR_EXCEPTION_STACKTRACE]: error.stack
39
- };
40
- const customAttributes = this._applyCustomAttributes(error);
41
- const logRecord = {
42
- eventName: EXCEPTION_EVENT_NAME,
43
- severityNumber: SeverityNumber.ERROR,
44
- attributes: {
45
- ...errorAttributes,
46
- ...customAttributes
47
- }
48
- };
49
- this.logger.emit(logRecord);
31
+ const isRejection = "reason" in event;
32
+ let error = isRejection ? event.reason : event.error;
33
+ if (error == null && !isRejection && typeof event.message === "string" && event.message.length > 0) error = event.message;
34
+ if (error == null) {
35
+ this._diag.debug(isRejection ? "ignored unhandledrejection event with no reason" : "ignored error event with no error and no message", error);
36
+ return;
37
+ }
38
+ const capturedError = error;
39
+ safeExecuteInTheMiddle(() => {
40
+ let errorAttributes;
41
+ if (typeof capturedError === "string") errorAttributes = { [ATTR_EXCEPTION_MESSAGE]: capturedError };
42
+ else errorAttributes = {
43
+ [ATTR_EXCEPTION_TYPE]: capturedError.name,
44
+ [ATTR_EXCEPTION_MESSAGE]: capturedError.message,
45
+ [ATTR_EXCEPTION_STACKTRACE]: capturedError.stack
46
+ };
47
+ const customAttributes = this._applyCustomAttributes(capturedError);
48
+ const logRecord = {
49
+ eventName: EXCEPTION_EVENT_NAME,
50
+ severityNumber: SeverityNumber.ERROR,
51
+ attributes: {
52
+ ...errorAttributes,
53
+ ...customAttributes
54
+ }
55
+ };
56
+ this.logger.emit(logRecord);
57
+ }, (err) => {
58
+ if (err) this._diag.error("failed to record exception", err);
59
+ }, true);
50
60
  }
51
61
  _applyCustomAttributes(error) {
52
62
  const hook = this.getConfig().applyCustomAttributes;
@@ -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 declare _isEnabled: boolean;\n private declare _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 error: Error | string | null | undefined =\n 'reason' in event ? event.reason : event.error;\n\n if (error == null) {\n return;\n }\n\n let errorAttributes: AnyValueMap;\n if (typeof error === 'string') {\n errorAttributes = { [ATTR_EXCEPTION_MESSAGE]: error };\n } else {\n errorAttributes = {\n [ATTR_EXCEPTION_TYPE]: error.name,\n [ATTR_EXCEPTION_MESSAGE]: error.message,\n [ATTR_EXCEPTION_STACKTRACE]: error.stack,\n };\n }\n\n const customAttributes = this._applyCustomAttributes(error);\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\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,QACJ,YAAY,QAAQ,MAAM,SAAS,MAAM;EAE3C,IAAI,SAAS,MACX;EAGF,IAAI;EACJ,IAAI,OAAO,UAAU,UACnB,kBAAkB,GAAG,yBAAyB,MAAM;OAEpD,kBAAkB;IACf,sBAAsB,MAAM;IAC5B,yBAAyB,MAAM;IAC/B,4BAA4B,MAAM;EACrC;EAGF,MAAM,mBAAmB,KAAK,uBAAuB,KAAK;EAE1D,MAAM,YAAuB;GAC3B,WAAW;GACX,gBAAgB,eAAe;GAC/B,YAAY;IAAE,GAAG;IAAiB,GAAG;GAAiB;EACxD;EAEA,KAAK,OAAO,KAAK,SAAS;CAC5B;CAEA,uBAA+B,OAAmC;EAChE,MAAM,OAAO,KAAK,UAAU,EAAE;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"}
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 declare _isEnabled: boolean;\n private declare _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"}
@@ -1,6 +1,5 @@
1
1
  import { Attributes } from "@opentelemetry/api";
2
2
  import { InstrumentationConfig } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/errors/types.d.ts
5
4
  type ApplyCustomAttributesFunction = (error: Error | string) => Attributes;
6
5
  /**
@@ -1,6 +1,5 @@
1
1
  import { NavigationInstrumentationConfig } from "./types.js";
2
2
  import { InstrumentationBase } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/navigation/instrumentation.d.ts
5
4
  declare class NavigationInstrumentation extends InstrumentationBase<NavigationInstrumentationConfig> {
6
5
  private _isEnabled;
@@ -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 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,6 +1,5 @@
1
1
  import { LogRecord } from "@opentelemetry/api-logs";
2
2
  import { InstrumentationConfig } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/navigation/types.d.ts
5
4
  type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
6
5
  type SanitizeUrlFunction = (url: string) => string;
@@ -17,5 +16,5 @@ interface NavigationInstrumentationConfig extends InstrumentationConfig {
17
16
  sanitizeUrl?: SanitizeUrlFunction;
18
17
  }
19
18
  //#endregion
20
- export { NavigationInstrumentationConfig, NavigationType };
19
+ export { ApplyCustomLogRecordDataFunction, NavigationInstrumentationConfig, NavigationType, SanitizeUrlFunction };
21
20
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../src/navigation/utils.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * Determines if navigation between two URLs represents a hash change.\n * A hash change is true if the URLs are the same except for the hash part,\n * AND the hash is being added or changed (not removed).\n *\n * @param fromUrl - The source URL\n * @param toUrl - The destination URL\n * @returns true if this represents a hash change navigation\n */\nexport function isHashChange(fromUrl: string, toUrl: string): boolean {\n try {\n const a = new URL(fromUrl, window.location.origin);\n const b = new URL(toUrl, window.location.origin);\n const sameBase =\n a.origin === b.origin &&\n a.pathname === b.pathname &&\n a.search === b.search;\n const fromHasHash = a.hash !== '';\n const toHasHash = b.hash !== '';\n const hashesAreDifferent = a.hash !== b.hash;\n\n return (\n sameBase &&\n hashesAreDifferent &&\n ((fromHasHash && toHasHash) || (!fromHasHash && toHasHash))\n );\n } catch {\n const fromBase = fromUrl.split('#')[0];\n const toBase = toUrl.split('#')[0];\n const fromHash = fromUrl.split('#')[1] ?? '';\n const toHash = toUrl.split('#')[1] ?? '';\n\n const sameBase = fromBase === toBase;\n const hashesAreDifferent = fromHash !== toHash;\n const notRemovingHash = toHash !== '';\n\n return sameBase && hashesAreDifferent && notRemovingHash;\n }\n}\n"],"mappings":";;;;;;;;;;AAcA,SAAgB,aAAa,SAAiB,OAAwB;CACpE,IAAI;EACF,MAAM,IAAI,IAAI,IAAI,SAAS,OAAO,SAAS,MAAM;EACjD,MAAM,IAAI,IAAI,IAAI,OAAO,OAAO,SAAS,MAAM;EAC/C,MAAM,WACJ,EAAE,WAAW,EAAE,UACf,EAAE,aAAa,EAAE,YACjB,EAAE,WAAW,EAAE;EACjB,MAAM,cAAc,EAAE,SAAS;EAC/B,MAAM,YAAY,EAAE,SAAS;EAC7B,MAAM,qBAAqB,EAAE,SAAS,EAAE;EAExC,OACE,YACA,uBACE,eAAe,aAAe,CAAC,eAAe;CAEpD,QAAQ;EACN,MAAM,WAAW,QAAQ,MAAM,GAAG,EAAE;EACpC,MAAM,SAAS,MAAM,MAAM,GAAG,EAAE;EAChC,MAAM,WAAW,QAAQ,MAAM,GAAG,EAAE,MAAM;EAC1C,MAAM,SAAS,MAAM,MAAM,GAAG,EAAE,MAAM;EAMtC,OAJiB,aAAa,UACH,aAAa,UAChB,WAAW;CAGrC;AACF"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../src/navigation/utils.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * Determines if navigation between two URLs represents a hash change.\n * A hash change is true if the URLs are the same except for the hash part,\n * AND the hash is being added or changed (not removed).\n *\n * @param fromUrl - The source URL\n * @param toUrl - The destination URL\n * @returns true if this represents a hash change navigation\n */\nexport function isHashChange(fromUrl: string, toUrl: string): boolean {\n try {\n const a = new URL(fromUrl, window.location.origin);\n const b = new URL(toUrl, window.location.origin);\n const sameBase =\n a.origin === b.origin &&\n a.pathname === b.pathname &&\n a.search === b.search;\n const fromHasHash = a.hash !== '';\n const toHasHash = b.hash !== '';\n const hashesAreDifferent = a.hash !== b.hash;\n\n return (\n sameBase &&\n hashesAreDifferent &&\n ((fromHasHash && toHasHash) || (!fromHasHash && toHasHash))\n );\n } catch {\n const fromBase = fromUrl.split('#')[0];\n const toBase = toUrl.split('#')[0];\n const fromHash = fromUrl.split('#')[1] ?? '';\n const toHash = toUrl.split('#')[1] ?? '';\n\n const sameBase = fromBase === toBase;\n const hashesAreDifferent = fromHash !== toHash;\n const notRemovingHash = toHash !== '';\n\n return sameBase && hashesAreDifferent && notRemovingHash;\n }\n}\n"],"mappings":";;;;;;;;;;AAcA,SAAgB,aAAa,SAAiB,OAAwB;CACpE,IAAI;EACF,MAAM,IAAI,IAAI,IAAI,SAAS,OAAO,SAAS,MAAM;EACjD,MAAM,IAAI,IAAI,IAAI,OAAO,OAAO,SAAS,MAAM;EAC/C,MAAM,WACJ,EAAE,WAAW,EAAE,UACf,EAAE,aAAa,EAAE,YACjB,EAAE,WAAW,EAAE;EACjB,MAAM,cAAc,EAAE,SAAS;EAC/B,MAAM,YAAY,EAAE,SAAS;EAC7B,MAAM,qBAAqB,EAAE,SAAS,EAAE;EAExC,OACE,YACA,uBACE,eAAe,aAAe,CAAC,eAAe;CAEpD,QAAQ;EACN,MAAM,WAAW,QAAQ,MAAM,GAAG,CAAC,CAAC;EACpC,MAAM,SAAS,MAAM,MAAM,GAAG,CAAC,CAAC;EAChC,MAAM,WAAW,QAAQ,MAAM,GAAG,CAAC,CAAC,MAAM;EAC1C,MAAM,SAAS,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM;EAMtC,OAJiB,aAAa,UACH,aAAa,UAChB,WAAW;CAGrC;AACF"}
@@ -1,6 +1,5 @@
1
1
  import { NavigationTimingInstrumentationConfig } from "./types.js";
2
2
  import { InstrumentationBase } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/navigation-timing/instrumentation.d.ts
5
4
  /**
6
5
  * This class automatically instruments navigation timing within the browser.
@@ -1,5 +1,4 @@
1
1
  import { InstrumentationConfig } from "@opentelemetry/instrumentation";
2
-
3
2
  //#region src/navigation-timing/types.d.ts
4
3
  /**
5
4
  * NavigationTimingInstrumentation Configuration
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.5.2";
2
+ var version = "0.7.0";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -1,6 +1,5 @@
1
1
  import { ResourceTimingInstrumentationConfig } from "./types.js";
2
2
  import { InstrumentationBase } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/resource-timing/instrumentation.d.ts
5
4
  /**
6
5
  * OpenTelemetry instrumentation for resource timing for browser applications.
@@ -1,5 +1,4 @@
1
1
  import { InstrumentationConfig } from "@opentelemetry/instrumentation";
2
-
3
2
  //#region src/resource-timing/types.d.ts
4
3
  /**
5
4
  * ResourceTimingInstrumentation Configuration
@@ -1,6 +1,5 @@
1
1
  import { UserActionInstrumentationConfig } from "./types.js";
2
2
  import { InstrumentationBase } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/user-action/instrumentation.d.ts
5
4
  /**
6
5
  * This class automatically instruments different User Actions within the browser.
@@ -11,6 +10,7 @@ declare class UserActionInstrumentation extends InstrumentationBase<UserActionIn
11
10
  protected init(): never[];
12
11
  private _getMouseButtonFromMouseEvent;
13
12
  private onClick;
13
+ private _applyCustomLogRecordData;
14
14
  enable(): void;
15
15
  disable(): void;
16
16
  }
@@ -2,7 +2,7 @@ import { version } from "../package.js";
2
2
  import { getElementCSSSelector } from "../utils/getElementCSSSelector.js";
3
3
  import { ATTR_CSS_SELECTOR, ATTR_MOUSE_EVENT_BUTTON, ATTR_PAGE_X, ATTR_PAGE_Y, ATTR_TAGS, ATTR_TAG_NAME, CLICK_EVENT_NAME } from "./semconv.js";
4
4
  import { SeverityNumber } from "@opentelemetry/api-logs";
5
- import { InstrumentationBase } from "@opentelemetry/instrumentation";
5
+ import { InstrumentationBase, safeExecuteInTheMiddle } from "@opentelemetry/instrumentation";
6
6
  //#region src/user-action/instrumentation.ts
7
7
  const DEFAULT_AUTO_CAPTURED_ACTIONS = ["click"];
8
8
  const OTEL_ELEMENT_ATTRIBUTE_PREFIX = "data-otel-";
@@ -34,7 +34,7 @@ var UserActionInstrumentation = class extends InstrumentationBase {
34
34
  });
35
35
  const otelPrefixedAttributes = {};
36
36
  for (const attr of element.attributes) if (attr.name.startsWith(OTEL_ELEMENT_ATTRIBUTE_PREFIX)) otelPrefixedAttributes[attr.name.slice(10)] = attr.value;
37
- this.logger.emit({
37
+ const logRecord = {
38
38
  severityNumber: SeverityNumber.INFO,
39
39
  eventName: CLICK_EVENT_NAME,
40
40
  attributes: {
@@ -45,7 +45,15 @@ var UserActionInstrumentation = class extends InstrumentationBase {
45
45
  [ATTR_MOUSE_EVENT_BUTTON]: this._getMouseButtonFromMouseEvent(event),
46
46
  [ATTR_CSS_SELECTOR]: cssSelector
47
47
  }
48
- });
48
+ };
49
+ this._applyCustomLogRecordData(logRecord);
50
+ this.logger.emit(logRecord);
51
+ }
52
+ _applyCustomLogRecordData(logRecord) {
53
+ const applyCustomLogRecordData = this.getConfig().applyCustomLogRecordData;
54
+ if (applyCustomLogRecordData) safeExecuteInTheMiddle(() => applyCustomLogRecordData(logRecord), (error) => {
55
+ if (error) this._diag.error("applyCustomLogRecordData hook failed", error);
56
+ }, true);
49
57
  }
50
58
  enable() {
51
59
  const autoCapturedActions = this._config.autoCapturedActions ?? DEFAULT_AUTO_CAPTURED_ACTIONS;
@@ -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 */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } 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 declare _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 this.logger.emit({\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\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":";;;;;;AAwBA,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,KAAK,OAAO,KAAK;GACf,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,CAAC;CACH;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
+ {"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 declare _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,13 +1,16 @@
1
+ import { LogRecord } from "@opentelemetry/api-logs";
1
2
  import { InstrumentationConfig } from "@opentelemetry/instrumentation";
2
-
3
3
  //#region src/user-action/types.d.ts
4
4
  type AutoCapturedUserAction = 'click';
5
+ type ApplyCustomLogRecordDataFunction = (logRecord: LogRecord) => void;
5
6
  /**
6
7
  * UserActionInstrumentation Configuration
7
8
  */
8
9
  interface UserActionInstrumentationConfig extends InstrumentationConfig {
9
10
  autoCapturedActions?: AutoCapturedUserAction[];
11
+ /** Hook to modify log records before they are emitted. */
12
+ applyCustomLogRecordData?: ApplyCustomLogRecordDataFunction;
10
13
  }
11
14
  //#endregion
12
- export { UserActionInstrumentationConfig };
15
+ export { ApplyCustomLogRecordDataFunction, AutoCapturedUserAction, UserActionInstrumentationConfig };
13
16
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getElementCSSSelector.js","names":[],"sources":["../../src/utils/getElementCSSSelector.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\ntype GetElementCSSSelectorOptions = {\n /**\n * If true, the function will attempt to use element ID to create condensed CSS selector.\n */\n useIdForTargetElement?: boolean;\n /**\n * If true, the function will attempt to use element ID for all ancestor elements to create condensed CSS selector.\n */\n useIdForAncestors?: boolean;\n};\n\n/**\n * Generates the CSS selector of a given element in the DOM tree.\n *\n * @example #main > div:nth-child(2) > button.submit\n * @example #unique-id\n */\nexport const getElementCSSSelector = (\n element: Node,\n {\n useIdForTargetElement = false,\n useIdForAncestors = false,\n }: GetElementCSSSelectorOptions = {},\n): string => {\n // Handle document node\n if (element.nodeType === Node.DOCUMENT_NODE) {\n return '';\n }\n\n const htmlElement = element as HTMLElement;\n const nodeValue = getNodeSelector(\n htmlElement,\n useIdForTargetElement || useIdForAncestors,\n );\n\n // If optimized and found an ID selector, stop recursion early\n if (nodeValue.startsWith('#')) {\n return nodeValue;\n }\n\n const parent = htmlElement.parentElement;\n const parentSelector = parent\n ? getElementCSSSelector(parent, {\n useIdForAncestors,\n useIdForTargetElement: false,\n })\n : '';\n\n return parentSelector ? `${parentSelector} > ${nodeValue}` : nodeValue;\n};\n\nconst getNodeSelector = (element: Node, useElementId = false): string => {\n if (element.nodeType !== Node.ELEMENT_NODE) {\n return '';\n }\n\n const htmlElement = element as HTMLElement;\n const id = htmlElement.getAttribute('id');\n\n // Use ID if requested and it's unique\n if (useElementId && id) {\n // Check if ID is unique in the document\n const elementsWithSameId = htmlElement.ownerDocument.querySelectorAll(\n `#${CSS.escape(id)}`,\n );\n\n if (elementsWithSameId.length === 1) {\n return `#${CSS.escape(id)}`;\n }\n }\n\n let selector = getFullClassSelector(htmlElement);\n // Add nth-child if there are siblings with the same tag and classes\n const index = getNthChild(htmlElement);\n\n if (index > 0) {\n selector += `:nth-child(${index})`;\n }\n\n return selector;\n};\n\nconst getNthChild = (element: HTMLElement): number => {\n // parentElement is needed to access children\n if (!element.parentElement) {\n return 0;\n }\n\n const selector = getFullClassSelector(element);\n\n // Get all siblings that match the same selector\n const siblings = Array.from(element.parentElement.children).filter(\n (sibling) => getFullClassSelector(sibling) === selector,\n );\n\n // Only add nth-child if there are multiple matching siblings\n if (siblings.length > 1) {\n return siblings.indexOf(element) + 1;\n }\n\n return 0;\n};\n\nconst getFullClassSelector = (element: Element): string =>\n element.localName +\n (element.classList.length > 0\n ? Array.from(element.classList)\n .map((cls) => `.${CSS.escape(cls)}`)\n .join('')\n : '');\n"],"mappings":";;;;;;;AAsBA,MAAa,yBACX,SACA,EACE,wBAAwB,OACxB,oBAAoB,UACY,CAAC,MACxB;CAEX,IAAI,QAAQ,aAAa,KAAK,eAC5B,OAAO;CAGT,MAAM,cAAc;CACpB,MAAM,YAAY,gBAChB,aACA,yBAAyB,iBAC3B;CAGA,IAAI,UAAU,WAAW,GAAG,GAC1B,OAAO;CAGT,MAAM,SAAS,YAAY;CAC3B,MAAM,iBAAiB,SACnB,sBAAsB,QAAQ;EAC5B;EACA,uBAAuB;CACzB,CAAC,IACD;CAEJ,OAAO,iBAAiB,GAAG,eAAe,KAAK,cAAc;AAC/D;AAEA,MAAM,mBAAmB,SAAe,eAAe,UAAkB;CACvE,IAAI,QAAQ,aAAa,KAAK,cAC5B,OAAO;CAGT,MAAM,cAAc;CACpB,MAAM,KAAK,YAAY,aAAa,IAAI;CAGxC,IAAI,gBAAgB;MAES,YAAY,cAAc,iBACnD,IAAI,IAAI,OAAO,EAAE,GAGE,EAAE,WAAW,GAChC,OAAO,IAAI,IAAI,OAAO,EAAE;CAAA;CAI5B,IAAI,WAAW,qBAAqB,WAAW;CAE/C,MAAM,QAAQ,YAAY,WAAW;CAErC,IAAI,QAAQ,GACV,YAAY,cAAc,MAAM;CAGlC,OAAO;AACT;AAEA,MAAM,eAAe,YAAiC;CAEpD,IAAI,CAAC,QAAQ,eACX,OAAO;CAGT,MAAM,WAAW,qBAAqB,OAAO;CAG7C,MAAM,WAAW,MAAM,KAAK,QAAQ,cAAc,QAAQ,EAAE,QACzD,YAAY,qBAAqB,OAAO,MAAM,QACjD;CAGA,IAAI,SAAS,SAAS,GACpB,OAAO,SAAS,QAAQ,OAAO,IAAI;CAGrC,OAAO;AACT;AAEA,MAAM,wBAAwB,YAC5B,QAAQ,aACP,QAAQ,UAAU,SAAS,IACxB,MAAM,KAAK,QAAQ,SAAS,EACzB,KAAK,QAAQ,IAAI,IAAI,OAAO,GAAG,GAAG,EAClC,KAAK,EAAE,IACV"}
1
+ {"version":3,"file":"getElementCSSSelector.js","names":[],"sources":["../../src/utils/getElementCSSSelector.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\ntype GetElementCSSSelectorOptions = {\n /**\n * If true, the function will attempt to use element ID to create condensed CSS selector.\n */\n useIdForTargetElement?: boolean;\n /**\n * If true, the function will attempt to use element ID for all ancestor elements to create condensed CSS selector.\n */\n useIdForAncestors?: boolean;\n};\n\n/**\n * Generates the CSS selector of a given element in the DOM tree.\n *\n * @example #main > div:nth-child(2) > button.submit\n * @example #unique-id\n */\nexport const getElementCSSSelector = (\n element: Node,\n {\n useIdForTargetElement = false,\n useIdForAncestors = false,\n }: GetElementCSSSelectorOptions = {},\n): string => {\n // Handle document node\n if (element.nodeType === Node.DOCUMENT_NODE) {\n return '';\n }\n\n const htmlElement = element as HTMLElement;\n const nodeValue = getNodeSelector(\n htmlElement,\n useIdForTargetElement || useIdForAncestors,\n );\n\n // If optimized and found an ID selector, stop recursion early\n if (nodeValue.startsWith('#')) {\n return nodeValue;\n }\n\n const parent = htmlElement.parentElement;\n const parentSelector = parent\n ? getElementCSSSelector(parent, {\n useIdForAncestors,\n useIdForTargetElement: false,\n })\n : '';\n\n return parentSelector ? `${parentSelector} > ${nodeValue}` : nodeValue;\n};\n\nconst getNodeSelector = (element: Node, useElementId = false): string => {\n if (element.nodeType !== Node.ELEMENT_NODE) {\n return '';\n }\n\n const htmlElement = element as HTMLElement;\n const id = htmlElement.getAttribute('id');\n\n // Use ID if requested and it's unique\n if (useElementId && id) {\n // Check if ID is unique in the document\n const elementsWithSameId = htmlElement.ownerDocument.querySelectorAll(\n `#${CSS.escape(id)}`,\n );\n\n if (elementsWithSameId.length === 1) {\n return `#${CSS.escape(id)}`;\n }\n }\n\n let selector = getFullClassSelector(htmlElement);\n // Add nth-child if there are siblings with the same tag and classes\n const index = getNthChild(htmlElement);\n\n if (index > 0) {\n selector += `:nth-child(${index})`;\n }\n\n return selector;\n};\n\nconst getNthChild = (element: HTMLElement): number => {\n // parentElement is needed to access children\n if (!element.parentElement) {\n return 0;\n }\n\n const selector = getFullClassSelector(element);\n\n // Get all siblings that match the same selector\n const siblings = Array.from(element.parentElement.children).filter(\n (sibling) => getFullClassSelector(sibling) === selector,\n );\n\n // Only add nth-child if there are multiple matching siblings\n if (siblings.length > 1) {\n return siblings.indexOf(element) + 1;\n }\n\n return 0;\n};\n\nconst getFullClassSelector = (element: Element): string =>\n element.localName +\n (element.classList.length > 0\n ? Array.from(element.classList)\n .map((cls) => `.${CSS.escape(cls)}`)\n .join('')\n : '');\n"],"mappings":";;;;;;;AAsBA,MAAa,yBACX,SACA,EACE,wBAAwB,OACxB,oBAAoB,UACY,CAAC,MACxB;CAEX,IAAI,QAAQ,aAAa,KAAK,eAC5B,OAAO;CAGT,MAAM,cAAc;CACpB,MAAM,YAAY,gBAChB,aACA,yBAAyB,iBAC3B;CAGA,IAAI,UAAU,WAAW,GAAG,GAC1B,OAAO;CAGT,MAAM,SAAS,YAAY;CAC3B,MAAM,iBAAiB,SACnB,sBAAsB,QAAQ;EAC5B;EACA,uBAAuB;CACzB,CAAC,IACD;CAEJ,OAAO,iBAAiB,GAAG,eAAe,KAAK,cAAc;AAC/D;AAEA,MAAM,mBAAmB,SAAe,eAAe,UAAkB;CACvE,IAAI,QAAQ,aAAa,KAAK,cAC5B,OAAO;CAGT,MAAM,cAAc;CACpB,MAAM,KAAK,YAAY,aAAa,IAAI;CAGxC,IAAI,gBAAgB;MAES,YAAY,cAAc,iBACnD,IAAI,IAAI,OAAO,EAAE,GAGE,CAAC,CAAC,WAAW,GAChC,OAAO,IAAI,IAAI,OAAO,EAAE;CAAA;CAI5B,IAAI,WAAW,qBAAqB,WAAW;CAE/C,MAAM,QAAQ,YAAY,WAAW;CAErC,IAAI,QAAQ,GACV,YAAY,cAAc,MAAM;CAGlC,OAAO;AACT;AAEA,MAAM,eAAe,YAAiC;CAEpD,IAAI,CAAC,QAAQ,eACX,OAAO;CAGT,MAAM,WAAW,qBAAqB,OAAO;CAG7C,MAAM,WAAW,MAAM,KAAK,QAAQ,cAAc,QAAQ,CAAC,CAAC,QACzD,YAAY,qBAAqB,OAAO,MAAM,QACjD;CAGA,IAAI,SAAS,SAAS,GACpB,OAAO,SAAS,QAAQ,OAAO,IAAI;CAGrC,OAAO;AACT;AAEA,MAAM,wBAAwB,YAC5B,QAAQ,aACP,QAAQ,UAAU,SAAS,IACxB,MAAM,KAAK,QAAQ,SAAS,CAAC,CAC1B,KAAK,QAAQ,IAAI,IAAI,OAAO,GAAG,GAAG,CAAC,CACnC,KAAK,EAAE,IACV"}
@@ -1,6 +1,5 @@
1
1
  import { WebVitalsInstrumentationConfig } from "./types.js";
2
2
  import { InstrumentationBase } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/web-vitals/instrumentation.d.ts
5
4
  /**
6
5
  * Instrumentation for Core Web Vitals using the `web-vitals` library.
@@ -1,6 +1,5 @@
1
1
  import { LogRecord } from "@opentelemetry/api-logs";
2
2
  import { InstrumentationConfig } from "@opentelemetry/instrumentation";
3
-
4
3
  //#region src/web-vitals/types.d.ts
5
4
  /**
6
5
  * WebVitalsInstrumentation Configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentelemetry/browser-instrumentation",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "description": "OpenTelemetry browser instrumentations.",
5
5
  "keywords": [
6
6
  "opentelemetry",
@@ -38,6 +38,7 @@
38
38
  "./experimental/user-action": "./dist/user-action/index.js",
39
39
  "./experimental/web-vitals": "./dist/web-vitals/index.js"
40
40
  },
41
+ "sideEffects": false,
41
42
  "files": [
42
43
  "dist"
43
44
  ],
@@ -50,17 +51,17 @@
50
51
  "test:coverage": "vitest --coverage"
51
52
  },
52
53
  "dependencies": {
53
- "@opentelemetry/api-logs": "^0.218.0",
54
- "@opentelemetry/core": "^2.7.1",
55
- "@opentelemetry/instrumentation": "^0.218.0",
56
- "@opentelemetry/semantic-conventions": "^1.41.1",
57
- "web-vitals": "^5.2.0"
54
+ "@opentelemetry/api-logs": "^0.221.0",
55
+ "@opentelemetry/core": "^2.10.0",
56
+ "@opentelemetry/instrumentation": "^0.221.0",
57
+ "@opentelemetry/semantic-conventions": "^1.43.0",
58
+ "web-vitals": "^5.3.0"
58
59
  },
59
60
  "peerDependencies": {
60
61
  "@opentelemetry/api": "^1.9.1"
61
62
  },
62
63
  "devDependencies": {
63
- "@opentelemetry/sdk-logs": "0.218.0"
64
+ "@opentelemetry/sdk-logs": "0.221.0"
64
65
  },
65
66
  "publishConfig": {
66
67
  "access": "public"