com.guru.unity.sdk.core/Runtime/GuruAnalytics/Runtime/Script/GuruAnalytics.cs

624 lines
19 KiB
C#

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<string, string> _userProperties;
/// <summary>
/// 用户属性缓存字典
/// </summary>
public static Dictionary<string, string> UserProperties
{
get
{
if (_userProperties == null)
{
_userProperties = new Dictionary<string, string>(10);
}
return _userProperties;
}
}
/// <summary>
/// 错误 code 表
/// </summary>
public static List<int> ErrorCodeList = new List<int>();
private static bool _autoSyncProperties = false;
private static bool _enableErrorLog = false;
/// <summary>
/// 启动日志错误上报
/// </summary>
public static bool EnableErrorLog
{
get => _enableErrorLog;
set
{
_enableErrorLog = value;
if (_enableErrorLog) InitCallbacks(); // 激活错误日志回调
if (Agent != null) Agent.EnableErrorLog = _enableErrorLog;
}
}
#region 公用接口
/// <summary>
/// 初始化接口
/// </summary>
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(); // 激活错误日志回调
}
/// <summary>
/// 设置视图名称
/// </summary>
/// <param name="screenName"></param>
public static void SetScreen(string screenName)
{
if (string.IsNullOrEmpty(screenName)) return;
CacheUserProperty($"screen_name", screenName);
Agent?.SetScreen(screenName);
}
/// <summary>
/// 设置广告ID
/// </summary>
/// <param name="id"></param>
public static void SetAdId(string id)
{
if (string.IsNullOrEmpty(id)) return;
CacheUserProperty($"ad_id", id);
Agent?.SetAdId(id);
}
/// <summary>
/// 设置用户属性
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public static void SetUserProperty(string key, string value)
{
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return;
CacheUserProperty(key, value); // 添加用户属性
Agent?.SetUserProperty(key, value);
}
/// <summary>
/// 设置Firebase ID
/// </summary>
/// <param name="id"></param>
public static void SetFirebaseId(string id)
{
if (string.IsNullOrEmpty(id)) return;
CacheUserProperty($"firebase_id", id);
Agent?.SetFirebaseId(id);
}
/// <summary>
/// 设置Adjust ID
/// </summary>
/// <param name="id"></param>
public static void SetAdjustId(string id)
{
if (string.IsNullOrEmpty(id)) return;
CacheUserProperty($"adjust_id", id);
Agent?.SetAdjustId(id);
}
/// <summary>
/// 设置设备ID
/// </summary>
/// <param name="deviceId"></param>
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);
}
/// <summary>
/// 设置用户ID
/// </summary>
/// <param name="uid"></param>
public static void SetUid(string uid)
{
if (string.IsNullOrEmpty(uid)) return;
CacheUserProperty($"uid", uid);
Agent?.SetUid(uid);
}
/// <summary>
/// 上报事件成功率
/// </summary>
public static void ReportEventSuccessRate() => Agent?.ReportEventSuccessRate();
/// <summary>
/// 上报打点事件
/// </summary>
/// <param name="eventName">事件名称</param>
/// <param name="data">INT类型的值</param>
public static void LogEvent(string eventName, Dictionary<string, dynamic> 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<string, dynamic> data)
{
string raw = "";
List<string> strList = new List<string>(data.Count);
foreach (var kvp in data)
{
strList.Add(BuildStringValue(kvp));
raw = string.Join(",", strList);
}
return raw;
}
private static string BuildParamsJson(Dictionary<string, dynamic> data)
{
try
{
// 强制转换加入国家设置
return JsonConvert.SerializeObject(data, new JsonSerializerSettings()
{
Culture = new CultureInfo("en-US"),
});
}
catch (Exception e)
{
Debug.LogError(e);
}
return "";
}
/// <summary>
/// 构建带有类型格式的Str值
/// </summary>
/// <param name="kvp"></param>
/// <returns></returns>
private static string BuildStringValue(KeyValuePair<string, dynamic> 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}";
}
/// <summary>
/// 设置太极02值
/// </summary>
/// <param name="value"></param>
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 用户属性
/// <summary>
/// 记录用户属性
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
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);
}
}
/// <summary>
/// 获取SDK回调
/// </summary>
/// <param name="msg"></param>
private static void OnSDKCallback(string raw)
{
if (string.IsNullOrEmpty(raw)) return;
if (!raw.Contains($"\"{ActionName}\"")) return; // 不对其他行为的日志进行过滤
ParseWithJson(raw);
}
/// <summary>
/// 上报错误信息
/// </summary>
/// <param name="code"></param>
/// <param name="errorInfo"></param>
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<string, dynamic> parameters = new Dictionary<string, dynamic>()
{
{"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<JObject>(json);
if(dict != null && dict.TryGetValue("data", out var jData))
{
var j = jData.Value<JObject>();
if (j != null && j.TryGetValue("code", out var jCode))
{
code = jCode.Value<int>();
if (j.TryGetValue("msg", out var jMsg))
{
info = jMsg.Value<string>();
}
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
}
/// <summary>
/// 网络状态枚举
/// </summary>
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,
}
}