elastic / ecs-dotnet

https://www.elastic.co/guide/en/ecs-logging/dotnet/current/setup.html
Apache License 2.0
114 stars 58 forks source link

[BUG] HttpContext.User is not used in Serilog EcsTextFormatter #350

Open luuksommers opened 8 months ago

luuksommers commented 8 months ago

ECS integration/library project(s): Elastic.CommonSchema.Serilog 8.6.1, Serilog 3.1.1

ECS .NET assembly version (e.g. 1.4.2): 8.6.1

.NET framework / OS: .NET 6 / Windows 11

Description of the problem, including expected versus actual behavior:

Steps to reproduce:

  1. Use ASP.NET Core with authentication and ClaimsIdentity (e.g. a user is logged in)
  2. The user claims are not included in the ecs output

Cause: The logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(PropertyName, r)); in the HttpContextEnricher doesn't store the raw object, instead the default propertyFactory does a .ToString() of the r-value, which makes the value of the property: Elastic.CommonSchema.Serilog.HttpContextEnricher,Elastic.CommonSchema.Serilog instead or the original object. In the LogEventConverter, the object cannot be retrieved in (for example) the GetUser method

if (e.TryGetScalarPropertyValue(SpecialKeys.HttpContext, out var httpContext)
     && httpContext.Value is HttpContextEnricher.HttpContextEnrichments enriched) // httpContext.Value is a string!!
    return enriched.User; // <-- never returned

This causes that the reported user is always the environment user instead of the user doing the request.

A workaround is to override the enricher:

using Elastic.CommonSchema.Serilog;
using Serilog.Core;
using Serilog.Events;

public class PatchedHttpContextEnricher : HttpContextEnricher, ILogEventEnricher
{
    private readonly RawPropertyFactory _rawPropertyFactory;

    public PatchedHttpContextEnricher(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
    {
        _rawPropertyFactory = new RawPropertyFactory();
    }

    void ILogEventEnricher.Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        base.Enrich(logEvent, _rawPropertyFactory);
    }

    private class RawPropertyFactory : ILogEventPropertyFactory
    {
        public LogEventProperty CreateProperty(
            string name,
            object? value,
            bool destructureObjects = false)
        {
            return new LogEventProperty(name, new ScalarValue(value));
        }
    }
}
builder.Host.UseSerilog((ctx, config) =>
{
    // Ensure HttpContextAccessor is accessible
    var httpAccessor = ctx.Configuration.Get<HttpContextAccessor>();

    config
        .ReadFrom.Configuration(ctx.Configuration)
        //.Enrich.WithEcsHttpContext(httpAccessor) <-- this is the unpatched code
        .Enrich.With(new PatchedHttpContextEnricher(httpAccessor));
});
fetters5 commented 8 months ago

Thanks I also ran into this issue and this helped me get httpContext information

One minor change was that for me the following line in the RawPropertyFactory has to be

return new LogEventProperty(name, new ScalarValue(value));

luuksommers commented 7 months ago

Thanks I also ran into this issue and this helped me get httpContext information

One minor change was that for me the following line in the RawPropertyFactory has to be

return new LogEventProperty(name, new ScalarValue(value));

You're right, I've updated the original post with your comment.