113 lines
4.0 KiB
C#
113 lines
4.0 KiB
C#
using System;
|
||
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace Check.Main.Common
|
||
{
|
||
/// <summary>
|
||
/// 一个线程安全的、基于队列的日志记录器。
|
||
/// 使用一个独立的后台线程来处理文件写入,避免业务线程阻塞。
|
||
/// </summary>
|
||
public static class ThreadSafeLogger
|
||
{
|
||
// 使用线程安全的队列作为日志消息的缓冲区
|
||
private static readonly BlockingCollection<string> _logQueue = new BlockingCollection<string>();
|
||
|
||
// 日志写入线程
|
||
private static Thread _logWriterThread;
|
||
|
||
private static StreamWriter _logFileWriter;
|
||
|
||
// 事件,用于将格式化后的日志消息广播给UI等监听者
|
||
public static event Action<string> OnLogMessage;
|
||
|
||
/// <summary>
|
||
/// 初始化日志记录器,启动后台写入线程。
|
||
/// </summary>
|
||
public static void Initialize()
|
||
{
|
||
try
|
||
{
|
||
string logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||
Directory.CreateDirectory(logDirectory);
|
||
|
||
string logFileName = $"Log_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.txt";
|
||
string logFilePath = Path.Combine(logDirectory, logFileName);
|
||
|
||
_logFileWriter = new StreamWriter(logFilePath, append: true, encoding: Encoding.UTF8) { AutoFlush = true };
|
||
|
||
// 创建并启动后台线程
|
||
_logWriterThread = new Thread(ProcessLogQueue)
|
||
{
|
||
IsBackground = true, // 设置为后台线程,这样主程序退出时它会自动终止
|
||
Name = "LogWriterThread"
|
||
};
|
||
_logWriterThread.Start();
|
||
|
||
Log("日志系统已初始化。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 如果初始化失败,尝试通过事件通知UI
|
||
OnLogMessage?.Invoke($"[CRITICAL] 日志系统初始化失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将一条日志消息添加到队列中。这个方法是线程安全的,且执行速度非常快。
|
||
/// </summary>
|
||
/// <param name="message">原始日志消息。</param>
|
||
public static void Log(string message)
|
||
{
|
||
string formattedMessage = $"[{DateTime.Now:HH:mm:ss.fff}] {message}";
|
||
_logQueue.Add(formattedMessage);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 后台线程的工作方法。它会持续不断地从队列中取出消息并处理。
|
||
/// </summary>
|
||
private static void ProcessLogQueue()
|
||
{
|
||
// GetConsumingEnumerable会阻塞等待,直到有新的项加入队列或队列被标记为已完成
|
||
foreach (string message in _logQueue.GetConsumingEnumerable())
|
||
{
|
||
try
|
||
{
|
||
// 1. 写入文件
|
||
_logFileWriter?.WriteLine(message);
|
||
|
||
// 2. 触发事件,通知UI
|
||
OnLogMessage?.Invoke(message);
|
||
}
|
||
catch
|
||
{
|
||
// 忽略在日志线程本身发生的写入错误
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭日志记录器,释放资源。
|
||
/// </summary>
|
||
public static void Shutdown()
|
||
{
|
||
Log("日志系统正在关闭...");
|
||
|
||
// 标记队列不再接受新的项目。这会让ProcessLogQueue中的循环在处理完所有剩余项后自然结束。
|
||
_logQueue.CompleteAdding();
|
||
|
||
// 等待日志线程处理完所有剩余的日志,最多等待2秒
|
||
_logWriterThread?.Join(2000);
|
||
|
||
// 关闭文件流
|
||
_logFileWriter?.Close();
|
||
_logFileWriter?.Dispose();
|
||
}
|
||
}
|
||
}
|