ti-enxame.com

O que é uma boa estratégia global de tratamento de exceções para o Unity3D?

Estou pensando em fazer algumas coisas sobre scripts do Unity3D e gostaria de configurar o sistema global de tratamento de exceções. Isso não é para rodar na versão de lançamento do jogo, a intenção é capturar exceções nos scripts do usuário e também nos scripts do editor e garantir que eles sejam encaminhados para um banco de dados para análise (e também para enviar email aos desenvolvedores relevantes para que possam conserte seu brilho).

Em um aplicativo Vanilla C #, eu tentaria pegar o método Main. No WPF, eu conectaria um ou mais dos eventos de exceção sem tratamento . Na unidade ...?

Até agora, o melhor que pude apresentar é algo como isto:

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

}

Em outros scripts, derivo o BehaviourBase em vez do MonoBehavior e implemento performUpdate () em vez de Update (). Não implementei uma versão paralela para clases do Editor, mas presumo que teria que fazer a mesma coisa lá.

Não gosto dessa estratégia, no entanto, porque precisarei fazer o backport para todos os scripts que pegarmos da comunidade (e terei que aplicá-la na equipe). Os scripts do Editor também não têm um único ponto de entrada comparável ao MonoBehavior, portanto, suponho que eu teria que implementar versões seguras de exceção de assistentes, editores e assim por diante.

Eu já vi sugestões sobre como capturar mensagens de log (em oposição a exceções) usando Application.RegisterLogCallback , mas isso me deixa desconfortável, porque eu precisaria analisar a cadeia de log de depuração em vez de ter acesso ao real exceções e traços de pilha.

Então ... qual é a coisa certa a fazer?

22
theodox

Existe uma implementação funcional do RegisterLogCallback que eu encontrei aqui: http://answers.unity3d.com/questions/47659/callback-for-unhandled-exceptions.html

Em minha própria implementação, eu o uso para chamar meu próprio MessageBox.Show em vez de gravar em um arquivo de log. Eu apenas chamo SetupExceptionHandling de cada uma das minhas cenas.

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

Agora também tenho o manipulador de erros por e-mail por meio dessa rotina, para que eu sempre saiba quando meu aplicativo falha e obtenha o máximo de detalhes possível.

        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

Crie um GameObject vazio na sua cena e anexe este script a ele:

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

verifique se há ma instância.

O resto é com você. Você também pode armazenar os logs no sistema de arquivos , servidor Web ou armazenamento em nuvem .


Observe que DontDestroyOnLoad(gameObject) torna este GameObject persistente, impedindo que seja destruído em caso de mudança de cena.

8
Bizhan

Os desenvolvedores de unidade simplesmente não nos fornecem ferramentas como essa. Eles capturam exceções internamente na estrutura aqui e ali e as registram como strings, fornecendo-nos o Application.logMessageReceived [Threaded]. Portanto, se você precisar que exceções aconteçam ou que sejam registradas com seu próprio processamento (não da unidade), posso pensar em:

  1. não use a mecânica da estrutura, mas use a sua própria, para que a exceção não seja capturada pela estrutura

  2. faça sua própria classe implementando UnityEngine.ILogHandler:

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

E use-o como dito em documentos oficiais para registrar suas exceções. Mas dessa maneira você não recebe exceções não tratadas e exceções registradas de plug-ins (sim, alguém registra exceções em estruturas em vez de lançá-las)

  1. Ou você pode fazer uma sugestão/solicitação à unidade para fazer Debug.unityLogger (Debug.logger foi descontinuado no Unity 2017) possui setter ou outro mecanismo para que possamos passar por nós mesmos.

  2. Basta definir com reflexão. Mas é um hack temporário e não funcionará quando o código de alteração de unidade.

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

Nota: Para obter os rastreamentos de pilha corretos, você precisa definir StackTraceLogType nas configurações/código do editor como ScriptOnly (na maioria das vezes é o que você precisa, escrevi um artigo sobre como funciona) E , ao criar para iOS, diz-se que a otimização de chamada de script deve ser definida como lenta e segura

Se estiver interessado, você pode ler como a popular ferramenta de análise de falhas funciona. Se você analisar os crashlytics (ferramenta de relatório de falhas para Android/ios), descobrirá que ele usa internamente Application.logMessageReceived e AppDomain.CurrentDomain.UnhandledException eventos para registrar exceções gerenciadas em C #.

Se estiver interessado em exemplos na estrutura de unidade que captura exceções, você pode olhar para ExecuteEvents.Update E outro artigo meu, testando a exceção de captura no ouvinte de clique no botão, pode ser encontrado aqui .

Algum resumo sobre maneiras oficiais de registrar a exceção não tratada:

I. Application.logMessageReceived é acionado quando ocorre uma exceção no thread principal. Existem maneiras de isso acontecer:

  1. exceção capturada no código c # e registrada através de Debug.LogException
  2. exceção capturada no código nativo (provavelmente código c ++ ao usar o il2cpp). Nesse caso, o código nativo chama Application.CallLogCallback que resulta em disparo Application.logMessageReceived

Nota: A string StackTrace conterá "relançamento" quando a exceção original tiver exceções internas

II Application.logMessageReceivedThreaded é acionado quando ocorre uma exceção em qualquer thread, incluindo main (é dito em docs ) Nota: deve ser seguro para threads

III AppDomain.CurrentDomain.UnhandledException por exemplo, é acionado quando:

Você chama o seguinte código no editor:

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

Mas causa falha na versão Android 4.4.2 compilada ao usar o Unity 5.5.1f1

Nota: Eu reproduzi alguns erros com falta de frames de pilha da unidade ao registrar exceções e asserções. Enviei m deles.

5
Deepscorn

Você mencionou Application.RegisterLogCallback, tentou implementá-lo? Porque o registro de retorno de chamada retorna um rastreamento de pilha, um erro e um tipo de erro (aviso, erro, etc.).

A estratégia que você descreveu acima seria difícil de implementar porque os MonoBehaviours não têm apenas um ponto de entrada único . Você precisaria lidar com OnTriggerEvent, OnCollisionEvent, OnGUI e assim por diante. Cada um agrupando sua lógica em um manipulador de exceções.

IMHO, manipulação de exceção é uma má idéia aqui. Se você não lançar imediatamente a exceção, acabará propagando esses erros de maneiras estranhas. Talvez Foo confie em Bar e Bar em Baz. Digamos que Baz lança uma exceção que é capturada e registrada. Então Bar lança uma exceção porque o valor necessário de Baz está incorreto. Finalmente, Foo lança uma exceção porque o valor que estava recebendo de Bar é inválido.

2
Jerdak

Você pode usar um plug-in chamado Reporter para receber um e-mail de logs de depuração, rastreamento de pilha e captura de tela no momento do erro não tratado. Captura de tela e rastreamento de pilha geralmente são suficientes para descobrir o motivo do erro. Para erros sorrateiros teimosos, você deve registrar mais dados suspeitos, criar e aguardar novamente pelo erro. Espero que isso ajude.

0
yusuf baklacı