@opentelemetry/browser-instrumentation
v0.11.0
Published
OpenTelemetry browser instrumentations.
Readme
@opentelemetry/browser-instrumentation
Note: This is an experimental package under active development. New releases may include breaking changes. See VERSIONING.md.
OpenTelemetry browser instrumentations, available as subpath exports under
./experimental/*.
Moving from opentelemetry-js or opentelemetry-js-contrib? See the
migration guide for import paths, behavior
changes, and remaining gaps.
Installation
npm install @opentelemetry/browser-instrumentationInstrumentations
- Navigation — automatic instrumentation for browser navigations (initial load and SPA route changes)
- Navigation Timing — automatic instrumentation for navigation timing
- Resource Timing — automatic instrumentation for resource timing
- Element Timing — automatic instrumentation for paint timing of annotated elements
- User Action — automatic instrumentation for user actions (clicks)
- Web Vitals — automatic instrumentation for Core Web Vitals
- Console — automatic instrumentation for console API calls (log, warn, error, info, debug)
- Errors — automatic instrumentation for unhandled errors and promise rejections
- Fetch — automatic instrumentation for request using the
fetchAPI
Usage
The easiest way to use these instrumentations is the Browser SDK. Pass
them to startBrowserSdk() and the SDK enables them with its own providers.
You can also enable them yourself. Call enable() on each one:
import { logs } from '@opentelemetry/api-logs';
import {
ConsoleLogRecordExporter,
LoggerProvider,
SimpleLogRecordProcessor,
} from '@opentelemetry/sdk-logs';
import { ErrorsInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/errors';
import { NavigationInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/navigation';
import { NavigationTimingInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/navigation-timing';
import { ResourceTimingInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/resource-timing';
import { UserActionInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/user-action';
import { WebVitalsInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/web-vitals';
const loggerProvider = new LoggerProvider({
processors: [
new SimpleLogRecordProcessor({ exporter: new ConsoleLogRecordExporter() }),
],
});
logs.setGlobalLoggerProvider(loggerProvider);
const instrumentations = [
new ErrorsInstrumentation(),
new NavigationInstrumentation(),
new NavigationTimingInstrumentation(),
new ResourceTimingInstrumentation(),
new UserActionInstrumentation(),
new WebVitalsInstrumentation(),
];
for (const instrumentation of instrumentations) {
instrumentation.enable();
}Navigation
import { NavigationInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/navigation';Emits a browser.navigation event for the initial page load (hard navigation)
and for subsequent in-page navigations (soft navigations), including
history.pushState, history.replaceState, popstate, and hash changes. When
enabled via config, the
Navigation API
is used in preference to patching history.
Configuration
import {
NavigationInstrumentation,
defaultSanitizeUrl,
} from '@opentelemetry/browser-instrumentation/experimental/navigation';
new NavigationInstrumentation({
// Use window.navigation (Navigation API) when available instead of
// patching history.pushState / history.replaceState. Default: false.
useNavigationApiIfAvailable: true,
// Rewrite the captured URL before it is emitted. Useful for stripping
// path segments, query parameters, or tokens that should not be exported.
sanitizeUrl: (url) => defaultSanitizeUrl(url),
// [Deprecated] Mutate the log record before it is emitted.
// Prefer using a custom LogRecordProcessor registered with LoggerProvider.
applyCustomLogRecordData: (logRecord) => {
logRecord.attributes = {
...logRecord.attributes,
'app.route.id': '...',
};
},
});| Option | Type | Default | Description |
| ----------------------------- | -------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| useNavigationApiIfAvailable | boolean | false | When true, subscribes to the Navigation API (currententrychange) instead of patching history.pushState / history.replaceState. Falls back to history patching when the Navigation API is unavailable. The choice is made once, at the first enable(). |
| sanitizeUrl | (url: string) => string | — | Called before the URL is written to url.full. |
| applyCustomLogRecordData | (logRecord: LogRecord) => void | — | [Deprecated] Hook to modify log records before they are emitted. Errors thrown from this hook are caught and logged via the instrumentation diag logger. Prefer using a custom LogRecordProcessor registered with LoggerProvider. |
Note on Custom Attributes:
applyCustomLogRecordDatais deprecated. To enrich or mutate log records with custom attributes, register a customLogRecordProcessorwithLoggerProvider:import type { LogRecordProcessor, SdkLogRecord } from '@opentelemetry/sdk-logs'; class CustomAttributesProcessor implements LogRecordProcessor { onEmit(logRecord: SdkLogRecord) { logRecord.setAttribute('app.route.id', '...'); } async forceFlush() {} async shutdown() {} }
defaultSanitizeUrl is exported for composition — it redacts user:password@
credentials and a set of common sensitive query parameters (api_key, token,
password, etc.).
Captured Attributes
Each browser.navigation event includes:
| Attribute | Description |
| ---------------------------------- | ----------------------------------------------------------------------------------------- |
| url.full | The destination URL (after sanitizeUrl if configured). |
| browser.navigation.same_document | true for SPA route changes; false for full-page loads. |
| browser.navigation.hash_change | true when the navigation only adds or changes the URL fragment. |
| browser.navigation.type | One of push, replace, reload, traverse (omitted for the initial hard navigation). |
Navigation Timing
import { NavigationTimingInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/navigation-timing';Provides automatic instrumentation for Navigation Timing in web applications.
Resource Timing
import { ResourceTimingInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/resource-timing';Provides automatic instrumentation for Resource Timing in web applications, capturing performance metrics for all resources loaded by the browser (scripts, stylesheets, images, fonts, XHR/fetch requests, etc.).
- Uses
requestIdleCallbackto avoid blocking the main thread (with automaticsetTimeoutfallback for Safari) - Processes resources in configurable batches
- Captures historical resources loaded before instrumentation was enabled via buffered mode
- Flushes pending entries on visibility change to prevent data loss
Configuration
new ResourceTimingInstrumentation({
// Process 100 resources per batch (default: 50)
batchSize: 100,
// Wait max 2 seconds for idle time before forcing processing (default: 1000)
forceProcessingAfter: 2000,
// Spend max 100ms processing per idle callback (default: 50)
maxProcessingTime: 100,
// Maximum queue size before forcing immediate flush (default: 1000)
maxQueueSize: 2000,
});| Option | Type | Default | Description |
| ---------------------- | -------- | ------- | ------------------------------------------------------------------------- |
| batchSize | number | 50 | Number of resources to process per batch. |
| forceProcessingAfter | number | 1000 | Maximum time (ms) to wait for an idle callback before forcing processing. |
| maxProcessingTime | number | 50 | Maximum time (ms) to spend processing resources per idle callback. |
| maxQueueSize | number | 1000 | Maximum number of resources to queue before forcing immediate flush. |
Captured Data
Each resource timing event includes:
- URL and Initiator Type (script, css, img, xmlhttprequest, fetch, etc.)
- Duration — total resource load time
- Timing Phases — DNS lookup, TCP connection, TLS handshake, request, response
- Size Metrics — transfer size, encoded size, decoded size
- Protocol — HTTP version (h1, h2, h3)
- Redirect Info — redirect timing if applicable
- Service Worker — worker start time if intercepted
- Render Blocking — whether the resource blocked rendering (Chromium only)
Element Timing
import { ElementTimingInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/element-timing';Emits a browser.element_timing event for each element annotated with the
elementtiming
attribute, reporting when the browser painted it.
Only image elements and text-containing block elements are eligible, and the attribute must be present before the element is first painted:
<img src="hero.jpg" elementtiming="hero-image">
<p elementtiming="headline">Welcome</p>Entries recorded before the instrumentation was enabled are captured via buffered mode. Element Timing is Chromium-only. On other browsers the instrumentation stays disabled and logs a diag message.
Captured Attributes
Each browser.element_timing event includes:
| Attribute | Description |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| browser.element_timing.identifier | The elementtiming attribute value. |
| browser.element_timing.element | The lower-cased tag name (omitted when the element left the document before the entry was processed). |
| browser.element_timing.render_time | Paint time relative to navigation start. Cross-origin images without a Timing-Allow-Origin header report 0 or a coarsened value. |
| browser.element_timing.start_time | render_time when non-zero, otherwise load_time. Always present, so prefer this for charting. |
| browser.element_timing.load_time | Resource load time relative to navigation start. Image elements only. |
| url.full | The image resource URL. Image elements only. |
| browser.element_timing.natural_width | Intrinsic width in CSS pixels. Image elements only. |
| browser.element_timing.natural_height | Intrinsic height in CSS pixels. Image elements only. |
User Action
import { UserActionInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/user-action';Provides automatic instrumentation for user actions in web applications.
Compatible with OpenTelemetry JS API and SDK 1.0+.
Configuration
By default the instrumentation captures click events. Middle clicks are
captured through auxclick and right clicks through contextmenu, since native
context menus can consume the mouse release before auxclick fires. Only button
2 context menus are recorded, so keyboard menu events are ignored. Right-button
auxclick events are ignored to avoid duplicate records. You can configure
which events to capture by passing an options object:
new UserActionInstrumentation({
// Array of actions to automatically capture. Default: ['click'].
autoCapturedActions: [],
// [Deprecated] Mutate the log record before it is emitted.
// Prefer using a custom LogRecordProcessor registered with LoggerProvider.
applyCustomLogRecordData: (logRecord) => {
logRecord.attributes = {
...logRecord.attributes,
'app.user.role': 'admin',
};
},
});| Option | Type | Default | Description |
| -------------------------- | -------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| autoCapturedActions | AutoCapturedUserAction[] | ['click'] | Array of actions to automatically capture. |
| applyCustomLogRecordData | (logRecord: LogRecord) => void | — | [Deprecated] Hook to modify log records before they are emitted. Errors thrown from this hook are caught and logged via the instrumentation diag logger. Prefer using a custom LogRecordProcessor registered with LoggerProvider. |
Additional Attributes
Data attributes with the prefix data-otel- on the target element will be added
as additional attributes to the generated log record. For example:
<button id="btn1" data-otel-user-id="12345" data-otel-feature="signup">
Sign Up
</button>Web Vitals
import { WebVitalsInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/web-vitals';Provides automatic instrumentation for
Core Web Vitals using the
web-vitals library.
Configuration
| Option | Type | Default | Description |
| -------------------------- | -------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| includeRawAttribution | boolean | false | When true, sets the log record body to the JSON-stringified web-vitals attribution object. |
| applyCustomLogRecordData | (logRecord: LogRecord) => void | — | [Deprecated] Hook to modify log records before they are emitted. Prefer using a custom LogRecordProcessor registered with LoggerProvider. |
| reportAllChanges | boolean | false | When true, CLS, INP and LCP are reported each time their value changes, not only when the value is final. FCP and TTFB are not affected. delta is the change since the previous report for the same metric id.web-vitals recommends this for debugging, not for production. |
| reportSoftNavs | boolean | false | When true, metrics are reported again after each soft navigation (a route change in a single-page app). CLS and INP start again from zero. FCP and LCP are the paints after the soft navigation. TTFB is not reported for soft navigations. Metrics for the first page are final at the first soft navigation, so its CLS and INP can be lower than without this option. Adds browser.web_vital.navigation_url. Use it to group metrics by route, because a metric can be reported after the URL has changed. browser.web_vital.navigation_id is added when the browser exposes a navigation id, with or without this option. Requires Chromium 151 or later. Other browsers ignore this option. See the web-vitals docs. |
| sanitizeUrl | (url: string) => string | — | Called before the URL is written to browser.web_vital.navigation_url. |
Console
import { ConsoleInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/console';Provides automatic instrumentation for browser console API calls. By default
captures log, warn, error, info, and debug methods.
Configuration
new ConsoleInstrumentation({
// Specify which console methods to capture (default: log, warn, error, info, debug)
logMethods: ['error', 'warn'],
});Captured Attributes
Each browser.console event includes the following attributes:
| Attribute | Description | Example |
| ------------------------ | ---------------------------------- | --------------------------------------- |
| browser.console.method | The console method that was called | error, warn, log, info, debug |
Errors
import { ErrorsInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/errors';Emits an exception event for every uncaught error
(window.addEventListener('error', ...)) and unhandled promise rejection
(window.addEventListener('unhandledrejection', ...)).
Configuration
new ErrorsInstrumentation({
// Return extra attributes to attach to the emitted log record.
applyCustomAttributes: (error) => ({
'app.error.severity':
error instanceof Error && error.name === 'ValidationError'
? 'warning'
: 'error',
}),
});| Option | Type | Default | Description |
| ----------------------- | ---------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| applyCustomAttributes | (error: Error \| string) => Attributes | — | Returns extra attributes to merge onto the emitted log record. Errors thrown from this hook are caught and logged via the instrumentation diag logger. |
Captured Attributes
Each exception event includes:
| Attribute | Description |
| ---------------------- | ---------------------------------------------------------------- |
| exception.type | The error's name (omitted when the thrown value is a string). |
| exception.message | The error's message, or the thrown string itself. |
| exception.stacktrace | The error's stack (omitted when the thrown value is a string). |
Fetch
import { FetchInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/fetch';Emits a Span for every HTTP request made using the window.fetch browser API.
This instrumentation also propagates the context for distributed traces.
Configuration
new FetchInstrumentation({
ignoreUrls: ['https://example.com/api'],
propagateTraceHeaderCorsUrls: [/domain.com\/api.*/],
measureRequestSize: true,
applyCustomAttributesOnSpan: (span, request, result) => {
span.setAttribute('foo', 'bar');
},
});| Option | Type | Default | Description |
| ------------------------------ | --------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ignoreUrls | Array<string \| RegExp> | — | List of URLs that we do not want its requests instrumented. You can use strings for a single match or regular expressions to match many. |
| propagateTraceHeaderCorsUrls | Array<string \| RegExp> | — | List of cross origin URLs to appens trace context headers. You can use strings for a single match or regular expressions to match many. |
| measureRequestSize | boolean | — | Set it to true to add the request size as a Span attribute. |
| sanitizeUrl | (url: string) => string | — | Returns the given URL with sensitive fields REDACTED. |
| requestHook | (span: Span, request: Request \| RequestInit) => void | — | Function for adding Span attributes or request headers before the request starts and trace headers are injected. |
| applyCustomAttributesOnSpan | (span: Span, request: Request \| RequestInit, result: Response \| FetchError) => void | — | Function that allows you to interact with the Span once the request is finished, before the Span ends. |
See Sharing fetch and XHR hooks to reuse callbacks across both instrumentations.
Captured Attributes
Each fetch Span includes:
| Attribute | Description |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| http.request.method | The method of the request in uppercase (GET, POST, PUT, PATCH, QUERY). |
| http.request.method_original | Original HTTP method sent by the client in the request line.. |
| url.full | Absolute URL describing a network resource according to RFC3986 (sanitized if passing sanitizeUrl config option). |
| server.address | The hostmane of the request's URL. |
| server.port | The port of the request's URL. |
| http.response.status_code | HTTP response status code. |
| error.type | If request failed. Describes a class of error the operation ended with. |
XHR (XmlHttpRequest)
import { XhrInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/xhr';Emits a Span for every HTTP request made using the window.XmlHttpRequest
browser API. This instrumentation also propagates the context for distributed
traces.
Configuration
new XhrInstrumentation({
ignoreUrls: ['https://example.com/api'],
propagateTraceHeaderCorsUrls: [/domain.com\/api.*/],
measureRequestSize: true,
applyCustomAttributesOnSpan: (span, xhr) => {
span.setAttribute('foo', 'bar');
},
});| Option | Type | Default | Description |
| ------------------------------ | ------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| ignoreUrls | Array<string \| RegExp> | — | List of URLs that we do not want its requests instrumented. You can use strings for a single match or regular expressions to match many. |
| propagateTraceHeaderCorsUrls | Array<string \| RegExp> | — | List of cross origin URLs to appens trace context headers. You can use strings for a single match or regular expressions to match many. |
| measureRequestSize | boolean | — | Set it to true to add the request size as a Span attribute. |
| sanitizeUrl | (url: string) => string | — | Returns the given URL with sensitive fields REDACTED. |
| requestHook | (span: Span, xhr: XMLHttpRequest) => void | — | Function for adding Span attributes or request headers via xhr.setRequestHeader. Called after open, before send and trace-header injection. |
| applyCustomAttributesOnSpan | (span: Span, xhr: XMLHttpRequest) => void | — | Function that allows you to interact with the Span and completed XHR once the request is finished, before the Span ends. |
Sharing fetch and XHR hooks
Both instrumentations accept the same callbacks when the request parameter is
typed as Request | RequestInit | XMLHttpRequest. For
applyCustomAttributesOnSpan, make the third parameter optional: fetch supplies
a Response or FetchError, while XHR supplies no third argument. Read XHR
response information from the XMLHttpRequest itself.
Hooks run synchronously. Errors thrown by either hook are caught and reported through the instrumentation's diagnostic logger without interrupting the request or preventing the Span from ending. Ignored requests and disabled instrumentations do not invoke hooks.
Note: For fetch(url, init), the example below changes the caller's
init.headers. These changes persist if init is reused; pass a fresh init
object for each request to keep them request-specific.
import type { Span } from '@opentelemetry/api';
import {
FetchInstrumentation,
type FetchError,
} from '@opentelemetry/browser-instrumentation/experimental/fetch';
import { XhrInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/xhr';
type HttpRequest = Request | RequestInit | XMLHttpRequest;
const hooks = {
requestHook: (span: Span, request: HttpRequest) => {
span.setAttribute(
'request.api',
request instanceof XMLHttpRequest ? 'xhr' : 'fetch',
);
if (request instanceof XMLHttpRequest) {
request.setRequestHeader('x-custom-header', 'value');
} else if (request instanceof Request) {
request.headers.set('x-custom-header', 'value');
} else {
const headers = new Headers(request.headers);
headers.set('x-custom-header', 'value');
request.headers = headers;
}
},
applyCustomAttributesOnSpan: (
span: Span,
request: HttpRequest,
result?: Response | FetchError,
) => {
const status =
request instanceof XMLHttpRequest ? request.status : result?.status;
if (status !== undefined) {
span.setAttribute('custom.response.status', status);
}
},
};
new FetchInstrumentation(hooks);
new XhrInstrumentation(hooks);Captured Attributes
Each xhr Span includes:
| Attribute | Description |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| http.request.method | The method of the request in uppercase (GET, POST, PUT, PATCH, QUERY). |
| http.request.method_original | Original HTTP method sent by the client in the request line.. |
| url.full | Absolute URL describing a network resource according to RFC3986 (sanitized if passing sanitizeUrl config option). |
| server.address | The hostmane of the request's URL. |
| server.port | The port of the request's URL. |
| http.response.status_code | HTTP response status code. |
| error.type | If request failed. Describes a class of error the operation ended with. |
Enabling and disabling
Each instrumentation has three methods: enable(), disable() and
isEnabled().
new X()does nothing. It only stores the config. It does not patch, listen or emit.- The first
enable(deps?)runs a one-time init and then starts emitting. Init patches browser APIs and checks that the browser supports what the instrumentation needs. - The first
enable()also locks the dependencies for the life of the instance. A dependency that you do not pass uses the global API from@opentelemetry/apior@opentelemetry/api-logs. Laterenable()calls ignoredeps. They log a warning if a passed dependency differs from the locked one, and a debug message if it is the same. disable()stops emitting and removes listeners. Patched browser APIs stay patched, because other code may have wrapped them after us. A laterenable()starts emitting again, unlessdisable()failed.isEnabled()istrueonly while the instrumentation emits.
The dependencies are:
| Dependency | Used for | Default |
| ---------------- | ---------------------------------- | -------------------------- |
| tracerProvider | Spans | The global tracer provider |
| loggerProvider | Log records (events) | The global logger provider |
| propagator | Trace context in outgoing requests | The global propagation API |
import { ErrorsInstrumentation } from '@opentelemetry/browser-instrumentation/experimental/errors';
const errors = new ErrorsInstrumentation();
errors.isEnabled(); // false. Nothing is patched yet.
// No deps are passed, so it uses the global providers.
errors.enable();
errors.isEnabled(); // true
errors.disable();
errors.isEnabled(); // falseThe Browser SDK calls enable() and disable() for you. For example,
to wait for user consent, start the SDK with enabled: false and call
sdk.enable() later. See the SDK README.
Listeners are added only when the instrumentation is enabled. So a late
enable() still records the initial page load, buffered resource entries, and
web vitals that are reported only once. One-time events, such as the initial
page load, are not emitted a second time after disable() and enable().
To keep an instrumentation fully off, with no patches, do not call enable().
Init can fail, for example when a needed browser API is missing or another
script has locked it. A passed provider that cannot give a tracer or logger
fails the same way. The reason is logged as an error through the OpenTelemetry
diag logger, so set one with diag.setLogger() to see it. A failed
instrumentation stays off for good. Later calls to enable() only log a debug
line. A disable() that throws, for example when a listener cannot be removed,
is also logged as an error and keeps the instrumentation off in the same way. An
_onEnable() that throws is logged as an error, and a later enable() tries
again, unless the _onDisable() that cleans up after it throws too.
Differences from classic instrumentations
These instrumentations do not use the base class from
@opentelemetry/instrumentation. So they behave differently from the
instrumentations in opentelemetry-js and opentelemetry-js-contrib:
- Creating an instrumentation does not enable it. Call
enable(), or pass it to the Browser SDK. - Providers and the propagator are passed once, to the first
enable(). There are no setters for them. disable()does not unpatch browser APIs.- The config is set once, in the constructor. There is no
enabledconfig option.
Do not use a classic instrumentation for the same API as one of these. A classic instrumentation cannot detect our patches, so both would record every call.
Writing an instrumentation
An instrumentation is any object that implements the Instrumentation
interface: instrumentationName, enable(deps?), disable() and
isEnabled(). The Browser SDK accepts any such object.
The easiest way is to extend InstrumentationBase. It implements the lifecycle
in Enabling and disabling. InstrumentationBase,
Instrumentation and InstrumentationDeps are exported from
@opentelemetry/browser-api. Add it as a dependency. You do not need
this package, so your instrumentation is not tied to its releases.
npm install @opentelemetry/browser-apiOverride these hooks. All of them must be synchronous, because the lifecycle
does not wait for a returned promise. Do not use async here: a rejection is
not logged, and it reaches the page as an unhandled rejection. Hooks must not
call enable() or disable().
_init()runs once, at the firstenable(). It only patches browser APIs with_wrap()and checks that the browser supports what the instrumentation needs. Throw if the instrumentation cannot run. A failed init is final._onEnable()runs each timeenable()turns emission on. Add listeners and observers here, including buffered ones. If it throws,_onDisable()runs to remove what it added, and a laterenable()tries again. If that_onDisable()throws too, the instrumentation stays off for good._onDisable()runs each timedisable()turns emission off. Remove what_onEnable()added. If it throws, some listeners may still be attached, so the instrumentation stays off for good.
From the first enable(), use this.tracer, this.logger and
this.propagator. They come from the locked dependencies. Use this.propagator
instead of the global propagation API, so a second SDK on the page can inject
its own trace context.
Take the instrumentation's own dependencies, such as a store, in the
constructor. Only tracerProvider, loggerProvider and propagator go through
enable(). They cannot be constructor arguments, because startBrowserSdk
creates them after you construct the instrumentation.
There is _wrap() but no unwrap. Patches are never removed, because other code
may have wrapped on top of ours. So a patch must check isEnabled(), and pass
the call through when it is false. Run user hooks and emit code through
_safeExecute(), so that a throw, or a rejected promise, is logged and does not
break the page. In your own catch blocks, pass the caught value through
_toError() before you log it. A page can throw a value that is not an Error.
import { InstrumentationBase } from '@opentelemetry/browser-api';
export interface RouteConfig {
includeHash?: boolean;
}
export class RouteInstrumentation extends InstrumentationBase<RouteConfig> {
private _onPopState: (() => void) | undefined;
constructor(config: RouteConfig = {}) {
super('route-example', '0.1.0', config);
}
protected override _init(): void {
const instrumentation = this;
this._wrap(
history,
'pushState',
(original) =>
function patchedPushState(
this: History,
...args: Parameters<History['pushState']>
) {
const result = original.apply(this, args);
if (instrumentation.isEnabled()) {
instrumentation._record();
}
return result;
},
);
}
protected override _onEnable(): void {
this._onPopState = () => this._record();
window.addEventListener('popstate', this._onPopState);
}
protected override _onDisable(): void {
if (this._onPopState) {
window.removeEventListener('popstate', this._onPopState);
this._onPopState = undefined;
}
}
private _record(): void {
this._safeExecute('failed to record a route change', () => {
const url = this._config.includeHash
? location.href
: location.href.split('#')[0];
this.logger.emit({
eventName: 'example.route_change',
attributes: { 'url.full': url },
});
});
}
}Useful links
- For more information on OpenTelemetry, visit: https://opentelemetry.io/
- For more about OpenTelemetry Browser: https://github.com/open-telemetry/opentelemetry-browser
- For help or feedback on this project, join us in GitHub Discussions
License
Apache 2.0 - See LICENSE for more information.
