Skip to content

Browser instrumentation

The browser half of Belemetry is one dependency-free ES module, bit-belemetry.js. It starts from the configuration <BelemetryScript /> writes into the page, before Blazor boots, so it sees the initial load, the web vitals and the earliest errors - on interactive and static SSR pages alike. Every section below has a live trigger and, under it, the records it produced, read back from this site's own OTLP receiver.

Nothing is recorded until you grant it. Each instrumentation belongs to one consent feature (named in its section); if a feed below says nothing is granted, accept the banner or pick features on the consent page. Records of a feature you have not decided on yet are held in memory and sent once you grant it.

Page views

Feature PageViews. The script records the initial load and every client-side navigation - history.pushState, replaceState, popstate and Blazor's enhanced navigation - as a log record with the event name browser.page_view, carrying url.full, url.path, page.title, page.referrer and browser.navigation.type (initial, push, replace, pop, hash or manual). Fragment-only changes count only with TrackHashChanges. With TrackPageVisitTime the time spent on each page is recorded too, as a browser.page_visit_time event carrying page.visit.duration (milliseconds) when the next page view starts or the page is hidden - what Application Insights shows as the PageVisitTime event.

For views the URL does not show - a dialog, a wizard step, a tab - call IBelemetry.TrackPageView. In this demo the .NET side runs in WebAssembly, so that record goes through the .NET pipeline and shows up with the source wasm.

LivePageViews
Go to the .NET page (then come back)
The live feed starts once the page is interactive.

No page views yet - navigate away and back, or press the button.

Interactions

Feature Interactions. A capture-phase listener records every click (or the events in InteractionEvents) on a link, a button, a submit input or any element carrying data-bit-belemetry-name, as a browser.user_interaction event with target.element, target.id, target.name and target.xpath. The name comes from data-bit-belemetry-name, then aria-label, then name. The element's text is recorded only with CaptureInteractionText, because text can be personal data, and input values are never read. Anything under data-bit-belemetry-ignore is skipped.

LiveInteractions

Recorded

Not recorded

The live feed starts once the page is interactive.

No interactions yet - press the buttons above. Every other button on this page is recorded too.

Web vitals

Feature Performance. The five Core Web Vitals and their companions are measured from the Performance Timeline and recorded as browser.web_vital events with web_vital.name, web_vital.value, web_vital.delta, web_vital.rating (good, needs-improvement or poor, on the official thresholds) and web_vital.id:

VitalMeasuresReported
TTFBTime to first byte of the documentAt start, once the response has begun
FCPFirst contentful paintWhen it happens
LCPLargest contentful paintAt the first click or key press, or when the page is hidden
CLSCumulative layout shift (largest session window)When the page is hidden
INPInteraction to next paintWhen the page is hidden

CLS and INP keep changing for as long as the page is open, so they are reported when it is hidden - switch to another tab and back after pressing the button. A shift that follows your input within 500 ms does not count (the spec's hadRecentInput), so the spacer this button inserts is excluded; the shift when it is removed 1.5 s later is the one CLS records.

LivePerformance - web vitals

This paragraph moves down while a spacer is inserted above it, then jumps back.

The live feed starts once the page is interactive.

No web vitals yet - TTFB and FCP arrive first; switch tabs and come back for CLS and INP.

Document load, resources and long tasks

Also feature Performance. DocumentLoad (on by default) records the initial load as an internal documentLoad span with the navigation-timing milestones as events (domInteractive, domContentLoadedEventEnd, loadEventEnd, ...). ResourceTiming (off by default) adds a resourceFetch span per script, stylesheet and image with url.full, http.response.body.size and network.protocol.name - a lot of records, so turn it on deliberately. LongTasks (off by default, on in this demo) records every main-thread task over 50 ms as a longtask span.

LivePerformance - spans
The live feed starts once the page is interactive.

No documentLoad or longtask spans yet - press the button, or reload the page.

fetch and XHR with trace context

Feature Dependencies. Every fetch and XMLHttpRequest becomes a client span named after its method, with http.request.method, url.full, server.address, server.port, http.response.status_code and, for a failure or a 4xx/5xx answer, error.type and an error status. For targets matched by PropagateTraceContextTo (same-origin by default) the script adds a W3C traceparent header, so the browser span and the server's request span join one trace.

The echo endpoint below answers with the traceparent it received: its trace id is the trace id of the browser span in the feed. URLs in IgnoreUrls (the Blazor framework routes by default, plus the exporters' own endpoints) are never recorded, and neither is a request that already carries a traceparent - in WebAssembly that is .NET's HttpClient, which traces itself.

LiveDependencies
The live feed starts once the page is interactive.

No dependency spans yet - press one of the buttons above.

Errors

Feature Errors. Uncaught errors (window.onerror) and unhandled promise rejections become log records with severity ERROR, the event name exception, and exception.type, exception.message and exception.stacktrace - scrubbed by the privacy rules first. Messages matching IgnoreErrorMessages are dropped; the default list has the harmless ResizeObserver loop warning every browser raises. Handled .NET exceptions go through IBelemetry.TrackException.

LiveErrors
The live feed starts once the page is interactive.

No errors yet - press one of the buttons above.

Console capture

With CaptureConsole (off by default, on in this demo) the script wraps the console methods named in ConsoleLevels (error and warn by default) and records each call as a log record. console.error belongs to the Errors feature, the other levels to Logs. The original method always runs first, so the developer tools look the same as before.

LiveConsole capture
The live feed starts once the page is interactive.

No console records yet - press one of the buttons above.

Custom events

Feature Events. A custom event is a log record whose event name is the name you give it, with your attributes. From C# call IBelemetry.TrackEvent; from JavaScript call BitBelemetry.trackEvent on the global the module sets. Both carry session.id (and the user ids with UserTracking), so a backend can join them with everything else the session did.

LiveEvents
The live feed starts once the page is interactive.

No custom events yet - press one of the buttons above.

API

Every option of the script lives on options.Browser:

BelemetryBrowserOptions Class

The browser script (bit-belemetry.js): what it instruments and how it ships records.

Name Type and default Description
Enabled bool= true Whether the browser script runs at all. Defaults to true.
Transport BelemetryBrowserTransport= BelemetryBrowserTransport.Auto How browser records leave the page. Defaults to BelemetryBrowserTransport.Auto.
TrackHashChanges bool= false Whether URL changes made only to the fragment (#...) count as page views. Defaults to false.
TrackPageVisitTime bool= false Whether the time spent on each page is recorded, as a browser.page_visit_time event carrying page.visit.duration (milliseconds) when the next page view starts or the page is hidden (Application Insights shows it as the PageVisitTime event). Defaults to false.
InteractionEvents List<string>= ["click"] The DOM events recorded as interactions. Defaults to click.
CaptureInteractionText bool= false Whether the visible text of the clicked element is recorded (truncated to BelemetryBrowserOptions.MaxInteractionTextLength). Defaults to false because text can contain personal data; prefer data-bit-belemetry-name. Input values are never read.
MaxInteractionTextLength int= 64 The maximum length of recorded element text. Defaults to 64.
WebVitals bool= true Whether web vitals (LCP, INP, CLS, FCP, TTFB) are recorded. Defaults to true.
DocumentLoad bool= true Whether the initial document load is recorded as a span with navigation-timing events. Defaults to true.
ResourceTiming bool= false Whether every resource fetched by the page (scripts, styles, images) is recorded as a span. Defaults to false.
LongTasks bool= false Whether main-thread tasks longer than 50 ms are recorded. Defaults to false.
Fetch bool= true Whether fetch calls are recorded as dependency spans. Defaults to true.
Xhr bool= true Whether XMLHttpRequest calls are recorded as dependency spans. Defaults to true.
PropagateTraceContextTo List<string>= ["same-origin"] Where a W3C traceparent header is added to fetch/XHR calls, so the browser span and the server span join one trace: same-origin, *, or regular expressions matched against the absolute URL. Defaults to same-origin. Cross-origin targets must allow the header (CORS).
IgnoreUrls List<string>= ["/_framework/", "/_blazor", "/_content/"] Regular expressions of URLs never recorded as dependencies. Defaults to the Blazor framework routes.
IgnoreErrorMessages List<string>= ["ResizeObserver loop"] Regular expressions of JS error messages never recorded.
CaptureConsole bool= false Whether console calls are recorded as logs. Defaults to false.
ConsoleLevels List<string>= ["error", "warn"] The console levels recorded when BelemetryBrowserOptions.CaptureConsole is on. Defaults to error and warn.
SessionInactivityTimeout TimeSpan= 00:30:00 How long without activity ends a session. Defaults to 30 minutes.
SessionMaxDuration TimeSpan= 1.00:00:00 The maximum length of a session. Defaults to 24 hours.
SessionSampleRatio double= 1 The fraction of sessions whose browser telemetry is recorded (decided once per session). Defaults to 1.0.
MaxQueueSize int= 1024 The maximum number of records the script holds; the oldest are dropped past it. Defaults to 1024.
MaxBatchSize int= 100 The maximum number of records per request. Defaults to 100.
FlushInterval TimeSpan= 00:00:05 How often the script exports. Defaults to 5 seconds (it also exports when the page is hidden).
Debug bool= false Whether the script writes its own diagnostics to console.debug. Defaults to false.

Manual telemetry from C#:

IBelemetry Interface

Manual telemetry from C#: custom events, page views and handled exceptions, the authenticated user, and flushing. Everything else (logs, traces, metrics, browser instrumentation) is collected automatically.

Name Type and default Description
TrackEvent(string name, IReadOnlyDictionary<string, object>? attributes = null) void Records a custom event (feature BelemetryFeatures.Events).
TrackPageView(string? name = null, string? url = null, IReadOnlyDictionary<string, object>? attributes = null) void Records a page view (feature BelemetryFeatures.PageViews). Navigation is already tracked automatically; use this for virtual pages such as dialogs or wizard steps.
TrackException(Exception exception, IReadOnlyDictionary<string, object>? attributes = null) void Records a handled exception (feature BelemetryFeatures.Errors).
SetUserAsync(string? userId) ValueTask Sets (or with null clears) the authenticated user id sent as user.id. Only kept while BelemetryFeatures.UserTracking is granted.
FlushAsync(CancellationToken cancellationToken = null) ValueTask Exports everything queued now - e.g. before a Hybrid app is suspended.
An unhandled error has occurred. Reload 🗙