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

模块化单体架构中如何在日志输出中添加模块名称

解决方案:给模块化日志添加模块标识前缀

推荐方案:包装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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 08:10:54