Using Dotnet Core Logging without using the DI container


Just before Christmas, one of our regular contributors to Lidnug posed the question on how to use the ILogger interface in dotnet core 3+ without injecting logger instances into classes used within a dotnet core application.

In his specific case he was working on a project where by the classes being consumed had parameter-less constructors, and as we all know if we can’t use parametered constructors, then we can’t inject anything into our class at runtime.

Now I happen to have an application skeleton that I use for most of my dotnet core projects, and in that skeleton I routinely use the built in logging service (Based around ILogger) with the very excellent serilog, and I have things set up in a way that allows me to use a static logging instance should I need to, but to also be able to inject loggers into classes where I am able to use the DI container built into dotnet core.

Here’s how I do it.

First off, we need to add into our project the required NuGet packages to support serilog and what we want to do with the ILogger interface, in my case it’s these 3 packages

untitled

With those 3 packages in place, we then turn our attention to the “Program.cs” code file and make the following changes.

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;

namespace theapp

{
   public class Program
   {
     public static void Main(string[] args)
     {
       Log.Logger = new LoggerConfiguration()
         .MinimumLevel.Verbose()
         .Enrich.FromLogContext()
         .WriteTo.Console(LogEventLevel.Information)
         .WriteTo.RollingFile("Logs/MainLog-{Date}.log", LogEventLevel.Verbose)
         .CreateLogger();
         
       Log.Information("[MAIN] Starting Application.");
       CreateHostBuilder(args).Build().Run();
     }
     
     public static IHostBuilder CreateHostBuilder(string[] args)
     {
       return Host.CreateDefaultBuilder(args)
         .UseSerilog()
         .ConfigureWebHostDefaults(webBuilder =>         
         {           
           webBuilder.UseStartup();         
         });     
     }   
     
  }
}





In the project I’m using to document this, I’m creating a stand alone kestrel based web application, but since this is dotnet core 3, then this will work for any project that uses the new program/startup class way of doing things.

The code should be fairly self explanatory, but essentially what were doing is attaching Serilog to a static instance of the “Microsoft.Extensions.Hosting.Log.Logger” which is a global singleton attached to the app at it’s lowest level (IE: Executing program assembly)

You can see that we also configure it here, so we have both a rolling file output and console output, mores the point, I dumb down the amount of output sent to the console, but open up the gates to send everything possible to the rolling file.

Doing this means I don’t get a massively cluttered console output, but when there is something I need to investigate, then I know the log files will have to complete story.

There is nothing I need to add to my startup class, and as you can see from the next bit of code, I can continue to use the static “Log” instance in there without DI injection or anything.

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using System;
using System.Linq;
 
namespace theapp
{   
  public class Startup   
  {     
    public IConfiguration Configuration { get; }     
    public IWebHostEnvironment HostEnvironment { get; }     
    
    public Startup(IConfiguration configuration, IWebHostEnvironment hostEnvironment)     
    {       
      Log.Information("[Startup] (CTOR)");       
      Configuration = configuration;       
      HostEnvironment = hostEnvironment;     
    }     
    
    public void ConfigureServices(IServiceCollection services)     
    {       
      Log.Information("[Startup] (ConfigureServices)");       
      
      if (!HostEnvironment.IsDevelopment())       
      {         
        if (!HostEnvironment.IsStaging())         
        {           
          if (!HostEnvironment.IsProduction())           
          {             
            throw new Exception("Invalid Hosting Environment!!!  Application Aborting");           
          }         
        }       
      }       
      
      // Other services     
    }     
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)     
    {       
      if (env.IsDevelopment())       
      {         
        Log.Information($"[Startup] Running in development mode.");         
        app.UseDeveloperExceptionPage();       
      }       
      else       
      {         
        Log.Information($"[Startup] Running in stage or production mode.");         
        app.UseExceptionHandler("/Error");         
        
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.         
        app.UseHsts();         
        app.UseHttpsRedirection();       
      }       
      
      app.UseStaticFiles();       
      app.UseRouting();     
      
    }   
    
  }
}

I now have the choice of the following, I can simply just use the logger as above

untitled

Or I can inject it in as normal using the DI container

untitled

The astute among you may have noticed that the API is different in each case, and you’d be correct, one is the raw ILogger interface, the other is serilog’s wrapper around the ILogger interface, the net result however is that in both cases, the logging info still gets sent through serilog and obeys the configuration set up in program.cs

I can’t tell you if the exact same methods work with other loggers that attach themselves to the Microsoft ILogger interface as I don’t routinely use anything else except for serilog, but since the interface is unified, and all loggers are expected to attach to it the same way, then it would make sense to me that this approach would work with other loggers that have dotnet core capabilities.

Leave a comment