.NET 5中Serilog依赖注入Logger无法发送邮件问题
我正在为C# .NET 5 Web应用及API集成Serilog,已配置Email和MS SQL接收器且均可正常工作,但发现仅直接使用Log类时能发送邮件,依赖注入的ILogger<T>无法发送邮件。
相关代码
Program.cs
public static void Main(string[] args) { var builder = new ConfigurationBuilder(); BuildConfig(builder); Log.Logger = CreateLogger(builder); try { CreateHostBuilder(args).Build().Run(); } catch (Exception e) { Log.Fatal(e, "Host terminated unexpectedly"); } finally { Log.CloseAndFlush(); } } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSerilog() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); public static Serilog.ILogger CreateLogger(ConfigurationBuilder builder) { var emailSettings = Configuration.GetSection("Serilog").GetSection("WriteTo").GetSection("0").GetSection("Args"); var emailInfo = CreateEmailInformation(emailSettings); return new LoggerConfiguration() .ReadFrom.Configuration(Configuration) .Enrich.FromLogContext() .WriteTo.Email(emailInfo) .CreateLogger(); }
API服务代码(CollectionRepository)
private readonly AppDbContext _context; private readonly ILogger<CollectionRepository> _logger; public CollectionRepository(AppDbContext appDbContext, ILogger<CollectionRepository> logger) { _context = appDbContext; _logger = logger; } public async Task<List<amp.collection>> GetCollections() { var result = new List<amp.collection>(); try { result = await db.GetCollections(); } catch (Exception e) { _logger.LogError(e, "Exception occurred in GetCollections()"); } return result; }
更新测试发现
在_logger.LogError()后立即添加Log.CloseAndFlush(),邮件即可发送,推测问题与邮件的异步特性有关。
原因及解决方案
核心原因
Serilog的Email接收器默认采用异步批量投递策略:日志会先缓存,等待达到批量阈值或指定周期后才实际发送邮件。直接使用Log.Fatal()时,程序后续进入finally块执行了Log.CloseAndFlush(),强制等待所有异步日志任务完成,所以邮件能发送成功;而依赖注入的ILogger<T>写入日志后,没有触发刷新操作,异步投递任务还没执行就继续后续流程,导致邮件未发送。
解决方法
修改Email接收器配置,关闭批量异步投递
在代码配置或配置文件中,设置batchPostingLimit: 1和period: TimeSpan.Zero,让日志立即发送(注意:此方式会同步阻塞线程,影响高并发场景性能,仅适合对实时性要求极高的场景)。
代码示例:.WriteTo.Email(emailInfo, batchPostingLimit: 1, period: TimeSpan.Zero)在合适时机触发日志刷新
- 全局层面:Web应用正常关闭时,Serilog会自动执行刷新,但如果需要在请求过程中确保邮件立即发送,可以注入Serilog原生的
Serilog.ILogger(而非微软的ILogger<T>),调用其Flush()方法; - 临时处理:在需要立即发送邮件的日志调用后,手动调用
Log.Flush()(注意全局调用的线程安全)。
- 全局层面:Web应用正常关闭时,Serilog会自动执行刷新,但如果需要在请求过程中确保邮件立即发送,可以注入Serilog原生的
统一使用Serilog原生Logger注入
放弃微软的ILogger<T>抽象层,直接注入Serilog的Serilog.ILogger,它提供了Flush()方法可手动触发日志投递,更贴合Serilog的原生特性。
额外注意点
你的CreateLogger方法同时使用了ReadFrom.Configuration()和手动添加WriteTo.Email(),可能导致重复配置Email接收器,建议统一通过配置文件或代码完成配置,避免潜在冲突。
内容的提问来源于stack exchange,提问作者onionvampire

