namespace Guru { using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; public class GuruAnalytics { // Plugin Version public const string Version = "1.10.4"; public static readonly string Tag = "[ANU]"; private static readonly string ActionName = "logger_error"; private static IAnalyticsAgent _agent; public static IAnalyticsAgent Agent { get { if (_agent == null) { #if UNITY_EDITOR _agent = new AnalyticsAgentStub(); #elif UNITY_ANDROID _agent = new AnalyticsAgentAndroid(); #elif UNITY_IOS _agent = new AnalyticsAgentIOS(); #endif } return _agent; } } private static Dictionary _userProperties; /// /// 用户属性缓存字典 /// public static Dictionary UserProperties { get { if (_userProperties == null) { _userProperties = new Dictionary(10); } return _userProperties; } } /// /// 错误 code 表 /// public static List ErrorCodeList = new List(); private static bool _autoSyncProperties = false; private static bool _enableErrorLog = false; /// /// 启动日志错误上报 /// public static bool EnableErrorLog { get => _enableErrorLog; set { _enableErrorLog = value; if (_enableErrorLog) InitCallbacks(); // 激活错误日志回调 if (Agent != null) Agent.EnableErrorLog = _enableErrorLog; } } #region 公用接口 /// /// 初始化接口 /// public static void Init(string appId, string deviceInfo, bool isDebug = false, bool enableErrorLog = false, bool syncProperties = false) { _autoSyncProperties = syncProperties; _enableErrorLog = enableErrorLog; Agent?.Init(appId, deviceInfo, isDebug); if(_enableErrorLog) InitCallbacks(); // 激活错误日志回调 } /// /// 设置视图名称 /// /// public static void SetScreen(string screenName) { if (string.IsNullOrEmpty(screenName)) return; CacheUserProperty($"screen_name", screenName); Agent?.SetScreen(screenName); } /// /// 设置广告ID /// /// public static void SetAdId(string id) { if (string.IsNullOrEmpty(id)) return; CacheUserProperty($"ad_id", id); Agent?.SetAdId(id); } /// /// 设置用户属性 /// /// /// public static void SetUserProperty(string key, string value) { if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return; CacheUserProperty(key, value); // 添加用户属性 Agent?.SetUserProperty(key, value); } /// /// 设置Firebase ID /// /// public static void SetFirebaseId(string id) { if (string.IsNullOrEmpty(id)) return; CacheUserProperty($"firebase_id", id); Agent?.SetFirebaseId(id); } /// /// 设置Adjust ID /// /// public static void SetAdjustId(string id) { if (string.IsNullOrEmpty(id)) return; CacheUserProperty($"adjust_id", id); Agent?.SetAdjustId(id); } /// /// 设置设备ID /// /// public static void SetDeviceId(string deviceId) { if (string.IsNullOrEmpty(deviceId)) return; CacheUserProperty($"device_id", deviceId); Agent?.SetDeviceId(deviceId); } public static void SetAndroidID(string androidId) { if (string.IsNullOrEmpty(androidId)) return; CacheUserProperty(Analytics.PropertyAndroidID, androidId); } public static void SetIDFV(string idfv) { if (string.IsNullOrEmpty(idfv)) return; CacheUserProperty(Analytics.PropertyIDFV, idfv); } public static void SetIDFA(string idfa) { if (string.IsNullOrEmpty(idfa)) return; CacheUserProperty(Analytics.PropertyIDFA, idfa); } /// /// 设置用户ID /// /// public static void SetUid(string uid) { if (string.IsNullOrEmpty(uid)) return; CacheUserProperty($"uid", uid); Agent?.SetUid(uid); } /// /// 上报事件成功率 /// public static void ReportEventSuccessRate() => Agent?.ReportEventSuccessRate(); /// /// 上报打点事件 /// /// 事件名称 /// INT类型的值 public static void LogEvent(string eventName, Dictionary data = null) { if(_autoSyncProperties) UpdateAllUserProperties(); // 每次打点更新用户属性 string raw = ""; if (data != null && data.Count > 0) { raw = BuildParamsJson(data); } Debug.Log($"{Tag} event:{eventName} | raw: {raw}"); Agent?.LogEvent(eventName, raw); } private static string BuildParamsString(Dictionary data) { string raw = ""; List strList = new List(data.Count); foreach (var kvp in data) { strList.Add(BuildStringValue(kvp)); raw = string.Join(",", strList); } return raw; } private static string BuildParamsJson(Dictionary data) { try { // 强制转换加入国家设置 return JsonConvert.SerializeObject(data, new JsonSerializerSettings() { Culture = new CultureInfo("en-US"), }); } catch (Exception e) { Debug.LogError(e); } return ""; } /// /// 构建带有类型格式的Str值 /// /// /// private static string BuildStringValue(KeyValuePair kvp) { if (kvp.Value is int || kvp.Value is long) { return $"{kvp.Key}:i{kvp.Value}"; } if (kvp.Value is double || kvp.Value is float) { double dValue = (double)((object)kvp.Value); return $"{kvp.Key}:d{dValue.ToString("F11", new CultureInfo("en-US"))}"; // 保留精度进行转换 } return $"{kvp.Key}:s{kvp.Value}"; } /// /// 设置太极02值 /// /// public static void SetTch02Value(double value) { Debug.Log($"{Tag} set tch_02_value:{value}"); Agent?.SetTch02Value(value); } #endregion #region iOS独有接口 #if UNITY_IOS // 触发测试崩溃埋点 public static void TestCrash() => AnalyticsAgentIOS.TestCrashEvent(); #endif #endregion #region 用户属性 /// /// 记录用户属性 /// /// /// private static void CacheUserProperty(string key, string value) { bool needUpdate = !UserProperties.ContainsKey(key) || UserProperties[key] != value; UserProperties[key] = value; // if (needUpdate) UpdateAllUserProperties(); } private static void UpdateAllUserProperties() { if (UserProperties != null && UserProperties.Count > 0) { var keys = UserProperties.Keys.ToArray(); int i = 0; string key = ""; while (i < keys.Length) { key = keys[i]; if(!string.IsNullOrEmpty(key)) SetUserProperty(key, UserProperties[key]); i++; } keys = null; } } #endregion #region 日志回调 private static void InitCallbacks() { try { GuruSDKCallback.RemoveCallback(OnSDKCallback); GuruSDKCallback.AddCallback(OnSDKCallback); if (Agent != null) Agent.InitCallback(GuruSDKCallback.ObjectName, GuruSDKCallback.MethodName); } catch (Exception ex) { Analytics.LogCrashlytics(ex); } } /// /// 获取SDK回调 /// /// private static void OnSDKCallback(string raw) { if (string.IsNullOrEmpty(raw)) return; if (!raw.Contains($"\"{ActionName}\"")) return; // 不对其他行为的日志进行过滤 ParseWithJson(raw); } /// /// 上报错误信息 /// /// /// private static void OnLoggerErrorEvent(int code, string errorInfo = "") { // Debug.Log($"{Tag} --- OnLoggerErrorEvent: code:{code}\tinfo:{errorInfo}"); var codeString = ((AnalyticsCode)code).ToString(); if (string.IsNullOrEmpty(codeString) || codeString == AnalyticsCode.Unknown.ToString()) codeString = $"ErrorCode:{code}"; Dictionary parameters = new Dictionary() { {"item_category", "error_event"}, {"item_name", codeString}, {"country", IPMConfig.IPM_COUNTRY_CODE}, {"network", Application.internetReachability.ToString()}, }; if (!string.IsNullOrEmpty(errorInfo)) { // if (errorInfo.Contains("\"")) errorInfo = errorInfo.Replace("\"", "\\\""); int len = 96; if (errorInfo.Length > len) errorInfo = errorInfo.TrimStart().Substring(0, len); } else { errorInfo = "empty error info"; } parameters["err"] = errorInfo; Debug.Log($"{Tag} ------ ErrorLogInfo:: code:{codeString}\tinfo:{errorInfo}"); #if !UNITY_EDITOR // Only for firebase GA Analytics.LogEvent("dev_audit", parameters, new Analytics.EventSetting() { EnableFirebaseAnalytics = true }); #endif } private static bool ParseWithJson(string json) { Debug.Log($"{Tag} ------ ParseWithJson: json:\n{json}"); int code = (int)AnalyticsCode.Unknown; string info = json; try { var dict = JsonConvert.DeserializeObject(json); if(dict != null && dict.TryGetValue("data", out var jData)) { var j = jData.Value(); if (j != null && j.TryGetValue("code", out var jCode)) { code = jCode.Value(); if (j.TryGetValue("msg", out var jMsg)) { info = jMsg.Value(); } else { info = $"wrong msg format: {json}"; } } } else { info = "no data property"; } ReportCodeInfo(code, info); return true; } catch (Exception ex) { string p = "\"msg\":\""; string m = json; if (json.Contains(p)) m = json.Substring(json.IndexOf(p) + p.Length); info = $"JsonEX:{m}"; // Debug.Log($"{Tag} --- {info}"); Analytics.LogCrashlytics(json, false); Analytics.LogCrashlytics(info); ReportCodeInfo(code, info); } return false; } private static void ParseWithRaw(string raw) { int code = (int)AnalyticsCode.Unknown; string info = raw; //------- message send to unity ---------- Debug.Log($"{Tag} get callback errorInfo:\n{raw}"); string patten = ""; string patten2 = ""; int idx = 0; int idx2 = 0; int len = 0; patten = "msg\":\""; if (raw.Contains(patten)) { info = raw.Substring(raw.IndexOf(patten, StringComparison.Ordinal) + patten.Length); if (!string.IsNullOrEmpty(info)) { if (info.StartsWith("\"")) info = info.Substring(1, info.Length - 1); if (info.EndsWith("\"}}")) info = info.Replace("\"}}", ""); } else { info = "msg is null"; } } else { info = "no msg property"; } try { idx = raw.IndexOf(patten, StringComparison.Ordinal) + patten.Length; string act = raw.Substring(idx, ActionName.Length); if (act == ActionName) { patten = "code\":"; patten2 = ",\"msg"; idx = raw.IndexOf(patten); idx2 = raw.IndexOf(patten2); len = idx2 - (idx + patten.Length); if (len > 0) { string c = raw.Substring(idx + patten.Length, len); int.TryParse(c, out code); } // Catch target code to report errors switch ((AnalyticsCode)code) { case AnalyticsCode.Network_Lost: case AnalyticsCode.ERROR_API: case AnalyticsCode.ERROR_RESPONSE: case AnalyticsCode.ERROR_CACHE_CONTROL: case AnalyticsCode.ERROR_DELETE_EXPIRED: case AnalyticsCode.ERROR_LOAD_MARK: case AnalyticsCode.ERROR_DNS: case AnalyticsCode.ERROR_ZIP: OnLoggerErrorEvent(code, info); return; } return; } } catch (Exception ex) { Debug.LogError($"{Tag} Catch ex: {ex}\tJson:{raw}"); Analytics.LogCrashlytics(raw, false); Analytics.LogCrashlytics($"{Tag} --- Json:{raw} Ex:{ex}"); OnLoggerErrorEvent(code, info); return; } if (raw.Contains("msg")) { Analytics.LogCrashlytics(raw, false); Analytics.LogCrashlytics($"{Tag} --- format error:{raw}"); OnLoggerErrorEvent(code, raw.Substring(raw.IndexOf("msg\":" ) + 5)); } } private static void ReportCodeInfo(int code, string info) { var ac = (AnalyticsCode)code; Debug.Log($"{Tag} ------ Get Code And Info: code:{code}[{ac}] \tinfo:{info}"); bool canCatch = false; switch (ac) { case AnalyticsCode.Unknown: case AnalyticsCode.DELETE_EXPIRED: case AnalyticsCode.UPLOAD_FAIL: case AnalyticsCode.Network_Lost: case AnalyticsCode.CRONET_INIT_FAIL: case AnalyticsCode.CRONET_INIT_EXCEPTION: case AnalyticsCode.ERROR_API: case AnalyticsCode.ERROR_RESPONSE: case AnalyticsCode.ERROR_CACHE_CONTROL: case AnalyticsCode.ERROR_DELETE_EXPIRED: case AnalyticsCode.ERROR_LOAD_MARK: case AnalyticsCode.ERROR_DNS: case AnalyticsCode.ERROR_ZIP: case AnalyticsCode.ERROR_DNS_CACHE: case AnalyticsCode.CRONET_INTERCEPTOR: canCatch = true; break; } if (code > 100 && code <= 200) { // 100 < code <= 200 canCatch = true; } if (ErrorCodeList != null && ErrorCodeList.Count > 0) { if (ErrorCodeList[0] == -1) { canCatch = true; } else { canCatch = ErrorCodeList.Contains(code); } } if(canCatch) OnLoggerErrorEvent(code, info); } #endregion #region UNIT_TEST #if UNITY_EDITOR public static void TestOnCallback(string msg) { OnSDKCallback(msg); } #endif #endregion } /// /// 网络状态枚举 /// public enum AnalyticsCode { Unknown = -1, DELETE_EXPIRED = 12, UPLOAD_FAIL = 14, Network_Lost = 22, CRONET_INIT_FAIL = 26, CRONET_INIT_EXCEPTION = 27, ERROR_API = 101, ERROR_RESPONSE = 102, ERROR_CACHE_CONTROL = 103, ERROR_DELETE_EXPIRED = 104, ERROR_LOAD_MARK = 105, ERROR_DNS = 106, ERROR_ZIP = 107, ERROR_DNS_CACHE = 108, CRONET_INTERCEPTOR = 109, } }