Logging the selected Endpoint Name with Serilog
Tags: aspnetcore, logging
Published: 2020-01-12 17:16:17

---
title: Logging the selected Endpoint Name with Serilog
published: true
date: 2020-01-07 10:00:00 UTC
tags: ASP.NET Core,Logging
canonical_url: https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-logging-the-selected-endpoint-name-with-serilog/
series: Using Serilog.AspNetCore in ASP.NET Core 3.0
---

 ![Logging the selected Endpoint Name with Serilog](https://andrewlock.net/content/images/2019/serilog_banner2.png)

In [my previous post](https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-reducing-log-verbosity/) I described how to configure Serilog's RequestLogging middleware to create "summary" logs for each request, to replace the 10 or more logs you get from ASP.NET Core by default.

In this post I show how you can add additional metadata to Serilog's summary request log, such as the Request's hostname, the Response's content-type, or the selected Endpoint Name from [the endpoint routing middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.0#endpoint-routing-differences-from-earlier-versions-of-routing) used in ASP.NET Core 3.0.

## ASP.NET Core infrastructure logs are verbose, but have more details by default

As I showed in [my previous post](https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-reducing-log-verbosity/), in the Development environment the ASP.NET Core infrastructure generates 10 log messages for a request to a RazorPage handler:

![Image of many infrastructure logs without using Serilog request logging](https://andrewlock.net/content/images/2019/logs_before_serilog.png)

By switching to Serilog's `RequestLoggingMiddleware` that comes with the [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore) NuGet package you can reduce this to a single log message:

![Image of the summary log generated by Serilog's request logging](https://andrewlock.net/content/images/2019/logs_after_serilog.png)

> All the images of logs used in this post are [taken using Seq](https://datalust.co/seq), which is an excellent tool for viewing structured logs

Clearly the original set of logs are more verbose, and a large part of that is not especially useful information. However, if you take the original 10 logs as a whole, they do record some additional fields in the structure log template when compared to the Serilog summary log.

Additional fields logged by the ASP.NET Core infrastructure which are _not_ logged by Serilog include:

- Host (`localhost:5001`)
- Scheme (`https`)
- Protocol (`HTTP/2`)
- QueryString (`test=true`)
- EndpointName (`/Index`)
- HandlerName (`OnGet`/`SerilogRequestLogging.Pages.IndexModel.OnGet`)
- ActionId (`1fbc88fa-42db-424f-b32b-c2d0994463f1`)
- ActionName (`/Index`)
- RouteData (`{page = "/Index"}`)
- ValidationState (`True`/`False`)
- ActionResult (`PageResult`)
- ContentType (`text/html; charset=utf-8`)

I'd argue that _some_ of these points would definitely be useful to include in the summary log message. For example, if your app is bound to multiple host names then `Host` is definitely important to log. `QueryString` is potentially another useful field. `EndpointName`/`HandlerName`, `ActionId`, and `ActionName` seem slightly less critical, given that you should be able to deduce those given the request path, but logging them explicitly will help you catch bugs, as well as make it easier to filter all requests to a specific action.

Broadly speaking, you can separate these properties into two categories:

- _Request/Response_ properties e.g. `Host`, `Scheme`, `ContentType`, `QueryString`, `EndpointName`
- _MVC/RazorPages-related_ properties e.g. `HandlerName`, `ActionId`, `ActionResult` etc

In this post i'll show how you can add the first of these categories, properties related to the Request/Response, and in the next post I'll show how you can add MVC/RazorPages-based properties.

## Adding additional data to the Serilog request log

I showed how to add Serilog request logging to your application [in my previous post](https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-reducing-log-verbosity/), so I'm not going to cover that again here. For now, I'll assume you've already set that up, and you have a `Startup.Configure` method that looks something like this:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ... Error handling/HTTPS middleware
    app.UseStaticFiles();

    app.UseSerilogRequestLogging(); // <-- Add this line

    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

```

The `UseSerilogRequestLogging()` extension method adds the Serilog `RequestLoggingMiddleware` to the pipeline. You can also call an overload to [configure an instance of `RequestLoggingOptions`](https://github.com/serilog/serilog-aspnetcore/blob/ffed9d231aefc3de7c13a03a570fb45c326632b0/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs). This class has several properties that let you customise how the request logger generates the log statements:

```csharp
public class RequestLoggingOptions
{
    public string MessageTemplate { get; set; }
    public Func<HttpContext, double, Exception, LogEventLevel> GetLevel { get; set; }
    public Action<IDiagnosticContext, HttpContext> EnrichDiagnosticContext { get; set; }
}

```

The `MessageTemplate` property controls how the log is rendered to a string and `GetLevel` lets you control whether a given log should be level `Debug`/`Info`/`Warning` etc. The property we're interested in here is the `EnrichDiagnosticContext` property.

When set, this `Action<>` is executed by Serilog's middleware when it generates a log message. It's run _just before_ the log is written, which means it runs _after_ the middleware pipeline has executed. For example, in the image below ([taken from my book ASP.NET Core in Action](https://www.manning.com/books/asp-dot-net-core-in-action?a_aid=aspnetcore-in-action&a_bid=5b1b11eb)) the log is written at step 5, when the response "passes back through" the middleware pipeline:

![Example of a middleware pipeline](https://andrewlock.net/content/images/2017/07/03_01.png)

The fact that the log is written _after_ the pipeline has processed means a couple of things:

- We can access properties of the _Response_, such as the status code, elapsed time, or the content type
- We can access _Features_ that are set by middleware _later_ in the pipeline, for example the `IEndpointFeature` set by the `EndpointRoutingMiddleware` (added via `UseRouting()`).

In the next section, I provide a helper function that adds all the "missing" properties to the Serilog request log message.

## Setting values in IDiagnosticContext

_Serilog.AspNetCore_ adds the interface `IDiagnosticContext` to the DI container as a singleton, so you can access it from any of your classes. You can then use it to attach additional properties to the request log message by calling `Set()`.

For example, [as shown in the documentation](https://github.com/serilog/serilog-aspnetcore#request-logging), you can add arbitrary values from an action method:

```csharp
public class HomeController : Controller
{
    readonly IDiagnosticContext _diagnosticContext;
    public HomeController(IDiagnosticContext diagnosticContext)
    {
        _diagnosticContext = diagnosticContext;
    }

    public IActionResult Index()
    {
        // The request completion event will carry this property
        _diagnosticContext.Set("CatalogLoadTime", 1423);
        return View();
    }
}

```

The resulting summary log will then include the property `CatalogLoadTime`.

We essentially use exactly the same approach to customise the `RequestLoggingOptions` used by the middleware, by setting values on the provided `IDiagnosticContext` instance. The static helper class below retrieves values from the current `HttpContext` and sets them if they're available.

```csharp
public static class LogHelper 
{
    public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
    {
        var request = httpContext.Request;

        // Set all the common properties available for every request
        diagnosticContext.Set("Host", request.Host);
        diagnosticContext.Set("Protocol", request.Protocol);
        diagnosticContext.Set("Scheme", request.Scheme);

        // Only set it if available. You're not sending sensitive data in a querystring right?!
        if(request.QueryString.HasValue)
        {
            diagnosticContext.Set("QueryString", request.QueryString.Value);
        }

        // Set the content-type of the Response at this point
        diagnosticContext.Set("ContentType", httpContext.Response.ContentType);

        // Retrieve the IEndpointFeature selected for the request
        var endpoint = httpContext.GetEndpoint();
        if (endpoint is object) // endpoint != null
        {
            diagnosticContext.Set("EndpointName", endpoint.DisplayName);
        }
    }
}

```

The helper function above retrieves values from the Request, from the Response, and from features set by other middleware (Endpoint Name). You could extend this to add other values from the request as appropriate.

You can register the helper by setting the `EnrichDiagnosticContext` property when you call `UseSerilogRequestLogging` in your `Startup.Configure()` method:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ... Other middleware

    app.UseSerilogRequestLogging(opts
        => opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest);

    // ... Other middleware
}

```

Now when you make requests, you will see all the extra properties added to your Serilog structured logs:

![Log message from Seq showing the additional properties](https://andrewlock.net/content/images/2019/logs_after_serilog_endriched.png)

You can use this approach whenever you have values that are generally available to the middleware pipeline via the current `HttpContext`. The exception to that is MVC-specific features which are "internal" to the MVC middleware, like action names, or RazorPage handler names. In the next post I'll show how you can also add those to your Serilog request log.

## Summary

By default, when you replace the ASP.NET Core infrastructure logging with Serilog's request logging middleware, you lose some information compared to the default logging configuration for Development environments. In this post I show how you can customise Serilog's `RequestLoggingOptions` to add these additional properties back in.

The method for doing so is very simple - you have access to the `HttpContext` so you can retrieve any values it has available and can set them as properties on the provided `IDiagnosticContext`. These are added as extra properties to the structured log generated by Serilog. In the next post I'll show how you can add MVC-specific values to the request log.