TF-4269 Apply PR review: breadcrumbs, log error details, and code cleanup for NSE Sentry integration

- Replace capture(message:) with addBreadcrumb(level: .warning) in NotificationService for non-error diagnostic checkpoints
- Add addBreadcrumb method to SentryManager with category/level support
- Include error details in KeychainController SentryConfig decode failure log
- Split combined guard into two separate guards to distinguish nil state vs unchanged state
- Remove dead do-catch block in onComplete closure; use errors.forEach to capture real errors
- Translate Vietnamese doc comment to English in SentryManager
This commit is contained in:
dab246
2026-04-22 09:00:56 +07:00
parent f335871513
commit 330c8cbaf5
4 changed files with 49 additions and 34 deletions
+3 -1
View File
@@ -178,8 +178,10 @@ jobs:
# 4. Upload dSYMs (Native code: Swift, Objective-C, C++)
DSYM_DIR="$XCARCHIVE/dSYMs"
if [ -d "$DSYM_DIR" ]; then
echo "dSYM contents:"
ls -la "$DSYM_DIR"
echo "Uploading dSYMs from $DSYM_DIR..."
sentry-cli debug-files upload --include-sources "$DSYM_DIR"
sentry-cli debug-files upload --include-sources --log-level info "$DSYM_DIR"
else
echo "::error::dSYMs directory not found inside xcarchive at $DSYM_DIR."
exit 1
@@ -71,7 +71,7 @@ extension KeychainController {
}
return try JSONDecoder().decode(SentryConfig.self, from: configData)
} catch {
TwakeLogger.shared.log(message: "SentryConfig could not be decoded from Keychain")
TwakeLogger.shared.log(message: "SentryConfig could not be decoded from Keychain: \(error)")
return nil
}
}
+31 -31
View File
@@ -32,7 +32,7 @@ class NotificationService: UNNotificationServiceExtension {
guard let payloadData = request.content.userInfo as? [String: Any],
!keychainController.retrieveSharingSessions().isEmpty else {
SentryManager.shared.capture(message: "NSE: Payload invalid or No Session found in Keychain")
SentryManager.shared.addBreadcrumb(message: "NSE: Payload invalid or No Session found in Keychain", level: .warning)
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
return self.notify()
}
@@ -63,29 +63,34 @@ class NotificationService: UNNotificationServiceExtension {
let mapStateChanges: [String: [TypeName: String]] = PayloadParser.shared.parsingPayloadNotification(payloadData: payloadData)
if (mapStateChanges.isEmpty) {
SentryManager.shared.capture(message: "NSE: Payload parsing returned empty state changes")
SentryManager.shared.addBreadcrumb(message: "NSE: Payload parsing returned empty state changes", level: .warning)
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
return self.notify()
} else {
guard let currentAccountId = mapStateChanges.keys.first,
let keychainSharingSession = keychainController.retrieveSharingSessionFromKeychain(accountId: currentAccountId),
keychainSharingSession.tokenOIDC != nil || keychainSharingSession.basicAuth != nil else {
SentryManager.shared.capture(message: "NSE: Session missing or invalid credential for account: \(mapStateChanges.keys.first ?? "unknown")")
SentryManager.shared.addBreadcrumb(message: "NSE: Session missing or invalid credential for account: \(mapStateChanges.keys.first ?? "unknown")", level: .warning)
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
return self.notify()
}
SentryManager.shared.setSentryUser(keychainSharingSession.sentryUser)
guard let listStateOfAccount = mapStateChanges[currentAccountId],
let newEmailDeliveryState = listStateOfAccount[TypeName.emailDelivery] else {
SentryManager.shared.capture(message: "NSE: Missing emailDelivery state in payload")
SentryManager.shared.addBreadcrumb(message: "NSE: Missing emailDelivery state in payload", level: .warning)
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
return self.notify()
}
guard let oldEmailDeliveryState = keychainSharingSession.emailDeliveryState ?? keychainSharingSession.emailState,
newEmailDeliveryState != oldEmailDeliveryState else {
guard let oldEmailDeliveryState = keychainSharingSession.emailDeliveryState ?? keychainSharingSession.emailState else {
SentryManager.shared.addBreadcrumb(message: "NSE: No stored email state for account: \(currentAccountId)", level: .warning)
self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable"))
return self.notify()
}
guard newEmailDeliveryState != oldEmailDeliveryState else {
self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable"))
return self.notify()
}
@@ -101,33 +106,28 @@ class NotificationService: UNNotificationServiceExtension {
oidcScopes: keychainSharingSession.oidcScopes,
isTWP: keychainSharingSession.isTWP,
onComplete: { (emails, errors) in
do {
if emails.isEmpty {
self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable"))
return self.notify()
} else {
self.keychainController.updateEmailDeliveryStateToKeychain(
accountId: keychainSharingSession.accountId,
newEmailDeliveryState: newEmailDeliveryState
)
errors.forEach { SentryManager.shared.capture(error: $0) }
let mailboxIdsBlockNotification = keychainSharingSession.mailboxIdsBlockNotification ?? []
if (mailboxIdsBlockNotification.isEmpty) {
return self.showListNotification(emails: emails)
} else {
let emailFiltered = self.filterEmailsToPushNotification(
emails: emails,
mailboxIdsBlockNotification: mailboxIdsBlockNotification)
return self.showListNotification(emails: emailFiltered)
}
}
} catch {
TwakeLogger.shared.log(message: "Error processing emails: \(error)")
SentryManager.shared.capture(error: error)
if emails.isEmpty {
self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable"))
return self.notify()
}
self.keychainController.updateEmailDeliveryStateToKeychain(
accountId: keychainSharingSession.accountId,
newEmailDeliveryState: newEmailDeliveryState
)
let mailboxIdsBlockNotification = keychainSharingSession.mailboxIdsBlockNotification ?? []
if mailboxIdsBlockNotification.isEmpty {
return self.showListNotification(emails: emails)
} else {
let emailFiltered = self.filterEmailsToPushNotification(
emails: emails,
mailboxIdsBlockNotification: mailboxIdsBlockNotification)
return self.showListNotification(emails: emailFiltered)
}
}
)
}
+14 -1
View File
@@ -75,7 +75,20 @@ class SentryManager {
}
}
/// Set user context cho Sentry
/// Adds a breadcrumb to the current Sentry scope.
/// Breadcrumbs are accumulated in-memory and automatically attached to the next
/// captured error event. Use this for diagnostic checkpoints that provide context
/// for real errors they are NOT sent as standalone events.
func addBreadcrumb(message: String, category: String = "nse", level: SentryLevel = .info) {
guard isInitialized else { return }
let crumb = Breadcrumb()
crumb.message = message
crumb.category = category
crumb.level = level
SentrySDK.addBreadcrumb(crumb)
}
/// Set user context for Sentry
func setSentryUser(_ user: User) {
guard isInitialized else { return }
SentrySDK.setUser(user)