Blazor Server中能否实现将日志输出到浏览器控制台的ILogger?
在Blazor Server中实现输出到浏览器控制台的ILogger
当然可以实现,但你当前的代码存在两个核心问题:
- 在
LoggerProvider中调用BuildServiceProvider()会触发日志系统的初始化,进而导致无限递归循环 - 直接在Singleton生命周期的
LoggerProvider中获取Scoped的IJSRuntime不符合DI的生命周期规则
正确的实现方案
我们可以通过IServiceScopeFactory延迟创建作用域来获取IJSRuntime,避免循环依赖和生命周期冲突。
1. 实现BrowserLogger
public class BrowserLogger : ILogger { private readonly IServiceScopeFactory _scopeFactory; private readonly string _categoryName; public BrowserLogger(IServiceScopeFactory scopeFactory, string categoryName) { _scopeFactory = scopeFactory; _categoryName = categoryName; } public IDisposable BeginScope<TState>(TState state) => default!; public bool IsEnabled(LogLevel logLevel) => true; // 可根据需求调整过滤逻辑 public async void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) { var message = formatter(state, exception); using var scope = _scopeFactory.CreateScope(); var jsRuntime = scope.ServiceProvider.GetRequiredService<IJSRuntime>(); // 调用JS方法输出到控制台,可根据日志级别调用不同的console方法 await jsRuntime.InvokeVoidAsync("console.log", $"[{logLevel}] {_categoryName}: {message}"); if (exception != null) { await jsRuntime.InvokeVoidAsync("console.error", exception.ToString()); } } }
2. 实现BrowserLoggerProvider
public class BrowserLoggerProvider : ILoggerProvider { private readonly IServiceScopeFactory _scopeFactory; private readonly Dictionary<string, BrowserLogger> _loggers = new(); public BrowserLoggerProvider(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public ILogger CreateLogger(string categoryName) { lock (_loggers) { if (!_loggers.TryGetValue(categoryName, out var logger)) { logger = new BrowserLogger(_scopeFactory, categoryName); _loggers[categoryName] = logger; } return logger; } } public void Dispose() { _loggers.Clear(); } }
3. 注册日志提供程序
在Program.cs中,我们需要先确保IServiceScopeFactory已注册(Blazor Server默认已注册),然后添加自定义日志提供程序:
builder.Services.AddLogging(builder => { builder.AddProvider(new BrowserLoggerProvider(builder.Services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>())); });
关键注意事项
- 生命周期匹配:通过
IServiceScopeFactory创建临时作用域,确保每次日志输出都能获取到当前请求/连接对应的IJSRuntime实例 - 后台线程限制:如果在后台线程(非组件上下文、非SignalR连接作用域)中输出日志,
IJSRuntime可能无法正常工作,此时可以添加判断逻辑跳过这类日志 - 日志级别过滤:可以在
IsEnabled方法中添加日志级别过滤,避免不必要的JS调用
内容的提问来源于stack exchange,提问作者Breeky
相关产品推荐
相关产品推荐

