深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

发布于:2025-06-11 ⋅ 阅读:(15) ⋅ 点赞:(0)

ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。

ASP.NET Core 中的日志记录

.NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程序将日志写入不同的目标。 基本日志记录提供程序是内置的,并且还可以使用许多第三方提供程序。

配置依赖于 ILogger 的服务

若要配置依赖于 ILogger的服务,请使用构造函数注入或提供工厂方法。 仅当没有其他选项时,才建议使用工厂方法方法。 例如,假设某个服务需要由 DI 提供的 ILogger 实例:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<IExampleService>(
    container => new DefaultExampleService
    {
        Logger = container.GetRequiredService<ILogger<IExampleService>>()
    });

内置日志记录提供程序

Microsoft扩展包括以下日志记录提供程序作为运行时库的一部分:

  • 控制台
  • 调试
  • EventSource
  • 事件日志

日志记录提供程序保留日志,Console 提供程序除外,该提供程序仅将日志显示为标准输出。 例如,Azure Application Insights 提供程序将日志存储在 Azure Application Insights 中。 可以启用多个提供程序。

appsettings.config配置Logging节点,进行指定类跟踪事件

"Logging": {
  // 所有提供者,LogLevel适用于所有启用的提供者
  "LogLevel": {
    "Default": "Information",
    // 常规ASP.NET Core诊断
    "Microsoft.AspNetCore": "Warning",
    //"Microsoft": "Warning",
    "Microsoft.Hosting.Lifetime": "Information",
    // EFCore Sql执行跟踪
    "Microsoft.EntityFrameworkCore.Database.Command": "Information"
  },
  // 调试提供者
  "Debug": {
    "LogLevel": {
      "Default": "Information", // Overrides preceding LogLevel:Default setting.
      "Microsoft.Hosting": "Trace" // Debug:Microsoft.Hosting category.
    }
  },
  // 事件来源提供者
  "EventSource": {
    "LogLevel": {
      "Default": "Warning" // All categories of EventSource provider.
    }
  }
}
控制台

Console 提供程序将输出记录到控制台。 如需详细了解如何在开发环境中查看 Console 日志,请参阅记录来自 dotnet run 和 Visual Studio 的输出。

// 从 builder 中删除所有记录器提供程序
builder.Logging.ClearProviders();

// 控制台输出
builder.Logging.AddConsole();
调试

Debug 提供程序使用 System.Diagnostics.Debug 类写入日志输出。 对 System.Diagnostics.Debug.WriteLine 的调用写入到 Debug 提供程序。

// 调试输出
builder.Logging.AddDebug();
事件来源

EventSource 提供程序写入名称为 Microsoft.Extensions.Logging 的跨平台事件源。 在 Windows 上,提供程序使用的是 ETW。

// 事件来源输出
builder.Logging.AddEventSourceLogger();

日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性。我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger、LoggerFactory和LoggerProvider这三个核心对象组成。我们可以通过简单的配置实现对LoggerFactory的定制,以及对LoggerProvider添加。

自定义日志记录提供程序

有许多 日志记录提供程序 可用于常见日志记录需求。 当某个可用提供程序不符合应用程序需求时,可能需要实现自定义 ILoggerProvider。 本节将实现一个自定义日志提供器,以便在控制台中对日志进行着色。

实现接口结构顺序
ILogger -> ILoggerProvider -> ILoggingBuilder(LoggerFactory)

1. 创建自定义记录器

ILogger接口
泛型接口用于记录从指定的 TCategoryName 类型名称派生类别名称的位置。

using Microsoft.Extensions.Logging;

namespace ContosoUniversity.Extensions.Logger;
// 自定义记录器配置
public sealed class ColorConsoleLoggerConfiguration
{
    // 事件ID
    public int EventId { get; set; }
	// 日志字典:包括日志级别与内容描述呈现颜色风格等
    public Dictionary<LogLevel, ConsoleColor> LogLevelToColorMap { get; set; } = new()
    {
        [LogLevel.Information] = ConsoleColor.Green,
    };
}

// ILogger 实现类别名称通常是日志记录源。例如,创建记录器的类型:
public sealed class ColorConsoleLogger(string name,
    Func<ColorConsoleLoggerConfiguration> getCurrentConfig) : ILogger
{
    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;

    // 检查 _getCurrentConfig().LogLevelToColorMap.ContainsKey(logLevel),因此每个 logLevel 都有一个唯一的记录器。
    // 在此实现中,每个日志级别都需要显式配置条目才能记录
    public bool IsEnabled(LogLevel logLevel) =>
        getCurrentConfig().LogLevelToColorMap.ContainsKey(logLevel);

    /* 实现[写入日志项]Log方法
     * LogLevel: 日志级别
     * EventId: 事件ID
     * TState: 要写入的项或对象
     * Exception?: 与此项相关的异常
     * Func<TState, Exception?, string>: 函数体,用于创建state和exception的String消息
    */    
    public void Log<TState>(LogLevel logLevel, EventId eventId,
        TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        ColorConsoleLoggerConfiguration config = getCurrentConfig();
        if (config.EventId == 0 || config.EventId == eventId.Id)
        {
            ConsoleColor originalColor = Console.ForegroundColor;

            Console.ForegroundColor = config.LogLevelToColorMap[logLevel];
            Console.Write($"{logLevel.ToString().ToLower()[..4]}: ");

            Console.ForegroundColor = originalColor;
            Console.WriteLine($"{name}[{eventId.Id}]");

            if (config.LogLevelToColorMap[logLevel] != ConsoleColor.DarkGreen)
                Console.ForegroundColor = config.LogLevelToColorMap[logLevel];
            Console.Write($"      {formatter(state, exception)}");

            Console.ForegroundColor = originalColor;
            Console.WriteLine();
        }
    }
}

前面的代码:

  • 为每个类别名称创建一个记录器实例。
  • IsEnabled 中检查 _getCurrentConfig().LogLevelToColorMap.ContainsKey(logLevel),因此每个 logLevel 都有一个唯一的记录器。 在此实现中,每个日志级别都需要显式配置条目才能记录。

最好在 ILogger.Log 的实现中调用 ILogger.IsEnabled,因为 Log 可以被任何使用者调用,无法保证它以前已被检查过。 在大多数实现中,IsEnabled 方法应该非常快。

2. 自定义记录器提供程序

ILoggerProvider 接口
负责创建记录器实例。 不需要为每个类别创建记录器实例,但对于某些记录器(例如 NLog 或 log4net)来说,这很有意义。 此策略允许你为每个类别选择不同的日志记录输出目标,如以下示例所示:

using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.Runtime.Versioning;

namespace ContosoUniversity.Extensions.Logger;

[UnsupportedOSPlatform("browser")]  //定义类范围属性,"browser" 中不支持ColorConsoleLogger类型
[ProviderAlias("ColorConsole")]     //在提供程序上定义别名,控制ColorConsoleLogger的配置
public sealed class ColorConsoleLoggerProvider : ILoggerProvider
{
    private readonly IDisposable? _onChangeToken;
    private ColorConsoleLoggerConfiguration _currentConfig;
    // 可多线程访问的键/值对的线程安全集合<日志类别名称, 日志实例ILogger>
    private readonly ConcurrentDictionary<string, ColorConsoleLogger> _loggers =
        new(StringComparer.OrdinalIgnoreCase);

    //通过IOptionsMonitor<TOptions>接口来更新对基础ColorConsoleLoggerConfiguration对象的更改
    public ColorConsoleLoggerProvider(
        IOptionsMonitor<ColorConsoleLoggerConfiguration> config)
    {
        _currentConfig = config.CurrentValue;
        _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
    }

    // 为每个类别名称创建一个ColorConsoleLogger实例,并将其存储在 ConcurrentDictionary<TKey,TValue>中
    public ILogger CreateLogger(string categoryName) =>
        _loggers.GetOrAdd(categoryName, name => new ColorConsoleLogger(name, GetCurrentConfig));

    private ColorConsoleLoggerConfiguration GetCurrentConfig() => _currentConfig;

    public void Dispose()
    {
        _loggers.Clear();
        _onChangeToken?.Dispose();
    }
}

ColorConsoleLoggerProvider 类定义两个类范围属性:

可以使用任何有效的 配置提供程序指定配置。 请考虑以下 appsettings.json 文件:

{
    "Logging": {
        //在此处添加 ColorConsole子节点
        "ColorConsole": {
          "LogLevelToColorMap": {
            "Information": "DarkGreen",
            "Warning": "Yellow",
            "Error": "Red"
          }
        }
    }
}

这会将日志级别配置为以下值:

Information 日志级别设置为 DarkGreen,这将替代 ColorConsoleLoggerConfiguration 对象中设置的默认值。

3. 服务实例注册及注入

实现ILoggingBuilder 接口
实现自定义日志记录提供程序扩展接口

using ContosoUniversity.Extensions.Logger;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging.Configuration;

namespace ContosoUniversity.Extensions;

public static class ColorConsoleLoggerExtensions
{
    public static ILoggingBuilder AddColorConsoleLogger(this ILoggingBuilder builder)
    {
        // 添加使用ILoggerProviderConfiguration接口或所需的服务
        builder.AddConfiguration();
        // 尝试注册单例服务<服务类型, 服务描述及实现>
        builder.Services.TryAddEnumerable(
            ServiceDescriptor.Singleton<ILoggerProvider, ColorConsoleLoggerProvider>());
        // 指示应将日志设置加载到ILogger实例的类型中
        LoggerProviderOptions.RegisterProviderOptions
            <ColorConsoleLoggerConfiguration, ColorConsoleLoggerProvider>(builder.Services);

        return builder;
    }

    public static ILoggingBuilder AddColorConsoleLogger(this ILoggingBuilder builder,
        Action<ColorConsoleLoggerConfiguration> configure)
    {
        builder.AddColorConsoleLogger();
        builder.Services.Configure(configure);

        return builder;
    }
}

服务注入
按照约定,注册依赖项注入的服务作为应用程序的启动例程的一部分进行。 注册发生在 Program 类中,也可以委托给 Startup 类。 在此示例中,你将直接从 Program.cs注册。

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Logging.ClearProviders();
builder.Services.AddLogging(logBuilder =>
{
    //logBuilder.AddConsole();    // 控制台日志
    logBuilder.AddColorConsoleLogger();
    /* 还可通过回调函数,继续对自定义记录器配置项进行添加或修改
    logBuilder.AddColorConsoleLogger(configuration =>
    {
        // Replace warning value from appsettings.json of "Cyan"
        configuration.LogLevelToColorMap[LogLevel.Warning] = ConsoleColor.DarkCyan;
        // Replace warning value from appsettings.json of "Red"
        configuration.LogLevelToColorMap[LogLevel.Error] = ConsoleColor.DarkRed;
    });
    */
});

var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var logger = services.GetRequiredService<ILogger<Program>>();

    logger.LogError(7, "Oops, there was an error.");
    logger.LogTrace(5, "== 120.");	//没有配置Track类型日志,输出时则会自动过滤
}

运行此简单应用程序会将颜色输出呈现到控制台窗口,如下图所示:
在这里插入图片描述

第三方日志记录提供程序

下面是一些适用于各种 .NET 工作负荷的第三方日志记录框架:

某些第三方框架可以执行 语义日志记录,也称为结构化日志记录。
使用第三方框架类似于使用其中一个内置提供程序:

  1. 将 NuGet 包添加到项目。
  2. 调用日志记录框架提供的 ILoggerFactoryILoggingBuilder 扩展方法。

Log4Net日志

Log4net是从Java中的Log4j迁移过来的一个.Net版的开源日志框架,它的功能很强大,可以将日志分为不同的等级,以不同的格式输出到不同的存储介质中,比如:数据库、txt文件、内存缓冲区、邮件、控制台、ANSI终端、远程接收端等等。

注册及配置
# NuGet包
Install-package Microsoft.Extensions.Logging.Log4Net.AspNetCore 8.0

ILoggingBuilder服务注入

builder.Services.AddLogging(logBuilder =>
{
    logBuilder.ClearProviders();	//清空默认的日志提供程序[appsettings.json中Logging节点]
    
    logBuilder.AddConsole();    // 控制台日志
    builder.Logging.AddLog4Net();
});

// 启用敏感数据以及详细错误跟踪
public partial class SchoolContext : DbContext
{
    //......
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder//.LogTo(Console.WriteLine)
            // 启用敏感数据
            .EnableSensitiveDataLogging()
            // 启用详细错误
            .EnableDetailedErrors();
        base.OnConfiguring(optionsBuilder);
    }    
}
日志项配置

日志配置文件log4net.config,默认配置可存放在项目根下

<?xml version="1.0" encoding="utf-8"?>
<log4net>
  <!-- Define some output appenders -->
  <appender name="FileAppenderDefault" type="log4net.Appender.RollingFileAppender">
    <!--日志输出到exe程序这个相对目录下-->
    <file value= "..\logs\"/>

    <!--防止多线程时不能写Log,官方说线程非安全-->
    <!--实际使用时,本地测试正常,部署后没有不能写日志的情况-->
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />

    <!--追加日志内容,true后续输出的日志会追加到之前的日志文件-->
    <appendToFile value="true" />

    <!--可以为:Once|Size|Date|Composite-->
    <!--Composite为Size和Date的组合-->
    <rollingStyle value="Composite" />

    <!--置为true,当前最新日志文件名永远为file节中的名字-->
    <staticLogFileName value="false" />

    <!--当备份文件时,为文件名加的后缀-->
    <datePattern value="SqlTrack_yyyy-MM-dd'.log'" />

    <!--日志最大个数,都是最新的-->
    <!--rollingStyle节点为Size时,只能有value个日志-->
    <!--rollingStyle节点为Composite时,每天有value个日志-->
    <maxSizeRollBackups value="100" />

    <!--可用的单位:KB|MB|GB-->
    <maximumFileSize value="10MB" />

    <!--输出级别在INFO和ERROR之间的日志-->
    <filter type="log4net.Filter.LevelRangeFilter">
      <param name="LevelMin" value="ALL" />
      <param name="LevelMax" value="FATAL" />
    </filter>
    <!--<layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%n==========
			%n【日志级别】%-5level
			%n【记录时间】%date
			%n【执行时间】[%r]毫秒
			%n【执行Log分类的名称】%logger
			%n【传入信息内容】%message
			%n=========="/>
    </layout>-->
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="[%p][%d{HH:mm:ss}]【%logger】 %message %n" />
    </layout>
  </appender>
  <root>
    <!--(高) OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL (低) -->
    <!--<priority value="ALL"/>-->
    <level value="ALL"/>
    <!-- 根据LOG4NET_CONFIG_FILE环境变量确定appender -->
    <appender-ref ref="FileAppenderDefault" />
  </root>
</log4net>

注解
默认配置文档log4net.config也可以指定配置路径和配置文件名,比如
builder.Logging.AddLog4Net("Cfg\log4net_Development.config");
注意Log4Net需通过项目的appsettings.json中Logging节点配置项进行输出关键性日志。

项目运行所生成的日志内容
在这里插入图片描述

Serilog日志

Serilog 是一个功能强大的日志记录库,专为 .NET 平台设计。它提供了丰富的 API 和可插拔的输出器及格式化器,使得开发者能够轻松定制和扩展日志记录功能。

注意
Srilog.AspNetCore是集成 Serilog 到 ASP.NET Core项目中,其中包含了以下包而无须再手工引入
在这里插入图片描述

JSON格式配置
"Serilog": {
  //Sink接口(NuGet包引用,为ASPCore内置时可无免指定包引用)
  "Using": [ "Serilog.Enrichers.Thread", "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
  //最小级别
  "MinimumLevel": {
    "Default": "Information",
    "Override": {
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  },
  //丰富器
  "Enrich": [
    {
      "Name": "FromLogContext"
    },
    {
      "Name": "host",
      "Args": {
        "value": "Environment.MachineName"
      }
    },
    {
      "Name": "WithThreadId" //扩充当前管理的ThreadId属性
    }
  ],
  //过滤器
  //"Filter": [
  //  {
  //    "Name": "ByExcluding",
  //    "Args": {
  //      //只包括
  //      "expression": "StartsWith(SourceContext, 'Microsoft.EntityFrameworkCore.')"
  //    }
  //  },
  //  {
  //    "Name": "ByIncludingOnly",
  //    "Args": {
  //      //不包括
  //      "expression": "StartsWith(SourceContext, 'Microsoft.Hosting.Lifetime.')"
  //    }
  //  }
  //],
  //输出器
  "WriteTo": [
    {
      "Name": "Console",
      "Args": {
        "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
        "outputTemplate": "[{Level:u4}][{Timestamp:HH:mm:ss}][{SourceContext}]{Exception}{NewLine}{Message:lj}{NewLine}"
      }
    },
    {
      "Name": "File",
      "Args": {
        "path": "logs/traceSql-.log",
        "restrictedToMinimumlevel": "Information",
        "fileSizeLimitBytes": 10485760,
        //"levelSwitch": "InitialLevel",
        "rollingInterval": "Day",
        "rollOnFileSizeLimit": true,
        "retainedFileCountLimit": 7,
        "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter,Serilog.Formatting.Compact",
        "outputTemplate": "[{Level:u4}][{Timestamp:HH:mm:ss}][{EventId.Id}]{SourceContext}{Exception}{NewLine}{Message:lj}{NewLine}"
      }
    }
  ]
},

通过引用NuGet包Serilog.Settings.Configuration实现JSON配置注入该服务:

// 注册服务
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(new ConfigurationBuilder()
                            .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
                            .Build())
    .CreateLogger();
//DI注入
builder.Host.UseSerilog();

这是最简单服务配置方式,下面以不同环境进行配置为例

  • 先定义两个json环境配置文件,如appsettings.Developmentappsettings.Production
    在这里插入图片描述
    两个Json配置内容可以先配置为一样,比如
{
	"Logging": {
		"LogLevel": {
			"Default": "Information",
			"Microsoft.AspNetCore": "Warning"
			}
		},
		"Serilog": {
			//......省略
		},
		"AllowedHosts": "*"
	}
}

提示
两者最主要的区别,无非就是WriteTo输出,在appsettings.Production生产环境实际项目中可以不需要Console终端输出模式,包含输出方式可以是Sinks.MSSqlServer、Sinks.MySQL数据库或Sinks.Seq、Sinks.Elasticsearch日志服务器平台等等。

  • Json配置加载及服务注入
var builder = WebApplication.CreateBuilder(args);
var appEnvironment = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json";

Log.Logger = new LoggerConfiguration()
	.ReadFrom.Configuration(new ConfigurationBuilder()
							.AddJsonFile(appEnvironment, optional: true)
							.Build())
	.CreateLogger();

builder.Host.UseSerilog();
Log.Information($"当前项目环境Json配置为:【{appEnvironment}】");

重点就是通过appEnvironment变量动态获取ASPNETCORE_ENVIRONMENT环境变量,当在Debug调试模式下运行时,系统会动态加载appsettings.Development配置。反之,以Publish发布后则会调用appsettings.Production配置进行注入服务。

  • 当发布到生产环境运行时,比如发布到Publist目录直接执行主程序exe文件
    在这里插入图片描述

注意
日志文件存储路径是运行目录(编译后)的logs\xxx.log位置,而Debug调试模式时存放在项目根下。

编程Code方式配置
builder.Host.UseSerilog((context, loggerconfig) =>
{
    string _outputTemplate = "[{Level:u4}][{Timestamp:HH:mm:ss}][<{ThreadId}>{SourceContext}]{Exception}{NewLine}{Message:lj}{NewLine}";
    // formatProvider: 提供特定于区域性的格式化信息或为null
    var _formatter = new MessageTemplateTextFormatter(_outputTemplate, formatProvider: null);
    
    loggerconfig.MinimumLevel.Information()
        .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
        .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
        .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Information)
        //.Filter.ByIncludingOnly(logEvent => logEvent.Level >= LogEventLevel.Error)
        //.Filter.ByExcluding(Matching.FromSource("Microsoft.EntityFrameworkCore"))
        .Enrich.FromLogContext()    //扩充日志上下文,使用LogContext.PushProperty添加和移除属性
        .Enrich.WithProperty("host", Environment.MachineName)
        .Enrich.WithThreadId()

        .WriteTo.Console(outputTemplate: _outputTemplate, theme: AnsiConsoleTheme.Literate)
        .WriteTo.Async(c => c.File(
            formatter: _formatter,
            path: Path.Combine("logs", @"traceSql-.log"),
            restrictedToMinimumLevel: LogEventLevel.Information,
            fileSizeLimitBytes: 10 * 1024 * 1024,      // Singlefile:10M
            levelSwitch: null, //new Serilog.Core.LoggingLevelSwitch(LevelAlias.Minimum),
            //outputTemplate: _outputTemplate,
            //buffered: false,
            //shared: false,
            //flushToDiskInterval: null,
            rollingInterval: RollingInterval.Day,
            rollOnFileSizeLimit: true,
            retainedFileCountLimit: 7
            //encoding: null,
            //hooks: null,
            //retainedFileTimeLimit: null
            ));
    
});

var app = builder.Build();
// 启用HttpRequestLogging
app.UseForwardedHeaders();
app.UseSerilogRequestLogging(options =>
    options.EnrichDiagnosticContext = RequestEnricher.LogAdditionalInfo);

Log.Verbose("This is a verbose log message.");
Log.Warning("This is a warning log message.");
Log.Error("This is an error log message.");
//Log.CloseAndFlush();

namespace ContosoUniversity.Extensions.Logger;

public static class RequestEnricher
{
    //增加Client请求IP
    public static void LogAdditionalInfo(IDiagnosticContext diagnosticContext, HttpContext httpContext)
    {
        diagnosticContext.Set(
            "ClientIP",
            httpContext.Connection.RemoteIpAddress?.ToString());

    }
}

其中,.Enrich.WithThreadId()丰富器扩充当前管理的子线程信息,比如在输出模板中获取子线程ID{ThreadId}等,而.WriteTo.Async是以异步模型写入文件,需引用NuGet包Serilog.Sinks.Async

LoggerConfiguration配置结构
  • Sinks接收器
  • MinimumLevel最小级别
  • Enrich丰富器
  • Filter过滤(筛选)器
  • WriteTo输出器
  • Property扩展属性

Serilog配置分为Sinks接收器、WriteTo输出器以及Filter过滤器等部分组成,可以通过编程方式或XML、JSON编码方式进行配置。

1. Sinks接收器

提供给.WriteTo输出使用,需引用NeGet包(dotnet add package)支持

# 将日志输出到控制台[Serilog.AspNetCore内置]
Serilog.Sinks.Console

# 将日志写入文件中,支持按时间滚动[Serilog.AspNetCore内置]
Serilog.Sinks.File

# 将日志发送到 Seq 日志服务器,Seq 提供了强大的查询和可视化功能
Serilog.Sinks.Seq

# 将日志发送到 Elasticsearch,配合 Kibana 使用可以进行复杂的日志分析和可视化
Serilog.Sinks.Elasticsearch

# 将日志记录到 Microsoft SQL Server 数据库
Serilog.Sinks.MSSqlServer
# 将日志记录到 MySQL 数据库
Serilog.Sinks.MySQL

# 通过 HTTP POST 请求发送日志到远程 API 或服务
Serilog.Sinks.Http

# 通过电子邮件发送错误或其他关键日志信息
Serilog.Sinks.Email

# 将日志发送到 Azure Application Insights,提供性能监控和诊断工具
Serilog.Sinks.ApplicationInsights
2. MinimumLevel最小记录级别

配置将事件传递到接收器的最低级别。如果未指定,则仅 LogEventlevel.lnformation 级别的事件及以上都将通过。

3. Enrichers丰富器

丰富器是一种扩展机制,可以将额外的上下文信息添加到日志事件中。Serilog提供了一些内置的丰富器,例如WithProperty和WithProperties,可以用于添加自定义属性到日志事件中。

除了内置的丰富器,Serilog还支持自定义丰富器,开发人员可以根据自己的需求实现自己的丰富器。自定义丰富器可以从各种来源获取上下文信息,例如从请求头、数据库、配置文件等。

常见的Enrichers

  • WithThreadId ‌:为日志事件添加线程ID属性,帮助追踪线程相关的日志。
  • WithEnvironmentUserName ‌:为日志事件添加环境用户名属性,适用于需要记录用户信息的场景。
  • Serilog.Enrichers.Sensitive ‌:自动屏蔽日志中的敏感信息,如电子邮件地址和IBAN账号,确保日志既详尽又安全‌。
  • Serilog.Enrichers.MassTransit ‌:集成 MassTransit消息传递框架 ,记录消息和事件,便于分析和跟踪应用程序行为‌。

参考配置文档:丰富配置

提供给.Enrich扩充使用,第三方丰富器需引用NeGet包(dotnet add package)支持

# 使用环境变量扩充 Serilog 事件。
Serilog.Enrichers.Context

# 使用 System.Environment 中的属性扩充 Serilog 日志事件。
Serilog.Enrichers.Environment
# Serilog 的进程丰富器。
Serilog.Enrichers.Process
# 使用当前线程中的属性扩充 Serilog 事件。
Serilog.Enrichers.Thread

# 使用客户端 IP、相关 ID 和 HTTP 请求标头丰富日志。
Serilog.Enrichers.ClientInfo
4. Filters过滤器

过滤器控制要处理的日志事件。该属性提供了用于配置过滤器的方法。

// Example
builder.Host.UseSerilog((context, loggerconfig) =>
{
    loggerconfig.MinimumLevel.Information()
        //ByIncludingOnly: 只包括与谓词匹配的事件
        .Filter.ByIncludingOnly(logEvent => logEvent.Level >= LogEventLevel.Error)
        //ByExcluding: 不包括匹配谓词的日志事件
        .Filter.ByExcluding(Matching.FromSource("Microsoft.EntityFrameworkCore"));

    //loggerconfig.CreateLogger();
});                            
5. WriteTo输出

以下以输出到文件.WriteTo.File参数配置Serilog.Sinks.File 5.0.0为例

/// <summary>
/// 将日志事件写入指定文件。
/// </summary>
/// <param name="sinkConfiguration">日志记录接收器配置</param>
/// <param name="path">文件路径</param>
/// <param name="restrictedToMinimumLevel">通过接收器的事件的最小级别,当指定levelSwitch时忽略
/// <param name="formatProvider">提供特定于区域性的格式化信息或为空</param>
/// <param name="outputTemplate">描述用于写入接收器的格式的消息模板,
/// 默认值为“{Timestamp:yyyy-MM-dd HH:mm:ss”。fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"</param>
/// <param name="fileSizeLimitBytes">允许日志文件增长到的大致最大大小,以字节为单位
/// 对于不受限制的增长,请传递 null。默认值为 1GB。为避免写入部分事件,即使超出限制,也会完整写入限制内的最后一个事件
/// <param name="levelSwitch">允许在运行时更改最小直通级别的开关
/// <param name="buffered">是否使用缓冲来刷新输出文件,缺省为false</param>
/// <param name="shared">允许多个进程共享日志文件,缺省为false</param>
/// <param name="flushToDiskInterval">按指定的时间间隔定期执行完全磁盘刷新,缺省为null</param>
/// <param name="rollingInterval">日志记录将滚动到新文件的时间间隔</param>
/// <param name="rollOnFileSizeLimit">如果为 true,则在达到文件大小限制时将创建新文件,
/// 文件名将以 _NNN 格式附加一个数字,第一个文件名没有编号</param>
/// <param name="retainedFileCountLimit">保留的日志文件的最大数量,包括当前日志文件
/// 缺省为null,则是无限保留(默认值为31)</param>
/// <param name="encoding">写入文本文件的字符编码,默认值为不带BOM的UTF-8</param>
/// <param name="hooks">(可选)启用挂钩日志文件生命周期事件</param>
/// <param name="retainedFileTimeLimit">间隔结束后,滚动日志文件将保留的最长时间。
/// 必须大于或等于"TimeSpan.Zero"。如果"rollingInterval"为"RollingInterval.Infinite",则忽略默认值为无限期保留文件。
/// <returns>配置对象允许方法链接</returns>
public static LoggerConfiguration File(
    this LoggerSinkConfiguration sinkConfiguration,
    string path,
    LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
    string outputTemplate = DefaultOutputTemplate,
    IFormatProvider? formatProvider = null,
    long? fileSizeLimitBytes = DefaultFileSizeLimitBytes,
    LoggingLevelSwitch? levelSwitch = null,
    bool buffered = false,
    bool shared = false,
    TimeSpan? flushToDiskInterval = null,
    RollingInterval rollingInterval = RollingInterval.Infinite,
    bool rollOnFileSizeLimit = false,
    int? retainedFileCountLimit = DefaultRetainedFileCountLimit,
    Encoding? encoding = null,
    FileLifecycleHooks? hooks = null,
    TimeSpan? retainedFileTimeLimit = null)
        
/// <summary>
/// 将日志事件写入指定文件。
/// </summary>
/// <param name="sinkConfiguration">日志记录接收器配置</param>
/// <param name="formatter">格式化程序,例如“JsonFormatter”,用于将日志事件转换为文件的文本。
/// 如果需要控制常规文本格式,请使用"File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan?, RollingInterval, bool, int?, Encoding, FileLifecycleHooks, TimeSpan?)"
/// 的另一个重载,并改为指定 outputTemplate 参数</param>
/// <param name="path">文件路径</param>
/// <param name="restrictedToMinimumLevel">通过接收器的事件的最小级别,当指定levelSwitch时忽略
/// <param name="levelSwitch">允许在运行时更改最小直通级别的开关
/// <param name="fileSizeLimitBytes">允许日志文件增长到的大致最大大小,以字节为单位
/// 对于不受限制的增长,请传递 null。默认值为 1GB。为避免写入部分事件,即使超出限制,也会完整写入限制内的最后一个事件
/// will be written in full even if it exceeds the limit.</param>
/// <param name="buffered">是否使用缓冲器来刷新输出文件</param>
/// <param name="shared">允许多个进程共享日志文件,默认值为false</param>
/// <param name="flushToDiskInterval">按指定的时间间隔定期执行完全磁盘刷新,缺省为null</param>
/// <param name="rollingInterval">日志记录将滚动到新文件的时间间隔</param>
/// <param name="rollOnFileSizeLimit">如果为 true,则在达到文件大小限制时将创建新文件,
/// 如果为 true,则在达到文件大小限制时将创建新文件。文件名将以 _NNN 格式附加一个数字,第一个文件名没有编号</param>
/// <param name="retainedFileCountLimit">保留的日志文件的最大数量,包括当前日志文件
/// 缺省为null,则是无限保留(默认值为31)</param>
/// <param name="encoding">写入文本文件的字符编码,默认值为不带BOM的UTF-8</param>
/// <param name="hooks">(可选)启用挂钩日志文件生命周期事件</param>
/// <param name="retainedFileTimeLimit">间隔结束后,滚动日志文件将保留的最长时间。
/// 必须大于或等于 “TimeSpan.Zero”。
public static LoggerConfiguration File(
    this LoggerSinkConfiguration sinkConfiguration,
    ITextFormatter formatter,
    string path,
    LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
    long? fileSizeLimitBytes = DefaultFileSizeLimitBytes,
    LoggingLevelSwitch? levelSwitch = null,
    bool buffered = false,
    bool shared = false,
    TimeSpan? flushToDiskInterval = null,
    RollingInterval rollingInterval = RollingInterval.Infinite,
    bool rollOnFileSizeLimit = false,
    int? retainedFileCountLimit = DefaultRetainedFileCountLimit,
    Encoding? encoding = null,
    FileLifecycleHooks? hooks = null,
    TimeSpan? retainedFileTimeLimit = null)

/// <summary>
/// 将审计日志事件写入指定文件
/// </summary>
/// <param name="sinkConfiguration">日志记录接收器配置</param>
/// <param name="path">文件路径</param>
/// <param name="restrictedToMinimumLevel">通过接收器的事件的最小级别,当指定levelSwitch时忽略
/// <param name="levelSwitch">允许在运行时更改最小直通级别的开关
/// <param name="formatProvider">提供特定于区域性的格式化信息或为空</param>
/// <param name="outputTemplate">描述用于写入接收器的格式的消息模板,
/// 默认值为“{Timestamp:yyyy-MM-dd HH:mm:ss”。fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"</param>
/// <param name="encoding">写入文本文件的字符编码,默认值为不带BOM的UTF-8</param>
/// <param name="hooks">(可选)启用挂钩日志文件生命周期事件</param>
/// <returns>配置对象允许方法链接</returns>
public static LoggerConfiguration File(
    this LoggerAuditSinkConfiguration sinkConfiguration,
    string path,
    LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
    string outputTemplate = DefaultOutputTemplate,
    IFormatProvider? formatProvider = null,
    LoggingLevelSwitch? levelSwitch = null,
    Encoding? encoding = null,
    FileLifecycleHooks? hooks = null)

/// <summary>
/// 将审计日志事件写入指定文件
/// </summary>
/// <param name="sinkConfiguration">日志记录接收器配置</param>
/// <param name="formatter">格式化程序,例如"JsonFormatter",用于将日志事件转换为文件的文本。
/// 如果需要控制常规文本格式,请使用
/// "File(LoggerAuditSinkConfiguration, string, LogEventLevel, string, IFormatProvider, LoggingLevelSwitch, Encoding, FileLifecycleHooks)"的另一个重载,并改为指定 outputTemplate 参数。/>
/// </param>
/// <param name="path">文件路径</param>
/// <param name="restrictedToMinimumLevel">通过接收器的事件的最小级别,当指定levelSwitch时忽略
/// <param name="levelSwitch">允许在运行时更改最小直通级别的开关
/// <param name="encoding">写入文本文件的字符编码,默认值为不带BOM的UTF-8</param>
/// <param name="hooks">(可选)启用挂钩日志文件生命周期事件</param>
/// <returns>配置对象允许方法链接</returns>
public static LoggerConfiguration File(
    this LoggerAuditSinkConfiguration sinkConfiguration,
    ITextFormatter formatter,
    string path,
    LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
    LoggingLevelSwitch? levelSwitch = null,
    Encoding? encoding = null,
    FileLifecycleHooks? hooks = null)

提示
上述方法体只提供几种通用最较强的几种,过时或废弃不再列出。

6. Property附加属性

静态附加

builder.Host.UseSerilog((context, loggerconfig) =>
{
    loggerconfig.Enrich.WithThreadId()
        .Enrich.WithProperty("host", Environment.MachineName);
});

或者在Json中附加

"Serilog":{
    "Properties": {
      "Home": "testRoot"
    }    
}

动态附加
调用IDiagnosticContext接口,进行附加扩展属性:

var app = builder.Build();
// ......
app.UseStaticFiles();
// 添加中间件以简化请求日志记录
app.UseSerilogRequestLogging();

public class HomeController : Controller
{
    readonly IDiagnosticContext _diagnosticContext;
    readonly ILogger<HomeController> _logger;
    readonly SchoolContext _context;

    public HomeController(IDiagnosticContext diagnosticContext,
        ILogger<HomeController> logger, SchoolContext context)
    {
        _diagnosticContext = diagnosticContext;
        _logger = logger;
        _context = context;
    }

    public IActionResult Index()
    {
        // The request completion event will carry this property.
        _diagnosticContext.Set("CatalogLoadTime", 1423);
        return View();
    }    
}

还可以通过RequestLoggingOptions设置所提供IDiagnosticContext实例的值,我们基本上使用完全相同的方法来定制中间件所使用的方法。下面的静态帮助器类从当前HttpContext上下文检索值,并在值可用时对其进行设置。
下面的静态helper类从当前HttpContext检索值,并在值可用时设置它们。

public static class LogHelper 
{
    public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
    {
        var request = httpContext.Request;

        // Set all the common properties available for every request
        diagnosticContext.Set("Host", request.Host);
        diagnosticContext.Set("Protocol", request.Protocol);
        diagnosticContext.Set("Scheme", request.Scheme);

        // Only set it if available. You're not sending sensitive data in a querystring right?!
        if(request.QueryString.HasValue)
        {
            diagnosticContext.Set("QueryString", request.QueryString.Value);
        }

        // Set the content-type of the Response at this point
        diagnosticContext.Set("ContentType", httpContext.Response.ContentType);

        // Retrieve the IEndpointFeature selected for the request
        var endpoint = httpContext.GetEndpoint();
        if (endpoint is object) // endpoint != null
        {
            diagnosticContext.Set("EndpointName", endpoint.DisplayName);
        }
    }
}

上面的帮助器函数从“Request”,“Response”以及其他中间件(端点名称)设置的功能中检索值。您可以扩展它,以根据需要在请求中添加其他值。
您可以通过调用UseSerilogRequestLoggingEnrichDiagnosticContext属性,来注册上面的帮助类:

var app = builder.Build();
// ... Other middleware
app.UseStaticFiles();

app.UseSerilogRequestLogging(opts
        => opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest);

// ... Other middleware

现在,当您发出请求时,您将看到添加到Serilog结构化日志中的所有其他属性:
在这里插入图片描述
只要您具有通过当前HttpContext可供中间件管道使用的值,就可以使用此方法。但是MVC的相关属性是个例外,它们是MVC中间件“内部”的特性,例如action 名称或RazorPage处理程序名称。

-持续更新-