1. .NET日志框架核心原理剖析
日志系统是任何应用程序不可或缺的组成部分,它如同程序的"黑匣子",记录着运行时的重要信息。在.NET生态中,微软提供了强大的日志抽象框架,让我们先深入理解其架构设计。
1.1 日志系统分层架构
.NET日志系统采用典型的三层架构设计:
- 日志记录层(Logger):开发者直接调用的API接口
- 日志过滤层(Filter):根据配置决定哪些日志需要记录
- 日志输出层(Provider):将日志写入不同目标(控制台、文件等)
这种分层设计的关键在于ILogger接口,它是整个系统的核心契约。当我们在代码中调用LogInformation()等方法时,实际上是通过这个接口将日志消息传递到日志管道中。
1.2 日志记录流程详解
一个典型的日志记录过程会经历以下阶段:
- 日志类别确定:每个日志记录器都有关联的类别,通常使用
ILogger<T>时,T的类型全名就是类别 - 日志级别检查:系统首先检查当前日志级别是否满足配置要求
- 作用域处理:如果启用了作用域,会将作用域信息附加到日志消息
- 格式化和输出:最终将日志消息按照指定格式输出到各个Provider
重要提示:日志级别检查发生在日志记录的最早期,这可以避免不必要的字符串格式化和对象分配,对性能影响极小。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 手写日志框架实战
理解了原理后,我们尝试从零构建一个简易日志框架。这个练习将帮助我们深入理解.NET日志框架的内部机制。
2.1 基础接口定义
首先定义核心接口,这与微软的设计非常相似:
csharp复制public interface ILogger
{
void Log(LogLevel level, string message, Exception exception = null);
bool IsEnabled(LogLevel level);
IDisposable BeginScope<TState>(TState state);
}
public interface ILoggerProvider : IDisposable
{
ILogger CreateLogger(string categoryName);
}
public enum LogLevel
{
Trace,
Debug,
Information,
Warning,
Error,
Critical,
None
}
2.2 实现控制台日志Provider
创建一个将日志输出到控制台的简单实现:
csharp复制public class ConsoleLogger : ILogger
{
private readonly string _categoryName;
public ConsoleLogger(string categoryName)
{
_categoryName = categoryName;
}
public IDisposable BeginScope<TState>(TState state) => null;
public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
public void Log(LogLevel logLevel, string message, Exception exception = null)
{
if (!IsEnabled(logLevel)) return;
var color = logLevel switch
{
LogLevel.Critical => ConsoleColor.Red,
LogLevel.Error => ConsoleColor.DarkRed,
LogLevel.Warning => ConsoleColor.Yellow,
LogLevel.Information => ConsoleColor.White,
_ => ConsoleColor.Gray
};
Console.ForegroundColor = color;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss} {logLevel}] {_categoryName}: {message}");
if (exception != null)
Console.WriteLine(exception);
Console.ResetColor();
}
}
2.3 日志工厂实现
创建一个简单的LoggerFactory来管理日志提供者:
csharp复制public class LoggerFactory : ILoggerFactory
{
private readonly List<ILoggerProvider> _providers = new();
public void AddProvider(ILoggerProvider provider)
{
_providers.Add(provider);
}
public ILogger CreateLogger(string categoryName)
{
var loggers = _providers.Select(p => p.CreateLogger(categoryName)).ToList();
return new CompositeLogger(loggers);
}
public void Dispose()
{
foreach (var provider in _providers)
{
provider.Dispose();
}
}
private class CompositeLogger : ILogger
{
private readonly IEnumerable<ILogger> _loggers;
public CompositeLogger(IEnumerable<ILogger> loggers)
{
_loggers = loggers;
}
public IDisposable BeginScope<TState>(TState state)
{
return new CompositeDisposable(_loggers.Select(l => l.BeginScope(state)));
}
public bool IsEnabled(LogLevel logLevel)
{
return _loggers.Any(l => l.IsEnabled(logLevel));
}
public void Log(LogLevel logLevel, string message, Exception exception = null)
{
foreach (var logger in _loggers.Where(l => l.IsEnabled(logLevel)))
{
logger.Log(logLevel, message, exception);
}
}
}
private class CompositeDisposable : IDisposable
{
private readonly IEnumerable<IDisposable> _disposables;
public CompositeDisposable(IEnumerable<IDisposable> disposables)
{
_disposables = disposables;
}
public void Dispose()
{
foreach (var disposable in _disposables)
{
disposable.Dispose();
}
}
}
}
3. 高级日志功能实现
基础框架完成后,我们需要实现一些高级功能,使日志系统更加实用。
3.1 结构化日志支持
现代日志系统都支持结构化日志,让我们实现这个功能:
csharp复制public static class LoggerExtensions
{
public static void LogInformation(this ILogger logger, string message, params object[] args)
{
logger.Log(LogLevel.Information, string.Format(message, args));
}
public static void LogError(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Error, string.Format(message, args), exception);
}
// 其他级别的方法类似...
}
3.2 日志过滤配置
实现基于配置的日志过滤:
csharp复制public class FilterLogger : ILogger
{
private readonly ILogger _innerLogger;
private readonly Func<string, LogLevel, bool> _filter;
public FilterLogger(ILogger innerLogger, Func<string, LogLevel, bool> filter)
{
_innerLogger = innerLogger;
_filter = filter;
}
public IDisposable BeginScope<TState>(TState state) => _innerLogger.BeginScope(state);
public bool IsEnabled(LogLevel logLevel) => _filter(_innerLogger.GetType().FullName, logLevel);
public void Log(LogLevel logLevel, string message, Exception exception = null)
{
if (IsEnabled(logLevel))
{
_innerLogger.Log(logLevel, message, exception);
}
}
}
3.3 日志作用域实现
作用域可以帮助我们关联相关日志:
csharp复制public class LoggerScope : IDisposable
{
private readonly Action _onDispose;
public LoggerScope(Action onDispose)
{
_onDispose = onDispose;
}
public void Dispose()
{
_onDispose?.Invoke();
}
}
// 在ConsoleLogger中添加作用域支持
private readonly Stack<object> _scopes = new Stack<object>();
public IDisposable BeginScope<TState>(TState state)
{
_scopes.Push(state);
return new LoggerScope(() => _scopes.Pop());
}
public void Log(LogLevel logLevel, string message, Exception exception = null)
{
if (!IsEnabled(logLevel)) return;
var scopeInfo = _scopes.Count > 0 ? $" ({string.Join(" => ", _scopes.Reverse())})" : "";
Console.WriteLine($"[{DateTime.Now:HH:mm:ss} {logLevel}] {_categoryName}{scopeInfo}: {message}");
// 其他代码...
}
4. 性能优化技巧
日志系统性能至关重要,特别是在高并发场景下。以下是几个关键优化点:
4.1 避免不必要的日志计算
csharp复制// 不好的写法 - 即使日志被过滤,字符串拼接仍会执行
logger.LogDebug($"Processing item {item.Id} with data {ComplexToString(item.Data)}");
// 好的写法 - 先检查日志级别
if (logger.IsEnabled(LogLevel.Debug))
{
logger.LogDebug($"Processing item {item.Id} with data {ComplexToString(item.Data)}");
}
4.2 使用高性能字符串构建
csharp复制// 使用StringBuilder代替字符串拼接
var sb = new StringBuilder();
sb.Append("Processing item ");
sb.Append(item.Id);
sb.Append(" with status ");
sb.Append(status);
logger.LogInformation(sb.ToString());
4.3 异步日志记录
对于文件或网络日志,考虑实现异步记录:
csharp复制public class AsyncLogger : ILogger
{
private readonly ILogger _innerLogger;
private readonly BlockingCollection<LogEntry> _queue = new BlockingCollection<LogEntry>();
private readonly Task _processingTask;
public AsyncLogger(ILogger innerLogger)
{
_innerLogger = innerLogger;
_processingTask = Task.Run(ProcessLogs);
}
private void ProcessLogs()
{
foreach (var entry in _queue.GetConsumingEnumerable())
{
_innerLogger.Log(entry.Level, entry.Message, entry.Exception);
}
}
public void Log(LogLevel level, string message, Exception exception = null)
{
_queue.Add(new LogEntry(level, message, exception));
}
// 其他方法实现...
private record LogEntry(LogLevel Level, string Message, Exception Exception);
}
5. 常见问题与解决方案
在实际使用中,我们可能会遇到各种问题,这里总结一些典型场景:
5.1 日志丢失问题
现象:应用程序崩溃时,最后的几条日志没有记录。
解决方案:
- 实现日志缓冲并定期刷新
- 对于关键日志,使用同步记录方式
- 考虑使用内存日志作为最后保障
csharp复制public class BufferedLogger : ILogger, IDisposable
{
private readonly List<string> _buffer = new List<string>();
private readonly Timer _flushTimer;
public BufferedLogger(TimeSpan flushInterval)
{
_flushTimer = new Timer(FlushBuffer, null, flushInterval, flushInterval);
}
private void FlushBuffer(object state)
{
lock (_buffer)
{
if (_buffer.Count > 0)
{
File.AppendAllLines("logs.txt", _buffer);
_buffer.Clear();
}
}
}
public void Log(LogLevel level, string message, Exception exception = null)
{
lock (_buffer)
{
_buffer.Add($"[{DateTime.Now}] {level}: {message}");
}
}
public void Dispose()
{
_flushTimer.Dispose();
FlushBuffer(null); // 最后一次刷新
}
}
5.2 日志性能问题
现象:添加日志后应用程序性能明显下降。
解决方案:
- 检查是否在热路径中记录了过多低级别日志
- 使用更高效的日志提供者
- 考虑采样日志或聚合日志
csharp复制// 采样日志实现示例
public class SamplingLogger : ILogger
{
private readonly ILogger _innerLogger;
private readonly int _sampleInterval;
private int _counter;
public SamplingLogger(ILogger innerLogger, int sampleInterval)
{
_innerLogger = innerLogger;
_sampleInterval = sampleInterval;
}
public void Log(LogLevel level, string message, Exception exception = null)
{
if (++_counter % _sampleInterval == 0)
{
_innerLogger.Log(level, $"[Sampled {_sampleInterval}] {message}", exception);
}
}
}
5.3 日志配置动态更新
需求:在不重启应用的情况下更改日志级别。
实现方案:
csharp复制public class ReloadableLoggerFactory : ILoggerFactory
{
private readonly IConfiguration _config;
private ILoggerFactory _innerFactory;
public ReloadableLoggerFactory(IConfiguration config)
{
_config = config;
_config.GetReloadToken().RegisterChangeCallback(ReloadConfig, null);
_innerFactory = CreateInnerFactory();
}
private void ReloadConfig(object state)
{
var newFactory = CreateInnerFactory();
Interlocked.Exchange(ref _innerFactory, newFactory)?.Dispose();
_config.GetReloadToken().RegisterChangeCallback(ReloadConfig, null);
}
private ILoggerFactory CreateInnerFactory()
{
var factory = new LoggerFactory();
var logLevel = _config.GetValue<LogLevel>("Logging:LogLevel");
// 根据配置添加Provider和过滤器...
return factory;
}
// 其他方法委托给_innerFactory...
}
6. 与官方日志框架集成
虽然我们实现了自己的日志框架,但在实际项目中,可能需要与官方框架集成:
6.1 将自定义Logger作为Provider
csharp复制public class CustomLoggerProvider : ILoggerProvider
{
private readonly MyCustomLoggerFactory _factory;
public CustomLoggerProvider(MyCustomLoggerFactory factory)
{
_factory = factory;
}
public ILogger CreateLogger(string categoryName)
{
return _factory.CreateLogger(categoryName);
}
public void Dispose()
{
_factory.Dispose();
}
}
// 在ASP.NET Core中注册
builder.Logging.AddProvider(new CustomLoggerProvider(myFactory));
6.2 从官方框架接收日志
csharp复制public class ForwardingLogger : ILogger
{
private readonly ILogger _officialLogger;
private readonly MyCustomLogger _customLogger;
public ForwardingLogger(ILogger officialLogger, MyCustomLogger customLogger)
{
_officialLogger = officialLogger;
_customLogger = customLogger;
}
public void Log(LogLevel level, string message, Exception exception = null)
{
_officialLogger.Log(level, message, exception);
_customLogger.Log(level, message, exception);
}
// 其他方法实现...
}
通过这个手写日志框架的练习,我深刻理解了.NET日志系统的内部工作原理。在实际项目中,虽然我们通常会直接使用Microsoft.Extensions.Logging,但了解其底层机制对于排查复杂问题和进行高级定制非常有帮助。特别是在性能敏感场景下,这些知识能帮助我们做出更合理的设计决策。
