-
-
Notifications
You must be signed in to change notification settings - Fork 474
feat(core): [Unhandled Sessions 1] Add Unhandled session state and non-terminating error flag #5919
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
Changes from all commits
d9f14eb
a2f6fbe
03159d4
038bb2f
d5d1c94
d6cd366
69172e3
21961dd
54aa13f
cddad30
2cdc4c2
00f8001
e2532bc
575fc68
0503853
6ca1908
f9d6315
769d174
7baac41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,7 +21,13 @@ public enum State { | |
| Ok, | ||
| Exited, | ||
| Crashed, | ||
| Abnormal | ||
| Abnormal, | ||
| /** | ||
| * Final status when an unhandled error did not kill the process, such as a Flutter exception. | ||
| * The session stays {@link #Ok} until {@link Session#end()}. Native crashes still end as {@link | ||
| * #Crashed}. | ||
| */ | ||
| Unhandled | ||
| } | ||
|
|
||
| /** started timestamp */ | ||
|
|
@@ -66,6 +72,9 @@ public enum State { | |
| /** the Abnormal mechanism, e.g. what was the reason for session to become abnormal (ANR) */ | ||
| private @Nullable String abnormalMechanism; | ||
|
|
||
| /** Whether an unhandled error occurred that did not terminate the process */ | ||
| private boolean hasNonTerminatingUnhandledError; | ||
|
|
||
| /** The session lock, ops should be atomic */ | ||
| private final @NotNull AutoClosableReentrantLock sessionLock = new AutoClosableReentrantLock(); | ||
|
|
||
|
|
@@ -188,6 +197,45 @@ public int errorCount() { | |
| return abnormalMechanism; | ||
| } | ||
|
|
||
| /** | ||
| * Whether the session experienced an unhandled error that did <em>not</em> terminate the process, | ||
| * e.g. an unhandled Flutter exception, and so finalizes as {@link State#Unhandled} rather than | ||
| * {@link State#Exited}. A native crash is also unhandled, but it kills the process and ends the | ||
| * session as {@link State#Crashed} instead. | ||
| * | ||
| * <p>Never sent as a status while the session is alive; it is only persisted with the session. | ||
| */ | ||
| @ApiStatus.Internal | ||
| public boolean hasNonTerminatingUnhandledError() { | ||
|
buenaflor marked this conversation as resolved.
|
||
| return hasNonTerminatingUnhandledError; | ||
| } | ||
|
|
||
| /** | ||
| * Records that an active session experienced an unhandled error which did not terminate the | ||
|
buenaflor marked this conversation as resolved.
|
||
| * process, counting the error and advancing the session's sequence without ending it. On {@link | ||
| * #end()} the session is finalized as {@link State#Unhandled} unless a terminal status such as | ||
| * {@link State#Crashed} or {@link State#Abnormal} took over first. | ||
| * | ||
| * <p>Hybrid SDKs whose unhandled errors do not kill the process. Native Java/Android capture | ||
| * should not call this. | ||
| * | ||
| * @return whether the session was updated, i.e. false if it had already reached a terminal state | ||
| */ | ||
| @ApiStatus.Internal | ||
| public boolean recordNonTerminatingUnhandledError() { | ||
| try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { | ||
| if (status != State.Ok) { | ||
| return false; | ||
| } | ||
| hasNonTerminatingUnhandledError = true; | ||
| errorCount.incrementAndGet(); | ||
| init = null; | ||
| timestamp = DateUtils.getCurrentDateTime(); | ||
| sequence = getSequenceTimestamp(timestamp); | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| @SuppressWarnings({"JdkObsolete", "JavaUtilDate"}) | ||
| public @Nullable Date getTimestamp() { | ||
| return timestamp; | ||
|
|
@@ -209,7 +257,7 @@ public void end(final @Nullable Date timestamp) { | |
|
|
||
| // at this state it might be Crashed already, so we don't check for it. | ||
| if (status == State.Ok) { | ||
| status = State.Exited; | ||
| status = hasNonTerminatingUnhandledError ? State.Unhandled : State.Exited; | ||
| } | ||
|
|
||
| if (timestamp != null) { | ||
|
Comment on lines
257
to
263
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. Bug: A persisted session with Suggested FixIn Prompt for AI Agent |
||
|
|
@@ -262,6 +310,11 @@ public boolean update( | |
| boolean sessionHasBeenUpdated = false; | ||
| if (status != null) { | ||
| this.status = status; | ||
| // the flag only decides how an Ok session is finalized, so an explicit terminal status | ||
| // such as a crash or an ANR takes precedence over a non-terminating error. | ||
| if (status != State.Ok) { | ||
| hasNonTerminatingUnhandledError = false; | ||
| } | ||
| sessionHasBeenUpdated = true; | ||
| } | ||
|
|
||
|
sentry[bot] marked this conversation as resolved.
|
||
|
|
@@ -318,21 +371,24 @@ private long getSequenceTimestamp(final @NotNull Date timestamp) { | |
| */ | ||
| @SuppressWarnings("MissingOverride") | ||
| public @NotNull Session clone() { | ||
| return new Session( | ||
| status, | ||
| started, | ||
| timestamp, | ||
| errorCount.get(), | ||
| distinctId, | ||
| sessionId, | ||
| init, | ||
| sequence, | ||
| duration, | ||
| ipAddress, | ||
| userAgent, | ||
| environment, | ||
| release, | ||
| abnormalMechanism); | ||
| final @NotNull Session session = | ||
| new Session( | ||
| status, | ||
| started, | ||
| timestamp, | ||
| errorCount.get(), | ||
| distinctId, | ||
| sessionId, | ||
| init, | ||
| sequence, | ||
| duration, | ||
| ipAddress, | ||
| userAgent, | ||
| environment, | ||
| release, | ||
| abnormalMechanism); | ||
| session.hasNonTerminatingUnhandledError = hasNonTerminatingUnhandledError; | ||
| return session; | ||
| } | ||
|
|
||
| // JsonSerializable | ||
|
|
@@ -354,6 +410,7 @@ public static final class JsonKeys { | |
| public static final String IP_ADDRESS = "ip_address"; | ||
| public static final String USER_AGENT = "user_agent"; | ||
| public static final String ABNORMAL_MECHANISM = "abnormal_mechanism"; | ||
| public static final String NON_TERMINATING_UNHANDLED_ERROR = "non_terminating_unhandled_error"; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -384,6 +441,9 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger | |
| if (abnormalMechanism != null) { | ||
| writer.name(JsonKeys.ABNORMAL_MECHANISM).value(logger, abnormalMechanism); | ||
| } | ||
| if (hasNonTerminatingUnhandledError) { | ||
| writer.name(JsonKeys.NON_TERMINATING_UNHANDLED_ERROR).value(hasNonTerminatingUnhandledError); | ||
| } | ||
|
buenaflor marked this conversation as resolved.
|
||
| writer.name(JsonKeys.ATTRS); | ||
| writer.beginObject(); | ||
| writer.name(JsonKeys.RELEASE).value(logger, release); | ||
|
|
@@ -440,6 +500,7 @@ public static final class Deserializer implements JsonDeserializer<Session> { | |
| String environment = null; | ||
| String release = null; // @NotNull | ||
| String abnormalMechanism = null; | ||
| boolean hasNonTerminatingUnhandledError = false; | ||
|
|
||
| Map<String, Object> unknown = null; | ||
| while (reader.peek() == JsonToken.NAME) { | ||
|
|
@@ -483,6 +544,12 @@ public static final class Deserializer implements JsonDeserializer<Session> { | |
| case JsonKeys.ABNORMAL_MECHANISM: | ||
| abnormalMechanism = reader.nextStringOrNull(); | ||
| break; | ||
| case JsonKeys.NON_TERMINATING_UNHANDLED_ERROR: | ||
| final Boolean hasNonTerminatingUnhandledErrorValue = reader.nextBooleanOrNull(); | ||
| hasNonTerminatingUnhandledError = | ||
| hasNonTerminatingUnhandledErrorValue != null | ||
| && hasNonTerminatingUnhandledErrorValue; | ||
| break; | ||
| case JsonKeys.ATTRS: | ||
| reader.beginObject(); | ||
| while (reader.peek() == JsonToken.NAME) { | ||
|
|
@@ -542,6 +609,7 @@ public static final class Deserializer implements JsonDeserializer<Session> { | |
| environment, | ||
| release, | ||
| abnormalMechanism); | ||
| session.hasNonTerminatingUnhandledError = hasNonTerminatingUnhandledError; | ||
| session.setUnknown(unknown); | ||
| reader.endObject(); | ||
| return session; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.