Skip to content

Commit f394bd4

Browse files
authored
New class and methods to implement bulk custom key/value logging. (#2443)
1 parent ce1a078 commit f394bd4

File tree

9 files changed

+321
-12
lines changed

9 files changed

+321
-12
lines changed

firebase-crashlytics-ndk/api.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Signature format: 2.0

firebase-crashlytics/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Unreleased
2+
- [added] Added a public API to allow bulk logging of custom keys and values.
3+
14
# 17.1.0
25

36
- [fixed] Updated Crashlytics integration with Firebase Analytics to

firebase-crashlytics/api.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
// Signature format: 2.0
22
package com.google.firebase.crashlytics {
33

4+
public class CustomKeysAndValues {
5+
}
6+
7+
public static class CustomKeysAndValues.Builder {
8+
ctor public CustomKeysAndValues.Builder();
9+
method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues build();
10+
method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putBoolean(@NonNull String, boolean);
11+
method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putDouble(@NonNull String, double);
12+
method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putFloat(@NonNull String, float);
13+
method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putInt(@NonNull String, int);
14+
method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putLong(@NonNull String, long);
15+
method @NonNull public com.google.firebase.crashlytics.CustomKeysAndValues.Builder putString(@NonNull String, @NonNull String);
16+
}
17+
418
public class FirebaseCrashlytics {
519
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Boolean> checkForUnsentReports();
620
method public void deleteUnsentReports();
@@ -17,6 +31,7 @@ package com.google.firebase.crashlytics {
1731
method public void setCustomKey(@NonNull String, int);
1832
method public void setCustomKey(@NonNull String, long);
1933
method public void setCustomKey(@NonNull String, @NonNull String);
34+
method public void setCustomKeys(@NonNull com.google.firebase.crashlytics.CustomKeysAndValues);
2035
method public void setUserId(@NonNull String);
2136
}
2237

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import com.google.firebase.crashlytics.internal.settings.model.SettingsData;
3939
import com.google.firebase.crashlytics.internal.unity.UnityVersionProvider;
4040
import com.google.firebase.installations.FirebaseInstallationsApi;
41+
import java.util.HashMap;
42+
import java.util.Map;
4143
import java.util.concurrent.TimeUnit;
4244
import org.mockito.Mockito;
4345

@@ -125,6 +127,126 @@ public void testCustomAttributes() throws Exception {
125127
assertEquals(longValue, metadata.getCustomKeys().get(key1));
126128
}
127129

130+
public void testBulkCustomKeys() throws Exception {
131+
final double DELTA = 1e-15;
132+
133+
UserMetadata metadata = crashlyticsCore.getController().getUserMetadata();
134+
135+
final String stringKey = "string key";
136+
final String stringValue = "value1";
137+
final String trimmedKey = "trimmed key";
138+
final String trimmedValue = "trimmed value";
139+
140+
final StringBuffer idBuffer = new StringBuffer("id012345");
141+
while (idBuffer.length() < UserMetadata.MAX_ATTRIBUTE_SIZE) {
142+
idBuffer.append("0");
143+
}
144+
final String longId = idBuffer.toString();
145+
final String superLongId = longId + "more chars";
146+
final String longStringValue = longId.replaceAll("0", "x");
147+
final String superLongValue = longStringValue + "some more chars";
148+
149+
final String booleanKey = "boolean key";
150+
final Boolean booleanValue = true;
151+
152+
final String doubleKey = "double key";
153+
final double doubleValue = 1.000000000000001;
154+
155+
final String floatKey = "float key";
156+
final float floatValue = 2.000002f;
157+
158+
final String longKey = "long key";
159+
final long longValue = 3;
160+
161+
final String intKey = "int key";
162+
final int intValue = 4;
163+
164+
Map<String, String> keysAndValues = new HashMap<>();
165+
keysAndValues.put(stringKey, stringValue);
166+
keysAndValues.put(" " + trimmedKey + " ", " " + trimmedValue + " ");
167+
keysAndValues.put(longId, longStringValue);
168+
keysAndValues.put(superLongId, superLongValue);
169+
keysAndValues.put(booleanKey, booleanValue.toString());
170+
keysAndValues.put(doubleKey, String.valueOf(doubleValue));
171+
keysAndValues.put(floatKey, String.valueOf(floatValue));
172+
keysAndValues.put(longKey, String.valueOf(longValue));
173+
keysAndValues.put(intKey, String.valueOf(intValue));
174+
175+
crashlyticsCore.setCustomKeys(keysAndValues);
176+
177+
assertEquals(stringValue, metadata.getCustomKeys().get(stringKey));
178+
assertEquals(trimmedValue, metadata.getCustomKeys().get(trimmedKey));
179+
assertEquals(longStringValue, metadata.getCustomKeys().get(longId));
180+
// Test truncation of custom keys and attributes
181+
assertNull(metadata.getCustomKeys().get(superLongId));
182+
assertTrue(Boolean.parseBoolean(metadata.getCustomKeys().get(booleanKey)));
183+
assertEquals(doubleValue, Double.parseDouble(metadata.getCustomKeys().get(doubleKey)), DELTA);
184+
assertEquals(floatValue, Float.parseFloat(metadata.getCustomKeys().get(floatKey)), DELTA);
185+
assertEquals(longValue, Long.parseLong(metadata.getCustomKeys().get(longKey)), DELTA);
186+
assertEquals(intValue, Integer.parseInt(metadata.getCustomKeys().get(intKey)), DELTA);
187+
188+
// Add the max number of attributes (already set 8)
189+
Map<String, String> addlKeysAndValues = new HashMap<>();
190+
for (int i = 8; i < UserMetadata.MAX_ATTRIBUTES; ++i) {
191+
final String key = "key" + i;
192+
final String value = "value" + i;
193+
addlKeysAndValues.put(key, value);
194+
}
195+
crashlyticsCore.setCustomKeys(addlKeysAndValues);
196+
197+
// Ensure all keys have been set
198+
assertEquals(UserMetadata.MAX_ATTRIBUTES, metadata.getCustomKeys().size(), DELTA);
199+
200+
// Make sure the first MAX_ATTRIBUTES - 8 keys were set
201+
for (int i = 8; i < UserMetadata.MAX_ATTRIBUTES + 1; ++i) {
202+
final String key = "key" + i;
203+
final String value = "value" + i;
204+
}
205+
206+
Map<String, String> extraKeysAndValues = new HashMap<>();
207+
for (int i = UserMetadata.MAX_ATTRIBUTES; i < UserMetadata.MAX_ATTRIBUTES + 10; ++i) {
208+
final String key = "key" + i;
209+
final String value = "value" + i;
210+
extraKeysAndValues.put(key, value);
211+
}
212+
crashlyticsCore.setCustomKeys(extraKeysAndValues);
213+
214+
// Make sure the extra keys were not added
215+
for (int i = UserMetadata.MAX_ATTRIBUTES; i < UserMetadata.MAX_ATTRIBUTES + 10; ++i) {
216+
final String key = "key" + i;
217+
assertFalse(metadata.getCustomKeys().containsKey(key));
218+
}
219+
220+
// Check updating existing keys and setting to null
221+
final String updatedStringValue = "string value 1";
222+
final boolean updatedBooleanValue = false;
223+
final double updatedDoubleValue = -1.000000000000001;
224+
final float updatedFloatValue = -2.000002f;
225+
final long updatedLongValue = -3;
226+
final int updatedIntValue = -4;
227+
228+
Map<String, String> updatedKeysAndValues = new HashMap<>();
229+
updatedKeysAndValues.put(stringKey, updatedStringValue);
230+
updatedKeysAndValues.put(longId, null);
231+
updatedKeysAndValues.put(booleanKey, String.valueOf(updatedBooleanValue));
232+
updatedKeysAndValues.put(doubleKey, String.valueOf(updatedDoubleValue));
233+
updatedKeysAndValues.put(floatKey, String.valueOf(updatedFloatValue));
234+
updatedKeysAndValues.put(longKey, String.valueOf(updatedLongValue));
235+
updatedKeysAndValues.put(intKey, String.valueOf(updatedIntValue));
236+
237+
crashlyticsCore.setCustomKeys(updatedKeysAndValues);
238+
239+
assertEquals(updatedStringValue, metadata.getCustomKeys().get(stringKey));
240+
assertFalse(Boolean.parseBoolean(metadata.getCustomKeys().get(booleanKey)));
241+
assertEquals(
242+
updatedDoubleValue, Double.parseDouble(metadata.getCustomKeys().get(doubleKey)), DELTA);
243+
assertEquals(
244+
updatedFloatValue, Float.parseFloat(metadata.getCustomKeys().get(floatKey)), DELTA);
245+
assertEquals(updatedLongValue, Long.parseLong(metadata.getCustomKeys().get(longKey)), DELTA);
246+
assertEquals(updatedIntValue, Integer.parseInt(metadata.getCustomKeys().get(intKey)), DELTA);
247+
assertEquals("", metadata.getCustomKeys().get(longId));
248+
}
249+
128250
public void testGetVersion() {
129251
assertFalse(TextUtils.isEmpty(CrashlyticsCore.getVersion()));
130252
assertFalse(CrashlyticsCore.getVersion().equalsIgnoreCase("version"));
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.crashlytics;
16+
17+
import androidx.annotation.NonNull;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
21+
/**
22+
* Helper class which handles the storage and conversion to strings of key/value pairs with
23+
* heterogenous value types.
24+
*/
25+
public class CustomKeysAndValues {
26+
27+
final Map<String, String> keysAndValues;
28+
29+
public static class Builder {
30+
31+
// Holds the converted pairs of custom keys and values.
32+
private Map<String, String> keysAndValues = new HashMap<String, String>();
33+
34+
// Methods to accept keys and values and convert values to strings.
35+
36+
@NonNull
37+
public Builder putString(@NonNull String key, @NonNull String value) {
38+
keysAndValues.put(key, value);
39+
return this;
40+
}
41+
42+
@NonNull
43+
public Builder putBoolean(@NonNull String key, boolean value) {
44+
keysAndValues.put(key, Boolean.toString(value));
45+
return this;
46+
}
47+
48+
@NonNull
49+
public Builder putDouble(@NonNull String key, double value) {
50+
keysAndValues.put(key, Double.toString(value));
51+
return this;
52+
}
53+
54+
@NonNull
55+
public Builder putFloat(@NonNull String key, float value) {
56+
keysAndValues.put(key, Float.toString(value));
57+
return this;
58+
}
59+
60+
@NonNull
61+
public Builder putLong(@NonNull String key, long value) {
62+
keysAndValues.put(key, Long.toString(value));
63+
return this;
64+
}
65+
66+
@NonNull
67+
public Builder putInt(@NonNull String key, int value) {
68+
keysAndValues.put(key, Integer.toString(value));
69+
return this;
70+
}
71+
72+
@NonNull
73+
public CustomKeysAndValues build() {
74+
return new CustomKeysAndValues(this);
75+
}
76+
}
77+
78+
CustomKeysAndValues(@NonNull Builder builder) {
79+
this.keysAndValues = builder.keysAndValues;
80+
}
81+
}

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,30 @@ public void setCustomKey(@NonNull String key, @NonNull String value) {
442442
core.setCustomKey(key, value);
443443
}
444444

445+
/**
446+
* Sets multiple custom keys and values that are associated with subsequent fatal and non-fatal
447+
* reports. This method is intended as an alternative to setCustomKey in order to reduce the
448+
* computational load of writing out multiple key/value pairs at the same time.
449+
*
450+
* <p>Multiple calls to this method with the same key update the value for that key.
451+
*
452+
* <p>The value of any key at the time of a fatal or non-fatal event is associated with that
453+
* event.
454+
*
455+
* <p>Keys and associated values are visible in the session view on the Firebase Crashlytics
456+
* console.
457+
*
458+
* <p>Accepts a maximum of 64 key/value pairs. If calling this method results in the number of
459+
* custom keys exceeding this limit, only some of the keys will be logged (however many are needed
460+
* to get to 64). Which are logged versus dropped is unpredictable as there is no intrinsic
461+
* sorting of keys. Keys or values that exceed 1024 characters are truncated.
462+
*
463+
* @param keysAndValues A dictionary of keys and the values to associate with each key
464+
*/
465+
public void setCustomKeys(@NonNull CustomKeysAndValues keysAndValues) {
466+
core.setCustomKeys(keysAndValues.keysAndValues);
467+
}
468+
445469
// region Unsent report management.
446470

447471
/**

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,13 @@ void setCustomKey(String key, String value) {
437437
cacheKeyData(userMetadata.getCustomKeys());
438438
}
439439

440+
void setCustomKeys(Map<String, String> keysAndValues) {
441+
// Write all the key/value pairs before doing anything computationally expensive.
442+
userMetadata.setCustomKeys(keysAndValues);
443+
// Once all the key/value pairs are added, update the cache.
444+
cacheKeyData(userMetadata.getCustomKeys());
445+
}
446+
440447
/**
441448
* Cache user metadata asynchronously in case of a non-graceful process exit. Can be reloaded and
442449
* sent with the previous crash data on app restart. NOTE: Because this is asynchronous, it is

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCore.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.google.firebase.crashlytics.internal.stacktrace.RemoveRepeatsStrategy;
3737
import com.google.firebase.crashlytics.internal.stacktrace.StackTraceTrimmingStrategy;
3838
import java.io.File;
39+
import java.util.Map;
3940
import java.util.concurrent.Callable;
4041
import java.util.concurrent.ExecutionException;
4142
import java.util.concurrent.ExecutorService;
@@ -329,6 +330,23 @@ public void setCustomKey(String key, String value) {
329330
controller.setCustomKey(key, value);
330331
}
331332

333+
/**
334+
* Sets multiple values to be associated with given keys for your crash data. This method should
335+
* be used instead of setCustomKey when many different key/value pairs are to be set at the same
336+
* time in order to optimize the process of writing out the data. The key/value pairs will be
337+
* reported with any crash that occurs in this session. A maximum of 64 key/value pairs can be
338+
* stored for any type. New keys added over that limit will be ignored. If calling this method
339+
* would exceed the maximum number of keys, some keys will not be added; as there is no intrinsic
340+
* sorting of keys it is unpredictable which will be logged versus dropped. Keys and values are
341+
* trimmed ({@link String#trim()}), and keys or values that exceed 1024 characters will be
342+
* truncated.
343+
*
344+
* @throws NullPointerException if any key in keysAndValues is null.
345+
*/
346+
public void setCustomKeys(Map<String, String> keysAndValues) {
347+
controller.setCustomKeys(keysAndValues);
348+
}
349+
332350
// endregion
333351

334352
// region Package-protected getters

0 commit comments

Comments
 (0)