Serilog 日志記錄庫(kù)
Serilog 是一個(gè) .NET 平臺(tái)上的強(qiáng)大的日志記錄庫(kù)。它提供了豐富的 API 以及可插拔的日志格式化器和輸出器,使得在 .NET 應(yīng)用程序中實(shí)現(xiàn)可定制化的、可擴(kuò)展的日志記錄變得輕而易舉。
在本文中,我們將探討 Serilog 的一些基礎(chǔ)知識(shí)、API、配置和示例。
基礎(chǔ)知識(shí)
日志級(jí)別
Serilog 支持多個(gè)日志級(jí)別,包括以下級(jí)別(按照嚴(yán)重程度從高到低排列):
Fatal: 程序已經(jīng)無法繼續(xù)運(yùn)行,需要立即解決的問題。Error: 一個(gè)錯(cuò)誤發(fā)生,需要被處理。Warning: 一個(gè)警告,通常需要被留意,但是不需要立即處理。Information: 提供有用的信息,通常只有在調(diào)試應(yīng)用程序時(shí)才需要關(guān)注。Debug: 提供調(diào)試信息,有助于調(diào)試應(yīng)用程序。Verbose: 提供大量的細(xì)節(jié)信息,通常只用于調(diào)試復(fù)雜的問題。
日志輸出
Serilog 支持多種日志輸出,包括:
- Console
- File
- Seq
- Elasticsearch
此外,Serilog 還支持自定義日志輸出器。
日志格式
Serilog 支持多種日志格式化方式,包括:
- 簡(jiǎn)單文本格式
- JSON 格式
- Message Templates 格式(一種更加靈活的格式)
安裝
可以通過 NuGet 安裝 Serilog。
Install-Package Serilog
使用
基礎(chǔ)使用
在應(yīng)用程序中使用 Serilog 很簡(jiǎn)單。下面的示例演示了如何在控制臺(tái)輸出日志:
using Serilog; class Program { static void Main() { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .CreateLogger(); Log.Information("Hello, Serilog!"); Log.CloseAndFlush(); } }
上面的代碼將 Hello, Serilog! 輸出到控制臺(tái)。
詳細(xì)使用
日志級(jí)別
下面的示例演示了如何在 Serilog 中設(shè)置不同的日志級(jí)別:
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(); } }
上面的代碼將演示如何使用不同的日志級(jí)別來記錄日志消息。
#### 消息模板
Serilog 采用消息模板來格式化日志消息。下面是一個(gè)簡(jiǎn)單的消息模板示例:
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(); } }
在上面的示例中,我們使用了一個(gè)帶有模板的控制臺(tái)輸出器,并且在消息模板中使用了占位符 {Name}。當(dāng)日志記錄方法被調(diào)用時(shí),{Name} 將被替換為 Serilog。我們可以看到 Hello, Serilog! 在控制臺(tái)輸出。
日志屬性
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(); } }
在上面的示例中,我們使用了一個(gè)匿名類型來表示日志屬性。在日志消息中,我們使用了 @ 符號(hào)來引用這個(gè)匿名類型。當(dāng)日志記錄方法被調(diào)用時(shí),Serilog 將自動(dòng)將匿名類型的屬性添加到日志消息中。上面的代碼將輸出 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}")
這個(gè)也是官方的默認(rèn)模板,我們可以這個(gè)擴(kuò)展
.WriteTo.File("log.txt", outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
輸出到文件
rollingInterval: RollingInterval.Day 每天一個(gè)日志文件
outputTemplate 輸出格式模板
.WriteTo.File( $"logs\\log-.txt" , //每天一個(gè)文件 ,生成類似:log-20230914.txt //fileSizeLimitBytes: null 文件限制大?。簾o 如果不配置默認(rèn)是:1GB 1GB就不記錄了 //retainedFileCountLimit: null 保留文件數(shù)量 如果不配置只保留31天的日志 //https://github.com/serilog/serilog-sinks-file rollingInterval: RollingInterval.Day , retainedFileCountLimit: null , //fileSizeLimitBytes: null, //單個(gè)文件大?。?1024000 1024000是1M //rollOnFileSizeLimit: true 就是滾動(dòng)文件,如果超過單個(gè)文件大小,會(huì)滾動(dòng)文件 產(chǎn)生類似:log.txt log_001.txt log_002.txt fileSizeLimitBytes: 3024000 , rollOnFileSizeLimit: true , //非必填:指定最小等級(jí) restrictedToMinimumLevel: LogEventLevel.Information , //非必填: 也可以指定輸出格式:這種格式好像與系統(tǒng)默認(rèn)沒有什么區(qū)別 //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}" )
結(jié)構(gòu)化記錄日志
寫入變量
var itemNumber = 10; var itemCount = 999; // 使用占位符寫入 // 結(jié)果: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 );
寫入類
特別提示:記錄對(duì)象后 1.日志中會(huì)多一個(gè)$type屬性 2.日期類型數(shù)據(jù)格式化后都是這樣格式:2023-09-16T22:26:27.5905512+08:00
var wf = new WeatherForecast { Date = DateTime.Now.AddDays( 1 ) , TemperatureC = 55 , Summary = "" }; // @表示一個(gè)對(duì)象 這樣就可以把一個(gè)對(duì)象直接傳遞進(jìn)去 // 特別提示:記錄對(duì)象后 // 1.日志中會(huì)多一個(gè)$type屬性 // 2.日期類型數(shù)據(jù)格式化后都是這樣格式:2023-09-16T22:26:27.5905512+08:00 //結(jié)果:2023-09-15 22:26:27.601 +08:00 [INF] WeatherForecast 的數(shù)據(jù) {"Date":"2023-09-16T22:26:27.5905512+08:00","TemperatureC":55,"TemperatureF":130,"Summary":"","$type":"WeatherForecast"} this._logger.LogInformation( "WeatherForecast 的數(shù)據(jù) {@wf}" , wf );
寫入集合
List<string> list1 = new List<string>() { "q1" , "q2" }; //寫入集合 //結(jié)果:2023-09-15 22:36:46.751 +08:00 [INF] 集合的數(shù)據(jù) ["q1","q2"] this._logger.LogInformation( "集合的數(shù)據(jù) {@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" }}; //寫入集合 //結(jié)果: 2023-09-15 22:39:53.863 +08:00 [INF] 集合的數(shù)據(jù) [{"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( "集合的數(shù)據(jù) {@listw}" , listw );
寫入匿名類
var user = new { Name = "Nick" , Id = "nblumhardt" , add = new List<string>() { "add1" , "add2" } , man = new { age = 1 , names = "qq" } }; // @表示一個(gè)對(duì)象(上面這個(gè)是匿名類也可以寫入的) //結(jié)果: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() // 注冊(cè)日志上下文 .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:動(dòng)態(tài)傳遞上下文
配置模版定義上下文
outputTemplate中定義了2個(gè)上下文: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( "我傳遞上下文參數(shù)過來了" ); } //不是每個(gè)都要傳遞上下文,沒有傳遞也不報(bào)錯(cuò) this._logger.LogError( new Exception( "0異常啦" ) , "我自己造的" ); this._logger.LogInformation( new Random().Next( 10000 ).ToString() );
日志過濾器
Serilog 支持過濾器來控制日志輸出。下面的示例演示了如何使用過濾器來僅輸出錯(cuò)誤級(jí)別以上的日志:
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 過濾器來僅輸出錯(cuò)誤級(jí)別以上的日志。
幾種常用過濾方式
Filter.ByIncludingOnly:是包括
Filter.ByExcluding:是不包括
a.不同等級(jí),輸出不同文件
Func<LogEvent , bool> isInformation = ( logEvent ) => logEvent.Level == LogEventLevel.Information; Func<LogEvent , bool> isError = ( logEvent ) => logEvent.Level == LogEventLevel.Error; // 輸出到文件 //不同等級(jí),輸出不同文件 .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.根據(jù)消息內(nèi)容過濾,輸出不同文件
Func<LogEvent , bool> isbsWeatherForecastController = ( p ) => { var msg = p.RenderMessage(); if ( !string.IsNullOrEmpty( msg ) ) { return msg.Contains( "bs:WeatherForecastController" , StringComparison.OrdinalIgnoreCase ); } return false; }; //根據(jù)消息內(nèi)容過濾,輸出不同文件 .WriteTo.Logger( lg => { lg.Filter.ByIncludingOnly( p => isbsWeatherForecastController( p ) ) .Filter.ByIncludingOnly( isMidServices ) .WriteTo.File( $"logs\\WeatherForecastController-.txt" , rollingInterval: RollingInterval.Day ); } )
d.根據(jù)上下文值,輸出不同文件
//根據(jù)上下文值,輸出不同文件 .WriteTo.Logger( lg => { //lg.Filter.ByIncludingOnly( Matching.WithProperty( "UserId" , "svrooij" ) ) //下面這個(gè),也是等效寫法 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.實(shí)現(xiàn)接口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等級(jí)的,MidServices類產(chǎn)生的,判斷某個(gè)上下文是否為某個(gè)值 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 還支持使用配置文件來配置日志記錄。下面是一個(gè)配置文件示例:
<?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>
在上面的示例中,我們使用了一個(gè)配置文件來配置 Serilog。我們將日志級(jí)別設(shè)置為 Verbose,并且將輸出器設(shè)置為 Console。
擴(kuò)展
Serilog 提供了豐富的擴(kuò)展方式,包括:
- 日志輸出器擴(kuò)展
- 日志過濾器擴(kuò)展
- 日志格式化器擴(kuò)展
日志輸出器擴(kuò)展
Serilog 提供了多種擴(kuò)展方式來添加自定義日志輸出器。
控制臺(tái)輸出器擴(kuò)展
以下示例演示了如何添加一個(gè)自定義的控制臺(tái)輸出器:
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(); } }
在上面的示例中,我們定義了一個(gè)名為 CustomConsoleSink 的自定義控制臺(tái)輸出器,并將其添加到 Serilog 的輸出器列表中。
Elasticsearch 輸出器擴(kuò)展
以下示例演示了如何添加一個(gè)自定義的 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(); } }
在上面的示例中,我們定義了一個(gè)名為 CustomElasticsearchSink 的自定義 Elasticsearch 輸出器,并將其添加到 Serilog 的輸出器列表中。
日志過濾器擴(kuò)展
Serilog 提供了多種擴(kuò)展方式來添加自定義日志過濾器。
以下示例演示了如何添加一個(gè)自定義的日志過濾器:
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(); } }
在上面的示例中,我們定義了一個(gè)名為 CustomFilter 的自定義過濾器,并將其添加到 Serilog 的過濾器列表中。該過濾器將僅輸出 Environment 屬性為 Production 的日志。
日志格式化器擴(kuò)展
Serilog 提供了多種擴(kuò)展方式來添加自定義日志格式化器。
以下示例演示了如何添加一個(gè)自定義的日志格式化器:
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(); } }
本文來自博客園,作者:一事冇誠(chéng),轉(zhuǎn)載請(qǐng)注明原文鏈接:http://www.rzrgm.cn/ysmc/p/18086283

浙公網(wǎng)安備 33010602011771號(hào)