diff --git a/backtrace-library/src/androidTest/java/backtraceio/library/ApplicationMetadataTest.java b/backtrace-library/src/androidTest/java/backtraceio/library/ApplicationMetadataTest.java new file mode 100644 index 00000000..ca4e0727 --- /dev/null +++ b/backtrace-library/src/androidTest/java/backtraceio/library/ApplicationMetadataTest.java @@ -0,0 +1,48 @@ +package backtraceio.library; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import backtraceio.library.common.ApplicationMetadataCache; + +@RunWith(AndroidJUnit4.class) +public class ApplicationMetadataTest { + private Context context; + + @Before + public void setUp() { + context = InstrumentationRegistry.getInstrumentation().getContext(); + // prepare instance + ApplicationMetadataCache.getInstance(context); + } + + @Test + public void shouldCorrectlyRetrieveApplicationName() { + ApplicationMetadataCache cache = ApplicationMetadataCache.getInstance(context); + assertEquals(cache.getApplicationName(), context.getOpPackageName()); + } + + @Test + public void shouldCorrectlyRetrieveApplicationPackageName() { + ApplicationMetadataCache cache = ApplicationMetadataCache.getInstance(context); + assertEquals(cache.getPackageName(), context.getOpPackageName()); + } + + @Test + public void shouldCorrectlyRetrieveApplicationVersion() throws PackageManager.NameNotFoundException { + ApplicationMetadataCache cache = ApplicationMetadataCache.getInstance(context); + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getOpPackageName(), 0); + assertEquals(cache.getApplicationVersion(), String.valueOf(packageInfo.versionCode)); + } + +} diff --git a/backtrace-library/src/androidTest/java/backtraceio/library/BacktraceClientUniqueEventTest.java b/backtrace-library/src/androidTest/java/backtraceio/library/BacktraceClientUniqueEventTest.java index af44bff8..ad6a15d0 100644 --- a/backtrace-library/src/androidTest/java/backtraceio/library/BacktraceClientUniqueEventTest.java +++ b/backtrace-library/src/androidTest/java/backtraceio/library/BacktraceClientUniqueEventTest.java @@ -400,48 +400,4 @@ public void uniqueEventUpdateAttributes() { assertEquals(expectedValue, backtraceClient.metrics.getUniqueEvents().getLast().getAttributes().get(expectedKey)); } - - @Test - public void uniqueEventEmptyAttributeValueShouldNotOverridePreviousValueOnUpdate() { - backtraceClient.metrics.enable(new BacktraceMetricsSettings(credentials, defaultBaseUrl, 0)); - - String expectedKey = "foo"; - String expectedValue = "bar"; - - backtraceClient.attributes.put(expectedKey, expectedValue); - assertTrue(backtraceClient.metrics.addUniqueEvent(uniqueAttributeName[0])); - - assertEquals(uniqueAttributeName[0], backtraceClient.metrics.getUniqueEvents().getLast().getName()); - assertEquals(expectedValue, backtraceClient.metrics.getUniqueEvents().getLast().getAttributes().get(expectedKey)); - - backtraceClient.attributes.put(expectedKey, ""); - assertEquals("", backtraceClient.attributes.get(expectedKey)); - - // Force update - backtraceClient.metrics.send(); - - assertEquals(expectedValue, backtraceClient.metrics.getUniqueEvents().getLast().getAttributes().get(expectedKey)); - } - - @Test - public void uniqueEventNullAttributeValueShouldNotOverridePreviousValueOnUpdate() { - backtraceClient.metrics.enable(new BacktraceMetricsSettings(credentials, defaultBaseUrl, 0)); - - String expectedKey = "foo"; - String expectedValue = "bar"; - - backtraceClient.attributes.put(expectedKey, expectedValue); - assertTrue(backtraceClient.metrics.addUniqueEvent(uniqueAttributeName[0])); - - assertEquals(uniqueAttributeName[0], backtraceClient.metrics.getUniqueEvents().getLast().getName()); - assertEquals(expectedValue, backtraceClient.metrics.getUniqueEvents().getLast().getAttributes().get(expectedKey)); - - backtraceClient.attributes.put(expectedKey, null); - assertNull(backtraceClient.attributes.get(expectedKey)); - - // Force update - backtraceClient.metrics.send(); - - assertEquals(expectedValue, backtraceClient.metrics.getUniqueEvents().getLast().getAttributes().get(expectedKey)); - } } diff --git a/backtrace-library/src/androidTest/java/backtraceio/library/metrics/BacktraceMetricsTest.java b/backtrace-library/src/androidTest/java/backtraceio/library/metrics/BacktraceMetricsTest.java index f7e46145..d4b3b7ba 100644 --- a/backtrace-library/src/androidTest/java/backtraceio/library/metrics/BacktraceMetricsTest.java +++ b/backtrace-library/src/androidTest/java/backtraceio/library/metrics/BacktraceMetricsTest.java @@ -57,7 +57,7 @@ public void setUp() { @Test public void addAttributesSummedEvent() { SummedEvent summedEvent = new SummedEvent(summedEventName, null); - Map attributes = new HashMap() {{ + Map attributes = new HashMap() {{ put("foo", "bar"); }}; summedEvent.addAttributes(attributes); @@ -67,7 +67,7 @@ public void addAttributesSummedEvent() { @Test public void addAttributesUniqueEvent() { UniqueEvent uniqueEvent = new UniqueEvent(uniqueAttributeName[0], null); - Map attributes = new HashMap() {{ + Map attributes = new HashMap() {{ put("foo", "bar"); }}; uniqueEvent.update(BacktraceTimeHelper.getTimestampSeconds(), attributes); diff --git a/backtrace-library/src/main/java/backtraceio/library/common/ApplicationMetadataCache.java b/backtrace-library/src/main/java/backtraceio/library/common/ApplicationMetadataCache.java new file mode 100644 index 00000000..34637180 --- /dev/null +++ b/backtrace-library/src/main/java/backtraceio/library/common/ApplicationMetadataCache.java @@ -0,0 +1,102 @@ +package backtraceio.library.common; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import backtraceio.library.logger.BacktraceLogger; + +public class ApplicationMetadataCache { + + private static final transient String LOG_TAG = ApplicationMetadataCache.class.getSimpleName(); + + private static volatile ApplicationMetadataCache instance; + + /** + * Cached application name + */ + private String applicationName; + + /** + * Cached application version + */ + private String applicationVersion; + + /** + * Cached package name + */ + private String packageName; + + private final Context context; + + private ApplicationMetadataCache(Context context) { + this.context = context; + } + + /** + * Returns current application cache. This instance is a singleton since we can only operate + * in a single application scope. + * + * @param context Application context + * @return Application metadata cache + */ + public static ApplicationMetadataCache getInstance(Context context) { + if (instance == null) { + synchronized (ApplicationMetadataCache.class) { + if (instance == null) { + instance = new ApplicationMetadataCache(context); + } + } + } + return instance; + } + + /** + * Retrieves application name from context. The name will be cached over checks + * + * @return application name + */ + public String getApplicationName() { + if (!BacktraceStringHelper.isNullOrEmpty(applicationName)) { + return applicationName; + } + + applicationName = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); + return applicationName; + } + + /** + * Retrieves application version from the context. If the version name is not defined, the version code will be used instead. + * + * @return current application version. + */ + public String getApplicationVersion() { + if (!BacktraceStringHelper.isNullOrEmpty(applicationVersion)) { + return applicationVersion; + } + try { + PackageInfo info = context.getPackageManager() + .getPackageInfo(context.getPackageName(), 0); + applicationVersion = BacktraceStringHelper.isNullOrEmpty(info.versionName) ? String.valueOf(info.versionCode) : info.versionName; + + return applicationVersion; + } catch (PackageManager.NameNotFoundException e) { + BacktraceLogger.e(LOG_TAG, "Could not resolve application version", e); + return ""; + } + } + + /** + * Retrieves package name from the context. + * + * @return current package name. + */ + public String getPackageName() { + if (!BacktraceStringHelper.isNullOrEmpty(packageName)) { + return packageName; + } + packageName = context.getApplicationContext().getPackageName(); + + return packageName; + } +} diff --git a/backtrace-library/src/main/java/backtraceio/library/common/DeviceAttributesHelper.java b/backtrace-library/src/main/java/backtraceio/library/common/DeviceAttributesHelper.java index 046c6651..63b6100d 100644 --- a/backtrace-library/src/main/java/backtraceio/library/common/DeviceAttributesHelper.java +++ b/backtrace-library/src/main/java/backtraceio/library/common/DeviceAttributesHelper.java @@ -19,7 +19,7 @@ import android.text.TextUtils; import java.io.BufferedReader; -import java.io.InputStreamReader; +import java.io.FileReader; import java.util.HashMap; import java.util.UUID; @@ -36,6 +36,11 @@ public class DeviceAttributesHelper { private final Context context; + /* + * Current Device id + */ + private static String uuid; + public DeviceAttributesHelper(Context context) { this.context = context; } @@ -61,15 +66,17 @@ public HashMap getDeviceAttributes(Boolean includeDynamicAttribu result.put("device.cpu.temperature", String.valueOf(getCpuTemperature())); result.put("device.is_power_saving_mode", String.valueOf(isPowerSavingMode())); result.put("device.wifi.status", getWifiStatus().toString()); - result.put("system.memory.total", getMaxRamSize()); - result.put("system.memory.free", getDeviceFreeRam()); - result.put("system.memory.active", getDeviceActiveRam()); result.put("app.storage_used", getAppUsedStorageSize()); result.put("battery.level", String.valueOf(getBatteryLevel())); result.put("battery.state", getBatteryState().toString()); result.put("cpu.boottime", String.valueOf(java.lang.System.currentTimeMillis() - android.os.SystemClock .elapsedRealtime())); + + ActivityManager.MemoryInfo memoryInfo = getMemoryInformation(); + result.put("system.memory.total", Long.toString(memoryInfo.totalMem)); + result.put("system.memory.free", Long.toString(memoryInfo.availMem)); + result.put("system.memory.active", Long.toString(memoryInfo.totalMem - memoryInfo.availMem)); return result; } @@ -144,14 +151,14 @@ private BluetoothStatus isBluetoothEnabled() { private float getCpuTemperature() { Process p; try { - p = Runtime.getRuntime().exec("cat sys/class/thermal/thermal_zone0/temp"); - p.waitFor(); - BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + BufferedReader reader = new BufferedReader(new FileReader("/sys/class/thermal/thermal_zone0/temp")); String line = reader.readLine(); if (line == null) { return 0.0f; } + reader.close(); + return Float.parseFloat(line) / 1000.0f; } catch (Exception e) { return 0.0f; @@ -255,33 +262,20 @@ private BatteryState getBatteryState() { * @return unique device identifier */ private String generateDeviceId() { - String androidId = Settings.Secure.getString(this.context.getContentResolver(), - Settings.Secure.ANDROID_ID); - - if (TextUtils.isEmpty(androidId)) { - return null; + if (!BacktraceStringHelper.isNullOrEmpty(uuid)) { + return uuid; } - return UUID.nameUUIDFromBytes(androidId.getBytes()).toString(); - } - - /** - * Get RAM size of current device - * available from API 16 - * - * @return device RAM size - */ - private String getMaxRamSize() { - return Long.toString(getMemoryInformation().totalMem); - } + String androidId = Settings.Secure.getString(this.context.getContentResolver(), + Settings.Secure.ANDROID_ID); - private String getDeviceFreeRam() { - return Long.toString(getMemoryInformation().availMem); - } + // if the android id is not defined we want to cache at least guid + // for the current session + uuid = TextUtils.isEmpty(androidId) + ? UUID.randomUUID().toString() + : UUID.nameUUIDFromBytes(androidId.getBytes()).toString(); - private String getDeviceActiveRam() { - ActivityManager.MemoryInfo mi = getMemoryInformation(); - return Long.toString(mi.totalMem - mi.availMem); + return uuid; } private ActivityManager.MemoryInfo getMemoryInformation() { diff --git a/backtrace-library/src/main/java/backtraceio/library/models/attributes/ReportDataAttributes.java b/backtrace-library/src/main/java/backtraceio/library/models/attributes/ReportDataAttributes.java new file mode 100644 index 00000000..889c82d1 --- /dev/null +++ b/backtrace-library/src/main/java/backtraceio/library/models/attributes/ReportDataAttributes.java @@ -0,0 +1,27 @@ +package backtraceio.library.models.attributes; + +import java.util.HashMap; +import java.util.Map; + +public class ReportDataAttributes { + private final Map reportAttributes = new HashMap<>(); + + private final Map reportAnnotations = new HashMap<>(); + + + public void addAnnotation(String key, Object value) { + reportAnnotations.put(key, value); + } + + public void addAttribute(String key, String value) { + reportAttributes.put(key, value); + } + + public Map getAttributes() { + return reportAttributes; + } + + public Map getAnnotations() { + return reportAnnotations; + } +} diff --git a/backtrace-library/src/main/java/backtraceio/library/models/attributes/ReportDataBuilder.java b/backtrace-library/src/main/java/backtraceio/library/models/attributes/ReportDataBuilder.java new file mode 100644 index 00000000..b6922ce2 --- /dev/null +++ b/backtrace-library/src/main/java/backtraceio/library/models/attributes/ReportDataBuilder.java @@ -0,0 +1,53 @@ +package backtraceio.library.models.attributes; + +import java.util.Map; + +import backtraceio.library.common.TypeHelper; + +public class ReportDataBuilder { + + /** + * Divide custom user attributes into primitive and complex attributes and add to this object. By default null values will be included. + * + * @param attributes client's attributes + * @return Report data attributes divided into attributes and annotations + */ + public static ReportDataAttributes getReportAttributes(Map attributes) { + return getReportAttributes(attributes, false); + } + + /** + * Divide custom user attributes into primitive and complex attributes and add to this object + * + * @param attributes client's attributes + * @param skipNull define attributes behavior on null value. By default all null values + * will be included in the report. For some features like metrics, we don't want to send + * null values, because they can generate invalid behavior/incorrect information. + * @return Report data attributes divided into attributes and annotations + */ + public static ReportDataAttributes getReportAttributes(Map attributes, boolean skipNull) { + ReportDataAttributes reportDataAttributes = new ReportDataAttributes(); + + if (attributes == null) { + return reportDataAttributes; + } + + for (Map.Entry entry : attributes.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + if (!skipNull) { + reportDataAttributes.addAttribute(key, null); + } + continue; + } + if (TypeHelper.isPrimitiveOrPrimitiveWrapperOrString(value.getClass())) { + reportDataAttributes.addAttribute(key, value.toString()); + } else { + reportDataAttributes.addAnnotation(key, value); + } + } + + return reportDataAttributes; + } +} diff --git a/backtrace-library/src/main/java/backtraceio/library/models/json/BacktraceAttributes.java b/backtrace-library/src/main/java/backtraceio/library/models/json/BacktraceAttributes.java index de9d247c..ad03bd9b 100644 --- a/backtrace-library/src/main/java/backtraceio/library/models/json/BacktraceAttributes.java +++ b/backtrace-library/src/main/java/backtraceio/library/models/json/BacktraceAttributes.java @@ -1,7 +1,6 @@ package backtraceio.library.models.json; import android.content.Context; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Build; import android.provider.Settings; @@ -15,18 +14,17 @@ import java.util.UUID; import backtraceio.library.BacktraceClient; +import backtraceio.library.common.ApplicationMetadataCache; import backtraceio.library.common.BacktraceStringHelper; import backtraceio.library.common.DeviceAttributesHelper; -import backtraceio.library.common.TypeHelper; import backtraceio.library.enums.ScreenOrientation; -import backtraceio.library.logger.BacktraceLogger; +import backtraceio.library.models.attributes.ReportDataAttributes; +import backtraceio.library.models.attributes.ReportDataBuilder; /** * Class instance to get a built-in attributes from current application */ public class BacktraceAttributes { - private static final transient String LOG_TAG = BacktraceAttributes.class.getSimpleName(); - /** * Get built-in primitive attributes */ @@ -42,15 +40,10 @@ public class BacktraceAttributes { */ private final Context context; - /** - * Are metrics enabled? - */ - private static boolean isMetricsEnabled = false; - /** * Metrics session ID */ - private static String sessionId = UUID.randomUUID().toString(); + private final static String sessionId = UUID.randomUUID().toString(); /** * Create instance of Backtrace Attribute @@ -76,7 +69,7 @@ public BacktraceAttributes(Context context, BacktraceReport report, Map clientAttributes) { - convertAttributes(clientAttributes); - } - /** * Divide report attributes into primitive and complex attributes and add to this object * @@ -211,41 +195,15 @@ private void convertReportAttributes(BacktraceReport report) { } } - /** - * Divide custom user attributes into primitive and complex attributes and add to this object - * - * @param attributes client's attributes - */ - private void convertAttributes(Map attributes) { - for (Map.Entry entry : attributes.entrySet()) { - Object value = entry.getValue(); - if (value == null) { - continue; - } - Class type = value.getClass(); - if (TypeHelper.isPrimitiveOrPrimitiveWrapperOrString(type)) { - this.attributes.put(entry.getKey(), value.toString()); - } else { - this.complexAttributes.put(entry.getKey(), value); - } + private void convertAttributes(Map clientAttributes) { + if (clientAttributes == null || clientAttributes.isEmpty()) { + return; } + ReportDataAttributes data = ReportDataBuilder.getReportAttributes(clientAttributes); + this.attributes.putAll(data.getAttributes()); + this.complexAttributes.putAll(data.getAnnotations()); } - public String getApplicationName() { - return this.context.getApplicationInfo().loadLabel(this.context - .getPackageManager()).toString(); - } - - public String getApplicationVersionOrEmpty() { - try { - return this.context.getPackageManager() - .getPackageInfo(this.context.getPackageName(), 0).versionName; - } catch (PackageManager.NameNotFoundException e) { - BacktraceLogger.e(LOG_TAG, "Could not resolve application version"); - e.printStackTrace(); - } - return ""; - } public Map getAllAttributes() { Map attributes = new HashMap(); diff --git a/backtrace-library/src/main/java/backtraceio/library/models/metrics/Event.java b/backtrace-library/src/main/java/backtraceio/library/models/metrics/Event.java index fce15e6e..6c475a5f 100644 --- a/backtrace-library/src/main/java/backtraceio/library/models/metrics/Event.java +++ b/backtrace-library/src/main/java/backtraceio/library/models/metrics/Event.java @@ -12,8 +12,11 @@ public abstract class Event { @SerializedName("timestamp") protected long timestamp; + /** + * The event should always have attributes. Event without attributes won't be processed + */ @SerializedName("attributes") - protected Map attributes; + protected Map attributes = new HashMap<>(); public Event(long timestamp) { this.timestamp = timestamp; @@ -23,30 +26,16 @@ public long getTimestamp() { return this.timestamp; } - public Map getAttributes() { + public Map getAttributes() { return this.attributes; } public abstract String getName(); - protected void addAttributesImpl(Map attributes) { - if (attributes == null || attributes.size() == 0) { + protected void addAttributesImpl(Map attributes) { + if (attributes == null || attributes.isEmpty()) { return; } - - Map attributesNoEmpty = new HashMap(); - - for (String key : attributes.keySet()) { - Object value = attributes.get(key); - if (BacktraceStringHelper.isObjectNotNullOrNotEmptyString(value)) { - attributesNoEmpty.put(key, value); - } - } - - if (this.attributes == null) { - this.attributes = attributesNoEmpty; - } else { - this.attributes.putAll(attributesNoEmpty); - } + this.attributes.putAll(attributes); } } diff --git a/backtrace-library/src/main/java/backtraceio/library/models/metrics/EventsPayload.java b/backtrace-library/src/main/java/backtraceio/library/models/metrics/EventsPayload.java index 05198c75..f07d9241 100644 --- a/backtrace-library/src/main/java/backtraceio/library/models/metrics/EventsPayload.java +++ b/backtrace-library/src/main/java/backtraceio/library/models/metrics/EventsPayload.java @@ -33,5 +33,13 @@ public void setDroppedEvents(int droppedEvents) { this.eventsMetadata.setDroppedEvents(droppedEvents); } + public String getApplicationVersion() { + return appVersion; + } + + public String getApplicationName() { + return application; + } + public abstract ConcurrentLinkedDeque getEvents(); } diff --git a/backtrace-library/src/main/java/backtraceio/library/models/metrics/SummedEvent.java b/backtrace-library/src/main/java/backtraceio/library/models/metrics/SummedEvent.java index b9665f01..bcd8efb8 100644 --- a/backtrace-library/src/main/java/backtraceio/library/models/metrics/SummedEvent.java +++ b/backtrace-library/src/main/java/backtraceio/library/models/metrics/SummedEvent.java @@ -13,14 +13,14 @@ public final class SummedEvent extends Event { private final String name; public SummedEvent(String name) { - this(name, new HashMap()); + this(name, null); } - public SummedEvent(String name, Map attributes) { + public SummedEvent(String name, Map attributes) { this(name, BacktraceTimeHelper.getTimestampSeconds(), attributes); } - public SummedEvent(String name, long timestamp, Map attributes) { + public SummedEvent(String name, long timestamp, Map attributes) { super(timestamp); this.name = name; addAttributesImpl(attributes); @@ -35,7 +35,7 @@ public String getName() { return this.name; } - public void addAttributes(Map attributes) { + public void addAttributes(Map attributes) { addAttributesImpl(attributes); } } diff --git a/backtrace-library/src/main/java/backtraceio/library/models/metrics/UniqueEvent.java b/backtrace-library/src/main/java/backtraceio/library/models/metrics/UniqueEvent.java index 789b9825..151bbe93 100644 --- a/backtrace-library/src/main/java/backtraceio/library/models/metrics/UniqueEvent.java +++ b/backtrace-library/src/main/java/backtraceio/library/models/metrics/UniqueEvent.java @@ -22,14 +22,14 @@ public class UniqueEvent extends Event { private final List name; public UniqueEvent(String name) { - this(name, new HashMap()); + this(name, null); } - public UniqueEvent(String name, Map attributes) { + public UniqueEvent(String name, Map attributes) { this(name, BacktraceTimeHelper.getTimestampSeconds(), attributes); } - public UniqueEvent(String name, long timestamp, Map attributes) { + public UniqueEvent(String name, long timestamp, Map attributes) { super(timestamp); this.name = new ArrayList() {{ add(name); @@ -49,7 +49,7 @@ public String getName() { return ""; } - public void update(long timestamp, Map attributes) { + public void update(long timestamp, Map attributes) { this.timestamp = timestamp; addAttributesImpl(attributes); } diff --git a/backtrace-library/src/main/java/backtraceio/library/services/BacktraceEventsHandler.java b/backtrace-library/src/main/java/backtraceio/library/services/BacktraceEventsHandler.java index 50e6ff60..6d0ad44d 100644 --- a/backtrace-library/src/main/java/backtraceio/library/services/BacktraceEventsHandler.java +++ b/backtrace-library/src/main/java/backtraceio/library/services/BacktraceEventsHandler.java @@ -10,7 +10,6 @@ import backtraceio.library.common.BacktraceSerializeHelper; import backtraceio.library.interfaces.Api; import backtraceio.library.logger.BacktraceLogger; -import backtraceio.library.models.json.BacktraceAttributes; import backtraceio.library.models.metrics.Event; import backtraceio.library.models.metrics.EventsPayload; import backtraceio.library.models.metrics.EventsResult; @@ -52,22 +51,13 @@ abstract class BacktraceEventsHandler extends Handler { */ private int maximumNumberOfEvents = 350; - /** - * The application name - */ - protected String application; - - /** - * The application version - */ - protected String appVersion; - /** * Create BacktraceEventsHandler instance - * @param backtraceMetrics Backtrace metrics object - * @param api Backtrace API object - * @param backtraceHandlerThread Backtrace handler thread object - * @param urlPrefix Url routing prefix for metrics + * + * @param backtraceMetrics Backtrace metrics object + * @param api Backtrace API object + * @param backtraceHandlerThread Backtrace handler thread object + * @param urlPrefix Url routing prefix for metrics */ public BacktraceEventsHandler(BacktraceMetrics backtraceMetrics, Api api, @@ -89,11 +79,7 @@ public BacktraceEventsHandler(BacktraceMetrics backtraceMetrics, this.timeBetweenRetriesMillis = backtraceMetrics.settings.getTimeBetweenRetriesMillis(); long timeIntervalMillis = backtraceMetrics.settings.getTimeIntervalMillis(); - - BacktraceAttributes backtraceAttributes = new BacktraceAttributes(backtraceMetrics.getContext(), null, null); - this.application = backtraceAttributes.getApplicationName(); - this.appVersion = backtraceAttributes.getApplicationVersionOrEmpty(); - + if (timeIntervalMillis != 0) { final BacktraceEventsHandler handler = this; handler.postDelayed(new Runnable() { diff --git a/backtrace-library/src/main/java/backtraceio/library/services/BacktraceMetrics.java b/backtrace-library/src/main/java/backtraceio/library/services/BacktraceMetrics.java index 82dbf461..980c6f84 100644 --- a/backtrace-library/src/main/java/backtraceio/library/services/BacktraceMetrics.java +++ b/backtrace-library/src/main/java/backtraceio/library/services/BacktraceMetrics.java @@ -4,11 +4,11 @@ import org.jetbrains.annotations.NotNull; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentLinkedDeque; import backtraceio.library.BacktraceCredentials; +import backtraceio.library.common.ApplicationMetadataCache; import backtraceio.library.common.BacktraceStringHelper; import backtraceio.library.common.BacktraceTimeHelper; import backtraceio.library.common.serialization.DebugHelper; @@ -19,6 +19,7 @@ import backtraceio.library.logger.BacktraceLogger; import backtraceio.library.models.BacktraceMetricsSettings; import backtraceio.library.models.json.BacktraceAttributes; +import backtraceio.library.models.attributes.ReportDataBuilder; import backtraceio.library.models.metrics.SummedEvent; import backtraceio.library.models.metrics.UniqueEvent; @@ -117,6 +118,16 @@ public final class BacktraceMetrics implements Metrics { */ private final BacktraceCredentials credentials; + /** + * Application name + */ + private String applicationName; + + /** + * Application version + */ + private String applicationVersion; + /** * Create new Backtrace metrics instance * @@ -170,6 +181,11 @@ public void enable(BacktraceMetricsSettings settings, String uniqueEventName) { if (uniqueEventName == null || uniqueEventName.length() == 0) { throw new IllegalArgumentException("Unique event name must be defined!"); } + + ApplicationMetadataCache applicationMetadata = ApplicationMetadataCache.getInstance(this.getContext()); + this.applicationName = applicationMetadata.getApplicationName(); + this.applicationVersion = applicationMetadata.getApplicationVersion(); + final long startMetricsSetup = DebugHelper.getCurrentTimeMillis(); setStartupUniqueEventName(uniqueEventName); @@ -186,6 +202,17 @@ public void enable(BacktraceMetricsSettings settings, String uniqueEventName) { BacktraceLogger.d(LOG_TAG, "Setup metrics integration took " + (endMetricsSetup - startMetricsSetup) + " milliseconds"); } + /** + * Attributes are passed by the reference from the Backtrace client instance. + * If we modify them in the constructor, we won't be able to get "up to date" + * version from the client anymore. + * Due to that, we need to have a getter that will always transform attributes to a simple format. + */ + private Map getClientMetricsAttributes() { + return ReportDataBuilder.getReportAttributes(customReportAttributes, true).getAttributes(); + } + + private void verifyIfMetricsAvailable() { if (!enabled) { throw new IllegalArgumentException("Metrics are not available!"); @@ -213,6 +240,7 @@ public void sendStartupEvent() { verifyIfMetricsAvailable(); addUniqueEvent(startupUniqueEventName); addSummedEvent(startupSummedEventName); + uniqueEventsHandler.send(); summedEventsHandler.send(); } @@ -251,7 +279,9 @@ public boolean addUniqueEvent(String attributeName, Map attribut return false; } - Map localAttributes = createLocalAttributes(attributes); + Map metricsAttributes = ReportDataBuilder.getReportAttributes(attributes, true).getAttributes(); + + Map localAttributes = createLocalAttributes(metricsAttributes); // validate if unique event attribute is available and // prevent undefined attributes @@ -304,7 +334,7 @@ public int count() { /** * Add a summed event to the next Backtrace Metrics request * - * @param metricGroupName + * @param metricGroupName name of the metrics group * @return true if success */ public boolean addSummedEvent(String metricGroupName) { @@ -314,8 +344,8 @@ public boolean addSummedEvent(String metricGroupName) { /** * Add a summed event to the next Backtrace Metrics request * - * @param metricGroupName - * @param attributes + * @param metricGroupName name of the metrics group + * @param attributes metrics attributes * @return true if success */ public boolean addSummedEvent(String metricGroupName, Map attributes) { @@ -326,12 +356,10 @@ public boolean addSummedEvent(String metricGroupName, Map attrib return false; } - Map localAttributes = new HashMap<>(); - if (attributes != null) { - localAttributes.putAll(attributes); - } + Map metricsAttributes = ReportDataBuilder.getReportAttributes(attributes, true).getAttributes(); - SummedEvent summedEvent = new SummedEvent(metricGroupName, BacktraceTimeHelper.getTimestampSeconds(), localAttributes); + SummedEvent summedEvent = new SummedEvent(metricGroupName); + summedEvent.addAttributes(metricsAttributes); summedEventsHandler.events.addLast(summedEvent); if (count() == maximumNumberOfEvents) { uniqueEventsHandler.send(); @@ -386,17 +414,25 @@ private boolean shouldProcessEvent(String name) { return true; } - protected Map createLocalAttributes(Map attributes) { - Map localAttributes = new HashMap<>(); + protected Map createLocalAttributes(Map attributes) { + BacktraceAttributes backtraceAttributes = new BacktraceAttributes(context, null, null, true); - if (attributes != null) { - localAttributes.putAll(attributes); + Map result = backtraceAttributes.attributes; + result.putAll(this.getClientMetricsAttributes()); + + if (attributes != null && !attributes.isEmpty()) { + result.putAll(attributes); } - BacktraceAttributes backtraceAttributes = new BacktraceAttributes(context, null, customReportAttributes); - localAttributes.putAll(backtraceAttributes.getAllAttributes()); + return result; + } + + protected String getApplicationName() { + return applicationName; + } - return localAttributes; + protected String getApplicationVersion() { + return applicationVersion; } /** diff --git a/backtrace-library/src/main/java/backtraceio/library/services/SummedEventsHandler.java b/backtrace-library/src/main/java/backtraceio/library/services/SummedEventsHandler.java index 14206828..85b27ade 100644 --- a/backtrace-library/src/main/java/backtraceio/library/services/SummedEventsHandler.java +++ b/backtrace-library/src/main/java/backtraceio/library/services/SummedEventsHandler.java @@ -1,5 +1,7 @@ package backtraceio.library.services; +import java.util.ArrayList; +import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentLinkedDeque; @@ -21,18 +23,15 @@ public SummedEventsHandler(BacktraceMetrics backtraceMetrics, @Override protected SummedEventsPayload getEventsPayload() { - Map attributes = backtraceMetrics.createLocalAttributes(null); - - ConcurrentLinkedDeque eventsCopy = new ConcurrentLinkedDeque<>(); - - for (SummedEvent event : events) { + Map attributes = backtraceMetrics.createLocalAttributes(null); + for (SummedEvent event: events) { event.addAttributes(attributes); - eventsCopy.addLast(new SummedEvent((SummedEvent) event)); } + SummedEventsPayload payload = new SummedEventsPayload( + new ConcurrentLinkedDeque<>(events), + backtraceMetrics.getApplicationName(), + backtraceMetrics.getApplicationVersion()); events.clear(); - - SummedEventsPayload payload = new SummedEventsPayload(eventsCopy, application, appVersion); - return payload; } diff --git a/backtrace-library/src/main/java/backtraceio/library/services/UniqueEventsHandler.java b/backtrace-library/src/main/java/backtraceio/library/services/UniqueEventsHandler.java index 96d12846..6b7199f7 100644 --- a/backtrace-library/src/main/java/backtraceio/library/services/UniqueEventsHandler.java +++ b/backtrace-library/src/main/java/backtraceio/library/services/UniqueEventsHandler.java @@ -22,14 +22,13 @@ public UniqueEventsHandler(BacktraceMetrics backtraceMetrics, @Override protected UniqueEventsPayload getEventsPayload() { - Map attributes = backtraceMetrics.createLocalAttributes(null); + Map attributes = backtraceMetrics.createLocalAttributes(null); for (UniqueEvent event : events) { event.update(BacktraceTimeHelper.getTimestampSeconds(), attributes); } - UniqueEventsPayload payload = new UniqueEventsPayload(events, application, appVersion); - return payload; + return new UniqueEventsPayload(events, backtraceMetrics.getApplicationName(), backtraceMetrics.getApplicationVersion()); } @Override diff --git a/backtrace-library/src/test/java/backtraceio/library/ReportDataAnnotationBuilderTest.java b/backtrace-library/src/test/java/backtraceio/library/ReportDataAnnotationBuilderTest.java new file mode 100644 index 00000000..a0259ee9 --- /dev/null +++ b/backtrace-library/src/test/java/backtraceio/library/ReportDataAnnotationBuilderTest.java @@ -0,0 +1,49 @@ +package backtraceio.library; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; + +import static org.junit.Assert.assertEquals; + +import backtraceio.library.models.attributes.ReportDataAttributes; +import backtraceio.library.models.attributes.ReportDataBuilder; + +@RunWith(Parameterized.class) +public class ReportDataAnnotationBuilderTest { + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][]{ + {new HashMap() {{ + put("add", "value"); + }}}, + {new HashSet() {{ + add("value"); + }}}, + {new Object()}, + }); + } + + private final Object annotation; + + public ReportDataAnnotationBuilderTest(Object annotation) { + this.annotation = annotation; + } + + @Test + public void correctlySetComplexObjectAsAnnotation() { + String key = "annotation-key"; + ReportDataAttributes data = ReportDataBuilder.getReportAttributes(new HashMap() { + { + put(key, annotation); + } + }); + assertEquals(data.getAnnotations().get(key), annotation); + } +} diff --git a/backtrace-library/src/test/java/backtraceio/library/ReportDataAttributeBuilderTest.java b/backtrace-library/src/test/java/backtraceio/library/ReportDataAttributeBuilderTest.java new file mode 100644 index 00000000..dd0cd75d --- /dev/null +++ b/backtrace-library/src/test/java/backtraceio/library/ReportDataAttributeBuilderTest.java @@ -0,0 +1,67 @@ +package backtraceio.library; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import backtraceio.library.models.attributes.ReportDataAttributes; +import backtraceio.library.models.attributes.ReportDataBuilder; + +@RunWith(Parameterized.class) +public class ReportDataAttributeBuilderTest { + + private final String key = "attribute-key"; + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][]{ + {"String value"}, + {123}, + {45.67}, + {true}, + }); + } + + private final Object primitiveValue; + + public ReportDataAttributeBuilderTest(Object primitiveValue) { + this.primitiveValue = primitiveValue; + } + + @Test + public void correctlySetPrimitiveValueIntoAttribute() { + ReportDataAttributes data = ReportDataBuilder.getReportAttributes(new HashMap() { + { + put(key, primitiveValue); + } + }); + assertEquals(data.getAttributes().get(key), primitiveValue.toString()); + } + + @Test + public void shouldSetNullableValueAsAttribute() { + ReportDataAttributes data = ReportDataBuilder.getReportAttributes(new HashMap() { + { + put(key, null); + } + }); + assertNull(data.getAttributes().get(key)); + } + + @Test + public void shouldSkipNullableValue() { + ReportDataAttributes data = ReportDataBuilder.getReportAttributes(new HashMap() { + { + put(key, null); + } + }, true); + assertNull(data.getAttributes().get(key)); + } +} diff --git a/example-app/src/main/java/backtraceio/backtraceio/MainActivity.java b/example-app/src/main/java/backtraceio/backtraceio/MainActivity.java index f0c3f1cb..e0f5be57 100644 --- a/example-app/src/main/java/backtraceio/backtraceio/MainActivity.java +++ b/example-app/src/main/java/backtraceio/backtraceio/MainActivity.java @@ -51,7 +51,7 @@ public void setOnServerResponseEventListener(OnServerResponseEventListener e) { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - + backtraceClient = initializeBacktrace(BuildConfig.BACKTRACE_SUBMISSION_URL); symlinkAndWriteFile();