[原创]分享一个轻量级日志类
程序员文章站
2022-07-10 23:50:24
日常开发中,常常会在程序部署到生产环境后发现有些问题,但无法直接调试,这就需要用到日志,本来想找一些开源的完善的日志类来实现,但试了几个都感觉太重。于是意识到一个问题,懒是偷不得的,只好撸起袖子,自己写一个。这个日志类是基于订阅模式的,而且是线程安全的,现在分享给大家,希望能给大家带来帮助。 闲话不 ......
日常开发中,常常会在程序部署到生产环境后发现有些问题,但无法直接调试,这就需要用到日志,本来想找一些开源的完善的日志类来实现,但试了几个都感觉太重。于是意识到一个问题,懒是偷不得的,只好撸起袖子,自己写一个。这个日志类是基于订阅模式的,而且是线程安全的,现在分享给大家,希望能给大家带来帮助。
闲话不多说,直接上代码。代码有两个实现版本(java与c#),这里放出的是c#。
一共用到三个类:jzglogs.cs主类,logbuffer.cs日志缓冲类,loginfo是用于日志缓冲中做记录的实体类,基本原理是把所有待写入的日志写到缓冲中,然后一个线程轮询缓冲,发现有需要写入的数据,就写入,否则就下一次。
no.1 jzglogs.cs
1 using system; 2 using system.collections.generic; 3 using system.linq; 4 using system.text; 5 using system.io; 6 using system.text.regularexpressions; 7 using system.threading; 8 9 /**************************************************************************** 10 单例类 :日志记录类 11 作者 :贾渊 12 版本 :1.5 13 上一版本 :1.4 14 更新日志 : 15 2018-07-06 创建 16 2018-08-06 增加了属性logmode,用于改变记录日志的方式 17 2018-12-26 修改了读写文件的线程锁定方式 18 2019-01-14 改进日志写入的时的对象锁定方式 19 2019-01-15 改进了日志写入的方式为多线程队列写入,操你大爷的lock,又慢又烂 20 ****************************************************************************/ 21 22 namespace jzg.logs 23 { 24 /// <summary> 25 /// 日志记录类 26 /// </summary> 27 public class jzglogs : idisposable 28 { 29 /// <summary> 30 /// 记录类型:消息 31 /// </summary> 32 public const string logtype_info = "info"; 33 /// <summary> 34 /// 记录类型:错误 35 /// </summary> 36 public const string logtype_error = "error"; 37 38 //线程锁 39 private static readonly object locker = new object(); 40 41 //日志记录路径 42 private string logpath = ""; 43 44 //日志记录方式 45 private logmode logmode = logmode.lmall; 46 47 /// <summary> 48 /// 日志记录方式,从枚举logmode中取值 49 /// </summary> 50 public logmode logmode 51 { 52 get 53 { 54 return logmode; 55 } 56 57 set 58 { 59 logmode = value; 60 } 61 } 62 63 /// <summary> 64 /// 私有构造方法 65 /// </summary> 66 private jzglogs() 67 { 68 //默认为全记录 69 logmode = logmode.lmall; 70 //创建线程安全的消息队列 71 logqueue = new logbuffer(); 72 //开启写入线程 73 writethread = new thread(flushbuffer); 74 writethread.isbackground = true; 75 writethread.start(); 76 } 77 78 private logbuffer logqueue; 79 80 //是否停止处理缓存 81 private volatile bool _flushalive = true; 82 83 private void flushbuffer() 84 { 85 while (_flushalive) 86 { 87 loginfo loginfo = logqueue.readbuffer(); 88 if (loginfo == null) 89 { 90 //如果没有要写入的内容则延时100毫秒再看 91 thread.sleep(200); 92 } 93 else 94 { 95 try 96 { 97 using (filestream fs = new filestream(loginfo.logfile, filemode.openorcreate, fileaccess.write, fileshare.readwrite)) 98 { 99 using (streamwriter sw = new streamwriter(fs)) 100 { 101 sw.basestream.seek(0, seekorigin.end); 102 sw.writeline(loginfo.logcontent); 103 sw.flush(); 104 } 105 } 106 } 107 catch (exception) 108 { 109 //出错就不管丫的 110 } 111 } 112 } 113 } 114 115 private static jzglogs instance = null; 116 117 /// <summary> 118 /// 获取或设置日志记录路径 119 /// </summary> 120 public string logpath 121 { 122 get 123 { 124 //补齐路径结束的\符号 125 if (!logpath.endswith("\\")) 126 { 127 logpath += "\\"; 128 } 129 return logpath; 130 } 131 132 set 133 { 134 logpath = value; 135 } 136 } 137 138 139 /// <summary> 140 /// 静态方法:获取实例(单例模式) 141 /// </summary> 142 /// <returns></returns> 143 [obsolete("该方法已过时,推荐使用静态属性instance代替")] 144 public static jzglogs getinstance() 145 { 146 //2019-01-14 改为二次验证单例模式,提高了性能和并发安全性 147 if (instance == null) 148 { 149 lock (locker) 150 { 151 if (instance == null) 152 { 153 var tmp = new jzglogs(); 154 thread.memorybarrier(); 155 instance = tmp; 156 } 157 } 158 } 159 160 return instance; 161 } 162 163 /// <summary> 164 /// 静态属性:单例实例 165 /// </summary> 166 public static jzglogs instance 167 { 168 get 169 { 170 //2019-01-14 改为二次验证单例模式,提高了性能和并发安全性 171 if (instance == null) 172 { 173 lock (locker) 174 { 175 if (instance == null) 176 { 177 var tmp = new jzglogs(); 178 thread.memorybarrier(); 179 instance = tmp; 180 } 181 } 182 } 183 184 return instance; 185 } 186 } 187 188 //写入线程 189 private thread writethread = null; 190 191 /// <summary> 192 /// 记录日志 193 /// </summary> 194 /// <param name="subpath">子路径</param> 195 /// <param name="logtype">记录类型</param> 196 /// <param name="tag">模块标识</param> 197 /// <param name="logcontent">记录内容</param> 198 public void log(string subpath, string logtype, string tag, string logcontent) 199 { 200 //如果未设置路径则抛出错误 201 if (string.isnullorempty(logpath)) 202 { 203 throw new exception("logpath not set"); 204 } 205 206 //判断记录模式 207 bool canlog = (logmode == logmode.lmall) || (logmode == logmode.lmerror && logtype == logtype_error) || (logmode == logmode.lminfo && logtype == logtype_info); 208 //如果不需要记录则直接退出 209 if (!canlog) 210 { 211 return; 212 } 213 214 //当前时间 215 datetime logtime = datetime.now; 216 //记录时间的字符串 217 string logtimestr = logtime.tostring("yyyy/mm/dd hh:mm:ss:fff"); 218 //文件名 219 string filename = string.format("log_{0}.log", datetime.now.tostring("yyyymmdd")); 220 221 //计算子路径 222 string fulllogpath = logpath + subpath; 223 //补齐路径结尾\符号 224 if (!fulllogpath.endswith("\\") && !string.isnullorempty(fulllogpath)) 225 { 226 fulllogpath += "\\"; 227 } 228 //自动创建路径 229 directoryinfo di = new directoryinfo(fulllogpath); 230 if (!di.exists) 231 { 232 di.create(); 233 } 234 235 //文件完整路径 236 string fullfilepath = fulllogpath + filename; 237 //记录格式模板 238 string contenttemplate = "【{0}】 【{1}】 【{2}】 【记录】{3}"; 239 logcontent = regex.replace(logcontent, @"[\n\r]", "");//去掉换行符 240 //计算日志内容 241 string linecontent = string.format(contenttemplate, logtype, logtimestr, tag, logcontent); 242 243 loginfo loginfo = new loginfo() 244 { 245 logfile = fullfilepath, 246 logcontent = linecontent 247 }; 248 249 //写入缓存 250 logqueue.writebuffer(loginfo); 251 } 252 253 /// <summary> 254 /// 记录日志 255 /// </summary> 256 /// <param name="logtype">记录类型</param> 257 /// <param name="tag">模块标识</param> 258 /// <param name="logcontent">记录内容</param> 259 public void log(string logtype, string tag, string logcontent) 260 { 261 log("", logtype, tag, logcontent); 262 } 263 264 /// <summary> 265 /// 释放资源 266 /// </summary> 267 public void dispose() 268 { 269 _flushalive = false; 270 } 271 } 272 273 /// <summary> 274 /// 枚举:日志记录方式 275 /// </summary> 276 public enum logmode 277 { 278 /// <summary> 279 /// 不记录 280 /// </summary> 281 lmnone = 0, 282 /// <summary> 283 /// 全记录 284 /// </summary> 285 lmall = 1, 286 /// <summary> 287 /// 只记录info类型 288 /// </summary> 289 lminfo = 2, 290 /// <summary> 291 /// 只记录error类型 292 /// </summary> 293 lmerror = 3 294 } 295 }
no.2 logbuffer.cs
1 using system; 2 using system.collections.generic; 3 using system.linq; 4 using system.text; 5 using system.collections.concurrent; 6 7 namespace jzg.logs 8 { 9 /// <summary> 10 /// 日志写入缓冲 11 /// </summary> 12 public class logbuffer 13 { 14 private concurrentqueue<loginfo> logs; 15 16 /// <summary> 17 /// 构造方法 18 /// </summary> 19 public logbuffer() 20 { 21 logs = new concurrentqueue<loginfo>(); 22 } 23 24 /// <summary> 25 /// 把日志加入写入队列末端 26 /// </summary> 27 /// <param name="loginfo">要写入的日志对象</param> 28 public void writebuffer(loginfo loginfo) 29 { 30 logs.enqueue(loginfo); 31 } 32 33 /// <summary> 34 /// 从日志中取出开头的一条 35 /// </summary> 36 /// <returns>为null表示队列为空了</returns> 37 public loginfo readbuffer() 38 { 39 loginfo loginfo = null; 40 41 if (!logs.trydequeue(out loginfo)) 42 return null; 43 44 return loginfo; 45 } 46 47 /// <summary> 48 /// 队列中的数量 49 /// </summary> 50 /// <returns></returns> 51 public int count() 52 { 53 return logs.count; 54 } 55 } 56 }
no.3 loginfo.cs
1 using system; 2 using system.collections.generic; 3 using system.linq; 4 using system.text; 5 6 namespace jzg.logs 7 { 8 /// <summary> 9 /// 用于缓存的日志记录实体 10 /// </summary> 11 public class loginfo 12 { 13 /// <summary> 14 /// 要记录到哪个文件里 15 /// </summary> 16 public string logfile 17 { 18 get; set; 19 } 20 21 /// <summary> 22 /// 记录的文字内容 23 /// </summary> 24 public string logcontent 25 { 26 get; set; 27 } 28 } 29 }