What's a good global exception handling strategy for Unity3D?

前端 未结 5 1960
悲&欢浪女
悲&欢浪女 2021-02-01 04:40

I\'m looking into doing some Unity3D scripting stuff, and I\'d like to set up global exception handling system. This is not for running in the release version of the game, the i

相关标签:
5条回答
  • 2021-02-01 04:56

    You mentioned Application.RegisterLogCallback, have you tried implementing it? Because the logging callback passes back a stack trace, an error, and an error type (warning, error, etc).

    The strategy you outline above would be tough to implement because MonoBehaviours don't just have a single entry point. You'd have to handle OnTriggerEvent, OnCollisionEvent, OnGUI, and so on. Each one wrapping its logic in an exception handler.

    IMHO, exception handling is a bad idea here. If you don't immediately re-throw the exception, you'll end up propagating those errors in weird ways. Maybe Foo relies on Bar, and Bar on Baz. Say Baz throws an exception that is caught and logged. Then Bar throws an exception because the value it needs from Baz is incorrect. Finally Foo throws an exception because the value it was getting from Bar is invalid.

    0 讨论(0)
  • 2021-02-01 05:04

    Unity devs just do not provide us with tools like that. They catch exceptions internally in framework here and there and log them as strings, giving us Application.logMessageReceived[Threaded]. So, if you need exceptions to happen or be logged with your own processing (not unity's) I can think of:

    1. do not use framework mechanics, but use your own so exception is not caught by framework

    2. make your own class implementing UnityEngine.ILogHandler:

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

    And use it as said in official docs to log your exceptions. But that way you do not receive unhandled exceptions and exceptions logged from plugins (yes, someone do log exceptions in frameworks instead of throwing them)

    1. Or you can make a suggestion/request to unity to make Debug.unityLogger (Debug.logger is deprecated in Unity 2017) have setter or other mechanism so we can pass our own.

    2. Just set it with reflection. But it's temporary hack and will not work when unity change code.

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

    Note: To get correct stacktraces you need to set StackTraceLogType in editor settings/code to ScriptOnly (most times it's what you need, I wrote an article on how it work) And, when building for iOS, it is said that Script call optimization must be set to slow and safe

    If interested, you can read how popular crash analytics tool works. If you look into crashlytics (crash report tool for android/ios), than you'll find out that it internally uses Application.logMessageReceived and AppDomain.CurrentDomain.UnhandledException events to log managed C# exceptions.

    If interested in examples on unity framework catching exceptions, you may look at ExecuteEvents.Update And another article from me with testing it catching exception in button click listener can be found here.

    Some summary on official ways to log unhandled exception:

    I. Application.logMessageReceived is fired when exception happens on main thread. There are ways for it to happen:

    1. exception caught in c# code and logged through Debug.LogException
    2. exception caught in native code (probably c++ code when using il2cpp). In that case native code calls Application.CallLogCallback which results in firing Application.logMessageReceived

    Note: StackTrace string will contain "rethrow" when original exception have inner exceptions

    II. Application.logMessageReceivedThreaded is fired when exception happens on any thread, including main (it's said in docs) Note: it must be thread-safe

    III. AppDomain.CurrentDomain.UnhandledException for example is fired when:

    You call the following code in editor:

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

    But it causes crash on android 4.4.2 release build when using Unity 5.5.1f1

    Note: I reproduced some bugs with unity missing stackframes when logging exceptions and assertions. I submited one of them.

    0 讨论(0)
  • 2021-02-01 05:06

    You can use a plugin called Reporter to receive an email of Debug Logs, Stack trace and screen capture on the moment of unhandled Error. Screen capture and stack trace are usually enough to figure out the reason of the Error. For stubborn sneaky Errors you should log more of suspicious data, build and wait again for the error.I Hope this helps.

    0 讨论(0)
  • 2021-02-01 05:10

    Create an empty GameObject in your scene and attach this script to it:

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

    make sure there is one instance.

    The rest is up to you. You can also store the logs in file system, web server or cloud storage.


    Note that DontDestroyOnLoad(gameObject) makes this GameObject persistent, by preventing it from being destroyed in case of scene change.

    0 讨论(0)
  • 2021-02-01 05:14

    There is a working implementation of RegisterLogCallback that I found here: http://answers.unity3d.com/questions/47659/callback-for-unhandled-exceptions.html

    In my own implementation I use it to call my own MessageBox.Show instead of writing to a log file. I just call SetupExceptionHandling from each of my scenes.

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

    I also now have the error handler email me via this routine, so I always know when my app crashes and get as much detail as 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());
            }
        }
    
    0 讨论(0)
提交回复
热议问题