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

来源:互联网 发布:苹果numbers是什么软件 编辑:程序博客网 时间:2024/06/02 01:41

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

好久没有写博客分享了,主要有三个原因:
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。

贴下完整的代码:

/* * File: Assets/Scripts/Game/Utility/LoggerUtility.cs * Project: **** * Company: Lucky * Code Porter: D.S.Qiu  * Create Date: 10/9/2015 10:11:53 PM */using System;using System.Collections.Generic;using System.Diagnostics;using System.IO;using System.Text;#if UNITY_EDITORusing System.Reflection;using UnityEditor;using UnityEditor.Callbacks;#endifusing UnityEngine;using Debug = UnityEngine.Debug;namespace Utility{    public class LogUtility    {        public enum LogLevel : byte        {            None = 0,            Exception = 1,            Error = 2,            Warning = 3,            Info = 4,        }        public static LogLevel logLevel = LogLevel.Info;        public static string infoColor = "#909090";        public static string warningColor = "orange";        public static string errorColor = "red";        public static void LogBreak(object message, UnityEngine.Object sender = null)        {            LogInfo(message, sender);            Debug.Break();        }        public static void LogFormat(string format, UnityEngine.Object sender, params object[] message)        {            if (logLevel >= LogLevel.Info)                LogLevelFormat(LogLevel.Info, string.Format(format, message), sender);        }        public static void LogFormat(string format, params object[] message)        {            if (logLevel >= LogLevel.Info)                LogLevelFormat(LogLevel.Info, string.Format(format, message), null);        }        public static void LogInfo(object message, UnityEngine.Object sender = null)        {            if(logLevel >= LogLevel.Info)                LogLevelFormat(LogLevel.Info,message,sender);        }        public static void LogWarning(object message, UnityEngine.Object sender = null)        {            if (logLevel >= LogLevel.Warning)                LogLevelFormat(LogLevel.Warning, message,  sender);        }        public static void LogError(object message, UnityEngine.Object sender = null)        {            if (logLevel >= LogLevel.Error)            {                LogLevelFormat(LogLevel.Error, message, sender);            }        }        public static void LogException(Exception exption, UnityEngine.Object sender = null)        {            if (logLevel >= LogLevel.Exception)            {                LogLevelFormat(LogLevel.Exception, exption, sender);            }        }        private static void LogLevelFormat(LogLevel level, object message, UnityEngine.Object sender)        {            string levelFormat =  level.ToString().ToUpper();            StackTrace stackTrace = new StackTrace(true);            var stackFrame = stackTrace.GetFrame(2);#if UNITY_EDITOR            s_LogStackFrameList.Add(stackFrame);#endif            string stackMessageFormat = Path.GetFileName(stackFrame.GetFileName()) + ":" + stackFrame.GetMethod().Name + "():at line " + stackFrame.GetFileLineNumber();            string timeFormat = "Frame:" + Time.frameCount + "," + DateTime.Now.Millisecond + "ms";            string objectName = string.Empty;            string colorFormat = infoColor;            if (level == LogLevel.Warning)                colorFormat = warningColor;            else if (level == LogLevel.Error)                colorFormat = errorColor;            StringBuilder sb = new StringBuilder();            sb.AppendFormat("<color={3}>[{0}][{4}][{1}]{2}</color>", levelFormat, timeFormat, message, colorFormat, stackMessageFormat);            Debug.Log(sb,sender);        }#if UNITY_EDITOR        private static int s_InstanceID;        private static int s_Line = 104;        private static List<StackFrame> s_LogStackFrameList = new List<StackFrame>();        //ConsoleWindow        private static object s_ConsoleWindow;        private static object s_LogListView;        private static FieldInfo s_LogListViewTotalRows;        private static FieldInfo s_LogListViewCurrentRow;        //LogEntry        private static MethodInfo s_LogEntriesGetEntry;        private static object s_LogEntry;        //instanceId 非UnityEngine.Object的运行时 InstanceID 为零所以只能用 LogEntry.Condition 判断        private static FieldInfo s_LogEntryInstanceId;        private static FieldInfo s_LogEntryLine;        private static FieldInfo s_LogEntryCondition;        static LogUtility()        {            s_InstanceID = AssetDatabase.LoadAssetAtPath<MonoScript>("Assets/Scripts/Game/Utility/LoggerUtility.cs").GetInstanceID();            s_LogStackFrameList.Clear();            GetConsoleWindowListView();        }        private static void GetConsoleWindowListView()        {            if (s_LogListView == null)            {                Assembly unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow));                Type consoleWindowType = unityEditorAssembly.GetType("UnityEditor.ConsoleWindow");                FieldInfo fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);                s_ConsoleWindow = fieldInfo.GetValue(null);                FieldInfo listViewFieldInfo = consoleWindowType.GetField("m_ListView", BindingFlags.Instance | BindingFlags.NonPublic);                s_LogListView = listViewFieldInfo.GetValue(s_ConsoleWindow);                s_LogListViewTotalRows = listViewFieldInfo.FieldType.GetField("totalRows", BindingFlags.Instance | BindingFlags.Public);                s_LogListViewCurrentRow = listViewFieldInfo.FieldType.GetField("row", BindingFlags.Instance | BindingFlags.Public);                //LogEntries                Type logEntriesType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntries");                s_LogEntriesGetEntry = logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public);                Type logEntryType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntry");                s_LogEntry = Activator.CreateInstance(logEntryType);                s_LogEntryInstanceId = logEntryType.GetField("instanceID", BindingFlags.Instance | BindingFlags.Public);                s_LogEntryLine = logEntryType.GetField("line", BindingFlags.Instance | BindingFlags.Public);                s_LogEntryCondition = logEntryType.GetField("condition", BindingFlags.Instance | BindingFlags.Public);            }        }        private static StackFrame GetListViewRowCount()        {            GetConsoleWindowListView();            if (s_LogListView == null)                return null;            else            {                int totalRows = (int)s_LogListViewTotalRows.GetValue(s_LogListView);                int row = (int)s_LogListViewCurrentRow.GetValue(s_LogListView);                int logByThisClassCount = 0;                for (int i = totalRows - 1; i >= row; i--)                {                    s_LogEntriesGetEntry.Invoke(null, new object[] { i, s_LogEntry });                    string condition = s_LogEntryCondition.GetValue(s_LogEntry) as string;                    //判断是否是由LoggerUtility打印的日志                    if (condition.Contains("][") && condition.Contains("Frame"))                        logByThisClassCount++;                }                //同步日志列表,ConsoleWindow 点击Clear 会清理                while (s_LogStackFrameList.Count > totalRows)                    s_LogStackFrameList.RemoveAt(0);                if (s_LogStackFrameList.Count >= logByThisClassCount)                    return s_LogStackFrameList[s_LogStackFrameList.Count - logByThisClassCount];                return null;            }        }        [UnityEditor.Callbacks.OnOpenAssetAttribute(0)]        public static bool OnOpenAsset(int instanceID, int line)        {            if (instanceID == s_InstanceID && s_Line == line)            {                var stackFrame = GetListViewRowCount();                if (stackFrame != null)                {                    string fileName = stackFrame.GetFileName();                    string fileAssetPath = fileName.Substring(fileName.IndexOf("Assets"));                    AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<MonoScript>(fileAssetPath), stackFrame.GetFileLineNumber());                    return true;                }            }            return false;        }#endif    }}

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

   欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!   如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。   转载请在文首注明出处:http://dsqiu.iteye.com/blog/2263664

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

0 0