在Serilog中更改/覆盖日志事件级别

时间:2017-10-31 13:17:27

标签: c# .net serilog

有没有办法动态更改某些事件的日志级别? (可能是命名空间或谓词)

我正在寻找类似.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)的内容,但我真正想要做的是将Information名称空间的Microsoft事件级别更改为Verbose。更重要的事件应保持原样。

编辑:

Enrichers无法更改日志事件级别。 enter image description here

3 个答案:

答案 0 :(得分:8)

这是可能的,但并非完全直截了当,所以要束缚自己!

1。创建一个接收器包装器

您需要在目标接收器周围创建一个包装器,而不是增强器。包装器将从日志记录管道接收事件(相当便宜)创建具有相同属性的新事件,并将它们转发到实际的接收器:

class LevelBoostingWrapper : ILogEventSink, IDisposable
{
    readonly ILogEventSink _wrappedSink;

    public LevelBoostingWrapper(ILogEventSink wrappedSink)
    {
        _wrappedSink = wrappedSink;
    }

    public void Emit(LogEvent logEvent)
    {
        if (logEvent.Level == LogEventLevel.Warning)
        {
            var boosted = new LogEvent(
                logEvent.Timestamp,
                LogEventLevel.Warning, // <- the boost
                logEvent.Exception,
                logEvent.MessageTemplate,
                logEvent.Properties
                    .Select(kvp => new LogEventProperty(kvp.Key, kvp.Value)));

            _wrappedSink.Emit(boosted);
        }
        else
        {
            _wrappedSink.Emit(logEvent);
        }
    }

    public void Dispose()
    {
        (_wrappedSink as IDisposable)?.Dispose();
    }
}

当然,决定修改哪些事件的实际标准取决于你。

2。将包装器挂钩到配置语法

这个小扩展使得设置包装器更加愉快:

static class LoggerSinkConfigurationExtensions
{
    public static LoggerConfiguration Boosted(
        this LoggerSinkConfiguration lsc,
        Action<LoggerSinkConfiguration> writeTo)
    {
        return LoggerSinkConfiguration.Wrap(
            lsc,
            wrapped => new LevelBoostingWrapper(wrapped),
            writeTo);
    }
}

3。将包装器添加到配置

最后,在记录器配置中,应用包装器:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Boosted(wt => wt.Console())
    .CreateLogger();

Log.Information("This will be unchanged");
Log.Warning("This will be boosted to Error");

Log.CloseAndFlush();

答案 1 :(得分:2)

此解决方案只是基于上述尼古拉斯答案的更具体的示例。

在我的.NET Core 3.1应用程序中,我具有与Tamas相同的更改日志级别的要求。我也有创建审计日志的要求。

更具体地说,我想保留https://github.com/aspnet/HttpClientFactory/blob/master/src/Microsoft.Extensions.Http/Logging/LoggingHttpMessageHandler.cs中定义的默认http客户端日志记录 但将其作为DEBUG而不是INFO。

我尝试了Serilog UseSerilogRequestLogging,如 https://nblumhardt.com/2019/10/serilog-in-aspnetcore-3/ https://nblumhardt.com/2019/10/serilog-mvc-logging/ 但没有成功。

我已经创建了一个Enricher,它可以获取日志级别并从中创建另一个属性。我可以扩展此扩展程序,以将较低的级别报告为httpClient的实际日志级别,但是它不能解决日志问题,只是以不同的方式显示。

上面的尼古拉斯定义的接收器是正确的方法。

棘手的部分是在水槽之后获得浓缩器。 因此,我们可以创建https://github.com/serilog/serilog/wiki/Configuration-Basics

底部记录的子记录器

一些代码显示了具体示例

public sealed class HttpClientLogLevelWrapper : ILogEventSink, IDisposable
{
    private const string SourceContext = "SourceContext";
    private const string HttpClientNamespace = "\"System.Net.Http.HttpClient";

    private readonly ILogEventSink _wrappedSink;
    private readonly LogEventLevel _logEventLevelTarget;

    /// <summary>
    /// Initializes a new instance of the <see cref="HttpClientLogLevelWrapper"/> class.
    /// </summary>
    /// <param name="wrappedSink">The wrapped sink.</param>
    /// <param name="logEventLevelTarget">The log event level target.</param>
    public HttpClientLogLevelWrapper(ILogEventSink wrappedSink, LogEventLevel logEventLevelTarget)
    {
        _wrappedSink = wrappedSink;
        _logEventLevelTarget = logEventLevelTarget;
    }

    public void Emit(LogEvent logEvent)
    {
        if (logEvent == null)
        {
            throw new ArgumentNullException(nameof(logEvent));
        }

        if (IsHttpClientInfoLog(logEvent))
        {
            var newLogEvent = new LogEvent(logEvent.Timestamp,
                _logEventLevelTarget,  // this is the only differnce with the original logEvent
                logEvent.Exception, logEvent.MessageTemplate,
                logEvent.Properties.Select(kvp => new LogEventProperty(kvp.Key, kvp.Value)));
            _wrappedSink.Emit(newLogEvent);
        }
        else
        {
            _wrappedSink.Emit(logEvent);
        }
    }

    private static bool IsHttpClientInfoLog(LogEvent logEvent)
    {
        if (logEvent.Properties.TryGetValue(SourceContext, out LogEventPropertyValue sourceContext))
        {
            string className = sourceContext.ToString();
            if (!string.IsNullOrEmpty(className)
                && className.StartsWith(HttpClientNamespace, StringComparison.OrdinalIgnoreCase)
                && logEvent.Level == LogEventLevel.Information)
            {
                return true;
            }
        }

        return false;
    }

    public void Dispose()
    {
        (_wrappedSink as IDisposable)?.Dispose();
    }
}

public static class LoggerSinkConfigurationExtensions
{
    public static LoggerConfiguration LowerHttpClientLoggingSink(this LoggerSinkConfiguration lsc, Action<LoggerSinkConfiguration> writeTo)
    {
        return LoggerSinkConfiguration.Wrap(lsc, wrapped => new HttpClientLogLevelWrapper(wrapped, LogEventLevel.Verbose), writeTo, LogEventLevel.Debug, null);
    }
}

,然后是程序/主程序中现在非常复杂的Logger配置

 // First read the wished minimum logger level, read from the enviromment variable.
 LogEventLevel minimumLoggerLevel = GetMinimumLogLevelFromEnvironmentVariable();

 // global shared logger, created BEFORE the host build to be able to log starting and ending the service.
 Log.Logger = new LoggerConfiguration()
     .MinimumLevel.Is(minimumLoggerLevel) 
     .MinimumLevel.Override("Microsoft", LogEventLevel.Error)
     .Enrich.FromLogContext()
     .Enrich.WithThreadId()
     .WriteTo.LowerHttpClientLoggingSink(wt => wt  // LowerHttpClientLogging update the log level from Info to Debug for HttpClient related logs.
        .Logger(lc => lc  // require a sub logger to have the Enrich AFTER the Sink!
        .Enrich.With(new LogLevelEnricher(LoggerAudit.AuditProperty, LogLevelUpperName)) // create levelUpper property and manage AuditProperty AFTER the sink!             
         .WriteTo
         .Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss,fff} [{ThreadId}] {" + LogLevelUpperName + "} - {Message:lj}{NewLine}{Exception}")))
         .CreateLogger();

上面的outputTemplate实际上是要匹配在其他项目中为log4j定义的模式,因为filebeat会在ElasticSearch / Kibana中考虑此模式。

答案 2 :(得分:0)

此接收器换行非常适合默认接收器。但是,我想使用配置文件配置serilog,而且还要包装已配置的接收器,以将特定的调用修改为较低的日志级别。

这是我在appsetting.json中的接收器的配置

{
"Serilog": {
    "MinimumLevel": {
        "Default": "Verbose",
        "Override": {
            "Microsoft": "Warning",
            "System": "Warning"
        }
    },
    "WriteTo": [
        {
            "Name": "Console",
            "Args": {
                "outputTemplate": "===> {Timestamp:HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}"
            }
        },
        {
            "Name": "RollingFile",
            "Args": {
                "pathFormat": "c:\path\file.txt",
                "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] - {Message}{NewLine}{Exception}"
            }
        },
        {
            "Name": "DurableHttpUsingTimeRolledBuffers",
            "Args": {
                "requestUri": "https://[elastic]",
                "bufferPathFormat": "c:\path\file.json"
            }
        }

现在我要做的是从此配置创建记录器:

var configuration = new ConfigurationBuilder()
                  .SetBasePath(Directory.GetCurrentDirectory())
                  .AddJsonFile("appsettings.json", false, true)
                  .Build();
var configLoggerConfig = new LoggerConfiguration().ReadFrom.Configuration(configuration);
var configLogger = configLoggerConfig.CreateLogger();

然后尝试将其包装:

var wrappedLoggerconfig = new LoggerConfiguration().WriteTo.LogLevelModifyingSink(wt => wt.Sink(configLogger), modifiers, levelSwitch);
        Log.Logger = wrappedLoggerconfig.CreateLogger();

修饰符是一个类,其中包含要修改的特定事件的逻辑。 LogModifyingSink扩展方法看起来像这样:

public static LoggerConfiguration LogLevelModifyingSink(
         this LoggerSinkConfiguration loggerConfiguration,
         Action<LoggerSinkConfiguration> writeTo,
         ILogLevelModifiers logLevelModifiers,
         LoggingLevelSwitch levelSwitch)
    {
        return LoggerSinkConfiguration.Wrap(
            loggerConfiguration,
            wrapped => new LogLevelModifyingSink(wrapped, logLevelModifiers),
            writeTo,
            LogEventLevel.Verbose,
            levelSwitch); 
    }

其中LogLevelModifyingSink是发出修改后的日志的wrapperink:

public class LogLevelModifyingSink : ILogEventSink, IDisposable
{
    readonly ILogEventSink wrappedSink;
    readonly IEnumerable<LogLevelModifier> logLevelModifiers;

    public LogLevelModifyingSink(ILogEventSink wrappedSink, ILogLevelModifiers logLevelModifiers)
    {
        this.wrappedSink = wrappedSink;
        this.logLevelModifiers = logLevelModifiers?.GetLevelModifiers();
    }

    public void Dispose()
    {
        (wrappedSink as IDisposable)?.Dispose();
    }

    public void Emit(LogEvent logEvent)
    {
        var message = logEvent.RenderMessage();
        Console.WriteLine(DateTimeOffset.Now.ToString() + " " + message);

        if (wrappedSink != null && logLevelModifiers != null && logLevelModifiers.Any())
        {
            foreach(var modifier in logLevelModifiers)
            {
                if (modifier.ShouldModify(logEvent))
                {
                    wrappedSink.Emit(modifier.ModifyEvent(logEvent));
                    return;
                }
            }
        }
        wrappedSink.Emit(logEvent);
    }
}

现在这部分起作用。由所有三个接收器的包装程序处理日志消息,但是,通过从新配置创建记录器,不会从配置文件中传递最低级别和最低级别替代的设置,并且接缝无法在运行时获取这些设置。 我觉得这种解决方案不是可行的方法,因为我两次创建了记录器。所以我的问题是,是否有更好的方法来包装配置中的接收器?有可能吗?以及如何保留配置的设置?