Using Asp.Net Core 2 Injection for Serilog with Multiple Projects

25,084

Solution 1

One method that worked for me:

I added an instance of Serilog.Core.Logger using the AddSingleton() method in the ConfigureServices method. This resolved the DI issue. This step replaces the step of assigning a Logger instance to Log.Logger in the StartUp constructor.

services.AddSingleton((ILogger)new LoggerConfiguration()
            .MinimumLevel.Information()
            .WriteTo.File(<...>)
            .CreateLogger());

Also change references in your class files to point to Serilog.Ilogger

Solution 2

This solution correctly injects the logger into classes in an external project or library.

I also tried the answer suggested by Rufus. I didn't have the problem described by the OP, but I had an even weirder problem: after ILogger was injected into the external project, logging to MS SQL stopped working. Console logging worked. Since Serilog is mainly a static service, I realized I should treat Log.Logger as the factory. The benefit is that you can still customize ILogger references (for example, adding ForContext in the constructor) without affecting the "singleton" service.

I've posted a full working solution on github including a console program that sets up DI (would work the same from ASP.NET Core), an external library that contains the Serilog getting-started divide-by-zero demo, and another library that manages Serilog as a service.

The important part, Serilog service registration, looks like this (and as a bonus, we tie to ProcessExit for transparent cleanup):

using Microsoft.Extensions.DependencyInjection;
using System;

namespace Serilog.Injection
{
    public static class RegisterSerilogServices
    {
        public static IServiceCollection AddSerilogServices(this IServiceCollection services)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Verbose()
                .WriteTo.Console()
                .WriteTo.MSSqlServer(@"xxxxxxxxxxxxx", "Logs")
                .CreateLogger();

            AppDomain.CurrentDomain.ProcessExit += (s, e) => Log.CloseAndFlush();

            return services.AddSingleton(Log.Logger);
        }
    }
}

Solution 3

I have actually been struggling with the very same setup, but I managed to get it to work. The solution is only slightly different from your code examples in these ways:

  • It depends on the Microsoft.Extensions.Logging.ILogger.
  • The Logger is injected using ILogger<Type>

Your controller code already fit these requirements, which is why that did work.

using Microsoft.Extensions.Logging;

public class HomeController : Controller
{
    ILogger _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
        _logger.LogInformation("Controller instantiated");
    }
}

For other classes in other projects just make sure you depend on the default ILogger interface, not Serilog's implementation. This also has the benefit that, if you want to replace Serilog later, you can do this without having to remove all references to it throughout your other projects as well.

using Microsoft.Extensions.Logging;

public class MyBusinessObject
{
    ILogger _logger;

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

    public void DoBusinessLog()
    {
        _logger.Information("Executing Business Logic");
    }
}

For some reason DI only manages to instantiate a Logger instance when requiring an ILogger<Type> but not for ILogger. Why that is exactly I don't know, maybe someone else can answer that one.

Solution 4

You don't access Serilog through DI in Net Core. It's a static. Having configured and initialised the Logger correctly in your Program.Main(),simply access like any other static in your Startup, Controller etc. i.e.

public void ConfigureServices(IServiceCollection services)
            {
    Serilog.Log.Information("here I am in startup");
    //further options
};

or add at the top:

using static Serilog.Log;

and simply:

Information("keystroke saving");

Share:
25,084
platypusjh
Author by

platypusjh

Updated on July 05, 2022

Comments

  • platypusjh
    platypusjh almost 2 years

    I have Serilog configured for Asp.Net Core 2.0 and it works great via .Net Core dependency injection in my startup web project (if I use it through Microsoft.Extensions.Logging), but I can't access it from any other project.

    Here's what I have:

    Program.cs

    using System;
    using System.IO;
    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Serilog;
    
    namespace ObApp.Web.Mvc
    {
        public class Program
        {
            public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
                .Build();
    
            public static void Main(string[] args)
            {
                Log.Logger = new LoggerConfiguration()
                    .ReadFrom.Configuration(Configuration)
                    .CreateLogger();
    
                try
                {
                    Log.Warning("Starting BuildWebHost");
    
                    BuildWebHost(args).Run();
                }
                catch (Exception ex)
                {
                    Log.Fatal(ex, "Host terminated unexpectedly");
                }
                finally
                {
                    Log.CloseAndFlush();
                }
            }
    
            public static IWebHost BuildWebHost(string[] args) =>
                WebHost.CreateDefaultBuilder(args)
                    .UseStartup<Startup>()
                    .UseSerilog()
                    .Build();
        }
    }
    

    Startup.cs

    Startup .cs is pure default. I haven't altered it in any way from the original template for the new Asp.Net Core MVC project.

    I have thought that I might need to add Serilog in the IServiceCollection services, but the article Leaner, meaner ASP.NET Core 2 logging at https://nblumhardt.com/2017/08/use-serilog/ tells me it's unnecessary.

    You can then go ahead and delete any other logger configuration that’s hanging around: there’s no need for a "Logging" section in appsettings.json, no AddLogging() anywhere, and no configuration through ILoggerFactory in Startup.cs.

    UseSerilog() replaces the built-in ILoggerFactory so that every logger in your application, whether it’s Serilog’s Log class, Serilog’s ILogger, or Microsoft.Extensions.Logging.ILogger, will be backed with the same Serilog implementation, and controlled through the same configuration.

    What I Expected

    The ability to simply inject a logger via a constructor in any class in any project in the solution. For example:

    using Serilog;
    public MyTestClass(ILogger logger) { ... }
    

    What I Got - Web (Startup) Project

    Injection works in the HomeController if I use the Microsoft.Extensions.Logging wrapper:

    using Microsoft.Extensions.Logging;
    
    public class HomeController : Controller
    {
        ILogger<HomeController> _logger;
    
        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
            _logger.LogDebug("Controller instantiated.");
        }
    }
    

    Injection fails in any project/class if I try to inject Serilog.ILogger

    using Serilog;
    using Serilog.Extensions.Logging; // Not sure if this would help.
    
    public class HomeController : Controller
    {
        ILogger _logger;
    
        public HomeController(ILogger logger)
        {
            _logger = logger;
            _logger.Debug("Controller instantiated.");
        }
    }
    

    InvalidOperationException: Unable to resolve service for type 'Serilog.ILogger' while attempting to activate 'ObApp2.Web.Mvc.Controllers.HomeController'.

    My Bigger Issue - OtherProjects

    My bigger issue is that I can't get a logger via DI through any method I've tried if I'm working in another project in the solution.

    When I try to inject either using Microsoft.Extensions.Logging or using Serilog I get a missing parameter exception at build time.

    using Microsoft.Extensions.Logging;
    namespace ObApp.Domain
    {
        public class MyTestClass(ILogger<MyTestClass> logger) { ... }
    
    - or -
    
    using Serilog;
    namespace ObApp.Domain
    {
        public class MyTestClass(ILogger logger) { ... }
    

    Both generate build error similar to:

    There is no argument given that corresponds to the required formal parameter 'logger' of 'MyTestClass.MyTestClass(ILogger)'

    Questions

    1. When injecting with this type of Serilog configuration, is it recommended that I reference Microsoft.Extensions.Logging or Serilog in the class files where I'm doing the injection?

    2. How can I get DI to work through all projects?