Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
ControlCenter
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
import Sentry
public enum SentryTag: String {
case swiftData = "SwiftData"
case browserDB = "BrowserDB"
case notificationService = "NotificationService"
case unifiedTelemetry = "UnifiedTelemetry"
case general = "General"
case tabManager = "TabManager"
case bookmarks = "Bookmarks"
}
public class Sentry {
public static let shared = Sentry()
public static var crashedLastLaunch: Bool {
return Client.shared?.crashedLastLaunch() ?? false
}
private let SentryDSNKey = "SentryDSN"
private let SentryDeviceAppHashKey = "SentryDeviceAppHash"
private let DefaultDeviceAppHash = "0000000000000000000000000000000000000000"
private let DeviceAppHashLength = UInt(20)
private var enabled = false
private var attributes: [String: Any] = [:]
public func setup(sendUsageData: Bool) {
assert(!enabled, "Sentry.setup() should only be called once")
if DeviceInfo.isSimulator() {
Logger.browserLogger.debug("Not enabling Sentry; Running in Simulator")
return
}
if !sendUsageData {
Logger.browserLogger.debug("Not enabling Sentry; Not enabled by user choice")
return
}
guard let dsn = Bundle.main.object(forInfoDictionaryKey: SentryDSNKey) as? String, !dsn.isEmpty else {
Logger.browserLogger.debug("Not enabling Sentry; Not configured in Info.plist")
return
}
Logger.browserLogger.debug("Enabling Sentry crash handler")
do {
Client.shared = try Client(dsn: dsn)
try Client.shared?.startCrashHandler()
enabled = true
// If we have not already for this install, generate a completely random identifier
// for this device. It is stored in the app group so that the same value will
// be used for both the main application and the app extensions.
if let defaults = UserDefaults(suiteName: AppInfo.sharedContainerIdentifier), defaults.string(forKey: SentryDeviceAppHashKey) == nil {
defaults.set(Bytes.generateRandomBytes(DeviceAppHashLength).hexEncodedString, forKey: SentryDeviceAppHashKey)
defaults.synchronize()
}
// For all outgoing reports, override the default device identifier with our own random
// version. Default to a blank (zero) identifier in case of errors.
Client.shared?.beforeSerializeEvent = { event in
let deviceAppHash = UserDefaults(suiteName: AppInfo.sharedContainerIdentifier)?.string(forKey: self.SentryDeviceAppHashKey)
event.context?.appContext?["device_app_hash"] = deviceAppHash ?? self.DefaultDeviceAppHash
var attributes = event.extra ?? [:]
attributes.merge(with: self.attributes)
event.extra = attributes
}
} catch let error {
Logger.browserLogger.error("Failed to initialize Sentry: \(error)")
}
}
public func crash() {
Client.shared?.crash()
}
/*
This is the behaviour we want for Sentry logging
.info .error .severe
Debug y y y
Beta y y y
Relase n n y
*/
private func shouldNotSendEventFor(_ severity: SentrySeverity) -> Bool {
return !enabled || (AppConstants.BuildChannel == .release && severity != .fatal)
}
private func makeEvent(message: String, tag: String, severity: SentrySeverity, extra: [String: Any]?) -> Event {
let event = Event(level: severity)
event.message = message
event.tags = ["tag": tag]
if let extra = extra {
event.extra = extra
}
return event
}
public func send(message: String, tag: SentryTag = .general, severity: SentrySeverity = .info, extra: [String: Any]? = nil, description: String? = nil, completion: SentryRequestFinished? = nil) {
// Build the dictionary
var extraEvents: [String: Any] = [:]
if let paramEvents = extra {
extraEvents.merge(with: paramEvents)
}
if let extraString = description {
extraEvents.merge(with: ["errorDescription": extraString])
}
printMessage(message: message, extra: extraEvents)
// Only report fatal errors on release
if shouldNotSendEventFor(severity) {
completion?(nil)
return
}
let event = makeEvent(message: message, tag: tag.rawValue, severity: severity, extra: extraEvents)
Client.shared?.send(event: event, completion: completion)
}
public func sendWithStacktrace(message: String, tag: SentryTag = .general, severity: SentrySeverity = .info, extra: [String: Any]? = nil, description: String? = nil, completion: SentryRequestFinished? = nil) {
var extraEvents: [String: Any] = [:]
if let paramEvents = extra {
extraEvents.merge(with: paramEvents)
}
if let extraString = description {
extraEvents.merge(with: ["errorDescription": extraString])
}
printMessage(message: message, extra: extraEvents)
// Do not send messages to Sentry if disabled OR if we are not on beta and the severity isnt severe
if shouldNotSendEventFor(severity) {
completion?(nil)
return
}
Client.shared?.snapshotStacktrace {
let event = self.makeEvent(message: message, tag: tag.rawValue, severity: severity, extra: extraEvents)
Client.shared?.appendStacktrace(to: event)
event.debugMeta = nil
Client.shared?.send(event: event, completion: completion)
}
}
public func addAttributes(_ attributes: [String: Any]) {
self.attributes.merge(with: attributes)
}
private func printMessage(message: String, extra: [String: Any]? = nil) {
let string = extra?.reduce("") { (result: String, arg1) in
let (key, value) = arg1
return "\(result), \(key): \(value)"
}
Logger.browserLogger.debug("Sentry: \(message) \(string ??? "")")
}
}