Skip to content

Commit debc6d6

Browse files
Mobile Ads Developer Relationscopybara-github
authored andcommitted
Update RCS config.
PiperOrigin-RevId: 827799163
1 parent 5f7422e commit debc6d6

File tree

7 files changed

+658
-5
lines changed

7 files changed

+658
-5
lines changed

source/plugin/Assets/GoogleMobileAds/Common/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@
2222
[assembly: InternalsVisibleTo("GoogleMobileAds.iOS")]
2323
[assembly: InternalsVisibleTo("GoogleMobileAds.Unity")]
2424
[assembly: InternalsVisibleTo("GoogleMobileAdsNative.Api")]
25+
[assembly: InternalsVisibleTo("UnitTests")]
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Security.Cryptography;
5+
using System.Text;
6+
using UnityEngine;
7+
using UnityEngine.Networking;
8+
9+
namespace GoogleMobileAds.Common
10+
{
11+
#region Exception payload definition
12+
[Serializable]
13+
public struct ExceptionLoggablePayload
14+
{
15+
public ExceptionReport unity_gma_sdk_exception_message;
16+
}
17+
18+
/// <summary>
19+
/// A data structure to hold all relevant info for a single exception event.
20+
/// </summary>
21+
[Serializable]
22+
public class ExceptionReport
23+
{
24+
// JSPB compatibility: 64-bit integers must be sent as strings to avoid precision loss.
25+
public string time_msec;
26+
public bool trapped;
27+
public string name;
28+
public string exception_class;
29+
public string top_exception;
30+
public string stacktrace;
31+
public string stacktrace_hash;
32+
33+
// Static metadata.
34+
public string session_id;
35+
public string app_id;
36+
public string app_version_name;
37+
public string platform;
38+
public string unity_version;
39+
public string os_version;
40+
public string device_model;
41+
public string country;
42+
public int total_cpu;
43+
public string total_memory_bytes;
44+
45+
// Dynamic metadata.
46+
public string network_type;
47+
public string orientation;
48+
}
49+
#endregion
50+
51+
/// <summary>
52+
/// A persistent singleton that captures all trapped and untrapped C# exceptions.
53+
/// It enriches them with device metadata and sends them in batches to a backend service (RCS)
54+
/// based on either a count or time threshold.
55+
/// </summary>
56+
public class GlobalExceptionHandler : RcsClient<ExceptionReport>
57+
{
58+
private static GlobalExceptionHandler _instance;
59+
public static GlobalExceptionHandler Instance
60+
{
61+
get
62+
{
63+
if (_instance == null && Application.isPlaying)
64+
{
65+
_instance = FindObjectOfType<GlobalExceptionHandler>();
66+
if (_instance == null)
67+
{
68+
_instance = new GameObject("GlobalExceptionHandler")
69+
.AddComponent<GlobalExceptionHandler>();
70+
}
71+
}
72+
return _instance;
73+
}
74+
private set
75+
{
76+
_instance = value;
77+
}
78+
}
79+
80+
#region Unity lifecycle methods
81+
private void Awake()
82+
{
83+
// Enforce the singleton pattern.
84+
if (_instance != null && _instance != this)
85+
{
86+
Destroy(gameObject);
87+
return;
88+
}
89+
_instance = this;
90+
DontDestroyOnLoad(gameObject);
91+
}
92+
93+
private void OnEnable()
94+
{
95+
Application.logMessageReceivedThreaded += OnLogMessageReceivedThreaded;
96+
}
97+
98+
private void OnDisable()
99+
{
100+
Application.logMessageReceivedThreaded -= OnLogMessageReceivedThreaded;
101+
}
102+
#endregion
103+
104+
#region Public reporting method
105+
/// <summary>
106+
/// Call this from any 'try-catch' block to report a TRAPPED exception.
107+
/// This method is thread-safe and adds the exception to the queue.
108+
/// </summary>
109+
public void ReportTrappedException(Exception e, string name = null)
110+
{
111+
if (e == null) return;
112+
113+
var report = new ExceptionReport
114+
{
115+
time_msec = GetEpochMillis(),
116+
trapped = true,
117+
name = name,
118+
exception_class = e.GetType().FullName,
119+
top_exception = e.Message,
120+
stacktrace = e.StackTrace ?? "",
121+
stacktrace_hash = Sha256Hash(e.StackTrace ?? ""),
122+
};
123+
Enqueue(report);
124+
if (Debug.isDebugBuild)
125+
{
126+
Debug.Log("Trapped exception queued for batch.");
127+
}
128+
}
129+
#endregion
130+
131+
#region Core logic
132+
/// <summary>
133+
/// This callback handles UNTRAPPED exceptions from *any* thread.
134+
/// It must be thread-safe and very fast.
135+
/// </summary>
136+
internal void OnLogMessageReceivedThreaded(string logString, string stackTrace, LogType type)
137+
{
138+
if (type != LogType.Exception) return;
139+
140+
// Parse exception details from the log string.
141+
string topException = logString.Split(new[] { '\n' }, 2)[0].Trim();
142+
string exceptionClass = topException.Split(':')[0].Trim();
143+
144+
var report = new ExceptionReport
145+
{
146+
time_msec = GetEpochMillis(),
147+
trapped = false,
148+
exception_class = exceptionClass,
149+
top_exception = topException,
150+
stacktrace = stackTrace ?? "",
151+
stacktrace_hash = Sha256Hash(stackTrace ?? ""),
152+
};
153+
Enqueue(report);
154+
if (Debug.isDebugBuild)
155+
{
156+
Debug.Log("Untrapped exception queued for batch.");
157+
}
158+
}
159+
160+
private string Sha256Hash(string rawData)
161+
{
162+
try
163+
{
164+
using (SHA256 sha256Hash = SHA256.Create())
165+
{
166+
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
167+
// Convert each byte of the computed hash into a two-character hexadecimal
168+
// string.
169+
StringBuilder builder = new StringBuilder();
170+
for (int i = 0; i < bytes.Length; i++)
171+
{
172+
builder.Append(bytes[i].ToString("X2"));
173+
}
174+
return builder.ToString();
175+
}
176+
}
177+
catch (Exception)
178+
{
179+
return "";
180+
}
181+
}
182+
183+
/// <summary>
184+
/// Builds and sends a batch of exception reports.
185+
/// </summary>
186+
protected override void SendBatch(List<ExceptionReport> batch)
187+
{
188+
if (Debug.isDebugBuild)
189+
{
190+
Debug.Log(string.Format("Processing and sending a batch of {0} exceptions...",
191+
batch.Count));
192+
}
193+
194+
var staticMetadata = RcsPayload.GetStaticMetadata();
195+
var dynamicMetadata = RcsPayload.GetDynamicMetadata();
196+
197+
foreach(var report in batch)
198+
{
199+
report.session_id = staticMetadata.session_id;
200+
report.app_id = staticMetadata.app_id;
201+
report.app_version_name = staticMetadata.app_version_name;
202+
report.platform = staticMetadata.platform;
203+
report.unity_version = staticMetadata.unity_version;
204+
report.os_version = staticMetadata.os_version;
205+
report.device_model = staticMetadata.device_model;
206+
report.country = staticMetadata.country;
207+
report.total_cpu = staticMetadata.total_cpu;
208+
report.total_memory_bytes = staticMetadata.total_memory_bytes;
209+
report.network_type = dynamicMetadata.network_type;
210+
report.orientation = dynamicMetadata.orientation;
211+
}
212+
213+
var payloads = new List<ExceptionLoggablePayload>();
214+
foreach (var report in batch)
215+
{
216+
payloads.Add(new ExceptionLoggablePayload
217+
{
218+
unity_gma_sdk_exception_message = report
219+
});
220+
}
221+
222+
var request = new LoggableRemoteCaptureRequest<ExceptionLoggablePayload>
223+
{
224+
payloads = payloads,
225+
client_ping_metadata = new ClientPingMetadata
226+
{
227+
binary_name = 21, // UNITY_GMA_SDK
228+
}
229+
};
230+
string jspbPayload = JspbConverter.ToJspb(request);
231+
if (jspbPayload != null)
232+
{
233+
SendToRcs(jspbPayload);
234+
}
235+
}
236+
#endregion
237+
}
238+
}

source/plugin/Assets/GoogleMobileAds/Common/Insight.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ private static void CacheBaseProperties()
4040

4141
public Insight()
4242
{
43+
Success = true;
4344
StartTimeEpochMillis = (long)DateTime.UtcNow
4445
.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc))
4546
.TotalMilliseconds;
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
using System.Collections.Generic;
2+
using System.Text;
3+
using UnityEngine;
4+
5+
namespace GoogleMobileAds.Common
6+
{
7+
/// <summary>
8+
/// A helper class to serialize objects to JSPB format.
9+
/// </summary>
10+
internal static class JspbConverter
11+
{
12+
internal static string ToJspb<TPayload>(LoggableRemoteCaptureRequest<TPayload> request)
13+
{
14+
var payloads = new List<string>();
15+
if (request.payloads != null)
16+
{
17+
foreach (var payload in request.payloads)
18+
{
19+
if (payload is ExceptionLoggablePayload)
20+
{
21+
payloads.Add(ToJspb((ExceptionLoggablePayload)(object)payload));
22+
}
23+
else
24+
{
25+
Debug.LogError("JspbConverter encountered an unknown payload type: " +
26+
payload.GetType());
27+
}
28+
}
29+
}
30+
if (payloads.Count == 0)
31+
{
32+
Debug.LogError("No payloads found in the request.");
33+
return null;
34+
}
35+
return string.Format("[[{0}],{1}]",
36+
string.Join(",", payloads.ToArray()),
37+
ToJspb(request.client_ping_metadata));
38+
}
39+
40+
// VisibleForTesting
41+
internal static string ToJspb(ClientPingMetadata metadata)
42+
{
43+
return string.Format("[{0}]", metadata.binary_name);
44+
}
45+
46+
// VisibleForTesting
47+
internal static string QuoteString(string s)
48+
{
49+
if (s == null) return "null";
50+
51+
StringBuilder sb = new StringBuilder();
52+
sb.Append("\"");
53+
foreach (char c in s)
54+
{
55+
switch (c)
56+
{
57+
// Escape quotes and slashes.
58+
case '\"':
59+
sb.Append("\\\"");
60+
break;
61+
case '\\':
62+
sb.Append("\\\\");
63+
break;
64+
case '/':
65+
sb.Append("\\/");
66+
break;
67+
// Escape control characters.
68+
case '\b':
69+
sb.Append("\\b");
70+
break;
71+
case '\f':
72+
sb.Append("\\f");
73+
break;
74+
case '\n':
75+
sb.Append("\\n");
76+
break;
77+
case '\r':
78+
sb.Append("\\r");
79+
break;
80+
case '\t':
81+
sb.Append("\\t");
82+
break;
83+
default:
84+
// Characters within the printable ASCII range (32-126) are appended
85+
// directly. Other characters (control characters or outside ASCII) are
86+
// escaped as Unicode.
87+
int i = (int)c;
88+
if (i < 32 || i > 126)
89+
{
90+
sb.AppendFormat("\\u{0:X4}", i);
91+
}
92+
else
93+
{
94+
sb.Append(c);
95+
}
96+
break;
97+
}
98+
}
99+
sb.Append("\"");
100+
return sb.ToString();
101+
}
102+
103+
#region Exceptions handling
104+
// VisibleForTesting
105+
internal static string ToJspb(ExceptionLoggablePayload payload)
106+
{
107+
if (payload.unity_gma_sdk_exception_message == null) return "[]";
108+
109+
// unity_gma_sdk_exception_message has field index 35.
110+
return string.Format("[{{\"35\":{0}}}]",
111+
ToJspb(payload.unity_gma_sdk_exception_message));
112+
}
113+
114+
// VisibleForTesting
115+
internal static string ToJspb(ExceptionReport report)
116+
{
117+
return string.Format(
118+
"[{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18}]",
119+
QuoteString(report.time_msec),
120+
report.trapped.ToString().ToLower(),
121+
QuoteString(report.name),
122+
QuoteString(report.exception_class),
123+
QuoteString(report.top_exception),
124+
QuoteString(report.stacktrace),
125+
QuoteString(report.stacktrace_hash),
126+
QuoteString(report.session_id),
127+
QuoteString(report.app_id),
128+
QuoteString(report.app_version_name),
129+
QuoteString(report.platform),
130+
QuoteString(report.unity_version),
131+
QuoteString(report.os_version),
132+
QuoteString(report.device_model),
133+
QuoteString(report.country),
134+
report.total_cpu,
135+
QuoteString(report.total_memory_bytes),
136+
QuoteString(report.network_type),
137+
QuoteString(report.orientation));
138+
}
139+
#endregion
140+
}
141+
}

0 commit comments

Comments
 (0)