从零封装一个实用的C#日志助手:基于NLog的动态分类与异常捕获实战

张开发
2026/4/19 16:41:02 15 分钟阅读

分享文章

从零封装一个实用的C#日志助手:基于NLog的动态分类与异常捕获实战
从零封装一个实用的C#日志助手基于NLog的动态分类与异常捕获实战日志记录是软件开发中不可或缺的一环它不仅是排查问题的第一手资料更是系统运行状态的晴雨表。对于C#开发者而言NLog以其高性能和灵活性成为众多项目的首选日志框架。但直接使用NLog的基础API往往会导致代码中散落着重复的日志调用缺乏统一的异常处理机制也难以实现日志的精细化分类管理。本文将带你从工程化角度出发设计一个既健壮又易用的日志工具类。不同于基础的配置教程我们会深入探讨如何通过工厂模式实现动态日志分类、如何安全地处理日志过程中的异常、以及如何自动捕获丰富的上下文信息。最终呈现的解决方案可以直接应用于Web API或后台服务项目显著提升日志管理的专业度。1. 日志分类的工厂模式实现在复杂系统中不同类型的日志往往需要分开存储和分析。比如系统操作日志、业务流水日志和性能指标日志就应该有各自独立的存储路径和格式。我们可以通过工厂模式来优雅地实现这一需求。首先定义日志分类的枚举public enum LogCategory { System, // 系统运行日志 Business, // 业务操作日志 Performance // 性能指标日志 }接着创建日志工厂类负责根据分类返回对应的Logger实例public static class LoggerFactory { private static readonly ConcurrentDictionaryLogCategory, ILogger _loggers new ConcurrentDictionaryLogCategory, ILogger(); public static ILogger GetLogger(LogCategory category) { return _loggers.GetOrAdd(category, c LogManager.GetLogger(c.ToString())); } }这种实现方式有三大优势线程安全使用ConcurrentDictionary避免多线程竞争性能高效Logger实例只创建一次并缓存扩展方便新增日志类型只需扩展LogCategory枚举对应的NLog配置文件需要为每个分类设置独立的targettargets target nameSystem xsi:typeFile fileName${basedir}/logs/${shortdate}.system.log layout${longdate}|${level}|${message} / target nameBusiness xsi:typeFile fileName${basedir}/logs/${shortdate}.business.log layout${longdate}|${level}|${message} / /targets2. 异常安全的日志记录实现日志记录本身也可能抛出异常 - 比如磁盘空间不足或权限问题。我们需要确保日志操作不会影响主业务流程同时又能捕获并记录这些日志的日志。改进后的SafeLogger类实现了双重保护机制public static class SafeLogger { private static readonly ILogger _fallbackLogger LogManager.GetLogger(EmergencyLogger); public static void Log(LogLevel level, string message, Exception ex null) { try { var logger LoggerFactory.GetLogger(LogCategory.System); logger.Log(level, ex, message); } catch (Exception logEx) { // 使用备用日志记录器记录日志失败的情况 _fallbackLogger.Error(logEx, 主日志记录失败); // 可选将关键日志信息写入Windows事件日志 EventLog.WriteEntry(Application, $日志记录失败: {message}. 错误: {logEx.Message}, EventLogEntryType.Error); } } }关键设计要点分离关注点主日志和应急日志使用不同的Logger实例多级回退当文件日志失败时回退到Windows事件日志异常隔离所有异常被捕获并处理不会向上传播3. 上下文信息的自动捕获丰富的上下文信息能极大提升日志的排查效率。NLog的LayoutRenderer可以自动捕获线程、方法调用栈等信息。首先在NLog.config中配置增强的layouttarget nameEnhancedFile xsi:typeFile fileName${basedir}/logs/${shortdate}.enhanced.log layout${longdate}|${level}|${threadid}|${callsite}|${message} /然后创建自动记录上下文的扩展方法public static class LoggerExtensions { public static void LogWithContext( this ILogger logger, LogLevel level, string message, [CallerMemberName] string memberName , [CallerFilePath] string sourceFilePath , [CallerLineNumber] int sourceLineNumber 0) { var enhancedMessage $[{memberName}{Path.GetFileName(sourceFilePath)}:{sourceLineNumber}] {message}; logger.Log(level, enhancedMessage); } }这样使用时就能自动记录调用位置logger.LogWithContext(LogLevel.Info, 用户登录成功); // 输出示例2023-08-20 14:00:00|INFO|1|LoginAuthService.cs:32|用户登录成功4. 在Web API项目中的集成实践将上述组件整合到ASP.NET Core项目中我们可以创建一个更完善的解决方案。首先注册日志服务public static class ServiceCollectionExtensions { public static IServiceCollection AddEnhancedLogging(this IServiceCollection services) { services.AddSingletonILoggerProvider, NLogLoggerProvider(); services.AddSingleton(typeof(ILogger), typeof(Logger)); return services; } }然后创建中间件自动记录请求日志public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public RequestLoggingMiddleware(RequestDelegate next, ILoggerRequestLoggingMiddleware logger) { _next next; _logger logger; } public async Task Invoke(HttpContext context) { var stopwatch Stopwatch.StartNew(); try { await _next(context); stopwatch.Stop(); _logger.LogInformation( $请求 {context.Request.Method} {context.Request.Path} 完成 - $状态码: {context.Response.StatusCode} - $耗时: {stopwatch.ElapsedMilliseconds}ms); } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, $请求 {context.Request.Method} {context.Request.Path} 失败 - $耗时: {stopwatch.ElapsedMilliseconds}ms); throw; } } }最后在Program.cs中启用app.UseMiddlewareRequestLoggingMiddleware();5. 高级技巧与性能优化对于高性能场景我们可以进一步优化日志实现异步批量写入target nameasyncFile xsi:typeAsyncWrapper target xsi:typeFile fileName${basedir}/logs/async.log / /target日志过滤规则rules logger name* minlevelInfo writeToasyncFile / logger nameMicrosoft.* maxlevelInfo finaltrue / /rules结构化日志支持logger.Info(订单处理 {Order}, new { Id 123, Amount 99.9 });通过这样的封装我们获得了一个具备以下特性的日志组件分类明确的多目标日志异常安全的记录机制丰富的自动上下文易于集成的API设计高性能的底层实现在实际项目中这种设计显著提升了日志系统的可靠性和可维护性。特别是在分布式系统中良好的日志实践能为问题排查节省大量时间。

更多文章