-
-
Notifications
You must be signed in to change notification settings - Fork 463
feat: minimal tombstone integration #4933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
supervacuus
wants to merge
29
commits into
getsentry:main
Choose a base branch
from
supervacuus:feat/tombstone_integration
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
ba0c7db
feat: minimal tombstone integration (disabled by default, options int…
supervacuus 9933e47
redo a seemingly missed spotlessApply
supervacuus 10b1e94
use non-plural option + mark integration also as internal + update API
supervacuus f12b658
Merge branch 'main' into feat/tombstone_integration
supervacuus 4b6e1fb
init size exceptions since we know we only need one
supervacuus 95bd726
move `instructionAddressAdjustment` to `SentryStackTrace`
supervacuus 4f03857
add copyright notice to tombstone.proto
supervacuus 936a2f0
add historical tombstone option
supervacuus 28652e0
looks like we need to add a TombstoneEventProcessor anyway.
supervacuus 920221a
...so let's add it to the options if the SDK supports it (adapt to >=…
supervacuus 48ed5e1
Adapt `AndroidEnvelopeCache` to also write Tombstone timestamp markers
supervacuus 10c7a1f
Integrate handling of historical Tombstone option + last tombstone ma…
supervacuus 6cbfdf8
sprinkle with TODOs to highlight next PR steps
supervacuus 4b1919e
fix typo
supervacuus d931ddc
implement TombstoneParser as Closable and close the tombstone stream
supervacuus 65bc76b
tighten code with final and null annotations.
supervacuus 25f4089
eliminate obsolete null check
supervacuus 7db5895
Deduplicate ApplicationExitInfo handling for corresponding Integratio…
supervacuus a8ea230
update tombstone message construction
supervacuus 92cb264
Merge branch 'main' into feat/tombstone_integration
supervacuus ed81771
fix abortMessage check
supervacuus 480bfd6
Merge branch 'main' into feat/tombstone_integration
supervacuus c8acdf9
Merge branch 'main' into feat/tombstone_integration
supervacuus 125b0c4
convert AnrV2EventProcessor to a more generic ApplicationExitInfoEven…
supervacuus 7e29fbf
reintroduce, update and correct old inline docs where they make sense.
supervacuus 605a840
remove obsolete TombstoneEventProcessor
supervacuus 76ef13c
Merge branch 'main' into feat/tombstone_integration
supervacuus 7f6dbc9
clean up tombstone error handling
supervacuus 58a342f
convert ApplicationExitInfoHistoryDispatcher.removeLatest() to use an…
supervacuus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
197 changes: 197 additions & 0 deletions
197
sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| package io.sentry.android.core; | ||
|
|
||
| import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; | ||
|
|
||
| import android.app.ActivityManager; | ||
| import android.app.ApplicationExitInfo; | ||
| import android.content.Context; | ||
| import android.os.Build; | ||
|
|
||
| import androidx.annotation.RequiresApi; | ||
|
|
||
| import io.sentry.DateUtils; | ||
| import io.sentry.IScopes; | ||
| import io.sentry.Integration; | ||
| import io.sentry.SentryEvent; | ||
| import io.sentry.SentryLevel; | ||
| import io.sentry.SentryOptions; | ||
| import io.sentry.android.core.internal.tombstone.TombstoneParser; | ||
| import io.sentry.cache.EnvelopeCache; | ||
| import io.sentry.cache.IEnvelopeCache; | ||
| import io.sentry.transport.CurrentDateProvider; | ||
| import io.sentry.transport.ICurrentDateProvider; | ||
| import io.sentry.util.Objects; | ||
|
|
||
| import java.io.Closeable; | ||
| import java.io.IOException; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| import org.jetbrains.annotations.NotNull; | ||
| import org.jetbrains.annotations.Nullable; | ||
|
|
||
| public class TombstoneIntegration implements Integration, Closeable { | ||
| static final long NINETY_DAYS_THRESHOLD = TimeUnit.DAYS.toMillis(91); | ||
|
|
||
| private final @NotNull Context context; | ||
| private final @NotNull ICurrentDateProvider dateProvider; | ||
| private @Nullable SentryAndroidOptions options; | ||
|
|
||
| public TombstoneIntegration(final @NotNull Context context) { | ||
| // using CurrentDateProvider instead of AndroidCurrentDateProvider as AppExitInfo uses | ||
| // System.currentTimeMillis | ||
| this(context, CurrentDateProvider.getInstance()); | ||
| } | ||
|
|
||
| TombstoneIntegration( | ||
| final @NotNull Context context, final @NotNull ICurrentDateProvider dateProvider) { | ||
| this.context = ContextUtils.getApplicationContext(context); | ||
| this.dateProvider = dateProvider; | ||
| } | ||
|
|
||
| @Override | ||
| public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) { | ||
| this.options = | ||
| Objects.requireNonNull( | ||
| (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, | ||
| "SentryAndroidOptions is required"); | ||
|
|
||
| this.options | ||
| .getLogger() | ||
| .log(SentryLevel.DEBUG, "TombstoneIntegration enabled: %s", this.options.isTombstonesEnabled()); | ||
|
|
||
| if (this.options.isTombstonesEnabled()) { | ||
| if (this.options.getCacheDirPath() == null) { | ||
| this.options | ||
| .getLogger() | ||
| .log(SentryLevel.INFO, "Cache dir is not set, unable to process Tombstones"); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| options | ||
| .getExecutorService() | ||
| .submit( | ||
| new TombstoneProcessor( | ||
| context, scopes, this.options, dateProvider)); | ||
| } catch (Throwable e) { | ||
| options.getLogger().log(SentryLevel.DEBUG, "Failed to start TombstoneProcessor.", e); | ||
| } | ||
| options.getLogger().log(SentryLevel.DEBUG, "TombstoneIntegration installed."); | ||
| addIntegrationToSdkVersion("Tombstone"); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void close() throws IOException { | ||
| if (options != null) { | ||
| options.getLogger().log(SentryLevel.DEBUG, "TombstoneIntegration removed."); | ||
| } | ||
| } | ||
|
|
||
| public static class TombstoneProcessor implements Runnable { | ||
|
|
||
| @NotNull | ||
| private final Context context; | ||
| @NotNull | ||
| private final IScopes scopes; | ||
| @NotNull | ||
| private final SentryAndroidOptions options; | ||
| private final long threshold; | ||
|
|
||
| public TombstoneProcessor( | ||
| @NotNull Context context, | ||
| @NotNull IScopes scopes, | ||
| @NotNull SentryAndroidOptions options, | ||
| @NotNull ICurrentDateProvider dateProvider) { | ||
| this.context = context; | ||
| this.scopes = scopes; | ||
| this.options = options; | ||
|
|
||
| this.threshold = dateProvider.getCurrentTimeMillis() - NINETY_DAYS_THRESHOLD; | ||
| } | ||
|
|
||
| @Override | ||
| @RequiresApi(api = Build.VERSION_CODES.R) | ||
| public void run() { | ||
| final ActivityManager activityManager = | ||
| (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); | ||
|
|
||
| final List<ApplicationExitInfo> applicationExitInfoList; | ||
| applicationExitInfoList = activityManager.getHistoricalProcessExitReasons(null, 0, 0); | ||
|
|
||
| if (applicationExitInfoList.isEmpty()) { | ||
| options.getLogger().log(SentryLevel.DEBUG, "No records in historical exit reasons."); | ||
| return; | ||
| } | ||
|
|
||
| final IEnvelopeCache cache = options.getEnvelopeDiskCache(); | ||
| if (cache instanceof EnvelopeCache) { | ||
| if (options.isEnableAutoSessionTracking() | ||
| && !((EnvelopeCache) cache).waitPreviousSessionFlush()) { | ||
| options | ||
| .getLogger() | ||
| .log( | ||
| SentryLevel.WARNING, | ||
| "Timed out waiting to flush previous session to its own file."); | ||
|
|
||
| // if we timed out waiting here, we can already flush the latch, because the timeout is | ||
| // big | ||
| // enough to wait for it only once and we don't have to wait again in | ||
| // PreviousSessionFinalizer | ||
| ((EnvelopeCache) cache).flushPreviousSession(); | ||
| } | ||
| } | ||
supervacuus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // making a deep copy as we're modifying the list | ||
| final List<ApplicationExitInfo> exitInfos = new ArrayList<>(applicationExitInfoList); | ||
|
|
||
| // search for the latest Tombstone to report it separately as we're gonna enrich it. The | ||
| // latest | ||
| // Tombstone will be first in the list, as it's filled last-to-first in order of appearance | ||
| ApplicationExitInfo latestTombstone = null; | ||
| for (ApplicationExitInfo applicationExitInfo : exitInfos) { | ||
| if (applicationExitInfo.getReason() == ApplicationExitInfo.REASON_CRASH_NATIVE) { | ||
| latestTombstone = applicationExitInfo; | ||
| // remove it, so it's not reported twice | ||
| // TODO: if we fail after this, we effectively lost the ApplicationExitInfo (maybe only remove after we reported it) | ||
| exitInfos.remove(applicationExitInfo); | ||
supervacuus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (latestTombstone == null) { | ||
| options | ||
| .getLogger() | ||
| .log( | ||
| SentryLevel.DEBUG, | ||
| "No Tombstones have been found in the historical exit reasons list."); | ||
| return; | ||
| } | ||
|
|
||
| if (latestTombstone.getTimestamp() < threshold) { | ||
| options | ||
| .getLogger() | ||
| .log(SentryLevel.DEBUG, "Latest Tombstones happened too long ago, returning early."); | ||
| return; | ||
| } | ||
|
|
||
| reportAsSentryEvent(latestTombstone); | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| @RequiresApi(api = Build.VERSION_CODES.R) | ||
| private void reportAsSentryEvent(ApplicationExitInfo exitInfo) { | ||
| SentryEvent event; | ||
| try { | ||
| TombstoneParser parser = new TombstoneParser(exitInfo.getTraceInputStream()); | ||
supervacuus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| event = parser.parse(); | ||
supervacuus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| event.setTimestamp(DateUtils.getDateTime(exitInfo.getTimestamp())); | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| scopes.captureEvent(event); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.