627 lines
19 KiB
C#
627 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.11.1";
|
|
|
|
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>
|
|
public List<int> ErrorCodeList = new List<int>();
|
|
private bool _enableErrorLog = false;
|
|
|
|
/// <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, Action onInitComplete, bool isDebug = false,
|
|
bool enableErrorLog = false)
|
|
{
|
|
Debug.Log($"{Tag} --- Guru Analytics [{Version}] initialing...");
|
|
|
|
if (_instance == null)
|
|
{
|
|
_instance = new GuruAnalytics();
|
|
_instance.Agent.Init(appId, deviceInfo, onInitComplete, isDebug);
|
|
_instance.EnableErrorLog = 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); // 添加用户属性
|
|
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>
|
|
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;
|
|
}
|
|
*/
|
|
|
|
private 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}\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}");
|
|
|
|
// Only for firebase GA
|
|
Analytics.TrackEvent("dev_audit", parameters, new Analytics.EventSetting() { EnableFirebaseAnalytics = true });
|
|
}
|
|
|
|
|
|
private 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, StringComparison.Ordinal) + p.Length);
|
|
info = $"JsonEX:{m}";
|
|
// Debug.Log($"{Tag} --- {info}");
|
|
Analytics.LogCrashlytics(json, false);
|
|
Analytics.LogCrashlytics(info);
|
|
ReportCodeInfo(code, info);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
private void ParseWithRaw(string raw)
|
|
{
|
|
var code = (int)AnalyticsCode.Unknown;
|
|
string info;
|
|
|
|
//------- message send to unity ----------
|
|
Debug.Log($"{Tag} get callback errorInfo:\n{raw}");
|
|
|
|
|
|
var 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
|
|
{
|
|
|
|
var idx = raw.IndexOf(patten, StringComparison.Ordinal) + patten.Length;
|
|
string act = raw.Substring(idx, ActionName.Length);
|
|
if (act == ActionName)
|
|
{
|
|
patten = "code\":";
|
|
var patten2 = ",\"msg";
|
|
idx = raw.IndexOf(patten, StringComparison.Ordinal);
|
|
var idx2 = raw.IndexOf(patten2, StringComparison.Ordinal);
|
|
|
|
var 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\":", StringComparison.Ordinal) + 5));
|
|
}
|
|
}
|
|
**/
|
|
|
|
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:
|
|
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)
|
|
{
|
|
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_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,
|
|
}
|
|
}
|
|
|