Skip to content

Commit 71ceb1b

Browse files
authored
Add an overload to FirebaseCrashlytics.recordException to attach additional custom key value pairs (#6528)
This PR adds a method `public void recordException(@nonnull Throwable throwable, CustomKeysAndValues keysAndValues)` as an overload to the existing `recordException` method in Crashlytics to allow attaching additional custom keys to the specific event. Details: - The total number of custom key/value pairs are still restricted to 64 - App level custom keys take precedence over event specific custom keys for the 64 key/value pair limit - The values of event keys override the value of global custom keys if they're identical Additionally: - Creates a new EventMetadata class to represent `sessionId` and `timestamp` attached to non fatal events.
1 parent 37f8134 commit 71ceb1b

File tree

12 files changed

+428
-77
lines changed

12 files changed

+428
-77
lines changed

firebase-crashlytics/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
2-
2+
* [feature] Added an overload for `recordException` that allows logging additional custom
3+
keys to the non fatal event [#3551]
34

45
# 19.3.0
56
* [fixed] Fixed inefficiency in the Kotlin `FirebaseCrashlytics.setCustomKeys` extension.

firebase-crashlytics/api.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ package com.google.firebase.crashlytics {
2020
method public void deleteUnsentReports();
2121
method public boolean didCrashOnPreviousExecution();
2222
method @NonNull public static com.google.firebase.crashlytics.FirebaseCrashlytics getInstance();
23+
method public boolean isCrashlyticsCollectionEnabled();
2324
method public void log(@NonNull String);
2425
method public void recordException(@NonNull Throwable);
26+
method public void recordException(@NonNull Throwable, @NonNull com.google.firebase.crashlytics.CustomKeysAndValues);
2527
method public void sendUnsentReports();
26-
method public boolean isCrashlyticsCollectionEnabled();
2728
method public void setCrashlyticsCollectionEnabled(boolean);
2829
method public void setCrashlyticsCollectionEnabled(@Nullable Boolean);
2930
method public void setCustomKey(@NonNull String, boolean);

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsControllerTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import com.google.firebase.crashlytics.internal.NativeSessionFileProvider;
4545
import com.google.firebase.crashlytics.internal.analytics.AnalyticsEventLogger;
4646
import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsWorkers;
47+
import com.google.firebase.crashlytics.internal.metadata.EventMetadata;
4748
import com.google.firebase.crashlytics.internal.metadata.LogFileManager;
4849
import com.google.firebase.crashlytics.internal.metadata.UserMetadata;
4950
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
@@ -57,6 +58,7 @@
5758
import java.util.Arrays;
5859
import java.util.Collections;
5960
import java.util.List;
61+
import java.util.Map;
6062
import java.util.TreeSet;
6163
import java.util.concurrent.Executor;
6264
import java.util.concurrent.TimeUnit;
@@ -216,14 +218,14 @@ public void testWriteNonFatal_callsSessionReportingCoordinatorPersistNonFatal()
216218
when(mockSessionReportingCoordinator.listSortedOpenSessionIds())
217219
.thenReturn(new TreeSet<>(Collections.singleton(sessionId)));
218220

219-
controller.writeNonFatalException(thread, nonFatal);
221+
controller.writeNonFatalException(thread, nonFatal, Map.of());
220222
crashlyticsWorkers.common.submit(() -> controller.doCloseSessions(testSettingsProvider));
221223

222224
crashlyticsWorkers.common.await();
223225
crashlyticsWorkers.diskWrite.await();
224226

225227
verify(mockSessionReportingCoordinator)
226-
.persistNonFatalEvent(eq(nonFatal), eq(thread), eq(sessionId), anyLong());
228+
.persistNonFatalEvent(eq(nonFatal), eq(thread), any(EventMetadata.class));
227229
}
228230

229231
@SdkSuppress(minSdkVersion = 30) // ApplicationExitInfo
@@ -377,7 +379,7 @@ public void testLoggedExceptionsAfterCrashOk() {
377379
testSettingsProvider, Thread.currentThread(), new RuntimeException());
378380

379381
// This should not throw.
380-
controller.writeNonFatalException(Thread.currentThread(), new RuntimeException());
382+
controller.writeNonFatalException(Thread.currentThread(), new RuntimeException(), Map.of());
381383
}
382384

383385
/**

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCoreTest.java

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,143 @@ public void testCustomAttributes() throws Exception {
159159
assertEquals(longValue, metadata.getCustomKeys().get(key1));
160160
}
161161

162+
@Test
163+
public void testCustomAttributes_retrievedWithEmptyEventKeys() throws Exception {
164+
UserMetadata metadata = crashlyticsCore.getController().getUserMetadata();
165+
166+
assertTrue(metadata.getCustomKeys(Map.of()).isEmpty());
167+
168+
final String id = "id012345";
169+
crashlyticsCore.setUserId(id);
170+
crashlyticsWorkers.common.await();
171+
assertEquals(id, metadata.getUserId());
172+
173+
final StringBuffer idBuffer = new StringBuffer(id);
174+
while (idBuffer.length() < UserMetadata.MAX_ATTRIBUTE_SIZE) {
175+
idBuffer.append("0");
176+
}
177+
final String longId = idBuffer.toString();
178+
final String superLongId = longId + "more chars";
179+
180+
crashlyticsCore.setUserId(superLongId);
181+
crashlyticsWorkers.common.await();
182+
assertEquals(longId, metadata.getUserId());
183+
184+
final String key1 = "key1";
185+
final String value1 = "value1";
186+
crashlyticsCore.setCustomKey(key1, value1);
187+
crashlyticsWorkers.common.await();
188+
assertEquals(value1, metadata.getCustomKeys(Map.of()).get(key1));
189+
190+
// Adding an existing key with the same value should return false
191+
assertFalse(metadata.setCustomKey(key1, value1));
192+
assertTrue(metadata.setCustomKey(key1, "someOtherValue"));
193+
assertTrue(metadata.setCustomKey(key1, value1));
194+
assertFalse(metadata.setCustomKey(key1, value1));
195+
196+
final String longValue = longId.replaceAll("0", "x");
197+
final String superLongValue = longValue + "some more chars";
198+
199+
// test truncation of custom keys and attributes
200+
crashlyticsCore.setCustomKey(superLongId, superLongValue);
201+
crashlyticsWorkers.common.await();
202+
assertNull(metadata.getCustomKeys(Map.of()).get(superLongId));
203+
assertEquals(longValue, metadata.getCustomKeys().get(longId));
204+
205+
// test the max number of attributes. We've already set 2.
206+
for (int i = 2; i < UserMetadata.MAX_ATTRIBUTES; ++i) {
207+
final String key = "key" + i;
208+
final String value = "value" + i;
209+
crashlyticsCore.setCustomKey(key, value);
210+
crashlyticsWorkers.common.await();
211+
assertEquals(value, metadata.getCustomKeys(Map.of()).get(key));
212+
}
213+
// should be full now, extra key, value pairs will be dropped.
214+
final String key = "new key";
215+
crashlyticsCore.setCustomKey(key, "some value");
216+
crashlyticsWorkers.common.await();
217+
assertFalse(metadata.getCustomKeys(Map.of()).containsKey(key));
218+
219+
// should be able to update existing keys
220+
crashlyticsCore.setCustomKey(key1, longValue);
221+
crashlyticsWorkers.common.await();
222+
assertEquals(longValue, metadata.getCustomKeys(Map.of()).get(key1));
223+
224+
// when we set a key to null, it should still exist with an empty value
225+
crashlyticsCore.setCustomKey(key1, null);
226+
crashlyticsWorkers.common.await();
227+
assertEquals("", metadata.getCustomKeys(Map.of()).get(key1));
228+
229+
// keys and values are trimmed.
230+
crashlyticsCore.setCustomKey(" " + key1 + " ", " " + longValue + " ");
231+
crashlyticsWorkers.common.await();
232+
assertTrue(metadata.getCustomKeys(Map.of()).containsKey(key1));
233+
assertEquals(longValue, metadata.getCustomKeys(Map.of()).get(key1));
234+
}
235+
236+
@Test
237+
public void testCustomKeysMergedWithEventKeys() throws Exception {
238+
UserMetadata metadata = crashlyticsCore.getController().getUserMetadata();
239+
240+
Map<String, String> keysAndValues = new HashMap<>();
241+
keysAndValues.put("customKey1", "value");
242+
keysAndValues.put("customKey2", "value");
243+
keysAndValues.put("customKey3", "value");
244+
245+
crashlyticsCore.setCustomKeys(keysAndValues);
246+
crashlyticsWorkers.common.await();
247+
248+
Map<String, String> eventKeysAndValues = new HashMap<>();
249+
eventKeysAndValues.put("eventKey1", "eventValue");
250+
eventKeysAndValues.put("eventKey2", "eventValue");
251+
252+
// Tests reading custom keys with event keys.
253+
assertEquals(keysAndValues.size(), metadata.getCustomKeys().size());
254+
assertEquals(keysAndValues.size(), metadata.getCustomKeys(Map.of()).size());
255+
assertEquals(
256+
keysAndValues.size() + eventKeysAndValues.size(),
257+
metadata.getCustomKeys(eventKeysAndValues).size());
258+
259+
// Tests event keys don't add to custom keys in future reads.
260+
assertEquals(keysAndValues.size(), metadata.getCustomKeys().size());
261+
assertEquals(keysAndValues.size(), metadata.getCustomKeys(Map.of()).size());
262+
263+
// Tests additional event keys.
264+
eventKeysAndValues.put("eventKey3", "eventValue");
265+
eventKeysAndValues.put("eventKey4", "eventValue");
266+
assertEquals(
267+
keysAndValues.size() + eventKeysAndValues.size(),
268+
metadata.getCustomKeys(eventKeysAndValues).size());
269+
270+
// Tests overriding custom key with event keys.
271+
keysAndValues.put("eventKey1", "value");
272+
crashlyticsCore.setCustomKeys(keysAndValues);
273+
crashlyticsWorkers.common.await();
274+
275+
assertEquals("value", metadata.getCustomKeys().get("eventKey1"));
276+
assertEquals("value", metadata.getCustomKeys(Map.of()).get("eventKey1"));
277+
assertEquals("eventValue", metadata.getCustomKeys(eventKeysAndValues).get("eventKey1"));
278+
279+
// Test the event key behavior when the count of custom keys is max.
280+
for (int i = keysAndValues.size(); i < UserMetadata.MAX_ATTRIBUTES; ++i) {
281+
final String key = "key" + i;
282+
final String value = "value" + i;
283+
crashlyticsCore.setCustomKey(key, value);
284+
crashlyticsWorkers.common.await();
285+
assertEquals(value, metadata.getCustomKeys().get(key));
286+
}
287+
288+
assertEquals(UserMetadata.MAX_ATTRIBUTES, metadata.getCustomKeys().size());
289+
290+
// Tests event keys override global custom keys when the key exists.
291+
assertEquals("value", metadata.getCustomKeys().get("eventKey1"));
292+
assertEquals("value", metadata.getCustomKeys(Map.of()).get("eventKey1"));
293+
assertEquals("eventValue", metadata.getCustomKeys(eventKeysAndValues).get("eventKey1"));
294+
295+
// Test when event keys *don't* override global custom keys.
296+
assertNull(metadata.getCustomKeys(eventKeysAndValues).get("eventKey2"));
297+
}
298+
162299
@Test
163300
public void testBulkCustomKeys() throws Exception {
164301
final double DELTA = 1e-15;

0 commit comments

Comments
 (0)