Skip to content
Open source · MIT · net8.0 / net9.0 / net10.0 · ships with an MCP server

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
Checkout.razor · your records, live
@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 *@
Choose what is sent
The live feed starts once the page is interactive.

Nothing yet - press the button, or click around the site.

Both halvesthe browser script and the .NET pipeline, one registration
No OpenTelemetry packagesthe OTel data model and OTLP, written by hand
Every Blazor hostWebAssembly, Server, Auto, static SSR and Hybrid
Twelve consent featuresgranted and revoked one by one, at runtime

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

01

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
});
02

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.

Browser instrumentation →
@* App.razor *@
<head>
    ...
    <BelemetryScript />
</head>
03

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.

Consent and GDPR →
@* 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.

ConcernOpenTelemetry SDKsBelemetry
PackagesThe SDK, an exporter and instrumentation packages - plus a JavaScript SDK for the browserOne package; the browser script ships inside it
Browser and .NETTwo SDKs, configured separatelyOne registration; <BelemetryScript /> hands the browser the server's options
ConsentNot part of the SDK - gating the exporters is yours to buildTwelve features, opt-in by default, purge on revoke, Global Privacy Control
Azure MonitorThe Azure Monitor exporter, and the Application Insights JS SDK in the pageNative /v2.1/track from both sides, with Live Metrics
Trimming and AOTDepends on the packages you addTrimmable 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.

Try the MCP server →

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
An unhandled error has occurred. Reload 🗙