首页 > 解决方案 > 将本机日志记录机制转发到 C# Microsoft.Extensions.Logging.ILogger

问题描述

我正在尝试设置本机记录器以调用 C# 函数。由于使用单例 (log4cpp) 设置了本机端的登录,因此我需要确保尽早设置侦听器(在任何其他本机调用之前)。所以我决定遵循以下建议:

因此,我编写了以下静态类(充当我的单身人士):

internal static class MyNativeService
{
  private const string NativeLibraryName = "my.so.0";
  private static Delegate _logDelegate; // to prevent garbage collection of C# delegate
  static void LogMessage(int logLevel, string loggerName, string message)
  {
    Console.WriteLine($"{logLevel} - {loggerName} - {message}");
  }

  static MyNativeService()
  {
    _logDelegate = new LogDelegate(LogMessage);
    IntPtr ptr = Marshal.GetFunctionPointerForDelegate(_logDelegate);
    // my_listener_configure should be called before any other native calls (only once)
    my_listener_configure(ptr);
  }

  [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  internal delegate void LogDelegate(int logLevel,
    [MarshalAs(UnmanagedType.LPUTF8Str)] string loggerName, 
    [MarshalAs(UnmanagedType.LPUTF8Str)] string logMessage);

  [DllImport(NativeLibraryName, CallingConvention = CallingConvention.Cdecl)]
  private static extern void my_listener_configure(IntPtr aCallback);
}

这按预期工作,但这并不是我想要的。我更愿意将消息转发到实际的ILogger框架(Microsoft.Extensions.Logging)。那么我应该如何重构上面的代码,以便我LogMessage变成:

using Microsoft.Extensions.Logging;
internal class MyNativeLoggerListener
{
  private readonly ILogger<MyNativeLoggerListener> _logger; // created somehow at startup
  private void LogMessage(int logLevel, string loggerName, string message)
  {
    _logger.Log((LogLevel)logLevel, 0, $"{loggerName} - {message}", null, null);
  }
}

由于我不能混合依赖注入和静态构造函数,我如何以及何时在我的 C# 应用程序中创建我的单例,以设置将本机消息转发到正确配置的记录器(在应用程序中定义)?


另一个天真的解决方案是在Startup类中创建一个记录器,以便:

namespace Acme
{
    public class Startup
    {
        private readonly ILogger<Startup> _logger;
        private readonly Delegate _logDelegate; // to prevent garbage collection of C# delegate
        public void LogMessage(int logLevel, string loggerName, string message)
        {
            _logger.Log((LogLevel)logLevel, 0, message, null, null);
        }

        public Startup(IConfiguration configuration, ILogger<Startup> logger)
        {
            Configuration = configuration;
            _logger = logger;
            _logDelegate = MyNativeService.my_listener_configure(LogMessage);
        }

当然,这不会起作用,因为 ILogger 机制尚未设置。上面抛出一个:

System.InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger`1[Acme.Startup]' while attempting to activate 'Acme.Startup'.

作为参考,这是我遵循的模式:

标签: c#loggingdependency-injection

解决方案


静态上下文不适合依赖注入上下文。

不过,您有一些选择:

一个)

只需使用LoggerFactory并创建您的记录器。在下面的示例中,我创建了一个控制台记录器。然后,您可以将创建的记录器设置为静态类(或封装其构造),如:

using (var loggerFactory = LoggerFactory.Create(o => o.SetMinimumLevel(LogLevel.Trace).AddConsole()))
{
    MyNativeService.Logger = loggerFactory.CreateLogger("foo");
}

二)

如果您仍想从 DI 容器中获取某些内容,请使用 Startup 类中的应用程序服务提供程序创建记录器,如下所示:

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

// Ommitted ConfigureServices... [...]

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // startup code [...]

            var loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>();
            var logger = loggerFactory.CreateLogger("foo"); // You can give your own name or nameof(MyNativeService), for instance
            
            MyNativeService.Logger = logger;
        }
    }

C) 您还可以在静态类中设置 Services 属性,因此您可以访问全套 DI 容器服务,例如:

 public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
    // Ommitted ConfigureServices... [...]
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                // startup code [...]    
               
                MyNativeService.Services = app.ApplicationServices;
            }
        }

并且您的 LogMessage 实现可能是:

public static void LogMessage(int logLevel, string loggerName, string message)
   {
      var loggerFactory = Services.GetService<ILoggerFactory>();

      var logger = loggerFactory.CreateLogger(loggerName);
      logger.Log((LogLevel)logLevel, 0, $"{loggerName} - {message}");

   }

你不会摆脱一些初始化代码。在所有这些情况下,您可能希望保护您的静态类在初始化之前不被调用。


推荐阅读