Serilog 输出到 hangfire 上下文控制台
Serilog output to hangfire context console
我希望能够使用我的日志通过 Hangfires context.console 发出事件,这样我就不需要使用 context.writeline 将日志事件输出到我的 hangfire 仪表板。
我试图为此目的实现一个特定的 serilog 接收器。但是由于我需要来自 hangfire 的 PerformContext(它在运行时注入到任务方法中),所以我无法在应用程序启动时配置日志接收器。我试图在任务方法中创建一个新的记录器,只是为了看看接收器是否真的有效,但它没有 - 任何人都可以看到它为什么不起作用,或者建议不同的方法吗?
这是水槽:
class HangfireContextSink : ILogEventSink {
private readonly IFormatProvider formatProvider;
private readonly PerformContext context;
public HangfireContextSink(IFormatProvider formatProvider, PerformContext context) {
this.formatProvider = formatProvider;
this.context = context;
}
public void Emit(LogEvent logEvent) {
var message = logEvent.RenderMessage(formatProvider);
context.WriteLine(ConsoleTextColor.Blue, DateTimeOffset.Now.ToString() + " " + message);
}
接收器配置:
public static class SinkExtensions {
public static LoggerConfiguration HangfireContextSink(this LoggerSinkConfiguration loggerSinkConfiguration, PerformContext context, IFormatProvider formatProvider = null) {
return loggerSinkConfiguration.Sink(new HangfireContextSink(formatProvider, context));
}
}
任务方法:
public static bool TestJob(PerformContext context) {
using (LogContext.PushProperty("Hangfirejob", "TestJob")) {
try {
using (var hangfireLog = new LoggerConfiguration().WriteTo.HangfireContextSink(context).CreateLogger()) {
var progress = context.WriteProgressBar("Progress");
for (int i = 0; i < 10; i++) {
context.WriteLine("Working with {0}", i);
progress.SetValue((i + 1) * 10);
Log.Debug("Test serilog");
hangfireLog.Debug("Test from hangfirelog");
Thread.Sleep(5000);
}
}
Log.Debug("Done testjob");
return true;
} catch (Exception ex) {
Log.Error(ex, "Error!");
return false;
}
}
}
消息不会记录到 Hangfire 控制台,因为您使用 Debug
日志级别记录它们,而默认 Serilog 级别是 Information
。您可以通过在 LoggerConfiguration
上调用 .MinimumLevel.Debug()
来更改日志记录级别。同样对于通过 Serilog.Log
static class 记录消息,您应该设置它的 Logger
属性.
这是一个固定的代码,将记录到 Hangfire 控制台:
using (LogContext.PushProperty("Hangfirejob", "TestJob"))
{
try
{
using (var hangfireLog = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.HangfireContextSink(context).CreateLogger())
{
// This is a dirty code that will be removed in final solution
var prevLogger = Log.Logger;
Log.Logger = hangfireLog;
var progress = context.WriteProgressBar("Progress");
for (int i = 0; i < 10; i++)
{
context.WriteLine("Working with {0}", i);
progress.SetValue((i + 1) * 10);
Log.Debug("Test serilog");
hangfireLog.Debug("Test from hangfirelog");
Thread.Sleep(5000);
}
Log.Debug("Done testjob");
Log.Logger = prevLogger;
}
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Error!");
return false;
}
}
这可行,但是为每个作业创建新日志的总体方法非常糟糕。您可以通过 LogEvent
中的 属性 传递 PerformContext
的实例来避免这种情况。您应该定义一个自定义 属性 class 派生自抽象 LogEventPropertyValue
并公开 PerformContext
.
的实例
这是最终代码:
PerformContextProperty.cs:
public class PerformContextProperty : LogEventPropertyValue
{
public PerformContext PerformContext { get; }
public PerformContextProperty(PerformContext performContext)
{
PerformContext = performContext;
}
public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null)
{
}
}
PerformContextEnricher.cs:
public class PerformContextEnricher : ILogEventEnricher
{
private readonly PerformContext performContext;
public PerformContextEnricher(PerformContext performContext)
{
this.performContext = performContext;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(new LogEventProperty(HangfireContextSink.PerformContextProperty, new PerformContextProperty(performContext)));
}
}
TestJob.cs:
public class TestJob
{
public static bool Execute(PerformContext context)
{
using (LogContext.PushProperty("Hangfirejob", "TestJob"))
using (LogContext.Push(new PerformContextEnricher(context)))
{
try
{
var progress = context.WriteProgressBar("Progress");
for (int i = 0; i < 10; i++)
{
context.WriteLine("Working with {0}", i);
progress.SetValue((i + 1) * 10);
Log.Debug("Test serilog", context);
Log.Debug("Test from hangfirelog");
Thread.Sleep(5000);
}
Log.Debug("Done testjob");
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Error!");
return false;
}
}
}
}
HangfireContextSink.cs:
class HangfireContextSink : ILogEventSink
{
public const string PerformContextProperty = "PerformContext";
private readonly IFormatProvider formatProvider;
public HangfireContextSink(IFormatProvider formatProvider)
{
this.formatProvider = formatProvider;
}
public void Emit(LogEvent logEvent)
{
var message = logEvent.RenderMessage(formatProvider);
LogEventPropertyValue propertyValue;
if (logEvent.Properties.TryGetValue(PerformContextProperty, out propertyValue))
{
var context = (propertyValue as PerformContextProperty)?.PerformContext;
context?.WriteLine(ConsoleTextColor.Green, DateTimeOffset.Now + " " + message);
}
}
}
SinkExtensions.cs:
public static class SinkExtensions
{
public static LoggerConfiguration HangfireContextSink(this LoggerSinkConfiguration loggerSinkConfiguration, IFormatProvider formatProvider = null)
{
return loggerSinkConfiguration.Sink(new HangfireContextSink(formatProvider));
}
}
Serilog 配置:
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.HangfireContextSink()
.CreateLogger();
在 Serilog 配置中不要忘记调用 .Enrich.FromLogContext()
以便日志事件使用来自 LogContext
.
的属性进行丰富
上面的代码很简单,所以我不详细评论。如果您对代码有任何疑问,请提问,我会尽力解释不清楚的地方。
我希望能够使用我的日志通过 Hangfires context.console 发出事件,这样我就不需要使用 context.writeline 将日志事件输出到我的 hangfire 仪表板。
我试图为此目的实现一个特定的 serilog 接收器。但是由于我需要来自 hangfire 的 PerformContext(它在运行时注入到任务方法中),所以我无法在应用程序启动时配置日志接收器。我试图在任务方法中创建一个新的记录器,只是为了看看接收器是否真的有效,但它没有 - 任何人都可以看到它为什么不起作用,或者建议不同的方法吗?
这是水槽:
class HangfireContextSink : ILogEventSink {
private readonly IFormatProvider formatProvider;
private readonly PerformContext context;
public HangfireContextSink(IFormatProvider formatProvider, PerformContext context) {
this.formatProvider = formatProvider;
this.context = context;
}
public void Emit(LogEvent logEvent) {
var message = logEvent.RenderMessage(formatProvider);
context.WriteLine(ConsoleTextColor.Blue, DateTimeOffset.Now.ToString() + " " + message);
}
接收器配置:
public static class SinkExtensions {
public static LoggerConfiguration HangfireContextSink(this LoggerSinkConfiguration loggerSinkConfiguration, PerformContext context, IFormatProvider formatProvider = null) {
return loggerSinkConfiguration.Sink(new HangfireContextSink(formatProvider, context));
}
}
任务方法:
public static bool TestJob(PerformContext context) {
using (LogContext.PushProperty("Hangfirejob", "TestJob")) {
try {
using (var hangfireLog = new LoggerConfiguration().WriteTo.HangfireContextSink(context).CreateLogger()) {
var progress = context.WriteProgressBar("Progress");
for (int i = 0; i < 10; i++) {
context.WriteLine("Working with {0}", i);
progress.SetValue((i + 1) * 10);
Log.Debug("Test serilog");
hangfireLog.Debug("Test from hangfirelog");
Thread.Sleep(5000);
}
}
Log.Debug("Done testjob");
return true;
} catch (Exception ex) {
Log.Error(ex, "Error!");
return false;
}
}
}
消息不会记录到 Hangfire 控制台,因为您使用 Debug
日志级别记录它们,而默认 Serilog 级别是 Information
。您可以通过在 LoggerConfiguration
上调用 .MinimumLevel.Debug()
来更改日志记录级别。同样对于通过 Serilog.Log
static class 记录消息,您应该设置它的 Logger
属性.
这是一个固定的代码,将记录到 Hangfire 控制台:
using (LogContext.PushProperty("Hangfirejob", "TestJob"))
{
try
{
using (var hangfireLog = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.HangfireContextSink(context).CreateLogger())
{
// This is a dirty code that will be removed in final solution
var prevLogger = Log.Logger;
Log.Logger = hangfireLog;
var progress = context.WriteProgressBar("Progress");
for (int i = 0; i < 10; i++)
{
context.WriteLine("Working with {0}", i);
progress.SetValue((i + 1) * 10);
Log.Debug("Test serilog");
hangfireLog.Debug("Test from hangfirelog");
Thread.Sleep(5000);
}
Log.Debug("Done testjob");
Log.Logger = prevLogger;
}
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Error!");
return false;
}
}
这可行,但是为每个作业创建新日志的总体方法非常糟糕。您可以通过 LogEvent
中的 属性 传递 PerformContext
的实例来避免这种情况。您应该定义一个自定义 属性 class 派生自抽象 LogEventPropertyValue
并公开 PerformContext
.
这是最终代码:
PerformContextProperty.cs:
public class PerformContextProperty : LogEventPropertyValue
{
public PerformContext PerformContext { get; }
public PerformContextProperty(PerformContext performContext)
{
PerformContext = performContext;
}
public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null)
{
}
}
PerformContextEnricher.cs:
public class PerformContextEnricher : ILogEventEnricher
{
private readonly PerformContext performContext;
public PerformContextEnricher(PerformContext performContext)
{
this.performContext = performContext;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(new LogEventProperty(HangfireContextSink.PerformContextProperty, new PerformContextProperty(performContext)));
}
}
TestJob.cs:
public class TestJob
{
public static bool Execute(PerformContext context)
{
using (LogContext.PushProperty("Hangfirejob", "TestJob"))
using (LogContext.Push(new PerformContextEnricher(context)))
{
try
{
var progress = context.WriteProgressBar("Progress");
for (int i = 0; i < 10; i++)
{
context.WriteLine("Working with {0}", i);
progress.SetValue((i + 1) * 10);
Log.Debug("Test serilog", context);
Log.Debug("Test from hangfirelog");
Thread.Sleep(5000);
}
Log.Debug("Done testjob");
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Error!");
return false;
}
}
}
}
HangfireContextSink.cs:
class HangfireContextSink : ILogEventSink
{
public const string PerformContextProperty = "PerformContext";
private readonly IFormatProvider formatProvider;
public HangfireContextSink(IFormatProvider formatProvider)
{
this.formatProvider = formatProvider;
}
public void Emit(LogEvent logEvent)
{
var message = logEvent.RenderMessage(formatProvider);
LogEventPropertyValue propertyValue;
if (logEvent.Properties.TryGetValue(PerformContextProperty, out propertyValue))
{
var context = (propertyValue as PerformContextProperty)?.PerformContext;
context?.WriteLine(ConsoleTextColor.Green, DateTimeOffset.Now + " " + message);
}
}
}
SinkExtensions.cs:
public static class SinkExtensions
{
public static LoggerConfiguration HangfireContextSink(this LoggerSinkConfiguration loggerSinkConfiguration, IFormatProvider formatProvider = null)
{
return loggerSinkConfiguration.Sink(new HangfireContextSink(formatProvider));
}
}
Serilog 配置:
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.HangfireContextSink()
.CreateLogger();
在 Serilog 配置中不要忘记调用 .Enrich.FromLogContext()
以便日志事件使用来自 LogContext
.
上面的代码很简单,所以我不详细评论。如果您对代码有任何疑问,请提问,我会尽力解释不清楚的地方。