深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。
ASP.NET Core 中的日志记录
.NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程序将日志写入不同的目标。 基本日志记录提供程序是内置的,并且还可以使用许多第三方提供程序。
文章目录
- ASP.NET Core 中的日志记录
- 配置依赖于 ILogger 的服务
- 内置日志记录提供程序
- 控制台
- 调试
- 事件来源
- 自定义日志记录提供程序
- 1. 创建自定义记录器
- 2. 自定义记录器提供程序
- 3. 服务实例注册及注入
- 第三方日志记录提供程序
- Log4Net日志
- 注册及配置
- 日志项配置
- Serilog日志
- JSON格式配置
- 编程Code方式配置
- LoggerConfiguration配置结构
- 1. Sinks接收器
- 2. MinimumLevel最小记录级别
- 3. Enrichers丰富器
- 4. Filters过滤器
- 5. WriteTo输出
- 6. Property附加属性
配置依赖于 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
{// 事件IDpublic 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
类定义两个类范围属性:
- UnsupportedOSPlatformAttribute:
"browser"
中不支持ColorConsoleLogger
类型。 - ProviderAliasAttribute:配置节可以使用
"ColorConsole"
键定义选项。
可以使用任何有效的 配置提供程序指定配置。 请考虑以下 appsettings.json 文件:
{"Logging": {//在此处添加 ColorConsole子节点"ColorConsole": {"LogLevelToColorMap": {"Information": "DarkGreen","Warning": "Yellow","Error": "Red"}}}
}
这会将日志级别配置为以下值:
- LogLevel.Information:ConsoleColor.DarkGreen
- LogLevel.Warning:ConsoleColor.Cyan
- LogLevel.Error:ConsoleColor.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 工作负荷的第三方日志记录框架:
- EFLogger
- JSNLog
- Log4Net
- Nlog
- Serilog
某些第三方框架可以执行 语义日志记录,也称为结构化日志记录。
使用第三方框架类似于使用其中一个内置提供程序:
- 将 NuGet 包添加到项目。
- 调用日志记录框架提供的
ILoggerFactory
或ILoggingBuilder
扩展方法。
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.Development
和appsettings.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: 提供特定于区域性的格式化信息或为nullvar _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:10MlevelSwitch: 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请求IPpublic 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 requestdiagnosticContext.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 pointdiagnosticContext.Set("ContentType", httpContext.Response.ContentType);// Retrieve the IEndpointFeature selected for the requestvar endpoint = httpContext.GetEndpoint();if (endpoint is object) // endpoint != null{diagnosticContext.Set("EndpointName", endpoint.DisplayName);}}
}
上面的帮助器函数从“Request”,“Response”以及其他中间件(端点名称)设置的功能中检索值。您可以扩展它,以根据需要在请求中添加其他值。
您可以通过调用UseSerilogRequestLogging
的EnrichDiagnosticContext
属性,来注册上面的帮助类:
var app = builder.Build();
// ... Other middleware
app.UseStaticFiles();app.UseSerilogRequestLogging(opts=> opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest);// ... Other middleware
现在,当您发出请求时,您将看到添加到Serilog结构化日志中的所有其他属性:
只要您具有通过当前HttpContext可供中间件管道使用的值,就可以使用此方法。但是MVC的相关属性是个例外,它们是MVC中间件“内部”的特性,例如action 名称或RazorPage处理程序名称。
-持续更新-
相关文章:

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...