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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Unreleased

### Features

- Add opt-in binder (IPC) tracing and logging instrumentation for Android ([#5515](https://github.com/getsentry/sentry-java/pull/5515))
- Enable spans via `options.isEnableBinderTracing = true` or manifest: `<meta-data android:name="io.sentry.traces.binder.enable" android:value="true" />`
- Enable logs via `options.isEnableBinderLogging = true` or manifest: `<meta-data android:name="io.sentry.logs.binder.enable" android:value="true" />`

## 8.43.1

### Fixes
Expand Down
4 changes: 4 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun isEnableAppLifecycleBreadcrumbs ()Z
public fun isEnableAutoActivityLifecycleTracing ()Z
public fun isEnableAutoTraceIdGeneration ()Z
public fun isEnableBinderLogging ()Z
public fun isEnableBinderTracing ()Z
public fun isEnableFramesTracking ()Z
public fun isEnableNdk ()Z
public fun isEnableNetworkEventBreadcrumbs ()Z
Expand Down Expand Up @@ -417,6 +419,8 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setEnableAppLifecycleBreadcrumbs (Z)V
public fun setEnableAutoActivityLifecycleTracing (Z)V
public fun setEnableAutoTraceIdGeneration (Z)V
public fun setEnableBinderLogging (Z)V
public fun setEnableBinderTracing (Z)V
public fun setEnableFramesTracking (Z)V
public fun setEnableNdk (Z)V
public fun setEnableNetworkEventBreadcrumbs (Z)V
Expand Down
2 changes: 2 additions & 0 deletions sentry-android-core/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,5 @@
-dontwarn io.sentry.spotlight.SpotlightIntegration
-keepnames class io.sentry.spotlight.SpotlightIntegration
##---------------End: proguard configuration for sentry-spotlight ----------

-keepnames class io.sentry.android.core.internal.binder.SentryBinderAdapter { *; }
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ final class ManifestMetadataReader {

static final String ENABLE_ANR_FINGERPRINTING = "io.sentry.anr.enable-fingerprinting";

static final String ENABLE_BINDER_TRACING = "io.sentry.traces.binder.enable";

static final String ENABLE_BINDER_LOGGING = "io.sentry.logs.binder.enable";

/** ManifestMetadataReader ctor */
private ManifestMetadataReader() {}

Expand Down Expand Up @@ -725,6 +729,12 @@ static void applyMetadata(
options.setEnableAnrFingerprinting(
readBool(
metadata, logger, ENABLE_ANR_FINGERPRINTING, options.isEnableAnrFingerprinting()));

options.setEnableBinderTracing(
readBool(metadata, logger, ENABLE_BINDER_TRACING, options.isEnableBinderTracing()));

options.setEnableBinderLogging(
readBool(metadata, logger, ENABLE_BINDER_LOGGING, options.isEnableBinderLogging()));
}
options
.getLogger()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.Session;
import io.sentry.android.core.internal.binder.SentryBinderAdapter;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.android.core.performance.TimeSpan;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
Expand Down Expand Up @@ -151,6 +152,9 @@ public static void init(
t);
}

SentryBinderAdapter.setEnabled(
options.isEnableBinderTracing(), options.isEnableBinderLogging());

// if SentryPerformanceProvider was disabled or removed,
// we set the app start / sdk init time here instead
final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ public interface BeforeCaptureCallback {

private boolean enableAnrFingerprinting = true;

/** Enable or disable creating spans for binder (IPC) calls. Default is disabled. */
private boolean enableBinderTracing = false;

/** Enable or disable logging of binder (IPC) calls. Default is disabled. */
private boolean enableBinderLogging = false;

public SentryAndroidOptions() {
setSentryClientName(BuildConfig.SENTRY_ANDROID_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
setSdkVersion(createSdkVersion());
Expand Down Expand Up @@ -755,6 +761,42 @@ public void setEnableAnrFingerprinting(final boolean enableAnrFingerprinting) {
this.enableAnrFingerprinting = enableAnrFingerprinting;
}

/**
* Returns whether creating spans for binder (IPC) calls is enabled. Default is disabled.
*
* @return true if binder spans are enabled
*/
public boolean isEnableBinderTracing() {
return enableBinderTracing;
}

/**
* Enables or disables creating spans for binder (IPC) calls.
*
* @param enableBinderTracing true to enable binder spans
*/
public void setEnableBinderTracing(final boolean enableBinderTracing) {
this.enableBinderTracing = enableBinderTracing;
}

/**
* Returns whether logging of binder (IPC) calls is enabled. Default is disabled.
*
* @return true if binder logging is enabled
*/
public boolean isEnableBinderLogging() {
return enableBinderLogging;
}

/**
* Enables or disables logging of binder (IPC) calls.
*
* @param enableBinderLogging true to enable binder logging
*/
public void setEnableBinderLogging(final boolean enableBinderLogging) {
this.enableBinderLogging = enableBinderLogging;
}

static class AndroidUserFeedbackFormHandler implements SentryFeedbackOptions.IFormHandler {
@Override
public void showForm(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package io.sentry.android.core.internal.binder;

import android.os.Build;
import io.sentry.ISpan;
import io.sentry.Sentry;
import io.sentry.SentryAttributes;
import io.sentry.SentryLogLevel;
import io.sentry.SpanDataConvention;
import io.sentry.logger.SentryLogParameters;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@SuppressWarnings({"unused", "deprecation"})
@ApiStatus.Internal
public final class SentryBinderAdapter {

private static final AtomicInteger cookieCounter = new AtomicInteger();
private static final int NO_COOKIE = -1;

private static volatile boolean tracingEnabled = false;
private static volatile boolean loggingEnabled = false;

/** Configures which binder features are active. Expected to be called once during SDK init. */
public static void setEnabled(final boolean tracingEnabled, final boolean loggingEnabled) {
SentryBinderAdapter.tracingEnabled = tracingEnabled;
SentryBinderAdapter.loggingEnabled = loggingEnabled;
}

private static final ThreadLocal<Map<Integer, ISpan>> spanMap =
new ThreadLocal<Map<Integer, ISpan>>() {
@Override
protected Map<Integer, ISpan> initialValue() {
return new HashMap<>();
}
};

public static int onCallStart(final @NotNull String component, final @NotNull String name) {
if (!tracingEnabled && !loggingEnabled) {
return NO_COOKIE;
}

try {
final @NotNull Thread currentThread = Thread.currentThread();
final long threadId;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
threadId = currentThread.threadId();
} else {
threadId = currentThread.getId();
}
final @Nullable String threadName = currentThread.getName();

if (loggingEnabled) {
recordLog(component, name, threadId, threadName);
}
if (tracingEnabled) {
final int cookie = cookieCounter.incrementAndGet();
recordSpan(component, name, threadId, threadName, cookie);
return cookie;
}
} catch (Throwable t) {
// ignored, as instrumentation should never crash
}
return NO_COOKIE;
}

public static void onCallEnd(final int cookie) {
if (cookie == NO_COOKIE) {
return;
}
try {
final @Nullable Map<Integer, ISpan> map = spanMap.get();
if (map == null) {
return;
}
final @Nullable ISpan span = map.remove(cookie);
if (span != null) {
span.finish();
}
} catch (Throwable t) {
// ignored
}
}

private static void recordSpan(
final @NotNull String component,
final @NotNull String name,
final long threadId,
final @Nullable String threadName,
final int cookie) {

final @Nullable ISpan parent = Sentry.getCurrentScopes().getTransaction();
if (parent == null) {
return;
}
final @Nullable Map<Integer, ISpan> map = spanMap.get();
if (map == null) {
return;
}
final @NotNull ISpan span = parent.startChild("binder", component + "." + name);
span.setData(SpanDataConvention.THREAD_ID, String.valueOf(threadId));
span.setData(SpanDataConvention.THREAD_NAME, threadName);
map.put(cookie, span);
}

private static void recordLog(
final @NotNull String component,
final @NotNull String name,
final long threadId,
final @Nullable String threadName) {
final @NotNull Map<String, Object> logAttributes = new HashMap<>();
logAttributes.put(SpanDataConvention.THREAD_ID, threadId);
logAttributes.put(SpanDataConvention.THREAD_NAME, threadName);

Sentry.logger()
.log(
SentryLogLevel.INFO,
SentryLogParameters.create(SentryAttributes.fromMap(logAttributes)),
"binder call: %s.%s",
component,
name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2586,4 +2586,54 @@ class ManifestMetadataReaderTest {
// Assert
assertEquals("12345", fixture.options.orgId)
}

@Test
fun `applyMetadata reads enableBinderTracing to options`() {
// Arrange
val bundle = bundleOf(ManifestMetadataReader.ENABLE_BINDER_TRACING to true)
val context = fixture.getContext(metaData = bundle)

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

// Assert
assertTrue(fixture.options.isEnableBinderTracing)
}

@Test
fun `applyMetadata keeps enableBinderTracing default when not set in manifest`() {
// Arrange
val context = fixture.getContext()

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

// Assert
assertFalse(fixture.options.isEnableBinderTracing)
}

@Test
fun `applyMetadata reads enableBinderLogging to options`() {
// Arrange
val bundle = bundleOf(ManifestMetadataReader.ENABLE_BINDER_LOGGING to true)
val context = fixture.getContext(metaData = bundle)

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

// Assert
assertTrue(fixture.options.isEnableBinderLogging)
}

@Test
fun `applyMetadata keeps enableBinderLogging default when not set in manifest`() {
// Arrange
val context = fixture.getContext()

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

// Assert
assertFalse(fixture.options.isEnableBinderLogging)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,36 @@ class SentryAndroidOptionsTest {
assertFalse(sentryOptions.isAttachViewHierarchy)
}

@Test
fun `binder tracing is disabled by default for Android`() {
val sentryOptions = SentryAndroidOptions()

assertFalse(sentryOptions.isEnableBinderTracing)
}

@Test
fun `binder logging is disabled by default for Android`() {
val sentryOptions = SentryAndroidOptions()

assertFalse(sentryOptions.isEnableBinderLogging)
}

@Test
fun `binder tracing can be enabled`() {
val sentryOptions = SentryAndroidOptions()
sentryOptions.isEnableBinderTracing = true

assertTrue(sentryOptions.isEnableBinderTracing)
}

@Test
fun `binder logging can be enabled`() {
val sentryOptions = SentryAndroidOptions()
sentryOptions.isEnableBinderLogging = true

assertTrue(sentryOptions.isEnableBinderLogging)
}

@Test
fun `native sdk name is null by default`() {
val sentryOptions = SentryAndroidOptions()
Expand Down
Loading
Loading