【C#】 高效日志打印 Logger代码实现【串口通信日志,服务器日志,异常记录,系统状态监控,程序崩溃 日志也不会丢失】
文章目录
- 1 问题引入 及 分析
- 1.1 特点
- 2 高效日志写入 (多个线程同时调用)
- 3 .代码优化(避免lock对主线程的影响)
-
- 3.1 优化后的源码
- 3.2 化后代码的优势
- 3.3 日志调用示例
- 3.4 如果不调用 StopLogger() 会发生什么?
- 3.5 如何确保 StopLogger() 被调用
- 4 AutoFlush = true 和 Flush() 适用场景
- 5 降低磁盘负担,提高写入性能
-
- 5.1 完整代码
- 5.2 多线程日志写入示例
1 问题引入 及 分析
程序在运行过程中,偶发性的出现故障,时有时无。
为了对运行过程以及程序故障的跟踪,我们对串口数据监控事件
的和TCP监控事件
中的数据及状态都写入日志记录。
下面串口事件和tcp事件TcpClientHelper.getMsgInvoked += TcpClientHelper_getMsgInvoked;
和 SP.ReceivedBytesEventHandler += SP_ReceivedBytesEventHandler;
,对于事件可以理解为线程,这里同时启动两个事件,就相当于开了两个线程。
那么既然那是 对运行过程以及程序故障的跟踪,我们的日志需要具备以下特点:
1.1 特点
-
1 .使用 StreamWriter 并开启 AutoFlush = true:避免频繁打开/关闭文件,提高写入效率。
-
2.使用 File.AppendText():高效地追加写入,不需要每次打开新文件。
-
3.多个线程同时调用:在高并发场景下优化写入性能。
-
4.日志按日期分类存储:每天生成一个新的日志文件,避免单个文件过大影响性能。
-
5.不能能影响 事件处理函数(主线程)的执行效率,尤其是在高频调用的情况下。
-
6 日志不会丢失 ,即使程序崩溃,日志数据也会写入文件。
2 高效日志写入 (多个线程同时调用)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace Helper
{
public static class Logger
{
// **日志文件存储目录**
private static readonly string LogDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log");
// **当前日志文件的路径(按日期命名)**
private static string logFile = GetLogFilePath();
// **记录上一次写日志的日期**
private static DateTime lastLogDate = DateTime.Now.Date;
// **线程锁,确保多线程环境下的安全写入**
private static readonly object lockObj = new object();
///
/// 计算并返回当前日期对应的日志文件路径
///
private static string GetLogFilePath()
{
return Path.Combine(LogDirectory, DateTime.Now.ToString("yyyy-MM-dd") + ".log");
}
///
/// 写日志(高效方式)
///
/// 要记录的日志信息
public static void WriteLog(string message,bool bNormal)
{
lock (lockObj) // 确保多线程写入安全
{
// **检查日期是否变更**
if (DateTime.Now.Date > lastLogDate)
{
lastLogDate = DateTime.Now.Date; // 更新日期
logFile = GetLogFilePath(); // 重新计算日志文件路径
}
try
{
// **确保日志文件夹存在**
if (!Directory.Exists(LogDirectory))
{
Directory.CreateDirectory(LogDirectory);
}
// **使用 StreamWriter 进行高效日志写入**
using (StreamWriter writer = new StreamWriter(logFile, true, Encoding.UTF8))
{
writer.AutoFlush = true;//数据立即写入磁盘,日志不会丢失,即使程序崩溃,日志数据也会写入文件。
// **写入日志内容**
if (bNormal)
writer.WriteLine($"[{
DateTime.Now:yyyy-MM-dd HH:mm:ss}]--[NORMAL]: {
message}");
else
writer.WriteLine($"[{
DateTime.Now:yyyy-MM-dd HH:mm:ss}]--[ERROR]: {
message}");
//writer.Flush(); // 手动刷新缓冲区,仅在需要时调用,减少磁盘频繁刷新负担
}
}
catch (Exception ex)
{
**异常处理(防止日志写入异常影响程序运行)**
//Console.WriteLine("日志写入失败:" + ex.Message);
}
}
}
}
}
足实时性和多线程安全性的需求,但 lock 作用域内打开文件并写入日志,会阻塞主线程,这可能会影响 事件处理函数 的执行效率 尤其是在高频调用的情况下。
所以进一步改进
3 .代码优化(避免lock对主线程的影响)
上面的代码已经 满足实时性和多线程安全性的需求,但 lock 作用域内打开文件并写入日志,会阻塞主线程,这可能会影响 事件处理函数 的执行效率 尤其是在高频调用的情况下。
解决方案:
-
1.使用 ConcurrentQueue 作为日志缓冲区,并在单独的后台线程中写入日志,减少对主线程的影响。
-
2.使用 Task.Run()异步 或 后台线程 处理日志写入,减少 事件处理函数 受到的影响。
3.1 优化后的源码
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Helper
{
public static class Logger
{
// **日志存储目录**
private static readonly string