Redirect all NLog output to Serilog with a custom Target

别说谁变了你拦得住时间么 提交于 2019-12-20 02:14:21

问题


As a step in switching from NLog to Serilog, I want to redirect the standard wiring underlying standard invocations of NLog's LogManager.GetLogger(name) to Bridge any code logging to NLog to forward immediately to the ambient Serilog Log.Logger - i.e. I want to just one piece of config that simply forwards the message, without buffering as Log4net.Appender.Serilog does for Log4net.

Can anyone concoct or point me to a canonical snippet that does this correctly and efficiently please? Requirements I can think of:

  • Maintain the level, i.e. nlog.Warn should be equivalent to serilog.Warning
  • It's ok for Serilog to generate the time anew
  • materializing the message in the appender - i.e., there's no need to maintain any properties associated with the 'message' (the LogEvent in Serilog terms)
  • no buffering
  • I don't need any other NLog Target to be used (i.e. mutating/deleting the message would be fine)

回答1:


I think the best option is indeed a custom NLog target. Something like this: (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.Error)
                return Serilog.Events.LogEventLevel.Error;
            return Serilog.Events.LogEventLevel.Fatal;
        }
    }
}

register it in your main() or app_start:

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

Before any logging takes place, the Target needs to be wired in so LogManager.GetLogger() can actually trigger a call to 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;
    }

See also: https://github.com/nlog/nlog/wiki/How-to-write-a-custom-target

the optimal way to do this without inducing any avoidable perf impact etc.

This is the optimal way in NLog and has no performance impact on the NLog's site.

what does the TargetAttribute buy me ?

Well in this case you don't need it. The TargetAttribute is used when registering a full assembly, but because we register manually, it's not needed. I think it's best practice, but you could leave it out.

Also what does the Register buy me

This is indeed not needed when using programmatically config. But if you have XML config, you need the register.

I've the habit to write targets that works in all ways (register manually, register by assembly, config from code, config from XML). I could understand that could be confusing.


F# port:

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)


来源:https://stackoverflow.com/questions/49592351/redirect-all-nlog-output-to-serilog-with-a-custom-target

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!