You need to enable JavaScript to run this app. 如何将原生日志机制转发到C# Microsoft.Extensions.Logging.ILogger
优惠活动
大模型
产品
解决方案
定价
更多

如何将原生日志机制转发到C# Microsoft.Extensions.Logging.ILogger

可行实现方案

1. 调整原生交互封装类

把原生配置相关的逻辑从静态构造函数移到可注入的服务类里,同时保留委托引用防止被GC回收,确保配置仅执行一次:

using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;

internal sealed class MyNativeLogListener : IDisposable
{
    private const string NativeLibraryName = "my.so.0";
    // 静态字段持有委托引用,防止GC回收
    private static LogDelegate _logDelegate;
    // 标记是否已经完成配置,避免重复调用
    private static bool _isConfigured;
    private readonly ILogger<MyNativeLogListener> _logger;

    public MyNativeLogListener(ILogger<MyNativeLogListener> logger)
    {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        
        if (!_isConfigured)
        {
            _logDelegate = LogMessage;
            IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(_logDelegate);
            my_listener_configure(funcPtr);
            _isConfigured = true;
        }
    }

    private void LogMessage(int logLevel, string loggerName, string message)
    {
        _logger.Log((LogLevel)logLevel, "{LoggerName} - {Message}", loggerName, message);
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    private 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);

    public void Dispose()
    {
        // 如果原生层提供了清理回调的接口,可以在这里调用
        _logDelegate = null;
        _isConfigured = false;
    }
}

2. 注册服务并指定初始化时机

.NET 6+ 顶层语句(Program.cs)

在构建完WebApplication之后,启动服务之前手动解析一次监听器,触发配置,保证所有业务逻辑执行前原生配置已经完成:

var builder = WebApplication.CreateBuilder(args);
// 注册日志监听器为单例
builder.Services.AddSingleton<MyNativeLogListener>();
// 你的其他服务注册、日志配置逻辑...

var app = builder.Build();
// 关键:在启动服务前从根容器解析监听器,触发原生配置,此时ILogger已经完全初始化完成
_ = app.Services.GetRequiredService<MyNativeLogListener>();

// 你的中间件配置、路由配置...
app.Run();

Startup类模式

不要在Startup构造函数里做配置,改为在Configure方法中注入监听器,此时DI容器已经构建完成,可以正常解析ILogger:

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

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<MyNativeLogListener>();
        // 其他服务注册...
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyNativeLogListener logListener)
    {
        // 此时logListener已经被容器构造完成,原生配置已经执行
        // 其他中间件配置...
    }
}

3. 之前报错的原因说明

你在Startup构造函数注入ILogger<Startup>报错,是因为Startup构造函数执行时,DI容器还未完成构建,日志服务也未完全初始化,只有容器构建完成后,才能正确解析到ILogger实例。

注意事项

  • 确保所有原生调用都在上述初始化步骤之后执行,不要在服务注册阶段、Startup构造函数阶段调用任何原生方法
  • 委托用静态字段持有,避免实例被GC回收后原生层调用空指针引发崩溃
  • 如果有多个Host实例的场景,可以加锁保证配置仅执行一次

内容的提问来源于stack exchange,提问作者malat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.13 10:57:04