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 intention is to catch exceptions in user scripts and also in editor scripts and make sure they are forwarded to a database for analysis (and also to send email to relevant devs so they can fix their shizzle).
In a vanilla C# app I'd have a try-catch around the Main method. In WPF I'd hook one or more of the unhandled exception events. In Unity...?
So far the best I've been able to come up with is something like this:
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(); }
In other scripts, I derive BehaviourBase instead of MonoBehavior and implement performUpdate() instead of Update(). I haven't implemented a parallel version for Editor clases but I assume I'd have to do the same thing there.
I don't like this strategy, however, because I'll have to backport it to any scripts we grab from the community (and I'll have to enforce it on the team). The Editor scripts don't have a single point of entry comparable to MonoBehavior either, so I assume I'd have to implement exception safe versions of wizards, editors and so on.
I've seen suggestions about catching log messages (as opposed to exceptions) using Application.RegisterLogCallback, but this makes me uncomfortable because I'd need to parse the debug log string rather than having access to the actual exceptions and stacktraces.
So... what's the right thing to do?
The Controller Advice class to handle the exception globally is given below. We can define any Exception Handler methods in this class file. The Product Service API controller file is given below to update the Product. If the Product is not found, then it throws the ProductNotFoundException class.
The best practices for Exception Handling in C# are based on logging the exception. The log should be to logging library to keep a record of the exceptions. Log exceptions using log4net, NLog, and other frameworks used for the same purpose.
Adding a Global Exception Handler The New Global Handler window opens. Type in a Name for the handler and save it in the project path. Click Create, a Global Exception Handler is added to the automation project.
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.
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()); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With