web-dev-qa-db-ja.com

Unity3Dの優れたグローバルな例外処理戦略は何ですか?

Unity3Dのスクリプト作成を検討しており、グローバルな例外処理システムをセットアップしたいと考えています。これはゲームのリリースバージョンで実行するためのものではありません。ユーザースクリプトおよびエディタースクリプトで例外をキャッチし、分析のためにデータベースに転送されるようにします(また、関連する開発者に電子メールを送信して、開発者ができるようにします)彼らのシズルを修正します)。

バニラC#アプリでは、Mainメソッドの周りに試行錯誤があります。 WPFでは、1つ以上の 未処理の例外イベント をフックします。 Unityで...?

これまでのところ、私が思いつくことができた最高のものは次のようなものです:

using UnityEngine;
using System.Collections;

public abstract class BehaviourBase : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {
        try
        {
            performUpdate();
            print("hello");
        }
        catch (System.Exception e)
        {
            print(e.ToString());
        }

    }

    public abstract void performUpdate();

}

他のスクリプトでは、MonoBehaviorの代わりにBehaviourBaseを派生させ、Update()の代わりにperformUpdate()を実装しています。エディタークラスの並列バージョンを実装していませんが、そこで同じことをする必要があると思います。

ただし、この戦略は好きではありません。コミュニティから取得したすべてのスクリプトにバックポートする必要があるためです(そして、チームに強制する必要があります)。エディタースクリプトには、MonoBehaviorに匹敵する単一のエントリポイントがないため、ウィザードやエディターなどの例外セーフバージョンを実装する必要があると思います。

(Application.RegisterLogCallback を使用して(例外ではなく)ログメッセージをキャッチすることについての提案を見てきましたが、実際のログにアクセスするのではなく、デバッグログ文字列を解析する必要があるため、これは不快になります例外とスタックトレース。

それで...正しいことは何ですか?

22
theodox

私がここで見つけたRegisterLogCallbackの実用的な実装があります: http://answers.unity3d.com/questions/47659/callback-for-unhandled-exceptions.html

私自身の実装では、ログファイルに書き込む代わりに、それを使用して自分のMessageBox.Showを呼び出します。各シーンからSetupExceptionHandlingを呼び出すだけです。

    static bool isExceptionHandlingSetup;
    public static void SetupExceptionHandling()
    {
        if (!isExceptionHandlingSetup)
        {
            isExceptionHandlingSetup = true;
            Application.RegisterLogCallback(HandleException);
        }
    }

    static void HandleException(string condition, string stackTrace, LogType type)
    {
        if (type == LogType.Exception)
        {
            MessageBox.Show(condition + "\n" + stackTrace);
        }
    }

また、エラーハンドラーにこのルーチンを介してメールを送信するようにしたので、アプリがいつクラッシュするかを常に把握し、可能な限り詳細を取得します。

        internal static void ReportCrash(string message, string stack)
    {
        //Debug.Log("Report Crash");
        var errorMessage = new StringBuilder();

        errorMessage.AppendLine("FreeCell Quest " + Application.platform);

        errorMessage.AppendLine();
        errorMessage.AppendLine(message);
        errorMessage.AppendLine(stack);

        //if (exception.InnerException != null) {
        //    errorMessage.Append("\n\n ***INNER EXCEPTION*** \n");
        //    errorMessage.Append(exception.InnerException.ToString());
        //}

        errorMessage.AppendFormat
        (
            "{0} {1} {2} {3}\n{4}, {5}, {6}, {7}x {8}\n{9}x{10} {11}dpi FullScreen {12}, {13}, {14} vmem: {15} Fill: {16} Max Texture: {17}\n\nScene {18}, Unity Version {19}, Ads Disabled {18}",
            SystemInfo.deviceModel,
            SystemInfo.deviceName,
            SystemInfo.deviceType,
            SystemInfo.deviceUniqueIdentifier,

            SystemInfo.operatingSystem,
            Localization.language,
            SystemInfo.systemMemorySize,
            SystemInfo.processorCount,
            SystemInfo.processorType,

            Screen.currentResolution.width,
            Screen.currentResolution.height,
            Screen.dpi,
            Screen.fullScreen,
            SystemInfo.graphicsDeviceName,
            SystemInfo.graphicsDeviceVendor,
            SystemInfo.graphicsMemorySize,
            SystemInfo.graphicsPixelFillrate,
            SystemInfo.maxTextureSize,

            Application.loadedLevelName,
            Application.unityVersion,
            GameSettings.AdsDisabled
        );

        //if (Main.Player != null) {
        //    errorMessage.Append("\n\n ***PLAYER*** \n");
        //    errorMessage.Append(XamlServices.Save(Main.Player));
        //}

        try {
            using (var client = new WebClient()) {
                var arguments = new NameValueCollection();
                //if (loginResult != null)
                //    arguments.Add("SessionId", loginResult.SessionId.ToString());
                arguments.Add("report", errorMessage.ToString());
                var result = Encoding.ASCII.GetString(client.UploadValues(serviceAddress + "/ReportCrash", arguments));
                //Debug.Log(result);
            }
        } catch (WebException e) {
            Debug.Log("Report Crash: " + e.ToString());
        }
    }
12
Bryan Legend

シーンに空のGameObjectを作成し、このスクリプトをそれにアタッチします。

_using UnityEngine;
public class ExceptionManager : MonoBehaviour
{
    void Awake()
    {
        Application.logMessageReceived += HandleException;
        DontDestroyOnLoad(gameObject);
    }

    void HandleException(string logString, string stackTrace, LogType type)
    {
        if (type == LogType.Exception)
        {
             //handle here
        }
    }
}
_

1つのインスタンスがあることを確認します。

後は君しだい。ログをファイルシステムWebサーバー、またはcloud storage


DontDestroyOnLoad(gameObject)は、シーンが変更されたときに破棄されないようにすることで、このGameObjectを永続化することに注意してください。

8
Bizhan

Unity開発者は、そのようなツールを提供していません。それらはフレームワークのあちこちで例外を内部的にキャッチし、それらを文字列としてログに記録し、Application.logMessageReceived [Threaded]を提供します。したがって、例外が発生したり、独自の処理でログに記録したりする必要がある場合(単一ではありません)、次のように考えることができます。

  1. フレームワークの仕組みを使用せず、独自のメカニズムを使用して、フレームワークで例外がキャッチされないようにする

  2. UnityEngine.ILogHandlerを実装する独自のクラスを作成します。

    public interface ILogHandler
    {
        void LogFormat(LogType logType, Object context, string format, params object[] args);
        void LogException(Exception exception, Object context);
    }
    

公式ドキュメント で述べられているようにそれを使用して、例外をログに記録します。しかし、そうすることで、未処理の例外やプラグインからログに記録された例外を受け取りません(そう、誰かが例外をスローする代わりにフレームワークでログに記録します)

  1. または、Debug.unityLoggerDebug.loggerはUnity 2017で非推奨)にセッターまたは他のメカニズムを持たせて独自のものを渡すようにするために、単一性への提案/リクエストを行うことができます。

  2. リフレクションで設定するだけです。しかし、それは一時的なハックであり、コードを統一して変更すると機能しなくなります。

    var field = typeof(UnityEngine.Debug)
        .GetField("s_Logger", BindingFlags.Static | BindingFlags.NonPublic);
    field.SetValue(null, your_debug_logger);
    

注:正しいスタックトレースを取得するには、エディター設定/コードで StackTraceLogType をScriptOnlyに設定する必要があります(ほとんどの場合、これは必要なものですが、 article on it it works)そして、iOS向けにビルドする場合、 スクリプト呼び出しの最適化を低速に設定し、安全

興味があれば、人気のあるクラッシュ分析ツールの仕組みを読むことができます。 crashlytics(Android/iosのクラッシュレポートツール)を調べると、内部でApplication.logMessageReceivedおよびAppDomain.CurrentDomain.UnhandledExceptionイベントを使用してマネージC#の例外をログに記録していることがわかります。

例外をキャッチするユニティフレームワークの例に興味がある場合は、 ExecuteEvents.Update を参照してください。ボタンクリックリスナーで例外をキャッチすることをテストした私の別の記事では、 ここにあります

未処理の例外をログに記録するための公式の方法に関するいくつかの要約:

I. Application.logMessageReceivedは、メインスレッドで例外が発生したときに発生します。それが起こる方法があります:

  1. 例外がc#コードでキャッチされ、Debug.LogExceptionを通じてログに記録されました
  2. ネイティブコードでキャッチされた例外(おそらくil2cppを使用する場合はc ++コード)。その場合、ネイティブコードはApplication.CallLogCallbackを呼び出し、結果としてApplication.logMessageReceivedが呼び出されます。

注:元の例外に内部例外がある場合、StackTrace文字列には「rethrow」が含まれます

II。 Application.logMessageReceivedThreadedは、メインを含む任意のスレッドで例外が発生したときに発生します( docs に記載されています)注:スレッドセーフである必要があります

III。たとえば、AppDomain.CurrentDomain.UnhandledExceptionは次の場合に発生します。

エディターで次のコードを呼び出します。

 new Thread(() =>
    {
        Thread.Sleep(10000);
        object o = null;
        o.ToString();
    }).Start();

しかし、Unity 5.5.1f1を使用すると、Android 4.4.2リリースビルドでクラッシュが発生します。

注:例外とアサーションをログに記録するときに、スタックフレームが1つなくなっているバグをいくつか再現しました。提出したのは 1つ です。

5
Deepscorn

Application.RegisterLogCallbackについて説明しましたが、実装したことはありますか? logging callback は、スタックトレース、エラー、およびエラータイプ(警告、エラーなど)を返すためです。

MonoBehaviourには 単一のエントリポイント しかないため、上で概説した戦略は実装が困難です。 OnTriggerEvent、OnCollisionEvent、OnGUIなどを処理する必要があります。それぞれが例外ハンドラでロジックをラップします。

私見、例外処理はここでは悪い考えです。すぐに例外を再スローしないと、これらのエラーが奇妙な方法で伝播することになります。たぶんフーはバーに依存し、バーはバズに依存している。バズがキャッチしてログに記録される例外をスローするとします。次に、Bazから必要な値が正しくないため、Barは例外をスローします。最後に、Fooは、Barから取得していた値が無効であるため、例外をスローします。

2
Jerdak

Reporter と呼ばれるプラグインを使用して、未処理のエラーの発生時にデバッグログ、スタックトレース、画面キャプチャの電子メールを受信できます。画面キャプチャとスタックトレースは通常、エラーの理由を解明するのに十分です。頑固で卑劣なエラーの場合は、疑わしいデータをさらにログに記録し、ビルドしてエラーを再度待機する必要があります。

0
yusuf baklacı