Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serilog Logging from Class Library method called from .NET 6 Minimal API

I have a .Net 6 Core Minimal API set up to use SeriLog to log to MS SQL Server. In my class library I have managed to get logging working with SeriLog ONLY IF I modify the constructors in my class libraries. I am trying to avoid modifying the constructors of my class library classes or methods.

In my experience with console apps, if I set up SeriLog in my main Program.cs, then I can use logging in any class in my class library without passing the logger to the constructors. So, I can just use Log.Information("my message") anywhere in the class library and it works. I am trying to achieve the same with my Program.cs in a .Net 6 minimal API project.

I sense it should be possible to do from looking at other questions on this topic. In particular this one in which the answer stated that:

You don't have to do anything in your class library. Only the main application has a composition root (earliest point in an application lifecycle you can set up your object graph).

So following from the above, in my API Program.CS, I have this code (I have indicated what works and what does not in the comments):

    //Configure SeriLog
    builder.Logging.ClearProviders();
    var appSettings = new ConfigurationBuilder()
       .SetBasePath(Directory.GetCurrentDirectory())
       .AddJsonFile("appsettings.json")
       .Build();
    
    var logDB = 
    builder.Configuration.GetSection("ConnectionStrings:Default").Value;
    var sinkOpts = new MSSqlServerSinkOptions { TableName = "Logs" };
    var columnOptions = new ColumnOptions();
    
    var logger = new LoggerConfiguration()
       .MinimumLevel.Override("Microsoft", 
    Serilog.Events.LogEventLevel.Information)
      .WriteTo.MSSqlServer(
          connectionString: logDB,        
          sinkOptions: sinkOpts,
          columnOptions: columnOptions,
          appConfiguration: appSettings
        ).CreateLogger();
    
    builder.Logging.AddSerilog(logger);

    //Depency injection for Class Library method 
    //LoggerTestClass is a class in my Class Library project
    builder.Services.AddScoped<ILoggerTestClass, LoggerTestClass>();

    var app = builder.Build();
    app.ConfigureTestAPI();

The Method "ConfigureTestAPI()" is in a Extension Class that is shown below:

    public static class API_Test
    {

    public static void ConfigureTestAPI(this WebApplication app) 
    //Extension method for app
    {               
        app.MapGet("/test/", GetTest);
        
    }

    private static async Task<IResult> GetTest(int id, 
    ILogger<LoggerTestClass> logger, ILoggerTestClass testClass)
    {
        try
        {
            try
            {
                //This works
                logger.LogInformation("Starting test now");  

                //This does NOT work
                Log.Information("Using Log. directly"); 

                
                testClass.Test();  //Call to class library method

                logger.LogInformation("Test finished");  //Also works
                return Results.Ok("OK");
            }
            catch (Exception ex)
            {
                return Results.Problem(ex.Message);
            }
        }
        catch (Exception ex)
        {
            return Results.Problem(ex.Message);
        }
     }
    }

And finally here is the class with the test method in my class library:

        namespace TestingLib.Testing;
    public class LoggerTestClass : ILoggerTestClass
    {
    
        private Microsoft.Extensions.Logging.ILogger _logger;
    
        public LoggerTestClass(ILogger<LoggerTestClass> logger)
        {
            _logger = logger;
        }
    
        public void Test()
        {
            try
            {
    
               //Does not work
               Log.Information("Test logging from class library using Log.");
    
               //Does not work
               Log.Logger.Information("In Test Class in DLL. Trying loging with [Log.Logger.Information]");
    
               //This works
               _logger.LogInformation("In Test Class in DLL. Trying loging with [_logger.LogInformation]");
            }
            catch (Exception ex)
            {
                Log.Error("An error in class library");
            }
        }
    
    }

like image 960
Fritz45 Avatar asked Oct 22 '25 15:10

Fritz45


2 Answers

I found the problem was a single line of code that was lacking in my API Program.cs file. I needed to add: "Log.Logger = logger;" after setting up the SeriLog logger.

The code for the working solution is below.

In my .NET6 API Program.cs file I have this:

var builder = WebApplication.CreateBuilder(args);

//Configure SeriLog
builder.Logging.ClearProviders();
var appSettings = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .Build();

var logDB = builder.Configuration.GetSection("ConnectionStrings:Default").Value;
var sinkOpts = new MSSqlServerSinkOptions { TableName = "Logs" };
var columnOptions = new ColumnOptions
{
    AdditionalColumns = new Collection<SqlColumn>
    {
        new SqlColumn("UserID", SqlDbType.Int),
        new SqlColumn("RunTag", SqlDbType.NVarChar),
        new SqlColumn("CustomType", SqlDbType.NVarChar)
    }
};

var logger = new LoggerConfiguration()
    .MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Information)
    .WriteTo.MSSqlServer(
        connectionString: logDB,
        sinkOptions: sinkOpts,
        columnOptions: columnOptions,
        appConfiguration: appSettings
    ).CreateLogger();

builder.Logging.AddSerilog(logger);

//NOTE that this is not needed
//builder.Services.AddScoped<TestingLib.Testing.ILoggerTestClass, TestingLib.Testing.LoggerTestClass>();

And then lower down in this file I have this:

Log.Logger = logger; //This was the line I was missing!

var app = builder.Build();

My API Get method that calls into the Class Library now looks like this (if compared to the original one in the question, you will see there is no longer a need to pass ILogger around):

private static async Task<IResult> GetTest(int id)
    {
        try
        {
            try
            {
                string runTag = "In API GetTest(id)";
                string custom = "Calling from API method";                
                Log.Information("{Message}-{RunTag}-{CustomType}", "Message logging in API method", runTag, custom);
                
                //Call to Class Library Method - no need to pass ILogger
                LoggerTestClass testClass = new LoggerTestClass();
                testClass.Test();
               
                return Results.Ok("OK");
            }
            catch (Exception ex)
            {
                return Results.Problem(ex.Message);
            }
        }
        catch (Exception ex)
        {
            return Results.Problem(ex.Message);
        }
    }

And finally, here is the full Test Class in my class library. The class library contains a reference to SeriLog:

using Serilog;

namespace TestingLib.Testing;
public class LoggerTestClass //: ILoggerTestClass
{

    public LoggerTestClass()
    {
        //No need to pass in ILogger to class
    }

    public void Test()
    {
        try
        {
            string runTag = "In Class Library Method)";
            string custom = "Calling class library method Test()";
            Log.Information("{Message}-{RunTag}-{CustomType}", "Message logging in Class Library", runTag, custom);            
        }
        catch (Exception ex)
        {
            Log.Error("An error in class library");
        }
    }

}

I found this works perfectly for my project. Loggging from the class library goes directly into my SQL database with the custom columns etc.

like image 107
Fritz45 Avatar answered Oct 25 '25 18:10

Fritz45


I would recommend to use Two-stage initialization: https://github.com/serilog/serilog-aspnetcore#two-stage-initialization

This has the benefit of catching and reporting exceptions thrown during set-up of the ASP.NET Core host

So init Log.Logger as early as possible not before the builder.Build()

// usings here

var logger = new LoggerConfiguration()
    .WriteTo.Console(LogEventLevel.Information)
    .CreateLogger();

Log.Logger = logger;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddSerilog(logger);
builder.Host.UseSerilog(logger);

like image 25
SerjG Avatar answered Oct 25 '25 20:10

SerjG



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!