当前位置: 首页 > 工具软件 > Serilog > 使用案例 >

.Net-Serilog

卜季萌
2023-12-01

目录

1. 快速开始

1.1 控制台应用

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

1.2.1 安装nuget包

1.2.2 修改Program.cs代码

1.2.3 删除appsettings.json里的Logging节点

1.2.4 两步初始化

2. 基本配置

2.1 Sink

2.2 输出模板

2.3 日志等级

2.3.1 日志等级判断

2.3.2 动态日志等级

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

2.5 Enrichers

2.6 根据Enricher的值进行过滤

2.7 Sub-logger

2.8 从配置文件读取配置

3. 结构化数据

4. 消息模板

5. 自定义序列化json


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)//日志打印到文件上
        .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.AspNetCore

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:lj}{NewLine}{Exception}")

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

{Level:u3}表示将日志等级的输出显示为3个大写字符,如DBG、INF、ERR等。{Level:w3}表示三个字符的小写。

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

2.3 日志等级


同大多数的日志框架一样,分为Verbose、Debug、Information、Warning、Error和Fatal六个等级。在之前的例子中我们可以看到,使用.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:yyyy}{Level}都属于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}) {Message}{NewLine}{Exception}")
    .CreateLogger();

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

Log.Logger = new LoggerConfiguration()
    .Enrich.WithProperty("ThreadId","123")
    .WriteTo.Console(
        outputTemplate: "{Timestamp:HH:mm} [{Level}] ({ThreadId}) {Message}{NewLine}{Exception}")
    .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();

2.8 从配置文件读取配置

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

3. 结构化数据

  1. 对于int、bool、string、Guid、Uri等简单的数据类型,Serilog可以转为字符串输出。
  2. 对于List、Dictionary等集合类型,会自动序列化为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.format{0}{1}。如:

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

转载自 Serilog 2.10 中文文档_catshitone的专栏-CSDN博客_serilog中文文档

 类似资料: