C# 实现简单的日志打印
设计一个线程安全、支持按日期自动分文件、具备自动清理旧日志功能的通用日志类(Logger)。
这个类可以直接集成到项目中使用。
1. 核心日志类代码 (Logger.cs)
你可以直接复制以下代码保存为Logger.cs文件。
usingSystem;usingSystem.IO;usingSystem.Reflection;usingSystem.Security.Permissions;usingSystem.Text;usingSystem.Threading;/// <summary>/// 通用日志记录类 (线程安全)/// </summary>publicclassLogger{#region单例模式privatestaticLogger_instance;privatestaticreadonlyobject_lockObj=newobject();publicstaticLoggerInstance{get{if(_instance==null){lock(_lockObj){if(_instance==null){_instance=newLogger();}}}return_instance;}}#endregion#region配置参数// 日志根目录 (默认在程序运行目录下的 Log 文件夹)privatestring_logRootPath=Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"Log");// 日志文件名前缀privatestring_fileNamePrefix="Log_";// 日志文件后缀privatestring_fileExtension=".txt";// 单个文件最大大小 (字节),默认 10MBprivatelong_maxFileSize=10*1024*1024;// 保留的日志天数 (自动删除超过天数的旧日志)privateint_keepDays=7;// 编码格式privateEncoding_encoding=Encoding.UTF8;#endregion#region私有成员privatereadonlyobject_fileLock=newobject();// 文件写入锁,保证线程安全privateStreamWriter_currentWriter=null;privatestring_currentFileName="";privateTimer_cleanupTimer;// 定时清理旧文件的定时器#endregion/// <summary>/// 私有构造函数/// </summary>privateLogger(){// 初始化目录if(!Directory.Exists(_logRootPath)){Directory.CreateDirectory(_logRootPath);}// 启动定时器,每天凌晨清理一次旧日志 (这里简化为启动后1小时执行,实际项目中可用Quartz等框架)// 这里仅做演示,实际可结合Windows服务或计划任务_cleanupTimer=newTimer(state=>CleanOldFiles(),null,TimeSpan.FromHours(1),TimeSpan.FromHours(24));}/// <summary>/// 初始化日志配置 (可以在程序启动时调用)/// </summary>/// <param name="logPath">日志存储路径</param>/// <param name="maxFileSize">单个文件最大字节</param>/// <param name="keepDays">保留天数</param>publicvoidInit(stringlogPath=null,longmaxFileSize=0,intkeepDays=0){if(!string.IsNullOrEmpty(logPath)){_logRootPath=logPath;if(!Directory.Exists(_logRootPath))Directory.CreateDirectory(_logRootPath);}if(maxFileSize>0)_maxFileSize=maxFileSize;if(keepDays>0)_keepDays=keepDays;}/// <summary>/// 写入日志 (公共接口)/// </summary>/// <param name="level">日志级别</param>/// <param name="content">日志内容</param>/// <param name="ex">异常对象 (可选)</param>publicvoidWrite(LogLevelslevel,stringcontent,Exceptionex=null){try{stringlogLine=FormatLog(level,content,ex);// 确保写入线程安全lock(_fileLock){// 检查文件是否存在或是否需要滚动 (按天或按大小)stringtodayFileName=GetTodayFileName();// 如果文件名变了(新一天)或者文件太大了,关闭旧流,创建新流if(_currentFileName!=todayFileName||(_currentWriter!=null&&_currentWriter.BaseStream.Length>_maxFileSize)){CloseWriter();_currentFileName=todayFileName;}// 如果当前写入器为空,创建新的if(_currentWriter==null){// 追加模式打开文件varfileStream=newFileStream(_currentFileName,FileMode.Append,FileAccess.Write,FileShare.Read);_currentWriter=newStreamWriter(fileStream,_encoding);}// 写入日志_currentWriter.WriteLine(logLine);_currentWriter.Flush();// 立即写入磁盘,防止丢失}}catch(Exception){// 注意:这里为了防止递归死循环,不建议再抛出异常或写入日志。// 在实际生产环境中,可以尝试写入到Windows事件日志作为备选方案。}}#region辅助方法/// <summary>/// 格式化日志行/// </summary>privatestringFormatLog(LogLevelslevel,stringcontent,Exceptionex){StringBuildersb=newStringBuilder();sb.Append($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}]");sb.Append($" [{level}]");// 获取调用者信息 (跳过Logger的Write方法,获取实际调用者的类名/方法名)varstack=newStackTrace(skipFrames:1);varframe=stack.GetFrame(0);varmethod=frame.GetMethod();vartype=method.DeclaringType;sb.Append($" [{type?.Name}.{method.Name}]");sb.Append($" :{content}");if(ex!=null){sb.Append($" | Exception:{ex.Message}| StackTrace:{ex.StackTrace}");}returnsb.ToString();}/// <summary>/// 获取今天的日志文件名/// </summary>privatestringGetTodayFileName(){stringdateStr=DateTime.Now.ToString("yyyy-MM-dd");returnPath.Combine(_logRootPath,$"{_fileNamePrefix}{dateStr}{_fileExtension}");}/// <summary>/// 关闭当前写入流/// </summary>privatevoidCloseWriter(){if(_currentWriter!=null){_currentWriter.Dispose();_currentWriter=null;}}/// <summary>/// 清理过期文件/// </summary>privatevoidCleanOldFiles(){try{if(Directory.Exists(_logRootPath)){varfiles=Directory.GetFiles(_logRootPath,$"{_fileNamePrefix}*{_fileExtension}");DateTimecutoffDate=DateTime.Now.AddDays(-_keepDays);foreach(varfileinfiles){try{// 根据文件名中的日期判断 (例如 Log_2023-10-01.txt)stringfileName=Path.GetFileNameWithoutExtension(file);stringdatePart=fileName.Replace(_fileNamePrefix,"");if(DateTime.TryParseExact(datePart,"yyyy-MM-dd",null,System.Globalization.DateTimeStyles.None,outDateTimefileDate)){if(fileDate<cutoffDate){File.Delete(file);}}}catch(Exceptionex){// 记录删除失败的日志(仅控制台或调试输出,防止死循环)DebugWrite($"清理文件失败{file}:{ex.Message}");}}}}catch{}}/// <summary>/// 调试用的简单控制台输出 (防止Logger自身出错导致程序崩溃)/// </summary>[Conditional("DEBUG")]privatevoidDebugWrite(stringmsg){Console.WriteLine($"[Logger Debug]{DateTime.Now}:{msg}");}#endregion#regionIDisposable Support// 实现IDisposable以确保资源释放publicvoidDispose(){lock(_fileLock){CloseWriter();_cleanupTimer?.Dispose();}}#endregion}/// <summary>/// 日志级别枚举 (与你代码中的 QATE_TOOLS_ENUM_LogLevel 对应)/// </summary>publicenumLogLevels{Debug=0,Info=1,Warning=2,Error=3,Fatal=4}/// <summary>/// 静态扩展类 (方便像你代码里那样直接调用 Logger.Debug(...) )/// </summary>publicstaticclassLog{/// <summary>/// 调试信息/// </summary>publicstaticvoidDebug(stringmsg,Exceptionex=null)=>Logger.Instance.Write(LogLevels.Debug,msg,ex);/// <summary>/// 普通信息/// </summary>publicstaticvoidInfo(stringmsg,Exceptionex=null)=>Logger.Instance.Write(LogLevels.Info,msg,ex);/// <summary>/// 警告/// </summary>publicstaticvoidWarning(stringmsg,Exceptionex=null)=>Logger.Instance.Write(LogLevels.Warning,msg,ex);/// <summary>/// 错误/// </summary>publicstaticvoidError(stringmsg,Exceptionex=null)=>Logger.Instance.Write(LogLevels.Error,msg,ex);/// <summary>/// 致命错误/// </summary>publicstaticvoidFatal(stringmsg,Exceptionex=null)=>Logger.Instance.Write(LogLevels.Fatal,msg,ex);/// <summary>/// 初始化配置/// </summary>publicstaticvoidInit(stringpath=null,longmaxFileSize=0,intkeepDays=0)=>Logger.Instance.Init(path,maxFileSize,keepDays);}2. 使用方法
在你的qate_pcbafct_5g类或其他业务代码中,直接调用即可:
A. 初始化 (在InitAll或程序启动时调用一次)
// 初始化日志配置 (可选)Log.Init(path:Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"Logs"),keepDays:14);// 保留14天B. 记录日志 (替代原来的pAteTools.WriteLog或配合它使用)
你可以直接使用这个类,或者将其封装进你的pAteTools接口。
publicboolMyTestFunction(){try{Log.Info("测试开始执行");// 模拟测试逻辑boolresult=DoSomething();if(result)Log.Info("测试成功完成");elseLog.Warning("测试未通过,但非异常");returnresult;}catch(Exceptionex){// 自动记录异常消息和堆栈Log.Error("测试过程中发生未处理异常",ex);returnfalse;}}3. 代码亮点解析
- 单例模式 (Singleton):
- 保证整个应用程序只有一个
Logger实例,避免多线程同时创建多个文件句柄导致的“文件被占用”错误。
- 保证整个应用程序只有一个
- 线程锁 (Thread Safety):
- 使用
lock (_fileLock)确保在多线程环境下(例如你的ExternalOperationTestItems中可能有异步任务),写入文件是串行的,不会出现日志内容交错混乱的情况。
- 使用
- 按天分文件 (Rolling by Date):
- 生成的文件名为
Log_2023-10-01.txt。这样方便你按天归档,查找特定日期的日志非常快,也不会出现单个日志文件过大(几GB)导致无法打开的情况。
- 生成的文件名为
- 自动清理 (Auto Cleanup):
- 代码中包含了一个简单的定时器逻辑(或你可以在程序启动时调用),会自动删除超过 7 天(可配置)的旧日志,防止硬盘被日志填满。
- Caller Info (调用者信息):
- 利用
StackTrace,日志中会自动打印出是哪个类(Class)和哪个方法(Method)输出的日志,极大方便了定位问题。 - 输出示例:
[2023-10-01 12:00:00] [Error] [qate_pcbafct_5g.DetectVisionTestItems] : 设备连接失败
- 利用
