使用自定义目标将所有 NLog 输出重定向到 Serilog
Redirect all NLog output to Serilog with a custom Target
作为从 NLog 切换到 Serilog 的一个步骤,我想将 NLog LogManager.GetLogger(name)
的标准调用底层标准连接重定向到桥接任何代码记录到 NLog 以立即转发到环境 Serilog Log.Logger
- 即我只想要一个简单地转发消息的配置,而不像 Log4net.Appender.Serilog
对 Log4net 所做的那样进行缓冲。
任何人都可以编造或指向我正确有效地执行此操作的规范片段吗?我能想到的要求:
- 保持水平,即
nlog.Warn
应该等同于serilog.Warning
- Serilog重新生成时间就可以了
- 在 appender 中具体化消息 - 即,无需维护与 'message'(Serilog 术语中的
LogEvent
)相关的任何属性
- 无缓冲
- 我不需要使用任何其他 NLog 目标(即 mutating/deleting 消息就可以了)
我认为最好的选择确实是自定义 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
the optimal way to do this without inducing any avoidable perf impact etc.
这是NLog中的最优方式,对NLog站点没有性能影响。
what does the TargetAttribute
buy me ?
那么在这种情况下你不需要它。 TargetAttribute
在注册完整程序集时使用,但因为我们手动注册,所以不需要。我认为这是最佳做法,但您可以忽略它。
Also what does the Register
buy me
以编程方式使用配置时确实不需要这样做。但是如果你有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
[<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)
作为从 NLog 切换到 Serilog 的一个步骤,我想将 NLog LogManager.GetLogger(name)
的标准调用底层标准连接重定向到桥接任何代码记录到 NLog 以立即转发到环境 Serilog Log.Logger
- 即我只想要一个简单地转发消息的配置,而不像 Log4net.Appender.Serilog
对 Log4net 所做的那样进行缓冲。
任何人都可以编造或指向我正确有效地执行此操作的规范片段吗?我能想到的要求:
- 保持水平,即
nlog.Warn
应该等同于serilog.Warning
- Serilog重新生成时间就可以了
- 在 appender 中具体化消息 - 即,无需维护与 'message'(Serilog 术语中的
LogEvent
)相关的任何属性 - 无缓冲
- 我不需要使用任何其他 NLog 目标(即 mutating/deleting 消息就可以了)
我认为最好的选择确实是自定义 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
the optimal way to do this without inducing any avoidable perf impact etc.
这是NLog中的最优方式,对NLog站点没有性能影响。
what does the
TargetAttribute
buy me ?
那么在这种情况下你不需要它。 TargetAttribute
在注册完整程序集时使用,但因为我们手动注册,所以不需要。我认为这是最佳做法,但您可以忽略它。
Also what does the
Register
buy me
以编程方式使用配置时确实不需要这样做。但是如果你有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
[<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)