Какая хорошая стратегия обработки исключений для Unity3D?
Я изучаю некоторые скриптовые материалы Unity3D, и я бы хотел настроить глобальную систему обработки исключений. Это не для запуска в версии релиза игры, целью является исключение из пользовательских сценариев, а также в сценарии редактора и их перенаправление в базу данных для анализа (а также для отправки электронной почты соответствующим разработчикам, чтобы они могли исправить их shizzle).
В приложении с ванилью С# у меня будет try-catch вокруг метода Main. В WPF я зацепил один или несколько необработанных событий исключения. В единстве...?
Пока лучшее, что мне удалось найти, это что-то вроде этого:
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();
}
В других сценариях я получаю BehaviourBase вместо MonoBehavior и реализую performUpdate() вместо Update(). Я не реализовал параллельную версию для редакторов, но я предполагаю, что мне придется делать то же самое.
Мне не нравится эта стратегия, потому что мне нужно будет передать ее в любой скрипт, который мы выберем из сообщества (и мне нужно будет обеспечить его исполнение в команде). Сценарии редактора не имеют ни одной точки входа, сравнимой с MonoBehavior, поэтому я предполагаю, что мне придется внедрять безопасные версии мастеров, редакторов и т.д.
Я видел предложения об улавливании сообщений журнала (в отличие от исключений) с помощью Application.RegisterLogCallback, но это делает меня неудобным, потому что я 'd нужно проанализировать строку журнала отладки, а не иметь доступ к фактическим исключениям и stacktraces.
Итак... что правильно делать?
Ответы
Ответ 1
Существует рабочая реализация 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());
}
}
Ответ 2
Вы упомянули Application.RegisterLogCallback, пытались ли вы его реализовать? Поскольку обратный вызов отсылает обратно трассировку стека, ошибку и тип ошибки (предупреждение, ошибка и т.д.).
Стратегия, описанная выше, будет сложной для реализации, поскольку MonoBehaviours не просто единственная точка входа. Вам придется обрабатывать OnTriggerEvent, OnCollisionEvent, OnGUI и так далее. Каждый из них завершает свою логику в обработчике исключений.
IMHO, обработка исключений - плохая идея здесь. Если вы не сразу перебросите исключение, вы, в конечном итоге, будете распространять эти ошибки по странным путям. Возможно, Foo полагается на Bar и Bar on Baz. Скажем, Baz выдает исключение, которое поймано и зарегистрировано. Затем Bar выдает исключение, потому что значение, которое ему требуется от Baz, неверно. Наконец, Foo выдает исключение, потому что значение, которое оно получало из Bar, недействительно.
Ответ 3
Прикрепите этот script к пустующему игровому объекту в вашей сцене:
using UnityEngine;
public class ExceptionManager : MonoBehaviour
{
void Awake()
{
Application.logMessageReceived += HandleException;
DontDestroyOnLoad(gameObject);
}
void HandleException(string condition, string stackTrace, LogType type)
{
if (type == LogType.Exception)
{
//handle here
}
}
}
убедитесь, что есть один экземпляр.
Ответ 4
Единственные разработчики просто не предоставляют нам таких инструментов. Они ломают исключения внутри в рамках здесь и там и записывают их как строки, предоставляя нам Application.logMessageReceived [Threaded]. Итак, если вам нужны исключения, которые могут произойти или будут зарегистрированы с вашей собственной обработкой (не единственными), я могу подумать:
-
не использовать фреймворк, но использовать свои собственные, поэтому исключение не попадает в рамки
-
создайте собственный класс, реализующий UnityEngine.ILogHandler:
открытый интерфейс ILogHandler { void LogFormat (LogType logType, контекст объекта, строковый формат, params object [] args); void LogException (исключение исключения, контекст объекта); }
И используйте его, как сказано в официальных документах, чтобы регистрировать свои исключения. Но таким образом вы не получаете необработанных исключений и исключений, зарегистрированных из плагинов (да, кто-то делает исключения журналов в фреймворках, а не бросает их)
-
Или вы можете сделать предложение/запрос к единству, чтобы сделать Debug.unityLogger(Debug.logger устарел в Unity 2017) имеют сеттер или другой механизм, чтобы мы могли передавать свои собственные.
-
Просто установите его с отражением. Но это временный взлом и не будет работать, когда код изменения единицы.
var field = typeof (UnityEngine.Debug) .GetField( "s_Logger", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, your_debug_logger);
Примечание. Чтобы получить правильные стеки, вам нужно установить StackTraceLogType в настройках/коде редактора в ScriptOnly (чаще всего это то, что вам нужно, Я написал статью о том, как она работает) И, при создании для iOS, говорится, что Script оптимизация вызовов должна быть медленной и безопасной
Если вам интересно, вы можете прочитать, как работает популярный инструмент анализа сбоев. Если вы посмотрите на crashlytics (инструмент отчета об авариях для android/ios), вы обнаружите, что он внутренне использует события Application.logMessageReceived и AppDomain.CurrentDomain.UnhandledException для регистрации управляемых исключений С#.
Если вы заинтересованы в примерах об единстве, в которых есть исключения, вы можете посмотреть ExecuteEvents.Update И еще одна статья от меня с проверкой ее исключения в прослушивателе кнопок можно найти здесь.
Некоторое резюме по официальным способам регистрации необработанного исключения:
я. Application.logMessageReceived запускается, когда исключение происходит в основном потоке. Есть способы для этого:
- исключение попало в код С# и зарегистрировано через Debug.LogException
- исключение попало в собственный код (возможно, код С++ при использовании il2cpp). В этом случае собственный код вызывает Application.CallLogCallback, что приводит к запуску Application.logMessageReceived
Примечание: строка stacktrace будет содержать "rethrow", когда исходное исключение содержит внутренние исключения
II. Application.logMessageReceivedThreaded запускается, когда исключение происходит в любом потоке, включая main (он указан в docs). Примечание: он должен быть потокобезопасным
III. Например, приложение AppDomain.CurrentDomain.UnhandledException запускается, когда:
Вы вызываете следующий код в редакторе:
new Thread(() =>
{
Thread.Sleep(10000);
object o = null;
o.ToString();
}).Start();
Но это приводит к сбою в выпуске сборки Android 4.4.2 при использовании Unity 5.5.1f1
Примечание. При регистрации исключений и утверждений я воспроизвел некоторые ошибки с единицей, отсутствующими стековыми кадрами. Я отправил один из них.
Ответ 5
Вы можете использовать плагин под названием Reporter для получения электронной почты отладочных журналов, трассировки стека и захвата экрана в момент необработанной ошибки. Трассировки экрана и трассировки стека обычно достаточно, чтобы выяснить причину ошибки. Для упрямых проницательных ошибок вы должны записать больше подозрительных данных, построить и снова ждать ошибки. Надеюсь, это поможет.