現在現成的日志組件實在是太多太多,為什麼我還需要自己實現呢?????
需求來源於java的log4j,
[07-31 16:40:00:557:WARN : com.game.engine.thread.ServerThread:117] -> 全局排行榜同步執行器-->ServerThread[全局排行榜同步執行器]執行 執行時間過長:23
簡單的一句日志信息,但是我卻可以很清晰的定位日志輸出的代碼位置;com.game.engine.thread包下面的ServerThread這個類文件的第117行;
而log4net卻不行,
也許是因為我米有找到正確的對應配置文件、可惜吧~!
於是我打算自己重組日志組件來實現清洗的日志記錄。當然你也可以說,.net平台下面的exception拋錯的話日志也很清晰。
但是有時候我們邏輯錯誤或者是參數錯誤,根本不會拋錯,這種情況下我們沒有得到預期結果的時候只能通過簡單調試找出原因。
那麼很明顯費時,費力,所以我就想得到像log4j那樣打印出清晰的日志。
可能你會問有這個必要嘛?很有必要哦,比如你程序上線一個版本,然後打印的信息和現在最新版本行號已經不符合了,數據流向和清晰度自然而然就不存在多大的意義~!
如果需要找到這樣清晰的日志。那麼就需要得到方法的調用堆棧信息。
查閱文檔發現 System.Diagnostics.StackTrace 類是記錄堆棧信息的
於是開始無盡的測試之行
1 namespace Sz.StackTraceTest 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Test(); 8 Console.ReadLine(); 9 } 10 11 static public void Test() 12 { 13 ShowStackTrace(); 14 } 15 16 static public void ShowStackTrace() 17 { 18 19 StackTrace trace = new StackTrace(); 20 var frames = trace.GetFrames(); 21 foreach (var item in frames) 22 { 23 Console.WriteLine(string.Format("[{0},文件{1},方法{2},行{3}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), item.GetFileName(), item.GetMethod(), item.GetFileLineNumber())); 24 } 25 } 26 27 } 28 }
運行後發現:
並未得到文件等信息,為啥會這樣
// 摘要: // 用調用方的幀初始化 System.Diagnostics.StackTrace 類的新實例。 public StackTrace(); // // 摘要: // 用調用方的幀初始化 System.Diagnostics.StackTrace 類的新實例,可以選擇捕獲源信息。 // // 參數: // fNeedFileInfo: // 如果為 true,則捕獲文件名、行號和列號;否則為 false。 public StackTrace(bool fNeedFileInfo);
查詢了一下StackTrace類的重載得知,應該是默認構造函數並未捕獲信息
那麼重來一次
這次取到了。可是意外的發現,記錄的文件居然 是全路徑。,
顯然這不是我想要的結果。
那麼再試試看有麼有其他路徑。
於是想到,既然有函數,那麼可以通過函數入手啊,函數,肯定有父節啊。
// // 摘要: // 獲取聲明該成員的類。 // // 返回結果: // 聲明該成員的類的 Type 對象。 public abstract Type DeclaringType { get; }
於是找到這個屬性,聲明該函數的對象。那麼肯定會得到類型的完全限定名稱了;
1 namespace Sz.StackTraceTest 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Test(); 8 Console.ReadLine(); 9 } 10 11 static public void Test() 12 { 13 ShowStackTrace(); 14 } 15 16 static public void ShowStackTrace() 17 { 18 19 StackTrace trace = new StackTrace(true); 20 var frames = trace.GetFrames(); 21 foreach (var item in frames) 22 { 23 Console.WriteLine(string.Format("[{0},文件{1},方法{2},行{3}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), item.GetMethod().DeclaringType.FullName, item.GetMethod(), item.GetFileLineNumber())); 24 } 25 } 26 27 } 28 }
看看那運行結果
得到命名空間和類型名了。
但是圖中我們看到,其實我只想Test()中輸出打印的地方加入一個而已,
但是打印出了整個走向,這個時候我們是普通打印日志根本不需要整個的流程走向
再次查看StackTrace類還有一個重載方式
1 // 2 // 摘要: 3 // 從調用方的幀初始化 System.Diagnostics.StackTrace 類的新實例,跳過指定的幀數並可以選擇捕獲源信息。 4 // 5 // 參數: 6 // skipFrames: 7 // 堆棧中的幀數,將從其上開始跟蹤。 8 // 9 // fNeedFileInfo: 10 // 如果為 true,則捕獲文件名、行號和列號;否則為 false。 11 // 12 // 異常: 13 // System.ArgumentOutOfRangeException: 14 // skipFrames 參數為負數。 15 public StackTrace(int skipFrames, bool fNeedFileInfo);
跳過指定幀;
1 namespace Sz.StackTraceTest 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Test(); 8 Console.ReadLine(); 9 } 10 11 static public void Test() 12 { 13 Log("test"); 14 Log("test"); 15 Log("test"); 16 } 17 18 static public void Log(string msg) 19 { 20 StackTrace trace = new StackTrace(1, true); 21 if (trace.GetFrames().Length > 0) 22 { 23 var item = trace.GetFrame(0); 24 Console.WriteLine(string.Format("[{0},文件{1},方法{2},行{3}]:{4}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), item.GetMethod().DeclaringType.FullName, item.GetMethod(), item.GetFileLineNumber(), msg)); 25 } 26 else 27 { 28 Console.WriteLine(string.Format("[{0},文件{1},方法{2},行{3}]:{4}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), null, null, null, msg)); 29 } 30 //var frames = trace.GetFrames(); 31 32 //foreach (var item in frames) 33 //{ 34 // Console.WriteLine(string.Format("[{0},文件{1},方法{2},行{3}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), item.GetMethod().DeclaringType.FullName, item.GetMethod(), item.GetFileLineNumber())); 35 //} 36 } 37 } 38 }
再來看看
ok已完全符合我的需求了。是不是呢???這樣檢查邏輯錯誤的時候方便許多了。
附上我的全部日志組件源碼
組件實現了簡單的配置;
由於日志打印控制還是輸出文本文件都是比較耗時的事情;所以加入線程模型
1 using System; 2 using System.Collections.Generic; 3 using System.Diagnostics; 4 using System.IO; 5 using System.Linq; 6 using System.Text; 7 using System.Threading; 8 9 /** 10 * 11 * @author 失足程序員 12 * @Blog http://www.cnblogs.com/ty408/ 13 * @mail [email protected] 14 * @phone 13882122019 15 * 16 */ 17 namespace Sz 18 { 19 /// <summary> 20 /// 日志輔助 21 /// <para>AppSettings 設置 LogRootPath 為日志的根目錄</para> 22 /// </summary> 23 public class Logger 24 { 25 static ThreadModel logConsoleThread = new ThreadModel("Console Log Thread"); 26 static ThreadModel logFileThread = new ThreadModel("File Log Thread"); 27 static string logInfoPath = "log/info/"; 28 static string logErrorPath = "log/error/"; 29 30 /// <summary> 31 /// 設置日志的輸出根目錄 32 /// </summary> 33 /// <param name="path"></param> 34 static public void SetLogRootPath(string path) 35 { 36 37 logInfoPath = path + logInfoPath; 38 logErrorPath = path + logErrorPath; 39 if (!Directory.Exists(logInfoPath)) { Directory.CreateDirectory(logInfoPath); } 40 if (!Directory.Exists(logErrorPath)) { Directory.CreateDirectory(logErrorPath); } 41 42 } 43 44 static Logger() 45 { 46 47 if (System.Configuration.ConfigurationManager.AppSettings.AllKeys.Contains("LogRootPath")) 48 { 49 string logPath = System.Configuration.ConfigurationManager.AppSettings["LogRootPath"].ToString(); 50 if (!(logPath.EndsWith("\\") || logPath.EndsWith("/"))) 51 { 52 logPath = "\\"; 53 } 54 logInfoPath = logPath + logInfoPath; 55 logErrorPath = logPath + logErrorPath; 56 } 57 if (!Directory.Exists(logInfoPath)) { Directory.CreateDirectory(logInfoPath); } 58 if (!Directory.Exists(logErrorPath)) { Directory.CreateDirectory(logErrorPath); } 59 60 } 61 62 #region 日子寫入文件輔助任務 class LogTaskFile : TaskBase 63 /// <summary> 64 /// 日子寫入文件輔助任務 65 /// </summary> 66 class LogTaskFile : TaskBase 67 { 68 string msg, mathed; 69 Exception exce; 70 StackTrace trace; 71 static readonly StringBuilder sb = new StringBuilder(); 72 public LogTaskFile(StackTrace trace, string mathed, string msg, Exception exce) 73 : base("File Log Task") 74 { 75 this.mathed = mathed; 76 this.trace = trace; 77 this.msg = msg; 78 this.exce = exce; 79 } 80 81 public override void TaskRun() 82 { 83 var frame = trace.GetFrame(0); 84 DateTime dnow = DateTime.Now; 85 sb.Clear(); 86 sb.Append("[") 87 .Append(dnow.NowString()) 88 .Append(mathed.PadRight(5)) 89 .Append(":") 90 .Append(frame.GetMethod().DeclaringType.FullName) 91 .Append(", ") 92 .Append(frame.GetMethod()) 93 .Append(", ") 94 .Append(frame.GetFileLineNumber()) 95 .Append("] ").Append(msg); 96 97 if (exce != null) 98 { 99 sb.AppendLine("") 100 .AppendLine("----------------------Exception--------------------------") 101 .Append(exce.GetType().FullName).Append(": ").AppendLine(exce.Message) 102 .AppendLine(exce.StackTrace) 103 .AppendLine("----------------------Exception--------------------------"); 104 } 105 string logPath = string.Format("{0}info_{1}.log", logInfoPath, dnow.ToString("yyyyMMdd")); 106 107 System.IO.File.AppendAllText(logPath, sb.ToString(), UTF8Encoding.Default); 108 System.IO.File.AppendAllText(logPath, "\r\n", UTF8Encoding.Default); 109 110 logPath = string.Format("{0}error_{1}.log", logErrorPath, dnow.ToString("yyyyMMdd")); 111 System.IO.File.AppendAllText(logPath, sb.ToString(), UTF8Encoding.Default); 112 System.IO.File.AppendAllText(logPath, "\r\n", UTF8Encoding.Default); 113 114 } 115 } 116 #endregion 117 118 #region 日志寫入控制台輸出 class LogTaskConsole : TaskBase 119 /// <summary> 120 /// 日志寫入控制台輸出 121 /// </summary> 122 class LogTaskConsole : TaskBase 123 { 124 string msg, mathed; 125 Exception exce; 126 StackTrace trace; 127 static readonly StringBuilder sb = new StringBuilder(); 128 129 public LogTaskConsole(StackTrace trace, string mathed, string msg, Exception exce) 130 : base("Console Log Task") 131 { 132 this.mathed = mathed; 133 this.trace = trace; 134 this.msg = msg; 135 this.exce = exce; 136 } 137 138 public override void TaskRun() 139 { 140 sb.Clear(); 141 var frame = trace.GetFrame(0); 142 sb.Append("[") 143 .Append(DateTime.Now.NowString()) 144 .Append(mathed.PadRight(5)) 145 .Append(":") 146 .Append(frame.GetMethod().DeclaringType.FullName) 147 //.Append(", ") 148 //.Append(frame.GetMethod()) 149 .Append(", ") 150 .Append(frame.GetFileLineNumber()) 151 .Append("] ").Append(msg); 152 153 if (exce != null) 154 { 155 sb.AppendLine("") 156 .AppendLine("----------------------Exception--------------------------") 157 .Append(exce.GetType().FullName).Append(": ").AppendLine(exce.Message) 158 .AppendLine(exce.StackTrace) 159 .AppendLine("----------------------Exception--------------------------"); 160 } 161 Console.WriteLine(sb.ToString()); 162 } 163 } 164 #endregion 165 166 string name; 167 /// <summary> 168 /// 169 /// </summary> 170 /// <param name="name"></param> 171 public Logger(string name) 172 { 173 this.name = name; 174 } 175 176 /// <summary> 177 /// 輸出到控制台 178 /// </summary> 179 /// <param name="msg"></param> 180 static public void Debug(string msg) 181 { 182 StackTrace trace = new StackTrace(1, true); 183 LogTaskConsole logConsole = new LogTaskConsole(trace, "Debug", msg, null); 184 logConsoleThread.AddTask(logConsole); 185 } 186 187 /// <summary> 188 /// 控制台和文本文件 189 /// </summary> 190 /// <param name="msg"></param> 191 static public void Info(string msg) 192 { 193 AddLog("Info", msg, null); 194 } 195 /// <summary> 196 /// 控制台和文本文件 197 /// </summary> 198 static public void Error(string msg) 199 { 200 AddLog("Error", msg, null); 201 } 202 /// <summary> 203 /// 控制台和文本文件 204 /// </summary> 205 static public void Error(string msg, Exception exception) 206 { 207 AddLog("Error", msg, exception); 208 } 209 210 static void AddLog(string mathed, string msg, Exception exception) 211 { 212 StackTrace trace = new StackTrace(2, true); 213 LogTaskConsole logConsole = new LogTaskConsole(trace, mathed, msg, exception); 214 logConsoleThread.AddTask(logConsole); 215 LogTaskFile logFile = new LogTaskFile(trace, mathed, msg, exception); 216 logFileThread.AddTask(logFile); 217 } 218 } 219 } View Code線程模型的任務類型
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 /** 8 * 9 * @author 失足程序員 10 * @Blog http://www.cnblogs.com/ty408/ 11 * @mail [email protected] 12 * @phone 13882122019 13 * 14 */ 15 namespace Sz 16 { 17 internal abstract class TaskBase 18 { 19 20 public string Name { get; private set; } 21 22 public TaskBase(string name) 23 { 24 this.Name = name; 25 TempAttribute = new ObjectAttribute(); 26 } 27 public ObjectAttribute TempAttribute { get; set; } 28 29 public abstract void TaskRun(); 30 } 31 } View Code線程模型
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 /** 9 * 10 * @author 失足程序員 11 * @Blog http://www.cnblogs.com/ty408/ 12 * @mail [email protected] 13 * @phone 13882122019 14 * 15 */ 16 namespace Sz 17 { 18 /// <summary> 19 /// 線程模型 20 /// </summary> 21 internal class ThreadModel 22 { 23 public bool IsStop = false; 24 /// <summary> 25 /// ID 26 /// </summary> 27 public int ID; 28 29 static int StaticID = 0; 30 31 public ThreadModel(string name) 32 { 33 lock (typeof(ThreadModel)) 34 { 35 StaticID++; 36 } 37 ID = StaticID; 38 System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); 39 thread.Name = name; 40 thread.IsBackground = true; 41 thread.Start(); 42 } 43 44 /// <summary> 45 /// 任務隊列 46 /// </summary> 47 protected System.Collections.Concurrent.ConcurrentQueue<TaskBase> taskQueue = new System.Collections.Concurrent.ConcurrentQueue<TaskBase>(); 48 49 /// <summary> 50 /// 加入任務 51 /// </summary> 52 /// <param name="t"></param> 53 public virtual void AddTask(TaskBase t) 54 { 55 taskQueue.Enqueue(t); 56 //防止線程正在阻塞時添加進入了新任務 57 are.Set(); 58 } 59 60 //通知一個或多個正在等待的線程已發生事件 61 protected ManualResetEvent are = new ManualResetEvent(false); 62 63 protected virtual void Run() 64 { 65 while (true) 66 { 67 while (!taskQueue.IsEmpty) 68 { 69 TaskBase t = null; 70 if (!taskQueue.IsEmpty && taskQueue.TryDequeue(out t)) 71 { 72 try 73 { 74 t.TaskRun();//執行任務 75 t = null; 76 } 77 catch (Exception ex) 78 { 79 Logger.Error("Thread:<" + Thread.CurrentThread.Name + "> TaskRun <" + t.Name + ">", ex); 80 } 81 } 82 } 83 are.Reset(); 84 //隊列為空等待200毫秒繼續 85 are.WaitOne(200); 86 } 87 } 88 } 89 } View Code
到此為止,日志組件完成。如果有需要的或者原因的可以自己加入,數據庫,和mail處理的