-
-
Notifications
You must be signed in to change notification settings - Fork 474
feat(android): [Unhandled Sessions 3] Add internal non-terminating envelope capture #5921
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
buenaflor
wants to merge
27
commits into
feat/unhandled-sessions-cache
from
feat/unhandled-sessions-internal-api
+268
−22
Open
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
e84200f
feat(android): Add InternalSentrySdk.captureEnvelopeNonTerminating
buenaflor 790c521
changelog
buenaflor 8caa61d
ref: follow Session rename in InternalSentrySdk
buenaflor 71f94e3
changelog
buenaflor a7bb89e
ref: drop a redundant comment and stale pending wording
buenaflor aa1f531
ref(android): share one event scan between the two captureEnvelope me…
buenaflor 97adc1b
ref(session): drop the inert ApiStatus.Internal from IWithSession
buenaflor c840fa3
ref(android): rename scanEvents to eventStateOf
buenaflor b01be8a
ref(android): restore catch (Throwable) in captureEnvelope
buenaflor 43d6344
Merge branch 'feat/unhandled-sessions-cache' into feat/unhandled-sess…
buenaflor 841df11
Merge branch 'feat/unhandled-sessions-cache' into feat/unhandled-sess…
buenaflor 86a48ac
Merge branch 'feat/unhandled-sessions-cache' into feat/unhandled-sess…
buenaflor 6e01479
Merge branch 'feat/unhandled-sessions-cache' into feat/unhandled-sess…
buenaflor 6a2a891
Merge branch 'feat/unhandled-sessions-cache' into feat/unhandled-sess…
buenaflor 24da0ef
ref(scope): mark IWithSession as internal
buenaflor f3f34df
ref(scope): Drop Internal from IWithSession
buenaflor 5452526
ref(scope): Keep IWithSession package-private
buenaflor 25922d4
ref(scope): Mark IWithSession public internal like IWithTransaction
buenaflor cbe9cb0
Merge remote-tracking branch 'origin/feat/unhandled-sessions-cache' i…
buenaflor 84ba05a
changelog
buenaflor 350fe77
Merge branch 'feat/unhandled-sessions-cache' into feat/unhandled-sess…
buenaflor c7c5a61
Merge branch 'feat/unhandled-sessions-cache' into feat/unhandled-sess…
buenaflor 668122e
Merge branch 'feat/unhandled-sessions-cache' into feat/unhandled-sess…
buenaflor da288f8
ref(android): Persist the session snapshot outside the scope lock
buenaflor e953d29
Revert "ref(android): Persist the session snapshot outside the scope …
buenaflor c1cbf99
Merge branch 'feat/unhandled-sessions-cache' into feat/unhandled-sess…
buenaflor 960c618
docs(android): Record why the session persist sits inside withSession
buenaflor 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
Some comments aren't visible on the classic Files Changed page.
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,7 @@ | |
| import io.sentry.util.TracingUtils; | ||
| import java.io.ByteArrayInputStream; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
|
|
@@ -153,7 +154,12 @@ public static Map<String, Object> serializeScope( | |
| * - will not perform any sampling: it's up to the caller to take care of this<br> | ||
| * - will enrich the envelope with a Session update if applicable<br> | ||
| * | ||
| * <p>Unhandled events ({@code handled=false}) end the session as {@code crashed}. Prefer {@link | ||
| * #captureEnvelopeNonTerminating(byte[])} for hybrid runtimes where the process is expected to | ||
| * continue (e.g. Flutter). | ||
| * | ||
| * @param envelopeData the serialized envelope data | ||
| * @param maybeStartNewSession if true, starts a new session after a crashed session is cleared | ||
| * @return The Id (SentryId object) of the event, or null in case the envelope could not be | ||
| * captured | ||
| */ | ||
|
|
@@ -163,35 +169,25 @@ public static SentryId captureEnvelope( | |
| final @NotNull IScopes scopes = ScopesAdapter.getInstance(); | ||
| final @NotNull SentryOptions options = scopes.getOptions(); | ||
|
|
||
| try (final InputStream envelopeInputStream = new ByteArrayInputStream(envelopeData)) { | ||
| final @Nullable SentryEnvelope envelope = readEnvelope(options, envelopeData); | ||
| if (envelope == null) { | ||
| return null; | ||
| } | ||
|
|
||
| try { | ||
| final @NotNull ISerializer serializer = options.getSerializer(); | ||
| final @Nullable SentryEnvelope envelope = | ||
| options.getEnvelopeReader().read(envelopeInputStream); | ||
| if (envelope == null) { | ||
| return null; | ||
| } | ||
| final @NotNull EnvelopeEventState eventState = eventStateOf(envelope, serializer); | ||
|
|
||
| final @NotNull List<SentryEnvelopeItem> envelopeItems = new ArrayList<>(); | ||
|
|
||
| // determine session state based on events inside envelope | ||
| @Nullable Session.State status = null; | ||
| boolean crashedOrErrored = false; | ||
| for (SentryEnvelopeItem item : envelope.getItems()) { | ||
| envelopeItems.add(item); | ||
|
|
||
| final SentryEvent event = item.getEvent(serializer); | ||
| if (event != null) { | ||
| if (event.isCrashed()) { | ||
| status = Session.State.Crashed; | ||
| } | ||
| if (event.isCrashed() || event.isErrored()) { | ||
| crashedOrErrored = true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // update session and add it to envelope if necessary | ||
| final @Nullable Session session = updateSession(scopes, options, status, crashedOrErrored); | ||
| final @Nullable Session.State status = | ||
| eventState == EnvelopeEventState.UNHANDLED ? Session.State.Crashed : null; | ||
| final @Nullable Session session = | ||
| updateSession(scopes, options, status, eventState != EnvelopeEventState.NONE); | ||
|
buenaflor marked this conversation as resolved.
|
||
| if (session != null) { | ||
| final SentryEnvelopeItem sessionItem = SentryEnvelopeItem.fromSession(serializer, session); | ||
| envelopeItems.add(sessionItem); | ||
|
|
@@ -213,6 +209,125 @@ public static SentryId captureEnvelope( | |
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Captures the provided envelope for a non-terminating hybrid exception (e.g. Flutter). | ||
| * | ||
| * <p>Compared to {@link #captureEnvelope(byte[], boolean)} this method does <strong>not</strong> | ||
| * treat {@code handled=false} as a crash that ends the session. Instead it: | ||
| * | ||
| * <ul> | ||
| * <li>flags the current session with a non-terminating unhandled error and increments the error | ||
| * count | ||
|
buenaflor marked this conversation as resolved.
|
||
| * <li>keeps session status {@code Ok} and the same session id on the scope | ||
| * <li>does not attach a session update item to this envelope | ||
| * <li>does not start a new session | ||
| * <li>persists the current session so the flag survives process death | ||
| * </ul> | ||
| * | ||
| * <p>The session is finalized later by normal lifecycle ({@code endSession} / background / | ||
| * previous-session recovery) as {@code unhandled}, unless a terminal status takes over first, | ||
| * such as {@code crashed} for a native crash or {@code abnormal} for an ANR. | ||
| * | ||
| * <p>Same as {@link #captureEnvelope(byte[], boolean)}, this method will not enrich events, run | ||
| * {@code beforeSend}, or sample — the caller is responsible for that. | ||
| * | ||
| * @param envelopeData the serialized envelope data | ||
| * @return the id of the captured envelope, or null if capture failed | ||
| */ | ||
| @Nullable | ||
| public static SentryId captureEnvelopeNonTerminating(final @NotNull byte[] envelopeData) { | ||
| final @NotNull IScopes scopes = ScopesAdapter.getInstance(); | ||
| final @NotNull SentryOptions options = scopes.getOptions(); | ||
|
|
||
| final @Nullable SentryEnvelope envelope = readEnvelope(options, envelopeData); | ||
| if (envelope == null) { | ||
| return null; | ||
| } | ||
|
|
||
| try { | ||
| final @NotNull ISerializer serializer = options.getSerializer(); | ||
| final @NotNull EnvelopeEventState eventState = eventStateOf(envelope, serializer); | ||
|
|
||
| if (eventState != EnvelopeEventState.NONE) { | ||
| scopes.configureScope( | ||
| scope -> { | ||
| // the write stays inside the callback so the mutation and the persist are one | ||
| // critical section. Persisting outside it lets a concurrent caller's older snapshot | ||
| // land last and drop the unhandled marker. | ||
| scope.withSession( | ||
| session -> { | ||
| if (session != null) { | ||
| final boolean updated = | ||
| eventState == EnvelopeEventState.UNHANDLED | ||
| ? session.recordNonTerminatingUnhandledError() | ||
| : session.update(null, null, true, null); | ||
| if (updated && options.getEnvelopeDiskCache() instanceof EnvelopeCache) { | ||
| ((EnvelopeCache) options.getEnvelopeDiskCache()) | ||
| .persistCurrentSession(session); | ||
| } | ||
|
buenaflor marked this conversation as resolved.
|
||
| } else { | ||
| options | ||
| .getLogger() | ||
| .log(INFO, "Session is null on captureEnvelopeNonTerminating"); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| return scopes.captureEnvelope(envelope); | ||
| } catch (Exception e) { | ||
| options.getLogger().log(SentryLevel.ERROR, "Failed to capture envelope", e); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** What the events inside an envelope amount to, from the session's point of view. */ | ||
| private enum EnvelopeEventState { | ||
| /** No event carried an exception. */ | ||
| NONE, | ||
| /** At least one event carried an exception, none of them unhandled. */ | ||
| ERRORED, | ||
| /** At least one event carried an unhandled exception. */ | ||
| UNHANDLED | ||
| } | ||
|
|
||
| private static @NotNull EnvelopeEventState eventStateOf( | ||
| final @NotNull SentryEnvelope envelope, final @NotNull ISerializer serializer) | ||
| throws Exception { | ||
| boolean unhandled = false; | ||
| boolean errored = false; | ||
| for (SentryEnvelopeItem item : envelope.getItems()) { | ||
| final SentryEvent event = item.getEvent(serializer); | ||
| if (event != null) { | ||
| if (event.isCrashed()) { | ||
| unhandled = true; | ||
| } | ||
| if (event.isCrashed() || event.isErrored()) { | ||
| errored = true; | ||
| } | ||
| } | ||
| } | ||
| if (unhandled) { | ||
| return EnvelopeEventState.UNHANDLED; | ||
| } | ||
| return errored ? EnvelopeEventState.ERRORED : EnvelopeEventState.NONE; | ||
| } | ||
|
|
||
| /** | ||
| * Reads an envelope from the given bytes. Besides the declared {@link IOException}, {@link | ||
| * io.sentry.IEnvelopeReader#read(InputStream)} also rejects malformed payloads with an unchecked | ||
| * {@link IllegalArgumentException}, hence the broader catch. | ||
| */ | ||
| private static @Nullable SentryEnvelope readEnvelope( | ||
| final @NotNull SentryOptions options, final @NotNull byte[] envelopeData) { | ||
| try (final InputStream envelopeInputStream = new ByteArrayInputStream(envelopeData)) { | ||
| return options.getEnvelopeReader().read(envelopeInputStream); | ||
| } catch (Exception e) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should only catch the exceptions expect to throw here. Looks like we expect |
||
| options.getLogger().log(SentryLevel.ERROR, "Failed to read envelope", e); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| public static Map<String, Object> getAppStartMeasurement() { | ||
| final @NotNull AppStartMetrics metrics = AppStartMetrics.getInstance(); | ||
| final @NotNull List<Map<String, Object>> spans = new ArrayList<>(); | ||
|
|
||
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
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.
Uh oh!
There was an error while loading. Please reload this page.