Unity日志工具——封裝,跳轉(zhuǎn)
By D.S.Qiu
尊重他人的勞動,支持原創(chuàng),轉(zhuǎn)載請注明出處:http://dsqiu.
好久沒有寫博客分享了,主要有三個原因:1.iteye博客不支持公式等高級特性的支持(不知道iteye的產(chǎn)品經(jīng)理是怎么想的),就一直想自己搭建一個類似stackedit.io編輯器的博客站點,一直沒有憋出來就一直沒繼續(xù)寫了;2.自己想做的事情太多了(比如像寫一個Visual Studio MFC的那種界面一樣的Unity UGUI編輯工具,寫博客花了太多時間了,可是還是沒有憋出來(憂傷啊);3.之前寫的大部分就沒有什么含量,當然是作為自己學(xué)習(xí)的一個途徑吧,所以還是需要大量的積累先!
應(yīng)該所有的團隊都會自己封裝日志工具,除非引擎已經(jīng)集成了,在Unity也不例外,當時之前的同事封裝了一個有一個很大不爽的地方是:從Unity ConsoleWindow 雙擊日志跳轉(zhuǎn)到代碼總是跳轉(zhuǎn)到封裝類中的函數(shù),而不能直接跳轉(zhuǎn)到調(diào)用封裝類被調(diào)用的地方。
切好在準備新項目,我把原來不夠優(yōu)良的地方都進行了改進直至盡可能的完美。之前一直就知道1.利用反射可以獲取Unity的private FieldInfo和 MethodInfo 可以做很多事情,2.可以利用Unity提供的api調(diào)整到指定的代碼中去,3.Unity提供跳轉(zhuǎn)回調(diào)的機制。算是理論只是具備了,今天來公司就把這個給寫出來了,當然還對LogLevel和StackFrame信息進行了優(yōu)化(之前的有點丑,是13年一個前前同事寫的)。
其實是很簡單的,直接說下思路吧(Unity5.3):
1.記錄通過封裝日志工具的函數(shù)調(diào)用棧信息 StackFrame。
2.添加UnityEditor.Callbacks.OnOpenAssetAttribute(0)的回調(diào)方法,處理從ConsoleWindow雙擊跳轉(zhuǎn)
3.利用反射獲取ConsoleWindow 的 ListeViewState 的 row(當前雙擊的行)和總行數(shù)
4.利用3得到行數(shù)反射獲取LogEntry信息進行匹配獲得對應(yīng)StackFrame
5.調(diào)用AssetDatabase.OpenAsset()即可。
更新到Unity5.3發(fā)現(xiàn),他提供Logger這個類,本來還以為可以實現(xiàn)這些功能,不過簡單測試下來發(fā)現(xiàn)是不行的,我就還不清楚Unity構(gòu)造一個Logger類是干嘛的,搞得我把下面的類名改成LoggerUtility。
貼下完整的代碼:
C#代碼  - /*
- * 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_EDITOR
- using System.Reflection;
- using UnityEditor;
- using UnityEditor.Callbacks;
- #endif
- using 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
- }
-
- }
小結(jié):
其實都沒有什么小結(jié)的,多說幾句:對于這個日志工具我還會進一步增加兩個優(yōu)化:遠程日志和通過字符串反射查詢運行時的值(前端調(diào)試還是沒有后端的來的方便,打斷點太低效了)。雨松MOMO最近分享了很多Editor的小trick,可以去他的博客和微博上找下,這里分享一個他反編譯Unity5.3的Bitbucket代碼 ,不過還不夠完美,反編譯的看不到private的 FieldInfo 和 MethdInfo ,這個也很有用。
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發(fā)郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉(zhuǎn)載請在文首注明出處:http://dsqiu./blog/2263664
更多精彩請關(guān)注D.S.Qiu的博客和微博(ID:靜水逐風(fēng))
|