C# WPF 多级日志管理标准化接口设计文档
1. 引言
本文档定义了一套用于WPF应用程序的多级日志管理标准化接口。该接口设计参考了业界通用的日志库(如Microsoft.Extensions.Logging、NLog、log4net),旨在提供灵活、可扩展的日志记录能力,支持不同日志级别和多种输出目标。通过该接口,开发人员可以轻松切换底层日志实现,而无需修改业务代码。
2. 日志级别枚举
定义日志的严重程度级别,从低到高排序。
/// <summary>
/// 定义日志级别,用于控制日志的输出粒度。
/// </summary>
public enum LogLevel
{
/// <summary>
/// 最详细的调试信息,通常仅在开发环境使用。
/// </summary>
Debug,
/// <summary>
/// 常规信息,记录应用程序的正常运行状态。
/// </summary>
Info,
/// <summary>
/// 警告信息,表示可能出现问题,但应用程序仍可继续运行。
/// </summary>
Warning,
/// <summary>
/// 错误信息,记录发生的异常或功能故障。
/// </summary>
Error,
/// <summary>
/// 严重错误,可能导致应用程序崩溃或无法继续运行。
/// </summary>
Fatal
}
3. 核心日志接口 ILogger
该接口是日志记录的核心,提供记录日志的基本方法。
/// <summary>
/// 表示一个日志记录器,用于记录不同级别的日志。
/// </summary>
public interface ILogger
{
/// <summary>
/// 记录一条日志。
/// </summary>
/// <param name="logLevel">日志级别。</param>
/// <param name="message">日志消息。</param>
/// <param name="exception">关联的异常(可选)。</param>
void Log(LogLevel logLevel, string message, Exception? exception = null);
/// <summary>
/// 检查指定的日志级别是否已启用。
/// </summary>
/// <param name="logLevel">要检查的日志级别。</param>
/// <returns>如果该级别已启用,返回 true;否则返回 false。</returns>
bool IsEnabled(LogLevel logLevel);
}
设计说明:IsEnabled 方法允许调用方在记录日志前进行级别检查,避免构造高开销的日志消息。
4. 扩展方法 LoggerExtensions
为简化调用,提供基于 ILogger 的扩展方法。
public static class LoggerExtensions
{
public static void LogDebug(this ILogger logger, string message, Exception? exception = null)
{
if (logger.IsEnabled(LogLevel.Debug))
logger.Log(LogLevel.Debug, message, exception);
}
public static void LogInfo(this ILogger logger, string message, Exception? exception = null)
{
if (logger.IsEnabled(LogLevel.Info))
logger.Log(LogLevel.Info, message, exception);
}
public static void LogWarning(this ILogger logger, string message, Exception? exception = null)
{
if (logger.IsEnabled(LogLevel.Warning))
logger.Log(LogLevel.Warning, message, exception);
}
public static void LogError(this ILogger logger, string message, Exception? exception = null)
{
if (logger.IsEnabled(LogLevel.Error))
logger.Log(LogLevel.Error, message, exception);
}
public static void LogFatal(this ILogger logger, string message, Exception? exception = null)
{
if (logger.IsEnabled(LogLevel.Fatal))
logger.Log(LogLevel.Fatal, message, exception);
}
}
5. 日志提供者接口 ILoggerProvider
用于创建特定输出目标的日志记录器实例。
public interface ILoggerProvider : IDisposable
{
ILogger CreateLogger(string categoryName);
}
6. 日志工厂接口 ILoggerFactory
管理多个日志提供者,并负责创建日志记录器。
public interface ILoggerFactory : IDisposable
{
void AddProvider(ILoggerProvider provider);
ILogger CreateLogger(string categoryName);
}
7. 默认实现示例
7.1 控制台日志提供者
public class ConsoleLoggerProvider : ILoggerProvider
{
private readonly LogLevel _minimumLevel;
public ConsoleLoggerProvider(LogLevel minimumLevel = LogLevel.Info) => _minimumLevel = minimumLevel;
public ILogger CreateLogger(string categoryName) => new ConsoleLogger(categoryName, _minimumLevel);
public void Dispose() { }
}
internal class ConsoleLogger : ILogger
{
private readonly string _categoryName;
private readonly LogLevel _minimumLevel;
public ConsoleLogger(string categoryName, LogLevel minimumLevel) { _categoryName = categoryName; _minimumLevel = minimumLevel; }
public bool IsEnabled(LogLevel logLevel) => logLevel >= _minimumLevel;
public void Log(LogLevel logLevel, string message, Exception? exception = null)
{
if (!IsEnabled(logLevel)) return;
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{logLevel}] [{_categoryName}] {message}");
if (exception != null) Console.WriteLine(exception);
}
}
7.2 文件日志提供者
public class FileLoggerProvider : ILoggerProvider
{
private readonly string _filePath;
private readonly LogLevel _minimumLevel;
public FileLoggerProvider(string filePath, LogLevel minimumLevel = LogLevel.Info) { _filePath = filePath; _minimumLevel = minimumLevel; }
public ILogger CreateLogger(string categoryName) => new FileLogger(categoryName, _filePath, _minimumLevel);
public void Dispose() { }
}
internal class FileLogger : ILogger
{
private readonly string _categoryName, _filePath;
private readonly LogLevel _minimumLevel;
private readonly object _lock = new();
public FileLogger(string categoryName, string filePath, LogLevel minimumLevel) { _categoryName = categoryName; _filePath = filePath; _minimumLevel = minimumLevel; }
public bool IsEnabled(LogLevel logLevel) => logLevel >= _minimumLevel;
public void Log(LogLevel logLevel, string message, Exception? exception = null)
{
if (!IsEnabled(logLevel)) return;
var logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{logLevel}] [{_categoryName}] {message}";
if (exception != null) logEntry += Environment.NewLine + exception;
lock (_lock) File.AppendAllText(_filePath, logEntry + Environment.NewLine);
}
}
7.3 默认日志工厂实现
public class LoggerFactory : ILoggerFactory
{
private readonly List<ILoggerProvider> _providers = new();
public void AddProvider(ILoggerProvider provider) => _providers.Add(provider ?? throw new ArgumentNullException(nameof(provider)));
public ILogger CreateLogger(string categoryName)
{
var loggers = _providers.Select(p => p.CreateLogger(categoryName)).ToArray();
return new CompositeLogger(loggers);
}
public void Dispose() => _providers.ForEach(p => p.Dispose());
private class CompositeLogger : ILogger
{
private readonly ILogger[] _loggers;
public CompositeLogger(ILogger[] loggers) => _loggers = loggers;
public bool IsEnabled(LogLevel logLevel) => _loggers.Any(l => l.IsEnabled(logLevel));
public void Log(LogLevel logLevel, string message, Exception? exception = null)
{
foreach (var logger in _loggers)
if (logger.IsEnabled(logLevel))
logger.Log(logLevel, message, exception);
}
}
}
8. 在WPF应用程序中的使用示例
8.1 配置日志工厂(App.xaml.cs)
public partial class App : Application
{
public static ILoggerFactory LoggerFactory { get; private set; }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
LoggerFactory = new LoggerFactory();
LoggerFactory.AddProvider(new ConsoleLoggerProvider(LogLevel.Info));
string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "app.log");
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
LoggerFactory.AddProvider(new FileLoggerProvider(logFilePath, LogLevel.Debug));
var logger = LoggerFactory.CreateLogger("App");
logger.LogInfo("应用程序启动");
}
protected override void OnExit(ExitEventArgs e)
{
var logger = LoggerFactory?.CreateLogger("App");
logger?.LogInfo("应用程序退出");
LoggerFactory?.Dispose();
base.OnExit(e);
}
}
8.2 在ViewModel中使用日志
public class MainViewModel
{
private readonly ILogger _logger;
public MainViewModel() => _logger = App.LoggerFactory.CreateLogger(GetType().FullName);
public void SomeOperation()
{
_logger.LogDebug("进入 SomeOperation 方法");
try
{
// 业务逻辑
_logger.LogInfo("操作执行成功");
}
catch (Exception ex)
{
_logger.LogError("操作失败", ex);
}
}
}
9. 高级扩展建议
-
结构化日志:可在 ILogger.Log 方法中增加参数 object[] args 或支持模板字符串(类似 string.Format),便于后期分析。
-
日志上下文:引入 ILogger.BeginScope 方法,用于在同一个操作中附加上下文信息(如请求ID)。
-
异步日志:日志提供者内部可考虑异步写入,避免阻塞主线程(WPF UI线程敏感)。
-
配置热更新:支持在运行时动态调整日志级别或输出目标。
10. 总结
本文档定义了一套标准化的多级日志管理接口,具有良好的扩展性和解耦性,适用于WPF项目。