using System;
using System.IO;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting.Compact;
using Serilog.Sinks.SystemConsole.Themes;
namespace Dpz.Core.Infrastructure.Configuration;
public static class LogConfiguration
{
private static bool LogFilter(LogEvent logEvent)
{
if (logEvent.Properties.TryGetValue("RequestPath", out var requestPath))
{
var pathPrefix = new[] { "/runtask", "/account", "/notification", "/app/notification" };
if (requestPath is ScalarValue value)
{
var result = pathPrefix.Any(x =>
value
.Value?.ToString()
?.StartsWith(x, StringComparison.CurrentCultureIgnoreCase)
?? false
);
return result;
}
}
return false;
}
public static void ConfigurationLog(
this IHostBuilder host,
LogSeq? logSeq,
Func<LogEvent, bool>? filter = null
)
{
if (
logSeq == null
|| string.IsNullOrEmpty(logSeq.ServerUri)
||
#if !DEBUG
string.IsNullOrEmpty(logSeq.ApiKey)
||
#endif
string.IsNullOrEmpty(logSeq.Program)
)
{
throw new ArgumentNullException(nameof(logSeq));
}
filter ??= LogFilter;
var levelSwitch = new LoggingLevelSwitch();
host.UseSerilog(
(context, services, cfg) =>
cfg
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
//.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Warning)
.MinimumLevel.Override("System.Net.Sockets", LogEventLevel.Warning)
.MinimumLevel.Override(
"ZiggyCreatures.Caching.Fusion.FusionCache",
LogEventLevel.Warning
)
.MinimumLevel.Override("ZiggyCreatures.Caching.Fusion", LogEventLevel.Warning)
.MinimumLevel.Override(
"Dpz.Core.Web.Jobs.Services.Implements",
LogEventLevel.Verbose
)
.MinimumLevel.Information()
.Enrich.WithProperty("Version", ApplicationTools.ApplicationVersion.Value)
.Enrich.WithProperty("Server", Environment.MachineName)
.Enrich.WithProperty("Program", logSeq.Program)
.Enrich.FromLogContext()
.WriteTo.Logger(lc =>
{
lc.WriteTo.File(
new CompactJsonFormatter(),
Path.Combine("logs", $"{DateTime.Now:yyyyMM}", ".json"),
rollingInterval: RollingInterval.Day,
fileSizeLimitBytes: 10240
);
})
.WriteTo.Logger(lc =>
{
lc.Filter.ByExcluding(filter)
.WriteTo.Async(x =>
x.Console(
theme: AnsiConsoleTheme.Literate,
applyThemeToRedirectedOutput: true
)
);
})
.WriteTo.Logger(lc =>
{
var loggerConfiguration = lc.Filter.ByExcluding(filter);
if (string.IsNullOrEmpty(logSeq.ApiKey))
{
loggerConfiguration.WriteTo.Seq(
logSeq.ServerUri,
controlLevelSwitch: levelSwitch
);
}
else
{
loggerConfiguration.WriteTo.Seq(
logSeq.ServerUri,
apiKey: logSeq.ApiKey,
controlLevelSwitch: levelSwitch
);
}
})
);
#if DEBUG
// 日志追踪需要更多评估,暂时补上生产环境
host.ConfigureServices(
(_, services) => services.AddHostedService<TracingLifetimeService>()
);
#endif
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这段代码是一个用于配置 Serilog 日志系统的静态扩展类,属于 Dpz.Core.Infrastructure.Configuration 命名空间。它集中定义了日志过滤规则、输出目标(文件、控制台、Seq)以及日志级别覆盖,并对 Seq 配置和调试环境做了特殊处理。
下面按代码结构逐部分解释:
1. LogFilter 方法 —— 日志过滤规则
private static bool LogFilter(LogEvent logEvent)
- 作用是判断某条日志是否“应被排除”。
- 它从日志事件的
Properties中尝试读取RequestPath属性。 - 如果存在该属性,且它是一个
ScalarValue,则取其字符串值,检查是否以数组{ "/runtask", "/account", "/notification", "/app/notification" }中的任意前缀开头(忽略大小写)。 - 只要前缀匹配,就返回
true。 - 在后面的配置中,这个方法会作为
Filter.ByExcluding(filter)的默认参数,即返回true的日志会被过滤掉(不写入控制台和 Seq)。因此这些路径的请求日志不会被输出到外部,可能是为了避免敏感或高频日志干扰。
2. ConfigurationLog 扩展方法 —— 核心配置入口
public static void ConfigurationLog(this IHostBuilder host, LogSeq? logSeq, Func<LogEvent, bool>? filter = null)
- 为
IHostBuilder扩展配置 Serilog 的方法。 - 参数
logSeq封装了 Seq 服务器的相关配置(如ServerUri、ApiKey、Program)。 - 参数
filter是自定义日志过滤委托,默认为null,若不传则使用上面的LogFilter。
2.1 配置校验
if (logSeq == null || string.IsNullOrEmpty(logSeq.ServerUri) || ...)
{
throw new ArgumentNullException(nameof(logSeq));
}
- 强制要求
logSeq不能为空,且必须包含ServerUri和Program。 - 在非 DEBUG 编译环境下(
#if !DEBUG),还要求ApiKey非空,否则抛出异常。这保证了生产环境 Seq 通信的安全性。
2.2 设置默认过滤器
filter ??= LogFilter;
- 如果没有显式传入 filter,则使用
LogFilter作为默认排除条件。
2.3 启用级别开关
var levelSwitch = new LoggingLevelSwitch();
- 创建一个动态日志级别开关,用于在运行时通过 Seq 的动态级别控制来调整日志级别。
2.4 配置 Serilog
host.UseSerilog((context, services, cfg) => ...)
- 使用
UseSerilog扩展,定义一个完整的 Serilog 配置。
2.4.1 从配置和 DI 中读取
ReadFrom.Configuration:读取appsettings.json等配置文件中的 Serilog 设置。ReadFrom.Services:从依赖注入容器中获取需要的服务(例如自定义IDestructuringPolicy)。
2.4.2 覆盖多个命名空间的日志等级
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Warning)
.MinimumLevel.Override("System.Net.Sockets", LogEventLevel.Warning)
...
.MinimumLevel.Information()
- 将常用框架或第三方库的日志级别提升为
Warning,减少噪音。 - 特别地将某个业务命名空间
Dpz.Core.Web.Jobs.Services.Implements设为Verbose,可能是为了调试该模块的详细日志。 - 最后通过
.MinimumLevel.Information()将全局默认级别设置为Information。
2.4.3 丰富日志上下文
.Enrich.WithProperty("Version", ...)
.Enrich.WithProperty("Server", Environment.MachineName)
.Enrich.WithProperty("Program", logSeq.Program)
.Enrich.FromLogContext()
- 为每条日志添加固定的属性:
Version(应用程序版本)、Server(机器名)、Program(程序标识),并从LogContext中引入活动上下文属性。
2.4.4 定义多个输出目标
整个配置通过三个独立的 WriteTo.Logger 子记录器将日志路由到不同目标,互不干扰。
a. 文件日志
.WriteTo.Logger(lc =>
{
lc.WriteTo.File(
new CompactJsonFormatter(),
Path.Combine("logs", $"{DateTime.Now:yyyyMM}", ".json"),
rollingInterval: RollingInterval.Day,
fileSizeLimitBytes: 10240
);
})
- 将日志以压缩 JSON 格式写入
logs/yyyyMM/.json路径下。 - 按天滚动(
RollingInterval.Day)并限制单文件大小为 10 KB。 - 注意该路径写法有些可疑:
Path.Combine("logs", "202504", ".json")会产生logs/202504/.json,文件名是.json(隐藏文件),也许实际应生成类似logs/202504/yyyyMMdd.json或指定具体文件名。这是一个潜在的 bug 或故意的写法,但按此代码执行会生成以.json为文件名的文件,并且DateTime.Now:yyyyMM只精确到月份,导致所有日志都放在同一月目录下的.json文件中,再靠按天滚动区分旧文件。
b. 控制台日志
.WriteTo.Logger(lc =>
{
lc.Filter.ByExcluding(filter)
.WriteTo.Async(x =>
x.Console(
theme: AnsiConsoleTheme.Literate,
applyThemeToRedirectedOutput: true
)
);
})
- 使用默认的
LogFilter排除与请求路径匹配的日志。 - 通过
Async包装异步写入控制台(避免阻塞主线程)。 - 启用主题,并且即便输出重定向也应用 ANSI 主题色。
c. Seq 日志服务器
.WriteTo.Logger(lc =>
{
var loggerConfiguration = lc.Filter.ByExcluding(filter);
if (string.IsNullOrEmpty(logSeq.ApiKey))
{
loggerConfiguration.WriteTo.Seq(logSeq.ServerUri, controlLevelSwitch: levelSwitch);
}
else
{
loggerConfiguration.WriteTo.Seq(logSeq.ServerUri, apiKey: logSeq.ApiKey, controlLevelSwitch: levelSwitch);
}
})
- 同样使用
ByExcluding(filter)排除某些日志。 - 如果
ApiKey为空,则不带认证连接 Seq;否则携带 ApiKey 认证。 - 同时传入
controlLevelSwitch,使得 Seq 服务器可以远程控制客户端的最低日志级别(动态调整)。
2.5 DEBUG 环境特殊处理
#if DEBUG
host.ConfigureServices((_, services) => services.AddHostedService<TracingLifetimeService>());
#endif
- 仅在 Debug 编译下,注册一个后台主机服务
TracingLifetimeService(根据注释推测,可能是用于跟踪进程生命周期或调试日志追踪)。 - 注释说明“日志追踪需要更多评估,暂时补上生产环境”,暗示这是临时措施,未来可能在生产环境也开启。
总结
这段代码是一个集中式的 Serilog 配置中心,具备以下特性:
- 条件性配置:根据编译模式(DEBUG/RELEASE)要求不同的 Seq 配置。
- 灵活过滤:默认会排除特定请求路径的日志,避免不必要的信息淹没日志系统。
- 多端输出:同时输出到本地文件、控制台和远程 Seq 服务器,各端可独立过滤。
- 级别调控:通过覆盖不同命名空间的级别,抑制框架日志噪音,保留业务日志。
- 动态级别控制:借助
LoggingLevelSwitch使 Seq 能远程调整客户端日志等级。 - 调试辅助:在 Debug 构建下额外运行生命周期跟踪服务。
整体设计旨在为 ASP.NET Core 应用程序提供既安全又便于调试的日志基础设施。
AI 正在分析代码…
评论加载中...