-
-
Notifications
You must be signed in to change notification settings - Fork 475
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
1ae2b46
e62793f
af3fd91
59717b2
55fc569
407c39d
8551441
d198a04
b24a47e
aaa4154
f236516
2c1d462
b28cadf
d3af8ad
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,8 @@ public enum State { | |
| Ok, | ||
| Exited, | ||
| Crashed, | ||
| Abnormal | ||
| Abnormal, | ||
| Unhandled | ||
| } | ||
|
|
||
| /** started timestamp */ | ||
|
|
@@ -66,6 +67,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 +192,42 @@ 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() { | ||
|
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. do we need to grab the otherwise if this could be set to true before |
||
| return hasNonTerminatingUnhandledError; | ||
| } | ||
|
|
||
| /** | ||
| * Records that an active session experienced an unhandled error which did not terminate the | ||
|
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. should we document that this is only meant to be called from hybrid sdks and never from java/kotlin/android? |
||
| * 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 crash escalated it to | ||
| * {@link State#Crashed} first. | ||
| * | ||
| * @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 +249,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) { | ||
|
|
@@ -262,6 +302,10 @@ public boolean update( | |
| boolean sessionHasBeenUpdated = false; | ||
| if (status != null) { | ||
| this.status = status; | ||
| // a crash terminates the process, so it takes precedence over a non-terminating one. | ||
| if (status == State.Crashed) { | ||
| hasNonTerminatingUnhandledError = false; | ||
| } | ||
| sessionHasBeenUpdated = true; | ||
| } | ||
|
|
||
|
|
@@ -318,21 +362,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 +401,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 +432,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); | ||
| } | ||
| writer.name(JsonKeys.ATTRS); | ||
| writer.beginObject(); | ||
| writer.name(JsonKeys.RELEASE).value(logger, release); | ||
|
|
@@ -440,6 +491,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 +535,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 +600,7 @@ public static final class Deserializer implements JsonDeserializer<Session> { | |
| environment, | ||
| release, | ||
| abnormalMechanism); | ||
| session.hasNonTerminatingUnhandledError = hasNonTerminatingUnhandledError; | ||
| session.setUnknown(unknown); | ||
| reader.endObject(); | ||
| return session; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "sid": "3c1ffc32-f68f-4af2-a1ee-dd72f4d62d17", | ||
| "did": "631693c2-3d61-4a93-8fd1-89817426ba5a", | ||
| "started": "1945-06-16T06:36:49.000Z", | ||
| "status": "unhandled", | ||
| "seq": 9538341000, | ||
| "errors": 9002, | ||
| "duration": 7.84090532E8, | ||
| "timestamp": "1970-04-21T09:32:21.000Z", | ||
| "non_terminating_unhandled_error": true, | ||
| "attrs": | ||
| { | ||
| "release": "b2d0224b-4b1f-49db-94c9-fd4a439b3ef5", | ||
| "environment": "d732be55-b57e-48ec-afe6-b0040c7f93de", | ||
| "ip_address": "5a174e69-a297-4ba4-b6e1-2244a8299ec8", | ||
| "user_agent": "790da4ae-50ca-48a2-98f6-9b7f4e05a8c3" | ||
| } | ||
| } |
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.
should we javadoc this? i know the other ones don't but this feels like it is worht documenting given the surprising behavior