Files
workavia-mail-front/ios/TwakeMailNSE/Utils/SentryManager.swift
T
dab246 330c8cbaf5 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
2026-04-22 09:00:56 +07:00

103 lines
4.2 KiB
Swift

import Foundation
import Sentry
class SentryManager {
/// Singleton instance for easy access
static let shared = SentryManager()
/// Internal flag to prevent multiple initializations
private var isInitialized: Bool = false
private init() {}
/// Configures Sentry using the config stored in Keychain.
func configure(with keychainController: KeychainController) {
// Prevent re-initialization
if isInitialized { return }
// Retrieve config and validate 'isAvailable' and DSN presence
guard let config = keychainController.retrieveSentryConfig(),
config.isAvailable,
!config.dsn.isEmpty else {
TwakeLogger.shared.log(message: "Sentry is disabled or config is missing")
return
}
// Start Sentry SDK with options mapped from the config
SentrySDK.start { options in
options.dsn = config.dsn
options.environment = config.environment
options.releaseName = config.release
options.dist = config.dist
options.debug = config.isDebug
// Map enableLogs to diagnostic level if needed
options.diagnosticLevel = config.isDebug ? .debug : .none
// Maps 'onErrorSampleRate' (Dart) to 'sampleRate' (iOS).
// tracesSampleRate, profilesSampleRate, sessionSampleRate are intentionally not applied:
// NSE has no UI and its lifecycle is too short for performance/session tracking.
options.sampleRate = NSNumber(value: config.onErrorSampleRate)
// Disable App Hang tracking: NSE execution is short, this causes false positives.
options.enableAppHangTracking = false
// Disable Watchdog tracking: Prevent OOM reports specific to extensions.
options.enableWatchdogTerminationTracking = false
// Disable UI/Interaction tracing: NSE has no UI.
options.enableUserInteractionTracing = false
options.enableAutoPerformanceTracing = false
options.enablePreWarmedAppStartTracing = false
}
isInitialized = true
TwakeLogger.shared.log(message: "Sentry has been successfully initialized.")
}
/// Safely captures an error if Sentry is initialized.
/// - Parameter flushTimeout: If provided, blocks until events are sent or the timeout elapses.
/// Use in critical paths (e.g. serviceExtensionTimeWillExpire) where the process may be
/// suspended before Sentry flushes its queue.
func capture(error: Error, flushTimeout: TimeInterval? = nil) {
guard isInitialized else { return }
SentrySDK.capture(error: error)
if let flushTimeout {
SentrySDK.flush(timeout: flushTimeout)
}
}
/// Safely captures a message if Sentry is initialized.
/// - Parameter flushTimeout: If provided, blocks until events are sent or the timeout elapses.
/// Use in critical paths (e.g. serviceExtensionTimeWillExpire) where the process may be
/// suspended before Sentry flushes its queue.
func capture(message: String, flushTimeout: TimeInterval? = nil) {
guard isInitialized else { return }
SentrySDK.capture(message: message)
if let flushTimeout {
SentrySDK.flush(timeout: flushTimeout)
}
}
/// 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)
}
/// Clear user
func clearUser() {
guard isInitialized else { return }
SentrySDK.setUser(nil)
}
}