Unity日志工具重定向与封装

   控制台日志重定向及Debug封装 

 

using UnityEngine;
#if UNITY_EDITOR
using System.Reflection;
using UnityEditor;
using System;
using UnityEditor.Callbacks;
#endif

namespace Demo021
{
    public enum LogColor
    {
        red,
        blue,
        green,
        yellow,
        //······
    }

    // ----- 封装Debug -----

    public class Debuger
    {
        static bool enable = false;
        public static bool Enable
        {
            get { return enable; }
            set { enable = value; }
        }

        //暂停编辑器
        public static void Break()
        {
            Debug.Break();
        }

        //清空控制台
        public static void Clear()
        {
            Assembly assembly = Assembly.GetAssembly(typeof(SceneView));
            Type logEntries = assembly.GetType("UnityEditor.LogEntries");
            Debug.Log(logEntries);
            MethodInfo clearConsoleMethod = logEntries.GetMethod("Clear");
            clearConsoleMethod.Invoke(new object(), null);
        }

        public static void Log(object message)
        {
            if (enable)
                Debug.Log(message);
        }
        public static void Log(object message, UnityEngine.Object context)
        {
            if (enable)

                Debug.Log(message, context);
        }
        //不同文字颜色
        public static void Log(object message, LogColor color)
        {
            if (enable)
            {
                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                sb.AppendFormat("<color={0}>{1}</color>", color.ToString(), message);
                Debug.Log(sb);
            }
        }

        public static void LogWarning(object message)
        {
            if (enable)
                Debug.LogWarning(message);
        }
        public static void LogWarning(object message, UnityEngine.Object context)
        {
            if (enable)
                Debug.LogWarning(message, context);
        }

        public static void LogError(object message)
        {
            if (enable)
                Debug.LogError(message);
        }
        public static void LogError(object message, UnityEngine.Object context)
        {
            if (enable)
                Debug.LogError(message, context);
        }

        //······
    }

    // ----- Log重定向 -----

#if UNITY_EDITOR
    //Log重定向
    //http://dsqiu.iteye.com/blog/2263664
    //https://blog.csdn.net/suifcd/article/details/72553678
    public class DebugRedirect
    {
        const string logCSName = "Debuger.cs";

        static object logListView;
        static object logEntry;
        static FieldInfo logListViewCurrentRow;
        static FieldInfo logEntryCondition;
        static MethodInfo LogEntriesGetEntry;

        static bool GetConsoleWindowListView()
        {
            if (logListView == null)
            {
                Assembly unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow));
                Type consoleWindowType = unityEditorAssembly.GetType("UnityEditor.ConsoleWindow");
                FieldInfo fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);
                EditorWindow consoleWindow = fieldInfo.GetValue(null) as EditorWindow;

                if (consoleWindow == null)
                {
                    logListView = null;
                    return false;
                }

                FieldInfo listViewFieldInfo = consoleWindowType.GetField("m_ListView", BindingFlags.Instance | BindingFlags.NonPublic);
                logListView = listViewFieldInfo.GetValue(consoleWindow);
                logListViewCurrentRow = listViewFieldInfo.FieldType.GetField("row", BindingFlags.Instance | BindingFlags.Public);

                //Type logEntriesType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntries");
                Type logEntriesType = unityEditorAssembly.GetType("UnityEditor.LogEntries");
                LogEntriesGetEntry = logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public);

                //Type logEntryType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntry");
                Type logEntryType = unityEditorAssembly.GetType("UnityEditor.LogEntry");
                logEntry = Activator.CreateInstance(logEntryType);
                logEntryCondition = logEntryType.GetField("condition", BindingFlags.Instance | BindingFlags.Public);
            }
            return true;
        }

        static string GetListViewRowCount(ref int line)
        {
            if (!GetConsoleWindowListView())
                return null;

            int row = (int)logListViewCurrentRow.GetValue(logListView);
            LogEntriesGetEntry.Invoke(null, new object[] { row, logEntry });
            string condition = logEntryCondition.GetValue(logEntry) as string;

            int index = condition.IndexOf(logCSName, StringComparison.Ordinal);
            //不是经过我们封装的日志
            if (index < 0)
                return null;

            int lineIndex = condition.IndexOf(")", index, StringComparison.Ordinal);
            condition = condition.Substring(lineIndex + 2);
            index = condition.IndexOf(".cs:", StringComparison.Ordinal);

            if (index >= 0)
            {
                int lineStartIndex = condition.IndexOf(")", StringComparison.Ordinal);
                int lineEndIndex = condition.IndexOf(")", index, StringComparison.Ordinal);
                string _line = condition.Substring(index + 4, lineEndIndex - index - 4);
                Int32.TryParse(_line, out line);

                condition = condition.Substring(0, index);
                int startIndex = condition.LastIndexOf("/", StringComparison.Ordinal);

                string fileName = condition.Substring(startIndex + 1);
                fileName += ".cs";
                return fileName;
            }
            return null;
        }

        static int openInstanceID;
        static int openLine;

        [OnOpenAssetAttribute(0)]
        public static bool OnOpenAsset(int instanceID, int line)
        {
            //只对控制台的开启进行重定向
            if (!EditorWindow.focusedWindow.titleContent.text.Equals("Console"))
                return false;

            只对开启的脚本进行重定向
            //UnityEngine.Object assetObj = EditorUtility.InstanceIDToObject(instanceID);
            //Type assetType = assetObj.GetType();
            //if (assetType != typeof(UnityEditor.MonoScript))
            //return false;

            if (openInstanceID == instanceID && openLine == line)
            {
                openInstanceID = -1;
                openLine = -1;
                return false;
            }

            openInstanceID = instanceID;
            openLine = line;

            string fileName = GetListViewRowCount(ref line);

            if (string.IsNullOrEmpty(fileName) || !fileName.EndsWith(".cs", StringComparison.Ordinal))
                return false;

            string filter = fileName.Substring(0, fileName.Length - 3);
            filter += " t:MonoScript";
            string[] searchPaths = AssetDatabase.FindAssets(filter);

            for (int i = 0; i < searchPaths.Length; i++)
            {
                string path = AssetDatabase.GUIDToAssetPath(searchPaths[i]);

                if (path.EndsWith(fileName, StringComparison.Ordinal))
                {
                    UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(path, typeof(MonoScript));
                    AssetDatabase.OpenAsset(obj, line);
                    return true;
                }
            }
            return false;
        }
    }

#endif
}

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

应该所有的团队都会自己封装日志工具,除非引擎已经集成了,在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,可以去他的博客和微博上找下,不过还不够完美,反编译的看不到private的 FieldInfo 和 MethdInfo ,这个也很有用。

///    Unity控制台日志开启重定向  ///

在Unity中,一般都会自己手动封装一次日志,或者自定义一些日志输出,比如lua代码的日志输出,双击开启时我们希望重新定向的其他的文件,而不是封装代码里,这里要用到的主要是[OnOpenAssetAttribute(0)],每次文件开启时,先执行自定义的代码,代码里自己重定向文件和行数,然后再选择开启。对于Unity控制台,我们还要用反射取得对应的日志内容。
直接上代码,使用时直接修改logCSName变量,将自己封装的日志类写入即可。

using System.Reflection;
using UnityEditor;
using System;
using UnityEditor.Callbacks;


public class LogRedirect
{
    private const string logCSName = "MyLog.cs";
    private static object logListView;
    private static EditorWindow consoleWindow;
    private static FieldInfo logListViewCurrentRow;
    private static MethodInfo LogEntriesGetEntry;
    private static object logEntry;
    private static FieldInfo logEntryCondition;
    private static int openInstanceID;
    private static int openLine;

    private static bool GetConsoleWindowListView()
    {
        if (logListView == null)
        {
            Assembly unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow));
            Type consoleWindowType = unityEditorAssembly.GetType("UnityEditor.ConsoleWindow");
            FieldInfo fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);
            consoleWindow = fieldInfo.GetValue(null) as EditorWindow;

            if (consoleWindow == null)
            {
                logListView = null;
                return false;
            }

            FieldInfo listViewFieldInfo = consoleWindowType.GetField("m_ListView", BindingFlags.Instance | BindingFlags.NonPublic);
            logListView = listViewFieldInfo.GetValue(consoleWindow);
            logListViewCurrentRow = listViewFieldInfo.FieldType.GetField("row", BindingFlags.Instance | BindingFlags.Public);

            Type logEntriesType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntries");
            LogEntriesGetEntry = logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public);
            Type logEntryType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntry");
            logEntry = Activator.CreateInstance(logEntryType);
            logEntryCondition = logEntryType.GetField("condition", BindingFlags.Instance | BindingFlags.Public);
        }

        return true;
    }


    private static string GetListViewRowCount(ref int line)
    {
        int row = (int)logListViewCurrentRow.GetValue(logListView);
        LogEntriesGetEntry.Invoke(null, new object[] { row, logEntry });
        string condition = logEntryCondition.GetValue(logEntry) as string;

        int index = condition.IndexOf(logCSName);
        if(index < 0)//不是经过我们封装的日志
        {
            return null;
        }

        int lineIndex = condition.IndexOf(")", index);
        condition = condition.Substring(lineIndex + 2);
        index = condition.IndexOf(".cs:");

        if (index >= 0)
        {
            int lineStartIndex = condition.IndexOf(")");
            int lineEndIndex = condition.IndexOf(")", index);
            string _line = condition.Substring(index + 4, lineEndIndex - index - 4);
            Int32.TryParse(_line, out line);

            condition = condition.Substring(0,index);
            int startIndex = condition.LastIndexOf("/");

            string fileName = condition.Substring(startIndex+1);
            fileName += ".cs";
            return fileName;
        }

        return null;
    }

    [OnOpenAssetAttribute(0)]
    public static bool OnOpenAsset(int instanceID, int line)
    {
        if (!EditorWindow.focusedWindow.titleContent.text.Equals("Console"))//只对控制台的开启进行重定向
            return false;

        //UnityEngine.Object assetObj = EditorUtility.InstanceIDToObject(instanceID);
        //Type assetType = assetObj.GetType();
        //if(assetType != typeof(UnityEditor.MonoScript))//只对开启的脚本进行重定向
        //{
        //    return false;
        //}

        if (openInstanceID == instanceID && openLine == line)
        {
            openInstanceID = -1;
            openLine = -1;
            return false;
        }
        openInstanceID = instanceID;
        openLine = line;

        if (!GetConsoleWindowListView())
        {
            return false;
        }


        string fileName = GetListViewRowCount(ref line);

        if (fileName == null)
        {
            return false;
        }

        if (fileName.EndsWith(".cs"))
        {
            string filter = fileName.Substring(0, fileName.Length - 3);
            filter += " t:MonoScript";
            string[] searchPaths = AssetDatabase.FindAssets(filter);

            for (int i = 0; i < searchPaths.Length; i++)
            {
                string path = AssetDatabase.GUIDToAssetPath(searchPaths[i]);

                if (path.EndsWith(fileName))
                {
                    UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(path, typeof(MonoScript));
                    AssetDatabase.OpenAsset(obj, line);
                    return true;
                }
            }
        }


        return false;
    }
}

///

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值