分享

Unity日志工具

 kiki的号 2017-03-20

Unity日志工具——封装,跳转

 

By D.S.Qiu

尊重他人的劳动,支持原创,转载请注明出处:http://dsqiu.

       

        好久没有写博客分享了,主要有三个原因:1.iteye博客不支持公式等高级特性的支持(不知道iteye的产品经理是怎么想的),就一直想自己搭建一个类似stackedit.io编辑器的博客站点,一直没有憋出来就一直没继续写了;2.自己想做的事情太多了(比如像写一个Visual Studio MFC的那种界面一样的Unity UGUI编辑工具,写博客花了太多时间了,可是还是没有憋出来(忧伤啊);3.之前写的大部分就没有什么含量,当然是作为自己学习的一个途径吧,所以还是需要大量的积累先!

       

        应该所有的团队都会自己封装日志工具,除非引擎已经集成了,在Unity也不例外,当时之前的同事封装了一个有一个很大不爽的地方是:从Unity ConsoleWindow 双击日志跳转到代码总是跳转到封装类中的函数,而不能直接跳转到调用封装类被调用的地方。

 

       切好在准备新项目,我把原来不够优良的地方都进行了改进直至尽可能的完美。之前一直就知道1.利用反射可以获取Unity的private FieldInfo和 MethodInfo 可以做很多事情,2.可以利用Unity提供的api调整到指定的代码中去,3.Unity提供跳转回调的机制。算是理论只是具备了,今天来公司就把这个给写出来了,当然还对LogLevel和StackFrame信息进行了优化(之前的有点丑,是13年一个前前同事写的)。

 

其实是很简单的,直接说下思路吧(Unity5.3):

       1.记录通过封装日志工具的函数调用栈信息 StackFrame。

       2.添加UnityEditor.Callbacks.OnOpenAssetAttribute(0)的回调方法,处理从ConsoleWindow双击跳转

       3.利用反射获取ConsoleWindow 的 ListeViewState 的 row(当前双击的行)和总行数

       4.利用3得到行数反射获取LogEntry信息进行匹配获得对应StackFrame

       5.调用AssetDatabase.OpenAsset()即可。

 

 更新到Unity5.3发现,他提供Logger这个类,本来还以为可以实现这些功能,不过简单测试下来发现是不行的,我就还不清楚Unity构造一个Logger类是干嘛的,搞得我把下面的类名改成LoggerUtility。

 

贴下完整的代码:

C#代码 复制代码 收藏代码
  1. /* 
  2.  * File: Assets/Scripts/Game/Utility/LoggerUtility.cs 
  3.  * Project: **** 
  4.  * Company: Lucky 
  5.  * Code Porter: D.S.Qiu  
  6.  * Create Date: 10/9/2015 10:11:53 PM 
  7.  */  
  8.   
  9. using System;  
  10. using System.Collections.Generic;  
  11. using System.Diagnostics;  
  12. using System.IO;  
  13. using System.Text;  
  14. #if UNITY_EDITOR  
  15. using System.Reflection;  
  16. using UnityEditor;  
  17. using UnityEditor.Callbacks;  
  18. #endif  
  19. using UnityEngine;  
  20. using Debug = UnityEngine.Debug;  
  21.   
  22. namespace Utility  
  23. {  
  24.     public class LogUtility  
  25.     {  
  26.         public enum LogLevel : byte  
  27.         {  
  28.             None = 0,  
  29.             Exception = 1,  
  30.             Error = 2,  
  31.             Warning = 3,  
  32.             Info = 4,  
  33.         }  
  34.   
  35.         public static LogLevel logLevel = LogLevel.Info;  
  36.         public static string infoColor = "#909090";  
  37.         public static string warningColor = "orange";  
  38.         public static string errorColor = "red";  
  39.   
  40.         public static void LogBreak(object message, UnityEngine.Object sender = null)  
  41.         {  
  42.             LogInfo(message, sender);  
  43.             Debug.Break();  
  44.         }  
  45.   
  46.         public static void LogFormat(string format, UnityEngine.Object sender, params object[] message)  
  47.         {  
  48.             if (logLevel >= LogLevel.Info)  
  49.                 LogLevelFormat(LogLevel.Info, string.Format(format, message), sender);  
  50.         }  
  51.   
  52.         public static void LogFormat(string format, params object[] message)  
  53.         {  
  54.             if (logLevel >= LogLevel.Info)  
  55.                 LogLevelFormat(LogLevel.Info, string.Format(format, message), null);  
  56.         }  
  57.   
  58.         public static void LogInfo(object message, UnityEngine.Object sender = null)  
  59.         {  
  60.             if(logLevel >= LogLevel.Info)  
  61.                 LogLevelFormat(LogLevel.Info,message,sender);  
  62.         }  
  63.   
  64.         public static void LogWarning(object message, UnityEngine.Object sender = null)  
  65.         {  
  66.             if (logLevel >= LogLevel.Warning)  
  67.                 LogLevelFormat(LogLevel.Warning, message,  sender);  
  68.         }  
  69.   
  70.         public static void LogError(object message, UnityEngine.Object sender = null)  
  71.         {  
  72.             if (logLevel >= LogLevel.Error)  
  73.             {  
  74.                 LogLevelFormat(LogLevel.Error, message, sender);  
  75.             }  
  76.         }  
  77.   
  78.         public static void LogException(Exception exption, UnityEngine.Object sender = null)  
  79.         {  
  80.             if (logLevel >= LogLevel.Exception)  
  81.             {  
  82.                 LogLevelFormat(LogLevel.Exception, exption, sender);  
  83.             }  
  84.         }  
  85.   
  86.         private static void LogLevelFormat(LogLevel level, object message, UnityEngine.Object sender)  
  87.         {  
  88.             string levelFormat =  level.ToString().ToUpper();  
  89.             StackTrace stackTrace = new StackTrace(true);  
  90.             var stackFrame = stackTrace.GetFrame(2);  
  91. #if UNITY_EDITOR  
  92.             s_LogStackFrameList.Add(stackFrame);  
  93. #endif  
  94.             string stackMessageFormat = Path.GetFileName(stackFrame.GetFileName()) + ":" + stackFrame.GetMethod().Name + "():at line " + stackFrame.GetFileLineNumber();  
  95.             string timeFormat = "Frame:" + Time.frameCount + "," + DateTime.Now.Millisecond + "ms";  
  96.             string objectName = string.Empty;  
  97.             string colorFormat = infoColor;  
  98.             if (level == LogLevel.Warning)  
  99.                 colorFormat = warningColor;  
  100.             else if (level == LogLevel.Error)  
  101.                 colorFormat = errorColor;  
  102.             StringBuilder sb = new StringBuilder();  
  103.             sb.AppendFormat("<color={3}>[{0}][{4}][{1}]{2}</color>", levelFormat, timeFormat, message, colorFormat, stackMessageFormat);  
  104.             Debug.Log(sb,sender);  
  105.         }  
  106.  
  107. #if UNITY_EDITOR  
  108.         private static int s_InstanceID;  
  109.         private static int s_Line = 104;  
  110.         private static List<StackFrame> s_LogStackFrameList = new List<StackFrame>();  
  111.         //ConsoleWindow  
  112.         private static object s_ConsoleWindow;  
  113.         private static object s_LogListView;  
  114.         private static FieldInfo s_LogListViewTotalRows;  
  115.         private static FieldInfo s_LogListViewCurrentRow;  
  116.         //LogEntry  
  117.         private static MethodInfo s_LogEntriesGetEntry;  
  118.         private static object s_LogEntry;  
  119.         //instanceId 非UnityEngine.Object的运行时 InstanceID 为零所以只能用 LogEntry.Condition 判断  
  120.         private static FieldInfo s_LogEntryInstanceId;  
  121.         private static FieldInfo s_LogEntryLine;  
  122.         private static FieldInfo s_LogEntryCondition;  
  123.         static LogUtility()  
  124.         {  
  125.             s_InstanceID = AssetDatabase.LoadAssetAtPath<MonoScript>("Assets/Scripts/Game/Utility/LoggerUtility.cs").GetInstanceID();  
  126.             s_LogStackFrameList.Clear();  
  127.   
  128.             GetConsoleWindowListView();  
  129.         }  
  130.   
  131.         private static void GetConsoleWindowListView()  
  132.         {  
  133.             if (s_LogListView == null)  
  134.             {  
  135.                 Assembly unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow));  
  136.                 Type consoleWindowType = unityEditorAssembly.GetType("UnityEditor.ConsoleWindow");  
  137.                 FieldInfo fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);  
  138.                 s_ConsoleWindow = fieldInfo.GetValue(null);  
  139.                 FieldInfo listViewFieldInfo = consoleWindowType.GetField("m_ListView", BindingFlags.Instance | BindingFlags.NonPublic);  
  140.                 s_LogListView = listViewFieldInfo.GetValue(s_ConsoleWindow);  
  141.                 s_LogListViewTotalRows = listViewFieldInfo.FieldType.GetField("totalRows", BindingFlags.Instance | BindingFlags.Public);  
  142.                 s_LogListViewCurrentRow = listViewFieldInfo.FieldType.GetField("row", BindingFlags.Instance | BindingFlags.Public);  
  143.                 //LogEntries  
  144.                 Type logEntriesType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntries");  
  145.                 s_LogEntriesGetEntry = logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public);  
  146.                 Type logEntryType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntry");  
  147.                 s_LogEntry = Activator.CreateInstance(logEntryType);  
  148.                 s_LogEntryInstanceId = logEntryType.GetField("instanceID", BindingFlags.Instance | BindingFlags.Public);  
  149.                 s_LogEntryLine = logEntryType.GetField("line", BindingFlags.Instance | BindingFlags.Public);  
  150.                 s_LogEntryCondition = logEntryType.GetField("condition", BindingFlags.Instance | BindingFlags.Public);  
  151.             }  
  152.         }  
  153.         private static StackFrame GetListViewRowCount()  
  154.         {  
  155.             GetConsoleWindowListView();  
  156.             if (s_LogListView == null)  
  157.                 return null;  
  158.             else  
  159.             {  
  160.                 int totalRows = (int)s_LogListViewTotalRows.GetValue(s_LogListView);  
  161.                 int row = (int)s_LogListViewCurrentRow.GetValue(s_LogListView);  
  162.                 int logByThisClassCount = 0;  
  163.                 for (int i = totalRows - 1; i >= row; i--)  
  164.                 {  
  165.                     s_LogEntriesGetEntry.Invoke(nullnew object[] { i, s_LogEntry });  
  166.                     string condition = s_LogEntryCondition.GetValue(s_LogEntry) as string;  
  167.                     //判断是否是由LoggerUtility打印的日志  
  168.                     if (condition.Contains("][") && condition.Contains("Frame"))  
  169.                         logByThisClassCount++;  
  170.                 }  
  171.   
  172.                 //同步日志列表,ConsoleWindow 点击Clear 会清理  
  173.                 while (s_LogStackFrameList.Count > totalRows)  
  174.                     s_LogStackFrameList.RemoveAt(0);  
  175.                 if (s_LogStackFrameList.Count >= logByThisClassCount)  
  176.                     return s_LogStackFrameList[s_LogStackFrameList.Count - logByThisClassCount];  
  177.                 return null;  
  178.             }  
  179.         }  
  180.   
  181.         [UnityEditor.Callbacks.OnOpenAssetAttribute(0)]  
  182.         public static bool OnOpenAsset(int instanceID, int line)  
  183.         {  
  184.             if (instanceID == s_InstanceID && s_Line == line)  
  185.             {  
  186.                 var stackFrame = GetListViewRowCount();  
  187.                 if (stackFrame != null)  
  188.                 {  
  189.                     string fileName = stackFrame.GetFileName();  
  190.                     string fileAssetPath = fileName.Substring(fileName.IndexOf("Assets"));  
  191.                     AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<MonoScript>(fileAssetPath), stackFrame.GetFileLineNumber());  
  192.                     return true;  
  193.                 }  
  194.             }  
  195.              
  196.             return false;  
  197.         }  
  198. #endif  
  199.     }  
  200.   
  201. }  

 

 小结:

        其实都没有什么小结的,多说几句:对于这个日志工具我还会进一步增加两个优化:远程日志和通过字符串反射查询运行时的值(前端调试还是没有后端的来的方便,打断点太低效了)。雨松MOMO最近分享了很多Editor的小trick,可以去他的博客和微博上找下,这里分享一个他反编译Unity5.3的Bitbucket代码 ,不过还不够完美,反编译的看不到private的 FieldInfo 和 MethdInfo ,这个也很有用。

 

       欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!

 

       如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

       转载请在文首注明出处:http://dsqiu./blog/2263664

更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)

      

 

 

 

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多