-
Notifications
You must be signed in to change notification settings - Fork 13
Crash logging: Add hybrid SDK helpers to allow better stack traces for other platforms #183
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
744dfbd
6bbd7f3
f51c167
e6b3ebf
4bb9e60
ec703a0
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 |
|---|---|---|
|
|
@@ -50,19 +50,27 @@ public class CrashLogging { | |
| return self | ||
| } | ||
|
|
||
| public func shouldSendEvent() -> Bool { | ||
| #if DEBUG | ||
| return UserDefaults.standard.bool(forKey: "force-crash-logging") | ||
| #else | ||
| return !dataProvider.userHasOptedOut | ||
| #endif | ||
| } | ||
|
|
||
| func beforeSend(event: Sentry.Event?) -> Sentry.Event? { | ||
|
|
||
| DDLogDebug("📜 Firing `beforeSend`") | ||
|
|
||
| #if DEBUG | ||
| DDLogDebug("📜 This is a debug build") | ||
| let shouldSendEvent = UserDefaults.standard.bool(forKey: "force-crash-logging") | ||
| #else | ||
| let shouldSendEvent = !dataProvider.userHasOptedOut | ||
| #endif | ||
|
|
||
| /// If we shouldn't send the event we have nothing else to do here | ||
| guard let event = event, shouldSendEvent else { | ||
| if !shouldSendEvent() { | ||
| return nil | ||
| } | ||
| guard let event = event else { | ||
| return nil | ||
| } | ||
|
|
||
|
|
@@ -278,6 +286,104 @@ extension CrashLogging { | |
| } | ||
| } | ||
|
|
||
| // MARK: - Helpers for hybrid SDKs | ||
| extension CrashLogging { | ||
| /// Returns the options required to initialize Sentry in other platforms. | ||
| public func getOptionsDict() -> [String: Any] { | ||
| return [ | ||
| "dsn": self.dataProvider.sentryDSN, | ||
| "environment": self.dataProvider.buildType, | ||
| "releaseName": self.dataProvider.releaseName | ||
| ] | ||
| } | ||
|
Comment on lines
+292
to
+298
Contributor
Author
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. In hybrid SDKs like React Native (used in Gutenberg), we need to initialize the SDK so I exposed the required options to match the same values we use for the native SDK. |
||
|
|
||
| /// Return the current Sentry user. | ||
| /// This helper allows events triggered by other platforms to include the current user. | ||
| public func getSentryUserDict() -> [String: Any]? { | ||
| return dataProvider.currentUser?.sentryUser.serialize() | ||
| } | ||
|
Comment on lines
+302
to
+304
Contributor
Author
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. Similar to |
||
|
|
||
| /// Attachs the current scope to an event and returns it. | ||
| /// This helper allows events triggered by other platforms to include the same scope as if they would be triggered in this platform. | ||
| /// | ||
| /// - Parameters: | ||
| /// - eventDict: The event object | ||
| public func attachScopeToEvent(_ eventDict: [String: Any]) -> [String: Any] { | ||
| let scope = SentrySDK.currentHub().getScope().serialize() | ||
|
|
||
| // Setup tags | ||
| var tags = scope["tags"] as? [String: String] ?? [String: String]() | ||
| tags["locale"] = NSLocale.current.languageCode | ||
|
|
||
| /// Always provide a value in order to determine how often we're unable to retrieve application state | ||
| tags["app.state"] = ApplicationFacade().applicationState ?? "unknown" | ||
|
|
||
| tags["release"] = self.dataProvider.releaseName | ||
|
|
||
| // Assign scope to event | ||
| var eventWithScope = eventDict | ||
| eventWithScope["tags"] = tags | ||
| eventWithScope["breadcrumbs"] = scope["breadcrumbs"] | ||
| eventWithScope["contexts"] = scope["context"] | ||
|
|
||
| return eventWithScope | ||
| } | ||
|
Comment on lines
+311
to
+330
Contributor
Author
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. The scope used in the events generated in hybrid SDKs is different from the one of the native side so I expose this function to incorporate the same data we see in native crashes. This logic serializes the current scope and includes the same extra tags added in the
|
||
|
|
||
| /// Writes the envelope to the Crash Logging system, the envelope contains all the data required for data ingestion in Sentry. | ||
| /// This function is based on the original Sentry implementation for React native: https://github.com/getsentry/sentry-react-native/blob/aa4eb11415cbb73bbd0033e4f0926b539d22315b/ios/RNSentry.m#L118-L158 | ||
| /// | ||
| /// - Parameters: | ||
| /// - envelopeDict: The envelope object. | ||
| public func logEnvelope(_ envelopeDict: [String: Any]) { | ||
| if JSONSerialization.isValidJSONObject(envelopeDict) { | ||
| guard let headerDict = envelopeDict["header"] as? [String: Any] else { | ||
| DDLogError("⛔️ Unable to send envelope to Sentry – header is not defined in the envelope.") | ||
| return | ||
| } | ||
| guard let headerEventId = headerDict["event_id"] as? String else { | ||
| DDLogError("⛔️ Unable to send envelope to Sentry – event id is not defined in the envelope header.") | ||
| return | ||
| } | ||
| guard let payloadDict = envelopeDict["payload"] as? [String: Any] else { | ||
| DDLogError("⛔️ Unable to send envelope to Sentry – payload is not defined in the envelope.") | ||
| return | ||
| } | ||
| guard let eventLevel = payloadDict["level"] as? String else { | ||
| DDLogError("⛔️ Unable to send envelope to Sentry – level is not defined in the envelope payload.") | ||
| return | ||
| } | ||
|
|
||
| // Define the envelope header | ||
| let sdkInfo = SentrySdkInfo.init(dict: headerDict) | ||
| let eventId = SentryId.init(uuidString: headerEventId) | ||
| let envelopeHeader = SentryEnvelopeHeader.init(id: eventId, andSdkInfo: sdkInfo) | ||
|
|
||
| guard let envelopeItemData = try? JSONSerialization.data(withJSONObject: payloadDict) else { | ||
| DDLogError("⛔️ Unable to send envelope to Sentry – payload could not be serialized.") | ||
| return | ||
| } | ||
|
|
||
| let itemType = payloadDict["type"] as? String ?? "event" | ||
| let envelopeItemHeader = SentryEnvelopeItemHeader.init(type: itemType, length: UInt(bitPattern: envelopeItemData.count)) | ||
| let envelopeItem = SentryEnvelopeItem.init(header: envelopeItemHeader, data: envelopeItemData) | ||
| let envelope = SentryEnvelope.init(header: envelopeHeader, singleItem: envelopeItem) | ||
|
|
||
| #if DEBUG | ||
| SentrySDK.currentHub().getClient()?.capture(envelope: envelope) | ||
| #else | ||
| if eventLevel == "fatal" { | ||
| // Storing to disk happens asynchronously with captureEnvelope | ||
| SentrySDK.currentHub().getClient()?.store(envelope) | ||
| } else { | ||
| SentrySDK.currentHub().getClient()?.capture(envelope: envelope) | ||
| } | ||
| #endif | ||
| } else { | ||
| DDLogError("⛔️ Unable to send envelope to Sentry – envelope is not a valid JSON object.") | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+337
to
+385
Contributor
Author
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. Hybrid SDKs send the events as envelopes, it's basically an object that contains all the data required to ingest an event into Sentry. This implementation is based on the original version of the Sentry React native SDK (version |
||
|
|
||
| // MARK: - Event Logging | ||
| extension Event { | ||
|
|
||
|
|
||
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.
Initially, I thought that
beforeSendwould be called in all cases but looks like that for capturing envelopes this is skipped. For this reason, I moved out this logic frombeforeSendand expose it, this way Gutenberg can access it and determine if an event should be sent.