579 lines
18 KiB
C#
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,
|
|
}
|
|
}
|
|
|