Skip to content

Allow for custom crash directory and FileProtectionType #155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
15 changes: 14 additions & 1 deletion Sources/Public/BacktraceClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,20 @@ import Foundation
@objc public convenience init(configuration: BacktraceClientConfiguration) throws {
let api = BacktraceApi(credentials: configuration.credentials,
reportsPerMin: configuration.reportsPerMin)
let reporter = try BacktraceReporter(reporter: BacktraceCrashReporter(), api: api, dbSettings: configuration.dbSettings,

let crashReporter: BacktraceCrashReporter
if let customDir = configuration.crashDirectory {
crashReporter = BacktraceCrashReporter(
crashDirectory: customDir,
fileProtection: configuration.fileProtection,
signalHandlerType: .BSD,
symbolicationStrategy: .all
)
} else {
crashReporter = BacktraceCrashReporter()
}

let reporter = try BacktraceReporter(reporter: crashReporter, api: api, dbSettings: configuration.dbSettings,
credentials: configuration.credentials)
try self.init(configuration: configuration, debugger: DebuggerChecker.self, reporter: reporter,
dispatcher: Dispatcher(), api: api)
Expand Down
41 changes: 39 additions & 2 deletions Sources/Public/BacktraceClientConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ import Foundation

/// Flag responsible for detecting and sending possible OOM cashes
@objc public var detectOom: Bool = false

/// Custom directory for storing `.plcrash` files. Defaults to `nil`,
/// Defaults to PLCrashReporter standard directory.
@objc public var crashDirectory: URL?

/// File protection for the custom directory. Defaults to `.none`.
///
/// - Important: Using `.none` ensures the app can write crash reports
/// even if the device is locked. More secure options can cause missed crash logs.
@objc public var fileProtection: FileProtectionType = .none

/// Produces Backtrace client configuration settings.
///
/// - Parameters:
Expand All @@ -41,8 +52,7 @@ import Foundation
/// - credentials: Backtrace server API credentials.
/// - dbSettings: Backtrace database settings.
/// - reportsPerMin: Maximum number of records sent to Backtrace services in 1 minute. Default: `30`.
/// - allowsAttachingDebugger: if set to `true` BacktraceClient will report reports even when the debugger
/// is attached. Default: `false`.
/// - allowsAttachingDebugger: if set to `true` BacktraceClient will report reports even when the debugger is attached. Default: `false`.
/// - detectOOM: if set to `true` BacktraceClient will detect when the app is out of memory. Default: `false`.
@objc public init(credentials: BacktraceCredentials,
dbSettings: BacktraceDatabaseSettings = BacktraceDatabaseSettings(),
Expand All @@ -55,4 +65,31 @@ import Foundation
self.allowsAttachingDebugger = allowsAttachingDebugger
self.detectOom = detectOOM
}

/// Produces Backtrace client configuration settings, custom crash log directory and file protection level.
///
/// - Parameters:
/// - credentials: Backtrace server API credentials.
/// - dbSettings: Backtrace database settings.
/// - reportsPerMin: Maximum number of records sent to Backtrace services in 1 minute. Default: `30`.
/// - allowsAttachingDebugger: if set to `true` BacktraceClient will report reports even when the debugger is attached. Default: `false`.
/// - detectOOM: if set to `true` BacktraceClient will detect when the app is out of memory. Default: `false`.
/// - crashDirectory: Custom directory for storing `.plcrash` files. Defaults to `nil`PLCrashReporter standard directory.
/// - fileProtection: OS file protection level. Default to`.none`to ensure the crash reports writes even if the device is locked. More secure options can cause missed crash logs.
@objc public init(credentials: BacktraceCredentials,
dbSettings: BacktraceDatabaseSettings = BacktraceDatabaseSettings(),
reportsPerMin: Int = 30,
allowsAttachingDebugger: Bool = false,
detectOOM: Bool = false,
crashDirectory: URL? = nil,
fileProtection: FileProtectionType = .none) {

self.credentials = credentials
self.dbSettings = dbSettings
self.reportsPerMin = reportsPerMin
self.allowsAttachingDebugger = allowsAttachingDebugger
self.detectOom = detectOOM
self.crashDirectory = crashDirectory
self.fileProtection = fileProtection
}
}
42 changes: 42 additions & 0 deletions Sources/Public/BacktraceCrashReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,48 @@ import Darwin
@objc public convenience init(config: PLCrashReporterConfig = PLCrashReporterConfig(signalHandlerType: .BSD, symbolicationStrategy: .all)) {
self.init(reporter: PLCrashReporter(configuration: config))
}

/**
Convenience initializer to create an instance of a crash reporter, allow storing crash logs in a custom directory.

- Parameters:
- crashDirectory: Directory for `.plcrash` logs.
- fileProtection: File protection level. Default is `.none`.
- signalHandlerType: Type of crash signal handling. Defaults to `.BSD`.
- symbolicationStrategy: Strategy for local symbolication. Defaults to `.all`.

The directory is created if it doesn't exist, and the file protection attribute is applied.
*/
@objc public convenience init(crashDirectory: URL,
fileProtection: FileProtectionType = .none,
signalHandlerType: PLCrashReporterSignalHandlerType = .BSD,
symbolicationStrategy: PLCrashReporterSymbolicationStrategy = .all) {
do {
let fm = FileManager.default
var attributes = [FileAttributeKey: Any]()
attributes[.protectionKey] = fileProtection
try fm.createDirectory(
at: crashDirectory,
withIntermediateDirectories: true,
attributes: attributes
)
} catch {
BacktraceLogger.error("Could not create custom crash directory: \(error)")
}

let basePathConfig = PLCrashReporterConfig(
signalHandlerType: signalHandlerType,
symbolicationStrategy: symbolicationStrategy,
basePath: crashDirectory.path
)

let defaultConfig = PLCrashReporterConfig(
signalHandlerType: .BSD,
symbolicationStrategy: .all
)

self.init(reporter: PLCrashReporter(configuration: basePathConfig) ?? PLCrashReporter(configuration: defaultConfig))
}

/// Creates an instance of a crash reporter.
/// - Parameter reporter: An instance of `PLCrashReporter` to use.
Expand Down
64 changes: 64 additions & 0 deletions Tests/BacktraceClientSpec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import XCTest
import Nimble
import Quick
@testable import Backtrace

final class BacktraceClientSpec: QuickSpec {
override func spec() {
describe("BacktraceClient") {

context("when crashDirectory is set") {
it("creates the directory and uses it for crash logs") {
let fileManager = FileManager.default
let customDirectory = fileManager.temporaryDirectory.appendingPathComponent("bt-client-spec-\(UUID().uuidString)")
try? fileManager.removeItem(at: customDirectory)

let creds = BacktraceCredentials(
endpoint: URL(string: "https://yourteam.backtrace.io")!,
token: "test-token"
)
let config = BacktraceClientConfiguration(
credentials: creds,
dbSettings: BacktraceDatabaseSettings(),
reportsPerMin: 30,
allowsAttachingDebugger: true,
detectOOM: true,
crashDirectory: customDirectory,
fileProtection: .none
)

expect {
try BacktraceClient(configuration: config)
}.toNot(throwError())

var isDir: ObjCBool = false
let exists = fileManager.fileExists(atPath: customDirectory.path, isDirectory: &isDir)
expect(exists).to(beTrue())
expect(isDir.boolValue).to(beTrue())

let attributes = try? fileManager.attributesOfItem(atPath: customDirectory.path)
let protection = attributes?[.protectionKey] as? FileProtectionType
#if !targetEnvironment(simulator)
expect(protection).to(equal(FileProtectionType.none))
#endif
}
}

context("when crashDirectory is nil") {
it("should not create the custom directory (logic only)") {
let fileManager = FileManager.default
let customDirectory = fileManager.temporaryDirectory.appendingPathComponent("bt-client-spec-\(UUID().uuidString)")
try? fileManager.removeItem(at: customDirectory)

let crashDirectory: URL? = nil

expect(crashDirectory).to(beNil())

let exists = fileManager.fileExists(atPath: customDirectory.path)
expect(exists).to(beFalse(), description: "Directory should not exist when crashDirectory is nil")
}
}
}
}
}

56 changes: 56 additions & 0 deletions Tests/BacktraceCrashReporterSpec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import XCTest
import Nimble
import Quick
@testable import Backtrace

final class BacktraceCrashReporterSpec: QuickSpec {
override func spec() {
describe("BacktraceCrashReporter") {

context("when using the new convenience initializer") {
let fileManager = FileManager.default
var customDirectory: URL!

beforeEach {
customDirectory = fileManager
.temporaryDirectory
.appendingPathComponent("bt-test-crash-reporter-\(UUID().uuidString)")
try? fileManager.removeItem(at: customDirectory)
}

it("creates the custom directory with the specified file protection") {
let reporter = BacktraceCrashReporter(
crashDirectory: customDirectory,
fileProtection: .none,
signalHandlerType: .BSD,
symbolicationStrategy: .all
)

var isDir: ObjCBool = false
let exists = fileManager.fileExists(atPath: customDirectory.path, isDirectory: &isDir)
expect(exists).to(beTrue(), description: "Expected directory to exist.")
expect(isDir.boolValue).to(beTrue(), description: "Expected path to be a directory.")

#if !targetEnvironment(simulator)
let attributes = try? fileManager.attributesOfItem(atPath: customDirectory.path)
let protection = attributes?[.protectionKey] as? FileProtectionType
expect(protection).to(equal(FileProtectionType.none), description: "Expected file protection to match input (.none).")
#endif

expect { try reporter.generateLiveReport(attributes: [:]) }.toNot(throwError())
}

it("falls back to default config if custom config fails") {
let invalidPath = URL(fileURLWithPath: "/dev/null/invalid")

let reporter = BacktraceCrashReporter(
crashDirectory: invalidPath,
fileProtection: .complete
)

expect { try reporter.generateLiveReport(attributes: [:]) }.toNot(throwError())
}
}
}
}
}