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实例,不会重复创建对象,性能开销可忽略。基于此解答三个问题:
是否应始终创建新的ILogger?
不需要,但直接调用CreateLogger是安全的。Factory自动缓存同名Logger,重复调用无额外开销,代码更简洁。是否应在Invoke方法内创建Dictionary存储已创建的Logger?
完全没必要。单次请求内多次创建同名Logger时,Factory会返回同一个实例,手动维护Dictionary只会增加代码复杂度,无任何收益。是否应在类级别创建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
相关产品推荐
相关产品推荐

