mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-13 04:09:25 +00:00
199 lines
6.7 KiB
Swift
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()
|
|
}
|
|
}
|