接口文档汇总 - 1.多级日志管理

12 阅读4分钟

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项目。