【问题标题】:Serilog output to hangfire context consoleSerilog 输出到hangfire 上下文控制台
【发布时间】:2018-08-14 07:50:23
【问题描述】:

我希望能够使用我的日志通过 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;
                }
            }
        }

【问题讨论】:

    标签: .net hangfire serilog


    【解决方案1】:

    消息不会记录到 Hangfire 控制台,因为您使用 Debug 日志级别记录它们,而默认 Serilog 级别是 Information。您可以通过在LoggerConfiguration 上调用.MinimumLevel.Debug() 来更改日志记录级别。此外,对于通过Serilog.Log 静态类记录消息,您应该设置其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 的实例来避免这种情况。您应该定义一个从抽象 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 的属性丰富日志事件。

    上面的代码非常简单,这就是为什么我不详细评论它的原因。如果您对代码有任何疑问,请提出,我会尽力解释不清楚的时刻。

    【讨论】:

    • 我现在正在浏览它,没有工作,它看起来和我想要的完全一样 - 我知道每个hangfire作业的一次性日志非常混乱(我不敢相信我忘记了最低级别!)。丰富的方法看起来正是我需要的“漂亮”方法 - 非常感谢!
    • 快速跟进 - 我有一个问题,使用此接收器会阻止输出到其他接收器。我似乎无法弄清楚阻塞发生在哪里,并且在记录器配置中首先链接哪个接收器并不重要。 hangfire 上的上下文接收消息,但其他接收器不接收消息:/
    • 我似乎已经缩小了范围 - 它并没有阻止所有接收器,只有 SEQ 接收器,它似乎是一个格式问题,因为 SEQ 接收器需要 json 可格式化属性值。我现在正在研究一个简单的修复方法
    • 我将 PerformContextProperty 更改为继承自 ScalarValue 而不是基本抽象类。可能应该弄清楚如何正确呈现属性以支持 json 格式,但我现在只需要快速修复。
    • 您可以通过在PerformContextProperty 类的Render() 方法中删除NotImplementedException 的抛出来解决此问题。我已经编辑了我的答案 - 只需将此方法留给空的主体即可。没关系,因为 PerformContextProperty.Render() 只会为 PerformContext 属性调用,它不会带来任何有用的有效负载。但是,这种方法将在结果 json 中输出带有空字符串值的 PerformContext 属性。我希望这对您来说不是问题,因为没有简单的方法可以从输出中抑制添加的属性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多