从 .NET 6 Minimal API 调用的类库方法进行 Serilog 日志记录

发布于 2025-01-09 02:49:54 字数 3915 浏览 4 评论 0原文

我设置了一个 .Net 6 Core Minimal API,以使用 SeriLog 登录到 MS SQL Server。在我的类库中,仅当我修改类库中的构造函数时,我才能够使用 SeriLog 进行日志记录。我试图避免修改类库类或方法的构造函数。

根据我使用控制台应用程序的经验,如果我在主 Program.cs 中设置 SeriLog,那么我可以在类库中的任何类中使用日志记录,而无需将记录器传递给构造函数。因此,我可以在类库中的任何位置使用 Log.Information("my message") 并且它可以工作。我试图在 .Net 6 最小 API 项目中使用我的 Program.cs 实现相同的目标。

我觉得通过查看有关该主题的其他问题应该可以做到。特别是这个,其中答案指出:

您无需在类库中执行任何操作。只有主应用程序具有组合根(应用程序生命周期中可以设置对象图的最早点)。

因此,根据上面的内容,在我的 API Program.CS 中,我有以下代码(我已在注释中指出了哪些有效,哪些无效):

    //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();

方法“ConfigureTestAPI()”位于扩展类中,如下所示

    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);
        }
     }
    }

:最后这是我的类库中带有测试方法的类:

        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");
            }
        }
    
    }

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");
            }
        }
    
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

野味少女 2025-01-16 02:49:54

我发现问题出在我的 API Program.cs 文件中缺少一行代码。我需要添加:“Log.Logger = logger;”设置 SeriLog 记录器后。

工作解决方案的代码如下。

在我的 .NET6 API Program.cs 文件中,我有这样的:

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>();

然后在这个文件的下方,我有这样的:

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

var app = builder.Build();

调用类库的 API Get 方法现在看起来像这样(如果与问题中的原始方法相比,您将看到不再需要传递 ILogger):

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);
        }
    }

最后,这是我的类库中的完整测试类。类库包含对 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");
        }
    }

}

我发现这非常适合我的项目。类库中的日志记录直接进入我的 SQL 数据库,其中包含自定义列等。

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.

落墨 2025-01-16 02:49:54

我建议使用两阶段初始化https ://github.com/serilog/serilog-aspnetcore#two-stage-initialization

这样做的好处是可以捕获并报告 ASP.NET Core 主机设置期间引发的异常

因此请尽早初始化 Log.Logger,而不是在 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);

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);

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文