うにてぃブログ

UnityやUnreal Engineの記事を書いていきます

【Unity】ログをハンドルする (StackTrace 解析もあるよ)

実機でUnityの出力するログを見たいときは
Application.logMessageReceived にイベントを登録することでログをハンドルすることができます

Console を自作するときにも利用できますが、スクリプトコンパイル時の warning の管理が面倒なので
既存の Console の代わりとして利用することはあまりおすすめしません

using System.Collections.Generic;
using UnityEngine;

public class SampleBehaviour : MonoBehaviour
{
    private List<LogData> _logData = new List<LogData>();
    
    private void OnEnable()
    {
        Application.logMessageReceived += HandleLog;
    }

    private void OnDisable()
    {
        Application.logMessageReceived -= HandleLog;
    }

    private void HandleLog(string message, string stackTrace, LogType type)
    {
        _logData.Add(new LogData(message, stackTrace, type));
    }

    private class LogData
    {
        public string Message;
        public string StackTrace;
        public LogType Type;

        public LogData(string message, string stackTrace, LogType type)
        {
            Message = message;
            StackTrace = stackTrace;
            Type = type;
        }
    }
}

ログを解析するクラス

ログデータからスタックトレースを解析できるクラスもついでに記述
ログを受け取った際に利用するとstring周りの処理でゴミが出るので
表示するときに解析するようにするといい感じになります

using System;
using System.Collections.Generic;
using UnityEngine;
using System.Text.RegularExpressions;

[Serializable]
public class LogInfo
{
    public readonly string Message;
    public readonly string DisplayMessage;
    public readonly LogType LogType;
    public readonly List<StackFrame> StackTraces;
    public readonly bool IsCompileError;

    // Unityのエラーメッセージ形式
    private static readonly Regex UnityMessageRegex = new Regex(@"(.*)\((\d+).*\)");

    public LogInfo(string logString, string stackTrace, LogType type)
    {
        Message = new Regex("\r|\n|\r\n").Replace("\n", logString);
        LogType = type;
        
        StackTraces = GetStackTraces(stackTrace);

        var lines = Message.Split('\n');
        var match = UnityMessageRegex.Matches(logString);
        if (match.Count > 0 && lines.Length < 3)
        {
            var filename = match[0].Groups[1].Value;
            var lineNumber = Convert.ToInt32(match[0].Groups[2].Value);

            IsCompileError = true;
            StackTraces.Add(new StackFrame(logString, filename, lineNumber));
        }

        DisplayMessage = GetDisplayMessage(lines);
    }

    private string GetDisplayMessage(string[] lines)
    {
        if (IsCompileError)
            return Message;

        if (!Message.Contains("\n") && StackTraces != null && StackTraces.Count >= 1)
            return string.Format("{0}\n{1}", Message, StackTraces[0].FormattedMethodName);
        
        if (lines != null && lines.Length >= 2)
            return lines[0] + "\n" + lines[1];

        if (lines != null && lines.Length >= 1)
            return lines[0];

        return string.Empty;
    }

    private List<StackFrame> GetStackTraces(string stackTrace)
    {
        var lines = Regex.Split(stackTrace, System.Environment.NewLine); 
        var stacks = new List<StackFrame>(lines.Length + 1);
        foreach (var l in lines)
        {
            var frame = new StackFrame(l);

            if (string.IsNullOrEmpty(frame.FormattedMethodName))
                continue;

            stacks.Add(frame);
        }
        return stacks;
    }
}

[Serializable]
public class StackFrame
{
    public readonly string MethodName;
    public readonly string FormattedMethodName;
    public readonly string DeclaringType;
    public readonly string ParameterSig;
    public readonly int LineNumber;
    public readonly string FileName;
    
    private static readonly Regex StackTraceRegex = new Regex(@"(.*)\:(.*)\s*\(.*\(at (.*):(\d+)");

    public StackFrame(string message, string filename, int lineNumber)
    {
        FileName = filename;
        LineNumber = lineNumber;
        FormattedMethodName = message;
    }

    public StackFrame(string stackFrame)
    {
        var match = StackTraceRegex.Matches(stackFrame);

        FormattedMethodName = stackFrame;
        if (match.Count <= 0)
            return;

        DeclaringType = match[0].Groups[1].Value;
        MethodName = match[0].Groups[2].Value;
        FileName = match[0].Groups[3].Value;
        LineNumber = Convert.ToInt32(match[0].Groups[4].Value);

        string filename = FileName;
        if (!String.IsNullOrEmpty(FileName))
        {
            var startSubNameIndex = FileName.IndexOf("Assets", StringComparison.OrdinalIgnoreCase);
            if (startSubNameIndex > 0)
                filename = FileName.Substring(startSubNameIndex);
        }
        FormattedMethodName = String.Format("{0}.{1}({2}) (at {3}:{4})", DeclaringType, MethodName, ParameterSig, filename, LineNumber);
    }
}