Blazor Server自定义日志写库引发项目崩溃的解决方法咨询
修复Blazor Server自定义ILogger批量写入数据库导致的崩溃问题
你的核心问题是每次日志都同步调用SaveChanges(),短时间内大量日志请求直接打满数据库连接,导致Blazor Server进程崩溃。下面是几个实用的修复方案:
1. 批量缓存+定时写入日志
不要每条日志都立刻提交到数据库,而是先缓存到线程安全队列,达到阈值或定时批量写入:
public class DatabaseLogger<T> : ILogger<T> { private readonly ConcurrentQueue<LogEntry> _logQueue = new(); private readonly IServiceScopeFactory _scopeFactory; private readonly Timer _flushTimer; private const int BatchSize = 50; // 每攒50条写一次 private const int FlushIntervalMs = 5000; // 每5秒强制写一次 public DatabaseLogger(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; _flushTimer = new Timer(FlushLogs, null, FlushIntervalMs, FlushIntervalMs); } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { // 构造日志实体 var logEntry = new LogEntry { Level = logLevel.ToString(), Message = formatter(state, exception), Timestamp = DateTime.UtcNow, // 补充其他字段 }; _logQueue.Enqueue(logEntry); // 达到批量阈值立刻写入 if (_logQueue.Count >= BatchSize) { FlushLogs(); } } private void FlushLogs(object state = null) { if (_logQueue.IsEmpty) return; using var scope = _scopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var batch = new List<LogEntry>(); while (_logQueue.TryDequeue(out var entry)) { batch.Add(entry); if (batch.Count >= BatchSize) break; } if (batch.Any()) { dbContext.LogEntries.AddRange(batch); dbContext.SaveChangesAsync().Wait(); // 后台任务用Wait避免异步阻塞问题 } } // 实现ILogger接口的其他方法... }
2. 过滤冗余框架日志
Blazor Server启动和运行时会产生大量Microsoft.*类别的Debug/Trace级日志,绝大多数不需要存数据库。在日志提供器里添加过滤规则:
public class DatabaseLoggerProvider : ILoggerProvider { public ILogger CreateLogger(string categoryName) { // 过滤掉框架内置日志,只保留业务相关日志 if (categoryName.StartsWith("Microsoft.") || categoryName.StartsWith("System.")) { return NullLogger.Instance; } return new DatabaseLogger<object>(_serviceScopeFactory); } // 其他实现代码... }
也可以在Program.cs全局配置过滤级别:
builder.Logging.AddFilter<DatabaseLoggerProvider>( category => !category.StartsWith("Microsoft."), level => level >= LogLevel.Warning);
3. 异步写入+避免阻塞主线程
同步调用SaveChanges()会阻塞Blazor的渲染线程,改成异步批量写入,且不在Log方法内等待执行结果:
private async Task FlushLogsAsync() { if (_logQueue.IsEmpty) return; using var scope = _scopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var batch = new List<LogEntry>(); while (_logQueue.TryDequeue(out var entry)) { batch.Add(entry); } if (batch.Any()) { dbContext.LogEntries.AddRange(batch); await dbContext.SaveChangesAsync(); } } // 在Log方法里触发异步任务,不等待执行完成 if (_logQueue.Count >= BatchSize) { _ = FlushLogsAsync(); }
4. 优化数据库性能
- 给日志表的
Timestamp、Level等常用查询字段添加索引 - 使用EF Core批量扩展库(如
EFCore.BulkExtensions)替代AddRange,减少数据库往返次数:
await dbContext.BulkInsertAsync(batch);
内容的提问来源于stack exchange,提问作者Sadabadi
相关产品推荐
相关产品推荐

