2
votes

I'm having problems using the logger in the context of a fire-and-forget background job with ASP.NET Core WebApi. When using logger.LogInformation("some message"). The following exception occurs:

Newtonsoft.Json.JsonSerializationException: Could not create an instance of type Microsoft.Extensions.Logging.ILogger. Type is an interface or abstract class and cannot be instantiated. Path '', line 1, position 2.

Obvisouly, it seems that hangfire is unable to resolve the instantion of the logger based on the parameters instructed in the application's bootstrap.

I am certain that:

  • Hangfire is properly configured and works
  • The logger (using Serilog) works when used in the same scope as the app (not in backgorund)
  • Service injection works for background jobs for other services than the ILogger

Here is my configuration:

Program.cs

public static IWebHost BuildWebHost (string[] args) =>
                WebHost.CreateDefaultBuilder (args)
                .UseStartup<Startup> ()
                .UseSerilog ()
                .Build ();

Startup.cs

public Startup (IHostingEnvironment env) {
              Log.Logger = new LoggerConfiguration ()
                .MinimumLevel.Debug ()
                .MinimumLevel.Override ("Microsoft", LogEventLevel.Information)
                .Enrich.FromLogContext ()
                .WriteTo.Console ()
                .WriteTo.Debug ()
                .WriteTo.RollingFile (System.IO.Path.Combine ("logs/{Date}-logs.txt"))
                .CreateLogger ();
        }

public void ConfigureServices (IServiceCollection services) {
    services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(dispose: true));
    // ... registering other services
    services.AddMvc ();
    services.AddHangfire (conf => conf.UseSqlServerStorage (Configuration.GetConnectionString ("HangFire")));
}

public void Configure (IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider) {
            app.UseMvc ();

            //hangfire
            GlobalConfiguration.Configuration.UseActivator(new HangfireActivator(serviceProvider));
            app.UseHangfireServer ();
            app.UseHangfireDashboard ();
}

HangfireActivator

public class HangfireActivator : Hangfire.JobActivator {
        private readonly IServiceProvider _serviceProvider;

        public HangfireActivator (IServiceProvider serviceProvider) {
            _serviceProvider = serviceProvider;
        }

        public override object ActivateJob (Type type) {
            Debug.WriteLine("ActivateJob() => " + type.Name);
            return _serviceProvider.GetService (type);
        }
}

ValueController.cs

public class ValuesController : Controller {

        private readonly ILogger<ValuesController> logger;
        private readonly SomeService someService;

        public ValuesController (ILogger<ValuesController> logger, SomeService someService) {
            this.logger = logger;
            this.someService = someService;
        }

        [Route ("task")]
        public  JsonResult StartTask () {
            var server = new BackgroundJobServer ();
            BackgroundJob.Enqueue(() => logger.LogInformation("HELLO from background job instanciated logger"));
            return new JsonResult (new { Result = "task started" });
        }
}

Am I missing something ? Any help or link to a related topic would be much appreciated. Thank you for your help !

2

2 Answers

1
votes

I hate to receive those kind of answers, but this is the case where generic "do not use it" is better then waste of time to find specific solution. Just do not use the logger provider at least for background jobs (what advantage it can provide you?). If you do not want to see SerialLog references - wrap it to your own interface and realization. LoggerProvider is one huge abstraction leak (https://github.com/aspnet/EntityFrameworkCore/issues/10420) because of it is not a real DI as you can expect but Service Locator with fancy behaviour.

And even more: DI can't provide good logging at all. Good logging is not just tracing but flexible audit and is always per session e.g. imagine you want to enable verbose logging only from specific test user and log each his session to specific file (quite natural requirement, trying to repeat user problems and analyze situation quickly). You can't achieve this with logger that you expect to get from IoC container at every places you use it, opposite you should construct it at the start of the session and pass carefully through all constructors (and sometimes remote services) "manually".

Do not rely on DI. Be functional. Be more as node.js not as MVC. It is like "less razor" is better than "more razor" (usage should be motivated).

As I said I hate to receive such kind of answers too.

0
votes

Side not... configuring the Serilog logger in the Main() or Startup classes using the static Log class enabled me to get around this error with serilog... this worked for me:

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
    .Enrich.FromLogContext()
    .WriteTo.ColoredConsole()
    .CreateLogger();

then in the Enqueue() method of Hangfire simply call Log.Information($"");