mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-11 23:33:36 -06:00
[PM-19577] Log ErrorReporter errors to the flight recorder (#1547)
This commit is contained in:
parent
129973cad5
commit
c6993cd212
@ -1,4 +1,5 @@
|
|||||||
import AuthenticatorShared
|
import AuthenticatorShared
|
||||||
|
import BitwardenKit
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/// A protocol for an `AppDelegate` that can be used by the `SceneDelegate` to look up the
|
/// A protocol for an `AppDelegate` that can be used by the `SceneDelegate` to look up the
|
||||||
|
|||||||
@ -6,6 +6,11 @@ import FirebaseCrashlytics
|
|||||||
/// An `ErrorReporter` that logs non-fatal errors to Crashlytics for investigation.
|
/// An `ErrorReporter` that logs non-fatal errors to Crashlytics for investigation.
|
||||||
///
|
///
|
||||||
final class CrashlyticsErrorReporter: ErrorReporter {
|
final class CrashlyticsErrorReporter: ErrorReporter {
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
/// A list of additional loggers that errors will be logged to.
|
||||||
|
private var additionalLoggers: [any BitwardenLogger] = []
|
||||||
|
|
||||||
// MARK: ErrorReporter Properties
|
// MARK: ErrorReporter Properties
|
||||||
|
|
||||||
var isEnabled: Bool {
|
var isEnabled: Bool {
|
||||||
@ -25,7 +30,16 @@ final class CrashlyticsErrorReporter: ErrorReporter {
|
|||||||
|
|
||||||
// MARK: ErrorReporter
|
// MARK: ErrorReporter
|
||||||
|
|
||||||
|
public func add(logger: any BitwardenLogger) {
|
||||||
|
additionalLoggers.append(logger)
|
||||||
|
}
|
||||||
|
|
||||||
func log(error: Error) {
|
func log(error: Error) {
|
||||||
|
let callStack = Thread.callStackSymbols.joined(separator: "\n")
|
||||||
|
for logger in additionalLoggers {
|
||||||
|
logger.log("Error: \(error)\n\(callStack)")
|
||||||
|
}
|
||||||
|
|
||||||
// Don't log networking related errors to Crashlytics.
|
// Don't log networking related errors to Crashlytics.
|
||||||
guard !error.isNetworkingError else { return }
|
guard !error.isNetworkingError else { return }
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import BitwardenKit
|
||||||
import BitwardenShared
|
import BitwardenShared
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,11 @@ import FirebaseCrashlytics
|
|||||||
/// An `ErrorReporter` that logs non-fatal errors to Crashlytics for investigation.
|
/// An `ErrorReporter` that logs non-fatal errors to Crashlytics for investigation.
|
||||||
///
|
///
|
||||||
final class CrashlyticsErrorReporter: ErrorReporter {
|
final class CrashlyticsErrorReporter: ErrorReporter {
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
/// A list of additional loggers that errors will be logged to.
|
||||||
|
private var additionalLoggers: [any BitwardenLogger] = []
|
||||||
|
|
||||||
// MARK: ErrorReporter Properties
|
// MARK: ErrorReporter Properties
|
||||||
|
|
||||||
var isEnabled: Bool {
|
var isEnabled: Bool {
|
||||||
@ -25,7 +30,16 @@ final class CrashlyticsErrorReporter: ErrorReporter {
|
|||||||
|
|
||||||
// MARK: ErrorReporter
|
// MARK: ErrorReporter
|
||||||
|
|
||||||
|
public func add(logger: any BitwardenLogger) {
|
||||||
|
additionalLoggers.append(logger)
|
||||||
|
}
|
||||||
|
|
||||||
func log(error: Error) {
|
func log(error: Error) {
|
||||||
|
let callStack = Thread.callStackSymbols.joined(separator: "\n")
|
||||||
|
for logger in additionalLoggers {
|
||||||
|
logger.log("Error: \(error)\n\(callStack)")
|
||||||
|
}
|
||||||
|
|
||||||
// Don't log networking related errors to Crashlytics.
|
// Don't log networking related errors to Crashlytics.
|
||||||
guard !error.isNetworkingError else { return }
|
guard !error.isNetworkingError else { return }
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import BitwardenKit
|
||||||
import BitwardenShared
|
import BitwardenShared
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import AuthenticationServices
|
import AuthenticationServices
|
||||||
|
import BitwardenKit
|
||||||
import BitwardenSdk
|
import BitwardenSdk
|
||||||
import BitwardenShared
|
import BitwardenShared
|
||||||
import Combine
|
import Combine
|
||||||
|
|||||||
@ -8,6 +8,12 @@ public protocol ErrorReporter: AnyObject {
|
|||||||
|
|
||||||
// MARK: Methods
|
// MARK: Methods
|
||||||
|
|
||||||
|
/// Add an additional logger that will any errors will be logged to.
|
||||||
|
///
|
||||||
|
/// - Parameter logger: The additional logger that any errors will be logged to.
|
||||||
|
///
|
||||||
|
func add(logger: BitwardenLogger)
|
||||||
|
|
||||||
/// Logs an error to be reported.
|
/// Logs an error to be reported.
|
||||||
///
|
///
|
||||||
/// - Parameter error: The error to log.
|
/// - Parameter error: The error to log.
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
@testable import BitwardenKit
|
@testable import BitwardenKit
|
||||||
|
|
||||||
public class MockErrorReporter: ErrorReporter {
|
public class MockErrorReporter: ErrorReporter {
|
||||||
|
public var additionalLoggers = [any BitwardenLogger]()
|
||||||
public var currentUserId: String?
|
public var currentUserId: String?
|
||||||
public var errors = [Error]()
|
public var errors = [Error]()
|
||||||
public var isEnabled = false
|
public var isEnabled = false
|
||||||
@ -8,6 +9,10 @@ public class MockErrorReporter: ErrorReporter {
|
|||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
|
public func add(logger: any BitwardenLogger) {
|
||||||
|
additionalLoggers.append(logger)
|
||||||
|
}
|
||||||
|
|
||||||
public func log(error: Error) {
|
public func log(error: Error) {
|
||||||
errors.append(error)
|
errors.append(error)
|
||||||
}
|
}
|
||||||
|
|||||||
25
BitwardenKit/Core/Platform/Utilities/BitwardenLogger.swift
Normal file
25
BitwardenKit/Core/Platform/Utilities/BitwardenLogger.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/// A protocol for an object that handles logging app messages.
|
||||||
|
///
|
||||||
|
public protocol BitwardenLogger {
|
||||||
|
/// Logs a message.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to log.
|
||||||
|
/// - file: The file that called the log method.
|
||||||
|
/// - line: The line number in the file that called the log method.
|
||||||
|
///
|
||||||
|
func log(_ message: String, file: String, line: UInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension BitwardenLogger {
|
||||||
|
/// Logs a message.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to log.
|
||||||
|
/// - file: The file that called the log method.
|
||||||
|
/// - line: The line number in the file that called the log method.
|
||||||
|
///
|
||||||
|
func log(_ message: String, file: String = #file, line: UInt = #line) {
|
||||||
|
log(message, file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import BitwardenKit
|
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
/// An `ErrorReporter` that logs non-fatal errors to the console via OSLog.
|
/// An `ErrorReporter` that logs non-fatal errors to the console via OSLog.
|
||||||
@ -6,6 +5,9 @@ import OSLog
|
|||||||
public final class OSLogErrorReporter: ErrorReporter {
|
public final class OSLogErrorReporter: ErrorReporter {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
|
/// A list of additional loggers that errors will be logged to.
|
||||||
|
private var additionalLoggers: [any BitwardenLogger] = []
|
||||||
|
|
||||||
/// The logger instance to log local messages.
|
/// The logger instance to log local messages.
|
||||||
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ErrorReporter")
|
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ErrorReporter")
|
||||||
|
|
||||||
@ -21,9 +23,18 @@ public final class OSLogErrorReporter: ErrorReporter {
|
|||||||
|
|
||||||
// MARK: ErrorReporter
|
// MARK: ErrorReporter
|
||||||
|
|
||||||
|
public func add(logger: any BitwardenLogger) {
|
||||||
|
additionalLoggers.append(logger)
|
||||||
|
}
|
||||||
|
|
||||||
public func log(error: Error) {
|
public func log(error: Error) {
|
||||||
logger.error("Error: \(error)")
|
logger.error("Error: \(error)")
|
||||||
|
|
||||||
|
let callStack = Thread.callStackSymbols.joined(separator: "\n")
|
||||||
|
for logger in additionalLoggers {
|
||||||
|
logger.log("Error: \(error as NSError)\n\(callStack)")
|
||||||
|
}
|
||||||
|
|
||||||
// Don't crash for networking related errors.
|
// Don't crash for networking related errors.
|
||||||
guard !error.isNetworkingError else { return }
|
guard !error.isNetworkingError else { return }
|
||||||
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import BitwardenKit
|
||||||
import BitwardenShared
|
import BitwardenShared
|
||||||
import Social
|
import Social
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|||||||
@ -424,3 +424,13 @@ extension DefaultFlightRecorder: FlightRecorder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: DefaultFlightRecorder + BitwardenLogger
|
||||||
|
|
||||||
|
extension DefaultFlightRecorder: BitwardenLogger {
|
||||||
|
nonisolated func log(_ message: String, file: String, line: UInt) {
|
||||||
|
Task {
|
||||||
|
await log(message, file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -598,4 +598,18 @@ class FlightRecorderTests: BitwardenTestCase { // swiftlint:disable:this type_bo
|
|||||||
await subject.log("Hello world!")
|
await subject.log("Hello world!")
|
||||||
XCTAssertNil(stateService.flightRecorderData)
|
XCTAssertNil(stateService.flightRecorderData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: DefaultFlightRecorder Tests
|
||||||
|
|
||||||
|
/// `DefaultFlightRecorder` implements `BitwardenLogger.log()` which logs to the active log.
|
||||||
|
func test_log_bitwardenLogger() throws {
|
||||||
|
stateService.flightRecorderData = FlightRecorderData(activeLog: activeLog)
|
||||||
|
|
||||||
|
(subject as? DefaultFlightRecorder)?.log("Hello world!")
|
||||||
|
waitFor { self.fileManager.appendDataData != nil }
|
||||||
|
|
||||||
|
let appendedMessage = try String(data: XCTUnwrap(fileManager.appendDataData), encoding: .utf8)
|
||||||
|
XCTAssertEqual(appendedMessage, "2025-01-01T00:00:00Z: Hello world!\n")
|
||||||
|
XCTAssertEqual(stateService.flightRecorderData, FlightRecorderData(activeLog: activeLog))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -398,6 +398,7 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
|
|||||||
stateService: stateService,
|
stateService: stateService,
|
||||||
timeProvider: timeProvider
|
timeProvider: timeProvider
|
||||||
)
|
)
|
||||||
|
errorReporter.add(logger: flightRecorder)
|
||||||
|
|
||||||
let rehydrationHelper = DefaultRehydrationHelper(
|
let rehydrationHelper = DefaultRehydrationHelper(
|
||||||
errorReporter: errorReporter,
|
errorReporter: errorReporter,
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
import BitwardenKit
|
|
||||||
import OSLog
|
|
||||||
|
|
||||||
/// An `ErrorReporter` that logs non-fatal errors to the console via OSLog.
|
|
||||||
///
|
|
||||||
public final class OSLogErrorReporter: ErrorReporter {
|
|
||||||
// MARK: Properties
|
|
||||||
|
|
||||||
/// The logger instance to log local messages.
|
|
||||||
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ErrorReporter")
|
|
||||||
|
|
||||||
// MARK: ErrorReporter Properties
|
|
||||||
|
|
||||||
/// This exists here satisfy the `ErrorReporter` protocol, but doesn't do anything since we
|
|
||||||
/// don't report these errors to an external service.
|
|
||||||
public var isEnabled = true
|
|
||||||
|
|
||||||
// MARK: Initialization
|
|
||||||
|
|
||||||
public init() {}
|
|
||||||
|
|
||||||
// MARK: ErrorReporter
|
|
||||||
|
|
||||||
public func log(error: Error) {
|
|
||||||
logger.error("Error: \(error)")
|
|
||||||
|
|
||||||
// Don't crash for networking related errors.
|
|
||||||
guard !error.isNetworkingError else { return }
|
|
||||||
|
|
||||||
// Crash in debug builds to make the error more visible during development.
|
|
||||||
assertionFailure("Unexpected error: \(error)")
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setRegion(_ region: String, isPreAuth: Bool) {
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setUserId(_ userId: String?) {
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user