Search code examples
c#.netasp.net-coredependency-injectionhangfire

Serilog logging in background task


I have an ASP.NET 6 API project and having an issue with logging from methods that are run by a hangfire background job.

I setup Serilog just like this video

It works great in the controller or any other class/method that is called from the controller (i.e. same thread as the incoming request). Simply using Dependency Injection from the constructor and the logger object is initialized just fine.

public class MyController : ControllerBase
{
    private readonly ILogger<MyController> _logger;

    public MyController(ILogger<MyController> logger)
    {
        _logger = logger;
    }

    [HttpPost]
    public async Task<ActionResult> MyActionMethod([FromBody] RequestBody request)
    {
        _logger.LogInformation("This works fine");

        var jobId = BackgroundJob.Enqueue<JobHandler>(x =>
                handler.Main(param1, param2, param3);

        return Ok($"Job dispatched successfully, id: {jobId}");
    }

The problem lies when trying to log from within the code that is being run by the background job. Using DI doesn't resolve the logger object and always returns null.

I have a LogService that I use in various parts of the project, and logger always resolves to null.

public class LogService
{
    private readonly ILogger<LogService> _logger;

    public LogService(ILogger<LogService> logger)
    {
        _logger = logger;
    }

    public void LogSolverStatus(RoutingModel model)
    {
        string status = model.GetStatus().ToString();
        //Console.WriteLine($"Done Solving, Solution Status: {status}");

        // This doesn't work because _logger is null and throws an exception
        _logger.LogInformation("Done Solving, Solution Status: {status}", status);
    }

One solution is passing dependencies when firing the background job.

Is there a better/easier solution?

I understand from this SO thread that the logger is supposed to be registered as Transient or Singleton.

How can I do this?


Solution

  • If you want to use Serilog inside background worker, then first you need to initialize it as Singleton. I prefer creating static method and call it like that: services.AddLogging()

        private static IServiceCollection AddLogging(this IServiceCollection services)
        {
            _loggerFactory = LoggerFactory.Create(builder =>
            {
                builder
                    .AddFilter("Microsoft", LogLevel.Critical)
                    .AddFilter("System", LogLevel.Critical)
                    .AddFilter("ReturnCenter.API", LogLevel.Critical);
            }).AddFile("PATH");
            return services.AddSingleton(_loggerFactory);
        }
    

    Then inside your class which inherits BackgroundService class, you should be able to get ILogger from DI like that inside your ExecuteAsync method:

    using (var scope = _scopeFactory.CreateScope())
    {
        var logger = scope.ServiceProvider.GetRequiredService<ILogger>();
    }