From 2f15ed68283acde08566a27290a56352392c934f Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Wed, 21 Jun 2023 13:27:49 +0200 Subject: [PATCH] feat: Add sentry for each step --- MailCore/Cache/MailboxManager.swift | 29 ++++++++++++++++++++-- MailCore/Models/Message.swift | 24 ++++++++++++++----- MailCore/Utils/SentryDebug.swift | 37 ++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/MailCore/Cache/MailboxManager.swift b/MailCore/Cache/MailboxManager.swift index fb86ce826..6a482537d 100644 --- a/MailCore/Cache/MailboxManager.swift +++ b/MailCore/Cache/MailboxManager.swift @@ -266,7 +266,7 @@ public class MailboxManager: ObservableObject { } } - private func deleteMessages(uids: [String], folder: Folder) async { + private func deleteMessages(uids: [String]) async { guard !uids.isEmpty && !Task.isCancelled else { return } await backgroundRealm.execute { realm in @@ -756,9 +756,34 @@ public class MailboxManager: ObservableObject { } private func handleMessagesUids(messageUids: MessagesUids, folder: Folder) async throws { - await deleteMessages(uids: messageUids.deletedUids, folder: folder) + let alreadyWrongIds = folder.fresh(using: getRealm())?.threads + .where { $0.date == SentryDebug.knownDebugDate } + .map { $0.uid } ?? [] + await deleteMessages(uids: messageUids.deletedUids) + var shouldIgnoreNextEvents = SentryDebug.captureWrongDate( + step: "After delete", + folder: folder, + alreadyWrongIds: alreadyWrongIds, + realm: getRealm() + ) await updateMessages(updates: messageUids.updated, folder: folder) + if !shouldIgnoreNextEvents { + shouldIgnoreNextEvents = SentryDebug.captureWrongDate( + step: "After updateMessages", + folder: folder, + alreadyWrongIds: alreadyWrongIds, + realm: getRealm() + ) + } try await addMessages(shortUids: messageUids.addedShortUids, folder: folder, newCursor: messageUids.cursor) + if !shouldIgnoreNextEvents { + _ = SentryDebug.captureWrongDate( + step: "After addMessages", + folder: folder, + alreadyWrongIds: alreadyWrongIds, + realm: getRealm() + ) + } } private func addMessages(shortUids: [String], folder: Folder, newCursor: String?) async throws { diff --git a/MailCore/Models/Message.swift b/MailCore/Models/Message.swift index 8b5d48232..556078e5f 100644 --- a/MailCore/Models/Message.swift +++ b/MailCore/Models/Message.swift @@ -22,7 +22,7 @@ import MailResources import RealmSwift import Sentry -// TODO move to core +// TODO: move to core public extension String { /// Max length of a string before we need to truncate it. static let closeToMaxRealmSize = 14_000_000 @@ -33,7 +33,7 @@ public extension String { var truncatedForRealmIfNeeded: Self { Self.truncatedForRealmIfNeeded(self) } - + /// Truncate a string for compatibility with Realm if needed /// /// The string will be terminated by " [truncated]" if it was @@ -303,14 +303,27 @@ public final class Message: Object, Decodable, Identifiable { public required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) - uid = try values.decode(String.self, forKey: .uid) + let uid = try values.decode(String.self, forKey: .uid) + self.uid = uid if let msgId = try? values.decode(String.self, forKey: .messageId) { messageId = msgId linkedUids = [msgId].toRealmSet() } subject = try values.decodeIfPresent(String.self, forKey: .subject) priority = try values.decode(MessagePriority.self, forKey: .priority) - date = (try? values.decode(Date.self, forKey: .date)) ?? Date() + if let date = (try? values.decode(Date.self, forKey: .date)) { + self.date = date + } else { + // FIXME: Remove after thread date bug fix + date = SentryDebug.knownDebugDate + SentrySDK + .addBreadcrumb(SentryDebug.createBreadcrumb( + level: .warning, + category: "Thread algo", + message: "Nil message date decoded", + data: ["uid": uid] + )) + } size = try values.decode(Int.self, forKey: .size) from = try values.decode(List.self, forKey: .from) to = try values.decode(List.self, forKey: .to) @@ -454,10 +467,9 @@ final class ProxyBody: Codable { /// Generate a new persisted realm object on the fly public func realmObject() -> Body { - // truncate message if needed let truncatedValue = value?.truncatedForRealmIfNeeded - + let body = Body() body.value = truncatedValue body.type = type diff --git a/MailCore/Utils/SentryDebug.swift b/MailCore/Utils/SentryDebug.swift index 6d91699f4..beee39c98 100644 --- a/MailCore/Utils/SentryDebug.swift +++ b/MailCore/Utils/SentryDebug.swift @@ -21,6 +21,7 @@ import RealmSwift import Sentry enum SentryDebug { + static let knownDebugDate = Date(timeIntervalSince1970: 1_893_456_000) static func sendMissingMessagesSentry(sentUids: [String], receivedMessages: [Message], folder: Folder, newCursor: String?) { if receivedMessages.count != sentUids.count { let receivedUids = Set(receivedMessages.map { Constants.shortUid(from: $0.uid) }) @@ -72,7 +73,7 @@ enum SentryDebug { } } } - + static func threadHasNilLastMessageFromFolderDate(thread: Thread) { SentrySDK.capture(message: "Thread has nil lastMessageFromFolderDate") { scope in scope.setContext(value: ["dates": "\(thread.messages.map { $0.date })", @@ -83,4 +84,38 @@ enum SentryDebug { scope.setContext(value: ["date before error": thread.date], key: "thread") } } + + static func createBreadcrumb(level: SentryLevel, + category: String, + message: String, + data: [String: Any]? = nil) -> Breadcrumb { + let crumb = Breadcrumb(level: level, category: category) + crumb.type = level == .info ? "info" : "error" + crumb.message = message + crumb.data = data + return crumb + } + + static func captureWrongDate(step: String, folder: Folder, alreadyWrongIds: [String], realm: Realm) -> Bool { + guard let freshFolder = folder.fresh(using: realm) else { return false } + + let threads = freshFolder.threads.where { $0.date == knownDebugDate }.filter { !alreadyWrongIds.contains($0.uid) } + guard !threads.isEmpty else { return false } + + SentrySDK.capture(message: "Threads with wrong date on step \(step)") { scope in + scope.setLevel(.error) + scope.setContext(value: ["threads": Array(threads).map { + [ + "uid": "\($0.uid)", + "subject": $0.subject ?? "No subject", + "messageIds": "\($0.messageIds.joined(separator: ","))", + "lastMessageFromFolder": $0.lastMessageFromFolder?.uid ?? "nil", + "messages": Array($0.messages) + .map { ["message uid": $0.uid, "message subject": $0.subject ?? "No subject", "message date": $0.date] } + ] + }], + key: "threads") + } + return true + } }