我正在研究做一些Unity3D脚本的东西,我想建立全局异常处理系统。这不是为了在游戏的发布版本中运行,目的是捕获用户脚本和编辑器脚本中的异常,并确保将它们转发到数据库进行分析(以及向相关开发人员发送电子邮件,以便他们可以修复他们的shizzle)。
在一个vanilla C#应用程序中,我有一个围绕Main方法的try-catch。在WPF中,我会挂钩unhandled exception events中的一个或多个。在Unity ......?
到目前为止,我能想出的最好的东西是这样的:
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()。我没有为Editor clases实现并行版本,但我认为我必须在那里做同样的事情。
但是,我不喜欢这种策略,因为我必须将它反向移植到我们从社区中获取的任何脚本(我必须在团队中强制执行它)。编辑器脚本也没有与MonoBehavior相当的单一入口点,因此我假设我必须实现向导,编辑器等的异常安全版本。
我已经看到了使用Application.RegisterLogCallback捕获日志消息(而不是异常)的建议,但这让我很不舒服,因为我需要解析调试日志字符串而不是访问实际异常和踪迹。
那么......做什么是正确的?
答案 0 :(得分:8)
我在这里找到了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());
}
}
答案 1 :(得分:5)
将此脚本附加到场景中的空游戏对象:
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
}
}
}
确保有一个实例。
答案 2 :(得分:3)
Unity开发人员只是不向我们提供这样的工具。它们在框架内部和内部捕获异常,并将它们记录为字符串,为我们提供Application.logMessageReceived [Threaded]。因此,如果您需要例外情况发生或使用您自己的处理(而不是统一性)记录,我可以想到:
不使用框架机制,但是使用自己的框架机制,因此框架不会捕获
让你自己的类实现UnityEngine.ILogHandler:
公共接口ILogHandler { void LogFormat(LogType logType,Object context,string format,params object [] args); void LogException(异常异常,Object context); }
按照official docs中的说法使用它来记录您的例外情况。但是这样你就不会收到从插件中记录的未处理的异常和异常(是的,有人会在框架中记录异常而不是抛出异常)
或者您可以向Unity提出建议/请求以使Debug.unityLogger(在Unity 2017中不推荐使用Debug.logger)具有setter或其他机制,以便我们可以通过我们自己的。
只需用反射设置即可。但它是暂时的黑客攻击,并且在统一改变代码时不会起作用。
var field = typeof(UnityEngine.Debug) .GetField(" s_Logger",BindingFlags.Static | BindingFlags.NonPublic); field.SetValue(null,your_debug_logger);
注意:要获得正确的堆栈跟踪,您需要在编辑器设置/代码中将StackTraceLogType设置为ScriptOnly(大多数时候它是您需要的,我写了article如何工作)而且,在为iOS构建时,可以说是Script call optimization must be set to slow and safe
如果有兴趣,您可以阅读流行的崩溃分析工具的工作原理。如果你研究一下crashlytics(android / ios的崩溃报告工具),你会发现它在内部使用Application.logMessageReceived和AppDomain.CurrentDomain.UnhandledException事件来记录托管的C#异常。
如果对关于统一框架捕获异常的示例感兴趣,您可以查看ExecuteEvents.Update我可以找到另一篇文章测试它在按钮点击监听器中捕获异常here。
关于记录未处理异常的官方方法的一些摘要:
予。在主线程上发生异常时会触发Application.logMessageReceived。有很多方法可以实现:
注意:stacktrace字符串将包含" rethrow"当原始异常有内部异常时
II。在任何线程上发生异常时会触发Application.logMessageReceivedThreaded,包括main(它在docs中说的)注意:它必须是线程安全的
III。例如,在以下情况下触发AppDomain.CurrentDomain.UnhandledException:
您在编辑器中调用以下代码:
new Thread(() =>
{
Thread.Sleep(10000);
object o = null;
o.ToString();
}).Start();
但是当使用Unity 5.5.1f1
时,它会导致android 4.4.2发布版本崩溃注意:在记录异常和断言时,我复制了一些缺少stackframes的错误。我提交了one他们。
答案 3 :(得分:2)
您提到了Application.RegisterLogCallback,您是否尝试过实现它?因为logging callback传回堆栈跟踪,错误和错误类型(警告,错误等)。
您在上面概述的策略很难实现,因为MonoBehaviours不仅仅有single entry point。您必须处理OnTriggerEvent,OnCollisionEvent,OnGUI等。每个都将其逻辑包装在异常处理程序中。
恕我直言,异常处理在这里是一个坏主意。如果你没有立即重新抛出异常,你最终会以奇怪的方式传播这些错误。也许Foo依赖Bar和Bar on Baz。 Say Baz抛出一个捕获并记录的异常。然后Bar抛出异常,因为Baz需要的值不正确。最后,Foo抛出异常,因为它从Bar获得的值无效。答案 4 :(得分:0)
您可以使用名为Reporter的插件在未处理的错误时刻收到调试日志,堆栈跟踪和屏幕捕获的电子邮件。屏幕捕获和堆栈跟踪通常足以找出错误的原因。对于顽固的偷偷摸摸的错误,你应该记录更多的可疑数据,构建并再次等待错误。我希望这会有所帮助。