Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- Add Android SDK support for reporting `MemoryLimiter` app exits recovered from `ApplicationExitInfo` ([#6111](https://github.com/getsentry/sentry-java/pull/6111)).
- Sentry can now configure Log4j2 automatically for Spring Boot 4 when `sentry-log4j2` is on the classpath and Log4j2 Core is the active logging backend ([#5403](https://github.com/getsentry/sentry-java/pull/5403))
- Enable automatic appender registration with:
```properties
Expand Down
24 changes: 23 additions & 1 deletion sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,20 @@ public final class io/sentry/android/core/LoadClass : io/sentry/util/LoadClass {
public fun loadClass (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/Class;
}

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

public final class io/sentry/android/core/MemoryLimiterIntegration$MemoryLimiterHint : io/sentry/hints/BlockingFlushHint, io/sentry/hints/Backfillable {
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 ()J
}

public final class io/sentry/android/core/NativeEventCollector {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public fun collect ()V
Expand Down Expand Up @@ -446,7 +460,9 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun isEnableStandaloneAppStartTracing ()Z
public fun isEnableSystemEventBreadcrumbs ()Z
public fun isEnableSystemEventBreadcrumbsExtras ()Z
public fun isMemoryLimiterEnabled ()Z
public fun isReportHistoricalAnrs ()Z
public fun isReportHistoricalMemoryLimiterExits ()Z
public fun isReportHistoricalTombstones ()Z
public fun isTombstoneEnabled ()Z
public fun setAnrEnabled (Z)V
Expand Down Expand Up @@ -480,10 +496,12 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setEnableSystemEventBreadcrumbs (Z)V
public fun setEnableSystemEventBreadcrumbsExtras (Z)V
public fun setFrameMetricsCollector (Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;)V
public fun setMemoryLimiterEnabled (Z)V
public fun setNativeHandlerStrategy (Lio/sentry/android/core/NdkHandlerStrategy;)V
public fun setNativeSdkName (Ljava/lang/String;)V
public fun setNdkAppHangTimeoutIntervalMillis (J)V
public fun setReportHistoricalAnrs (Z)V
public fun setReportHistoricalMemoryLimiterExits (Z)V
public fun setReportHistoricalTombstones (Z)V
public fun setTombstoneEnabled (Z)V
}
Expand Down Expand Up @@ -641,8 +659,8 @@ public class io/sentry/android/core/TombstoneIntegration$TombstonePolicy : io/se
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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

TombstonePolicy is @ApiStatus.Internal.

public fun markReported (J)V
public fun matches (Landroid/app/ApplicationExitInfo;)Z
public fun shouldReportHistorical ()Z
}

Expand Down Expand Up @@ -747,14 +765,18 @@ public final class io/sentry/android/core/anr/StackTraceConverter {
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_MEMORY_LIMITER_MARKER_LABEL Ljava/lang/String;
public static final field LAST_MEMORY_LIMITER_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 lastReportedMemoryLimiter (Lio/sentry/SentryOptions;)Ljava/lang/Long;
public static fun lastReportedTombstone (Lio/sentry/SentryOptions;)Ljava/lang/Long;
public static fun markAnrReported (Lio/sentry/SentryOptions;J)V
public static fun markMemoryLimiterReported (Lio/sentry/SentryOptions;J)V
public static fun markTombstoneReported (Lio/sentry/SentryOptions;J)V
public fun store (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
public fun storeEnvelope (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)Z
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,10 @@ static void installDefaultIntegrations(
options.addIntegration(new TombstoneIntegration(context));
}

if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.CINNAMON_BUN) {
options.addIntegration(new MemoryLimiterIntegration(context, buildInfoProvider));
}

// 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 @@ -118,8 +118,8 @@ private static final class AnrV2Policy
}

@Override
public int getTargetReason() {
return ApplicationExitInfo.REASON_ANR;
public boolean matches(final @NotNull ApplicationExitInfo exitInfo) {
return exitInfo.getReason() == ApplicationExitInfo.REASON_ANR;
Comment thread
0xadam-brown marked this conversation as resolved.
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,8 @@ private void setDist(
timestamp = ((AbnormalExit) hint).timestamp();
} else if (hint instanceof NativeCrashExit) {
timestamp = ((NativeCrashExit) hint).timestamp();
} else if (hint instanceof MemoryLimiterIntegration.MemoryLimiterHint) {
timestamp = ((MemoryLimiterIntegration.MemoryLimiterHint) hint).timestamp();
} else {
timestamp = null;
}
Expand Down Expand Up @@ -800,9 +802,7 @@ private final class AnrHintEnricher implements HintEnricher {

@Override
public boolean supports(@NotNull Object hint) {
// While this is specifically an ANR enricher we discriminate enrichment application
// on the broader AbnormalExit hints for now.
return hint instanceof AbnormalExit;
return hint instanceof AnrV2Integration.AnrV2Hint;

@0xadam-brown 0xadam-brown Sep 14, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Note: AbnormalExit has two implementations: AnrV2Hint and AnrHint. We can safely ignore AnrHint here because ApplicationExitInfoEventProcessor only processes hints that are Backfillable, and AnrHint isn't.

That's a clarity win, too, because AnrHint belongs to the older ANR flow that doesn't make use of ApplicationExitInfo.

}

// by default we assume that the ANR is foreground, unless abnormalMechanism is "anr_background"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Shared startup-time pipeline for reporting process deaths recovered from Android's {@link
* ApplicationExitInfo} history.
*
* <p>This class provides the generic flow used by integrations like ANR, tombstone, and
* MemoryLimiter recovery. That flow involves:
*
* <ul>
* <li>reading the system's historical exit list;
* <li>finding matching records through an {@link ApplicationExitInfoPolicy};
* <li>skipping exits that are too old or have already been reported; and
* <li>capturing synthetic Sentry events for the remaining matches.
* </ul>
*
* <p>The {@code ApplicationExitInfoPolicy} provides the exit-specific rules, while the dispatcher
* owns the lifecycle and bookkeeping around them.
*/
@ApiStatus.Internal
final class ApplicationExitInfoHistoryDispatcher implements Runnable {

Expand Down Expand Up @@ -135,7 +152,7 @@ private void waitPreviousSessionFlush() {
final @NotNull List<ApplicationExitInfo> exitInfos) {
for (Iterator<ApplicationExitInfo> it = exitInfos.iterator(); it.hasNext(); ) {
ApplicationExitInfo applicationExitInfo = it.next();
if (applicationExitInfo.getReason() == policy.getTargetReason()) {
if (policy.matches(applicationExitInfo)) {
it.remove();
return applicationExitInfo;
}
Expand All @@ -149,7 +166,7 @@ private void reportHistorical(
final @Nullable Long lastReportedTimestamp) {
Collections.reverse(exitInfos);
for (ApplicationExitInfo applicationExitInfo : exitInfos) {
if (applicationExitInfo.getReason() == policy.getTargetReason()) {
if (policy.matches(applicationExitInfo)) {
if (applicationExitInfo.getTimestamp() < threshold) {
options
.getLogger()
Expand Down Expand Up @@ -223,20 +240,43 @@ private void report(final @NotNull ApplicationExitInfo exitInfo, final boolean e
}
}

/**
* Exit-specific contract for the shared {@link ApplicationExitInfo} recovery pipeline.
*
* <p>{@link ApplicationExitInfoHistoryDispatcher} owns the generic startup flow for recovering
* past process deaths from Android's historical exit list. An {@code ApplicationExitInfoPolicy}
* provides the rules for a given exit family, including how to recognize matching records,
* whether older matches should also be reported, how deduplication is tracked, and how matching
* record is turned into a synthetic Sentry event.
*/
interface ApplicationExitInfoPolicy {

/** Returns the human-readable label used in dispatcher logs for this exit family. */
@NotNull
String getLabel();

int getTargetReason();
/** Returns {@code true} when the given {@link ApplicationExitInfo} belongs to this policy. */
boolean matches(@NotNull ApplicationExitInfo exitInfo);

/** Returns whether older matching exits should be reported in addition to the latest one. */
boolean shouldReportHistorical();

/** Returns the timestamp of the most recently reported matching exit, if one was recorded. */
@Nullable
Long getLastReportedTimestamp();

/** Records {@code timestamp} as the last reported exit, so it is not reported again. */
/**
* Records {@code timestamp} as the last reported matching exit, so it is not reported again.
*/
void markReported(long timestamp);

/**
* Builds the synthetic report for a matching exit.
*
* <p>{@code enrich} indicates whether the dispatcher is reporting the latest recovered exit,
* which may be backfilled with persisted launch state, or an older historical one, which is
* typically kept leaner.
*/
@Nullable
Report buildReport(@NotNull ApplicationExitInfo exitInfo, boolean enrich);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ final class ManifestMetadataReader {
static final String TOMBSTONE_ENABLE = "io.sentry.tombstone.enable";
static final String TOMBSTONE_ATTACH_RAW = "io.sentry.tombstone.attach-raw";
static final String TOMBSTONE_REPORT_HISTORICAL = "io.sentry.tombstone.report-historical";
static final String MEMORY_LIMITER_ENABLE = "io.sentry.memory-limiter.enable";
static final String MEMORY_LIMITER_REPORT_HISTORICAL =
"io.sentry.memory-limiter.report-historical";

static final String AUTO_INIT = "io.sentry.auto-init";
static final String NDK_ENABLE = "io.sentry.ndk.enable";
Expand Down Expand Up @@ -253,6 +256,14 @@ static void applyMetadata(
logger,
TOMBSTONE_REPORT_HISTORICAL,
options.isReportHistoricalTombstones()));
options.setMemoryLimiterEnabled(
readBool(metadata, logger, MEMORY_LIMITER_ENABLE, options.isMemoryLimiterEnabled()));
options.setReportHistoricalMemoryLimiterExits(
readBool(
metadata,
logger,
MEMORY_LIMITER_REPORT_HISTORICAL,
options.isReportHistoricalMemoryLimiterExits()));

// use enableAutoSessionTracking as fallback
options.setEnableAutoSessionTracking(
Expand Down
Loading
Loading