你的位置:首页 > ASP.net教程

[ASP.net教程]你的日志组件记录够清晰嘛?


现在现成的日志组件实在是太多太多,为什么我还需要自己实现呢?????

需求来源于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 492794628@qq.com 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 }

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 492794628@qq.com12  * @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 }

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 492794628@qq.com13  * @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 }

View Code

 

到此为止,日志组件完成。如果有需要的或者原因的可以自己加入,数据库,和mail处理的