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

Web应用中间件中ILogger创建与复用的最佳实践咨询

中间件中ILogger创建的最佳实践

我为Web应用编写了一个中间件,负责处理特殊请求,并将来自多源的多条信息写入日志,初始实现代码如下:

public class MyMiddleware
{
  public async Task Invoke(HttpContext context)
  {
    var dataToBeLogged = await context.Request.ReadFromJsonAsync<LogEntry[]>();
    foreach(var l in dataToBeLogged)
    {
      var loggerName = string.IsNullOrEmpty(l.LoggerName) ? "Default" : l.LoggerName;
      var logger = _loggerFactory.CreateLogger($"{env.ApplicationName}.Client.{loggerName}");
      logger.Log(l.Level, l.Exception, l.Message);
    }
  }
}

LoggerName可能各不相同,但也可能存在已创建过同名Logger的情况,现咨询Logger创建的最佳实践:

  • 是否应始终创建新的ILogger?
  • 是否应在Invoke方法内创建Dictionary存储已创建的Logger(示例1)?
  • 由于中间件实例在运行时不会改变,是否应在类级别创建Dictionary(示例2)?

示例1:Invoke方法内存储Logger

public class MyMiddleware
{
  public async Task Invoke(HttpContext context)
  {
    var dataToBeLogged = await context.Request.ReadFromJsonAsync<LogEntry[]>();
    var dict = new Dictionary<string, ILogger>();
    foreach(var l in dataToBeLogged)
    {
      var loggerName = string.IsNullOrEmpty(l.LoggerName) ? "Default" : l.LoggerName;
      if (!dict.ContainsKey(loggerName))
        dict.Add(loggerName, _loggerFactory.CreateLogger($"{env.ApplicationName}.Client.{loggerName}"));
      var logger = dict[loggerName];
      logger.Log(l.Level, l.Exception, l.Message);
    }
  }
}

示例2:类级别存储Logger

public class MyMiddleware
{
  private readonly Dictionary<string, ILogger> dict = new Dictionary<string, ILogger>();
  public async Task Invoke(HttpContext context)
  {
    var dataToBeLogged = await context.Request.ReadFromJsonAsync<LogEntry[]>();
    
    foreach(var l in dataToBeLogged)
    {
      var loggerName = string.IsNullOrEmpty(l.LoggerName) ? "Default" : l.LoggerName;
      if (!dict.ContainsKey(loggerName))
        dict.Add(loggerName, _loggerFactory.CreateLogger($"{env.ApplicationName}.Client.{loggerName}"));
      var logger = dict[loggerName];
      logger.Log(l.Level, l.Exception, l.Message);
    }
  }
}

LogEntry类定义

public class LogEntry
{
  public string LoggerName { get;set; }
  public int Level { get;set; }
  public Exception Exception { get;set; }
  public string Message { get;set; }
}

示例请求数据

[
 { "loggerName": "LoggerOne", "level": 2, "exception": null, "message": "This is an information" },
 { "loggerName": "LoggerTwo", "level": 3, "exception": null, "message": "This is a warning" },
 { "loggerName": "LoggerOne", "level": 4, "exception": { "message": "A Exception message", "stackTrace": "..." }, "message": "This is an error" }
]

预期日志输出

MyProject.Client.LoggerOne: Information: This is an information
MyProject.Client.LoggerTwo: Warning: This is a warning
MyProject.Client.LoggerOne: Error: This is an error
MyProject.Client.LoggerOne: Error: A Exception message
   at ...
   at ...
   at ...

最佳实践解答

首先明确核心特性:.NET的ILoggerFactory内部已实现Logger实例缓存,调用CreateLogger(string name)时,相同名称会返回同一个Logger实例,不会重复创建对象,性能开销可忽略。基于此解答三个问题:

  1. 是否应始终创建新的ILogger?
    不需要,但直接调用CreateLogger是安全的。Factory自动缓存同名Logger,重复调用无额外开销,代码更简洁。

  2. 是否应在Invoke方法内创建Dictionary存储已创建的Logger?
    完全没必要。单次请求内多次创建同名Logger时,Factory会返回同一个实例,手动维护Dictionary只会增加代码复杂度,无任何收益。

  3. 是否应在类级别创建Dictionary?
    不应该。一是Factory的全局缓存比类级缓存更合理;二是中间件为单例模式,多请求并发时无锁访问Dictionary会引发线程安全问题(如重复添加同一LoggerName时抛出异常),引入潜在bug。

优化后的代码示例

预先计算日志名称前缀减少重复拼接,同时将int类型Level转换为LogLevel枚举符合API规范:

public class MyMiddleware
{
    private readonly ILoggerFactory _loggerFactory;
    private readonly string _loggerPrefix;

    public MyMiddleware(ILoggerFactory loggerFactory, IWebHostEnvironment env)
    {
        _loggerFactory = loggerFactory;
        _loggerPrefix = $"{env.ApplicationName}.Client.";
    }

    public async Task Invoke(HttpContext context)
    {
        var dataToBeLogged = await context.Request.ReadFromJsonAsync<LogEntry[]>();
        foreach(var l in dataToBeLogged)
        {
            var loggerName = string.IsNullOrEmpty(l.LoggerName) ? "Default" : l.LoggerName;
            var logger = _loggerFactory.CreateLogger($"{_loggerPrefix}{loggerName}");
            logger.Log((LogLevel)l.Level, l.Exception, l.Message);
        }
    }
}

内容的提问来源于stack exchange,提问作者NCC-2909-M

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 14:10:35