当前位置: 首页 > news >正文

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. 代码亮点解析

  1. 单例模式 (Singleton)
    • 保证整个应用程序只有一个Logger实例,避免多线程同时创建多个文件句柄导致的“文件被占用”错误。
  2. 线程锁 (Thread Safety)
    • 使用lock (_fileLock)确保在多线程环境下(例如你的ExternalOperationTestItems中可能有异步任务),写入文件是串行的,不会出现日志内容交错混乱的情况。
  3. 按天分文件 (Rolling by Date)
    • 生成的文件名为Log_2023-10-01.txt。这样方便你按天归档,查找特定日期的日志非常快,也不会出现单个日志文件过大(几GB)导致无法打开的情况。
  4. 自动清理 (Auto Cleanup)
    • 代码中包含了一个简单的定时器逻辑(或你可以在程序启动时调用),会自动删除超过 7 天(可配置)的旧日志,防止硬盘被日志填满。
  5. Caller Info (调用者信息)
    • 利用StackTrace,日志中会自动打印出是哪个类(Class)和哪个方法(Method)输出的日志,极大方便了定位问题。
    • 输出示例:[2023-10-01 12:00:00] [Error] [qate_pcbafct_5g.DetectVisionTestItems] : 设备连接失败
http://www.jsqmd.com/news/648731/

相关文章:

  • Qwen3-14B私有部署:3步完成Java开发环境集成与测试
  • 2026年国内降AI工具和海外降AI工具对比:留学生该怎么选
  • 中山旺来展示现货中岛柜,有哪些款式值得了解?
  • 集鲜鲜肉核心业务模式
  • Z-Image-Turbo LoRA人物一致性解析:跨提示词保持面容/发质/肤色的秘诀
  • Nanbeige4.1-3B提示词工程实践:提升推理准确率的5个关键技巧
  • 【一图看懂】手机里的SIM卡到底能查出什么? | 手机篇
  • 小白入门GLM-4-9B-Chat-1M:vllm部署教程,轻松实现长文本问答
  • Qwen3-VL-WEBUI部署避坑指南:从环境配置到WebUI访问全流程
  • Granite-4.0-H-350M工具调用实战:快速集成外部API
  • PP-DocLayoutV3开发环境配置:确保Windows系统拥有完整的微软运行库支持
  • 小程序如何持续增长?
  • YOLO X Layout开箱即用:免费文档版面分析工具体验
  • Qwen-Image-2512-Pixel-Art-LoRA 结合YOLOv8:为生成的像素画智能添加检测框标注
  • Qwen3-TTS-12Hz-1.7B-Base创意应用:AI广播剧制作全流程
  • C语言数组通关攻略!从一维到字符数组,零基础也能轻松掌握
  • 为什么92%的多模态模型上云后推理延迟飙升300%?:揭秘GPU-IO-NPU三端协同失配的底层真相
  • Rust 生命周期
  • 企业级到产品标准化的转型路径
  • Windows用户的AI绘画捷径:Z-Image-Turbo_UI界面实测体验与效果分享
  • 紧急预警:92%的开源多模态模型在印地语/斯瓦希里语场景存在隐性文化语义漂移!3小时内修复方案已验证
  • 运营版开源代码 多语言跨境商城 跨境电商平台
  • 3步解决显示器色彩过饱和问题:novideo_srgb让你的NVIDIA显卡实现硬件级色彩校准
  • 研一必看!2026年文献管理工具怎么选?实测9款工具后我只推荐这个组合
  • 为什么失业的那么多,社会总体还稳定?这是最近很多人心里最大的疑问。是毕业即失业的焦虑。刷刷短视频,是大厂裁员、中年降薪的哀嚎。数据似乎也在印证这种体感,青年失业率,喊着钱难赚
  • 网站主机介绍
  • S2-Pro算法优化实战:LSTM时间序列预测模型的调参与部署
  • 2026 全球 AI 大模型全景榜单:国产强势崛起,国际格局重塑
  • Leather Dress Collection 实战:自动化生成商品详情页与 SEO 文案
  • 前端最常用的两种请求数据格式application/json 和 multipart/form-data 完全解析