如何通过依赖注入在自定义ILogger中使用ISupportExternalScope与中间件
自定义日志组件集成外部作用域的DI实现方案
1. 获取IExternalScopeProvider的正确方式
不需要手动注册IExternalScopeProvider,.NET日志框架已默认将LoggerExternalScopeProvider作为单例服务注册。你可以直接在中间件或其他需要的类中通过构造函数注入该服务。
示例中间件代码:
public class LogContextMiddleware { private readonly RequestDelegate _next; private readonly IExternalScopeProvider _scopeProvider; public LogContextMiddleware(RequestDelegate next, IExternalScopeProvider scopeProvider) { _next = next; _scopeProvider = scopeProvider; } public async Task InvokeAsync(HttpContext context) { // 从HttpContext提取需注入日志的字段,如TraceId、请求路径等 var scopeData = new Dictionary<string, object> { ["TraceId"] = context.TraceIdentifier, ["RequestPath"] = context.Request.Path }; // 推入作用域,using块结束后自动释放 using (_scopeProvider.Push(scopeData)) { await _next(context); } } }
2. 让自定义LoggerProvider支持外部作用域
你的MyLoggerProvider需要实现ISupportExternalScope接口,框架会自动将全局的IExternalScopeProvider实例注入进来,无需手动实例化。
示例代码:
public class MyLoggerProvider : ILoggerProvider, ISupportExternalScope { private IExternalScopeProvider _scopeProvider; // 实现ISupportExternalScope的SetScopeProvider方法,框架会自动调用此方法注入作用域提供者 public void SetScopeProvider(IExternalScopeProvider scopeProvider) { _scopeProvider = scopeProvider; } public ILogger CreateLogger(string categoryName) { // 将作用域提供者传递给Logger实例,用于后续读取作用域数据 return new MyLogger(categoryName, _scopeProvider); } public void Dispose() { // 按需清理资源 } } // 自定义Logger接收作用域提供者,用于收集日志作用域数据 public class MyLogger : ILogger { private readonly string _categoryName; private readonly IExternalScopeProvider _scopeProvider; public MyLogger(string categoryName, IExternalScopeProvider scopeProvider) { _categoryName = categoryName; _scopeProvider = scopeProvider; } public IDisposable BeginScope<TState>(TState state) { // 委托给外部作用域提供者处理作用域创建 return _scopeProvider?.Push(state) ?? NullScope.Instance; } public bool IsEnabled(LogLevel logLevel) { // 按需实现日志级别过滤逻辑 return true; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { // 收集所有作用域中的自定义字段 var scopeData = new Dictionary<string, object>(); _scopeProvider?.ForEachScope((scope, target) => { if (scope is Dictionary<string, object> data) { foreach (var kvp in data) { target[kvp.Key] = kvp.Value; } } }, scopeData); // 将日志内容与作用域数据序列化为JSON输出 var logEntry = new { Category = _categoryName, LogLevel = logLevel.ToString(), Message = formatter(state, exception), Exception = exception?.ToString(), Scope = scopeData }; Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(logEntry)); } }
3. 注册自定义组件到DI容器
在Program.cs中注册日志提供者和中间件即可,框架会自动识别ISupportExternalScope并完成注入:
var builder = WebApplication.CreateBuilder(args); // 清空默认日志提供者,添加自定义实现 builder.Logging.ClearProviders(); builder.Logging.AddProvider(new MyLoggerProvider()); // 注册中间件 builder.Services.AddTransient<LogContextMiddleware>(); var app = builder.Build(); // 启用日志上下文中间件 app.UseMiddleware<LogContextMiddleware>(); app.Run();
关键说明
IExternalScopeProvider默认是单例注册,无需改为Scoped,其Push方法线程安全,请求级别的作用域会通过using块自动隔离。- 实现
ISupportExternalScope后,框架初始化日志提供者时会自动调用SetScopeProvider,无需手动处理注入逻辑。 - 中间件中推入的作用域数据,会在整个请求生命周期内的所有日志中被捕获,直到
using块结束自动释放。
内容的提问来源于stack exchange,提问作者JALLRED
相关产品推荐
相关产品推荐

