日常開發中,常常會在程序部署到生產環境后發現有些問題,但無法直接調試,這就需要用到日志,本來想找一些開源的完善的日志類來實現,但試了幾個都感覺太重。於是意識到一個問題,懶是偷不得的,只好擼起袖子,自己寫一個。這個日志類是基於訂閱模式的,而且是線程安全的,現在分享給大家,希望能給大家帶來幫助。
閑話不多說,直接上代碼。代碼有兩個實現版本(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 }