模块化单体架构中如何在日志输出中添加模块名称
解决方案:给模块化日志添加模块标识前缀
推荐方案:包装ILoggerFactory修改日志类别
通过包装主机的ILoggerFactory,为每个模块生成专属的LoggerFactory,自动给日志类别(Category)添加模块前缀,这样日志输出的类别部分会直接显示[模块名] 类名,完全匹配需求。
1. 实现ModuleLoggerFactory包装类
public class ModuleLoggerFactory : ILoggerFactory { private readonly ILoggerFactory _innerFactory; private readonly string _moduleName; public ModuleLoggerFactory(ILoggerFactory innerFactory, string moduleName) { _innerFactory = innerFactory; _moduleName = moduleName; } public ILogger CreateLogger(string categoryName) { // 给原始类别名添加模块前缀 var decoratedCategory = $"[{_moduleName}] {categoryName}"; return _innerFactory.CreateLogger(decoratedCategory); } public void AddProvider(ILoggerProvider provider) => _innerFactory.AddProvider(provider); public void Dispose() => _innerFactory.Dispose(); }
2. 修改模块Startup的DI注册
以ModuleA为例,替换原有的ILoggerFactory注册为包装后的实例:
class ModuleAStartUp : IHostedService { readonly ILoggerFactory _hostLoggerFactory; public ModuleAStartup(ILoggerFactory loggerFactory) { _hostLoggerFactory = loggerFactory; } public async Task StartAsync(CancellationToken cancellationToken) { ContainerBuilder cb = new(); // 注册带模块标识的LoggerFactory var moduleLoggerFactory = new ModuleLoggerFactory(_hostLoggerFactory, "ModuleA"); cb.RegisterInstance(moduleLoggerFactory).As<ILoggerFactory>(); // 保持原有Logger<>注册不变,它会自动使用上面的Factory创建日志实例 cb.RegisterGeneric(typeof(Logger<>)) .As(typeof(ILogger<>)) .InstancePerDependency(); // 注册其他组件 StaticModule.Container = cb.Build(); } }
3. 效果验证
模块内任意类(包括共享框架类)注入ILogger<T>后,日志输出的类别会自动带上模块前缀:
HH:mm:ss:fff info: [ModuleA] CommandExecutor My Log Message
备选方案:包装ILogger修改日志消息
如果需要更灵活地控制日志内容,可以直接包装ILogger<T>,给每条日志消息添加模块前缀。但这种方式会把前缀放到消息内容里,而非类别位置,适合不需要修改类别的场景:
1. 实现ModuleLogger包装类
public class ModuleLogger<T> : ILogger<T> { private readonly ILogger<T> _innerLogger; private readonly string _moduleName; public ModuleLogger(ILogger<T> innerLogger, string moduleName) { _innerLogger = innerLogger; _moduleName = moduleName; } public IDisposable BeginScope<TState>(TState state) => _innerLogger.BeginScope(state); public bool IsEnabled(LogLevel logLevel) => _innerLogger.IsEnabled(logLevel); public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { // 在日志消息前添加模块前缀 var formattedMessage = formatter(state, exception); var modulePrefixedMessage = $"[{_moduleName}] {formattedMessage}"; _innerLogger.Log(logLevel, eventId, state, exception, (s, e) => modulePrefixedMessage); } }
2. 修改模块Startup的DI注册
public async Task StartAsync(CancellationToken cancellationToken) { ContainerBuilder cb = new(); // 先注册原始Logger<>作为内部依赖 cb.RegisterGeneric(typeof(Logger<>)) .Named("OriginalLogger", typeof(ILogger<>)) .InstancePerDependency(); // 注册包装后的ModuleLogger<>,注入原始Logger和模块名 cb.RegisterGeneric(typeof(ModuleLogger<>)) .As(typeof(ILogger<>)) .InstancePerDependency() .WithParameter((pi, ctx) => pi.ParameterType.IsGenericType && pi.ParameterType.GetGenericTypeDefinition() == typeof(ILogger<>), (pi, ctx) => ctx.ResolveNamed("OriginalLogger", pi.ParameterType)) .WithParameter("moduleName", "ModuleA"); // 注册其他组件 StaticModule.Container = cb.Build(); }
内容的提问来源于stack exchange,提问作者Neil W
相关产品推荐
相关产品推荐

