Skip to content

Commit 0fc6cd1

Browse files
authored
Merge pull request #495 from Microsoft/feature/truncate-event-properties
Truncate property key and value if it is longer than limit
2 parents 9c22acd + 82ec979 commit 0fc6cd1

File tree

2 files changed

+116
-36
lines changed

2 files changed

+116
-36
lines changed

sdk/mobile-center-analytics/src/main/java/com/microsoft/azure/mobile/analytics/Analytics.java

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ public class Analytics extends AbstractMobileCenterService {
4949
*/
5050
private static final String ACTIVITY_SUFFIX = "Activity";
5151

52+
/**
53+
* Max number of properties.
54+
*/
55+
private static final int MAX_PROPERTY_COUNT = 5;
56+
57+
/**
58+
* Max length of event/page name.
59+
*/
60+
@VisibleForTesting
61+
static final int MAX_NAME_LENGTH = 256;
62+
63+
/**
64+
* Max length of properties.
65+
*/
66+
@VisibleForTesting
67+
static final int MAX_PROPERTY_ITEM_LENGTH = 64;
68+
5269
/**
5370
* Shared instance.
5471
*/
@@ -184,8 +201,9 @@ protected static void trackPage(String name) {
184201
* Track a custom page with name and optional properties.
185202
* The name parameter can not be null or empty. Maximum allowed length = 256.
186203
* The properties parameter maximum item count = 5.
187-
* The properties keys/names can not be null or empty, maximum allowed key length = 64.
204+
* The properties keys can not be null or empty, maximum allowed key length = 64.
188205
* The properties values can not be null, maximum allowed value length = 64.
206+
* Any length of name/keys/values that are longer than each limit will be truncated.
189207
* <p>
190208
* TODO the backend does not support that service yet, will be public method later.
191209
*
@@ -195,7 +213,8 @@ protected static void trackPage(String name) {
195213
@SuppressWarnings("WeakerAccess")
196214
protected static void trackPage(String name, Map<String, String> properties) {
197215
final String logType = "Page";
198-
if (validateName(name, logType)) {
216+
name = validateName(name, logType);
217+
if (name != null) {
199218
Map<String, String> validatedProperties = validateProperties(properties, name, logType);
200219
getInstance().trackPageAsync(name, validatedProperties);
201220
}
@@ -215,16 +234,18 @@ public static void trackEvent(String name) {
215234
* Track a custom event with name and optional properties.
216235
* The name parameter can not be null or empty. Maximum allowed length = 256.
217236
* The properties parameter maximum item count = 5.
218-
* The properties keys/names can not be null or empty, maximum allowed key length = 64.
237+
* The properties keys can not be null or empty, maximum allowed key length = 64.
219238
* The properties values can not be null, maximum allowed value length = 64.
239+
* Any length of name/keys/values that are longer than each limit will be truncated.
220240
*
221241
* @param name An event name.
222242
* @param properties Optional properties.
223243
*/
224244
@SuppressWarnings("WeakerAccess")
225245
public static void trackEvent(String name, Map<String, String> properties) {
226246
final String logType = "Event";
227-
if (validateName(name, logType)) {
247+
name = validateName(name, logType);
248+
if (name != null) {
228249
Map<String, String> validatedProperties = validateProperties(properties, name, logType);
229250
getInstance().trackEventAsync(name, validatedProperties);
230251
}
@@ -250,19 +271,18 @@ private static String generatePageName(Class<?> activityClass) {
250271
*
251272
* @param name Log name to validate.
252273
* @param logType Log type.
253-
* @return <code>true</code> if validation succeeds, otherwise <code>false</code>.
274+
* @return <code>null</code> if validation failed, otherwise a valid name within the length limit will be returned.
254275
*/
255-
private static boolean validateName(String name, String logType) {
256-
final int maxNameLength = 256;
276+
private static String validateName(String name, String logType) {
257277
if (name == null || name.isEmpty()) {
258278
MobileCenterLog.error(Analytics.LOG_TAG, logType + " name cannot be null or empty.");
259-
return false;
279+
return null;
260280
}
261-
if (name.length() > maxNameLength) {
262-
MobileCenterLog.error(Analytics.LOG_TAG, String.format("%s '%s' : name length cannot be longer than %s characters.", logType, name, maxNameLength));
263-
return false;
281+
if (name.length() > MAX_NAME_LENGTH) {
282+
MobileCenterLog.warn(Analytics.LOG_TAG, String.format("%s '%s' : name length cannot be longer than %s characters. Name will be truncated.", logType, name, MAX_NAME_LENGTH));
283+
name = name.substring(0, MAX_NAME_LENGTH);
264284
}
265-
return true;
285+
return name;
266286
}
267287

268288
/**
@@ -277,36 +297,36 @@ private static Map<String, String> validateProperties(Map<String, String> proper
277297
if (properties == null)
278298
return null;
279299
String message;
280-
final int maxPropertiesCount = 5;
281-
final int maxPropertyItemLength = 64;
282300
Map<String, String> result = new HashMap<>();
283301
for (Map.Entry<String, String> property : properties.entrySet()) {
284-
if (result.size() >= maxPropertiesCount) {
285-
message = String.format("%s '%s' : properties cannot contain more than %s items. Skipping other properties.", logType, logName, maxPropertiesCount);
302+
String key = property.getKey();
303+
String value = property.getValue();
304+
if (result.size() >= MAX_PROPERTY_COUNT) {
305+
message = String.format("%s '%s' : properties cannot contain more than %s items. Skipping other properties.", logType, logName, MAX_PROPERTY_COUNT);
286306
MobileCenterLog.warn(Analytics.LOG_TAG, message);
287307
break;
288308
}
289-
if (property.getKey() == null || property.getKey().isEmpty()) {
309+
if (key == null || key.isEmpty()) {
290310
message = String.format("%s '%s' : a property key cannot be null or empty. Property will be skipped.", logType, logName);
291311
MobileCenterLog.warn(Analytics.LOG_TAG, message);
292312
continue;
293313
}
294-
if (property.getKey().length() > maxPropertyItemLength) {
295-
message = String.format("%s '%s' : property '%s' : property key length cannot be longer than %s characters. Property '%s' will be skipped.", logType, logName, property.getKey(), maxPropertyItemLength, property.getKey());
314+
if (value == null) {
315+
message = String.format("%s '%s' : property '%s' : property value cannot be null. Property '%s' will be skipped.", logType, logName, key, key);
296316
MobileCenterLog.warn(Analytics.LOG_TAG, message);
297317
continue;
298318
}
299-
if (property.getValue() == null) {
300-
message = String.format("%s '%s' : property '%s' : property value cannot be null. Property '%s' will be skipped.", logType, logName, property.getKey(), property.getKey());
319+
if (key.length() > MAX_PROPERTY_ITEM_LENGTH) {
320+
message = String.format("%s '%s' : property '%s' : property key length cannot be longer than %s characters. Property key will be truncated.", logType, logName, key, MAX_PROPERTY_ITEM_LENGTH);
301321
MobileCenterLog.warn(Analytics.LOG_TAG, message);
302-
continue;
322+
key = key.substring(0, MAX_PROPERTY_ITEM_LENGTH);
303323
}
304-
if (property.getValue().length() > maxPropertyItemLength) {
305-
message = String.format("%s '%s' : property '%s' : property value cannot be longer than %s characters. Property '%s' will be skipped.", logType, logName, property.getKey(), maxPropertyItemLength, property.getKey());
324+
if (value.length() > MAX_PROPERTY_ITEM_LENGTH) {
325+
message = String.format("%s '%s' : property '%s' : property value cannot be longer than %s characters. Property value will be truncated.", logType, logName, key, MAX_PROPERTY_ITEM_LENGTH);
306326
MobileCenterLog.warn(Analytics.LOG_TAG, message);
307-
continue;
327+
value = value.substring(0, MAX_PROPERTY_ITEM_LENGTH);
308328
}
309-
result.put(property.getKey(), property.getValue());
329+
result.put(key, value);
310330
}
311331
return result;
312332
}

sdk/mobile-center-analytics/src/test/java/com/microsoft/azure/mobile/analytics/AnalyticsTest.java

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,18 +258,28 @@ public void testTrackEvent() {
258258
Analytics.trackEvent(" ", null);
259259
verify(channel, times(1)).enqueue(any(Log.class), anyString());
260260
reset(channel);
261-
Analytics.trackEvent(generateString(257, '*'), null);
262-
verify(channel, never()).enqueue(any(Log.class), anyString());
261+
final String maxName = generateString(Analytics.MAX_NAME_LENGTH, '*');
262+
Analytics.trackEvent(maxName + "*", null);
263+
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
264+
265+
@Override
266+
public boolean matches(Object item) {
267+
if (item instanceof EventLog) {
268+
EventLog eventLog = (EventLog) item;
269+
return eventLog.getName().equals(maxName) && eventLog.getProperties() == null;
270+
}
271+
return false;
272+
}
273+
}), anyString());
263274
reset(channel);
264-
Analytics.trackEvent(generateString(256, '*'), null);
275+
Analytics.trackEvent(maxName, null);
265276
verify(channel, times(1)).enqueue(any(Log.class), anyString());
266277
reset(channel);
267278
Analytics.trackEvent("eventName", new HashMap<String, String>() {{
268279
put(null, null);
269280
put("", null);
270-
put(generateString(65, '*'), null);
281+
put(generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH + 1, '*'), null);
271282
put("1", null);
272-
put("2", generateString(65, '*'));
273283
}});
274284
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
275285

@@ -300,6 +310,26 @@ public boolean matches(Object item) {
300310
return false;
301311
}
302312
}), anyString());
313+
reset(channel);
314+
final String longerMapItem = generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH + 1, '*');
315+
Analytics.trackEvent("eventName", new HashMap<String, String>() {{
316+
put(longerMapItem, longerMapItem);
317+
}});
318+
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
319+
320+
@Override
321+
public boolean matches(Object item) {
322+
if (item instanceof EventLog) {
323+
EventLog eventLog = (EventLog) item;
324+
if (eventLog.getProperties().size() == 1) {
325+
Map.Entry<String, String> entry = eventLog.getProperties().entrySet().iterator().next();
326+
String truncatedMapItem = generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH, '*');
327+
return entry.getKey().length() == Analytics.MAX_PROPERTY_ITEM_LENGTH && entry.getValue().length() == Analytics.MAX_PROPERTY_ITEM_LENGTH;
328+
}
329+
}
330+
return false;
331+
}
332+
}), anyString());
303333
}
304334

305335
@Test
@@ -317,18 +347,28 @@ public void testTrackPage() {
317347
Analytics.trackPage(" ", null);
318348
verify(channel, times(1)).enqueue(any(Log.class), anyString());
319349
reset(channel);
320-
Analytics.trackPage(generateString(257, '*'), null);
321-
verify(channel, never()).enqueue(any(Log.class), anyString());
350+
final String maxName = generateString(Analytics.MAX_NAME_LENGTH, '*');
351+
Analytics.trackPage(maxName + "*", null);
352+
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
353+
354+
@Override
355+
public boolean matches(Object item) {
356+
if (item instanceof PageLog) {
357+
PageLog pageLog = (PageLog) item;
358+
return pageLog.getName().equals(maxName) && pageLog.getProperties() == null;
359+
}
360+
return false;
361+
}
362+
}), anyString());
322363
reset(channel);
323-
Analytics.trackPage(generateString(256, '*'), null);
364+
Analytics.trackPage(maxName, null);
324365
verify(channel, times(1)).enqueue(any(Log.class), anyString());
325366
reset(channel);
326367
Analytics.trackPage("pageName", new HashMap<String, String>() {{
327368
put(null, null);
328369
put("", null);
329-
put(generateString(65, '*'), null);
370+
put(generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH + 1, '*'), null);
330371
put("1", null);
331-
put("2", generateString(65, '*'));
332372
}});
333373
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
334374

@@ -359,6 +399,26 @@ public boolean matches(Object item) {
359399
return false;
360400
}
361401
}), anyString());
402+
reset(channel);
403+
final String longerMapItem = generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH + 1, '*');
404+
Analytics.trackPage("pageName", new HashMap<String, String>() {{
405+
put(longerMapItem, longerMapItem);
406+
}});
407+
verify(channel, times(1)).enqueue(argThat(new ArgumentMatcher<Log>() {
408+
409+
@Override
410+
public boolean matches(Object item) {
411+
if (item instanceof PageLog) {
412+
PageLog pageLog = (PageLog) item;
413+
if (pageLog.getProperties().size() == 1) {
414+
Map.Entry<String, String> entry = pageLog.getProperties().entrySet().iterator().next();
415+
String truncatedMapItem = generateString(Analytics.MAX_PROPERTY_ITEM_LENGTH, '*');
416+
return entry.getKey().length() == Analytics.MAX_PROPERTY_ITEM_LENGTH && entry.getValue().length() == Analytics.MAX_PROPERTY_ITEM_LENGTH;
417+
}
418+
}
419+
return false;
420+
}
421+
}), anyString());
362422
}
363423

364424
@Test

0 commit comments

Comments
 (0)