Unity日志双击后处理

背景交代

在实际开发的过程中,为了使得项目的log能更加的有规范和控制性,通常我们都会对unity自身的Log进行二次包装!

实现

由于unity的Console Log在双击后都是打开某个资源的,所以我们需要监听Unity的Console的双击事件:

双击回调

Unity提供了一系列的回调APi

OnOpenAsset是Unity提供的一个打开资源前的回调;

/// <summary>
/// 双击Unity日志
/// </summary>
/// <param name="instanceId">instance id</param>
/// <param name="line">行数</param>
/// <returns>false:Unity继续默认打开;true:需要自行打开</returns>
[OnOpenAsset(0)]
public static bool DoubleClickLog(int instanceId, int line)
{
    return true;
}

添加以上代码后,我们发现无论是在project中双击资源还是在Console Log中双击资源,都会执行该方法。

因此我们需要在双击的时候检查一下双击的目标是不是在Console Log中:

信息获取

/// <summary>
/// 获取Console Log中的信息
/// </summary>
/// <returns>信息</returns>
private static string GetStackTrace()
{
    var editorWindowAssembly = typeof(EditorWindow).Assembly;
    var consoleWindowType =editorWindowAssembly.GetType("UnityEditor.ConsoleWindow");

    var consoleWindowFieldInfo = consoleWindowType.GetField(
        "ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);

    var consoleWindow = consoleWindowFieldInfo.GetValue(null) as EditorWindow;

    if (consoleWindow != EditorWindow.focusedWindow)
    {
        return null;
    }

    var activeTextFieldInfo = consoleWindowType.GetField(
        "m_ActiveText", BindingFlags.Instance | BindingFlags.NonPublic);

    return (string)activeTextFieldInfo.GetValue(consoleWindow);
}

使用反射的方式获取Unity当前正在使用的Console Log窗口,利用EditorWindow.focusedWindow来判定用户当前是在哪个Window下双击的。

自定义规则

在实际项目中,经常有这样的log

WDFramework.AddressableBridge==>Addressables.InitializeAsync() OnComplete==>Succeeded
UnityEngine.Debug:LogError(Object)
WDFramework.WDLog:Error(Object) (at Packages/framework/Runtime/Log/WDLog.cs:35)
WDFramework.AddressableBridge:OnComplete(AsyncOperationHandle`1) (at Packages/framework/Runtime/ResourceSystem/AddressableBridge.cs:56)
DelegateList`1:Invoke(AsyncOperationHandle`1) (at Library/PackageCache/com.unity.addressables@1.16.1/Runtime/ResourceManager/Util/DelegateList.cs:69)
UnityEngine.ResourceManagement.ResourceManager:Update(Single)
MonoBehaviourCallbackHooks:Update() (at Library/PackageCache/com.unity.addressables@1.16.1/Runtime/ResourceManager/Util/MonoBehaviourCallbackHooks.cs:26)

当我们需要查看该日志输出的位置的时候,直接双击都是跳转到WDLog这个文件下,那么我们古河过滤掉自己写的包装Log文件?

public static bool DoubleClickLog(int instanceId, int line)
{
    var trackInfo = GetStackTrace();
    // 不是在Log界面点击的!
    if (string.IsNullOrEmpty(trackInfo))
        return false;
    return true;
}

通过上文说的,如何不是在log界面点击的,我们先过滤掉。

然后过滤不是自定义log包装的日志!

/// <summary>
/// 需要过滤的cs文件
/// </summary>
private static string keyCS = "WDLog.cs";

DoubleClickLog方法内...
var trackInfo = GetStackTrace();
// 不是在Log界面点击的!
if (string.IsNullOrEmpty(trackInfo))
    return false;
// 不是wdlog的输出
if (!trackInfo.Contains(keyCS))
    return false;
...

然后我们拦截掉自定的输出,取出上一级调用堆栈的文件及其行号,然后通过unity打开就可以了!

/// <summary>
/// 匹配
/// </summary>
private static readonly Regex LogRegex = new Regex(@" \(at (.+)\:(\d+)\)\r?\n");

DoubleClickLog方法内...
// 不是wdlog的输出
if (!trackInfo.Contains(keyCS))
    return false;
var match = LogRegex.Match(trackInfo);
if (!match.Success) return false;

match = match.NextMatch();
if (!match.Success) return false;

var file = match.Groups[1].Value;
var lineId = int.Parse(match.Groups[2].Value);

// 上一级目录
var projectRootPath = Path.GetFullPath(Path.Combine(Application.dataPath, "../"));
InternalEditorUtility.OpenFileAtLineExternal(
    Path.Combine(projectRootPath, file),lineId);
return true;

到此已经完成了?

简单测试了一下,发现和网上其他人写的blog一样,完全覆盖了unity自身的单个文件跳转的问题,所有我们需要小小的修复一下!

补丁

经过一轮测试后,难发现,如果使用者是通过点击具体的栈信息跳转指定文件的,那么DoubleClickLog中的行instanceId就一定和WDLog的id不一致!!

那么需要如何才能获取到WDLog的instanceId呢?

  • instanceId查找gameobject

unity提供了一个EditorUtility.InstanceIDToObject的api可以找到点击的是哪个文件,所以只需要在打开的文件里识别一下是否是打开的log文件就能正确了

...
// 不是wdlog的输出
if (!trackInfo.Contain(keyCS))
    return false;
var className =EditorUtilityInstanceIDToObjec(instanceId).name;
if (className != "WDLog")
{
    return false;
}
...

测试了一下,完全正确!