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

按作用域/操作读取ASP.NET Core日志并在Blazor中展示

问题描述

我拥有多个ASP.NET BackgroundService,每个服务都在各自的作用域/操作(OP1和OP2)中记录日志。

背景服务代码

MyBackgroundService1

public class MyBackgroundService1 : BackgroundService
{
    private readonly ILogger<MyBackgroundService1> _logger;

    public MyBackgroundService1(ILogger<MyBackgroundService1> logger)
    {
        _logger = logger;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var activity = new Activity("OP1");
        activity.Start();
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Hello from MyBackgroundService1");
            await Task.Delay(5000, stoppingToken);
        }
    }
}

MyBackgroundService2

public class MyBackgroundService2 : BackgroundService
{
    private readonly ILogger<MyBackgroundService2> _logger;

    public MyBackgroundService2(ILogger<MyBackgroundService2> logger)
    {
        _logger = logger;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var activity = new Activity("OP2");
        activity.Start();
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Hello from MyBackgroundService2");
            await Task.Delay(1000, stoppingToken);
        }
    }
}

现在希望使用Blazor,为每个操作展示对应的日志表格,示例效果如下:

  • OP1日志:
    • Hello from MyBackgroundService1
    • Hello from MyBackgroundService1
  • OP2日志:
    • Hello from MyBackgroundService2
    • Hello from MyBackgroundService2

请问该如何实现?

实现方案

1. 创建自定义日志提供器与日志状态容器

需要一个能捕获日志并按Activity操作分组的容器,同时支持通知Blazor组件更新:

日志状态容器(支持实时通知)

using System.Collections.ObjectModel;
using System.ComponentModel;

public class LogContainer : INotifyPropertyChanged
{
    private readonly Dictionary<string, ObservableCollection<string>> _operationLogs = new();

    public IReadOnlyDictionary<string, ObservableCollection<string>> OperationLogs => _operationLogs;

    public void AddLog(string operationName, string message)
    {
        if (!_operationLogs.TryGetValue(operationName, out var logs))
        {
            logs = new ObservableCollection<string>();
            _operationLogs[operationName] = logs;
            OnPropertyChanged(nameof(OperationLogs));
        }
        logs.Add(message);
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

自定义日志提供器

using Microsoft.Extensions.Logging;

public class OperationLogProvider : ILoggerProvider
{
    private readonly LogContainer _logContainer;

    public OperationLogProvider(LogContainer logContainer)
    {
        _logContainer = logContainer;
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new OperationLogger(_logContainer);
    }

    public void Dispose() { }

    private class OperationLogger : ILogger
    {
        private readonly LogContainer _logContainer;

        public OperationLogger(LogContainer logContainer)
        {
            _logContainer = logContainer;
        }

        public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default;

        public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Information;

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
        {
            if (!IsEnabled(logLevel)) return;

            // 获取当前Activity的操作名称
            var operationName = System.Diagnostics.Activity.Current?.Name;
            if (string.IsNullOrEmpty(operationName)) return;

            var message = formatter(state, exception);
            _logContainer.AddLog(operationName, message);
        }
    }
}

2. 注册服务到DI容器

在Program.cs中注册日志提供器和日志容器:

builder.Services.AddSingleton<LogContainer>();
builder.Services.AddLogging(builder =>
{
    builder.AddProvider(new OperationLogProvider(builder.Services.BuildServiceProvider().GetRequiredService<LogContainer>()));
});

// 注册后台服务
builder.Services.AddHostedService<MyBackgroundService1>();
builder.Services.AddHostedService<MyBackgroundService2>();

3. 创建Blazor日志展示组件

创建OperationLogs.razor组件展示每个操作的日志:

@inject LogContainer LogContainer
@implements IDisposable

<h3>按操作分组日志</h3>

@foreach (var operation in LogContainer.OperationLogs)
{
    <div class="mb-4">
        <h4>@operation.Key 日志:</h4>
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>日志内容</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var log in operation.Value)
                {
                    <tr>
                        <td>@log</td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
}

@code {
    protected override void OnInitialized()
    {
        LogContainer.PropertyChanged += LogContainer_PropertyChanged;
    }

    private void LogContainer_PropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(LogContainer.OperationLogs))
        {
            InvokeAsync(StateHasChanged);
        }
    }

    public void Dispose()
    {
        LogContainer.PropertyChanged -= LogContainer_PropertyChanged;
    }
}

4. 在Blazor页面中使用组件

在Index.razor或其他页面引用组件:

@page "/"

<OperationLogs />

后台服务产生的日志会自动按操作分组,并实时更新到页面的对应表格中。

内容的提问来源于stack exchange,提问作者Rookian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 23:16:12