spiegel-keyman/ios/engine/KMEI/KeymanEngine/Classes/Errors/SentryManager.swift

156 lines
5 KiB
Swift

//
// SentryManager.swift
// KeymanEngine
//
// Created by Joshua Horton on 3/5/20.
// Copyright © 2020 SIL International. All rights reserved.
//
import Foundation
import Sentry
import os.log
/**
* 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
os_log("Sentry error logging disabled for development mode.", log: KeymanEngineLogger.settings, type: .debug)
#else
let allowEnabled = true
os_log("Sentry error logging enabled.", log: KeymanEngineLogger.settings, type: .debug)
#endif
let infoDict = Bundle(for: SentryManager.self).infoDictionary
let versionWithTag = infoDict?["KeymanVersionWithTag"] as? String ?? ""
let environment = infoDict?["KeymanVersionEnvironment"] as? String ?? ""
let versionGitTag = "release@\(versionWithTag)"
let options = Sentry.Options()
options.dsn = "https://d14d2efb594e4345b8367dbb61ebceaf@o1005580.ingest.sentry.io/5983521"
options.enabled = allowEnabled && sendingEnabled
options.environment = environment
options.releaseName = versionGitTag
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
}
/**
* Captures a Sentry event and safely bypass the Sentry component if not activated by the app.
*/
public static func capture(_ event: Sentry.Event) {
// Guarded in case a library consumer decides against initializing Sentry.
if _started {
SentrySDK.capture(event: event)
}
}
/**
* Captures a Sentry event for the specified error. If the logging level is not specified, it will default to .error.
* Will safely bypass the Sentry component if not activated by the app.
*/
public static func capture(_ error: Error, message: String? = nil, sentryLevel: Sentry.SentryLevel = .error) {
let event = Sentry.Event(error: error)
event.level = sentryLevel
if let message = message {
event.message = SentryMessage(formatted: message)
}
self.capture(event)
}
/**
* Constructs a SentryEvent around a message. If the logging level is not specified, it will default to .error.
* Will safely bypass the Sentry component if not activated by the app.
*/
public static func capture(_ message: String, sentryLevel: Sentry.SentryLevel = .error) {
let event = Sentry.Event(level: sentryLevel)
event.message = SentryMessage(formatted: message)
self.capture(event)
}
/**
* Adds a Sentry breadcrumb. Will safely bypass the Sentry component if not activated by the app.
*/
public static func breadcrumb(crumb: Sentry.Breadcrumb) {
// Guarded in case a library consumer decides against initializing Sentry.
if _started {
SentrySDK.addBreadcrumb(crumb)
}
}
/**
* Adds a Sentry breadcrumb. Will safely bypass the Sentry component if not activated by the app.
*/
public static func breadcrumb(_ message: String, category: String? = nil, sentryLevel: Sentry.SentryLevel = .info) {
let crumb = Sentry.Breadcrumb()
crumb.level = sentryLevel
if let category = category {
crumb.category = category
}
crumb.message = message
self.breadcrumb(crumb: crumb)
}
public static func forceError() {
SentrySDK.addBreadcrumb(Sentry.Breadcrumb(level: .info, category: "Deliberate testing error"))
SentrySDK.crash()
}
}