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

579 lines
18 KiB
C#

namespace Guru
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using System.Diagnostics.CodeAnalysis;
/// <summary>
/// 自打点初始化配置
/// </summary>
public class GuruAnalyticsInitConfig
{
public bool usingExtraSettings = false;
public string baseUrl = "";
public string[] uploadIpAddress = null;
public bool enableErrorLog = false;
public bool isDebug = false;
public Action onInitComplete = null;
}
public class GuruAnalytics
{
// Plugin Version
private const string Version = "1.12.0";
public static readonly string Tag = "[ANU]";
private static readonly string ActionName = "logger_error";
private const int EventPriorityDefault = 10;
private static GuruAnalytics _instance;
public static GuruAnalytics Instance
{
get
{
if (_instance == null)
{
throw new Exception("GuruAnalytics not initialized. Please call <Analytics.InitAnalytics()> first.");
}
return _instance;
}
}
private bool _isReady = false;
public bool IsReady => _isReady;
private IAnalyticsAgent _agent;
private IAnalyticsAgent Agent
{
get
{
if (_agent == null)
{
#if UNITY_EDITOR
_agent = new AnalyticsAgentMock();
#elif UNITY_ANDROID
_agent = new AnalyticsAgentAndroid();
#elif UNITY_IOS
_agent = new AnalyticsAgentIOS();
#endif
}
if (_agent == null)
{
throw new NotImplementedException("You Should Implement IAnalyticsAgent on platform first.");
}
return _agent;
}
}
private Dictionary<string, string> _userProperties;
/// <summary>
/// 用户属性缓存字典
/// </summary>
private Dictionary<string, string> UserProperties
{
get
{
if (_userProperties == null)
{
_userProperties = new Dictionary<string, string>(10);
}
return _userProperties;
}
}
/// <summary>
/// 错误 code 表
/// </summary>
private readonly List<int> _errorCodeList = new List<int>();
private bool _enableErrorLog;
/// <summary>
/// 启动日志错误上报
/// </summary>
public 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, GuruAnalyticsInitConfig initConfig, Action onInitComplete = null)
{
Debug.Log($"{Tag} --- Guru Analytics [{Version}] initialing...");
if (_instance == null)
{
_instance = new GuruAnalytics();
#if UNITY_ANDROID
if (initConfig.usingExtraSettings && Instance.Agent is AnalyticsAgentAndroid androidAgent)
{
// 强制转换为 Android 的自打点初始化接口
androidAgent.InitAndroidConfig(appId, deviceInfo,
initConfig.baseUrl, initConfig.uploadIpAddress, // <--- Android 附加参数
onInitComplete,initConfig.isDebug);
}
else
{
// 外部(云控)如果关闭使用 Android 自打点附加参数, 则使用正常的启动接口
_instance.Agent.Init(appId, deviceInfo, onInitComplete, initConfig.isDebug);
}
#else
// iOS 使用正常的启动接口
_instance.Agent.Init(appId, deviceInfo, onInitComplete, initConfig.isDebug);
#endif
_instance.EnableErrorLog = initConfig.enableErrorLog;
_instance._isReady = true;
}
}
/// <summary>
/// 设置视图名称
/// </summary>
/// <param name="screenName"></param>
public void SetScreen(string screenName)
{
if (string.IsNullOrEmpty(screenName)) return;
CacheUserProperty($"screen_name", screenName);
Agent.SetScreen(screenName);
}
/// <summary>
/// 设置广告ID
/// </summary>
/// <param name="id"></param>
public 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 void SetUserProperty(string key, string value)
{
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return;
CacheUserProperty(key, value); // 添加用户属性
// ReSharper disable once Unity.PerformanceCriticalCodeInvocation
Agent.SetUserProperty(key, value);
}
/// <summary>
/// 设置Firebase ID
/// </summary>
/// <param name="id"></param>
public 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 void SetAdjustId(string id)
{
if (string.IsNullOrEmpty(id)) return;
CacheUserProperty($"adjust_id", id);
Agent.SetAdjustId(id);
}
/// <summary>
/// 设置设备ID
/// </summary>
/// <param name="deviceId"></param>
public void SetDeviceId(string deviceId)
{
if (string.IsNullOrEmpty(deviceId)) return;
CacheUserProperty($"device_id", deviceId);
Agent.SetDeviceId(deviceId);
}
public void SetAndroidID(string androidId)
{
if (string.IsNullOrEmpty(androidId)) return;
CacheUserProperty(Analytics.PropertyAndroidID, androidId);
}
public void SetIDFV(string idfv)
{
if (string.IsNullOrEmpty(idfv)) return;
CacheUserProperty(Analytics.PropertyIDFV, idfv);
}
public void SetIDFA(string idfa)
{
if (string.IsNullOrEmpty(idfa)) return;
CacheUserProperty(Analytics.PropertyIDFA, idfa);
}
/// <summary>
/// 设置用户ID
/// </summary>
/// <param name="uid"></param>
public void SetUid(string uid)
{
if (string.IsNullOrEmpty(uid)) return;
CacheUserProperty($"uid", uid);
Agent.SetUid(uid);
}
/// <summary>
/// 上报事件成功率
/// </summary>
public void ReportEventSuccessRate() => Agent.ReportEventSuccessRate();
/// <summary>
/// 上报打点事件
/// </summary>
/// <param name="eventName">事件名称</param>
/// <param name="data">INT类型的值</param>
/// <param name="priority"></param>
[SuppressMessage("ReSharper", "Unity.PerformanceCriticalCodeInvocation")]
public void LogEvent(string eventName, Dictionary<string, dynamic> data = null, int priority = -1)
{
string raw = "";
if (data != null && data.Count > 0)
{
raw = BuildParamsJson(data);
}
if (priority < 0) priority = EventPriorityDefault;
Debug.Log($"{Tag} --- LogEvent GuruAnalytics:{eventName} | raw: {raw} | priority: {priority}");
Agent.LogEvent(eventName, raw, priority);
}
/*
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;
}
*/
[SuppressMessage("ReSharper", "Unity.PerformanceCriticalCodeInvocation")]
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 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 void CacheUserProperty(string key, string value)
{
// bool needUpdate = !UserProperties.ContainsKey(key) || UserProperties[key] != value;
UserProperties[key] = value;
// if (needUpdate) UpdateAllUserProperties();
}
#endregion
#region 日志回调
private 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="raw"></param>
private 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 void OnLoggerErrorEvent(int code, string errorInfo = "")
{
// Debug.Log($"{Tag} --- OnLoggerErrorEvent: code:{code}\t info:{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}");
// Only for firebase GA
Analytics.TrackEvent("dev_audit", parameters, new Analytics.EventSetting() { EnableFirebaseAnalytics = true });
}
private void 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);
}
catch (Exception)
{
string p = "\"msg\":\"";
string m = json;
if (json.Contains(p)) m = json.Substring(json.IndexOf(p, StringComparison.Ordinal) + p.Length);
info = $"JsonEX:{m}";
// Debug.Log($"{Tag} --- {info}");
Analytics.LogCrashlytics(json, false);
Analytics.LogCrashlytics(info);
ReportCodeInfo(code, info);
}
}
private 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:
case AnalyticsCode.EVENT_LOOKUP:
case AnalyticsCode.EVENT_SESSION_ACTIVE:
canCatch = true;
break;
}
if (!canCatch && code is > 100 and <= 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)
{
Instance.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失败
CRONET_INIT_EXCEPTION = 27, // 开启Cronet报错
ERROR_API = 101, // 调用api出错
ERROR_RESPONSE = 102, // api返回结果错误
ERROR_CACHE_CONTROL = 103, // 设置cacheControl出错
ERROR_DELETE_EXPIRED = 104, // 删除过期事件出错
ERROR_LOAD_MARK = 105, // 从数据库取事件以及更改事件状态为正在上报出错
ERROR_DNS = 106, // dns 错误
ERROR_ZIP = 107, // zip 错误
ERROR_DNS_CACHE = 108, // zip 错误
CRONET_INTERCEPTOR = 109, // cronet拦截器
EVENT_LOOKUP = 1003,
EVENT_SESSION_ACTIVE = 1004,
}
}