Telemetry for Blazor,
both sides of the app.
One lightweight SDK for WebAssembly, Server, Auto, static SSR and Hybrid. It records the browser -
page views, clicks, web vitals, errors, fetch and XHR - and .NET - ILogger,
Activity and Meter - in OpenTelemetry's data model, without the
OpenTelemetry packages, and only what the user consented to.
dotnet add package Bit.Belemetry
@inject IBelemetry Belemetry <button @onclick="TrackEvent">Track an event</button> @code { void TrackEvent() => Belemetry.TrackEvent("demo.home.click"); } @* Page views, clicks, web vitals and errors take no code *@
Nothing yet - press the button, or click around the site.
Capabilities
Twelve features, each under its own consent
A record is collected only when its feature is enabled by configuration and granted by the user. Nothing is sent before the user decides, revoking a feature purges what it queued, and every tile below opens the page that shows it working.
Errors
JS errors and unhandled rejections, .NET logs at Error or with an exception, and TrackException - with the stack trace, scrubbed.
Page views
History and enhanced navigation as browser.page_view events with the URL redacted - plus TrackPageView for dialogs and wizard steps.
Interactions
Clicks as browser.user_interaction events, named with data-bit-belemetry-name and kept out with data-bit-belemetry-ignore. Input values are never read.
Performance
LCP, INP, CLS, FCP and TTFB, the document-load span with navigation timing, resource timing and long tasks.
Dependencies
fetch and XHR client spans that add a W3C traceparent to your own origin, so the server's span joins the browser's trace.
Sessions
A session id that survives reloads in sessionStorage, rotated after inactivity. Without consent it lives in memory only.
User tracking
A pseudonymous enduser.pseudo.id and the authenticated user.id from SetUserAsync - both cleared the moment it is revoked.
Logs
ILogger entries below Error with their structured values, scopes and trace context, and browser console capture.
Traces
Activity spans from the sources you list, with parent-based sampling compatible with OpenTelemetry's TraceIdRatioBased.
Metrics
Counters, histograms, up-down counters and gauges from your Meters, aggregated with a cardinality limit per instrument.
Persistence
Exports that fail offline are kept - in localStorage, or in files on Hybrid and servers - and retried when the network is back.
Events
Custom events from TrackEvent in C# or BitBelemetry.trackEvent in JavaScript, with attributes of your own.
In practice
One registration, two components
Register it with an exporter
In every project that hosts Blazor. The call is idempotent and follows the Options pattern, and the same options configure the browser script too.
Blazor Web App →builder.Services.AddBitBelemetryServices(options => { options.ServiceName = "MyApp"; options.AddOtlpExporter("http://localhost:4318"); // Aspire, OTel Collector, ... options.Consent.Default = BelemetryFeatures.None; // opt-in: the default });
Start the script in the head
<BelemetryScript /> starts before Blazor does, so it sees the initial page
load, the web vitals and the earliest errors - on static SSR pages as well.
@* App.razor *@ <head> ... <BelemetryScript /> </head>
Connect .NET, and ask
<BelemetryInitializer /> joins the script to .NET once the app is
interactive. Then ask the user: until IBelemetryConsent hears an answer,
nothing leaves the device.
@* Routes.razor *@ <Router AppAssembly="typeof(Program).Assembly">...</Router> <BelemetryInitializer /> // The banner's "Accept all" await Consent.SetAsync(BelemetryFeatures.All);
Comparison
Why not the OpenTelemetry SDKs?
A Blazor app has two runtimes to instrument, and a user who has to agree first. With the OpenTelemetry SDKs that is two SDKs and a consent layer of your own; Belemetry is one package built on the same BCL listeners, with both halves and the consent built in.
| Concern | OpenTelemetry SDKs | Belemetry |
|---|---|---|
| Packages | The SDK, an exporter and instrumentation packages - plus a JavaScript SDK for the browser | One package; the browser script ships inside it |
| Browser and .NET | Two SDKs, configured separately | One registration; <BelemetryScript /> hands the browser the server's options |
| Consent | Not part of the SDK - gating the exporters is yours to build | Twelve features, opt-in by default, purge on revoke, Global Privacy Control |
| Azure Monitor | The Azure Monitor exporter, and the Application Insights JS SDK in the page | Native /v2.1/track from both sides, with Live Metrics |
| Trimming and AOT | Depends on the packages you add | Trimmable and AOT-compatible: no reflection-based serialization |
Where to start
Choose your path
New to Belemetry?
Install, register, two components - a Blazor Web App reporting to the Aspire dashboard in five minutes.
Set it up →Want to watch it work?
This site exports to itself. Answer the banner, click around, and read your own records as they arrive.
Live telemetry →Building with an AI agent?
This site is an MCP server: point your agent at /mcp to generate a configuration and review the one it wrote.
Questions
Frequently asked
Does it use the OpenTelemetry packages?
No. It follows the OpenTelemetry data model and semantic conventions and speaks OTLP, but it is
built on the BCL's own ActivityListener, MeterListener and
ILoggerProvider, with hand-written OTLP and Application Insights encoders and one
dependency-free script.
Is anything sent before the user agrees?
Not by default: Consent.Default is None. Records of features nobody has
decided on yet are held in memory (up to Consent.PendingLimit, 500), then sent or
dropped by the decision. Revoking a feature purges what it queued and what it stored on the device.
Which Blazor hosts does it work in?
WebAssembly, Server, Auto, static SSR and Hybrid (MAUI, WPF, WinForms), on .NET 8, 9 and 10. In a Hybrid app the browser script relays its records to .NET, which exports them and keeps them in files while the device is offline.
Can I keep an API key out of the browser?
Yes: set IncludeInBrowser = false on the exporter that carries it and give the browser a
credential-free route, or use the Relay transport on Blazor Server and Hybrid. In WebAssembly the
.NET pipeline runs in the browser too, so its configuration is public.
Where does this site send what I do here?
To its own server, only after you answer the banner. An in-process OTLP receiver keeps the last few hundred records per session in memory - never on disk - and hands them back only to the browser session that produced them, on the Live telemetry page.
Start measuring
One package, one registration, two components - and only what your users said yes to.
dotnet add package Bit.Belemetry