using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Debugging; namespace MAX.Framework.DependencyInjection { /// /// Serilog 日志接入:读取 appsettings.Serilog.json(文件缺失时回退到主配置里的 Serilog 节点), /// 再把 Serilog 接入 ASP.NET Core 日志管线,使 ILogger<T> 与 Log 静态入口共用同一套 Sink。 /// 日志按等级分目录(logs/等级/日期.log)由 appsettings.Serilog.json 里的 File sink + rollingInterval 负责。 /// public static class LoggerExtensions { /// Serilog 独立配置文件(构建/发布会复制到输出目录)。 private const string ConfigurationFileName = "appsettings.Serilog.json"; public static IHostApplicationBuilder AddLogger(this IHostApplicationBuilder host) { IConfigurationManager configuration = host.Configuration; // Serilog 默认会静默吞掉配置错误(Sink 名写错、过滤器语法错误等), // 开发环境把自诊断信息输出到控制台,避免再出现「配置了却没有日志」却查不到原因。 if (host.Environment.IsDevelopment()) { SelfLog.Enable(Console.Error.WriteLine); } string? configurationPath = ResolveConfigurationPath(host); if (configurationPath is null) { // 不直接抛异常:允许把 Serilog 节点写进 appsettings.json,但必须显式提示,而不是静默降级成「无任何 Sink」。 Console.Error.WriteLine($"[Serilog] 未找到 {ConfigurationFileName},将使用主配置中的 Serilog 节点。"); } else { configuration.AddJsonFile(configurationPath, optional: false, reloadOnChange: true); } Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .WriteTo.Console() .CreateLogger(); // 接管 ASP.NET Core 日志管线:宿主日志与 ILogger 统一写入 Log.Logger; // dispose: true 让宿主停止时自动 CloseAndFlush,保证缓冲中的日志落盘。 host.Services.AddSerilog(Log.Logger, dispose: true); return host; } /// /// 定位 Serilog 配置文件:支持绝对路径,其余情况依次在内容根目录(开发时即项目目录)与程序输出目录中查找。 /// private static string? ResolveConfigurationPath(IHostApplicationBuilder host) { if (Path.IsPathRooted(ConfigurationFileName)) { return File.Exists(ConfigurationFileName) ? ConfigurationFileName : null; } string[] candidates = [ Path.Combine(host.Environment.ContentRootPath, ConfigurationFileName), Path.Combine(AppContext.BaseDirectory, ConfigurationFileName), ]; return candidates.FirstOrDefault(File.Exists); } } }