切换至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
相关产品推荐
相关产品推荐

