有没有办法动态更改某些事件的日志级别? (可能是命名空间或谓词)
我正在寻找类似.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
的内容,但我真正想要做的是将Information
名称空间的Microsoft
事件级别更改为Verbose
。更重要的事件应保持原样。
编辑:
答案 0 :(得分:8)
这是可能的,但并非完全直截了当,所以要束缚自己!
您需要在目标接收器周围创建一个包装器,而不是增强器。包装器将从日志记录管道接收事件(相当便宜)创建具有相同属性的新事件,并将它们转发到实际的接收器:
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();
}
}
当然,决定修改哪些事件的实际标准取决于你。
这个小扩展使得设置包装器更加愉快:
static class LoggerSinkConfigurationExtensions
{
public static LoggerConfiguration Boosted(
this LoggerSinkConfiguration lsc,
Action<LoggerSinkConfiguration> writeTo)
{
return LoggerSinkConfiguration.Wrap(
lsc,
wrapped => new LevelBoostingWrapper(wrapped),
writeTo);
}
}
最后,在记录器配置中,应用包装器:
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);
}
}
现在这部分起作用。由所有三个接收器的包装程序处理日志消息,但是,通过从新配置创建记录器,不会从配置文件中传递最低级别和最低级别替代的设置,并且接缝无法在运行时获取这些设置。 我觉得这种解决方案不是可行的方法,因为我两次创建了记录器。所以我的问题是,是否有更好的方法来包装配置中的接收器?有可能吗?以及如何保留配置的设置?