Skip to content
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

Use Sentry flush to send events immediately #232

Merged
merged 14 commits into from
Nov 9, 2022
Merged

Conversation

mokagio
Copy link
Contributor

@mokagio mokagio commented Oct 17, 2022

This PR removes our custom logic to implement the "send all enqueue events and wait" functionality in favor of using the new Sentry's native flush method.

One big disadvantage of this approach is that we lose the precise callback management, because flush is blocking for as long as it takes to send all events or for a given timeout to run out.

I used the demo app to verify our custom implementation works and the events it sends reach Sentry. In the screenshot below, the top two events have been sent from this branch. Also notice the third and fourth events have "(no value)" for release and environment. That's a bug introduced at some point when migrating to version Sentry version 7. I haven't looked into it yet.

image

I'm of two minds on whether to go for using flush under the hood. I really dislike how flush doesn't provide a callback out of the box, and how it results in a less precise UI state handling on our end. At the same time, relying on Sentry's native code instead of implementing our own logic accessing their private API simplifies and de-risks the library.

I'd like other folks input on this, but I'm tempted to adopt this implementation on the basis that logErrorAndWait, logErrorImmediately, and logErrorsImmediately are used very sparingly by the library's clients. Namely, in one occasion in Jetpack/WordPress and in two occasions in WooCommerce](https://github.com/woocommerce/woocommerce-ios/search?q=logFatalErrorAndExit).

}
try crashLogging.logErrorImmediately(
error,
userInfo: ["custom-userInfo-key": "custom-userInfo-value"]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I realized we never "tested" sending custom information, so I added it. Here's the result:

image

error,
userInfo: ["custom-userInfo-key": "custom-userInfo-value"]
) {
sendErrorAndWaitStatus = .success
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we decide to adopt flush, it might be appropriate to update this enum to remove the error-tracking state, since there's no longer any error information with flush.

@@ -168,54 +171,24 @@ public extension CrashLogging {
}

/// Sends an `Event` to Sentry and triggers a callback on completion
func logErrorImmediately(_ error: Error, userInfo: [String: Any]? = nil, level: SentryLevel = .error, callback: @escaping (Result<Bool, Error>) -> Void) throws {
func logErrorImmediately(_ error: Error, userInfo: [String: Any]? = nil, level: SentryLevel = .error, callback: @escaping () -> Void) throws {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Surprisingly, I'm not getting any errors or warnings from the compiler because of these methods still having throws despite there not being any genuine try call in the chain.

If we commit to flush, we should remove throws as well.

queue.async {
SentrySDK.flush(timeout: CrashLogging.eventsFlushingTimeout)
callback()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@crazytonyli what do you think of this as an implementation of your suggestion to make the call in a background queue?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In regards to your suggestion to add a wait: Bool parameter, are you thinking of something along the lines of WordPress iOS where we use a Bool parameter to decide whether to call context.performAndWait(_:) or context.perform(_:)?

In this, I'm thinking if wait == true we wouldn't run the code in queue.async.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, so that we don't need to use semaphore like the original implementation 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

😕 The new diff makes this conversation odd because the code it referred to has moved. Anyways... @crazytonyli I had an initial go at adding a wait parameter.

I did this in the form of a private func for the other methods to call.

private func logErrorsImmediately(
        _ errors: [Error],
        userInfo: [String: Any]? = nil,
        level: SentryLevel = .error,
        andWait wait: Bool,
        callback: @escaping () -> Void
    ) {

I didn't modify the original method signatures to avoid a breaking change, but also because I wasn't sure how it would have felt to have a andWait:, together with a callback:. If wait == true, then there's no need for a callback because the code that would run in there essentially runs once the method has finished anyway.

mokagio added a commit that referenced this pull request Oct 27, 2022
@mokagio mokagio changed the title Experiment: Use Sentry flush to send events immediately Use Sentry flush to send events immediately Oct 31, 2022
@mokagio mokagio marked this pull request as ready for review October 31, 2022 05:06
Sources/Remote Logging/Crash Logging/CrashLogging.swift Outdated Show resolved Hide resolved
if wait {
flushEventThenCallCallback()
} else {
let queue = DispatchQueue(label: "com.automattic.tracks.flushSentry", qos: .background)
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason why using a custom queue instead of DispatchQueue.global? Also, any reason why choose .background QoS, instead of the default one? (I almost always use the default QoS since I've heard scary things about QoS but don't really understand it 😸 )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I followed the suggestion from one of the Sentry engineers from here.

I trust your advice on this matter, though. I'll update.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

let didSucceed = 200...299 ~= (urlResponse as! HTTPURLResponse).statusCode
callback(.success(didSucceed))
}.resume()
func logErrorsImmediately(_ errors: [Error], userInfo: [String: Any]? = nil, level: SentryLevel = .error, callback: @escaping () -> Void) throws {
Copy link
Contributor

Choose a reason for hiding this comment

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

I had a quick look, it appears we can't get the result of flush call, is that correct? I think it would be good to at least have a "success or not" result, if we can't get a concrete error. But I guess there isn't anything we can do if Sentry doesn't expose such API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct. See the details given here. It's quite the let down. There must be a reason for the lack of callback in their unified API rationale, but to be honest I haven't read that documentation.

Notice that, because `flush` doesn't support callbacks but instead
blocks the main thread up to a given timeout interval, we had to remove
our more refined callback mechanism and UI state management logic.
This code was all custom interaction with the Sentry SDK for the purpose
of extracting information to add to events sent to Sentry "manually" via
HTTP requests. Now that we use Sentry's native method for that, all the
information tracking is handled by the Sentry SDK.
The word "events" made me think it ought to belong as part of
`EventLogging` which was confusing because performance monitoring is
currently implemented via Sentry (crash logging) instead.
…tatus`

We no longer have error handling because of Sentry's `flush`.
None of the code left from the `flush` adoption threw errors.
The change in the `SendErrorAndWaitStatus` `enum` is not mentioned
because the type is `internal`.
@mokagio mokagio merged commit 8744936 into trunk Nov 9, 2022
@mokagio mokagio deleted the mokagio/sentry-flush branch November 9, 2022 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants