C# 什么';Unity3D有一个很好的全局异常处理策略吗?

C# 什么';Unity3D有一个很好的全局异常处理策略吗?,c#,unity3d,C#,Unity3d,我正在考虑做一些Unity3D脚本编写的东西,我想建立一个全局异常处理系统。这不是为了在游戏的发行版中运行,目的是捕获用户脚本和编辑器脚本中的异常,并确保它们被转发到数据库进行分析(还向相关开发人员发送电子邮件,以便他们能够修复其漏洞) 在一个普通的C#应用程序中,我会尝试一下主要方法。在WPF中,我会钩住一个或多个。团结一致 到目前为止,我能想到的最好的办法是: using UnityEngine; using System.Collections; public abstract clas

我正在考虑做一些Unity3D脚本编写的东西,我想建立一个全局异常处理系统。这不是为了在游戏的发行版中运行,目的是捕获用户脚本和编辑器脚本中的异常,并确保它们被转发到数据库进行分析(还向相关开发人员发送电子邮件,以便他们能够修复其漏洞)

在一个普通的C#应用程序中,我会尝试一下主要方法。在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();

}
在其他脚本中,我派生BehaviorBase而不是MonoBehavior,并实现performUpdate()而不是Update()。我还没有为编辑器类实现一个并行版本,但我认为我必须在那里做同样的事情

然而,我不喜欢这个策略,因为我必须将它向后移植到我们从社区获取的任何脚本(并且我必须在团队中强制实施它)。编辑器脚本也没有一个可与MonoBehavior相比的入口点,因此我假设我必须实现向导、编辑器等的异常安全版本

我看到过关于使用捕获日志消息(而不是异常)的建议,但这让我感到不舒服,因为我需要解析调试日志字符串,而不是访问实际的异常和堆栈跟踪


所以。。。正确的做法是什么?

您提到Application.RegisterLogCallback,您尝试过实现它吗?因为会传回堆栈跟踪、错误和错误类型(警告、错误等)

您在上面概述的策略将很难实施,因为单一行为不仅仅有。您必须处理OnTriggerEvent、OnCollisionEvent、OnGUI等。每一个都将其逻辑包装在异常处理程序中


嗯,异常处理在这里不是个好主意。如果不立即重新抛出异常,最终将以奇怪的方式传播这些错误。也许富依赖于酒吧,而酒吧依赖于巴兹。假设Baz抛出一个捕获并记录的异常。然后Bar抛出一个异常,因为它需要来自Baz的值不正确。最后,Foo抛出了一个异常,因为它从Bar中获取的值无效。

我在这里找到了RegisterLogCallback的一个工作实现:

在我自己的实现中,我使用它来调用我自己的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());
        }
    }

您可以使用一个名为的插件在出现未处理错误时接收调试日志、堆栈跟踪和屏幕捕获的电子邮件。屏幕捕获和堆栈跟踪通常足以找出错误的原因。对于顽固的秘密错误,您应该记录更多可疑数据,构建并再次等待错误。我希望这会有所帮助。

在场景中创建一个空的游戏对象,并将此脚本附加到其中:

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
        }
    }
}
确保有一个实例

剩下的就看你了。您还可以将日志存储在文件系统、web服务器或云存储中



请注意,
DontDestroyOnLoad(gameObject)
通过防止在场景更改时将其销毁,使该gameObject持久化。

Unity开发者只是没有为我们提供类似的工具。它们在框架内部到处捕获异常,并将它们作为字符串记录,从而为我们提供Application.logMessageReceived[Threaded]。因此,如果您需要发生异常,或者需要使用自己的处理(而不是unity)记录异常,我可以想到:

  • 不要使用框架机制,但要使用自己的机制,这样框架就不会捕获异常

  • 让您自己的类实现
    UnityEngine.ILogHandler

    public interface ILogHandler
    {
        void LogFormat(LogType logType, Object context, string format, params object[] args);
        void LogException(Exception exception, Object context);
    }
    
  • 并按中所述使用它来记录您的异常。但这样,您就不会收到未处理的异常和从插件记录的异常(是的,有人会在框架中记录异常,而不是抛出异常)

  • 或者您可以向unity提出建议/请求,要求unity使用setter或其他机制进行调试。unityLogger(
    Debug.logger
    在unity 2017中被弃用),以便我们可以通过自己的机制

  • 只需将其设置为反射。但这只是暂时的黑客行为,当unity更改代码时,它将不起作用

    var field = typeof(UnityEngine.Debug)
        .GetField("s_Logger", BindingFlags.Static | BindingFlags.NonPublic);
    field.SetValue(null, your_debug_logger);
    
  • 注意:要获得正确的堆栈跟踪,您需要在编辑器设置/代码中设置为ScriptOnly(大多数情况下,这是您需要的,我写了一篇关于它如何工作的文章),并且在构建iOS时,据说


    如果感兴趣,您可以阅读流行的崩溃分析工具的工作原理。如果您查看crashlytics(android/ios的崩溃报告工具),您会发现它在内部使用
    Application.logMessageReceived
    AppDomain.CurrentDomain.UnhandledException
    事件来记录托管C#异常

    如果您对unity框架捕获异常的示例感兴趣,您可以查看我的另一篇文章,在button click listener中测试它捕获异常

    有关记录未处理异常的官方方法的一些摘要:


    I.Application.logMessageReceived在主线程上发生异常时被激发。有几种方法可以实现:

  • 在c#代码中捕获异常,并通过
    Debug.LogException

  • 异常在本机代码中捕获(可能是在使用IL2CPP时C++代码)。在这种情况下,本机代码调用
    Application.CallLogCallback
    ,从而触发
    Application.logMessageReceived
    注意:当原始异常有内部异常时,StackTrace字符串将包含“rethrow”


    二,
    Application.logMessageReceivedThreaded
    在任何线程上发生异常时都会触发,包括main(如中所述)注意:它必须是thread-s
     new Thread(() =>
        {
            Thread.Sleep(10000);
            object o = null;
            o.ToString();
        }).Start();