Live telemetry
This site is instrumented with Belemetry and exports to itself. Everything you do here - page views, clicks, web vitals, errors, fetch spans, the WebAssembly app's logs, traces and metrics - is sent to the demo server as OTLP/JSON, decoded, and handed back to you below. Only you see your records, and nothing is sent until you have answered the consent banner.
Your records
Make something happen, and watch it arrive within a couple of seconds. Each row opens to show its attributes, trace and span ids, and the resource it came with. The source column says who recorded it: browser (the script), wasm (the .NET pipeline running in this tab) or server (the demo server's own pipeline).
Nothing yet. Click around the site - page views, clicks, web vitals and your .NET logs land here within a few seconds.
What each button records
| Button | Call | What arrives | Feature |
|---|---|---|---|
| Track an event | IBelemetry.TrackEvent("demo.live.event", ...) |
A wasm log record with the event name and your attributes | Events |
| Log with ILogger | Logger.LogInformation(...) |
A wasm log record: the category as its scope, the template's values as attributes, and the trace context | Logs |
| Throw a JS error | blDemo.throwError(...) - an uncaught TypeError |
A browser exception event with exception.type, exception.message and exception.stacktrace |
Errors |
| Fetch /api/demo/echo | blDemo.fetch("/api/demo/echo") - a plain fetch |
A browser client span GET, and - because the script added a traceparent - the server's request span and log in the same trace, marked correlated |
Dependencies |
How this works
- Export. The browser script and the WebAssembly app's .NET pipeline both export
OTLP/HTTP with the JSON encoding to this site's own server -
/otlp/v1/traces,/otlp/v1/logsand/otlp/v1/metrics. JSON rather than protobuf so the payloads are readable in your browser's network tab. - Receive. An in-process receiver decodes each request and files every record under
its
session.id, keeping the last few hundred per session in memory. Nothing is ever written to disk or a database, and a session idle for an hour is forgotten. - Attribute. Some records carry no session. Metric points never do (it would multiply
their cardinality by the number of visitors), so they are matched to you through the
service.instance.idof the WebAssembly app that sent them. The server's own request spans and logs go into one shared bucket. - Read back. This page asks the script for your session id
(
BitBelemetry.getContext()) and pollsGET /api/telemetry?sessionId=...for what is new. The server can only be asked for a session id your browser generated, so nobody sees anyone else's clicks. - Correlate. A server span from the shared bucket is shown to you when it shares a
trace id with one of your records - your fetch span's
traceparentmade it that span's child. Include server telemetry shows the rest of the shared bucket too.
Nothing showing up? The feed says so when nothing is granted. Answer the banner, or choose per feature on the consent page. Before you decide, records are held in memory in the page - and they arrive the moment you grant their feature.
The demo's configuration
This is the real setup of this site, copied from its source. Both projects call
AddBitBelemetryServices(DemoTelemetry.ConfigureShared), and each adds its own exporter on top;
every configure callback runs, in order, on the same options.
// Client/Services/DemoTelemetry.cs - called by both projects through AddDemoServices:
// services.AddBitBelemetryServices(DemoTelemetry.ConfigureShared);
public static void ConfigureShared(BelemetryOptions options)
{
options.ServiceName = ServiceName; // "belemetry-demo"
options.ServiceNamespace = ServiceNamespace; // "bitplatform"
// Opt-in: nothing leaves the browser until the reader answers the consent banner. This is
// the library default, written out because the whole consent flow is part of the demo.
options.Consent.Default = BelemetryFeatures.None;
// Short intervals so a click shows up on the Live telemetry page within a couple of
// seconds. The defaults (5 s, 60 s for metrics) are the right ones for a real app.
options.Browser.FlushInterval = TimeSpan.FromSeconds(2);
options.Batch.FlushInterval = TimeSpan.FromSeconds(2);
options.Metrics.ExportInterval = TimeSpan.FromSeconds(10);
// Off by default; on here so the browser page can demonstrate them.
options.Browser.LongTasks = true;
options.Browser.CaptureConsole = true;
// The demo's own instrumentation (the ".NET instrumentation" page's buttons).
options.Traces.Sources.Add(DemoDiagnostics.Name);
options.Metrics.Meters.Add(DemoDiagnostics.Name);
foreach (var url in SelfUrls) options.Browser.IgnoreUrls.Add(url); // "/otlp/", "/api/telemetry"
options.AddProcessor(new DemoNoiseFilter());
}
The WebAssembly client exports its .NET records to its own origin. The endpoint is absolute, because a .NET exporter has no page to resolve a relative URL against:
// Client/Program.cs
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddDemoServices(builder.HostEnvironment.BaseAddress);
builder.Services.AddBitBelemetryServices(options =>
{
options.AddOtlpExporter(builder.HostEnvironment.BaseAddress + "otlp", otlp =>
{
otlp.Protocol = BelemetryOtlpProtocol.HttpJson;
});
});
await builder.Build().RunAsync();
The server renders <BelemetryScript />, so the browser script's exporter comes from
its registration: BrowserEndpoint is relative, so the script posts to whatever origin it
was served from. The server also turns on its own pipeline, which is where the correlated request spans
come from.
// Server/Program.cs
builder.Services.AddDemoServices();
builder.Services.AddBitBelemetryServices(options =>
{
// The server's own pipeline posts to its listening address over loopback; the browser
// script posts to the origin it was served from.
options.AddOtlpExporter(DemoEndpoints.SelfOtlpEndpoint(builder.Configuration), otlp =>
{
otlp.Protocol = BelemetryOtlpProtocol.HttpJson;
otlp.BrowserEndpoint = new Uri("/otlp", UriKind.Relative);
});
// Request spans, logs and metrics of the server, governed by Server.Features.
options.Server.CollectDotnetTelemetry = true;
if (builder.Configuration["ApplicationInsights:ConnectionString"] is { Length: > 0 } connectionString)
{
options.AddAzureMonitorExporter(connectionString);
}
});