BlazorAmpowering.Observability 1.0.0

dotnet add package BlazorAmpowering.Observability --version 1.0.0
                    
NuGet\Install-Package BlazorAmpowering.Observability -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BlazorAmpowering.Observability" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BlazorAmpowering.Observability" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="BlazorAmpowering.Observability" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BlazorAmpowering.Observability --version 1.0.0
                    
#r "nuget: BlazorAmpowering.Observability, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BlazorAmpowering.Observability@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BlazorAmpowering.Observability&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=BlazorAmpowering.Observability&version=1.0.0
                    
Install as a Cake Tool

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 with dotnet-trace and push them to Pyroscope in pprof format.

Three ready-to-use scripts are bundled in the NuGet package under scripts/:

  • run-with-profiling-windows.ps1
  • run-with-profiling-macos.sh
  • run-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_NAME and PYROSCOPE_SERVER_ADDRESS must 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

InstrumentedComponentBase uses the template method patternOnInitializedAsync and OnParametersSetAsync are sealed.
Override the Core variants 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

License

Apache 2.0 — Copyright 2026 Gaetan Delpierre

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 29 5/15/2026