Serilog 日志记录库
Serilog 是一个 .NET 平台上的强大的日志记录库。它提供了丰富的 API 以及可插拔的日志格式化器和输出器,使得在 .NET 应用程序中实现可定制化的、可扩展的日志记录变得轻而易举。
在本文中,我们将探讨 Serilog 的一些基础知识、API、配置和示例。
基础知识
日志级别
Serilog 支持多个日志级别,包括以下级别(按照严重程度从高到低排列):
Fatal
: 程序已经无法继续运行,需要立即解决的问题。Error
: 一个错误发生,需要被处理。Warning
: 一个警告,通常需要被留意,但是不需要立即处理。Information
: 提供有用的信息,通常只有在调试应用程序时才需要关注。Debug
: 提供调试信息,有助于调试应用程序。Verbose
: 提供大量的细节信息,通常只用于调试复杂的问题。
日志输出
Serilog 支持多种日志输出,包括:
- Console
- File
- Seq
- Elasticsearch
此外,Serilog 还支持自定义日志输出器。
日志格式
Serilog 支持多种日志格式化方式,包括:
- 简单文本格式
- JSON 格式
- Message Templates 格式(一种更加灵活的格式)
安装
可以通过 NuGet 安装 Serilog。
Install-Package Serilog
使用
基础使用
在应用程序中使用 Serilog 很简单。下面的示例演示了如何在控制台输出日志:
using Serilog; class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .CreateLogger(); Log.Information("Hello, Serilog!"); Log.CloseAndFlush(); } }
上面的代码将 Hello, Serilog!
输出到控制台。
详细使用
日志级别
下面的示例演示了如何在 Serilog 中设置不同的日志级别:
using Serilog; class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() .WriteTo.Console() .CreateLogger(); Log.Verbose("This is a verbose log message."); Log.Debug("This is a debug log message."); Log.Information("This is an informational log message."); Log.Warning("This is a warning log message."); Log.Error("This is an error log message."); Log.Fatal("This is a fatal log message."); Log.CloseAndFlush(); } }
上面的代码将演示如何使用不同的日志级别来记录日志消息。
#### 消息模板
Serilog 采用消息模板来格式化日志消息。下面是一个简单的消息模板示例:
using Serilog; class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") .CreateLogger(); Log.Information("Hello, {Name}!", "Serilog"); Log.CloseAndFlush(); } }
在上面的示例中,我们使用了一个带有模板的控制台输出器,并且在消息模板中使用了占位符 {Name}
。当日志记录方法被调用时,{Name}
将被替换为 Serilog
。我们可以看到 Hello, Serilog!
在控制台输出。
日志属性
Serilog 支持日志属性,这使得我们可以在日志消息中记录更多的信息。下面的示例演示了如何在日志消息中添加属性:
using Serilog; class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .CreateLogger(); Log.Information("Processed {@Count} records in {Time} ms.", new { Count = 10, Time = 123 }); Log.CloseAndFlush(); } }
在上面的示例中,我们使用了一个匿名类型来表示日志属性。在日志消息中,我们使用了 @
符号来引用这个匿名类型。当日志记录方法被调用时,Serilog 将自动将匿名类型的属性添加到日志消息中。上面的代码将输出 Processed { Count: 10, Time: 123 } records in 0 ms.
。
输出模板定义
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
这个也是官方的默认模板,我们可以这个扩展
.WriteTo.File("log.txt", outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
输出到文件
rollingInterval: RollingInterval.Day 每天一个日志文件
outputTemplate 输出格式模板
.WriteTo.File( $"logs\\log-.txt" , //每天一个文件 ,生成类似:log-20230914.txt //fileSizeLimitBytes: null 文件限制大小:无 如果不配置默认是:1GB 1GB就不记录了 //retainedFileCountLimit: null 保留文件数量 如果不配置只保留31天的日志 //https://github.com/serilog/serilog-sinks-file rollingInterval: RollingInterval.Day , retainedFileCountLimit: null , //fileSizeLimitBytes: null, //单个文件大小: 1024000 1024000是1M //rollOnFileSizeLimit: true 就是滚动文件,如果超过单个文件大小,会滚动文件 产生类似:log.txt log_001.txt log_002.txt fileSizeLimitBytes: 3024000 , rollOnFileSizeLimit: true , //非必填:指定最小等级 restrictedToMinimumLevel: LogEventLevel.Information , //非必填: 也可以指定输出格式:这种格式好像与系统默认没有什么区别 //outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}" //outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {NewLine}{Version}{myval}" outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {NewLine}{Version}{myval} {NewLine}{UserId}{myid}{NewLine}" )
结构化记录日志
写入变量
var itemNumber = 10; var itemCount = 999; // 使用占位符写入 // 结果:2023-09-15 22:23:54.576 +08:00 [INF] Processing item 10 of 999 this._logger.LogDebug( "Processing item {ItemNumber} of {ItemCount}" , itemNumber , itemCount );
写入类
特别提示:记录对象后 1.日志中会多一个$type属性 2.日期类型数据格式化后都是这样格式:2023-09-16T22:26:27.5905512+08:00
var wf = new WeatherForecast { Date = DateTime.Now.AddDays( 1 ) , TemperatureC = 55 , Summary = "" }; // @表示一个对象 这样就可以把一个对象直接传递进去 // 特别提示:记录对象后 // 1.日志中会多一个$type属性 // 2.日期类型数据格式化后都是这样格式:2023-09-16T22:26:27.5905512+08:00 //结果:2023-09-15 22:26:27.601 +08:00 [INF] WeatherForecast 的数据 {"Date":"2023-09-16T22:26:27.5905512+08:00","TemperatureC":55,"TemperatureF":130,"Summary":"","$type":"WeatherForecast"} this._logger.LogInformation( "WeatherForecast 的数据 {@wf}" , wf );
写入集合
List<string> list1 = new List<string>() { "q1" , "q2" }; //写入集合 //结果:2023-09-15 22:36:46.751 +08:00 [INF] 集合的数据 ["q1","q2"] this._logger.LogInformation( "集合的数据 {@list1}" , list1 ); List<WeatherForecast> listw = new List<WeatherForecast>() { new WeatherForecast { Date = DateTime.Now.AddDays( 1 ) , TemperatureC = 11 , Summary = "one" }, new WeatherForecast { Date = DateTime.Now.AddDays( 2 ) , TemperatureC = 22 , Summary = "two" }}; //写入集合 //结果: 2023-09-15 22:39:53.863 +08:00 [INF] 集合的数据 [{"Date":"2023-09-16T22:39:53.8634787+08:00","TemperatureC":11,"TemperatureF":51,"Summary":"one","$type":"WeatherForecast"},{"Date":"2023-09-17T22:39:53.8634842+08:00","TemperatureC":22,"TemperatureF":71,"Summary":"two","$type":"WeatherForecast"}] this._logger.LogInformation( "集合的数据 {@listw}" , listw );
写入匿名类
var user = new { Name = "Nick" , Id = "nblumhardt" , add = new List<string>() { "add1" , "add2" } , man = new { age = 1 , names = "qq" } }; // @表示一个对象(上面这个是匿名类也可以写入的) //结果:2023-09-15 22:23:54.576 +08:00 [INF] Logged on user {"Name":"Nick","Id":"nblumhardt","add":["add1","add2"],"man":{"age":1,"names":"qq"}} this._logger.LogInformation( "Logged on user {@user}" , user );
输出上下文
方式1:固定值的上下文
Log.Logger = new LoggerConfiguration() // 注册日志上下文 .Enrich.FromLogContext() // 上下文 .Enrich.WithProperty( "Version" , "1.0.0" ) .Enrich.WithProperty( "myval" , 123 )
配置模版定义上下文
//outputTemplate中配置写入上下文 outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {NewLine}{Version}{myval}"
方式2:动态传递上下文
配置模版定义上下文
outputTemplate中定义了2个上下文:UserId和myid outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {NewLine}{Version}{myval} {NewLine}{UserId}{myid}{NewLine}"
代码传递值
//利用BeginScope传递上下文 using ( this._logger.BeginScope( new Dictionary<string , object> { ["UserId"] = "svrooij" , ["myid"] = 123456 , } ) ) { this._logger.LogInformation( "我传递上下文参数过来了" ); } //不是每个都要传递上下文,没有传递也不报错 this._logger.LogError( new Exception( "0异常啦" ) , "我自己造的" ); this._logger.LogInformation( new Random().Next( 10000 ).ToString() );
日志过滤器
Serilog 支持过滤器来控制日志输出。下面的示例演示了如何使用过滤器来仅输出错误级别以上的日志:
using Serilog; class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .Filter.ByIncludingOnly(logEvent => logEvent.Level >= LogEventLevel.Error) .CreateLogger(); Log.Verbose("This is a verbose log message."); Log.Debug("This is a debug log message."); Log.Information("This is an informational log message."); Log.Warning("This is a warning log message."); Log.Error("This is an error log message."); Log.Fatal("This is a fatal log message."); Log.CloseAndFlush(); } }
在上面的示例中,我们使用了 ByIncludingOnly
过滤器来仅输出错误级别以上的日志。
几种常用过滤方式
Filter.ByIncludingOnly:是包括
Filter.ByExcluding:是不包括
a.不同等级,输出不同文件
Func<LogEvent , bool> isInformation = ( logEvent ) => logEvent.Level == LogEventLevel.Information; Func<LogEvent , bool> isError = ( logEvent ) => logEvent.Level == LogEventLevel.Error; // 输出到文件 //不同等级,输出不同文件 .WriteTo.Logger( lg => { lg.Filter.ByIncludingOnly( p => isInformation( p ) ) .WriteTo.File( $"logs\\log_Information-.txt" , rollingInterval: RollingInterval.Day ); } ) .WriteTo.Logger( lg => { lg.Filter.ByIncludingOnly( p => isError( p ) ) .WriteTo.File( $"logs\\log_Error-.txt" , rollingInterval: RollingInterval.Day ); } )
b.不同类,输出不同文件
//不同类,输出不同文件 .WriteTo.Logger( lg => { lg.Filter.ByIncludingOnly( Matching.FromSource<WeatherForecastController>() ) .WriteTo.File( $"logs\\WeatherForecastController-.txt" , rollingInterval: RollingInterval.Day ); } ) .WriteTo.Logger( lg => { lg.Filter.ByIncludingOnly( Matching.FromSource<ValuesController>() ) .WriteTo.File( $"logs\\ValuesController-.txt" , rollingInterval: RollingInterval.Day ); } )
c.根据消息内容过滤,输出不同文件
Func<LogEvent , bool> isbsWeatherForecastController = ( p ) => { var msg = p.RenderMessage(); if ( !string.IsNullOrEmpty( msg ) ) { return msg.Contains( "bs:WeatherForecastController" , StringComparison.OrdinalIgnoreCase ); } return false; }; //根据消息内容过滤,输出不同文件 .WriteTo.Logger( lg => { lg.Filter.ByIncludingOnly( p => isbsWeatherForecastController( p ) ) .Filter.ByIncludingOnly( isMidServices ) .WriteTo.File( $"logs\\WeatherForecastController-.txt" , rollingInterval: RollingInterval.Day ); } )
d.根据上下文值,输出不同文件
//根据上下文值,输出不同文件 .WriteTo.Logger( lg => { //lg.Filter.ByIncludingOnly( Matching.WithProperty( "UserId" , "svrooij" ) ) //下面这个,也是等效写法 lg.Filter.ByIncludingOnly( Matching.WithProperty<string>( "UserId" , str => !string.IsNullOrEmpty( str ) && str.Equals( "svrooij" , StringComparison.OrdinalIgnoreCase ) ) ) .WriteTo.File( $"logs\\WeatherForecastController-.txt" , rollingInterval: RollingInterval.Day , outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {NewLine}{UserId}{myid}{NewLine}" ); } )
e.实现接口ILogEventFilter
public class CustomFilter : ILogEventFilter { private readonly string _propertyName; private readonly string _propertyValue; public CustomFilter ( string propertyName , string propertyValue ) { _propertyName = propertyName; _propertyValue = propertyValue; } public bool IsEnabled ( LogEvent logEvent ) { //下面的判断逻辑是:Information等级的,MidServices类产生的,判断某个上下文是否为某个值 if ( logEvent.Level == LogEventLevel.Information ) { var f = Matching.FromSource<MidServices>(); bool bl = f.Invoke( logEvent ); if ( bl ) { var f2 = Matching.WithProperty<string>( _propertyName , str => !string.IsNullOrEmpty( str ) && str.Equals( _propertyValue , StringComparison.OrdinalIgnoreCase ) ); bool bl2 = f2.Invoke( logEvent ); if ( bl2 ) { return true; } } } return false; } }
配置文件
Serilog 还支持使用配置文件来配置日志记录。下面是一个配置文件示例:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="serilog:minimum-level" value="Verbose" /> <add key="serilog:write-to:Console" /> </appSettings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /> </startup> </configuration>
在上面的示例中,我们使用了一个配置文件来配置 Serilog。我们将日志级别设置为 Verbose
,并且将输出器设置为 Console
。
扩展
Serilog 提供了丰富的扩展方式,包括:
- 日志输出器扩展
- 日志过滤器扩展
- 日志格式化器扩展
日志输出器扩展
Serilog 提供了多种扩展方式来添加自定义日志输出器。
控制台输出器扩展
以下示例演示了如何添加一个自定义的控制台输出器:
using Serilog; using Serilog.Configuration; using Serilog.Events; using Serilog.Formatting; public static class CustomConsoleSinkExtensions { public static LoggerConfiguration CustomConsole( this LoggerSinkConfiguration sinkConfiguration, ITextFormatter formatter = null, LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum) { return sinkConfiguration.Sink( new CustomConsoleSink(formatter), restrictedToMinimumLevel); } } public class CustomConsoleSink : ILogEventSink { private readonly ITextFormatter _formatter; public CustomConsoleSink(ITextFormatter formatter) { _formatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); } public void Emit(LogEvent logEvent) { var message = new StringWriter(); _formatter.Format(logEvent, message); Console.WriteLine(message.ToString()); } } class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.CustomConsole() .CreateLogger(); Log.Information("Hello, Serilog!"); Log.CloseAndFlush(); } }
在上面的示例中,我们定义了一个名为 CustomConsoleSink
的自定义控制台输出器,并将其添加到 Serilog 的输出器列表中。
Elasticsearch 输出器扩展
以下示例演示了如何添加一个自定义的 Elasticsearch 输出器:
using Serilog; using Serilog.Configuration; using Serilog.Events; using Serilog.Formatting; using Serilog.Sinks.Elasticsearch; public static class CustomElasticsearchSinkExtensions { public static LoggerConfiguration CustomElasticsearch( this LoggerSinkConfiguration sinkConfiguration, ITextFormatter formatter = null, ElasticsearchSinkOptions elasticsearchOptions = null, LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum) { return sinkConfiguration.Sink( new CustomElasticsearchSink(formatter, elasticsearchOptions), restrictedToMinimumLevel); } } public class CustomElasticsearchSink : ElasticsearchSink { public CustomElasticsearchSink( ITextFormatter formatter, ElasticsearchSinkOptions elasticsearchOptions) : base(elasticsearchOptions) { Formatter = formatter; } public ITextFormatter Formatter { get; } protected override void EmitBatch( IEnumerable<LogEvent> events, IBulkWriter writer) { foreach (var logEvent in events) { var message = new StringWriter(); Formatter.Format(logEvent, message); var json = message.ToString(); writer.IndexDocument(json); } } } class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.CustomElasticsearch(new ElasticsearchSinkOptions(new Uri("[http://localhost:9200](http://localhost:9200/) ")),new ElasticsearchJsonFormatter(),LogEventLevel.Information) .CreateLogger(); Log.Information("Hello, Serilog!"); Log.CloseAndFlush(); } }
在上面的示例中,我们定义了一个名为 CustomElasticsearchSink
的自定义 Elasticsearch 输出器,并将其添加到 Serilog 的输出器列表中。
日志过滤器扩展
Serilog 提供了多种扩展方式来添加自定义日志过滤器。
以下示例演示了如何添加一个自定义的日志过滤器:
using Serilog; using Serilog.Configuration; using Serilog.Events; public static class CustomFilterExtensions { public static LoggerConfiguration CustomFilter( this LoggerFilterConfiguration filterConfiguration, string propertyName, string propertyValue, LogEventLevel minimumLevel = LevelAlias.Minimum) { return filterConfiguration.Add( new CustomFilter(propertyName, propertyValue), minimumLevel); } } public class CustomFilter : ILogEventFilter { private readonly string _propertyName; private readonly string _propertyValue; public CustomFilter(string propertyName, string propertyValue) { _propertyName = propertyName; _propertyValue = propertyValue; } public bool IsEnabled(LogEvent logEvent) { if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); var property = logEvent.Properties[_propertyName]; if (property == null) return false; return property.ToString().Equals(_propertyValue); } } class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .Filter.CustomFilter("Environment", "Production") .CreateLogger(); Log.Information("Hello, Serilog!"); Log.CloseAndFlush(); } }
在上面的示例中,我们定义了一个名为 CustomFilter
的自定义过滤器,并将其添加到 Serilog 的过滤器列表中。该过滤器将仅输出 Environment
属性为 Production
的日志。
日志格式化器扩展
Serilog 提供了多种扩展方式来添加自定义日志格式化器。
以下示例演示了如何添加一个自定义的日志格式化器:
using Serilog; using Serilog.Configuration; using Serilog.Events; using Serilog.Formatting.Display; public static class CustomFormatterExtensions { public static LoggerConfiguration CustomFormatter( this LoggerConfiguration loggerConfiguration, string format, IFormatProvider formatProvider = null, LogEventLevel minimumLevel = LevelAlias.Minimum) { return loggerConfiguration.Sink( new CustomFormatterSink(format, formatProvider), minimumLevel); } } public class CustomFormatterSink : ILogEventSink { private readonly MessageTemplateTextFormatter _formatter; public CustomFormatterSink(string format, IFormatProvider formatProvider = null) { _formatter = new MessageTemplateTextFormatter(format, formatProvider); } public void Emit(LogEvent logEvent) { var message = new StringWriter(); _formatter.Format(logEvent, message); Console.WriteLine(message.ToString()); } } class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.CustomFormatter("Hello, {Name}!", null, LogEventLevel.Information) .CreateLogger(); Log.Information("{Name}", "Serilog"); Log.CloseAndFlush(); } }
本文来自博客园,作者:一事冇诚,转载请注明原文链接:https://www.cnblogs.com/ysmc/p/18086283