Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Performance

- Use manifest metadata resolved at build time, reducing Android SDK initialization time by 6.5% ([#5969](https://github.com/getsentry/sentry-java/pull/5969))

### Dependencies

- Bump Native SDK from v0.16.2 to v0.16.3 ([#5962](https://github.com/getsentry/sentry-java/pull/5962))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -217,7 +218,7 @@ static void applyMetadata(
Objects.requireNonNull(options, "The options object is required.");

try {
final Bundle metadata = getMetadata(context, options.getLogger(), buildInfoProvider);
final Object metadata = getMetadata(context, options.getLogger(), buildInfoProvider);
final ILogger logger = options.getLogger();

if (metadata != null) {
Expand Down Expand Up @@ -491,7 +492,7 @@ static void applyMetadata(
List<String> tracePropagationTargets =
readList(metadata, logger, TRACE_PROPAGATION_TARGETS);

if (metadata.containsKey(TRACE_PROPAGATION_TARGETS) && tracePropagationTargets == null) {
if (containsKey(metadata, TRACE_PROPAGATION_TARGETS) && tracePropagationTargets == null) {
options.setTracePropagationTargets(Collections.emptyList());
} else if (tracePropagationTargets != null) {
options.setTracePropagationTargets(tracePropagationTargets);
Expand Down Expand Up @@ -779,44 +780,68 @@ static void applyMetadata(
}

private static boolean readBool(
final @NotNull Bundle metadata,
final @NotNull Object metadata,
final @NotNull ILogger logger,
final @NotNull String key,
final boolean defaultValue) {
final boolean value = metadata.getBoolean(key, defaultValue);
final boolean value;
if (metadata instanceof Bundle) {
value = ((Bundle) metadata).getBoolean(key, defaultValue);
} else {
final Object raw = ((Map<?, ?>) metadata).get(key);
value = raw instanceof Boolean ? (Boolean) raw : defaultValue;
}
if (logger.isEnabled(SentryLevel.DEBUG)) {
logger.log(SentryLevel.DEBUG, key + " read: " + value);
}
return value;
}

private static @Nullable String readString(
final @NotNull Bundle metadata,
final @NotNull Object metadata,
final @NotNull ILogger logger,
final @NotNull String key,
final @Nullable String defaultValue) {
final String value = metadata.getString(key, defaultValue);
final String value;
if (metadata instanceof Bundle) {
value = ((Bundle) metadata).getString(key, defaultValue);
} else {
final Object raw = ((Map<?, ?>) metadata).get(key);
value = raw instanceof String ? (String) raw : defaultValue;
}
if (logger.isEnabled(SentryLevel.DEBUG)) {
logger.log(SentryLevel.DEBUG, key + " read: " + value);
}
return value;
}

private static @NotNull String readStringNotNull(
final @NotNull Bundle metadata,
final @NotNull Object metadata,
final @NotNull ILogger logger,
final @NotNull String key,
final @NotNull String defaultValue) {
final String value = metadata.getString(key, defaultValue);
final String value;
if (metadata instanceof Bundle) {
value = ((Bundle) metadata).getString(key, defaultValue);
} else {
final Object raw = ((Map<?, ?>) metadata).get(key);
value = raw instanceof String ? (String) raw : defaultValue;
}
if (logger.isEnabled(SentryLevel.DEBUG)) {
logger.log(SentryLevel.DEBUG, key + " read: " + value);
}
return value;
}

private static @Nullable List<String> readList(
final @NotNull Bundle metadata, final @NotNull ILogger logger, final @NotNull String key) {
final String value = metadata.getString(key);
final @NotNull Object metadata, final @NotNull ILogger logger, final @NotNull String key) {
final String value;
if (metadata instanceof Bundle) {
value = ((Bundle) metadata).getString(key);
} else {
final Object raw = ((Map<?, ?>) metadata).get(key);
value = raw instanceof String ? (String) raw : null;
}
if (logger.isEnabled(SentryLevel.DEBUG)) {
logger.log(SentryLevel.DEBUG, key + " read: " + value);
}
Expand All @@ -828,11 +853,21 @@ private static boolean readBool(
}

private static double readDouble(
final @NotNull Bundle metadata, final @NotNull ILogger logger, final @NotNull String key) {
// manifest meta-data only reads float
double value = ((Float) metadata.getFloat(key, -1)).doubleValue();
if (value == -1) {
value = ((Integer) metadata.getInt(key, -1)).doubleValue();
final @NotNull Object metadata, final @NotNull ILogger logger, final @NotNull String key) {
final double value;
if (metadata instanceof Bundle) {
// manifest meta-data only reads float
double bundleValue = ((Float) ((Bundle) metadata).getFloat(key, -1)).doubleValue();
if (bundleValue == -1) {
bundleValue = ((Integer) ((Bundle) metadata).getInt(key, -1)).doubleValue();
}
value = bundleValue;
} else {
final Object raw = ((Map<?, ?>) metadata).get(key);
value =
raw instanceof Float
? ((Float) raw).doubleValue()
: raw instanceof Integer ? ((Integer) raw).doubleValue() : -1;
}
if (logger.isEnabled(SentryLevel.DEBUG)) {
logger.log(SentryLevel.DEBUG, key + " read: " + value);
Expand All @@ -841,12 +876,18 @@ private static double readDouble(
}

private static long readLong(
final @NotNull Bundle metadata,
final @NotNull Object metadata,
final @NotNull ILogger logger,
final @NotNull String key,
final long defaultValue) {
// manifest meta-data only reads int if the value is not big enough
final long value = metadata.getInt(key, (int) defaultValue);
final long value;
if (metadata instanceof Bundle) {
// manifest meta-data only reads int if the value is not big enough
value = ((Bundle) metadata).getInt(key, (int) defaultValue);
} else {
final Object raw = ((Map<?, ?>) metadata).get(key);
value = raw instanceof Integer ? (Integer) raw : defaultValue;
}
if (logger.isEnabled(SentryLevel.DEBUG)) {
logger.log(SentryLevel.DEBUG, key + " read: " + value);
}
Expand All @@ -865,7 +906,7 @@ static boolean isAutoInit(final @NotNull Context context, final @NotNull ILogger

boolean autoInit = true;
try {
final Bundle metadata = getMetadata(context, logger, null);
final Object metadata = getMetadata(context, logger, null);
if (metadata != null) {
autoInit = readBool(metadata, logger, AUTO_INIT, true);
}
Expand All @@ -876,18 +917,28 @@ static boolean isAutoInit(final @NotNull Context context, final @NotNull ILogger
}

/**
* Returns the Bundle attached from the given Context
* Returns build-time metadata when available, otherwise metadata attached to the given Context.
*
* @param context the application context
* @return the Bundle attached to the PackageManager
* @return metadata as a Map or PackageManager Bundle
*/
private static @Nullable Bundle getMetadata(
private static @Nullable Object getMetadata(
final @NotNull Context context,
final @NotNull ILogger logger,
final @Nullable BuildInfoProvider buildInfoProvider) {
final @Nullable Map<String, Object> injected = SentryBuildTimeMetadata.manifestMetadata;
if (injected != null) {
return injected;
}
final ApplicationInfo app =
ContextUtils.getApplicationInfo(
context, buildInfoProvider != null ? buildInfoProvider : new BuildInfoProvider(logger));
return app != null ? app.metaData : null;
}

private static boolean containsKey(final @NotNull Object metadata, final @NotNull String key) {
return metadata instanceof Bundle
? ((Bundle) metadata).containsKey(key)
: ((Map<?, ?>) metadata).containsKey(key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.sentry.android.core;

import java.util.Map;
import org.jetbrains.annotations.Nullable;

/** Manifest metadata populated by the Sentry Android Gradle plugin at build time. */
final class SentryBuildTimeMetadata {

static @Nullable Map<String, Object> manifestMetadata;

private SentryBuildTimeMetadata() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import android.content.Context
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import io.sentry.FilterString
import io.sentry.ILogger
import io.sentry.ProfileLifecycle
import io.sentry.SentryLevel
import io.sentry.SentryReplayOptions
import io.sentry.TransactionOptions
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
Expand All @@ -23,6 +25,7 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions

@RunWith(AndroidJUnit4::class)
class ManifestMetadataReaderTest {
Expand All @@ -42,6 +45,52 @@ class ManifestMetadataReaderTest {
ContextUtils.resetInstance()
}

@AfterTest
fun `tear down`() {
SentryBuildTimeMetadata.manifestMetadata = null
}

@Test
fun `applyMetadata reads typed build-time metadata without querying context`() {
val context = mock<Context>()
SentryBuildTimeMetadata.manifestMetadata =
mapOf(
ManifestMetadataReader.DEBUG to true,
ManifestMetadataReader.DIST to "dist",
ManifestMetadataReader.SAMPLE_RATE to 0.5f,
ManifestMetadataReader.MAX_BREADCRUMBS to 42,
)

ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

assertThat(fixture.options.isDebug).isTrue()
assertThat(fixture.options.dist).isEqualTo("dist")
assertThat(fixture.options.sampleRate).isEqualTo(0.5)
assertThat(fixture.options.maxBreadcrumbs).isEqualTo(42)
verifyNoInteractions(context)
}

@Test
fun `build-time metadata is authoritative when a key is absent`() {
val context = mock<Context>()
fixture.options.dist = "configured"
SentryBuildTimeMetadata.manifestMetadata = emptyMap()

ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

assertThat(fixture.options.dist).isEqualTo("configured")
verifyNoInteractions(context)
}

@Test
fun `isAutoInit reads build-time metadata without querying context`() {
val context = mock<Context>()
SentryBuildTimeMetadata.manifestMetadata = mapOf(ManifestMetadataReader.AUTO_INIT to false)

assertThat(ManifestMetadataReader.isAutoInit(context, fixture.logger)).isFalse()
verifyNoInteractions(context)
}

@Test
fun `isAutoInit won't throw exception and is enabled by default`() {
fixture.options.setDebug(true)
Expand Down
Loading