From 70858ac6f7ccb4fc09284a77d7e149b3728acc06 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Sat, 17 Feb 2024 18:18:54 +0100 Subject: [PATCH 1/7] RUM-2925 Move `DDCrashReport` APIs to `DatadogInternal` --- Datadog/Datadog.xcodeproj/project.pbxproj | 28 +++ .../Mocks/CrashReportingFeatureMocks.swift | 2 +- .../Sources/CrashReportingPlugin.swift | 165 +----------------- .../DDCrashReportBuilder.swift | 2 +- .../DDCrashReportExporter.swift | 10 +- .../PLCrashReporterIntegration.swift | 2 +- .../Sources/ThirdPartyCrashReporter.swift | 2 +- DatadogCrashReporting/Tests/Mocks.swift | 1 + .../CrashReporting/DDCrashReport.swift | 102 +++++++++++ .../CrashReporting/StackTrace.swift | 77 ++++++++ 10 files changed, 218 insertions(+), 173 deletions(-) create mode 100644 DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift create mode 100644 DatadogInternal/Sources/FeatureAPIs/CrashReporting/StackTrace.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 94f1c7f1bc..b3181347c9 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -295,6 +295,10 @@ 6167E6DB2B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D92B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift */; }; 6167E6DD2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */; }; 6167E6DE2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */; }; + 6167E6E22B81207200C3CA2D /* DDCrashReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E12B81207200C3CA2D /* DDCrashReport.swift */; }; + 6167E6E32B81207200C3CA2D /* DDCrashReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E12B81207200C3CA2D /* DDCrashReport.swift */; }; + 6167E6E52B8120C100C3CA2D /* StackTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E42B8120C100C3CA2D /* StackTrace.swift */; }; + 6167E6E62B8120C100C3CA2D /* StackTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E42B8120C100C3CA2D /* StackTrace.swift */; }; 616B668E259CC28E00968EE8 /* DDRUMMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */; }; 6170DC1C25C18729003AED5C /* PLCrashReporterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6170DC1B25C18729003AED5C /* PLCrashReporterPlugin.swift */; }; 6172472725D673D7007085B3 /* CrashContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6172472625D673D7007085B3 /* CrashContextTests.swift */; }; @@ -2156,6 +2160,8 @@ 6167E6D52B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsWatchdogThread.swift; sourceTree = ""; }; 6167E6D92B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsWatchdogThreadTests.swift; sourceTree = ""; }; 6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsMonitoringTests.swift; sourceTree = ""; }; + 6167E6E12B81207200C3CA2D /* DDCrashReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDCrashReport.swift; sourceTree = ""; }; + 6167E6E42B8120C100C3CA2D /* StackTrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackTrace.swift; sourceTree = ""; }; 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDRUMMonitorTests.swift; sourceTree = ""; }; 616C0A9D28573DFF00C13264 /* RUMOperatingSystemInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfo.swift; sourceTree = ""; }; 616C0AA028573F6300C13264 /* RUMOperatingSystemInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfoTests.swift; sourceTree = ""; }; @@ -4309,6 +4315,23 @@ path = AppHangs; sourceTree = ""; }; + 6167E6DF2B81203A00C3CA2D /* FeatureAPIs */ = { + isa = PBXGroup; + children = ( + 6167E6E02B81204B00C3CA2D /* CrashReporting */, + ); + path = FeatureAPIs; + sourceTree = ""; + }; + 6167E6E02B81204B00C3CA2D /* CrashReporting */ = { + isa = PBXGroup; + children = ( + 6167E6E12B81207200C3CA2D /* DDCrashReport.swift */, + 6167E6E42B8120C100C3CA2D /* StackTrace.swift */, + ); + path = CrashReporting; + sourceTree = ""; + }; 616CCE11250A181C009FED46 /* Instrumentation */ = { isa = PBXGroup; children = ( @@ -5170,6 +5193,7 @@ D23039A6298D513D001A1FA3 /* DatadogInternal */ = { isa = PBXGroup; children = ( + 6167E6DF2B81203A00C3CA2D /* FeatureAPIs */, D23039CA298D5235001A1FA3 /* Attributes */, D23039C3298D5235001A1FA3 /* Codable */, D23039DA298D5235001A1FA3 /* Concurrency */, @@ -7904,6 +7928,7 @@ D2303A01298D5236001A1FA3 /* DateFormatting.swift in Sources */, D2216EC02A94DE2900ADAEC8 /* FeatureBaggage.swift in Sources */, D23039F1298D5236001A1FA3 /* AnyDecodable.swift in Sources */, + 6167E6E22B81207200C3CA2D /* DDCrashReport.swift in Sources */, D2160CC529C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, D23039DD298D5235001A1FA3 /* DD.swift in Sources */, D2160C9A29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, @@ -7929,6 +7954,7 @@ D23039E0298D5235001A1FA3 /* DatadogCoreProtocol.swift in Sources */, D23039FD298D5236001A1FA3 /* DataCompression.swift in Sources */, D23039F0298D5236001A1FA3 /* AnyEncoder.swift in Sources */, + 6167E6E52B8120C100C3CA2D /* StackTrace.swift in Sources */, D2A783D429A5309F003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD72A543B3B00446CF9 /* Event.swift in Sources */, 3CBDE68A2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */, @@ -8719,6 +8745,7 @@ D2DA236F298D57AA00C6C7E6 /* DateFormatting.swift in Sources */, D2216EC12A94DE2900ADAEC8 /* FeatureBaggage.swift in Sources */, D2DA2370298D57AA00C6C7E6 /* AnyDecodable.swift in Sources */, + 6167E6E32B81207200C3CA2D /* DDCrashReport.swift in Sources */, D2160CC629C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, D2DA2372298D57AA00C6C7E6 /* DD.swift in Sources */, D2160C9B29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, @@ -8744,6 +8771,7 @@ D2DA237C298D57AA00C6C7E6 /* DatadogCoreProtocol.swift in Sources */, D2DA237D298D57AA00C6C7E6 /* DataCompression.swift in Sources */, D2DA237E298D57AA00C6C7E6 /* AnyEncoder.swift in Sources */, + 6167E6E62B8120C100C3CA2D /* StackTrace.swift in Sources */, D2A783D529A530A0003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD82A543B3B00446CF9 /* Event.swift in Sources */, 3CBDE68B2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */, diff --git a/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift index 67fa374c59..014e57c6a6 100644 --- a/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift @@ -197,7 +197,7 @@ internal extension DDCrashReport { type: String = .mockAny(), message: String = .mockAny(), stack: String = .mockAny(), - threads: [Thread] = [], + threads: [DDThread] = [], binaryImages: [BinaryImage] = [], meta: Meta = .mockAny(), wasTruncated: Bool = .mockAny(), diff --git a/DatadogCrashReporting/Sources/CrashReportingPlugin.swift b/DatadogCrashReporting/Sources/CrashReportingPlugin.swift index 04751077ad..39c7f8308b 100644 --- a/DatadogCrashReporting/Sources/CrashReportingPlugin.swift +++ b/DatadogCrashReporting/Sources/CrashReportingPlugin.swift @@ -4,170 +4,7 @@ * Copyright 2019-Present Datadog, Inc. */ -import Foundation - -/// Crash Report format supported by Datadog SDK. -@objc -internal class DDCrashReport: NSObject, Codable { - struct Thread: Codable { - /// The name of the thread, e.g. `"Thread 0"` - let name: String - /// Unsymbolicated stack trace of the crash. - let stack: String - /// If the thread was halted. - let crashed: Bool - /// Thread state (CPU registers dump), only available for halted thread. - let state: String? - - init( - name: String, - stack: String, - crashed: Bool, - state: String? - ) { - self.name = name - self.stack = stack - self.crashed = crashed - self.state = state - } - - // MARK: - Encoding - - enum CodingKeys: String, CodingKey { - case name = "name" - case stack = "stack" - case crashed = "crashed" - case state = "state" - } - } - - struct BinaryImage: Codable { - let libraryName: String - let uuid: String - let architecture: String - let isSystemLibrary: Bool - let loadAddress: String - let maxAddress: String - - init( - libraryName: String, - uuid: String, - architecture: String, - isSystemLibrary: Bool, - loadAddress: String, - maxAddress: String - ) { - self.libraryName = libraryName - self.uuid = uuid - self.architecture = architecture - self.isSystemLibrary = isSystemLibrary - self.loadAddress = loadAddress - self.maxAddress = maxAddress - } - - // MARK: - Encoding - - enum CodingKeys: String, CodingKey { - case libraryName = "name" - case uuid = "uuid" - case architecture = "arch" - case isSystemLibrary = "is_system" - case loadAddress = "load_address" - case maxAddress = "max_address" - } - } - - /// Meta information about the process. - /// Ref.: https://developer.apple.com/documentation/xcode/examining-the-fields-in-a-crash-report - struct Meta: Codable { - /// A client-generated 16-byte UUID of the incident. - let incidentIdentifier: String? - /// The name of the crashed process. - let process: String? - /// Parent process information. - let parentProcess: String? - /// The location of the executable on disk. - let path: String? - /// The CPU architecture of the process that crashed. - let codeType: String? - /// The name of the corresponding BSD termination signal. - let exceptionType: String? - /// CPU specific information about the exception encoded into 64-bit hexadecimal number preceded by the signal code. - let exceptionCodes: String? - - init( - incidentIdentifier: String?, - process: String?, - parentProcess: String?, - path: String?, - codeType: String?, - exceptionType: String?, - exceptionCodes: String? - ) { - self.incidentIdentifier = incidentIdentifier - self.process = process - self.parentProcess = parentProcess - self.path = path - self.codeType = codeType - self.exceptionType = exceptionType - self.exceptionCodes = exceptionCodes - } - - enum CodingKeys: String, CodingKey { - case incidentIdentifier = "incident_identifier" - case process = "process" - case parentProcess = "parent_process" - case path = "path" - case codeType = "code_type" - case exceptionType = "exception_type" - case exceptionCodes = "exception_codes" - } - } - - /// The date of the crash occurrence. - let date: Date? - /// Crash report type - used to group similar crash reports. - /// In Datadog Error Tracking this corresponds to `error.type`. - let type: String - /// Crash report message - if possible, it should provide additional troubleshooting information in addition to the crash type. - /// In Datadog Error Tracking this corresponds to `error.message`. - let message: String - /// Unsymbolicated stack trace related to the crash (this can be either uncaugh exception backtrace or stack trace of the halted thread). - /// In Datadog Error Tracking this corresponds to `error.stack`. - let stack: String - /// All threads running in the process. - let threads: [Thread] - /// List of binary images referenced from all stack traces. - let binaryImages: [BinaryImage] - /// Meta information about the crash and process. - let meta: Meta - /// If any stack trace information was truncated due to crash report minimization. - let wasTruncated: Bool - /// The last context injected through `inject(context:)` - let context: Data? - - init( - date: Date?, - type: String, - message: String, - stack: String, - threads: [Thread], - binaryImages: [BinaryImage], - meta: Meta, - wasTruncated: Bool, - context: Data? - ) { - self.date = date - self.type = type - self.message = message - self.stack = stack - self.threads = threads - self.binaryImages = binaryImages - self.meta = meta - self.wasTruncated = wasTruncated - self.context = context - } -} +import DatadogInternal /// An interface for enabling crash reporting feature in Datadog SDK. /// diff --git a/DatadogCrashReporting/Sources/PLCrashReporterIntegration/DDCrashReportBuilder.swift b/DatadogCrashReporting/Sources/PLCrashReporterIntegration/DDCrashReportBuilder.swift index 6ac51a6491..52c331a163 100644 --- a/DatadogCrashReporting/Sources/PLCrashReporterIntegration/DDCrashReportBuilder.swift +++ b/DatadogCrashReporting/Sources/PLCrashReporterIntegration/DDCrashReportBuilder.swift @@ -4,7 +4,7 @@ * Copyright 2019-Present Datadog, Inc. */ -import Foundation +import DatadogInternal import CrashReporter /// Builds `DDCrashReport` from `PLCrashReport`. diff --git a/DatadogCrashReporting/Sources/PLCrashReporterIntegration/DDCrashReportExporter.swift b/DatadogCrashReporting/Sources/PLCrashReporterIntegration/DDCrashReportExporter.swift index 53d118aef1..2832b6d49c 100644 --- a/DatadogCrashReporting/Sources/PLCrashReporterIntegration/DDCrashReportExporter.swift +++ b/DatadogCrashReporting/Sources/PLCrashReporterIntegration/DDCrashReportExporter.swift @@ -4,7 +4,7 @@ * Copyright 2019-Present Datadog, Inc. */ -import Foundation +import DatadogInternal /// Exports intermediate `CrashReport` to `DDCrashReport`. /// @@ -141,9 +141,9 @@ internal struct DDCrashReportExporter { // MARK: - Exporting threads and binary images - private func formattedThreads(from crashReport: CrashReport) -> [DDCrashReport.Thread] { + private func formattedThreads(from crashReport: CrashReport) -> [DDThread] { return crashReport.threads.map { thread in - return DDCrashReport.Thread( + return DDThread( name: "Thread \(thread.threadNumber)", stack: string(from: thread.stackFrames), // we don't sanitize frames in `error.threads[]` crashed: thread.crashed, @@ -152,7 +152,7 @@ internal struct DDCrashReportExporter { } } - private func formattedBinaryImages(from crashReport: CrashReport) -> [DDCrashReport.BinaryImage] { + private func formattedBinaryImages(from crashReport: CrashReport) -> [BinaryImage] { return crashReport.binaryImages.map { image in // Ref. for this computation: // https://github.com/microsoft/plcrashreporter/blob/dbb05c0bc883bde1cfcad83e7add25862c95d11f/Source/PLCrashReportTextFormatter.m#L447 @@ -162,7 +162,7 @@ internal struct DDCrashReportExporter { let maxAddress = image.imageBaseAddress.addIfNoOverflow(maxAddressOffset) ?? image.imageBaseAddress let maxAddressHex = "0x\(maxAddress.toHex)" - return DDCrashReport.BinaryImage( + return BinaryImage( libraryName: image.imageName, uuid: image.uuid ?? unavailable, architecture: image.codeType?.architectureName ?? unavailable, diff --git a/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift b/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift index db3a2f5b8b..e7e80067f4 100644 --- a/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift +++ b/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift @@ -4,7 +4,7 @@ * Copyright 2019-Present Datadog, Inc. */ -import Foundation +import DatadogInternal import CrashReporter internal extension PLCrashReporterConfig { diff --git a/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift b/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift index d55a8c63c6..3c1b749421 100644 --- a/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift +++ b/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift @@ -4,7 +4,7 @@ * Copyright 2019-Present Datadog, Inc. */ -import Foundation +import DatadogInternal /// An interface of 3rd party crash reporter used by the DatadogCrashReporting. internal protocol ThirdPartyCrashReporter { diff --git a/DatadogCrashReporting/Tests/Mocks.swift b/DatadogCrashReporting/Tests/Mocks.swift index cf298449ca..9a8fb07581 100644 --- a/DatadogCrashReporting/Tests/Mocks.swift +++ b/DatadogCrashReporting/Tests/Mocks.swift @@ -4,6 +4,7 @@ * Copyright 2019-Present Datadog, Inc. */ +import DatadogInternal import CrashReporter @testable import DatadogCrashReporting diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift new file mode 100644 index 0000000000..965a05e8d7 --- /dev/null +++ b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift @@ -0,0 +1,102 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation + +/// Crash Report format supported by Datadog SDK. +@objc +public final class DDCrashReport: NSObject, Codable { + /// Meta information about the process. + /// Ref.: https://developer.apple.com/documentation/xcode/examining-the-fields-in-a-crash-report + public struct Meta: Codable { + /// A client-generated 16-byte UUID of the incident. + public let incidentIdentifier: String? + /// The name of the crashed process. + public let process: String? + /// Parent process information. + public let parentProcess: String? + /// The location of the executable on disk. + public let path: String? + /// The CPU architecture of the process that crashed. + public let codeType: String? + /// The name of the corresponding BSD termination signal. + public let exceptionType: String? + /// CPU specific information about the exception encoded into 64-bit hexadecimal number preceded by the signal code. + public let exceptionCodes: String? + + public init( + incidentIdentifier: String?, + process: String?, + parentProcess: String?, + path: String?, + codeType: String?, + exceptionType: String?, + exceptionCodes: String? + ) { + self.incidentIdentifier = incidentIdentifier + self.process = process + self.parentProcess = parentProcess + self.path = path + self.codeType = codeType + self.exceptionType = exceptionType + self.exceptionCodes = exceptionCodes + } + + enum CodingKeys: String, CodingKey { + case incidentIdentifier = "incident_identifier" + case process = "process" + case parentProcess = "parent_process" + case path = "path" + case codeType = "code_type" + case exceptionType = "exception_type" + case exceptionCodes = "exception_codes" + } + } + + /// The date of the crash occurrence. + public let date: Date? + /// Crash report type - used to group similar crash reports. + /// In Datadog Error Tracking this corresponds to `error.type`. + public let type: String + /// Crash report message - if possible, it should provide additional troubleshooting information in addition to the crash type. + /// In Datadog Error Tracking this corresponds to `error.message`. + public let message: String + /// Unsymbolicated stack trace related to the crash (this can be either uncaugh exception backtrace or stack trace of the halted thread). + /// In Datadog Error Tracking this corresponds to `error.stack`. + public let stack: String + /// All threads running in the process. + public let threads: [DDThread] + /// List of binary images referenced from all stack traces. + public let binaryImages: [BinaryImage] + /// Meta information about the crash and process. + public let meta: Meta + /// If any stack trace information was truncated due to crash report minimization. + public let wasTruncated: Bool + /// The last context injected through `inject(context:)` + public let context: Data? + + public init( + date: Date?, + type: String, + message: String, + stack: String, + threads: [DDThread], + binaryImages: [BinaryImage], + meta: Meta, + wasTruncated: Bool, + context: Data? + ) { + self.date = date + self.type = type + self.message = message + self.stack = stack + self.threads = threads + self.binaryImages = binaryImages + self.meta = meta + self.wasTruncated = wasTruncated + self.context = context + } +} diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/StackTrace.swift b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/StackTrace.swift new file mode 100644 index 0000000000..f62f077e2a --- /dev/null +++ b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/StackTrace.swift @@ -0,0 +1,77 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation + +/// Unsymbolicated stack trace thread. +public struct DDThread: Codable { + /// The name of the thread, e.g. `"Thread 0"` + public let name: String + /// Unsymbolicated stack trace of the crash. + public let stack: String + /// If the thread was halted. + public let crashed: Bool + /// Thread state (CPU registers dump), only available for halted thread. + public let state: String? + + public init( + name: String, + stack: String, + crashed: Bool, + state: String? + ) { + self.name = name + self.stack = stack + self.crashed = crashed + self.state = state + } + + // MARK: - Encoding + + enum CodingKeys: String, CodingKey { + case name = "name" + case stack = "stack" + case crashed = "crashed" + case state = "state" + } +} + +/// Binary Image referenced in frames from `DDThread`. +public struct BinaryImage: Codable { + public let libraryName: String + public let uuid: String + public let architecture: String + public let isSystemLibrary: Bool + public let loadAddress: String + public let maxAddress: String + + public init( + libraryName: String, + uuid: String, + architecture: String, + isSystemLibrary: Bool, + loadAddress: String, + maxAddress: String + ) { + self.libraryName = libraryName + self.uuid = uuid + self.architecture = architecture + self.isSystemLibrary = isSystemLibrary + self.loadAddress = loadAddress + self.maxAddress = maxAddress + } + + // MARK: - Encoding + + enum CodingKeys: String, CodingKey { + case libraryName = "name" + case uuid = "uuid" + case architecture = "arch" + case isSystemLibrary = "is_system" + case loadAddress = "load_address" + case maxAddress = "max_address" + } +} From 758bc6d7e3804bab4e8ebb2c32e1cb05f9200e6a Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Sat, 17 Feb 2024 18:25:56 +0100 Subject: [PATCH 2/7] RUM-2925 Add `BacktraceReport` API to capture thread backtrace --- Datadog/Datadog.xcodeproj/project.pbxproj | 24 ++++++++--- .../CrashReporting/BacktraceReport.swift | 41 +++++++++++++++++++ .../{StackTrace.swift => BinaryImage.swift} | 33 --------------- .../CrashReporting/DDCrashReport.swift | 2 +- .../FeatureAPIs/CrashReporting/DDThread.swift | 40 ++++++++++++++++++ 5 files changed, 100 insertions(+), 40 deletions(-) create mode 100644 DatadogInternal/Sources/FeatureAPIs/CrashReporting/BacktraceReport.swift rename DatadogInternal/Sources/FeatureAPIs/CrashReporting/{StackTrace.swift => BinaryImage.swift} (60%) create mode 100644 DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDThread.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index b3181347c9..1c69a66a05 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -297,8 +297,12 @@ 6167E6DE2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */; }; 6167E6E22B81207200C3CA2D /* DDCrashReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E12B81207200C3CA2D /* DDCrashReport.swift */; }; 6167E6E32B81207200C3CA2D /* DDCrashReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E12B81207200C3CA2D /* DDCrashReport.swift */; }; - 6167E6E52B8120C100C3CA2D /* StackTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E42B8120C100C3CA2D /* StackTrace.swift */; }; - 6167E6E62B8120C100C3CA2D /* StackTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E42B8120C100C3CA2D /* StackTrace.swift */; }; + 6167E6E82B8122E900C3CA2D /* BacktraceReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E72B8122E900C3CA2D /* BacktraceReport.swift */; }; + 6167E6E92B8122E900C3CA2D /* BacktraceReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6E72B8122E900C3CA2D /* BacktraceReport.swift */; }; + 6167E6F62B81E94C00C3CA2D /* DDThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6F52B81E94C00C3CA2D /* DDThread.swift */; }; + 6167E6F72B81E94C00C3CA2D /* DDThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6F52B81E94C00C3CA2D /* DDThread.swift */; }; + 6167E6F92B81E95900C3CA2D /* BinaryImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6F82B81E95900C3CA2D /* BinaryImage.swift */; }; + 6167E6FA2B81E95900C3CA2D /* BinaryImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6F82B81E95900C3CA2D /* BinaryImage.swift */; }; 616B668E259CC28E00968EE8 /* DDRUMMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */; }; 6170DC1C25C18729003AED5C /* PLCrashReporterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6170DC1B25C18729003AED5C /* PLCrashReporterPlugin.swift */; }; 6172472725D673D7007085B3 /* CrashContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6172472625D673D7007085B3 /* CrashContextTests.swift */; }; @@ -2161,7 +2165,9 @@ 6167E6D92B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsWatchdogThreadTests.swift; sourceTree = ""; }; 6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsMonitoringTests.swift; sourceTree = ""; }; 6167E6E12B81207200C3CA2D /* DDCrashReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDCrashReport.swift; sourceTree = ""; }; - 6167E6E42B8120C100C3CA2D /* StackTrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackTrace.swift; sourceTree = ""; }; + 6167E6E72B8122E900C3CA2D /* BacktraceReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReport.swift; sourceTree = ""; }; + 6167E6F52B81E94C00C3CA2D /* DDThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDThread.swift; sourceTree = ""; }; + 6167E6F82B81E95900C3CA2D /* BinaryImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryImage.swift; sourceTree = ""; }; 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDRUMMonitorTests.swift; sourceTree = ""; }; 616C0A9D28573DFF00C13264 /* RUMOperatingSystemInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfo.swift; sourceTree = ""; }; 616C0AA028573F6300C13264 /* RUMOperatingSystemInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfoTests.swift; sourceTree = ""; }; @@ -4327,7 +4333,9 @@ isa = PBXGroup; children = ( 6167E6E12B81207200C3CA2D /* DDCrashReport.swift */, - 6167E6E42B8120C100C3CA2D /* StackTrace.swift */, + 6167E6E72B8122E900C3CA2D /* BacktraceReport.swift */, + 6167E6F52B81E94C00C3CA2D /* DDThread.swift */, + 6167E6F82B81E95900C3CA2D /* BinaryImage.swift */, ); path = CrashReporting; sourceTree = ""; @@ -7902,9 +7910,11 @@ D2303A03298D5236001A1FA3 /* DDError.swift in Sources */, D23039F4298D5236001A1FA3 /* AnyCodable.swift in Sources */, D29A9F9529DDB1DB005C54A4 /* UIKitExtensions.swift in Sources */, + 6167E6E82B8122E900C3CA2D /* BacktraceReport.swift in Sources */, D2BEEDB52B3360820065F3AC /* URLSessionSwizzler.swift in Sources */, D2EBEE2529BA160F00B15732 /* TraceID.swift in Sources */, D2EBEE2129BA160F00B15732 /* W3CHTTPHeaders.swift in Sources */, + 6167E6F62B81E94C00C3CA2D /* DDThread.swift in Sources */, D2BEEDAC2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */, D23039E3298D5236001A1FA3 /* BatteryStatus.swift in Sources */, D2EBEE2A29BA160F00B15732 /* TracingHTTPHeaders.swift in Sources */, @@ -7953,8 +7963,8 @@ D23039E5298D5236001A1FA3 /* DateProvider.swift in Sources */, D23039E0298D5235001A1FA3 /* DatadogCoreProtocol.swift in Sources */, D23039FD298D5236001A1FA3 /* DataCompression.swift in Sources */, + 6167E6F92B81E95900C3CA2D /* BinaryImage.swift in Sources */, D23039F0298D5236001A1FA3 /* AnyEncoder.swift in Sources */, - 6167E6E52B8120C100C3CA2D /* StackTrace.swift in Sources */, D2A783D429A5309F003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD72A543B3B00446CF9 /* Event.swift in Sources */, 3CBDE68A2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */, @@ -8719,9 +8729,11 @@ D2DA2360298D57AA00C6C7E6 /* DDError.swift in Sources */, D2DA2361298D57AA00C6C7E6 /* AnyCodable.swift in Sources */, D29A9F9629DDB1DB005C54A4 /* UIKitExtensions.swift in Sources */, + 6167E6E92B8122E900C3CA2D /* BacktraceReport.swift in Sources */, D2BEEDB62B3360830065F3AC /* URLSessionSwizzler.swift in Sources */, D2EBEE3329BA161100B15732 /* TraceID.swift in Sources */, D2EBEE2F29BA161100B15732 /* W3CHTTPHeaders.swift in Sources */, + 6167E6F72B81E94C00C3CA2D /* DDThread.swift in Sources */, D2BEEDAD2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */, D2DA2363298D57AA00C6C7E6 /* BatteryStatus.swift in Sources */, D2EBEE3829BA161100B15732 /* TracingHTTPHeaders.swift in Sources */, @@ -8770,8 +8782,8 @@ D2DA237B298D57AA00C6C7E6 /* DateProvider.swift in Sources */, D2DA237C298D57AA00C6C7E6 /* DatadogCoreProtocol.swift in Sources */, D2DA237D298D57AA00C6C7E6 /* DataCompression.swift in Sources */, + 6167E6FA2B81E95900C3CA2D /* BinaryImage.swift in Sources */, D2DA237E298D57AA00C6C7E6 /* AnyEncoder.swift in Sources */, - 6167E6E62B8120C100C3CA2D /* StackTrace.swift in Sources */, D2A783D529A530A0003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD82A543B3B00446CF9 /* Event.swift in Sources */, 3CBDE68B2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */, diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/BacktraceReport.swift b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/BacktraceReport.swift new file mode 100644 index 0000000000..2fb0730ef5 --- /dev/null +++ b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/BacktraceReport.swift @@ -0,0 +1,41 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation + +/// A snapshot of all running threads in the current process. It focuses on tracing back from the error point (where backtrace +/// generation started) to the root cause or the origin of the problem. +/// +/// - Unlike `DDCrashReport`, the backtrace report can be generated on-demand without the actual crash being triggered. +/// - Like in `DDCrashReport`, threads and stacks information in `BacktraceReport` follows the format compatible with Datadog symbolication. +public struct BacktraceReport { + /// The stack trace of the thread for which the backtrace is generated. + public let stack: String + /// Represents all threads currently running in the process. + public let threads: [DDThread] + /// A list of binary images referenced from all stack traces. + public let binaryImages: [BinaryImage] + /// Indicates whether any stack trace information in `threads` was truncated due to stack trace minimization. + public let wasTruncated: Bool + + /// Initializes a new instance of `BacktraceReport`. + /// - Parameters: + /// - stack: The stack trace of the thread. + /// - threads: All threads currently running in the process. + /// - binaryImages: A list of binary images referenced from all stack traces. + /// - wasTruncated: Indicates whether stack trace information was truncated. + public init( + stack: String, + threads: [DDThread], + binaryImages: [BinaryImage], + wasTruncated: Bool + ) { + self.stack = stack + self.threads = threads + self.binaryImages = binaryImages + self.wasTruncated = wasTruncated + } +} diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/StackTrace.swift b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/BinaryImage.swift similarity index 60% rename from DatadogInternal/Sources/FeatureAPIs/CrashReporting/StackTrace.swift rename to DatadogInternal/Sources/FeatureAPIs/CrashReporting/BinaryImage.swift index f62f077e2a..14b31ba2b7 100644 --- a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/StackTrace.swift +++ b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/BinaryImage.swift @@ -6,39 +6,6 @@ import Foundation -/// Unsymbolicated stack trace thread. -public struct DDThread: Codable { - /// The name of the thread, e.g. `"Thread 0"` - public let name: String - /// Unsymbolicated stack trace of the crash. - public let stack: String - /// If the thread was halted. - public let crashed: Bool - /// Thread state (CPU registers dump), only available for halted thread. - public let state: String? - - public init( - name: String, - stack: String, - crashed: Bool, - state: String? - ) { - self.name = name - self.stack = stack - self.crashed = crashed - self.state = state - } - - // MARK: - Encoding - - enum CodingKeys: String, CodingKey { - case name = "name" - case stack = "stack" - case crashed = "crashed" - case state = "state" - } -} - /// Binary Image referenced in frames from `DDThread`. public struct BinaryImage: Codable { public let libraryName: String diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift index 965a05e8d7..0702c1894e 100644 --- a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift +++ b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift @@ -73,7 +73,7 @@ public final class DDCrashReport: NSObject, Codable { public let binaryImages: [BinaryImage] /// Meta information about the crash and process. public let meta: Meta - /// If any stack trace information was truncated due to crash report minimization. + /// If any stack trace information in `threads` was truncated due to stack trace minimization. public let wasTruncated: Bool /// The last context injected through `inject(context:)` public let context: Data? diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDThread.swift b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDThread.swift new file mode 100644 index 0000000000..e7f187fa3b --- /dev/null +++ b/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDThread.swift @@ -0,0 +1,40 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation + +/// Unsymbolicated stack trace of a running thread. +public struct DDThread: Codable { + /// The name of the thread, e.g. `"Thread 0"` + public let name: String + /// Unsymbolicated stack trace of the crash. + public let stack: String + /// If the thread was halted. + public let crashed: Bool + /// Thread state (CPU registers dump), only available for halted thread. + public let state: String? + + public init( + name: String, + stack: String, + crashed: Bool, + state: String? + ) { + self.name = name + self.stack = stack + self.crashed = crashed + self.state = state + } + + // MARK: - Encoding + + enum CodingKeys: String, CodingKey { + case name = "name" + case stack = "stack" + case crashed = "crashed" + case state = "state" + } +} From 9f62c3e7d47a58d1240cdef1200402225bd3bcc2 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Sun, 18 Feb 2024 22:09:13 +0100 Subject: [PATCH 3/7] RUM-2925 Add `BacktraceReporting` capability to `DatadogCore` --- Datadog/Datadog.xcodeproj/project.pbxproj | 26 ++++++++ DatadogCore/Sources/Core/DatadogCore.swift | 3 +- .../Sources/CrashReporting.swift | 4 ++ .../Sources/CrashReportingPlugin.swift | 1 + .../Integrations/BacktraceReporter.swift | 20 ++++++ .../PLCrashReporterIntegration.swift | 17 +++++ .../Sources/ThirdPartyCrashReporter.swift | 7 ++ DatadogCrashReporting/Tests/Mocks.swift | 17 +++++ .../BacktraceReporter.swift | 66 +++++++++++++++++++ .../BacktraceReportingFeature.swift | 26 ++++++++ 10 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 DatadogCrashReporting/Sources/Integrations/BacktraceReporter.swift create mode 100644 DatadogInternal/Sources/BacktraceReporting/BacktraceReporter.swift create mode 100644 DatadogInternal/Sources/BacktraceReporting/BacktraceReportingFeature.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 1c69a66a05..0a6dd6c817 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -303,6 +303,12 @@ 6167E6F72B81E94C00C3CA2D /* DDThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6F52B81E94C00C3CA2D /* DDThread.swift */; }; 6167E6F92B81E95900C3CA2D /* BinaryImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6F82B81E95900C3CA2D /* BinaryImage.swift */; }; 6167E6FA2B81E95900C3CA2D /* BinaryImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6F82B81E95900C3CA2D /* BinaryImage.swift */; }; + 6167E6FD2B81EC0400C3CA2D /* BacktraceReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6FC2B81EC0400C3CA2D /* BacktraceReporter.swift */; }; + 6167E6FE2B81EC0400C3CA2D /* BacktraceReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6FC2B81EC0400C3CA2D /* BacktraceReporter.swift */; }; + 6167E7002B81EF7500C3CA2D /* BacktraceReportingFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6FF2B81EF7500C3CA2D /* BacktraceReportingFeature.swift */; }; + 6167E7012B81EF7500C3CA2D /* BacktraceReportingFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6FF2B81EF7500C3CA2D /* BacktraceReportingFeature.swift */; }; + 6167E7032B81F2EB00C3CA2D /* BacktraceReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */; }; + 6167E7042B81F2EB00C3CA2D /* BacktraceReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */; }; 616B668E259CC28E00968EE8 /* DDRUMMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */; }; 6170DC1C25C18729003AED5C /* PLCrashReporterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6170DC1B25C18729003AED5C /* PLCrashReporterPlugin.swift */; }; 6172472725D673D7007085B3 /* CrashContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6172472625D673D7007085B3 /* CrashContextTests.swift */; }; @@ -2168,6 +2174,9 @@ 6167E6E72B8122E900C3CA2D /* BacktraceReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReport.swift; sourceTree = ""; }; 6167E6F52B81E94C00C3CA2D /* DDThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDThread.swift; sourceTree = ""; }; 6167E6F82B81E95900C3CA2D /* BinaryImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryImage.swift; sourceTree = ""; }; + 6167E6FC2B81EC0400C3CA2D /* BacktraceReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReporter.swift; sourceTree = ""; }; + 6167E6FF2B81EF7500C3CA2D /* BacktraceReportingFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReportingFeature.swift; sourceTree = ""; }; + 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReporter.swift; sourceTree = ""; }; 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDRUMMonitorTests.swift; sourceTree = ""; }; 616C0A9D28573DFF00C13264 /* RUMOperatingSystemInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfo.swift; sourceTree = ""; }; 616C0AA028573F6300C13264 /* RUMOperatingSystemInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfoTests.swift; sourceTree = ""; }; @@ -4340,6 +4349,15 @@ path = CrashReporting; sourceTree = ""; }; + 6167E6FB2B81EBD100C3CA2D /* BacktraceReporting */ = { + isa = PBXGroup; + children = ( + 6167E6FC2B81EC0400C3CA2D /* BacktraceReporter.swift */, + 6167E6FF2B81EF7500C3CA2D /* BacktraceReportingFeature.swift */, + ); + path = BacktraceReporting; + sourceTree = ""; + }; 616CCE11250A181C009FED46 /* Instrumentation */ = { isa = PBXGroup; children = ( @@ -5214,6 +5232,7 @@ D23039BF298D5235001A1FA3 /* MessageBus */, D23039AE298D5235001A1FA3 /* Storage */, D23039CD298D5235001A1FA3 /* Telemetry */, + 6167E6FB2B81EBD100C3CA2D /* BacktraceReporting */, D23039D1298D5235001A1FA3 /* Upload */, D2A783D329A53049003B03BB /* Utils */, D2160CE229C0DFED00FAA9A5 /* Swizzling */, @@ -5382,6 +5401,7 @@ isa = PBXGroup; children = ( 6112B11325C84E7900B37771 /* CrashReportSender.swift */, + 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */, ); path = Integrations; sourceTree = ""; @@ -7799,6 +7819,7 @@ D214DA8329DF2D5E004D0AE8 /* CrashReporting.swift in Sources */, 61F2728B25C9561A00D54BF8 /* PLCrashReporterIntegration.swift in Sources */, D214DA8129DF2D5E004D0AE8 /* CrashReportingPlugin.swift in Sources */, + 6167E7032B81F2EB00C3CA2D /* BacktraceReporter.swift in Sources */, D214DA8A29DF2D6A004D0AE8 /* CrashContext.swift in Sources */, 612556B0268C8D31002BCE74 /* CrashReport.swift in Sources */, D214DA8B29DF2D6A004D0AE8 /* CrashContextProvider.swift in Sources */, @@ -7940,6 +7961,7 @@ D23039F1298D5236001A1FA3 /* AnyDecodable.swift in Sources */, 6167E6E22B81207200C3CA2D /* DDCrashReport.swift in Sources */, D2160CC529C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, + 6167E6FD2B81EC0400C3CA2D /* BacktraceReporter.swift in Sources */, D23039DD298D5235001A1FA3 /* DD.swift in Sources */, D2160C9A29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, D2EBEE2229BA160F00B15732 /* TracePropagationHeadersReader.swift in Sources */, @@ -7949,6 +7971,7 @@ D23039FF298D5236001A1FA3 /* Foundation+Datadog.swift in Sources */, D2F8235329915E12003C7E99 /* DatadogSite.swift in Sources */, D2D3199A29E98D970004F169 /* DefaultJSONEncoder.swift in Sources */, + 6167E7002B81EF7500C3CA2D /* BacktraceReportingFeature.swift in Sources */, D2EBEE2729BA160F00B15732 /* B3HTTPHeadersWriter.swift in Sources */, D23039E2298D5236001A1FA3 /* UserInfo.swift in Sources */, D23039FB298D5236001A1FA3 /* URLRequestBuilder.swift in Sources */, @@ -8678,6 +8701,7 @@ D214DA8429DF2D5E004D0AE8 /* CrashReporting.swift in Sources */, D2CB6FC327C5348200A62B57 /* PLCrashReporterIntegration.swift in Sources */, D214DA8229DF2D5E004D0AE8 /* CrashReportingPlugin.swift in Sources */, + 6167E7042B81F2EB00C3CA2D /* BacktraceReporter.swift in Sources */, D214DA8D29DF2D6B004D0AE8 /* CrashContext.swift in Sources */, D2CB6FC427C5348200A62B57 /* CrashReport.swift in Sources */, D214DA8E29DF2D6B004D0AE8 /* CrashContextProvider.swift in Sources */, @@ -8759,6 +8783,7 @@ D2DA2370298D57AA00C6C7E6 /* AnyDecodable.swift in Sources */, 6167E6E32B81207200C3CA2D /* DDCrashReport.swift in Sources */, D2160CC629C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, + 6167E6FE2B81EC0400C3CA2D /* BacktraceReporter.swift in Sources */, D2DA2372298D57AA00C6C7E6 /* DD.swift in Sources */, D2160C9B29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, D2EBEE3029BA161100B15732 /* TracePropagationHeadersReader.swift in Sources */, @@ -8768,6 +8793,7 @@ D2DA2375298D57AA00C6C7E6 /* Foundation+Datadog.swift in Sources */, D2F8235429915E12003C7E99 /* DatadogSite.swift in Sources */, D2D3199B29E98D970004F169 /* DefaultJSONEncoder.swift in Sources */, + 6167E7012B81EF7500C3CA2D /* BacktraceReportingFeature.swift in Sources */, D2EBEE3529BA161100B15732 /* B3HTTPHeadersWriter.swift in Sources */, D2DA2376298D57AA00C6C7E6 /* UserInfo.swift in Sources */, D2DA2377298D57AA00C6C7E6 /* URLRequestBuilder.swift in Sources */, diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index 90b71373db..89190f42b2 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -229,8 +229,6 @@ extension DatadogCore: DatadogCoreProtocol { /// /// - Parameter feature: The Feature instance. func register(feature: T) throws where T: DatadogFeature { - let featureDirectories = try directory.getFeatureDirectories(forFeatureNamed: T.name) - let performancePreset: PerformancePreset if let override = feature.performanceOverride { performancePreset = performance.updated(with: override) @@ -239,6 +237,7 @@ extension DatadogCore: DatadogCoreProtocol { } if let feature = feature as? DatadogRemoteFeature { + let featureDirectories = try directory.getFeatureDirectories(forFeatureNamed: T.name) let storage = FeatureStorage( featureName: T.name, queue: readWriteQueue, diff --git a/DatadogCrashReporting/Sources/CrashReporting.swift b/DatadogCrashReporting/Sources/CrashReporting.swift index dc18fe898d..e70271d724 100644 --- a/DatadogCrashReporting/Sources/CrashReporting.swift +++ b/DatadogCrashReporting/Sources/CrashReporting.swift @@ -38,6 +38,10 @@ public final class CrashReporting { try core.register(feature: reporter) + if let plcr = PLCrashReporterPlugin.thirdPartyCrashReporter { + try core.register(backtraceReporter: BacktraceReporter(reporter: plcr)) + } + reporter.sendCrashReportIfFound() core.telemetry diff --git a/DatadogCrashReporting/Sources/CrashReportingPlugin.swift b/DatadogCrashReporting/Sources/CrashReportingPlugin.swift index 39c7f8308b..dfc1d71e60 100644 --- a/DatadogCrashReporting/Sources/CrashReportingPlugin.swift +++ b/DatadogCrashReporting/Sources/CrashReportingPlugin.swift @@ -4,6 +4,7 @@ * Copyright 2019-Present Datadog, Inc. */ +import Foundation import DatadogInternal /// An interface for enabling crash reporting feature in Datadog SDK. diff --git a/DatadogCrashReporting/Sources/Integrations/BacktraceReporter.swift b/DatadogCrashReporting/Sources/Integrations/BacktraceReporter.swift new file mode 100644 index 0000000000..88f73ee273 --- /dev/null +++ b/DatadogCrashReporting/Sources/Integrations/BacktraceReporter.swift @@ -0,0 +1,20 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import DatadogInternal + +internal struct BacktraceReporter: DatadogInternal.BacktraceReporting { + let reporter: ThirdPartyCrashReporter + + func generateBacktrace() -> DatadogInternal.BacktraceReport? { + do { + return try reporter.generateBacktrace() + } catch let error { + DD.logger.error("Encountered an error when generating backtrace", error: error) + return nil + } + } +} diff --git a/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift b/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift index e7e80067f4..46823ab8cb 100644 --- a/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift +++ b/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift @@ -57,4 +57,21 @@ internal final class PLCrashReporterIntegration: ThirdPartyCrashReporter { func purgePendingCrashReport() throws { try crashReporter.purgePendingCrashReportAndReturnError() } + + func generateBacktrace() throws -> BacktraceReport { + let liveReportData = try crashReporter.generateLiveReportAndReturnError() + let liveReport = try PLCrashReport(data: liveReportData) + + // This is quite opportunistic - we map PLCR's live report through existing `DDCrashReport` builder to + // then extract essential elements for assembling `BacktraceReport`. It works for now, but be careful + // with how this evolves. We may need a dedicated `BacktraceReport` builder that only shares some code + // with `DDCrashReport` builder. + let crashReport = try builder.createDDCrashReport(from: liveReport) + return BacktraceReport( + stack: crashReport.stack, + threads: crashReport.threads, + binaryImages: crashReport.binaryImages, + wasTruncated: crashReport.wasTruncated + ) + } } diff --git a/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift b/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift index 3c1b749421..c7a20468e9 100644 --- a/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift +++ b/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift @@ -4,6 +4,7 @@ * Copyright 2019-Present Datadog, Inc. */ +import Foundation import DatadogInternal /// An interface of 3rd party crash reporter used by the DatadogCrashReporting. @@ -11,6 +12,8 @@ internal protocol ThirdPartyCrashReporter { /// Initializes and enables the crash reporter. init() throws + // MARK: - Crash Reporting + /// Tells if there is a crash report available. func hasPendingCrashReport() -> Bool @@ -22,4 +25,8 @@ internal protocol ThirdPartyCrashReporter { /// Deletes the available crash report. func purgePendingCrashReport() throws + + // MARK: - Backtrace Generation + + func generateBacktrace() throws -> BacktraceReport } diff --git a/DatadogCrashReporting/Tests/Mocks.swift b/DatadogCrashReporting/Tests/Mocks.swift index 9a8fb07581..0b513b429d 100644 --- a/DatadogCrashReporting/Tests/Mocks.swift +++ b/DatadogCrashReporting/Tests/Mocks.swift @@ -20,6 +20,8 @@ internal class ThirdPartyCrashReporterMock: ThirdPartyCrashReporter { var hasPurgedPendingCrashReport = false var hasPurgedPendingCrashReportError: Error? + var generatedBacktrace: BacktraceReport = .mockAny() + required init() throws { if let error = ThirdPartyCrashReporterMock.initializationError { throw error @@ -47,6 +49,21 @@ internal class ThirdPartyCrashReporterMock: ThirdPartyCrashReporter { } hasPurgedPendingCrashReport = true } + + func generateBacktrace() throws -> BacktraceReport { + return generatedBacktrace + } +} + +internal extension BacktraceReport { + static func mockAny() -> BacktraceReport { + return BacktraceReport( + stack: "any", + threads: [], + binaryImages: [], + wasTruncated: false + ) + } } internal extension DDCrashReport { diff --git a/DatadogInternal/Sources/BacktraceReporting/BacktraceReporter.swift b/DatadogInternal/Sources/BacktraceReporting/BacktraceReporter.swift new file mode 100644 index 0000000000..060e46ccca --- /dev/null +++ b/DatadogInternal/Sources/BacktraceReporting/BacktraceReporter.swift @@ -0,0 +1,66 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation + +/// A protocol for types capable of generating backtrace reports. +public protocol BacktraceReporting { + /// Generates a backtrace report. + /// - Returns: A `BacktraceReport` containing information about the current state of all running threads in the process, + /// focusing on tracing back from the error point to the root cause or the origin of the problem. Returns `nil` if + /// the backtrace report cannot be generated. + func generateBacktrace() -> BacktraceReport? +} + +internal struct CoreBacktraceReporter: BacktraceReporting { + /// A weak core reference. + private weak var core: DatadogCoreProtocol? + + /// Creates backtrace reporter associated with a core instance. + /// + /// The `CoreBacktraceReporter` keeps a weak reference to the provided core. + /// + /// - Parameter core: The core instance. + init(core: DatadogCoreProtocol) { + self.core = core + } + + func generateBacktrace() -> BacktraceReport? { + guard let core = core else { + return nil + } + guard let backtraceFeature = core.get(feature: BacktraceReportingFeature.self) else { + DD.logger.warn( + """ + Backtrace will not be generated as this capability is not available. + Enable `DatadogCrashReporting` to leverage backtrace generation. + """ + ) + return nil + } + return backtraceFeature.generateBacktrace() + } +} + +/// Adds capability of reporting backtraces. +extension DatadogCoreProtocol { + /// Registers backtrace reporter in Core. + /// - Parameter backtraceReporter: the implementation of backtrace reporter. + public func register(backtraceReporter: BacktraceReporting) throws { + guard get(feature: BacktraceReportingFeature.self) == nil else { + DD.logger.debug("Backtrace reporter is already registered to this core. Skipping registration of next one.") + return + } + + let feature = BacktraceReportingFeature(reporter: backtraceReporter) + try register(feature: feature) + } + + /// Backtrace reporter. Use it to snapshot all running threads in the current process. + /// + /// It requires `BacktraceReportingFeature` registered to Datadog core. Otherwise reported backtraces will be `nil`. + public var backtraceReporter: BacktraceReporting { CoreBacktraceReporter(core: self) } +} diff --git a/DatadogInternal/Sources/BacktraceReporting/BacktraceReportingFeature.swift b/DatadogInternal/Sources/BacktraceReporting/BacktraceReportingFeature.swift new file mode 100644 index 0000000000..0efdb1f59a --- /dev/null +++ b/DatadogInternal/Sources/BacktraceReporting/BacktraceReportingFeature.swift @@ -0,0 +1,26 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation + +internal final class BacktraceReportingFeature: DatadogFeature { + static var name: String = "backtrace-reporting" + + let messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver() + + /// A type capable of generating backtrace reports. + private let reporter: BacktraceReporting + + /// Creates `BacktraceReportingFeature`. + /// - Parameter reporter: An external implementation of a type capable of generating backtrace reports. + init(reporter: BacktraceReporting) { + self.reporter = reporter + } + + internal func generateBacktrace() -> BacktraceReport? { + reporter.generateBacktrace() + } +} From 17a9b03627a25f09db0bb9be4ff4b2168435316f Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 19 Feb 2024 08:22:50 +0100 Subject: [PATCH 4/7] RUM-2925 Add tests for backtrace generation --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 ++ .../GeneratingBacktraceTests.swift | 61 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 0a6dd6c817..f73382c72e 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -309,6 +309,8 @@ 6167E7012B81EF7500C3CA2D /* BacktraceReportingFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6FF2B81EF7500C3CA2D /* BacktraceReportingFeature.swift */; }; 6167E7032B81F2EB00C3CA2D /* BacktraceReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */; }; 6167E7042B81F2EB00C3CA2D /* BacktraceReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */; }; + 6167E7062B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7052B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift */; }; + 6167E7072B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7052B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift */; }; 616B668E259CC28E00968EE8 /* DDRUMMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */; }; 6170DC1C25C18729003AED5C /* PLCrashReporterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6170DC1B25C18729003AED5C /* PLCrashReporterPlugin.swift */; }; 6172472725D673D7007085B3 /* CrashContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6172472625D673D7007085B3 /* CrashContextTests.swift */; }; @@ -2177,6 +2179,7 @@ 6167E6FC2B81EC0400C3CA2D /* BacktraceReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReporter.swift; sourceTree = ""; }; 6167E6FF2B81EF7500C3CA2D /* BacktraceReportingFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReportingFeature.swift; sourceTree = ""; }; 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReporter.swift; sourceTree = ""; }; + 6167E7052B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratingBacktraceTests.swift; sourceTree = ""; }; 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDRUMMonitorTests.swift; sourceTree = ""; }; 616C0A9D28573DFF00C13264 /* RUMOperatingSystemInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfo.swift; sourceTree = ""; }; 616C0AA028573F6300C13264 /* RUMOperatingSystemInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfoTests.swift; sourceTree = ""; }; @@ -4448,6 +4451,7 @@ isa = PBXGroup; children = ( 6179DB552B6022EA00E9E04E /* SendingCrashReportTests.swift */, + 6167E7052B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift */, ); path = CrashReporting; sourceTree = ""; @@ -7517,6 +7521,7 @@ D20605B92875729E0047275C /* ContextValuePublisherMock.swift in Sources */, D24C9C4D29A7BA3F002057CF /* LogsMocks.swift in Sources */, 61B5E42B26DFC433000B0A5F /* DDNSURLSessionDelegate+apiTests.m in Sources */, + 6167E7062B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift in Sources */, D20605C52875895E0047275C /* KronosClockMock.swift in Sources */, 61133C642423990D00786299 /* LoggerTests.swift in Sources */, 6134CDB12A691E850061CCD9 /* CoreMetricsTests.swift in Sources */, @@ -8570,6 +8575,7 @@ D24C9C6529A7CB7D002057CF /* CrashLogReceiverTests.swift in Sources */, D29A9FD129DDC590005C54A4 /* RUMFeatureTests.swift in Sources */, D2CB6F1027C520D400A62B57 /* DDNSURLSessionDelegateTests.swift in Sources */, + 6167E7072B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift in Sources */, D2CB6F1327C520D400A62B57 /* DDConfigurationTests.swift in Sources */, D2CB6F1727C520D400A62B57 /* ObjcExceptionHandlerTests.swift in Sources */, D28F836B29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift in Sources */, diff --git a/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift b/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift new file mode 100644 index 0000000000..96a56c1625 --- /dev/null +++ b/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift @@ -0,0 +1,61 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +import DatadogCrashReporting + +/// Tests integration of `DatadogCore` and `DatadogCrashReporting` for backtrace generation. +class GeneratingBacktraceTests: XCTestCase { + private var core: DatadogCoreProxy! // swiftlint:disable:this implicitly_unwrapped_optional + + override func setUp() { + super.setUp() + core = DatadogCoreProxy(context: .mockWith(trackingConsent: .granted)) + } + + override func tearDown() { + core.flushAndTearDown() + core = nil + super.tearDown() + } + + func testGivenCrashReportingIsEnabled_thenCoreCanGenerateBacktrace() throws { + // Given + CrashReporting.enable(in: core) + + // When + let backtrace = try XCTUnwrap(core.backtraceReporter.generateBacktrace()) + + // Then + XCTAssertGreaterThan(backtrace.threads.count, 0, "Some thread(s) should be recorded") + XCTAssertGreaterThan(backtrace.binaryImages.count, 0, "Some binary image(s) should be recorded") + + XCTAssertTrue( + backtrace.stack.contains("DatadogCoreTests"), + "Backtrace stack should include at least one frame from `DatadogCoreTests` image" + ) + XCTAssertTrue( + backtrace.stack.contains("XCTest"), + "Backtrace stack should include at least one frame from `XCTest` image" + ) + #if os(iOS) + XCTAssertTrue( + backtrace.binaryImages.contains(where: { $0.libraryName == "DatadogCoreTests iOS" }), + "Backtrace should include the image for `DatadogCoreTests iOS`" + ) + #elseif os(tvOS) + XCTAssertTrue( + backtrace.binaryImages.contains(where: { $0.libraryName == "DatadogCoreTests tvOS" }), + "Backtrace should include the image for `DatadogCoreTests tvOS`" + ) + #endif + XCTAssertTrue( + // Assert on prefix as it is `XCTestCore` on iOS 15+ and `XCTest` earlier: + backtrace.binaryImages.contains(where: { $0.libraryName.hasPrefix("XCTest") }), + "Backtrace should include the image for `XCTest`" + ) + } +} From 4c4fe4153800a3946796ec69fda788e7f365ef07 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 19 Feb 2024 11:35:20 +0100 Subject: [PATCH 5/7] RUM-2925 Add test for feature directories creation in Remote vs not Feature --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 ++ .../GeneratingBacktraceTests.swift | 2 + DatadogCore/Sources/Core/DatadogCore.swift | 15 ++-- .../DatadogCore+FeatureDirectoriesTests.swift | 71 +++++++++++++++++++ 4 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 DatadogCore/Tests/Datadog/DatadogCore/DatadogCore+FeatureDirectoriesTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index f73382c72e..bdd3245ddf 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -311,6 +311,8 @@ 6167E7042B81F2EB00C3CA2D /* BacktraceReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */; }; 6167E7062B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7052B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift */; }; 6167E7072B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7052B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift */; }; + 6167E70E2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E70D2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift */; }; + 6167E70F2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E70D2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift */; }; 616B668E259CC28E00968EE8 /* DDRUMMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */; }; 6170DC1C25C18729003AED5C /* PLCrashReporterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6170DC1B25C18729003AED5C /* PLCrashReporterPlugin.swift */; }; 6172472725D673D7007085B3 /* CrashContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6172472625D673D7007085B3 /* CrashContextTests.swift */; }; @@ -2180,6 +2182,7 @@ 6167E6FF2B81EF7500C3CA2D /* BacktraceReportingFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReportingFeature.swift; sourceTree = ""; }; 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReporter.swift; sourceTree = ""; }; 6167E7052B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratingBacktraceTests.swift; sourceTree = ""; }; + 6167E70D2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatadogCore+FeatureDirectoriesTests.swift"; sourceTree = ""; }; 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDRUMMonitorTests.swift; sourceTree = ""; }; 616C0A9D28573DFF00C13264 /* RUMOperatingSystemInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfo.swift; sourceTree = ""; }; 616C0AA028573F6300C13264 /* RUMOperatingSystemInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfoTests.swift; sourceTree = ""; }; @@ -5832,6 +5835,7 @@ children = ( 617699162A8608C20030022B /* Context */, 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */, + 6167E70D2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift */, D21C26D028A64599005DD405 /* MessageBusTests.swift */, ); path = DatadogCore; @@ -7458,6 +7462,7 @@ 61A2CC212A443D330000FF25 /* DDRUMConfigurationTests.swift in Sources */, D2A434AE2A8E426C0028E329 /* DDSessionReplayTests.swift in Sources */, 61D03BE0273404E700367DE0 /* RUMDataModels+objcTests.swift in Sources */, + 6167E70E2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift in Sources */, E143CCAF27D236F600F4018A /* CITestIntegrationTests.swift in Sources */, D224430D29E95D6700274EC7 /* CrashReportReceiverTests.swift in Sources */, D234613228B7713000055D4C /* FeatureContextTests.swift in Sources */, @@ -8650,6 +8655,7 @@ D21831562B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift in Sources */, 61DA8CB928647A500074A606 /* InternalLoggerTests.swift in Sources */, D2CB6F7C27C520D400A62B57 /* CrashReporterTests.swift in Sources */, + 6167E70F2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift in Sources */, D2CB6F7D27C520D400A62B57 /* CrashContextTests.swift in Sources */, D28F836629C9E6A200EF8EA2 /* DatadogTraceFeatureTests.swift in Sources */, 612C13D72AAB35EB0086B5D1 /* SRSegmentMatcher.swift in Sources */, diff --git a/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift b/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift index 96a56c1625..cd595d2b82 100644 --- a/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift +++ b/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift @@ -6,6 +6,7 @@ import XCTest import DatadogCrashReporting +@testable import DatadogInternal /// Tests integration of `DatadogCore` and `DatadogCrashReporting` for backtrace generation. class GeneratingBacktraceTests: XCTestCase { @@ -25,6 +26,7 @@ class GeneratingBacktraceTests: XCTestCase { func testGivenCrashReportingIsEnabled_thenCoreCanGenerateBacktrace() throws { // Given CrashReporting.enable(in: core) + XCTAssertNotNil(core.get(feature: BacktraceReportingFeature.self), "`BacktraceReportingFeature` is registered") // When let backtrace = try XCTUnwrap(core.backtraceReporter.generateBacktrace()) diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index 89190f42b2..60f5db0f72 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -229,15 +229,16 @@ extension DatadogCore: DatadogCoreProtocol { /// /// - Parameter feature: The Feature instance. func register(feature: T) throws where T: DatadogFeature { - let performancePreset: PerformancePreset - if let override = feature.performanceOverride { - performancePreset = performance.updated(with: override) - } else { - performancePreset = performance - } - if let feature = feature as? DatadogRemoteFeature { let featureDirectories = try directory.getFeatureDirectories(forFeatureNamed: T.name) + + let performancePreset: PerformancePreset + if let override = feature.performanceOverride { + performancePreset = performance.updated(with: override) + } else { + performancePreset = performance + } + let storage = FeatureStorage( featureName: T.name, queue: readWriteQueue, diff --git a/DatadogCore/Tests/Datadog/DatadogCore/DatadogCore+FeatureDirectoriesTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/DatadogCore+FeatureDirectoriesTests.swift new file mode 100644 index 0000000000..e1461de5ba --- /dev/null +++ b/DatadogCore/Tests/Datadog/DatadogCore/DatadogCore+FeatureDirectoriesTests.swift @@ -0,0 +1,71 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +import DatadogInternal +@testable import DatadogCore + +private struct RemoteFeatureMock: DatadogRemoteFeature { + static let name: String = "remote-feature-mock" + + var requestBuilder: FeatureRequestBuilder = FeatureRequestBuilderMock() + var messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver() +} + +private struct FeatureMock: DatadogFeature { + static let name: String = "feature-mock" + + var messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver() +} + +class DatadogCore_FeatureDirectoriesTests: XCTestCase { + private var core: DatadogCore! // swiftlint:disable:this implicitly_unwrapped_optional + + override func setUp() { + super.setUp() + temporaryCoreDirectory.create() + core = DatadogCore( + directory: temporaryCoreDirectory, + dateProvider: SystemDateProvider(), + initialConsent: .mockRandom(), + performance: .mockRandom(), + httpClient: HTTPClientMock(), + encryption: nil, + contextProvider: .mockAny(), + applicationVersion: .mockAny(), + maxBatchesPerUpload: .mockRandom(min: 1, max: 100), + backgroundTasksEnabled: .mockAny() + ) + } + + override func tearDown() { + core.flushAndTearDown() + core = nil + temporaryCoreDirectory.delete() + super.tearDown() + } + + func testWhenRegisteringRemoteFeature_itCreatesFeatureDirectories() throws { + // When + try core.register(feature: RemoteFeatureMock()) + + // Then + let featureDirectory = try temporaryCoreDirectory.coreDirectory.subdirectory(path: RemoteFeatureMock.name) + XCTAssertNoThrow(try featureDirectory.subdirectory(path: "v2"), "Authorized data directory must exist") + XCTAssertNoThrow(try featureDirectory.subdirectory(path: "intermediate-v2"), "Intermediate data directory must exist") + } + + func testWhenRegisteringFeature_itDoesNotCreateFeatureDirectories() throws { + // When + try core.register(feature: FeatureMock()) + + // Then + XCTAssertThrowsError( + try temporaryCoreDirectory.coreDirectory.subdirectory(path: FeatureMock.name), + "Feature directory must not exist" + ) + } +} From bcd14284da94a2ccb440a5c58e76ac4aae80f76c Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 20 Feb 2024 09:12:17 +0100 Subject: [PATCH 6/7] RUM-2925 CR feedback - rename `DatadogInternal/*/FeatureAPI` to `*/Models` --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 +++--- .../CrashReporting/BacktraceReport.swift | 0 .../CrashReporting/BinaryImage.swift | 0 .../CrashReporting/DDCrashReport.swift | 0 .../{FeatureAPIs => Models}/CrashReporting/DDThread.swift | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename DatadogInternal/Sources/{FeatureAPIs => Models}/CrashReporting/BacktraceReport.swift (100%) rename DatadogInternal/Sources/{FeatureAPIs => Models}/CrashReporting/BinaryImage.swift (100%) rename DatadogInternal/Sources/{FeatureAPIs => Models}/CrashReporting/DDCrashReport.swift (100%) rename DatadogInternal/Sources/{FeatureAPIs => Models}/CrashReporting/DDThread.swift (100%) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index bdd3245ddf..08bb152f96 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -4336,12 +4336,12 @@ path = AppHangs; sourceTree = ""; }; - 6167E6DF2B81203A00C3CA2D /* FeatureAPIs */ = { + 6167E6DF2B81203A00C3CA2D /* Models */ = { isa = PBXGroup; children = ( 6167E6E02B81204B00C3CA2D /* CrashReporting */, ); - path = FeatureAPIs; + path = Models; sourceTree = ""; }; 6167E6E02B81204B00C3CA2D /* CrashReporting */ = { @@ -5226,7 +5226,7 @@ D23039A6298D513D001A1FA3 /* DatadogInternal */ = { isa = PBXGroup; children = ( - 6167E6DF2B81203A00C3CA2D /* FeatureAPIs */, + 6167E6DF2B81203A00C3CA2D /* Models */, D23039CA298D5235001A1FA3 /* Attributes */, D23039C3298D5235001A1FA3 /* Codable */, D23039DA298D5235001A1FA3 /* Concurrency */, diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/BacktraceReport.swift b/DatadogInternal/Sources/Models/CrashReporting/BacktraceReport.swift similarity index 100% rename from DatadogInternal/Sources/FeatureAPIs/CrashReporting/BacktraceReport.swift rename to DatadogInternal/Sources/Models/CrashReporting/BacktraceReport.swift diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/BinaryImage.swift b/DatadogInternal/Sources/Models/CrashReporting/BinaryImage.swift similarity index 100% rename from DatadogInternal/Sources/FeatureAPIs/CrashReporting/BinaryImage.swift rename to DatadogInternal/Sources/Models/CrashReporting/BinaryImage.swift diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift b/DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift similarity index 100% rename from DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDCrashReport.swift rename to DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift diff --git a/DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDThread.swift b/DatadogInternal/Sources/Models/CrashReporting/DDThread.swift similarity index 100% rename from DatadogInternal/Sources/FeatureAPIs/CrashReporting/DDThread.swift rename to DatadogInternal/Sources/Models/CrashReporting/DDThread.swift From 15687914d290118c076056123456c88a8d9d5158 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 20 Feb 2024 09:12:32 +0100 Subject: [PATCH 7/7] RUM-2925 Make `DDCrashReport` a struct as in V2 it no longer requires visibility in Obj-c API --- .../Tests/Datadog/CrashReporting/CrashReporterTests.swift | 2 +- DatadogCrashReporting/Sources/CrashReportingPlugin.swift | 1 - DatadogCrashReporting/Tests/CrashReportingPluginTests.swift | 4 ++-- .../Sources/Models/CrashReporting/DDCrashReport.swift | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/DatadogCore/Tests/Datadog/CrashReporting/CrashReporterTests.swift b/DatadogCore/Tests/Datadog/CrashReporting/CrashReporterTests.swift index e4da2de8ff..f4199e6640 100644 --- a/DatadogCore/Tests/Datadog/CrashReporting/CrashReporterTests.swift +++ b/DatadogCore/Tests/Datadog/CrashReporting/CrashReporterTests.swift @@ -40,7 +40,7 @@ class CrashReporterTests: XCTestCase { feature.sendCrashReportIfFound() waitForExpectations(timeout: 0.5, handler: nil) - XCTAssertEqual(sender.sentCrashReport, crashReport, "It should send the crash report retrieved from the `plugin`") + DDAssertReflectionEqual(sender.sentCrashReport, crashReport, "It should send the crash report retrieved from the `plugin`") let sentCrashContext = try XCTUnwrap(sender.sentCrashContext, "It should send the crash context") DDAssertDictionariesEqual( try sentCrashContext.data.toJSONObject(), diff --git a/DatadogCrashReporting/Sources/CrashReportingPlugin.swift b/DatadogCrashReporting/Sources/CrashReportingPlugin.swift index dfc1d71e60..0afde4208e 100644 --- a/DatadogCrashReporting/Sources/CrashReportingPlugin.swift +++ b/DatadogCrashReporting/Sources/CrashReportingPlugin.swift @@ -10,7 +10,6 @@ import DatadogInternal /// An interface for enabling crash reporting feature in Datadog SDK. /// /// The SDK calls each API on a background thread and succeeding calls are synchronized. -@objc internal protocol CrashReportingPlugin: AnyObject { /// Reads unprocessed crash report if available. /// - Parameter completion: the completion block called with the value of `DDCrashReport` if a crash report is available diff --git a/DatadogCrashReporting/Tests/CrashReportingPluginTests.swift b/DatadogCrashReporting/Tests/CrashReportingPluginTests.swift index f4dbe1e01e..0ab2e26187 100644 --- a/DatadogCrashReporting/Tests/CrashReportingPluginTests.swift +++ b/DatadogCrashReporting/Tests/CrashReportingPluginTests.swift @@ -34,7 +34,7 @@ class CrashReportingPluginTests: XCTestCase { // When plugin.readPendingCrashReport { crashReport in - XCTAssertEqual(crashReport, crashReporter.pendingCrashReport) + DDAssertReflectionEqual(crashReport, crashReporter.pendingCrashReport) expectation.fulfill() return true // the caller succeeded in processing the crash report } @@ -55,7 +55,7 @@ class CrashReportingPluginTests: XCTestCase { // When plugin.readPendingCrashReport { crashReport in - XCTAssertEqual(crashReport, crashReporter.pendingCrashReport) + DDAssertReflectionEqual(crashReport, crashReporter.pendingCrashReport) expectation.fulfill() return true } diff --git a/DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift b/DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift index 0702c1894e..2b4fa13a62 100644 --- a/DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift +++ b/DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift @@ -7,8 +7,7 @@ import Foundation /// Crash Report format supported by Datadog SDK. -@objc -public final class DDCrashReport: NSObject, Codable { +public struct DDCrashReport: Codable { /// Meta information about the process. /// Ref.: https://developer.apple.com/documentation/xcode/examining-the-fields-in-a-crash-report public struct Meta: Codable {