Serilog 2.10 中文文档

Posted JimCarter

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Serilog 2.10 中文文档相关的知识,希望对你有一定的参考价值。

文章目录

本文基于发稿时的最新版本,Serilog: 2.10

1. 快速开始

1.1 控制台应用

这里以控制台应用为例,首先安装以下三个nuget包:

Serilog
Serilog.Sinks.Console
Serilog.Sinks.File

(可以用命令行方式安装或通过VS安装,随意)
第二个nuget包,用来将日志输出到控制台。第三个用来将日志输出到文件。

然后,修改代码如下:

static void Main()

    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Debug()//最小日志等级
        //日志打印到控制台
        .WriteTo.Console()
        //日志打印到文件上
        .WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day)
        //按日期生成日志路径,需要安装nuget: Serilog.Sinks.Map
        .WriteTo.Map(
            le => le.Timestamp.Date,
            (d, lc) =>  lc.File($"logs/d:yyyyMMdd/log.txt"); )
        .CreateLogger();
    Log.Information("Hello, world!");
    int a = 10, b = 0;
    try
    
        Log.Debug("Dividing A by B", a, b);
        Console.WriteLine(a / b);
    
    catch (Exception ex)
    
        Log.Error(ex, "Something went wrong");
    
    finally
    
        Log.CloseAndFlush();
    


1.2 在ASP.NET Core应用中使用Serilog

1.2.1 安装nuget包

首先安装Serilog.AspNetCor

1.2.2 修改Program.cs代码

    public static int Main(string[] args)
    
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .CreateLogger();

        try
        
            Log.Information("Starting web host");
            CreateHostBuilder(args).Build().Run();
            return 0;
        
        catch (Exception ex)
        
            Log.Fatal(ex, "Host terminated unexpectedly");
            return 1;
        
        finally
        
            Log.CloseAndFlush();
        
    
    
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSerilog() // 添加Serilog
            .ConfigureWebHostDefaults(webBuilder =>
            
                webBuilder.UseStartup<Startup>();
            );

1.2.3 删除appsettings.json里的Logging节点

删除之后,即可使用

1.2.4 两步初始化

在上面的例子中我们在程序启动时就初始化了Serilog,这样做好处是可以捕获到Host配置的异常,但没法使用appsettings.json和依赖注入。

所以为了能够使用配置文件和依赖注入,Serilog支持第二次初始化(两步初始化)。通过在UseSerilog里配置回调实现:

首先,将CreateLogger改为CreateBootstrapLogger

public static int Main(string[] args)
    
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .CreateBootstrapLogger(); // 修改此项

然后,在UserSerilog里配置回调:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSerilog((context, services, configuration) => configuration
            .ReadFrom.Configuration(context.Configuration)
            .ReadFrom.Services(services)
            .Enrich.FromLogContext()
            .WriteTo.Console())
        .ConfigureWebHostDefaults(webBuilder =>
        
            webBuilder.UseStartup<Startup>();
        );

2. 基本配置

2.1 Sink

上面的实例代码的WriteTo.Console()WriteTo.File(),就是不同的sinker。用来控制把日志写入到哪里。除了控制台和文件系统,你还可以通过安装不同的nuget包把日志写入各种存储系统,如数据库、消息队列、AWS、Azure、邮箱、HTTP等很多地方。

详见Provided-Sinks

2.2 输出模板

.WriteTo.File("log.txt",
        outputTemplate: "Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz [Level:u3] Message:ljNewLineException")

默认的输出模板就是上面的样子,TimestampLevel都是内置属性。Message:lj表示将消息序列化成json字符串,string类型除外(j表示json,l表示except for string literals)。

Level:u3表示将日志等级的输出显示为3个大写字符,如DBGINFERR等。Level:w3表示三个字符的小写。

同理,可以增加一个Properties:j用来显示额外的上下文信息。

2.3 日志等级

同大多数的日志框架一样,分为VerboseDebugInformationWarningErrorFatal六个等级。在之前的例子中我们可以看到,使用.MinimumLevel.Debug()配置了最低等级,小于此等级的日志不会被打印出来。

默认日志等级:如果没有配置MinimumLevel的话,默认等级为Information

2.3.1 日志等级判断

可以通过Log.IsEnabled(LogEventLevel.Debug)来判断某个等级是否启用。

2.3.2 动态日志等级

var levelSwitch = new LoggingLevelSwitch();
levelSwitch.MinimumLevel = LogEventLevel.Warning;
var log = new LoggerConfiguration()
  .MinimumLevel.ControlledBy(levelSwitch)
  .WriteTo.ColoredConsole()
  .CreateLogger();

当需要切换日志等级时,直接修改:

levelSwitch.MinimumLevel = LogEventLevel.Verbose;
log.Verbose("This will now be logged");

2.4 不同级别的日志输出到不同的地方

我们可以通过配置不同sinker的restrictedToMinimumLevel的属性,来让不同级别的日志落到不同的sinker上。

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

以上代码会将所有的level的日志输出到log.txt上,但只有Information及以上级别的日志会输出到Console上。

看到这里,也许你会问:MinimumLevel.Debug()和sinker里的restrictedToMinimumLevel有什么区别?

其实MinimumLevel.Debug()只是来负责控制哪些级别的日志可以触发WriteTo操作,而restrictedToMinimumLevel只是用来根据级别过滤这些日志。如果MinimumLevel设置为Information,即使restrictedToMinimumLevel设置为Debug,最终也不会看到Debug级别的日志。

2.5 Enrichers

输出模板里我们介绍过Timestamp:yyyyLevel都属于Enricher,只不过这些都是框架内置的。我们也可以定义自己的Enricher来打印自定义的内容,如下就是一个需要打印出线程Id的Enricher:

class ThreadIdEnricher : ILogEventEnricher

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    
        logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
                "ThreadId", Thread.CurrentThread.ManagedThreadId));
    

使用,通过ThreadId

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

如果你想打印的ThreadId是固定的,就不用定义ThreadIdEnricher类,可以直接这么写:

Log.Logger = new LoggerConfiguration()
    .Enrich.WithProperty("ThreadId","123")
    .WriteTo.Console(
        outputTemplate: "Timestamp:HH:mm [Level] (ThreadId) MessageNewLineException")
    .CreateLogger();

框架支持的Enricher有以下几种:

名称nuget包
WithMachineName()WithEnvironmentUserName()Serilog.Enrichers.Environment
WithProcessId()Serilog.Enrichers.Process
WithThreadId()Serilog.Enrichers.Thread
WithHttpRequestId()Serilog.Web.Classic
WithExceptionDetails()Serilog.Exceptions
WithDemystifiedStackTraces()Serilog.Enrichers.Demystify
WithCorrelationId()Serilog.Enrichers.CorrelationId
WithClientIp()WithClientAgent()Serilog.Enrichers.ClientInfo
WithXllPath()Serilog.Enrichers.ExcelDna
WithSensitiveDataMasking()Serilog.Enrichers.Sensitive
FromGlobalLogContext()Serilog.Enrichers.GlobalLogContext

详见

2.6 根据Enricher的值进行过滤

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .Filter.ByExcluding(Matching.WithProperty<int>("Count", p => p < 10))
    .CreateLogger();

Count的值小于10时,不会打印日志。

2.7 Sub-logger

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.Logger(lc => lc
        .Filter.ByIncludingOnly(...)
        .WriteTo.File("log.txt"))
    .CreateLogger();

个人感觉是sinker加上filter。

2.8 从配置文件读取配置

可以通过 Serilog.Settings.AppSettingsSerilog.Settings.Configuration 两个nuget包实现。

3. 结构化数据

  1. 对于intboolstringGuidUri等简单的数据类型,Serilog可以转为字符串输出。

  2. 对于ListDictionary等集合类型,会自动序列化为json字符串。

  3. 对于复杂对象,可以使用@操作符将对象序列化为json字符串,否则会直接调用ToString()方法。如有以下代码:

var person = new Person  Name = "aa", FirstName = "bb", Id = 5 ;
Log.Information("Processing Person", person);
Log.Information("Processing @Person", person);

输出为:

[16:22:33 INF] Processing CalcStringDuplicated.Person
[16:22:33 INF] Processing "Name": "aa", "FirstName": "bb", "Id": 5, "$type": "Person"

如何自定义数据的输出结构?

可以使用ByTransforming,比如下述代码我们只需要把Person对象的NameId属性输出:

Log.Logger = new LoggerConfiguration()
    .Destructure.ByTransforming<Person>(
        r => new  Name = r.Name, Id = r.Id )
    .WriteTo...

输出为:

[16:27:39 INF] Processing CalcStringDuplicated.Person
[16:27:39 INF] Processing "Name": "aa", "Id": 5

强制将object转为string

上面说到对于List等集合类型,Serilog会自动序列化为json字符串,如果不想这么做,可以使用$操作符

var c =new List<int>10;
Log.Information("value = $c", c);

输出为:

[16:35:13 INF] value = System.Collections.Generic.List`1[System.Int32]

4. 消息模板

Serilog建议使用消息模板将日志展示出来,而不是直接展示消息。

// 不推荐
Log.Information("The time is " + DateTime.Now);
// 推荐做法
Log.Information("The time is Now", DateTime.Now);

这个模板的语法类似于string.format(). 大括号里的属性命名规则同普通属性一致,建议使用Pascal命名,但你也可以随便写。

属性和后面参数对象是根据先后位置对应的,而不是根据名称。

以下代码:

var person = new Person  Name = "aa", FirstName = "bb", Id = 5 ;
var person2 = new Person  Name = "aa2", FirstName = "22", Id = 5 ;
Log.Information("Processing @Person2---@Person",person, person2);
Log.Information("Processing @person---@person2", person, person2);
Log.Information("Processing @person2---@person", person, person2);
Log.Information("Processing @a---@b", person, person2);

输出结果都一样:

[17:21:58 INF] Processing "Name": "aa", "Id": 5---"Name": "aa2", "Id": 5
[17:21:58 INF] Processing "Name": "aa", "Id": 5---"Name": "aa2", "Id": 5
[17:21:58 INF] Processing "Name": "aa", "Id": 5---"Name": "aa2", "Id": 5
[17:21:58 INF] Processing "Name": "aa", "Id": 5---"Name": "aa2", "Id": 5

消息模板也支持类似string.format01。如:

Log.Information("Processing @1---@0", person, person2);

输出为:

[17:24:07 INF] Processing "Name": "aa2", "Id": 5---"Name": "aa", "Id": 5

消息模板详见

5. 自定义序列化json

Serilog自带了三个json序列化程序:

  • Serilog.Formatting.Json.JsonFormatter- 这是Serilog软件包中附带的历史默认值。它生成日志事件的完整呈现,并支持一些配置选项。
  • Serilog.Formatting.Compact.CompactJsonFormatter- 存在于Serilog.Formatting.Compactnuget包,提供了更节省空间的 JSON 格式化程序。
  • Serilog.Formatting.Compact.RenderedCompactJsonFormatter- 同样附带在Serilog.Formatting.Compact包中,这个格式化程序将消息模板预渲染成文本。

自定义formater:

class User

    public int Id  get; set; 
    public string Name  get; set; 
    public DateTime Created  get; set; 


class CustomDateFormatter : IFormatProvider

    readonly IFormatProvider basedOn;
    readonly string shortDatePattern;
    public CustomDateFormatter(string shortDatePattern, IFormatProvider basedOn)
    
        this.shortDatePattern = shortDatePattern;
        this.basedOn = basedOn;
    
    public object GetFormat(Type formatType)
    
        if (formatType == typeof(DateTimeFormatInfo))
        
            var basedOnFormatInfo = (DateTimeFormatInfo)basedOn.GetFormat(formatType);
            var dateFormatInfo = (DateTimeFormatInfo)basedOnFormatInfo.Clone();
            dateFormatInfo.ShortDatePattern = this.shortDatePattern;
            return dateFormatInfo;
        
        return this.basedOn.GetFormat(formatType);
    


public class Program

    public static void Main(string[] args)
    
        var formatter = new CustomDateFormatter("dd-MMM-yyyy", new CultureInfo("en-AU"));
        Log.Logger = new LoggerConfiguration() 
            .WriteTo.Console(formatProvider: new CultureInfo("en-AU")) // Console 1
            .WriteTo.Console(formatProvider: formatter)                // Console 2
            .CreateLogger();

        var exampleUser = new User  Id = 1, Name = "Adam", Created = DateTime.Now ;
        Log.Information("Created @User on Created", exampleUser, DateTime.Now);

        Log.CloseAndFlush();
    

输出:

[13:57:12 INF] Created "Id": 1, "Name": "Adam", "Created": "2020-09-01T13:56:59.7803740-05:00", "$type": "User" on 1/09/2020 1:57:12 PM
[13:57:12 INF] Created "Id": 1, "Name": "Adam", "Created": "2020-09-01T13:56:59.7803740-05:00", "$type": "User" on 01-Sep-2020 1:57:12 PM

参考:
https://github.com/serilog/serilog/wiki/Getting-Started

以上是关于Serilog 2.10 中文文档的主要内容,如果未能解决你的问题,请参考以下文章

Serilog 2.10 中文文档

Serilog 2.10 中文文档

Serilog 的 AddSerilog 无法识别

Serilog 日志框架如何自动删除超过 N 天的日志 ?

2.10环境变量PATH;2.11cp命令;2.12mv命令;2.13文档查看cat_more...

解决错误 - 无法解析“Serilog.ILogger”类型的服务