web-dev-qa-db-fra.com

Quelle est une bonne stratégie globale de gestion des exceptions pour Unity3D?

Je cherche à faire des trucs de script Unity3D, et j'aimerais mettre en place un système global de gestion des exceptions. Ce n'est pas pour fonctionner dans la version finale du jeu, l'intention est de détecter les exceptions dans les scripts utilisateur et également dans les scripts de l'éditeur et de vous assurer qu'elles sont transmises à une base de données pour analyse (et également d'envoyer un e-mail aux développeurs concernés afin qu'ils puissent réparer leur shizzle).

Dans une application Vanilla C #, j'aurais un essai autour de la méthode Main. Dans WPF, j'accrocherais un ou plusieurs des événements d'exception non gérés . Dans l'unité ...?

Jusqu'à présent, le meilleur que j'ai pu trouver est quelque chose comme ceci:

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();

}

Dans d'autres scripts, je dérive BehaviourBase au lieu de MonoBehavior et implémente performUpdate () au lieu de Update (). Je n'ai pas implémenté de version parallèle pour les classes Editor mais je suppose que je devrais faire la même chose là-bas.

Je n'aime pas cette stratégie, cependant, car je devrai la rétroporter sur tous les scripts que nous récupèrerons de la communauté (et je devrai l'appliquer à l'équipe). Les scripts de l'éditeur n'ont pas non plus un seul point d'entrée comparable à MonoBehavior, donc je suppose que je devrais implémenter des versions d'exception des assistants, des éditeurs, etc.

J'ai vu des suggestions sur la capture des messages de journal (par opposition aux exceptions) en utilisant Application.RegisterLogCallback , mais cela me met mal à l'aise car j'aurais besoin d'analyser la chaîne de journal de débogage plutôt que d'avoir accès à la réelle exceptions et stacktraces.

Alors ... quelle est la bonne chose à faire?

22
theodox

Il existe une implémentation fonctionnelle de RegisterLogCallback que j'ai trouvée ici: http://answers.unity3d.com/questions/47659/callback-for-unhandled-exceptions.html

Dans ma propre implémentation, je l'utilise pour appeler mon propre MessageBox.Show au lieu d'écrire dans un fichier journal. J'appelle simplement SetupExceptionHandling à partir de chacune de mes scènes.

    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);
        }
    }

J'ai également maintenant le gestionnaire d'erreurs qui m'envoie un e-mail via cette routine, donc je sais toujours quand mon application se bloque et obtient autant de détails que possible.

        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

Créez un GameObject vide dans votre scène et attachez-y ce script:

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
        }
    }
}

assurez-vous qu'il y a ne instance.

Le reste dépend de toi. Vous pouvez également stocker les journaux dans le système de fichiers , le serveur Web ou stockage cloud .


Notez que DontDestroyOnLoad(gameObject) rend ce GameObject persistant, en l'empêchant d'être détruit en cas de changement de scène.

8
Bizhan

Les développeurs Unity ne nous fournissent tout simplement pas de tels outils. Ils interceptent les exceptions en interne dans le cadre ici et là et les enregistrent sous forme de chaînes, ce qui nous donne Application.logMessageReceived [Threaded]. Donc, si vous avez besoin d'exceptions ou d'être connecté avec votre propre traitement (pas celui de l'unité), je peux penser à:

  1. n'utilisez pas la mécanique du framework, mais utilisez la vôtre afin que l'exception ne soit pas interceptée par le framework

  2. créez votre propre classe implémentant UnityEngine.ILogHandler:

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

Et utilisez-le comme indiqué dans documents officiels pour enregistrer vos exceptions. Mais de cette façon, vous ne recevez pas d'exceptions non gérées et d'exceptions enregistrées à partir de plugins (oui, quelqu'un enregistre les exceptions dans les cadres au lieu de les lancer)

  1. Ou vous pouvez faire une suggestion/demande à l'unité de faire Debug.unityLogger (Debug.logger est déconseillé dans Unity 2017) ont un setter ou un autre mécanisme afin que nous puissions passer le nôtre.

  2. Réglez-le simplement avec réflexion. Mais c'est un hack temporaire et ne fonctionnera pas lorsque l'unité change de code.

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

Remarque: Pour obtenir des traces de pile correctes, vous devez définir StackTraceLogType dans les paramètres de l'éditeur/le code sur ScriptOnly (la plupart du temps, c'est ce dont vous avez besoin, j'ai écrit un article sur la façon dont cela fonctionne) Et , lors de la construction pour iOS, il est dit que l'optimisation des appels de script doit être définie sur lente et sûre

Si vous êtes intéressé, vous pouvez lire le fonctionnement de l'outil d'analyse des plantages. Si vous regardez dans crashlytics (outil de rapport de crash pour Android/ios), vous découvrirez qu'il utilise en interne Application.logMessageReceived et AppDomain.CurrentDomain.UnhandledException événements pour consigner les exceptions C # gérées.

Si vous êtes intéressé par des exemples sur le cadre d'unité interceptant des exceptions, vous pouvez consulter ExecuteEvents.Update Et un autre article de moi avec le test d'exception d'interception dans l'écouteur de clic de bouton peut être trouvé ici .

Un résumé des moyens officiels de consigner l'exception non gérée:

I. Application.logMessageReceived est déclenché lorsqu'une exception se produit sur le thread principal. Il existe des moyens pour que cela se produise:

  1. exception interceptée dans le code c # et connectée via Debug.LogException
  2. exception interceptée dans le code natif (probablement du code c ++ lors de l'utilisation d'il2cpp). Dans ce cas, le code natif appelle Application.CallLogCallback ce qui entraîne un tir Application.logMessageReceived

Remarque: la chaîne StackTrace contiendra "rethrow" lorsque l'exception d'origine a des exceptions internes

II. Application.logMessageReceivedThreaded est déclenché lorsqu'une exception se produit sur n'importe quel thread, y compris main (il est dit dans docs ) Remarque: il doit être thread-safe

III. AppDomain.CurrentDomain.UnhandledException par exemple, est déclenché lorsque:

Vous appelez le code suivant dans l'éditeur:

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

Mais cela provoque un crash sur Android 4.4.2 build build lors de l'utilisation de Unity 5.5.1f1

Remarque: J'ai reproduit certains bogues avec des stackframes manquants à l'unité lors de la journalisation des exceptions et des assertions. J'ai soumis n d'entre eux.

5
Deepscorn

Vous avez mentionné Application.RegisterLogCallback, avez-vous essayé de l'implémenter? Parce que rappel de journalisation renvoie une trace de pile, une erreur et un type d'erreur (avertissement, erreur, etc.).

La stratégie que vous décrivez ci-dessus serait difficile à mettre en œuvre car les MonoBehaviours n'ont pas seulement un point d'entrée unique . Vous devez gérer OnTriggerEvent, OnCollisionEvent, OnGUI, etc. Chacun enveloppant sa logique dans un gestionnaire d'exceptions.

À mon humble avis, la gestion des exceptions est une mauvaise idée ici. Si vous ne relancez pas immédiatement l'exception, vous finirez par propager ces erreurs de manière étrange. Peut-être que Foo s'appuie sur Bar et Bar sur Baz. Supposons que Baz lève une exception qui est interceptée et enregistrée. Ensuite, Bar lève une exception car la valeur dont il a besoin de Baz est incorrecte. Enfin, Foo lève une exception car la valeur qu'il obtenait de Bar n'est pas valide.

2
Jerdak

Vous pouvez utiliser un plugin appelé Reporter pour recevoir un e-mail de journaux de débogage, de trace de pile et de capture d'écran au moment de l'erreur non gérée. La capture d'écran et la trace de pile sont généralement suffisantes pour comprendre la raison de l'erreur. Pour les erreurs sournoises tenaces, vous devez enregistrer davantage de données suspectes, générer et attendre à nouveau l'erreur. J'espère que cela vous aidera.

0
yusuf baklacı