You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

切换至Serilog时将NLog日志无缓冲转发的高效实现方案问询

NLog转Serilog无缓冲日志转发实现

这个需求我之前处理过,核心思路就是替换NLog的LogManager背后的日志实例创建逻辑,让所有通过LogManager.GetLogger获取的NLog Logger都变成“转发器”,直接把日志调用传递给Serilog的Log.Logger,完全跳过NLog自身的缓冲和目标输出逻辑。

实现步骤

1. 自定义NLog转发Logger

首先我们需要实现一个NLog的Logger子类,它的核心逻辑就是把NLog的日志事件转换成Serilog的格式,直接调用Serilog输出:

using NLog;
using NLog.Common;
using Serilog;
using Serilog.Events;
using System.Collections.Concurrent;

public class SerilogForwardingNLogLogger : Logger
{
    private readonly ILogger _serilogLogger;
    // 预定义NLog和Serilog的日志级别映射
    private static readonly ConcurrentDictionary<LogLevel, LogEventLevel> _levelMap = new()
    {
        [LogLevel.Trace] = LogEventLevel.Verbose,
        [LogLevel.Debug] = LogEventLevel.Debug,
        [LogLevel.Info] = LogEventLevel.Information,
        [LogLevel.Warn] = LogEventLevel.Warning,
        [LogLevel.Error] = LogEventLevel.Error,
        [LogLevel.Fatal] = LogEventLevel.Fatal
    };

    public SerilogForwardingNLogLogger(string name) : base(name)
    {
        // 给Serilog日志加上SourceContext,对应NLog的Logger名称
        _serilogLogger = Log.Logger.ForContext(Constants.SourceContextPropertyName, name);
    }

    // 重写NLog的日志写入逻辑
    protected override void Write(AsyncLogEventInfo logEvent)
    {
        // 跳过Off级别日志
        if (!_levelMap.TryGetValue(logEvent.LogEvent.Level, out var serilogLevel))
            return;

        // 收集NLog日志事件的属性
        var logProperties = new Dictionary<string, object>();
        foreach (var property in logEvent.LogEvent.Properties)
        {
            logProperties[property.Key] = property.Value.ToString();
        }

        // 传递NLog的逻辑上下文(MappedDiagnosticsLogicalContext)到Serilog
        if (MappedDiagnosticsLogicalContext.HasItems)
        {
            foreach (var contextItem in MappedDiagnosticsLogicalContext.GetAllItems())
            {
                logProperties[contextItem.Key] = contextItem.Value;
            }
        }

        // 直接调用Serilog写入,无缓冲
        _serilogLogger.Write(
            serilogLevel,
            logEvent.LogEvent.Exception,
            logEvent.LogEvent.FormattedMessage,
            logProperties);
    }

    // 重写日志级别检查,避免不必要的日志处理
    public override bool IsEnabled(LogLevel level)
    {
        if (!_levelMap.TryGetValue(level, out var serilogLevel))
            return false;
        return _serilogLogger.IsEnabled(serilogLevel);
    }
}

2. 自定义NLog LoggerFactory

接下来实现一个NLog的ILoggerFactory,用来创建我们的转发Logger:

using NLog;
using System.Collections.Concurrent;

public class SerilogForwardingNLogLoggerFactory : ILoggerFactory
{
    // 缓存Logger实例,避免重复创建
    private readonly ConcurrentDictionary<string, Logger> _loggerCache = new();

    public Logger GetLogger(string name)
    {
        return _loggerCache.GetOrAdd(name, key => new SerilogForwardingNLogLogger(key));
    }

    public void Shutdown()
    {
        // Serilog的生命周期由自身管理,这里无需操作
    }

    public void Flush(TimeSpan timeout)
    {
        // 无缓冲设计,不需要Flush操作
    }
}

3. 替换NLog的全局LoggerFactory

在你的应用启动代码中,先初始化Serilog,然后替换NLog的全局LoggerFactory:

// 1. 先初始化Serilog(根据你的实际需求配置)
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Verbose()
    .WriteTo.Console()
    // 其他Serilog配置...
    .CreateLogger();

// 2. 替换NLog的LoggerFactory,从此所有NLog日志都会转发到Serilog
LogManager.SetLoggerFactory(new SerilogForwardingNLogLoggerFactory());

关键注意事项

  • Serilog优先初始化:必须确保Serilog已经完成初始化,再替换NLog的LoggerFactory,避免空引用问题。
  • 无缓冲保证:这个实现直接在NLog的Write方法中调用Serilog的同步写入,没有任何缓冲逻辑,完全符合你的需求。
  • 上下文与属性传递:已经处理了NLog的事件属性和逻辑上下文的转发,你可以根据自己的需求调整属性转换逻辑(比如处理复杂类型的属性)。
  • NLog配置可忽略:既然所有NLog日志都被转发,你可以完全移除NLog的配置文件(比如nlog.config),或者保留但禁用所有目标输出。
  • 性能优化:通过ConcurrentDictionary缓存Logger实例,避免重复创建,同时重写IsEnabled方法跳过不必要的日志处理,保证性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:35:15