Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 74 additions & 21 deletions BitwardenShared/Core/Platform/Services/FlightRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,78 @@ enum FlightRecorderError: Error {
/// Deletion of the log isn't permitted if the log is the active log.
case deletionNotPermitted

/// Unable to determine the log's file size.
case fileSizeError(Error)

/// Error waiting for next flight recorder log lifecycle.
case logLifecycleTimerError(Error)

/// The specified log wasn't found in the stored flight recorder data.
case logNotFound

/// Unable to remove file for expired log.
case removeExpiredLogError(Error)

/// Unable to write message to log.
case writeMessageError(Error)
}

// MARK: - FlightRecorderError + CustomNSError

extension FlightRecorderError: CustomNSError {
static var errorDomain: String { "FlightRecorderError" }

var errorCode: Int {
// NOTE: New cases should be appended (vs alphabetized) to this switch statement with an
// incremented integer. This ensures the code for existing errors doesn't change.
switch self {
case .dataUnavailable: 1
case .deletionNotPermitted: 2
case .fileSizeError: 3
case .logLifecycleTimerError: 4
case .logNotFound: 5
case .removeExpiredLogError: 6
case .writeMessageError: 7
}
}

var errorUserInfo: [String: Any] {
var userInfo: [String: Any] = [
"Error Type": String(reflecting: self),
]

switch self {
case let .fileSizeError(underlyingError),
let .logLifecycleTimerError(underlyingError),
let .removeExpiredLogError(underlyingError),
let .writeMessageError(underlyingError):
userInfo[NSUnderlyingErrorKey] = underlyingError
default:
break
}

return userInfo
}
}

// MARK: - FlightRecorderError + Equatable

extension FlightRecorderError: Equatable {
static func == (lhs: FlightRecorderError, rhs: FlightRecorderError) -> Bool {
switch (lhs, rhs) {
case (.dataUnavailable, .dataUnavailable),
(.deletionNotPermitted, .deletionNotPermitted),
(.logNotFound, .logNotFound):
true
case let (.fileSizeError(lhsError), .fileSizeError(rhsError)),
let (.logLifecycleTimerError(lhsError), .logLifecycleTimerError(rhsError)),
let (.removeExpiredLogError(lhsError), .removeExpiredLogError(rhsError)),
let (.writeMessageError(lhsError), .writeMessageError(rhsError)):
String(reflecting: lhsError) == String(reflecting: rhsError)
default:
false
}
}
}

// MARK: - DefaultFlightRecorder
Expand Down Expand Up @@ -214,11 +284,7 @@ actor DefaultFlightRecorder {
} catch is CancellationError {
// No-op: don't log or alert for cancellation errors.
} catch {
await self?.errorReporter.log(error: BitwardenError.generalError(
type: "Flight Recorder Log Lifecycle Timer Error",
message: "Error waiting for next flight recorder log lifecycle",
error: error,
))
await self?.errorReporter.log(error: FlightRecorderError.logLifecycleTimerError(error))
}
}
}
Expand Down Expand Up @@ -266,11 +332,7 @@ actor DefaultFlightRecorder {
do {
try removeLog(at: fileURL(for: log))
} catch {
errorReporter.log(error: BitwardenError.generalError(
type: "Flight Recorder Remove Log Error",
message: "Unable to remove file for expired log",
error: error,
))
errorReporter.log(error: FlightRecorderError.removeExpiredLogError(error))
}

data.inactiveLogs.remove(at: index)
Expand Down Expand Up @@ -298,11 +360,7 @@ actor DefaultFlightRecorder {
error.code == NSFileReadNoSuchFileError {
return ""
} catch {
errorReporter.log(error: BitwardenError.generalError(
type: "Flight Recorder File Size Error",
message: "Unable to determine the log's file size",
error: error,
))
errorReporter.log(error: FlightRecorderError.fileSizeError(error))
return ""
}
}
Expand Down Expand Up @@ -444,12 +502,7 @@ extension DefaultFlightRecorder: FlightRecorder {
data.activeLog = nil
await setFlightRecorderData(data)

let fileName = URL(fileURLWithPath: file).lastPathComponent
errorReporter.log(error: BitwardenError.generalError(
type: "Flight Recorder Log Error",
message: "\(fileName):\(line) Unable to write message to log: \(message)",
error: error,
))
errorReporter.log(error: FlightRecorderError.writeMessageError(error))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,9 +442,8 @@ class FlightRecorderTests: BitwardenTestCase { // swiftlint:disable:this type_bo

XCTAssertEqual(logs[0].fileSize, "")
XCTAssertEqual(errorReporter.errors.count, 1)
let error = try XCTUnwrap(errorReporter.errors.first as? NSError)
XCTAssertEqual(error.domain, "General Error: Flight Recorder File Size Error")
XCTAssertEqual(error.code, BitwardenError.Code.generalError.rawValue)
let error = try XCTUnwrap(errorReporter.errors.first as? FlightRecorderError)
XCTAssertEqual(error, FlightRecorderError.fileSizeError(BitwardenTestError.example))
}

/// `fetchLogs()` return an empty list if there's no flight recorder data on the device.
Expand Down Expand Up @@ -535,9 +534,8 @@ class FlightRecorderTests: BitwardenTestCase { // swiftlint:disable:this type_bo
try await waitForAsync { !self.errorReporter.errors.isEmpty }

XCTAssertEqual(errorReporter.errors.count, 1)
let error = try XCTUnwrap(errorReporter.errors.first as? NSError)
XCTAssertEqual(error.domain, "General Error: Flight Recorder Remove Log Error")
XCTAssertEqual(error.code, BitwardenError.Code.generalError.rawValue)
let error = try XCTUnwrap(errorReporter.errors.first as? FlightRecorderError)
XCTAssertEqual(error, .removeExpiredLogError(BitwardenTestError.example))
try XCTAssertEqual(
fileManager.removeItemURLs,
[FileManager.default.flightRecorderLogURL().appendingPathComponent(inactiveLog1.fileName)],
Expand Down Expand Up @@ -613,9 +611,8 @@ class FlightRecorderTests: BitwardenTestCase { // swiftlint:disable:this type_bo
await subject.log("Hello world!")

XCTAssertEqual(errorReporter.errors.count, 1)
let error = try XCTUnwrap(errorReporter.errors.last as? NSError)
XCTAssertEqual(error.code, BitwardenError.Code.generalError.rawValue)
XCTAssertEqual(error.domain, "General Error: Flight Recorder Log Error")
let error = try XCTUnwrap(errorReporter.errors.last as? FlightRecorderError)
XCTAssertEqual(error, .writeMessageError(BitwardenTestError.example))
XCTAssertEqual(stateService.flightRecorderData, FlightRecorderData(inactiveLogs: [activeLog]))
}

Expand Down