6

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

任何人都可以编造或指向我正确有效地执行此操作的规范片段吗?我能想到的要求:

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

1 回答 1

9

我认为最好的选择确实是自定义 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()在您的or中注册它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)
于 2018-04-03T20:54:14.107 回答