将本机日志记录机制转发到 C# Microsoft.Extensions.Logging.ILogger
Forwarding native logging mechanism to C# Microsoft.Extensions.Logging.ILogger
我正在尝试设置本机记录器以调用 C# 函数。由于本机端的登录是使用单例 (log4cpp) 设置的,因此我需要确保尽早设置侦听器(在任何其他本机调用之前)。所以我决定听从以下建议:
因此我写了下面的静态class(作为我的单身人士):
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
class 中创建一个记录器,以便:
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'.
作为参考,这是我遵循的模式:
静态上下文不适合依赖注入上下文。
虽然你有一些选择:
A)
只需使用 LoggerFactory 并创建您的记录器。在下面的示例中,我创建了一个控制台记录器。然后,您可以将创建的记录器设置为静态 class(或封装其构造),如:
using (var loggerFactory = LoggerFactory.Create(o => o.SetMinimumLevel(LogLevel.Trace).AddConsole()))
{
MyNativeService.Logger = loggerFactory.CreateLogger("foo");
}
B)
如果您仍想从 DI 容器中获取某些内容,请在 Startup class 中使用应用程序服务提供程序创建记录器,如下所示:
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) 您还可以在静态 class 中设置服务 属性,这样您就可以访问全套 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}");
}
您不会删除一些初始化代码。在所有这些情况下,您可能希望保护您的静态 class 在初始化之前不被调用。
我正在尝试设置本机记录器以调用 C# 函数。由于本机端的登录是使用单例 (log4cpp) 设置的,因此我需要确保尽早设置侦听器(在任何其他本机调用之前)。所以我决定听从以下建议:
因此我写了下面的静态class(作为我的单身人士):
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
class 中创建一个记录器,以便:
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'.
作为参考,这是我遵循的模式:
静态上下文不适合依赖注入上下文。
虽然你有一些选择:
A)
只需使用 LoggerFactory 并创建您的记录器。在下面的示例中,我创建了一个控制台记录器。然后,您可以将创建的记录器设置为静态 class(或封装其构造),如:
using (var loggerFactory = LoggerFactory.Create(o => o.SetMinimumLevel(LogLevel.Trace).AddConsole()))
{
MyNativeService.Logger = loggerFactory.CreateLogger("foo");
}
B)
如果您仍想从 DI 容器中获取某些内容,请在 Startup class 中使用应用程序服务提供程序创建记录器,如下所示:
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) 您还可以在静态 class 中设置服务 属性,这样您就可以访问全套 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}");
}
您不会删除一些初始化代码。在所有这些情况下,您可能希望保护您的静态 class 在初始化之前不被调用。