Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ba0c7db
feat: minimal tombstone integration (disabled by default, options int…
supervacuus Nov 25, 2025
9933e47
redo a seemingly missed spotlessApply
supervacuus Nov 26, 2025
10b1e94
use non-plural option + mark integration also as internal + update API
supervacuus Nov 26, 2025
f12b658
Merge branch 'main' into feat/tombstone_integration
supervacuus Nov 26, 2025
4b6e1fb
init size exceptions since we know we only need one
supervacuus Nov 27, 2025
95bd726
move `instructionAddressAdjustment` to `SentryStackTrace`
supervacuus Dec 2, 2025
4f03857
add copyright notice to tombstone.proto
supervacuus Dec 2, 2025
936a2f0
add historical tombstone option
supervacuus Dec 2, 2025
28652e0
looks like we need to add a TombstoneEventProcessor anyway.
supervacuus Dec 2, 2025
920221a
...so let's add it to the options if the SDK supports it (adapt to >=…
supervacuus Dec 2, 2025
48ed5e1
Adapt `AndroidEnvelopeCache` to also write Tombstone timestamp markers
supervacuus Dec 2, 2025
10c7a1f
Integrate handling of historical Tombstone option + last tombstone ma…
supervacuus Dec 2, 2025
6cbfdf8
sprinkle with TODOs to highlight next PR steps
supervacuus Dec 2, 2025
4b1919e
fix typo
supervacuus Dec 2, 2025
d931ddc
implement TombstoneParser as Closable and close the tombstone stream
supervacuus Dec 2, 2025
65bc76b
tighten code with final and null annotations.
supervacuus Dec 2, 2025
25f4089
eliminate obsolete null check
supervacuus Dec 2, 2025
7db5895
Deduplicate ApplicationExitInfo handling for corresponding Integratio…
supervacuus Dec 3, 2025
a8ea230
update tombstone message construction
supervacuus Dec 3, 2025
92cb264
Merge branch 'main' into feat/tombstone_integration
supervacuus Dec 3, 2025
ed81771
fix abortMessage check
supervacuus Dec 3, 2025
480bfd6
Merge branch 'main' into feat/tombstone_integration
supervacuus Dec 3, 2025
c8acdf9
Merge branch 'main' into feat/tombstone_integration
supervacuus Dec 3, 2025
125b0c4
convert AnrV2EventProcessor to a more generic ApplicationExitInfoEven…
supervacuus Dec 3, 2025
7e29fbf
reintroduce, update and correct old inline docs where they make sense.
supervacuus Dec 3, 2025
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 gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ spotless = "7.0.4"
gummyBears = "0.12.0"
camerax = "1.3.0"
openfeature = "1.18.2"
protobuf = "4.33.1"

[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
Expand All @@ -60,6 +61,7 @@ spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.8" }
jacoco-android = { id = "com.mxalbert.gradle.jacoco-android", version = "0.2.0" }
kover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" }
protobuf = { id = "com.google.protobuf", version = "0.9.5" }
vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.30.0" }
springboot2 = { id = "org.springframework.boot", version.ref = "springboot2" }
springboot3 = { id = "org.springframework.boot", version.ref = "springboot3" }
Expand Down Expand Up @@ -138,6 +140,8 @@ otel-javaagent-extension-api = { module = "io.opentelemetry.javaagent:openteleme
otel-semconv = { module = "io.opentelemetry.semconv:opentelemetry-semconv", version.ref = "otelSemanticConventions" }
otel-semconv-incubating = { module = "io.opentelemetry.semconv:opentelemetry-semconv-incubating", version.ref = "otelSemanticConventionsAlpha" }
p6spy = { module = "p6spy:p6spy", version = "3.9.1" }
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@romtsn Not sure if you followed the conversations, but as you can see here, protobuf requires a runtime dependency. It will have an impact of around 10kb. IMHO fine for now, we should still check how stable this library is to avoid and consumer version mismatch issues.

protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
quartz = { module = "org.quartz-scheduler:quartz", version = "2.3.0" }
reactor-core = { module = "io.projectreactor:reactor-core", version = "3.5.3" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
Expand Down
35 changes: 35 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun isEnableSystemEventBreadcrumbs ()Z
public fun isEnableSystemEventBreadcrumbsExtras ()Z
public fun isReportHistoricalAnrs ()Z
public fun isReportHistoricalTombstones ()Z
public fun isTombstoneEnabled ()Z
public fun setAnrEnabled (Z)V
public fun setAnrReportInDebug (Z)V
public fun setAnrTimeoutIntervalMillis (J)V
Expand Down Expand Up @@ -367,6 +369,8 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setNativeHandlerStrategy (Lio/sentry/android/core/NdkHandlerStrategy;)V
public fun setNativeSdkName (Ljava/lang/String;)V
public fun setReportHistoricalAnrs (Z)V
public fun setReportHistoricalTombstones (Z)V
public fun setTombstoneEnabled (Z)V
}

public abstract interface class io/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback {
Expand Down Expand Up @@ -455,6 +459,33 @@ public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : i
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
}

public class io/sentry/android/core/TombstoneEventProcessor : io/sentry/BackfillingEventProcessor {
public fun <init> (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)V
}

public class io/sentry/android/core/TombstoneIntegration : io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/content/Context;)V
public fun close ()V
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/TombstoneIntegration$TombstoneHint : io/sentry/hints/BlockingFlushHint, io/sentry/hints/Backfillable, io/sentry/hints/NativeCrashExit {
public fun <init> (JLio/sentry/ILogger;JZ)V
public fun isFlushable (Lio/sentry/protocol/SentryId;)Z
public fun setFlushable (Lio/sentry/protocol/SentryId;)V
public fun shouldEnrich ()Z
public fun timestamp ()Ljava/lang/Long;
}

public class io/sentry/android/core/TombstoneIntegration$TombstonePolicy : io/sentry/android/core/ApplicationExitInfoHistoryDispatcher$ApplicationExitInfoPolicy {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public fun buildReport (Landroid/app/ApplicationExitInfo;Z)Lio/sentry/android/core/ApplicationExitInfoHistoryDispatcher$Report;
public fun getLabel ()Ljava/lang/String;
public fun getLastReportedTimestamp ()Ljava/lang/Long;
public fun getTargetReason ()I
public fun shouldReportHistorical ()Z
}

public final class io/sentry/android/core/UserInteractionIntegration : android/app/Application$ActivityLifecycleCallbacks, io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/app/Application;Lio/sentry/util/LoadClass;)V
public fun close ()V
Expand All @@ -481,11 +512,15 @@ public final class io/sentry/android/core/ViewHierarchyEventProcessor : io/sentr
}

public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry/cache/EnvelopeCache {
public static final field LAST_ANR_MARKER_LABEL Ljava/lang/String;
public static final field LAST_ANR_REPORT Ljava/lang/String;
public static final field LAST_TOMBSTONE_MARKER_LABEL Ljava/lang/String;
public static final field LAST_TOMBSTONE_REPORT Ljava/lang/String;
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public fun getDirectory ()Ljava/io/File;
public static fun hasStartupCrashMarker (Lio/sentry/SentryOptions;)Z
public static fun lastReportedAnr (Lio/sentry/SentryOptions;)Ljava/lang/Long;
public static fun lastReportedTombstone (Lio/sentry/SentryOptions;)Ljava/lang/Long;
public fun store (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
public fun storeEnvelope (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)Z
}
Expand Down
9 changes: 9 additions & 0 deletions sentry-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {
alias(libs.plugins.jacoco.android)
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.protobuf)
}

android {
Expand Down Expand Up @@ -83,6 +84,7 @@ dependencies {
implementation(libs.androidx.lifecycle.common.java8)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.core)
implementation(libs.protobuf.javalite)

errorprone(libs.errorprone.core)
errorprone(libs.nopen.checker)
Expand All @@ -109,3 +111,10 @@ dependencies {
testRuntimeOnly(libs.androidx.fragment.ktx)
testRuntimeOnly(libs.timber)
}

protobuf {
protoc { artifact = libs.protoc.get().toString() }
generateProtoTasks {
all().forEach { task -> task.builtins { create("java") { option("lite") } } }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Build;
import io.sentry.CompositePerformanceCollector;
import io.sentry.DeduplicateMultithreadedEventProcessor;
import io.sentry.DefaultCompositePerformanceCollector;
Expand Down Expand Up @@ -188,6 +189,9 @@ static void initializeIntegrationsAndProcessors(
options.addEventProcessor(new ScreenshotEventProcessor(options, buildInfoProvider));
options.addEventProcessor(new ViewHierarchyEventProcessor(options));
options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider));
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.S) {
options.addEventProcessor(new TombstoneEventProcessor(context, options, buildInfoProvider));
}
if (options.getTransportGate() instanceof NoOpTransportGate) {
options.setTransportGate(new AndroidTransportGate(options));
}
Expand Down Expand Up @@ -372,6 +376,10 @@ static void installDefaultIntegrations(
final Class<?> sentryNdkClass = loadClass.loadClass(SENTRY_NDK_CLASS_NAME, options.getLogger());
options.addIntegration(new NdkIntegration(sentryNdkClass));

if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.S) {
options.addIntegration(new TombstoneIntegration(context));
}

// this integration uses android.os.FileObserver, we can't move to sentry
// before creating a pure java impl.
options.addIntegration(EnvelopeFileObserverIntegration.getOutboxFileObserver());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ public AnrV2EventProcessor(
return event;
}

// TODO: right now, any event with a Backfillable Hint will get here. This is fine if the
// enrichment code is generically applicable to any Backfilling Event. However some parts
// in the ANRv2EventProcessor are specific to ANRs and currently override the tombstone
// events. Similar to the Integration we should find an abstraction split that allows to
// separate the generic from the specific.

// we always set exception values, platform, os and device even if the ANR is not enrich-able
// even though the OS context may change in the meantime (OS update), we consider this an
// edge-case
Expand Down Expand Up @@ -255,6 +261,7 @@ private void setFingerprints(final @NotNull SentryEvent event, final @NotNull Ob
// sentry does not yet have a capability to provide default server-side fingerprint rules,
// so we're doing this on the SDK side to group background and foreground ANRs separately
// even if they have similar stacktraces
// TODO: this will always set the fingerprint of tombstones to foreground-anr
final boolean isBackgroundAnr = isBackgroundAnr(hint);
if (event.getFingerprints() == null) {
event.setFingerprints(
Expand Down Expand Up @@ -385,6 +392,8 @@ private void setApp(final @NotNull SentryBaseEvent event, final @NotNull Object
// TODO: not entirely correct, because we define background ANRs as not the ones of
// IMPORTANCE_FOREGROUND, but this doesn't mean the app was in foreground when an ANR happened
// but it's our best effort for now. We could serialize AppState in theory.

// TODO: this will always be true of tombstones
app.setInForeground(!isBackgroundAnr(hint));

final PackageInfo packageInfo = ContextUtils.getPackageInfo(context, buildInfoProvider);
Expand Down Expand Up @@ -533,6 +542,9 @@ private void setStaticValues(final @NotNull SentryEvent event) {
private void setPlatform(final @NotNull SentryBaseEvent event) {
if (event.getPlatform() == null) {
// this actually means JVM related.
// TODO: since we write this from the tombstone parser we are current unaffected. It is good
// that it doesn't overwrite previous platform values, however we still rely on the
// order in which each event processor was called.
event.setPlatform(SentryBaseEvent.DEFAULT_PLATFORM);
}
}
Expand Down Expand Up @@ -564,12 +576,16 @@ private void setExceptions(final @NotNull SentryEvent event, final @NotNull Obje
// and make an exception out of its stacktrace
final Mechanism mechanism = new Mechanism();
if (!((Backfillable) hint).shouldEnrich()) {
// TODO: this currently overrides the signalhandler mechanism we set in the TombstoneParser
// with a new type (can be the right choice down the road, but currently is
// unintentional, and it might be better to leave it close to the Native SDK)
// we only enrich the latest ANR in the list, so this is historical
mechanism.setType("HistoricalAppExitInfo");
} else {
mechanism.setType("AppExitInfo");
}

// TODO: this currently overrides the tombstone exceptions
final boolean isBackgroundAnr = isBackgroundAnr(hint);
String message = "ANR";
if (isBackgroundAnr) {
Expand Down
Loading
Loading