spiegel-keyman/ios/engine/KMEI/KeymanEngine/Classes/Errors/SentryManager.swift
2021-10-04 15:01:36 +11:00

199 lines
6.7 KiB
Swift

//
// SentryManager.swift
// KeymanEngine
//
// Created by Joshua Horton on 3/5/20.
// Copyright © 2020 SIL International. All rights reserved.
//
import Foundation
import XCGLogger
import Sentry
/**
* This class centralizes the methods used among the KeymanEngine library, Keyman app, and SWKeyboard app-ex for Sentry-based
* error reporting.
*/
public class SentryManager {
private static var _started: Bool = false
public static var hasStarted: Bool {
return _started
}
public static func start(sendingEnabled: Bool = true) {
// First things first: enable Sentry for crash reporting.
#if NO_SENTRY
// If doing development debugging (and NOT for Sentry code), silence Sentry reporting.
let allowEnabled = false
log.debug("Sentry error logging disabled for development mode.")
#else
let allowEnabled = true
log.debug("Sentry error logging enabled.")
#endif
let infoDict = Bundle(for: SentryManager.self).infoDictionary
let versionWithTag = infoDict?["KeymanVersionWithTag"] as? String ?? ""
let environment = infoDict?["KeymanVersionEnvironment"] as? String ?? ""
let release = "release-\(versionWithTag)"
let options = Sentry.Options()
options.dsn = "https://d14d2efb594e4345b8367dbb61ebceaf@o1005580.ingest.sentry.io/5983521"
options.enabled = allowEnabled && sendingEnabled
options.environment = environment
options.releaseName = release
options.beforeSend = { event in
// This function is called on _every_ event and crash that may occur, giving us a place to
// capture and filter them.
if SentryManager.shouldSendEventHandler(event: event) {
return event
} else {
return nil
}
}
SentrySDK.start(options: options)
_started = true
}
public static func altEnabled() -> Bool {
let userData = Storage.active.userDefaults
return userData.bool(forKey: Key.optShouldReportErrors)
}
public static var enabled: Bool {
get {
let userData = Storage.active.userDefaults
return userData.bool(forKey: Key.optShouldReportErrors)
}
set(flag) {
let userData = Storage.active.userDefaults
// Save the preference
userData.set(flag, forKey: Key.optShouldReportErrors)
userData.synchronize()
// Ensure that the embedded KeymanWeb engine's crash-reporting state is also updated.
Manager.shared.inputViewController.setSentryState(enabled: flag)
}
}
// Note: this function is called from a separate thread!
// For some reason, does not update when `enabled` is changed unless the app is restarted.
private static func shouldSendEventHandler(event: Sentry.Event) -> Bool {
#if NO_SENTRY
// Prevents Sentry from buffering the event.
return false
#else
return SentryManager.enabled
#endif
}
private static func mapLoggingLevel(_ level: Sentry.SentryLevel) -> XCGLogger.Level {
switch(level) {
case .none:
return XCGLogger.Level.none
case .debug:
return .debug
case .info:
return .info
case .warning:
return .warning
case .error:
return .error
case .fatal:
return .severe
default:
return .info
}
}
/**
* Captures a Sentry event and copies its message to the engine's logging mechanism.
* If the logging level is not specified, the Sentry event's log-level will be used as a default.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
*/
public static func captureAndLog(_ event: Sentry.Event, logLevel: XCGLogger.Level? = nil) {
// Guarded in case a library consumer decides against initializing Sentry.
if _started {
SentrySDK.capture(event: event)
}
let level = logLevel ?? mapLoggingLevel(event.level)
log.logln(event.message?.formatted, level: level)
}
/**
* Captures a Sentry event and copies its message to the engine's logging mechanism.
* If the logging level is not specified, the Sentry event's log-level will be used as a default.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
*/
public static func captureAndLog(_ error: Error, message: String? = nil, sentryLevel: Sentry.SentryLevel = .error, logLevel: XCGLogger.Level? = nil) {
let event = Sentry.Event(error: error)
event.level = sentryLevel
if let message = message {
event.message = SentryMessage(formatted: message)
}
self.captureAndLog(event, logLevel: logLevel)
}
/**
* Constructs a SentryEvent around a message and also passes it to the engine's logging mechanism.
* If the logging level is not specified, it will default to .error.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
*/
public static func captureAndLog(_ message: String, sentryLevel: Sentry.SentryLevel = .error, logLevel: XCGLogger.Level? = nil) {
let event = Sentry.Event(level: sentryLevel)
event.message = SentryMessage(formatted: message)
self.captureAndLog(event, logLevel: logLevel)
}
/**
* Adds a Sentry breadcrumb and copies its message to the engine's logging mechanism.
* If the logging level is not specified, the Sentry event's log-level will be used as a default.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
*/
public static func breadcrumbAndLog(crumb: Sentry.Breadcrumb, logLevel: XCGLogger.Level? = nil) {
// Guarded in case a library consumer decides against initializing Sentry.
if _started {
SentrySDK.addBreadcrumb(crumb: crumb)
}
let level = logLevel ?? mapLoggingLevel(crumb.level)
log.logln(crumb.message, level: level)
}
/**
* Adds a Sentry breadcrumb and copies its message to the engine's logging mechanism.
* If the logging level is not specified, the Sentry event's log-level will be used as a default.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
*/
public static func breadcrumbAndLog(_ message: String, category: String? = nil, sentryLevel: Sentry.SentryLevel = .info, logLevel: XCGLogger.Level? = nil) {
let crumb = Sentry.Breadcrumb()
crumb.level = sentryLevel
if let category = category {
crumb.category = category
}
crumb.message = message
self.breadcrumbAndLog(crumb: crumb, logLevel: logLevel)
}
public static func forceError() {
SentrySDK.addBreadcrumb(crumb: Sentry.Breadcrumb(level: .info, category: "Deliberate testing error"))
SentrySDK.crash()
}
}