-
Notifications
You must be signed in to change notification settings - Fork 0
Diagnostics
MSL.Plumber.Diagnostics adds two middleware that instrument a request as it flows through the pipeline — a tracing span and a set of metrics, emitted through the BCL ActivitySource and Meter primitives. It carries no OpenTelemetry SDK dependency: the middleware records to System.Diagnostics, and you bring the OpenTelemetry SDK to collect and export.
This page covers wiring it up and what it emits. The full options surface lives on Diagnostics options.
dotnet add package MSL.Plumber.DiagnosticsThe package targets .NET 10 and references the core MSL.Plumber.Pipeline package. It depends on no OpenTelemetry package — instrumentation flows through the in-box System.Diagnostics.ActivitySource and System.Diagnostics.Metrics.Meter.
Registration splits across the builder and the handler, the same shape as the Serilog extension: AddPlumberDiagnostics registers the options the middleware resolve, and the Use* methods add the middleware to the pipeline.
using Plumber;
using Plumber.Diagnostics;
using var handler = RequestHandlerBuilder.Create<MyRequest, MyResponse>()
.ConfigureServices((services, _) =>
services.AddPlumberDiagnostics<MyRequest, MyResponse>())
.Build()
.UseRequestDiagnostics()
.Use<ValidationMiddleware>()
.Use<ProcessingMiddleware>();
var response = await handler.InvokeAsync(request);Three Use* methods register the middleware:
-
UseRequestTracing— the span only. -
UseRequestMetrics— the metrics only. -
UseRequestDiagnostics— both, tracing then metrics.
AddPlumberDiagnostics<TRequest, TResponse> registers the IOptions infrastructure for both option types, which is what makes the parameterless Use* overloads self-sufficient. In the CreateBuilder/Configure pattern, AddPlumberDiagnostics belongs in CreateBuilder and the Use* call in Configure.
The request type must be a reference type — the extension methods constrain TRequest : class.
The middleware emit on an ActivitySource and Meter both named Plumber.Diagnostics, exposed as PlumberDiagnostics.ActivitySourceName and PlumberDiagnostics.MeterName. Nothing is exported until an OpenTelemetry SDK provider subscribes to them. In a host-free app, build the providers eagerly so their listeners are active before the first request:
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using Plumber.Diagnostics;
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(PlumberDiagnostics.ActivitySourceName)
.AddConsoleExporter()
.Build();
using var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter(PlumberDiagnostics.MeterName)
.AddConsoleExporter()
.Build();In a hosted app, AddSource(PlumberDiagnostics.ActivitySourceName) and AddMeter(PlumberDiagnostics.MeterName) on the host's AddOpenTelemetry().WithTracing(...)/.WithMetrics(...) do the same. Swap the console exporter for OTLP or any other to ship the data on.
Register the Use* call first, as the outermost middleware. The span and metrics then wrap the whole pipeline, so the span duration and the recorded request count cover every downstream step, including exceptions thrown beneath. Elapsed time on the metrics comes from context.Elapsed, measured from the start of the request (see Request lifecycle).
The tracing middleware starts one Activity per request, named by OperationName (default Plumber.HandleRequest):
- Default tags
request.id,request.type,request.elapsed_ms, andresponse.type, controlled byAddDefaultAttributes. - Status
Okon success;Errorwith the exception recorded as a span event on failure, controlled byRecordException. - A failure rethrows unless you set
ThrowOnExceptiontofalse.
When no listener samples the source, StartActivity returns null and the middleware falls straight through — zero overhead when tracing is off.
The metrics middleware records three instruments, each tagged with request.type:
-
plumber.requests.count— a counter of requests processed. -
plumber.requests.duration— a histogram of request duration in milliseconds, taggedsuccess. -
plumber.requests.errors— a counter of failed requests.
The default instruments are controlled by AddDefaultMetrics; a failure rethrows unless ThrowOnException is false.
A cancelled or timed-out request arrives at the middleware as an OperationCanceledException — Plumber surfaces both caller cancellation and a pipeline timeout that way. Both middleware treat it as an expected outcome, not a defect:
- the span stays
Unset, with no exception event; - the metrics keep it out of
plumber.requests.errorsand the duration histogram, and theRecordCustomMetricshook does not fire; - it still counts in
plumber.requests.countas an attempt.
ThrowOnException governs whether the cancellation propagates, the same as any other exception. The caller still owns timeout handling — Plumber rethrows a timeout to the caller as TimeoutException.
Add to the span through EnrichSpan, and record your own measurements through RecordCustomMetrics:
handler.UseRequestDiagnostics(
tracing => tracing.EnrichSpan = (activity, context) =>
activity.SetTag("tenant.id", context.Request.TenantId),
metrics => metrics.RecordCustomMetrics = (context, succeeded) =>
myCounter.Add(1, new KeyValuePair<string, object?>("succeeded", succeeded)));EnrichSpan runs against the live Activity before the span closes. RecordCustomMetrics runs once per request in a finally, with a bool that reports whether the request succeeded.
Three ways to supply the options, matching the Serilog extension:
- At registration — pass
configureTracingandconfigureMetricstoAddPlumberDiagnostics. - Inline on the middleware — pass an
Action<RequestTracingOptions<TRequest, TResponse>>orAction<RequestMetricsOptions<TRequest, TResponse>>to theUse*method. Inline options take precedence over anything in the container. - From the container — register
Configure<RequestTracingOptions<MyRequest, MyResponse>>(...)inConfigureServicesand call the parameterlessUse*overload. The middleware resolves them asIOptions<T>.
With nothing configured, the middleware run on the defaults baked into RequestTracingOptions and RequestMetricsOptions.
- Diagnostics options — the full options surface for tracing and metrics
- Serilog Extensions — per-request structured logging, which stamps the same trace and span ids
-
Request lifecycle —
RequestContext,Elapsed,Id, and the cancellation token the middleware honor -
Middleware —
Use<T>registration and pipeline ordering
Documents Plumber v4.x · Repository · MIT License · Report an issue
Getting Started
Pipeline (core)
Testing
Serilog Extensions
Diagnostics
Recipes
- AWS Lambda — API Gateway
- AWS Lambda — SQS
- Azure Functions — HTTP
- SQS polling console
- ASP.NET Core integration
- BackgroundService worker
- Webhook receiver
- Multi-command CLI
- File watcher
- Configuration reload
Repo · NuGet · NuGet — Testing · NuGet — Serilog · NuGet — Diagnostics