Files
XplorePlane/XP.Common/Documents/Logging.README.md
T

5.2 KiB
Raw Blame History

日志服务使用指南 | Logger Service Usage Guide

概述 | Overview

XplorePlane 使用 Serilog 作为底层日志框架,通过 ILoggerService 接口提供统一的日志服务。

基本用法 | Basic Usage

使用泛型方法 ForModule<T>() 自动获取类型的完整名称(命名空间 + 类名):

public class PlcService
{
    private readonly ILoggerService _logger;

    public PlcService(ILoggerService logger)
    {
        // 自动使用 "XP.Hardware.Plc.Services.PlcService" 作为模块名
        // Automatically uses "XP.Hardware.Plc.Services.PlcService" as module name
        _logger = logger?.ForModule<PlcService>() ?? throw new ArgumentNullException(nameof(logger));
    }

    public void DoSomething()
    {
        _logger.Info("执行操作 | Performing operation");
    }
}

方式 2:手动指定模块名 | Method 2: Manual Module Name

如果需要自定义模块名,可以使用字符串参数:

public class PlcService
{
    private readonly ILoggerService _logger;

    public PlcService(ILoggerService logger)
    {
        // 手动指定简短的模块名
        // Manually specify a short module name
        _logger = logger?.ForModule("PlcService") ?? throw new ArgumentNullException(nameof(logger));
    }
}

方式 3:使用 typeof 获取类型名 | Method 3: Using typeof for Type Name

在静态方法或无法使用泛型的场景:

public class PlcService
{
    private readonly ILoggerService _logger;

    public PlcService(ILoggerService logger)
    {
        // 使用 typeof 获取类型
        // Use typeof to get type
        _logger = logger?.ForModule<PlcService>() ?? throw new ArgumentNullException(nameof(logger));
    }

    public static void StaticMethod(ILoggerService logger)
    {
        // 静态方法中也可以使用泛型
        // Can also use generics in static methods
        var log = logger.ForModule<PlcService>();
        log.Info("静态方法日志 | Static method log");
    }
}

日志级别 | Log Levels

// 调试信息(开发环境)| Debug information (development environment)
_logger.Debug("调试信息:变量值={Value} | Debug info: variable value={Value}", someValue);

// 一般信息 | General information
_logger.Info("操作成功 | Operation successful");

// 警告信息 | Warning information
_logger.Warn("连接不稳定 | Connection unstable");

// 错误信息(带异常)| Error information (with exception)
_logger.Error(ex, "操作失败:{Message} | Operation failed: {Message}", ex.Message);

// 致命错误 | Fatal error
_logger.Fatal(ex, "系统崩溃 | System crash");

日志输出格式 | Log Output Format

使用 ForModule<T>() 后,日志会自动包含完整的类型信息:

2026-03-12 10:30:15.123 [INF] [XP.Hardware.Plc.Services.PlcService] 正在初始化 PLC 连接... | Initializing PLC connection...
2026-03-12 10:30:16.456 [INF] [XP.Hardware.Plc.Services.PlcService] PLC 连接成功 | PLC connection successful

最佳实践 | Best Practices

1. 在构造函数中初始化日志器 | Initialize Logger in Constructor

public class MyService
{
    private readonly ILoggerService _logger;

    public MyService(ILoggerService logger)
    {
        _logger = logger?.ForModule<MyService>() ?? throw new ArgumentNullException(nameof(logger));
    }
}

2. 使用结构化日志 | Use Structured Logging

// 好的做法:使用占位符 | Good: use placeholders
_logger.Info("用户 {UserId} 执行了操作 {Action} | User {UserId} performed action {Action}", userId, action);

// 不好的做法:字符串拼接 | Bad: string concatenation
_logger.Info($"用户 {userId} 执行了操作 {action}");

3. 异常日志包含上下文 | Exception Logs Include Context

try
{
    await _plcClient.ConnectAsync(config);
}
catch (PlcException ex)
{
    _logger.Error(ex, "PLC 连接失败:地址={Address}, 端口={Port} | PLC connection failed: address={Address}, port={Port}", 
        config.Address, config.Port);
    throw;
}

对比:三种方式的输出 | Comparison: Output of Three Methods

// 方式 1ForModule<T>() - 完整类型名
// Method 1: ForModule<T>() - Full type name
_logger = logger.ForModule<PlcService>();
// 输出 | Output: [XP.Hardware.Plc.Services.PlcService]

// 方式 2ForModule("PlcService") - 自定义名称
// Method 2: ForModule("PlcService") - Custom name
_logger = logger.ForModule("PlcService");
// 输出 | Output: [PlcService]

// 方式 3:不调用 ForModule - 无模块标记
// Method 3: Don't call ForModule - No module tag
_logger = logger;
// 输出 | Output: [] (空标记 | empty tag)

配置 | Configuration

日志配置在 App.config 中设置,通过 SerilogConfig 加载:

<appSettings>
  <add key="Serilog:LogPath" value="C:\Logs\XplorePlane" />
  <add key="Serilog:MinimumLevel" value="Information" />
  <add key="Serilog:EnableConsole" value="true" />
  <add key="Serilog:RollingInterval" value="Day" />
  <add key="Serilog:FileSizeLimitMB" value="100" />
  <add key="Serilog:RetainedFileCountLimit" value="30" />
</appSettings>