【问题标题】:Redirect all NLog output to Serilog with a custom Target使用自定义目标将所有 NLog 输出重定向到 Serilog
【发布时间】:2023-04-08 04:06:01
【问题描述】:

作为从 NLog 切换到 Serilog 的一个步骤,我想将 NLog 的 LogManager.GetLogger(name) 的标准布线底层标准调用重定向到桥接任何代码日志记录到 NLog 以立即转发到环境 Serilog Log.Logger - 即我想只需一项配置即可简单地转发消息,而无需像 Log4net.Appender.Serilog 为 Log4net 所做的那样进行缓冲。

任何人都可以编造或指出一个规范的 sn-p 可以正确有效地执行此操作吗?我能想到的要求:

  • 保持水平,即nlog.Warn应该等价于serilog.Warning
  • Serilog 可以重新生成时间
  • 在附加程序中实现消息 - 即,无需维护与“消息”相关的任何属性(Serilog 术语中的 LogEvent
  • 无缓冲
  • 我不需要使用任何其他 NLog 目标(即改变/删除消息就可以了)

【问题讨论】:

  • 很好奇你为什么要远离 NLog。 NLog 中是否缺少某些内容?
  • 线程间异步包装器协调的方式导致了问题,我也有与相同机制相关的泄漏。 Serilog 水槽非常干净,您可以通过阅读它们来相信它们所做的事情。还有一个很好的 gitter 社区。它还使我在负载下切换的容量增加了 30%
  • 您是否有一个示例配置(或示例代码)显示了 NLog 中的这种不良行为? (不必编译,只需说明您是如何使用 NLog 的)。我自己对 NLog 和 Serilog 的测试给了我不同的画面。
  • 从来没有把它正确地归结为,但我们使用的是大约 1.5yo 版本的 NLog 异步包装器,而更新的版本对我来说看起来更可怕。两个泄露的对象都与异步转换有关。 NLog 配置都是程序化的。最近,我查看了一个内存转储,它显示 60 多个线程都在异步写入操作中(所有线程都命中同一个文件目标)。我对 NLog 代码的扫描表明,不是将信息简单地传递给编写器线程,而是等待切换的复杂安排(这有助于阻塞/增长行为以及毫无疑问的其他强大功能)
  • 是的,设置适合所有场景的性能测试非常困难。您还可以从单个 NLog 基准项目中看到。可以调整许多不同的场景参数。并且包含许多不同的 NLog 配置用于测试。在我的世界中,低内存分配是一个非常重要的要求。所以是的,让它保持谎言:)

标签: nlog target porting serilog sink


【解决方案1】:

我认为最好的选择确实是自定义 NLog 目标。像这样的东西:(C#)

using NLog;
using NLog.Targets;
using Serilog;
using Serilog.Events;

namespace MyNamespace
{
    [Target("SerilogTarget")]
    public sealed class SerilogTarget : TargetWithLayout
    {
        protected override void Write(LogEventInfo logEvent)
        {
            var log = Log.ForContext(Serilog.Core.Constants.SourceContextPropertyName, logEvent.LoggerName);
            var logEventLevel = ConvertLevel(logEvent.Level);
            if ((logEvent.Parameters?.Length ?? 0) == 0)
            {
                // NLog treats a single string as a verbatim string; Serilog treats it as a String.Format format and hence collapses doubled braces
                // This is the most direct way to emit this without it being re-processed by Serilog (via @nblumhardt)
                var template = new Serilog.Events.MessageTemplate(new[] { new Serilog.Parsing.TextToken(logEvent.FormattedMessage) });
                log.Write(new Serilog.Events.LogEvent(DateTimeOffset.Now, logEventLevel, logEvent.Exception, template, Enumerable.Empty<Serilog.Events.LogEventProperty>()));
            }
            else
                // Risk: tunneling an NLog format and assuming it will Just Work as a Serilog format
#pragma warning disable Serilog004 // Constant MessageTemplate verifier
                log.Write(logEventLevel, logEvent.Exception, logEvent.Message, logEvent.Parameters);
#pragma warning restore Serilog004
        }

        static Serilog.Events.LogEventLevel ConvertLevel(LogLevel logEventLevel)
        {
            if (logEventLevel == LogLevel.Info)
                return Serilog.Events.LogEventLevel.Information;
            else if (logEventLevel == LogLevel.Trace)
                return Serilog.Events.LogEventLevel.Verbose;
            else if (logEventLevel == LogLevel.Debug)
                return Serilog.Events.LogEventLevel.Debug;
            else if (logEventLevel == LogLevel.Warn)
                return Serilog.Events.LogEventLevel.Warning;
            else if (logEventLevel == LogLevel.Error)
                return Serilog.Events.LogEventLevel.Error;
            return Serilog.Events.LogEventLevel.Fatal;
        }
    }
}

在您的main()app_start 中注册:

// Register so it can be used by config file parsing etc
Target.Register<MyNamespace.SerilogTarget>("SerilogTarget"); 

在进行任何日志记录之前,需要连接Target,以便LogManager.GetLogger() 实际上可以触发对SerilogTarget.Write 的调用

    public static void ReplaceAllNLogTargetsWithSingleSerilogForwarder()
    {
        // sic: blindly overwrite the forwarding rules every time
        var target = new SerilogTarget();
        var cfg = new NLog.Config.LoggingConfiguration();
        cfg.AddTarget(nameof(SerilogTarget), target);
        cfg.LoggingRules.Add(new NLog.Config.LoggingRule("*", LogLevel.Trace, target));
        // NB assignment must happen last; rules get ingested upon assignment
        LogManager.Configuration = cfg;
    }

另请参阅:https://github.com/nlog/nlog/wiki/How-to-write-a-custom-target

在不引起任何可避免的性能影响等的情况下执行此操作的最佳方法。

这是 NLog 中的最佳方式,对 NLog 的站点没有性能影响。

TargetAttribute 给我买了什么?

好吧,在这种情况下,您不需要它。 TargetAttribute 在注册完整程序集时使用,但因为我们手动注册,所以不需要它。我认为这是最佳做法,但您可以忽略它。

还有Register给我买什么

使用编程方式配置时确实不需要这样做。但是如果你有 XML 配置,你需要注册。

我有编写可在所有方面工作的目标的习惯(手动注册、按程序集注册、从代码配置、从 XML 配置)。我可以理解这可能会令人困惑。


F# 端口:

module SerilogHelpers =

    let private mapLevel = function
        | x when x = NLog.LogLevel.Info -> LogEventLevel.Information
        | x when x = NLog.LogLevel.Off || x = NLog.LogLevel.Trace -> LogEventLevel.Verbose
        | x when x = NLog.LogLevel.Debug -> LogEventLevel.Debug
        | x when x = NLog.LogLevel.Warn -> LogEventLevel.Warning
        | x when x = NLog.LogLevel.Error ->  LogEventLevel.Error
        | _ -> LogEventLevel.Fatal

    // via https://stackoverflow.com/a/49639001/11635
    [<NLog.Targets.Target("SerilogTarget")>]
    type SerilogTarget() =
        inherit NLog.Targets.Target()

        static member InitializeAsGlobalTarget() =
            // sic: blindly overwrite the forwarding rules every time
            // necessary as Azure Startup establishes a different config as a bootstrapping step
            // see: LogModule.To.target("rollingFile", create, "*", LogLevel.Trace)
            let cfg, target = NLog.Config.LoggingConfiguration(), SerilogTarget()
            cfg.AddTarget("SerilogTarget", target)
            cfg.LoggingRules.Add(NLog.Config.LoggingRule("*", NLog.LogLevel.Trace, target))
            // NB assignment must happen last; rules get ingested upon assignment
            NLog.LogManager.Configuration <- cfg

        override __.Write(logEvent : NLog.LogEventInfo) =
            let log = Log.ForContext(Serilog.Core.Constants.SourceContextPropertyName, logEvent.LoggerName)
            match logEvent.Parameters with
            | xs when isNull xs || xs.Length = 0 ->
                // NLog treats a single string as a verbatim string; Serilog treats it as a String.Format format and hence collapses doubled braces
                // This is the most direct way to emit this without it being re-processed by Serilog (via @nblumhardt)
                let template = MessageTemplate [| Serilog.Parsing.TextToken(logEvent.FormattedMessage) |]
                log.Write(new LogEvent(DateTimeOffset.Now, mapLevel logEvent.Level, logEvent.Exception, template, Seq.empty<LogEventProperty>))
            | _ ->
                // Risk: tunneling an NLog format and assuming it will Just Work as a Serilog format
                log.Write(mapLevel logEvent.Level, logEvent.Exception, logEvent.Message, logEvent.Parameters)

【讨论】:

  • 非常感谢 - 很快将删除 my answer。我实际上想不出我的 Inner 的用途,所以将使用您直接使用 Log 的想法。
  • TargetAttribute 给我买了什么?还有Register 给我买什么?我计划的初始化是:LogManager.Configuration = new NLog.Config.LoggingConfiguration(); LogManager.Configuration.AddTarget(new SerilogTarget()); ...我猜对了吗.Register 会产生相同的效果,但仍然让其他人附加目标?
  • 我已经更新了我的答案。如果我从您的帖子中复制更改,我不确定您是否可以。
  • 谢谢您的礼貌;但是请全部删除 - 没有什么比删除它更让我高兴的了!我的悬而未决的问题是在我的应用程序启动中我需要多少LogManager.Configuration = new NLog.Config.LoggingConfiguration(); LogManager.Configuration.AddTarget("SerilogTarget",new SerilogTarget()); NLog.Targets.Target.Register&lt;SerilogTarget&gt;("SerilogTarget"); 将其作为LogManager 中的默认目标连接。我将冒昧地将其粘贴到您的帖子中;请随意清洁。
  • 感谢@TysonWilliams,添加了它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-08
相关资源
最近更新 更多