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

## Unreleased

### Features

- Add binder IPC instrumentation adapter for the Sentry Android Gradle plugin ([#5326](https://github.com/getsentry/sentry-java/pull/5326))
- New opt-in `SentryAndroidOptions.enableBinderTracing` creates a `binder.ipc` child span per instrumented binder call
- New opt-in `SentryAndroidOptions.enableBinderLogs` emits a Sentry log per instrumented binder call
- Both enrich the span/log with `thread.id` and `thread.name`

## 8.39.1

### Fixes
Expand Down
9 changes: 9 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,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 isEnableBinderLogs ()Z
public fun isEnableBinderTracing ()Z
public fun isEnableFramesTracking ()Z
public fun isEnableNdk ()Z
public fun isEnableNetworkEventBreadcrumbs ()Z
Expand Down Expand Up @@ -415,6 +417,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 setEnableBinderLogs (Z)V
public fun setEnableBinderTracing (Z)V
public fun setEnableFramesTracking (Z)V
public fun setEnableNdk (Z)V
public fun setEnableNetworkEventBreadcrumbs (Z)V
Expand Down Expand Up @@ -443,6 +447,11 @@ public final class io/sentry/android/core/SentryInitProvider {
public fun shutdown ()V
}

public final class io/sentry/android/core/SentryIpcTracer {
public static fun onCallEnd (I)V
public static fun onCallStart (Ljava/lang/String;Ljava/lang/String;)I
}

public final class io/sentry/android/core/SentryLogcatAdapter {
public fun <init> ()V
public static fun d (Ljava/lang/String;Ljava/lang/String;)I
Expand Down
5 changes: 5 additions & 0 deletions sentry-android-core/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@

-keepnames class io.sentry.android.core.ApplicationNotResponding

# Bytecode-instrumented IPC tracer โ€” the Sentry Android Gradle plugin emits
# direct static calls to these symbols, so both the class name and its static
# methods must be preserved.
-keep class io.sentry.android.core.SentryIpcTracer { *; }


##---------------End: proguard configuration for android-core ----------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ final class ManifestMetadataReader {
static final String ENABLE_AUTO_TRACE_ID_GENERATION =
"io.sentry.traces.enable-auto-id-generation";

@ApiStatus.Experimental
static final String ENABLE_BINDER_TRACING = "io.sentry.traces.binder-ipc.enable";

@ApiStatus.Experimental
static final String ENABLE_BINDER_LOGS = "io.sentry.logs.binder-ipc.enable";

static final String DEADLINE_TIMEOUT = "io.sentry.traces.deadline-timeout";

static final String FEEDBACK_NAME_REQUIRED = "io.sentry.feedback.is-name-required";
Expand Down Expand Up @@ -508,6 +514,12 @@ static void applyMetadata(
ENABLE_AUTO_TRACE_ID_GENERATION,
options.isEnableAutoTraceIdGeneration()));

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

options.setEnableBinderLogs(
readBool(metadata, logger, ENABLE_BINDER_LOGS, options.isEnableBinderLogs()));

options.setDeadlineTimeout(
readLong(metadata, logger, DEADLINE_TIMEOUT, options.getDeadlineTimeout()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,21 @@ public interface BeforeCaptureCallback {

private boolean enableAnrFingerprinting = true;

/**
* Enables Binder IPC tracing. When enabled and a transaction is active, the SDK creates a child
* span (op {@code binder.ipc}) around each binder call instrumented by the Sentry Android Gradle
* plugin. Requires the Sentry Android Gradle plugin with binder IPC instrumentation enabled.
* Defaults to {@code false}.
*/
private boolean enableBinderTracing = false;

/**
* Enables Binder IPC logs. When enabled, the SDK emits a Sentry log entry for each binder call
* instrumented by the Sentry Android Gradle plugin. Requires the Sentry Android Gradle plugin
* with binder IPC instrumentation enabled and Sentry logs enabled. Defaults to {@code false}.
*/
private boolean enableBinderLogs = false;

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

@ApiStatus.Experimental
public boolean isEnableBinderTracing() {
return enableBinderTracing;
}

@ApiStatus.Experimental
public void setEnableBinderTracing(final boolean enableBinderTracing) {
this.enableBinderTracing = enableBinderTracing;
}

@ApiStatus.Experimental
public boolean isEnableBinderLogs() {
return enableBinderLogs;
}

@ApiStatus.Experimental
public void setEnableBinderLogs(final boolean enableBinderLogs) {
this.enableBinderLogs = enableBinderLogs;
}

static class AndroidUserFeedbackIDialogHandler implements SentryFeedbackOptions.IDialogHandler {
@Override
public void showDialog(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package io.sentry.android.core;

import io.sentry.IScopes;
import io.sentry.ISpan;
import io.sentry.Sentry;
import io.sentry.SentryAttribute;
import io.sentry.SentryAttributes;
import io.sentry.SentryLogLevel;
import io.sentry.SentryOptions;
import io.sentry.SpanDataConvention;
import io.sentry.logger.SentryLogParameters;
import io.sentry.util.thread.IThreadChecker;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

/**
* Entry point invoked by bytecode instrumented by the Sentry Android Gradle plugin around binder
* IPC call sites. The instrumentation emits calls to {@link #onCallStart(String, String)} and
* {@link #onCallEnd(int)} wrapped in a try/finally, so both methods MUST NOT throw and MUST stay
* cheap when the feature is disabled.
*/
@ApiStatus.Internal
public final class SentryIpcTracer {

private static final String OP_BINDER = "binder.ipc";
private static final int DISABLED = -1;

private static final AtomicInteger COUNTER = new AtomicInteger();
private static final ConcurrentHashMap<Integer, ISpan> IN_FLIGHT = new ConcurrentHashMap<>();

private SentryIpcTracer() {}

public static int onCallStart(final @NotNull String component, final @NotNull String method) {
try {
final @NotNull IScopes scopes = Sentry.getCurrentScopes();
final @NotNull SentryOptions options = scopes.getOptions();
if (!(options instanceof SentryAndroidOptions)) {
return DISABLED;
}
final SentryAndroidOptions androidOptions = (SentryAndroidOptions) options;
final boolean tracingEnabled = androidOptions.isEnableBinderTracing();
final boolean logsEnabled = androidOptions.isEnableBinderLogs();
if (!tracingEnabled && !logsEnabled) {
return DISABLED;
}

final @NotNull IThreadChecker threadChecker = options.getThreadChecker();
final @NotNull String threadId = String.valueOf(threadChecker.currentThreadSystemId());
final @NotNull String threadName = threadChecker.getCurrentThreadName();

if (logsEnabled) {
final @NotNull SentryAttributes attributes =
SentryAttributes.of(
SentryAttribute.stringAttribute(SpanDataConvention.THREAD_ID, threadId),
SentryAttribute.stringAttribute(SpanDataConvention.THREAD_NAME, threadName));
scopes
.logger()
.log(
SentryLogLevel.INFO,
SentryLogParameters.create(attributes),
"Binder IPC %s.%s",
component,
method);
}

if (tracingEnabled) {
final @Nullable ISpan parent = scopes.getSpan();
if (parent == null) {
return DISABLED;
}
final ISpan child = parent.startChild(OP_BINDER, component + "." + method);
child.setData(SpanDataConvention.THREAD_ID, threadId);
child.setData(SpanDataConvention.THREAD_NAME, threadName);
// keep cookies non-negative so they never collide with the DISABLED sentinel
// even after AtomicInteger overflow past Integer.MAX_VALUE
final int cookie = COUNTER.incrementAndGet() & Integer.MAX_VALUE;
IN_FLIGHT.put(cookie, child);
return cookie;
}
} catch (Throwable ignored) {
// never throw from an instrumented call site
}
return DISABLED;
}

public static void onCallEnd(final int cookie) {
if (cookie == DISABLED) {
return;
}
try {
final @Nullable ISpan span = IN_FLIGHT.remove(cookie);
if (span != null) {
span.finish();
}
} catch (Throwable ignored) {
// never throw from an instrumented call site
}
}

@TestOnly
static void resetForTest() {
IN_FLIGHT.clear();
COUNTER.set(0);
}

@TestOnly
static int inFlightCount() {
return IN_FLIGHT.size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2511,4 +2511,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 reads enableBinderTracing and keeps default if not found`() {
// Arrange
val context = fixture.getContext()

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

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

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

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

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

@Test
fun `applyMetadata reads enableBinderLogs and keeps default if not found`() {
// Arrange
val context = fixture.getContext()

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

// Assert
assertFalse(fixture.options.isEnableBinderLogs)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,22 @@ class SentryAndroidOptionsTest {
sentryOptions.anrProfilingSampleRate = 2.0
}

@Test
fun `binder tracing and logs are disabled by default`() {
val sentryOptions = SentryAndroidOptions()
assertFalse(sentryOptions.isEnableBinderTracing)
assertFalse(sentryOptions.isEnableBinderLogs)
}

@Test
fun `binder tracing and logs can be toggled`() {
val sentryOptions = SentryAndroidOptions()
sentryOptions.isEnableBinderTracing = true
sentryOptions.isEnableBinderLogs = true
assertTrue(sentryOptions.isEnableBinderTracing)
assertTrue(sentryOptions.isEnableBinderLogs)
}

private class CustomDebugImagesLoader : IDebugImagesLoader {
override fun loadDebugImages(): List<DebugImage>? = null

Expand Down
Loading