
1. .NET日志框架核心原理剖析日志系统作为应用程序的黑匣子其设计优劣直接影响故障排查效率。在.NET生态中主流日志框架如Serilog、NLog、log4net等虽然实现各异但核心架构都遵循相似的设计模式。1.1 日志框架的四大核心组件所有成熟日志框架都包含以下关键组件日志记录器(Logger)对外暴露的API接口负责接收日志消息。以Serilog为例其核心接口是ILogger通过静态类Log提供全局访问点。设计上采用分级日志(LogLevel)机制典型级别包括Verbose (最详细)DebugInformationWarningErrorFatal (最严重)日志事件(LogEvent)封装单条日志的完整上下文信息包含时间戳日志级别消息模板如User {UserId} logged in属性字典如UserId123异常对象可选输出目标(Sink)决定日志的最终去向。常见实现包括// Serilog的Sink配置示例 new LoggerConfiguration() .WriteTo.Console() .WriteTo.File(logs/log.txt) .WriteTo.Seq(http://localhost:5341)格式化器(Formatter)控制日志的呈现格式。如JSON格式{ Timestamp: 2023-06-15T09:30:45.123Z, Level: Information, Message: User 123 logged in, Properties: { UserId: 123, SourceContext: AuthService } }1.2 日志管道的执行流程当调用Log.Information(User {UserId} logged in, 123)时框架内部经历以下处理链日志过滤先检查配置的MinimumLevel丢弃低于阈值的日志消息模板解析将占位符{UserId}转换为结构化属性上下文丰富添加线程ID、机器名等环境信息Sink路由根据配置决定输出目标异步缓冲高级框架会使用生产者-消费者模式提升性能关键设计原则日志记录应当是无副作用的操作绝不能因为日志系统故障导致主业务逻辑失败。2. 手写简易日志框架实战现在我们从零实现一个名为MiniLogger的基础日志框架完整代码约200行包含核心功能。2.1 基础架构设计首先定义核心接口public enum LogLevel { Verbose, Debug, Info, Warn, Error, Fatal } public interface IMiniLogger { void Log(LogLevel level, string message, Exception ex null); bool IsEnabled(LogLevel level); } // 扩展方法提供便捷API public static class LoggerExtensions { public static void Debug(this IMiniLogger logger, string message) logger.Log(LogLevel.Debug, message); // 其他级别方法类似... }2.2 核心实现类public class MiniLogger : IMiniLogger { private readonly string _category; private readonly LogLevel _minLevel; private readonly ILogSink[] _sinks; public MiniLogger(string category, LogLevel minLevel, IEnumerableILogSink sinks) { _category category; _minLevel minLevel; _sinks sinks.ToArray(); } public void Log(LogLevel level, string message, Exception ex null) { if (!IsEnabled(level)) return; var entry new LogEntry( DateTime.UtcNow, level, _category, message, ex, new Dictionarystring, object() ); foreach (var sink in _sinks) { try { sink.Emit(entry); } catch { /* 确保不影响主流程 */ } } } public bool IsEnabled(LogLevel level) level _minLevel; }2.3 实现Console和File两种Sinkpublic interface ILogSink { void Emit(LogEntry entry); } public class ConsoleSink : ILogSink { public void Emit(LogEntry entry) { var color entry.Level switch { LogLevel.Error or LogLevel.Fatal ConsoleColor.Red, LogLevel.Warn ConsoleColor.Yellow, LogLevel.Info ConsoleColor.Green, _ ConsoleColor.Gray }; Console.ForegroundColor color; Console.WriteLine($[{entry.Timestamp:HH:mm:ss} {entry.Level}] {entry.Message}); Console.ResetColor(); } } public class FileSink : ILogSink, IDisposable { private readonly StreamWriter _writer; public FileSink(string path) { Directory.CreateDirectory(Path.GetDirectoryName(path)); _writer new StreamWriter(path, append: true); } public void Emit(LogEntry entry) { _writer.WriteLine(${entry.Timestamp:O}|{entry.Level}|{entry.Category}|{entry.Message}); if (entry.Exception ! null) _writer.WriteLine($EXCEPTION: {entry.Exception}); _writer.Flush(); } public void Dispose() _writer.Dispose(); }2.4 日志上下文实现为支持类似Serilog的上下文属性我们增加属性字典public class LogEntry { public DateTime Timestamp { get; } public LogLevel Level { get; } public string Category { get; } public string Message { get; } public Exception Exception { get; } public Dictionarystring, object Properties { get; } // 构造函数... } // 使用示例 logger.Log(LogLevel.Info, Order {OrderId} created, properties: new { OrderId 456 });3. 高级功能实现技巧3.1 异步日志处理同步日志可能阻塞主线程我们引入BlockingCollection实现生产者-消费者模式public class AsyncSinkProxy : ILogSink { private readonly BlockingCollectionLogEntry _queue new(); private readonly Thread _workerThread; private readonly ILogSink _innerSink; public AsyncSinkProxy(ILogSink innerSink) { _innerSink innerSink; _workerThread new Thread(ProcessQueue) { IsBackground true }; _workerThread.Start(); } private void ProcessQueue() { foreach (var entry in _queue.GetConsumingEnumerable()) _innerSink.Emit(entry); } public void Emit(LogEntry entry) _queue.Add(entry); public void Dispose() { _queue.CompleteAdding(); _workerThread.Join(); _innerSink.Dispose(); } }3.2 结构化日志优化改进消息模板解析public class LogEntry { // 新增方法 public static (string message, Dictionarystring, object props) ParseTemplate( string template, object[] args) { var props new Dictionarystring, object(); var regex new Regex(\{([^\}])\}); var matches regex.Matches(template); for (int i 0; i matches.Count; i) { var propName matches[i].Groups[1].Value; props[propName] args[i]; } return (regex.Replace(template, m ${{{m.Groups[1].Value}}}), props); } }3.3 依赖注入集成创建LoggerFactory适配Microsoft.Extensions.Loggingpublic class MiniLoggerProvider : ILoggerProvider { private readonly ConcurrentDictionarystring, IMiniLogger _loggers new(); private readonly LogLevel _minLevel; private readonly ILogSink[] _sinks; public MiniLoggerProvider(LogLevel minLevel, params ILogSink[] sinks) { _minLevel minLevel; _sinks sinks; } public Microsoft.Extensions.Logging.ILogger CreateLogger(string category) _loggers.GetOrAdd(category, name new MiniLogger(name, _minLevel, _sinks)); public void Dispose() { /* 清理资源 */ } }4. 性能优化关键指标日志系统需要特别关注以下性能指标吞吐量单线程下记录10万条日志的耗时同步文件写入~500ms异步缓冲后写入~50ms内存占用避免频繁分配对象使用对象池管理LogEntry实例预分配缓冲区线程安全多线程场景下的竞争条件使用ConcurrentDictionary管理Logger实例Sink内部做好同步控制实测对比Debug模式i7-11800H操作耗时(ms)内存分配(MB)直接Console.WriteLine12015基础MiniLogger18022带异步缓冲的MiniLogger4530Serilog(Console)65185. 生产环境实用技巧5.1 日志采样策略高流量场景下可采用采样日志public class SamplingSink : ILogSink { private readonly ILogSink _inner; private readonly Random _random new(); private readonly double _sampleRate; public SamplingSink(ILogSink inner, double sampleRate) { _inner inner; _sampleRate sampleRate; } public void Emit(LogEntry entry) { if (entry.Level LogLevel.Error || _random.NextDouble() _sampleRate) _inner.Emit(entry); } }5.2 敏感信息过滤实现数据脱敏public class DataMaskingSink : ILogSink { private readonly ILogSink _inner; private readonly string[] _sensitiveKeys; public DataMaskingSink(ILogSink inner, params string[] sensitiveKeys) { _inner inner; _sensitiveKeys sensitiveKeys; } public void Emit(LogEntry entry) { foreach (var key in _sensitiveKeys) { if (entry.Properties.TryGetValue(key, out var value)) entry.Properties[key] new string(*, value?.ToString()?.Length ?? 0); } _inner.Emit(entry); } }5.3 动态日志级别控制运行时调整日志级别public class DynamicLevelLogger : IMiniLogger { private readonly FuncLogLevel _getCurrentLevel; private readonly IMiniLogger _inner; public DynamicLevelLogger(IMiniLogger inner, FuncLogLevel getCurrentLevel) { _inner inner; _getCurrentLevel getCurrentLevel; } public bool IsEnabled(LogLevel level) level _getCurrentLevel(); public void Log(LogLevel level, string message, Exception ex null) { if (IsEnabled(level)) _inner.Log(level, message, ex); } }6. 与主流框架的对比分析6.1 功能对比表特性MiniLoggerSerilogNLoglog4net异步日志✓✓✓✗结构化日志基础✓✓✗动态日志级别✓✗✓✗依赖注入支持✓✓✓✓多Sink输出✓✓✓✓采样策略✓✓✗✗6.2 性能优化建议避免高频日志调用// 错误示范 - 字符串拼接在日志调用前执行 logger.Debug($Processing item {item.Id} - {item.Name}); // 正确做法 - 延迟消息构造 logger.Debug(Processing item {ItemId} - {ItemName}, item.Id, item.Name);合理配置日志级别生产环境建议Information及以上开发环境Debug及以上Sink选择原则控制台开发环境文件中小规模生产环境日志服务(Seq/ELK)大规模分布式系统7. 常见问题排查指南7.1 日志丢失问题现象部分日志未出现在目标输出中排查步骤检查MinimumLevel设置确认Sink配置是否正确对于异步日志检查队列是否已满验证是否有未处理的Sink异常7.2 性能问题现象应用程序响应变慢CPU占用高排查方法// 在MiniLogger中添加性能计数器 public class PerfCounterSink : ILogSink { private long _totalLogs; private Stopwatch _sw Stopwatch.StartNew(); public void Emit(LogEntry entry) { Interlocked.Increment(ref _totalLogs); if (_totalLogs % 1000 0) Console.WriteLine($Log throughput: {_totalLogs/_sw.Elapsed.TotalSeconds:0.##}/s); } }7.3 日志文件过大解决方案实现日志滚动策略public class RollingFileSink : ILogSink { private const int MaxFileSize 10 * 1024 * 1024; // 10MB private StreamWriter _currentWriter; public void Emit(LogEntry entry) { if (_currentWriter?.BaseStream.Length MaxFileSize) RotateFile(); _currentWriter.WriteLine(/* 日志内容 */); } private void RotateFile() { // 实现文件滚动逻辑 } }配置自动清理旧日志文件8. 扩展方向建议分布式追踪集成在日志中注入TraceId/SpanId指标监控统计各日志级别计数AI分析异常日志模式识别云原生适配支持Kubernetes环境日志收集实现一个生产可用的日志系统需要考虑的细节远比表面看起来复杂。我在实际开发中最大的教训是日志系统的稳定性必须高于业务系统本身因为当业务出现问题时日志往往是最后的排查手段。建议在自研框架前先充分评估是否可以直接使用Serilog等成熟方案仅在确有特殊需求时再考虑定制开发。