@opentelemetry/browser-instrumentation 0.1.0 → 0.3.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.
@@ -0,0 +1,35 @@
1
+ //#region src/resource-timing/idle-callback-shim.ts
2
+ const IDLE_DEADLINE_MS = 50;
3
+ const supportsIdleCallback = typeof window.requestIdleCallback === "function";
4
+ /**
5
+ * Schedules a callback during idle time, using the native requestIdleCallback
6
+ * when available. Falls back to setTimeout with a synthetic IdleDeadline that
7
+ * reports ~50ms of available time (per the W3C spec recommendation).
8
+ */
9
+ function requestIdleCallbackShim(callback, options) {
10
+ if (supportsIdleCallback) return {
11
+ id: window.requestIdleCallback(callback, options),
12
+ native: true
13
+ };
14
+ return {
15
+ id: window.setTimeout(() => {
16
+ const start = performance.now();
17
+ callback({
18
+ didTimeout: false,
19
+ timeRemaining: () => Math.max(0, IDLE_DEADLINE_MS - (performance.now() - start))
20
+ });
21
+ }, 1),
22
+ native: false
23
+ };
24
+ }
25
+ /**
26
+ * Cancels a previously scheduled idle callback.
27
+ */
28
+ function cancelIdleCallbackShim(handle) {
29
+ if (handle.native) window.cancelIdleCallback(handle.id);
30
+ else clearTimeout(handle.id);
31
+ }
32
+ //#endregion
33
+ export { cancelIdleCallbackShim, requestIdleCallbackShim };
34
+
35
+ //# sourceMappingURL=idle-callback-shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idle-callback-shim.js","names":[],"sources":["../../src/resource-timing/idle-callback-shim.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nconst IDLE_DEADLINE_MS = 50;\n\n// requestIdleCallback is not yet Baseline (not supported in Safari).\n// Feature-detect here so we can shim it below for unsupported browsers.\n// eslint-disable-next-line baseline-js/use-baseline\nconst supportsIdleCallback = typeof window.requestIdleCallback === 'function';\n\nexport interface IdleCallbackHandle {\n id: number;\n native: boolean;\n}\n\n/**\n * Schedules a callback during idle time, using the native requestIdleCallback\n * when available. Falls back to setTimeout with a synthetic IdleDeadline that\n * reports ~50ms of available time (per the W3C spec recommendation).\n */\nexport function requestIdleCallbackShim(\n callback: IdleRequestCallback,\n options?: IdleRequestOptions,\n): IdleCallbackHandle {\n if (supportsIdleCallback) {\n // requestIdleCallback is not yet Baseline (not supported in Safari).\n // eslint-disable-next-line baseline-js/use-baseline\n const id = window.requestIdleCallback(callback, options);\n return { id, native: true };\n }\n\n const id = window.setTimeout(() => {\n const start = performance.now();\n callback({\n didTimeout: false,\n timeRemaining: () =>\n Math.max(0, IDLE_DEADLINE_MS - (performance.now() - start)),\n });\n }, 1);\n return { id, native: false };\n}\n\n/**\n * Cancels a previously scheduled idle callback.\n */\nexport function cancelIdleCallbackShim(handle: IdleCallbackHandle): void {\n if (handle.native) {\n // eslint-disable-next-line baseline-js/use-baseline\n window.cancelIdleCallback(handle.id);\n } else {\n clearTimeout(handle.id);\n }\n}\n"],"mappings":";AAKA,MAAM,mBAAmB;AAKzB,MAAM,uBAAuB,OAAO,OAAO,wBAAwB;;;;;;AAYnE,SAAgB,wBACd,UACA,SACoB;AACpB,KAAI,qBAIF,QAAO;EAAE,IADE,OAAO,oBAAoB,UAAU,QACrC;EAAE,QAAQ;EAAM;AAW7B,QAAO;EAAE,IARE,OAAO,iBAAiB;GACjC,MAAM,QAAQ,YAAY,KAAK;AAC/B,YAAS;IACP,YAAY;IACZ,qBACE,KAAK,IAAI,GAAG,oBAAoB,YAAY,KAAK,GAAG,OAAO;IAC9D,CAAC;KACD,EACQ;EAAE,QAAQ;EAAO;;;;;AAM9B,SAAgB,uBAAuB,QAAkC;AACvE,KAAI,OAAO,OAET,QAAO,mBAAmB,OAAO,GAAG;KAEpC,cAAa,OAAO,GAAG"}
@@ -0,0 +1,3 @@
1
+ import { ResourceTimingInstrumentationConfig } from "./types.js";
2
+ import { ResourceTimingInstrumentation } from "./instrumentation.js";
3
+ export { ResourceTimingInstrumentation, type ResourceTimingInstrumentationConfig };
@@ -0,0 +1,2 @@
1
+ import { ResourceTimingInstrumentation } from "./instrumentation.js";
2
+ export { ResourceTimingInstrumentation };
@@ -0,0 +1,33 @@
1
+ import { ResourceTimingInstrumentationConfig } from "./types.js";
2
+ import { InstrumentationBase } from "@opentelemetry/instrumentation";
3
+
4
+ //#region src/resource-timing/instrumentation.d.ts
5
+ /**
6
+ * OpenTelemetry instrumentation for resource timing for browser applications.
7
+ *
8
+ * This instrumentation captures resource timing data using PerformanceObserver
9
+ * and batches emissions to avoid overwhelming the main thread. It uses
10
+ * requestIdleCallback when available (with fallback for Safari) to ensure
11
+ * processing happens during idle periods.
12
+ */
13
+ declare class ResourceTimingInstrumentation extends InstrumentationBase<ResourceTimingInstrumentationConfig> {
14
+ private _observer?;
15
+ private _pendingEntries;
16
+ private _idleHandle?;
17
+ private _isEnabled;
18
+ private _loadHandler;
19
+ private _visibilityChangeHandler;
20
+ constructor(config?: ResourceTimingInstrumentationConfig);
21
+ protected init(): never[];
22
+ enable(): void;
23
+ disable(): void;
24
+ private _setupObserver;
25
+ private _scheduleProcessing;
26
+ private _processChunk;
27
+ private _emitResource;
28
+ private _flush;
29
+ private _cancelScheduledProcessing;
30
+ }
31
+ //#endregion
32
+ export { ResourceTimingInstrumentation };
33
+ //# sourceMappingURL=instrumentation.d.ts.map
@@ -0,0 +1,160 @@
1
+ import { version } from "../package.js";
2
+ import { cancelIdleCallbackShim, requestIdleCallbackShim } from "./idle-callback-shim.js";
3
+ import { ATTR_RESOURCE_CONNECT_END, ATTR_RESOURCE_CONNECT_START, ATTR_RESOURCE_DECODED_BODY_SIZE, ATTR_RESOURCE_DOMAIN_LOOKUP_END, ATTR_RESOURCE_DOMAIN_LOOKUP_START, ATTR_RESOURCE_DURATION, ATTR_RESOURCE_ENCODED_BODY_SIZE, ATTR_RESOURCE_FETCH_START, ATTR_RESOURCE_INITIATOR_TYPE, ATTR_RESOURCE_NEXT_HOP_PROTOCOL, ATTR_RESOURCE_REDIRECT_END, ATTR_RESOURCE_REDIRECT_START, ATTR_RESOURCE_RENDER_BLOCKING_STATUS, ATTR_RESOURCE_REQUEST_START, ATTR_RESOURCE_RESPONSE_END, ATTR_RESOURCE_RESPONSE_START, ATTR_RESOURCE_SECURE_CONNECTION_START, ATTR_RESOURCE_TRANSFER_SIZE, ATTR_RESOURCE_URL, ATTR_RESOURCE_WORKER_START, RESOURCE_TIMING_EVENT_NAME } from "./semconv.js";
4
+ import { SeverityNumber } from "@opentelemetry/api-logs";
5
+ import { InstrumentationBase } from "@opentelemetry/instrumentation";
6
+ //#region src/resource-timing/instrumentation.ts
7
+ const DEFAULT_BATCH_SIZE = 50;
8
+ const DEFAULT_FORCE_PROCESSING_AFTER = 1e3;
9
+ const DEFAULT_MAX_PROCESSING_TIME = 50;
10
+ const DEFAULT_MAX_QUEUE_SIZE = 1e3;
11
+ const MIN_BATCH_SIZE = 1;
12
+ const MIN_FORCE_PROCESSING_AFTER = 0;
13
+ const MIN_PROCESSING_TIME = 0;
14
+ const MIN_QUEUE_SIZE = 1;
15
+ /**
16
+ * OpenTelemetry instrumentation for resource timing for browser applications.
17
+ *
18
+ * This instrumentation captures resource timing data using PerformanceObserver
19
+ * and batches emissions to avoid overwhelming the main thread. It uses
20
+ * requestIdleCallback when available (with fallback for Safari) to ensure
21
+ * processing happens during idle periods.
22
+ */
23
+ var ResourceTimingInstrumentation = class extends InstrumentationBase {
24
+ _observer;
25
+ _pendingEntries = [];
26
+ _idleHandle;
27
+ constructor(config = {}) {
28
+ super("@opentelemetry/browser-instrumentation/resource-timing", version, config);
29
+ }
30
+ init() {
31
+ return [];
32
+ }
33
+ enable() {
34
+ if (this._isEnabled) return;
35
+ if (!("PerformanceObserver" in window)) {
36
+ this._diag.debug("PerformanceObserver is not supported, resource timings will not be collected");
37
+ return;
38
+ }
39
+ this._isEnabled = true;
40
+ if (document.readyState === "complete") this._setupObserver();
41
+ else {
42
+ this._loadHandler = () => this._setupObserver();
43
+ window.addEventListener("load", this._loadHandler, { once: true });
44
+ }
45
+ this._visibilityChangeHandler = () => {
46
+ if (document.hidden) this._flush();
47
+ };
48
+ document.addEventListener("visibilitychange", this._visibilityChangeHandler);
49
+ }
50
+ disable() {
51
+ this._isEnabled = false;
52
+ this._flush();
53
+ this._observer?.disconnect();
54
+ this._observer = void 0;
55
+ if (this._loadHandler) {
56
+ window.removeEventListener("load", this._loadHandler);
57
+ this._loadHandler = void 0;
58
+ }
59
+ if (this._visibilityChangeHandler) {
60
+ document.removeEventListener("visibilitychange", this._visibilityChangeHandler);
61
+ this._visibilityChangeHandler = void 0;
62
+ }
63
+ }
64
+ _setupObserver() {
65
+ if (!this._isEnabled) return;
66
+ try {
67
+ const observer = new PerformanceObserver((list) => {
68
+ if (!this._isEnabled) return;
69
+ const maxQueueSize = Math.max(this._config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, MIN_QUEUE_SIZE);
70
+ const entries = list.getEntries();
71
+ const initiatorTypes = this._config.initiatorTypes;
72
+ for (const entry of entries) {
73
+ if (initiatorTypes !== void 0 && !initiatorTypes.includes(entry.initiatorType)) continue;
74
+ if (this._pendingEntries.length >= maxQueueSize) this._flush();
75
+ this._pendingEntries.push(entry);
76
+ }
77
+ if (this._pendingEntries.length > 0) this._scheduleProcessing();
78
+ });
79
+ this._observer = observer;
80
+ observer.observe({
81
+ type: "resource",
82
+ buffered: true
83
+ });
84
+ } catch {
85
+ this._diag.warn("PerformanceObserver not supported, resource timings will not be collected");
86
+ }
87
+ }
88
+ _scheduleProcessing() {
89
+ if (this._idleHandle !== void 0) return;
90
+ const timeout = Math.max(this._config.forceProcessingAfter ?? DEFAULT_FORCE_PROCESSING_AFTER, MIN_FORCE_PROCESSING_AFTER);
91
+ this._idleHandle = requestIdleCallbackShim((deadline) => this._processChunk(deadline), { timeout });
92
+ }
93
+ _processChunk(deadline) {
94
+ this._idleHandle = void 0;
95
+ if (!this._isEnabled || this._pendingEntries.length === 0) return;
96
+ const maxTime = Math.max(this._config.maxProcessingTime ?? DEFAULT_MAX_PROCESSING_TIME, MIN_PROCESSING_TIME);
97
+ const batchSize = Math.max(this._config.batchSize ?? DEFAULT_BATCH_SIZE, MIN_BATCH_SIZE);
98
+ const startTime = performance.now();
99
+ let cursor = 0;
100
+ try {
101
+ for (let i = 0; i < batchSize; i++) {
102
+ const entry = this._pendingEntries[cursor];
103
+ if (entry === void 0) break;
104
+ if (performance.now() - startTime >= maxTime || deadline.timeRemaining() < 1) break;
105
+ cursor++;
106
+ this._emitResource(entry);
107
+ }
108
+ } finally {
109
+ this._pendingEntries.splice(0, cursor);
110
+ if (this._pendingEntries.length > 0) this._scheduleProcessing();
111
+ }
112
+ }
113
+ _emitResource(entry) {
114
+ try {
115
+ this.logger.emit({
116
+ eventName: RESOURCE_TIMING_EVENT_NAME,
117
+ severityNumber: SeverityNumber.INFO,
118
+ attributes: {
119
+ [ATTR_RESOURCE_URL]: entry.name,
120
+ [ATTR_RESOURCE_INITIATOR_TYPE]: entry.initiatorType,
121
+ [ATTR_RESOURCE_DURATION]: entry.duration,
122
+ [ATTR_RESOURCE_FETCH_START]: entry.fetchStart,
123
+ [ATTR_RESOURCE_DOMAIN_LOOKUP_START]: entry.domainLookupStart,
124
+ [ATTR_RESOURCE_DOMAIN_LOOKUP_END]: entry.domainLookupEnd,
125
+ [ATTR_RESOURCE_CONNECT_START]: entry.connectStart,
126
+ [ATTR_RESOURCE_CONNECT_END]: entry.connectEnd,
127
+ [ATTR_RESOURCE_SECURE_CONNECTION_START]: entry.secureConnectionStart,
128
+ [ATTR_RESOURCE_REQUEST_START]: entry.requestStart,
129
+ [ATTR_RESOURCE_RESPONSE_START]: entry.responseStart,
130
+ [ATTR_RESOURCE_RESPONSE_END]: entry.responseEnd,
131
+ [ATTR_RESOURCE_TRANSFER_SIZE]: entry.transferSize,
132
+ [ATTR_RESOURCE_ENCODED_BODY_SIZE]: entry.encodedBodySize,
133
+ [ATTR_RESOURCE_DECODED_BODY_SIZE]: entry.decodedBodySize,
134
+ [ATTR_RESOURCE_REDIRECT_START]: entry.redirectStart,
135
+ [ATTR_RESOURCE_REDIRECT_END]: entry.redirectEnd,
136
+ [ATTR_RESOURCE_WORKER_START]: entry.workerStart,
137
+ [ATTR_RESOURCE_NEXT_HOP_PROTOCOL]: entry.nextHopProtocol,
138
+ [ATTR_RESOURCE_RENDER_BLOCKING_STATUS]: entry.renderBlockingStatus
139
+ }
140
+ });
141
+ } catch (error) {
142
+ this._diag.error(`Failed to emit resource timing entry for "${entry.name}"`, error);
143
+ }
144
+ }
145
+ _flush() {
146
+ this._cancelScheduledProcessing();
147
+ for (const entry of this._pendingEntries) this._emitResource(entry);
148
+ this._pendingEntries = [];
149
+ }
150
+ _cancelScheduledProcessing() {
151
+ if (this._idleHandle !== void 0) {
152
+ cancelIdleCallbackShim(this._idleHandle);
153
+ this._idleHandle = void 0;
154
+ }
155
+ }
156
+ };
157
+ //#endregion
158
+ export { ResourceTimingInstrumentation };
159
+
160
+ //# sourceMappingURL=instrumentation.js.map
@@ -0,0 +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 { SeverityNumber } from '@opentelemetry/api-logs';\nimport { InstrumentationBase } from '@opentelemetry/instrumentation';\nimport { version } from '../../package.json' with { type: 'json' };\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 try {\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 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 observer.observe({ type: 'resource', buffered: true });\n } catch {\n this._diag.warn(\n 'PerformanceObserver not supported, resource timings will not be collected',\n );\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 this.logger.emit({\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 } 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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"}
@@ -0,0 +1,131 @@
1
+ //#region src/resource-timing/semconv.ts
2
+ /**
3
+ * Event name for resource timing
4
+ *
5
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
6
+ */
7
+ const RESOURCE_TIMING_EVENT_NAME = "browser.resource_timing";
8
+ /**
9
+ * The URL of the resource
10
+ *
11
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
12
+ */
13
+ const ATTR_RESOURCE_URL = "url.full";
14
+ /**
15
+ * The type of resource (script, stylesheet, img, xmlhttprequest, fetch, etc.)
16
+ *
17
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
18
+ */
19
+ const ATTR_RESOURCE_INITIATOR_TYPE = "browser.resource_timing.initiator_type";
20
+ /**
21
+ * Total duration of the resource load (in milliseconds)
22
+ *
23
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
24
+ */
25
+ const ATTR_RESOURCE_DURATION = "browser.resource_timing.duration";
26
+ /**
27
+ * Start time of the resource fetch (relative to navigation start)
28
+ *
29
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
30
+ */
31
+ const ATTR_RESOURCE_FETCH_START = "browser.resource_timing.fetch_start";
32
+ /**
33
+ * Domain lookup start time
34
+ *
35
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
36
+ */
37
+ const ATTR_RESOURCE_DOMAIN_LOOKUP_START = "browser.resource_timing.domain_lookup_start";
38
+ /**
39
+ * Domain lookup end time
40
+ *
41
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
42
+ */
43
+ const ATTR_RESOURCE_DOMAIN_LOOKUP_END = "browser.resource_timing.domain_lookup_end";
44
+ /**
45
+ * Connection start time
46
+ *
47
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
48
+ */
49
+ const ATTR_RESOURCE_CONNECT_START = "browser.resource_timing.connect_start";
50
+ /**
51
+ * Connection end time
52
+ *
53
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
54
+ */
55
+ const ATTR_RESOURCE_CONNECT_END = "browser.resource_timing.connect_end";
56
+ /**
57
+ * Secure connection start time (HTTPS)
58
+ *
59
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
60
+ */
61
+ const ATTR_RESOURCE_SECURE_CONNECTION_START = "browser.resource_timing.secure_connection_start";
62
+ /**
63
+ * Request start time
64
+ *
65
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
66
+ */
67
+ const ATTR_RESOURCE_REQUEST_START = "browser.resource_timing.request_start";
68
+ /**
69
+ * Response start time
70
+ *
71
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
72
+ */
73
+ const ATTR_RESOURCE_RESPONSE_START = "browser.resource_timing.response_start";
74
+ /**
75
+ * Response end time
76
+ *
77
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
78
+ */
79
+ const ATTR_RESOURCE_RESPONSE_END = "browser.resource_timing.response_end";
80
+ /**
81
+ * Transfer size in bytes (including headers)
82
+ *
83
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
84
+ */
85
+ const ATTR_RESOURCE_TRANSFER_SIZE = "browser.resource_timing.transfer_size";
86
+ /**
87
+ * Encoded body size in bytes (compressed)
88
+ *
89
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
90
+ */
91
+ const ATTR_RESOURCE_ENCODED_BODY_SIZE = "browser.resource_timing.encoded_body_size";
92
+ /**
93
+ * Decoded body size in bytes (uncompressed)
94
+ *
95
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
96
+ */
97
+ const ATTR_RESOURCE_DECODED_BODY_SIZE = "browser.resource_timing.decoded_body_size";
98
+ /**
99
+ * Redirect start time
100
+ *
101
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
102
+ */
103
+ const ATTR_RESOURCE_REDIRECT_START = "browser.resource_timing.redirect_start";
104
+ /**
105
+ * Redirect end time
106
+ *
107
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
108
+ */
109
+ const ATTR_RESOURCE_REDIRECT_END = "browser.resource_timing.redirect_end";
110
+ /**
111
+ * Worker start time (for Service Worker interception)
112
+ *
113
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
114
+ */
115
+ const ATTR_RESOURCE_WORKER_START = "browser.resource_timing.worker_start";
116
+ /**
117
+ * Next hop protocol (h2, h3, http/1.1, etc.)
118
+ *
119
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
120
+ */
121
+ const ATTR_RESOURCE_NEXT_HOP_PROTOCOL = "browser.resource_timing.next_hop_protocol";
122
+ /**
123
+ * Render blocking status (blocking, non-blocking)
124
+ *
125
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
126
+ */
127
+ const ATTR_RESOURCE_RENDER_BLOCKING_STATUS = "browser.resource_timing.render_blocking_status";
128
+ //#endregion
129
+ export { ATTR_RESOURCE_CONNECT_END, ATTR_RESOURCE_CONNECT_START, ATTR_RESOURCE_DECODED_BODY_SIZE, ATTR_RESOURCE_DOMAIN_LOOKUP_END, ATTR_RESOURCE_DOMAIN_LOOKUP_START, ATTR_RESOURCE_DURATION, ATTR_RESOURCE_ENCODED_BODY_SIZE, ATTR_RESOURCE_FETCH_START, ATTR_RESOURCE_INITIATOR_TYPE, ATTR_RESOURCE_NEXT_HOP_PROTOCOL, ATTR_RESOURCE_REDIRECT_END, ATTR_RESOURCE_REDIRECT_START, ATTR_RESOURCE_RENDER_BLOCKING_STATUS, ATTR_RESOURCE_REQUEST_START, ATTR_RESOURCE_RESPONSE_END, ATTR_RESOURCE_RESPONSE_START, ATTR_RESOURCE_SECURE_CONNECTION_START, ATTR_RESOURCE_TRANSFER_SIZE, ATTR_RESOURCE_URL, ATTR_RESOURCE_WORKER_START, RESOURCE_TIMING_EVENT_NAME };
130
+
131
+ //# sourceMappingURL=semconv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semconv.js","names":[],"sources":["../../src/resource-timing/semconv.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/*\n * This file contains a copy of unstable semantic convention definitions\n * used by this package.\n * @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv\n */\n\n/**\n * Event name for resource timing\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const RESOURCE_TIMING_EVENT_NAME = 'browser.resource_timing';\n\n// Resource timing attributes\n\n/**\n * The URL of the resource\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_URL = 'url.full';\n\n/**\n * The type of resource (script, stylesheet, img, xmlhttprequest, fetch, etc.)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_INITIATOR_TYPE =\n 'browser.resource_timing.initiator_type';\n\n/**\n * Total duration of the resource load (in milliseconds)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_DURATION = 'browser.resource_timing.duration';\n\n/**\n * Start time of the resource fetch (relative to navigation start)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_FETCH_START = 'browser.resource_timing.fetch_start';\n\n/**\n * Domain lookup start time\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_DOMAIN_LOOKUP_START =\n 'browser.resource_timing.domain_lookup_start';\n\n/**\n * Domain lookup end time\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_DOMAIN_LOOKUP_END =\n 'browser.resource_timing.domain_lookup_end';\n\n/**\n * Connection start time\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_CONNECT_START =\n 'browser.resource_timing.connect_start';\n\n/**\n * Connection end time\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_CONNECT_END = 'browser.resource_timing.connect_end';\n\n/**\n * Secure connection start time (HTTPS)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_SECURE_CONNECTION_START =\n 'browser.resource_timing.secure_connection_start';\n\n/**\n * Request start time\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_REQUEST_START =\n 'browser.resource_timing.request_start';\n\n/**\n * Response start time\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_RESPONSE_START =\n 'browser.resource_timing.response_start';\n\n/**\n * Response end time\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_RESPONSE_END =\n 'browser.resource_timing.response_end';\n\n/**\n * Transfer size in bytes (including headers)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_TRANSFER_SIZE =\n 'browser.resource_timing.transfer_size';\n\n/**\n * Encoded body size in bytes (compressed)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_ENCODED_BODY_SIZE =\n 'browser.resource_timing.encoded_body_size';\n\n/**\n * Decoded body size in bytes (uncompressed)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_DECODED_BODY_SIZE =\n 'browser.resource_timing.decoded_body_size';\n\n/**\n * Redirect start time\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_REDIRECT_START =\n 'browser.resource_timing.redirect_start';\n\n/**\n * Redirect end time\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_REDIRECT_END =\n 'browser.resource_timing.redirect_end';\n\n/**\n * Worker start time (for Service Worker interception)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_WORKER_START =\n 'browser.resource_timing.worker_start';\n\n/**\n * Next hop protocol (h2, h3, http/1.1, etc.)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_NEXT_HOP_PROTOCOL =\n 'browser.resource_timing.next_hop_protocol';\n\n/**\n * Render blocking status (blocking, non-blocking)\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_RESOURCE_RENDER_BLOCKING_STATUS =\n 'browser.resource_timing.render_blocking_status';\n"],"mappings":";;;;;;AAgBA,MAAa,6BAA6B;;;;;;AAS1C,MAAa,oBAAoB;;;;;;AAOjC,MAAa,+BACX;;;;;;AAOF,MAAa,yBAAyB;;;;;;AAOtC,MAAa,4BAA4B;;;;;;AAOzC,MAAa,oCACX;;;;;;AAOF,MAAa,kCACX;;;;;;AAOF,MAAa,8BACX;;;;;;AAOF,MAAa,4BAA4B;;;;;;AAOzC,MAAa,wCACX;;;;;;AAOF,MAAa,8BACX;;;;;;AAOF,MAAa,+BACX;;;;;;AAOF,MAAa,6BACX;;;;;;AAOF,MAAa,8BACX;;;;;;AAOF,MAAa,kCACX;;;;;;AAOF,MAAa,kCACX;;;;;;AAOF,MAAa,+BACX;;;;;;AAOF,MAAa,6BACX;;;;;;AAOF,MAAa,6BACX;;;;;;AAOF,MAAa,kCACX;;;;;;AAOF,MAAa,uCACX"}
@@ -0,0 +1,38 @@
1
+ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
2
+
3
+ //#region src/resource-timing/types.d.ts
4
+ /**
5
+ * ResourceTimingInstrumentation Configuration
6
+ */
7
+ interface ResourceTimingInstrumentationConfig extends InstrumentationConfig {
8
+ /**
9
+ * Number of resources to process per batch.
10
+ * Default: 50
11
+ */
12
+ batchSize?: number;
13
+ /**
14
+ * Maximum time in milliseconds to wait for an idle callback before forcing processing.
15
+ * Default: 1000
16
+ */
17
+ forceProcessingAfter?: number;
18
+ /**
19
+ * Maximum time in milliseconds to spend processing resources per idle callback.
20
+ * Default: 50
21
+ */
22
+ maxProcessingTime?: number;
23
+ /**
24
+ * Maximum number of resources to queue before forcing immediate flush.
25
+ * Default: 1000
26
+ */
27
+ maxQueueSize?: number;
28
+ /**
29
+ * Filter which resource types to capture by their initiator type
30
+ * (e.g. 'xmlhttprequest', 'fetch', 'script', 'link', 'img', 'css', etc.).
31
+ * When set, only entries whose initiatorType matches one of the listed values
32
+ * are captured. When unset, all resource entries are captured.
33
+ */
34
+ initiatorTypes?: string[];
35
+ }
36
+ //#endregion
37
+ export { ResourceTimingInstrumentationConfig };
38
+ //# sourceMappingURL=types.d.ts.map
@@ -1,2 +1,3 @@
1
+ import { UserActionInstrumentationConfig } from "./types.js";
1
2
  import { UserActionInstrumentation } from "./instrumentation.js";
2
- export { UserActionInstrumentation };
3
+ export { UserActionInstrumentation, type UserActionInstrumentationConfig };
@@ -1,4 +1,5 @@
1
- import { getElementCSSSelector } from "../web-utils/dist/getElementCSSSelector.js";
1
+ import { version } from "../package.js";
2
+ import { getElementCSSSelector } from "../utils/getElementCSSSelector.js";
2
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";
3
4
  import { SeverityNumber } from "@opentelemetry/api-logs";
4
5
  import { InstrumentationBase } from "@opentelemetry/instrumentation";
@@ -10,7 +11,7 @@ const OTEL_ELEMENT_ATTRIBUTE_PREFIX = "data-otel-";
10
11
  */
11
12
  var UserActionInstrumentation = class extends InstrumentationBase {
12
13
  constructor(config = {}) {
13
- super("@opentelemetry/instrumentation-user-action", "0.1.0", config);
14
+ super("@opentelemetry/browser-instrumentation/user-action", version, config);
14
15
  }
15
16
  init() {
16
17
  return [];
@@ -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 '@opentelemetry/web-utils';\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('@opentelemetry/instrumentation-user-action', '0.1.0', config);\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":";;;;;AAuBA,MAAM,gCAA0D,CAAC,QAAQ;AACzE,MAAM,gCAAgC;;;;AAKtC,IAAa,4BAAb,cAA+C,oBAAqD;CAGlG,YAAY,SAA0C,EAAE,EAAE;AACxD,QAAM,8CAA8C,SAAS,OAAO;;CAGtE,OAA0B;AACxB,SAAO,EAAE;;CAGX,8BAAsC,OAAgC;AACpE,UAAQ,MAAM,QAAd;GACE,KAAK,EACH,QAAO;GACT,KAAK,EACH,QAAO;GACT,KAAK,EACH,QAAO;GACT,QACE,QAAO;;;CAIb,QAAgB,OAAmB;EACjC,MAAM,UAAU,MAAM;AAEtB,MAAI,EAAE,mBAAmB,aACvB;AAGF,MAAI,QAAQ,aAAa,WAAW,CAClC;EAGF,MAAM,cAAc,sBAAsB,SAAS;GACjD,uBAAuB;GACvB,mBAAmB;GACpB,CAAC;EACF,MAAM,yBAAiD,EAAE;AAGzD,OAAK,MAAM,QAAQ,QAAQ,WACzB,KAAI,KAAK,KAAK,WAAW,8BAA8B,CACrD,wBACE,KAAK,KAAK,MAAM,GAAqC,IACnD,KAAK;AAIb,OAAK,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,MAAM;KACnE,oBAAoB;IACtB;GACF,CAAC;;CAGJ,SAAwB;EACtB,MAAM,sBACJ,KAAK,QAAQ,uBAAuB;AACtC,MAAI,CAAC,KAAK,gBACR,MAAK,kBAAkB,KAAK,QAAQ,KAAK,KAAK;AAGhD,MAAI,oBAAoB,SAAS,QAAQ,CACvC,UAAS,iBAAiB,SAAS,KAAK,iBAAiB,KAAK;;CAIlE,UAAyB;AACvB,MAAI,KAAK,gBACP,UAAS,oBAAoB,SAAS,KAAK,iBAAiB,KAAK"}
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 '#instrumentation-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,QAAQ;AACzE,MAAM,gCAAgC;;;;AAKtC,IAAa,4BAAb,cAA+C,oBAAqD;CAGlG,YAAY,SAA0C,EAAE,EAAE;AACxD,QACE,sDACA,SACA,OACD;;CAGH,OAA0B;AACxB,SAAO,EAAE;;CAGX,8BAAsC,OAAgC;AACpE,UAAQ,MAAM,QAAd;GACE,KAAK,EACH,QAAO;GACT,KAAK,EACH,QAAO;GACT,KAAK,EACH,QAAO;GACT,QACE,QAAO;;;CAIb,QAAgB,OAAmB;EACjC,MAAM,UAAU,MAAM;AAEtB,MAAI,EAAE,mBAAmB,aACvB;AAGF,MAAI,QAAQ,aAAa,WAAW,CAClC;EAGF,MAAM,cAAc,sBAAsB,SAAS;GACjD,uBAAuB;GACvB,mBAAmB;GACpB,CAAC;EACF,MAAM,yBAAiD,EAAE;AAGzD,OAAK,MAAM,QAAQ,QAAQ,WACzB,KAAI,KAAK,KAAK,WAAW,8BAA8B,CACrD,wBACE,KAAK,KAAK,MAAM,GAAqC,IACnD,KAAK;AAIb,OAAK,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,MAAM;KACnE,oBAAoB;IACtB;GACF,CAAC;;CAGJ,SAAwB;EACtB,MAAM,sBACJ,KAAK,QAAQ,uBAAuB;AACtC,MAAI,CAAC,KAAK,gBACR,MAAK,kBAAkB,KAAK,QAAQ,KAAK,KAAK;AAGhD,MAAI,oBAAoB,SAAS,QAAQ,CACvC,UAAS,iBAAiB,SAAS,KAAK,iBAAiB,KAAK;;CAIlE,UAAyB;AACvB,MAAI,KAAK,gBACP,UAAS,oBAAoB,SAAS,KAAK,iBAAiB,KAAK"}
@@ -1,4 +1,4 @@
1
- //#region ../web-utils/dist/getElementCSSSelector.js
1
+ //#region src/utils/getElementCSSSelector.ts
2
2
  /**
3
3
  * Generates the CSS selector of a given element in the DOM tree.
4
4
  *
@@ -0,0 +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,EAAE,KACzB;AAEX,KAAI,QAAQ,aAAa,KAAK,cAC5B,QAAO;CAGT,MAAM,cAAc;CACpB,MAAM,YAAY,gBAChB,aACA,yBAAyB,kBAC1B;AAGD,KAAI,UAAU,WAAW,IAAI,CAC3B,QAAO;CAGT,MAAM,SAAS,YAAY;CAC3B,MAAM,iBAAiB,SACnB,sBAAsB,QAAQ;EAC5B;EACA,uBAAuB;EACxB,CAAC,GACF;AAEJ,QAAO,iBAAiB,GAAG,eAAe,KAAK,cAAc;;AAG/D,MAAM,mBAAmB,SAAe,eAAe,UAAkB;AACvE,KAAI,QAAQ,aAAa,KAAK,aAC5B,QAAO;CAGT,MAAM,cAAc;CACpB,MAAM,KAAK,YAAY,aAAa,KAAK;AAGzC,KAAI,gBAAgB;MAES,YAAY,cAAc,iBACnD,IAAI,IAAI,OAAO,GAAG,GAGE,CAAC,WAAW,EAChC,QAAO,IAAI,IAAI,OAAO,GAAG;;CAI7B,IAAI,WAAW,qBAAqB,YAAY;CAEhD,MAAM,QAAQ,YAAY,YAAY;AAEtC,KAAI,QAAQ,EACV,aAAY,cAAc,MAAM;AAGlC,QAAO;;AAGT,MAAM,eAAe,YAAiC;AAEpD,KAAI,CAAC,QAAQ,cACX,QAAO;CAGT,MAAM,WAAW,qBAAqB,QAAQ;CAG9C,MAAM,WAAW,MAAM,KAAK,QAAQ,cAAc,SAAS,CAAC,QACzD,YAAY,qBAAqB,QAAQ,KAAK,SAChD;AAGD,KAAI,SAAS,SAAS,EACpB,QAAO,SAAS,QAAQ,QAAQ,GAAG;AAGrC,QAAO;;AAGT,MAAM,wBAAwB,YAC5B,QAAQ,aACP,QAAQ,UAAU,SAAS,IACxB,MAAM,KAAK,QAAQ,UAAU,CAC1B,KAAK,QAAQ,IAAI,IAAI,OAAO,IAAI,GAAG,CACnC,KAAK,GAAG,GACX"}
@@ -1,3 +1,4 @@
1
+ import { version } from "../package.js";
1
2
  import { ATTR_WEB_VITAL_DELTA, ATTR_WEB_VITAL_ID, ATTR_WEB_VITAL_NAME, ATTR_WEB_VITAL_NAVIGATION_TYPE, ATTR_WEB_VITAL_RATING, ATTR_WEB_VITAL_VALUE, WEB_VITAL_EVENT_NAME } from "./semconv.js";
2
3
  import { SeverityNumber } from "@opentelemetry/api-logs";
3
4
  import { InstrumentationBase, safeExecuteInTheMiddle } from "@opentelemetry/instrumentation";
@@ -15,7 +16,7 @@ var WebVitalsInstrumentation = class extends InstrumentationBase {
15
16
  _applyCustomLogRecordData;
16
17
  _includeRawAttribution;
17
18
  constructor(config = {}) {
18
- super("@opentelemetry/instrumentation-web-vitals", "0.1.0", config);
19
+ super("@opentelemetry/browser-instrumentation/web-vitals", version, config);
19
20
  this._applyCustomLogRecordData = config.applyCustomLogRecordData;
20
21
  this._includeRawAttribution = config.includeRawAttribution ?? false;
21
22
  }
@@ -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 {\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 declare _isEnabled: boolean;\n private declare _listenersRegistered: boolean;\n private _applyCustomLogRecordData?: (logRecord: LogRecord) => void;\n private _includeRawAttribution: boolean;\n\n constructor(config: WebVitalsInstrumentationConfig = {}) {\n super('@opentelemetry/instrumentation-web-vitals', '0.1.0', 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":";;;;;;;;;;;;;AAqCA,IAAa,2BAAb,cAA8C,oBAAoD;CAMhG;CACA;CAEA,YAAY,SAAyC,EAAE,EAAE;AACvD,QAAM,6CAA6C,SAAS,OAAO;AACnE,OAAK,4BAA4B,OAAO;AACxC,OAAK,yBAAyB,OAAO,yBAAyB;;CAGhE,OAA0B;AACxB,SAAO,EAAE;;;;;;CAOX,SAAwB;AACtB,MAAI,OAAO,wBAAwB,aAAa;AAC9C,QAAK,MAAM,MACT,sEACD;AACD;;AAGF,OAAK,aAAa;AAElB,MAAI,KAAK,sBAAsB;AAC7B,QAAK,MAAM,MAAM,kDAAkD;AACnE;;AAGF,OAAK,uBAAuB;AAC5B,OAAK,MAAM,MAAM,wBAAwB;AAGzC,SAAO,WAAW,KAAK,cAAc,OAAO,CAAC;AAC7C,SAAO,WAAW,KAAK,cAAc,OAAO,CAAC;AAC7C,SAAO,WAAW,KAAK,cAAc,OAAO,CAAC;AAC7C,SAAO,WAAW,KAAK,cAAc,OAAO,CAAC;AAC7C,UAAQ,WAAW,KAAK,cAAc,OAAO,CAAC;;;;;;CAOhD,UAAyB;AACvB,OAAK,aAAa;AAClB,OAAK,MAAM,MAAM,6CAA6C;;;;;;;CAQhE,uBACE,QACoB;AACpB,MAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;AACxB,OAAI,YAAY,qBAAqB,KAAA,EACnC,QAAO,YAAY;AAErB;;AAEF,MAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;AACxB,UAAO,YAAY;;AAGrB,SAAO,OAAO;;CAGhB,cAAsB,QAAqC;AACzD,MAAI,CAAC,KAAK,WACR;EAEF,MAAM,aAAyB;IAC5B,sBAAsB,OAAO,KAAK,aAAa;IAC/C,uBAAuB,OAAO;IAE9B,uBAAuB,OAAO;IAC9B,wBAAwB,OAAO;IAC/B,oBAAoB,OAAO;IAC3B,iCAAiC,OAAO;GAC1C;EAED,MAAM,YAAY,KAAK,uBAAuB,OAAO;EAErD,MAAM,YAAuB;GAC3B,WAAW;GACX,gBAAgB,eAAe;GAC/B;GACA,GAAI,KAAK,yBACL,EAAE,MAAM,KAAK,UAAU,OAAO,YAAY,EAAE,GAC5C,EAAE;GACN,GAAI,cAAc,KAAA,IAAY,EAAE,WAAW,GAAG,EAAE;GACjD;AAED,MAAI,KAAK,0BACP,8BACQ,KAAK,4BAA4B,UAAU,GAChD,UAAU;AACT,OAAI,MACF,MAAK,MAAM,MAAM,wCAAwC,MAAM;KAGnE,KACD;AAGH,OAAK,OAAO,KAAK,UAAU"}
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 declare _isEnabled: boolean;\n private declare _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,EAAE,EAAE;AACvD,QAAM,qDAAqD,SAAS,OAAO;AAC3E,OAAK,4BAA4B,OAAO;AACxC,OAAK,yBAAyB,OAAO,yBAAyB;;CAGhE,OAA0B;AACxB,SAAO,EAAE;;;;;;CAOX,SAAwB;AACtB,MAAI,OAAO,wBAAwB,aAAa;AAC9C,QAAK,MAAM,MACT,sEACD;AACD;;AAGF,OAAK,aAAa;AAElB,MAAI,KAAK,sBAAsB;AAC7B,QAAK,MAAM,MAAM,kDAAkD;AACnE;;AAGF,OAAK,uBAAuB;AAC5B,OAAK,MAAM,MAAM,wBAAwB;AAGzC,SAAO,WAAW,KAAK,cAAc,OAAO,CAAC;AAC7C,SAAO,WAAW,KAAK,cAAc,OAAO,CAAC;AAC7C,SAAO,WAAW,KAAK,cAAc,OAAO,CAAC;AAC7C,SAAO,WAAW,KAAK,cAAc,OAAO,CAAC;AAC7C,UAAQ,WAAW,KAAK,cAAc,OAAO,CAAC;;;;;;CAOhD,UAAyB;AACvB,OAAK,aAAa;AAClB,OAAK,MAAM,MAAM,6CAA6C;;;;;;;CAQhE,uBACE,QACoB;AACpB,MAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;AACxB,OAAI,YAAY,qBAAqB,KAAA,EACnC,QAAO,YAAY;AAErB;;AAEF,MAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;AACxB,UAAO,YAAY;;AAGrB,SAAO,OAAO;;CAGhB,cAAsB,QAAqC;AACzD,MAAI,CAAC,KAAK,WACR;EAEF,MAAM,aAAyB;IAC5B,sBAAsB,OAAO,KAAK,aAAa;IAC/C,uBAAuB,OAAO;IAE9B,uBAAuB,OAAO;IAC9B,wBAAwB,OAAO;IAC/B,oBAAoB,OAAO;IAC3B,iCAAiC,OAAO;GAC1C;EAED,MAAM,YAAY,KAAK,uBAAuB,OAAO;EAErD,MAAM,YAAuB;GAC3B,WAAW;GACX,gBAAgB,eAAe;GAC/B;GACA,GAAI,KAAK,yBACL,EAAE,MAAM,KAAK,UAAU,OAAO,YAAY,EAAE,GAC5C,EAAE;GACN,GAAI,cAAc,KAAA,IAAY,EAAE,WAAW,GAAG,EAAE;GACjD;AAED,MAAI,KAAK,0BACP,8BACQ,KAAK,4BAA4B,UAAU,GAChD,UAAU;AACT,OAAI,MACF,MAAK,MAAM,MAAM,wCAAwC,MAAM;KAGnE,KACD;AAGH,OAAK,OAAO,KAAK,UAAU"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentelemetry/browser-instrumentation",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "OpenTelemetry browser instrumentations.",
5
5
  "keywords": [
6
6
  "opentelemetry",
@@ -9,7 +9,8 @@
9
9
  "instrumentation",
10
10
  "navigation-timing",
11
11
  "user-action",
12
- "web-vitals"
12
+ "web-vitals",
13
+ "resource-timing"
13
14
  ],
14
15
  "homepage": "https://github.com/open-telemetry/opentelemetry-browser",
15
16
  "bugs": "https://github.com/open-telemetry/opentelemetry-browser/issues",
@@ -21,10 +22,15 @@
21
22
  "directory": "packages/instrumentation"
22
23
  },
23
24
  "type": "module",
25
+ "imports": {
26
+ "#instrumentation-utils": "./src/utils/index.ts",
27
+ "#instrumentation-test-utils": "./src/test-utils/index.ts"
28
+ },
24
29
  "exports": {
25
30
  "./experimental/navigation-timing": "./dist/navigation-timing/index.js",
26
31
  "./experimental/user-action": "./dist/user-action/index.js",
27
- "./experimental/web-vitals": "./dist/web-vitals/index.js"
32
+ "./experimental/web-vitals": "./dist/web-vitals/index.js",
33
+ "./experimental/resource-timing": "./dist/resource-timing/index.js"
28
34
  },
29
35
  "files": [
30
36
  "dist"
@@ -38,16 +44,15 @@
38
44
  "test:coverage": "vitest --coverage"
39
45
  },
40
46
  "dependencies": {
41
- "@opentelemetry/api-logs": "^0.213.0",
42
- "@opentelemetry/instrumentation": "^0.213.0",
43
- "web-vitals": "^5.1.0"
47
+ "@opentelemetry/api-logs": "^0.215.0",
48
+ "@opentelemetry/instrumentation": "^0.215.0",
49
+ "web-vitals": "^5.2.0"
44
50
  },
45
51
  "peerDependencies": {
46
52
  "@opentelemetry/api": "^1.9.0"
47
53
  },
48
54
  "devDependencies": {
49
- "@opentelemetry/test-utils": "*",
50
- "@opentelemetry/web-utils": "*"
55
+ "@opentelemetry/sdk-logs": "^0.215.0"
51
56
  },
52
57
  "publishConfig": {
53
58
  "access": "public"