BlazorAmpowering.Observability
1.1.0
dotnet add package BlazorAmpowering.Observability --version 1.1.0
NuGet\Install-Package BlazorAmpowering.Observability -Version 1.1.0
<PackageReference Include="BlazorAmpowering.Observability" Version="1.1.0" />
<PackageVersion Include="BlazorAmpowering.Observability" Version="1.1.0" />
<PackageReference Include="BlazorAmpowering.Observability" />
paket add BlazorAmpowering.Observability --version 1.1.0
#r "nuget: BlazorAmpowering.Observability, 1.1.0"
#:package BlazorAmpowering.Observability@1.1.0
#addin nuget:?package=BlazorAmpowering.Observability&version=1.1.0
#tool nuget:?package=BlazorAmpowering.Observability&version=1.1.0
BlazorAmpowering.Observability
Codeless OpenTelemetry instrumentation for Blazor Server applications.
Add full observability (traces, metrics, profiling) to your Blazor Server app with minimal code changes — no manual span creation required in your business logic.
What is instrumented automatically
| Signal | What is traced |
|---|---|
| HTTP requests | Incoming ASP.NET Core requests, outgoing HttpClient calls |
| SignalR circuits | Connection, disconnection, circuit lifetime |
| Component lifecycle | OnInitialized, OnParametersSet, OnAfterRender, user events (clicks, inputs) |
| SPA navigation | Every route change |
| Business services | Every method call via AOP (Castle DynamicProxy) |
| Runtime metrics | GC, thread pool, JIT (.NET runtime metrics) |
Installation
dotnet add package BlazorAmpowering.Observability
EF Core tracing is available as a separate package —
BlazorAmpowering.Observability.EntityFrameworkCore(pre-release):dotnet add package BlazorAmpowering.Observability.EntityFrameworkCore --prerelease
Configuration
1. appsettings.json
{
"OpenTelemetry": {
"Enabled": true,
"ApplicationName": "my-app",
"OtlpEndpoint": "http://localhost:4318/v1/traces",
"OtlpMetricsEndpoint": "http://localhost:4318/v1/metrics",
"OtlpHeaders": "",
"OtlpMetricsHeaders": "",
"SamplingProbability": 1.0
}
}
| Key | Default | Description |
|---|---|---|
Enabled |
false |
Enable or disable the entire OTel pipeline |
ApplicationName |
"BlazorApp" |
Service name displayed in Grafana / Tempo |
OtlpEndpoint |
http://localhost:4318/v1/traces |
OTLP endpoint for traces |
OtlpHeaders |
(empty) | Auth header for traces: Authorization=Basic xxx |
OtlpMetricsEndpoint |
derived from OtlpEndpoint |
OTLP endpoint for metrics |
OtlpMetricsHeaders |
derived from OtlpHeaders |
Auth header for metrics (required on Grafana Cloud) |
SamplingProbability |
0.1 |
Sampling rate — 1.0 in dev, 0.1 (10%) in prod |
Grafana Cloud
{
"OpenTelemetry": {
"Enabled": true,
"ApplicationName": "my-app",
"OtlpEndpoint": "https://otlp-gateway-prod-eu-west-2.grafana.net/otlp/v1/traces",
"OtlpHeaders": "Authorization=Basic <base64(gatewayInstanceId:apiToken)>",
"OtlpMetricsEndpoint": "https://prometheus-prod-xx-prod-eu-west-2.grafana.net/otlp/v1/metrics",
"OtlpMetricsHeaders": "Authorization=Basic <base64(prometheusInstanceId:apiToken)>",
"SamplingProbability": 0.1
}
}
Traces endpoint: use the OTLP Gateway (
otlp-gateway-prod-<zone>.grafana.net), not the Tempo direct URL.
The instance ID is shown in Grafana Cloud → Stack → Tempo → "OTLP Endpoint" section.
Local Docker stack
{
"OpenTelemetry": {
"Enabled": true,
"ApplicationName": "my-app",
"OtlpEndpoint": "http://localhost:4318/v1/traces",
"SamplingProbability": 1.0
}
}
Pyroscope (Linux in-process profiler)
Add a Pyroscope section to enable CPU and memory profiling. The native profiler must be loaded via CORECLR_* environment variables — see the Pyroscope profiling section below.
{
"Pyroscope": {
"Enable": true,
"EnableAllocationTracking": false,
"EnableContentionTracking": false,
"EnableExceptionTracking": false
}
}
| Key | Default | Description |
|---|---|---|
Enable |
false |
Enable CPU profiling |
EnableAllocationTracking |
false |
Track memory allocations (adds overhead) |
EnableContentionTracking |
false |
Track lock contention |
EnableExceptionTracking |
false |
Track thrown exceptions |
2. Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents(); // if hosting a Blazor WASM client
// Web API controllers — one span per action, reads X-Blazor-* headers from WASM automatically
builder.Services
.AddControllers()
.AddApiObservability();
// OpenTelemetry pipeline (traces + metrics)
builder.AddBlazorTelemetry();
// Pyroscope CPU profiler — no-op if native profiler is not loaded
builder.AddBlazorProfiler();
// Circuit handler + navigation instrumentation
builder.Services.AddBlazorObservability();
var app = builder.Build();
// ...
app.Run();
3. Pyroscope profiling
AddBlazorProfiler() is a no-op when the native profiler is not loaded. To activate it, the CLR must be started with the Pyroscope native profiler injected via environment variables.
Platform support
Linux — native in-process profiler (Pyroscope.Profiler.Native.so). Profiles are sent directly from the app process.
Windows / macOS — native profiler not supported. Use the provided scripts which capture profiles withdotnet-traceand push them to Pyroscope in pprof format.Three ready-to-use scripts are bundled in the NuGet package under
scripts/:
run-with-profiling-windows.ps1run-with-profiling-macos.shrun-with-profiling-linux.sh
Required environment variables (Linux)
Download the native profiler from grafana/pyroscope-dotnet releases and extract it to observability/profiler/.
# Load the native profiler into the CLR
export CORECLR_ENABLE_PROFILING=1
export CORECLR_PROFILER={BD1A650D-AC5D-4896-B64F-D6FA25D6B26A}
export CORECLR_PROFILER_PATH=./observability/profiler/Pyroscope.Profiler.Native.so
# Pyroscope server (read by the native profiler — cannot come from appsettings.json)
export PYROSCOPE_APPLICATION_NAME=my-app
export PYROSCOPE_SERVER_ADDRESS=http://localhost:4040
# Grafana Cloud Pyroscope (instead of local server)
export PYROSCOPE_SERVER_ADDRESS=https://profiles-prod-XXX.grafana.net
export PYROSCOPE_BASIC_AUTH_USER=<pyroscopeInstanceId>
export PYROSCOPE_BASIC_AUTH_PASSWORD=<apiToken>
dotnet run --project MyApp.csproj
PYROSCOPE_APPLICATION_NAMEandPYROSCOPE_SERVER_ADDRESSmust be set as environment variables — the native profiler reads them at CLR startup before any managed code runs.
Dynamic tags added automatically
AddBlazorProfiler() attaches the following tags to every profile sent to Pyroscope:
| Tag | Value |
|---|---|
environment |
IHostEnvironment.EnvironmentName (e.g. Production) |
host |
Environment.MachineName |
version |
Assembly version |
4. Component instrumentation
Add to Components/_Imports.razor to instrument all components in the folder:
@using BlazorAmpowering.Observability.Components
@inherits InstrumentedComponentBase
InstrumentedComponentBaseuses the template method pattern —OnInitializedAsyncandOnParametersSetAsyncaresealed.
Override theCorevariants instead:
| Override this | Not this |
|---|---|
OnInitializedCoreAsync() |
OnInitializedAsync() |
OnParametersSetCoreAsync() |
OnParametersSetAsync() |
OnAfterRenderCoreAsync(bool) |
OnAfterRenderAsync(bool) |
@code {
protected override async Task OnInitializedCoreAsync()
{
data = await MyService.GetDataAsync();
}
}
For a custom span inside a component:
@code {
private async Task LoadData()
{
var result = await TraceOperationAsync(
"LoadData.ApiCall",
() => MyService.GetDataAsync(),
tags: new() { ["api.endpoint"] = "/data" });
}
}
5. Navigation tracing
Inject NavigationInstrumentation in MainLayout.razor — injection alone activates route change tracing:
@using BlazorAmpowering.Observability.Services
@inject NavigationInstrumentation NavInstrumentation
6. Automatic service tracing (AOP)
Replace AddScoped/Singleton/Transient with the traced variants to automatically create a span for every method call.
Requires registration via an interface (Castle DynamicProxy limitation):
// Every method call generates a span automatically
builder.Services.AddTracedScoped<IMyService, MyService>();
builder.Services.AddTracedSingleton<IMyService, MyService>();
builder.Services.AddTracedTransient<IMyService, MyService>();
7. Web API controller tracing
AddApiObservability() registers an action filter that automatically creates a child span for every controller action:
builder.Services
.AddControllers()
.AddApiObservability();
// Disable parameter capture in production (sensitive data)
builder.Services
.AddControllers()
.AddApiObservability(options =>
{
options.CaptureActionParameters = false;
});
Each span includes:
| Tag | Example |
|---|---|
controller |
Products |
action |
AddProduct |
http.method |
POST |
http.route |
api/products |
http.status_code |
201 |
action.param.* |
primitive action parameters (if CaptureActionParameters = true) |
When called from a Blazor WebAssembly client (via BlazorAmpowering.Observability.WebAssembly), the span is also enriched automatically with blazor.component, blazor.method, blazor.event, and client.latency_ms from the request headers.
8. Manual business context enrichment
using BlazorAmpowering.Observability.Services;
using var activity = BusinessContextEnricher.StartBusinessOperation("Order.Place");
BusinessContextEnricher.EnrichWithUserContext(activity, userId, role: "customer");
BusinessContextEnricher.EnrichWithFeatureContext(activity, "Cart", "Checkout");
activity?.SetTag("order.id", orderId);
ActivitySource constants
Use ActivitySources.* when creating custom spans to stay within the correct trace context:
using BlazorAmpowering.Observability;
private static readonly ActivitySource Source = new(ActivitySources.Services);
| Constant | Value |
|---|---|
ActivitySources.Circuits |
BlazorApp.Circuits |
ActivitySources.Components |
BlazorApp.Components |
ActivitySources.Business |
BlazorApp.Business |
ActivitySources.Services |
BlazorApp.Services |
ActivitySources.Navigation |
BlazorApp.Navigation |
Changelog
v1.1.0
- Built-in Production Code Coverage support —
AddBlazorTelemetry()now registersAddMeter("BlazorAmpowering.Coverage")inside its.WithMetrics()block. Callbuilder.AddProductionCoverage()afterAddBlazorTelemetry()inProgram.cs— no additional OTel configuration required in the entry project.
builder.AddBlazorTelemetry(); // also registers the coverage meter
builder.AddProductionCoverage(); // starts the IHostedService that publishes instruments
- Fix: module initializer — the Fody weaver's module initializer is now wired directly into
<Module>::.cctorvia Mono.Cecil. The previous approach ([ModuleInitializerAttribute]) was never executed because Fody weaves after Roslyn. Methods now correctly go from 0 to N instrumented on the first build. - Fix: deferred Meter creation — the
Meteris now created inCoverageMetricService.StartAsync()(anIHostedService) instead of a static constructor, ensuring the OTelMeterProvideris started before instruments are published.
Disclaimer
These packages are provided free of charge, as is, under the Apache 2.0 license.
No warranty of any kind is provided — correctness, fitness for a particular purpose, or otherwise.
No support is guaranteed. Community issues and pull requests are welcome, but response time is not committed.
Use in production at your own risk.
License
Apache 2.0 — Copyright 2026 Gaetan Delpierre
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Castle.Core (>= 5.2.1)
- OpenTelemetry (>= 1.15.3)
- OpenTelemetry.Api (>= 1.15.3)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.15.3)
- OpenTelemetry.Extensions.Hosting (>= 1.15.3)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.15.2)
- OpenTelemetry.Instrumentation.Http (>= 1.15.1)
- OpenTelemetry.Instrumentation.Runtime (>= 1.15.1)
- Pyroscope (>= 0.15.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.