question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

HttpContext from an Enricher or a Middleware

See original GitHub issue

Hi everyone,

I’m using .Net Core 3.1 and I’ve read a lot on how to enrich Serilog with HttpContext information. I’ve found more than one way to do it and i would like to have your opinion.

Suppose that I would like to enrich with the logged username. I must have access to the HttpContext. Here’s the 2 ways that I’ve found:

  1. Create a new class UserNameEnricher that receive IHttpContextAccessor in the constructor and add it to the Serilog configuration
  2. Create a new class SerilogUserNameMiddleware and add it to the pipeline in the Configure function

UserNameEnricher

public static class UserNameLoggerConfigurationExtensions
{
    public static LoggerConfiguration WithUserName(this LoggerEnrichmentConfiguration enrichmentConfiguration)
    {
        if (enrichmentConfiguration == null) throw new ArgumentNullException(nameof(enrichmentConfiguration));
        return enrichmentConfiguration.With<UserNameEnricher>();
    }
}

public class UserNameEnricher : ILogEventEnricher
{
    private readonly IHttpContextAccessor _contextAccessor;

    public UserNameEnricher() : this(new HttpContextAccessor())
    {
    }

    public UserNameEnricher(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var userName = _contextAccessor.HttpContext?.User?.Identity?.Name;
        var property = propertyFactory.CreateProperty("UserName", userName);

        logEvent.AddOrUpdateProperty(property);
    }
}

And then, add it to Serilog

public class Program
{
    public static int Main(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();

        // Configure logger
        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(configuration)
            .Enrich.FromLogContext()
            .Enrich.WithUserName()
            .CreateLogger();

        try
        {
            Log.Information("Configuring host...");
            var host = CreateHostBuilder(args).Build();

            Log.Information("Starting host...");
            host.Run();

            Log.Information("Program terminated successfully!");
            return 0;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Program terminated unexpectedly!");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }

    private static IHostBuilder CreateHostBuilder(string[] args)
    {
        var builder = Host.CreateDefaultBuilder(args)
                        .ConfigureWebHostDefaults(webBuilder =>
                        {
                            webBuilder.UseStartup<Startup>();
                            webBuilder.UseSerilog();
                        });

        return builder;
    }        
}

SerilogUserNameMiddleware

public class SerilogUserNameMiddleware
{
    readonly RequestDelegate _next;

    public SerilogUserNameMiddleware(RequestDelegate next)
    {
        if (next == null)
        {
            throw new ArgumentNullException(nameof(next));
        }

        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException(nameof(httpContext));
        }

        var userName = httpContext.User?.Identity?.Name;

        // Push the user name into the log context so that it is included in all log entries
        using (LogContext.PushProperty("UserName", userName))
        {
            await _next(httpContext);
        }            
    }
}

And add it to the pipeline

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();
    app.UseRouting();
    app.UseMiddleware<SerilogUserNameMiddleware>();
    app.UseCookiePolicy();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

So what is the best way to implement the Enricher if we must consider speed, memory allocation, etc. My goal is to add all the HttpRequest information available for the log (headers, querystring, etc).

My opinion would be to use the Enricher since that its seems to be cleaner than the middleware. Also, the code is only executed if the code use the logger. With the middleware, it is executed everytime. But since that many user propose the middleware, i’m a little bit confused.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:16 (8 by maintainers)

github_iconTop GitHub Comments

1reaction
thomaswurtzcommented, Oct 13, 2021

I had problems getting the approach with ILogEventEnricher to work, but later found that you need to add a call to AddHttpContextAccessor() in ConfigureServices to be able to get the correct instance of IHttpContextAccessor. Otherwise it will use the one created in the default constructor which will always have HttpContext = null.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();
    ...
}

See: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-5.0#use-httpcontext-from-custom-components

1reaction
alexandrejobincommented, Dec 18, 2019

I’ve made an HttpRequestEnricherMiddleware because I can use async method more easly to read the request body. By using the Serilog Enricher, I had to read it async but synchronise it afterward and I think it is not recommended. But when I read the log, i’m getting a $type": "HttpContextInfo. Is it possible to get ride of it?

/// <summary>
/// Add the current http request to the Serilog context.
/// </summary>
public class HttpRequestEnricherMiddleware
{
    private const string HttpRequestPropertyName = "HttpRequest";
    readonly RequestDelegate _next;

    public HttpRequestEnricherMiddleware(RequestDelegate next)
    {
        if (next == null)
        {
            throw new ArgumentNullException(nameof(next));
        }

        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException(nameof(httpContext));
        }

        var httpRequestInfo = await GetHttpRequestInfoAsync(httpContext);

        // Push the user name into the log context so that it is included in all log entries
        using (LogContext.PushProperty(HttpRequestPropertyName, httpRequestInfo, true))
        {
            await _next(httpContext);
        }
    }

    private async Task<HttpContextInfo> GetHttpRequestInfoAsync(HttpContext httpContext)
    {
        var httpRequest = httpContext?.Request;

        if (httpRequest == null)
        {
            return null;
        }

        string body = "";

        if (httpRequest.ContentLength.HasValue && httpRequest.ContentLength > 0)
        {
            httpRequest.EnableBuffering();

            using (var reader = new StreamReader(httpRequest.Body, Encoding.UTF8, false, 1024, true))
            {
                body = await reader.ReadToEndAsync();
            }

            // Reset the request body stream position so the next middleware can read it
            httpRequest.Body.Position = 0;
        }

        return new HttpContextInfo()
        {
            Host = httpRequest.Host.ToString(),
            Path = httpRequest.Path,
            Scheme = httpRequest.Scheme,
            Method = httpRequest.Method,
            Protocol = httpRequest.Protocol,
            QueryString = httpRequest.Query.ToDictionary(x => x.Key, y => y.Value.ToString()),
            Headers = httpRequest.Headers
                        .Where(x => x.Key != "Cookie") // remove Cookie from header since it is analysed separatly
                        .ToDictionary(x => x.Key, y => y.Value.ToString()),
            Cookies = httpRequest.Cookies.ToDictionary(x => x.Key, y => y.Value.ToString()),
            Body = body
        };
    }
}

internal class HttpContextInfo
{
    public string Host { get; set; }
    public string Path { get; set; }
    public string Scheme { get; set; }        
    public string Method { get; set; }        
    public string Protocol { get; set; }        
    public Dictionary<string, string> QueryString { get; set; }        
    public Dictionary<string, string> Headers { get; set; }        
    public Dictionary<string, string> Cookies { get; set; }        
    public string Body { get; set; }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

How can I get the current HttpContext in a SeriLog Sink?
The idea is to register a custom middleware that will add all the contextual information to the current LogContext during the request. For...
Read more >
Access HttpContext in ASP.NET Core
The HttpContext instance is accessible by middleware and app frameworks such as Web API controllers, Razor Pages, SignalR, gRPC, and more. For ...
Read more >
Serilog.Enrichers.AspNetCore.HttpContext 1.0.1
Serilog Enricher to Set Properties From AspNetCore HttpContext to Serilog LogContext.
Read more >
Serilog Best Practices - Ben Foster
Log Context enricher - Built in to Serilog, this enricher ensures any properties added to the Log Context are pushed into log events. ......
Read more >
How to create a Serilog enricher that adds HTTP ...
A Serilog enricher gives us the ability to dynamically add useful, contextual properties to our logs. It is one of those things that...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found