基于设计模式的Winform软件框架-01Xml\Log\Ini日志(单例模式+生产者消费者模式)

目录

  1. 概述
  2. 架构设计
  3. 核心设计模式原理
  4. 核心概念
  5. 详细API文档
  6. 集成指南
  7. 常见问题FAQ
  8. 附录

1. 概述

Related_Log 是一个专为 Windows 桌面应用程序设计的日志和配置管理类库。它提供三大核心功能:

功能模块 说明 典型用途
INI配置管理 读写 INI 格式配置文件 保存设备参数、通讯配置、用户设置
文本日志系统 记录系统运行时的调试、警告、错误信息 问题诊断、系统监控、审计追踪
XML操作日志 记录用户操作行为(谁在什么时候做了什么) 安全审计、操作追溯、责任追溯

1.2 核心特性

  • 高性能异步写入:日志写入采用队列批处理机制,不阻塞主线程
  • 零依赖:仅依赖 .NET Framework 基础类库
  • 线程安全:所有服务均支持多线程并发调用
  • 自动分类存储:日志按级别和日期自动分目录、分文件存储
  • 易于集成:单例模式设计,一行代码即可调用

1.3 文件目录结构

复制代码
应用程序根目录/
├── config.ini              # INI配置文件(手动或通过API创建)
├── logs/                   # 文本日志目录(自动创建)
│   ├── INFO/              # 信息级别日志
│   │   ├── 2026-06-10.log # 按日期分文件
│   │   └── 2026-06-11.log
│   ├── WARN/              # 警告级别日志
│   ├── ERROR/             # 错误级别日志
│   ├── DEBUG/             # 调试级别日志
│   └── FATAL/             # 致命错误级别日志
└── logs_xml/              # XML操作日志目录(自动创建)
    ├── 2026-06-10_log.xml # 按日期分文件
    └── 2026-06-11_log.xml

2. 架构设计

2.1 设计模式

本类库综合运用了多种设计模式:

设计模式 应用位置 作用
单例模式 SystemConfigService 确保全局唯一访问入口
外观模式 SystemConfigService 隐藏内部复杂性,提供简洁API
生产者-消费者模式 LogQueueProcessor 异步队列处理,提升性能
观察者模式 LogMessage/XmlMessage 事件 实时推送日志到UI
策略模式 日志保留策略 灵活配置清理规则

2.2 类图

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    SystemConfigService (单例)                    │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐ │
│  │   Config    │  │     Log     │  │          Xml             │ │
│  │ (属性公开)  │  │  (属性公开)  │  │      (属性公开)          │ │
│  └──────┬──────┘  └──────┬──────┘  └──────────┬──────────────┘ │
└─────────┼─────────────────┼────────────────────┼────────────────┘
          │                 │                    │
          ▼                 ▼                    ▼
┌─────────────────┐ ┌─────────────┐   ┌─────────────────────┐
│IniConfigService│ │LogService   │   │XmlLogServiceWrapper │
│   (内部实现)    │ │  (封装类)   │   │    (封装类)          │
└─────────────────┘ └──────┬──────┘   └──────────┬──────────┘
                           │                    │
                           ▼                    ▼
                ┌─────────────────────────────────┐
                │       LogQueueProcessor          │
                │    (队列处理器 - 后台线程)        │
                │  ┌─────────────────────────┐    │
                │  │  _logQueue (文本日志队列) │    │
                │  │  _xmlQueue (XML日志队列) │    │
                │  └─────────────────────────┘    │
                └──────────────┬──────────────────┘
                               │
                ┌──────────────┴──────────────────┐
                ▼                                  ▼
        ┌───────────────┐              ┌──────────────┐
        │LogFileService │              │XmlLogService │
        │ (磁盘写入)    │              │ (磁盘写入)   │
        └───────────────┘              └──────────────┘

2.3 数据流程图

复制代码
┌──────────┐
│  用户代码  │
└─────┬────┘
      │ 调用API
      ▼
┌─────────────────────────────────────┐
│      SystemConfigService.Instance    │
│  .Log.Info("消息", "来源")            │
│  .Xml.Log(sender, "用户名")         │
│  .Config.Read("Section", "Key")      │
└─────────────────────────────────────┘
      │
      ├─→ INI读写:立即返回
      │
      ├─→ Log/Xml写入:
      │    1. 创建请求对象
      │    2. 放入内存队列(瞬间完成)
      │    3. 触发事件(UI实时显示)
      │    4. 后台线程批量写入文件
      │
      ▼
┌─────────────────────────────────────┐
│         磁盘文件                      │
│  config.ini / logs/ / logs_xml/    │
└─────────────────────────────────────┘

3. 核心设计模式原理

本章目标 :深入理解类库使用的两大核心设计模式------单例模式生产者-消费者模式(消息队列),帮助开发者掌握其原理并能够应用到自己的项目中。

3.1 单例模式(Singleton Pattern)原理详解

3.1.1 什么是单例模式?

单例模式 是一种创建型设计模式,它确保一个类在整个应用程序生命周期中只能有一个实例,并提供一个全局访问点来获取该实例。

生活中的类比

  • 打印机管理器:办公室里可能有多台电脑,但只有一台打印机。所有电脑通过同一个"打印机管理器"来排队打印任务。
  • Windows任务管理器:无论你打开多少次任务管理器,系统只允许一个窗口存在。
3.1.2 为什么需要单例模式?

在日志系统中使用单例模式的理由:

理由 说明
资源共享 日志文件是共享资源,多个实例同时写入会导致文件冲突
状态一致性 配置信息需要在整个程序中保持一致
性能优化 避免重复创建对象,节省内存
全局访问 任何地方都能方便地调用,无需传递引用
3.1.3 单例模式的实现方式
方式1:简单单例(非线程安全)
csharp 复制代码
public class SimpleSingleton
{
    private static SimpleSingleton _instance;  // 静态变量存储唯一实例

    private SimpleSingleton()  // 私有构造函数,防止外部创建实例
    {
    }

    public static SimpleSingleton Instance  // 全局访问点
    {
        get
        {
            if (_instance == null)  // 如果实例不存在,创建它
                _instance = new SimpleSingleton();
            return _instance;
        }
    }
}

问题 :多线程环境下,两个线程可能同时通过 if (_instance == null) 检查,导致创建多个实例!

方式2:线程安全的单例(双重检查锁定)
csharp 复制代码
public class ThreadSafeSingleton
{
    private static ThreadSafeSingleton _instance;
    private static readonly object _lock = new object();  // 锁对象

    private ThreadSafeSingleton()
    {
    }

    public static ThreadSafeSingleton Instance
    {
        get
        {
            if (_instance == null)  // 第一次检查(未加锁,提高性能)
            {
                lock (_lock)  // 加锁
                {
                    if (_instance == null)  // 第二次检查(加锁后)
                    {
                        _instance = new ThreadSafeSingleton();
                    }
                }
            }
            return _instance;
        }
    }
}

工作原理

  1. 第一次检查:如果实例已存在,直接返回,避免不必要的锁等待
  2. 加锁:确保同一时间只有一个线程能进入创建代码
  3. 第二次检查:防止多个线程同时通过第一次检查后,在锁内重复创建

流程图

复制代码
多个线程同时调用 Instance
         │
         ▼
    _instance == null?  ←───┐
         │                 │
    是/Yes              否/No │ 直接返回已存在的实例
         │                 │
         ▼                 │
    获取锁 _lock           │
         │                 │
         ▼                 │
    等待获取锁              │
         │                 │
         ▼                 │
    获得锁后再次检查 ───────┘
         │
    _instance == null?
         │
    是/Yes → 创建新实例 → 释放锁 → 返回实例
    否/No  → 释放锁 → 返回现有实例
方式3:本类库使用的写法(简化双重检查)
csharp 复制代码
public partial class SystemConfigService : IDisposable
{
    private static SystemConfigService _instance;
    private static readonly object _lock = new object();

    private SystemConfigService()  // 私有构造函数
    {
        // 初始化各个服务
        Config = new IniConfigService();
        // ...
    }

    public static SystemConfigService Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (_lock)
                {
                    // 使用 ?? 运算符简化写法
                    _instance = _instance ?? new SystemConfigService();
                }
            }
            return _instance;
        }
    }
}

代码解读

  • _instance = _instance ?? new SystemConfigService() 等价于:

    csharp 复制代码
    if (_instance == null)
        _instance = new SystemConfigService();
3.1.4 单例模式在类库中的应用
csharp 复制代码
// ========== 任何地方都可以这样调用 ==========
// 不需要 new,不需要传递引用
SystemConfigService.Instance.Log.Info("消息", "来源");

// ========== 在不同类中使用 ==========
public class FrmMain
{
    public void LoadData()
    {
        // 直接使用,无需传递
        SystemConfigService.Instance.Log.Info("加载开始", "FrmMain");
    }
}

public class DataService
{
    public void SaveData()
    {
        // 同一个实例,共享状态
        SystemConfigService.Instance.Log.Info("保存开始", "DataService");
    }
}
3.1.5 单例模式的优缺点
优点 缺点
✅ 全局唯一访问点 ❌ 难以单元测试(全局状态)
✅ 节省内存资源 ❌ 违反单一职责原则(管理自身+业务逻辑)
✅ 延迟初始化(用到时才创建) ❌ 隐藏依赖关系(类之间的耦合不明显)
✅ 线程安全(正确实现时) ❌ 可能被滥用(全局变量替代品)
3.1.6 何时使用单例模式?

适用场景

  • ✅ 日志服务、配置管理器、数据库连接池
  • ✅ 线程池、缓存管理器、打印机后台处理程序

不适用场景

  • ❌ 普通的业务对象(如用户、订单)
  • ❌ 需要多实例的对象(如网络连接)

3.2 生产者-消费者模式(消息队列)原理详解

3.2.1 什么是生产者-消费者模式?

生产者-消费者模式是一种并发设计模式,它解耦了"生产数据"和"处理数据"两个过程,通过一个**缓冲区(队列)**来连接它们。

生活中的类比

  • 餐厅后厨
    • 生产者:厨师(不断炒菜)
    • 队列:出餐口(放菜的地方)
    • 消费者:服务员(取菜送给客人)
  • 快递中转站
    • 生产者:快递员(不断送来包裹)
    • 队列:分拣传送带
    • 消费者:派送员(取走包裹派送)
3.2.2 为什么需要消息队列?

场景对比

对比项 直接写入磁盘(传统方式) 消息队列(本类库方式)
写入方式 每次调用立即写文件 先放入队列,后台批量写
主线程阻塞 每次写入可能耗时 5-50ms 入队耗时 <0.1ms
100条日志总耗时 500-5000ms 入队 <10ms,后台异步处理
用户体验 界面可能卡顿 界面流畅无感
磁盘I/O次数 100次(每条1次) 1-2次(批量)

核心优势

  1. 解耦:生产者和消费者互不依赖,可以独立变化
  2. 削峰:处理突发大量请求时,队列起到缓冲作用
  3. 异步:主线程快速返回,耗时操作在后台执行
  4. 批量处理:合并多次写入为一次,减少磁盘I/O
3.2.3 消息队列的工作原理
核心组件
复制代码
┌─────────────────────────────────────────────────────────────┐
│                      生产者-消费者模式                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────┐         ┌───────────┐         ┌──────────┐  │
│  │ 生产者    │  入队   │  消息队列  │  出队   │  消费者   │  │
│  │(多个线程)│ ───────→│ (缓冲区)  │ ───────→│(后台线程)│  │
│  └──────────┘         └───────────┘         └──────────┘  │
│       │                     │                    │          │
│       │                     │                    │          │
│   用户代码              内存队列              批量写入文件    │
│  调用Log.Info()      ConcurrentQueue       LogFileService  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
时序图
复制代码
时间线 ──────────────────────────────────────────────────────→

主线程(生产者):                       后台线程(消费者):
     │                                      │
     │ Log.Info("消息1")                    │
     ├─→ 创建LogRequest对象                 │
     ├─→ 入队(瞬间完成)                    │
     ├─→ 立即返回 <0.1ms ──────────────────→ 休眠中...
     │                                      │
     │ Log.Info("消息2")                    │
     ├─→ 创建LogRequest对象                 │
     ├─→ 入队                               │
     ├─→ 立即返回                           │
     │                                      │
     │ Log.Info("消息3")                    │
     ├─→ 创建LogRequest对象                 │
     │ ...(主线程继续运行)                 │
     │                                      │ 1秒后或队列满时被唤醒
     │                                      ├─→ 从队列取出100条
     │                                      ├─→ 批量写入文件(一次性)
     │                                      ├─→ 写入完成
     │                                      └─→ 继续休眠
3.2.4 本类库中的队列实现
队列处理器核心代码
csharp 复制代码
internal class LogQueueProcessor : IDisposable
{
    // 线程安全的队列(.NET 提供)
    private readonly ConcurrentQueue<LogRequest> _logQueue;
    private readonly ConcurrentQueue<XmlLogRequest> _xmlQueue;

    // 后台线程
    private Thread _logThread;
    private Thread _xmlThread;

    // 控制标志
    private bool _isRunning;
    private readonly ManualResetEventSlim _logEvent;  // 通知信号

    // 批量写入配置
    private const int BATCH_SIZE = 100;        // 每次批量写入100条
    private const int FLUSH_INTERVAL_MS = 1000; // 或者每1秒写一次

    public LogQueueProcessor(
        Action<LogRequest[]> processLogBatch,
        Action<XmlLogRequest[]> processXmlBatch)
    {
        _logQueue = new ConcurrentQueue<LogRequest>();
        _xmlQueue = new ConcurrentQueue<XmlLogRequest>();
        _logEvent = new ManualResetEventSlim(false);

        // 启动后台线程
        Start();
    }

    // ========== 生产者方法(主线程调用) ==========

    public void EnqueueLog(LogRequest request)
    {
        if (request == null) return;
        _logQueue.Enqueue(request);  // 入队(线程安全,<0.01ms)
        _logEvent.Set();  // 通知后台线程"有新数据"
    }

    // ========== 消费者方法(后台线程执行) ==========

    private void ProcessLogQueue()
    {
        while (_isRunning)
        {
            // 等待信号或超时(1秒)
            _logEvent.Wait(FLUSH_INTERVAL_MS);
            _logEvent.Reset();

            // 收集一批日志
            var batch = new LogRequest[BATCH_SIZE];
            int count = 0;

            // 从队列中取出(最多100条)
            while (count < BATCH_SIZE && _logQueue.TryDequeue(out var request))
            {
                batch[count++] = request;
            }

            // 如果有日志,批量处理
            if (count > 0)
            {
                Array.Resize(ref batch, count);  // 调整数组大小
                _processLogBatch(batch);  // 调用写入方法
            }
        }
    }
}
关键技术点解析

1. ConcurrentQueue - 线程安全队列

csharp 复制代码
// 普通队列(非线程安全)
Queue<LogRequest> queue = new Queue<LogRequest>();
queue.Enqueue(item);     // 多线程同时调用会出错
var item = queue.Dequeue();

// 线程安全队列
ConcurrentQueue<LogRequest> queue = new ConcurrentQueue<LogRequest>();
queue.Enqueue(item);     // 多线程同时调用安全 ✓
queue.TryDequeue(out item);  // 尝试取出,失败返回false

为什么需要线程安全?

  • 主线程(生产者)在入队
  • 后台线程(消费者)在出队
  • 两个线程同时操作同一个队列,必须使用线程安全的队列

2. ManualResetEventSlim - 线程间通信

csharp 复制代码
// 创建事件(初始状态:无信号)
var event = new ManualResetEventSlim(false);

// 后台线程:等待信号
event.Wait(1000);  // 等待1000ms或收到信号
event.Reset();     // 重置为无信号状态

// 主线程:发送信号
event.Set();  // 通知后台线程"醒来工作"

工作原理

复制代码
后台线程              事件状态              主线程
   │                   │                    │
   │ Wait()           □ 无信号              │
   │ (阻塞)            │                   │
   │                   │                   │ Enqueue()
   │                   │                   │
   │                   │                   ├─ Set()
   │                   │                   │
   │ ← 醒来!         ■ 有信号              │
   │ 开始工作          │                   │
   │                   │                   │
   │ Reset()          □ 无信号              │
   │ Wait()           │                   │
   │ (阻塞)            │                   │

3. 批量处理 - 性能优化的关键

csharp 复制代码
// ❌ 效率低:每次写一条
foreach (var log in logs)
{
    File.AppendAllText("file.log", log.ToString());  // 100次磁盘I/O
}

// ✅ 效率高:批量写入
var content = string.Join("\r\n", logs);
File.AppendAllText("file.log", content);  // 1次磁盘I/O

性能对比(100条日志):

  • 逐条写入:~500ms(100次磁盘I/O)
  • 批量写入:~50ms(1次磁盘I/O)
3.2.5 完整数据流程图
复制代码
┌──────────────────────────────────────────────────────────────────┐
│                         用户代码                                  │
│  SystemConfigService.Instance.Log.Info("设备连接成功", "PLC");  │
└────────────────────────────┬─────────────────────────────────────┘
                             │ 调用
                             ▼
┌──────────────────────────────────────────────────────────────────┐
│                        LogService.Log()                          │
│  1. 创建 LogRequest 对象(包含级别、消息、来源、时间戳)          │
│  2. 调用 _queueProcessor.EnqueueLog(request)  ← 瞬间完成         │
└────────────────────────────┬─────────────────────────────────────┘
                             │ 入队
                             ▼
┌──────────────────────────────────────────────────────────────────┐
│                     ConcurrentQueue<LogRequest>                   │
│                      内存中的日志队列                               │
│  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐              │
│  │Log1 │ │Log2 │ │Log3 │ │Log4 │ │Log5 │ │ ... │  ...          │
│  └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘              │
└────────────────────────────┬─────────────────────────────────────┘
                             │ 后台线程定时取出(100条或1秒)
                             ▼
┌──────────────────────────────────────────────────────────────────┐
│                     LogFileService.WriteBatch()                   │
│  1. 按日期和级别分组                                               │
│  2. 拼接成字符串(用 \r\n 连接)                                  │
│  3. File.AppendAllText() 一次性写入文件                           │
└────────────────────────────┬─────────────────────────────────────┘
                             │ 写入
                             ▼
┌──────────────────────────────────────────────────────────────────┐
│                     磁盘日志文件                                   │
│  logs/INFO/2026-06-11.log                                        │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ 2026-06-11 14:30:25.1234|INFO|PLC|设备连接成功            │   │
│  │ 2026-06-11 14:30:26.5678|INFO|Modbus|读取寄存器            │   │
│  │ ...                                                         │   │
│  └──────────────────────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────────────────┘
3.2.6 队列的优缺点与权衡
优点 缺点 权衡方案
✅ 主线程不被阻塞 ❌ 程序崩溃可能丢失队列中的数据 程序退出时调用 Flush()
✅ 批量写入,减少I/O ❌ 增加内存消耗 限制队列最大长度
✅ 处理突发流量 ❌ 日志可能有延迟 设置合理的刷新间隔
✅ 线程安全 ❌ 调试难度增加 添加队列大小监控
3.2.7 何时使用消息队列?

适用场景

  • ✅ 日志记录
  • ✅ 消息发送(邮件、短信)
  • ✅ 文件上传
  • ✅ 数据库批量写入
  • ✅ 任何"生产快、消费慢"的场景

不适用场景

  • ❌ 需要立即确认的操作(如支付)
  • ❌ 数据量极小,直接处理更简单
  • ❌ 消息不能丢失且无法容忍延迟

3.3 设计模式组合运用

本类库巧妙地将单例模式生产者-消费者模式组合使用:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                   组合架构示意                                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌───────────────────────────────────────────────────────┐    │
│   │          SystemConfigService (单例)                   │    │
│   │    确保全局只有一个入口,管理所有日志和配置服务          │    │
│   └─────────────────────┬─────────────────────────────────┘    │
│                         │                                       │
│          ┌──────────────┼──────────────┐                      │
│          ▼              ▼              ▼                      │
│   ┌────────────┐  ┌────────────┐  ┌────────────┐            │
│   │ Config服务  │  │ Log服务    │  │ Xml服务     │            │
│   │ (立即执行)  │  │ (入队返回)  │  │ (入队返回)  │            │
│   └────────────┘  └──────┬─────┘  └──────┬─────┘            │
│                          │                │                    │
│                          ▼                ▼                    │
│                   ┌────────────────────────────┐             │
│                   │    LogQueueProcessor        │             │
│                   │   (生产者-消费者模式)         │             │
│                   │   统一的队列处理中心          │             │
│                   └─────────────┬──────────────┘             │
│                                │                            │
│                    ┌───────────┴───────────┐                │
│                    ▼                       ▼                │
│            ┌─────────────┐         ┌─────────────┐         │
│            │LogFileService│         │XmlLogService│         │
│            │  消费者:批量写入  │         │  消费者:批量写入  │         │
│            └─────────────┘         └─────────────┘         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

组合的优势

  1. 单例提供统一入口,调用简单
  2. 队列提供异步处理,性能优秀
  3. 两者结合,既好用又高效

4. 核心概念

3.1 日志级别(LogLevel)

文本日志支持 5 个级别,从低到高:

级别 枚举值 说明 适用场景
信息 LogLevel.Info 正常运行信息 连接成功、数据更新、流程完成
警告 LogLevel.Warn 潜在问题 参数异常、重连尝试、接近阈值
错误 LogLevel.Error 错误但可恢复 通讯失败、文件读写错误
调试 LogLevel.Debug 调试信息 详细数据、中间状态
致命 LogLevel.Fatal 严重错误 程序无法继续运行

代码示例

csharp 复制代码
SystemConfigService.Instance.Log.Info("设备连接成功", "FrmMain");
SystemConfigService.Instance.Log.Warn("通讯延迟超过阈值", "CommService");
SystemConfigService.Instance.Log.Error("读取寄存器失败", ex, "ModbusRTU");
SystemConfigService.Instance.Log.Debug("发送数据: 01 03 00 00 00 0A", "CommService");
SystemConfigService.Instance.Log.Fatal("系统崩溃,无法启动", ex, "Program");

3.2 日志保留策略(LogRetentionDays)

用于控制日志文件的清理周期:

枚举值 天数 说明
Never -1 不清理,永久保留
Days7 7 保留 7 天
Days30 30 保留 30 天(默认)
Days60 60 保留 60 天
Days90 90 保留 90 天
Days180 180 保留 180 天

3.3 INI配置文件结构

INI 文件采用经典的"节-键-值"结构:

ini 复制代码
[Section1]
Key1=Value1
Key2=Value2

[Section2]
Key1=Value1
Key2=Value2

示例

ini 复制代码
[ModbusTCP]
IP=192.168.1.100
Port=502
SlaveId=1

[Storage]
Save_Log=30天
Save_Xml=30天

5. 详细API文档

4.1 SystemConfigService - 统一访问入口

这是类库的主入口,采用单例模式,通过 SystemConfigService.Instance 访问。

4.1.1 属性
属性 类型 说明
Config IniConfigService INI配置服务实例
Log LogService 文本日志服务实例
Xml XmlLogServiceWrapper XML操作日志服务实例
4.1.2 方法

日志清理

csharp 复制代码
// 方式1:使用整数天数
CleanOldLogs(int logRetentionDays = -1, int xmlRetentionDays = -1)

// 方式2:使用枚举(推荐)
CleanOldLogs(LogRetentionDays logRetention, LogRetentionDays xmlRetention)

统计信息

csharp 复制代码
// 获取队列大小(用于监控)
(int logQueueSize, int xmlQueueSize) GetQueueSize()

// 获取日志占用空间
long GetTotalLogSize()

// 获取日志文件数量
(int logCount, int xmlCount) GetLogFileCount()

资源释放

csharp 复制代码
void Dispose()
4.1.3 事件
事件 参数类型 说明
LogMessage EventHandler<LogEventArgs> 文本日志消息事件
XmlMessage EventHandler<XmlLogEventArgs> XML日志消息事件

事件订阅示例

csharp 复制代码
SystemConfigService.Instance.LogMessage += OnLogReceived;
SystemConfigService.Instance.XmlMessage += OnXmlLogReceived;

private void OnLogReceived(object sender, LogEventArgs e)
{
    // e.Level - 日志级别
    // e.Message - 日志消息
    // e.Source - 来源
    // e.Timestamp - 时间戳
}

private void OnXmlLogReceived(object sender, XmlLogEventArgs e)
{
    // e.Username - 用户名
    // e.Action - 操作
    // e.Details - 详情
    // e.Timestamp - 时间戳
}

4.2 LogService - 文本日志服务

通过 SystemConfigService.Instance.Log 访问。

4.2.1 方法

基本方法

csharp 复制代码
// 记录信息
void Info(string message, string source = "")

// 记录警告
void Warn(string message, string source = "")

// 记录错误(无异常)
void Error(string message, string source = "")

// 记录错误(带异常)
void Error(string message, Exception ex, string source = "")

// 记录调试信息
void Debug(string message, string source = "")

// 记录致命错误(无异常)
void Fatal(string message, string source = "")

// 记录致命错误(带异常)
void Fatal(string message, Exception ex, string source = "")

// 核心方法(直接指定级别)
void Log(LogLevel level, string message, string source = "", Exception ex = null)
4.2.2 使用示例
csharp 复制代码
// 简单记录
SystemConfigService.Instance.Log.Info("应用程序启动", "Program");

// 带来源记录
SystemConfigService.Instance.Log.Warn("配置文件不存在,使用默认值", "ConfigService");

// 记录异常
try
{
    File.Delete("somefile.txt");
}
catch (Exception ex)
{
    SystemConfigService.Instance.Log.Error("删除文件失败", ex, "FileService");
}

// 使用核心方法
SystemConfigService.Instance.Log.Log(
    LogLevel.Info,
    "用户登录成功",
    "LoginService"
);
4.2.3 日志文件格式

文本日志文件采用管道符分隔格式:

复制代码
时间戳|级别|来源|消息

示例

复制代码
2026-06-11 14:30:25.1234|INFO|Program|应用程序启动
2026-06-11 14:30:26.4567|WARN|ConfigService|配置文件不存在,使用默认值
2026-06-11 14:30:27.7890|ERROR|ModbusRTU|读取寄存器失败 | 异常: TimeoutException: 连接超时 | 堆栈: at ModbusRTU.Read()...

4.3 XmlLogServiceWrapper - XML操作日志服务

通过 SystemConfigService.Instance.Xml 访问。

4.3.1 方法
csharp 复制代码
// 自动提取控件信息记录(推荐)
void Log(object sender, string username, string additionalAction = null, string details = null)

// 直接指定参数记录
void Log(string username, string action, string details = "")
4.3.2 使用示例
csharp 复制代码
// 在按钮点击事件中使用(推荐)
private void btnSave_Click(object sender, EventArgs e)
{
    // 自动提取控件名称、文本、页面信息
    SystemConfigService.Instance.Xml.Log(sender, Environment.UserName);

    // 或者附加额外信息
    SystemConfigService.Instance.Xml.Log(
        sender,
        Environment.UserName,
        additionalAction: "保存配方",
        details: "配方名称: Recipe001"
    );
}

// 直接记录
SystemConfigService.Instance.Xml.Log(
    "Admin",
    "UserLogin",
    "IP: 192.168.1.100"
);
4.3.3 自动提取的信息

当使用 Log(object sender, ...) 重载时,系统会自动提取:

信息 来源 示例
控件名称 Control.Name btnSave
控件文本 Control.Text 保存
控件标签 Control.Tag SaveRecipe
页面名称 查找父级 UserControl Page_Recipe_01List
页面标题 Page.Text 配方列表

生成的 Action 格式:控件名-控件文本(Tag信息)-附加操作-被按下

4.3.4 XML日志文件格式

XML日志文件使用 .NET XML 序列化,结构如下:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfXmlLogEntry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <XmlLogEntry>
    <Timestamp>2026-06-11 14:30:25.123456</Timestamp>
    <CurrentUser>Admin</CurrentUser>
    <Action>btnSave-保存(SaveRecipe)-被按下</Action>
    <Details>页面:Page_Recipe_01List(配方列表); 配方名称: Recipe001</Details>
  </XmlLogEntry>
  <XmlLogEntry>
    <Timestamp>2026-06-11 14:31:10.654321</Timestamp>
    <CurrentUser>Admin</CurrentUser>
    <Action>UserLogin</Action>
    <Details>IP: 192.168.1.100</Details>
  </XmlLogEntry>
</ArrayOfXmlLogEntry>

4.4 IniConfigService - INI配置服务

通过 SystemConfigService.Instance.Config 访问。

4.4.1 读取方法
csharp 复制代码
// 读取字符串
string Read(string section, string key, string defaultValue = "")

// 读取整数
int ReadInt(string section, string key, int defaultValue = 0)

// 读取长整数
long ReadLong(string section, string key, long defaultValue = 0)

// 读取布尔值
bool ReadBool(string section, string key, bool defaultValue = false)

// 读取浮点数
double ReadDouble(string section, string key, double defaultValue = 0.0)

// 读取单精度浮点数
float ReadFloat(string section, string key, float defaultValue = 0.0f)

// 读取整个Section的所有键值对
Dictionary<string, string> ReadSection(string section)

// 获取所有Section名称
List<string> GetSections()
4.4.2 写入方法
csharp 复制代码
// 写入单个配置
bool Write(string section, string key, object value)

// 批量写入一个Section的多个键值对
void WriteSection(string section, Dictionary<string, object> keyValues)
4.4.3 删除方法
csharp 复制代码
// 删除指定键
bool DeleteKey(string section, string key)

// 删除指定Section
bool DeleteSection(string section)
4.4.4 检查方法
csharp 复制代码
// 检查键是否存在
bool KeyExists(string section, string key)

// 检查Section是否存在
bool SectionExists(string section)
4.4.5 属性
csharp 复制代码
// 获取INI文件路径
string FilePath { get; }
4.4.6 使用示例
csharp 复制代码
var config = SystemConfigService.Instance.Config;

// ========== 读取 ==========

// 读取字符串
string ip = config.Read("ModbusTCP", "IP", "127.0.0.1");

// 读取整数
int port = config.ReadInt("ModbusTCP", "Port", 502);

// 读取布尔值
bool activate = config.ReadBool("ModbusTCP", "Activate", false);

// 读取整个Section
var allSettings = config.ReadSection("ModbusTCP");
// 结果: {"IP": "192.168.1.100", "Port": "502", "SlaveId": "1"}

// ========== 写入 ==========

// 写入单个配置
config.Write("ModbusTCP", "IP", "192.168.1.100");
config.Write("ModbusTCP", "Port", 502);
config.Write("ModbusTCP", "Activate", true);

// 批量写入
config.WriteSection("ModbusTCP", new Dictionary<string, object>
{
    { "IP", "192.168.1.100" },
    { "Port", 502 },
    { "SlaveId", 1 },
    { "Activate", true }
});

// ========== 删除 ==========

config.DeleteKey("ModbusTCP", "OldKey");
config.DeleteSection("OldSection");

// ========== 检查 ==========

bool hasKey = config.KeyExists("ModbusTCP", "IP");        // true
bool hasSection = config.SectionExists("ModbusTCP");     // true

4.5 日志清理服务

通过 SystemConfigService.Instance.CleanOldLogs(...) 访问。

4.5.1 清理结果类
csharp 复制代码
public class CleanResult
{
    public bool Success { get; set; }              // 是否成功
    public DateTime CleanTime { get; set; }        // 清理时间
    public int LogFilesDeleted { get; set; }       // 删除的Log文件数
    public long LogSpaceFreed { get; set; }       // 释放的Log空间(字节)
    public int XmlFilesDeleted { get; set; }       // 删除的XML文件数
    public long XmlSpaceFreed { get; set; }       // 释放的XML空间(字节)
    public List<string> LogErrors { get; set; }    // Log清理错误列表
    public List<string> XmlErrors { get; set; }    // XML清理错误列表
    public int TotalFilesDeleted { get; }          // 总删除文件数
    public long TotalSpaceFreed { get; }          // 总释放空间
    public string GetSummary() { }                 // 格式化摘要
}
4.5.2 使用示例
csharp 复制代码
// 清理30天前的文本日志和XML日志
var result = SystemConfigService.Instance.CleanOldLogs(
    LogRetentionDays.Days30,  // 文本日志保留30天
    LogRetentionDays.Days60   // XML日志保留60天
);

if (result.Success)
{
    MessageBox.Show(result.GetSummary());
    // 示例输出: "清理完成: 删除 15 个文件, 释放 25.6 MB 空间"
}

// 或使用整数
var result2 = SystemConfigService.Instance.CleanOldLogs(
    logRetentionDays: 30,
    xmlRetentionDays: 60
);

6. 集成指南

5.1 将类库打包为DLL

步骤1:创建类库项目
  1. 在 Visual Studio 中创建新的 类库(.NET Framework) 项目
  2. 项目命名:Related_Log
  3. 目标框架:.NET Framework 4.8(或你的项目使用的版本)
步骤2:复制源文件

将以下文件复制到类库项目中:

复制代码
Related_Log/
├── Models/
│   └── LogModels.cs
├── Service/
│   ├── Related_Log/
│   │   ├── LogFileService.cs
│   │   └── LogQueueProcessor.cs
│   ├── Related_Xml/
│   │   └── XmlLogService.cs
│   ├── Related_Ini/
│   │   └── IniConfigService.cs
│   └── Related_Clean/
│       └── LogCleanService.cs
└── SystemConfigService.cs
步骤3:添加引用

项目需要引用:

  • System(默认)
  • System.Core(默认)
  • System.Windows.Forms(需要添加)
步骤4:编译生成DLL
  1. 选择 Release 配置
  2. 点击 生成 → 生成解决方案
  3. bin\Release\ 目录下找到 Related_Log.dll

5.2 在新项目中集成

步骤1:添加DLL引用
  1. 在目标项目中,右键 引用 → 添加引用
  2. 选择 浏览 ,找到 Related_Log.dll
  3. 勾选并确定
步骤2:添加命名空间
csharp 复制代码
using Related_Log;
using Related_Log.Models;
步骤3:初始化(可选)
csharp 复制代码
// 在程序启动时访问一次,确保单例初始化
var service = SystemConfigService.Instance;

// 可选:订阅日志事件用于实时显示
service.LogMessage += OnLogMessage;
service.XmlMessage += OnXmlMessage;
步骤4:开始使用
csharp 复制代码
// INI配置
string ip = SystemConfigService.Instance.Config.Read("Network", "IP", "127.0.0.1");

// 文本日志
SystemConfigService.Instance.Log.Info("程序启动", "Program");

// XML日志
SystemConfigService.Instance.Xml.Log(button1, "Admin");

5.3 完整集成示例

示例1:简单控制台应用
csharp 复制代码
using System;
using Related_Log;

namespace MyConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // 记录启动
            SystemConfigService.Instance.Log.Info("程序启动", "Program");

            try
            {
                // 读取配置
                var config = SystemConfigService.Instance.Config;
                string ip = config.Read("Server", "IP", "127.0.0.1");
                int port = config.ReadInt("Server", "Port", 8080);

                Console.WriteLine($"服务器地址: {ip}:{port}");

                // 保存配置
                config.Write("Server", "LastConnect", DateTime.Now.ToString());

                // 记录日志
                SystemConfigService.Instance.Log.Info($"连接到服务器 {ip}:{port}", "Program");
            }
            catch (Exception ex)
            {
                SystemConfigService.Instance.Log.Error("程序运行出错", ex, "Program");
            }

            // 清理旧日志
            var result = SystemConfigService.Instance.CleanOldLogs(
                LogRetentionDays.Days30,
                LogRetentionDays.Days30
            );
            Console.WriteLine(result.GetSummary());
        }
    }
}
示例2:WinForms 应用
csharp 复制代码
using System;
using System.Windows.Forms;
using Related_Log;
using Related_Log.Models;

namespace MyWinFormsApp
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();

            // 订阅日志事件,实时显示在界面上
            SystemConfigService.Instance.LogMessage += OnLogMessage;
            SystemConfigService.Instance.XmlMessage += OnXmlMessage;
        }

        private void OnLogMessage(object sender, LogEventArgs e)
        {
            // 跨线程更新UI
            if (txtLog.InvokeRequired)
            {
                txtLog.Invoke(new Action(() => OnLogMessage(sender, e)));
                return;
            }

            txtLog.AppendText($"[{e.Level}] {e.Message}\r\n");
        }

        private void OnXmlMessage(object sender, XmlLogEventArgs e)
        {
            if (txtXml.InvokeRequired)
            {
                txtXml.Invoke(new Action(() => OnXmlMessage(sender, e)));
                return;
            }

            txtXml.AppendText($"[{e.Timestamp:HH:mm:ss}] {e.Username} - {e.Action}\r\n");
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                // 记录操作
                SystemConfigService.Instance.Xml.Log(sender, Environment.UserName);

                // 读取配置
                var config = SystemConfigService.Instance.Config;
                string ip = config.Read("PLC", "IP", "192.168.1.1");

                // 连接逻辑...
                SystemConfigService.Instance.Log.Info($"PLC连接成功: {ip}", "MainForm");
            }
            catch (Exception ex)
            {
                SystemConfigService.Instance.Log.Error("PLC连接失败", ex, "MainForm");
            }
        }

        private void btnClearLogs_Click(object sender, EventArgs e)
        {
            var result = SystemConfigService.Instance.CleanOldLogs(7, 30);
            MessageBox.Show(result.GetSummary(), "清理结果");
            SystemConfigService.Instance.Xml.Log(sender, Environment.UserName);
        }
    }
}

5.3 配置文件模板

创建默认的 config.ini 文件(可选):

ini 复制代码
; ============================================
; 系统配置文件
; ============================================

[Network]
; 服务器IP地址
IP=127.0.0.1
; 服务器端口
Port=8080

[PLC]
; PLC通讯配置
IP=192.168.1.1
Port=502
SlaveId=1
; 是否启用PLC通讯
Activate=False

[Storage]
; 日志保留天数
Save_Log=30天
Save_Xml=30天

7. 常见问题FAQ

Q1: 日志写入会影响程序性能吗?

A: 不会。日志写入采用异步队列机制:

  1. 调用日志方法时,只是将日志放入内存队列(微秒级)
  2. 后台线程定期批量写入文件,不阻塞主线程
  3. 即使文件写入失败,也不会影响主程序运行

Q2: 程序意外关闭,队列中的日志会丢失吗?

A: 是的,队列中的日志会丢失。如果需要确保日志不丢失:

  1. 在程序退出时调用 SystemConfigService.Instance.Dispose() 刷新队列
  2. 或在关键操作后调用强制刷新(需要扩展接口)
csharp 复制代码
protected override void OnFormClosing(FormClosingEventArgs e)
{
    SystemConfigService.Instance.Dispose();  // 刷新队列
    base.OnFormClosing(e);
}

Q3: 如何在界面上实时显示日志?

A : 订阅 LogMessageXmlMessage 事件:

csharp 复制代码
SystemConfigService.Instance.LogMessage += (s, e) =>
{
    // 使用 Invoke 确保线程安全
    if (txtLog.InvokeRequired)
    {
        txtLog.Invoke(new Action(() => txtLog.AppendText(e.Message + "\r\n")));
    }
    else
    {
        txtLog.AppendText(e.Message + "\r\n");
    }
};

Q4: INI文件读写失败怎么办?

A: 检查以下几点:

  1. 文件路径权限:确保程序对 INI 文件所在目录有读写权限
  2. 文件编码:确保 INI 文件使用 UTF-8 编码保存
  3. 调用异常Read 方法失败时会抛出异常,建议加 try-catch
csharp 复制代码
try
{
    string value = config.Read("Section", "Key", "default");
}
catch (Exception ex)
{
    SystemConfigService.Instance.Log.Error("读取配置失败", ex, "Config");
}

Q5: 如何自定义日志文件存储路径?

A : 当前版本日志路径固定为应用程序目录下的 logs/logs_xml/。如需自定义,需要修改以下类:

  • LogFileService.cs:修改 _logBaseDir 初始化
  • XmlLogService.cs:修改 _logDirectory 初始化
  • LogCleanService.cs:修改对应的路径

Q6: 多个程序可以同时读写同一个 INI 文件吗?

A: 技术上可以,但不推荐。INI 文件没有内置的并发控制机制。

解决方案

  1. 每个程序使用独立的 INI 文件
  2. 或使用数据库等支持并发的配置存储方式

Q7: 日志文件会无限增大吗?

A: 默认不会,需要手动调用清理方法。建议:

  1. 在程序启动时定期清理
  2. 或在配置页面提供清理按钮
csharp 复制代码
// 程序启动时清理30天前的日志
SystemConfigService.Instance.CleanOldLogs(LogRetentionDays.Days30, LogRetentionDays.Days30);

8. 附录

7.1 命名空间汇总

命名空间 说明
Related_Log 主命名空间,包含 SystemConfigService
Related_Log.Models 数据模型类(LogEntryXmlLogEntry、事件参数等)
Related_Log.Service.Related_Log 文本日志服务(内部)
Related_Log.Service.Related_Xml XML日志服务(内部)
Related_Log.Service.Related_Ini INI配置服务(内部)
Related_Log.Service.Related_Clean 日志清理服务(内部)

7.2 类型汇总

类型 命名空间 说明
SystemConfigService Related_Log 主入口类(单例)
LogService Related_Log 文本日志封装类
XmlLogServiceWrapper Related_Log XML日志封装类
IniConfigService Related_Log.Service.Related_Ini INI配置服务
LogLevel Related_Log.Models 日志级别枚举
LogRetentionDays Related_Log.Models 日志保留天数枚举
LogEntry Related_Log.Models 文本日志实体
XmlLogEntry Related_Log.Models XML日志实体
LogEventArgs Related_Log.Models 日志事件参数
XmlLogEventArgs Related_Log.Models XML日志事件参数
LogCleanService.CleanResult Related_Log.Service.Related_Clean 清理结果类

7.3 快速参考卡片

复制代码
╔═══════════════════════════════════════════════════════════════╗
║                    Related_Log 快速参考                         ║
╠═══════════════════════════════════════════════════════════════╣
║                                                                ║
║  🔹 INI配置                                                    ║
║     var config = SystemConfigService.Instance.Config;         ║
║     config.Read(section, key, defaultValue)                  ║
║     config.Write(section, key, value)                        ║
║     config.ReadInt/ReadBool/ReadDouble...                    ║
║                                                                ║
║  🔹 文本日志                                                   ║
║     SystemConfigService.Instance.Log.Info("消息", "来源")    ║
║     .Warn("警告")                                             ║
║     .Error("错误", exception)                                 ║
║     .Debug("调试")                                            ║
║     .Fatal("致命")                                            ║
║                                                                ║
║  🔹 XML操作日志                                                ║
║     SystemConfigService.Instance.Xml.Log(sender, "用户名")   ║
║     .Log("用户名", "操作", "详情")                            ║
║                                                                ║
║  🔹 日志清理                                                   ║
║     SystemConfigService.Instance.CleanOldLogs(               ║
║         LogRetentionDays.Days30,  // 文本日志30天              ║
║         LogRetentionDays.Days60   // XML日志60天              ║
║     )                                                          ║
║                                                                ║
║  🔹 事件订阅                                                   ║
║     SystemConfigService.Instance.LogMessage += OnLog;         ║
║     SystemConfigService.Instance.XmlMessage += OnXml;        ║
║                                                                ║
╚═══════════════════════════════════════════════════════════════╝

7.4 版本历史

版本 日期 说明
1.0 2026-06-11 初始版本,包含INI、Log、XML三大核心功能

相关推荐
艾利克斯冰13 小时前
Java 设计模式-行为型模式(更新中)
java·开发语言·设计模式
星心源七境18 小时前
七境体系全解析:从六韬兵法到AI锁颜,一套贯穿古典智慧与现代应用的成长操作系统
人工智能·设计模式·设计
qq_2975746719 小时前
设计模式系列文章(基础篇第21篇):迭代器模式——遍历聚合解耦,实现统一迭代访问
设计模式·迭代器模式
仙俊红21 小时前
Java 单例模式:类里面为什么可以有自己类型的字段?
java·开发语言·单例模式
禅思院1 天前
前端请求取消与调度完全指南:从 AbortController 到企业级优先级架构
前端·设计模式·前端框架
小bo波1 天前
用匿名内部类优雅地计算方法执行时间
java·设计模式·性能测试·模板方法模式·lambda·代码优化·匿名内部类
写代码的小阿帆1 天前
行为型设计模式之观察者(发布-订阅)模式
设计模式
王_teacher1 天前
23种设计模式全解析(GoF 设计模式)
设计模式·软考·软件设计师·软考中级
阿坤带你走近大数据1 天前
分别介绍下java主流的开发框架、设计模式与对应编程语言的高级特性
java·开发语言·设计模式