用到的单词

Sink 接收器模块、输出方式、接收模块库、输出模块库 Diagnostic 诊断 Enricher 扩展器 embedded 嵌入式的 compact 紧凑的、简洁的 concept 概念 usage 用法 restrict 限制、约束 raise 提升 necessary 必要的 digging 挖掘

一、基础用法

1.1 添加 Nuget 引用

Serilog.AspNetCore 日志包主体

Serilog.AspNetCore.RollingFile 将日志写入文件

1.2 注册服务

1.2.1 在 appsettings.json 中添加 Serilog 节点。

简单说明下配置文件的意思:

  • 将日志写入RollingFile(文件)和Console(控制台)。
  • RollingFile 的具体配置:记录文件到 根目录/logs/{日期}.txt 文件内,每天记录一个文件,并且只记录 Warning 及其以上的日志;
  • 默认日志级别记录 Debug 及其以上的日志。
  • 如果日志包含 Microsoft System ,只记录级别为 Information 及以上的日志。
{
"Serilog": {
"WriteTo": [
{
"Name": "RollingFile",
"Args": {
"pathFormat": "logs\\{Date}.txt",
"RestrictedToMinimumLevel": "Warning"
}
},
{
"Name": "Console"
}
],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Information",
"System": "Information"
}
}
},
}

1.2.2 修改 program.cs,注册 Serilog

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).UseSerilog((context, configure) =>
{
configure.ReadFrom.Configuration(context.Configuration);
});

1.2.3 简单配置完成,现在可以在项目中方便的使用 Serilog 了。

private readonly ILogger<HomeController> _logger;

public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
} public IActionResult Index()
{
_logger.LogError("Error 测试");
return View();
}

二、Serilog 的重要组成部分

2.1 日志接收器 (Sink)

接收器用来配置日志记录到哪种介质。比如说 Console(输出到控制台),File(日志文件)等。就是我们上面配置的 WriteTo 节点。

接收器一般以扩展库的方式提供的,为了将日志写入文件,我们在上面引入了 Serilog.AspNetCore.Sinks.RollingFile 包。

除了在配置文件中使用 WriteTo 指定,也可以通过代码配置,比如在 Console 控制台程序中使用:

Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger(); Log.Information("Ah, there you are!");

可以同时使用多个接收器,用法很简单,支持链式调用:

Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("log-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();

基于文本的接收器使用 output templates 来控制格式。

Log.Logger = new LoggerConfiguration()
.WriteTo.File("log.txt",
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")

格式这部分涉及到扩展器,会在后面具体说明,这里只是简单提一下如何使用。

可以使用每个接收器指定不同的日志级别

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.File("log.txt")
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information)
.CreateLogger();

上面代码中指定了默认日志级别为 Debug,但为 Console Sink 的日志重写级别为 Information

==========

在声明Logger时可以通过 MinimumLevel.Debug() 指定最小级别,而在 WriteTo 中指定接收器Sink时,也可以通过 restrictetToMinimumLevel:LogEventLevel.Information 指定最小级别,那两者之间的关系是怎么样的呢?

注意:接收器Sink的级别必须高于Logger的级别。 假设Logger的默认级别为 Information,即便 Sink 重写日志级别为 LogEventLevel.Debug,也只能看到 Information 及以上级别的日志。这是因为默认级别的配置控制哪些事件级别的日志记录语句可以被创建,而 Sink 级别仅仅是对这些事件进行过滤。

2.2 扩展器 (Enricher)

顾名思义,扩展器可以添加,删除或修改附加到日志事件的属性。 例如,下面的代码可以达到将线程ID附加到每个事件的目的。

class ThreadIdEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
"ThreadId", Thread.CurrentThread.ManagedThreadId));
}
}

使用 Enricher 将扩展添加到配置对象

Log.Logger = new LoggerConfiguration()
.Enrich.With(new ThreadIdEnricher())
.WriteTo.Console(
outputTemplate: "{Timestamp:HH:mm} [{Level}] ({ThreadId}) {Message}{NewLine}{Exception}")
.CreateLogger();

注意模板中的 {ThreadId},在日志中打印的便是当前线程的ID了

如果扩展的属性值在整个应用程序运行期间都是恒定的,则可以使用快捷方式WithProperty方法简化配置。

Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("Version", "1.0.0")
.WriteTo.Console()
.CreateLogger();
可用的扩展器包
  • Serilog.Enrichers.Environment - WithMachineName() and WithEnvironmentUserName()
  • Serilog.Enrichers.Process - WithProcessId()
  • Serilog.Enrichers.Thread - WithThreadId()
其他有意思的扩展器包
  • Serilog.Web.Classic - WithHttpRequestId() and many other enrichers useful in classic ASP.NET applications
  • Serilog.Exceptions - WithExceptionDetails() adds additional structured properties from + exceptions
  • Serilog.Enrichers.Demystify - WithDemystifiedStackTraces()

2.3 过滤器 (Filter)

可以通过过滤器有选择的记录事件。过滤器只是 LogEvent 的谓词,其中一些常见的情况由 Matching 类处理。

只要在调试过程中打个断点,就可以看到 LogEvent 的详细属性,这里就不赘述,懒~~~

var log = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {Count} {Message:j} {NewLine}")
.Filter.ByExcluding(Matching.WithProperty<int>("Count", p => p < 10))
.CreateLogger(); log.Information("测试 {Count}", 20);
log.Information("测试 {Count}", 3);
log.Information("测试 {Count}", 11); Console.Read(); // 输出:
// 2020-05-05 00:04:46 INF 20 测试 20
// 2020-05-05 00:04:47 INF 11 测试 11

代码说明:忽略参数 Count 小于 10 的日志,所以最终只输出了 2 条。

三、说说输出格式吧

一个基本的OutTemplate模板大概长这样:

{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} {NewLine} {Exception}
从头到尾分别是:日期、日志级别、日志内容、换行、异常信息

模板可用参数

Serilog的日志是由LogEvent构成的,其可用的参数对应了LogEvent的public属性,分别是下面几个:

  • 时间戳【Timestamp】- 日志产生时的时间戳,可定义格式
  • 日志级别【Level】- 日志的级别,可选定义格式
  • 日志内容【Message】- 日志的内容,一般由字符串模板和参数构成,类似 string.Format(temp, param1, param2, ...),但比Format强大的多
  • 命名属性【Properties】- 包涵 扩展器定义的属性 以及 日志内容中提供的参数,后面细说
  • 异常【Exception】- 如果是输出的异常,会完整异常信息和栈跟踪信息。如果没有异常则为空
  • 换行【NewLine】- 就是调用 System.Environment.NewLine

个人理解的模板可以分两种

  • 【日志】的输出模板,由 OutTemplate 定义
  • 【日志内容】的输出模板,就是上面整体的模板中【Message】的部分,在写日志时由调用者提供,如:
    log.Error("测试 {Count}", 10)

格式化输出

serilog 的输出很有意思,并不仅仅能输出字符串,还可以输出对象或枚举。

这里主要以【Message】为说明对象,事实上也有【Properties】用法也差不多,但输出样式稍微有些区别

请看下面的代码:

var user = new User { Id = 1, Name = "张三" };
log.Debug("当前用户: {User},当前时间:{CurrentTime}", user, DateTime.Now)
  1. 在模板字符串内,包含在 {} 中的内容为“标量”名称,这里输出的内容由后面实参决定
  2. 如果要原样输出大括号,只要 {{内容}} 就可以了
  3. 标量和参数的对应关系是从左到右一一对应
  4. 标准 string.Format 也能用,比如 log.Debug("{0} {1}!", "Hello", "world"),但建议不要混用

默认的输出行为

1. 简单标量类型:
  • 布尔类型 - bool
  • 数值类型 - byte,short,ushort,int,unit,long,ulong,float,double,decimal
  • 字符串 - string,byte[]
  • 日期 - DateTime,DateTimeOffset,TimeSpan
  • 其他 - Guid,Uri
  • 可空类型 - 上面所有类型的可空版本

上面这些类型,表达的内容基本上不会有歧义,所以一般处理方式就是直接调用 .ToString() 方法

var count = 100;
log.Debug("检索到{Count}条记录", count); // 检索到100条记录

其他类型都差不多,大家可以自己测试。

2. 集合 Collection

如果属性值传递的IEnumerable,Serilog会将其视为一个集合

var fruits = new[] {"apple", "pear", "orange"};
log.Information("我有这些水果:{fruit}", fruits); // 我有这些水果:["apple", "pear", "orange"] var fruits2 = new Dictionary<string,int> {{ "Apple", 1}, { "Pear", 5 }};
log.Information("In my bowl I have {Fruit}", fruits2); // In my bowl I have {"Apple": 1, "Pear": 5}

可以看出,Serilog很智能,根据不同的集合类型输出了不同格式,而该格式正好可以表达数据的内容

3. 对象 object
var user = new User() {Id = 1, Name = "张三"};
log.Information("{User}", user); // "SerilogSample.User"
log.Information("{@User}", user); // {"Id": 1, "Name": "张三", "$type": "User"}
log.Information("{$User}", user); // "SerilogSample.User"
  • 默认情况下,如果Serilog没能识别数据类型,直接调用 ToString(),对应实例 1
  • 上面那种情况,我们一般希望保留对象的结构,对此,Serilog提供了 @ 符号,叫做 析构运算符。添加该符号后,默认输出对象的JSON格式,对应实例 2
  • 强制字符串化,通过 $ 符号,可以强制调用对应参数的 ToString() 方法,对应实例3
  • 补充下上面一条,如果 log.Information("{$User}", "abc") 输出啥呢?很简单,"abc".ToString() 所以输出内容就是 abc 嘛
  • 自定义数据,注意代码的第二行,Serilog提供了很多不同的析构策略,大家自己试吧。注意:ByTransforming 必须返回与输入参数不同的类型,不然会被递归调用
var log = new LoggerConfiguration()
.Destructure.ByTransforming<User>(u => new { UserName = u.Name })
.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {Message:j} {NewLine}")
.CreateLogger(); var user = new User() {Id = 1, Name = "张三"};
log.Information("{@User:l}", user); // {"UserName": "张三"}

格式化输出

var log = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} {User} {EventType} {Level:u3} {Message} {Exception} {NewLine}")
.Enrich.WithProperty("EventType", "事件类型")
.Enrich.WithProperty("User", new User { Id = 1, Name = "测试" })
.CreateLogger(); var exception = new Exception("人工异常");
log.Error(exception, "啥也不是");

格式说明:

  • Timestamp:时间戳,指定格式:{Timestamp: yyyy-MM-dd HH:mm:ss.fff zzz}
  • Level:日志级别,默认为完整的级别名称,如:Information。可选格式 :u3 (日志级别缩写,三个字母,大写)和 :w3 (日志级别缩写,三个字母,小写),效果:{Level:u3}=INF;{Level:w3}=inf
  • Properties:命名属性,除了上面提到过的5个参数(Timestamp,Exception,Message,NewLine,Level)外的所有其他参数。这些参数来源于:
    1. 上面提到过的扩展器(Enricher)
    2. Message 中提供的参数,如果两者同时指定了同名参数,Message的参数会覆盖扩展器的配置
var log = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {User:lj} {Message:j} {NewLine}")
.Filter.ByExcluding(Matching.WithProperty<int>("Count", p => p < 10))
.Enrich.WithProperty("User", "Enricher")
.CreateLogger(); log.Information("{User}", "Message"); // 输出:2020-05-05 00:19:39 INF Message "Message"

虽然在Enricher中指定了属性User,但最终输出的是Message模板指定的实参。修改一下代码:

var log = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {User:lj} {Message:j} {NewLine}")
.Filter.ByExcluding(Matching.WithProperty<int>("Count", p => p < 10))
.Enrich.WithProperty("User", "Enricher")
.CreateLogger(); log.Information("{User1}", "Message"); // 输出:2020-05-05 00:19:39 INF Enricher "Message"
  • Message: 消息内容;如果要输出对象的JSON,一般会指定格式参数 {Message:lj}

    参数 l 的作用:
var log = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {Message:l}")
.CreateLogger(); log.Information("{a} {b}", 23, "abc"); // 输出为:2020-05-04 23:41:12 INF 23 abc
var log = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {Message}")
.CreateLogger(); log.Information("{a} {b}", 23, "abc"); // 输出为:2020-05-04 23:44:21 INF 23 "abc"

注意输出的 abc 字符串,添加了参数 :l后,若格式的实参是字符串类型,会自动删除双引号。

如果要输出一个对象,应该怎么做呢?看下面的三种做法:

var log = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {Message}")
.CreateLogger(); var user = new User {Id = 1, Name = "张三"}; log.Information("{u}", user); // 输出为:2020-05-04 23:47:08 INF "SerilogSample.User"

可以看到,输出的是对象类型的名称,修改代码如下:

var log = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {Message}")
.CreateLogger(); var user = new User {Id = 1, Name = "张三"}; log.Information("{@u}", user); // 输出为:2020-05-04 23:49:59 INF User {Id=1, Name="张三"}

添加参数 :j,修改代码如下:

var log = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {Message:j}")
.CreateLogger(); var user = new User {Id = 1, Name = "张三"}; log.Information("{@u}", user); // 输出为:2020-05-04 23:51:42 INF {"Id": 1, "Name": "张三", "$type": "User"}

由上面的测试可以看出,

  • 如果要输出一个对象,输出模板形参前要添加@符号
  • 如果要将对象输出成标准json格式,需要在日志模板中添加格式符号 {Message:j}
  • 一般情况下,只要固定写法 {Message:lj} 就够了

四、最后尝试使用代码的方式实现一个跟简单示例差不多功能的配置练练手吧

修改下 Program.cs 文件

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureAppConfiguration(configure => configure.AddJsonFile("Serilog.json"));
webBuilder.UseStartup<Startup>();
})
.UseSerilog((context, configure) =>
{
configure.MinimumLevel.Information()
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Debug)
.Filter.With<MicrosoftFilter>()
.WriteTo.RollingFile("logs\\{Date}.txt", LogEventLevel.Warning);
});

自定义一个 Filter

public class MicrosoftFilter : ILogEventFilter
{
public bool IsEnabled(LogEvent logEvent)
{
if (!logEvent.Properties.TryGetValue("SourceContext", out var source))
{
return logEvent.Level >= LogEventLevel.Debug;
} if (source.ToString().StartsWith("\"Microsoft"))
{
return logEvent.Level >= LogEventLevel.Warning;
} return logEvent.Level >= LogEventLevel.Debug;
}
}

差不多就这样吧~~告辞!!!

[AspNetCore3.1] 使用Serilog记录日志的更多相关文章

  1. Serilog 记录日志

    Serilog 记录日志 Serilog是.net里面非常不错的记录日志的库,另外一个我认为比较好的Log库是NLog. 在我个人的asp.net web api 2 基础框架(Github地址)里, ...

  2. 在asp.net web api 2 使用 Serilog 记录日志

    Serilog是.net里面非常不错的记录日志的库,另外一个我认为比较好的Log库是NLog. 在我个人的asp.net web api 2 基础框架(Github地址)里,我原来使用的是NLog,但 ...

  3. 在asp.net web api 2 (ioc autofac) 使用 Serilog 记录日志

    Serilog是.net里面非常不错的记录日志的库,另外一个我认为比较好的Log库是NLog. 在我个人的asp.net web api 2 基础框架(Github地址)里,我原来使用的是NLog,但 ...

  4. ASP.NET Core 3.1使用log4net/nlog/Serilog记录日志

    Serilog中的结构化日志支持非常好,而且配置简便.我能够比其他任何人更轻松地启动和运行Seirlog.Serilog中的日志可以发送到很多目的地.Serilog称这些东西为"接收器&qu ...

  5. 在Winform项目和Web API的.NetCore项目中使用Serilog 来记录日志信息

    在我们常规的调试或者测试的时候,喜欢把一些测试信息打印在控制台或者记录在文件中,对于.netframework项目来说,我们输出控制台的日志信息习惯的用Console.WriteLine来输出查看,不 ...

  6. .NET跨平台之旅:在Linux上将ASP.NET 5运行日志写入文件

    在前一篇博文(增加文件日志功能遇到的挫折)中,我们遇到了这样一个问题:虽然有一些.NET日志组件(比如Serilog, NLog)已经开始支持.NET Core,但目前只支持控制台输出日志,不支持将日 ...

  7. .Net Core个人笔记

    目录 前言 IOC注册 三种生命周期 如何注册一个IOC服务 .Net Core部署IIS之后500错误 管道和中间件 示意图 管道方法 中间件 加日志观看 使用MVC MVC服务注入 MVC管道调用 ...

  8. ABP 使用ElasticSearch、Kibana、Docker 进行日志收集

    ABP 使用ElasticSearch.Kibana.Docker 进行日志收集 后续会根据公司使用的技术,进行技术整理分享,都是干货哦别忘了关注我!!! 最近领导想要我把项目日志进行一个统一收集,因 ...

  9. .NET Core的日志[1]:采用统一的模式记录日志

    记录各种级别的日志是所有应用不可或缺的功能.关于日志记录的实现,我们有太多第三方框架可供选择,比如Log4Net.NLog.Loggr和Serilog 等,当然我们还可以选择微软原生的诊断框架(相关A ...

随机推荐

  1. 与运算(&)、或运算(|)、异或运算(^)、右移运算符(>>>)本质介绍

    按位与运算符(&) 参加运算的两个数据,按二进制位进行"与"运算. 运算规则:0&0=0;  0&1=0;   1&0=0;    1&1= ...

  2. kafka producer 概要(看源码前,最好能掌握)

        kafakproducer概要(看源码前,最好能理解) 摘要 kafak 被设计用来作为一个统一的平台来处理庞大的数据的实时工具,在设计上有诸多变态的要求 它必须具有高吞吐量才能支持大量事件流 ...

  3. 网络管理监视很重要!学编程的你知道哪些不错的网络监控工具?2020 最好的Linux网络监控工具分享给你

    以下文章来源于新钛云服 翻译:侯明明 前言 虽然这个清单包含开源的和闭源的产品,但它着重于介绍基于 Linux 的网络监控工具, 少数常用工具只能在 Windows,Pandora 或其他系统上运行, ...

  4. 【Golang】基础-操作 csv 文件

    1. csv plugins,自带极简 1.1 写数据到csv文件 知识点:encoding/csv 库的 Write 方法使用[]string的切片格式追加方式写入数据 1.1.1 追加写入 pac ...

  5. apply 、call 以及 bind 的使用和区别

    一.被apply和call调用的函数中没有传递参数 (一)不传参数 结果: (二)传递 null 结果: 总结: 1.当使用 apply和 call去调用函数并且没有传递参数时,前提这个函数中也没有传 ...

  6. 初探Lerna

    1.简介 首先是关于Monorepo(一篇不错的介绍Monorepo的文章),它是管理项目代码的一种方式,主要手段是通过在一个项目仓库中管理多个模块/仓库包.而Multirepo是传统的仓库管理方法, ...

  7. od中低位地址和高位的顺序,以及数据的存放读写

    在观察内存的时候应当注意"内存数据"与"数值数据"的区别. 在我们的调试环境中,内存由低到高分布,你可以简单地把这种情形理解成Win32系统在内存中由地位向高位 ...

  8. python应用(7):输入与输出

    如前文,流程是程序的主角,而流程一般都需要处理数据,那数据如何进到流程,而最终处理后的数据又如何表现,这就是流程的输入与输出的问题. 本文介绍流程处理的数据的输入与输出. 流程中的输入,一般都会先保存 ...

  9. 从docker介绍及其简介

    一.引言 1.我本地代码运行没问题啊,但是别人机器运行不了,从而导致环境不一致的问题 2.那个兄弟又写死循环了,怎么这么卡,在多用户的操作系统下,会相互影响. 天猫双十一的情况下,用户量暴涨,从而导致 ...

  10. 第15.33节 PyQt(Python+Qt)入门学习:containers容器类部件QTabWidget选项窗部件简介

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 容器部件就是可以在部件内放置其他部件的部件,在Qt Designer中可以使用的容器部件有 ...