1. .NET日志框架核心原理剖析
日志系统是现代应用程序不可或缺的组成部分,它如同飞机的黑匣子,记录着程序运行时的关键信息。在.NET生态中,日志框架的设计哲学主要体现在以下几个核心维度:
1.1 日志分级机制
.NET日志系统采用分级设计,这种设计类似于医院的急诊分诊制度:
public enum LogLevel { Trace = 0, // 诊断级细节,如方法入参值 Debug = 1, // 调试阶段关注信息 Information = 2, // 常规运行信息 Warning = 3, // 异常但可恢复的情况 Error = 4, // 当前操作失败 Critical = 5, // 应用级灾难事件 None = 6 // 完全禁用 }日志分级带来三个关键优势:
- 性能优化:生产环境可只记录Warning及以上级别日志
- 问题定位:通过级别快速过滤关键事件
- 存储控制:不同级别日志可配置不同存储策略
1.2 日志提供器(Provider)架构
.NET日志系统采用Provider模式,这种设计类似于多端同步的云笔记应用:
核心接口关系:
ILoggerFactory:日志工厂,管理Provider集合ILoggerProvider:提供器基类,如Console、File等ILogger:实际执行日志记录
典型配置示例:
Host.CreateDefaultBuilder(args) .ConfigureLogging(builder => { builder.ClearProviders(); builder.AddConsole(); builder.AddFile("logs/app-{Date}.txt"); });1.3 日志过滤规则
过滤系统如同海关安检,决定哪些日志可以"通关":
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "System": "Error" }, "Console": { "LogLevel": { "MyApp.Controllers": "Debug" } } } }过滤规则执行优先级:
- 特定Provider的特定命名空间规则
- 特定Provider的默认规则
- 全局特定命名空间规则
- 全局默认规则
1.4 结构化日志的优势
传统文本日志如同手写笔记,而结构化日志则是数据库记录:
// 传统日志 logger.LogInformation($"User {userId} accessed {resource}"); // 结构化日志 logger.LogInformation("User {UserId} accessed {Resource}", userId, resource);结构化日志的核心价值:
- 查询能力:可按特定字段过滤(如UserId=123)
- 分析效率:日志系统可直接解析为JSON
- 上下文关联:通过关联字段追踪请求链路
经验之谈:在微服务架构中,建议所有日志消息至少包含TraceId、ServiceName等基础字段
2. 手写日志框架实战
理解原理后,我们动手实现一个轻量级日志框架。这个框架将包含核心功能,代码控制在200行以内。
2.1 基础架构设计
我们的迷你框架包含以下组件:
classDiagram class ILogger { <<interface>> +Log(level, message, exception) } class Logger { -_providers List<ILoggerProvider> +AddProvider() +Log() } class ConsoleLogger { +Log() } class FileLogger { +Log() } ILogger <|.. Logger ILogger <|.. ConsoleLogger ILogger <|.. FileLogger2.2 核心接口实现
首先定义关键接口:
public enum LogLevel { Debug, Info, Warn, Error, Fatal } public interface ILogProvider { void WriteLog(LogLevel level, string message, Exception ex = null); } public class Logger { private readonly List<ILogProvider> _providers = new(); public void AddProvider(ILogProvider provider) => _providers.Add(provider); public void Log(LogLevel level, string message, Exception ex = null) { foreach (var provider in _providers) { try { provider.WriteLog(level, message, ex); } catch { /* 防止日志循环 */ } } } }2.3 控制台日志实现
实现彩色控制台输出:
public class ConsoleLogger : ILogProvider { private static readonly Dictionary<LogLevel, ConsoleColor> _colors = new() { [LogLevel.Debug] = ConsoleColor.Gray, [LogLevel.Info] = ConsoleColor.White, [LogLevel.Warn] = ConsoleColor.Yellow, [LogLevel.Error] = ConsoleColor.Red, [LogLevel.Fatal] = ConsoleColor.DarkRed }; public void WriteLog(LogLevel level, string message, Exception ex) { var color = _colors.GetValueOrDefault(level, ConsoleColor.White); var prefix = $"[{DateTime.Now:HH:mm:ss} {level.ToString().ToUpper()}]"; Console.ForegroundColor = color; Console.Write(prefix); Console.ResetColor(); Console.WriteLine($" {message}"); if (ex != null) { Console.ForegroundColor = ConsoleColor.DarkRed; Console.WriteLine(ex.ToString()); Console.ResetColor(); } } }2.4 文件日志实现
带日志滚动的文件记录:
public class FileLogger : ILogProvider { private readonly string _basePath; private readonly long _maxFileSize; private readonly object _lock = new(); public FileLogger(string basePath = "logs", long maxFileSize = 10 * 1024 * 1024) { _basePath = basePath; _maxFileSize = maxFileSize; Directory.CreateDirectory(basePath); } public void WriteLog(LogLevel level, string message, Exception ex) { lock (_lock) { var date = DateTime.Today.ToString("yyyyMMdd"); var filePath = Path.Combine(_basePath, $"app-{date}.log"); // 检查文件大小 if (File.Exists(filePath) && new FileInfo(filePath).Length > _maxFileSize) { var ts = DateTime.Now.ToString("HHmmss"); filePath = Path.Combine(_basePath, $"app-{date}-{ts}.log"); } var content = $"[{DateTime.Now:HH:mm:ss} {level}] {message}\n"; if (ex != null) { content += $"{ex}\n\n"; } File.AppendAllText(filePath, content); } } }2.5 使用示例
组合各种提供器:
var logger = new Logger(); logger.AddProvider(new ConsoleLogger()); logger.AddProvider(new FileLogger()); logger.Log(LogLevel.Info, "系统启动完成"); try { throw new InvalidOperationException("模拟异常"); } catch (Exception ex) { logger.Log(LogLevel.Error, "处理请求时出错", ex); }3. 高级功能扩展
基础框架完成后,我们可以添加一些增强功能。
3.1 日志过滤支持
public class FilteredLogger : ILogProvider { private readonly ILogProvider _inner; private readonly Func<LogLevel, bool> _filter; public FilteredLogger(ILogProvider inner, Func<LogLevel, bool> filter) { _inner = inner; _filter = filter; } public void WriteLog(LogLevel level, string message, Exception ex) { if (_filter(level)) { _inner.WriteLog(level, message, ex); } } } // 使用示例: logger.AddProvider( new FilteredLogger( new ConsoleLogger(), level => level >= LogLevel.Warn ) );3.2 异步日志处理
避免阻塞主线程:
public class AsyncLogger : ILogProvider { private readonly ILogProvider _inner; private readonly BlockingCollection<LogItem> _queue = new(); private readonly Task _worker; public AsyncLogger(ILogProvider inner) { _inner = inner; _worker = Task.Run(ProcessQueue); } private void ProcessQueue() { foreach (var item in _queue.GetConsumingEnumerable()) { _inner.WriteLog(item.Level, item.Message, item.Exception); } } public void WriteLog(LogLevel level, string message, Exception ex) { _queue.Add(new LogItem(level, message, ex)); } private record LogItem(LogLevel Level, string Message, Exception Exception); }3.3 结构化日志支持
public static class LoggerExtensions { public static void LogInfo(this Logger logger, string template, params object[] args) { var message = FormatTemplate(template, args); logger.Log(LogLevel.Info, message); } private static string FormatTemplate(string template, object[] args) { // 简单实现:实际项目可使用更复杂的模板引擎 for (int i = 0; i < args.Length; i++) { template = template.Replace($"{{{i}}}", args[i]?.ToString()); } return template; } } // 使用示例: logger.LogInfo("用户 {0} 从 {1} 登录", userId, ipAddress);4. 性能优化技巧
日志系统性能直接影响应用整体表现,以下是关键优化点:
4.1 日志级别预检查
public class OptimizedLogger { private readonly LogLevel _minLevel; public bool IsEnabled(LogLevel level) => level >= _minLevel; public void Log(LogLevel level, string message, Exception ex = null) { if (!IsEnabled(level)) return; // 实际记录逻辑... } }4.2 避免字符串拼接
低效写法:
logger.Log(LogLevel.Debug, "开始处理订单 " + orderId);高效写法:
if (logger.IsEnabled(LogLevel.Debug)) { logger.Log(LogLevel.Debug, $"开始处理订单 {orderId}"); }4.3 缓冲写入策略
文件日志的缓冲优化:
public class BufferedFileLogger : IDisposable { private readonly StreamWriter _writer; private readonly Timer _flushTimer; public BufferedFileLogger(string path) { _writer = new StreamWriter(path, true) { AutoFlush = false }; _flushTimer = new Timer(_ => _writer.Flush(), null, 1000, 1000); } public void WriteLog(string message) { _writer.WriteLine(message); } public void Dispose() { _flushTimer?.Dispose(); _writer?.Flush(); _writer?.Dispose(); } }5. 常见问题排查
5.1 日志丢失问题
可能原因及解决方案:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 部分日志消失 | 异步日志未正确关闭 | 实现IDisposable并确保Flush |
| 文件日志不全 | 进程崩溃未刷新缓冲区 | 减小缓冲大小或定时Flush |
| 控制台日志缺失 | 日志级别设置过高 | 检查过滤规则 |
5.2 性能问题排查
日志性能问题诊断步骤:
- 使用Stopwatch测量日志调用耗时
- 检查是否有同步IO操作
- 分析日志消息构建成本(特别是字符串操作)
- 检查日志框架锁竞争情况
5.3 日志配置最佳实践
推荐配置方案:
{ "Logging": { "MinimumLevel": { "Default": "Information", "Microsoft": "Warning" }, "WriteTo": [ { "Name": "Console", "Args": { "outputTemplate": "{Timestamp:HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}" } }, { "Name": "File", "Args": { "path": "logs/app-.log", "rollingInterval": "Day", "retainedFileCountLimit": 7 } } ] } }在实现自己的日志框架时,有几个关键点需要特别注意:首先是线程安全性,特别是在多Provider场景下;其次是异常处理,要避免日志系统自身异常影响主业务流程;最后是资源释放,确保文件句柄等资源及时释放。