现在现成的日志组件实在是太多太多,为什么我还需要自己实现呢?????
需求来源于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 else27 {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 #endregion117 118 #region 日志写入控制台输出 class LogTaskConsole : TaskBase119 /// <summary>120 /// 日志写入控制台输出121 /// </summary>122 class LogTaskConsole : TaskBase123 {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 #endregion165 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 }
线程模型的任务类型
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 1388212201913 * 14 */15 namespace Sz16 {17 internal abstract class TaskBase18 {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 }
线程模型
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 1388212201914 * 15 */16 namespace Sz17 {18 /// <summary>19 /// 线程模型20 /// </summary> 21 internal class ThreadModel22 {23 public bool IsStop = false;24 /// <summary>25 /// ID26 /// </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 try73 {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 }
到此为止,日志组件完成。如果有需要的或者原因的可以自己加入,数据库,和mail处理的
- 2楼Soar、毅
- 应该就是PDB文件。这个东西其实可以做成ILogger的扩展方法,这样的话最赞了~
- 1楼失足程序员
- 忘记补充一点了,就是堆栈信息需要依赖生成的PDF文件。如果删除pdf文件没办法了
- Re: JeffWong
- @失足程序员,是PDB文件吧?