Skip to content

Serilog Extensions

Mark Lauter edited this page Jun 13, 2026 · 3 revisions

Serilog Extensions

MSL.Plumber.Serilog.Extensions adds one middleware that emits a single structured Serilog event when a request completes — carrying the request id, elapsed time, trace and span ids, and anything you enrich onto Serilog's DiagnosticContext during the request. It's the request-logging line you'd otherwise hand-write at the top of every pipeline.

This page covers wiring it up and the logging behavior. The full options surface lives on RequestLoggerOptions.

Install

dotnet add package MSL.Plumber.Serilog.Extensions

The package targets .NET 10 and references the core MSL.Plumber.Pipeline package.

Wiring it up

The package is two calls that split across the builder and the handler: AddSerilogRequestLogging registers what the middleware needs, and UseSerilogRequestLogging adds the middleware to the pipeline.

using Plumber;
using Plumber.Serilog;
using Serilog;
using Serilog.Formatting.Compact;

using var handler = RequestHandlerBuilder.Create<MyRequest, MyResponse>()
    .ConfigureServices((services, _) =>
        services.AddSerilogRequestLogging<MyRequest, MyResponse>(
            logger => logger.WriteTo.Console(new CompactJsonFormatter())))
    .Build()
    .UseSerilogRequestLogging()
    .Use<ValidationMiddleware>()
    .Use<ProcessingMiddleware>();

var response = await handler.InvokeAsync(request);

AddSerilogRequestLogging<TRequest, TResponse> registers Serilog's ILogger and DiagnosticContext and the RequestLoggerOptions infrastructure in one call — which is what makes the parameterless UseSerilogRequestLogging() self-sufficient. You build the logger inline through the Action<LoggerConfiguration>, so there's no static Log.Logger to set up. In the CreateBuilder/Configure pattern, AddSerilogRequestLogging belongs in CreateBuilder and UseSerilogRequestLogging in Configure.

The middleware needs three things in the provider — ILogger, DiagnosticContext, and IOptions<RequestLoggerOptions<TRequest, TResponse>>. AddSerilogRequestLogging is the one-call convenience that registers all three; if you already configure Serilog through a host or your own AddSerilog, register the options with AddOptions/Configure and the middleware resolves the rest.

The request type must be a reference type — the extension methods constrain TRequest : class.

Where it sits in the pipeline

Register UseSerilogRequestLogging first, as the outermost middleware. It wraps the rest of the pipeline in a try/catch and logs once on the way out, so registering it first lets it observe the whole pipeline's outcome — including exceptions thrown by anything downstream. Elapsed time comes from context.Elapsed, measured from the start of the request (see Request lifecycle), so the timing is whole-request regardless of placement.

What it logs

One event per request, when the pipeline returns or throws:

  • A success completes at the configured LogLevel (default Information).
  • A failure completes at Error, with the exception attached, then rethrows unless you set ThrowOnException to false.

Every event carries the message-template properties (by default RequestId from context.Id and Elapsed from context.Elapsed.TotalMilliseconds), plus the current Activity trace id and span id when one is in flight — so request logs line up with distributed traces with no extra work.

Enriching the event

Anything written to the DiagnosticContext during the request attaches to that single completion event. Use EnrichDiagnosticContext to add request- and response-derived properties:

handler.UseSerilogRequestLogging(options =>
{
    options.EnrichDiagnosticContext = (diagnosticContext, context) =>
    {
        diagnosticContext.Set("Request", context.Request);
        diagnosticContext.Set("Response", context.Response);
    };
});

One completion event then carries Request, Response, RequestId, Elapsed, and the trace ids — a single line that describes the whole request rather than a scatter of log statements.

Configuring

Three ways to supply RequestLoggerOptions:

  • At registration — pass configureOptions to AddSerilogRequestLogging, alongside the logger configuration.
  • Inline on the middleware — pass an Action<RequestLoggerOptions<TRequest, TResponse>> to UseSerilogRequestLogging. Inline options take precedence over anything in the container.
  • From the container — register Configure<RequestLoggerOptions<MyRequest, MyResponse>>(...) in ConfigureServices and call the parameterless UseSerilogRequestLogging(). The middleware resolves them as IOptions<T>.

With nothing configured, the middleware runs on the defaults baked into RequestLoggerOptions.

See also

  • RequestLoggerOptions — the full options surface: level, message template, enrichment, exception handling
  • Request lifecycleRequestContext, Elapsed, Id, and the cancellation token the middleware honors
  • MiddlewareUse<T> registration and pipeline ordering
  • Building a pipelineConfigureServices and the builder surface

Clone this wiki locally