You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 02:15:44