2

I have a .NET 6 application with a Scoped service that holds data from the context running, like trace id, correlation id, etc., I want to use this data to enrich my log that is a structured json using Serilog lib overriding the ILoggerFactory.

I have a custom log Provider used in the Serilog LoggerConfiguration to format the text to json and inject data from this Scoped service, the problem is that logger is a singleton and I can not access this scoped object inside it.

I can not use IHttpContextAccessor because usually the code running on it is a BackgroundService to read some Queue's not an HTTP Request, and I can not use the ServiceProvider to create a scope, because I would lose the real current scope.

internal class CustomFormatter : ITextFormatter
    {
        private readonly ILogProvider _provider;
        private readonly IServiceProvider _serviceProvider;

        static readonly JsonSerializerSettings _jsonOptions = new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore,
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        };

        public CustomFormatter(ILogProvider provider, IServiceProvider serviceProvider)
        {
            _provider = provider;
            _serviceProvider = serviceProvider;
        }

        public void Format(LogEvent logEvent, TextWriter output)
        {
            var logObj = _provider.Create(logEvent);

            // Error in this line, since ICustomContext is a scoped object
            var customContext = _serviceProvider.GetRequiredService<ICustomContext>();

            logObj.CorrelationId = customContext.correlationId;

            output.WriteLine(JsonConvert.SerializeObject(logObj, _jsonOptions));
        }
    }
3
  • You should provide details about your application configuration, there should be something about ILoggerFactory and call to AddProvider which will eventually sort out how to correctly reference IHttpContextAccessor Commented Sep 22, 2022 at 18:55
  • documentation: learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/… exact approach for .net6 is perhaps a bit different than what I outlined above Commented Sep 22, 2022 at 19:01
  • If ComponentB cannot "live" longer than ComponentA, then ComponentA cannot reference it. An alternative is to create a DecoratedComponent which references both ComponentA and ComponentB, does whatever you need, and only has the lifetime of ComponentB Commented Sep 22, 2022 at 19:39

1 Answer 1

1

You need to use Log enrichment (see https://github.com/serilog/serilog/wiki/Enrichment) to push the properties you are interested in onto the LogEvent.

You mention a HTTP request so I assume you can use the request middleware to achieve this.

Your scoped service registration,

services.AddScoped<IMyService, MyService>();

The middleware registration,

public static void Configure(this WebApplication app)
{            
    ...

    app.UseMiddleware<MyMiddleware>();    
}

Your middlware class

public class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext, IMyService service)
    {
        using (LogContext.PushProperty("CorrelationId", service.CorrelationId))
        {
            await next(context);
        }
    }
}

In your CustomFormatter,

if (logEvent.Properties.TryGetValue("CorrelationId", out LogEventPropertyValue logEventPropertyValue))
{
    // Use the CorrelationId here in logEventPropertyValue
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.