// ----------------------------------------
//
//  CrashSightAgent.cs
//
//  Author:
//       Yeelik,
//
//  Copyright (c) 2015 CrashSight. All rights reserved.
//
// ----------------------------------------
//
using UnityEngine;

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Globalization;

using GCloud.UQM;

/// <summary>
/// [EN] Defines the logType<br/>
/// [ZH] 定义日志的级别
/// </summary>
/// <remarks>
/// [EN] We dont use LogType enum in Unity as the numerical order doesnt suit our purposes<br/>
/// [ZH] 我们未使用 Unity 的 LogType 枚举，因其数值顺序不符合需求
/// </remarks>
public enum CSLogSeverity
{
    LogSilent,
    LogError,
    LogWarning,
    LogInfo,
    LogDebug,
    LogVerbose
}

/// <summary>
/// [EN] Defines the output type of the log report<br/>
/// [ZH] 定义日志报告的输出方式类型
/// </summary>
public enum CSReportType
{
    InterfaceReport,
    LogCallback,
    LogCallbackThreaded
}

/// <summary>
/// [EN] Defines information options for jank report<br/>
/// [ZH] 定义卡顿报告的信息选项
/// </summary>
public enum JankReportInfoOption
{
     JankOnlyBasicInfo = 0,
     JankSystemLog = 1,
     JankCustomLog = 2,
     JankCustomKv = 4,
     JankCallbackInfo = 8,
     JankAutoDumpStack = 16,
     JankAndroidAllJavaStack = 32
}

/// <summary>
/// CrashSight agent.
/// </summary>
public sealed class CrashSightAgent
{
    private static string crashUploadUrl = string.Empty;

    public static List<int> callbackThreads = new List<int>();

    public static object callbackThreadsLock = new object();

    /// <summary>
    /// [EN] Defines the log callback delegate<br/>
    /// [ZH] 定义日志回调委托
    /// </summary>
    /// <remarks>
    /// [EN] Defines delegate support multicasting to replace the 'Application.LogCallback'<br/>
    /// [ZH] 定义支持多播的委托，用于替换'Application.LogCallback'
    /// </remarks>
    public delegate void LogCallbackDelegate(string condition, string stackTrace, LogType type);

    private static event LogCallbackDelegate _LogCallbackEventHandler;

    private static bool _isInitialized = false;

    private static LogType _autoReportLogLevel = LogType.Error;

#pragma warning disable 414
    private static bool _debugMode = false;

    private static bool _autoQuitApplicationAfterReport = false;

    private static Func<Dictionary<string, string>> _LogCallbackExtrasHandler;

    private static bool _uncaughtAutoReportOnce = false;


    /************************************全平台接口************************************/

    /// <summary>
    /// [EN] Initializes CrashSight<br/>
    /// [ZH] 初始化CrashSight
    /// </summary>
    /// <remarks>
    /// [EN] Initializes as early as possible to enable crash detection and reporting features<br/>
    /// [ZH] 在尽可能早的位置进行初始化以开启崩溃捕获和上报功能
    /// </remarks>
    /// <param name="appId">
    /// [EN] APPID of registered projects<br/>
    /// [ZH] 已注册项目的APPID
    /// </param>
    public static void InitWithAppId(string appId, bool forceOnUiThread = false)
    {
        if (IsInitialized)
        {
            DebugLog(null, "CrashSightAgent has already been initialized.");

            return;
        }

        if (string.IsNullOrEmpty(appId))
        {
            return;
        }

        // init the sdk with app id
        UQMCrash.InitWithAppId(appId, forceOnUiThread);
        DebugLog(null, "Initialized with app id: " + appId + " crashUploadUrl: " + crashUploadUrl);

        // Register the LogCallbackHandler by Application.RegisterLogCallback(Application.LogCallback)
        _RegisterExceptionHandler();
    }

    /// <summary>
    /// [EN] Actively reports errors<br/>
    /// [ZH] 主动上报错误
    /// </summary>
    /// <remarks>
    /// [EN] Used to report captured C# exceptions<br/>
    /// [ZH] 用于上报捕获的c#异常
    /// </remarks>
    /// <param name="e">
    /// [EN] Caught exception<br/>
    /// [ZH] 捕获到的异常
    /// </param>
    /// <param name="message">
    /// [EN] Exception message<br/>
    /// [ZH] 异常信息</param>
    public static void ReportException(System.Exception e, string message)
    {
        if (!IsInitialized)
        {
            return;
        }

        DebugLog(null, "Report exception: " + message + "\n------------\n" + e + "\n------------");

        _HandleException(e, message, false);
    }

    /// <summary>
    /// [EN] Actively reports errors<br/>
    /// [ZH] 主动上报错误
    /// </summary>
    /// <remarks>
    /// [EN] Can be called manually when an error is caught or needs to be reported, multi-threaded calls are supported<br/>
    /// [ZH] 可以在捕获到错误或者需要上报的时候手动调用，支持多线程调用
    /// </remarks>
    /// <param name="name">
    /// [EN] Exception Name, cannot be null<br/>
    /// [ZH] 异常名称，不能为null
    /// </param>
    /// <param name="message">
    /// [EN] Exception message, cannot be null<br/>
    /// [ZH] 异常信息，不能为null
    /// </param>
    /// <param name="stackTrace">
    /// [EN] Stack, cannot be null<br/>
    /// [ZH] 堆栈，不能为null
    /// </param>
    public static void ReportException(string name, string message, string stackTrace)
    {
        if (!IsInitialized)
        {
            return;
        }

        DebugLog(null, "Report exception: " + name + " " + message + " \n" + stackTrace);

        _HandleException(LogType.Exception, name, message, stackTrace, false, CSReportType.InterfaceReport);
    }

    /// <summary>
    /// [EN] Actively reports errors<br/>
    /// [ZH] 主动上报错误
    /// </summary>
    /// <remarks>
    /// [EN] Can be called manually when an error is caught or needs to be reported, multi-threaded calls are supported<br/>
    /// [ZH] 可以在捕获到错误或者需要上报的时候手动调用，支持多线程调用<br/>
    /// </remarks>
    /// <param name="type">
    /// [EN] Exception Type, 0~3 are internal reserved types and are invalid if passed in<br/>
    /// [ZH] 异常类型, 0~3为内部保留类型传入无效, C#: 4, js: 5, lua: 6
    /// </param>
    /// <param name="exceptionName">
    /// [EN] Exception Name, cannot be null<br/>
    /// [ZH] 异常名称，不能为null
    /// </param>
    /// <param name="exceptionMsg">
    /// [EN] Exception message, cannot be null<br/>
    /// [ZH] 异常信息，不能为null
    /// </param>
    /// <param name="exceptionStack">
    /// [EN] Stack, cannot be null<br/>
    /// [ZH] 堆栈，不能为null
    /// </param>
    /// <param name="extInfo">
    /// [EN] Other Info<br/>
    /// [ZH] 其他信息
    /// </param>
    /// <param name="dumpNativeType">
    /// [EN] 0: close, 1: call system interface dump, 3: minidump (only valid for mobile)<br/>
    /// [ZH] 0：关闭，1：调用系统接口dump，3：minidump(仅移动端有效)
    /// </param>
    /// <param name="errorAttachmentPath">
    /// [EN] Absolute path of log attachment (only valid for Android)<br/>
    /// [ZH] 日志附件的绝对路径(仅Android有效)
    /// </param>
    public static void ReportException(int type, string exceptionName, string exceptionMsg, string exceptionStack, Dictionary<string, string> extInfo, int dumpNativeType = 0, string errorAttachmentPath = "")
    {
        if (!IsInitialized)
        {
            return;
        }
        UQMCrash.ReportException(type, exceptionName, exceptionMsg, exceptionStack, extInfo, dumpNativeType, errorAttachmentPath);
    }

    /// <summary>
    /// [EN] Sets user ID<br/>
    /// [ZH] 设置用户ID
    /// </summary>
    /// <remarks>
    /// [EN] The user id defaults to unknown<br/>
    /// [ZH] 用户ID默认为unknown
    /// </remarks>
    /// <param name="userId">
    /// [EN] User ID<br/>
    /// [ZH] 用户ID
    /// </param>
    public static void SetUserId(string userId)
    {
        if (!IsInitialized)
        {
            return;
        }
        DebugLog(null, "Set user id: " + userId);

        UQMCrash.SetUserId(userId);
    }

    /// <summary>
    /// [EN] Adds custom data<br/>
    /// [ZH] 添加自定义数据
    /// </summary>
    /// <remarks>
    /// [EN] Set the Key-Value data customized by the user. It will be reported together with exception info when sending the crash<br/>
    /// [ZH] 设置用户自定义的 Key-Value 数据，将在发送 Crash 时随异常信息一起上报<br/>
    /// [EN] View page: Crash Details->Download Attachments->valueMapOthers.txt<br/>
    /// [ZH] 页面查看：崩溃详情页->附件下载->valueMapOthers.txt
    /// </remarks>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public static void AddSceneData(string key, string value)
    {
        if (!IsInitialized)
        {
            return;
        }

        DebugLog(null, "Add scene data: [" + key + ", " + value + "]");

        UQMCrash.AddSceneData(key, value);
    }

    /// <summary>
    /// [EN] Adds custom data<br/>
    /// [ZH] 添加自定义数据
    /// </summary>
    /// <remarks>
    /// [EN] Call <see cref="AddSceneData"/> to set the Key-Value data customized by the user, where the value is int type. 
    ///      It will be reported together with exception info when sending the crash<br/>
    /// [ZH] 调用 <see cref="AddSceneData"/> 设置用户自定义的 Key-Value 数据，其中 value 为 int 类型，将在发送 Crash 时随异常信息一起上报<br/>
    /// [EN] View page: Crash Details->Download Attachments->valueMapOthers.txt<br/>
    /// [ZH] 页面查看：崩溃详情页->附件下载->valueMapOthers.txt
    /// </remarks>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public static void SetUserValue(string key, int value)
    {
        AddSceneData("I#" + key, "" + value);
    }

    /// <summary>
    /// [EN] Adds custom data<br/>
    /// [ZH] 添加自定义数据
    /// </summary>
    /// <remarks>
    /// [EN] Call <see cref="AddSceneData"/> to set the Key-Value data customized by the user, where the value is string type. 
    ///      It will be reported together with exception info when sending the crash<br/>
    /// [ZH] 调用 <see cref="AddSceneData"/> 设置用户自定义的 Key-Value 数据，其中 value 为 string 类型，将在发送 Crash 时随异常信息一起上报<br/>
    /// [EN] View page: Crash Details->Download Attachments->valueMapOthers.txt<br/>
    /// [ZH] 页面查看：崩溃详情页->附件下载->valueMapOthers.txt
    /// </remarks>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public static void SetUserValue(string key, string value)
    {
        AddSceneData("K#" + key, value);
    }

    /// <summary>
    /// [EN] Adds custom data<br/>
    /// [ZH] 添加自定义数据
    /// </summary>
    /// <remarks>
    /// [EN] Call <see cref="AddSceneData"/> to set the Key-Value data customized by the user, where the value is string[] type. 
    ///      It will be reported together with exception info when sending the crash<br/>
    /// [ZH] 调用 <see cref="AddSceneData"/> 设置用户自定义的 Key-Value 数据，其中 value 为 string[] 类型，将在发送 Crash 时随异常信息一起上报<br/>
    /// [EN] View page: Crash Details->Download Attachments->valueMapOthers.txt<br/>
    /// [ZH] 页面查看：崩溃详情页->附件下载->valueMapOthers.txt
    /// </remarks>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public static void SetUserValue(string key, string[] value)
    {
        string valueStr = string.Join("#", value);
        AddSceneData("S#" + key, valueStr);
    }

    /// <summary>
    /// [EN] Sets application version<br/>
    /// [ZH] 设置应用版本号
    /// </summary>
    /// <remarks>
    /// [EN] Need to be called before <see cref="InitWithAppId"/> interface<br/>
    /// [ZH] 需要在 <see cref="InitWithAppId"/> 接口之前调用
    /// </remarks>
    /// <param name="appVersion">
    /// [EN] Version Number<br/>
    /// [ZH] 版本号
    /// </param>
    public static void SetAppVersion(string appVersion)
    {
        DebugLog(null, "Set app version: " + appVersion);

        UQMCrash.SetAppVersion(appVersion);
    }

    /// <summary>
    /// [EN] Sets the reporting domain name<br/>
    /// [ZH] 设置上报域名
    /// </summary>
    /// <remarks>
    /// [EN] Need to be called before <see cref="InitWithAppId"/> interface<br/>
    /// [ZH] 需要在 <see cref="InitWithAppId"/> 接口之前调用
    /// </remarks>
    /// <param name="crashServerUrl">
    /// [EN] Target domain name of reporting<br/>
    /// [ZH] 要上报的域名
    /// </param>
    public static void ConfigCrashServerUrl(string crashServerUrl)
    {
        DebugLog(null, "Config crashServerUrl:" + crashServerUrl);
        UQMCrash.ConfigCrashServerUrl(crashServerUrl);
    }

    /// <summary>
    /// [EN] Sets upload path for log after crash<br/>
    /// [ZH] 设置崩溃后上传的日志路径
    /// </summary>
    /// <remarks>
    /// [EN] Read permission is required. On Android and iOS, this interface has a lower priority than <see cref="RegisterCrashLogCallback"/>. 
    ///      It is not available on Switch.<br/>
    /// [ZH] 需要可读权限，在Android和iOS端上，该接口的优先级低于 <see cref="RegisterCrashLogCallback"/> , Switch暂不可用<br/>
    /// </remarks>
    /// <param name="logPath">
    /// [EN] Absolute path for log files<br/>
    /// [ZH] 日志绝对路径
    /// </param>
    public static void SetLogPath(string logPath)
    {
        UQMCrash.SetLogPath(logPath);
    }

    /// <summary>
    /// [EN] Sets debug mode<br/>
    /// [ZH] 设置debug模式
    /// </summary>
    /// <remarks>
    /// [EN] Disabled by default. Need to be called before <see cref="InitWithAppId"/> interface. 
    ///      When enabled, more logs will be printed to facilitate problem location.<br/>
    /// [ZH] 默认关闭, 需要在 <see cref="InitWithAppId"/> 接口之前调用，开启后会打印更多便于问题定位的Log
    /// </remarks>
    /// <param name="enable"></param>
    public static void ConfigDebugMode(bool enable)
    {
        _debugMode = enable;
        UQMCrash.ConfigDebugMode(enable);
        DebugLog(null, (enable ? "Enable" : "Disable") + " the log message print to console");
    }

    /// <summary>
    /// [EN] Sets device id<br/>
    /// [ZH] 设置设备Id
    /// </summary>
    /// <remarks>
    /// [EN] By default, uuid is used as the device ID. Need to be called before <see cref="InitWithAppId"/> interface.<br/>
    /// [ZH] 默认采用uuid作为设备ID, 需要在 <see cref="InitWithAppId"/> 接口之前调用
    /// </remarks>
    /// <param name="deviceId">
    /// [EN] Device ID<br/>
    /// [ZH] 设备唯一标识
    /// </param>
    public static void SetDeviceId(string deviceId)
    {
        UQMCrash.SetDeviceId(deviceId);
    }

    /// <summary>
    /// [EN] Sets custom log reporting level<br/>
    /// [ZH] 设置自定义日志上报级别
    /// </summary>
    /// <remarks>
    /// [EN] Need to be called before <see cref="InitWithAppId"/> interface.<br/>
    /// [ZH] 需要在 <see cref="InitWithAppId"/> 接口之前调用
    /// </remarks>
    /// <param name="logLevel">
    /// [EN] Off=0, Error=1, Warn=2, Info=3, Debug=4, default is Info<br/>
    /// [ZH] Off=0, Error=1, Warn=2, Info=3, Debug=4, 默认Info
    /// </param>
    public static void ConfigCrashReporter(int logLevel)
    {
        UQMCrash.ConfigAutoReportLogLevel(logLevel);
    }

    /// <summary>
    /// [EN] Sets custom log reporting level<br/>
    /// [ZH] 设置自定义日志上报级别
    /// </summary>
    /// <remarks>
    /// [EN] Need to be called before <see cref="InitWithAppId"/> interface.<br/>
    /// [ZH] 需要在 <see cref="InitWithAppId"/> 接口之前调用
    /// </remarks>
    /// <param name="logLevel">
    /// [EN] Off=0, Error=1, Warn=2, Info=3, Debug=4, default is Info, <see cref="CSLogSeverity"/><br/>
    /// [ZH] Off=0, Error=1, Warn=2, Info=3, Debug=4, 默认Info, <see cref="CSLogSeverity"/>
    /// </param>
    public static void ConfigCrashReporter(CSLogSeverity logLevel)
    {
        UQMCrash.ConfigAutoReportLogLevel((int)logLevel);
    }

    /// <summary>
    /// [EN] Adds custom logs<br/>
    /// [ZH] 添加自定义日志
    /// </summary>
    /// <remarks>
    /// [EN] The custom log shouldn't exceed 30KB. Custom log view: <br/>
    ///      Android, iOS, PS4, PS5, Switch: issue details->tracking log->custom log <br/>
    ///      Windows, Xbox, Linux: issue details -> custom log (from interface)<br/>
    /// [ZH] 自定义日志，限制30KB。 自定义日志查看：<br/>
    ///      Android、iOS、PS4、PS5、Switch：问题详情->跟踪日志->custom log <br/>
    ///      Windows、Xbox、Linux：问题详情->自定义日志(来自接口)
    /// </remarks>
    /// <param name="level">
    /// [EN] Log level, <see cref="CSLogSeverity"/><br/>
    /// [ZH] 日志级别 <see cref="CSLogSeverity"/>
    /// </param>
    /// <param name="format">
    /// [EN] Log format<br/>
    /// [ZH] 日志格式
    /// </param>
    /// <param name="args">
    /// [EN] Variable parameter array<br/>
    /// [ZH] 可变参数数组
    /// </param>
    public static void PrintLog(CSLogSeverity level, string format, params object[] args)
    {
        if (string.IsNullOrEmpty(format))
        {
            return;
        }
        if (args == null || args.Length == 0)
        {
            UQMCrash.LogRecord((int)level, format);
        }
        else
        {
            UQMCrash.LogRecord((int)level, string.Format(format, args));
        }
    }

    /// <summary>
    /// [EN] Tests native crash<br/>
    /// [ZH] 测试 native 崩溃
    /// </summary>
    public static void TestNativeCrash()
    {
        if (!IsInitialized)
        {
            return;
        }
        DebugLog(null, "test native crash");

        UQMCrash.TestNativeCrash();
    }

    /// <summary>
    /// [EN] Sets the distribution channel<br/>
    /// [ZH] 设置发行渠道
    /// </summary>
    /// <remarks>
    /// [EN] Each network connection or report can carry this field to collect statistics for different distribution channels.
    ///      It is not available on PS4、PS5、Switch and Linux.<br/>
    /// [ZH] 每一个联网或者上报都可以携带该字段，可实现针对不同的发行渠道统计数据。 PS4、PS5、Switch、Linux暂不可用
    /// </remarks>
    /// <param name="serverEnv"></param>
    public static void SetEnvironmentName(string serverEnv)
    {
        UQMCrash.SetServerEnv(serverEnv);
    }

    /// <summary>
    /// [EN] Enables Unity ANR monitoring<br/>
    /// [ZH] 启用 Unity ANR 监控
    /// </summary>
    /// <remarks>
    /// [EN] Here ANR means that the Unity main thread is unresponsive, which is different from Android ANR<br/>
    /// [ZH] 此处ANR是指Unity主线程无响应，区别于Android ANR
    /// </remarks>
    /// <param name="timeoutMs">
    /// [EN] Timeout in milliseconds to trigger ANR report when main thread is unresponsive<br/>
    /// [ZH] 主线程无响应多少毫秒触发上报
    /// </param>
    public static void EnableAnrMonitor(int timeoutMs = 5000)
    {
        CrashSightAnrMonitor.Start(timeoutMs);
    }

    /// <summary>
    /// [EN] Disables Unity ANR monitoring<br/>
    /// [ZH] 停用 Unity ANR 监控
    /// </summary>
    public static void DisableAnrMonitor()
    {
        CrashSightAnrMonitor.Stop();
    }

    /// <summary>
    /// [EN] Registers crash callback<br/>
    /// [ZH] 注册崩溃回调
    /// </summary>
    /// <remarks>
    /// [EN] Callback is called when a crash occurs and the return value will be included in the crash report.<br/>
    ///      Create a custom class inheriting from CrashSightCallback and implement OnCrashBaseRetEvent method.<br/>
    ///      Not available on PS4, PS5, Switch, Linux and HarmonyOS.<br/>
    /// [ZH] 回调在崩溃时调用，返回值随崩溃信息一起上报<br/>
    ///      自定义CsCrashCallBack类，继承 CrashSightCallback，实现 OnCrashBaseRetEvent方法<br/>
    ///      PS4、PS5、Switch、Linux、鸿蒙暂不可用<br/>
    /// </remarks>
    /// <example>
    /// <code>
    /// public class CsCrashCallBack: CrashSightCallback  {
    ///     //Put you own code to implemente the callback func
    ///     public override string OnCrashBaseRetEvent(int methodId)
    ///     {
    ///         if (methodId == (int)UQMMethodNameID.UQM_CRASH_CALLBACK_EXTRA_MESSAGE)
    ///         {
    ///             // For Android/iOS: return extra message
    ///             return "this is extra message.";
    ///         }
    ///         else if (methodId == (int)UQMMethodNameID.UQM_CRASH_CALLBACK_NO_RET)
    ///         {
    ///             // For Win/Xbox: return value is ignored but you can perform operations here
    ///         }
    ///         return "";
    ///     }
    /// }
    /// </code>
    /// </example>
    /// <param name="callback">
    /// [EN] Callback instance that inherits from CrashSightCallback<br/>
    /// [ZH] 继承自 CrashSightCallback 的回调实例
    /// </param>
    public static void RegisterCrashCallback(CrashSightCallback callback)
    {
        if (callback != null)
        {
            // 使用包装方法，在调用前检查退出状态
            UQMCrash.CrashBaseRetEvent += (methodId, crashType) =>
            {
                // 如果应用正在退出，直接返回空字符串，避免执行Unity相关操作
                if (CrashSightCallback.IsQuitting())
                {
                    return "";
                }

                try
                {
                    // 调用原始回调方法
                    return callback.OnCrashBaseRetEvent(methodId, crashType);
                }
                catch (System.Exception)
                {
                    // 如果Unity已经退出，捕获异常并返回空字符串
                    return "";
                }
            };
            UQMCrash.ConfigCallBack();
        }
        else
        {
            DebugLog(null, "RegisterCallback failed: callback is null.");
        }
    }

    /// <summary>
    /// [EN] Unregisters the crash callback<br/>
    /// [ZH] 注销崩溃回调
    /// </summary>
    public static void UnregisterCrashCallback()
    {
        UQMCrash.UnregisterCallBack();
    }

    /// <summary>
    /// [EN] Registers crash log upload callback<br/>
    /// [ZH] 注册上传日志回调
    /// </summary>
    /// <remarks>
    /// [EN] Called after handling a crash.<br/>
    ///      Customize the CsCrashLogCallback class, inherit CrashSightLogCallback, and use OnSetLogPathEvent and OnLogUploadResultEvent methods.<br/>
    /// [ZH] 崩溃处理后调用，自定义CsCrashLogCallback类，继承CrashSightLogCallback，实现OnSetLogPathEvent、OnLogUploadResultEvent方法
    /// </remarks>
    /// <example>
    /// <code>
    /// public class CsCrashLogCallBack : CrashSightLogCallback
    /// {
    ///     // Returns absolute path of log file to upload
    ///     // methodId: Method ID (can be ignored)
    ///     // crashType: Crash type (0: Java/OC crash, 2: Native crash)
    ///     public override string OnSetLogPathEvent(int methodId, int crashType)
    ///     {
    ///         return "";
    ///     }
    /// 
    ///     // Notifies log upload result
    ///     // methodId: Method ID (can be ignored)
    ///     // crashType: Crash type (0: Java/OC crash, 2: Native crash)
    ///     // result: Upload result (0: success, others: failure)
    ///     public override void OnLogUploadResultEvent(int methodId, int crashType, int result)
    ///     {
    /// 
    ///     }
    /// }
    /// </code>
    /// </example>
    /// <param name="callback">
    /// [EN] Callback instance that inherits from CrashSightLogCallback<br/>
    /// [ZH] 继承自CrashSightLogCallback的回调实例
    /// </param>
    public static void RegisterCrashLogCallback(CrashSightLogCallback callback)
    {
        if (callback != null)
        {
            // 使用包装方法，在调用前检查退出状态
            UQMCrash.CrashSetLogPathRetEvent += (methodId, crashType) =>
            {
                // 如果应用正在退出，直接返回空字符串
                if (CrashSightLogCallback.IsQuitting())
                {
                    return "";
                }

                try
                {
                    // 调用原始回调方法
                    return callback.OnSetLogPathEvent(methodId, crashType);
                }
                catch (System.Exception)
                {
                    // 如果Unity已经退出，捕获异常并返回空字符串
                    return "";
                }
            };

            UQMCrash.CrashLogUploadRetEvent += (methodId, crashType, result) =>
            {
                // 如果应用正在退出，直接返回
                if (CrashSightLogCallback.IsQuitting())
                {
                    return;
                }

                try
                {
                    // 调用原始回调方法
                    callback.OnLogUploadResultEvent(methodId, crashType, result);
                }
                catch (System.Exception)
                {
                    // 忽略异常
                }
            };

            UQMCrash.ConfigLogCallBack();
        }
        else
        {
            DebugLog(null, "RegisterCallback failed: callback is null.");
        }
    }

    /// <summary>
    /// [EN] Report Unity Log Error<br/>
    /// [ZH] 上报Unity Log Error
    /// </summary>
    /// <remarks>
    /// [EN] Enables automatic reporting of Unity log errors to CrashSight.<br/>
    ///      The triggering frequency of Unity Log Error in the project should be kept low after enabling the feature.<br/>
    /// [ZH] 调用该接口可以将Unity Log Error作为错误上报到CrashSight<br/>
    ///      开启该功能后请尽量控制项目中Unity Log Error的触发频率
    /// </remarks>
    public static void EnableExceptionHandler()
    {
        if (IsInitialized)
        {
            DebugLog(null, "CrashSightAgent has already been initialized.");
            return;
        }

        DebugLog(null, "Only enable the exception handler, please make sure you has initialized the sdk in the native code in associated Android or iOS project.");

        // Register the LogCallbackHandler by Application.RegisterLogCallback(Application.LogCallback)
        _RegisterExceptionHandler();
    }

    /// <summary>
    /// [EN] Registers log callback<br/>
    /// [ZH] 注册日志回调
    /// </summary>
    /// <param name="handler">
    /// [EN] Log callback delegate<br/>
    /// [ZH] 日志回调委托
    /// </param>
    public static void RegisterLogCallback(LogCallbackDelegate handler)
    {
        if (handler != null)
        {
            DebugLog(null, "Add log callback handler: " + handler);

            _LogCallbackEventHandler += handler;
        }
    }

    /// <summary>
    /// [EN] Unregisters log callback<br/>
    /// [ZH] 注销日志回调
    /// </summary>
    /// <param name="handler">
    /// [EN] Log callback delegate to unregister<br/>
    /// [ZH] 要注销的日志回调委托
    /// </param>
    public static void UnregisterLogCallback(LogCallbackDelegate handler)
    {
        if (handler != null)
        {
            DebugLog(null, "Remove log callback handler");

            _LogCallbackEventHandler -= handler;
        }
    }

    /// <summary>
    /// [EN] Sets the extra data handler for log callbacks<br/>
    /// [ZH] 设置日志回调的额外数据处理函数
    /// </summary>
    /// <param name="handler">
    /// [EN] Function that returns extra data dictionary<br/>
    /// [ZH] 返回额外数据的函数
    /// </param>
    public static void SetLogCallbackExtrasHandler(Func<Dictionary<string, string>> handler)
    {
        if (handler != null)
        {
            _LogCallbackExtrasHandler = handler;

            DebugLog(null, "Add log callback extra data handler : " + handler);
        }
    }

    /// <summary>
    /// [EN] Configures whether to automatically quit the application after crash reporting<br/>
    /// [ZH] 配置崩溃上报后是否自动退出应用
    /// </summary>
    /// <param name="autoQuit">
    /// [EN] Whether to auto quit<br/>
    /// [ZH] 是否自动退出
    /// </param>
    public static void ConfigAutoQuitApplication(bool autoQuit)
    {
        _autoQuitApplicationAfterReport = autoQuit;
    }

    /// <summary>
    /// [EN] Gets whether the application will auto quit after crash reporting<br/>
    /// [ZH] 获取崩溃上报后是否自动退出应用的配置
    /// </summary>
    /// <returns>
    /// [EN] Current auto quit configuration<br/>
    /// [ZH] 当前自动退出配置
    /// </returns>
    public static bool AutoQuitApplicationAfterReport
    {
        get { return _autoQuitApplicationAfterReport; }
    }

    /// <summary>
    /// [EN] Outputs debug log messages<br/>
    /// [ZH] 调试日志输出
    /// </summary>
    /// <remarks>
    /// [EN] Only works when debugMode is enabled.<br/>
    /// [ZH] 仅当debugMode为true时有效
    /// </remarks>
    /// <param name="tag">
    /// [EN] Log tag
    /// [ZH] 日志标签</param>
    /// <param name="format">
    /// [EN] Format string with placeholders<br/>
    /// [ZH] 带占位符的格式化字符串
    /// </param>
    public static void DebugLog(string tag, string format)
    {
        if (!_debugMode)
        {
            return;
        }

        if (string.IsNullOrEmpty(format))
        {
            return;
        }

        UQMLog.Log(string.Format("{0}:{1}", tag, format));
    }

    /// <summary>
    /// [EN] Checks if CrashSight has been initialized<br/>
    /// [ZH] 获取CrashSight是否已初始化
    /// </summary>
    /// <returns>
    /// [EN] Initialization status<br/>
    /// [ZH] 初始化状态
    /// </returns>
    public static bool IsInitialized
    {
        get { return _isInitialized; }
    }

    /// <summary>
    /// [EN] Registers exception handler callbacks<br/>
    /// [ZH] 注册异常处理回调
    /// </summary>
    public static void _RegisterExceptionHandler()
    {
        try
        {
            // hold only one instance

#if UNITY_5 || UNITY_5_3_OR_NEWER
            Application.logMessageReceived += _OnLogCallbackHandlerMain;
            Application.logMessageReceivedThreaded += _OnLogCallbackHandlerThreaded;
#else
            Application.RegisterLogCallback(_OnLogCallbackHandlerMain);
            Application.RegisterLogCallbackThreaded(_OnLogCallbackHandlerThreaded);
#endif
            AppDomain.CurrentDomain.UnhandledException += _OnUncaughtExceptionHandler;

            string version = Application.unityVersion;
#if UNITY_EDITOR
            string buildConfig = "editor";
#elif DEVELOPMENT_BUILD
            string buildConfig = "development";
#else
            string buildConfig = "release";
#endif
            SystemLanguage language = Application.systemLanguage;
            CultureInfo locale = CultureInfo.CurrentCulture;
            UQMCrash.SetEngineInfo(version, buildConfig, language.ToString(), locale.ToString());

            _isInitialized = true;

            DebugLog(null, "Register the log callback in Unity " + Application.unityVersion);
        }
        catch
        {

        }
    }

    /// <summary>
    /// [EN] Unregisters exception handler callbacks<br/>
    /// [ZH] 注销异常处理回调
    /// </summary>
    public static void _UnregisterExceptionHandler()
    {
        try
        {
#if UNITY_5 || UNITY_5_3_OR_NEWER
            Application.logMessageReceived -= _OnLogCallbackHandlerMain;
            Application.logMessageReceivedThreaded -= _OnLogCallbackHandlerThreaded;
#else
            Application.RegisterLogCallback(null);
            Application.RegisterLogCallbackThreaded(null);
#endif
            System.AppDomain.CurrentDomain.UnhandledException -= _OnUncaughtExceptionHandler;
            DebugLog(null, "Unregister the log callback in unity " + Application.unityVersion);
        }
        catch
        {

        }
    }

    /// <summary>
    /// [EN] Enables/disables CrashSight stack trace collection<br/>
    /// [ZH] 设置是否启用CrashSight堆栈跟踪
    /// </summary>
    /// <param name="enable"></param>
    public static void SetCrashSightStackTraceEnable(bool enable)
    {
        CrashSightStackTrace.setEnable(enable);
    }

    /************************************Android、iOS、Mac端接口************************************/

    /// <summary>
    /// [EN] Configures callback types for reports (Android, iOS, Mac)<br/>
    /// [ZH] 设置各类上报的回调开关(Android、iOS、Mac)
    /// </summary>
    /// <param name="callbackType">
    /// [EN] There are currently 5 types of callback represented by 5 digits. The first digit represents crash, the second digit represents anr,
    ///      the third digit represents u3d c# error, the fourth digit represents js, and the fifth digit represents lua<br/>
    /// [ZH] 目前是5种类型，用5位表示。第一位表示crash，第二位表示anr，第三位表示u3d c# error第四位表示js，第五位表示lua。默认全开
    /// </param>
    public static void ConfigCallbackType(Int32 callbackType)
    {
        UQMCrash.ConfigCallbackType(callbackType);
    }

    /// <summary>
    /// [EN] Sets device model (Android, iOS, Mac)<br/> 
    /// [ZH] 设置设备型号(Android、iOS、Mac)
    /// </summary>
    /// <remarks>
    /// [EN] Need to be called before <see cref="InitWithAppId"/> interface.<br/>
    /// [ZH] 需要在 <see cref="InitWithAppId"/> 接口之前调用
    /// </remarks>
    /// <param name="deviceModel">
    /// [EN] Device model name<br/>
    /// [ZH] 手机型号
    /// </param>
    public static void SetDeviceModel(string deviceModel)
    {
        UQMCrash.SetDeviceModel(deviceModel);
    }

    /// <summary>
    /// [EN] Reports lightweight log (Android, iOS, Mac)<br/> 
    /// [ZH] 上报轻量级日志(Android、iOS、Mac)
    /// </summary>
    /// <param name="msgType">
    /// [EN] Message type<br/>
    /// [ZH] 消息类型
    /// </param>
    /// <param name="msg">
    /// [EN] Message content<br/> 
    /// [ZH] 消息内容
    /// </param>
    public static void ReportLogInfo(string msgType, string msg)
    {
        UQMCrash.ReportLogInfo(msgType, msg);
    }

    /// <summary>
    /// [EN] Sets the scene field name(Android、iOS、Mac)<br/>
    /// [ZH] 设置场景字段(Android、iOS、Mac)
    /// </summary>
    /// <remarks>
    /// [EN] Each network connection or report can carry this field to calculate crash rates and other data for different scenes.
    ///      It is not available on PS4、PS5、Switch and Linux.<br/>
    /// [ZH] 每一个联网或者上报都可以携带该字段，实现针对不同的子场景计算崩溃率等数据。 PS4、PS5、Switch、Linux暂不可用
    /// </remarks>
    /// <param name="sceneId"></param>
    /// <param name="upload">
    /// [EN] Whether to report the change of scene<br/>
    /// [ZH] 是否上报场景变更
    /// </param>
    public static void SetScene(string sceneId, bool upload = false)
    {
        if (!IsInitialized)
        {
            return;
        }
        DebugLog(null, "Set scene: " + sceneId + ", upload:" + upload);

        UQMCrash.SetScene(sceneId, upload);
    }

    /// <summary>
    /// [EN] Sets the scene field name(Android、iOS、Mac)<br/>
    /// [ZH] 设置场景字段(Android、iOS、Mac)
    /// </summary>
    /// <remarks>
    /// [EN] Each network connection or report can carry this field to calculate crash rates and other data for different scenes.
    ///      It is not available on PS4、PS5、Switch and Linux.<br/>
    /// [ZH] 每一个联网或者上报都可以携带该字段，实现针对不同的子场景计算崩溃率等数据。 PS4、PS5、Switch、Linux暂不可用
    /// </remarks>
    /// <param name="sceneId"></param>
    /// <param name="upload">
    /// [EN] Whether to report the change of scene<br/>
    /// [ZH] 是否上报场景变更
    /// </param>
    public static void SetScene(int sceneId, bool upload = false)
    {
        SetScene(sceneId.ToString(), upload);
    }

    /// <summary>
    /// [EN] Gets crash thread ID (Android, iOS, Mac)<br/>
    /// [ZH] 获取崩溃线程ID(Android、iOS、Mac)
    /// </summary>
    /// <remarks>
    /// [EN] Can be called in callback<br/> 
    /// [ZH] 可在回调中调用
    /// </remarks>
    /// <returns>
    /// [EN] Crash thread ID, returns -1 on failure<br/> 
    /// [ZH] 崩溃线程ID，失败时返回-1
    /// </returns>
    public static long GetCrashThreadId()
    {
        if (!IsInitialized)
        {
            return -1;
        }
        DebugLog(null, "GetCrashThreadId");

        return UQMCrash.GetCrashThreadId();
    }

    /// <summary>
    /// [EN] Sets customized device ID (Android, iOS, Mac)<br/>
    /// 设置自定义device ID(Android、iOS、Mac)
    /// </summary>
    /// <param name="deviceId">
    /// [EN] Custom device id<br/>
    /// [ZH] 自定义device ID
    /// </param>
    public static void SetCustomizedDeviceID(string deviceId)
    {
        UQMCrash.SetCustomizedDeviceID(deviceId);
    }

    /// <summary>
    /// [EN] Gets SDK generated device ID (Android, iOS, Mac)<br/>
    /// [ZH] 获取SDK生成的device ID(Android、iOS、Mac)
    /// </summary>
    /// <returns>
    /// [EN] SDK generated device ID<br/>
    /// [ZH] SDK生成的device ID
    /// </returns>
    public static string GetSDKDefinedDeviceID()
    {
        return UQMCrash.GetSDKDefinedDeviceID();
    }

    /// <summary>
    /// [EN] Sets customized match ID (Android, iOS, Mac)<br/>
    /// [ZH] 设置自定义 match ID(Android、iOS、Mac)
    /// </summary>
    /// <remarks>
    /// [EN] Match ID can be used to search crashes and errors in "Advanced Search"<br/>
    /// [ZH] match id可用于在"高级搜索"中查找崩溃和错误
    /// </remarks>
    /// <param name="matchId">match ID</param>
    public static void SetCustomizedMatchID(string matchId)
    {
        UQMCrash.SetCustomizedMatchID(matchId);
    }

    /// <summary>
    /// [EN] Gets SDK generated session ID (Android, iOS, Mac)<br/>
    /// [ZH] 获取SDK生成的session ID(Android、iOS、Mac)
    /// </summary>
    /// <remarks>
    /// [EN] Session ID uniquely marks a launch session, typically used in callbacks to determine if it's the same launch.<br/>
    /// [ZH] session ID用于唯一标记一次启动，一般用于在回调中确定是否为同一次启动。
    /// </remarks>
    /// <returns>
    /// [EN] SDK generated session ID<br/>
    /// [ZH] SDK生成的session ID
    /// </returns>
    public static string GetSDKSessionID()
    {
        return UQMCrash.GetSDKSessionID();
    }

    /// <summary>
    /// [EN] Triggers test OOM crash (Android, iOS, Mac)<br/>
    /// [ZH] 测试OOM崩溃(Android、iOS、Mac)
    /// </summary>
    public static void TestOomCrash()
    {
        if (!IsInitialized)
        {
            return;
        }
        DebugLog(null, "test oom crash");

        UQMCrash.TestOomCrash();
    }

    /// <summary>
    /// [EN] Triggers test Java crash (Android)<br/>
    /// [ZH] 测试java崩溃(Android)
    /// </summary>
    public static void TestJavaCrash()
    {
        if (!IsInitialized)
        {
            return;
        }
        DebugLog(null, "test java crash");

        UQMCrash.TestJavaCrash();
    }

    /// <summary>
    /// [EN] Triggers test ANR (Android only)<br/>
    /// [ZH] 测试ANR(仅Android)
    /// </summary>
    public static void TestANR()
    {
        if (!IsInitialized)
        {
            return;
        }
        DebugLog(null, "test ANR");

        UQMCrash.TestANR();
    }

    /// <summary>
    /// [EN] Gets crash UUID<br/>
    /// [ZH] 获取崩溃UUID
    /// </summary>
    /// <remarks>
    /// [EN] This UUID is used to uniquely identify a report and is generally used in callbacks.<br/>
    /// [ZH] 该UUID用于唯一标识一次上报，一般在回调中使用
    /// </remarks>
    /// <returns></returns>
    public static string GetCrashUuid()
    {
        return UQMCrash.GetCrashUuid();
    }

    /// <summary>
    /// [EN] Sets the logcat buffer size(Android)<br/>
    /// [ZH] 设置logcat缓存大小(Android)
    /// </summary>
    /// <remarks>
    /// [EN] Default is 10KB in non-debug mode, 128KB in debug mode<br/>
    /// [ZH] 默认非debug模式下10KB，debug模式下128KB
    /// </remarks>  
    /// <param name="size">
    /// [EN] Buffer size in KB<br/>
    /// [ZH] logcat缓存大小(单位：KB)
    /// </param>
    public static void SetLogcatBufferSize(int size)
    {
        UQMCrash.SetLogcatBufferSize(size);
    }

    /// <summary>
    /// [EN] Tests Objective-C crash (iOS, Mac)<br/>
    /// [ZH] 测试Objective-C崩溃(iOS、Mac)
    /// </summary>
    public static void TestOcCrash()
    {
        if (!IsInitialized)
        {
            return;
        }
        DebugLog(null, "test oc crash");

        UQMCrash.TestOcCrash();
    }

    /// <summary>
    /// [EN] Starts scheduled dump routine<br/>
    /// [ZH] 启动定时dump任务
    /// </summary>
    /// <remarks>
    /// [EN] Starts a thread to periodically capture and report dumps.
    ///      Performance overhead depends on dump interval and count settings.
    ///      Mainly for testing purposes. Disable before release.<br/>
    /// [ZH] 启动一个线程定时获取dump并上报。根据dump间隔和次数会产生性能开销。一般用于测试，正式发布前请关闭。
    /// </remarks>
    /// <param name="dumpMode">
    /// [EN] 0: close, 1: call system interface dump, 3: minidump (only valid for mobile)<br/>
    /// [ZH] 0：关闭，1：调用系统接口dump，3：minidump(仅移动端有效)
    /// </param>
    /// <param name="startTimeMode">
    /// [EN] Start time mode: 0=absolute time, 1=relative time (ms)<br/>
    /// [ZH] 启动时间模式：0=绝对时间，1=相对时间(毫秒)
    /// </param>
    /// <param name="startTime">
    /// [EN] Start time<br/>
    /// [ZH] 启动时间
    /// </param>
    /// <param name="dumpInterval">
    /// [EN] Dump interval in milliseconds<br/>
    /// [ZH] dump间隔(毫秒)
    /// </param>
    /// <param name="dumpTimes">
    /// [EN] Number of dumps to perform<br/>
    /// [ZH] dump执行次数
    /// </param>
    /// <param name="saveLocal">
    /// [EN] Whether to save dump locally<br/>
    /// [ZH] 是否本地保存dump
    /// </param>
    /// <param name="savePath">
    /// [EN] Local save path for dumps<br/>
    /// [ZH] dump本地保存路径
    /// </param>
    public static void StartDumpRoutine(int dumpMode, int startTimeMode, long startTime,
                long dumpInterval, int dumpTimes, bool saveLocal, string savePath)
    {
        if (!IsInitialized)
        {
            return;
        }
        UQMCrash.StartDumpRoutine(dumpMode, startTimeMode, startTime, dumpInterval, dumpTimes, saveLocal, savePath);
    }

    /// <summary>
    /// [EN] Starts monitoring file descriptor count<br/>
    /// [ZH] 监控FD数量
    /// </summary>
    /// <param name="interval">
    /// [EN] Scan interval in milliseconds<br/>
    /// [ZH] 扫描间隔，单位：毫秒
    /// </param>
    /// <param name="limit">FD数量限制<br/>
    /// [EN] Maximum allowed FD count
    /// [ZH] FD数量限制
    /// </param>
    /// <param name="dumpType">
    /// [EN] 0: close, 1: call system interface dump, 3: minidump (only valid for mobile)<br/>
    /// [ZH] 0：关闭，1：调用系统接口dump，3：minidump(仅移动端有效)
    /// </param>
    public static void StartMonitorFdCount(int interval, int limit, int dumpType)
    {
        if (!IsInitialized)
        {
            return;
        }
        UQMCrash.StartMonitorFdCount(interval, limit, dumpType);
    }

    /// <summary>
    /// [EN] Gets exception type code<br/>
    /// [ZH] 获取异常类型编号
    /// </summary>
    /// <param name="name">
    /// [EN] Exception type name (e.g. "c#", "js", "lua", "custom1")<br/>
    /// [ZH] 异常类型名(如"c#","js","lua","custom1"等)
    /// </param>
    /// <returns>
    /// [EN] Exception type code for ReportException<br/>
    /// [ZH] 用于ReportException接口的异常类型编号
    /// </returns>
    public static int getExceptionType(string name)
    {
        if (!IsInitialized)
        {
            return 0;
        }
        return UQMCrash.getExceptionType(name);
    }

    /// <summary>
    /// [EN] Tests use-after-free memory error (Android)<br/>
    /// [ZH] 测试释放后使用内存错误(Android)
    /// </summary>
    /// <remarks>
    /// [EN] Only works when GWP-Asan or MTE is enabled<br/>
    /// [ZH] 仅当GWP-Asan或MTE功能启用时有效
    /// </remarks>
    public static void TestUseAfterFree()
    {
        if (!IsInitialized)
        {
            return;
        }
        DebugLog(null, "test UseAfterFree");

        UQMCrash.TestUseAfterFree();
    }

    /// <summary>
    /// [EN] Restarts CrashSight monitoring (Android, iOS)<br/>
    /// [ZH] 重启CrashSight监控(Android, iOS)
    /// </summary>
    /// <remarks>
    /// [EN] Used in native Android integration scenarios<br/>
    /// [ZH] 用于Android原生接入场景
    /// </remarks>
    public static void ReRegistAllMonitors()
    {
        _isInitialized = true;
        UQMCrash.ReRegistAllMonitors();
        DebugLog(null, "ReRegistAllMonitors");
    }

    /// <summary>
    /// [EN] Stops CrashSight monitoring (Android, iOS, Mac)<br/>
    /// [ZH] 关闭CrashSight监控(Android, iOS, Mac)
    /// </summary>
    public static void CloseAllMonitors()
    {
        UQMCrash.CloseAllMonitors();
        DebugLog(null, "CloseAllMonitors");
    }

    /// <summary>
    /// [EN] Toggles getPackageInfo system API (Android)<br/>
    /// [ZH] 设置getPackageInfo系统接口调用开关(Android)
    /// </summary>
    /// <remarks>
    /// [EN] Enabled by default, can be disabled if rejected by Google Play<br/>
    /// [ZH] 默认开启，谷歌审核不通过时可关闭
    /// </remarks>
    /// <param name="enable"></param>
    public static void setEnableGetPackageInfo(bool enable)
    {
        DebugLog(null, "setEnableGetPackageInfo: " + enable);

        UQMCrash.setEnableGetPackageInfo(enable);
    }

    /// <summary>
    /// [EN] Sets multi-thread signal handling switch (Android)<br/>
    /// [ZH] 设置安卓多线程信号处理开关(仅Android)
    /// </summary>
    /// <param name="enable"></param>
    public static void setCatchMultiSignal(bool enable)
    {
        DebugLog(null, "setCatchMultiSignal: " + enable);

        UQMCrash.setCatchMultiSignal(enable);
    }

    /// <summary>
    /// [EN] Sets Android Fragment Tracking, iOS Page Tracking Switch<br/>
    /// [ZH] 设置安卓Fragment追踪、iOS页面追踪开关
    /// </summary>
    /// <param name="enable"></param>
    public static void enableDetailedPageTracing(bool enable)
    {
        DebugLog(null, "enableDetailedPageTracing: " + enable);

        UQMCrash.enableDetailedPageTracing(enable);
    }

    /// <summary>
    /// [EN] Checks if last session crashed<br/>
    /// [ZH] 判断上次启动会话是否发生崩溃
    /// </summary>
    /// <returns></returns>
    public static bool IsLastSessionCrash()
    {
        if (!IsInitialized)
        {
            return false;
        }
        DebugLog(null, "IsLastSessionCrash");

        return UQMCrash.IsLastSessionCrash();
    }

    /// <summary>
    /// [EN] Gets user ID of last session<br/>
    /// [ZH] 获取上次会话的用户ID
    /// </summary>
    /// <returns></returns>
    public static string GetLastSessionUserId()
    {
        if (!IsInitialized)
        {
            return "";
        }
        DebugLog(null, "GetLastSessionUserId");

        return UQMCrash.GetLastSessionUserId();
    }

    /// <summary>
    /// [EN] Checks if file descriptor count exceeds limit<br/>
    /// [ZH] 检查文件描述符(FD)数量是否超过限制
    /// </summary>
    /// <param name="limit">
    /// [EN] Maximum FD count<br/>
    /// [ZH] FD数量限制
    /// </param>
    /// <param name="dumpType">
    /// [EN] 0: close, 1: call system interface dump, 3: minidump (only valid for mobile)<br/>
    /// [ZH] 0：关闭，1：调用系统接口dump，3：minidump(仅移动端有效)
    /// </param>
    /// <param name="upload">
    /// [EN] Whether to upload report<br/>
    /// [ZH] 是否上报
    /// </param>
    /// <returns>
    /// [EN] true if FD count exceeds limit, false otherwise<br/>
    /// [ZH] FD数量超过限制返回true，否则返回false
    /// </returns>
    public static bool CheckFdCount(int limit, int dumpType, bool upload)
    {
        if (!IsInitialized)
        {
            return false;
        }
        return UQMCrash.CheckFdCount(limit, dumpType, upload);
    }

    /// <summary>
    /// [EN] Sets OOM log path (Android, iOS)<br/>
    /// [ZH] 设置OOM日志路径(Android、iOS)
    /// </summary>
    /// <param name="logPath">
    /// [EN] Path for OOM logs<br/>
    /// [ZH] OOM日志路径
    /// </param>
    public static void SetOomLogPath(string logPath)
    {
        UQMCrash.SetOomLogPath(logPath);
    }

    /// <summary>
    /// [EN] Reports jank<br/>
    /// [ZH] 上报卡顿事件
    /// </summary>
    /// <param name="type">
    /// [EN] Jank type (reserved for future use)<br/>
    /// [ZH] 卡顿类型(保留字段，暂未使用)
    /// </param>
    /// <param name="exceptionName">
    /// [EN] Name of the jank exception<br/>
    /// [ZH] 卡顿异常名称
    /// </param>
    /// <param name="exceptionMsg">
    /// [EN] Description of the jank event<br/>
    /// [ZH] 卡顿事件描述信息
    /// </param>
    /// <param name="exceptionStack">
    /// [EN] Stack trace of jank<br/>
    /// [ZH] 卡顿堆栈内容
    /// </param>
    /// <param name="paramsJson">
    /// [EN] Additional information in JSON format<br/>
    /// [ZH] 额外信息，JSON格式
    /// </param>
    /// <param name="reportInfoOption">
    /// [EN] Collection options for jank reporting information, represented by 6 binary bits for different options, 0 indicating no collection of additional information.<br/>
    ///      The 1st bit: Collect system logs, the 2nd bit: Collect custom logs, the 3rd bit: Collect user-defined key-value pairs, 
    ///      the 4th bit: Trigger user callbacks, the 5th bit: Automatically dump stack traces, the 6th bit:Automatically collect full-thread Java stack.<br/>
    /// [ZH] jank上报信息收集选项，用6位二进制位表示不同选项，0表示不收集任何额外信息<br/>
    ///      第1位:收集系统日志，第2位:收集自定义日志，第3位:收集用户自定义键值对，第4位:触发用户回调，第5位:自动dump堆栈，第6位:自动采集全线程Java堆栈
    /// </param>
    /// <param name="jankAttachmentPath">
    /// [EN] Path to attachment file for jank report<br/>
    /// [ZH] 卡顿报告附件文件路径
    /// </param>
    public static void ReportJank(int type, string exceptionName,
                                 string exceptionMsg, string exceptionStack,
                                 string paramsJson, int reportInfoOption,
                                 string jankAttachmentPath)
    {
        UQMCrash.ReportJank(type, exceptionName, exceptionMsg, exceptionStack, paramsJson, reportInfoOption, jankAttachmentPath);
    }

    public static void ReportStuck(int threadId, int maxChecks, long checkInterval,
                                  string name, string message, Dictionary<string, string> extInfo,
                                  int dumpNativeType, string attachPath)
    {
        UQMCrash.ReportStuck(threadId, maxChecks, checkInterval, name, message, extInfo, dumpNativeType, attachPath);
    }

    /************************************PC、Xbox端接口************************************/

    /// <summary>
    /// [EN] Sets VEH(Vectored Exception Handling) enable status (Win, Xbox)<br/>
    /// [ZH] 设置VEH(向量化异常处理)捕获开关(Win、Xbox)
    /// </summary>
    /// <remarks>
    /// [EN] Enabled by default. Recommended to disable for Unity projects to avoid false crash reports.<br/>
    /// [ZH] 默认开启，建议所有Unity项目关闭此开关，否则可能产生crash误报。
    /// </remarks>
    /// <param name="enable"></param>
    public static void SetVehEnable(bool enable)
    {
        UQMCrash.SetVehEnable(enable);
        DebugLog(null, "SetVehEnable");
    }

    /// <summary>
    /// [EN] Manually reports a crash (Win, Xbox)<br/>
    /// [ZH] 主动上报崩溃信息(Win、Xbox)
    /// </summary>
    /// <remarks>
    /// [EN] Reports a crash manually. Normally not needed, use only when necessary.<br/>
    /// [ZH] 主动上报一条崩溃信息，一般没有使用场景，可根据项目需要酌情使用。
    /// </remarks>
    public static void ReportCrash()
    {
        UQMCrash.ReportCrash();
        DebugLog(null, "ReportCrash");
    }

    /// <summary>
    /// [EN] Manually reports dump file(Win, Xbox)<br/>
    /// [ZH] 主动上报dump文件(Win、Xbox)
    /// </summary>
    /// <param name="dump_path">
    /// [EN] Directory containing dump files<br/>
    /// [ZH] dump文件所在目录
    /// </param>
    /// <param name="is_async">
    /// [EN] Whether to report asynchronously<br/>
    /// [ZH] 是否异步上报
    /// </param>
    public static void ReportDump(string dump_path, bool is_async)
    {
        UQMCrash.ReportDump(dump_path, is_async);
        DebugLog(null, "ReportDump");
    }

    /// <summary>
    /// [EN] Sets extra exception handler (Win, Xbox)<br/>
    /// [ZH] 设置额外异常捕获开关(Win、Xbox)
    /// </summary>
    /// <remarks>
    /// [EN] Disabled by default for backward compatibility.<br/>
    ///      When enabled, can catch illegal parameter crashes from security functions like strcpy_s,
    ///      and purecall errors from virtual function calls.<br/>
    /// [ZH] 默认为关闭，与旧版保持一致。<br/>
    ///      开启后，可以捕获上报strcpy_s一类的安全函数抛出的非法参数崩溃，以及虚函数调用purecall错误导致的崩溃。
    /// </remarks>
    /// <param name="extra_handle_enable"></param>
    public static void SetExtraHandler(bool extra_handle_enable)
    {
        UQMCrash.SetExtraHandler(extra_handle_enable);
        DebugLog(null, "SetExtraHandler");
    }

    /// <summary>
    /// [EN] Uploads dump files from specified path (Win, Xbox)<br/>
    /// [ZH] 上传指定路径下的dump文件(Win、Xbox)
    /// </summary>
    /// <param name="dump_dir">
    /// [EN] Directory containing dump files<br/>
    /// [ZH] dump文件所在目录
    /// </param>
    /// <param name="is_extra_check">
    /// [EN] Extra validation check, set to false by default<br/>
    /// [ZH] 额外校验检查，默认填false即可
    /// </param>
    public static void UploadGivenPathDump(string dump_dir, bool is_extra_check)
    {
        UQMCrash.UploadGivenPathDump(dump_dir, is_extra_check);
        DebugLog(null, "UploadGivenPathDump");
    }

    /// <summary>
    /// [EN] Sets dump type (Win, Xbox)<br/>
    /// [ZH] 设置dump类型(Win、Xbox)
    /// </summary>
    /// <param name="dump_type">
    /// [EN] Dump type value: see Windows documentation for valid values.<br/>
    /// [ZH] dump类型值,有效值请参考Windows官方文档
    /// </param>
    public static void SetDumpType(int dump_type)
    {
        UQMCrash.SetDumpType(dump_type);
    }

    /// <summary>
    /// [EN] Adds valid exception code (Win, Xbox)<br/>
    /// [ZH] 添加可用的异常类型(Win、Xbox)
    /// </summary>
    /// <remarks>
    /// [EN] Consult CrashSight developers before using<br/>
    /// [ZH] 使用前请咨询CrashSight开发人员
    /// </remarks>
    /// <param name="exp_code">
    /// [EN] Exception code: see Windows  documentation<br/>
    /// [ZH] 异常代码: 参考Windows官方文档
    /// </param>
    public static void AddValidExpCode(ulong exp_code)
    {
        UQMCrash.AddValidExpCode(exp_code);
    }

    /// <summary>
    /// [EN] Uploads crash with specified GUID (Win, Xbox)<br/>
    /// [ZH] 上报指定GUID的错误(Win、Xbox)
    /// </summary>
    /// <param name="guid">
    /// [EN] GUID identifying the specific issue, an be obtained through callback.<br/>
    /// [ZH] 唯一问题的代码，可通过回调获取
    /// </param>
    public static void UploadCrashWithGuid(string guid)
    {
        UQMCrash.UploadCrashWithGuid(guid);
    }

    /// <summary>
    /// [EN] Sets crash upload enable status (Win, Xbox)<br/>
    /// [ZH] 设置崩溃上报开关(Win、Xbox)
    /// </summary>
    /// <param name="enable"></param>
    public static void SetCrashUploadEnable(bool enable)
    {
        UQMCrash.SetCrashUploadEnable(enable);
    }

    /// <summary>
    /// [EN] Sets workspace path (Win, Xbox)<br/>
    /// [ZH] 设置工作空间路径(Win、Xbox)
    /// </summary>
    /// <param name="workspace">
    /// [EN] Absolute path to workspace directory<br/>
    /// [ZH] 工作空间的绝对路径
    /// </param>
    public static void SetWorkSpace(string workspace)
    {
        UQMCrash.SetWorkSpace(workspace);
    }

    /// <summary>
    /// [EN] Sets custom attachment directory (Win, Xbox)<br/>
    /// [ZH] 设置自定义附件目录(Win、Xbox)
    /// </summary>
    /// <param name="path">
    /// [EN] Absolute path to attachment directory<br/>
    /// [ZH] 附件的绝对路径
    /// </param>
    public static void SetCustomAttachDir(string path)
    {
        UQMCrash.SetCustomAttachDir(path);
    }

    /************************************PS4、PS5、Switch端接口************************************/

    /// <summary>
    /// [EN] Sets error report interval (PS4, PS5, Switch)<br/>
    /// [ZH] 设置错误上报间隔(PS4、PS5、Switch)
    /// </summary>
    /// <param name="interval">
    /// [EN] Report interval in seconds<br/>
    /// [ZH] 错误上报间隔(单位：秒)
    /// </param>
    public static void SetErrorUploadInterval(int interval)
    {
        UQMCrash.SetErrorUploadInterval(interval);
        DebugLog(null, "SetErrorUploadInterval");
    }

    /// <summary>
    /// [EN] Enables/disables error reporting (PS4, PS5, Switch)<br/>
    /// [ZH] 设置错误上报开关(PS4、PS5、Switch)
    /// </summary>
    /// <param name="enable"></param>
    public static void SetErrorUploadEnable(bool enable)
    {
        UQMCrash.SetErrorUploadEnable(enable);
        DebugLog(null, "SetErrorUploadEnable");
    }

    /************************************Linux端接口************************************/

    /// <summary>
    /// [EN] Sets directory for all record files (Linux)<br/>
    /// [ZH] 设置所有记录文件的路径(Linux)
    /// </summary>
    /// <remarks>
    /// [EN] Includes SDK logs and dump files, defaults to executable directory<br/>
    /// [ZH] 包括SDK日志和dump文件，默认为当前可执行文件的目录下
    /// </remarks>
    /// <param name="record_dir">
    /// [EN] Path to record files directory<br/>
    /// [ZH] 记录文件的路径
    /// </param>
    public static void SetRecordFileDir(string record_dir)
    {
        UQMCrash.SetRecordFileDir(record_dir);
        DebugLog(null, "SetRecordFileDir");
    }

    /************************************已废弃接口************************************/

    /// <summary>
    /// [EN] Initializes CrashSight (Windows/Xbox) - DEPRECATED, use <see cref="InitWithAppId"/> instead<br/>
    /// [ZH] 初始化CrashSight(Windows/Xbox平台)- 已废弃，请使用 <see cref="InitWithAppId"/> 替代
    /// </summary>
    /// <param name="userId"></param>
    /// <param name="version"></param>
    /// <param name="key"></param>
    [Obsolete("该接口已废弃，请使用 InitWithAppId 进行初始化")]
    public static void InitContext(string userId, string version, string key)
    {
        if (IsInitialized)
        {
            DebugLog(null, "CrashSightAgent has already been initialized.");

            return;
        }

        if (userId == null || string.IsNullOrEmpty(version) || string.IsNullOrEmpty(key))
        {
            return;
        }

        _isInitialized = true;
        // init the sdk with app id
        UQMCrash.InitContext(userId, version, key);
        DebugLog(null, "Initialized with userId: " + userId +" version: " + version + " key: " + key);

        // Register the LogCallbackHandler by Application.RegisterLogCallback(Application.LogCallback)
        _RegisterExceptionHandler();
    }

    /// <summary>
    /// [EN] Initializes CrashSight (Linux) - DEPRECATED, use <see cref="InitWithAppId"/> instead<br/>
    /// [ZH] 初始化CrashSight(Linux平台)- 已废弃，请使用 <see cref="InitWithAppId"/> 替代
    /// </summary>
    /// <param name="app_id"></param>
    /// <param name="app_key"></param>
    /// <param name="app_version"></param>
    [Obsolete("该接口已废弃，请使用 InitWithAppId 进行初始化")]
    public static void Init(string app_id, string app_key, string app_version)
    {
        if (IsInitialized)
        {
            DebugLog(null, "CrashSightAgent has already been initialized.");

            return;
        }

        if (string.IsNullOrEmpty(app_id) || string.IsNullOrEmpty(app_key) || string.IsNullOrEmpty(app_version))
        {
            return;
        }

        _isInitialized = true;
        // init the sdk with app id
        UQMCrash.Init(app_id, app_key, app_version);
        DebugLog(null, "Initialized with app_id: " + app_id + " app_key: " + app_key + " app_version: " + app_version);

        // Register the LogCallbackHandler by Application.RegisterLogCallback(Application.LogCallback)
        _RegisterExceptionHandler();
    }

    /// <summary>
    /// [EN] Configures default settings (Android/iOS/Switch) - DEPRECATED<br/>
    /// [ZH] 配置默认设置(Android/iOS/Switch平台)- 已废弃
    /// </summary>
    /// <param name="channel"></param>
    /// <param name="version"></param>
    /// <param name="user"></param>
    /// <param name="delay"></param>
    [Obsolete("该接口已废弃")]
    public static void ConfigDefault(string channel, string version, string user, long delay)
    {
        DebugLog(null, "Config default channel:" + channel + ", version:" + version + ", user:" + user + ", delay:" + delay);
        UQMCrash.ConfigDefault(channel, version, user, delay);
    }

    #region Privated Fields and Methods
    /************************************private部分************************************/

    private static void _OnLogCallbackHandlerMain(string condition, string stackTrace, LogType type)
    {
        _OnLogCallbackHandler(condition, stackTrace, type, CSReportType.LogCallback);
    }

    private static void _OnLogCallbackHandlerThreaded(string condition, string stackTrace, LogType type)
    {
        _OnLogCallbackHandler(condition, stackTrace, type, CSReportType.LogCallbackThreaded);
    }

    private static void _OnLogCallbackHandler(string condition, string stackTrace, LogType type, CSReportType rType)
    {
        if (_LogCallbackEventHandler != null)
        {
            _LogCallbackEventHandler(condition, stackTrace, type);
        }

        if (!IsInitialized)
        {
            return;
        }

        if (!string.IsNullOrEmpty(condition) && condition.Contains("[CrashSightAgent] <Log>"))
        {
            return;
        }

        if (_uncaughtAutoReportOnce)
        {
            return;
        }

        if (callbackThreads.Contains(Thread.CurrentThread.ManagedThreadId))
        {
            return;
        }

        _HandleException(type, null, condition, stackTrace, true, rType);
    }

    private static void _OnUncaughtExceptionHandler(object sender, System.UnhandledExceptionEventArgs args)
    {
        if (args == null || args.ExceptionObject == null)
        {
            return;
        }

        try
        {
            if (args.ExceptionObject.GetType() != typeof(System.Exception))
            {
                return;
            }
        }
        catch
        {
            if (UnityEngine.Debug.isDebugBuild == true)
            {
                UnityEngine.Debug.Log("CrashSightAgent: Failed to report uncaught exception");
            }

            return;
        }

        if (!IsInitialized)
        {
            return;
        }

        if (_uncaughtAutoReportOnce)
        {
            return;
        }

        _HandleException((System.Exception)args.ExceptionObject, null, true);
    }

    private static void _HandleException(System.Exception e, string message, bool uncaught)
    {
        if (e == null)
        {
            return;
        }

        if (!IsInitialized)
        {
            return;
        }

        string name = e.GetType().Name;
        string reason = e.Message;

        if (!string.IsNullOrEmpty(message))
        {
            reason = string.Format("{0}{1}***{2}", reason, Environment.NewLine, message);
        }

        StringBuilder stackTraceBuilder = new StringBuilder(512);

        StackTrace stackTrace = new StackTrace(e, true);
        int count = stackTrace.FrameCount;
        for (int i = 0; i < count; i++)
        {
            StackFrame frame = stackTrace.GetFrame(i);

            stackTraceBuilder.AppendFormat("{0}.{1}", frame.GetMethod().DeclaringType.Name, frame.GetMethod().Name);

            ParameterInfo[] parameters = frame.GetMethod().GetParameters();
            if (parameters == null || parameters.Length == 0)
            {
                stackTraceBuilder.Append(" () ");
            }
            else
            {
                stackTraceBuilder.Append(" (");

                int pcount = parameters.Length;

                ParameterInfo param = null;
                for (int p = 0; p < pcount; p++)
                {
                    param = parameters[p];
                    stackTraceBuilder.AppendFormat("{0} {1}", param.ParameterType.Name, param.Name);

                    if (p != pcount - 1)
                    {
                        stackTraceBuilder.Append(", ");
                    }
                }
                param = null;

                stackTraceBuilder.Append(") ");
            }

            string fileName = frame.GetFileName();
            if (!string.IsNullOrEmpty(fileName) && !fileName.ToLower().Equals("unknown"))
            {
                fileName = fileName.Replace("\\", "/");

                int loc = fileName.ToLower().IndexOf("/assets/");
                if (loc < 0)
                {
                    loc = fileName.ToLower().IndexOf("assets/");
                }

                if (loc > 0)
                {
                    fileName = fileName.Substring(loc);
                }

                stackTraceBuilder.AppendFormat("(at {0}:{1})", fileName, frame.GetFileLineNumber());
            }
            stackTraceBuilder.AppendLine();
        }

        // report
        _reportException(uncaught, name, reason, stackTraceBuilder.ToString());
    }

    private static bool ShouldSkipFrame(string frame)
    {
        string[] skipPatterns = { "System.Collections.Generic.", "ShimEnumerator", "CrashSight" };

        foreach (var pattern in skipPatterns)
        {
            if (frame.StartsWith(pattern, StringComparison.Ordinal))
            {
                return true;
            }
        }

        return false;
    }

    private static void _reportException(bool uncaught, string name, string reason, string stackTrace)
    {
        if (string.IsNullOrEmpty(name))
        {
            return;
        }

        if (string.IsNullOrEmpty(stackTrace))
        {
            stackTrace = StackTraceUtility.ExtractStackTrace();
        }

        if (string.IsNullOrEmpty(stackTrace))
        {
            stackTrace = "Empty";
        }
        else
        {

            try
            {
                string[] frames = stackTrace.Split('\n');

                if (frames != null && frames.Length > 0)
                {

                    StringBuilder trimFrameBuilder = new StringBuilder(512);

                    string frame = null;
                    int count = frames.Length;
                    for (int i = 0; i < count; i++)
                    {
                        frame = frames[i];

                        if (string.IsNullOrEmpty(frame) || string.IsNullOrEmpty(frame.Trim()))
                        {
                            continue;
                        }

                        frame = frame.Trim();

                        if (string.IsNullOrEmpty(frame) || ShouldSkipFrame(frame) || frame.Contains("..ctor"))
                        {
                            continue;
                        }

                        int start = -1;
                        int end = -1;
                        if (frame.Contains("(at") && frame.Contains("/assets/"))
                        {
                            start = frame.IndexOf("(at", StringComparison.OrdinalIgnoreCase);
                            end = frame.IndexOf("/assets/", StringComparison.OrdinalIgnoreCase);

                        }
                        if (start > 0 && end > 0)
                        {
                            trimFrameBuilder.AppendFormat("{0}(at {1}", frame.Substring(0, start).Replace(":", "."), frame.Substring(end));
                        }
                        else
                        {
                            trimFrameBuilder.Append(frame.Replace(":", "."));
                        }

                        trimFrameBuilder.AppendLine();
                    }

                    stackTrace = trimFrameBuilder.ToString();
                }
            }
            catch
            {
                PrintLog(CSLogSeverity.LogWarning, "{0}", "Error to parse the stack trace");
            }

        }

//        PrintLog(CSLogSeverity.LogError, "ReportException: " + name + " " + reason + "\n*********\n" + stackTrace + "\n*********");

        _uncaughtAutoReportOnce = uncaught && _autoQuitApplicationAfterReport;

        string extraInfo = string.Empty;
        Dictionary<string, string> extras = null;
        if (_LogCallbackExtrasHandler != null)
        {
            extras = _LogCallbackExtrasHandler();
        }

        if (extras != null && extras.Count > 0)
        {
            StringBuilder builder = new StringBuilder(128);
            foreach (KeyValuePair<string, string> kvp in extras)
            {
                builder.AppendFormat("\"{0}\" : \"{1}\", ", kvp.Key, kvp.Value);
            }
            builder.Length -= 2;  // 去掉最后一个逗号和空格
            extraInfo = "{" + builder.ToString() + "}";
        }
        UQMCrash.ReportException(4, name, reason, stackTrace, extraInfo, uncaught && AutoQuitApplicationAfterReport);
    }

    private static int valueOf(LogType logLevel)
    {
        switch(logLevel)
        {
            case LogType.Exception:
                return 1;
            case LogType.Error:
                return 2;
            case LogType.Assert:
                return 3;
            case LogType.Warning:
                return 4;
            case LogType.Log:
                return 5;
            default:
                return 6;
        }
    }

    private static bool isEnableAutoReport(LogType logLevel)
    {
        return valueOf(logLevel) <= valueOf(_autoReportLogLevel);
    }

    private static void _HandleException(LogType logLevel, string name, string message, string stackTrace, bool uncaught, CSReportType rType)
    {
        if (!IsInitialized)
        {
            DebugLog(null, "It has not been initialized.");
            return;
        }

        if ((uncaught && !isEnableAutoReport(logLevel)))
        {
            DebugLog(null, "Not report exception for level " + logLevel.ToString());
            return;
        }

        string type = null;
        string reason = "";

        if (!string.IsNullOrEmpty(message))
        {
            try
            {
                if ((LogType.Exception == logLevel) && message.Contains("Exception"))
                {

                    Match match = new Regex(@"^(?<errorType>\S+):\s*(?<errorMessage>.*)", RegexOptions.Singleline).Match(message);

                    if (match.Success)
                    {
                        if (stackTrace.Contains("UnityEngine.Debug:LogException"))
                        {
                            if (rType == CSReportType.LogCallback)
                            {
                                return;
                            }
                        }
                        else
                        {
                            if (rType == CSReportType.LogCallbackThreaded)
                            {
                                return;
                            }
                        }
                        type = match.Groups["errorType"].Value.Trim();
                        reason = match.Groups["errorMessage"].Value.Trim();
                    }
                    else if (rType == CSReportType.LogCallback)
                    {
                        return;
                    }
                }
                else if ((LogType.Error == logLevel) && message.StartsWith("Unhandled Exception:", StringComparison.Ordinal))
                {

                    Match match = new Regex(@"^Unhandled\s+Exception:\s*(?<exceptionName>\S+):\s*(?<exceptionDetail>.*)", RegexOptions.Singleline).Match(message);

                    if (match.Success)
                    {
                        if (rType == CSReportType.LogCallbackThreaded)
                        {
                            return;
                        }
                        string exceptionName = match.Groups["exceptionName"].Value.Trim();
                        string exceptionDetail = match.Groups["exceptionDetail"].Value.Trim();

                        //
                        int dotLocation = exceptionName.LastIndexOf(".");
                        if (dotLocation > 0 && dotLocation != exceptionName.Length)
                        {
                            type = exceptionName.Substring(dotLocation + 1);
                        }
                        else
                        {
                            type = exceptionName;
                        }

                        int stackLocation = exceptionDetail.IndexOf(" at ");
                        if (stackLocation > 0)
                        {
                            //
                            reason = exceptionDetail.Substring(0, stackLocation);
                            // substring after " at "
                            string callStacks = exceptionDetail.Substring(stackLocation + 3).Replace(" at ", "\n").Replace("in <filename unknown>:0", string.Empty).Replace("[0x00000]", string.Empty);
                            //
                            stackTrace = stackTrace + "\n" + callStacks.Trim();

                        }
                        else
                        {
                            reason = exceptionDetail;
                        }

                        // for LuaScriptException
                        if (type.Equals("LuaScriptException") && exceptionDetail.Contains(".lua") && exceptionDetail.Contains("stack traceback:"))
                        {
                            stackLocation = exceptionDetail.IndexOf("stack traceback:");
                            if (stackLocation > 0)
                            {
                                reason = exceptionDetail.Substring(0, stackLocation);
                                // substring after "stack traceback:"
                                string callStacks = exceptionDetail.Substring(stackLocation + 16).Replace(" [", " \n[");

                                //
                                stackTrace = stackTrace + "\n" + callStacks.Trim();
                            }
                        }
                    }
                    else if (rType == CSReportType.LogCallback)
                    {
                        return;
                    }
                }
                else if (rType == CSReportType.LogCallback)
                {
                    return;
                }
            }
            catch
            {

            }

            if (string.IsNullOrEmpty(reason))
            {
                reason = message;
            }
        }

        if (string.IsNullOrEmpty(name))
        {
            if (string.IsNullOrEmpty(type))
            {
                string sLogLevel = "Log";
                switch (logLevel)
                {
                    case LogType.Log:
                        sLogLevel = "Log";
                        break;
                    case LogType.Warning:
                        sLogLevel = "LogWarning";
                        break;
                    case LogType.Assert:
                        sLogLevel = "LogAssert";
                        break;
                    case LogType.Error:
                        sLogLevel = "LogError";
                        break;
                    case LogType.Exception:
                        sLogLevel = "LogException";
                        break;
                }
                type = "Unity" + sLogLevel;
                if (CrashSightStackTrace.enable)
                {
                    try
                    {
                        stackTrace = CrashSightStackTrace.ExtractStackTrace();
                        //去掉前三行
                        if (stackTrace.Contains("CrashSightAgent:_HandleException(LogType, String, String, String, Boolean)")
                            && stackTrace.Contains("CrashSightAgent:_OnLogCallbackHandler(String, String, LogType)")
                            && stackTrace.Contains("UnityEngine.Application:CallLogCallback(String, String, LogType, Boolean)"))
                        {
                            int count = stackTrace.IndexOf("\n");
                            count = stackTrace.IndexOf("\n", count + 1);
                            count = stackTrace.IndexOf("\n", count + 1);
                            stackTrace = stackTrace.Substring(count + 1);
                        }
                    }
                    catch
                    {
                    }
                }
            }
        }
        else
        {
            type = name;
        }

        _reportException(uncaught, type, reason, stackTrace);
    }

    #endregion

    /// <summary>
    /// 监听 footprint 接近 iOS 内存上限的回调（仅 iOS / Mac，OOM 监控开启后生效）。
    /// </summary>
    /// <param name="memoryThresholdBytes">footprint 距离上限的阈值（字节）；差值 ≤ 该值时触发回调。</param>
    /// <param name="timeIntervalSeconds">两次回调最小间隔（秒）；0 表示仅触发一次。</param>
    /// <param name="callback">回调；参数：footprint、当前 memoryLimit（=已用+可用）。传 null 取消注册。</param>
    public static void SetMemoryNearLimitCallback(ulong memoryThresholdBytes,
                                                   double timeIntervalSeconds,
                                                   System.Action<ulong, ulong> callback)
    {
        UQMCrash.SetMemoryNearLimitCallback(memoryThresholdBytes, timeIntervalSeconds, callback);
    }
}
