一. 简介
1. 说明
Serilog是一个用于.net应用程序的诊断日志库。它易于设置,拥有简介的API,并且可以在所有最新的.net平台上运行。虽然它甚至在最简单的应用程序中也很有用,但Serilog对结构化日志记录的支持在检测复杂、分布式和异步的应用程序和系统时非常出色。 和其它日志类库一样,Serilog可以输出到控制台、文件、数据库等多种载体。
2. 各种输出载体
下面是各种载体对应的程序集(很多,下面仅列出部分,可去Nuget上查找),本节重点介绍:控制台、各种文件(同步和异步)、SQLServer的写入。
【Serilog.Sinks.Console】
【Serilog.Sinks.File】
【Serilog.Sinks.Async】 异步写入文件,需要配合File程序集一块使用
【Serilog.Sinks.MSSqlServer】
【Serilog.Sinks.MySQL】
【Serilog.Sinks.Seq】
【Serilog.Sinks.Elasticsearch】
-----下面是Core Mvc专用的,内含多个程序集
【Serilog.AspNetCore】
3. 参考地址
(1).GitHub: https://github.com/serilog/serilog https://github.com/serilog/serilog-aspnetcore
(2).官网:https://serilog.net/
(3).参考博客:https://www.cnblogs.com/RainFate/p/12073662.html
https://www.cnblogs.com/diwu0510/p/12828519.html
二. 各种实操测试
1. 前言
(1). 日志级别
FATAL(致命错误) > ERROR(一般错误) > Warning(警告) > Information(一般信息) > DEBUG(调试信息)>Verbose(详细模式,即全部)
代码中通过MinimumLevel.Debug()来设置日志级别,设置后只能捕获该级别和高于该级别的日志信息了
(2). 日志模板
框架定义的参数有:LogLevel日志级别、Message日志信息、Exception异常信息、NewLine换行,可以自定义格式如下:
"{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100);
2. 输出控制台
(1). 通过Nuget安装程序集:【Serilog 2.10】【Serilog.Sinks.Console 3.1.1】
(2). 设置日志捕获级别、日志模板、输出途径(WriteTo.Console),并且createLogger即可
(3). 通过各种方法进行调用
注: Log.CloseAndFlush();就销毁了该对象,再次使用需要重新创建了。
代码分析:
{ string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100); Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() // 捕获的最小日志级别 .WriteTo.Console(LogEventLevel.Verbose, SerilogOutputTemplate) .CreateLogger(); Log.Information("123456789"); try { int.Parse("sfsdfsf"); } catch (Exception ex) { Log.Error(ex, ex.Message); } Log.CloseAndFlush(); }
3. 输入文件-同步
(1). 通过Nuget安装程序集:【Serilog 2.10】【Serilog.Sinks.File 4.1.0】
(2). 设置日志捕获级别、日志模板、输出途径(WriteTo.File),并且createLogger即可
(3). 通过各种方法进行调用
PS:WriteTo.File详解
path:默认路径是程序的bin目录+path参数,当然也可以写绝对路径,只需要写入参数就可以了
rollingInterval:创建文件的类别,可以是分钟,小时,天,月。 此参数可以让创建的log文件名 + 时间。例如ApiLog20210109.log
outputTemplate:日志模板,可以自定义
retainedFileCountLimit:设置日志文件个数最大值,默认31,意思就是只保留最近的31个日志文件,等于null时永远保留文件。
代码分享:
{ string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100); Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() // 捕获的最小日志级别 .WriteTo.File("MyLogs\ApiLog.txt", rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate, retainedFileCountLimit: null) .CreateLogger(); Log.Information("123456789"); try { int.Parse("sfsdfsf"); } catch (Exception ex) { Log.Error(ex, ex.Message); } Log.CloseAndFlush(); }
日志文件内容:
4. 输入文件-异步(推荐)
(1). 通过Nuget安装程序集:【Serilog 2.10】【Serilog.Sinks.File 4.1.0】【Serilog.Sinks.Async 1.4.0】
(2). 设置日志捕获级别、日志模板、输出途径(WriteTo.Async(a => a.File())),并且createLogger即可
(3). 通过各种方法进行调用
代码分享:
{ string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100); Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() // 捕获的最小日志级别 .WriteTo.Async(a => a.File("My_Logs\ApiLog.txt", rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate, retainedFileCountLimit: null)) .CreateLogger(); Log.Information("123456789"); try { int.Parse("sfsdfsf"); } catch (Exception ex) { Log.Error(ex, ex.Message); } Log.CloseAndFlush(); }
5. 按照日志级别存放到不同文件夹-(同步、异步)
(1). 通过Nuget安装程序集:【Serilog 2.10】【Serilog.Sinks.File 4.1.0】【Serilog.Sinks.Async 1.4.0】
(2). 通过Filter设置日志级别,其它类似
(3). 通过各种方法进行调用
代码分享:
{ string LogFilePath(string LogEvent) => $@"ALL_Logs{LogEvent}log.log"; string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100); //同步写法 //Log.Logger = new LoggerConfiguration() // .Enrich.FromLogContext() // .MinimumLevel.Debug() // 所有Sink的最小记录级别 // .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Debug).WriteTo.File(LogFilePath("Debug"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)) // .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Information).WriteTo.File(LogFilePath("Information"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)) // .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Warning).WriteTo.File(LogFilePath("Warning"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)) // .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Error).WriteTo.File(LogFilePath("Error"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)) // .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Fatal).WriteTo.File(LogFilePath("Fatal"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)) // .CreateLogger(); //异步写法 Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Debug() // 所有Sink的最小记录级别 .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Debug).WriteTo.Async(a => a.File(LogFilePath("Debug"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Information).WriteTo.Async(a => a.File(LogFilePath("Information"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Warning).WriteTo.Async(a => a.File(LogFilePath("Warning"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Error).WriteTo.Async(a => a.File(LogFilePath("Error"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Fatal).WriteTo.Async(a => a.File(LogFilePath("Fatal"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .CreateLogger(); Log.Debug("111111111"); Log.Information("22222222222"); Log.Warning("33333333333"); Log.Error("44444444444"); Log.Fatal("5555555555555"); try { int.Parse("sfsdfsf"); } catch (Exception ex) { Log.Error(ex, ex.Message); } Log.CloseAndFlush(); }
6. 自定义输出到指定文件夹(推荐)
思路:这里采用过滤器的来实现,需要在日志内容中定义一个属性,比如下面的{position},然后通过ByIncludingOnly(Matching.WithProperty<string>("position", p => p == "WebLog"))来筛选,然后再指定存放到那个文件夹下。
注:如果加上$符号,还要定义过滤器属性,则需要用{{}}
代码分享:
{ static string LogFilePath(string FileName) => $@"ALL_Logs{FileName}log.log"; string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100); Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Debug() // 所有Sink的最小记录级别 .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == "WebLog")).WriteTo.Async(a => a.File(LogFilePath("WebLog"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == "ApiLog")).WriteTo.Async(a => a.File(LogFilePath("ApiLog"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == "ErrorLog")).WriteTo.Async(a => a.File(LogFilePath("ErrorLog"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .CreateLogger(); Log.Information("{position}:111111", "WebLog"); Log.Information("{position}:222222", "ApiLog"); Log.Information($"{{position}}:333333", "ErrorLog"); try { int.Parse("sfsdfsf"); } catch (Exception ex) { Log.Error(ex, "{position}:" + ex.Message, "ErrorLog"); } Log.CloseAndFlush(); }
7. 输入SQLServer(未完成)
可参考:https://www.cnblogs.com/RainFate/p/12073662.html
三. 与Core MVC集成
前提:
通过Nuget安装程序集:【Serilog.AspNetCore 3.4.0】,该程序集涵盖了多个子程序,可以自己封装,你可以和控制台那样自行选择需要的程序集进行使用。
1. 写法1
类似控制台的形式,直接调用即可,可以自行封装一下, 然后在ConfigureService中实例化, 使用的时候直接通过log对象调用即可。这里封装LogUtils帮助类,可以分文件夹存储,包括普通信息的记录和Error错误,其它可自行封装。
PS:这种写法和上述控制台的类似,根据需要引入需要的程序集即可。
日志帮助类封装代码:
/// <summary> /// 日志帮助类 /// </summary> public class LogUtils { static string log1Name = "WebLog"; static string log2Name = "ApiLog"; static string log3Name = "ErrorLog"; /// <summary> /// 初始化日志 /// </summary> public static void InitLog() { static string LogFilePath(string FileName) => $@"{AppContext.BaseDirectory}ALL_Logs{FileName}log.log"; string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100); Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Debug() // 所有Sink的最小记录级别 .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log1Name)).WriteTo.Async(a => a.File(LogFilePath(log1Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log2Name)).WriteTo.Async(a => a.File(LogFilePath(log2Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log3Name)).WriteTo.Async(a => a.File(LogFilePath(log3Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .CreateLogger(); } /*****************************下面是不同日志级别*********************************************/ // FATAL(致命错误) > ERROR(一般错误) > Warning(警告) > Information(一般信息) > DEBUG(调试信息)>Verbose(详细模式,即全部) /// <summary> /// 普通日志 /// </summary> /// <param name="msg"></param> /// <param name="fileName"></param> public static void Info(string msg, string fileName="") { if (fileName==""|| fileName == log1Name) { Log.Information($"{{position}}:{msg}", log1Name); } else if (fileName == log2Name) { Log.Information($"{{position}}:{msg}", log2Name); } else { //输入其他的话,还是存放到第一个文件夹 Log.Information($"{{position}}:{msg}", log1Name); } } /// <summary> /// Error方法统一存放到ErrorLog文件夹 /// </summary> /// <param name="msg"></param> public static void Error(Exception ex) { Log.Error(ex, "{position}:" + ex.Message, log3Name); } }
ConfigureService注册:
public void ConfigureServices(IServiceCollection services) { //日志初始化 LogUtils.InitLog(); services.AddControllersWithViews(); }
调用代码:
public IActionResult Index() { LogUtils.Info("111111111", "WebLog"); LogUtils.Info("222222222", "ApiLog"); try { int.Parse("sfsdfafd"); } catch (Exception ex) { LogUtils.Error(ex); } return View(); }
2. 写法2
(1). 在Program中通过UseSerilog进行初始化日志,和上述基本类似,特别注意,这里要重写一下系统自带的日志捕捉,可以写为MinimumLevel.Override("Microsoft", LogEventLevel.Fatal) 否则,生成的日志文件中会生成很多我们不需要的日志信息。
(2). 在控制器中注入ILogger对象,使用即可。
注:这里实际上覆盖系统自带的日志。
program代码分享:
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); //初始化日志 webBuilder.UseSerilog((hostingContext, loggerConfiguration) => { string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:" + "{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100); loggerConfiguration .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Fatal) .Enrich.FromLogContext() .WriteTo.File($"{AppContext.BaseDirectory}MyLogs\log.log", rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate); }); });
调用代码分享:
public class Home2Controller : Controller { private readonly ILogger<Home2Controller> _logger; public Home2Controller(ILogger<Home2Controller> logger) { _logger = logger; } public string Index() { for (int i = 0; i < 50; i++) { Task.Run(() => { _logger.LogInformation($"测试日志信息:{Guid.NewGuid().ToString("N")}"); }); } return "ok"; } }
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。