From 4bfbd03651a18f8320b7f1e9120ff47fcf527961 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 2 Jan 2024 12:00:54 +0100 Subject: [PATCH 001/153] RUM-1836 feat(otel-tracer): take dependency on opentelemetry-swift --- Package.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Package.swift b/Package.swift index 759948fcec..e015a74ec5 100644 --- a/Package.swift +++ b/Package.swift @@ -45,6 +45,7 @@ let package = Package( ], dependencies: [ .package(name: "PLCrashReporter", url: "https://github.com/microsoft/plcrashreporter.git", from: "1.11.1"), + .package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", from: "1.8.0") ], targets: [ .target( @@ -105,6 +106,7 @@ let package = Package( name: "DatadogTrace", dependencies: [ .target(name: "DatadogInternal"), + .product(name: "OpenTelemetryApi", package: "opentelemetry-swift") ], path: "DatadogTrace/Sources" ), From 05272ecd6858ea68e450240e7994075c42b0a4c9 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 3 Jan 2024 11:55:10 +0100 Subject: [PATCH 002/153] RUM-1836 feat(otel-tracer): update project with opentelemetry-swift dependency --- Datadog/Datadog.xcodeproj/project.pbxproj | 34 +++++++++++++++++++++++ DatadogTrace/Sources/Trace.swift | 1 + DatadogTrace/Tests/TraceTests.swift | 1 + 3 files changed, 36 insertions(+) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 44cea6870f..6845215121 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -34,6 +34,8 @@ 3C394EFA2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */; }; 3C394EFB2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */; }; 3C41693C29FBF4D50042B9D2 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; + 3C6C7FDB2B45738C006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDA2B45738C006F5CBC /* OpenTelemetryApi */; }; + 3C6C7FDD2B457392006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDC2B457392006F5CBC /* OpenTelemetryApi */; }; 3C74305C29FBC0480053B80F /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2DA2385298D57AA00C6C7E6 /* DatadogInternal.framework */; }; 3C85D42129F7C5C900AFF894 /* WebViewTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */; }; 3C85D42A29F7C70300AFF894 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257953E298ABA65008A1BE5 /* TestUtilities.framework */; }; @@ -2945,6 +2947,7 @@ buildActionMask = 2147483647; files = ( D2C1A50E29C4C4EF00946C31 /* DatadogInternal.framework in Frameworks */, + 3C6C7FDB2B45738C006F5CBC /* OpenTelemetryApi in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2988,6 +2991,7 @@ buildActionMask = 2147483647; files = ( D2C1A57429C4F30000946C31 /* DatadogInternal.framework in Frameworks */, + 3C6C7FDD2B457392006F5CBC /* OpenTelemetryApi in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6468,6 +6472,9 @@ D2C1A51129C4C4EF00946C31 /* PBXTargetDependency */, ); name = "DatadogTrace iOS"; + packageProductDependencies = ( + 3C6C7FDA2B45738C006F5CBC /* OpenTelemetryApi */, + ); productName = DatadogTrace; productReference = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; productType = "com.apple.product-type.framework"; @@ -6560,6 +6567,9 @@ D2C1A57729C4F30000946C31 /* PBXTargetDependency */, ); name = "DatadogTrace tvOS"; + packageProductDependencies = ( + 3C6C7FDC2B457392006F5CBC /* OpenTelemetryApi */, + ); productName = DatadogTrace; productReference = D2C1A55A29C4F2DF00946C31 /* DatadogTrace.framework */; productType = "com.apple.product-type.framework"; @@ -6840,6 +6850,9 @@ Base, ); mainGroup = 61133B78242393DE00786299; + packageReferences = ( + 3C6C7FD92B457381006F5CBC /* XCRemoteSwiftPackageReference "opentelemetry-swift" */, + ); productRefGroup = 61133B83242393DE00786299 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -13073,7 +13086,28 @@ }; /* End XCConfigurationList section */ +/* Begin XCRemoteSwiftPackageReference section */ + 3C6C7FD92B457381006F5CBC /* XCRemoteSwiftPackageReference "opentelemetry-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/open-telemetry/opentelemetry-swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.9.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ + 3C6C7FDA2B45738C006F5CBC /* OpenTelemetryApi */ = { + isa = XCSwiftPackageProductDependency; + package = 3C6C7FD92B457381006F5CBC /* XCRemoteSwiftPackageReference "opentelemetry-swift" */; + productName = OpenTelemetryApi; + }; + 3C6C7FDC2B457392006F5CBC /* OpenTelemetryApi */ = { + isa = XCSwiftPackageProductDependency; + package = 3C6C7FD92B457381006F5CBC /* XCRemoteSwiftPackageReference "opentelemetry-swift" */; + productName = OpenTelemetryApi; + }; 6152C83D24BE1C91006A1679 /* HTTPServerMock */ = { isa = XCSwiftPackageProductDependency; productName = HTTPServerMock; diff --git a/DatadogTrace/Sources/Trace.swift b/DatadogTrace/Sources/Trace.swift index 46f830fc00..b25d69ba5e 100644 --- a/DatadogTrace/Sources/Trace.swift +++ b/DatadogTrace/Sources/Trace.swift @@ -6,6 +6,7 @@ import Foundation import DatadogInternal +import OpenTelemetryApi /// An entry point to Datadog Trace feature. public enum Trace { diff --git a/DatadogTrace/Tests/TraceTests.swift b/DatadogTrace/Tests/TraceTests.swift index b778fdafcd..1bd6f95913 100644 --- a/DatadogTrace/Tests/TraceTests.swift +++ b/DatadogTrace/Tests/TraceTests.swift @@ -6,6 +6,7 @@ import XCTest import TestUtilities +import OpenTelemetryApi @testable import DatadogInternal @testable import DatadogTrace From 4b61a0a2557e89861a2dc4a0bcd294606664eff0 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 2 Jan 2024 14:31:19 +0100 Subject: [PATCH 003/153] RUM-1836 feat(otel-tracer): scaffold conformance to otel Tracer, SpanBuilder and Span --- DatadogTrace/Sources/DDNoOps.swift | 9 +- DatadogTrace/Sources/DatadogTracer.swift | 9 +- .../Sources/OpenTelemetry/OTelNoOpSpan.swift | 47 ++++++++++ .../OpenTelemetry/OTelNoOpSpanBuilder.swift | 50 ++++++++++ .../Sources/OpenTelemetry/OTelSpan.swift | 94 +++++++++++++++++++ .../OpenTelemetry/OTelSpanBuilder.swift | 50 ++++++++++ DatadogTrace/Sources/Tracer.swift | 3 +- 7 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift create mode 100644 DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift create mode 100644 DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift create mode 100644 DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift diff --git a/DatadogTrace/Sources/DDNoOps.swift b/DatadogTrace/Sources/DDNoOps.swift index 1f013b0161..fe71dc1fc3 100644 --- a/DatadogTrace/Sources/DDNoOps.swift +++ b/DatadogTrace/Sources/DDNoOps.swift @@ -6,6 +6,7 @@ import Foundation import DatadogInternal +import OpenTelemetryApi internal struct DDNoopGlobals { static let tracer = DDNoopTracer() @@ -13,7 +14,7 @@ internal struct DDNoopGlobals { static let context = DDNoopSpanContext() } -internal struct DDNoopTracer: OTTracer { +internal class DDNoopTracer: OTTracer, OpenTelemetryApi.Tracer { var activeSpan: OTSpan? = nil private func warn() { @@ -44,6 +45,12 @@ internal struct DDNoopTracer: OTTracer { warn() return DDNoopGlobals.span } + + // MARK: - Open Telemetry + + func spanBuilder(spanName: String) -> OpenTelemetryApi.SpanBuilder { + fatalError("Not implemented") + } } internal struct DDNoopSpan: OTSpan { diff --git a/DatadogTrace/Sources/DatadogTracer.swift b/DatadogTrace/Sources/DatadogTracer.swift index 6d737634c7..ee26cb7414 100644 --- a/DatadogTrace/Sources/DatadogTracer.swift +++ b/DatadogTrace/Sources/DatadogTracer.swift @@ -6,8 +6,9 @@ import Foundation import DatadogInternal +import OpenTelemetryApi -internal class DatadogTracer: OTTracer { +internal class DatadogTracer: OTTracer, OpenTelemetryApi.Tracer { internal weak var core: DatadogCoreProtocol? /// Global tags configured for Trace feature. @@ -156,4 +157,10 @@ internal class DatadogTracer: OTTracer { forKey: SpanCoreContext.key ) } + + // MARK: - OpenTelemetry + + func spanBuilder(spanName: String) -> OpenTelemetryApi.SpanBuilder { + OTelSpanBuilder() + } } diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift new file mode 100644 index 0000000000..31e08e4c67 --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift @@ -0,0 +1,47 @@ +/* +* 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 +import OpenTelemetryApi + +class OTelNoOpSpan: Span { + var kind: OpenTelemetryApi.SpanKind = .internal + + var name: String = "" + + var context: SpanContext = SpanContext.create( + traceId: TraceId.invalid, + spanId: SpanId.invalid, + traceFlags: TraceFlags(), + traceState: TraceState() + ) + + var isRecording: Bool = false + + var status: Status = Status.unset + + var description: String = "NoOpSpan" + + func updateName(name: String) {} + + func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue?) {} + + func addEvent(name: String) {} + + func addEvent(name: String, timestamp: Date) {} + + func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue]) {} + + func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue], timestamp: Date) {} + + func end() { + OpenTelemetry.instance.contextProvider.removeContextForSpan(self) + } + + func end(time: Date) { + end() + } +} diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift new file mode 100644 index 0000000000..ae2b8abe70 --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift @@ -0,0 +1,50 @@ +/* +* 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 +import OpenTelemetryApi + +class NoOpSpanBuilder: SpanBuilder { + @discardableResult public func startSpan() -> Span { + return OTelNoOpSpan() + } + + @discardableResult public func setParent(_ parent: Span) -> Self { + return self + } + + @discardableResult public func setParent(_ parent: SpanContext) -> Self { + return self + } + + @discardableResult public func setNoParent() -> Self { + return self + } + + @discardableResult public func addLink(spanContext: SpanContext) -> Self { + return self + } + + @discardableResult public func addLink(spanContext: SpanContext, attributes: [String: OpenTelemetryApi.AttributeValue]) -> Self { + return self + } + + @discardableResult public func setSpanKind(spanKind: SpanKind) -> Self { + return self + } + + @discardableResult public func setStartTime(time: Date) -> Self { + return self + } + + public func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue) -> Self { + return self + } + + func setActive(_ active: Bool) -> Self { + return self + } +} diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift new file mode 100644 index 0000000000..aa83590685 --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -0,0 +1,94 @@ +/* +* 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 +import OpenTelemetryApi + +class OTelSpan: OpenTelemetryApi.Span { + private let tracer: DatadogTracer + private let queue: DispatchQueue + private var _status: OpenTelemetryApi.Status + private var _name: String + + init( + context: OpenTelemetryApi.SpanContext, + kind: OpenTelemetryApi.SpanKind, + name: String, + tracer: DatadogTracer + ) { + self._name = name + self._status = .unset + self.context = context + self.isRecording = true + self.kind = kind + self.queue = tracer.queue + self.tracer = tracer + } + + var kind: OpenTelemetryApi.SpanKind + + var context: OpenTelemetryApi.SpanContext + + var isRecording: Bool + + var status: OpenTelemetryApi.Status { + get { + queue.sync { + _status + } + } + set { + queue.sync { + _status = newValue + } + } + } + + var name: String { + get { + queue.sync { + _name + } + } + set { + queue.sync { + _name = newValue + } + } + } + + func addEvent(name: String) { + fatalError("Not implemented") + } + + func addEvent(name: String, timestamp: Date) { + fatalError("Not implemented") + } + + func addEvent(name: String, attributes: [String : OpenTelemetryApi.AttributeValue]) { + fatalError("Not implemented") + } + + func addEvent(name: String, attributes: [String : OpenTelemetryApi.AttributeValue], timestamp: Date) { + fatalError("Not implemented") + } + + func end() { + fatalError("Not implemented") + } + + func end(time: Date) { + fatalError("Not implemented") + } + + var description: String { + return "WrapperSpan" + } + + func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue?) { + fatalError("Not implemented") + } +} diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift new file mode 100644 index 0000000000..2f3b051883 --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift @@ -0,0 +1,50 @@ +/* +* 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 +import OpenTelemetryApi + +class OTelSpanBuilder: OpenTelemetryApi.SpanBuilder { + func setParent(_ parent: OpenTelemetryApi.Span) -> Self { + fatalError("Not implemented") + } + + func setParent(_ parent: OpenTelemetryApi.SpanContext) -> Self { + fatalError("Not implemented") + } + + func setNoParent() -> Self { + fatalError("Not implemented") + } + + func addLink(spanContext: OpenTelemetryApi.SpanContext) -> Self { + fatalError("Not implemented") + } + + func addLink(spanContext: OpenTelemetryApi.SpanContext, attributes: [String : OpenTelemetryApi.AttributeValue]) -> Self { + fatalError("Not implemented") + } + + func setSpanKind(spanKind: OpenTelemetryApi.SpanKind) -> Self { + fatalError("Not implemented") + } + + func setStartTime(time: Date) -> Self { + fatalError("Not implemented") + } + + func setActive(_ active: Bool) -> Self { + fatalError("Not implemented") + } + + func startSpan() -> OpenTelemetryApi.Span { + fatalError("Not implemented") + } + + func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue) -> Self { + fatalError("Not implemented") + } +} diff --git a/DatadogTrace/Sources/Tracer.swift b/DatadogTrace/Sources/Tracer.swift index fd3431f569..1f8cb36a13 100644 --- a/DatadogTrace/Sources/Tracer.swift +++ b/DatadogTrace/Sources/Tracer.swift @@ -6,6 +6,7 @@ import Foundation import DatadogInternal +import OpenTelemetryApi /// Datadog - specific span tags to be used with `Tracer.shared().startSpan(operationName:references:tags:startTime:)` /// and `span.setTag(key:value:)`. @@ -49,7 +50,7 @@ public class Tracer { /// It requires `Trace.enable(with:in:)` to be called first - otherwise it will return no-op implementation. /// - Parameter core: the instance of Datadog SDK the Trace feature was enabled in (global instance by default) /// - Returns: the Tracer that conforms to Open Tracing API (`OTTracer`) - public static func shared(in core: DatadogCoreProtocol = CoreRegistry.default) -> OTTracer { + public static func shared(in core: DatadogCoreProtocol = CoreRegistry.default) -> OTTracer & OpenTelemetryApi.Tracer { do { guard !(core is NOPDatadogCore) else { throw ProgrammerError( From 27ecb2a8a00a31e92879db180f2de1dafe8e7b69 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 3 Jan 2024 10:41:35 +0100 Subject: [PATCH 004/153] RUM-1836 feat(otel-tracer): implement core functionality of the otel tracer --- DatadogTrace/Sources/DDNoOps.swift | 3 +- DatadogTrace/Sources/DatadogTracer.swift | 10 +- .../Sources/OpenTelemetry/OTelNoOpSpan.swift | 8 +- .../OpenTelemetry/OTelNoOpSpanBuilder.swift | 28 ++-- .../Sources/OpenTelemetry/OTelSpan.swift | 123 +++++++++++++----- .../OpenTelemetry/OTelSpanBuilder.swift | 122 ++++++++++++++--- .../OpenTelemetry/OtelSpanId+Datadog.swift | 20 +++ .../OpenTelemetry/OtelTraceId+Datadog.swift | 20 +++ .../OtelSpanId+DatadogTests.swift | 20 +++ .../Tests/OpenTelemetry/OtelSpanTests.swift | 100 ++++++++++++++ .../OtelTraceId+DatadogTests.swift | 20 +++ 11 files changed, 413 insertions(+), 61 deletions(-) create mode 100644 DatadogTrace/Sources/OpenTelemetry/OtelSpanId+Datadog.swift create mode 100644 DatadogTrace/Sources/OpenTelemetry/OtelTraceId+Datadog.swift create mode 100644 DatadogTrace/Tests/OpenTelemetry/OtelSpanId+DatadogTests.swift create mode 100644 DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift create mode 100644 DatadogTrace/Tests/OpenTelemetry/OtelTraceId+DatadogTests.swift diff --git a/DatadogTrace/Sources/DDNoOps.swift b/DatadogTrace/Sources/DDNoOps.swift index fe71dc1fc3..b39f818020 100644 --- a/DatadogTrace/Sources/DDNoOps.swift +++ b/DatadogTrace/Sources/DDNoOps.swift @@ -49,7 +49,8 @@ internal class DDNoopTracer: OTTracer, OpenTelemetryApi.Tracer { // MARK: - Open Telemetry func spanBuilder(spanName: String) -> OpenTelemetryApi.SpanBuilder { - fatalError("Not implemented") + warn() + return OTelNoOpSpanBuilder() } } diff --git a/DatadogTrace/Sources/DatadogTracer.swift b/DatadogTrace/Sources/DatadogTracer.swift index ee26cb7414..cb371aa813 100644 --- a/DatadogTrace/Sources/DatadogTracer.swift +++ b/DatadogTrace/Sources/DatadogTracer.swift @@ -161,6 +161,14 @@ internal class DatadogTracer: OTTracer, OpenTelemetryApi.Tracer { // MARK: - OpenTelemetry func spanBuilder(spanName: String) -> OpenTelemetryApi.SpanBuilder { - OTelSpanBuilder() + OTelSpanBuilder( + active: false, + attributes: [:], + parent: .currentSpan, + spanKind: .client, + spanName: spanName, + startTime: nil, + tracer: self + ) } } diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift index 31e08e4c67..c0f08291f7 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift @@ -7,21 +7,21 @@ import Foundation import OpenTelemetryApi -class OTelNoOpSpan: Span { +internal class OTelNoOpSpan: Span { var kind: OpenTelemetryApi.SpanKind = .internal var name: String = "" - var context: SpanContext = SpanContext.create( + var context = SpanContext.create( traceId: TraceId.invalid, spanId: SpanId.invalid, traceFlags: TraceFlags(), traceState: TraceState() ) - var isRecording: Bool = false + var isRecording = false - var status: Status = Status.unset + var status = Status.unset var description: String = "NoOpSpan" diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift index ae2b8abe70..b4b0c4560d 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift @@ -7,40 +7,48 @@ import Foundation import OpenTelemetryApi -class NoOpSpanBuilder: SpanBuilder { - @discardableResult public func startSpan() -> Span { +internal class OTelNoOpSpanBuilder: SpanBuilder { + @discardableResult + func startSpan() -> Span { return OTelNoOpSpan() } - @discardableResult public func setParent(_ parent: Span) -> Self { + @discardableResult + func setParent(_ parent: Span) -> Self { return self } - @discardableResult public func setParent(_ parent: SpanContext) -> Self { + @discardableResult + func setParent(_ parent: SpanContext) -> Self { return self } - @discardableResult public func setNoParent() -> Self { + @discardableResult + func setNoParent() -> Self { return self } - @discardableResult public func addLink(spanContext: SpanContext) -> Self { + @discardableResult + func addLink(spanContext: SpanContext) -> Self { return self } - @discardableResult public func addLink(spanContext: SpanContext, attributes: [String: OpenTelemetryApi.AttributeValue]) -> Self { + @discardableResult + func addLink(spanContext: SpanContext, attributes: [String: OpenTelemetryApi.AttributeValue]) -> Self { return self } - @discardableResult public func setSpanKind(spanKind: SpanKind) -> Self { + @discardableResult + func setSpanKind(spanKind: SpanKind) -> Self { return self } - @discardableResult public func setStartTime(time: Date) -> Self { + @discardableResult + func setStartTime(time: Date) -> Self { return self } - public func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue) -> Self { + func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue) -> Self { return self } diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index aa83590685..b6d6f47380 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -7,33 +7,29 @@ import Foundation import OpenTelemetryApi -class OTelSpan: OpenTelemetryApi.Span { - private let tracer: DatadogTracer - private let queue: DispatchQueue +internal enum DatadogTagKeys: String { + case spanKind = "span.kind" + case errorMessage = "error.Message" +} + +internal class OTelSpan: OpenTelemetryApi.Span { private var _status: OpenTelemetryApi.Status private var _name: String - init( - context: OpenTelemetryApi.SpanContext, - kind: OpenTelemetryApi.SpanKind, - name: String, - tracer: DatadogTracer - ) { - self._name = name - self._status = .unset - self.context = context - self.isRecording = true - self.kind = kind - self.queue = tracer.queue - self.tracer = tracer - } - + var attributes: [String: OpenTelemetryApi.AttributeValue] + let context: OpenTelemetryApi.SpanContext var kind: OpenTelemetryApi.SpanKind + let nestedSpan: DDSpan + let tracer: DatadogTracer + let queue: DispatchQueue - var context: OpenTelemetryApi.SpanContext - + /// `isRecording` indicates whether the span is recording or not + /// and events can be added to it. var isRecording: Bool + /// `status` saves state of the code and description indicating + /// whether the span has recorded errors. This will be done by setting `error.message` + /// tag on the span. var status: OpenTelemetryApi.Status { get { queue.sync { @@ -42,11 +38,16 @@ class OTelSpan: OpenTelemetryApi.Span { } set { queue.sync { + guard isRecording else { + return + } + _status = newValue } } } + /// `name` of the span is akin to operation name in Datadog var name: String { get { queue.sync { @@ -55,33 +56,93 @@ class OTelSpan: OpenTelemetryApi.Span { } set { queue.sync { + guard isRecording else { + return + } _name = newValue } + nestedSpan.setOperationName(name) } } + init( + attributes: [String: OpenTelemetryApi.AttributeValue], + kind: OpenTelemetryApi.SpanKind, + name: String, + parentSpanID: OpenTelemetryApi.SpanId?, + spanContext: OpenTelemetryApi.SpanContext, + spanKind: OpenTelemetryApi.SpanKind, + startTime: Date, + tracer: DatadogTracer + ) { + self._name = name + self._status = .unset + self.attributes = attributes + self.context = spanContext + self.kind = kind + self.isRecording = true + self.queue = tracer.queue + self.tracer = tracer + self.nestedSpan = .init( + tracer: tracer, + context: .init( + traceID: context.traceId.toDatadog(), + spanID: context.spanId.toDatadog(), + parentSpanID: parentSpanID?.toDatadog(), + baggageItems: .init() + ), + operationName: name, + startTime: startTime, + tags: [:] + ) + } + + // swiftlint:disable unavailable_function func addEvent(name: String) { - fatalError("Not implemented") + fatalError("Not implemented yet") } func addEvent(name: String, timestamp: Date) { - fatalError("Not implemented") + fatalError("Not implemented yet") } - func addEvent(name: String, attributes: [String : OpenTelemetryApi.AttributeValue]) { - fatalError("Not implemented") + func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue]) { + fatalError("Not implemented yet") } - func addEvent(name: String, attributes: [String : OpenTelemetryApi.AttributeValue], timestamp: Date) { - fatalError("Not implemented") + func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue], timestamp: Date) { + fatalError("Not implemented yet") } + // swiftlint:enable unavailable_function func end() { - fatalError("Not implemented") + end(time: Date()) } func end(time: Date) { - fatalError("Not implemented") + queue.sync { + guard isRecording else { + return + } + isRecording = false + + // Attributes maps to tags in Datadog + for (key, value) in attributes { + nestedSpan.setTag(key: key, value: value.description) + } + + // If status is error, error.message tag is added + switch self._status { + case .error(description: let description): + nestedSpan.setTag(key: DatadogTagKeys.errorMessage.rawValue, value: description) + case .ok, .unset: + break + } + + // SpanKind maps to the `span.kind` tag in Datadog + nestedSpan.setTag(key: DatadogTagKeys.spanKind.rawValue, value: kind.rawValue) + } + nestedSpan.finish(at: time) } var description: String { @@ -89,6 +150,10 @@ class OTelSpan: OpenTelemetryApi.Span { } func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue?) { - fatalError("Not implemented") + guard isRecording else { + return + } + + attributes[key] = value } } diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift index 2f3b051883..93a4deb244 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift @@ -1,50 +1,140 @@ /* -* 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. -*/ + * 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 import OpenTelemetryApi -class OTelSpanBuilder: OpenTelemetryApi.SpanBuilder { +internal class OTelSpanBuilder: OpenTelemetryApi.SpanBuilder { + var tracer: DatadogTracer + var spanName: String + var spanKind = SpanKind.client + var attributes: [String: OpenTelemetryApi.AttributeValue] + var startTime: Date? + var active: Bool + var parent: Parent + + enum Parent { + case currentSpan + case span(OpenTelemetryApi.Span) + case spanContext(OpenTelemetryApi.SpanContext) + case noParent + + func context() -> OpenTelemetryApi.SpanContext? { + switch self { + case .currentSpan: + return OpenTelemetry.instance.contextProvider.activeSpan?.context + case .span(let span): + return span.context + case .spanContext(let context): + return context + case .noParent: + return nil + } + } + } + + init( + active: Bool, + attributes: [String: OpenTelemetryApi.AttributeValue], + parent: Parent, + spanKind: SpanKind, + spanName: String, + startTime: Date?, + tracer: DatadogTracer + ) { + self.tracer = tracer + self.spanName = spanName + self.spanKind = spanKind + self.attributes = attributes + self.startTime = startTime + self.active = active + self.parent = parent + } + func setParent(_ parent: OpenTelemetryApi.Span) -> Self { - fatalError("Not implemented") + self.parent = .span(parent) + return self } func setParent(_ parent: OpenTelemetryApi.SpanContext) -> Self { - fatalError("Not implemented") + self.parent = .spanContext(parent) + return self } func setNoParent() -> Self { - fatalError("Not implemented") + self.parent = .noParent + return self } + // swiftlint:disable unavailable_function func addLink(spanContext: OpenTelemetryApi.SpanContext) -> Self { - fatalError("Not implemented") + fatalError("Not implemented yet") } - func addLink(spanContext: OpenTelemetryApi.SpanContext, attributes: [String : OpenTelemetryApi.AttributeValue]) -> Self { - fatalError("Not implemented") + func addLink(spanContext: OpenTelemetryApi.SpanContext, attributes: [String: OpenTelemetryApi.AttributeValue]) -> Self { + fatalError("Not implemented yet") } + // swiftlint:enable unavailable_function func setSpanKind(spanKind: OpenTelemetryApi.SpanKind) -> Self { - fatalError("Not implemented") + self.spanKind = spanKind + return self } func setStartTime(time: Date) -> Self { - fatalError("Not implemented") + self.startTime = time + return self } func setActive(_ active: Bool) -> Self { - fatalError("Not implemented") + self.active = active + return self } func startSpan() -> OpenTelemetryApi.Span { - fatalError("Not implemented") + let parentContext = parent.context() + let traceId: TraceId + let spanId = SpanId.random() + let traceState: TraceState + + if let parentContext = parentContext, parentContext.isValid { + traceId = parentContext.traceId + traceState = parentContext.traceState + } else { + traceId = TraceId.random() + traceState = .init() + } + + let spanContext = SpanContext.create( + traceId: traceId, + spanId: spanId, + traceFlags: TraceFlags(), + traceState: traceState + ) + + let createdSpan = OTelSpan( + attributes: attributes, + kind: spanKind, + name: spanName, + parentSpanID: parentContext?.spanId, + spanContext: spanContext, + spanKind: spanKind, + startTime: startTime ?? Date(), + tracer: tracer + ) + + if active { + OpenTelemetry.instance.contextProvider.setActiveSpan(createdSpan) + } + + return createdSpan } func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue) -> Self { - fatalError("Not implemented") + attributes[key] = value + return self } } diff --git a/DatadogTrace/Sources/OpenTelemetry/OtelSpanId+Datadog.swift b/DatadogTrace/Sources/OpenTelemetry/OtelSpanId+Datadog.swift new file mode 100644 index 0000000000..6c8ff2c0b8 --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OtelSpanId+Datadog.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 Foundation +import OpenTelemetryApi +import DatadogInternal + +extension OpenTelemetryApi.SpanId { + /// Converts OpenTelemetry `SpanId` to Datadog `SpanID`. + /// - Returns: Datadog `SpanID`. + func toDatadog() -> SpanID { + var data = Data(count: 8) + self.copyBytesTo(dest: &data, destOffset: 0) + let integerLiteral = UInt64(bigEndian: data.withUnsafeBytes { $0.load(as: UInt64.self) }) + return .init(integerLiteral: integerLiteral) + } +} diff --git a/DatadogTrace/Sources/OpenTelemetry/OtelTraceId+Datadog.swift b/DatadogTrace/Sources/OpenTelemetry/OtelTraceId+Datadog.swift new file mode 100644 index 0000000000..9f34b8cf8d --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OtelTraceId+Datadog.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 Foundation +import OpenTelemetryApi +import DatadogInternal + +extension OpenTelemetryApi.TraceId { + /// Converts OpenTelemetry `TraceId` to Datadog `TraceID`. + /// - Returns: Datadog `TraceID` with only higher order bits considered. + func toDatadog() -> TraceID { + var data = Data(count: 16) + self.copyBytesTo(dest: &data, destOffset: 0) + let integerLiteral = UInt64(bigEndian: data.withUnsafeBytes { $0.load(as: UInt64.self) }) + return .init(integerLiteral: integerLiteral) + } +} diff --git a/DatadogTrace/Tests/OpenTelemetry/OtelSpanId+DatadogTests.swift b/DatadogTrace/Tests/OpenTelemetry/OtelSpanId+DatadogTests.swift new file mode 100644 index 0000000000..b1e2566259 --- /dev/null +++ b/DatadogTrace/Tests/OpenTelemetry/OtelSpanId+DatadogTests.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 XCTest +import TestUtilities +import DatadogInternal +import OpenTelemetryApi + +@testable import DatadogTrace + +class OtelSpanIdDatadogTests: XCTestCase { + func testToDatadog() { + let otelId = SpanId.random() + let ddId = otelId.toDatadog() + XCTAssertEqual(otelId.rawValue, ddId.rawValue) + } +} diff --git a/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift new file mode 100644 index 0000000000..80ebebb49a --- /dev/null +++ b/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift @@ -0,0 +1,100 @@ +/* + * 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 TestUtilities +import DatadogInternal + +@testable import DatadogTrace + +final class OtelSpanTests: XCTestCase { + func testSpanResourceNameDefault() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "OperationName").startSpan() + + // When + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let events: [SpanEventsEnvelope] = core.events() + XCTAssertEqual(events.count, 1) + let recordedSpan = events.first!.spans.first! + XCTAssertEqual(recordedSpan.resource, "OperationName") + XCTAssertEqual(recordedSpan.operationName, "OperationName") + } + + func testSpanSetName() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "OperationName").startSpan() + + // When + span.name = "NewOperationName" + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let events: [SpanEventsEnvelope] = core.events() + XCTAssertEqual(events.count, 1) + let recordedSpan = events.first!.spans.first! + XCTAssertEqual(recordedSpan.resource, "NewOperationName") + XCTAssertEqual(recordedSpan.operationName, "NewOperationName") + } + + func testSpanEnd() { + // Given + let (name, ignoredName) = ("trueName", "invalidName") + let (code, ignoredCode) = (200, 400) + let (message, ignoredMessage) = ("message", "ignoredMessage") + let (attributes, ignoredAttributes) = (["key": "value"], ["ignoredKey": "ignoredValue"]) + + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: name).startSpan() + span.putHttpStatusCode(statusCode: code, reasonPhrase: message) + for (key, value) in attributes { + span.setAttribute(key: key, value: value) + } + XCTAssertTrue(span.isRecording) + + // When + span.end() + XCTAssertFalse(span.isRecording) + + // Then ignores + span.name = ignoredName + span.putHttpStatusCode(statusCode: ignoredCode, reasonPhrase: ignoredMessage) + for (key, value) in ignoredAttributes { + span.setAttribute(key: key, value: value) + } + + span.end() + + waitForExpectations(timeout: 0.5, handler: nil) + let events: [SpanEventsEnvelope] = core.events() + XCTAssertEqual(events.count, 1) + let recordedSpan = events.first!.spans.first! + + XCTAssertEqual(recordedSpan.resource, name) + XCTAssertEqual(recordedSpan.operationName, name) + let expectedTags = [ + "http.status_code": "200", + "key": "value", + "span.kind": "client", + ] + XCTAssertEqual(recordedSpan.tags, expectedTags) + } +} diff --git a/DatadogTrace/Tests/OpenTelemetry/OtelTraceId+DatadogTests.swift b/DatadogTrace/Tests/OpenTelemetry/OtelTraceId+DatadogTests.swift new file mode 100644 index 0000000000..749ec16f63 --- /dev/null +++ b/DatadogTrace/Tests/OpenTelemetry/OtelTraceId+DatadogTests.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 XCTest +import TestUtilities +import DatadogInternal +import OpenTelemetryApi + +@testable import DatadogTrace + +class OtelTraceIdDatadogTests: XCTestCase { + func testToDatadog_onlyHigherOrderBitsAreConsidered() { + let otelId = TraceId.random() + let ddId = otelId.toDatadog() + XCTAssertEqual(otelId.rawHigherLong, ddId.rawValue) + } +} From c9143b0c2d58942982a665a6ea14dacf094f5332 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 3 Jan 2024 10:56:41 +0100 Subject: [PATCH 005/153] RUM-1836 feat(otel-tracer): add test cases --- .../Sources/OpenTelemetry/OTelNoOpSpan.swift | 2 +- .../Sources/OpenTelemetry/OTelSpan.swift | 2 +- .../Tests/OpenTelemetry/OtelSpanTests.swift | 77 ++++++++++++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift index c0f08291f7..8bd29b4e44 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift @@ -23,7 +23,7 @@ internal class OTelNoOpSpan: Span { var status = Status.unset - var description: String = "NoOpSpan" + var description: String = "OTelNoOpSpan" func updateName(name: String) {} diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index b6d6f47380..afae72b353 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -146,7 +146,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { } var description: String { - return "WrapperSpan" + return "OTelSpan" } func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue?) { diff --git a/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift index 80ebebb49a..5052221477 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift @@ -74,7 +74,7 @@ final class OtelSpanTests: XCTestCase { span.end() XCTAssertFalse(span.isRecording) - // Then ignores + // Then ignores span.name = ignoredName span.putHttpStatusCode(statusCode: ignoredCode, reasonPhrase: ignoredMessage) for (key, value) in ignoredAttributes { @@ -97,4 +97,79 @@ final class OtelSpanTests: XCTestCase { ] XCTAssertEqual(recordedSpan.tags, expectedTags) } + + func testSetParentSpan() { + let writeSpanExpectation = expectation(description: "write span event") + writeSpanExpectation.expectedFulfillmentCount = 2 + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let parentSpan = tracer.spanBuilder(spanName: "Parent").startSpan() + let _ = tracer.spanBuilder(spanName: "Noise").startSpan() + let childSpan = tracer.spanBuilder(spanName: "Child").setParent(parentSpan).startSpan() + + // When + childSpan.end() + parentSpan.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let events: [SpanEventsEnvelope] = core.events() + XCTAssertEqual(events.count, 2) + let child = events.first!.spans.first! + let parent = events.last!.spans.first! + XCTAssertEqual(parent.parentID, nil) + XCTAssertEqual(child.parentID, parent.spanID) + } + + func testSetParentContext() { + let writeSpanExpectation = expectation(description: "write span event") + writeSpanExpectation.expectedFulfillmentCount = 2 + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let parentSpan = tracer.spanBuilder(spanName: "Parent").startSpan() + let _ = tracer.spanBuilder(spanName: "Noise").startSpan() + let childSpan = tracer.spanBuilder(spanName: "Child").setParent(parentSpan.context).startSpan() + + // When + childSpan.end() + parentSpan.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let events: [SpanEventsEnvelope] = core.events() + XCTAssertEqual(events.count, 2) + let child = events.first!.spans.first! + let parent = events.last!.spans.first! + XCTAssertEqual(parent.parentID, nil) + XCTAssertEqual(child.parentID, parent.spanID) + } + + func testSetNoParent() { + let writeSpanExpectation = expectation(description: "write span event") + writeSpanExpectation.expectedFulfillmentCount = 2 + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let parentSpan = tracer.spanBuilder(spanName: "Parent").startSpan() + let _ = tracer.spanBuilder(spanName: "Noise").startSpan() + let childSpan = tracer.spanBuilder(spanName: "Child").setNoParent().startSpan() + + // When + childSpan.end() + parentSpan.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let events: [SpanEventsEnvelope] = core.events() + XCTAssertEqual(events.count, 2) + let child = events.first!.spans.first! + let parent = events.last!.spans.first! + XCTAssertEqual(parent.parentID, nil) + XCTAssertEqual(child.parentID, nil) + } } From bf55a6c3824bbc2c7c061c71b4eaa6c7af4805e8 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 3 Jan 2024 10:59:27 +0100 Subject: [PATCH 006/153] RUM-1836 feat(otel-tracer): fix linter --- DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift index 5052221477..5a19d431a6 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift @@ -106,7 +106,7 @@ final class OtelSpanTests: XCTestCase { // Given let tracer: DatadogTracer = .mockWith(core: core) let parentSpan = tracer.spanBuilder(spanName: "Parent").startSpan() - let _ = tracer.spanBuilder(spanName: "Noise").startSpan() + _ = tracer.spanBuilder(spanName: "Noise").startSpan() let childSpan = tracer.spanBuilder(spanName: "Child").setParent(parentSpan).startSpan() // When @@ -131,7 +131,7 @@ final class OtelSpanTests: XCTestCase { // Given let tracer: DatadogTracer = .mockWith(core: core) let parentSpan = tracer.spanBuilder(spanName: "Parent").startSpan() - let _ = tracer.spanBuilder(spanName: "Noise").startSpan() + _ = tracer.spanBuilder(spanName: "Noise").startSpan() let childSpan = tracer.spanBuilder(spanName: "Child").setParent(parentSpan.context).startSpan() // When @@ -156,7 +156,7 @@ final class OtelSpanTests: XCTestCase { // Given let tracer: DatadogTracer = .mockWith(core: core) let parentSpan = tracer.spanBuilder(spanName: "Parent").startSpan() - let _ = tracer.spanBuilder(spanName: "Noise").startSpan() + _ = tracer.spanBuilder(spanName: "Noise").startSpan() let childSpan = tracer.spanBuilder(spanName: "Child").setNoParent().startSpan() // When From 156454d415d178f6f6842c83604ccd988f5fb897 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 3 Jan 2024 11:35:20 +0100 Subject: [PATCH 007/153] RUM-1836 feat(otel-tracer): add tests --- .../Tests/OpenTelemetry/OtelSpanTests.swift | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift index 5a19d431a6..63be2c1322 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift @@ -95,7 +95,7 @@ final class OtelSpanTests: XCTestCase { "key": "value", "span.kind": "client", ] - XCTAssertEqual(recordedSpan.tags, expectedTags) + XCTAssertTagsEqual(recordedSpan.tags, expectedTags) } func testSetParentSpan() { @@ -172,4 +172,62 @@ final class OtelSpanTests: XCTestCase { XCTAssertEqual(parent.parentID, nil) XCTAssertEqual(child.parentID, nil) } + + func testSetAttribute() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "Span").startSpan() + + // When + span.setAttribute(key: "key", value: .bool(true)) + span.setAttribute(key: "key2", value: .string("value2")) + span.setAttribute(key: "key3", value: .int(3)) + span.setAttribute(key: "key4", value: .double(4.0)) + span.setAttribute(key: "key5", value: .stringArray(["value5", "value6"])) + span.setAttribute(key: "key6", value: .intArray([6, 7])) + span.setAttribute(key: "key7", value: .doubleArray([7.0, 8.0])) + span.setAttribute(key: "key8", value: .boolArray([true, false])) + + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let events: [SpanEventsEnvelope] = core.events() + XCTAssertEqual(events.count, 1) + let recordedSpan = events.first!.spans.first! + let expectedTags = + [ + "key": "true", + "key2": "value2", + "key3": "3", + "key4": "4.0", + "key5": "[\"value5\", \"value6\"]", + "key6": "[6, 7]", + "key7": "[7.0, 8.0]", + "key8": "[true, false]", + "span.kind": "client", + ] + XCTAssertTagsEqual(recordedSpan.tags, expectedTags) + } +} + +func XCTAssertTagsEqual( + _ dict1: [String: String], + _ dict2: [String: String], + file: StaticString = #filePath, + line: UInt = #line +) { + XCTAssertEqual(dict1.count, dict2.count, file: file, line: line) + for (key, value) in dict1 { + XCTAssertEqual( + dict2[key], + value, + "Expected \(key) to be \(value), but was \(String(describing: dict2[key]))", + file: file, + line: line + ) + } } From 3a8b2ca2bcfaaaaa4732352f739701b1d031c262 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 3 Jan 2024 14:39:55 +0100 Subject: [PATCH 008/153] RUM-1836 feat(otel-tracer): add source files to project --- Datadog/Datadog.xcodeproj/project.pbxproj | 70 +++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 6845215121..f4a2211ccd 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -36,6 +36,24 @@ 3C41693C29FBF4D50042B9D2 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; 3C6C7FDB2B45738C006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDA2B45738C006F5CBC /* OpenTelemetryApi */; }; 3C6C7FDD2B457392006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDC2B457392006F5CBC /* OpenTelemetryApi */; }; + 3C6C7FE52B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */; }; + 3C6C7FE62B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */; }; + 3C6C7FE72B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; + 3C6C7FE82B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; + 3C6C7FE92B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; }; + 3C6C7FEA2B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; }; + 3C6C7FEB2B459AAA006F5CBC /* OtelTraceId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE22B459AAA006F5CBC /* OtelTraceId+Datadog.swift */; }; + 3C6C7FEC2B459AAA006F5CBC /* OtelTraceId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE22B459AAA006F5CBC /* OtelTraceId+Datadog.swift */; }; + 3C6C7FED2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */; }; + 3C6C7FEE2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */; }; + 3C6C7FEF2B459AAA006F5CBC /* OtelSpanId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE42B459AAA006F5CBC /* OtelSpanId+Datadog.swift */; }; + 3C6C7FF02B459AAA006F5CBC /* OtelSpanId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE42B459AAA006F5CBC /* OtelSpanId+Datadog.swift */; }; + 3C6C7FFB2B459AF6006F5CBC /* OtelSpanId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF22B459AB3006F5CBC /* OtelSpanId+DatadogTests.swift */; }; + 3C6C7FFC2B459AF6006F5CBC /* OtelTraceId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF32B459AB3006F5CBC /* OtelTraceId+DatadogTests.swift */; }; + 3C6C7FFD2B459AF6006F5CBC /* OtelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF42B459AB3006F5CBC /* OtelSpanTests.swift */; }; + 3C6C7FFE2B459AF6006F5CBC /* OtelSpanId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF22B459AB3006F5CBC /* OtelSpanId+DatadogTests.swift */; }; + 3C6C7FFF2B459AF6006F5CBC /* OtelTraceId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF32B459AB3006F5CBC /* OtelTraceId+DatadogTests.swift */; }; + 3C6C80002B459AF6006F5CBC /* OtelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF42B459AB3006F5CBC /* OtelSpanTests.swift */; }; 3C74305C29FBC0480053B80F /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2DA2385298D57AA00C6C7E6 /* DatadogInternal.framework */; }; 3C85D42129F7C5C900AFF894 /* WebViewTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */; }; 3C85D42A29F7C70300AFF894 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257953E298ABA65008A1BE5 /* TestUtilities.framework */; }; @@ -1882,6 +1900,15 @@ 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaTypeExtensions.swift; sourceTree = ""; }; 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzler.swift; sourceTree = ""; }; 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzlerTests.swift; sourceTree = ""; }; + 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelNoOpSpanBuilder.swift; sourceTree = ""; }; + 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpan.swift; sourceTree = ""; }; + 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanBuilder.swift; sourceTree = ""; }; + 3C6C7FE22B459AAA006F5CBC /* OtelTraceId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OtelTraceId+Datadog.swift"; sourceTree = ""; }; + 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelNoOpSpan.swift; sourceTree = ""; }; + 3C6C7FE42B459AAA006F5CBC /* OtelSpanId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OtelSpanId+Datadog.swift"; sourceTree = ""; }; + 3C6C7FF22B459AB3006F5CBC /* OtelSpanId+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OtelSpanId+DatadogTests.swift"; sourceTree = ""; }; + 3C6C7FF32B459AB3006F5CBC /* OtelTraceId+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OtelTraceId+DatadogTests.swift"; sourceTree = ""; }; + 3C6C7FF42B459AB3006F5CBC /* OtelSpanTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OtelSpanTests.swift; sourceTree = ""; }; 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewTracking.swift; sourceTree = ""; }; 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsSanitizerMock.swift; sourceTree = ""; }; 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzler.swift; sourceTree = ""; }; @@ -3084,6 +3111,29 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3C6C7FDE2B459AAA006F5CBC /* OpenTelemetry */ = { + isa = PBXGroup; + children = ( + 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */, + 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */, + 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */, + 3C6C7FE22B459AAA006F5CBC /* OtelTraceId+Datadog.swift */, + 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */, + 3C6C7FE42B459AAA006F5CBC /* OtelSpanId+Datadog.swift */, + ); + path = OpenTelemetry; + sourceTree = ""; + }; + 3C6C7FF12B459AB3006F5CBC /* OpenTelemetry */ = { + isa = PBXGroup; + children = ( + 3C6C7FF22B459AB3006F5CBC /* OtelSpanId+DatadogTests.swift */, + 3C6C7FF32B459AB3006F5CBC /* OtelTraceId+DatadogTests.swift */, + 3C6C7FF42B459AB3006F5CBC /* OtelSpanTests.swift */, + ); + path = OpenTelemetry; + sourceTree = ""; + }; 3CE11A3B29F7BEE700202522 /* DatadogWebViewTracking */ = { isa = PBXGroup; children = ( @@ -5486,6 +5536,7 @@ D25EE93529C4C3C300CE3839 /* DatadogTrace */ = { isa = PBXGroup; children = ( + 3C6C7FDE2B459AAA006F5CBC /* OpenTelemetry */, 61A2CC382A44B0EA0000FF25 /* Trace.swift */, 61A2CC352A44B0A20000FF25 /* TraceConfiguration.swift */, 61A2CC3B2A44BED30000FF25 /* Tracer.swift */, @@ -5508,6 +5559,7 @@ D25EE93F29C4C3C400CE3839 /* DatadogTraceTests */ = { isa = PBXGroup; children = ( + 3C6C7FF12B459AB3006F5CBC /* OpenTelemetry */, 619CE75D2A458CE1005588CB /* TraceConfigurationTests.swift */, 61AD4E172451C7FF006E34EA /* TracingFeatureMocks.swift */, 61C5A89824509C1100DA608C /* DDSpanTests.swift */, @@ -8276,14 +8328,20 @@ D2C1A4FC29C4C4CB00946C31 /* RequestBuilder.swift in Sources */, D2C1A50D29C4C4CB00946C31 /* SpanTagsReducer.swift in Sources */, D2C1A51A29C4C5DD00946C31 /* JSONEncoder.swift in Sources */, + 3C6C7FED2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */, + 3C6C7FE52B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */, D2C1A51829C4C53F00946C31 /* OTSpan.swift in Sources */, D2C1A51429C4C53F00946C31 /* OTSpanContext.swift in Sources */, + 3C6C7FEB2B459AAA006F5CBC /* OtelTraceId+Datadog.swift in Sources */, + 3C6C7FE92B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */, D2C1A51329C4C53F00946C31 /* OTReference.swift in Sources */, + 3C6C7FEF2B459AAA006F5CBC /* OtelSpanId+Datadog.swift in Sources */, D2C1A4FB29C4C4CB00946C31 /* MessageReceivers.swift in Sources */, 61A2CC362A44B0A20000FF25 /* TraceConfiguration.swift in Sources */, 61A2CC392A44B0EA0000FF25 /* Trace.swift in Sources */, D2C1A50029C4C4CB00946C31 /* ActiveSpansPool.swift in Sources */, D2C1A50929C4C4CB00946C31 /* SpanEventEncoder.swift in Sources */, + 3C6C7FE72B459AAA006F5CBC /* OTelSpan.swift in Sources */, D2C1A4FE29C4C4CB00946C31 /* SpanEventMapper.swift in Sources */, D2C1A50329C4C4CB00946C31 /* DDFormat.swift in Sources */, D2C1A51629C4C53F00946C31 /* OTConstants.swift in Sources */, @@ -8314,10 +8372,13 @@ D2C1A51D29C4C75700946C31 /* SpanEventBuilderTests.swift in Sources */, D2C1A52229C4C75700946C31 /* DDNoopTracerTests.swift in Sources */, D2C1A51C29C4C75700946C31 /* ContextMessageReceiverTests.swift in Sources */, + 3C6C7FFD2B459AF6006F5CBC /* OtelSpanTests.swift in Sources */, 619CE7612A458D66005588CB /* TraceTests.swift in Sources */, D2C1A52029C4C75700946C31 /* DDSpanTests.swift in Sources */, + 3C6C7FFC2B459AF6006F5CBC /* OtelTraceId+DatadogTests.swift in Sources */, D2C1A51B29C4C75700946C31 /* DDSpanContextTests.swift in Sources */, D2C1A52729C4C7D000946C31 /* TracingFeatureMocks.swift in Sources */, + 3C6C7FFB2B459AF6006F5CBC /* OtelSpanId+DatadogTests.swift in Sources */, D2C1A51F29C4C75700946C31 /* ActiveSpansPoolTests.swift in Sources */, D2C1A52529C4C75700946C31 /* SpanSanitizerTests.swift in Sources */, ); @@ -8465,14 +8526,20 @@ D2C1A53A29C4F2DF00946C31 /* RequestBuilder.swift in Sources */, D2C1A53B29C4F2DF00946C31 /* SpanTagsReducer.swift in Sources */, D2C1A53C29C4F2DF00946C31 /* JSONEncoder.swift in Sources */, + 3C6C7FEE2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */, + 3C6C7FE62B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */, D2C1A53D29C4F2DF00946C31 /* OTSpan.swift in Sources */, D2C1A53E29C4F2DF00946C31 /* OTSpanContext.swift in Sources */, + 3C6C7FEC2B459AAA006F5CBC /* OtelTraceId+Datadog.swift in Sources */, + 3C6C7FEA2B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */, D2C1A53F29C4F2DF00946C31 /* OTReference.swift in Sources */, + 3C6C7FF02B459AAA006F5CBC /* OtelSpanId+Datadog.swift in Sources */, D2C1A54129C4F2DF00946C31 /* MessageReceivers.swift in Sources */, 61A2CC372A44B0A20000FF25 /* TraceConfiguration.swift in Sources */, 61A2CC3A2A44B0EA0000FF25 /* Trace.swift in Sources */, D2C1A54229C4F2DF00946C31 /* ActiveSpansPool.swift in Sources */, D2C1A54329C4F2DF00946C31 /* SpanEventEncoder.swift in Sources */, + 3C6C7FE82B459AAA006F5CBC /* OTelSpan.swift in Sources */, D2C1A54429C4F2DF00946C31 /* SpanEventMapper.swift in Sources */, D2C1A54529C4F2DF00946C31 /* DDFormat.swift in Sources */, D2C1A54629C4F2DF00946C31 /* OTConstants.swift in Sources */, @@ -8503,10 +8570,13 @@ D2C1A56229C4F2E800946C31 /* SpanEventBuilderTests.swift in Sources */, D2C1A56329C4F2E800946C31 /* DDNoopTracerTests.swift in Sources */, D2C1A56529C4F2E800946C31 /* ContextMessageReceiverTests.swift in Sources */, + 3C6C80002B459AF6006F5CBC /* OtelSpanTests.swift in Sources */, 619CE7622A458D66005588CB /* TraceTests.swift in Sources */, D2C1A56629C4F2E800946C31 /* DDSpanTests.swift in Sources */, + 3C6C7FFF2B459AF6006F5CBC /* OtelTraceId+DatadogTests.swift in Sources */, D2C1A56729C4F2E800946C31 /* DDSpanContextTests.swift in Sources */, D2C1A56829C4F2E800946C31 /* TracingFeatureMocks.swift in Sources */, + 3C6C7FFE2B459AF6006F5CBC /* OtelSpanId+DatadogTests.swift in Sources */, D2C1A56929C4F2E800946C31 /* ActiveSpansPoolTests.swift in Sources */, D2C1A56A29C4F2E800946C31 /* SpanSanitizerTests.swift in Sources */, ); From 84a7f78ac1d80ae18b24e481971dad589ca7404a Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 3 Jan 2024 14:43:19 +0100 Subject: [PATCH 009/153] RUM-1836 feat(otel-tracer): casing - now all OTel --- Datadog/Datadog.xcodeproj/project.pbxproj | 60 +++++++++---------- ...Datadog.swift => OTelSpanId+Datadog.swift} | 0 ...atadog.swift => OTelTraceId+Datadog.swift} | 0 ...ts.swift => OTelSpanId+DatadogTests.swift} | 2 +- ...telSpanTests.swift => OTelSpanTests.swift} | 2 +- ...s.swift => OTelTraceId+DatadogTests.swift} | 2 +- 6 files changed, 33 insertions(+), 33 deletions(-) rename DatadogTrace/Sources/OpenTelemetry/{OtelSpanId+Datadog.swift => OTelSpanId+Datadog.swift} (100%) rename DatadogTrace/Sources/OpenTelemetry/{OtelTraceId+Datadog.swift => OTelTraceId+Datadog.swift} (100%) rename DatadogTrace/Tests/OpenTelemetry/{OtelSpanId+DatadogTests.swift => OTelSpanId+DatadogTests.swift} (92%) rename DatadogTrace/Tests/OpenTelemetry/{OtelSpanTests.swift => OTelSpanTests.swift} (99%) rename DatadogTrace/Tests/OpenTelemetry/{OtelTraceId+DatadogTests.swift => OTelTraceId+DatadogTests.swift} (92%) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index f4a2211ccd..06da6fee37 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -42,18 +42,18 @@ 3C6C7FE82B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; 3C6C7FE92B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; }; 3C6C7FEA2B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; }; - 3C6C7FEB2B459AAA006F5CBC /* OtelTraceId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE22B459AAA006F5CBC /* OtelTraceId+Datadog.swift */; }; - 3C6C7FEC2B459AAA006F5CBC /* OtelTraceId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE22B459AAA006F5CBC /* OtelTraceId+Datadog.swift */; }; + 3C6C7FEB2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */; }; + 3C6C7FEC2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */; }; 3C6C7FED2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */; }; 3C6C7FEE2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */; }; - 3C6C7FEF2B459AAA006F5CBC /* OtelSpanId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE42B459AAA006F5CBC /* OtelSpanId+Datadog.swift */; }; - 3C6C7FF02B459AAA006F5CBC /* OtelSpanId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE42B459AAA006F5CBC /* OtelSpanId+Datadog.swift */; }; - 3C6C7FFB2B459AF6006F5CBC /* OtelSpanId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF22B459AB3006F5CBC /* OtelSpanId+DatadogTests.swift */; }; - 3C6C7FFC2B459AF6006F5CBC /* OtelTraceId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF32B459AB3006F5CBC /* OtelTraceId+DatadogTests.swift */; }; - 3C6C7FFD2B459AF6006F5CBC /* OtelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF42B459AB3006F5CBC /* OtelSpanTests.swift */; }; - 3C6C7FFE2B459AF6006F5CBC /* OtelSpanId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF22B459AB3006F5CBC /* OtelSpanId+DatadogTests.swift */; }; - 3C6C7FFF2B459AF6006F5CBC /* OtelTraceId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF32B459AB3006F5CBC /* OtelTraceId+DatadogTests.swift */; }; - 3C6C80002B459AF6006F5CBC /* OtelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF42B459AB3006F5CBC /* OtelSpanTests.swift */; }; + 3C6C7FEF2B459AAA006F5CBC /* OTelSpanId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE42B459AAA006F5CBC /* OTelSpanId+Datadog.swift */; }; + 3C6C7FF02B459AAA006F5CBC /* OTelSpanId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE42B459AAA006F5CBC /* OTelSpanId+Datadog.swift */; }; + 3C6C7FFB2B459AF6006F5CBC /* OTelSpanId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF22B459AB3006F5CBC /* OTelSpanId+DatadogTests.swift */; }; + 3C6C7FFC2B459AF6006F5CBC /* OTelTraceId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF32B459AB3006F5CBC /* OTelTraceId+DatadogTests.swift */; }; + 3C6C7FFD2B459AF6006F5CBC /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF42B459AB3006F5CBC /* OTelSpanTests.swift */; }; + 3C6C7FFE2B459AF6006F5CBC /* OTelSpanId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF22B459AB3006F5CBC /* OTelSpanId+DatadogTests.swift */; }; + 3C6C7FFF2B459AF6006F5CBC /* OTelTraceId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF32B459AB3006F5CBC /* OTelTraceId+DatadogTests.swift */; }; + 3C6C80002B459AF6006F5CBC /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF42B459AB3006F5CBC /* OTelSpanTests.swift */; }; 3C74305C29FBC0480053B80F /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2DA2385298D57AA00C6C7E6 /* DatadogInternal.framework */; }; 3C85D42129F7C5C900AFF894 /* WebViewTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */; }; 3C85D42A29F7C70300AFF894 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257953E298ABA65008A1BE5 /* TestUtilities.framework */; }; @@ -1903,12 +1903,12 @@ 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelNoOpSpanBuilder.swift; sourceTree = ""; }; 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpan.swift; sourceTree = ""; }; 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanBuilder.swift; sourceTree = ""; }; - 3C6C7FE22B459AAA006F5CBC /* OtelTraceId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OtelTraceId+Datadog.swift"; sourceTree = ""; }; + 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceId+Datadog.swift"; sourceTree = ""; }; 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelNoOpSpan.swift; sourceTree = ""; }; - 3C6C7FE42B459AAA006F5CBC /* OtelSpanId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OtelSpanId+Datadog.swift"; sourceTree = ""; }; - 3C6C7FF22B459AB3006F5CBC /* OtelSpanId+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OtelSpanId+DatadogTests.swift"; sourceTree = ""; }; - 3C6C7FF32B459AB3006F5CBC /* OtelTraceId+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OtelTraceId+DatadogTests.swift"; sourceTree = ""; }; - 3C6C7FF42B459AB3006F5CBC /* OtelSpanTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OtelSpanTests.swift; sourceTree = ""; }; + 3C6C7FE42B459AAA006F5CBC /* OTelSpanId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelSpanId+Datadog.swift"; sourceTree = ""; }; + 3C6C7FF22B459AB3006F5CBC /* OTelSpanId+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelSpanId+DatadogTests.swift"; sourceTree = ""; }; + 3C6C7FF32B459AB3006F5CBC /* OTelTraceId+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceId+DatadogTests.swift"; sourceTree = ""; }; + 3C6C7FF42B459AB3006F5CBC /* OTelSpanTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanTests.swift; sourceTree = ""; }; 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewTracking.swift; sourceTree = ""; }; 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsSanitizerMock.swift; sourceTree = ""; }; 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzler.swift; sourceTree = ""; }; @@ -3117,9 +3117,9 @@ 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */, 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */, 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */, - 3C6C7FE22B459AAA006F5CBC /* OtelTraceId+Datadog.swift */, + 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */, 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */, - 3C6C7FE42B459AAA006F5CBC /* OtelSpanId+Datadog.swift */, + 3C6C7FE42B459AAA006F5CBC /* OTelSpanId+Datadog.swift */, ); path = OpenTelemetry; sourceTree = ""; @@ -3127,9 +3127,9 @@ 3C6C7FF12B459AB3006F5CBC /* OpenTelemetry */ = { isa = PBXGroup; children = ( - 3C6C7FF22B459AB3006F5CBC /* OtelSpanId+DatadogTests.swift */, - 3C6C7FF32B459AB3006F5CBC /* OtelTraceId+DatadogTests.swift */, - 3C6C7FF42B459AB3006F5CBC /* OtelSpanTests.swift */, + 3C6C7FF22B459AB3006F5CBC /* OTelSpanId+DatadogTests.swift */, + 3C6C7FF32B459AB3006F5CBC /* OTelTraceId+DatadogTests.swift */, + 3C6C7FF42B459AB3006F5CBC /* OTelSpanTests.swift */, ); path = OpenTelemetry; sourceTree = ""; @@ -8332,10 +8332,10 @@ 3C6C7FE52B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */, D2C1A51829C4C53F00946C31 /* OTSpan.swift in Sources */, D2C1A51429C4C53F00946C31 /* OTSpanContext.swift in Sources */, - 3C6C7FEB2B459AAA006F5CBC /* OtelTraceId+Datadog.swift in Sources */, + 3C6C7FEB2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */, 3C6C7FE92B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */, D2C1A51329C4C53F00946C31 /* OTReference.swift in Sources */, - 3C6C7FEF2B459AAA006F5CBC /* OtelSpanId+Datadog.swift in Sources */, + 3C6C7FEF2B459AAA006F5CBC /* OTelSpanId+Datadog.swift in Sources */, D2C1A4FB29C4C4CB00946C31 /* MessageReceivers.swift in Sources */, 61A2CC362A44B0A20000FF25 /* TraceConfiguration.swift in Sources */, 61A2CC392A44B0EA0000FF25 /* Trace.swift in Sources */, @@ -8372,13 +8372,13 @@ D2C1A51D29C4C75700946C31 /* SpanEventBuilderTests.swift in Sources */, D2C1A52229C4C75700946C31 /* DDNoopTracerTests.swift in Sources */, D2C1A51C29C4C75700946C31 /* ContextMessageReceiverTests.swift in Sources */, - 3C6C7FFD2B459AF6006F5CBC /* OtelSpanTests.swift in Sources */, + 3C6C7FFD2B459AF6006F5CBC /* OTelSpanTests.swift in Sources */, 619CE7612A458D66005588CB /* TraceTests.swift in Sources */, D2C1A52029C4C75700946C31 /* DDSpanTests.swift in Sources */, - 3C6C7FFC2B459AF6006F5CBC /* OtelTraceId+DatadogTests.swift in Sources */, + 3C6C7FFC2B459AF6006F5CBC /* OTelTraceId+DatadogTests.swift in Sources */, D2C1A51B29C4C75700946C31 /* DDSpanContextTests.swift in Sources */, D2C1A52729C4C7D000946C31 /* TracingFeatureMocks.swift in Sources */, - 3C6C7FFB2B459AF6006F5CBC /* OtelSpanId+DatadogTests.swift in Sources */, + 3C6C7FFB2B459AF6006F5CBC /* OTelSpanId+DatadogTests.swift in Sources */, D2C1A51F29C4C75700946C31 /* ActiveSpansPoolTests.swift in Sources */, D2C1A52529C4C75700946C31 /* SpanSanitizerTests.swift in Sources */, ); @@ -8530,10 +8530,10 @@ 3C6C7FE62B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */, D2C1A53D29C4F2DF00946C31 /* OTSpan.swift in Sources */, D2C1A53E29C4F2DF00946C31 /* OTSpanContext.swift in Sources */, - 3C6C7FEC2B459AAA006F5CBC /* OtelTraceId+Datadog.swift in Sources */, + 3C6C7FEC2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */, 3C6C7FEA2B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */, D2C1A53F29C4F2DF00946C31 /* OTReference.swift in Sources */, - 3C6C7FF02B459AAA006F5CBC /* OtelSpanId+Datadog.swift in Sources */, + 3C6C7FF02B459AAA006F5CBC /* OTelSpanId+Datadog.swift in Sources */, D2C1A54129C4F2DF00946C31 /* MessageReceivers.swift in Sources */, 61A2CC372A44B0A20000FF25 /* TraceConfiguration.swift in Sources */, 61A2CC3A2A44B0EA0000FF25 /* Trace.swift in Sources */, @@ -8570,13 +8570,13 @@ D2C1A56229C4F2E800946C31 /* SpanEventBuilderTests.swift in Sources */, D2C1A56329C4F2E800946C31 /* DDNoopTracerTests.swift in Sources */, D2C1A56529C4F2E800946C31 /* ContextMessageReceiverTests.swift in Sources */, - 3C6C80002B459AF6006F5CBC /* OtelSpanTests.swift in Sources */, + 3C6C80002B459AF6006F5CBC /* OTelSpanTests.swift in Sources */, 619CE7622A458D66005588CB /* TraceTests.swift in Sources */, D2C1A56629C4F2E800946C31 /* DDSpanTests.swift in Sources */, - 3C6C7FFF2B459AF6006F5CBC /* OtelTraceId+DatadogTests.swift in Sources */, + 3C6C7FFF2B459AF6006F5CBC /* OTelTraceId+DatadogTests.swift in Sources */, D2C1A56729C4F2E800946C31 /* DDSpanContextTests.swift in Sources */, D2C1A56829C4F2E800946C31 /* TracingFeatureMocks.swift in Sources */, - 3C6C7FFE2B459AF6006F5CBC /* OtelSpanId+DatadogTests.swift in Sources */, + 3C6C7FFE2B459AF6006F5CBC /* OTelSpanId+DatadogTests.swift in Sources */, D2C1A56929C4F2E800946C31 /* ActiveSpansPoolTests.swift in Sources */, D2C1A56A29C4F2E800946C31 /* SpanSanitizerTests.swift in Sources */, ); diff --git a/DatadogTrace/Sources/OpenTelemetry/OtelSpanId+Datadog.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpanId+Datadog.swift similarity index 100% rename from DatadogTrace/Sources/OpenTelemetry/OtelSpanId+Datadog.swift rename to DatadogTrace/Sources/OpenTelemetry/OTelSpanId+Datadog.swift diff --git a/DatadogTrace/Sources/OpenTelemetry/OtelTraceId+Datadog.swift b/DatadogTrace/Sources/OpenTelemetry/OTelTraceId+Datadog.swift similarity index 100% rename from DatadogTrace/Sources/OpenTelemetry/OtelTraceId+Datadog.swift rename to DatadogTrace/Sources/OpenTelemetry/OTelTraceId+Datadog.swift diff --git a/DatadogTrace/Tests/OpenTelemetry/OtelSpanId+DatadogTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanId+DatadogTests.swift similarity index 92% rename from DatadogTrace/Tests/OpenTelemetry/OtelSpanId+DatadogTests.swift rename to DatadogTrace/Tests/OpenTelemetry/OTelSpanId+DatadogTests.swift index b1e2566259..8085592062 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OtelSpanId+DatadogTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanId+DatadogTests.swift @@ -11,7 +11,7 @@ import OpenTelemetryApi @testable import DatadogTrace -class OtelSpanIdDatadogTests: XCTestCase { +class OTelSpanIdDatadogTests: XCTestCase { func testToDatadog() { let otelId = SpanId.random() let ddId = otelId.toDatadog() diff --git a/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift similarity index 99% rename from DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift rename to DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 63be2c1322..48cc819c7d 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OtelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -10,7 +10,7 @@ import DatadogInternal @testable import DatadogTrace -final class OtelSpanTests: XCTestCase { +final class OTelSpanTests: XCTestCase { func testSpanResourceNameDefault() { let writeSpanExpectation = expectation(description: "write span event") let core = PassthroughCoreMock(expectation: writeSpanExpectation) diff --git a/DatadogTrace/Tests/OpenTelemetry/OtelTraceId+DatadogTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelTraceId+DatadogTests.swift similarity index 92% rename from DatadogTrace/Tests/OpenTelemetry/OtelTraceId+DatadogTests.swift rename to DatadogTrace/Tests/OpenTelemetry/OTelTraceId+DatadogTests.swift index 749ec16f63..f5bcc823bc 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OtelTraceId+DatadogTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelTraceId+DatadogTests.swift @@ -11,7 +11,7 @@ import OpenTelemetryApi @testable import DatadogTrace -class OtelTraceIdDatadogTests: XCTestCase { +class OTelTraceIdDatadogTests: XCTestCase { func testToDatadog_onlyHigherOrderBitsAreConsidered() { let otelId = TraceId.random() let ddId = otelId.toDatadog() From f03cd630d04a869859d843ff2f38a0e2ba435774 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 4 Jan 2024 09:41:38 +0100 Subject: [PATCH 010/153] RUM-1836 feat(otel-tracer): remove status API for now, will add later --- .../Sources/OpenTelemetry/OTelSpan.swift | 23 ++----------------- .../Tests/OpenTelemetry/OTelSpanTests.swift | 5 +--- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index afae72b353..d08f82f5ca 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -27,23 +27,12 @@ internal class OTelSpan: OpenTelemetryApi.Span { /// and events can be added to it. var isRecording: Bool - /// `status` saves state of the code and description indicating - /// whether the span has recorded errors. This will be done by setting `error.message` - /// tag on the span. var status: OpenTelemetryApi.Status { get { - queue.sync { - _status - } + fatalError("Not implemented yet") } set { - queue.sync { - guard isRecording else { - return - } - - _status = newValue - } + fatalError("Not implemented yet") } } @@ -131,14 +120,6 @@ internal class OTelSpan: OpenTelemetryApi.Span { nestedSpan.setTag(key: key, value: value.description) } - // If status is error, error.message tag is added - switch self._status { - case .error(description: let description): - nestedSpan.setTag(key: DatadogTagKeys.errorMessage.rawValue, value: description) - case .ok, .unset: - break - } - // SpanKind maps to the `span.kind` tag in Datadog nestedSpan.setTag(key: DatadogTagKeys.spanKind.rawValue, value: kind.rawValue) } diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 48cc819c7d..18f85f263f 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -55,7 +55,7 @@ final class OTelSpanTests: XCTestCase { func testSpanEnd() { // Given let (name, ignoredName) = ("trueName", "invalidName") - let (code, ignoredCode) = (200, 400) + let (message, ignoredMessage) = ("message", "ignoredMessage") let (attributes, ignoredAttributes) = (["key": "value"], ["ignoredKey": "ignoredValue"]) @@ -64,7 +64,6 @@ final class OTelSpanTests: XCTestCase { let tracer: DatadogTracer = .mockWith(core: core) let span = tracer.spanBuilder(spanName: name).startSpan() - span.putHttpStatusCode(statusCode: code, reasonPhrase: message) for (key, value) in attributes { span.setAttribute(key: key, value: value) } @@ -76,7 +75,6 @@ final class OTelSpanTests: XCTestCase { // Then ignores span.name = ignoredName - span.putHttpStatusCode(statusCode: ignoredCode, reasonPhrase: ignoredMessage) for (key, value) in ignoredAttributes { span.setAttribute(key: key, value: value) } @@ -91,7 +89,6 @@ final class OTelSpanTests: XCTestCase { XCTAssertEqual(recordedSpan.resource, name) XCTAssertEqual(recordedSpan.operationName, name) let expectedTags = [ - "http.status_code": "200", "key": "value", "span.kind": "client", ] From dec5b26e280eec622e2e19e5f61cca22607c40d3 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 4 Jan 2024 09:47:28 +0100 Subject: [PATCH 011/153] RUM-1836 feat(otel-tracer): handle attribute cases one by one --- .../Sources/OpenTelemetry/OTelSpan.swift | 22 +++++++++++++++++++ .../Tests/OpenTelemetry/OTelSpanTests.swift | 9 -------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index d08f82f5ca..09820d5424 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -117,6 +117,28 @@ internal class OTelSpan: OpenTelemetryApi.Span { // Attributes maps to tags in Datadog for (key, value) in attributes { + switch value { + case .string(let value): + nestedSpan.setTag(key: key, value: value) + case .bool(let value): + nestedSpan.setTag(key: key, value: value.description) + case .int(let value): + nestedSpan.setTag(key: key, value: value.description) + case .double(let value): + nestedSpan.setTag(key: key, value: value.description) + // swiftlint:disable unavailable_function + case .stringArray: + fatalError("Not implemented yet") + case .boolArray: + fatalError("Not implemented yet") + case .intArray: + fatalError("Not implemented yet") + case .doubleArray: + fatalError("Not implemented yet") + case .set: + fatalError("Not implemented yet") + // swiftlint:enable unavailable_function + } nestedSpan.setTag(key: key, value: value.description) } diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 18f85f263f..55b799e80d 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -55,7 +55,6 @@ final class OTelSpanTests: XCTestCase { func testSpanEnd() { // Given let (name, ignoredName) = ("trueName", "invalidName") - let (message, ignoredMessage) = ("message", "ignoredMessage") let (attributes, ignoredAttributes) = (["key": "value"], ["ignoredKey": "ignoredValue"]) @@ -183,10 +182,6 @@ final class OTelSpanTests: XCTestCase { span.setAttribute(key: "key2", value: .string("value2")) span.setAttribute(key: "key3", value: .int(3)) span.setAttribute(key: "key4", value: .double(4.0)) - span.setAttribute(key: "key5", value: .stringArray(["value5", "value6"])) - span.setAttribute(key: "key6", value: .intArray([6, 7])) - span.setAttribute(key: "key7", value: .doubleArray([7.0, 8.0])) - span.setAttribute(key: "key8", value: .boolArray([true, false])) span.end() @@ -201,10 +196,6 @@ final class OTelSpanTests: XCTestCase { "key2": "value2", "key3": "3", "key4": "4.0", - "key5": "[\"value5\", \"value6\"]", - "key6": "[6, 7]", - "key7": "[7.0, 8.0]", - "key8": "[true, false]", "span.kind": "client", ] XCTAssertTagsEqual(recordedSpan.tags, expectedTags) From c0450520d1bb609ea75cd2e0bc003bc1f59baa42 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 4 Jan 2024 09:53:01 +0100 Subject: [PATCH 012/153] RUM-1836 feat(otel-tracer): remove unused variables --- DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 55b799e80d..57b81701c1 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -55,7 +55,6 @@ final class OTelSpanTests: XCTestCase { func testSpanEnd() { // Given let (name, ignoredName) = ("trueName", "invalidName") - let (message, ignoredMessage) = ("message", "ignoredMessage") let (attributes, ignoredAttributes) = (["key": "value"], ["ignoredKey": "ignoredValue"]) let writeSpanExpectation = expectation(description: "write span event") From a53814ee93d24d146f1f852f8977e691ecc12843 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Jan 2024 13:24:34 +0100 Subject: [PATCH 013/153] RUM-1836 feat(otel-tracer): PR feedback --- DatadogTrace/Sources/DDNoOps.swift | 2 +- DatadogTrace/Sources/DatadogTracer.swift | 2 +- .../{OTelNoOpSpan.swift => NOPOTelSpan.swift} | 2 +- ...Builder.swift => NOPOTelSpanBuilder.swift} | 4 +- .../Sources/OpenTelemetry/OTelSpan.swift | 95 ++++++++++++------- .../Tests/OpenTelemetry/OTelSpanTests.swift | 71 ++++++-------- 6 files changed, 94 insertions(+), 82 deletions(-) rename DatadogTrace/Sources/OpenTelemetry/{OTelNoOpSpan.swift => NOPOTelSpan.swift} (97%) rename DatadogTrace/Sources/OpenTelemetry/{OTelNoOpSpanBuilder.swift => NOPOTelSpanBuilder.swift} (94%) diff --git a/DatadogTrace/Sources/DDNoOps.swift b/DatadogTrace/Sources/DDNoOps.swift index b39f818020..85730d6e51 100644 --- a/DatadogTrace/Sources/DDNoOps.swift +++ b/DatadogTrace/Sources/DDNoOps.swift @@ -50,7 +50,7 @@ internal class DDNoopTracer: OTTracer, OpenTelemetryApi.Tracer { func spanBuilder(spanName: String) -> OpenTelemetryApi.SpanBuilder { warn() - return OTelNoOpSpanBuilder() + return NOPOTelSpanBuilder() } } diff --git a/DatadogTrace/Sources/DatadogTracer.swift b/DatadogTrace/Sources/DatadogTracer.swift index cb371aa813..ceb9c5b3b9 100644 --- a/DatadogTrace/Sources/DatadogTracer.swift +++ b/DatadogTrace/Sources/DatadogTracer.swift @@ -8,7 +8,7 @@ import Foundation import DatadogInternal import OpenTelemetryApi -internal class DatadogTracer: OTTracer, OpenTelemetryApi.Tracer { +internal final class DatadogTracer: OTTracer, OpenTelemetryApi.Tracer { internal weak var core: DatadogCoreProtocol? /// Global tags configured for Trace feature. diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift b/DatadogTrace/Sources/OpenTelemetry/NOPOTelSpan.swift similarity index 97% rename from DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift rename to DatadogTrace/Sources/OpenTelemetry/NOPOTelSpan.swift index 8bd29b4e44..76e2705483 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/NOPOTelSpan.swift @@ -7,7 +7,7 @@ import Foundation import OpenTelemetryApi -internal class OTelNoOpSpan: Span { +internal class NOPOTelSpan: Span { var kind: OpenTelemetryApi.SpanKind = .internal var name: String = "" diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift b/DatadogTrace/Sources/OpenTelemetry/NOPOTelSpanBuilder.swift similarity index 94% rename from DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift rename to DatadogTrace/Sources/OpenTelemetry/NOPOTelSpanBuilder.swift index b4b0c4560d..d20796a70a 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelNoOpSpanBuilder.swift +++ b/DatadogTrace/Sources/OpenTelemetry/NOPOTelSpanBuilder.swift @@ -7,10 +7,10 @@ import Foundation import OpenTelemetryApi -internal class OTelNoOpSpanBuilder: SpanBuilder { +internal class NOPOTelSpanBuilder: SpanBuilder { @discardableResult func startSpan() -> Span { - return OTelNoOpSpan() + return NOPOTelSpan() } @discardableResult diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 09820d5424..a4091a3ad9 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -19,7 +19,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { var attributes: [String: OpenTelemetryApi.AttributeValue] let context: OpenTelemetryApi.SpanContext var kind: OpenTelemetryApi.SpanKind - let nestedSpan: DDSpan + let ddSpan: DDSpan let tracer: DatadogTracer let queue: DispatchQueue @@ -50,7 +50,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { } _name = newValue } - nestedSpan.setOperationName(name) + ddSpan.setOperationName(name) } } @@ -72,7 +72,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { self.isRecording = true self.queue = tracer.queue self.tracer = tracer - self.nestedSpan = .init( + self.ddSpan = .init( tracer: tracer, context: .init( traceID: context.traceId.toDatadog(), @@ -109,43 +109,64 @@ internal class OTelSpan: OpenTelemetryApi.Span { } func end(time: Date) { + let semaphore = DispatchSemaphore(value: 0) + var ended = false + var tags: [String: String] = [:] + queue.sync { guard isRecording else { + ended = true + semaphore.signal() return } isRecording = false + tags = makeTags() + semaphore.signal() + } + semaphore.wait() - // Attributes maps to tags in Datadog - for (key, value) in attributes { - switch value { - case .string(let value): - nestedSpan.setTag(key: key, value: value) - case .bool(let value): - nestedSpan.setTag(key: key, value: value.description) - case .int(let value): - nestedSpan.setTag(key: key, value: value.description) - case .double(let value): - nestedSpan.setTag(key: key, value: value.description) - // swiftlint:disable unavailable_function - case .stringArray: - fatalError("Not implemented yet") - case .boolArray: - fatalError("Not implemented yet") - case .intArray: - fatalError("Not implemented yet") - case .doubleArray: - fatalError("Not implemented yet") - case .set: - fatalError("Not implemented yet") - // swiftlint:enable unavailable_function - } - nestedSpan.setTag(key: key, value: value.description) - } + // if the span was already ended before, we don't want to end it again + guard !ended else { + return + } + + // There is no need to lock here, because `DDSpan` is thread-safe + for (key, value) in tags { + ddSpan.setTag(key: key, value: value) + } + + // SpanKind maps to the `span.kind` tag in Datadog + ddSpan.setTag(key: DatadogTagKeys.spanKind.rawValue, value: kind.rawValue) + ddSpan.finish(at: time) + } - // SpanKind maps to the `span.kind` tag in Datadog - nestedSpan.setTag(key: DatadogTagKeys.spanKind.rawValue, value: kind.rawValue) + private func makeTags() -> [String: String] { + var tags = [String: String]() + for (key, value) in attributes { + switch value { + case .string(let value): + tags[key] = value + case .bool(let value): + tags[key] = value.description + case .int(let value): + tags[key] = value.description + case .double(let value): + tags[key] = value.description + // swiftlint:disable unavailable_function + case .stringArray: + fatalError("Not implemented yet") + case .boolArray: + fatalError("Not implemented yet") + case .intArray: + fatalError("Not implemented yet") + case .doubleArray: + fatalError("Not implemented yet") + case .set: + fatalError("Not implemented yet") + // swiftlint:enable unavailable_function + } } - nestedSpan.finish(at: time) + return tags } var description: String { @@ -153,10 +174,12 @@ internal class OTelSpan: OpenTelemetryApi.Span { } func setAttribute(key: String, value: OpenTelemetryApi.AttributeValue?) { - guard isRecording else { - return - } + queue.sync { + guard isRecording else { + return + } - attributes[key] = value + attributes[key] = value + } } } diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 57b81701c1..e5d43d89fd 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -24,9 +24,9 @@ final class OTelSpanTests: XCTestCase { // Then waitForExpectations(timeout: 0.5, handler: nil) - let events: [SpanEventsEnvelope] = core.events() - XCTAssertEqual(events.count, 1) - let recordedSpan = events.first!.spans.first! + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + let recordedSpan = recordedSpans.first! XCTAssertEqual(recordedSpan.resource, "OperationName") XCTAssertEqual(recordedSpan.operationName, "OperationName") } @@ -45,9 +45,9 @@ final class OTelSpanTests: XCTestCase { // Then waitForExpectations(timeout: 0.5, handler: nil) - let events: [SpanEventsEnvelope] = core.events() - XCTAssertEqual(events.count, 1) - let recordedSpan = events.first!.spans.first! + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + let recordedSpan = recordedSpans.first! XCTAssertEqual(recordedSpan.resource, "NewOperationName") XCTAssertEqual(recordedSpan.operationName, "NewOperationName") } @@ -80,9 +80,9 @@ final class OTelSpanTests: XCTestCase { span.end() waitForExpectations(timeout: 0.5, handler: nil) - let events: [SpanEventsEnvelope] = core.events() - XCTAssertEqual(events.count, 1) - let recordedSpan = events.first!.spans.first! + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + let recordedSpan = recordedSpans.first! XCTAssertEqual(recordedSpan.resource, name) XCTAssertEqual(recordedSpan.operationName, name) @@ -90,7 +90,7 @@ final class OTelSpanTests: XCTestCase { "key": "value", "span.kind": "client", ] - XCTAssertTagsEqual(recordedSpan.tags, expectedTags) + DDAssertDictionariesEqual(recordedSpan.tags, expectedTags) } func testSetParentSpan() { @@ -110,10 +110,10 @@ final class OTelSpanTests: XCTestCase { // Then waitForExpectations(timeout: 0.5, handler: nil) - let events: [SpanEventsEnvelope] = core.events() - XCTAssertEqual(events.count, 2) - let child = events.first!.spans.first! - let parent = events.last!.spans.first! + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 2) + let child = recordedSpans.first! + let parent = recordedSpans.last! XCTAssertEqual(parent.parentID, nil) XCTAssertEqual(child.parentID, parent.spanID) } @@ -135,10 +135,10 @@ final class OTelSpanTests: XCTestCase { // Then waitForExpectations(timeout: 0.5, handler: nil) - let events: [SpanEventsEnvelope] = core.events() - XCTAssertEqual(events.count, 2) - let child = events.first!.spans.first! - let parent = events.last!.spans.first! + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 2) + let child = recordedSpans.first! + let parent = recordedSpans.last! XCTAssertEqual(parent.parentID, nil) XCTAssertEqual(child.parentID, parent.spanID) } @@ -160,10 +160,10 @@ final class OTelSpanTests: XCTestCase { // Then waitForExpectations(timeout: 0.5, handler: nil) - let events: [SpanEventsEnvelope] = core.events() - XCTAssertEqual(events.count, 2) - let child = events.first!.spans.first! - let parent = events.last!.spans.first! + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 2) + let child = recordedSpans.first! + let parent = recordedSpans.last! XCTAssertEqual(parent.parentID, nil) XCTAssertEqual(child.parentID, nil) } @@ -186,9 +186,9 @@ final class OTelSpanTests: XCTestCase { // Then waitForExpectations(timeout: 0.5, handler: nil) - let events: [SpanEventsEnvelope] = core.events() - XCTAssertEqual(events.count, 1) - let recordedSpan = events.first!.spans.first! + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + let recordedSpan = recordedSpans.first! let expectedTags = [ "key": "true", @@ -197,24 +197,13 @@ final class OTelSpanTests: XCTestCase { "key4": "4.0", "span.kind": "client", ] - XCTAssertTagsEqual(recordedSpan.tags, expectedTags) + DDAssertDictionariesEqual(recordedSpan.tags, expectedTags) } } -func XCTAssertTagsEqual( - _ dict1: [String: String], - _ dict2: [String: String], - file: StaticString = #filePath, - line: UInt = #line -) { - XCTAssertEqual(dict1.count, dict2.count, file: file, line: line) - for (key, value) in dict1 { - XCTAssertEqual( - dict2[key], - value, - "Expected \(key) to be \(value), but was \(String(describing: dict2[key]))", - file: file, - line: line - ) +extension PassthroughCoreMock { + func spans() -> [SpanEvent] { + let events: [SpanEventsEnvelope] = self.events() + return events.flatMap { $0.spans } } } From 140c95b19c45c4a884d8ba138aae85e28fc9ffcf Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Jan 2024 13:31:43 +0100 Subject: [PATCH 014/153] RUM-1836 feat(otel-tracer): corresponding reference change of rename --- Datadog/Datadog.xcodeproj/project.pbxproj | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 06da6fee37..99d61151a3 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -36,16 +36,12 @@ 3C41693C29FBF4D50042B9D2 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; 3C6C7FDB2B45738C006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDA2B45738C006F5CBC /* OpenTelemetryApi */; }; 3C6C7FDD2B457392006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDC2B457392006F5CBC /* OpenTelemetryApi */; }; - 3C6C7FE52B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */; }; - 3C6C7FE62B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */; }; 3C6C7FE72B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; 3C6C7FE82B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; 3C6C7FE92B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; }; 3C6C7FEA2B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; }; 3C6C7FEB2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */; }; 3C6C7FEC2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */; }; - 3C6C7FED2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */; }; - 3C6C7FEE2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */; }; 3C6C7FEF2B459AAA006F5CBC /* OTelSpanId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE42B459AAA006F5CBC /* OTelSpanId+Datadog.swift */; }; 3C6C7FF02B459AAA006F5CBC /* OTelSpanId+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE42B459AAA006F5CBC /* OTelSpanId+Datadog.swift */; }; 3C6C7FFB2B459AF6006F5CBC /* OTelSpanId+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FF22B459AB3006F5CBC /* OTelSpanId+DatadogTests.swift */; }; @@ -60,6 +56,10 @@ 3C85D42C29F7C87D00AFF894 /* HostsSanitizerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */; }; 3C85D42D29F7C87D00AFF894 /* HostsSanitizerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */; }; 3C9C6BB429F7C0C000581C43 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23039A5298D513C001A1FA3 /* DatadogInternal.framework */; }; + 3CB012DD2B482E0400557951 /* NOPOTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */; }; + 3CB012DE2B482E0400557951 /* NOPOTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */; }; + 3CB012DF2B482E0400557951 /* NOPOTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */; }; + 3CB012E02B482E0400557951 /* NOPOTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */; }; 3CB32AD42ACB733000D602ED /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */; }; 3CB32AD52ACB733000D602ED /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */; }; 3CB32AD72ACB735600D602ED /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */; }; @@ -1900,17 +1900,17 @@ 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaTypeExtensions.swift; sourceTree = ""; }; 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzler.swift; sourceTree = ""; }; 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzlerTests.swift; sourceTree = ""; }; - 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelNoOpSpanBuilder.swift; sourceTree = ""; }; 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpan.swift; sourceTree = ""; }; 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanBuilder.swift; sourceTree = ""; }; 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceId+Datadog.swift"; sourceTree = ""; }; - 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelNoOpSpan.swift; sourceTree = ""; }; 3C6C7FE42B459AAA006F5CBC /* OTelSpanId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelSpanId+Datadog.swift"; sourceTree = ""; }; 3C6C7FF22B459AB3006F5CBC /* OTelSpanId+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelSpanId+DatadogTests.swift"; sourceTree = ""; }; 3C6C7FF32B459AB3006F5CBC /* OTelTraceId+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceId+DatadogTests.swift"; sourceTree = ""; }; 3C6C7FF42B459AB3006F5CBC /* OTelSpanTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanTests.swift; sourceTree = ""; }; 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewTracking.swift; sourceTree = ""; }; 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsSanitizerMock.swift; sourceTree = ""; }; + 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NOPOTelSpan.swift; sourceTree = ""; }; + 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NOPOTelSpanBuilder.swift; sourceTree = ""; }; 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzler.swift; sourceTree = ""; }; 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzlerTests.swift; sourceTree = ""; }; 3CBDE66D2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzler.swift; sourceTree = ""; }; @@ -3114,11 +3114,11 @@ 3C6C7FDE2B459AAA006F5CBC /* OpenTelemetry */ = { isa = PBXGroup; children = ( - 3C6C7FDF2B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift */, + 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */, + 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */, 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */, 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */, 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */, - 3C6C7FE32B459AAA006F5CBC /* OTelNoOpSpan.swift */, 3C6C7FE42B459AAA006F5CBC /* OTelSpanId+Datadog.swift */, ); path = OpenTelemetry; @@ -8327,9 +8327,8 @@ D2C1A50C29C4C4CB00946C31 /* DDNoOps.swift in Sources */, D2C1A4FC29C4C4CB00946C31 /* RequestBuilder.swift in Sources */, D2C1A50D29C4C4CB00946C31 /* SpanTagsReducer.swift in Sources */, + 3CB012DD2B482E0400557951 /* NOPOTelSpan.swift in Sources */, D2C1A51A29C4C5DD00946C31 /* JSONEncoder.swift in Sources */, - 3C6C7FED2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */, - 3C6C7FE52B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */, D2C1A51829C4C53F00946C31 /* OTSpan.swift in Sources */, D2C1A51429C4C53F00946C31 /* OTSpanContext.swift in Sources */, 3C6C7FEB2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */, @@ -8340,6 +8339,7 @@ 61A2CC362A44B0A20000FF25 /* TraceConfiguration.swift in Sources */, 61A2CC392A44B0EA0000FF25 /* Trace.swift in Sources */, D2C1A50029C4C4CB00946C31 /* ActiveSpansPool.swift in Sources */, + 3CB012DF2B482E0400557951 /* NOPOTelSpanBuilder.swift in Sources */, D2C1A50929C4C4CB00946C31 /* SpanEventEncoder.swift in Sources */, 3C6C7FE72B459AAA006F5CBC /* OTelSpan.swift in Sources */, D2C1A4FE29C4C4CB00946C31 /* SpanEventMapper.swift in Sources */, @@ -8525,9 +8525,8 @@ D2C1A53929C4F2DF00946C31 /* DDNoOps.swift in Sources */, D2C1A53A29C4F2DF00946C31 /* RequestBuilder.swift in Sources */, D2C1A53B29C4F2DF00946C31 /* SpanTagsReducer.swift in Sources */, + 3CB012DE2B482E0400557951 /* NOPOTelSpan.swift in Sources */, D2C1A53C29C4F2DF00946C31 /* JSONEncoder.swift in Sources */, - 3C6C7FEE2B459AAA006F5CBC /* OTelNoOpSpan.swift in Sources */, - 3C6C7FE62B459AAA006F5CBC /* OTelNoOpSpanBuilder.swift in Sources */, D2C1A53D29C4F2DF00946C31 /* OTSpan.swift in Sources */, D2C1A53E29C4F2DF00946C31 /* OTSpanContext.swift in Sources */, 3C6C7FEC2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */, @@ -8538,6 +8537,7 @@ 61A2CC372A44B0A20000FF25 /* TraceConfiguration.swift in Sources */, 61A2CC3A2A44B0EA0000FF25 /* Trace.swift in Sources */, D2C1A54229C4F2DF00946C31 /* ActiveSpansPool.swift in Sources */, + 3CB012E02B482E0400557951 /* NOPOTelSpanBuilder.swift in Sources */, D2C1A54329C4F2DF00946C31 /* SpanEventEncoder.swift in Sources */, 3C6C7FE82B459AAA006F5CBC /* OTelSpan.swift in Sources */, D2C1A54429C4F2DF00946C31 /* SpanEventMapper.swift in Sources */, From 2eab9ac10d14144069ed6360fa2003b33413c5d4 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Jan 2024 17:18:41 +0100 Subject: [PATCH 015/153] RUM-1836 feat(otel-tracer): remove semaphore usage --- DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index a4091a3ad9..1ffcc037f0 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -109,21 +109,17 @@ internal class OTelSpan: OpenTelemetryApi.Span { } func end(time: Date) { - let semaphore = DispatchSemaphore(value: 0) var ended = false var tags: [String: String] = [:] queue.sync { guard isRecording else { ended = true - semaphore.signal() return } isRecording = false tags = makeTags() - semaphore.signal() } - semaphore.wait() // if the span was already ended before, we don't want to end it again guard !ended else { From 5996b42d58eca33cbc71592924d68dfe29208af7 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Jan 2024 13:56:36 +0100 Subject: [PATCH 016/153] RUM-1836 feat(otel-tracer): add support for event APIs --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 ++ .../Tests/Datadog/Tracing/OTelSpanTests.swift | 67 +++++++++++++++++++ DatadogTrace/Sources/DDSpan.swift | 10 ++- .../TracingWithLoggingIntegration.swift | 12 +++- .../Sources/OpenTelemetry/OTelSpan.swift | 46 +++++++++++-- 5 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 99d61151a3..f37d0b2280 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -84,6 +84,8 @@ 3CCCA5C82ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */; }; 3CE11A1129F7BE0900202522 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; 3CE11A1229F7BE0900202522 /* DatadogWebViewTracking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3CF673362B4807490016CE17 /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF673352B4807490016CE17 /* OTelSpanTests.swift */; }; + 3CF673372B4807490016CE17 /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF673352B4807490016CE17 /* OTelSpanTests.swift */; }; 3CFD81952ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */; }; 3CFD81962ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */; }; 49274906288048B500ECD49B /* InternalProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49274903288048AA00ECD49B /* InternalProxyTests.swift */; }; @@ -1924,6 +1926,7 @@ 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDURLSessionInstrumentationConfigurationTests.swift; sourceTree = ""; }; 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogWebViewTracking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3CE11A0529F7BE0300202522 /* DatadogWebViewTrackingTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogWebViewTrackingTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3CF673352B4807490016CE17 /* OTelSpanTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanTests.swift; sourceTree = ""; }; 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaTypeExtensionsTests.swift; sourceTree = ""; }; 49274903288048AA00ECD49B /* InternalProxyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalProxyTests.swift; sourceTree = ""; }; 49274908288048F400ECD49B /* RUMInternalProxyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMInternalProxyTests.swift; sourceTree = ""; }; @@ -4813,6 +4816,7 @@ 61C5A89724509C1100DA608C /* Tracing */ = { isa = PBXGroup; children = ( + 3CF673352B4807490016CE17 /* OTelSpanTests.swift */, 61AD4E3924534075006E34EA /* DatadogTraceFeatureTests.swift */, D28F836729C9E71C00EF8EA2 /* DDSpanTests.swift */, D28F836A29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift */, @@ -7631,6 +7635,7 @@ D24C9C6429A7CB7B002057CF /* CrashLogReceiverTests.swift in Sources */, 61B5E42126DF85C7000B0A5F /* DDRUMMonitor+apiTests.m in Sources */, 61133C4E2423990D00786299 /* UIKitMocks.swift in Sources */, + 3CF673362B4807490016CE17 /* OTelSpanTests.swift in Sources */, D20FD9D32ACC08D1004D3569 /* WebKitMocks.swift in Sources */, 618353BC2A69470A0085F84A /* CoreMetricsIntegrationTests.swift in Sources */, 61133C4D2423990D00786299 /* CoreTelephonyMocks.swift in Sources */, @@ -8752,6 +8757,7 @@ D2B3F053282E827B00C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */, D20605BA2875729E0047275C /* ContextValuePublisherMock.swift in Sources */, D22743E729DEB953001A7EF9 /* UIApplicationSwizzlerTests.swift in Sources */, + 3CF673372B4807490016CE17 /* OTelSpanTests.swift in Sources */, D25CFAA029C860E300E3A43D /* TracingFeatureMocks.swift in Sources */, D2CB6F5F27C520D400A62B57 /* DDNSURLSessionDelegate+apiTests.m in Sources */, D224430E29E95D6700274EC7 /* CrashReportReceiverTests.swift in Sources */, diff --git a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift new file mode 100644 index 0000000000..223ba33e73 --- /dev/null +++ b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift @@ -0,0 +1,67 @@ +/* + * 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 TestUtilities +import DatadogInternal +import OpenTelemetryApi + +@testable import DatadogLogs +@testable import DatadogTrace + +final class OTelSpanTests: XCTestCase { + func testAddEvent() { + let core = DatadogCoreProxy() + defer { core.flushAndTearDown() } + + Logs.enable(in: core) + Trace.enable(in: core) + + // Given + let tracer = Tracer.shared(in: core) + let span = tracer + .spanBuilder(spanName: "OperationName") + .startSpan() + + // When + let attributes: [String: OpenTelemetryApi.AttributeValue] = .mock() + span.addEvent(name: "Otel Span Event", attributes: attributes, timestamp: Date()) + + // Then + let logs: [LogEvent] = core.waitAndReturnEvents(ofFeature: LogsFeature.name, ofType: LogEvent.self) + XCTAssertEqual(logs.count, 1) + DDAssertJSONEqual(AnyEncodable(logs[0].attributes.userAttributes), AnyEncodable(attributes)) + } +} + +extension Dictionary where Key == String, Value == OpenTelemetryApi.AttributeValue { + static func mock() -> Self { + return [ + "string": .string("value"), + "bool": .bool(true), + "int": .int(2), + "double": .double(2.0), + "stringArray": .stringArray(["value1", "value2"]), + "boolArray": .boolArray([true, false]), + "intArray": .intArray([1, 2]), + "doubleArray": .doubleArray([1.0, 2.0]), + "set": .set(.init(labels: .leafMock())) + ] + } + + static func leafMock() -> Self { + return [ + "string": .string("value"), + "bool": .bool(true), + "int": .int(2), + "double": .double(2.0), + "stringArray": .stringArray(["value1", "value2"]), + "boolArray": .boolArray([true, false]), + "intArray": .intArray([1, 2]), + "doubleArray": .doubleArray([1.0, 2.0]) + ] + } +} diff --git a/DatadogTrace/Sources/DDSpan.swift b/DatadogTrace/Sources/DDSpan.swift index 24bcbe5e85..25f96cfeca 100644 --- a/DatadogTrace/Sources/DDSpan.swift +++ b/DatadogTrace/Sources/DDSpan.swift @@ -108,13 +108,17 @@ internal final class DDSpan: OTSpan { } func log(fields: [String: Encodable], timestamp: Date) { + log(message: nil, fields: fields, timestamp: timestamp) + } + + func log(message: String?, fields: [String: Encodable], timestamp: Date) { queue.async { if self.warnIfFinished("log(fields:timestamp:)") { return } self.unsafeLogFields.append(fields) } - sendSpanLogs(fields: fields, date: timestamp) + sendSpanLogs(message: message, fields: fields, date: timestamp) } func finish(at time: Date) { @@ -177,8 +181,8 @@ internal final class DDSpan: OTSpan { } } - private func sendSpanLogs(fields: [String: Encodable], date: Date) { - loggingIntegration.writeLog(withSpanContext: ddContext, fields: fields, date: date, else: { + private func sendSpanLogs(message: String?, fields: [String: Encodable], date: Date) { + loggingIntegration.writeLog(withSpanContext: ddContext, message: message, fields: fields, date: date, else: { self.queue.async { DD.logger.warn("The log for span \"\(self.unsafeOperationName)\" will not be send, because the Logs feature is not enabled.") } }) } diff --git a/DatadogTrace/Sources/Integrations/TracingWithLoggingIntegration.swift b/DatadogTrace/Sources/Integrations/TracingWithLoggingIntegration.swift index 26f5237577..58c6936867 100644 --- a/DatadogTrace/Sources/Integrations/TracingWithLoggingIntegration.swift +++ b/DatadogTrace/Sources/Integrations/TracingWithLoggingIntegration.swift @@ -60,7 +60,14 @@ internal struct TracingWithLoggingIntegration { self.networkInfoEnabled = networkInfoEnabled } - func writeLog(withSpanContext spanContext: DDSpanContext, fields: [String: Encodable], date: Date, else fallback: @escaping () -> Void) { + // swiftlint:disable function_default_parameter_at_end + func writeLog( + withSpanContext spanContext: DDSpanContext, + message: String? = nil, + fields: [String: Encodable], + date: Date, + else fallback: @escaping () -> Void + ) { guard let core = core else { return } @@ -69,7 +76,7 @@ internal struct TracingWithLoggingIntegration { // get the log message and optional error kind let errorKind = userAttributes.removeValue(forKey: OTLogFields.errorKind) as? String - let message = (userAttributes.removeValue(forKey: OTLogFields.message) as? String) ?? Constants.defaultLogMessage + let message = (userAttributes.removeValue(forKey: OTLogFields.message) as? String) ?? message ?? Constants.defaultLogMessage let errorStack = userAttributes.removeValue(forKey: OTLogFields.stack) as? String // infer the log level @@ -107,4 +114,5 @@ internal struct TracingWithLoggingIntegration { else: fallback ) } + // swiftlint:enable function_default_parameter_at_end } diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 1ffcc037f0..6e6ad761db 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -86,23 +86,57 @@ internal class OTelSpan: OpenTelemetryApi.Span { ) } - // swiftlint:disable unavailable_function + /// Sends a span event which is akin to a log in Datadog + /// - Parameter name: name of the event func addEvent(name: String) { - fatalError("Not implemented yet") + addEvent(name: name, timestamp: .init()) } + /// Sends a span event which is akin to a log in Datadog + /// - Parameters: + /// - name: name of the event + /// - timestamp: timestamp of the event func addEvent(name: String, timestamp: Date) { - fatalError("Not implemented yet") + addEvent(name: name, attributes: .init(), timestamp: .init()) } + /// Sends a span event which is akin to a log in Datadog + /// - Parameters: + /// - name: name of the event + /// - attributes: attributes of the event + /// - timestamp: timestamp of the event func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue]) { - fatalError("Not implemented yet") + addEvent(name: name, attributes: attributes, timestamp: .init()) } + /// Sends a span event which is akin to a log in Datadog + /// - Parameters: + /// - name: name of the event + /// - attributes: attributes of the event + /// - timestamp: timestamp of the event func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue], timestamp: Date) { - fatalError("Not implemented yet") + let semaphore = DispatchSemaphore(value: 0) + var ended = false + queue.sync { + guard isRecording else { + ended = true + return + } + semaphore.signal() + } + semaphore.wait() + + // if the span was already ended before, we don't want to end it again + guard !ended else { + return + } + + // There is no need to lock here, because `DDSpan` is thread-safe + + // fields needs to be a dictionary of [String: Encodable] which is satisfied by opentelemetry-swift + // and Datadog SDK doesn't care about the representation + ddSpan.log(message: name, fields: attributes, timestamp: timestamp) } - // swiftlint:enable unavailable_function func end() { end(time: Date()) From 2b20116072cabd91cb5960ac485ce255526d948d Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Jan 2024 16:26:08 +0100 Subject: [PATCH 017/153] RUM-1836 feat(otel-tracer): test only leaf nodes --- DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift index 223ba33e73..4220d2c184 100644 --- a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift +++ b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift @@ -27,7 +27,7 @@ final class OTelSpanTests: XCTestCase { .startSpan() // When - let attributes: [String: OpenTelemetryApi.AttributeValue] = .mock() + let attributes: [String: OpenTelemetryApi.AttributeValue] = .leafMock() span.addEvent(name: "Otel Span Event", attributes: attributes, timestamp: Date()) // Then From 053a7e59e13a15ca4c8fd9eba69d649d8c3e98f3 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Jan 2024 17:18:41 +0100 Subject: [PATCH 018/153] RUM-1836 feat(otel-tracer): remove semaphore usage --- DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 6e6ad761db..af53b6671b 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -115,16 +115,13 @@ internal class OTelSpan: OpenTelemetryApi.Span { /// - attributes: attributes of the event /// - timestamp: timestamp of the event func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue], timestamp: Date) { - let semaphore = DispatchSemaphore(value: 0) var ended = false queue.sync { guard isRecording else { ended = true return } - semaphore.signal() } - semaphore.wait() // if the span was already ended before, we don't want to end it again guard !ended else { From b69b66476c3488f14d5f6b3d9c23c31f280970e5 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 10 Jan 2024 14:19:36 +0100 Subject: [PATCH 019/153] RUM-1836 feat(otel-tracer): add support for status API --- .../Sources/OpenTelemetry/OTelSpan.swift | 50 +++++++- .../Tests/OpenTelemetry/OTelSpanTests.swift | 121 ++++++++++++++++++ 2 files changed, 168 insertions(+), 3 deletions(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index af53b6671b..cdc28658f1 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -9,7 +9,23 @@ import OpenTelemetryApi internal enum DatadogTagKeys: String { case spanKind = "span.kind" - case errorMessage = "error.Message" + case errorType = "error.type" + case errorMessage = "error.message" +} + +internal extension OpenTelemetryApi.Status { + /// Ok > Error > Unset + /// https://opentelemetry.io/docs/specs/otel/trace/api/#set-status + var priority: UInt { + switch self { + case .ok: + return 3 + case .error: + return 2 + case .unset: + return 1 + } + } } internal class OTelSpan: OpenTelemetryApi.Span { @@ -27,12 +43,28 @@ internal class OTelSpan: OpenTelemetryApi.Span { /// and events can be added to it. var isRecording: Bool + /// Saves status of the span indicating whether the span has recorded errors. + /// This will be done by setting `error.message` tag on the span. var status: OpenTelemetryApi.Status { get { - fatalError("Not implemented yet") + queue.sync { + _status + } } set { - fatalError("Not implemented yet") + queue.sync { + guard isRecording else { + return + } + + // If the code has been set to a higher value before (Ok > Error > Unset), + // the code will not be changed. + guard newValue.priority >= _status.priority else { + return + } + + _status = newValue + } } } @@ -162,6 +194,18 @@ internal class OTelSpan: OpenTelemetryApi.Span { ddSpan.setTag(key: key, value: value) } + switch status { + case .ok, .unset: + break + case .error(description: let description): + // set error tags on the span + tags[DatadogTagKeys.errorMessage.rawValue] = description + + // send error log to Datadog + // Empty kind or description is equivalent to not present + ddSpan.setError(kind: "" , message: description) + } + // SpanKind maps to the `span.kind` tag in Datadog ddSpan.setTag(key: DatadogTagKeys.spanKind.rawValue, value: kind.rawValue) ddSpan.finish(at: time) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index e5d43d89fd..189d3a4463 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -199,6 +199,127 @@ final class OTelSpanTests: XCTestCase { ] DDAssertDictionariesEqual(recordedSpan.tags, expectedTags) } + + func testStatus_whenStatusIsNotSet() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "Span").startSpan() + + // When + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + + let recordedSpan = recordedSpans.first! + XCTAssertFalse(recordedSpan.isError) + XCTAssertEqual(recordedSpan.tags["error.type"], nil) + XCTAssertEqual(recordedSpan.tags["error.message"], nil) + XCTAssertEqual(recordedSpan.tags["error.stack"], nil) + } + + func testStatus_whenStatusIsOk() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "Span").startSpan() + + // When + span.status = .ok + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + + let recordedSpan = recordedSpans.first! + XCTAssertFalse(recordedSpan.isError) + XCTAssertEqual(recordedSpan.tags["error.type"], nil) + XCTAssertEqual(recordedSpan.tags["error.message"], nil) + } + + func testStatus_whenStatusIsErrorWithMessage() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "Span").startSpan() + + // When + span.status = .error(description: "error description") + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + + let recordedSpan = recordedSpans.first! + XCTAssertTrue(recordedSpan.isError) + XCTAssertEqual(recordedSpan.tags["error.type"], "") + // In OLTP world, error message is set as `error.message` + // but during the migration we want to keep it as `error.msg`. + // https://github.com/open-telemetry/opentelemetry-proto/blob/724e427879e3d2bae2edc0218fff06e37b9eb46e/opentelemetry/proto/trace/v1/trace.proto#L264 + XCTAssertEqual(recordedSpan.tags["error.msg"], "error description") + } + + func testStatus_givenStatusOk_whenSetStatusCalledWithErrorAndUnset() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "Span").startSpan() + span.status = .ok + + // When + span.status = .error(description: "error description") + span.status = .unset + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + + let recordedSpan = recordedSpans.first! + XCTAssertFalse(recordedSpan.isError) + XCTAssertEqual(recordedSpan.tags["error.type"], nil) + XCTAssertEqual(recordedSpan.tags["error.msg"], nil) + } + + func testStatus_givenStatusError_whenSetStatusCalledWithOkAndUnset() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "Span").startSpan() + span.status = .error(description: "error description") + + // When + span.status = .unset + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + + let recordedSpan = recordedSpans.first! + XCTAssertTrue(recordedSpan.isError) + XCTAssertEqual(recordedSpan.tags["error.type"], "") + XCTAssertEqual(recordedSpan.tags["error.msg"], "error description") + } } extension PassthroughCoreMock { From b6eccac46e7120a73f23d229ad9c3bce124b6b29 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 10 Jan 2024 14:40:28 +0100 Subject: [PATCH 020/153] RUM-1836 feat(otel-tracer): fix linter --- DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index cdc28658f1..31e701cf55 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -203,7 +203,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { // send error log to Datadog // Empty kind or description is equivalent to not present - ddSpan.setError(kind: "" , message: description) + ddSpan.setError(kind: "", message: description) } // SpanKind maps to the `span.kind` tag in Datadog From ad114f48ca76bb0d3572e54110f052a9a62a5de9 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 10 Jan 2024 18:26:09 +0100 Subject: [PATCH 021/153] RUM-1836 feat(otel-tracer): support multi level tags using key flattening --- Datadog/Datadog.xcodeproj/project.pbxproj | 12 ++ .../OTelAttributeValue+Datadog.swift | 71 +++++++++++ .../Sources/OpenTelemetry/OTelSpan.swift | 31 +---- .../OTelAttributeValue+DatadogTests.swift | 119 ++++++++++++++++++ .../Tests/OpenTelemetry/OTelSpanTests.swift | 1 + 5 files changed, 204 insertions(+), 30 deletions(-) create mode 100644 DatadogTrace/Sources/OpenTelemetry/OTelAttributeValue+Datadog.swift create mode 100644 DatadogTrace/Tests/OpenTelemetry/OTelAttributeValue+DatadogTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 99d61151a3..330599b276 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -78,6 +78,10 @@ 3CBDE6882AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */; }; 3CBDE68A2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */; }; 3CBDE68B2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */; }; + 3CC6AD182B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */; }; + 3CC6AD192B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */; }; + 3CC6AD1D2B4F07FA00015B18 /* OTelAttributeValue+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */; }; + 3CC6AD1E2B4F07FB00015B18 /* OTelAttributeValue+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */; }; 3CCCA5C42ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */; }; 3CCCA5C52ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */; }; 3CCCA5C72ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */; }; @@ -1920,6 +1924,8 @@ 3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzlerTests.swift; sourceTree = ""; }; 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionTaskDelegate+Tracking.swift"; sourceTree = ""; }; 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionTask+Tracking.swift"; sourceTree = ""; }; + 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelAttributeValue+Datadog.swift"; sourceTree = ""; }; + 3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelAttributeValue+DatadogTests.swift"; sourceTree = ""; }; 3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DDURLSessionInstrumentation+objc.swift"; sourceTree = ""; }; 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDURLSessionInstrumentationConfigurationTests.swift; sourceTree = ""; }; 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogWebViewTracking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3114,6 +3120,7 @@ 3C6C7FDE2B459AAA006F5CBC /* OpenTelemetry */ = { isa = PBXGroup; children = ( + 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */, 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */, 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */, 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */, @@ -3127,6 +3134,7 @@ 3C6C7FF12B459AB3006F5CBC /* OpenTelemetry */ = { isa = PBXGroup; children = ( + 3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */, 3C6C7FF22B459AB3006F5CBC /* OTelSpanId+DatadogTests.swift */, 3C6C7FF32B459AB3006F5CBC /* OTelTraceId+DatadogTests.swift */, 3C6C7FF42B459AB3006F5CBC /* OTelSpanTests.swift */, @@ -8324,6 +8332,7 @@ files = ( 61A2CC3C2A44BED30000FF25 /* Tracer.swift in Sources */, D2C1A50229C4C4CB00946C31 /* Casting.swift in Sources */, + 3CC6AD182B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */, D2C1A50C29C4C4CB00946C31 /* DDNoOps.swift in Sources */, D2C1A4FC29C4C4CB00946C31 /* RequestBuilder.swift in Sources */, D2C1A50D29C4C4CB00946C31 /* SpanTagsReducer.swift in Sources */, @@ -8366,6 +8375,7 @@ buildActionMask = 2147483647; files = ( D2C1A51E29C4C75700946C31 /* Casting+Tracing.swift in Sources */, + 3CC6AD1D2B4F07FA00015B18 /* OTelAttributeValue+DatadogTests.swift in Sources */, D2C1A52429C4C75700946C31 /* TracingURLSessionHandlerTests.swift in Sources */, 619CE75E2A458CE1005588CB /* TraceConfigurationTests.swift in Sources */, D2C1A52329C4C75700946C31 /* WarningsTests.swift in Sources */, @@ -8522,6 +8532,7 @@ files = ( 61A2CC3D2A44BED30000FF25 /* Tracer.swift in Sources */, D2C1A53829C4F2DF00946C31 /* Casting.swift in Sources */, + 3CC6AD192B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */, D2C1A53929C4F2DF00946C31 /* DDNoOps.swift in Sources */, D2C1A53A29C4F2DF00946C31 /* RequestBuilder.swift in Sources */, D2C1A53B29C4F2DF00946C31 /* SpanTagsReducer.swift in Sources */, @@ -8564,6 +8575,7 @@ buildActionMask = 2147483647; files = ( D2C1A55F29C4F2E800946C31 /* Casting+Tracing.swift in Sources */, + 3CC6AD1E2B4F07FB00015B18 /* OTelAttributeValue+DatadogTests.swift in Sources */, D2C1A56029C4F2E800946C31 /* TracingURLSessionHandlerTests.swift in Sources */, 619CE75F2A458CE1005588CB /* TraceConfigurationTests.swift in Sources */, D2C1A56129C4F2E800946C31 /* WarningsTests.swift in Sources */, diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelAttributeValue+Datadog.swift b/DatadogTrace/Sources/OpenTelemetry/OTelAttributeValue+Datadog.swift new file mode 100644 index 0000000000..6d5ac98ea0 --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OTelAttributeValue+Datadog.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 Foundation +import OpenTelemetryApi + +extension Dictionary where Key == String, Value == OpenTelemetryApi.AttributeValue { + /// Converts OpenTelemetry attributes to Datadog tags. This method is recursive + /// and will flatten nested attributes. Collection attributes are flattened to multiple + /// tags with `key.index` naming convention. If attribute value is an empty collection, + /// it will be converted to empty string. + var tags: [String: String] { + var tags: [String: String] = [:] + for (key, value) in self { + switch value { + case .bool(let value): + tags[key] = value.description + case .string(let value): + tags[key] = value + case .int(let value): + tags[key] = value.description + case .double(let value): + tags[key] = value.description + case .stringArray(let array): + if array.isEmpty { + tags[key] = "" + } else { + for (index, element) in array.enumerated() { + tags["\(key).\(index)"] = element + } + } + case .boolArray(let array): + if array.isEmpty { + tags[key] = "" + } else { + for (index, element) in array.enumerated() { + tags["\(key).\(index)"] = element.description + } + } + case .intArray(let array): + if array.isEmpty { + tags[key] = "" + } else { + for (index, element) in array.enumerated() { + tags["\(key).\(index)"] = element.description + } + } + case .doubleArray(let array): + if array.isEmpty { + tags[key] = "" + } else { + for (index, element) in array.enumerated() { + tags["\(key).\(index)"] = element.description + } + } + case .set(let set): + if set.labels.tags.isEmpty { + tags[key] = "" + } else { + for (nestedKey, nestedValue) in set.labels.tags { + tags["\(key).\(nestedKey)"] = nestedValue + } + } + } + } + return tags + } +} diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 1ffcc037f0..601b8109bd 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -118,7 +118,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { return } isRecording = false - tags = makeTags() + tags = attributes.tags } // if the span was already ended before, we don't want to end it again @@ -136,35 +136,6 @@ internal class OTelSpan: OpenTelemetryApi.Span { ddSpan.finish(at: time) } - private func makeTags() -> [String: String] { - var tags = [String: String]() - for (key, value) in attributes { - switch value { - case .string(let value): - tags[key] = value - case .bool(let value): - tags[key] = value.description - case .int(let value): - tags[key] = value.description - case .double(let value): - tags[key] = value.description - // swiftlint:disable unavailable_function - case .stringArray: - fatalError("Not implemented yet") - case .boolArray: - fatalError("Not implemented yet") - case .intArray: - fatalError("Not implemented yet") - case .doubleArray: - fatalError("Not implemented yet") - case .set: - fatalError("Not implemented yet") - // swiftlint:enable unavailable_function - } - } - return tags - } - var description: String { return "OTelSpan" } diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelAttributeValue+DatadogTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelAttributeValue+DatadogTests.swift new file mode 100644 index 0000000000..87ccc26512 --- /dev/null +++ b/DatadogTrace/Tests/OpenTelemetry/OTelAttributeValue+DatadogTests.swift @@ -0,0 +1,119 @@ +/* + * 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 TestUtilities +import DatadogInternal +import OpenTelemetryApi + +@testable import DatadogTrace + +final class OTelAttributeValueDatadogTests: XCTestCase { + func testTags_givenMultipleLevelsAttributes() { + // Given + let attributes = makeAttributes(level: 3) + + // When + let tags = attributes.tags + + let expectedTags = + [ + "key3-0": "true", + "key3-1": "value1", + "key3-2": "2", + "key3-3": "3.0", + "key3-4.0": "value4", + "key3-4.1": "value5", + "key3-5.0": "true", + "key3-5.1": "false", + "key3-6.0": "7", + "key3-6.1": "8", + "key3-7.0": "7.0", + "key3-7.1": "8.0", + "key3-8.key2-0": "true", + "key3-8.key2-1": "value1", + "key3-8.key2-2": "2", + "key3-8.key2-3": "3.0", + "key3-8.key2-4.0": "value4", + "key3-8.key2-4.1": "value5", + "key3-8.key2-5.0": "true", + "key3-8.key2-5.1": "false", + "key3-8.key2-6.0": "7", + "key3-8.key2-6.1": "8", + "key3-8.key2-7.0": "7.0", + "key3-8.key2-7.1": "8.0", + "key3-8.key2-8.key1-0": "true", + "key3-8.key2-8.key1-1": "value1", + "key3-8.key2-8.key1-2": "2", + "key3-8.key2-8.key1-3": "3.0", + "key3-8.key2-8.key1-4.0": "value4", + "key3-8.key2-8.key1-4.1": "value5", + "key3-8.key2-8.key1-5.0": "true", + "key3-8.key2-8.key1-5.1": "false", + "key3-8.key2-8.key1-6.0": "7", + "key3-8.key2-8.key1-6.1": "8", + "key3-8.key2-8.key1-7.0": "7.0", + "key3-8.key2-8.key1-7.1": "8.0", + "key3-8.key2-8.key1-8": "" // when recursion ends, empty string is returned + ] + + // Then + DDAssertDictionariesEqual(expectedTags, tags) + } + + func testTags_givenOneLevelAttributesWithEmptyCollections() { + // Given + let attributes: [String: OpenTelemetryApi.AttributeValue] = [ + "key1": .bool(true), + "key2": .string("value1"), + "key3": .int(2), + "key4": .double(3.0), + "key5": .stringArray([]), + "key6": .boolArray([]), + "key7": .intArray([]), + "key8": .doubleArray([]), + "key9": .set(.init(labels: [:])), + ] + + // When + let tags = attributes.tags + + // Then + let expectedTags = + [ + "key1": "true", + "key2": "value1", + "key3": "2", + "key4": "3.0", + "key5": "", + "key6": "", + "key7": "", + "key8": "", + "key9": "", + ] + DDAssertDictionariesEqual(expectedTags, tags) + } + + // MARK: - Helpers + + func makeAttributes(level: UInt) -> [String: OpenTelemetryApi.AttributeValue] { + guard level > 0 else { + return [:] + } + + return [ + "key\(level)-0": .bool(true), + "key\(level)-1": .string("value1"), + "key\(level)-2": .int(2), + "key\(level)-3": .double(3.0), + "key\(level)-4": .stringArray(["value4", "value5"]), + "key\(level)-5": .boolArray([true, false]), + "key\(level)-6": .intArray([7, 8]), + "key\(level)-7": .doubleArray([7.0, 8.0]), + "key\(level)-8": .set(.init(labels: makeAttributes(level: level - 1))) + ] + } +} diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index e5d43d89fd..be3e22a159 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -7,6 +7,7 @@ import XCTest import TestUtilities import DatadogInternal +import OpenTelemetryApi @testable import DatadogTrace From a72fa925979f43f9ae849bf4d2c55ba7f5ed51a3 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 10 Jan 2024 18:33:32 +0100 Subject: [PATCH 022/153] RUM-1836 feat(otel-tracer): remove not required import --- DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index be3e22a159..e5d43d89fd 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -7,7 +7,6 @@ import XCTest import TestUtilities import DatadogInternal -import OpenTelemetryApi @testable import DatadogTrace From e78b9c344ff0cee48a8c5990c8bbac1e251c7f3a Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 10 Jan 2024 18:37:22 +0100 Subject: [PATCH 023/153] RUM-1836 feat(otel-tracer): improve test case with extra cases --- .../OTelAttributeValue+DatadogTests.swift | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelAttributeValue+DatadogTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelAttributeValue+DatadogTests.swift index 87ccc26512..7a0d69fffb 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelAttributeValue+DatadogTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelAttributeValue+DatadogTests.swift @@ -71,11 +71,15 @@ final class OTelAttributeValueDatadogTests: XCTestCase { "key2": .string("value1"), "key3": .int(2), "key4": .double(3.0), - "key5": .stringArray([]), - "key6": .boolArray([]), - "key7": .intArray([]), - "key8": .doubleArray([]), + "key5": .stringArray(["value5", "value6"]), + "key6": .boolArray([true, false]), + "key7": .intArray([7, 8]), + "key8": .doubleArray([8.0, 9.0]), "key9": .set(.init(labels: [:])), + "key10": .stringArray([]), + "key11": .boolArray([]), + "key12": .intArray([]), + "key13": .doubleArray([]), ] // When @@ -88,11 +92,19 @@ final class OTelAttributeValueDatadogTests: XCTestCase { "key2": "value1", "key3": "2", "key4": "3.0", - "key5": "", - "key6": "", - "key7": "", - "key8": "", + "key5.0": "value5", + "key5.1": "value6", + "key6.0": "true", + "key6.1": "false", + "key7.0": "7", + "key7.1": "8", + "key8.0": "8.0", + "key8.1": "9.0", "key9": "", + "key10": "", + "key11": "", + "key12": "", + "key13": "", ] DDAssertDictionariesEqual(expectedTags, tags) } From 2ec3130ad5d7f63791569945952731a3bc52713a Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 11 Jan 2024 10:12:26 +0100 Subject: [PATCH 024/153] Update DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift Co-authored-by: Maciek Grzybowski --- DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index af53b6671b..b31a9d11fb 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -97,7 +97,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { /// - name: name of the event /// - timestamp: timestamp of the event func addEvent(name: String, timestamp: Date) { - addEvent(name: name, attributes: .init(), timestamp: .init()) + addEvent(name: name, attributes: .init(), timestamp: timestamp) } /// Sends a span event which is akin to a log in Datadog From c940b3fa65bc45456f33b2991ded20e48232e0a2 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 11 Jan 2024 10:20:04 +0100 Subject: [PATCH 025/153] RUM-1836 feat(otel-tracer): fix indent --- .../Sources/OpenTelemetry/OTelSpan.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index b31a9d11fb..268b45199d 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -94,26 +94,26 @@ internal class OTelSpan: OpenTelemetryApi.Span { /// Sends a span event which is akin to a log in Datadog /// - Parameters: - /// - name: name of the event - /// - timestamp: timestamp of the event + /// - name: name of the event + /// - timestamp: timestamp of the event func addEvent(name: String, timestamp: Date) { addEvent(name: name, attributes: .init(), timestamp: timestamp) } /// Sends a span event which is akin to a log in Datadog /// - Parameters: - /// - name: name of the event - /// - attributes: attributes of the event - /// - timestamp: timestamp of the event + /// - name: name of the event + /// - attributes: attributes of the event + /// - timestamp: timestamp of the event func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue]) { addEvent(name: name, attributes: attributes, timestamp: .init()) } /// Sends a span event which is akin to a log in Datadog /// - Parameters: - /// - name: name of the event - /// - attributes: attributes of the event - /// - timestamp: timestamp of the event + /// - name: name of the event + /// - attributes: attributes of the event + /// - timestamp: timestamp of the event func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue], timestamp: Date) { var ended = false queue.sync { From 63400ec8121d29330781e5bdc1b188093a741464 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 11 Jan 2024 10:35:41 +0100 Subject: [PATCH 026/153] RUM-1836 feat(otel-tracer): fix test name --- DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 189d3a4463..76c90a0c13 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -297,7 +297,7 @@ final class OTelSpanTests: XCTestCase { XCTAssertEqual(recordedSpan.tags["error.msg"], nil) } - func testStatus_givenStatusError_whenSetStatusCalledWithOkAndUnset() { + func testStatus_givenStatusError_whenSetStatusCalledWithUnset() { let writeSpanExpectation = expectation(description: "write span event") let core = PassthroughCoreMock(expectation: writeSpanExpectation) From cca9f3a10e0c0bfb321be441ff9afadcedb55042 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 15 Jan 2024 12:42:25 +0100 Subject: [PATCH 027/153] RUM-1836 feat(otel-tracer): add support for span links --- Datadog/Datadog.xcodeproj/project.pbxproj | 24 ++++++ .../Sources/OpenTelemetry/OTelSpan.swift | 15 ++++ .../OpenTelemetry/OTelSpanBuilder.swift | 10 ++- .../Sources/OpenTelemetry/OTelSpanLink.swift | 55 ++++++++++++ .../OTelTraceState+Datadog.swift | 18 ++++ .../OpenTelemetry/OTelSpanLinkTests.swift | 85 +++++++++++++++++++ .../OTelTraceState+DatadogTests.swift | 30 +++++++ .../Tests/Span/SpanEventBuilderTests.swift | 59 +++++++++++++ 8 files changed, 292 insertions(+), 4 deletions(-) create mode 100644 DatadogTrace/Sources/OpenTelemetry/OTelSpanLink.swift create mode 100644 DatadogTrace/Sources/OpenTelemetry/OTelTraceState+Datadog.swift create mode 100644 DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift create mode 100644 DatadogTrace/Tests/OpenTelemetry/OTelTraceState+DatadogTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index ebfff652d0..cfa9426818 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -29,11 +29,19 @@ 3C2206F62AB9DBA700DE780C /* DatadogRUM.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D29A9F3429DD84AA005C54A4 /* DatadogRUM.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C2206F72AB9DBB600DE780C /* DatadogTrace.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C2206F82AB9DBC600DE780C /* DatadogInternal.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D23039A5298D513C001A1FA3 /* DatadogInternal.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3C32359D2B55386C000B4258 /* OTelSpanLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359C2B55386C000B4258 /* OTelSpanLink.swift */; }; + 3C32359E2B55386C000B4258 /* OTelSpanLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359C2B55386C000B4258 /* OTelSpanLink.swift */; }; + 3C3235A02B55387A000B4258 /* OTelSpanLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */; }; + 3C3235A12B55387A000B4258 /* OTelSpanLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */; }; 3C394EF72AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */; }; 3C394EF82AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */; }; 3C394EFA2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */; }; 3C394EFB2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */; }; 3C41693C29FBF4D50042B9D2 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; + 3C5D63692B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */; }; + 3C5D636A2B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */; }; + 3C5D636C2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */; }; + 3C5D636D2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */; }; 3C6C7FDB2B45738C006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDA2B45738C006F5CBC /* OpenTelemetryApi */; }; 3C6C7FDD2B457392006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDC2B457392006F5CBC /* OpenTelemetryApi */; }; 3C6C7FE72B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; @@ -1904,8 +1912,12 @@ 3C0D5DF42A5443B100446CF9 /* DataFormatTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataFormatTests.swift; sourceTree = ""; }; 3C1890132ABDE99200CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDURLSessionInstrumentationTests+apiTests.m"; sourceTree = ""; }; 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaTypeExtensions.swift; sourceTree = ""; }; + 3C32359C2B55386C000B4258 /* OTelSpanLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanLink.swift; sourceTree = ""; }; + 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanLinkTests.swift; sourceTree = ""; }; 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzler.swift; sourceTree = ""; }; 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzlerTests.swift; sourceTree = ""; }; + 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+Datadog.swift"; sourceTree = ""; }; + 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+DatadogTests.swift"; sourceTree = ""; }; 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpan.swift; sourceTree = ""; }; 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanBuilder.swift; sourceTree = ""; }; 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceId+Datadog.swift"; sourceTree = ""; }; @@ -3123,6 +3135,8 @@ 3C6C7FDE2B459AAA006F5CBC /* OpenTelemetry */ = { isa = PBXGroup; children = ( + 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */, + 3C32359C2B55386C000B4258 /* OTelSpanLink.swift */, 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */, 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */, 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */, @@ -3137,6 +3151,8 @@ 3C6C7FF12B459AB3006F5CBC /* OpenTelemetry */ = { isa = PBXGroup; children = ( + 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */, + 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */, 3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */, 3C6C7FF22B459AB3006F5CBC /* OTelSpanId+DatadogTests.swift */, 3C6C7FF32B459AB3006F5CBC /* OTelTraceId+DatadogTests.swift */, @@ -8335,6 +8351,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3C5D63692B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */, 61A2CC3C2A44BED30000FF25 /* Tracer.swift in Sources */, D2C1A50229C4C4CB00946C31 /* Casting.swift in Sources */, 3CC6AD182B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */, @@ -8350,6 +8367,7 @@ D2C1A51329C4C53F00946C31 /* OTReference.swift in Sources */, 3C6C7FEF2B459AAA006F5CBC /* OTelSpanId+Datadog.swift in Sources */, D2C1A4FB29C4C4CB00946C31 /* MessageReceivers.swift in Sources */, + 3C32359D2B55386C000B4258 /* OTelSpanLink.swift in Sources */, 61A2CC362A44B0A20000FF25 /* TraceConfiguration.swift in Sources */, 61A2CC392A44B0EA0000FF25 /* Trace.swift in Sources */, D2C1A50029C4C4CB00946C31 /* ActiveSpansPool.swift in Sources */, @@ -8390,6 +8408,8 @@ 3C6C7FFD2B459AF6006F5CBC /* OTelSpanTests.swift in Sources */, 619CE7612A458D66005588CB /* TraceTests.swift in Sources */, D2C1A52029C4C75700946C31 /* DDSpanTests.swift in Sources */, + 3C5D636C2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift in Sources */, + 3C3235A02B55387A000B4258 /* OTelSpanLinkTests.swift in Sources */, 3C6C7FFC2B459AF6006F5CBC /* OTelTraceId+DatadogTests.swift in Sources */, D2C1A51B29C4C75700946C31 /* DDSpanContextTests.swift in Sources */, D2C1A52729C4C7D000946C31 /* TracingFeatureMocks.swift in Sources */, @@ -8535,6 +8555,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3C5D636A2B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */, 61A2CC3D2A44BED30000FF25 /* Tracer.swift in Sources */, D2C1A53829C4F2DF00946C31 /* Casting.swift in Sources */, 3CC6AD192B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */, @@ -8550,6 +8571,7 @@ D2C1A53F29C4F2DF00946C31 /* OTReference.swift in Sources */, 3C6C7FF02B459AAA006F5CBC /* OTelSpanId+Datadog.swift in Sources */, D2C1A54129C4F2DF00946C31 /* MessageReceivers.swift in Sources */, + 3C32359E2B55386C000B4258 /* OTelSpanLink.swift in Sources */, 61A2CC372A44B0A20000FF25 /* TraceConfiguration.swift in Sources */, 61A2CC3A2A44B0EA0000FF25 /* Trace.swift in Sources */, D2C1A54229C4F2DF00946C31 /* ActiveSpansPool.swift in Sources */, @@ -8590,6 +8612,8 @@ 3C6C80002B459AF6006F5CBC /* OTelSpanTests.swift in Sources */, 619CE7622A458D66005588CB /* TraceTests.swift in Sources */, D2C1A56629C4F2E800946C31 /* DDSpanTests.swift in Sources */, + 3C5D636D2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift in Sources */, + 3C3235A12B55387A000B4258 /* OTelSpanLinkTests.swift in Sources */, 3C6C7FFF2B459AF6006F5CBC /* OTelTraceId+DatadogTests.swift in Sources */, D2C1A56729C4F2E800946C31 /* DDSpanContextTests.swift in Sources */, D2C1A56829C4F2E800946C31 /* TracingFeatureMocks.swift in Sources */, diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index de043fd9cd..0fe25fee92 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -11,6 +11,14 @@ internal enum DatadogTagKeys: String { case spanKind = "span.kind" case errorType = "error.type" case errorMessage = "error.message" + case spanLinks = "_dd.span_links" +} + +extension OpenTelemetryApi.TraceId { + /// Returns 32 character hexadecimal string representation of lower 64 bits of the trace ID. + var lowerLongHexString: String { + return String(format: "%016llx", rawLowerLong) + } } internal extension OpenTelemetryApi.Status { @@ -38,6 +46,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { let ddSpan: DDSpan let tracer: DatadogTracer let queue: DispatchQueue + let spanLinks: [OTelSpanLink] /// `isRecording` indicates whether the span is recording or not /// and events can be added to it. @@ -93,6 +102,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { parentSpanID: OpenTelemetryApi.SpanId?, spanContext: OpenTelemetryApi.SpanContext, spanKind: OpenTelemetryApi.SpanKind, + spanLinks: [OTelSpanLink], startTime: Date, tracer: DatadogTracer ) { @@ -104,6 +114,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { self.isRecording = true self.queue = tracer.queue self.tracer = tracer + self.spanLinks = spanLinks self.ddSpan = .init( tracer: tracer, context: .init( @@ -208,6 +219,10 @@ internal class OTelSpan: OpenTelemetryApi.Span { // SpanKind maps to the `span.kind` tag in Datadog ddSpan.setTag(key: DatadogTagKeys.spanKind.rawValue, value: kind.rawValue) + + // Datadog uses `_dd.span_links` tag to send span links + ddSpan.setTag(key: DatadogTagKeys.spanLinks.rawValue, value: spanLinks) + ddSpan.finish(at: time) } diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift index 93a4deb244..35b7fce86f 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift @@ -15,6 +15,7 @@ internal class OTelSpanBuilder: OpenTelemetryApi.SpanBuilder { var startTime: Date? var active: Bool var parent: Parent + var spanLinks: [OTelSpanLink] = [] enum Parent { case currentSpan @@ -69,15 +70,15 @@ internal class OTelSpanBuilder: OpenTelemetryApi.SpanBuilder { return self } - // swiftlint:disable unavailable_function func addLink(spanContext: OpenTelemetryApi.SpanContext) -> Self { - fatalError("Not implemented yet") + self.spanLinks.append(OTelSpanLink(context: spanContext, attributes: [:])) + return self } func addLink(spanContext: OpenTelemetryApi.SpanContext, attributes: [String: OpenTelemetryApi.AttributeValue]) -> Self { - fatalError("Not implemented yet") + self.spanLinks.append(OTelSpanLink(context: spanContext, attributes: attributes)) + return self } - // swiftlint:enable unavailable_function func setSpanKind(spanKind: OpenTelemetryApi.SpanKind) -> Self { self.spanKind = spanKind @@ -122,6 +123,7 @@ internal class OTelSpanBuilder: OpenTelemetryApi.SpanBuilder { parentSpanID: parentContext?.spanId, spanContext: spanContext, spanKind: spanKind, + spanLinks: spanLinks, startTime: startTime ?? Date(), tracer: tracer ) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpanLink.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpanLink.swift new file mode 100644 index 0000000000..04cb573126 --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpanLink.swift @@ -0,0 +1,55 @@ +/* +* 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 +import OpenTelemetryApi + +/// Represents a span link containing a `SpanContext` and additional attributes. +internal struct OTelSpanLink: Equatable { + /// Context of the linked span. + let context: OpenTelemetryApi.SpanContext + + /// Additional attributes of the linked span. + let attributes: [String: OpenTelemetryApi.AttributeValue] +} + +extension OTelSpanLink: Encodable { + enum CodingKeys: String, CodingKey { + case traceId = "trace_id" + case spanId = "span_id" + case attributes = "attributes" + case traceState = "tracestate" + case traceFlags = "flags" + } + + /// Encodes the span link to the following JSON format: + /// ```json + /// { + /// "trace_id": "", + /// "span_id": "", + /// "attributes": {"key":"value", "pairs":"of", "arbitrary":"values"}, + /// "dropped_attributes_count": , + /// "tracestate": "a tracestate as defined in the W3C standard", + /// "flags": + /// }, + /// ``` + /// - Parameter encoder: Encoder + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + let traceId = String(context.traceId.toDatadog(), representation: .hexadecimal32Chars) + + try container.encode(traceId, forKey: .traceId) + try container.encode(context.spanId.hexString, forKey: .spanId) + if !attributes.isEmpty { + try container.encode(attributes.tags, forKey: .attributes) + } + + if !context.traceState.entries.isEmpty { + try container.encode(context.traceState.w3c(), forKey: .traceState) + } + try container.encode(context.traceFlags.byte, forKey: .traceFlags) + } +} diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelTraceState+Datadog.swift b/DatadogTrace/Sources/OpenTelemetry/OTelTraceState+Datadog.swift new file mode 100644 index 0000000000..bac0d2d8ff --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OTelTraceState+Datadog.swift @@ -0,0 +1,18 @@ +/* +* 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 +import OpenTelemetryApi + +extension OpenTelemetryApi.TraceState { + /// Returns the tracestate as a string as defined in the W3C standard. + /// https://www.w3.org/TR/trace-context/#tracestate-header-field-values + /// Example: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE + /// - Returns: tracestate as a string + public func w3c() -> String { + return self.entries.map { "\($0.key)=\($0.value)" }.joined(separator: ",") + } +} diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift new file mode 100644 index 0000000000..3618765bcd --- /dev/null +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift @@ -0,0 +1,85 @@ +/* + * 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 TestUtilities +import DatadogInternal +import OpenTelemetryApi + +@testable import DatadogTrace + +final class OTelSpanLinkTests: XCTestCase { + func testEncoder_givenAllPropertiesArePresent() throws { + let encoder = JSONEncoder() + encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes] + + let traceId = TraceId(idHi: 101, idLo: 102) + let spanId = SpanId(id: 103) + var traceFlags = TraceFlags() + traceFlags.setIsSampled(true) + let traceState = TraceState( + entries: [ + .init(key: "foo", value: "bar")!, + .init(key: "bar", value: "baz")! + ] + )! + + let spanContext = OpenTelemetryApi.SpanContext.create( + traceId: traceId, + spanId: spanId, + traceFlags: traceFlags, + traceState: traceState + ) + let attributes: [String: OpenTelemetryApi.AttributeValue] = [ + "foo": .string("bar") + ] + + let spanLink = OTelSpanLink( + context: spanContext, + attributes: attributes + ) + + let encoded = try encoder.encode(spanLink) + let decoded = try JSONDecoder().decode([String: AnyDecodable].self, from: encoded) + + XCTAssertEqual(decoded["trace_id"]?.value as? String, "00000000000000000000000000000065") + XCTAssertEqual(decoded["span_id"]?.value as? String, "0000000000000067") + XCTAssertEqual(decoded["attributes"]?.value as? [String: String], ["foo": "bar"]) + XCTAssertEqual(decoded["tracestate"]?.value as? String, "foo=bar,bar=baz") + XCTAssertEqual(decoded["flags"]?.value as? Int, 1) + } + + func testEncoder_givenOnlyRequiredPropertiesArePresent() throws { + let encoder = JSONEncoder() + encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes] + + let traceId = TraceId(idHi: 101, idLo: 102) + let spanId = SpanId(id: 103) + let traceFlags = TraceFlags() + let traceState = TraceState() + + let spanContext = OpenTelemetryApi.SpanContext.create( + traceId: traceId, + spanId: spanId, + traceFlags: traceFlags, + traceState: traceState + ) + + let spanLink = OTelSpanLink( + context: spanContext, + attributes: [:] + ) + + let encoded = try encoder.encode(spanLink) + let decoded = try JSONDecoder().decode([String: AnyDecodable].self, from: encoded) + + XCTAssertEqual(decoded["trace_id"]?.value as? String, "00000000000000000000000000000065") + XCTAssertEqual(decoded["span_id"]?.value as? String, "0000000000000067") + XCTAssertNil(decoded["attributes"]?.value) + XCTAssertNil(decoded["tracestate"]?.value) + XCTAssertEqual(decoded["flags"]?.value as? Int, 0) + } +} diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelTraceState+DatadogTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelTraceState+DatadogTests.swift new file mode 100644 index 0000000000..6cf0582b6e --- /dev/null +++ b/DatadogTrace/Tests/OpenTelemetry/OTelTraceState+DatadogTests.swift @@ -0,0 +1,30 @@ +/* + * 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 TestUtilities +import DatadogInternal +import OpenTelemetryApi + +@testable import DatadogTrace + +final class OTelTraceStateDatadogTests: XCTestCase { + func testW3C_givenEmptyEntries() throws { + let traceState = TraceState(entries: [])! + XCTAssertEqual("", traceState.w3c()) + } + + func testW3C_givenSomeEntries() throws { + let traceState = TraceState( + entries: [ + .init(key: "foo", value: "bar")!, + .init(key: "bar", value: "baz")! + ] + )! + + XCTAssertEqual("foo=bar,bar=baz", traceState.w3c()) + } +} diff --git a/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift b/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift index f60263be0c..6d394f335b 100644 --- a/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift +++ b/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift @@ -7,6 +7,7 @@ import XCTest import TestUtilities import DatadogInternal +import OpenTelemetryApi @testable import DatadogTrace @@ -499,4 +500,62 @@ class SpanEventBuilderTests: XCTestCase { ) XCTAssertEqual(dd.logger.errorLog?.error?.message, "Value cannot be encoded.") } + + func testBuildingSpanWhenSpanLinkIsPresentInTags() { + let dd = DD.mockWith(logger: CoreLoggerMock()) + defer { dd.reset() } + + let builder: SpanEventBuilder = .mockAny() + + let traceId = TraceId(idHi: 101, idLo: 102) + let spanId = SpanId(id: 103) + var traceFlags = TraceFlags() + traceFlags.setIsSampled(true) + let traceState = TraceState( + entries: [ + .init(key: "foo", value: "bar")!, + .init(key: "bar", value: "baz")! + ] + )! + + let spanContext = OpenTelemetryApi.SpanContext.create( + traceId: traceId, + spanId: spanId, + traceFlags: traceFlags, + traceState: traceState + ) + let attributes: [String: OpenTelemetryApi.AttributeValue] = [ + "foo": .string("bar") + ] + + let spanLink = OTelSpanLink( + context: spanContext, + attributes: attributes + ) + + // When + let span = builder.createSpanEvent( + context: .mockAny(), + traceID: .mockAny(), + spanID: .mockAny(), + parentSpanID: .mockAny(), + operationName: .mockAny(), + startTime: .mockAny(), + finishTime: .mockAny(), + samplingRate: .mockAny(), + isKept: .mockAny(), + tags: [ + DatadogTagKeys.spanLinks.rawValue: [spanLink] + ], + baggageItems: [:], + logFields: [] + ) + + // Then + XCTAssertEqual(span.tags.count, 1) + let expectedTags = "[{\"trace_id\":\"00000000000000000000000000000065\",\"span_id\":\"0000000000000067\",\"tracestate\":\"foo=bar,bar=baz\",\"flags\":1,\"attributes\":{\"foo\":\"bar\"}}]" + let actualTags = span.tags["_dd.span_links"] + + DDAssertJSONEqual(expectedTags, actualTags) + } } From 0f6fc285e41bf485845f190c44a90f668766d77a Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 15 Jan 2024 12:50:48 +0100 Subject: [PATCH 028/153] RUM-1836 feat(otel-tracer): remove JSON encoder configuration --- DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift index 3618765bcd..a07166c76d 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift @@ -14,8 +14,6 @@ import OpenTelemetryApi final class OTelSpanLinkTests: XCTestCase { func testEncoder_givenAllPropertiesArePresent() throws { let encoder = JSONEncoder() - encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes] - let traceId = TraceId(idHi: 101, idLo: 102) let spanId = SpanId(id: 103) var traceFlags = TraceFlags() @@ -54,8 +52,6 @@ final class OTelSpanLinkTests: XCTestCase { func testEncoder_givenOnlyRequiredPropertiesArePresent() throws { let encoder = JSONEncoder() - encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes] - let traceId = TraceId(idHi: 101, idLo: 102) let spanId = SpanId(id: 103) let traceFlags = TraceFlags() From 8ce2edf0be67eca09e8c3bebc87cf8af99ad65a6 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 15 Jan 2024 13:04:31 +0100 Subject: [PATCH 029/153] RUM-1836 feat(otel-tracer): include span links key if non-empty --- DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 0fe25fee92..748edd9aef 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -221,7 +221,9 @@ internal class OTelSpan: OpenTelemetryApi.Span { ddSpan.setTag(key: DatadogTagKeys.spanKind.rawValue, value: kind.rawValue) // Datadog uses `_dd.span_links` tag to send span links - ddSpan.setTag(key: DatadogTagKeys.spanLinks.rawValue, value: spanLinks) + if !spanLinks.isEmpty { + ddSpan.setTag(key: DatadogTagKeys.spanLinks.rawValue, value: spanLinks) + } ddSpan.finish(at: time) } From 2002374f04bf98a43ebd1c57c3ebcd66008590fe Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 6 Feb 2024 18:42:28 +0100 Subject: [PATCH 030/153] Merge branch 'develop' into ganeshnj/feat/otel-tracer --- .gitlab-ci.yml | 81 +++ BenchmarkTests/BenchmarkMocks.swift | 27 - BenchmarkTests/BenchmarkTests.swift | 85 --- .../LoggingBenchmarkTests.swift | 42 -- .../DataCollection/RUMBenchmarkTests.swift | 36 -- .../TracingBenchmarkTests.swift | 41 -- .../LoggingStorageBenchmarkTests.swift | 128 ---- .../RUMStorageBenchmarkTests.swift | 90 --- .../TracingStorageBenchmarkTests.swift | 127 ---- .../DataUploaderBenchmarkTests.swift | 47 -- CHANGELOG.md | 30 + Datadog/Datadog.xcodeproj/project.pbxproj | 577 +++++------------- .../xcschemes/DatadogBenchmarkTests.xcscheme | 198 ------ .../xcschemes/DatadogRUM iOS.xcscheme | 3 +- .../Example/Base.lproj/Main iOS.storyboard | 285 +++++---- .../Debugging/DebugRUMViewController.swift | 62 +- .../Utils/ConsoleOutputInterceptor.swift | 3 +- .../SendingCrashReportTests.swift | 118 ++++ ...tworkInstrumentationIntegrationTests.swift | 89 +++ .../TelemetryCoreIntegrationTests.swift | 6 +- .../RUM/StartingRUMSessionTests.swift | 332 ++++++++++ .../DatadogBenchmarkTests.xcconfig | 8 - .../DatadogBenchmarkTests/Info.plist | 24 - DatadogAlamofireExtension.podspec | 2 +- DatadogCore.podspec | 3 +- DatadogCore/Resources/PrivacyInfo.xcprivacy | 19 + .../Core/Context/CarrierInfoPublisher.swift | 2 + .../Core/Context/DatadogContextProvider.swift | 4 +- DatadogCore/Sources/Core/DatadogCore.swift | 39 +- .../Core/Storage/FilesOrchestrator.swift | 2 - .../Core/Upload/DataUploadStatus.swift | 15 +- DatadogCore/Sources/Datadog.swift | 26 +- DatadogCore/Sources/Versioning.swift | 2 +- .../Datadog/Core/DD/InternalLoggerTests.swift | 8 +- .../Core/Upload/DataUploadStatusTests.swift | 3 + .../Datadog/DatadogConfigurationTests.swift | 2 +- .../Context/CarrierInfoPublisherTests.swift | 2 + .../DatadogCore/DatadogCoreTests.swift | 85 ++- DatadogCore/Tests/Datadog/DatadogTests.swift | 20 +- .../Datadog/Logs/CrashLogReceiverTests.swift | 146 +++-- .../Mocks/CrashReportingFeatureMocks.swift | 6 +- .../DatadogInternal/DatadogCoreProxy.swift | 30 +- .../Tests/Datadog/Mocks/LogsMocks.swift | 21 - .../Datadog/Mocks/RUMDataModelMocks.swift | 2 + .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 32 +- .../Datadog/Mocks/TracingFeatureMocks.swift | 64 +- .../Tests/Datadog/RUM/RUMDebuggingTests.swift | 6 +- .../Tests/Datadog/RUM/RUMFeatureTests.swift | 69 ++- .../Datadog/RUM/RUMInternalProxyTests.swift | 11 +- .../Tests/Datadog/RUM/RUMMonitorTests.swift | 221 ++++--- DatadogCore/Tests/Datadog/TracerTests.swift | 152 +++-- .../TracingURLSessionHandlerTests.swift | 2 +- .../Tests/Matchers/JSONDataMatcher.swift | 17 + .../Tests/Matchers/RUMEventMatcher.swift | 2 +- .../Tests/Matchers/RUMSessionMatcher.swift | 119 +++- DatadogCore/Tests/Matchers/SpanMatcher.swift | 4 +- .../TestsObserver/DatadogTestsObserver.swift | 30 +- DatadogCrashReporting.podspec | 2 +- .../Sources/CrashReporting.swift | 8 +- .../PLCrashReporterPlugin.swift | 4 +- .../Tests/CrashReportingPluginTests.swift | 8 +- DatadogInternal.podspec | 2 +- .../Sources/Concurrency/ReadWriteLock.swift | 12 +- .../Sources/Context/AppState.swift | 2 +- DatadogInternal/Sources/DD.swift | 28 +- .../Sources/DatadogCoreProtocol.swift | 39 +- .../DatadogURLSessionHandler.swift | 5 + .../HostsSanitizer.swift | 3 +- .../NetworkInstrumentationFeature.swift | 240 ++++---- .../DatadogURLSessionDelegate.swift | 70 ++- .../NetworkInstrumentationSwizzler.swift | 72 +++ .../URLSessionDataDelegateSwizzler.swift | 74 +-- .../URLSessionInstrumentation.swift | 14 +- .../URLSession/URLSessionSwizzler.swift | 154 +++-- .../URLSession/URLSessionTask+Tracking.swift | 36 +- .../URLSessionTaskDelegate+Tracking.swift | 27 - .../URLSessionTaskDelegateSwizzler.swift | 109 +--- .../URLSessionTaskInterception.swift | 34 +- .../URLSession/URLSessionTaskSwizzler.swift | 71 +-- .../Sources/Swizzling/MethodSwizzler.swift | 9 + .../Sources/Telemetry/InternalLogger.swift | 13 +- .../Sources/Utils/MetaTypeExtensions.swift | 38 -- .../Tests/Codable/AnyCodableTests.swift | 1 + .../HostsSanitizerTests.swift | 2 +- .../NetworkInstrumentationFeatureTests.swift | 154 ++++- .../URLSessionDataDelegateSwizzlerTests.swift | 143 ++--- .../URLSessionSwizzlerTests.swift | 106 +--- .../URLSessionTaskDelegateSwizzlerTests.swift | 164 ++--- .../URLSessionTaskSwizzlerTests.swift | 66 +- .../Tests/Swizzling/MethodSwizzlerTests.swift | 40 ++ .../Tests/Utils/MetaTypeExtensionsTests.swift | 52 -- DatadogLogs.podspec | 2 +- DatadogLogs/Sources/ConsoleLogger.swift | 7 +- DatadogLogs/Sources/Feature/Baggages.swift | 18 - .../Sources/Feature/MessageReceivers.swift | 47 +- DatadogLogs/Sources/Log/LogEventEncoder.swift | 22 + .../Sources/LogOutputs/LogFileOutput.swift | 17 - DatadogLogs/Sources/LoggerProtocol.swift | 11 + DatadogLogs/Sources/Logs.swift | 2 +- DatadogLogs/Sources/RemoteLogger.swift | 14 +- .../Tests/LogOutputs/LogFileOutputTests.swift | 26 - .../Tests/Mocks/LoggingFeatureMocks.swift | 21 - .../Tests/WebViewLogReceiverTests.swift | 2 +- DatadogObjc.podspec | 2 +- .../Sources/RUM/RUMDataModels+objc.swift | 375 +++++++++--- DatadogRUM.podspec | 2 +- .../Sources/DataModels/RUMDataModels.swift | 37 +- .../DataModels/RUMDataModelsMapping.swift | 1 + DatadogRUM/Sources/Feature/RUMFeature.swift | 4 + .../Instrumentation/RUMInstrumentation.swift | 6 +- .../URLSessionRUMResourcesHandler.swift | 9 +- .../Views/RUMViewsHandler.swift | 36 +- .../Integrations/TelemetryReceiver.swift | 1 + DatadogRUM/Sources/RUM+Internal.swift | 56 ++ DatadogRUM/Sources/RUM.swift | 34 +- .../RUMConnectivityInfoProvider.swift | 3 +- DatadogRUM/Sources/RUMMonitor.swift | 2 +- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 33 +- .../Sources/RUMMonitor/RUMCommand.swift | 14 +- .../Scopes/RUMApplicationScope.swift | 18 +- .../RUMMonitor/Scopes/RUMSessionScope.swift | 17 +- .../RUMMonitor/Scopes/RUMViewScope.swift | 15 +- .../Scopes/Utils/RUMViewIdentity.swift | 113 ---- .../Scopes/Utils/ViewIdentifier.swift | 30 + DatadogRUM/Sources/RUMMonitorProtocol.swift | 10 + .../RUMVitals/VitalRefreshRateReader.swift | 40 +- .../URLSessionRUMResourcesHandlerTests.swift | 49 +- .../Views/RUMViewsHandlerTests.swift | 74 +-- .../Tests/Mocks/RUMDataModelMocks.swift | 2 + DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 29 +- .../Tests/RUMMonitor/MonitorTests.swift | 36 ++ .../Scopes/RUMApplicationScopeTests.swift | 57 +- .../Scopes/RUMSessionScopeTests.swift | 22 +- .../Scopes/RUMUserActionScopeTests.swift | 24 +- .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 176 +++--- .../Scopes/Utils/RUMViewIdentityTests.swift | 113 ---- .../Scopes/Utils/ViewIdentifierTests.swift | 56 ++ DatadogRUM/Tests/RUMTests.swift | 48 +- DatadogSDK.podspec | 2 +- DatadogSDKAlamofireExtension.podspec | 2 +- DatadogSDKCrashReporting.podspec | 2 +- DatadogSDKObjc.podspec | 2 +- DatadogSessionReplay.podspec | 2 +- .../Utils/SnapshotTestCase.swift | 19 +- .../Feature/SessionReplayFeature.swift | 32 +- .../Sources/Models/EnrichedResource.swift | 6 +- .../Processor/ResourcesProcessor.swift | 41 ++ .../SRDataModelsBuilder/RecordsBuilder.swift | 4 + .../WireframesBuilder.swift | 11 +- ...rocessor.swift => SnapshotProcessor.swift} | 12 +- .../Sources/Recorder/Recorder.swift | 19 +- .../Utilities/ImageDataProvider.swift | 10 + .../Recorder/Utilities/UIKitExtensions.swift | 2 +- .../NodeRecorders/UIDatePickerRecorder.swift | 28 +- .../NodeRecorders/UIImageViewRecorder.swift | 49 +- .../NodeRecorders/UIPickerViewRecorder.swift | 16 +- .../NodeRecorders/UITextFieldRecorder.swift | 7 +- .../NodeRecorders/UIViewRecorder.swift | 2 +- .../ViewTreeSnapshot/ViewTreeRecorder.swift | 24 +- .../ViewTreeSnapshot/ViewTreeSnapshot.swift | 26 +- .../ViewTreeSnapshotBuilder.swift | 4 +- .../Sources/SessionReplay.swift | 5 +- .../Sources/Utilities/Queue.swift | 6 +- .../Sources/Writers/RecordWriter.swift | 4 +- .../Sources/Writers/ResourcesWriter.swift | 10 +- .../Tests/Mocks/QueueMocks.swift | 2 +- .../Tests/Mocks/RecorderMocks.swift | 50 +- .../Tests/Mocks/ResourceMocks.swift | 18 +- .../Tests/Mocks/ResourceProcessorSpy.swift | 17 + .../Tests/Mocks/SRDataModelsMocks.swift | 30 +- ...orSpy.swift => SnapshotProcessorSpy.swift} | 2 +- .../Processor/ResourceProcessorTests.swift | 65 ++ .../RecordsBuilderTests.swift | 32 + ...sts.swift => SnapshotProcessorTests.swift} | 56 +- .../Tests/Recorder/RecorderTests.swift | 25 +- .../Utilties/ImageDataProviderTests.swift | 10 +- .../Utilties/UIKitExtensionsTests.swift | 34 +- .../UIImageViewRecorderTests.swift | 18 + .../ViewTreeRecorderTests.swift | 104 ++-- .../ViewTreeSnapshotTests.swift | 2 +- .../Tests/SessionReplayTests.swift | 14 +- .../Tests/Writer/ResourcesWriterTests.swift | 2 +- DatadogTrace.podspec | 2 +- DatadogTrace/Sources/DDSpan.swift | 156 ++--- DatadogTrace/Sources/DDSpanContext.swift | 2 +- DatadogTrace/Sources/DatadogTracer.swift | 44 +- DatadogTrace/Sources/Feature/Baggages.swift | 2 +- .../Sources/Feature/MessageReceivers.swift | 29 - .../Sources/Feature/TraceFeature.swift | 21 +- .../TracingURLSessionHandler.swift | 24 +- .../Sources/OpenTelemetry/OTelSpan.swift | 88 +-- .../OpenTelemetry/OTelSpanBuilder.swift | 10 +- .../Sources/OpenTracing/OTConstants.swift | 1 - .../Sources/Span/SpanEventBuilder.swift | 29 +- .../Sources/Span/SpanWriteContext.swift | 51 ++ DatadogTrace/Sources/Trace.swift | 4 +- DatadogTrace/Sources/Tracer.swift | 21 +- DatadogTrace/Sources/Utils/JSONEncoder.swift | 23 - .../Tests/ContextMessageReceiverTests.swift | 137 +---- DatadogTrace/Tests/DDSpanTests.swift | 19 +- .../Tests/Span/SpanEventBuilderTests.swift | 101 ++- .../Tests/Span/SpanWriteContextTests.swift | 51 ++ DatadogTrace/Tests/TraceTests.swift | 18 +- DatadogTrace/Tests/TracingFeatureMocks.swift | 44 +- .../Tests/TracingURLSessionHandlerTests.swift | 168 ++++- .../Tests/Utils/ActiveSpansPoolTests.swift | 22 +- DatadogWebViewTracking.podspec | 2 +- .../Core/StopCoreScenarioTests.swift | 174 ++++++ .../CrashReportingWithRUMScenarioTests.swift | 17 +- .../Scenarios/RUM/RUMCommonAsserts.swift | 12 +- ...UMManualInstrumentationScenarioTests.swift | 11 +- .../RUM/RUMMobileVitalsScenarioTests.swift | 16 +- .../RUM/RUMModalViewsScenarioTests.swift | 114 ++-- ...RUMNavigationControllerScenarioTests.swift | 53 +- .../RUM/RUMResourcesScenarioTests.swift | 112 +++- .../RUM/RUMScrubbingScenarioTests.swift | 21 +- .../RUM/RUMStopSessionScenarioTests.swift | 24 +- .../RUM/RUMSwiftUIScenarioTests.swift | 85 ++- .../RUMTabBarControllerScenarioTests.swift | 59 +- .../RUM/RUMTapActionScenarioTests.swift | 66 +- ...RMultipleViewsRecordingScenarioTests.swift | 2 +- .../TracingURLSessionScenarioTests.swift | 70 ++- .../TrackingConsentScenarioTests.swift | 42 +- .../WebView/WebViewScenarioTest.swift | 14 +- .../project.pbxproj | 62 +- .../Runner/AppConfiguration.swift | 52 +- ...pleAppDelegate.swift => AppDelegate.swift} | 42 +- IntegrationTests/Runner/Environment.swift | 16 +- .../Runner/Scenarios/Core/CoreScenarios.swift | 59 ++ .../CSHomeViewController.swift | 22 + .../CSPictureViewController.swift | 53 ++ .../CSRootViewController.swift | 19 + .../StopCoreScenario.storyboard | 303 +++++++++ .../SendLogsFixtureViewController.swift | 22 +- .../Runner/Scenarios/RUM/RUMScenarios.swift | 8 +- .../SendTracesFixtureViewController.swift | 2 + .../Scenarios/Tracing/TracingScenarios.swift | 4 +- .../TSHomeViewController.swift | 6 +- .../URLSession/URLSessionScenarios.swift | 29 +- .../Utils/CustomURLSessionDelegate.swift | 6 +- Makefile | 25 +- Package.swift | 8 +- TestUtilities.podspec | 2 +- TestUtilities/Helpers/XCTestCase.swift | 9 + .../Mocks/CoreMocks/PassthroughCoreMock.swift | 12 +- TestUtilities/Mocks/DatadogContextMock.swift | 12 +- TestUtilities/Mocks/FoundationMocks.swift | 1 + .../Mocks/NetworkInstrumentationMocks.swift | 5 + TestUtilities/Mocks/PrintFunctionMock.swift | 3 +- bitrise.yml | 11 - .../Sources/HTTPServerMock/ServerMock.swift | 6 + .../python/start_mock_server.py | 21 + tools/distribution/requirements.txt | 2 +- .../Swift/JSONToSwiftTypeTransformer.swift | 17 +- .../JSONToSwiftTypeTransformerTests.swift | 108 ++++ tools/rum-models-generator/run.py | 10 +- 256 files changed, 6461 insertions(+), 4427 deletions(-) create mode 100644 .gitlab-ci.yml delete mode 100644 BenchmarkTests/BenchmarkMocks.swift delete mode 100644 BenchmarkTests/BenchmarkTests.swift delete mode 100644 BenchmarkTests/DataCollection/LoggingBenchmarkTests.swift delete mode 100644 BenchmarkTests/DataCollection/RUMBenchmarkTests.swift delete mode 100644 BenchmarkTests/DataCollection/TracingBenchmarkTests.swift delete mode 100644 BenchmarkTests/DataStorage/LoggingStorageBenchmarkTests.swift delete mode 100644 BenchmarkTests/DataStorage/RUMStorageBenchmarkTests.swift delete mode 100644 BenchmarkTests/DataStorage/TracingStorageBenchmarkTests.swift delete mode 100644 BenchmarkTests/DataUpload/DataUploaderBenchmarkTests.swift delete mode 100644 Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogBenchmarkTests.xcscheme create mode 100644 Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift create mode 100644 Datadog/IntegrationUnitTests/Public/NetworkInstrumentationIntegrationTests.swift create mode 100644 Datadog/IntegrationUnitTests/RUM/StartingRUMSessionTests.swift delete mode 100644 Datadog/TargetSupport/DatadogBenchmarkTests/DatadogBenchmarkTests.xcconfig delete mode 100644 Datadog/TargetSupport/DatadogBenchmarkTests/Info.plist create mode 100644 DatadogCore/Resources/PrivacyInfo.xcprivacy create mode 100644 DatadogInternal/Sources/NetworkInstrumentation/URLSession/NetworkInstrumentationSwizzler.swift delete mode 100644 DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskDelegate+Tracking.swift delete mode 100644 DatadogInternal/Sources/Utils/MetaTypeExtensions.swift delete mode 100644 DatadogInternal/Tests/Utils/MetaTypeExtensionsTests.swift delete mode 100644 DatadogLogs/Sources/LogOutputs/LogFileOutput.swift delete mode 100644 DatadogLogs/Tests/LogOutputs/LogFileOutputTests.swift delete mode 100644 DatadogRUM/Sources/RUMMonitor/Scopes/Utils/RUMViewIdentity.swift create mode 100644 DatadogRUM/Sources/RUMMonitor/Scopes/Utils/ViewIdentifier.swift delete mode 100644 DatadogRUM/Tests/RUMMonitor/Scopes/Utils/RUMViewIdentityTests.swift create mode 100644 DatadogRUM/Tests/RUMMonitor/Scopes/Utils/ViewIdentifierTests.swift create mode 100644 DatadogSessionReplay/Sources/Processor/ResourcesProcessor.swift rename DatadogSessionReplay/Sources/Processor/{Processor.swift => SnapshotProcessor.swift} (96%) create mode 100644 DatadogSessionReplay/Tests/Mocks/ResourceProcessorSpy.swift rename DatadogSessionReplay/Tests/Mocks/{ProcessorSpy.swift => SnapshotProcessorSpy.swift} (92%) create mode 100644 DatadogSessionReplay/Tests/Processor/ResourceProcessorTests.swift rename DatadogSessionReplay/Tests/Processor/{ProcessorTests.swift => SnapshotProcessorTests.swift} (88%) create mode 100644 DatadogTrace/Sources/Span/SpanWriteContext.swift delete mode 100644 DatadogTrace/Sources/Utils/JSONEncoder.swift create mode 100644 DatadogTrace/Tests/Span/SpanWriteContextTests.swift create mode 100644 IntegrationTests/IntegrationScenarios/Scenarios/Core/StopCoreScenarioTests.swift rename IntegrationTests/Runner/{ExampleAppDelegate.swift => AppDelegate.swift} (53%) create mode 100644 IntegrationTests/Runner/Scenarios/Core/CoreScenarios.swift create mode 100644 IntegrationTests/Runner/Scenarios/Core/StopCoreInstance/CSHomeViewController.swift create mode 100644 IntegrationTests/Runner/Scenarios/Core/StopCoreInstance/CSPictureViewController.swift create mode 100644 IntegrationTests/Runner/Scenarios/Core/StopCoreInstance/CSRootViewController.swift create mode 100644 IntegrationTests/Runner/Scenarios/Core/StopCoreInstance/StopCoreScenario.storyboard rename DatadogLogs/Sources/LogOutputs/LogOutput.swift => IntegrationTests/Runner/Utils/CustomURLSessionDelegate.swift (64%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..d3fdc8a8aa --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,81 @@ +stages: + - info + - lint + - test + +ENV info: + stage: info + tags: + - mac-ventura-preview + script: + - system_profiler SPSoftwareDataType # system info + - xcodebuild -version + - xcode-select -p # default Xcode + - ls /Applications/ | grep Xcode # other Xcodes + - xcodebuild -workspace "Datadog.xcworkspace" -scheme "DatadogCore iOS" -showdestinations -quiet # installed iOS destinations + - xcodebuild -workspace "Datadog.xcworkspace" -scheme "DatadogCore tvOS" -showdestinations -quiet # installed tvOS destinations + - xcbeautify --version + - swiftlint --version + - carthage version + - gh --version + - brew -v + - bundler --version + - python3 -V + +Lint: + stage: lint + tags: + - mac-ventura-preview + script: + - ./tools/lint/run-linter.sh + - ./tools/license/check-license.sh + +SDK unit tests (iOS): + stage: test + tags: + - mac-ventura-preview + variables: + TEST_WORKSPACE: "Datadog.xcworkspace" + TEST_DESTINATION: "platform=iOS Simulator,name=iPhone 15 Pro Max,OS=17.0.1" + script: + - make dependencies-gitlab + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore iOS" -only-testing:"DatadogCoreTests iOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore iOS" -only-testing:"DatadogInternalTests iOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore iOS" -only-testing:"DatadogLogsTests iOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore iOS" -only-testing:"DatadogTraceTests iOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore iOS" -only-testing:"DatadogRUMTests iOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore iOS" -only-testing:"DatadogWebViewTrackingTests iOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogSessionReplay iOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCrashReporting iOS" test | xcbeautify + +SDK unit tests (tvOS): + stage: test + tags: + - mac-ventura-preview + variables: + TEST_WORKSPACE: "Datadog.xcworkspace" + TEST_DESTINATION: "platform=tvOS Simulator,name=Apple TV,OS=17.0" + script: + - make dependencies-gitlab + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore tvOS" -only-testing:"DatadogCoreTests tvOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore tvOS" -only-testing:"DatadogInternalTests tvOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore tvOS" -only-testing:"DatadogLogsTests tvOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore tvOS" -only-testing:"DatadogTraceTests tvOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCore tvOS" -only-testing:"DatadogRUMTests tvOS" test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "DatadogCrashReporting tvOS" test | xcbeautify + +SDK integration tests (iOS): + stage: test + tags: + - mac-ventura-preview + variables: + TEST_WORKSPACE: "IntegrationTests/IntegrationTests.xcworkspace" + TEST_DESTINATION: "platform=iOS Simulator,name=iPhone 15 Pro Max,OS=17.0.1" + script: + - make dependencies-gitlab + - make prepare-integration-tests + # Before running crash reporting tests, disable Apple Crash Reporter so it doesn't capture the crash causing tests hang on " quit unexpectedly" prompt: + - launchctl unload -w /System/Library/LaunchAgents/com.apple.ReportCrash.plist + - ./tools/config/generate-http-server-mock-config.sh + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "IntegrationScenarios" -testPlan DatadogIntegrationTests test | xcbeautify + - xcodebuild -workspace "$TEST_WORKSPACE" -destination "$TEST_DESTINATION" -scheme "IntegrationScenarios" -testPlan DatadogCrashReportingIntegrationTests test | xcbeautify diff --git a/BenchmarkTests/BenchmarkMocks.swift b/BenchmarkTests/BenchmarkMocks.swift deleted file mode 100644 index 629a7756c7..0000000000 --- a/BenchmarkTests/BenchmarkMocks.swift +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 -@testable import DatadogCore - -extension PerformancePreset { - static let benchmarksPreset = PerformancePreset(batchSize: .small, uploadFrequency: .frequent, bundleType: .iOSApp) -} - -struct FeatureRequestBuilderMock: FeatureRequestBuilder { - let dataFormat = DataFormat(prefix: "", suffix: "", separator: "\n") - - func request(for events: [Event], with context: DatadogContext) -> URLRequest { - let builder = URLRequestBuilder( - url: .mockAny(), - queryItems: [.ddtags(tags: ["foo:bar"])], - headers: [] - ) - - let data = dataFormat.format(events.map { $0.data }) - return builder.uploadRequest(with: data) - } -} diff --git a/BenchmarkTests/BenchmarkTests.swift b/BenchmarkTests/BenchmarkTests.swift deleted file mode 100644 index e341527690..0000000000 --- a/BenchmarkTests/BenchmarkTests.swift +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 HTTPServerMock -import DatadogCore -import DatadogLogs -import DatadogTrace -import DatadogRUM - -struct ServerConnectionError: Error { - let description: String -} - -/// Base class providing mock server instrumentation and SDK initialization. -class BenchmarkTests: XCTestCase { - /// Python server instance. - var server: ServerMock { BenchmarkTests.connectedServer! } - - override class func setUp() { - super.setUp() - do { - try connectToServerIfNotConnected() - } catch let error { - fatalError("Failed to connect to Python server: \(error)") - } - initializeSDKIfNotInitialized() - } - - // MARK: - SDK Initialization - - private static var isSDKInitialized = false - - private static func initializeSDKIfNotInitialized() { - if BenchmarkTests.isSDKInitialized { - return - } - - BenchmarkTests.isSDKInitialized = true - - let anyURL = connectedServer!.obtainUniqueRecordingSession().recordingURL - - Datadog.initialize( - with: Datadog.Configuration(clientToken: "rum-abc", env: "benchmarks"), - trackingConsent: .granted - ) - - RUM.enable(with: .init(applicationID: "rum-123", customEndpoint: anyURL)) - Logs.enable(with: .init(customEndpoint: anyURL)) - Trace.enable(with: .init(customEndpoint: anyURL)) - } - - // MARK: - `HTTPServerMock` connection - - private static var connectedServer: ServerMock? - - private static func connectToServerIfNotConnected() throws { - if BenchmarkTests.connectedServer != nil { - return - } - - let testsBundle = Bundle(for: BenchmarkTests.self) - guard let serverAddress = testsBundle.object(forInfoDictionaryKey: "MockServerAddress") as? String else { - throw ServerConnectionError(description: "Cannot obtain `MockServerAddress` from `Info.plist`") - } - - guard let serverURL = URL(string: "http://\(serverAddress)") else { - throw ServerConnectionError(description: "`MockServerAddress` obtained from `Info.plist` is invalid.") - } - - let serverProcessRunner = ServerProcessRunner(serverURL: serverURL) - guard let serverProcess = serverProcessRunner.waitUntilServerIsReachable() else { - throw ServerConnectionError( - description: "The server seems to be not running properly on \(serverURL.absoluteString)" - ) - } - - print("🌍 Connected to mock server on \(serverURL.absoluteString)") - - BenchmarkTests.connectedServer = ServerMock(serverProcess: serverProcess) - } -} diff --git a/BenchmarkTests/DataCollection/LoggingBenchmarkTests.swift b/BenchmarkTests/DataCollection/LoggingBenchmarkTests.swift deleted file mode 100644 index 9a9cb7b5dd..0000000000 --- a/BenchmarkTests/DataCollection/LoggingBenchmarkTests.swift +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 DatadogLogs - -class LoggingBenchmarkTests: BenchmarkTests { - private let message = "foobar-message" - - func testCreatingOneLog() { - let logger = Logger.create() - - measure { - logger.info(message) - } - } - - func testCreatingOneLogWithAttributes() { - let logger = Logger.create() - (0..<16).forEach { index in - logger.addAttribute(forKey: "a\(index)", value: "v\(index)") - } - - measure { - logger.info(message) - } - } - - func testCreatingOneLogWithTags() { - let logger = Logger.create() - (0..<8).forEach { index in - logger.addTag(withKey: "t\(index)", value: "v\(index)") - } - - measure { - logger.info(message) - } - } -} diff --git a/BenchmarkTests/DataCollection/RUMBenchmarkTests.swift b/BenchmarkTests/DataCollection/RUMBenchmarkTests.swift deleted file mode 100644 index 10e2e35f0e..0000000000 --- a/BenchmarkTests/DataCollection/RUMBenchmarkTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 -import DatadogRUM - -class RUMBenchmarkTests: BenchmarkTests { - var rum: RUMMonitorProtocol { RUMMonitor.shared() } - - func testCreatingOneRUMEvent() { - let viewController = UIViewController() - rum.startView(viewController: viewController) - - measure { - rum.addAction(type: .tap, name: "tap") - } - } - - func testCreatingOneRUMEventWithAttributes() { - let viewController = UIViewController() - rum.startView(viewController: viewController) - - var attributes: [AttributeKey: AttributeValue] = [:] - (0..<16).forEach { index in - attributes["a\(index)"] = "v\(index)" - } - - measure { - rum.addAction(type: .tap, name: "tap", attributes: attributes) - } - } -} diff --git a/BenchmarkTests/DataCollection/TracingBenchmarkTests.swift b/BenchmarkTests/DataCollection/TracingBenchmarkTests.swift deleted file mode 100644 index ebf16ea47e..0000000000 --- a/BenchmarkTests/DataCollection/TracingBenchmarkTests.swift +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 DatadogCore -import XCTest - -import DatadogTrace - -class TracingBenchmarkTests: BenchmarkTests { - private let operationName = "foobar-span" - - func testCreatingAndEndingOneSpan() { - measure { - let testSpan = Tracer.shared().startSpan(operationName: operationName) - testSpan.finish() - } - } - - func testCreatingOneSpanWithBaggageItems() { - measure { - let testSpan = Tracer.shared().startSpan(operationName: operationName) - (0..<16).forEach { index in - testSpan.setBaggageItem(key: "a\(index)", value: "v\(index)") - } - testSpan.finish() - } - } - - func testCreatingOneSpanWithTags() { - measure { - let testSpan = Tracer.shared().startSpan(operationName: operationName) - (0..<8).forEach { index in - testSpan.setTag(key: "t\(index)", value: "v\(index)") - } - testSpan.finish() - } - } -} diff --git a/BenchmarkTests/DataStorage/LoggingStorageBenchmarkTests.swift b/BenchmarkTests/DataStorage/LoggingStorageBenchmarkTests.swift deleted file mode 100644 index d8df24edf0..0000000000 --- a/BenchmarkTests/DataStorage/LoggingStorageBenchmarkTests.swift +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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 DatadogLogs -@testable import DatadogCore - -class LoggingStorageBenchmarkTests: XCTestCase { - // swiftlint:disable implicitly_unwrapped_optional - private var queue: DispatchQueue! - private var directory: Directory! - private var writer: Writer! - private var reader: Reader! - // swiftlint:enable implicitly_unwrapped_optional - - override func setUpWithError() throws { - try super.setUpWithError() - self.directory = try Directory(withSubdirectoryPath: "logging-benchmark") - self.queue = DispatchQueue(label: "logging-benchmark") - - let storage = FeatureStorage( - featureName: "logging", - queue: queue, - directories: .init( - unauthorized: directory, - authorized: directory - ), - dateProvider: SystemDateProvider(), - performance: .benchmarksPreset, - encryption: nil, - telemetry: NOPTelemetry() - ) - - self.writer = storage.writer(for: .granted, forceNewBatch: false) - self.reader = storage.reader - - XCTAssertTrue(try directory.files().isEmpty) - } - - override func tearDown() { - try? FileManager.default.removeItem(at: directory.url) - queue = nil - directory = nil - writer = nil - reader = nil - super.tearDown() - } - - func testWritingLogsOnDisc() throws { - let log = createRandomizedLog() - - measure { - writer.write(value: log) - queue.sync {} // wait to complete async write - } - } - - func testReadingLogsFromDisc() throws { - while try directory.files().count < 10 { // `measureMetrics {}` is fired 10 times so 10 batch files are required - writer.write(value: createRandomizedLog()) - queue.sync {} // wait to complete async write - } - - // Wait enough time for `reader` to accept the youngest batch file - Thread.sleep(forTimeInterval: PerformancePreset.benchmarksPreset.minFileAgeForRead + 0.1) - - measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) { - self.startMeasuring() - let batch = reader.readNextBatches(1).first - self.stopMeasuring() - - XCTAssertNotNil(batch, "Not enough batch files were created for this benchmark.") - - if let batch = batch { - reader.markBatchAsRead(batch, reason: .flushed) - } - } - } - - // MARK: - Helpers - - private func createRandomizedLog() -> LogEvent { - return LogEvent( - date: Date(), - status: .info, - message: "message \(Int.random(in: 0..<100))", - error: .init( - kind: nil, - message: "description", - stack: nil - ), - serviceName: "service-name", - environment: "benchmarks", - loggerName: "logger-name", - loggerVersion: "0.0.0", - threadName: "main", - applicationVersion: "0.0.0", - applicationBuildNumber: "0", - buildId: "0", - dd: .init(device: .init(architecture: "testArch")), - os: .init( - name: "OS", - version: "1.0.0", - build: "FFFFF" - ), - userInfo: .init(id: "abc-123", name: "foo", email: "foo@bar.com", extraInfo: ["str": "value", "int": 11_235, "bool": true]), - networkConnectionInfo: .init( - reachability: .yes, - availableInterfaces: [.cellular], - supportsIPv4: true, - supportsIPv6: true, - isExpensive: false, - isConstrained: false - ), - mobileCarrierInfo: nil, - attributes: LogEvent.Attributes( - userAttributes: ["user.attribute": "value"], - internalAttributes: ["internal.attribute": "value"] - ), - tags: ["tag:value"] - ) - } -} diff --git a/BenchmarkTests/DataStorage/RUMStorageBenchmarkTests.swift b/BenchmarkTests/DataStorage/RUMStorageBenchmarkTests.swift deleted file mode 100644 index acc09206ce..0000000000 --- a/BenchmarkTests/DataStorage/RUMStorageBenchmarkTests.swift +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 -import DatadogRUM - -@testable import DatadogCore - -class RUMStorageBenchmarkTests: XCTestCase { - // swiftlint:disable implicitly_unwrapped_optional - private var queue: DispatchQueue! - private var directory: Directory! - private var writer: Writer! - private var reader: Reader! - // swiftlint:enable implicitly_unwrapped_optional - - override func setUpWithError() throws { - try super.setUpWithError() - self.directory = try Directory(withSubdirectoryPath: "rum-benchmark") - self.queue = DispatchQueue(label: "rum-benchmark") - - let storage = FeatureStorage( - featureName: "rum", - queue: queue, - directories: .init( - unauthorized: directory, - authorized: directory - ), - dateProvider: SystemDateProvider(), - performance: .benchmarksPreset, - encryption: nil, - telemetry: NOPTelemetry() - ) - - self.writer = storage.writer(for: .granted, forceNewBatch: false) - self.reader = storage.reader - - XCTAssertTrue(try directory.files().isEmpty) - } - - override func tearDown() { - try? FileManager.default.removeItem(at: directory.url) - queue = nil - directory = nil - writer = nil - reader = nil - super.tearDown() - } - - func testWritingRUMEventsOnDisc() throws { - let event: RUMViewEvent = .mockRandom() - - measure { - writer.write(value: event) - queue.sync {} // wait to complete async write - } - } - - func testReadingRUMEventsFromDisc() throws { - while try directory.files().count < 10 { // `measureMetrics {}` is fired 10 times so 10 batch files are required - writer.write(value: RUMViewEvent.mockRandom()) - queue.sync {} // wait to complete async write - } - - // Wait enough time for `reader` to accept the youngest batch file - Thread.sleep(forTimeInterval: PerformancePreset.benchmarksPreset.minFileAgeForRead + 0.1) - - measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) { - self.startMeasuring() - let batch = reader.readNextBatches(1).first - self.stopMeasuring() - - XCTAssertNotNil(batch, "Not enough batch files were created for this benchmark.") - - if let batch = batch { - reader.markBatchAsRead(batch, reason: .flushed) - } - } - } -} - -extension Reader { - func readNextBatches(_ limit: Int = .max) -> [Batch] { - return readFiles(limit: limit).compactMap { readBatch(from: $0) } - } -} diff --git a/BenchmarkTests/DataStorage/TracingStorageBenchmarkTests.swift b/BenchmarkTests/DataStorage/TracingStorageBenchmarkTests.swift deleted file mode 100644 index 5a4c33b858..0000000000 --- a/BenchmarkTests/DataStorage/TracingStorageBenchmarkTests.swift +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 DatadogTrace -@testable import DatadogCore - -class TracingStorageBenchmarkTests: XCTestCase { - // swiftlint:disable implicitly_unwrapped_optional - private var queue: DispatchQueue! - private var directory: Directory! - private var writer: Writer! - private var reader: Reader! - // swiftlint:enable implicitly_unwrapped_optional - - override func setUpWithError() throws { - try super.setUpWithError() - self.directory = try Directory(withSubdirectoryPath: "tracing-benchmark") - self.queue = DispatchQueue(label: "tracing-benchmark") - - let storage = FeatureStorage( - featureName: "tracing", - queue: queue, - directories: .init( - unauthorized: directory, - authorized: directory - ), - dateProvider: SystemDateProvider(), - performance: .benchmarksPreset, - encryption: nil, - telemetry: NOPTelemetry() - ) - - self.writer = storage.writer(for: .granted, forceNewBatch: false) - self.reader = storage.reader - - XCTAssertTrue(try directory.files().isEmpty) - } - - override func tearDown() { - try? FileManager.default.removeItem(at: directory.url) - queue = nil - directory = nil - writer = nil - reader = nil - super.tearDown() - } - - func testWritingSpansOnDisc() throws { - let log = createRandomizedSpan() - - measure { - writer.write(value: log) - queue.sync {} // wait to complete async write - } - } - - func testReadingSpansFromDisc() throws { - while try directory.files().count < 10 { // `measureMetrics {}` is fired 10 times so 10 batch files are required - writer.write(value: createRandomizedSpan()) - queue.sync {} // wait to complete async write - } - - // Wait enough time for `reader` to accept the youngest batch file - Thread.sleep(forTimeInterval: PerformancePreset.benchmarksPreset.minFileAgeForRead + 0.1) - - measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) { - self.startMeasuring() - let batch = reader.readNextBatches(1).first - self.stopMeasuring() - - XCTAssertNotNil(batch, "Not enough batch files were created for this benchmark.") - - if let batch = batch { - reader.markBatchAsRead(batch, reason: .flushed) - } - } - } - - // MARK: - Helpers - - private func createRandomizedSpan() -> SpanEvent { - let tracingUUIDGenerator = DefaultTraceIDGenerator() - return SpanEvent( - traceID: tracingUUIDGenerator.generate(), - spanID: tracingUUIDGenerator.generate(), - parentID: nil, - operationName: "span \(Int.random(in: 0..<100))", - serviceName: "service-name", - resource: "benchmarks", - startTime: Date(), - duration: Double.random(in: 0.0..<1.0), - isError: false, - source: "ios", - origin: nil, - samplingRate: 100, - isKept: true, - tracerVersion: "0.0.0", - applicationVersion: "0.0.0", - networkConnectionInfo: NetworkConnectionInfo( - reachability: .yes, - availableInterfaces: [.cellular], - supportsIPv4: true, - supportsIPv6: true, - isExpensive: false, - isConstrained: false - ), - mobileCarrierInfo: nil, - userInfo: .init( - id: "abc-123", - name: "foo", - email: "foo@bar.com", - extraInfo: [ - "info": .mockRandom(), - ] - ), - tags: [ - "tag": .mockRandom(), - ] - ) - } -} diff --git a/BenchmarkTests/DataUpload/DataUploaderBenchmarkTests.swift b/BenchmarkTests/DataUpload/DataUploaderBenchmarkTests.swift deleted file mode 100644 index 0671d36e4b..0000000000 --- a/BenchmarkTests/DataUpload/DataUploaderBenchmarkTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 TestUtilities -import HTTPServerMock -import DatadogInternal -@testable import DatadogCore - -@available(iOS 13.0, *) -class DataUploaderBenchmarkTests: BenchmarkTests { - override func setUpWithError() throws { - try super.setUpWithError() - CreateTemporaryDirectory() - } - - override func tearDownWithError() throws { - DeleteTemporaryDirectory() - try super.tearDownWithError() - } - - /// NOTE: In RUMM-610 we noticed that due to internal `NSCache` used by the `URLSession` - /// requests memory was leaked after upload. This benchmark ensures that uploading data with - /// `DataUploader` leaves no memory footprint (the memory peak after upload is less or equal `0kB`). - func testUploadingDataToServer_leavesNoMemoryFootprint() throws { - let dataUploader = DataUploader( - httpClient: URLSessionClient(), - requestBuilder: FeatureRequestBuilderMock() - ) - - let context: DatadogContext = .mockAny() - - // `measure` runs 5 iterations - measure(metrics: [XCTMemoryMetric()]) { - // in each, 10 requests are done: - try? (0..<10).forEach { _ in - let events = [Event(data: Data(repeating: 0x41, count: 10 * 1_024 * 1_024))] - _ = try dataUploader.upload(events: events, context: context) - } - // After all, the baseline asserts `0kB` or less grow in Physical Memory. - // This makes sure that no request data is leaked (e.g. due to internal caching). - } - } -} diff --git a/CHANGELOG.md b/CHANGELOG.md index de7c536c79..d7c6bc859e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,29 @@ # Unreleased + +- [FIX] Propagate parent span in distributing tracing. See [#1627][] +- [FIX] Privacy Report missing properties. See [#1656][] + +# 2.7.0 / 25-01-2024 + +- [FIX] RUM session not being linked to spans. See [#1615][] +- [FIX] `URLSessionTask.resume()` swizzling in iOS 13 and 12. See [#1637][] +- [FEATURE] Allow stopping a core instance. See [#1541][] +- [FEATURE] Link crashes sent as Log events to RUM session. See [#1645][] +- [IMPROVEMENT] Add extra HTTP codes to the list of retryable status codes. See [#1639][] +- [FEATURE] Add privacy manifest to `DatadogCore`. See [#1644][] + +# 2.6.0 / 09-01-2024 +- [FEATURE] Add `currentSessionID(completion:)` accessor to access the current session ID. - [FEATURE] Add `BatchProcessingLevel` configuration allowing to process more batches within single read/upload cycle. See [#1531][] +- [FIX] Use `currentRequest` instead `originalRequest` for URLSession request interception. See [#1609][] +- [FIX] Remove weak `UIViewController` references. See [#1597][] # 2.5.1 / 20-12-2023 - [BUGFIX] Fix `view.time_spent` in RUM view events. See [#1596][] +- [FEATURE] Start RUM session on RUM init. See [#1594][] + # 2.5.0 / 08-11-2023 - [BUGFIX] Optimize Session Replay diffing algorithm. See [#1524][] @@ -561,9 +580,20 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1524]: https://github.com/DataDog/dd-sdk-ios/pull/1524 [#1529]: https://github.com/DataDog/dd-sdk-ios/pull/1529 [#1533]: https://github.com/DataDog/dd-sdk-ios/pull/1533 +[#1645]: https://github.com/DataDog/dd-sdk-ios/pull/1645 +[#1594]: https://github.com/DataDog/dd-sdk-ios/pull/1594 [#1536]: https://github.com/DataDog/dd-sdk-ios/pull/1536 +[#1609]: https://github.com/DataDog/dd-sdk-ios/pull/1609 +[#1639]: https://github.com/DataDog/dd-sdk-ios/pull/1639 +[#1615]: https://github.com/DataDog/dd-sdk-ios/pull/1615 [#1531]: https://github.com/DataDog/dd-sdk-ios/pull/1531 +[#1637]: https://github.com/DataDog/dd-sdk-ios/pull/1637 +[#1541]: https://github.com/DataDog/dd-sdk-ios/pull/1541 [#1596]: https://github.com/DataDog/dd-sdk-ios/pull/1596 +[#1597]: https://github.com/DataDog/dd-sdk-ios/pull/1597 +[#1627]: https://github.com/DataDog/dd-sdk-ios/pull/1627 +[#1644]: https://github.com/DataDog/dd-sdk-ios/pull/1644 +[#1656]: https://github.com/DataDog/dd-sdk-ios/pull/1656 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index cfa9426818..4a26f7e720 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -23,8 +23,6 @@ 3C0D5DF62A5443B100446CF9 /* DataFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0D5DF42A5443B100446CF9 /* DataFormatTests.swift */; }; 3C1890152ABDE9BF00CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C1890132ABDE99200CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m */; }; 3C1890162ABDE9C000CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C1890132ABDE99200CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m */; }; - 3C2206F32AB9CE9300DE780C /* MetaTypeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */; }; - 3C2206F42AB9CE9300DE780C /* MetaTypeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */; }; 3C2206F52AB9DB9000DE780C /* DatadogSessionReplay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C2206F62AB9DBA700DE780C /* DatadogRUM.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D29A9F3429DD84AA005C54A4 /* DatadogRUM.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C2206F72AB9DBB600DE780C /* DatadogTrace.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -33,10 +31,6 @@ 3C32359E2B55386C000B4258 /* OTelSpanLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359C2B55386C000B4258 /* OTelSpanLink.swift */; }; 3C3235A02B55387A000B4258 /* OTelSpanLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */; }; 3C3235A12B55387A000B4258 /* OTelSpanLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */; }; - 3C394EF72AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */; }; - 3C394EF82AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */; }; - 3C394EFA2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */; }; - 3C394EFB2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */; }; 3C41693C29FBF4D50042B9D2 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; 3C5D63692B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */; }; 3C5D636A2B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */; }; @@ -68,22 +62,8 @@ 3CB012DE2B482E0400557951 /* NOPOTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */; }; 3CB012DF2B482E0400557951 /* NOPOTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */; }; 3CB012E02B482E0400557951 /* NOPOTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */; }; - 3CB32AD42ACB733000D602ED /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */; }; - 3CB32AD52ACB733000D602ED /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */; }; - 3CB32AD72ACB735600D602ED /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */; }; - 3CB32AD82ACB735600D602ED /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */; }; - 3CBDE66E2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE66D2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift */; }; - 3CBDE66F2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE66D2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift */; }; - 3CBDE6712AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6702AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift */; }; - 3CBDE6722AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6702AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift */; }; 3CBDE6742AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */; }; 3CBDE6752AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */; }; - 3CBDE6812AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6802AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift */; }; - 3CBDE6822AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6802AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift */; }; - 3CBDE6842AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */; }; - 3CBDE6852AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */; }; - 3CBDE6872AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */; }; - 3CBDE6882AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */; }; 3CBDE68A2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */; }; 3CBDE68B2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */; }; 3CC6AD182B4F07DD00015B18 /* OTelAttributeValue+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */; }; @@ -98,8 +78,6 @@ 3CE11A1229F7BE0900202522 /* DatadogWebViewTracking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3CF673362B4807490016CE17 /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF673352B4807490016CE17 /* OTelSpanTests.swift */; }; 3CF673372B4807490016CE17 /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF673352B4807490016CE17 /* OTelSpanTests.swift */; }; - 3CFD81952ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */; }; - 3CFD81962ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */; }; 49274906288048B500ECD49B /* InternalProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49274903288048AA00ECD49B /* InternalProxyTests.swift */; }; 49274907288048B800ECD49B /* InternalProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49274903288048AA00ECD49B /* InternalProxyTests.swift */; }; 49D8C0B72AC5D2160075E427 /* RUM+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D8C0B62AC5D2160075E427 /* RUM+Internal.swift */; }; @@ -159,7 +137,7 @@ 61054E922A6EE10A00AAA894 /* SegmentJSONBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E452A6EE10A00AAA894 /* SegmentJSONBuilder.swift */; }; 61054E932A6EE10A00AAA894 /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E472A6EE10A00AAA894 /* MultipartFormData.swift */; }; 61054E942A6EE10A00AAA894 /* TextObfuscator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E4A2A6EE10A00AAA894 /* TextObfuscator.swift */; }; - 61054E952A6EE10A00AAA894 /* Processor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E4B2A6EE10A00AAA894 /* Processor.swift */; }; + 61054E952A6EE10A00AAA894 /* SnapshotProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E4B2A6EE10A00AAA894 /* SnapshotProcessor.swift */; }; 61054E962A6EE10A00AAA894 /* Diff+SRWireframes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E4D2A6EE10A00AAA894 /* Diff+SRWireframes.swift */; }; 61054E972A6EE10A00AAA894 /* Diff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E4E2A6EE10A00AAA894 /* Diff.swift */; }; 61054E982A6EE10A00AAA894 /* RecordsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E502A6EE10A00AAA894 /* RecordsBuilder.swift */; }; @@ -190,7 +168,7 @@ 61054FA32A6EE1BA00AAA894 /* Diff+SRWireframesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F522A6EE1BA00AAA894 /* Diff+SRWireframesTests.swift */; }; 61054FA42A6EE1BA00AAA894 /* DiffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F532A6EE1BA00AAA894 /* DiffTests.swift */; }; 61054FA52A6EE1BA00AAA894 /* RecordsBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F552A6EE1BA00AAA894 /* RecordsBuilderTests.swift */; }; - 61054FA62A6EE1BA00AAA894 /* ProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F562A6EE1BA00AAA894 /* ProcessorTests.swift */; }; + 61054FA62A6EE1BA00AAA894 /* SnapshotProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F562A6EE1BA00AAA894 /* SnapshotProcessorTests.swift */; }; 61054FA72A6EE1BA00AAA894 /* NodesFlattenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F582A6EE1BA00AAA894 /* NodesFlattenerTests.swift */; }; 61054FA82A6EE1BA00AAA894 /* RecordingCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F5A2A6EE1BA00AAA894 /* RecordingCoordinatorTests.swift */; }; 61054FAA2A6EE1BA00AAA894 /* UIKitExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F5D2A6EE1BA00AAA894 /* UIKitExtensionsTests.swift */; }; @@ -223,7 +201,7 @@ 61054FC52A6EE1BA00AAA894 /* UIKitMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F7E2A6EE1BA00AAA894 /* UIKitMocks.swift */; }; 61054FC62A6EE1BA00AAA894 /* CoreGraphicsMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F7F2A6EE1BA00AAA894 /* CoreGraphicsMocks.swift */; }; 61054FC72A6EE1BA00AAA894 /* SRDataModelsMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F802A6EE1BA00AAA894 /* SRDataModelsMocks.swift */; }; - 61054FC82A6EE1BA00AAA894 /* ProcessorSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F812A6EE1BA00AAA894 /* ProcessorSpy.swift */; }; + 61054FC82A6EE1BA00AAA894 /* SnapshotProcessorSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F812A6EE1BA00AAA894 /* SnapshotProcessorSpy.swift */; }; 61054FC92A6EE1BA00AAA894 /* RecorderMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F822A6EE1BA00AAA894 /* RecorderMocks.swift */; }; 61054FCA2A6EE1BA00AAA894 /* TestScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F832A6EE1BA00AAA894 /* TestScheduler.swift */; }; 61054FCB2A6EE1BA00AAA894 /* QueueMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F842A6EE1BA00AAA894 /* QueueMocks.swift */; }; @@ -308,9 +286,6 @@ 6136CB4B2A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6136CB492A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift */; }; 6139CD712589FAFD007E8BB7 /* Retrying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6139CD702589FAFD007E8BB7 /* Retrying.swift */; }; 6139CD772589FEE3007E8BB7 /* RetryingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6139CD762589FEE3007E8BB7 /* RetryingTests.swift */; }; - 613BE0432563FB9E0015216C /* RUMBenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613BE0422563FB9E0015216C /* RUMBenchmarkTests.swift */; }; - 613BE04A25640FF80015216C /* BenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613BE04925640FF80015216C /* BenchmarkTests.swift */; }; - 613BE06225642F790015216C /* RUMStorageBenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613BE06125642F790015216C /* RUMStorageBenchmarkTests.swift */; }; 613E792F2577B0F900DFCC17 /* Reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613E792E2577B0F900DFCC17 /* Reader.swift */; }; 613E793B2577B6EE00DFCC17 /* DataReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613E793A2577B6EE00DFCC17 /* DataReader.swift */; }; 614396722A67D74F00197326 /* CoreMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614396712A67D74F00197326 /* CoreMetrics.swift */; }; @@ -318,9 +293,6 @@ 61441C0524616DE9003D8BB8 /* ExampleAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61441C0424616DE9003D8BB8 /* ExampleAppDelegate.swift */; }; 61441C0C24616DE9003D8BB8 /* Main iOS.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 61441C0A24616DE9003D8BB8 /* Main iOS.storyboard */; }; 61441C0E24616DEC003D8BB8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 61441C0D24616DEC003D8BB8 /* Assets.xcassets */; }; - 61441C6D24619FE4003D8BB8 /* DatadogCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61133B82242393DE00786299 /* DatadogCore.framework */; }; - 61441C7A2461A204003D8BB8 /* LoggingBenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61441C782461A204003D8BB8 /* LoggingBenchmarkTests.swift */; }; - 61441C7B2461A204003D8BB8 /* LoggingStorageBenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61441C792461A204003D8BB8 /* LoggingStorageBenchmarkTests.swift */; }; 61441C952461A649003D8BB8 /* ConsoleOutputInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61441C902461A648003D8BB8 /* ConsoleOutputInterceptor.swift */; }; 61441C962461A649003D8BB8 /* UIButton+Disabling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61441C912461A648003D8BB8 /* UIButton+Disabling.swift */; }; 61441C982461A649003D8BB8 /* DebugTracingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61441C932461A649003D8BB8 /* DebugTracingViewController.swift */; }; @@ -343,10 +315,7 @@ 614CADD72510BAC000B93D2D /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614CADD62510BAC000B93D2D /* Environment.swift */; }; 614E9EB3244719FA007EE3E1 /* BundleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614E9EB2244719FA007EE3E1 /* BundleType.swift */; }; 614ED36C260352DC00C8C519 /* CrashReporter.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 614ED36B260352DC00C8C519 /* CrashReporter.xcframework */; }; - 6152C83E24BE1C91006A1679 /* HTTPServerMock in Frameworks */ = {isa = PBXBuildFile; productRef = 6152C83D24BE1C91006A1679 /* HTTPServerMock */; }; - 6152C84024BE1CC8006A1679 /* DataUploaderBenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6152C83F24BE1CC8006A1679 /* DataUploaderBenchmarkTests.swift */; }; 61570005246AADFA00E96950 /* DatadogObjc.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61133BF0242397DA00786299 /* DatadogObjc.framework */; }; - 61570007246AAED100E96950 /* DatadogObjc.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61133BF0242397DA00786299 /* DatadogObjc.framework */; }; 615A4A8324A3431600233986 /* Trace+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615A4A8224A3431600233986 /* Trace+objc.swift */; }; 615A4A8924A34FD700233986 /* DDTracerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615A4A8824A34FD700233986 /* DDTracerTests.swift */; }; 615A4A8B24A3568900233986 /* OTSpan+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615A4A8A24A3568900233986 /* OTSpan+objc.swift */; }; @@ -374,6 +343,8 @@ 6176C1732ABDBA2E00131A70 /* MonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6176C1712ABDBA2E00131A70 /* MonitorTests.swift */; }; 61776CED273BEA5500F93802 /* DebugRUMSessionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61776CEC273BEA5500F93802 /* DebugRUMSessionViewController.swift */; }; 61776D4E273E6D9F00F93802 /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61776D4D273E6D9F00F93802 /* SwiftUI.swift */; }; + 6179DB562B6022EA00E9E04E /* SendingCrashReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6179DB552B6022EA00E9E04E /* SendingCrashReportTests.swift */; }; + 6179DB572B6022EA00E9E04E /* SendingCrashReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6179DB552B6022EA00E9E04E /* SendingCrashReportTests.swift */; }; 6179FFD3254ADB1700556A0B /* ObjcAppLaunchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 6179FFD2254ADB1100556A0B /* ObjcAppLaunchHandler.m */; }; 6179FFDE254ADBEF00556A0B /* ObjcAppLaunchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 6179FFD1254ADB1100556A0B /* ObjcAppLaunchHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 617B953D24BF4D8F00E6F443 /* RUMMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617B953C24BF4D8F00E6F443 /* RUMMonitorTests.swift */; }; @@ -389,6 +360,8 @@ 6188697D2A4376F700E8996B /* RUMConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6188697B2A4376F700E8996B /* RUMConfigurationTests.swift */; }; 6188900F2AC58B8C00D0B966 /* TelemetryReceiverMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6188900E2AC58B8C00D0B966 /* TelemetryReceiverMock.swift */; }; 618890102AC58B8C00D0B966 /* TelemetryReceiverMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6188900E2AC58B8C00D0B966 /* TelemetryReceiverMock.swift */; }; + 618C0FC02B482F6800266B38 /* SpanWriteContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618C0FBF2B482F6800266B38 /* SpanWriteContextTests.swift */; }; + 618C0FC12B482F6800266B38 /* SpanWriteContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618C0FBF2B482F6800266B38 /* SpanWriteContextTests.swift */; }; 618C365F248E85B400520CDE /* DateFormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618C365E248E85B400520CDE /* DateFormattingTests.swift */; }; 618F9843265BC486009959F8 /* E2EInstrumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618F9842265BC486009959F8 /* E2EInstrumentationTests.swift */; }; 618F984E265BC905009959F8 /* E2EConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618F984D265BC905009959F8 /* E2EConfig.swift */; }; @@ -414,7 +387,6 @@ 61A2CC2B2A4449300000FF25 /* DatadogRUM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23F8E9929DDCD28001CFAE8 /* DatadogRUM.framework */; }; 61A2CC302A4449CB0000FF25 /* DatadogRUM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29A9F3429DD84AA005C54A4 /* DatadogRUM.framework */; }; 61A2CC312A4449D70000FF25 /* DatadogRUM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23F8E9929DDCD28001CFAE8 /* DatadogRUM.framework */; }; - 61A2CC322A445D8A0000FF25 /* DatadogRUM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29A9F3429DD84AA005C54A4 /* DatadogRUM.framework */; }; 61A2CC332A44A5F60000FF25 /* DatadogRUM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29A9F3429DD84AA005C54A4 /* DatadogRUM.framework */; }; 61A2CC342A44A6030000FF25 /* DatadogRUM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23F8E9929DDCD28001CFAE8 /* DatadogRUM.framework */; }; 61A2CC362A44B0A20000FF25 /* TraceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A2CC352A44B0A20000FF25 /* TraceConfiguration.swift */; }; @@ -472,6 +444,8 @@ 61C713D12A3DEFF900FA735A /* FeatureRegistrationCoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713CF2A3DEFF900FA735A /* FeatureRegistrationCoreMock.swift */; }; 61C713D32A3DFB4900FA735A /* FuzzyHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713D22A3DFB4900FA735A /* FuzzyHelpers.swift */; }; 61C713D42A3DFB4900FA735A /* FuzzyHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713D22A3DFB4900FA735A /* FuzzyHelpers.swift */; }; + 61CE585A2B48174D00479510 /* SpanWriteContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CE58592B48174D00479510 /* SpanWriteContext.swift */; }; + 61CE585B2B48174D00479510 /* SpanWriteContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CE58592B48174D00479510 /* SpanWriteContext.swift */; }; 61D03BE0273404E700367DE0 /* RUMDataModels+objcTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D03BDF273404E700367DE0 /* RUMDataModels+objcTests.swift */; }; 61D3E0D2277B23F1008BE766 /* KronosInternetAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D3E0C8277B23F0008BE766 /* KronosInternetAddress.swift */; }; 61D3E0D3277B23F1008BE766 /* KronosDNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D3E0C9277B23F0008BE766 /* KronosDNSResolver.swift */; }; @@ -486,7 +460,6 @@ 61D3E0E4277B3D92008BE766 /* KronosNTPPacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D3E0DF277B3D92008BE766 /* KronosNTPPacketTests.swift */; }; 61D3E0E7277B3D92008BE766 /* KronosTimeStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D3E0E2277B3D92008BE766 /* KronosTimeStorageTests.swift */; }; 61D3E0EA277E0C58008BE766 /* KronosE2ETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D3E0E9277E0C58008BE766 /* KronosE2ETests.swift */; }; - 61D6FF7E24E53D3B00D0E375 /* BenchmarkMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D6FF7D24E53D3B00D0E375 /* BenchmarkMocks.swift */; }; 61DA20F026C40121004AFE6D /* DataUploadStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DA20EF26C40121004AFE6D /* DataUploadStatusTests.swift */; }; 61DA8CA928609C5B0074A606 /* Directories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DA8CA828609C5B0074A606 /* Directories.swift */; }; 61DA8CAA28609C5B0074A606 /* Directories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DA8CA828609C5B0074A606 /* Directories.swift */; }; @@ -502,6 +475,8 @@ 61E45BE724519A3700F2C652 /* JSONDataMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45BE624519A3700F2C652 /* JSONDataMatcher.swift */; }; 61E45ED12451A8730061DAC7 /* SpanMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45ED02451A8730061DAC7 /* SpanMatcher.swift */; }; 61E5333824B84EE2003D6C4E /* DebugRUMViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E5333724B84EE2003D6C4E /* DebugRUMViewController.swift */; }; + 61E8C5082B28898800E709B4 /* StartingRUMSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E8C5072B28898800E709B4 /* StartingRUMSessionTests.swift */; }; + 61E8C5092B28898800E709B4 /* StartingRUMSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E8C5072B28898800E709B4 /* StartingRUMSessionTests.swift */; }; 61E95D882695C00200EA3115 /* DDCrashReportExporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E95D872695C00200EA3115 /* DDCrashReportExporterTests.swift */; }; 61ED39D426C2A36B002C0F26 /* DataUploadStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ED39D326C2A36B002C0F26 /* DataUploadStatus.swift */; }; 61EF78C1257F842000EDCCB3 /* FeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61EF78C0257F842000EDCCB3 /* FeatureTests.swift */; }; @@ -533,10 +508,6 @@ A70A82662A935F210072F5DC /* BackgroundTaskCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70A82642A935F210072F5DC /* BackgroundTaskCoordinator.swift */; }; A71013D62B178FAD00101E60 /* ResourcesWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71013D52B178FAD00101E60 /* ResourcesWriterTests.swift */; }; A71265862B17980C007D63CE /* MockFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71265852B17980C007D63CE /* MockFeature.swift */; }; - A712658F2B179C94007D63CE /* EnrichedResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A712658B2B179C93007D63CE /* EnrichedResource.swift */; }; - A71265902B179C94007D63CE /* SRDataModels+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A712658C2B179C93007D63CE /* SRDataModels+UIKit.swift */; }; - A71265912B179C94007D63CE /* SRDataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A712658D2B179C93007D63CE /* SRDataModels.swift */; }; - A71265922B179C94007D63CE /* EnrichedRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = A712658E2B179C93007D63CE /* EnrichedRecord.swift */; }; A728ADAB2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728ADAA2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift */; }; A728ADAC2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728ADAA2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift */; }; A728ADB02934EB0900397996 /* DDW3CHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A728ADAD2934EB0300397996 /* DDW3CHTTPHeadersWriter+apiTests.m */; }; @@ -550,8 +521,15 @@ A79B0F65292BD074008742B3 /* DDB3HTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A79B0F63292BD074008742B3 /* DDB3HTTPHeadersWriter+apiTests.m */; }; A79B0F66292BD7CA008742B3 /* B3HTTPHeadersWriter+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79B0F5E292BA435008742B3 /* B3HTTPHeadersWriter+objc.swift */; }; A79B0F67292BD7CC008742B3 /* B3HTTPHeadersWriter+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79B0F5E292BA435008742B3 /* B3HTTPHeadersWriter+objc.swift */; }; + A7B932F52B1F694000AE6477 /* ResourcesProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B932F42B1F694000AE6477 /* ResourcesProcessor.swift */; }; + A7B932FB2B1F6A0A00AE6477 /* EnrichedRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B932F72B1F6A0A00AE6477 /* EnrichedRecord.swift */; }; + A7B932FC2B1F6A0A00AE6477 /* SRDataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B932F82B1F6A0A00AE6477 /* SRDataModels.swift */; }; + A7B932FD2B1F6A0A00AE6477 /* EnrichedResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B932F92B1F6A0A00AE6477 /* EnrichedResource.swift */; }; + A7B932FE2B1F6A0A00AE6477 /* SRDataModels+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B932FA2B1F6A0A00AE6477 /* SRDataModels+UIKit.swift */; }; A7C816AB2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C816AA2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift */; }; A7C816AC2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C816AA2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift */; }; + A7D9528A2B28BD94004C79B1 /* ResourceProcessorSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D952892B28BD94004C79B1 /* ResourceProcessorSpy.swift */; }; + A7D9528C2B28C18D004C79B1 /* ResourceProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D9528B2B28C18D004C79B1 /* ResourceProcessorTests.swift */; }; A7DA18042AB0C91200F76337 /* DDUIKitRUMViewsPredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DA18022AB0C8A700F76337 /* DDUIKitRUMViewsPredicateTests.swift */; }; A7DA18052AB0C91300F76337 /* DDUIKitRUMViewsPredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DA18022AB0C8A700F76337 /* DDUIKitRUMViewsPredicateTests.swift */; }; A7DA18072AB0CA5E00F76337 /* DDUIKitRUMActionsPredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DA18062AB0CA4700F76337 /* DDUIKitRUMActionsPredicateTests.swift */; }; @@ -583,15 +561,11 @@ D20731AB29A5279D00ECBF94 /* LogsFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616F1FAF283E227100651A3A /* LogsFeature.swift */; }; D20731B529A528DA00ECBF94 /* LogEventBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC32423979B00786299 /* LogEventBuilder.swift */; }; D20731B629A528DA00ECBF94 /* LogEventBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC32423979B00786299 /* LogEventBuilder.swift */; }; - D20731C129A528EB00ECBF94 /* LogOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC82423979B00786299 /* LogOutput.swift */; }; D20731C229A528EB00ECBF94 /* LogEventEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC22423979B00786299 /* LogEventEncoder.swift */; }; D20731C329A528EB00ECBF94 /* LogEventMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22C1F5B271484B400922024 /* LogEventMapper.swift */; }; - D20731C429A528EC00ECBF94 /* LogFileOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC72423979B00786299 /* LogFileOutput.swift */; }; D20731C529A528EC00ECBF94 /* LogEventSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC42423979B00786299 /* LogEventSanitizer.swift */; }; - D20731C629A528ED00ECBF94 /* LogOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC82423979B00786299 /* LogOutput.swift */; }; D20731C729A528ED00ECBF94 /* LogEventEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC22423979B00786299 /* LogEventEncoder.swift */; }; D20731C829A528ED00ECBF94 /* LogEventMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22C1F5B271484B400922024 /* LogEventMapper.swift */; }; - D20731C929A528ED00ECBF94 /* LogFileOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC72423979B00786299 /* LogFileOutput.swift */; }; D20731CA29A528ED00ECBF94 /* LogEventSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BC42423979B00786299 /* LogEventSanitizer.swift */; }; D20731CB29A52E6000ECBF94 /* Sampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613C6B8F2768FDDE00870CBF /* Sampler.swift */; }; D20731CC29A52E6000ECBF94 /* Sampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613C6B8F2768FDDE00870CBF /* Sampler.swift */; }; @@ -650,6 +624,10 @@ D2160CF529C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C49B52889416300802B2D /* UploadPerformancePreset.swift */; }; D2160CF729C0EE2B00FAA9A5 /* UploadMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2160CF629C0EE2B00FAA9A5 /* UploadMocks.swift */; }; D2160CF829C0EE2B00FAA9A5 /* UploadMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2160CF629C0EE2B00FAA9A5 /* UploadMocks.swift */; }; + D2181A8E2B051B7900A518C0 /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */; }; + D2181A8F2B051B7900A518C0 /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */; }; + D21831552B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21831542B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift */; }; + D21831562B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21831542B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift */; }; D21AE6BC29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */; }; D21AE6BD29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */; }; D21C26C528A3B49C005DD405 /* FeatureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26C428A3B49C005DD405 /* FeatureStorage.swift */; }; @@ -773,7 +751,7 @@ D23F8E6929DDCD28001CFAE8 /* RUMContextAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CBC26D294395A300134409 /* RUMContextAttributes.swift */; }; D23F8E6B29DDCD28001CFAE8 /* RUMMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E5333524B84B43003D6C4E /* RUMMonitor.swift */; }; D23F8E6C29DDCD28001CFAE8 /* RUMContextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6156CB8D24DDA1B5008CB2B2 /* RUMContextProvider.swift */; }; - D23F8E6D29DDCD28001CFAE8 /* RUMViewIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FF9A4425AC5DEA001058CC /* RUMViewIdentity.swift */; }; + D23F8E6D29DDCD28001CFAE8 /* ViewIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FF9A4425AC5DEA001058CC /* ViewIdentifier.swift */; }; D23F8E6E29DDCD28001CFAE8 /* RUMViewsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EFF3D22731822A00D09F33 /* RUMViewsHandler.swift */; }; D23F8E6F29DDCD28001CFAE8 /* RequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D25FF2ED29CC73240063802D /* RequestBuilder.swift */; }; D23F8E7029DDCD28001CFAE8 /* URLSessionRUMResourcesHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BCB11E29D30AF000737A9A /* URLSessionRUMResourcesHandler.swift */; }; @@ -827,7 +805,7 @@ D23F8EB629DDCD38001CFAE8 /* RUMViewsHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29889C72734136200A4D1A9 /* RUMViewsHandlerTests.swift */; }; D23F8EB829DDCD38001CFAE8 /* UIKitRUMUserActionsHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615C3195251DD5080018781C /* UIKitRUMUserActionsHandlerTests.swift */; }; D23F8EB929DDCD38001CFAE8 /* RUMFeatureMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E5333024B75DFC003D6C4E /* RUMFeatureMocks.swift */; }; - D23F8EBA29DDCD38001CFAE8 /* RUMViewIdentityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C1510C25AC8C1B00362D4B /* RUMViewIdentityTests.swift */; }; + D23F8EBA29DDCD38001CFAE8 /* ViewIdentifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C1510C25AC8C1B00362D4B /* ViewIdentifierTests.swift */; }; D23F8EBE29DDCD38001CFAE8 /* WebViewEventReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E53889B2773C4B300A7DC42 /* WebViewEventReceiverTests.swift */; }; D23F8EBF29DDCD38001CFAE8 /* URLSessionRUMResourcesHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BCB12129D34A5F00737A9A /* URLSessionRUMResourcesHandlerTests.swift */; }; D23F8EC029DDCD38001CFAE8 /* RUMEventSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61122EED25B1D75B00F9C7F5 /* RUMEventSanitizerTests.swift */; }; @@ -924,7 +902,6 @@ D2579592298ABCED008A1BE5 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2579591298ABCED008A1BE5 /* XCTest.framework */; }; D2579595298AC912008A1BE5 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257953E298ABA65008A1BE5 /* TestUtilities.framework */; }; D2579596298AC927008A1BE5 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257958B298ABB83008A1BE5 /* TestUtilities.framework */; }; - D2579597298AD1A8008A1BE5 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257953E298ABA65008A1BE5 /* TestUtilities.framework */; }; D2579599298AD95F008A1BE5 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257953E298ABA65008A1BE5 /* TestUtilities.framework */; }; D257959A298AD967008A1BE5 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257958B298ABB83008A1BE5 /* TestUtilities.framework */; }; D25CFA9829C4F41900E3A43D /* DatadogTrace.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2C1A55A29C4F2DF00946C31 /* DatadogTrace.framework */; }; @@ -951,9 +928,12 @@ D26C49C0288982DA00802B2D /* FeatureUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C49BE288982DA00802B2D /* FeatureUpload.swift */; }; D26F741129ACBDA100D25622 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23039A5298D513C001A1FA3 /* DatadogInternal.framework */; }; D26F741229ACBDAD00D25622 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2DA2385298D57AA00C6C7E6 /* DatadogInternal.framework */; }; + D270CDDD2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270CDDC2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift */; }; + D270CDDE2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270CDDC2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift */; }; + D270CDE02B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */; }; + D270CDE12B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */; }; D2777D9D29F6A75800FFBB40 /* TelemetryReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2777D9C29F6A75800FFBB40 /* TelemetryReceiverTests.swift */; }; D2777D9E29F6A75800FFBB40 /* TelemetryReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2777D9C29F6A75800FFBB40 /* TelemetryReceiverTests.swift */; }; - D2790C7229DEFCF400D88DA9 /* RUMDataModelMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22743E829DEC9A9001A7EF9 /* RUMDataModelMocks.swift */; }; D27D81C12A5D415200281CC2 /* CrashReporter.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 614ED36B260352DC00C8C519 /* CrashReporter.xcframework */; }; D27D81C22A5D415200281CC2 /* DatadogCrashReporting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61B7885425C180CB002675B5 /* DatadogCrashReporting.framework */; }; D27D81C32A5D415200281CC2 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23039A5298D513C001A1FA3 /* DatadogInternal.framework */; }; @@ -970,6 +950,8 @@ D28F836929C9E71D00EF8EA2 /* DDSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F836729C9E71C00EF8EA2 /* DDSpanTests.swift */; }; D28F836B29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F836A29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift */; }; D28F836C29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F836A29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift */; }; + D28FCC352B5EBAAF00CCC077 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D28FCC342B5EBAAF00CCC077 /* PrivacyInfo.xcprivacy */; }; + D28FCC362B5FCBD100CCC077 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D28FCC342B5EBAAF00CCC077 /* PrivacyInfo.xcprivacy */; }; D29294E0291D5ED100F8EFF9 /* ApplicationVersionPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29294DF291D5ECD00F8EFF9 /* ApplicationVersionPublisher.swift */; }; D29294E1291D5ED500F8EFF9 /* ApplicationVersionPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29294DF291D5ECD00F8EFF9 /* ApplicationVersionPublisher.swift */; }; D29294E3291D652C00F8EFF9 /* ApplicationVersionPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29294E2291D652900F8EFF9 /* ApplicationVersionPublisherTests.swift */; }; @@ -999,7 +981,7 @@ D29A9F5C29DD85BB005C54A4 /* RUMSessionScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C2C20624C098FC00C0321C /* RUMSessionScope.swift */; }; D29A9F5D29DD85BB005C54A4 /* RUMCommandSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616CCE12250A1868009FED46 /* RUMCommandSubscriber.swift */; }; D29A9F5E29DD85BB005C54A4 /* UIKitRUMViewsPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F3CDA62512144600C816E5 /* UIKitRUMViewsPredicate.swift */; }; - D29A9F6029DD85BB005C54A4 /* RUMViewIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FF9A4425AC5DEA001058CC /* RUMViewIdentity.swift */; }; + D29A9F6029DD85BB005C54A4 /* ViewIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FF9A4425AC5DEA001058CC /* ViewIdentifier.swift */; }; D29A9F6129DD85BB005C54A4 /* CrashReportReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236BE2729520FED00676E67 /* CrashReportReceiver.swift */; }; D29A9F6229DD85BB005C54A4 /* WebViewEventReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CBC26A294383F200134409 /* WebViewEventReceiver.swift */; }; D29A9F6329DD85BB005C54A4 /* RUMMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E5333524B84B43003D6C4E /* RUMMonitor.swift */; }; @@ -1060,7 +1042,7 @@ D29A9FAE29DDB483005C54A4 /* SessionReplayDependencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615950EA291C029700470E0C /* SessionReplayDependencyTests.swift */; }; D29A9FB029DDB483005C54A4 /* RUMOperatingSystemInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616C0AA028573F6300C13264 /* RUMOperatingSystemInfoTests.swift */; }; D29A9FB329DDB483005C54A4 /* RUMScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618DCFDE24C75FD300589570 /* RUMScopeTests.swift */; }; - D29A9FB729DDB483005C54A4 /* RUMViewIdentityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C1510C25AC8C1B00362D4B /* RUMViewIdentityTests.swift */; }; + D29A9FB729DDB483005C54A4 /* ViewIdentifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C1510C25AC8C1B00362D4B /* ViewIdentifierTests.swift */; }; D29A9FB829DDB483005C54A4 /* RUMViewScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6198D27024C6E3B700493501 /* RUMViewScopeTests.swift */; }; D29A9FB929DDB483005C54A4 /* RUMEventsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613E81F625A743600084B751 /* RUMEventsMapperTests.swift */; }; D29A9FBB29DDB483005C54A4 /* ErrorMessageReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26ED28AFB65B005DD405 /* ErrorMessageReceiverTests.swift */; }; @@ -1111,7 +1093,6 @@ D2A783DA29A530EF003B03BB /* SwiftExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */; }; D2A783E729A53468003B03BB /* LogEventBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C3B2423990D00786299 /* LogEventBuilderTests.swift */; }; D2A783E829A53468003B03BB /* ConsoleLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6194D51B287ECDC00091547D /* ConsoleLoggerTests.swift */; }; - D2A783E929A53468003B03BB /* LogFileOutputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C402423990D00786299 /* LogFileOutputTests.swift */; }; D2A783EA29A53468003B03BB /* LogMessageReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26EA28AFA11E005DD405 /* LogMessageReceiverTests.swift */; }; D2A783EB29A53468003B03BB /* LogSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C3C2423990D00786299 /* LogSanitizerTests.swift */; }; D2A783ED29A534F2003B03BB /* LoggingFeatureMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FB222C244A21ED00902D19 /* LoggingFeatureMocks.swift */; }; @@ -1120,7 +1101,6 @@ D2A783F529A534F9003B03BB /* LogEventBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C3B2423990D00786299 /* LogEventBuilderTests.swift */; }; D2A783F629A534F9003B03BB /* LoggingFeatureMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FB222C244A21ED00902D19 /* LoggingFeatureMocks.swift */; }; D2A783F729A534F9003B03BB /* LogMessageReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26EA28AFA11E005DD405 /* LogMessageReceiverTests.swift */; }; - D2A783F829A534F9003B03BB /* LogFileOutputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C402423990D00786299 /* LogFileOutputTests.swift */; }; D2A783FB29A534F9003B03BB /* DatadogLogs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D207317C29A5226A00ECBF94 /* DatadogLogs.framework */; }; D2A7840329A536AD003B03BB /* PrintFunctionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A7840229A536AD003B03BB /* PrintFunctionMock.swift */; }; D2A7840429A536AD003B03BB /* PrintFunctionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A7840229A536AD003B03BB /* PrintFunctionMock.swift */; }; @@ -1142,6 +1122,18 @@ D2B3F04E282A85FD00C2B5EE /* DatadogCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F04C282A85FD00C2B5EE /* DatadogCore.swift */; }; D2B3F052282E827700C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; D2B3F053282E827B00C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; + D2BEEDAC2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */; }; + D2BEEDAD2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */; }; + D2BEEDAF2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */; }; + D2BEEDB02B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */; }; + D2BEEDB22B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB12B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift */; }; + D2BEEDB32B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB12B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift */; }; + D2BEEDB52B3360820065F3AC /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB42B33607D0065F3AC /* URLSessionSwizzler.swift */; }; + D2BEEDB62B3360830065F3AC /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB42B33607D0065F3AC /* URLSessionSwizzler.swift */; }; + D2BEEDB82B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */; }; + D2BEEDB92B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */; }; + D2BEEDBA2B33638F0065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2181A8A2B0500BB00A518C0 /* NetworkInstrumentationSwizzler.swift */; }; + D2BEEDBB2B3363900065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2181A8A2B0500BB00A518C0 /* NetworkInstrumentationSwizzler.swift */; }; D2C1A4FA29C4C4CB00946C31 /* SpanSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61122ECD25B1B74500F9C7F5 /* SpanSanitizer.swift */; }; D2C1A4FB29C4C4CB00946C31 /* MessageReceivers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2546C0A29AF56270054E00B /* MessageReceivers.swift */; }; D2C1A4FC29C4C4CB00946C31 /* RequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2546C0729AF55E90054E00B /* RequestBuilder.swift */; }; @@ -1168,7 +1160,6 @@ D2C1A51629C4C53F00946C31 /* OTConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E909EB24A24DD3005EA2DE /* OTConstants.swift */; }; D2C1A51729C4C53F00946C31 /* OTFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E909E724A24DD3005EA2DE /* OTFormat.swift */; }; D2C1A51829C4C53F00946C31 /* OTSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E909E624A24DD3005EA2DE /* OTSpan.swift */; }; - D2C1A51A29C4C5DD00946C31 /* JSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C1A51929C4C5DD00946C31 /* JSONEncoder.swift */; }; D2C1A51B29C4C75700946C31 /* DDSpanContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1A620249A45E400075390 /* DDSpanContextTests.swift */; }; D2C1A51C29C4C75700946C31 /* ContextMessageReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E8D59728C7AB90007E5DE1 /* ContextMessageReceiverTests.swift */; }; D2C1A51D29C4C75700946C31 /* SpanEventBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45BD12450F65B00F2C652 /* SpanEventBuilderTests.swift */; }; @@ -1185,7 +1176,6 @@ D2C1A53929C4F2DF00946C31 /* DDNoOps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C5A87924509A0C00DA608C /* DDNoOps.swift */; }; D2C1A53A29C4F2DF00946C31 /* RequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2546C0729AF55E90054E00B /* RequestBuilder.swift */; }; D2C1A53B29C4F2DF00946C31 /* SpanTagsReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614872762485067300E3EBDB /* SpanTagsReducer.swift */; }; - D2C1A53C29C4F2DF00946C31 /* JSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C1A51929C4C5DD00946C31 /* JSONEncoder.swift */; }; D2C1A53D29C4F2DF00946C31 /* OTSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E909E624A24DD3005EA2DE /* OTSpan.swift */; }; D2C1A53E29C4F2DF00946C31 /* OTSpanContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E909EC24A24DD3005EA2DE /* OTSpanContext.swift */; }; D2C1A53F29C4F2DF00946C31 /* OTReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E909EA24A24DD3005EA2DE /* OTReference.swift */; }; @@ -1486,8 +1476,6 @@ D2FB1258292E0F10005B13F8 /* TrackingConsentPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB1256292E0F0B005B13F8 /* TrackingConsentPublisherTests.swift */; }; D2FB125D292FBB56005B13F8 /* Datadog+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB125C292FBB56005B13F8 /* Datadog+Internal.swift */; }; D2FB125E292FBB56005B13F8 /* Datadog+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB125C292FBB56005B13F8 /* Datadog+Internal.swift */; }; - E132727B24B333C700952F8B /* TracingBenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E132727A24B333C700952F8B /* TracingBenchmarkTests.swift */; }; - E132727D24B35B5F00952F8B /* TracingStorageBenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E132727C24B35B5F00952F8B /* TracingStorageBenchmarkTests.swift */; }; E143CCAF27D236F600F4018A /* CITestIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E143CCAE27D236F600F4018A /* CITestIntegrationTests.swift */; }; E1C853142AA9B9A300C74BCF /* TelemetryMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C853132AA9B9A300C74BCF /* TelemetryMocks.swift */; }; E1C853152AA9B9A300C74BCF /* TelemetryMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C853132AA9B9A300C74BCF /* TelemetryMocks.swift */; }; @@ -1586,13 +1574,6 @@ remoteGlobalIDString = 61441C0124616DE9003D8BB8; remoteInfo = Example; }; - 61441C7424619FED003D8BB8 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 61133B79242393DE00786299 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 61441C0124616DE9003D8BB8; - remoteInfo = Example; - }; 6158155A2AB4534F002C60D7 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 61133B79242393DE00786299 /* Project object */; @@ -1929,14 +1910,7 @@ 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsSanitizerMock.swift; sourceTree = ""; }; 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NOPOTelSpan.swift; sourceTree = ""; }; 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NOPOTelSpanBuilder.swift; sourceTree = ""; }; - 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzler.swift; sourceTree = ""; }; - 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzlerTests.swift; sourceTree = ""; }; - 3CBDE66D2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzler.swift; sourceTree = ""; }; - 3CBDE6702AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzler.swift; sourceTree = ""; }; 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionInstrumentation.swift; sourceTree = ""; }; - 3CBDE6802AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzlerTests.swift; sourceTree = ""; }; - 3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzlerTests.swift; sourceTree = ""; }; - 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionTaskDelegate+Tracking.swift"; sourceTree = ""; }; 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionTask+Tracking.swift"; sourceTree = ""; }; 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelAttributeValue+Datadog.swift"; sourceTree = ""; }; 3CC6AD1A2B4F07E700015B18 /* OTelAttributeValue+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelAttributeValue+DatadogTests.swift"; sourceTree = ""; }; @@ -1945,7 +1919,6 @@ 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogWebViewTracking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3CE11A0529F7BE0300202522 /* DatadogWebViewTrackingTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogWebViewTrackingTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 3CF673352B4807490016CE17 /* OTelSpanTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanTests.swift; sourceTree = ""; }; - 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaTypeExtensionsTests.swift; sourceTree = ""; }; 49274903288048AA00ECD49B /* InternalProxyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalProxyTests.swift; sourceTree = ""; }; 49274908288048F400ECD49B /* RUMInternalProxyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMInternalProxyTests.swift; sourceTree = ""; }; 49D8C0B62AC5D2160075E427 /* RUM+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUM+Internal.swift"; sourceTree = ""; }; @@ -2003,7 +1976,7 @@ 61054E452A6EE10A00AAA894 /* SegmentJSONBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentJSONBuilder.swift; sourceTree = ""; }; 61054E472A6EE10A00AAA894 /* MultipartFormData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormData.swift; sourceTree = ""; }; 61054E4A2A6EE10A00AAA894 /* TextObfuscator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextObfuscator.swift; sourceTree = ""; }; - 61054E4B2A6EE10A00AAA894 /* Processor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Processor.swift; sourceTree = ""; }; + 61054E4B2A6EE10A00AAA894 /* SnapshotProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotProcessor.swift; sourceTree = ""; }; 61054E4D2A6EE10A00AAA894 /* Diff+SRWireframes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Diff+SRWireframes.swift"; sourceTree = ""; }; 61054E4E2A6EE10A00AAA894 /* Diff.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Diff.swift; sourceTree = ""; }; 61054E502A6EE10A00AAA894 /* RecordsBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordsBuilder.swift; sourceTree = ""; }; @@ -2034,7 +2007,7 @@ 61054F522A6EE1BA00AAA894 /* Diff+SRWireframesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Diff+SRWireframesTests.swift"; sourceTree = ""; }; 61054F532A6EE1BA00AAA894 /* DiffTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffTests.swift; sourceTree = ""; }; 61054F552A6EE1BA00AAA894 /* RecordsBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordsBuilderTests.swift; sourceTree = ""; }; - 61054F562A6EE1BA00AAA894 /* ProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessorTests.swift; sourceTree = ""; }; + 61054F562A6EE1BA00AAA894 /* SnapshotProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotProcessorTests.swift; sourceTree = ""; }; 61054F582A6EE1BA00AAA894 /* NodesFlattenerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodesFlattenerTests.swift; sourceTree = ""; }; 61054F5A2A6EE1BA00AAA894 /* RecordingCoordinatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordingCoordinatorTests.swift; sourceTree = ""; }; 61054F5D2A6EE1BA00AAA894 /* UIKitExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitExtensionsTests.swift; sourceTree = ""; }; @@ -2067,7 +2040,7 @@ 61054F7E2A6EE1BA00AAA894 /* UIKitMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitMocks.swift; sourceTree = ""; }; 61054F7F2A6EE1BA00AAA894 /* CoreGraphicsMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreGraphicsMocks.swift; sourceTree = ""; }; 61054F802A6EE1BA00AAA894 /* SRDataModelsMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SRDataModelsMocks.swift; sourceTree = ""; }; - 61054F812A6EE1BA00AAA894 /* ProcessorSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessorSpy.swift; sourceTree = ""; }; + 61054F812A6EE1BA00AAA894 /* SnapshotProcessorSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotProcessorSpy.swift; sourceTree = ""; }; 61054F822A6EE1BA00AAA894 /* RecorderMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecorderMocks.swift; sourceTree = ""; }; 61054F832A6EE1BA00AAA894 /* TestScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestScheduler.swift; sourceTree = ""; }; 61054F842A6EE1BA00AAA894 /* QueueMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueueMocks.swift; sourceTree = ""; }; @@ -2108,8 +2081,6 @@ 61133BC22423979B00786299 /* LogEventEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogEventEncoder.swift; sourceTree = ""; }; 61133BC32423979B00786299 /* LogEventBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogEventBuilder.swift; sourceTree = ""; }; 61133BC42423979B00786299 /* LogEventSanitizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogEventSanitizer.swift; sourceTree = ""; }; - 61133BC72423979B00786299 /* LogFileOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogFileOutput.swift; sourceTree = ""; }; - 61133BC82423979B00786299 /* LogOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogOutput.swift; sourceTree = ""; }; 61133BF0242397DA00786299 /* DatadogObjc.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogObjc.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 61133BF2242397DA00786299 /* DatadogObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DatadogObjc.h; sourceTree = ""; }; 61133BF3242397DA00786299 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -2136,7 +2107,6 @@ 61133C382423990D00786299 /* LoggerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerTests.swift; sourceTree = ""; }; 61133C3B2423990D00786299 /* LogEventBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogEventBuilderTests.swift; sourceTree = ""; }; 61133C3C2423990D00786299 /* LogSanitizerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogSanitizerTests.swift; sourceTree = ""; }; - 61133C402423990D00786299 /* LogFileOutputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogFileOutputTests.swift; sourceTree = ""; }; 61133C412423990D00786299 /* DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatadogTests.swift; sourceTree = ""; }; 61133C432423990D00786299 /* LogMatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogMatcher.swift; sourceTree = ""; }; 61133C462423990D00786299 /* TestsDirectory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestsDirectory.swift; sourceTree = ""; }; @@ -2174,9 +2144,6 @@ 61378BB22555337900F28837 /* DatadogSDKTesting.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DatadogSDKTesting.local.xcconfig; sourceTree = ""; }; 6139CD702589FAFD007E8BB7 /* Retrying.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Retrying.swift; sourceTree = ""; }; 6139CD762589FEE3007E8BB7 /* RetryingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryingTests.swift; sourceTree = ""; }; - 613BE0422563FB9E0015216C /* RUMBenchmarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMBenchmarkTests.swift; sourceTree = ""; }; - 613BE04925640FF80015216C /* BenchmarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BenchmarkTests.swift; sourceTree = ""; }; - 613BE06125642F790015216C /* RUMStorageBenchmarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMStorageBenchmarkTests.swift; sourceTree = ""; }; 613C6B8F2768FDDE00870CBF /* Sampler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sampler.swift; sourceTree = ""; }; 613C6B912768FF3100870CBF /* SamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SamplerTests.swift; sourceTree = ""; }; 613E792E2577B0F900DFCC17 /* Reader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reader.swift; sourceTree = ""; }; @@ -2194,10 +2161,6 @@ 61441C0B24616DE9003D8BB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/Main iOS.storyboard"; sourceTree = ""; }; 61441C0D24616DEC003D8BB8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61441C1224616DEC003D8BB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 61441C6824619FE4003D8BB8 /* DatadogBenchmarkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DatadogBenchmarkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 61441C6C24619FE4003D8BB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 61441C782461A204003D8BB8 /* LoggingBenchmarkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingBenchmarkTests.swift; sourceTree = ""; }; - 61441C792461A204003D8BB8 /* LoggingStorageBenchmarkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingStorageBenchmarkTests.swift; sourceTree = ""; }; 61441C902461A648003D8BB8 /* ConsoleOutputInterceptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsoleOutputInterceptor.swift; sourceTree = ""; }; 61441C912461A648003D8BB8 /* UIButton+Disabling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIButton+Disabling.swift"; sourceTree = ""; }; 61441C932461A649003D8BB8 /* DebugTracingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugTracingViewController.swift; sourceTree = ""; }; @@ -2217,8 +2180,6 @@ 614CADD62510BAC000B93D2D /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; 614E9EB2244719FA007EE3E1 /* BundleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleType.swift; sourceTree = ""; }; 614ED36B260352DC00C8C519 /* CrashReporter.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = CrashReporter.xcframework; path = ../Carthage/Build/CrashReporter.xcframework; sourceTree = ""; }; - 6152C83F24BE1CC8006A1679 /* DataUploaderBenchmarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataUploaderBenchmarkTests.swift; sourceTree = ""; }; - 6152C84124BE1F47006A1679 /* DatadogBenchmarkTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DatadogBenchmarkTests.xcconfig; sourceTree = ""; }; 6152C84224BE2165006A1679 /* MockServerAddress.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = MockServerAddress.local.xcconfig; sourceTree = ""; }; 615519252461BCE7002A85CF /* Datadog.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.xcconfig; sourceTree = ""; }; 615519262461BCE7002A85CF /* Datadog.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.local.xcconfig; sourceTree = ""; }; @@ -2262,6 +2223,7 @@ 61776CEC273BEA5500F93802 /* DebugRUMSessionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugRUMSessionViewController.swift; sourceTree = ""; }; 61776D4D273E6D9F00F93802 /* SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUI.swift; sourceTree = ""; }; 61786F7624FCDE04009E6BAB /* RUMDebuggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMDebuggingTests.swift; sourceTree = ""; }; + 6179DB552B6022EA00E9E04E /* SendingCrashReportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendingCrashReportTests.swift; sourceTree = ""; }; 6179FFD1254ADB1100556A0B /* ObjcAppLaunchHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjcAppLaunchHandler.h; sourceTree = ""; }; 6179FFD2254ADB1100556A0B /* ObjcAppLaunchHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjcAppLaunchHandler.m; sourceTree = ""; }; 617B953C24BF4D8F00E6F443 /* RUMMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMonitorTests.swift; sourceTree = ""; }; @@ -2280,6 +2242,7 @@ 6187A53826FCBE240015D94A /* TracerE2ETests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracerE2ETests.swift; sourceTree = ""; }; 6188697B2A4376F700E8996B /* RUMConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMConfigurationTests.swift; sourceTree = ""; }; 6188900E2AC58B8C00D0B966 /* TelemetryReceiverMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryReceiverMock.swift; sourceTree = ""; }; + 618C0FBF2B482F6800266B38 /* SpanWriteContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanWriteContextTests.swift; sourceTree = ""; }; 618C365E248E85B400520CDE /* DateFormattingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormattingTests.swift; sourceTree = ""; }; 618D9DE6263AD78900A3FAD2 /* SpanEventMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanEventMapper.swift; sourceTree = ""; }; 618DCFD624C7265300589570 /* RUMUUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMUUID.swift; sourceTree = ""; }; @@ -2341,7 +2304,7 @@ 61BAD46926415FCE001886CA /* OTSpanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTSpanTests.swift; sourceTree = ""; }; 61BB2B1A244A185D009F3F56 /* PerformancePreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformancePreset.swift; sourceTree = ""; }; 61BBD19624ED50040023E65F /* DatadogConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogConfigurationTests.swift; sourceTree = ""; }; - 61C1510C25AC8C1B00362D4B /* RUMViewIdentityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewIdentityTests.swift; sourceTree = ""; }; + 61C1510C25AC8C1B00362D4B /* ViewIdentifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewIdentifierTests.swift; sourceTree = ""; }; 61C2C20624C098FC00C0321C /* RUMSessionScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMSessionScope.swift; sourceTree = ""; }; 61C2C20824C0C75500C0321C /* RUMSessionScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMSessionScopeTests.swift; sourceTree = ""; }; 61C2C21124C5951400C0321C /* RUMViewScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewScope.swift; sourceTree = ""; }; @@ -2378,6 +2341,7 @@ 61C713C92A3DC22700FA735A /* RUMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMTests.swift; sourceTree = ""; }; 61C713CF2A3DEFF900FA735A /* FeatureRegistrationCoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureRegistrationCoreMock.swift; sourceTree = ""; }; 61C713D22A3DFB4900FA735A /* FuzzyHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyHelpers.swift; sourceTree = ""; }; + 61CE58592B48174D00479510 /* SpanWriteContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanWriteContext.swift; sourceTree = ""; }; 61D03BDF273404E700367DE0 /* RUMDataModels+objcTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUMDataModels+objcTests.swift"; sourceTree = ""; }; 61D3E0C8277B23F0008BE766 /* KronosInternetAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KronosInternetAddress.swift; sourceTree = ""; }; 61D3E0C9277B23F0008BE766 /* KronosDNSResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KronosDNSResolver.swift; sourceTree = ""; }; @@ -2392,7 +2356,6 @@ 61D3E0DF277B3D92008BE766 /* KronosNTPPacketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KronosNTPPacketTests.swift; sourceTree = ""; }; 61D3E0E2277B3D92008BE766 /* KronosTimeStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KronosTimeStorageTests.swift; sourceTree = ""; }; 61D3E0E9277E0C58008BE766 /* KronosE2ETests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KronosE2ETests.swift; sourceTree = ""; }; - 61D6FF7D24E53D3B00D0E375 /* BenchmarkMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BenchmarkMocks.swift; sourceTree = ""; }; 61DA20EF26C40121004AFE6D /* DataUploadStatusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataUploadStatusTests.swift; sourceTree = ""; }; 61DA8CA828609C5B0074A606 /* Directories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Directories.swift; sourceTree = ""; }; 61DA8CAB2861C3720074A606 /* DirectoriesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoriesTests.swift; sourceTree = ""; }; @@ -2410,6 +2373,7 @@ 61E5333024B75DFC003D6C4E /* RUMFeatureMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMFeatureMocks.swift; sourceTree = ""; }; 61E5333524B84B43003D6C4E /* RUMMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMonitor.swift; sourceTree = ""; }; 61E5333724B84EE2003D6C4E /* DebugRUMViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugRUMViewController.swift; sourceTree = ""; }; + 61E8C5072B28898800E709B4 /* StartingRUMSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartingRUMSessionTests.swift; sourceTree = ""; }; 61E909E624A24DD3005EA2DE /* OTSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTSpan.swift; sourceTree = ""; }; 61E909E724A24DD3005EA2DE /* OTFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTFormat.swift; sourceTree = ""; }; 61E909E924A24DD3005EA2DE /* OTTracer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTTracer.swift; sourceTree = ""; }; @@ -2445,7 +2409,7 @@ 61FF282724B8A31E000B3D9B /* RUMEventMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMEventMatcher.swift; sourceTree = ""; }; 61FF282F24BC5E2D000B3D9B /* RUMEventFileOutputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMEventFileOutputTests.swift; sourceTree = ""; }; 61FF416125EE5FF400CE35EC /* CrashLogReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashLogReceiverTests.swift; sourceTree = ""; }; - 61FF9A4425AC5DEA001058CC /* RUMViewIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewIdentity.swift; sourceTree = ""; }; + 61FF9A4425AC5DEA001058CC /* ViewIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewIdentifier.swift; sourceTree = ""; }; 9E0542CA25F8EBBE007A3D0B /* Kronos.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Kronos.xcframework; path = ../Carthage/Build/Kronos.xcframework; sourceTree = ""; }; 9E26E6B824C87693000B3270 /* RUMDataModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMDataModels.swift; sourceTree = ""; }; 9E2EF44E2694FA14008A7DAE /* VitalInfoSamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalInfoSamplerTests.swift; sourceTree = ""; }; @@ -2471,10 +2435,6 @@ A70A82642A935F210072F5DC /* BackgroundTaskCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTaskCoordinator.swift; sourceTree = ""; }; A71013D52B178FAD00101E60 /* ResourcesWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcesWriterTests.swift; sourceTree = ""; }; A71265852B17980C007D63CE /* MockFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFeature.swift; sourceTree = ""; }; - A712658B2B179C93007D63CE /* EnrichedResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnrichedResource.swift; path = ../../Models/EnrichedResource.swift; sourceTree = ""; }; - A712658C2B179C93007D63CE /* SRDataModels+UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SRDataModels+UIKit.swift"; path = "../../Models/SRDataModels+UIKit.swift"; sourceTree = ""; }; - A712658D2B179C93007D63CE /* SRDataModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SRDataModels.swift; path = ../../Models/SRDataModels.swift; sourceTree = ""; }; - A712658E2B179C93007D63CE /* EnrichedRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnrichedRecord.swift; path = ../../Models/EnrichedRecord.swift; sourceTree = ""; }; A728AD9C2934CE4400397996 /* W3CHTTPHeaders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = W3CHTTPHeaders.swift; sourceTree = ""; }; A728AD9E2934CE5000397996 /* W3CHTTPHeadersWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = W3CHTTPHeadersWriter.swift; sourceTree = ""; }; A728ADA02934CE5D00397996 /* W3CHTTPHeadersReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = W3CHTTPHeadersReader.swift; sourceTree = ""; }; @@ -2491,7 +2451,14 @@ A79B0F5E292BA435008742B3 /* B3HTTPHeadersWriter+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "B3HTTPHeadersWriter+objc.swift"; sourceTree = ""; }; A79B0F60292BB071008742B3 /* B3HTTPHeadersReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = B3HTTPHeadersReaderTests.swift; sourceTree = ""; }; A79B0F63292BD074008742B3 /* DDB3HTTPHeadersWriter+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDB3HTTPHeadersWriter+apiTests.m"; sourceTree = ""; }; + A7B932F42B1F694000AE6477 /* ResourcesProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcesProcessor.swift; sourceTree = ""; }; + A7B932F72B1F6A0A00AE6477 /* EnrichedRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnrichedRecord.swift; sourceTree = ""; }; + A7B932F82B1F6A0A00AE6477 /* SRDataModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SRDataModels.swift; sourceTree = ""; }; + A7B932F92B1F6A0A00AE6477 /* EnrichedResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnrichedResource.swift; sourceTree = ""; }; + A7B932FA2B1F6A0A00AE6477 /* SRDataModels+UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SRDataModels+UIKit.swift"; sourceTree = ""; }; A7C816AA2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTaskCoordinatorTests.swift; sourceTree = ""; }; + A7D952892B28BD94004C79B1 /* ResourceProcessorSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceProcessorSpy.swift; sourceTree = ""; }; + A7D9528B2B28C18D004C79B1 /* ResourceProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceProcessorTests.swift; sourceTree = ""; }; A7DA18022AB0C8A700F76337 /* DDUIKitRUMViewsPredicateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDUIKitRUMViewsPredicateTests.swift; sourceTree = ""; }; A7DA18062AB0CA4700F76337 /* DDUIKitRUMActionsPredicateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDUIKitRUMActionsPredicateTests.swift; sourceTree = ""; }; A7EA88552B17639A00FE2580 /* ResourcesWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcesWriter.swift; sourceTree = ""; }; @@ -2536,6 +2503,9 @@ D2160CEC29C0E0E600FAA9A5 /* DatadogURLSessionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatadogURLSessionHandler.swift; sourceTree = ""; }; D2160CEF29C0EC4D00FAA9A5 /* SingleFeatureCoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFeatureCoreMock.swift; sourceTree = ""; }; D2160CF629C0EE2B00FAA9A5 /* UploadMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadMocks.swift; sourceTree = ""; }; + D2181A8A2B0500BB00A518C0 /* NetworkInstrumentationSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInstrumentationSwizzler.swift; sourceTree = ""; }; + D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzlerTests.swift; sourceTree = ""; }; + D21831542B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInstrumentationIntegrationTests.swift; sourceTree = ""; }; D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryTests.swift; sourceTree = ""; }; D21C26C428A3B49C005DD405 /* FeatureStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureStorage.swift; sourceTree = ""; }; D21C26D028A64599005DD405 /* MessageBusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBusTests.swift; sourceTree = ""; }; @@ -2652,10 +2622,13 @@ D26C49AE2886DC7B00802B2D /* ApplicationStatePublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationStatePublisherTests.swift; sourceTree = ""; }; D26C49B52889416300802B2D /* UploadPerformancePreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPerformancePreset.swift; sourceTree = ""; }; D26C49BE288982DA00802B2D /* FeatureUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureUpload.swift; sourceTree = ""; }; + D270CDDC2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzler.swift; sourceTree = ""; }; + D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzlerTests.swift; sourceTree = ""; }; D2777D9C29F6A75800FFBB40 /* TelemetryReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryReceiverTests.swift; sourceTree = ""; }; D286626D2A43487500852CE3 /* Datadog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Datadog.swift; sourceTree = ""; }; D28F836729C9E71C00EF8EA2 /* DDSpanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDSpanTests.swift; sourceTree = ""; }; D28F836A29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingURLSessionHandlerTests.swift; sourceTree = ""; }; + D28FCC342B5EBAAF00CCC077 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ../Resources/PrivacyInfo.xcprivacy; sourceTree = ""; }; D29294DF291D5ECD00F8EFF9 /* ApplicationVersionPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationVersionPublisher.swift; sourceTree = ""; }; D29294E2291D652900F8EFF9 /* ApplicationVersionPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationVersionPublisherTests.swift; sourceTree = ""; }; D293302B2A137DAD0029C9EA /* CrashReportingFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportingFeature.swift; sourceTree = ""; }; @@ -2695,7 +2668,11 @@ D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDHTTPHeadersWriter+apiTests.m"; sourceTree = ""; }; D2BCB11E29D30AF000737A9A /* URLSessionRUMResourcesHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionRUMResourcesHandler.swift; sourceTree = ""; }; D2BCB12129D34A5F00737A9A /* URLSessionRUMResourcesHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionRUMResourcesHandlerTests.swift; sourceTree = ""; }; - D2C1A51929C4C5DD00946C31 /* JSONEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONEncoder.swift; sourceTree = ""; }; + D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzler.swift; sourceTree = ""; }; + D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzlerTests.swift; sourceTree = ""; }; + D2BEEDB12B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzler.swift; sourceTree = ""; }; + D2BEEDB42B33607D0065F3AC /* URLSessionSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzler.swift; sourceTree = ""; }; + D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzlerTests.swift; sourceTree = ""; }; D2C1A55A29C4F2DF00946C31 /* DatadogTrace.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogTrace.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D2C1A57329C4F2E800946C31 /* DatadogTraceTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogTraceTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryStatusPublisherTests.swift; sourceTree = ""; }; @@ -2744,8 +2721,6 @@ D2FB125C292FBB56005B13F8 /* Datadog+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Datadog+Internal.swift"; sourceTree = ""; }; D2FCA238271D896E0020286F /* SwiftUIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIExtensions.swift; sourceTree = ""; }; E11625D727B681D200E428C6 /* CITestIntegration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CITestIntegration.swift; sourceTree = ""; }; - E132727A24B333C700952F8B /* TracingBenchmarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingBenchmarkTests.swift; sourceTree = ""; }; - E132727C24B35B5F00952F8B /* TracingStorageBenchmarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingStorageBenchmarkTests.swift; sourceTree = ""; }; E143CCAE27D236F600F4018A /* CITestIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CITestIntegrationTests.swift; sourceTree = ""; }; E179FB4D28F80A6400CC2698 /* PerformanceMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceMetric.swift; sourceTree = ""; }; E1B082CB25641DF9002DB9D2 /* Example.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Example.xcconfig; sourceTree = ""; }; @@ -2836,18 +2811,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 61441C6524619FE4003D8BB8 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 61A2CC322A445D8A0000FF25 /* DatadogRUM.framework in Frameworks */, - D2579597298AD1A8008A1BE5 /* TestUtilities.framework in Frameworks */, - 6152C83E24BE1C91006A1679 /* HTTPServerMock in Frameworks */, - 61441C6D24619FE4003D8BB8 /* DatadogCore.framework in Frameworks */, - 61570007246AAED100E96950 /* DatadogObjc.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 61569793256CF6C300C6AADA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -3202,7 +3165,7 @@ 61054E0C2A6EE10A00AAA894 /* SessionReplay.swift */, 61054E0B2A6EE10A00AAA894 /* SessionReplayConfiguration.swift */, 61054E542A6EE10A00AAA894 /* Utilities */, - 61054E042A6EE10A00AAA894 /* Models */, + A7B932F62B1F6A0A00AE6477 /* Models */, 61054E032A6EE10A00AAA894 /* Writers */, ); name = DatadogSessionReplay; @@ -3236,18 +3199,6 @@ path = Writers; sourceTree = ""; }; - 61054E042A6EE10A00AAA894 /* Models */ = { - isa = PBXGroup; - children = ( - A712658E2B179C93007D63CE /* EnrichedRecord.swift */, - A712658B2B179C93007D63CE /* EnrichedResource.swift */, - A712658D2B179C93007D63CE /* SRDataModels.swift */, - A712658C2B179C93007D63CE /* SRDataModels+UIKit.swift */, - ); - name = Models; - path = Writers/Models; - sourceTree = ""; - }; 61054E0D2A6EE10A00AAA894 /* Recorder */ = { isa = PBXGroup; children = ( @@ -3394,8 +3345,9 @@ 61054E482A6EE10A00AAA894 /* Processor */ = { isa = PBXGroup; children = ( + 61054E4B2A6EE10A00AAA894 /* SnapshotProcessor.swift */, + A7B932F42B1F694000AE6477 /* ResourcesProcessor.swift */, 61054E492A6EE10A00AAA894 /* Privacy */, - 61054E4B2A6EE10A00AAA894 /* Processor.swift */, 61054E4C2A6EE10A00AAA894 /* Diffing */, 61054E4F2A6EE10A00AAA894 /* SRDataModelsBuilder */, 61054E522A6EE10A00AAA894 /* Flattening */, @@ -3508,7 +3460,8 @@ 61054F4F2A6EE1BA00AAA894 /* Privacy */, 61054F512A6EE1BA00AAA894 /* Diffing */, 61054F542A6EE1BA00AAA894 /* SRDataModelsBuilder */, - 61054F562A6EE1BA00AAA894 /* ProcessorTests.swift */, + 61054F562A6EE1BA00AAA894 /* SnapshotProcessorTests.swift */, + A7D9528B2B28C18D004C79B1 /* ResourceProcessorTests.swift */, 61054F572A6EE1BA00AAA894 /* Flattening */, ); path = Processor; @@ -3636,7 +3589,7 @@ 61054F7E2A6EE1BA00AAA894 /* UIKitMocks.swift */, 61054F7F2A6EE1BA00AAA894 /* CoreGraphicsMocks.swift */, 61054F802A6EE1BA00AAA894 /* SRDataModelsMocks.swift */, - 61054F812A6EE1BA00AAA894 /* ProcessorSpy.swift */, + 61054F812A6EE1BA00AAA894 /* SnapshotProcessorSpy.swift */, 61054F822A6EE1BA00AAA894 /* RecorderMocks.swift */, 61054F832A6EE1BA00AAA894 /* TestScheduler.swift */, 61054F842A6EE1BA00AAA894 /* QueueMocks.swift */, @@ -3646,6 +3599,7 @@ A74A72862B10CE4100771FEB /* ResourceMocks.swift */, A74A72882B10D95D00771FEB /* MultipartBuilderSpy.swift */, A71265852B17980C007D63CE /* MockFeature.swift */, + A7D952892B28BD94004C79B1 /* ResourceProcessorSpy.swift */, ); path = Mocks; sourceTree = ""; @@ -3699,6 +3653,8 @@ 610ABD492A69309900AFEA34 /* IntegrationUnitTests */ = { isa = PBXGroup; children = ( + 6179DB542B60229D00E9E04E /* CrashReporting */, + 61E8C5062B28896100E709B4 /* RUM */, 610ABD4A2A6930AB00AFEA34 /* Public */, 618353BA2A6946F40085F84A /* Internal */, ); @@ -3711,6 +3667,7 @@ 6176991D2A8791880030022B /* Datadog+MultipleInstancesIntegrationTests.swift */, 610ABD4B2A6930CA00AFEA34 /* TelemetryCoreIntegrationTests.swift */, D20FD9D52ACC0934004D3569 /* WebLogIntegrationTests.swift */, + D21831542B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift */, ); path = Public; sourceTree = ""; @@ -3755,7 +3712,6 @@ 6170DC1425C18663003AED5C /* DatadogCrashReportingTests */, 3CE11A3B29F7BEE700202522 /* DatadogWebViewTracking */, 3CE11A3C29F7BEF300202522 /* DatadogWebViewTrackingTests */, - 61441C772461A204003D8BB8 /* DatadogBenchmarkTests */, 61133C07242397F200786299 /* TargetSupport */, 61441C0324616DE9003D8BB8 /* Example */, 6199362C265BA959009D7EA8 /* E2E */, @@ -3774,7 +3730,6 @@ 61133B8B242393DE00786299 /* DatadogCoreTests iOS.xctest */, 61133BF0242397DA00786299 /* DatadogObjc.framework */, 61441C0224616DE9003D8BB8 /* Example iOS.app */, - 61441C6824619FE4003D8BB8 /* DatadogBenchmarkTests.xctest */, 61B7885425C180CB002675B5 /* DatadogCrashReporting.framework */, 61B7885C25C180CB002675B5 /* DatadogCrashReportingTests iOS.xctest */, 6199362B265BA958009D7EA8 /* E2E.app */, @@ -3835,6 +3790,7 @@ isa = PBXGroup; children = ( 9E9EB37624468CE90002C80B /* Datadog.modulemap */, + D28FCC342B5EBAAF00CCC077 /* PrivacyInfo.xcprivacy */, D286626D2A43487500852CE3 /* Datadog.swift */, D2FB125C292FBB56005B13F8 /* Datadog+Internal.swift */, E1D5AEA624B4D45A007F194B /* Versioning.swift */, @@ -3898,15 +3854,6 @@ path = Log; sourceTree = ""; }; - 61133BC52423979B00786299 /* LogOutputs */ = { - isa = PBXGroup; - children = ( - 61133BC72423979B00786299 /* LogFileOutput.swift */, - 61133BC82423979B00786299 /* LogOutput.swift */, - ); - path = LogOutputs; - sourceTree = ""; - }; 61133BF1242397DA00786299 /* DatadogObjc */ = { isa = PBXGroup; children = ( @@ -3925,7 +3872,6 @@ 6170DC0525C184FA003AED5C /* DatadogCrashReporting */, 61133B8F242393DE00786299 /* DatadogTests */, 6170DC0625C184FA003AED5C /* DatadogCrashReportingTests */, - 61441C762461A01D003D8BB8 /* DatadogBenchmarkTests */, 61441C9E2461AF4D003D8BB8 /* Example */, 61993640265BAC34009D7EA8 /* E2E */, 61993670265BBF19009D7EA8 /* E2ETests */, @@ -4112,14 +4058,6 @@ path = Log; sourceTree = ""; }; - 61133C3D2423990D00786299 /* LogOutputs */ = { - isa = PBXGroup; - children = ( - 61133C402423990D00786299 /* LogFileOutputTests.swift */, - ); - path = LogOutputs; - sourceTree = ""; - }; 61133C422423990D00786299 /* Matchers */ = { isa = PBXGroup; children = ( @@ -4226,34 +4164,6 @@ path = Utils; sourceTree = ""; }; - 613BE07A25643C040015216C /* DataCollection */ = { - isa = PBXGroup; - children = ( - 61441C782461A204003D8BB8 /* LoggingBenchmarkTests.swift */, - E132727A24B333C700952F8B /* TracingBenchmarkTests.swift */, - 613BE0422563FB9E0015216C /* RUMBenchmarkTests.swift */, - ); - path = DataCollection; - sourceTree = ""; - }; - 613BE07B25643C080015216C /* DataUpload */ = { - isa = PBXGroup; - children = ( - 6152C83F24BE1CC8006A1679 /* DataUploaderBenchmarkTests.swift */, - ); - path = DataUpload; - sourceTree = ""; - }; - 613BE07C25643C100015216C /* DataStorage */ = { - isa = PBXGroup; - children = ( - 61441C792461A204003D8BB8 /* LoggingStorageBenchmarkTests.swift */, - E132727C24B35B5F00952F8B /* TracingStorageBenchmarkTests.swift */, - 613BE06125642F790015216C /* RUMStorageBenchmarkTests.swift */, - ); - path = DataStorage; - sourceTree = ""; - }; 613E79412577C08900DFCC17 /* Writing */ = { isa = PBXGroup; children = ( @@ -4325,7 +4235,7 @@ 6141CE652806B3F200EBB879 /* Utils */ = { isa = PBXGroup; children = ( - 61C1510C25AC8C1B00362D4B /* RUMViewIdentityTests.swift */, + 61C1510C25AC8C1B00362D4B /* ViewIdentifierTests.swift */, 61A614E9276B9D4C00A06CE7 /* RUMOffViewEventsHandlingRuleTests.swift */, ); path = Utils; @@ -4345,28 +4255,6 @@ path = Example; sourceTree = ""; }; - 61441C762461A01D003D8BB8 /* DatadogBenchmarkTests */ = { - isa = PBXGroup; - children = ( - 6152C84124BE1F47006A1679 /* DatadogBenchmarkTests.xcconfig */, - 61441C6C24619FE4003D8BB8 /* Info.plist */, - ); - path = DatadogBenchmarkTests; - sourceTree = ""; - }; - 61441C772461A204003D8BB8 /* DatadogBenchmarkTests */ = { - isa = PBXGroup; - children = ( - 613BE04925640FF80015216C /* BenchmarkTests.swift */, - 61D6FF7D24E53D3B00D0E375 /* BenchmarkMocks.swift */, - 613BE07A25643C040015216C /* DataCollection */, - 613BE07B25643C080015216C /* DataUpload */, - 613BE07C25643C100015216C /* DataStorage */, - ); - name = DatadogBenchmarkTests; - path = ../BenchmarkTests; - sourceTree = ""; - }; 61441C8F2461A648003D8BB8 /* Utils */ = { isa = PBXGroup; children = ( @@ -4407,7 +4295,7 @@ 61494B7827F3522C0082BBCC /* Utils */ = { isa = PBXGroup; children = ( - 61FF9A4425AC5DEA001058CC /* RUMViewIdentity.swift */, + 61FF9A4425AC5DEA001058CC /* ViewIdentifier.swift */, 61A614E7276B2BD000A06CE7 /* RUMOffViewEventsHandlingRule.swift */, ); path = Utils; @@ -4567,6 +4455,14 @@ path = Helpers; sourceTree = ""; }; + 6179DB542B60229D00E9E04E /* CrashReporting */ = { + isa = PBXGroup; + children = ( + 6179DB552B6022EA00E9E04E /* SendingCrashReportTests.swift */, + ); + path = CrashReporting; + sourceTree = ""; + }; 617B953B24BF4D7300E6F443 /* RUMMonitor */ = { isa = PBXGroup; children = ( @@ -4832,7 +4728,6 @@ 61C5A87C24509A0C00DA608C /* Casting.swift */, E1D202E924C065CF00D1AF3A /* ActiveSpansPool.swift */, 61C5A87D24509A0C00DA608C /* Warnings.swift */, - D2C1A51929C4C5DD00946C31 /* JSONEncoder.swift */, ); path = Utils; sourceTree = ""; @@ -4865,6 +4760,7 @@ 61C5A8A524509FAA00DA608C /* SpanEventBuilder.swift */, 61122ECD25B1B74500F9C7F5 /* SpanSanitizer.swift */, 614872762485067300E3EBDB /* SpanTagsReducer.swift */, + 61CE58592B48174D00479510 /* SpanWriteContext.swift */, ); path = Span; sourceTree = ""; @@ -4935,6 +4831,7 @@ children = ( 61E45BD12450F65B00F2C652 /* SpanEventBuilderTests.swift */, 61122EE725B1C92500F9C7F5 /* SpanSanitizerTests.swift */, + 618C0FBF2B482F6800266B38 /* SpanWriteContextTests.swift */, ); path = Span; sourceTree = ""; @@ -4981,6 +4878,14 @@ path = RUMEvent; sourceTree = ""; }; + 61E8C5062B28896100E709B4 /* RUM */ = { + isa = PBXGroup; + children = ( + 61E8C5072B28898800E709B4 /* StartingRUMSessionTests.swift */, + ); + path = RUM; + sourceTree = ""; + }; 61E909E524A24DD3005EA2DE /* OpenTracing */ = { isa = PBXGroup; children = ( @@ -5162,6 +5067,17 @@ path = W3C; sourceTree = ""; }; + A7B932F62B1F6A0A00AE6477 /* Models */ = { + isa = PBXGroup; + children = ( + A7B932F72B1F6A0A00AE6477 /* EnrichedRecord.swift */, + A7B932F82B1F6A0A00AE6477 /* SRDataModels.swift */, + A7B932F92B1F6A0A00AE6477 /* EnrichedResource.swift */, + A7B932FA2B1F6A0A00AE6477 /* SRDataModels+UIKit.swift */, + ); + path = Models; + sourceTree = ""; + }; A7F773D929253F5900AC1A62 /* Datadog */ = { isa = PBXGroup; children = ( @@ -5229,7 +5145,6 @@ 6194E4BB2878AF7600EB6307 /* ConsoleLogger.swift */, D24C9C4129A7986E002057CF /* Feature */, 61133BC12423979B00786299 /* Log */, - 61133BC52423979B00786299 /* LogOutputs */, D22C1F5A2714849700922024 /* Scrubbing */, ); name = DatadogLogs; @@ -5246,7 +5161,6 @@ D242C2A02A14D747004B4980 /* RemoteLoggerTests.swift */, D20FD9CE2AC6FF42004D3569 /* WebViewLogReceiverTests.swift */, 61133C3A2423990D00786299 /* Log */, - 61133C3D2423990D00786299 /* LogOutputs */, D2A783EC29A534DB003B03BB /* Mocks */, ); name = DatadogLogsTests; @@ -5258,14 +5172,14 @@ children = ( D295A16429F299C9007C0E9A /* URLSessionInterceptor.swift */, D2160CC129C0DED100FAA9A5 /* URLSessionTaskInterception.swift */, - D2160CC329C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift */, - 3CBDE66D2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift */, - 3CBDE6702AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift */, 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */, - 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */, + D2160CC329C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift */, 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */, - 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */, - 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */, + D2181A8A2B0500BB00A518C0 /* NetworkInstrumentationSwizzler.swift */, + D2BEEDB42B33607D0065F3AC /* URLSessionSwizzler.swift */, + D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */, + D2BEEDB12B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift */, + D270CDDC2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift */, ); path = URLSession; sourceTree = ""; @@ -5770,7 +5684,6 @@ D23039DC298D5235001A1FA3 /* DDError.swift */, 61133BBA2423979B00786299 /* SwiftExtensions.swift */, D29A9F9429DDB1DB005C54A4 /* UIKitExtensions.swift */, - 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */, ); path = Utils; sourceTree = ""; @@ -5780,7 +5693,6 @@ children = ( 613C6B912768FF3100870CBF /* SamplerTests.swift */, 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */, - 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */, ); path = Utils; sourceTree = ""; @@ -5892,10 +5804,10 @@ A79B0F60292BB071008742B3 /* B3HTTPHeadersReaderTests.swift */, A728ADA22934DB5000397996 /* W3CHTTPHeadersWriterTests.swift */, A728ADA52934DF2400397996 /* W3CHTTPHeadersReaderTests.swift */, - 3CBDE6802AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift */, - 3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */, - 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */, - 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */, + D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */, + D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */, + D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */, + D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */, ); path = NetworkInstrumentation; sourceTree = ""; @@ -6248,27 +6160,6 @@ productReference = 61441C0224616DE9003D8BB8 /* Example iOS.app */; productType = "com.apple.product-type.application"; }; - 61441C6724619FE4003D8BB8 /* DatadogBenchmarkTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 61441C7024619FE4003D8BB8 /* Build configuration list for PBXNativeTarget "DatadogBenchmarkTests" */; - buildPhases = ( - 61441C6424619FE4003D8BB8 /* Sources */, - 61441C6524619FE4003D8BB8 /* Frameworks */, - 61441C6624619FE4003D8BB8 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 61441C7524619FED003D8BB8 /* PBXTargetDependency */, - ); - name = DatadogBenchmarkTests; - packageProductDependencies = ( - 6152C83D24BE1C91006A1679 /* HTTPServerMock */, - ); - productName = DatadogBenchmarkTests; - productReference = 61441C6824619FE4003D8BB8 /* DatadogBenchmarkTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; 618F983F265BC486009959F8 /* E2EInstrumentationTests */ = { isa = PBXNativeTarget; buildConfigurationList = 618F9847265BC486009959F8 /* Build configuration list for PBXNativeTarget "E2EInstrumentationTests" */; @@ -6865,10 +6756,6 @@ CreatedOnToolsVersion = 11.4; LastSwiftMigration = 1200; }; - 61441C6724619FE4003D8BB8 = { - CreatedOnToolsVersion = 11.4; - TestTargetID = 61441C0124616DE9003D8BB8; - }; 618F983F265BC486009959F8 = { CreatedOnToolsVersion = 12.5; TestTargetID = 6199362A265BA958009D7EA8; @@ -6963,7 +6850,6 @@ D2CB6E0A27C50EAE00A62B57 /* DatadogCore tvOS */, D2CB6F9227C5217A00A62B57 /* DatadogObjc tvOS */, D2CB6ED327C520D400A62B57 /* DatadogCoreTests tvOS */, - 61441C6724619FE4003D8BB8 /* DatadogBenchmarkTests */, 61441C0124616DE9003D8BB8 /* Example iOS */, D24067F827CE6C9E00C04F44 /* Example tvOS */, 6199362A265BA958009D7EA8 /* E2E */, @@ -6998,6 +6884,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D28FCC352B5EBAAF00CCC077 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -7039,13 +6926,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 61441C6624619FE4003D8BB8 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 618F983E265BC486009959F8 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -7200,6 +7080,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D28FCC362B5FCBD100CCC077 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -7578,6 +7459,7 @@ 6176991E2A8791880030022B /* Datadog+MultipleInstancesIntegrationTests.swift in Sources */, 617B954224BF4E7600E6F443 /* RUMMonitorConfigurationTests.swift in Sources */, 61F9CABA2513A7F5000A5E61 /* RUMSessionMatcher.swift in Sources */, + 6179DB562B6022EA00E9E04E /* SendingCrashReportTests.swift in Sources */, 3C0D5DE22A543DC400446CF9 /* EventGeneratorTests.swift in Sources */, 6136CB4A2A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift in Sources */, 61C3638324361BE200C4D4E6 /* DatadogPrivateMocks.swift in Sources */, @@ -7592,7 +7474,9 @@ E143CCAF27D236F600F4018A /* CITestIntegrationTests.swift in Sources */, D224430D29E95D6700274EC7 /* CrashReportReceiverTests.swift in Sources */, D234613228B7713000055D4C /* FeatureContextTests.swift in Sources */, + D21831552B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift in Sources */, 61D3E0E4277B3D92008BE766 /* KronosNTPPacketTests.swift in Sources */, + 61E8C5082B28898800E709B4 /* StartingRUMSessionTests.swift in Sources */, 616B668E259CC28E00968EE8 /* DDRUMMonitorTests.swift in Sources */, 9EE5AD8226205B82001E699E /* DDNSURLSessionDelegateTests.swift in Sources */, 61133C4A2423990D00786299 /* DDConfigurationTests.swift in Sources */, @@ -7725,7 +7609,9 @@ files = ( 61054EA22A6EE10B00AAA894 /* Scheduler.swift in Sources */, A7EA88562B17639A00FE2580 /* ResourcesWriter.swift in Sources */, + A7B932FB2B1F6A0A00AE6477 /* EnrichedRecord.swift in Sources */, 61054E8D2A6EE10A00AAA894 /* RUMContextReceiver.swift in Sources */, + A7B932FD2B1F6A0A00AE6477 /* EnrichedResource.swift in Sources */, 61054E922A6EE10A00AAA894 /* SegmentJSONBuilder.swift in Sources */, 61054E622A6EE10A00AAA894 /* RecordWriter.swift in Sources */, 61054E692A6EE10A00AAA894 /* ImageDataProvider.swift in Sources */, @@ -7733,7 +7619,6 @@ 61054E822A6EE10A00AAA894 /* UILabelRecorder.swift in Sources */, A73A54982B16406900E1F7E3 /* ResourcesFeature.swift in Sources */, 61054E6C2A6EE10A00AAA894 /* SystemColors.swift in Sources */, - A71265902B179C94007D63CE /* SRDataModels+UIKit.swift in Sources */, 61054E812A6EE10A00AAA894 /* UIStepperRecorder.swift in Sources */, 61054E632A6EE10A00AAA894 /* SessionReplayConfiguration.swift in Sources */, 61054E702A6EE10A00AAA894 /* TouchSnapshotProducer.swift in Sources */, @@ -7757,10 +7642,8 @@ 61054E9C2A6EE10B00AAA894 /* UIImage+Scaling.swift in Sources */, 61054EA12A6EE10B00AAA894 /* MainThreadScheduler.swift in Sources */, 61054E7C2A6EE10A00AAA894 /* UINavigationBarRecorder.swift in Sources */, - A71265922B179C94007D63CE /* EnrichedRecord.swift in Sources */, 61054E772A6EE10A00AAA894 /* ViewTreeRecorder.swift in Sources */, 61054E9E2A6EE10B00AAA894 /* Queue.swift in Sources */, - A712658F2B179C94007D63CE /* EnrichedResource.swift in Sources */, 61054E872A6EE10A00AAA894 /* ViewAttributes+Copy.swift in Sources */, 61054E6A2A6EE10A00AAA894 /* UIKitExtensions.swift in Sources */, 61054E7D2A6EE10A00AAA894 /* UITextFieldRecorder.swift in Sources */, @@ -7771,21 +7654,23 @@ 61054E6E2A6EE10A00AAA894 /* RecordingCoordinator.swift in Sources */, 61054E9F2A6EE10B00AAA894 /* Errors.swift in Sources */, 61054E642A6EE10A00AAA894 /* SessionReplay.swift in Sources */, - 61054E952A6EE10A00AAA894 /* Processor.swift in Sources */, + 61054E952A6EE10A00AAA894 /* SnapshotProcessor.swift in Sources */, 61054E722A6EE10A00AAA894 /* TouchIdentifierGenerator.swift in Sources */, + A7B932F52B1F694000AE6477 /* ResourcesProcessor.swift in Sources */, 61054E912A6EE10A00AAA894 /* EnrichedRecordJSON.swift in Sources */, 61054E742A6EE10A00AAA894 /* ViewTreeSnapshotProducer.swift in Sources */, 61054E7E2A6EE10A00AAA894 /* NodeRecorder.swift in Sources */, 61054E6F2A6EE10A00AAA894 /* UIApplicationSwizzler.swift in Sources */, 61054E6D2A6EE10A00AAA894 /* CGRect+ContentFrame.swift in Sources */, 61054E942A6EE10A00AAA894 /* TextObfuscator.swift in Sources */, + A7B932FE2B1F6A0A00AE6477 /* SRDataModels+UIKit.swift in Sources */, 61054E862A6EE10A00AAA894 /* UnsupportedViewRecorder.swift in Sources */, 61054E882A6EE10A00AAA894 /* ViewTreeRecordingContext.swift in Sources */, 61054E932A6EE10A00AAA894 /* MultipartFormData.swift in Sources */, 61054E712A6EE10A00AAA894 /* TouchSnapshot.swift in Sources */, 61054E8A2A6EE10A00AAA894 /* WindowViewTreeSnapshotProducer.swift in Sources */, 61054E7A2A6EE10A00AAA894 /* UIImageViewRecorder.swift in Sources */, - A71265912B179C94007D63CE /* SRDataModels.swift in Sources */, + A7B932FC2B1F6A0A00AE6477 /* SRDataModels.swift in Sources */, 61054E752A6EE10A00AAA894 /* ViewTreeSnapshot.swift in Sources */, 61054EA02A6EE10B00AAA894 /* Colors.swift in Sources */, 61054E7F2A6EE10A00AAA894 /* UISliderRecorder.swift in Sources */, @@ -7836,7 +7721,7 @@ 61054F952A6EE1BA00AAA894 /* SessionReplayConfigurationTests.swift in Sources */, 61054FAC2A6EE1BA00AAA894 /* CGRect+ContentFrameTests.swift in Sources */, 61054FC72A6EE1BA00AAA894 /* SRDataModelsMocks.swift in Sources */, - 61054FC82A6EE1BA00AAA894 /* ProcessorSpy.swift in Sources */, + 61054FC82A6EE1BA00AAA894 /* SnapshotProcessorSpy.swift in Sources */, A74A72872B10CE4100771FEB /* ResourceMocks.swift in Sources */, 61054FA42A6EE1BA00AAA894 /* DiffTests.swift in Sources */, 61054FA02A6EE1BA00AAA894 /* SRCompressionTests.swift in Sources */, @@ -7850,8 +7735,10 @@ 61054FAF2A6EE1BA00AAA894 /* ViewTreeRecordingContextTests.swift in Sources */, 61054FC52A6EE1BA00AAA894 /* UIKitMocks.swift in Sources */, 61054FB92A6EE1BA00AAA894 /* UINavigationBarRecorderTests.swift in Sources */, - 61054FA62A6EE1BA00AAA894 /* ProcessorTests.swift in Sources */, + 61054FA62A6EE1BA00AAA894 /* SnapshotProcessorTests.swift in Sources */, 61054FB72A6EE1BA00AAA894 /* UISegmentRecorderTests.swift in Sources */, + A7D9528A2B28BD94004C79B1 /* ResourceProcessorSpy.swift in Sources */, + A7D9528C2B28C18D004C79B1 /* ResourceProcessorTests.swift in Sources */, A74A72892B10D95D00771FEB /* MultipartBuilderSpy.swift in Sources */, 61054FCF2A6EE1BA00AAA894 /* RUMContextReceiverTests.swift in Sources */, 61054FC92A6EE1BA00AAA894 /* RecorderMocks.swift in Sources */, @@ -7898,23 +7785,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 61441C6424619FE4003D8BB8 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 61441C7A2461A204003D8BB8 /* LoggingBenchmarkTests.swift in Sources */, - 61441C7B2461A204003D8BB8 /* LoggingStorageBenchmarkTests.swift in Sources */, - D2790C7229DEFCF400D88DA9 /* RUMDataModelMocks.swift in Sources */, - 6152C84024BE1CC8006A1679 /* DataUploaderBenchmarkTests.swift in Sources */, - E132727D24B35B5F00952F8B /* TracingStorageBenchmarkTests.swift in Sources */, - 613BE0432563FB9E0015216C /* RUMBenchmarkTests.swift in Sources */, - E132727B24B333C700952F8B /* TracingBenchmarkTests.swift in Sources */, - 613BE04A25640FF80015216C /* BenchmarkTests.swift in Sources */, - 613BE06225642F790015216C /* RUMStorageBenchmarkTests.swift in Sources */, - 61D6FF7E24E53D3B00D0E375 /* BenchmarkMocks.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 618F983C265BC486009959F8 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -8007,11 +7877,9 @@ D207319629A522F600ECBF94 /* ConsoleLogger.swift in Sources */, D20731C529A528EC00ECBF94 /* LogEventSanitizer.swift in Sources */, 49D8C0BD2AC5F2BB0075E427 /* Logs+Internal.swift in Sources */, - D20731C429A528EC00ECBF94 /* LogFileOutput.swift in Sources */, D207319529A522F600ECBF94 /* LogsFeature.swift in Sources */, D242C29E2A14D6A6004B4980 /* RemoteLogger.swift in Sources */, D20731B529A528DA00ECBF94 /* LogEventBuilder.swift in Sources */, - D20731C129A528EB00ECBF94 /* LogOutput.swift in Sources */, D243BBF529A620CC000B9CEC /* MessageReceivers.swift in Sources */, D22C5BC92A98A0B30024CC1F /* Baggages.swift in Sources */, ); @@ -8030,7 +7898,6 @@ D2A783ED29A534F2003B03BB /* LoggingFeatureMocks.swift in Sources */, D2B249972A45E10500DD4F9F /* LoggerTests.swift in Sources */, D2A783EA29A53468003B03BB /* LogMessageReceiverTests.swift in Sources */, - D2A783E929A53468003B03BB /* LogFileOutputTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8048,11 +7915,9 @@ D20731A929A5279D00ECBF94 /* ConsoleLogger.swift in Sources */, D20731CA29A528ED00ECBF94 /* LogEventSanitizer.swift in Sources */, 49D8C0BE2AC5F2BC0075E427 /* Logs+Internal.swift in Sources */, - D20731C929A528ED00ECBF94 /* LogFileOutput.swift in Sources */, D20731AB29A5279D00ECBF94 /* LogsFeature.swift in Sources */, D242C29F2A14D6A7004B4980 /* RemoteLogger.swift in Sources */, D20731B629A528DA00ECBF94 /* LogEventBuilder.swift in Sources */, - D20731C629A528ED00ECBF94 /* LogOutput.swift in Sources */, D243BBF629A620CC000B9CEC /* MessageReceivers.swift in Sources */, D22C5BC82A98A0B20024CC1F /* Baggages.swift in Sources */, ); @@ -8067,12 +7932,11 @@ D2EBEE1F29BA160F00B15732 /* HTTPHeadersReader.swift in Sources */, D263BCAF29DAFFEB00FA0E21 /* PerformancePresetOverride.swift in Sources */, D23039E7298D5236001A1FA3 /* NetworkConnectionInfo.swift in Sources */, - 3CBDE6712AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift in Sources */, D23039E9298D5236001A1FA3 /* TrackingConsent.swift in Sources */, D2EBEE2629BA160F00B15732 /* B3HTTPHeaders.swift in Sources */, D23354FC2A42E32000AFCAE2 /* InternalExtended.swift in Sources */, - 3C2206F32AB9CE9300DE780C /* MetaTypeExtensions.swift in Sources */, D23039F3298D5236001A1FA3 /* DynamicCodingKey.swift in Sources */, + D2BEEDB22B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */, D23039FE298D5236001A1FA3 /* FeatureRequestBuilder.swift in Sources */, D2160CE429C0DFEE00FAA9A5 /* MethodSwizzler.swift in Sources */, D2160CC929C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift in Sources */, @@ -8080,13 +7944,16 @@ D2432CF929EDB22C00D93657 /* Flushable.swift in Sources */, D23039F7298D5236001A1FA3 /* AttributesSanitizer.swift in Sources */, D23039EB298D5236001A1FA3 /* DatadogFeature.swift in Sources */, + D2BEEDBA2B33638F0065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */, 3CBDE6742AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift in Sources */, D23039E4298D5236001A1FA3 /* CarrierInfo.swift in Sources */, D2303A03298D5236001A1FA3 /* DDError.swift in Sources */, D23039F4298D5236001A1FA3 /* AnyCodable.swift in Sources */, D29A9F9529DDB1DB005C54A4 /* UIKitExtensions.swift in Sources */, + D2BEEDB52B3360820065F3AC /* URLSessionSwizzler.swift in Sources */, D2EBEE2529BA160F00B15732 /* TraceID.swift in Sources */, D2EBEE2129BA160F00B15732 /* W3CHTTPHeaders.swift in Sources */, + D2BEEDAC2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */, D23039E3298D5236001A1FA3 /* BatteryStatus.swift in Sources */, D2EBEE2A29BA160F00B15732 /* TracingHTTPHeaders.swift in Sources */, D23039EC298D5236001A1FA3 /* LaunchTime.swift in Sources */, @@ -8098,7 +7965,6 @@ D2160C9E29C0DE5700FAA9A5 /* TracingHeaderType.swift in Sources */, D23039F5298D5236001A1FA3 /* AnyEncodable.swift in Sources */, D2303A00298D5236001A1FA3 /* DatadogExtended.swift in Sources */, - 3CBDE66E2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift in Sources */, D23039E6298D5236001A1FA3 /* Sysctl.swift in Sources */, D2160CF429C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */, D23039E1298D5236001A1FA3 /* AppState.swift in Sources */, @@ -8108,13 +7974,11 @@ D2EBEE2329BA160F00B15732 /* B3HTTPHeadersReader.swift in Sources */, D23039F8298D5236001A1FA3 /* InternalLogger.swift in Sources */, D2303A01298D5236001A1FA3 /* DateFormatting.swift in Sources */, - 3CBDE6872AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */, D2216EC02A94DE2900ADAEC8 /* FeatureBaggage.swift in Sources */, D23039F1298D5236001A1FA3 /* AnyDecodable.swift in Sources */, D2160CC529C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, D23039DD298D5235001A1FA3 /* DD.swift in Sources */, D2160C9A29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, - 3CB32AD42ACB733000D602ED /* URLSessionSwizzler.swift in Sources */, D2EBEE2229BA160F00B15732 /* TracePropagationHeadersReader.swift in Sources */, D2303A02298D5236001A1FA3 /* ReadWriteLock.swift in Sources */, D2EBEE2429BA160F00B15732 /* W3CHTTPHeadersReader.swift in Sources */, @@ -8131,7 +7995,6 @@ D23039F2298D5236001A1FA3 /* AnyDecoder.swift in Sources */, D23039EF298D5236001A1FA3 /* FeatureMessage.swift in Sources */, D2160CA029C0DE5700FAA9A5 /* HostsSanitizer.swift in Sources */, - 3C394EF72AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */, D22F06D729DAFD500026CC3C /* FixedWidthInteger+Convenience.swift in Sources */, D295A16529F299C9007C0E9A /* URLSessionInterceptor.swift in Sources */, D23039E5298D5236001A1FA3 /* DateProvider.swift in Sources */, @@ -8141,6 +8004,7 @@ D2A783D429A5309F003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD72A543B3B00446CF9 /* Event.swift in Sources */, 3CBDE68A2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */, + D270CDDD2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift in Sources */, D22F06D929DAFD500026CC3C /* TimeInterval+Convenience.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -8174,7 +8038,7 @@ D23F8E6929DDCD28001CFAE8 /* RUMContextAttributes.swift in Sources */, D23F8E6B29DDCD28001CFAE8 /* RUMMonitor.swift in Sources */, D23F8E6C29DDCD28001CFAE8 /* RUMContextProvider.swift in Sources */, - D23F8E6D29DDCD28001CFAE8 /* RUMViewIdentity.swift in Sources */, + D23F8E6D29DDCD28001CFAE8 /* ViewIdentifier.swift in Sources */, 49D8C0B82AC5D2160075E427 /* RUM+Internal.swift in Sources */, D23F8E6E29DDCD28001CFAE8 /* RUMViewsHandler.swift in Sources */, 61C713BA2A3C935C00FA735A /* RUM.swift in Sources */, @@ -8249,7 +8113,7 @@ D23F8EB829DDCD38001CFAE8 /* UIKitRUMUserActionsHandlerTests.swift in Sources */, D23F8EB929DDCD38001CFAE8 /* RUMFeatureMocks.swift in Sources */, 61C713AE2A3B793E00FA735A /* RUMMonitorProtocolTests.swift in Sources */, - D23F8EBA29DDCD38001CFAE8 /* RUMViewIdentityTests.swift in Sources */, + D23F8EBA29DDCD38001CFAE8 /* ViewIdentifierTests.swift in Sources */, D23F8EBE29DDCD38001CFAE8 /* WebViewEventReceiverTests.swift in Sources */, D23F8EBF29DDCD38001CFAE8 /* URLSessionRUMResourcesHandlerTests.swift in Sources */, D23F8EC029DDCD38001CFAE8 /* RUMEventSanitizerTests.swift in Sources */, @@ -8359,7 +8223,7 @@ D2C1A4FC29C4C4CB00946C31 /* RequestBuilder.swift in Sources */, D2C1A50D29C4C4CB00946C31 /* SpanTagsReducer.swift in Sources */, 3CB012DD2B482E0400557951 /* NOPOTelSpan.swift in Sources */, - D2C1A51A29C4C5DD00946C31 /* JSONEncoder.swift in Sources */, + 61CE585A2B48174D00479510 /* SpanWriteContext.swift in Sources */, D2C1A51829C4C53F00946C31 /* OTSpan.swift in Sources */, D2C1A51429C4C53F00946C31 /* OTSpanContext.swift in Sources */, 3C6C7FEB2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */, @@ -8403,6 +8267,7 @@ 619CE75E2A458CE1005588CB /* TraceConfigurationTests.swift in Sources */, D2C1A52329C4C75700946C31 /* WarningsTests.swift in Sources */, D2C1A51D29C4C75700946C31 /* SpanEventBuilderTests.swift in Sources */, + 618C0FC02B482F6800266B38 /* SpanWriteContextTests.swift in Sources */, D2C1A52229C4C75700946C31 /* DDNoopTracerTests.swift in Sources */, D2C1A51C29C4C75700946C31 /* ContextMessageReceiverTests.swift in Sources */, 3C6C7FFD2B459AF6006F5CBC /* OTelSpanTests.swift in Sources */, @@ -8448,7 +8313,7 @@ D29A9F6829DD85BB005C54A4 /* RUMContextAttributes.swift in Sources */, D29A9F6329DD85BB005C54A4 /* RUMMonitor.swift in Sources */, D29A9F7029DD85BB005C54A4 /* RUMContextProvider.swift in Sources */, - D29A9F6029DD85BB005C54A4 /* RUMViewIdentity.swift in Sources */, + D29A9F6029DD85BB005C54A4 /* ViewIdentifier.swift in Sources */, 49D8C0B72AC5D2160075E427 /* RUM+Internal.swift in Sources */, D29A9F7629DD85BB005C54A4 /* RUMViewsHandler.swift in Sources */, 61C713B92A3C935C00FA735A /* RUM.swift in Sources */, @@ -8523,7 +8388,7 @@ D29A9FAC29DDB483005C54A4 /* UIKitRUMUserActionsHandlerTests.swift in Sources */, D29A9FC029DDB540005C54A4 /* RUMFeatureMocks.swift in Sources */, 61C713AD2A3B793E00FA735A /* RUMMonitorProtocolTests.swift in Sources */, - D29A9FB729DDB483005C54A4 /* RUMViewIdentityTests.swift in Sources */, + D29A9FB729DDB483005C54A4 /* ViewIdentifierTests.swift in Sources */, D29A9FA429DDB483005C54A4 /* WebViewEventReceiverTests.swift in Sources */, D29A9F9A29DDB483005C54A4 /* URLSessionRUMResourcesHandlerTests.swift in Sources */, D29A9FA229DDB483005C54A4 /* RUMEventSanitizerTests.swift in Sources */, @@ -8547,7 +8412,6 @@ D2A783F629A534F9003B03BB /* LoggingFeatureMocks.swift in Sources */, D2B249982A45E10500DD4F9F /* LoggerTests.swift in Sources */, D2A783F729A534F9003B03BB /* LogMessageReceiverTests.swift in Sources */, - D2A783F829A534F9003B03BB /* LogFileOutputTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8563,7 +8427,7 @@ D2C1A53A29C4F2DF00946C31 /* RequestBuilder.swift in Sources */, D2C1A53B29C4F2DF00946C31 /* SpanTagsReducer.swift in Sources */, 3CB012DE2B482E0400557951 /* NOPOTelSpan.swift in Sources */, - D2C1A53C29C4F2DF00946C31 /* JSONEncoder.swift in Sources */, + 61CE585B2B48174D00479510 /* SpanWriteContext.swift in Sources */, D2C1A53D29C4F2DF00946C31 /* OTSpan.swift in Sources */, D2C1A53E29C4F2DF00946C31 /* OTSpanContext.swift in Sources */, 3C6C7FEC2B459AAA006F5CBC /* OTelTraceId+Datadog.swift in Sources */, @@ -8607,6 +8471,7 @@ 619CE75F2A458CE1005588CB /* TraceConfigurationTests.swift in Sources */, D2C1A56129C4F2E800946C31 /* WarningsTests.swift in Sources */, D2C1A56229C4F2E800946C31 /* SpanEventBuilderTests.swift in Sources */, + 618C0FC12B482F6800266B38 /* SpanWriteContextTests.swift in Sources */, D2C1A56329C4F2E800946C31 /* DDNoopTracerTests.swift in Sources */, D2C1A56529C4F2E800946C31 /* ContextMessageReceiverTests.swift in Sources */, 3C6C80002B459AF6006F5CBC /* OTelSpanTests.swift in Sources */, @@ -8702,6 +8567,7 @@ D24C9C4E29A7BA41002057CF /* LogsMocks.swift in Sources */, D2CB6EDE27C520D400A62B57 /* RUMEventMatcher.swift in Sources */, D2CB6EE027C520D400A62B57 /* SpanMatcher.swift in Sources */, + 6179DB572B6022EA00E9E04E /* SendingCrashReportTests.swift in Sources */, 61112F8F2A4417D6006FFCA6 /* DDRUM+apiTests.m in Sources */, D2DC4BBD27F234E000E4FB96 /* CITestIntegrationTests.swift in Sources */, D2CB6EE427C520D400A62B57 /* FeatureTests.swift in Sources */, @@ -8803,6 +8669,7 @@ D2CB6F6427C520D400A62B57 /* LoggerTests.swift in Sources */, D29A9FD929DDC687005C54A4 /* UIKitRUMViewsPredicateTests.swift in Sources */, D2CB6F6627C520D400A62B57 /* RUMMonitorTests.swift in Sources */, + 61E8C5092B28898800E709B4 /* StartingRUMSessionTests.swift in Sources */, D22743DF29DEB8B5001A7EF9 /* VitalMemoryReaderTests.swift in Sources */, D2CB6F6827C520D400A62B57 /* SwiftUIExtensionsTests.swift in Sources */, D2CB6F6A27C520D400A62B57 /* DDRUMMonitor+apiTests.m in Sources */, @@ -8811,6 +8678,7 @@ D2CB6F7327C520D400A62B57 /* CoreTelephonyMocks.swift in Sources */, D20605B7287572640047275C /* DatadogContextProviderMock.swift in Sources */, D2CB6F7527C520D400A62B57 /* UIKitExtensionsTests.swift in Sources */, + D21831562B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift in Sources */, 61DA8CB928647A500074A606 /* InternalLoggerTests.swift in Sources */, D2CB6F7C27C520D400A62B57 /* CrashReporterTests.swift in Sources */, D2CB6F7D27C520D400A62B57 /* CrashContextTests.swift in Sources */, @@ -8903,12 +8771,11 @@ D2EBEE2D29BA161100B15732 /* HTTPHeadersReader.swift in Sources */, D263BCB029DAFFEB00FA0E21 /* PerformancePresetOverride.swift in Sources */, D2DA2359298D57AA00C6C7E6 /* NetworkConnectionInfo.swift in Sources */, - 3CBDE6722AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift in Sources */, D2DA235A298D57AA00C6C7E6 /* TrackingConsent.swift in Sources */, D2EBEE3429BA161100B15732 /* B3HTTPHeaders.swift in Sources */, D23354FD2A42E32000AFCAE2 /* InternalExtended.swift in Sources */, - 3C2206F42AB9CE9300DE780C /* MetaTypeExtensions.swift in Sources */, D2DA235B298D57AA00C6C7E6 /* DynamicCodingKey.swift in Sources */, + D2BEEDB32B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */, D2DA235C298D57AA00C6C7E6 /* FeatureRequestBuilder.swift in Sources */, D2160CE629C0DFEE00FAA9A5 /* MethodSwizzler.swift in Sources */, D2160CCA29C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift in Sources */, @@ -8916,13 +8783,16 @@ D2432CFA29EDB22C00D93657 /* Flushable.swift in Sources */, D2DA235D298D57AA00C6C7E6 /* AttributesSanitizer.swift in Sources */, D2DA235E298D57AA00C6C7E6 /* DatadogFeature.swift in Sources */, + D2BEEDBB2B3363900065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */, 3CBDE6752AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift in Sources */, D2DA235F298D57AA00C6C7E6 /* CarrierInfo.swift in Sources */, D2DA2360298D57AA00C6C7E6 /* DDError.swift in Sources */, D2DA2361298D57AA00C6C7E6 /* AnyCodable.swift in Sources */, D29A9F9629DDB1DB005C54A4 /* UIKitExtensions.swift in Sources */, + D2BEEDB62B3360830065F3AC /* URLSessionSwizzler.swift in Sources */, D2EBEE3329BA161100B15732 /* TraceID.swift in Sources */, D2EBEE2F29BA161100B15732 /* W3CHTTPHeaders.swift in Sources */, + D2BEEDAD2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */, D2DA2363298D57AA00C6C7E6 /* BatteryStatus.swift in Sources */, D2EBEE3829BA161100B15732 /* TracingHTTPHeaders.swift in Sources */, D2DA2364298D57AA00C6C7E6 /* LaunchTime.swift in Sources */, @@ -8934,7 +8804,6 @@ D2160C9F29C0DE5700FAA9A5 /* TracingHeaderType.swift in Sources */, D2DA2369298D57AA00C6C7E6 /* AnyEncodable.swift in Sources */, D2DA236A298D57AA00C6C7E6 /* DatadogExtended.swift in Sources */, - 3CBDE66F2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift in Sources */, D2DA236B298D57AA00C6C7E6 /* Sysctl.swift in Sources */, D2160CF529C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */, D2DA236C298D57AA00C6C7E6 /* AppState.swift in Sources */, @@ -8944,13 +8813,11 @@ D2EBEE3129BA161100B15732 /* B3HTTPHeadersReader.swift in Sources */, D2DA236E298D57AA00C6C7E6 /* InternalLogger.swift in Sources */, D2DA236F298D57AA00C6C7E6 /* DateFormatting.swift in Sources */, - 3CBDE6882AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */, D2216EC12A94DE2900ADAEC8 /* FeatureBaggage.swift in Sources */, D2DA2370298D57AA00C6C7E6 /* AnyDecodable.swift in Sources */, D2160CC629C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, D2DA2372298D57AA00C6C7E6 /* DD.swift in Sources */, D2160C9B29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, - 3CB32AD52ACB733000D602ED /* URLSessionSwizzler.swift in Sources */, D2EBEE3029BA161100B15732 /* TracePropagationHeadersReader.swift in Sources */, D2DA2373298D57AA00C6C7E6 /* ReadWriteLock.swift in Sources */, D2EBEE3229BA161100B15732 /* W3CHTTPHeadersReader.swift in Sources */, @@ -8967,7 +8834,6 @@ D2DA2379298D57AA00C6C7E6 /* AnyDecoder.swift in Sources */, D2DA237A298D57AA00C6C7E6 /* FeatureMessage.swift in Sources */, D2160CA129C0DE5700FAA9A5 /* HostsSanitizer.swift in Sources */, - 3C394EF82AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */, D22F06D829DAFD500026CC3C /* FixedWidthInteger+Convenience.swift in Sources */, D295A16629F299C9007C0E9A /* URLSessionInterceptor.swift in Sources */, D2DA237B298D57AA00C6C7E6 /* DateProvider.swift in Sources */, @@ -8977,6 +8843,7 @@ D2A783D529A530A0003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD82A543B3B00446CF9 /* Event.swift in Sources */, 3CBDE68B2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */, + D270CDDE2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift in Sources */, D22F06DA29DAFD500026CC3C /* TimeInterval+Convenience.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -8985,24 +8852,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3C394EFA2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, + D2BEEDAF2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift in Sources */, D26416B62A30E84F00BCD9F7 /* CoreRegistryTest.swift in Sources */, D2EBEE3C29BA163E00B15732 /* B3HTTPHeadersWriterTests.swift in Sources */, - 3CBDE6812AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */, D21AE6BC29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */, D2DA23A3298D58F400C6C7E6 /* AnyEncodableTests.swift in Sources */, D263BCB429DB014900FA0E21 /* FixedWidthInteger+ConvenienceTests.swift in Sources */, 3C0D5DF52A5443B100446CF9 /* DataFormatTests.swift in Sources */, D2EBEE4429BA168200B15732 /* TraceIDTests.swift in Sources */, - 3CBDE6842AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift in Sources */, D2EBEE4329BA168200B15732 /* TraceIDGeneratorTests.swift in Sources */, D2DA23A7298D58F400C6C7E6 /* AppStateHistoryTests.swift in Sources */, D2DA23A5298D58F400C6C7E6 /* AnyDecodableTests.swift in Sources */, - 3CFD81952ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */, D2EBEE3D29BA163E00B15732 /* W3CHTTPHeadersWriterTests.swift in Sources */, - 3CB32AD72ACB735600D602ED /* URLSessionSwizzlerTests.swift in Sources */, D2DA23A4298D58F400C6C7E6 /* AnyCodableTests.swift in Sources */, D2160CDE29C0DF6700FAA9A5 /* URLSessionTaskInterceptionTests.swift in Sources */, + D270CDE02B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, D2DA23A1298D58F400C6C7E6 /* ReadWriteLockTests.swift in Sources */, D2160CD829C0DF6700FAA9A5 /* FirstPartyHostsTests.swift in Sources */, D20731CD29A52E8700ECBF94 /* SamplerTests.swift in Sources */, @@ -9015,6 +8879,8 @@ D2F44FB8299AA1DA0074B0D9 /* DataCompressionTests.swift in Sources */, D2160CE029C0DF6700FAA9A5 /* URLSessionDelegateAsSuperclassTests.swift in Sources */, D2EBEE3B29BA163E00B15732 /* B3HTTPHeadersReaderTests.swift in Sources */, + D2BEEDB82B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */, + D2181A8E2B051B7900A518C0 /* URLSessionSwizzlerTests.swift in Sources */, D2A783DA29A530EF003B03BB /* SwiftExtensionsTests.swift in Sources */, D2D36DCB2AC6DCCA0021F28A /* DatadogCoreProtocolTests.swift in Sources */, D2160CD429C0DF6700FAA9A5 /* NetworkInstrumentationFeatureTests.swift in Sources */, @@ -9028,24 +8894,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3C394EFB2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, + D2BEEDB02B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift in Sources */, D26416B72A30E84F00BCD9F7 /* CoreRegistryTest.swift in Sources */, D2EBEE4029BA163F00B15732 /* B3HTTPHeadersWriterTests.swift in Sources */, - 3CBDE6822AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */, D21AE6BD29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */, D2DA23B1298D59DC00C6C7E6 /* AnyEncodableTests.swift in Sources */, D263BCB529DB014900FA0E21 /* FixedWidthInteger+ConvenienceTests.swift in Sources */, 3C0D5DF62A5443B100446CF9 /* DataFormatTests.swift in Sources */, D2EBEE4629BA168400B15732 /* TraceIDTests.swift in Sources */, - 3CBDE6852AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift in Sources */, D2EBEE4529BA168400B15732 /* TraceIDGeneratorTests.swift in Sources */, D2DA23B2298D59DC00C6C7E6 /* AppStateHistoryTests.swift in Sources */, D2DA23B3298D59DC00C6C7E6 /* AnyDecodableTests.swift in Sources */, - 3CFD81962ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */, D2EBEE4129BA163F00B15732 /* W3CHTTPHeadersWriterTests.swift in Sources */, - 3CB32AD82ACB735600D602ED /* URLSessionSwizzlerTests.swift in Sources */, D2DA23B4298D59DC00C6C7E6 /* AnyCodableTests.swift in Sources */, D2160CDF29C0DF6700FAA9A5 /* URLSessionTaskInterceptionTests.swift in Sources */, + D270CDE12B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, D2DA23B5298D59DC00C6C7E6 /* ReadWriteLockTests.swift in Sources */, D2160CD929C0DF6700FAA9A5 /* FirstPartyHostsTests.swift in Sources */, D20731CE29A52E8700ECBF94 /* SamplerTests.swift in Sources */, @@ -9058,6 +8921,8 @@ D2F44FB9299AA1DB0074B0D9 /* DataCompressionTests.swift in Sources */, D2160CE129C0DF6700FAA9A5 /* URLSessionDelegateAsSuperclassTests.swift in Sources */, D2EBEE3F29BA163F00B15732 /* B3HTTPHeadersReaderTests.swift in Sources */, + D2BEEDB92B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */, + D2181A8F2B051B7900A518C0 /* URLSessionSwizzlerTests.swift in Sources */, D2A783D929A530EF003B03BB /* SwiftExtensionsTests.swift in Sources */, D2D36DCC2AC6DCCA0021F28A /* DatadogCoreProtocolTests.swift in Sources */, D2160CD529C0DF6700FAA9A5 /* NetworkInstrumentationFeatureTests.swift in Sources */, @@ -9135,11 +9000,6 @@ target = 61441C0124616DE9003D8BB8 /* Example iOS */; targetProxy = 61441C5924619A08003D8BB8 /* PBXContainerItemProxy */; }; - 61441C7524619FED003D8BB8 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 61441C0124616DE9003D8BB8 /* Example iOS */; - targetProxy = 61441C7424619FED003D8BB8 /* PBXContainerItemProxy */; - }; 6158155B2AB4534F002C60D7 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 61441C0124616DE9003D8BB8 /* Example iOS */; @@ -9374,7 +9234,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9409,7 +9268,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9444,7 +9302,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9852,7 +9709,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9888,7 +9744,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9923,7 +9778,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -10113,72 +9967,6 @@ }; name = Integration; }; - 61441C7124619FE4003D8BB8 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 6152C84124BE1F47006A1679 /* DatadogBenchmarkTests.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = TargetSupport/DatadogBenchmarkTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.datadogqh.DatadogBenchmarkTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example iOS.app/Example iOS"; - }; - name = Debug; - }; - 61441C7224619FE4003D8BB8 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 6152C84124BE1F47006A1679 /* DatadogBenchmarkTests.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = TargetSupport/DatadogBenchmarkTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.datadogqh.DatadogBenchmarkTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example iOS.app/Example iOS"; - }; - name = Release; - }; - 61441C7324619FE4003D8BB8 /* Integration */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 6152C84124BE1F47006A1679 /* DatadogBenchmarkTests.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = TargetSupport/DatadogBenchmarkTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.datadogqh.DatadogBenchmarkTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example iOS.app/Example iOS"; - }; - name = Integration; - }; 618F9848265BC486009959F8 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 618F984C265BC53E009959F8 /* E2EInstrumentationTests.xcconfig */; @@ -10987,7 +10775,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11023,7 +10810,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11058,7 +10844,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11092,7 +10877,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11127,7 +10911,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11162,7 +10945,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11329,7 +11111,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "@executable_path/Frameworks", "@loader_path/Frameworks", @@ -11369,7 +11150,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "@executable_path/Frameworks", "@loader_path/Frameworks", @@ -11409,7 +11189,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "@executable_path/Frameworks", "@loader_path/Frameworks", @@ -11562,7 +11341,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11597,7 +11375,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11632,7 +11409,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11734,7 +11510,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11769,7 +11544,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11804,7 +11578,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -11970,7 +11743,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -12005,7 +11777,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -12040,7 +11811,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -12542,7 +12312,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -12579,7 +12348,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -12615,7 +12383,6 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Datadog. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -12896,16 +12663,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 61441C7024619FE4003D8BB8 /* Build configuration list for PBXNativeTarget "DatadogBenchmarkTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 61441C7124619FE4003D8BB8 /* Debug */, - 61441C7224619FE4003D8BB8 /* Release */, - 61441C7324619FE4003D8BB8 /* Integration */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 618F9847265BC486009959F8 /* Build configuration list for PBXNativeTarget "E2EInstrumentationTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogBenchmarkTests.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogBenchmarkTests.xcscheme deleted file mode 100644 index 4238ff2118..0000000000 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogBenchmarkTests.xcscheme +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogRUM iOS.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogRUM iOS.xcscheme index 6d0b5de4e3..3da59df68d 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogRUM iOS.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogRUM iOS.xcscheme @@ -29,8 +29,7 @@ shouldUseLaunchSchemeArgsEnv = "YES"> + skipped = "NO"> - + @@ -26,7 +26,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -106,7 +106,7 @@ - + @@ -126,7 +126,7 @@ - + @@ -146,7 +146,7 @@ - + @@ -166,7 +166,7 @@ - + @@ -204,7 +204,7 @@ - + @@ -224,26 +224,26 @@ - + - + - + @@ -261,33 +261,33 @@ - + - + - + @@ -301,27 +301,27 @@ - + - + - + @@ -329,14 +329,14 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IntegrationTests/Runner/Scenarios/Logging/ManualInstrumentation/SendLogsFixtureViewController.swift b/IntegrationTests/Runner/Scenarios/Logging/ManualInstrumentation/SendLogsFixtureViewController.swift index 739aa25f1d..a5c215ff03 100644 --- a/IntegrationTests/Runner/Scenarios/Logging/ManualInstrumentation/SendLogsFixtureViewController.swift +++ b/IntegrationTests/Runner/Scenarios/Logging/ManualInstrumentation/SendLogsFixtureViewController.swift @@ -11,18 +11,18 @@ internal class SendLogsFixtureViewController: UIViewController { super.viewDidLoad() // Send logs - logger.addTag(withKey: "tag1", value: "tag-value") - logger.add(tag: "tag2") + logger?.addTag(withKey: "tag1", value: "tag-value") + logger?.add(tag: "tag2") - logger.addAttribute(forKey: "logger-attribute1", value: "string value") - logger.addAttribute(forKey: "logger-attribute2", value: 1_000) - logger.addAttribute(forKey: "some-url", value: URL(string: "https://example.com/image.png")!) + logger?.addAttribute(forKey: "logger-attribute1", value: "string value") + logger?.addAttribute(forKey: "logger-attribute2", value: 1_000) + logger?.addAttribute(forKey: "some-url", value: URL(string: "https://example.com/image.png")!) - logger.debug("debug message", attributes: ["attribute": "value"]) - logger.info("info message", attributes: ["attribute": "value"]) - logger.notice("notice message", attributes: ["attribute": "value"]) - logger.warn("warn message", attributes: ["attribute": "value"]) - logger.error("error message", attributes: ["attribute": "value"]) - logger.critical("critical message", attributes: ["attribute": "value"]) + logger?.debug("debug message", attributes: ["attribute": "value"]) + logger?.info("info message", attributes: ["attribute": "value"]) + logger?.notice("notice message", attributes: ["attribute": "value"]) + logger?.warn("warn message", attributes: ["attribute": "value"]) + logger?.error("error message", attributes: ["attribute": "value"]) + logger?.critical("critical message", attributes: ["attribute": "value"]) } } diff --git a/IntegrationTests/Runner/Scenarios/RUM/RUMScenarios.swift b/IntegrationTests/Runner/Scenarios/RUM/RUMScenarios.swift index 57a14cf67f..d49d6944f0 100644 --- a/IntegrationTests/Runner/Scenarios/RUM/RUMScenarios.swift +++ b/IntegrationTests/Runner/Scenarios/RUM/RUMScenarios.swift @@ -181,7 +181,7 @@ class RUMResourcesBaseScenario: URLSessionBaseScenario { config.uiKitViewsPredicate = DefaultUIKitRUMViewsPredicate() switch setup.instrumentationMethod { - case .directWithGlobalFirstPartyHosts, .inheritance, .composition: + case .legacyWithFeatureFirstPartyHosts, .legacyInheritance, .legacyComposition, .delegateUsingFeatureFirstPartyHosts: config.urlSessionTracking = .init( firstPartyHostsTracing: .trace( hosts: [ @@ -193,7 +193,7 @@ class RUMResourcesBaseScenario: URLSessionBaseScenario { ), resourceAttributesProvider: rumResourceAttributesProvider(request:response:data:error:) ) - case .directWithAdditionalFirstyPartyHosts: + case .legacyWithAdditionalFirstyPartyHosts, .delegateWithAdditionalFirstyPartyHosts: config.urlSessionTracking = .init( firstPartyHostsTracing: .trace(hosts: [], sampleRate: 100), // hosts will be set through `DDURLSessionDelegate` resourceAttributesProvider: rumResourceAttributesProvider(request:response:data:error:) @@ -207,16 +207,12 @@ class RUMResourcesBaseScenario: URLSessionBaseScenario { /// sent with Swift `URLSession` from two VCs. The first VC calls first party resources, the second one calls third parties. final class RUMURLSessionResourcesScenario: RUMResourcesBaseScenario, TestScenario { static let storyboardName = "URLSessionScenario" - - override func configureFeatures() { super.configureFeatures() } } /// Scenario which uses RUM resources instrumentation to track bunch of network requests /// sent with Objective-c `NSURLSession` from two VCs. The first VC calls first party resources, the second one calls third parties. final class RUMNSURLSessionResourcesScenario: RUMResourcesBaseScenario, TestScenario { static let storyboardName = "NSURLSessionScenario" - - override func configureFeatures() { super.configureFeatures() } } /// Scenario which uses RUM manual instrumentation API to send bunch of RUM events. Each event contains some diff --git a/IntegrationTests/Runner/Scenarios/Tracing/ManualInstrumentation/SendTracesFixtureViewController.swift b/IntegrationTests/Runner/Scenarios/Tracing/ManualInstrumentation/SendTracesFixtureViewController.swift index c0d5861d6a..5ebf25b1d2 100644 --- a/IntegrationTests/Runner/Scenarios/Tracing/ManualInstrumentation/SendTracesFixtureViewController.swift +++ b/IntegrationTests/Runner/Scenarios/Tracing/ManualInstrumentation/SendTracesFixtureViewController.swift @@ -14,6 +14,8 @@ internal class SendTracesFixtureViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + let tracer = Tracer.shared() + let viewLoadingSpan = tracer .startRootSpan(operationName: "view loading") .setActive() diff --git a/IntegrationTests/Runner/Scenarios/Tracing/TracingScenarios.swift b/IntegrationTests/Runner/Scenarios/Tracing/TracingScenarios.swift index b082ac08a3..9dc5a24ae6 100644 --- a/IntegrationTests/Runner/Scenarios/Tracing/TracingScenarios.swift +++ b/IntegrationTests/Runner/Scenarios/Tracing/TracingScenarios.swift @@ -47,7 +47,7 @@ class TracingURLSessionBaseScenario: URLSessionBaseScenario { } switch setup.instrumentationMethod { - case .directWithGlobalFirstPartyHosts, .inheritance, .composition: + case .legacyWithFeatureFirstPartyHosts, .legacyInheritance, .legacyComposition, .delegateUsingFeatureFirstPartyHosts: config.urlSessionTracking = .init( firstPartyHostsTracing: .trace( hosts: [ @@ -58,7 +58,7 @@ class TracingURLSessionBaseScenario: URLSessionBaseScenario { sampleRate: 100 ) ) - case .directWithAdditionalFirstyPartyHosts: + case .legacyWithAdditionalFirstyPartyHosts, .delegateWithAdditionalFirstyPartyHosts: config.urlSessionTracking = .init( firstPartyHostsTracing: .trace(hosts: [], sampleRate: 100) // hosts will be set through `DDURLSessionDelegate` ) diff --git a/IntegrationTests/Runner/Scenarios/TrackingConsent/TrackingConsent/TSHomeViewController.swift b/IntegrationTests/Runner/Scenarios/TrackingConsent/TrackingConsent/TSHomeViewController.swift index 34e699e4f1..570666c619 100644 --- a/IntegrationTests/Runner/Scenarios/TrackingConsent/TrackingConsent/TSHomeViewController.swift +++ b/IntegrationTests/Runner/Scenarios/TrackingConsent/TrackingConsent/TSHomeViewController.swift @@ -6,6 +6,7 @@ import UIKit import DatadogCore +import DatadogTrace internal class TSHomeViewController: UIViewController { override func viewDidLoad() { @@ -39,13 +40,12 @@ internal class TSHomeViewController: UIViewController { @IBAction func didTapTestLogging(_ sender: UIButton) { sender.disableFor(seconds: 0.5) - logger.info("test message") + logger?.info("test message") } @IBAction func didTapTestTracing(_ sender: UIButton) { sender.disableFor(seconds: 0.5) - - let span = tracer.startSpan(operationName: "test span") + let span = Tracer.shared().startSpan(operationName: "test span") span.finish(at: Date(timeIntervalSinceNow: 1)) } } diff --git a/IntegrationTests/Runner/Scenarios/URLSession/URLSessionScenarios.swift b/IntegrationTests/Runner/Scenarios/URLSession/URLSessionScenarios.swift index c3af654fc1..f9e5efd261 100644 --- a/IntegrationTests/Runner/Scenarios/URLSession/URLSessionScenarios.swift +++ b/IntegrationTests/Runner/Scenarios/URLSession/URLSessionScenarios.swift @@ -20,18 +20,21 @@ private class InheritedURLSessionDelegate: DDURLSessionDelegate { /// An example of instrumenting existing `URLSessionDelegate` with `DDURLSessionDelegate` through composition. private class CompositedURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate, __URLSessionDelegateProviding { // MARK: - __URLSessionDelegateProviding conformance + let ddURLSessionDelegate = DatadogURLSessionDelegate() // MARK: - __URLSessionDelegateProviding handling - func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { + ddURLSessionDelegate.urlSession(session, task: task, didFinishCollecting: metrics) // forward to DD /* run custom logic */ } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + ddURLSessionDelegate.urlSession(session, task: task, didCompleteWithError: error) // forward to DD /* run custom logic */ } func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + ddURLSessionDelegate.urlSession(session, dataTask: dataTask, didReceive: data) // forward to DD /* run custom logic */ } } @@ -141,9 +144,9 @@ class URLSessionBaseScenario: NSObject { let delegate: URLSessionDataDelegate switch setup.instrumentationMethod { - case .directWithGlobalFirstPartyHosts: + case .legacyWithFeatureFirstPartyHosts: delegate = DDURLSessionDelegate() - case .directWithAdditionalFirstyPartyHosts: + case .legacyWithAdditionalFirstyPartyHosts: delegate = DDURLSessionDelegate( additionalFirstPartyHosts: [ customGETResourceURL.host!, @@ -151,11 +154,25 @@ class URLSessionBaseScenario: NSObject { badResourceURL.host! ] ) - case .inheritance: + case .legacyInheritance: delegate = InheritedURLSessionDelegate() - case .composition: + case .legacyComposition: delegate = CompositedURLSessionDelegate() - URLSessionInstrumentation.enable(with: .init(delegateClass: CompositedURLSessionDelegate.self)) + case .delegateUsingFeatureFirstPartyHosts: + URLSessionInstrumentation.enable(with: .init(delegateClass: CustomURLSessionDelegate.self)) + delegate = CustomURLSessionDelegate() + case .delegateWithAdditionalFirstyPartyHosts: + URLSessionInstrumentation.enable( + with: .init( + delegateClass: CustomURLSessionDelegate.self, + firstPartyHostsTracing: .trace(hosts: [ + customGETResourceURL.host!, + customPOSTRequest.url!.host!, + badResourceURL.host! + ]) + ) + ) + delegate = CustomURLSessionDelegate() } return URLSession( diff --git a/DatadogLogs/Sources/LogOutputs/LogOutput.swift b/IntegrationTests/Runner/Utils/CustomURLSessionDelegate.swift similarity index 64% rename from DatadogLogs/Sources/LogOutputs/LogOutput.swift rename to IntegrationTests/Runner/Utils/CustomURLSessionDelegate.swift index db597493d7..c25d162552 100644 --- a/DatadogLogs/Sources/LogOutputs/LogOutput.swift +++ b/IntegrationTests/Runner/Utils/CustomURLSessionDelegate.swift @@ -6,7 +6,7 @@ import Foundation -/// An interface for writing logs to some destination. -internal protocol LogOutput { - func write(log: LogEvent) +/// A custion ``URLSessionDataDelegate`` for instrumenting ``URLSession``. +internal class CustomURLSessionDelegate: NSObject, URLSessionDataDelegate { + } diff --git a/Makefile b/Makefile index 1dedd57754..5e743d7134 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,12 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) DD_SDK_ENABLE_EXPERIMENTAL_AP // To build only active architecture for all configurations. This gives us ~10% build time gain\n // in targets which do not use 'Debug' configuration.\n ONLY_ACTIVE_ARCH = YES\n +\n +// Adjust the deployment target for all projects and targets in `dd-sdk-ios` (including Datadog.xcworkspace and IntegrationTests.xcworkspace).\n +// This is to fix Xcode 15 warnings and errors like:\n +// - 'The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 11.0, but the range of supported deployment target versions is 12.0 to 17.0.99.'.\n +// - 'Compiling for iOS 11.0, but module 'SRFixtures' has a minimum deployment target of iOS 12.0'\n +IPHONEOS_DEPLOYMENT_TARGET=12.0\n endef export DD_SDK_BASE_XCCONFIG @@ -37,6 +43,9 @@ SWIFT_TREAT_WARNINGS_AS_ERRORS = YES\n \n // If running on CI. This value is injected to some targets through their `Info.plist`:\n IS_CI = true\n +\n +// Use iOS 11 deployment target on CI as long as we use Xcode 14.x for integration\n +IPHONEOS_DEPLOYMENT_TARGET=11.0\n endef export DD_SDK_BASE_XCCONFIG_CI @@ -72,6 +81,16 @@ endif endif +# Prepare project on GitLab CI (this will replace `make dependencies` once we're fully on GitLab). +dependencies-gitlab: + @echo "📝 Source xcconfigs..." + @echo $$DD_SDK_BASE_XCCONFIG > xcconfigs/Base.local.xcconfig; + @echo $$DD_SDK_BASE_XCCONFIG_CI >> xcconfigs/Base.local.xcconfig; + # We use Xcode 15 on GitLab, so overwrite deployment target in all projects to avoid build errors: + @echo "IPHONEOS_DEPLOYMENT_TARGET=12.0\n" >> xcconfigs/Base.local.xcconfig; + @echo "⚙️ Carthage bootstrap..." + @carthage bootstrap --platform iOS,tvOS --use-xcframeworks + xcodeproj-session-replay: @echo "⚙️ Generating 'DatadogSessionReplay.xcodeproj'..." @cd DatadogSessionReplay/ && swift package generate-xcodeproj @@ -110,9 +129,10 @@ test-xcframeworks: @cd dependency-manager-tests/xcframeworks && $(MAKE) # Generate RUM data models from rum-events-format JSON Schemas +# - run with `git_ref=` argument to generate models for given schema commit or branch name (default is 'master'). rum-models-generate: @echo "⚙️ Generating RUM models..." - ./tools/rum-models-generator/run.py generate rum + ./tools/rum-models-generator/run.py generate rum --git_ref=$(if $(git_ref),$(git_ref),master) @echo "OK 👌" # Verify if RUM data models follow rum-events-format JSON Schemas @@ -122,9 +142,10 @@ rum-models-verify: @echo "OK 👌" # Generate Session Replay data models from rum-events-format JSON Schemas +# - run with `git_ref=` argument to generate models for given schema commit or branch name (default is 'master'). sr-models-generate: @echo "⚙️ Generating Session Replay models..." - ./tools/rum-models-generator/run.py generate sr + ./tools/rum-models-generator/run.py generate sr --git_ref=$(if $(git_ref),$(git_ref),master) @echo "OK 👌" # Verify if Session Replay data models follow rum-events-format JSON Schemas diff --git a/Package.swift b/Package.swift index e015a74ec5..eb4ec80e8c 100644 --- a/Package.swift +++ b/Package.swift @@ -44,7 +44,7 @@ let package = Package( ), ], dependencies: [ - .package(name: "PLCrashReporter", url: "https://github.com/microsoft/plcrashreporter.git", from: "1.11.1"), + .package(url: "https://github.com/microsoft/plcrashreporter.git", from: "1.11.1"), .package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", from: "1.8.0") ], targets: [ @@ -54,7 +54,11 @@ let package = Package( .target(name: "DatadogInternal"), .target(name: "DatadogPrivate"), ], - path: "DatadogCore/Sources", + path: "DatadogCore", + sources: ["Sources"], + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") + ], swiftSettings: [.define("SPM_BUILD")] ), .target( diff --git a/TestUtilities.podspec b/TestUtilities.podspec index a2f2bf882b..3b96736e17 100644 --- a/TestUtilities.podspec +++ b/TestUtilities.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "TestUtilities" - s.version = "2.5.1" + s.version = "2.7.0" s.summary = "Datadog Testing Utilities. This module is for internal testing and should not be published." s.homepage = "https://www.datadoghq.com" diff --git a/TestUtilities/Helpers/XCTestCase.swift b/TestUtilities/Helpers/XCTestCase.swift index 40ebd9d97f..9b7d43edf9 100644 --- a/TestUtilities/Helpers/XCTestCase.swift +++ b/TestUtilities/Helpers/XCTestCase.swift @@ -63,4 +63,13 @@ extension XCTestCase { ) #endif } + + /// Creates and returns an expectation associated with the test case by setting `isInverted` to `true`. + /// - Parameter description: This string will be displayed in the test log to help diagnose failures. + /// - Returns: Inverted expectation. + public func invertedExpectation(description: String) -> XCTestExpectation { + let expectation = self.expectation(description: description) + expectation.isInverted = true + return expectation + } } diff --git a/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift b/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift index 7d4f9588b9..b09814f17b 100644 --- a/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift +++ b/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift @@ -112,10 +112,7 @@ open class PassthroughCoreMock: DatadogCoreProtocol, FeatureScope { /// Execute `block` with the current context and a `writer` to record events. /// /// - Parameter block: The block to execute. - public func eventWriteContext(bypassConsent: Bool, forceNewBatch: Bool, _ block: (DatadogContext, Writer) throws -> Void) { - XCTAssertNoThrow(try block(context, writer), "Encountered an error when executing `eventWriteContext`") - expectation?.fulfill() - + public func eventWriteContext(bypassConsent: Bool, forceNewBatch: Bool, _ block: @escaping (DatadogContext, Writer) -> Void) { if bypassConsent { bypassConsentExpectation?.fulfill() } @@ -123,6 +120,13 @@ open class PassthroughCoreMock: DatadogCoreProtocol, FeatureScope { if forceNewBatch { forceNewBatchExpectation?.fulfill() } + + block(context, writer) + expectation?.fulfill() + } + + public func context(_ block: @escaping (DatadogContext) -> Void) { + block(context) } /// Recorded events from feature scopes. diff --git a/TestUtilities/Mocks/DatadogContextMock.swift b/TestUtilities/Mocks/DatadogContextMock.swift index 88dc468da7..f70fd5925c 100644 --- a/TestUtilities/Mocks/DatadogContextMock.swift +++ b/TestUtilities/Mocks/DatadogContextMock.swift @@ -113,12 +113,12 @@ extension DeviceInfo { } public static func mockWith( - name: String = .mockAny(), - model: String = .mockAny(), - osName: String = .mockAny(), - osVersion: String = .mockAny(), - osBuildNumber: String = .mockAny(), - architecture: String = .mockAny() + name: String = "iPhone", + model: String = "iPhone10,1", + osName: String = "iOS", + osVersion: String = "15.4.1", + osBuildNumber: String = "13D20", + architecture: String = "arm64e" ) -> DeviceInfo { return .init( name: name, diff --git a/TestUtilities/Mocks/FoundationMocks.swift b/TestUtilities/Mocks/FoundationMocks.swift index 96446a41ef..c8e1e5017e 100644 --- a/TestUtilities/Mocks/FoundationMocks.swift +++ b/TestUtilities/Mocks/FoundationMocks.swift @@ -720,6 +720,7 @@ extension URLSessionTaskTransactionMetrics { private class URLSessionDataTaskMock: URLSessionDataTask { private let _originalRequest: URLRequest override var originalRequest: URLRequest? { _originalRequest } + override var currentRequest: URLRequest? { _originalRequest } private let _response: URLResponse override var response: URLResponse? { _response } diff --git a/TestUtilities/Mocks/NetworkInstrumentationMocks.swift b/TestUtilities/Mocks/NetworkInstrumentationMocks.swift index 95a4baeba1..f38644a3f5 100644 --- a/TestUtilities/Mocks/NetworkInstrumentationMocks.swift +++ b/TestUtilities/Mocks/NetworkInstrumentationMocks.swift @@ -49,6 +49,7 @@ public final class URLSessionHandlerMock: DatadogURLSessionHandler { public var firstPartyHosts: FirstPartyHosts public var modifiedRequest: URLRequest? + public var parentSpan: TraceContext? public var shouldInterceptRequest: ((URLRequest) -> Bool)? public var onRequestMutation: ((URLRequest, Set) -> Void)? @@ -76,6 +77,10 @@ public final class URLSessionHandlerMock: DatadogURLSessionHandler { return modifiedRequest ?? request } + public func traceContext() -> TraceContext? { + parentSpan + } + public func interceptionDidStart(interception: URLSessionTaskInterception) { onInterceptionDidStart?(interception) interceptions[interception.identifier] = interception diff --git a/TestUtilities/Mocks/PrintFunctionMock.swift b/TestUtilities/Mocks/PrintFunctionMock.swift index b7db949b1f..0935315c5a 100644 --- a/TestUtilities/Mocks/PrintFunctionMock.swift +++ b/TestUtilities/Mocks/PrintFunctionMock.swift @@ -5,6 +5,7 @@ */ import Foundation +import DatadogInternal // MARK: - Global Dependencies Mocks @@ -21,7 +22,7 @@ public class PrintFunctionMock { public init() { } - public func print(message: String) { + public func print(message: String, level: CoreLoggerLevel) { printedMessages.append(message) } diff --git a/bitrise.yml b/bitrise.yml index df1f058905..e6a5f01ac1 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -300,17 +300,6 @@ workflows: set -e make prepare-integration-tests ./tools/config/generate-http-server-mock-config.sh - - xcode-test: - title: Run benchmarks - DatadogBenchmarkTests on iOS Simulator - run_if: '{{enveq "DD_RUN_INTEGRATION_TESTS" "1"}}' - inputs: - - scheme: DatadogBenchmarkTests - - destination: platform=iOS Simulator,name=iPhone 11,OS=latest - - should_build_before_test: 'no' - - is_clean_build: 'no' - - generate_code_coverage_files: 'yes' - - project_path: Datadog.xcworkspace - - xcpretty_test_options: --color --report html --output "${BITRISE_DEPLOY_DIR}/Benchmark-tests.html" - xcode-test: title: Run integration tests for RUM, Logging, Tracing and SR (on iOS Simulator) run_if: '{{enveq "DD_RUN_INTEGRATION_TESTS" "1"}}' diff --git a/instrumented-tests/http-server-mock/Sources/HTTPServerMock/ServerMock.swift b/instrumented-tests/http-server-mock/Sources/HTTPServerMock/ServerMock.swift index 77b971786e..87b1ec631f 100644 --- a/instrumented-tests/http-server-mock/Sources/HTTPServerMock/ServerMock.swift +++ b/instrumented-tests/http-server-mock/Sources/HTTPServerMock/ServerMock.swift @@ -64,6 +64,12 @@ public class ServerMock { return ServerSession(server: self) } + public func clearAllRequests() { + var request = URLRequest(url: baseURL.appendingPathComponent("/requests")) + request.httpMethod = "DELETE" + URLSession.shared.dataTask(with: request).resume() + } + // MARK: - Endpoints /// Fetches all requests recorded by the server. diff --git a/instrumented-tests/http-server-mock/python/start_mock_server.py b/instrumented-tests/http-server-mock/python/start_mock_server.py index de22cddb38..d62c67925f 100755 --- a/instrumented-tests/http-server-mock/python/start_mock_server.py +++ b/instrumented-tests/http-server-mock/python/start_mock_server.py @@ -47,6 +47,14 @@ def do_GET(self): (r"/inspect$", self.__GET_inspect), ]) + def do_DELETE(self): + """ + Routes all incoming DELETE requests + """ + self.__route([ + (r"/requests$", self.__DELETE_requests), + ]) + def __POST_any(self, parameters): """ POST /* @@ -84,6 +92,16 @@ def __GET_inspect(self, parameters): return json.dumps(inspection_info).encode("utf-8") + def __DELETE_requests(self, parameters): + """ + DELETE /requests + + Remove all. + """ + global history + history.clear() + return bytes() + def __route(self, routes): try: for url_regexp, method in routes: @@ -132,6 +150,9 @@ def all_requests(self): def request(self, request_id): return self.__requests[int(request_id)] + def clear(self): + self.__requests.clear() + # If any previous instance of this server is running - kill it os.system('pkill -f start_mock_server.py') time.sleep(1) # wait a bit until socket is eventually released diff --git a/tools/distribution/requirements.txt b/tools/distribution/requirements.txt index 6d65549498..884b308d41 100644 --- a/tools/distribution/requirements.txt +++ b/tools/distribution/requirements.txt @@ -1,5 +1,5 @@ gitdb==4.0.10 -GitPython==3.1.37 +GitPython==3.1.41 smmap==5.0.0 packaging==23.1 pytest==7.4.0 diff --git a/tools/rum-models-generator/Sources/CodeGeneration/Generate/Transformers/Swift/JSONToSwiftTypeTransformer.swift b/tools/rum-models-generator/Sources/CodeGeneration/Generate/Transformers/Swift/JSONToSwiftTypeTransformer.swift index 9122f3379a..c2eb817ba9 100644 --- a/tools/rum-models-generator/Sources/CodeGeneration/Generate/Transformers/Swift/JSONToSwiftTypeTransformer.swift +++ b/tools/rum-models-generator/Sources/CodeGeneration/Generate/Transformers/Swift/JSONToSwiftTypeTransformer.swift @@ -92,8 +92,23 @@ internal class JSONToSwiftTypeTransformer { cases: jsonEnumeration.values.map { value in switch value { case .string(let value): - return SwiftEnum.Case(label: value, rawValue: .string(value: value)) + // In Swift, enum case names cannot start with digits. In such situation prefix the + // case with the name of enumeration so it is transformed into valid `SwiftEnum.Case`. + var labelValue = value + if let first = value.first?.unicodeScalars.first, CharacterSet.decimalDigits.contains(first) { + labelValue = "\(jsonEnumeration.name.lowercasingFirst)\(value)" + } + // In Swift, naming case a "none" might clash with `Optional.none` type if the property of + // this enum value is declared as optional. For that reason, prefix "none" case with the + // name of enumeration to avoid compiler ambiguity. + if labelValue == "none" { + labelValue = "\(jsonEnumeration.name.lowercasingFirst)\(labelValue.uppercasingFirst)" + } + + return SwiftEnum.Case(label: labelValue, rawValue: .string(value: value)) case .integer(let value): + // In Swift, enum case names cannot start with digits, so prefix the case with the name + // of enumeration so it is transformed into valid `SwiftEnum.Case`. return SwiftEnum.Case(label: "\(jsonEnumeration.name)\(value)", rawValue: .integer(value: value)) } }, diff --git a/tools/rum-models-generator/Tests/CodeGenerationTests/Generate/Transformers/JSONToSwiftTypeTransformerTests.swift b/tools/rum-models-generator/Tests/CodeGenerationTests/Generate/Transformers/JSONToSwiftTypeTransformerTests.swift index 442255718a..8ade6d58e6 100644 --- a/tools/rum-models-generator/Tests/CodeGenerationTests/Generate/Transformers/JSONToSwiftTypeTransformerTests.swift +++ b/tools/rum-models-generator/Tests/CodeGenerationTests/Generate/Transformers/JSONToSwiftTypeTransformerTests.swift @@ -158,6 +158,114 @@ final class JSONToSwiftTypeTransformerTests: XCTestCase { XCTAssertEqual(expected, actual[0]) } + func testTransformingJSONObjectWithStringEnumerationIntoSwiftStruct() throws { + let object = JSONObject( + name: "Container", + comment: nil, + properties: [ + JSONObject.Property( + name: "enumeration", + comment: nil, + type: JSONEnumeration( + name: "Foo", + comment: "Description of Foo", + values: [ + .string(value: "case1"), + .string(value: "case2"), + .string(value: "3case"), // case name starting with number + .string(value: "none"), // explicit case named as "none" + ] + ), + defaultValue: nil, + isRequired: false, + isReadOnly: false + ) + ] + ) + + let expected = SwiftStruct( + name: "Container", + properties: [ + SwiftStruct.Property( + name: "enumeration", + type: SwiftEnum( + name: "Foo", + comment: "Description of Foo", + cases: [ + SwiftEnum.Case(label: "case1", rawValue: .string(value: "case1")), + SwiftEnum.Case(label: "case2", rawValue: .string(value: "case2")), + SwiftEnum.Case(label: "foo3case", rawValue: .string(value: "3case")), + SwiftEnum.Case(label: "fooNone", rawValue: .string(value: "none")), + ], + conformance: [] + ), + isOptional: true, + mutability: .mutable, + codingKey: .static(value: "enumeration") + ) + ], + conformance: [] + ) + + let actual = try JSONToSwiftTypeTransformer().transform(jsonType: object) + + XCTAssertEqual(actual.count, 1) + XCTAssertEqual(expected, actual[0]) + } + + func testTransformingJSONObjectWithIntegerEnumerationIntoSwiftStruct() throws { + let object = JSONObject( + name: "Container", + comment: nil, + properties: [ + JSONObject.Property( + name: "enumeration", + comment: nil, + type: JSONEnumeration( + name: "Foo", + comment: "Description of Foo", + values: [ + .integer(value: 1), + .integer(value: 2), + .integer(value: 3), + ] + ), + defaultValue: nil, + isRequired: false, + isReadOnly: false + ) + ] + ) + + let expected = SwiftStruct( + name: "Container", + properties: [ + SwiftStruct.Property( + name: "enumeration", + type: SwiftEnum( + name: "Foo", + comment: "Description of Foo", + cases: [ + SwiftEnum.Case(label: "Foo1", rawValue: .integer(value: 1)), + SwiftEnum.Case(label: "Foo2", rawValue: .integer(value: 2)), + SwiftEnum.Case(label: "Foo3", rawValue: .integer(value: 3)), + ], + conformance: [] + ), + isOptional: true, + mutability: .mutable, + codingKey: .static(value: "enumeration") + ) + ], + conformance: [] + ) + + let actual = try JSONToSwiftTypeTransformer().transform(jsonType: object) + + XCTAssertEqual(actual.count, 1) + XCTAssertEqual(expected, actual[0]) + } + // MARK: - Transforming `additionalProperties` func testTransformingNestedJSONObjectWithIntAdditionalPropertiesIntoSwiftDictionaryInsideRootStruct() throws { diff --git a/tools/rum-models-generator/run.py b/tools/rum-models-generator/run.py index ebadc2fa21..bb5b4dd7b6 100755 --- a/tools/rum-models-generator/run.py +++ b/tools/rum-models-generator/run.py @@ -37,6 +37,9 @@ class Context: # Resolved path to JSON schema describing Session Replay events sr_schema_path: str + # Git reference to clone schemas repo at. + git_ref: str + # Resolved path to source code file with RUM model definitions (Swift) rum_swift_generated_file_path: str @@ -50,6 +53,7 @@ def __repr__(self): return f""" - cli_executable_path = {self.cli_executable_path}, - rum_schema_path = {self.rum_schema_path} + - git_ref = {self.git_ref} - sr_schema_path = {self.sr_schema_path} - rum_swift_generated_file_path = {self.rum_swift_generated_file_path} - rum_objc_generated_file_path = {self.rum_objc_generated_file_path} @@ -159,7 +163,7 @@ def validate_code(ctx: Context, language: str, convention: str, json_schema: str def generate_rum_models(ctx: Context): - sha = clone_schemas_repo(git_ref='master') + sha = clone_schemas_repo(git_ref=ctx.git_ref) with open(ctx.rum_swift_generated_file_path, 'w') as file: code = generate_code(ctx, language='swift', convention='rum', json_schema=ctx.rum_schema_path, git_sha=sha) @@ -171,7 +175,7 @@ def generate_rum_models(ctx: Context): def generate_sr_models(ctx: Context): - sha = clone_schemas_repo(git_ref='master') + sha = clone_schemas_repo(git_ref=ctx.git_ref) with open(ctx.sr_swift_generated_file_path, 'w') as file: code = generate_code(ctx, language='swift', convention='sr', json_schema=ctx.sr_schema_path, git_sha=sha) @@ -213,6 +217,7 @@ def validate_sr_models(ctx: Context): parser = argparse.ArgumentParser() parser.add_argument("command", choices=['generate', 'verify'], help="Run mode") parser.add_argument("product", choices=['rum', 'sr'], help="Either 'rum' (RUM) or 'sr' (Session Replay)") + parser.add_argument("--git_ref", help="The git reference to clone `rum-events-format` repo at (only effective for `generate` command).") args = parser.parse_args() try: @@ -220,6 +225,7 @@ def validate_sr_models(ctx: Context): cli_executable_path=build_swift_cli(), rum_schema_path=os.path.abspath(f'{script_dir}/{RUM_SCHEMA_PATH}'), sr_schema_path=os.path.abspath(f'{script_dir}/{SR_SCHEMA_PATH}'), + git_ref=args.git_ref if args.command else None, rum_swift_generated_file_path=os.path.abspath(f'{repository_root}/{RUM_SWIFT_GENERATED_FILE_PATH}'), rum_objc_generated_file_path=os.path.abspath(f'{repository_root}/{RUM_OBJC_GENERATED_FILE_PATH}'), sr_swift_generated_file_path=os.path.abspath(f'{repository_root}/{SR_SWIFT_GENERATED_FILE_PATH}'), From 9c546bddbf63b2a78651ff216694667f5786d039 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 7 Feb 2024 10:45:59 +0100 Subject: [PATCH 031/153] fix: handle escaped JSON string in asserts --- .../Tests/Span/SpanEventBuilderTests.swift | 2 +- TestUtilities/Helpers/DDAssert.swift | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift b/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift index 92a4bb8b65..7ebd91a2cf 100644 --- a/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift +++ b/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift @@ -556,7 +556,7 @@ class SpanEventBuilderTests: XCTestCase { let expectedTags = "[{\"trace_id\":\"00000000000000000000000000000065\",\"span_id\":\"0000000000000067\",\"tracestate\":\"foo=bar,bar=baz\",\"flags\":1,\"attributes\":{\"foo\":\"bar\"}}]" let actualTags = span.tags["_dd.span_links"] - DDAssertJSONEqual(expectedTags, actualTags) + DDAssertJSONStringEqual(expectedTags, actualTags!) } // MARK: - RUM context enrichment diff --git a/TestUtilities/Helpers/DDAssert.swift b/TestUtilities/Helpers/DDAssert.swift index 213ef9b875..13f9864ffd 100644 --- a/TestUtilities/Helpers/DDAssert.swift +++ b/TestUtilities/Helpers/DDAssert.swift @@ -208,3 +208,34 @@ public func DDAssertDictionariesNotEqual(_ expression1: @autoclosure () throws - throw DDAssertError.expectedFailure("Dictionaries are equal") } } + +public func DDAssertJSONStringEqual(_ jsonString1: String, _ jsonString2: String, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { + _DDEvaluateAssertion(message: message(), file: file, line: line) { + try _DDAssertJSONStringEqual(jsonString1, jsonString2) + } +} + +private func _DDAssertJSONStringEqual(_ jsonString1: String, _ jsonString2: String) throws { + guard let data1 = jsonString1.data(using: .utf8), + let data2 = jsonString2.data(using: .utf8) else { + throw DDAssertError.expectedFailure("Failed to convert JSON strings to data") + } + + do { + if let json1 = try JSONSerialization.jsonObject(with: data1, options: []) as? [String: Any], + let json2 = try JSONSerialization.jsonObject(with: data2, options: []) as? [String: Any] { + guard NSDictionary(dictionary: json1).isEqual(to: json2) else { + throw DDAssertError.expectedFailure("JSONs are not equal") + } + } else if let json1 = try JSONSerialization.jsonObject(with: data1, options: []) as? [Any], + let json2 = try JSONSerialization.jsonObject(with: data2, options: []) as? [Any] { + guard NSArray(array: json1).isEqual(to: json2) else { + throw DDAssertError.expectedFailure("JSONs are not equal") + } + } else { + throw DDAssertError.expectedFailure("JSONs are not equal") + } + } catch { + throw DDAssertError.expectedFailure("Failed to parse JSON strings") + } +} From 5b8d7da89afc26467f70fb0bb5c610f260a8c4d4 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 7 Feb 2024 11:03:31 +0100 Subject: [PATCH 032/153] fix: use mutate pattern for computed properties --- .../Sources/OpenTelemetry/OTelSpan.swift | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 7a87a4ecff..48fc98a2ed 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -44,8 +44,10 @@ internal class OTelSpan: OpenTelemetryApi.Span { @ReadWriteLock private var _name: String + @ReadWriteLock var attributes: [String: OpenTelemetryApi.AttributeValue] let context: OpenTelemetryApi.SpanContext + @ReadWriteLock var kind: OpenTelemetryApi.SpanKind let ddSpan: DDSpan let tracer: DatadogTracer @@ -53,6 +55,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { /// `isRecording` indicates whether the span is recording or not /// and events can be added to it. + @ReadWriteLock var isRecording: Bool /// Saves status of the span indicating whether the span has recorded errors. @@ -62,17 +65,19 @@ internal class OTelSpan: OpenTelemetryApi.Span { _status } set { - guard isRecording else { - return + __status.mutate { + guard isRecording else { + return + } + + // If the code has been set to a higher value before (Ok > Error > Unset), + // the code will not be changed. + guard newValue.priority >= $0.priority else { + return + } + + $0 = newValue } - - // If the code has been set to a higher value before (Ok > Error > Unset), - // the code will not be changed. - guard newValue.priority >= _status.priority else { - return - } - - _status = newValue } } @@ -82,11 +87,13 @@ internal class OTelSpan: OpenTelemetryApi.Span { _name } set { - guard isRecording else { - return + __name.mutate { + guard isRecording else { + return + } + $0 = newValue + ddSpan.setOperationName($0) } - _name = newValue - ddSpan.setOperationName(name) } } From 1a82df30d3fad33ef330b71023e929c0378a073c Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 9 Feb 2024 16:43:49 +0100 Subject: [PATCH 033/153] RUM-1836 feat(otel-tracer) integrate OpenTelemetrySwiftApi in package managers --- Cartfile | 1 + Cartfile.resolved | 1 + Datadog/Datadog.xcodeproj/project.pbxproj | 44 ++---------- DatadogTrace.podspec | 8 +-- .../OTelAttributeValue+Datadog.swift | 2 + .../Sources/OpenTelemetry/OTelSpan.swift | 4 ++ Makefile | 7 +- .../CTProject.xcodeproj/project.pbxproj | 10 +++ dependency-manager-tests/carthage/Makefile | 1 + .../CPProject.xcodeproj/project.pbxproj | 72 +++++++++++++++++++ .../xcframeworks/Makefile | 1 + .../XCProject.xcodeproj/project.pbxproj | 10 +++ tools/distribution/build-xcframework.sh | 1 + 13 files changed, 117 insertions(+), 45 deletions(-) diff --git a/Cartfile b/Cartfile index 0299765af6..523af3cf9a 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1,2 @@ github "microsoft/plcrashreporter" ~> 1.11.1 +binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/ganeshnj/ci/pod-push/OpenTelemetryApi.json" ~> 1.9.1 diff --git a/Cartfile.resolved b/Cartfile.resolved index 8e3025f36c..153b254b6a 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1,2 @@ +binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/ganeshnj/ci/pod-push/OpenTelemetryApi.json" "1.9.1" github "microsoft/plcrashreporter" "1.11.1" diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 4a26f7e720..2112cbcbee 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -36,8 +36,8 @@ 3C5D636A2B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */; }; 3C5D636C2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */; }; 3C5D636D2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */; }; - 3C6C7FDB2B45738C006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDA2B45738C006F5CBC /* OpenTelemetryApi */; }; - 3C6C7FDD2B457392006F5CBC /* OpenTelemetryApi in Frameworks */ = {isa = PBXBuildFile; productRef = 3C6C7FDC2B457392006F5CBC /* OpenTelemetryApi */; }; + 3C5D691F2B76825500C4E07E /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; }; + 3C5D69222B76826000C4E07E /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; }; 3C6C7FE72B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; 3C6C7FE82B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; 3C6C7FE92B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; }; @@ -1892,11 +1892,9 @@ 3C0D5DEE2A5442A900446CF9 /* EventMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMocks.swift; sourceTree = ""; }; 3C0D5DF42A5443B100446CF9 /* DataFormatTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataFormatTests.swift; sourceTree = ""; }; 3C1890132ABDE99200CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDURLSessionInstrumentationTests+apiTests.m"; sourceTree = ""; }; - 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaTypeExtensions.swift; sourceTree = ""; }; + 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OpenTelemetryApi.xcframework; path = ../Carthage/Build/OpenTelemetryApi.xcframework; sourceTree = ""; }; 3C32359C2B55386C000B4258 /* OTelSpanLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanLink.swift; sourceTree = ""; }; 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanLinkTests.swift; sourceTree = ""; }; - 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzler.swift; sourceTree = ""; }; - 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzlerTests.swift; sourceTree = ""; }; 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+Datadog.swift"; sourceTree = ""; }; 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+DatadogTests.swift"; sourceTree = ""; }; 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpan.swift; sourceTree = ""; }; @@ -2957,8 +2955,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3C5D691F2B76825500C4E07E /* OpenTelemetryApi.xcframework in Frameworks */, D2C1A50E29C4C4EF00946C31 /* DatadogInternal.framework in Frameworks */, - 3C6C7FDB2B45738C006F5CBC /* OpenTelemetryApi in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3001,8 +2999,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3C5D69222B76826000C4E07E /* OpenTelemetryApi.xcframework in Frameworks */, D2C1A57429C4F30000946C31 /* DatadogInternal.framework in Frameworks */, - 3C6C7FDD2B457392006F5CBC /* OpenTelemetryApi in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4087,6 +4085,7 @@ 61133C6F2423993200786299 /* Frameworks */ = { isa = PBXGroup; children = ( + 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */, D2579591298ABCED008A1BE5 /* XCTest.framework */, D2579593298ABCF5008A1BE5 /* XCTest.framework */, 61B03ECC274FF00E00EB1AE1 /* SwiftUI.framework */, @@ -6444,7 +6443,6 @@ ); name = "DatadogTrace iOS"; packageProductDependencies = ( - 3C6C7FDA2B45738C006F5CBC /* OpenTelemetryApi */, ); productName = DatadogTrace; productReference = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; @@ -6539,7 +6537,6 @@ ); name = "DatadogTrace tvOS"; packageProductDependencies = ( - 3C6C7FDC2B457392006F5CBC /* OpenTelemetryApi */, ); productName = DatadogTrace; productReference = D2C1A55A29C4F2DF00946C31 /* DatadogTrace.framework */; @@ -6818,7 +6815,6 @@ ); mainGroup = 61133B78242393DE00786299; packageReferences = ( - 3C6C7FD92B457381006F5CBC /* XCRemoteSwiftPackageReference "opentelemetry-swift" */, ); productRefGroup = 61133B83242393DE00786299 /* Products */; projectDirPath = ""; @@ -12954,34 +12950,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - 3C6C7FD92B457381006F5CBC /* XCRemoteSwiftPackageReference "opentelemetry-swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/open-telemetry/opentelemetry-swift.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.9.0; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 3C6C7FDA2B45738C006F5CBC /* OpenTelemetryApi */ = { - isa = XCSwiftPackageProductDependency; - package = 3C6C7FD92B457381006F5CBC /* XCRemoteSwiftPackageReference "opentelemetry-swift" */; - productName = OpenTelemetryApi; - }; - 3C6C7FDC2B457392006F5CBC /* OpenTelemetryApi */ = { - isa = XCSwiftPackageProductDependency; - package = 3C6C7FD92B457381006F5CBC /* XCRemoteSwiftPackageReference "opentelemetry-swift" */; - productName = OpenTelemetryApi; - }; - 6152C83D24BE1C91006A1679 /* HTTPServerMock */ = { - isa = XCSwiftPackageProductDependency; - productName = HTTPServerMock; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = 61133B79242393DE00786299 /* Project object */; } diff --git a/DatadogTrace.podspec b/DatadogTrace.podspec index 979b2dae4c..523cd3ebed 100644 --- a/DatadogTrace.podspec +++ b/DatadogTrace.podspec @@ -2,12 +2,12 @@ Pod::Spec.new do |s| s.name = "DatadogTrace" s.version = "2.7.0" s.summary = "Datadog Trace Module." - + s.homepage = "https://www.datadoghq.com" s.social_media_url = "https://twitter.com/datadoghq" s.license = { :type => "Apache", :file => 'LICENSE' } - s.authors = { + s.authors = { "Maciek Grzybowski" => "maciek.grzybowski@datadoghq.com", "Maciej Burda" => "maciej.burda@datadoghq.com", "Maxime Epain" => "maxime.epain@datadoghq.com", @@ -19,9 +19,9 @@ Pod::Spec.new do |s| s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } - + s.source_files = ["DatadogTrace/Sources/**/*.swift"] s.dependency 'DatadogInternal', s.version.to_s - + s.dependency 'OpenTelemetrySwiftApi', '1.9.1' end diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelAttributeValue+Datadog.swift b/DatadogTrace/Sources/OpenTelemetry/OTelAttributeValue+Datadog.swift index 6d5ac98ea0..43844c65c9 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelAttributeValue+Datadog.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelAttributeValue+Datadog.swift @@ -64,6 +64,8 @@ extension Dictionary where Key == String, Value == OpenTelemetryApi.AttributeVal tags["\(key).\(nestedKey)"] = nestedValue } } + @unknown default: + break } } return tags diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 48fc98a2ed..f111522ee7 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -33,6 +33,8 @@ internal extension OpenTelemetryApi.Status { return 2 case .unset: return 1 + @unknown default: + return 1 } } } @@ -202,6 +204,8 @@ internal class OTelSpan: OpenTelemetryApi.Span { // send error log to Datadog // Empty kind or description is equivalent to not present ddSpan.setError(kind: "", message: description) + @unknown default: + break } // SpanKind maps to the `span.kind` tag in Datadog diff --git a/Makefile b/Makefile index 5e743d7134..435a742937 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ all: dependencies templates # The release version of `dd-sdk-swift-testing` to use for tests instrumentation. DD_SDK_SWIFT_TESTING_VERSION = 2.3.2 +DD_DISABLE_TEST_INSTRUMENTING = true define DD_SDK_TESTING_XCCONFIG_CI DD_SDK_TESTING_PATH=$$(DD_SDK_TESTING_OVERRIDE_PATH:default=$$(SRCROOT)/../instrumented-tests/)\n @@ -42,7 +43,7 @@ define DD_SDK_BASE_XCCONFIG_CI SWIFT_TREAT_WARNINGS_AS_ERRORS = YES\n \n // If running on CI. This value is injected to some targets through their `Info.plist`:\n -IS_CI = true\n +IS_CI = true\n \n // Use iOS 11 deployment target on CI as long as we use Xcode 14.x for integration\n IPHONEOS_DEPLOYMENT_TARGET=11.0\n @@ -70,7 +71,7 @@ ifeq (${ci}, true) @echo $$DD_SDK_BASE_XCCONFIG_CI >> xcconfigs/Base.local.xcconfig; @echo $$DD_SDK_DATADOG_XCCONFIG_CI > xcconfigs/Datadog.local.xcconfig; ifndef DD_DISABLE_TEST_INSTRUMENTING - @echo $$DD_SDK_TESTING_XCCONFIG_CI > xcconfigs/DatadogSDKTesting.local.xcconfig; + @echo $$DD_SDK_TESTING_XCCONFIG_CI > xcconfigs/DatadogSDKTesting.local.xcconfig; @rm -rf instrumented-tests/DatadogSDKTesting.xcframework @rm -rf instrumented-tests/DatadogSDKTesting.zip @rm -rf instrumented-tests/LICENSE @@ -78,7 +79,7 @@ ifndef DD_DISABLE_TEST_INSTRUMENTING @unzip -q instrumented-tests/DatadogSDKTesting.zip -d instrumented-tests @[ -e "instrumented-tests/DatadogSDKTesting.xcframework" ] && echo "DatadogSDKTesting.xcframework - OK" || { echo "DatadogSDKTesting.xcframework - missing"; exit 1; } endif - + endif # Prepare project on GitLab CI (this will replace `make dependencies` once we're fully on GitLab). diff --git a/dependency-manager-tests/carthage/CTProject.xcodeproj/project.pbxproj b/dependency-manager-tests/carthage/CTProject.xcodeproj/project.pbxproj index 8ac10b2093..8f31380c2a 100644 --- a/dependency-manager-tests/carthage/CTProject.xcodeproj/project.pbxproj +++ b/dependency-manager-tests/carthage/CTProject.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 3C0F8E222B768A05004948CD /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C0F8E212B768A05004948CD /* OpenTelemetryApi.xcframework */; }; + 3C0F8E232B768A05004948CD /* OpenTelemetryApi.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C0F8E212B768A05004948CD /* OpenTelemetryApi.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3C0F8E242B768A17004948CD /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C0F8E212B768A05004948CD /* OpenTelemetryApi.xcframework */; }; + 3C0F8E252B768A17004948CD /* OpenTelemetryApi.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C0F8E212B768A05004948CD /* OpenTelemetryApi.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3CB135E729F6B90F0000234F /* DatadogWebViewTracking.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CB135E429F6B8F90000234F /* DatadogWebViewTracking.xcframework */; }; 3CB135E829F6B90F0000234F /* DatadogWebViewTracking.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3CB135E429F6B8F90000234F /* DatadogWebViewTracking.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 617803D22A6FF2EA005FE258 /* DatadogSessionReplay.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 617803D12A6FF2EA005FE258 /* DatadogSessionReplay.xcframework */; }; @@ -101,6 +105,7 @@ 9E9D5E8D25F90FC6002F12A0 /* DatadogCore.xcframework in Embed Frameworks */, D20D6FEB29F6C2F200D2886E /* DatadogRUM.xcframework in Embed Frameworks */, D2675BF42A019CF500190669 /* DatadogCrashReporting.xcframework in Embed Frameworks */, + 3C0F8E232B768A05004948CD /* OpenTelemetryApi.xcframework in Embed Frameworks */, 9E9D5E8B25F90FC6002F12A0 /* DatadogObjc.xcframework in Embed Frameworks */, D26F741929ACC61E00D25622 /* DatadogLogs.xcframework in Embed Frameworks */, ); @@ -114,6 +119,7 @@ dstSubfolderSpec = 10; files = ( D290BA2627CD09740019936D /* CrashReporter.xcframework in Embed Frameworks */, + 3C0F8E252B768A17004948CD /* OpenTelemetryApi.xcframework in Embed Frameworks */, D20D6FE729F6C2EB00D2886E /* DatadogRUM.xcframework in Embed Frameworks */, D20D6FE929F6C2ED00D2886E /* DatadogTrace.xcframework in Embed Frameworks */, D2675BFA2A019D0100190669 /* DatadogInternal.xcframework in Embed Frameworks */, @@ -128,6 +134,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 3C0F8E212B768A05004948CD /* OpenTelemetryApi.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OpenTelemetryApi.xcframework; path = Carthage/Build/OpenTelemetryApi.xcframework; sourceTree = ""; }; 3CB135E429F6B8F90000234F /* DatadogWebViewTracking.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogWebViewTracking.xcframework; path = Carthage/Build/DatadogWebViewTracking.xcframework; sourceTree = ""; }; 615519322461CDB4002A85CF /* Datadog.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.xcconfig; sourceTree = ""; }; 615519332461CDB4002A85CF /* Datadog.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.local.xcconfig; sourceTree = ""; }; @@ -169,6 +176,7 @@ D20D6FEA29F6C2F200D2886E /* DatadogRUM.xcframework in Frameworks */, 3CB135E729F6B90F0000234F /* DatadogWebViewTracking.xcframework in Frameworks */, 9E9D5E8C25F90FC6002F12A0 /* DatadogCore.xcframework in Frameworks */, + 3C0F8E222B768A05004948CD /* OpenTelemetryApi.xcframework in Frameworks */, D2675BF32A019CF500190669 /* DatadogCrashReporting.xcframework in Frameworks */, 9E9D5E8A25F90FC6002F12A0 /* DatadogObjc.xcframework in Frameworks */, ); @@ -193,6 +201,7 @@ buildActionMask = 2147483647; files = ( D2B946AE29ACF6C20080CB40 /* DatadogLogs.xcframework in Frameworks */, + 3C0F8E242B768A17004948CD /* OpenTelemetryApi.xcframework in Frameworks */, D20D6FE829F6C2ED00D2886E /* DatadogTrace.xcframework in Frameworks */, D2675BF92A019D0100190669 /* DatadogInternal.xcframework in Frameworks */, D290BA1F27CD09740019936D /* CrashReporter.xcframework in Frameworks */, @@ -287,6 +296,7 @@ 61C364492437547A00C4D4E6 /* Frameworks */ = { isa = PBXGroup; children = ( + 3C0F8E212B768A05004948CD /* OpenTelemetryApi.xcframework */, 617803D12A6FF2EA005FE258 /* DatadogSessionReplay.xcframework */, D20D6FE329F6C2D600D2886E /* DatadogRUM.xcframework */, D2966C2329CA1C5300FC6B3C /* DatadogTrace.xcframework */, diff --git a/dependency-manager-tests/carthage/Makefile b/dependency-manager-tests/carthage/Makefile index 9597ebd487..e1c5891d05 100644 --- a/dependency-manager-tests/carthage/Makefile +++ b/dependency-manager-tests/carthage/Makefile @@ -30,4 +30,5 @@ test: @[ -e "Carthage/Build/DatadogCrashReporting.xcframework" ] && echo "DatadogCrashReporting.xcframework - OK" || { echo "DatadogCrashReporting.xcframework - missing"; false; } @[ -e "Carthage/Build/CrashReporter.xcframework" ] && echo "CrashReporter.xcframework - OK" || { echo "CrashReporter.xcframework - missing"; false; } @[ -e "Carthage/Build/DatadogWebViewTracking.xcframework" ] && echo "DatadogWebViewTracking.xcframework - OK" || { echo "DatadogWebViewTracking.xcframework - missing"; false; } + @[ -e "Carthage/Build/OpenTelemetryApi.xcframework" ] && echo "OpenTelemetryApi.xcframework - OK" || { echo "OpenTelemetryApi.xcframework - missing"; false; } @echo "🧪 SUCCEEDED" diff --git a/dependency-manager-tests/cocoapods/CPProject.xcodeproj/project.pbxproj b/dependency-manager-tests/cocoapods/CPProject.xcodeproj/project.pbxproj index c1a092884a..c735407253 100644 --- a/dependency-manager-tests/cocoapods/CPProject.xcodeproj/project.pbxproj +++ b/dependency-manager-tests/cocoapods/CPProject.xcodeproj/project.pbxproj @@ -384,6 +384,8 @@ 61373B2526E0E78300E0F46E /* Sources */, 61373B2626E0E78300E0F46E /* Frameworks */, 61373B2726E0E78300E0F46E /* Resources */, + 9AB54E91F6CF9F39B153DDCF /* [CP] Copy Pods Resources */, + 8A0FC3D9BCDA15BFE1388572 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -586,6 +588,8 @@ D245427827C8E93D0039E0A6 /* Sources */, D245427C27C8E93D0039E0A6 /* Frameworks */, D245427E27C8E93D0039E0A6 /* Resources */, + 902D6AE45E60E1B9A2AD5BD5 /* [CP] Copy Pods Resources */, + CBDFC631D4E69499312261B3 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -797,6 +801,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 8A0FC3D9BCDA15BFE1388572 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Static iOS/Pods-Common-App Static iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Static iOS/Pods-Common-App Static iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-App Static iOS/Pods-Common-App Static iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 8D0504D0CD7CAFBBB5B63F13 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -819,6 +840,57 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 902D6AE45E60E1B9A2AD5BD5 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Static tvOS/Pods-Common-App Static tvOS-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Static tvOS/Pods-Common-App Static tvOS-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-App Static tvOS/Pods-Common-App Static tvOS-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9AB54E91F6CF9F39B153DDCF /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Static iOS/Pods-Common-App Static iOS-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Static iOS/Pods-Common-App Static iOS-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-App Static iOS/Pods-Common-App Static iOS-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + CBDFC631D4E69499312261B3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Static tvOS/Pods-Common-App Static tvOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Static tvOS/Pods-Common-App Static tvOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-App Static tvOS/Pods-Common-App Static tvOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; D235937827C8EB0500BF32D7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/dependency-manager-tests/xcframeworks/Makefile b/dependency-manager-tests/xcframeworks/Makefile index 2f129db523..b195057a5e 100644 --- a/dependency-manager-tests/xcframeworks/Makefile +++ b/dependency-manager-tests/xcframeworks/Makefile @@ -26,4 +26,5 @@ test: @[ -e "dd-sdk-ios/build/xcframeworks/DatadogObjc.xcframework" ] && echo "DatadogObjc.xcframework - OK" || { echo "DatadogObjc.xcframework - missing"; false; } @[ -e "dd-sdk-ios/build/xcframeworks/DatadogCrashReporting.xcframework" ] && echo "DatadogCrashReporting.xcframework - OK" || { echo "DatadogCrashReporting.xcframework - missing"; false; } @[ -e "dd-sdk-ios/build/xcframeworks/CrashReporter.xcframework" ] && echo "CrashReporter.xcframework - OK" || { echo "CrashReporter.xcframework - missing"; false; } + @[ -e "dd-sdk-ios/build/xcframeworks/OpenTelemetryApi.xcframework" ] && echo "OpenTelemetryApi.xcframework - OK" || { echo "OpenTelemetryApi.xcframework - missing"; false; } @echo "🧪 SUCCEEDED" diff --git a/dependency-manager-tests/xcframeworks/XCProject.xcodeproj/project.pbxproj b/dependency-manager-tests/xcframeworks/XCProject.xcodeproj/project.pbxproj index 4770064ca0..3794d78cde 100644 --- a/dependency-manager-tests/xcframeworks/XCProject.xcodeproj/project.pbxproj +++ b/dependency-manager-tests/xcframeworks/XCProject.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 3C14BEF42B76815800D8F265 /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C14BEF32B76815800D8F265 /* OpenTelemetryApi.xcframework */; }; + 3C14BEF52B76815800D8F265 /* OpenTelemetryApi.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C14BEF32B76815800D8F265 /* OpenTelemetryApi.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3CAD15512B7BCAE4006480B5 /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C14BEF32B76815800D8F265 /* OpenTelemetryApi.xcframework */; }; + 3CAD15522B7BCAE4006480B5 /* OpenTelemetryApi.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C14BEF32B76815800D8F265 /* OpenTelemetryApi.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 617803D52A701052005FE258 /* DatadogSessionReplay.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 617803D42A701051005FE258 /* DatadogSessionReplay.xcframework */; }; 617803D62A701052005FE258 /* DatadogSessionReplay.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 617803D42A701051005FE258 /* DatadogSessionReplay.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 61C36419243752A500C4D4E6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C36418243752A500C4D4E6 /* AppDelegate.swift */; }; @@ -101,6 +105,7 @@ D2F09A2629F6C65B0036B910 /* DatadogRUM.xcframework in Embed Frameworks */, D2F9244B29A4B9A4006733B2 /* CrashReporter.xcframework in Embed Frameworks */, D2675BFE2A019F7500190669 /* DatadogWebViewTracking.xcframework in Embed Frameworks */, + 3C14BEF52B76815800D8F265 /* OpenTelemetryApi.xcframework in Embed Frameworks */, D2675C002A01A03300190669 /* DatadogCrashReporting.xcframework in Embed Frameworks */, D2EBEDAF29B7867700B15732 /* DatadogInternal.xcframework in Embed Frameworks */, ); @@ -114,6 +119,7 @@ dstSubfolderSpec = 10; files = ( D2F9245A29A4B9D8006733B2 /* DatadogObjc.xcframework in Embed Frameworks */, + 3CAD15522B7BCAE4006480B5 /* OpenTelemetryApi.xcframework in Embed Frameworks */, D2EBEDAC29B7863B00B15732 /* DatadogLogs.xcframework in Embed Frameworks */, D2F9245629A4B9D8006733B2 /* DatadogCore.xcframework in Embed Frameworks */, D2966C2C29CA1E1C00FC6B3C /* DatadogTrace.xcframework in Embed Frameworks */, @@ -128,6 +134,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 3C14BEF32B76815800D8F265 /* OpenTelemetryApi.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OpenTelemetryApi.xcframework; path = "dd-sdk-ios/build/xcframeworks/OpenTelemetryApi.xcframework"; sourceTree = ""; }; 3C4C2BB629F6B9C100152C4B /* DatadogWebViewTracking.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogWebViewTracking.xcframework; path = "dd-sdk-ios/build/xcframeworks/DatadogWebViewTracking.xcframework"; sourceTree = ""; }; 615519322461CDB4002A85CF /* Datadog.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.xcconfig; sourceTree = ""; }; 615519332461CDB4002A85CF /* Datadog.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.local.xcconfig; sourceTree = ""; }; @@ -169,6 +176,7 @@ D2F09A2529F6C65B0036B910 /* DatadogRUM.xcframework in Frameworks */, D2F9244A29A4B9A4006733B2 /* CrashReporter.xcframework in Frameworks */, D2675BFD2A019F7500190669 /* DatadogWebViewTracking.xcframework in Frameworks */, + 3C14BEF42B76815800D8F265 /* OpenTelemetryApi.xcframework in Frameworks */, D2675BFF2A01A03300190669 /* DatadogCrashReporting.xcframework in Frameworks */, D2EBEDAE29B7867700B15732 /* DatadogInternal.xcframework in Frameworks */, ); @@ -193,6 +201,7 @@ buildActionMask = 2147483647; files = ( D2F9245929A4B9D8006733B2 /* DatadogObjc.xcframework in Frameworks */, + 3CAD15512B7BCAE4006480B5 /* OpenTelemetryApi.xcframework in Frameworks */, D2EBEDAB29B7863B00B15732 /* DatadogLogs.xcframework in Frameworks */, D2F9245529A4B9D8006733B2 /* DatadogCore.xcframework in Frameworks */, D2966C2B29CA1E1C00FC6B3C /* DatadogTrace.xcframework in Frameworks */, @@ -287,6 +296,7 @@ 61C364492437547A00C4D4E6 /* Frameworks */ = { isa = PBXGroup; children = ( + 3C14BEF32B76815800D8F265 /* OpenTelemetryApi.xcframework */, 617803D42A701051005FE258 /* DatadogSessionReplay.xcframework */, D2F09A2429F6C65A0036B910 /* DatadogRUM.xcframework */, D2966C2829CA1E1600FC6B3C /* DatadogTrace.xcframework */, diff --git a/tools/distribution/build-xcframework.sh b/tools/distribution/build-xcframework.sh index 04a58aa4d4..1a588c6eb4 100755 --- a/tools/distribution/build-xcframework.sh +++ b/tools/distribution/build-xcframework.sh @@ -90,6 +90,7 @@ rm -rf $OUTPUT carthage bootstrap --platform $PLATFORM --use-xcframeworks mkdir -p "$XCFRAMEWORK_OUTPUT" cp -r "Carthage/Build/CrashReporter.xcframework" "$XCFRAMEWORK_OUTPUT" +cp -r "Carthage/Build/OpenTelemetryApi.xcframework" "$XCFRAMEWORK_OUTPUT" bundle DatadogInternal bundle DatadogCore From c7629d1166ea8cc25ebd1b6767e3fa47c19b142d Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 15 Jan 2024 14:16:23 +0100 Subject: [PATCH 034/153] RUM-1836 feat(otel-tracer): refactor "how to get otel tracer" --- Cartfile | 2 +- Cartfile.resolved | 2 +- Datadog/Datadog.xcodeproj/project.pbxproj | 6 ++ .../Tests/Datadog/Tracing/OTelSpanTests.swift | 10 ++- .../OpenTelemetry/OTelTracerProvider.swift | 70 +++++++++++++++++++ DatadogTrace/Sources/Tracer.swift | 2 +- 6 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift diff --git a/Cartfile b/Cartfile index 523af3cf9a..caef0c626f 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ github "microsoft/plcrashreporter" ~> 1.11.1 -binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/ganeshnj/ci/pod-push/OpenTelemetryApi.json" ~> 1.9.1 +binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/main/OpenTelemetryApi.json" ~> 1.9.1 diff --git a/Cartfile.resolved b/Cartfile.resolved index 153b254b6a..28cfe79a44 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/ganeshnj/ci/pod-push/OpenTelemetryApi.json" "1.9.1" +binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/main/OpenTelemetryApi.json" "1.9.1" github "microsoft/plcrashreporter" "1.11.1" diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 2112cbcbee..57826ac104 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -78,6 +78,8 @@ 3CE11A1229F7BE0900202522 /* DatadogWebViewTracking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3CF673362B4807490016CE17 /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF673352B4807490016CE17 /* OTelSpanTests.swift */; }; 3CF673372B4807490016CE17 /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF673352B4807490016CE17 /* OTelSpanTests.swift */; }; + 3CFF5D492B555F4F00FC483A /* OTelTracerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFF5D482B555F4F00FC483A /* OTelTracerProvider.swift */; }; + 3CFF5D4A2B555F4F00FC483A /* OTelTracerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFF5D482B555F4F00FC483A /* OTelTracerProvider.swift */; }; 49274906288048B500ECD49B /* InternalProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49274903288048AA00ECD49B /* InternalProxyTests.swift */; }; 49274907288048B800ECD49B /* InternalProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49274903288048AA00ECD49B /* InternalProxyTests.swift */; }; 49D8C0B72AC5D2160075E427 /* RUM+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D8C0B62AC5D2160075E427 /* RUM+Internal.swift */; }; @@ -1917,6 +1919,7 @@ 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogWebViewTracking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3CE11A0529F7BE0300202522 /* DatadogWebViewTrackingTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogWebViewTrackingTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 3CF673352B4807490016CE17 /* OTelSpanTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanTests.swift; sourceTree = ""; }; + 3CFF5D482B555F4F00FC483A /* OTelTracerProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelTracerProvider.swift; sourceTree = ""; }; 49274903288048AA00ECD49B /* InternalProxyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalProxyTests.swift; sourceTree = ""; }; 49274908288048F400ECD49B /* RUMInternalProxyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMInternalProxyTests.swift; sourceTree = ""; }; 49D8C0B62AC5D2160075E427 /* RUM+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUM+Internal.swift"; sourceTree = ""; }; @@ -3096,6 +3099,7 @@ 3C6C7FDE2B459AAA006F5CBC /* OpenTelemetry */ = { isa = PBXGroup; children = ( + 3CFF5D482B555F4F00FC483A /* OTelTracerProvider.swift */, 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */, 3C32359C2B55386C000B4258 /* OTelSpanLink.swift */, 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */, @@ -8246,6 +8250,7 @@ D2C1A4FF29C4C4CB00946C31 /* Warnings.swift in Sources */, D2C1A51729C4C53F00946C31 /* OTFormat.swift in Sources */, D22C5BCB2A98A5400024CC1F /* Baggages.swift in Sources */, + 3CFF5D492B555F4F00FC483A /* OTelTracerProvider.swift in Sources */, D2C1A4FA29C4C4CB00946C31 /* SpanSanitizer.swift in Sources */, D2C1A50A29C4C4CB00946C31 /* TraceFeature.swift in Sources */, D2C1A50829C4C4CB00946C31 /* TracingURLSessionHandler.swift in Sources */, @@ -8450,6 +8455,7 @@ D2C1A54D29C4F2DF00946C31 /* Warnings.swift in Sources */, D2C1A54E29C4F2DF00946C31 /* OTFormat.swift in Sources */, D22C5BCC2A98A5400024CC1F /* Baggages.swift in Sources */, + 3CFF5D4A2B555F4F00FC483A /* OTelTracerProvider.swift in Sources */, D2C1A54F29C4F2DF00946C31 /* SpanSanitizer.swift in Sources */, D2C1A55029C4F2DF00946C31 /* TraceFeature.swift in Sources */, D2C1A55129C4F2DF00946C31 /* TracingURLSessionHandler.swift in Sources */, diff --git a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift index 4220d2c184..0fc8010941 100644 --- a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift +++ b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift @@ -21,7 +21,15 @@ final class OTelSpanTests: XCTestCase { Trace.enable(in: core) // Given - let tracer = Tracer.shared(in: core) + OpenTelemetry.registerTracerProvider( + tracerProvider: OTelTracerProvider(in: core) + ) + + let tracer = OpenTelemetry + .instance + .tracerProvider + .get(instrumentationName: "", instrumentationVersion: nil) + let span = tracer .spanBuilder(spanName: "OperationName") .startSpan() diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift b/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift new file mode 100644 index 0000000000..2c84b846b7 --- /dev/null +++ b/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift @@ -0,0 +1,70 @@ +/* +* 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 +import DatadogInternal +import OpenTelemetryApi + +/// The Datadog implementation of OpenTelemetry `TracerProvider`. +/// It takes the Datadog SDK instance as a dependency and returns the tracer from it. +/// +/// Usage: +/// +/// ```swift +/// import OpenTelemetryApi +/// import DatadogTrace +/// +/// // Register the tracer provider +/// OpenTelemetry.registerTracerProvider( +/// tracerProvider: OTelTracerProvider() +/// ) +/// +/// // Get the tracer +/// let tracer = OpenTelemetry +/// .instance +/// .tracerProvider +/// .get(instrumentationName: "", instrumentationVersion: nil) +/// +/// // Start a span +/// let span = tracer +/// .spanBuilder(spanName: "OperationName") +/// .startSpan() +/// ``` +public class OTelTracerProvider: OpenTelemetryApi.TracerProvider { + private weak var core: DatadogCoreProtocol? + + /// Creates a tracer provider with the given Datadog SDK instance. + /// - Parameter core: the instance of Datadog SDK the Trace feature was enabled in (global instance by default) + public init(in core: DatadogCoreProtocol = CoreRegistry.default) { + self.core = core + } + + /// Returns a tracer with the given instrumentation name and version. + /// - Parameters: + /// - instrumentationName: the name of the instrumentation library, not the name of the instrumented library + /// Note: This is ignored, as the Datadog SDK works on concept of core. + /// - instrumentationVersion: The version of the instrumentation library (e.g., "semver:1.0.0"). Optional + /// Note: This is ignored, as the Datadog SDK works on concept of core. + public func get(instrumentationName: String, instrumentationVersion: String?) -> OpenTelemetryApi.Tracer { + do { + guard !(core is NOPDatadogCore) else { + throw ProgrammerError( + description: "Datadog SDK must be initialized and RUM feature must be enabled before calling `OTelTracerProvider.get(instrumentationName:instrumentationVersion:)`." + ) + } + guard let feature = core?.get(feature: TraceFeature.self) else { + throw ProgrammerError( + description: "Trace feature must be enabled before calling `OTelTracerProvider.get(instrumentationName:instrumentationVersion:)`." + ) + } + + return feature.tracer + } catch { + consolePrint("\(error)", .error) + return DDNoopTracer() + } + } +} diff --git a/DatadogTrace/Sources/Tracer.swift b/DatadogTrace/Sources/Tracer.swift index 0c4b32ef3a..10a6f3dc7e 100644 --- a/DatadogTrace/Sources/Tracer.swift +++ b/DatadogTrace/Sources/Tracer.swift @@ -59,7 +59,7 @@ public class Tracer { /// It requires `Trace.enable(with:in:)` to be called first - otherwise it will return no-op implementation. /// - Parameter core: the instance of Datadog SDK the Trace feature was enabled in (global instance by default) /// - Returns: the Tracer that conforms to Open Tracing API (`OTTracer`) - public static func shared(in core: DatadogCoreProtocol = CoreRegistry.default) -> OTTracer & OpenTelemetryApi.Tracer { + public static func shared(in core: DatadogCoreProtocol = CoreRegistry.default) -> OTTracer { do { guard !(core is NOPDatadogCore) else { throw ProgrammerError( From 6af9ea0efcf366776affba235a78502441bd04fc Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 15 Feb 2024 15:31:26 +0100 Subject: [PATCH 035/153] Update DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift Co-authored-by: Maciek Grzybowski --- DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift b/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift index 2c84b846b7..0214d21b45 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift @@ -52,7 +52,7 @@ public class OTelTracerProvider: OpenTelemetryApi.TracerProvider { do { guard !(core is NOPDatadogCore) else { throw ProgrammerError( - description: "Datadog SDK must be initialized and RUM feature must be enabled before calling `OTelTracerProvider.get(instrumentationName:instrumentationVersion:)`." + description: "Datadog SDK must be initialized and Trace feature must be enabled before calling `OTelTracerProvider.get(instrumentationName:instrumentationVersion:)`." ) } guard let feature = core?.get(feature: TraceFeature.self) else { From d13a6dede3edeb30bf93749fa4489eb0d71ec687 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 16 Feb 2024 16:54:43 +0100 Subject: [PATCH 036/153] RUM-1836 feat(otel-tracer): update Example app with Otel tracer use cases --- Datadog/Datadog.xcodeproj/project.pbxproj | 14 + .../Example/Base.lproj/Main iOS.storyboard | 348 +++++++++++++++++- .../DebugOTelTracingViewController.swift | 123 +++++++ Datadog/Example/ExampleAppDelegate.swift | 12 + .../Sources/OpenTelemetry/OTelSpan.swift | 4 +- 5 files changed, 490 insertions(+), 11 deletions(-) create mode 100644 Datadog/Example/Debugging/DebugOTelTracingViewController.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 57826ac104..c9625aef2b 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 1434A4612B7F73110072E3BB /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; }; + 1434A4622B7F73110072E3BB /* OpenTelemetryApi.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 1434A4632B7F73170072E3BB /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; }; + 1434A4642B7F73170072E3BB /* OpenTelemetryApi.xcframework in ⚙️ Embed Framework Dependencies */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 1434A4662B7F8D880072E3BB /* DebugOTelTracingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1434A4652B7F8D880072E3BB /* DebugOTelTracingViewController.swift */; }; + 1434A4672B7F8D880072E3BB /* DebugOTelTracingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1434A4652B7F8D880072E3BB /* DebugOTelTracingViewController.swift */; }; 3C0D5DD72A543B3B00446CF9 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0D5DD62A543B3B00446CF9 /* Event.swift */; }; 3C0D5DD82A543B3B00446CF9 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0D5DD62A543B3B00446CF9 /* Event.swift */; }; 3C0D5DE22A543DC400446CF9 /* EventGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0D5DDF2A543DAE00446CF9 /* EventGeneratorTests.swift */; }; @@ -1858,6 +1864,7 @@ files = ( D240685927CF5D0100C04F44 /* DatadogCrashReporting.framework in ⚙️ Embed Framework Dependencies */, D240685527CF5D0100C04F44 /* DatadogCore.framework in ⚙️ Embed Framework Dependencies */, + 1434A4642B7F73170072E3BB /* OpenTelemetryApi.xcframework in ⚙️ Embed Framework Dependencies */, D24C9C4729A7A520002057CF /* DatadogLogs.framework in ⚙️ Embed Framework Dependencies */, D240686027CF5D0100C04F44 /* DatadogObjc.framework in ⚙️ Embed Framework Dependencies */, ); @@ -1872,6 +1879,7 @@ files = ( 3C2206F82AB9DBC600DE780C /* DatadogInternal.framework in Embed Frameworks */, 3C2206F72AB9DBB600DE780C /* DatadogTrace.framework in Embed Frameworks */, + 1434A4622B7F73110072E3BB /* OpenTelemetryApi.xcframework in Embed Frameworks */, 3C2206F62AB9DBA700DE780C /* DatadogRUM.framework in Embed Frameworks */, 3C2206F52AB9DB9000DE780C /* DatadogSessionReplay.framework in Embed Frameworks */, D240687E27CF982D00C04F44 /* DatadogCrashReporting.framework in Embed Frameworks */, @@ -1886,6 +1894,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1434A4652B7F8D880072E3BB /* DebugOTelTracingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugOTelTracingViewController.swift; sourceTree = ""; }; 3C0D5DD62A543B3B00446CF9 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; 3C0D5DDC2A543D5D00446CF9 /* EventGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGenerator.swift; sourceTree = ""; }; 3C0D5DDF2A543DAE00446CF9 /* EventGeneratorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventGeneratorTests.swift; sourceTree = ""; }; @@ -2807,6 +2816,7 @@ D240687827CF982B00C04F44 /* CrashReporter.xcframework in Frameworks */, D240687B27CF982C00C04F44 /* DatadogCore.framework in Frameworks */, D240687D27CF982D00C04F44 /* DatadogCrashReporting.framework in Frameworks */, + 1434A4612B7F73110072E3BB /* OpenTelemetryApi.xcframework in Frameworks */, D24C9C4229A7A50D002057CF /* DatadogLogs.framework in Frameworks */, D240687F27CF982F00C04F44 /* DatadogObjc.framework in Frameworks */, ); @@ -2926,6 +2936,7 @@ buildActionMask = 2147483647; files = ( 61A2CC342A44A6030000FF25 /* DatadogRUM.framework in Frameworks */, + 1434A4632B7F73170072E3BB /* OpenTelemetryApi.xcframework in Frameworks */, D25CFA9D29C4FC6E00E3A43D /* DatadogTrace.framework in Frameworks */, 9E5BD8062819742C00CB568E /* SwiftUI.framework in Frameworks */, D240687027CF971C00C04F44 /* CrashReporter.xcframework in Frameworks */, @@ -4273,6 +4284,7 @@ children = ( 61441C942461A649003D8BB8 /* DebugLoggingViewController.swift */, 61441C932461A649003D8BB8 /* DebugTracingViewController.swift */, + 1434A4652B7F8D880072E3BB /* DebugOTelTracingViewController.swift */, 617699202A8A7DF50030022B /* DebugManualTraceInjectionViewController.swift */, 61E5333724B84EE2003D6C4E /* DebugRUMViewController.swift */, 61F74AF326F20E4600E5F5ED /* DebugCrashReportingWithRUMViewController.swift */, @@ -7771,6 +7783,7 @@ 61441C952461A649003D8BB8 /* ConsoleOutputInterceptor.swift in Sources */, 618236892710560900125326 /* DebugWebviewViewController.swift in Sources */, 61F74AF426F20E4600E5F5ED /* DebugCrashReportingWithRUMViewController.swift in Sources */, + 1434A4662B7F8D880072E3BB /* DebugOTelTracingViewController.swift in Sources */, 61E5333824B84EE2003D6C4E /* DebugRUMViewController.swift in Sources */, 61441C0524616DE9003D8BB8 /* ExampleAppDelegate.swift in Sources */, 61020C2C2758E853005EEAEA /* DebugBackgroundEventsViewController.swift in Sources */, @@ -8128,6 +8141,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1434A4672B7F8D880072E3BB /* DebugOTelTracingViewController.swift in Sources */, D2F44FC3299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift in Sources */, D240680827CE6C9E00C04F44 /* ConsoleOutputInterceptor.swift in Sources */, D240681E27CE6C9E00C04F44 /* ExampleAppDelegate.swift in Sources */, diff --git a/Datadog/Example/Base.lproj/Main iOS.storyboard b/Datadog/Example/Base.lproj/Main iOS.storyboard index d4a0c551e4..66d65e05c9 100644 --- a/Datadog/Example/Base.lproj/Main iOS.storyboard +++ b/Datadog/Example/Base.lproj/Main iOS.storyboard @@ -18,7 +18,7 @@ - + @@ -65,9 +65,29 @@ - + + + + + + + + + + + + + + + @@ -86,7 +106,7 @@ - + @@ -106,7 +126,7 @@ - + @@ -126,7 +146,7 @@ - + @@ -146,7 +166,7 @@ - + @@ -166,7 +186,7 @@ - + @@ -784,7 +804,319 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Datadog/Example/Debugging/DebugOTelTracingViewController.swift b/Datadog/Example/Debugging/DebugOTelTracingViewController.swift new file mode 100644 index 0000000000..2a615cd1da --- /dev/null +++ b/Datadog/Example/Debugging/DebugOTelTracingViewController.swift @@ -0,0 +1,123 @@ +/* + * 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 UIKit +import DatadogCore +import DatadogTrace + +class DebugOTelTracingViewController: UIViewController { + @IBOutlet weak var serviceNameTextField: UITextField! + @IBOutlet weak var isErrorSegmentedControl: UISegmentedControl! + @IBOutlet weak var singleSpanOperationNameTextField: UITextField! + @IBOutlet weak var singleSpanResourceNameTextField: UITextField! + @IBOutlet weak var sendSingleSpanButton: UIButton! + @IBOutlet weak var complexSpanOperationNameTextField: UITextField! + @IBOutlet weak var sendComplexSpanButton: UIButton! + @IBOutlet weak var consoleTextView: UITextView! + + private let queue1 = DispatchQueue(label: "com.datadoghq.debug-tracing1") + private let queue2 = DispatchQueue(label: "com.datadoghq.debug-tracing2") + private let queue3 = DispatchQueue(label: "com.datadoghq.debug-tracing3") + + override func viewDidLoad() { + super.viewDidLoad() + serviceNameTextField.text = serviceName + hideKeyboardWhenTapOutside() + startDisplayingDebugInfo(in: consoleTextView) + } + + private var isError: Bool { + isErrorSegmentedControl.selectedSegmentIndex == 1 + } + + // MARK: - Sending single span + + private var singleSpanOperationName: String { + singleSpanOperationNameTextField.text!.isEmpty ? "otel single span" : singleSpanOperationNameTextField.text! + } + + private var singleSpanResourceName: String? { + singleSpanResourceNameTextField.text!.isEmpty ? nil : singleSpanResourceNameTextField.text! + } + + @IBAction func didTapSendSingleSpan(_ sender: Any) { + sendSingleSpanButton.disableFor(seconds: 0.5) + + let spanName = singleSpanOperationName + let resourceName = singleSpanResourceName + let isError = self.isError + + queue1.async { + let span = otelTracer.spanBuilder(spanName: spanName) + .startSpan() + if let resourceName = resourceName { + span.setAttribute(key: SpanTags.resource, value: resourceName) + } + if isError { + // To only mark the span as an error, use the Open Tracing `error` tag: + // span.setTag(key: "error", value: true) + span.status = .error(description: "error description") + } + wait(seconds: 1) + span.end() + } + } + + // MARK: - Sending complex span + + private var complexSpanOperationName: String { + complexSpanOperationNameTextField.text!.isEmpty ? "otel complex span" : complexSpanOperationNameTextField.text! + } + + @IBAction func didTapSendComplexSpan(_ sender: Any) { + sendComplexSpanButton.disableFor(seconds: 0.5) + + let spanName = complexSpanOperationName + + queue1.async { [weak self] in + guard let self = self else { return } + + let rootSpan = otelTracer + .spanBuilder(spanName: spanName) + .startSpan() + wait(seconds: 0.5) + + self.queue2.sync { + let child1 = otelTracer.spanBuilder(spanName: "otel child operation 1") + .setParent(rootSpan) + .startSpan() + wait(seconds: 0.5) + child1.end() + + wait(seconds: 0.1) + + let child2 = otelTracer + .spanBuilder(spanName: "otel child operation 2") + .setParent(rootSpan) + .startSpan() + wait(seconds: 0.5) + + self.queue3.sync { + let grandChild = otelTracer + .spanBuilder(spanName: "otel grandchild operation") + .setParent(child2) + .startSpan() + wait(seconds: 1) + grandChild.end() + } + + child2.end() + } + + wait(seconds: 0.5) + rootSpan.end() + } + } +} + +private func wait(seconds: TimeInterval) { + Thread.sleep(forTimeInterval: seconds) +} diff --git a/Datadog/Example/ExampleAppDelegate.swift b/Datadog/Example/ExampleAppDelegate.swift index c86121c55d..b73529bf32 100644 --- a/Datadog/Example/ExampleAppDelegate.swift +++ b/Datadog/Example/ExampleAppDelegate.swift @@ -10,12 +10,19 @@ import DatadogLogs import DatadogTrace import DatadogRUM import DatadogCrashReporting +import OpenTelemetryApi let serviceName = "ios-sdk-example-app" var logger: LoggerProtocol! var tracer: OTTracer { Tracer.shared() } var rumMonitor: RUMMonitorProtocol { RUMMonitor.shared() } +var otelTracer: OpenTelemetryApi.Tracer { + OpenTelemetry + .instance + .tracerProvider + .get(instrumentationName: "", instrumentationVersion: nil) +} @UIApplicationMain class ExampleAppDelegate: UIResponder, UIApplicationDelegate { @@ -74,6 +81,11 @@ class ExampleAppDelegate: UIResponder, UIApplicationDelegate { ) RUMMonitor.shared().debug = true + // Register Trace Provider + OpenTelemetry.registerTracerProvider( + tracerProvider: OTelTracerProvider() + ) + // Create Logger logger = Logger.create( with: Logger.Configuration( diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index f111522ee7..767a1958f1 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -171,9 +171,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { // There is no need to lock here, because `DDSpan` is thread-safe - // fields needs to be a dictionary of [String: Encodable] which is satisfied by opentelemetry-swift - // and Datadog SDK doesn't care about the representation - ddSpan.log(message: name, fields: attributes, timestamp: timestamp) + ddSpan.log(message: name, fields: attributes.tags, timestamp: timestamp) } func end() { From 7d39ed248aeedff1f45e628e5702c63283df0d79 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 19 Feb 2024 11:02:35 +0100 Subject: [PATCH 037/153] RUM-1836 feat(otel-tracer): fix test case --- .../Tests/Datadog/Tracing/OTelSpanTests.swift | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift index 0fc8010941..c039c91b2e 100644 --- a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift +++ b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift @@ -41,7 +41,22 @@ final class OTelSpanTests: XCTestCase { // Then let logs: [LogEvent] = core.waitAndReturnEvents(ofFeature: LogsFeature.name, ofType: LogEvent.self) XCTAssertEqual(logs.count, 1) - DDAssertJSONEqual(AnyEncodable(logs[0].attributes.userAttributes), AnyEncodable(attributes)) + + let expectedAttributes: [String: Encodable] = [ + "string": "value", + "bool": "true", + "int": "2", + "double": "2.0", + "stringArray.0": "value1", + "stringArray.1": "value2", + "boolArray.0": "true", + "boolArray.1": "false", + "intArray.0": "1", + "intArray.1": "2", + "doubleArray.0": "1.0", + "doubleArray.1": "2.0" + ] + DDAssertJSONEqual(AnyEncodable(expectedAttributes), AnyEncodable(logs[0].attributes.userAttributes)) } } From b0ae691fe52e61f174e321b3a29cf1007b505315 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 6 Mar 2024 10:58:15 +0100 Subject: [PATCH 038/153] RUM-1836 feat(otel-tracer): test case to translate resource.name attribute to resource --- .../Tests/OpenTelemetry/OTelSpanTests.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 76c90a0c13..33fec7afeb 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -31,6 +31,27 @@ final class OTelSpanTests: XCTestCase { XCTAssertEqual(recordedSpan.operationName, "OperationName") } + func testSpanResourceNameAttribute() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "OperationName").startSpan() + + // When + span.setAttribute(key: "resource.name", value: "ResourceName") + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + let recordedSpan = recordedSpans.first! + XCTAssertEqual(recordedSpan.resource, "ResourceName") + XCTAssertEqual(recordedSpan.operationName, "OperationName") + } + func testSpanSetName() { let writeSpanExpectation = expectation(description: "write span event") let core = PassthroughCoreMock(expectation: writeSpanExpectation) From 51402e7cd656405b797199a75b243a48e3d27cc7 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 6 Mar 2024 11:45:23 +0100 Subject: [PATCH 039/153] RUM-1836 feat(otel-tracer): support operation.name & service.name tags --- .../Sources/Span/SpanEventBuilder.swift | 4 +- .../Sources/Span/SpanTagsReducer.swift | 16 +++++ DatadogTrace/Sources/Tracer.swift | 4 ++ .../Tests/OpenTelemetry/OTelSpanTests.swift | 60 +++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/DatadogTrace/Sources/Span/SpanEventBuilder.swift b/DatadogTrace/Sources/Span/SpanEventBuilder.swift index 7405400bb6..ac72bb9877 100644 --- a/DatadogTrace/Sources/Span/SpanEventBuilder.swift +++ b/DatadogTrace/Sources/Span/SpanEventBuilder.swift @@ -75,8 +75,8 @@ internal struct SpanEventBuilder { traceID: traceID, spanID: spanID, parentID: parentSpanID, - operationName: operationName, - serviceName: service ?? context.service, + operationName: tagsReducer.extractedOperationName ?? operationName, + serviceName: tagsReducer.extractedServiceName ?? service ?? context.service, resource: tagsReducer.extractedResourceName ?? operationName, startTime: startTime.addingTimeInterval(context.serverTimeOffset), duration: finishTime.timeIntervalSince(startTime), diff --git a/DatadogTrace/Sources/Span/SpanTagsReducer.swift b/DatadogTrace/Sources/Span/SpanTagsReducer.swift index d706f80c66..69363df2cf 100644 --- a/DatadogTrace/Sources/Span/SpanTagsReducer.swift +++ b/DatadogTrace/Sources/Span/SpanTagsReducer.swift @@ -26,6 +26,10 @@ internal struct SpanTagsReducer { let extractedIsError: Bool? /// Resource name requiring a special encoding in `Span` JSON. let extractedResourceName: String? + /// Extracted operation name from operation tag. + let extractedOperationName: String? + /// Extracted service name from service tag. + let extractedServiceName: String? // MARK: - Initialization @@ -34,6 +38,8 @@ internal struct SpanTagsReducer { var extractedIsError: Bool? = nil var extractedResourceName: String? = nil + var extractedOperationName: String? = nil + var extractedServiceName: String? = nil // extract error from `logFields` for fields in logFields { @@ -59,8 +65,18 @@ internal struct SpanTagsReducer { extractedResourceName = resourceName } + if let operationName = mutableSpanTags.removeValue(forKey: SpanTags.operation) as? String { + extractedOperationName = operationName + } + + if let serviceName = mutableSpanTags.removeValue(forKey: SpanTags.service) as? String { + extractedServiceName = serviceName + } + self.reducedSpanTags = mutableSpanTags self.extractedIsError = extractedIsError self.extractedResourceName = extractedResourceName + self.extractedOperationName = extractedOperationName + self.extractedServiceName = extractedServiceName } } diff --git a/DatadogTrace/Sources/Tracer.swift b/DatadogTrace/Sources/Tracer.swift index 10a6f3dc7e..83dd84a639 100644 --- a/DatadogTrace/Sources/Tracer.swift +++ b/DatadogTrace/Sources/Tracer.swift @@ -17,6 +17,10 @@ public enum SpanTags { /// /// Expects `String` value set for a tag. public static let resource = "resource.name" + /// A Datadog-specific span tag, which sets the operation name + public static let operation = "operation.name" + /// A Datadog-specific span tag, which sets the value appearing in the "SERVICE" column + public static let service = "service.name" /// Internal tag. `Integer` value. Measures elapsed time at app's foreground state in nanoseconds. /// (duration - foregroundDuration) gives you the elapsed time while the app wasn't active (probably at background) internal static let foregroundDuration = "foreground_duration" diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 33fec7afeb..4bf4ba4431 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -31,6 +31,66 @@ final class OTelSpanTests: XCTestCase { XCTAssertEqual(recordedSpan.operationName, "OperationName") } + func testSpanOperationNameAttribute() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "https://httpbin.org/get").startSpan() + + // When + span.setAttribute(key: "operation.name", value: .string("GET")) + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + let recordedSpan = recordedSpans.first! + XCTAssertEqual(recordedSpan.resource, "https://httpbin.org/get") + XCTAssertEqual(recordedSpan.operationName, "GET") + } + + func testSpanServiceNameDefault() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "OperationName").startSpan() + + // When + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + let recordedSpan = recordedSpans.first! + XCTAssertEqual(recordedSpan.serviceName, "abc") + } + + func testSpanServiceNameAttribute() { + let writeSpanExpectation = expectation(description: "write span event") + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span = tracer.spanBuilder(spanName: "OperationName").startSpan() + + // When + span.setAttribute(key: "service.name", value: .string("ServiceName")) + span.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 1) + let recordedSpan = recordedSpans.first! + XCTAssertEqual(recordedSpan.serviceName, "ServiceName") + } + func testSpanResourceNameAttribute() { let writeSpanExpectation = expectation(description: "write span event") let core = PassthroughCoreMock(expectation: writeSpanExpectation) From e5b0638fc214e98d75e642b531b42f8fe0461ffd Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 6 Mar 2024 16:09:57 +0100 Subject: [PATCH 040/153] RUM-1836 feat(otel-tracer): SpanEventBuilder tests --- .../Tests/Span/SpanEventBuilderTests.swift | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift b/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift index 7ebd91a2cf..d08057751d 100644 --- a/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift +++ b/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift @@ -280,6 +280,58 @@ class SpanEventBuilderTests: XCTestCase { XCTAssertEqual(span.tags, [:]) } + func testBuildingSpanWithOperationNameTagSet() { + let builder: SpanEventBuilder = .mockAny() + + // given + let span = builder.createSpanEvent( + context: .mockAny(), + traceID: .mockAny(), + spanID: .mockAny(), + parentSpanID: .mockAny(), + operationName: .mockAny(), + startTime: .mockAny(), + finishTime: .mockAny(), + samplingRate: .mockAny(), + isKept: .mockAny(), + tags: [ + SpanTags.operation: "custom operation name" + ], + baggageItems: [:], + logFields: [] + ) + + // then + XCTAssertEqual(span.operationName, "custom operation name") + XCTAssertEqual(span.tags, [:]) + } + + func testBuildingSpanWithServiceNameTagSet() { + let builder: SpanEventBuilder = .mockAny() + + // given + let span = builder.createSpanEvent( + context: .mockAny(), + traceID: .mockAny(), + spanID: .mockAny(), + parentSpanID: .mockAny(), + operationName: .mockAny(), + startTime: .mockAny(), + finishTime: .mockAny(), + samplingRate: .mockAny(), + isKept: .mockAny(), + tags: [ + SpanTags.service: "custom service name" + ], + baggageItems: [:], + logFields: [] + ) + + // then + XCTAssertEqual(span.serviceName, "custom service name") + XCTAssertEqual(span.tags, [:]) + } + func testItSendsBaggageItemsAsTags() { let builder: SpanEventBuilder = .mockAny() From 0bc961d9bb7f32de99e12fb48fe2cad633bc9213 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 6 Mar 2024 17:18:32 +0100 Subject: [PATCH 041/153] RUM-1836 feat(otel-tracer): change default span kind to internal --- DatadogTrace/Sources/DatadogTracer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogTrace/Sources/DatadogTracer.swift b/DatadogTrace/Sources/DatadogTracer.swift index df0f73a391..4d310c38d3 100644 --- a/DatadogTrace/Sources/DatadogTracer.swift +++ b/DatadogTrace/Sources/DatadogTracer.swift @@ -149,7 +149,7 @@ internal final class DatadogTracer: OTTracer, OpenTelemetryApi.Tracer { active: false, attributes: [:], parent: .currentSpan, - spanKind: .client, + spanKind: .internal, spanName: spanName, startTime: nil, tracer: self From 26172203c1569d4d0117f552c40bc1159b6f7b36 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 7 Mar 2024 09:53:22 +0100 Subject: [PATCH 042/153] RUM-1836 feat(otel-tracer): fix test case --- DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 4bf4ba4431..88dbc07953 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -169,7 +169,7 @@ final class OTelSpanTests: XCTestCase { XCTAssertEqual(recordedSpan.operationName, name) let expectedTags = [ "key": "value", - "span.kind": "client", + "span.kind": "internal", ] DDAssertDictionariesEqual(recordedSpan.tags, expectedTags) } @@ -276,7 +276,7 @@ final class OTelSpanTests: XCTestCase { "key2": "value2", "key3": "3", "key4": "4.0", - "span.kind": "client", + "span.kind": "internal", ] DDAssertDictionariesEqual(recordedSpan.tags, expectedTags) } From 8371f8e6a2354650a1063b275b7dc26d40a1f6ad Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 12 Mar 2024 13:58:59 +0100 Subject: [PATCH 043/153] Merge branch 'develop' into ganeshnj/feat/otel-tracer # Conflicts: # .gitlab-ci.yml # Datadog/Datadog.xcodeproj/project.pbxproj # Datadog/Example/Base.lproj/Main iOS.storyboard # Datadog/Example/ExampleAppDelegate.swift # Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift # Datadog/IntegrationUnitTests/Public/NetworkInstrumentationIntegrationTests.swift # Datadog/IntegrationUnitTests/RUM/StartingRUMSessionTests.swift # DatadogAlamofireExtension.podspec # DatadogCore.podspec # DatadogCore/Sources/Core/DatadogCore.swift # DatadogCore/Sources/Datadog.swift # DatadogCore/Sources/Versioning.swift # DatadogCore/Tests/Datadog/Logs/CrashLogReceiverTests.swift # DatadogCore/Tests/Datadog/Mocks/DatadogInternal/DatadogCoreProxy.swift # DatadogCrashReporting.podspec # DatadogInternal.podspec # DatadogInternal/Sources/DatadogCoreProtocol.swift # DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift # DatadogInternal/Sources/NetworkInstrumentation/URLSession/DatadogURLSessionDelegate.swift # DatadogInternal/Sources/NetworkInstrumentation/URLSession/NetworkInstrumentationSwizzler.swift # DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionSwizzler.swift # DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift # DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift # DatadogInternal/Tests/NetworkInstrumentation/URLSessionSwizzlerTests.swift # DatadogLogs.podspec # DatadogLogs/Sources/Feature/MessageReceivers.swift # DatadogObjc.podspec # DatadogObjc/Sources/RUM/RUMDataModels+objc.swift # DatadogRUM.podspec # DatadogRUM/Sources/DataModels/RUMDataModels.swift # DatadogSDK.podspec # DatadogSDKAlamofireExtension.podspec # DatadogSDKCrashReporting.podspec # DatadogSDKObjc.podspec # DatadogSessionReplay.podspec # DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift # DatadogSessionReplay/Sources/Processor/ResourcesProcessor.swift # DatadogSessionReplay/Sources/Recorder/Recorder.swift # DatadogSessionReplay/Sources/Recorder/Utilities/ImageDataProvider.swift # DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorder.swift # DatadogSessionReplay/Sources/Writers/RecordWriter.swift # DatadogSessionReplay/Sources/Writers/ResourcesWriter.swift # DatadogSessionReplay/Tests/Mocks/RecorderMocks.swift # DatadogSessionReplay/Tests/Processor/ResourceProcessorTests.swift # DatadogSessionReplay/Tests/Processor/SnapshotProcessorTests.swift # DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorderTests.swift # DatadogTrace.podspec # DatadogTrace/Sources/DDSpan.swift # DatadogTrace/Sources/Span/SpanEventBuilder.swift # DatadogTrace/Sources/Span/SpanWriteContext.swift # DatadogTrace/Tests/Span/SpanEventBuilderTests.swift # DatadogTrace/Tests/Span/SpanWriteContextTests.swift # DatadogWebViewTracking.podspec # IntegrationTests/IntegrationScenarios/Scenarios/Core/StopCoreScenarioTests.swift # IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift # IntegrationTests/Runner/Scenarios/Logging/ManualInstrumentation/SendLogsFixtureViewController.swift # Makefile # Package.swift # TestUtilities.podspec # TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift # dependency-manager-tests/cocoapods/CPProject.xcodeproj/project.pbxproj --- .github/ISSUE_TEMPLATE/BugReport.yml | 111 ++ .github/ISSUE_TEMPLATE/CrashReport.yml | 76 ++ .github/ISSUE_TEMPLATE/FeatureRequest.yml | 32 + .github/ISSUE_TEMPLATE/Question.yml | 10 + .github/ISSUE_TEMPLATE/SetupIssue.yml | 81 ++ .github/ISSUE_TEMPLATE/bug_report.md | 58 - .github/ISSUE_TEMPLATE/compilation_issue.md | 56 - .github/ISSUE_TEMPLATE/crash_report.md | 52 - .github/ISSUE_TEMPLATE/feature_request.md | 21 - .github/ISSUE_TEMPLATE/other.md | 12 - .gitlab-ci.yml | 5 + CHANGELOG.md | 15 + CONTRIBUTING.md | 2 + Datadog/Datadog.xcodeproj/project.pbxproj | 312 +++-- .../Example/Base.lproj/Main iOS.storyboard | 348 +---- Datadog/Example/ExampleAppDelegate.swift | 11 +- .../GeneratingBacktraceTests.swift | 103 ++ .../SendingCrashReportTests.swift | 4 +- ...tworkInstrumentationIntegrationTests.swift | 121 +- .../Public/WebLogIntegrationTests.swift | 6 +- .../RUM/AppHangsMonitoringTests.swift | 149 +++ .../RUM/StartingRUMSessionTests.swift | 8 +- DatadogAlamofireExtension.podspec | 2 +- DatadogCore.podspec | 7 +- .../Core/Context/CarrierInfoPublisher.swift | 2 +- DatadogCore/Sources/Core/DatadogCore.swift | 25 +- .../Sources/Core/Storage/FeatureStorage.swift | 4 +- .../Core/Storage/FilesOrchestrator.swift | 24 +- .../Core/Storage/Writing/FileWriter.swift | 6 +- DatadogCore/Sources/Datadog.swift | 7 +- DatadogCore/Sources/Versioning.swift | 2 +- .../Tests/Datadog/Core/FeatureTests.swift | 43 +- .../FilesOrchestrator+MetricsTests.swift | 36 +- .../Persistence/FilesOrchestratorTests.swift | 13 - .../Persistence/Writing/FileWriterTests.swift | 44 - .../Core/Upload/DataUploadWorkerTests.swift | 1 - .../CrashReporting/CrashReporterTests.swift | 2 +- .../Datadog/DatadogConfigurationTests.swift | 19 + .../DatadogCore+FeatureDirectoriesTests.swift | 71 ++ .../DatadogCore/DatadogCoreTests.swift | 52 - DatadogCore/Tests/Datadog/LoggerTests.swift | 77 ++ .../Datadog/Logs/CrashLogReceiverTests.swift | 90 +- .../Tests/Datadog/Mocks/CoreMocks.swift | 1 - .../Mocks/CrashReportingFeatureMocks.swift | 11 +- .../DatadogInternal/DatadogCoreProxy.swift | 4 +- .../Tests/Datadog/Mocks/LogsMocks.swift | 9 + .../Datadog/Mocks/RUMDataModelMocks.swift | 9 +- .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 12 +- .../CrashReportReceiverTests.swift | 179 +++ .../RUMEventFileOutputTests.swift | 1 - .../Tests/Matchers/SRRequestMatcher.swift | 42 +- .../Tests/Matchers/SRSegmentMatcher.swift | 53 +- DatadogCrashReporting.podspec | 2 +- .../Sources/CrashContext/CrashContext.swift | 11 +- .../CrashContext/CrashContextProvider.swift | 23 +- .../Sources/CrashReporting.swift | 4 + .../Sources/CrashReportingPlugin.swift | 165 +-- .../Integrations/BacktraceReporter.swift | 20 + .../DDCrashReportBuilder.swift | 2 +- .../DDCrashReportExporter.swift | 10 +- .../PLCrashReporterIntegration.swift | 18 + .../Sources/ThirdPartyCrashReporter.swift | 7 + .../Tests/CrashReportingPluginTests.swift | 4 +- DatadogCrashReporting/Tests/Mocks.swift | 27 +- DatadogInternal.podspec | 2 +- .../Sources/Attributes/Attributes.swift | 4 + .../BacktraceReporter.swift | 93 ++ .../BacktraceReportingFeature.swift | 22 + .../Sources/Context/DatadogContext.swift | 5 + .../Sources/DatadogCoreProtocol.swift | 10 +- .../Sources/MessageBus/FeatureMessage.swift | 22 +- .../CrashReporting/BacktraceReport.swift | 41 + .../Models/CrashReporting/BinaryImage.swift | 44 + .../Models/CrashReporting/DDCrashReport.swift | 101 ++ .../Models/CrashReporting/DDThread.swift | 40 + .../WebViewTracking/WebViewMessage.swift | 73 ++ .../NetworkInstrumentationFeature.swift | 4 +- .../DatadogURLSessionDelegate.swift | 1 + .../NetworkInstrumentationSwizzler.swift | 8 +- .../URLSession/URLSessionSwizzler.swift | 31 +- .../URLSession/URLSessionTask+Tracking.swift | 16 + .../Storage/PerformancePresetOverride.swift | 4 +- .../Sources/Telemetry/Telemetry.swift | 8 +- DatadogInternal/Sources/Utils/DDError.swift | 6 +- .../Tests/Models/WebViewMessageTests.swift | 49 + .../NetworkInstrumentationFeatureTests.swift | 4 +- .../URLSessionSwizzlerTests.swift | 7 +- .../Tests/Telemetry/TelemetryMocks.swift | 1 + .../Tests/Telemetry/TelemetryTests.swift | 1 + DatadogLogs.podspec | 2 +- DatadogLogs/Sources/Feature/Baggages.swift | 27 + DatadogLogs/Sources/Feature/LogsFeature.swift | 15 + .../Sources/Feature/MessageReceivers.swift | 197 +-- DatadogLogs/Sources/Log/LogEventBuilder.swift | 11 +- DatadogLogs/Sources/Log/LogEventEncoder.swift | 18 + DatadogLogs/Sources/Logs.swift | 37 + DatadogLogs/Sources/RemoteLogger.swift | 20 +- .../Tests/Log/LogEventBuilderTests.swift | 15 + DatadogLogs/Tests/LogsTests.swift | 78 ++ .../Tests/Mocks/LoggingFeatureMocks.swift | 9 + DatadogLogs/Tests/RemoteLoggerTests.swift | 150 ++- .../Tests/WebViewLogReceiverTests.swift | 70 +- DatadogObjc.podspec | 2 +- .../Sources/RUM/RUMDataModels+objc.swift | 1119 +++++++++++++++-- DatadogRUM.podspec | 2 +- .../Sources/DataModels/RUMDataModels.swift | 436 ++++++- .../DataModels/RUMDataModelsMapping.swift | 25 + .../Sources/Debugging/RUMDebugging.swift | 4 +- DatadogRUM/Sources/Feature/RUMFeature.swift | 7 +- .../AppHangs/AppHangsObserver.swift | 85 ++ .../AppHangs/AppHangsWatchdogThread.swift | 166 +++ .../Instrumentation/RUMInstrumentation.swift | 46 +- .../Integrations/CrashReportReceiver.swift | 35 +- .../Integrations/TelemetryReceiver.swift | 1 + .../Integrations/WebViewEventReceiver.swift | 27 +- DatadogRUM/Sources/RUMConfiguration.swift | 21 + DatadogRUM/Sources/RUMMonitor/Monitor.swift | 7 +- .../Sources/RUMMonitor/RUMCommand.swift | 138 +- .../RUMMonitor/Scopes/RUMResourceScope.swift | 8 +- .../Scopes/RUMUserActionScope.swift | 2 +- .../RUMMonitor/Scopes/RUMViewScope.swift | 16 +- .../Sources/RUMVitals/VitalInfoSampler.swift | 12 +- .../RUMVitals/VitalRefreshRateReader.swift | 4 + .../AppHangsWatchdogThreadTests.swift | 119 ++ .../RUMInstrumentationTests.swift | 30 +- .../WebViewEventReceiverTests.swift | 126 +- .../Tests/Mocks/RUMDataModelMocks.swift | 9 +- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 56 +- DatadogRUM/Tests/RUMConfigurationTests.swift | 1 + .../Tests/RUMMonitor/RUMCommandTests.swift | 21 +- .../Scopes/RUMResourceScopeTests.swift | 3 + .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 63 +- DatadogRUM/Tests/RUMTests.swift | 32 + DatadogSDK.podspec | 2 +- DatadogSDKAlamofireExtension.podspec | 2 +- DatadogSDKCrashReporting.podspec | 2 +- DatadogSDKObjc.podspec | 2 +- DatadogSessionReplay.podspec | 2 +- .../dd_logo.imageset/Contents.json | 16 +- .../dd_logo.imageset/dd_logo.jpg | Bin 7073 -> 0 bytes .../dd_logo.imageset/login_logo.pdf | Bin 0 -> 16615 bytes .../Resources/Storyboards/Images.storyboard | 206 ++- .../SRSnapshotTests/SRSnapshotTests.swift | 30 +- .../Utils/ImageRendering.swift | 44 +- .../Utils/SnapshotTestCase.swift | 21 +- .../testImages()-allow-privacy.png.json | 2 +- .../testImages()-mask-privacy.png.json | 2 +- ...estImages()-maskUserInput-privacy.png.json | 1 - .../pointers/testSafari().png.json | 2 +- .../JSON/EnrichedRecordJSON.swift | 64 - .../RequestBuilders/JSON/SegmentJSON.swift | 127 ++ .../JSON/SegmentJSONBuilder.swift | 87 -- .../Multipart/MultipartFormData.swift | 33 +- .../ResourceRequestBuilder.swift | 6 +- .../SegmentRequestBuilder.swift | 81 +- .../Sources/Feature/ResourcesFeature.swift | 5 + .../Feature/SessionReplayFeature.swift | 15 +- .../Sources/Models/EnrichedRecord.swift | 51 +- .../Sources/Models/SRDataModels.swift | 126 +- .../Processor/Diffing/Diff+SRWireframes.swift | 29 + .../Processor/ResourcesProcessor.swift | 32 +- .../SRDataModelsBuilder/RecordsBuilder.swift | 2 + .../WireframesBuilder.swift | 32 +- .../Sources/Processor/SnapshotProcessor.swift | 6 +- .../Sources/Recorder/Recorder.swift | 8 +- .../Utilities/ImageDataProvider.swift | 124 -- .../Utilities/UIImage+SessionReplay.swift | 43 + .../NodeRecorders/UIImageResource.swift | 47 + .../NodeRecorders/UIImageViewRecorder.swift | 62 +- .../NodeRecorders/WKWebViewRecorder.swift | 60 + .../ViewTreeRecordingContext.swift | 2 - .../ViewTreeSnapshotBuilder.swift | 8 +- .../Sources/SessionReplay.swift | 2 + .../Sources/Utilities/Cache.swift | 114 -- .../Sources/Utilities/Queue.swift | 2 +- .../Sources/Utilities/UIImage+Scaling.swift | 19 +- .../Sources/Writers/RecordWriter.swift | 21 +- .../Sources/Writers/ResourcesWriter.swift | 2 +- .../JSON/EnrichedRecordJSONTests.swift | 67 - .../JSON/SegmentJSONBuilderTests.swift | 116 -- .../JSON/SegmentJSONTests.swift | 183 +++ .../Multipart/MultipartFormDataTests.swift | 2 +- .../ResourceRequestBuilderTests.swift | 2 +- .../SegmentRequestBuilderTests.swift | 104 +- .../Tests/Mocks/MockImageDataProvider.swift | 34 - .../Tests/Mocks/MultipartBuilderSpy.swift | 7 +- .../Tests/Mocks/RecorderMocks.swift | 15 +- .../Tests/Mocks/SRDataModelsMocks.swift | 8 + .../Processor/ResourceProcessorTests.swift | 32 + .../Processor/SnapshotProcessorTests.swift | 24 +- .../NodeRecorders/UIImageResourceTests.swift | 61 + .../UIImageViewRecorderTests.swift | 16 - .../UIImageViewWireframesBuilderTests.swift | 10 +- .../WKWebViewRecorderTests.swift | 84 ++ .../ViewTreeSnapshotBuilderTests.swift | 6 +- .../Tests/Utilities/CacheTests.swift | 64 - .../Utilities/UIImage+ScalingTests.swift | 4 +- .../Writer/Models/EnrichedRecordTests.swift | 47 - .../Tests/Writer/RecordsWriterTests.swift | 17 - DatadogTrace.podspec | 2 +- .../Sources/Span/SpanWriteContext.swift | 2 +- .../Tests/Span/SpanWriteContextTests.swift | 3 +- DatadogWebViewTracking.podspec | 2 +- .../Sources/DDScriptMessageHandler.swift | 9 +- .../Sources/MessageEmitter.swift | 83 +- .../Sources/WebViewMessage.swift | 73 -- .../Sources/WebViewTracking.swift | 2 +- .../Tests/MessageEmitterTests.swift | 58 +- .../Tests/WebViewMessageTests.swift | 206 --- .../Tests/WebViewTrackingTests.swift | 119 +- .../Core/StopCoreScenarioTests.swift | 8 +- ...ashReportingWithLoggingScenarioTests.swift | 5 + .../Logging/LoggingScenarioTests.swift | 12 +- .../SessionReplay/SRCommonAsserts.swift | 9 + ...RMultipleViewsRecordingScenarioTests.swift | 84 +- .../project.pbxproj | 36 + .../CrashReportingViewController.swift | 4 + .../SendLogsFixtureViewController.swift | 7 + SUPPORTED-VERSIONS.md | 4 + TestUtilities.podspec | 2 +- TestUtilities/Helpers/UIKitHelpers.swift | 16 + .../Mocks/BacktraceReportingMocks.swift | 20 + .../Mocks/CoreMocks/PassthroughCoreMock.swift | 14 +- .../CoreMocks/SingleFeatureCoreMock.swift | 8 - TestUtilities/Mocks/DatadogContextMock.swift | 8 +- TestUtilities/Mocks/FeatureMessageMocks.swift | 13 + .../CrashReporting/BacktraceReportMocks.swift | 36 + .../CrashReporting/BinaryImageMocks.swift | 42 + .../CrashReporting/DDCrashReportMocks.swift | 90 ++ .../CrashReporting/DDThreadMocks.swift | 36 + .../carthage/App/PrivacyInfo.xcprivacy | 5 + .../carthage/App/ViewController.swift | 7 - .../CTProject.xcodeproj/project.pbxproj | 6 + .../cocoapods/App/PrivacyInfo.xcprivacy | 5 + .../cocoapods/App/ViewController.swift | 7 - .../spm/App/PrivacyInfo.xcprivacy | 5 + .../spm/App/ViewController.swift | 7 - .../SPMProject.xcodeproj.src/project.pbxproj | 60 +- .../xcframeworks/App/PrivacyInfo.xcprivacy | 5 + .../xcframeworks/App/ViewController.swift | 7 - .../XCProject.xcodeproj/project.pbxproj | 6 + tools/lint/sources.swiftlint.yml | 4 +- tools/lint/tests.swiftlint.yml | 4 +- .../CodeDecoration/SRCodeDecorator.swift | 1 + .../sr-snapshots/Sources/Git/GitClient.swift | 2 +- 245 files changed, 7033 insertions(+), 3161 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/BugReport.yml create mode 100644 .github/ISSUE_TEMPLATE/CrashReport.yml create mode 100644 .github/ISSUE_TEMPLATE/FeatureRequest.yml create mode 100644 .github/ISSUE_TEMPLATE/Question.yml create mode 100644 .github/ISSUE_TEMPLATE/SetupIssue.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/compilation_issue.md delete mode 100644 .github/ISSUE_TEMPLATE/crash_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/other.md create mode 100644 Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift create mode 100644 Datadog/IntegrationUnitTests/RUM/AppHangsMonitoringTests.swift create mode 100644 DatadogCore/Tests/Datadog/DatadogCore/DatadogCore+FeatureDirectoriesTests.swift create mode 100644 DatadogCrashReporting/Sources/Integrations/BacktraceReporter.swift create mode 100644 DatadogInternal/Sources/BacktraceReporting/BacktraceReporter.swift create mode 100644 DatadogInternal/Sources/BacktraceReporting/BacktraceReportingFeature.swift create mode 100644 DatadogInternal/Sources/Models/CrashReporting/BacktraceReport.swift create mode 100644 DatadogInternal/Sources/Models/CrashReporting/BinaryImage.swift create mode 100644 DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift create mode 100644 DatadogInternal/Sources/Models/CrashReporting/DDThread.swift create mode 100644 DatadogInternal/Sources/Models/WebViewTracking/WebViewMessage.swift create mode 100644 DatadogInternal/Tests/Models/WebViewMessageTests.swift create mode 100644 DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsObserver.swift create mode 100644 DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsWatchdogThread.swift create mode 100644 DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsWatchdogThreadTests.swift delete mode 100644 DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/dd_logo.jpg create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/login_logo.pdf delete mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-maskUserInput-privacy.png.json delete mode 100644 DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/EnrichedRecordJSON.swift delete mode 100644 DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/SegmentJSONBuilder.swift delete mode 100644 DatadogSessionReplay/Sources/Recorder/Utilities/ImageDataProvider.swift create mode 100644 DatadogSessionReplay/Sources/Recorder/Utilities/UIImage+SessionReplay.swift create mode 100644 DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageResource.swift create mode 100644 DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorder.swift delete mode 100644 DatadogSessionReplay/Sources/Utilities/Cache.swift delete mode 100644 DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/EnrichedRecordJSONTests.swift delete mode 100644 DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/SegmentJSONBuilderTests.swift create mode 100644 DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/SegmentJSONTests.swift delete mode 100644 DatadogSessionReplay/Tests/Mocks/MockImageDataProvider.swift create mode 100644 DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageResourceTests.swift create mode 100644 DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorderTests.swift delete mode 100644 DatadogSessionReplay/Tests/Utilities/CacheTests.swift delete mode 100644 DatadogSessionReplay/Tests/Writer/Models/EnrichedRecordTests.swift delete mode 100644 DatadogWebViewTracking/Sources/WebViewMessage.swift delete mode 100644 DatadogWebViewTracking/Tests/WebViewMessageTests.swift create mode 100644 TestUtilities/Helpers/UIKitHelpers.swift create mode 100644 TestUtilities/Mocks/BacktraceReportingMocks.swift create mode 100644 TestUtilities/Mocks/FeatureModels/CrashReporting/BacktraceReportMocks.swift create mode 100644 TestUtilities/Mocks/FeatureModels/CrashReporting/BinaryImageMocks.swift create mode 100644 TestUtilities/Mocks/FeatureModels/CrashReporting/DDCrashReportMocks.swift create mode 100644 TestUtilities/Mocks/FeatureModels/CrashReporting/DDThreadMocks.swift create mode 100644 dependency-manager-tests/carthage/App/PrivacyInfo.xcprivacy create mode 100644 dependency-manager-tests/cocoapods/App/PrivacyInfo.xcprivacy create mode 100644 dependency-manager-tests/spm/App/PrivacyInfo.xcprivacy create mode 100644 dependency-manager-tests/xcframeworks/App/PrivacyInfo.xcprivacy diff --git a/.github/ISSUE_TEMPLATE/BugReport.yml b/.github/ISSUE_TEMPLATE/BugReport.yml new file mode 100644 index 0000000000..c03c6f1a6d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BugReport.yml @@ -0,0 +1,111 @@ +name: Bug Report +description: Is the SDK not working as expected? Help us improve by submitting a bug report. +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Ensure you go through our [troubleshooting](https://docs.datadoghq.com/real_user_monitoring/mobile_and_tv_monitoring/troubleshooting/#debugging-1) page before creating a new issue. + Before getting started, if the problem is urgent or easier to investigate with access to your organization's data please use our [official support channel](https://www.datadoghq.com/support/). + - type: textarea + id: description + attributes: + label: Describe the bug + description: Provide a clear and concise description of what the bug is. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Reproduction steps + description: | + Provide a self-contained piece of code demonstrating the bug. + For a more complex setup consider creating a small app that showcases the problem. + **Note** - Avoid sharing any business logic, credentials or tokens. + validations: + required: true + - type: textarea + id: logs + attributes: + label: SDK logs + description: | + Please provide console logs before, during and after the bug occurs. + validations: + required: false + - type: textarea + id: expected_behavior + attributes: + label: Expected behavior + description: Provide a clear and concise description of what you expected the SDK to do. + validations: + required: false + - type: input + id: affected_sdk_versions + attributes: + label: Affected SDK versions + description: What are the SDK versions you're seeing this bug in? + validations: + required: true + - type: input + id: last_working_sdk_version + attributes: + label: Latest working SDK version + description: What was the last SDK version that was working as expected? + validations: + required: true + - type: dropdown + id: checked_lastest_sdk + attributes: + label: Did you confirm if the latest SDK version fixes the bug? + options: + - 'Yes' + - 'No' + validations: + required: true + - type: dropdown + id: integration_method + attributes: + label: Integration Methods + options: + - SPM + - Cocoapods + - Carthage + - XCFramework + - Source + validations: + required: true + - type: input + id: xcode_version + attributes: + label: Xcode Version + description: e.g. Xcode 11.5 (15C500b), obtained with **xcodebuild -version** + - type: input + id: swift_version + attributes: + label: Swift Version + description: e.g. Swift 5.9 , obtained with **swift —version** + - type: input + id: mac_version + attributes: + label: MacOS Version + description: e.g. macOS Catalina 10.15.5 (19F96), obtained with **sw_vers** + - type: input + id: deployment_targe + attributes: + label: Deployment Target + description: | + What is the Deployment Target of your app? e.g. *iOS 12*, *iPhone* + *iPad* + - type: textarea + id: device_info + attributes: + label: Device Information + description: | + What are the common characteristics of devices you're seeing this bug in. + Specific models, OS versions, network state (wifi / cellular / offline), power state (plugged in / battery), etc. + - type: textarea + id: other_info + attributes: + label: Other relevant information + description: | + Other relevant information such as additional tooling in place, proxies, etc. + Anything that might be relevant for troubleshooting this bug. diff --git a/.github/ISSUE_TEMPLATE/CrashReport.yml b/.github/ISSUE_TEMPLATE/CrashReport.yml new file mode 100644 index 0000000000..74204d28b5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/CrashReport.yml @@ -0,0 +1,76 @@ +name: Crash Report +description: Report crashes caused by the SDK. +labels: ["crash"] +body: + - type: markdown + attributes: + value: | + Report crashes caused by the SDK. Please try to be as detailed as possible. + Before getting started, if the problem is urgent please use our [official support channel](https://www.datadoghq.com/support/). + - type: textarea + id: stacktrace + attributes: + label: Stack trace + description: Please provide us with the stack trace of the crash or a crash report. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Reproduction steps + description: | + Provide a self-contained piece of code demonstrating the crash if you can. + For a more complex setup consider creating a small app that showcases the problem. + **Note** - Avoid sharing any business logic, credentials or tokens. + validations: + required: false + - type: input + id: volume + attributes: + label: Volume + description: What percentage of your app sessions are impacted with this crash? + validations: + required: true + - type: input + id: affected_sdk_versions + attributes: + label: Affected SDK versions + description: What are the SDK versions you're seeing this crash in? + validations: + required: true + - type: input + id: last_working_sdk_version + attributes: + label: Latest working SDK version + description: If you know, what was the last SDK version where the crash did manifest itself? + validations: + required: true + - type: dropdown + id: checked_lastest_sdk + attributes: + label: Does the crash manifest in the latest SDK version? + options: + - 'Yes' + - 'No' + validations: + required: true + - type: input + id: deployment_targe + attributes: + label: Deployment Target + description: | + What is the Deployment Target of your app? e.g. *iOS 12*, *iPhone* + *iPad* + - type: textarea + id: device_info + attributes: + label: Device Information + description: | + What are the common characteristics of devices you're seeing this crash in? + Specific models, OS versions, etc. + validations: + required: false + - type: textarea + id: other_info + attributes: + label: Other relevant information + description: Anything that might be relevant to pinpoint the source of the crash. diff --git a/.github/ISSUE_TEMPLATE/FeatureRequest.yml b/.github/ISSUE_TEMPLATE/FeatureRequest.yml new file mode 100644 index 0000000000..7e7324be4f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FeatureRequest.yml @@ -0,0 +1,32 @@ +name: Feature Request +description: Have an idea or need a new feature? Request it here. +labels: ["feature"] +body: + - type: textarea + id: description + attributes: + label: Feature description + description: | + Provide a description for the feature request. Please include: + 1. Use case + 2. How the SDK currently delivers (or doesn't) + 3. What would you like to see + validations: + required: true + - type: textarea + id: proposed_solution + attributes: + label: Proposed solution + description: | + How would you implement this? + Propose an idea, solution or reference implementation. + validations: + required: false + - type: textarea + id: other_info + attributes: + label: Other relevant information + description: Any other relevant information you'd like we take into consideration. + validations: + required: false + diff --git a/.github/ISSUE_TEMPLATE/Question.yml b/.github/ISSUE_TEMPLATE/Question.yml new file mode 100644 index 0000000000..0f286a3568 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Question.yml @@ -0,0 +1,10 @@ +name: Question +description: Do you just have a question about the SDK or a product? Ask here. +labels: ["question"] +body: + - type: textarea + id: question + attributes: + label: Question + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/SetupIssue.yml b/.github/ISSUE_TEMPLATE/SetupIssue.yml new file mode 100644 index 0000000000..4812eeb0a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/SetupIssue.yml @@ -0,0 +1,81 @@ +name: Setup Issue +description: Having a hard time setting up the SDK for the first time? Maybe a compilation issue or just nothing seems to be happening. Seek help with this. +labels: ["compilation issue"] +body: + - type: markdown + attributes: + value: | + Before creating an issue, please ensure you go through the [troubleshooting page](https://docs.datadoghq.com/real_user_monitoring/mobile_and_tv_monitoring/troubleshooting/#debugging-1). + - type: textarea + id: issue + attributes: + label: Describe the issue + description: Provide a clear and concise description of the issue. Include compilation logs and SDK debug logs if relevant. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Reproduction steps + description: | + Provide a self-contained piece of code demonstrating the issue. + For a more complex setup consider creating a small app that showcases the problem. + **Note** - Avoid sharing any business logic, credentials or tokens. + validations: + required: true + - type: textarea + id: device_info + attributes: + label: Device Information + description: | + What are the common characteristics of devices you're seeing this issue in? + Simulators, specific models, OS versions, network state (wifi / cellular / offline), power state (plugged in / battery), etc. + validations: + required: false + - type: input + id: sdk_version + attributes: + label: SDK version + description: Which SDK version are you trying to use? + validations: + required: true + - type: dropdown + id: integration_method + attributes: + label: Integration Methods + options: + - SPM + - Cocoapods + - Carthage + - XCFramework + - Source + validations: + required: true + - type: input + id: xcode_version + attributes: + label: Xcode Version + description: e.g. Xcode 11.5 (15C500b), obtained with **xcodebuild -version** + - type: input + id: swift_version + attributes: + label: Swift Version + description: e.g. Swift 5.9 , obtained with **swift —version** + - type: input + id: mac_version + attributes: + label: MacOS Version + description: e.g. macOS Catalina 10.15.5 (19F96), obtained with **sw_vers** + - type: input + id: deployment_targe + attributes: + label: Deployment Target + description: | + What is the Deployment Target of your app? e.g. *iOS 12*, *iPhone* + *iPad* + - type: textarea + id: other_info + attributes: + label: Other relevant information + description: | + Other relevant information such as additional tooling in place, proxies, etc. + Anything that might be relevant for troubleshooting your setup. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 6d627a2b5a..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve the SDK. -title: '' -labels: 'bug' -assignees: '' - ---- - -### Describe the bug -A clear and concise description of what the bug is. - -### Reproduction - -Provide a self-contained, concise snippet of code that can be used to reproduce the issue. -For more complex issues provide a repo with the smallest sample that reproduces the bug. - -Avoid including business logic or unrelated code, it makes diagnosis more difficult. -The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. - -Do not share secrets or sensitive information in the code sample. - -### Expected behavior -A clear and concise description of what you expected to happen. - ---- - -#### Datadog SDK version: - -_Which version of the Datadog SDK causes this problem? e.g. `2.5.0`_ - -#### Last working Datadog SDK version: - -_What is the last Datadog SDK version where this problem didn't occur? e.g. `1.1.0`_ - -#### Dependency Manager: - -_Which dependency manager do you use? e.g. Cocoapods / Carthage / SPM / ..._ - -#### Other toolset: - -_Do you use additional tools with your dependency manager? e.g. [CarthageCache](https://github.com/Wolox/carthage_cache)_ - -#### Xcode version: - -_e.g. `Xcode 11.5 (11E608c)`_ - -#### Swift version: - -_e.g. `5.1`_ - -#### Deployment Target: - -_What is the Deployment Target of your app? e.g. `iOS 12`, `iPhone` + `iPad`_ - -#### macOS version: - -_e.g. `macOS Catalina 10.15.5 (19F96)`_ diff --git a/.github/ISSUE_TEMPLATE/compilation_issue.md b/.github/ISSUE_TEMPLATE/compilation_issue.md deleted file mode 100644 index 80bf197dfc..0000000000 --- a/.github/ISSUE_TEMPLATE/compilation_issue.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -name: Compilation Issue -about: Having a Cocoapods / Carthage / SPM problem when linking the SDK? -title: '' -labels: compilation issue -assignees: '' - ---- - -### The issue - -📝 Give us the error message you receive, describe the problem and answer the questions. - -### Reproduction - -Provide a self-contained, concise snippet of code that can be used to reproduce the issue. -For more complex issues provide a repo with the smallest sample that reproduces the bug. - -Avoid including business logic or unrelated code, it makes diagnosis more difficult. -The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. - -Do not share secrets or sensitive information in the code sample. - ---- - -#### Datadog SDK version: - -_Which version of the Datadog SDK causes this problem? e.g. `2.5.0`_ - -#### Last working Datadog SDK version: - -_What is the last Datadog SDK version where this problem didn't occur? e.g. `1.1.0`_ - -#### Dependency Manager: - -_Which dependency manager do you use? e.g. Cocoapods / Carthage / SPM / ..._ - -#### Other toolset: - -_Do you use additional tools with your dependency manager? e.g. [CarthageCache](https://github.com/Wolox/carthage_cache)_ - -#### Xcode version: - -_e.g. `Xcode 11.5 (11E608c)`_ - -#### Swift version: - -_e.g. `5.1`_ - -#### Deployment Target: - -_What is the Deployment Target of your app? e.g. `iOS 12`, `iPhone` + `iPad`_ - -#### macOS version: - -_e.g. `macOS Catalina 10.15.5 (19F96)`_ diff --git a/.github/ISSUE_TEMPLATE/crash_report.md b/.github/ISSUE_TEMPLATE/crash_report.md deleted file mode 100644 index 16a30671ae..0000000000 --- a/.github/ISSUE_TEMPLATE/crash_report.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: Crash -about: Noticed the SDK crash? -title: '' -labels: crash -assignees: '' - ---- - -### The crash - -📝 Give us the crash report or stack trace, describe the problem in details and answer the questions. - -### Reproduction - -Provide a self-contained, concise snippet of code that can be used to reproduce the issue. -For more complex issues provide a repo with the smallest sample that reproduces the bug. - -Avoid including business logic or unrelated code, it makes diagnosis more difficult. -The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. - -Do not share secrets or sensitive information in the code sample. - ---- - -#### Datadog SDK versions: - -_Which version(s) of the Datadog SDK you see this crash happening in?_ - -#### Last stable Datadog SDK version: - -_What is the last Datadog SDK version where this crash doesn't happen?_ - -#### Volume: - -_What % of your app sessions is impacted with this crash?_ - -#### OS version: - -_Which iOS versions does this crash happen on?_ - -#### Deployment Target: - -_What is the Deployment Target of your app? e.g. `iOS 12`, `iPhone` + `iPad`_ - -#### Device version: - -_Which devices does this crash happen on? e.g. `iPhone X` only or various iPads_ - -#### Environment: - -_Do you notice any environment correlation in crash reports? e.g. low battery, no internet connection, memory pressure_ diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 3a869ad599..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: 'feature' -assignees: '' - ---- - -### Is your feature request related to a problem? Please describe. - -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -### Describe the solution you'd like -A clear and concise description of what you want to happen. - -### Describe alternatives you've considered -A clear and concise description of any alternative solutions or features you've considered. - -### Additional context -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md deleted file mode 100644 index 2ee2fb6f39..0000000000 --- a/.github/ISSUE_TEMPLATE/other.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: Other -about: Noticed a bug, having a question or a feature request? -title: '' -labels: '' -assignees: '' - ---- - -### The thing - -Tell us the thing 🙂 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d3fdc8a8aa..ddf3917437 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,7 @@ ENV info: stage: info tags: - mac-ventura-preview + allow_failure: true # do not block GH PRs script: - system_profiler SPSoftwareDataType # system info - xcodebuild -version @@ -26,6 +27,7 @@ Lint: stage: lint tags: - mac-ventura-preview + allow_failure: true # do not block GH PRs script: - ./tools/lint/run-linter.sh - ./tools/license/check-license.sh @@ -34,6 +36,7 @@ SDK unit tests (iOS): stage: test tags: - mac-ventura-preview + allow_failure: true # do not block GH PRs variables: TEST_WORKSPACE: "Datadog.xcworkspace" TEST_DESTINATION: "platform=iOS Simulator,name=iPhone 15 Pro Max,OS=17.0.1" @@ -52,6 +55,7 @@ SDK unit tests (tvOS): stage: test tags: - mac-ventura-preview + allow_failure: true # do not block GH PRs variables: TEST_WORKSPACE: "Datadog.xcworkspace" TEST_DESTINATION: "platform=tvOS Simulator,name=Apple TV,OS=17.0" @@ -68,6 +72,7 @@ SDK integration tests (iOS): stage: test tags: - mac-ventura-preview + allow_failure: true # do not block GH PRs variables: TEST_WORKSPACE: "IntegrationTests/IntegrationTests.xcworkspace" TEST_DESTINATION: "platform=iOS Simulator,name=iPhone 15 Pro Max,OS=17.0.1" diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c6bc859e..b8f5fe6064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ - [FIX] Propagate parent span in distributing tracing. See [#1627][] - [FIX] Privacy Report missing properties. See [#1656][] +- [FEATURE] App Hangs are tracked as RUM errors. See [#1685][] +- [FIX] Propagate parent span in distributing tracing. See [#1627][] +- [IMPROVEMENT] Add Device's Brand, Name, and Model in LogEvent. See [#1672][] (Thanks [@aldoKelvianto][]) +- [FEATURE] Improved image recording in Session Replay. See [#1592][] + +# 2.7.1 / 12-02-2024 + +- [FIX] Privacy Report missing properties. See [#1656][] +- [FIX] Privacy manifest collision in static framework. See [#1666][] # 2.7.0 / 25-01-2024 @@ -589,11 +598,16 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1531]: https://github.com/DataDog/dd-sdk-ios/pull/1531 [#1637]: https://github.com/DataDog/dd-sdk-ios/pull/1637 [#1541]: https://github.com/DataDog/dd-sdk-ios/pull/1541 +[#1592]: https://github.com/DataDog/dd-sdk-ios/pull/1592 +[#1672]: https://github.com/DataDog/dd-sdk-ios/pull/1672 [#1596]: https://github.com/DataDog/dd-sdk-ios/pull/1596 [#1597]: https://github.com/DataDog/dd-sdk-ios/pull/1597 [#1627]: https://github.com/DataDog/dd-sdk-ios/pull/1627 [#1644]: https://github.com/DataDog/dd-sdk-ios/pull/1644 [#1656]: https://github.com/DataDog/dd-sdk-ios/pull/1656 +[#1685]: https://github.com/DataDog/dd-sdk-ios/pull/1685 +[#1656]: https://github.com/DataDog/dd-sdk-ios/pull/1656 +[#1666]: https://github.com/DataDog/dd-sdk-ios/pull/1666 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu @@ -611,6 +625,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [@lgaches]: https://github.com/lgaches [@lmramirez]: https://github.com/lmramirez [@marcusway]: https://github.com/marcusway +[@aldoKelvianto]: https://github.com/aldoKelvianto [@matcartmill]: https://github.com/matcartmill [@michalsrutek]: https://github.com/michalsrutek [@philtre]: https://github.com/philtre diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b314cb8d55..0caa069e9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,8 @@ First of all, thanks for contributing! This document provides some basic guidelines for contributing to this repository. To propose improvements, feel free to submit a PR or open an Issue. +**Note:** Datadog requires that all commits within this repository must be signed, including those within external contribution PRs. Please ensure you have followed GitHub's [Signing Commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) guide before proposing a contribution. PRs lacking signed commits will not be processed and may be rejected. + ## Have a feature request or idea? Many great ideas for new features come from the community, and we'd be happy to consider yours 👍. diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index c9625aef2b..aaae6e765a 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -102,7 +102,7 @@ 61054E662A6EE10A00AAA894 /* KeyWindowObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E102A6EE10A00AAA894 /* KeyWindowObserver.swift */; }; 61054E672A6EE10A00AAA894 /* Recorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E112A6EE10A00AAA894 /* Recorder.swift */; }; 61054E682A6EE10A00AAA894 /* PrivacyLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E122A6EE10A00AAA894 /* PrivacyLevel.swift */; }; - 61054E692A6EE10A00AAA894 /* ImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E142A6EE10A00AAA894 /* ImageDataProvider.swift */; }; + 61054E692A6EE10A00AAA894 /* UIImage+SessionReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E142A6EE10A00AAA894 /* UIImage+SessionReplay.swift */; }; 61054E6A2A6EE10A00AAA894 /* UIKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E152A6EE10A00AAA894 /* UIKitExtensions.swift */; }; 61054E6B2A6EE10A00AAA894 /* CFType+Safety.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E162A6EE10A00AAA894 /* CFType+Safety.swift */; }; 61054E6C2A6EE10A00AAA894 /* SystemColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E172A6EE10A00AAA894 /* SystemColors.swift */; }; @@ -141,8 +141,6 @@ 61054E8E2A6EE10A00AAA894 /* SRContextPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E3F2A6EE10A00AAA894 /* SRContextPublisher.swift */; }; 61054E8F2A6EE10A00AAA894 /* SegmentRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E412A6EE10A00AAA894 /* SegmentRequestBuilder.swift */; }; 61054E902A6EE10A00AAA894 /* SegmentJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E432A6EE10A00AAA894 /* SegmentJSON.swift */; }; - 61054E912A6EE10A00AAA894 /* EnrichedRecordJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E442A6EE10A00AAA894 /* EnrichedRecordJSON.swift */; }; - 61054E922A6EE10A00AAA894 /* SegmentJSONBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E452A6EE10A00AAA894 /* SegmentJSONBuilder.swift */; }; 61054E932A6EE10A00AAA894 /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E472A6EE10A00AAA894 /* MultipartFormData.swift */; }; 61054E942A6EE10A00AAA894 /* TextObfuscator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E4A2A6EE10A00AAA894 /* TextObfuscator.swift */; }; 61054E952A6EE10A00AAA894 /* SnapshotProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E4B2A6EE10A00AAA894 /* SnapshotProcessor.swift */; }; @@ -153,14 +151,12 @@ 61054E9A2A6EE10A00AAA894 /* NodesFlattener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E532A6EE10A00AAA894 /* NodesFlattener.swift */; }; 61054E9B2A6EE10B00AAA894 /* CGRectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E552A6EE10A00AAA894 /* CGRectExtensions.swift */; }; 61054E9C2A6EE10B00AAA894 /* UIImage+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E562A6EE10A00AAA894 /* UIImage+Scaling.swift */; }; - 61054E9D2A6EE10B00AAA894 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E572A6EE10A00AAA894 /* Cache.swift */; }; 61054E9E2A6EE10B00AAA894 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E582A6EE10A00AAA894 /* Queue.swift */; }; 61054E9F2A6EE10B00AAA894 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E592A6EE10A00AAA894 /* Errors.swift */; }; 61054EA02A6EE10B00AAA894 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E5A2A6EE10A00AAA894 /* Colors.swift */; }; 61054EA12A6EE10B00AAA894 /* MainThreadScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E5C2A6EE10A00AAA894 /* MainThreadScheduler.swift */; }; 61054EA22A6EE10B00AAA894 /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054E5D2A6EE10A00AAA894 /* Scheduler.swift */; }; 61054F952A6EE1BA00AAA894 /* SessionReplayConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F3D2A6EE1B900AAA894 /* SessionReplayConfigurationTests.swift */; }; - 61054F962A6EE1BA00AAA894 /* CacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F3F2A6EE1B900AAA894 /* CacheTests.swift */; }; 61054F972A6EE1BA00AAA894 /* UIImage+ScalingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F402A6EE1B900AAA894 /* UIImage+ScalingTests.swift */; }; 61054F982A6EE1BA00AAA894 /* CGRectExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F412A6EE1B900AAA894 /* CGRectExtensionsTests.swift */; }; 61054F992A6EE1BA00AAA894 /* ColorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F422A6EE1B900AAA894 /* ColorsTests.swift */; }; @@ -171,7 +167,6 @@ 61054F9E2A6EE1BA00AAA894 /* SessionReplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F482A6EE1B900AAA894 /* SessionReplayTests.swift */; }; 61054F9F2A6EE1BA00AAA894 /* RecordsWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F4A2A6EE1BA00AAA894 /* RecordsWriterTests.swift */; }; 61054FA02A6EE1BA00AAA894 /* SRCompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F4B2A6EE1BA00AAA894 /* SRCompressionTests.swift */; }; - 61054FA12A6EE1BA00AAA894 /* EnrichedRecordTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F4D2A6EE1BA00AAA894 /* EnrichedRecordTests.swift */; }; 61054FA22A6EE1BA00AAA894 /* TextObfuscatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F502A6EE1BA00AAA894 /* TextObfuscatorTests.swift */; }; 61054FA32A6EE1BA00AAA894 /* Diff+SRWireframesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F522A6EE1BA00AAA894 /* Diff+SRWireframesTests.swift */; }; 61054FA42A6EE1BA00AAA894 /* DiffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F532A6EE1BA00AAA894 /* DiffTests.swift */; }; @@ -180,7 +175,6 @@ 61054FA72A6EE1BA00AAA894 /* NodesFlattenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F582A6EE1BA00AAA894 /* NodesFlattenerTests.swift */; }; 61054FA82A6EE1BA00AAA894 /* RecordingCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F5A2A6EE1BA00AAA894 /* RecordingCoordinatorTests.swift */; }; 61054FAA2A6EE1BA00AAA894 /* UIKitExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F5D2A6EE1BA00AAA894 /* UIKitExtensionsTests.swift */; }; - 61054FAB2A6EE1BA00AAA894 /* ImageDataProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F5E2A6EE1BA00AAA894 /* ImageDataProviderTests.swift */; }; 61054FAC2A6EE1BA00AAA894 /* CGRect+ContentFrameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F5F2A6EE1BA00AAA894 /* CGRect+ContentFrameTests.swift */; }; 61054FAD2A6EE1BA00AAA894 /* WindowTouchSnapshotProducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F612A6EE1BA00AAA894 /* WindowTouchSnapshotProducerTests.swift */; }; 61054FAE2A6EE1BA00AAA894 /* TouchIdentifierGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F632A6EE1BA00AAA894 /* TouchIdentifierGeneratorTests.swift */; }; @@ -213,13 +207,10 @@ 61054FC92A6EE1BA00AAA894 /* RecorderMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F822A6EE1BA00AAA894 /* RecorderMocks.swift */; }; 61054FCA2A6EE1BA00AAA894 /* TestScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F832A6EE1BA00AAA894 /* TestScheduler.swift */; }; 61054FCB2A6EE1BA00AAA894 /* QueueMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F842A6EE1BA00AAA894 /* QueueMocks.swift */; }; - 61054FCC2A6EE1BA00AAA894 /* MockImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F852A6EE1BA00AAA894 /* MockImageDataProvider.swift */; }; 61054FCD2A6EE1BA00AAA894 /* SnapshotProducerMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F862A6EE1BA00AAA894 /* SnapshotProducerMocks.swift */; }; 61054FCE2A6EE1BA00AAA894 /* RUMContextObserverMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F872A6EE1BA00AAA894 /* RUMContextObserverMock.swift */; }; 61054FCF2A6EE1BA00AAA894 /* RUMContextReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F892A6EE1BA00AAA894 /* RUMContextReceiverTests.swift */; }; 61054FD02A6EE1BA00AAA894 /* SRContextPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F8A2A6EE1BA00AAA894 /* SRContextPublisherTests.swift */; }; - 61054FD12A6EE1BA00AAA894 /* SegmentJSONBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F8D2A6EE1BA00AAA894 /* SegmentJSONBuilderTests.swift */; }; - 61054FD22A6EE1BA00AAA894 /* EnrichedRecordJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F8E2A6EE1BA00AAA894 /* EnrichedRecordJSONTests.swift */; }; 61054FD32A6EE1BA00AAA894 /* MultipartFormDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F902A6EE1BA00AAA894 /* MultipartFormDataTests.swift */; }; 61054FD42A6EE1BA00AAA894 /* SegmentRequestBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F912A6EE1BA00AAA894 /* SegmentRequestBuilderTests.swift */; }; 61054FD52A6EE1BA00AAA894 /* XCTAssertRectsEqual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F932A6EE1BA00AAA894 /* XCTAssertRectsEqual.swift */; }; @@ -333,6 +324,44 @@ 615CC4132695957C0005F08C /* CrashReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615CC4122695957C0005F08C /* CrashReportTests.swift */; }; 6167C79326665D6900D4CF07 /* E2EUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167C79226665D6900D4CF07 /* E2EUtils.swift */; }; 6167C7952666622800D4CF07 /* LoggingE2EHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167C7942666622800D4CF07 /* LoggingE2EHelpers.swift */; }; + 6167E6D32B7F8B3300C3CA2D /* AppHangsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D22B7F8B3300C3CA2D /* AppHangsObserver.swift */; }; + 6167E6D42B7F8B3300C3CA2D /* AppHangsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D22B7F8B3300C3CA2D /* AppHangsObserver.swift */; }; + 6167E6D62B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D52B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift */; }; + 6167E6D72B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D52B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift */; }; + 6167E6DA2B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D92B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift */; }; + 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 */; }; + 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 */; }; + 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 */; }; + 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 */; }; + 6167E7142B837F0B00C3CA2D /* BacktraceReportingMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7112B837F0B00C3CA2D /* BacktraceReportingMocks.swift */; }; + 6167E7152B837F0B00C3CA2D /* BacktraceReportingMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7112B837F0B00C3CA2D /* BacktraceReportingMocks.swift */; }; + 6167E71B2B837F7A00C3CA2D /* BacktraceReportMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7182B837F7A00C3CA2D /* BacktraceReportMocks.swift */; }; + 6167E71C2B837F7A00C3CA2D /* BacktraceReportMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7182B837F7A00C3CA2D /* BacktraceReportMocks.swift */; }; + 6167E7202B837FB200C3CA2D /* DDThreadMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E71D2B837FB200C3CA2D /* DDThreadMocks.swift */; }; + 6167E7212B837FB200C3CA2D /* DDThreadMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E71D2B837FB200C3CA2D /* DDThreadMocks.swift */; }; + 6167E7252B837FF100C3CA2D /* BinaryImageMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7222B837FF100C3CA2D /* BinaryImageMocks.swift */; }; + 6167E7262B837FF100C3CA2D /* BinaryImageMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7222B837FF100C3CA2D /* BinaryImageMocks.swift */; }; + 6167E7292B84C11900C3CA2D /* DDCrashReportMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7282B84C11900C3CA2D /* DDCrashReportMocks.swift */; }; + 6167E72A2B84C11900C3CA2D /* DDCrashReportMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E7282B84C11900C3CA2D /* DDCrashReportMocks.swift */; }; + 6167E72C2B84C72B00C3CA2D /* UIKitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E72B2B84C72B00C3CA2D /* UIKitHelpers.swift */; }; + 6167E72D2B84C72B00C3CA2D /* UIKitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E72B2B84C72B00C3CA2D /* UIKitHelpers.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 */; }; @@ -404,7 +433,6 @@ 61A2CC3C2A44BED30000FF25 /* Tracer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A2CC3B2A44BED30000FF25 /* Tracer.swift */; }; 61A2CC3D2A44BED30000FF25 /* Tracer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A2CC3B2A44BED30000FF25 /* Tracer.swift */; }; 61A763DC252DB2B3005A23F2 /* NSURLSessionBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 61A763DB252DB2B3005A23F2 /* NSURLSessionBridge.m */; }; - 61AE74132AD6EF49008DB9BB /* WebViewMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AE740F2AD6EE4E008DB9BB /* WebViewMessageTests.swift */; }; 61AE74142AD6EF55008DB9BB /* JSONObjectMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612C13D22AAA20660086B5D1 /* JSONObjectMatcher.swift */; }; 61AE74152AD6EF55008DB9BB /* JSONObjectMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612C13D22AAA20660086B5D1 /* JSONObjectMatcher.swift */; }; 61AE74172AD7DA9B008DB9BB /* FeatureMessageMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AE74162AD7DA9B008DB9BB /* FeatureMessageMocks.swift */; }; @@ -514,6 +542,7 @@ 9EE5AD8226205B82001E699E /* DDNSURLSessionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EE5AD8126205B82001E699E /* DDNSURLSessionDelegateTests.swift */; }; A70A82652A935F210072F5DC /* BackgroundTaskCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70A82642A935F210072F5DC /* BackgroundTaskCoordinator.swift */; }; A70A82662A935F210072F5DC /* BackgroundTaskCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70A82642A935F210072F5DC /* BackgroundTaskCoordinator.swift */; }; + A70ADCD22B583B1300321BC9 /* UIImageResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70ADCD12B583B1300321BC9 /* UIImageResource.swift */; }; A71013D62B178FAD00101E60 /* ResourcesWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71013D52B178FAD00101E60 /* ResourcesWriterTests.swift */; }; A71265862B17980C007D63CE /* MockFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71265852B17980C007D63CE /* MockFeature.swift */; }; A728ADAB2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728ADAA2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift */; }; @@ -543,6 +572,7 @@ A7DA18072AB0CA5E00F76337 /* DDUIKitRUMActionsPredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DA18062AB0CA4700F76337 /* DDUIKitRUMActionsPredicateTests.swift */; }; A7EA11622AB0CE6C00C73970 /* DDUIKitRUMActionsPredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DA18062AB0CA4700F76337 /* DDUIKitRUMActionsPredicateTests.swift */; }; A7EA88562B17639A00FE2580 /* ResourcesWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7EA88552B17639A00FE2580 /* ResourcesWriter.swift */; }; + A7F651302B7655DE004B0EDB /* UIImageResourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F6512F2B7655DE004B0EDB /* UIImageResourceTests.swift */; }; D20605A3287464F40047275C /* ContextValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20605A2287464F40047275C /* ContextValuePublisher.swift */; }; D20605A4287464F40047275C /* ContextValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20605A2287464F40047275C /* ContextValuePublisher.swift */; }; D20605A6287476230047275C /* ServerOffsetPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20605A5287476230047275C /* ServerOffsetPublisher.swift */; }; @@ -636,6 +666,8 @@ D2181A8F2B051B7900A518C0 /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */; }; D21831552B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21831542B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift */; }; D21831562B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21831542B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift */; }; + D21A94F22B8397CA00AC4256 /* WebViewMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */; }; + D21A94F32B8397CA00AC4256 /* WebViewMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */; }; D21AE6BC29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */; }; D21AE6BD29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */; }; D21C26C528A3B49C005DD405 /* FeatureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26C428A3B49C005DD405 /* FeatureStorage.swift */; }; @@ -912,6 +944,7 @@ D2579596298AC927008A1BE5 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257958B298ABB83008A1BE5 /* TestUtilities.framework */; }; D2579599298AD95F008A1BE5 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257953E298ABA65008A1BE5 /* TestUtilities.framework */; }; D257959A298AD967008A1BE5 /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257958B298ABB83008A1BE5 /* TestUtilities.framework */; }; + D25C834C2B8657CF008E73B1 /* SegmentJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D25C834B2B8657CF008E73B1 /* SegmentJSONTests.swift */; }; D25CFA9829C4F41900E3A43D /* DatadogTrace.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2C1A55A29C4F2DF00946C31 /* DatadogTrace.framework */; }; D25CFA9929C4F41900E3A43D /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257958B298ABB83008A1BE5 /* TestUtilities.framework */; }; D25CFA9C29C4FC6900E3A43D /* DatadogTrace.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; }; @@ -970,7 +1003,6 @@ D295A16629F299C9007C0E9A /* URLSessionInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D295A16429F299C9007C0E9A /* URLSessionInterceptor.swift */; }; D29732492A5C108700827599 /* DDScriptMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29732462A5C108700827599 /* DDScriptMessageHandler.swift */; }; D297324B2A5C108700827599 /* MessageEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29732472A5C108700827599 /* MessageEmitter.swift */; }; - D297324D2A5C108700827599 /* WebViewMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29732482A5C108700827599 /* WebViewMessage.swift */; }; D29732512A5C109A00827599 /* MessageEmitterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D297324F2A5C109A00827599 /* MessageEmitterTests.swift */; }; D29732532A5C109A00827599 /* WebViewTrackingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29732502A5C109A00827599 /* WebViewTrackingTests.swift */; }; D29A9F3C29DD84AB005C54A4 /* DatadogRUM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29A9F3429DD84AA005C54A4 /* DatadogRUM.framework */; }; @@ -1130,6 +1162,8 @@ D2B3F04E282A85FD00C2B5EE /* DatadogCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F04C282A85FD00C2B5EE /* DatadogCore.swift */; }; D2B3F052282E827700C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; D2B3F053282E827B00C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; + D2BCB2A12B7B8107005C2AAB /* WKWebViewRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BCB2A02B7B8107005C2AAB /* WKWebViewRecorder.swift */; }; + D2BCB2A32B7B9683005C2AAB /* WKWebViewRecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BCB2A22B7B9683005C2AAB /* WKWebViewRecorderTests.swift */; }; D2BEEDAC2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */; }; D2BEEDAD2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */; }; D2BEEDAF2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */; }; @@ -1217,6 +1251,8 @@ D2C1A56929C4F2E800946C31 /* ActiveSpansPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D203FB24C1884500D1AF3A /* ActiveSpansPoolTests.swift */; }; D2C1A56A29C4F2E800946C31 /* SpanSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61122EE725B1C92500F9C7F5 /* SpanSanitizerTests.swift */; }; D2C1A57429C4F30000946C31 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2DA2385298D57AA00C6C7E6 /* DatadogInternal.framework */; }; + D2C5D5282B83FD5300B63F36 /* WebViewMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AE740F2AD6EE4E008DB9BB /* WebViewMessageTests.swift */; }; + D2C5D5292B83FD5400B63F36 /* WebViewMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AE740F2AD6EE4E008DB9BB /* WebViewMessageTests.swift */; }; D2C7E3AB28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */; }; D2C7E3AE28FEBDA10023B2CC /* LaunchTimePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C7E3AD28FEBDA10023B2CC /* LaunchTimePublisher.swift */; }; D2CB6E0C27C50EAE00A62B57 /* DatadogCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 61133B85242393DE00786299 /* DatadogCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1943,7 +1979,7 @@ 61054E102A6EE10A00AAA894 /* KeyWindowObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyWindowObserver.swift; sourceTree = ""; }; 61054E112A6EE10A00AAA894 /* Recorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Recorder.swift; sourceTree = ""; }; 61054E122A6EE10A00AAA894 /* PrivacyLevel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyLevel.swift; sourceTree = ""; }; - 61054E142A6EE10A00AAA894 /* ImageDataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDataProvider.swift; sourceTree = ""; }; + 61054E142A6EE10A00AAA894 /* UIImage+SessionReplay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+SessionReplay.swift"; sourceTree = ""; }; 61054E152A6EE10A00AAA894 /* UIKitExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitExtensions.swift; sourceTree = ""; }; 61054E162A6EE10A00AAA894 /* CFType+Safety.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CFType+Safety.swift"; sourceTree = ""; }; 61054E172A6EE10A00AAA894 /* SystemColors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemColors.swift; sourceTree = ""; }; @@ -1982,8 +2018,6 @@ 61054E3F2A6EE10A00AAA894 /* SRContextPublisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SRContextPublisher.swift; sourceTree = ""; }; 61054E412A6EE10A00AAA894 /* SegmentRequestBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentRequestBuilder.swift; sourceTree = ""; }; 61054E432A6EE10A00AAA894 /* SegmentJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentJSON.swift; sourceTree = ""; }; - 61054E442A6EE10A00AAA894 /* EnrichedRecordJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnrichedRecordJSON.swift; sourceTree = ""; }; - 61054E452A6EE10A00AAA894 /* SegmentJSONBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentJSONBuilder.swift; sourceTree = ""; }; 61054E472A6EE10A00AAA894 /* MultipartFormData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormData.swift; sourceTree = ""; }; 61054E4A2A6EE10A00AAA894 /* TextObfuscator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextObfuscator.swift; sourceTree = ""; }; 61054E4B2A6EE10A00AAA894 /* SnapshotProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotProcessor.swift; sourceTree = ""; }; @@ -1994,14 +2028,12 @@ 61054E532A6EE10A00AAA894 /* NodesFlattener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodesFlattener.swift; sourceTree = ""; }; 61054E552A6EE10A00AAA894 /* CGRectExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGRectExtensions.swift; sourceTree = ""; }; 61054E562A6EE10A00AAA894 /* UIImage+Scaling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = ""; }; - 61054E572A6EE10A00AAA894 /* Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = ""; }; 61054E582A6EE10A00AAA894 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = ""; }; 61054E592A6EE10A00AAA894 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 61054E5A2A6EE10A00AAA894 /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; 61054E5C2A6EE10A00AAA894 /* MainThreadScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainThreadScheduler.swift; sourceTree = ""; }; 61054E5D2A6EE10A00AAA894 /* Scheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scheduler.swift; sourceTree = ""; }; 61054F3D2A6EE1B900AAA894 /* SessionReplayConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionReplayConfigurationTests.swift; sourceTree = ""; }; - 61054F3F2A6EE1B900AAA894 /* CacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheTests.swift; sourceTree = ""; }; 61054F402A6EE1B900AAA894 /* UIImage+ScalingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+ScalingTests.swift"; sourceTree = ""; }; 61054F412A6EE1B900AAA894 /* CGRectExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGRectExtensionsTests.swift; sourceTree = ""; }; 61054F422A6EE1B900AAA894 /* ColorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorsTests.swift; sourceTree = ""; }; @@ -2012,7 +2044,6 @@ 61054F482A6EE1B900AAA894 /* SessionReplayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionReplayTests.swift; sourceTree = ""; }; 61054F4A2A6EE1BA00AAA894 /* RecordsWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordsWriterTests.swift; sourceTree = ""; }; 61054F4B2A6EE1BA00AAA894 /* SRCompressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SRCompressionTests.swift; sourceTree = ""; }; - 61054F4D2A6EE1BA00AAA894 /* EnrichedRecordTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnrichedRecordTests.swift; sourceTree = ""; }; 61054F502A6EE1BA00AAA894 /* TextObfuscatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextObfuscatorTests.swift; sourceTree = ""; }; 61054F522A6EE1BA00AAA894 /* Diff+SRWireframesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Diff+SRWireframesTests.swift"; sourceTree = ""; }; 61054F532A6EE1BA00AAA894 /* DiffTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffTests.swift; sourceTree = ""; }; @@ -2021,7 +2052,6 @@ 61054F582A6EE1BA00AAA894 /* NodesFlattenerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodesFlattenerTests.swift; sourceTree = ""; }; 61054F5A2A6EE1BA00AAA894 /* RecordingCoordinatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordingCoordinatorTests.swift; sourceTree = ""; }; 61054F5D2A6EE1BA00AAA894 /* UIKitExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitExtensionsTests.swift; sourceTree = ""; }; - 61054F5E2A6EE1BA00AAA894 /* ImageDataProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDataProviderTests.swift; sourceTree = ""; }; 61054F5F2A6EE1BA00AAA894 /* CGRect+ContentFrameTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect+ContentFrameTests.swift"; sourceTree = ""; }; 61054F612A6EE1BA00AAA894 /* WindowTouchSnapshotProducerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowTouchSnapshotProducerTests.swift; sourceTree = ""; }; 61054F632A6EE1BA00AAA894 /* TouchIdentifierGeneratorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchIdentifierGeneratorTests.swift; sourceTree = ""; }; @@ -2054,13 +2084,10 @@ 61054F822A6EE1BA00AAA894 /* RecorderMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecorderMocks.swift; sourceTree = ""; }; 61054F832A6EE1BA00AAA894 /* TestScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestScheduler.swift; sourceTree = ""; }; 61054F842A6EE1BA00AAA894 /* QueueMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueueMocks.swift; sourceTree = ""; }; - 61054F852A6EE1BA00AAA894 /* MockImageDataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockImageDataProvider.swift; sourceTree = ""; }; 61054F862A6EE1BA00AAA894 /* SnapshotProducerMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotProducerMocks.swift; sourceTree = ""; }; 61054F872A6EE1BA00AAA894 /* RUMContextObserverMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMContextObserverMock.swift; sourceTree = ""; }; 61054F892A6EE1BA00AAA894 /* RUMContextReceiverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMContextReceiverTests.swift; sourceTree = ""; }; 61054F8A2A6EE1BA00AAA894 /* SRContextPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SRContextPublisherTests.swift; sourceTree = ""; }; - 61054F8D2A6EE1BA00AAA894 /* SegmentJSONBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentJSONBuilderTests.swift; sourceTree = ""; }; - 61054F8E2A6EE1BA00AAA894 /* EnrichedRecordJSONTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnrichedRecordJSONTests.swift; sourceTree = ""; }; 61054F902A6EE1BA00AAA894 /* MultipartFormDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormDataTests.swift; sourceTree = ""; }; 61054F912A6EE1BA00AAA894 /* SegmentRequestBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentRequestBuilderTests.swift; sourceTree = ""; }; 61054F932A6EE1BA00AAA894 /* XCTAssertRectsEqual.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTAssertRectsEqual.swift; sourceTree = ""; }; @@ -2213,6 +2240,25 @@ 6167ACBD251A0B410012B4D0 /* Example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example-Bridging-Header.h"; sourceTree = ""; }; 6167C79226665D6900D4CF07 /* E2EUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = E2EUtils.swift; sourceTree = ""; }; 6167C7942666622800D4CF07 /* LoggingE2EHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingE2EHelpers.swift; sourceTree = ""; }; + 6167E6D22B7F8B3300C3CA2D /* AppHangsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsObserver.swift; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 6167E7112B837F0B00C3CA2D /* BacktraceReportingMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReportingMocks.swift; sourceTree = ""; }; + 6167E7182B837F7A00C3CA2D /* BacktraceReportMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BacktraceReportMocks.swift; sourceTree = ""; }; + 6167E71D2B837FB200C3CA2D /* DDThreadMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDThreadMocks.swift; sourceTree = ""; }; + 6167E7222B837FF100C3CA2D /* BinaryImageMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryImageMocks.swift; sourceTree = ""; }; + 6167E7282B84C11900C3CA2D /* DDCrashReportMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDCrashReportMocks.swift; sourceTree = ""; }; + 6167E72B2B84C72B00C3CA2D /* UIKitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitHelpers.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 = ""; }; @@ -2443,6 +2489,7 @@ 9EC8B5ED2668E4DB000F7529 /* VitalCPUReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalCPUReaderTests.swift; sourceTree = ""; }; 9EE5AD8126205B82001E699E /* DDNSURLSessionDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDNSURLSessionDelegateTests.swift; sourceTree = ""; }; A70A82642A935F210072F5DC /* BackgroundTaskCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTaskCoordinator.swift; sourceTree = ""; }; + A70ADCD12B583B1300321BC9 /* UIImageResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageResource.swift; sourceTree = ""; }; A71013D52B178FAD00101E60 /* ResourcesWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcesWriterTests.swift; sourceTree = ""; }; A71265852B17980C007D63CE /* MockFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFeature.swift; sourceTree = ""; }; A728AD9C2934CE4400397996 /* W3CHTTPHeaders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = W3CHTTPHeaders.swift; sourceTree = ""; }; @@ -2472,6 +2519,7 @@ A7DA18022AB0C8A700F76337 /* DDUIKitRUMViewsPredicateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDUIKitRUMViewsPredicateTests.swift; sourceTree = ""; }; A7DA18062AB0CA4700F76337 /* DDUIKitRUMActionsPredicateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDUIKitRUMActionsPredicateTests.swift; sourceTree = ""; }; A7EA88552B17639A00FE2580 /* ResourcesWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcesWriter.swift; sourceTree = ""; }; + A7F6512F2B7655DE004B0EDB /* UIImageResourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageResourceTests.swift; sourceTree = ""; }; A7F773D32924EA2D00AC1A62 /* B3HTTPHeaders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = B3HTTPHeaders.swift; sourceTree = ""; }; A7F773DB29253F8B00AC1A62 /* B3HTTPHeadersWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = B3HTTPHeadersWriter.swift; sourceTree = ""; }; A7F773DC29253F8B00AC1A62 /* B3HTTPHeadersReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = B3HTTPHeadersReader.swift; sourceTree = ""; }; @@ -2516,6 +2564,7 @@ D2181A8A2B0500BB00A518C0 /* NetworkInstrumentationSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInstrumentationSwizzler.swift; sourceTree = ""; }; D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzlerTests.swift; sourceTree = ""; }; D21831542B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInstrumentationIntegrationTests.swift; sourceTree = ""; }; + D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewMessage.swift; sourceTree = ""; }; D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryTests.swift; sourceTree = ""; }; D21C26C428A3B49C005DD405 /* FeatureStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureStorage.swift; sourceTree = ""; }; D21C26D028A64599005DD405 /* MessageBusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBusTests.swift; sourceTree = ""; }; @@ -2617,6 +2666,7 @@ D2579591298ABCED008A1BE5 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; D2579593298ABCF5008A1BE5 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/AppleTVOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; D25BADA029C1EF3000112069 /* TracingURLSessionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingURLSessionHandler.swift; sourceTree = ""; }; + D25C834B2B8657CF008E73B1 /* SegmentJSONTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentJSONTests.swift; sourceTree = ""; }; D25CFA9E29C85FA400E3A43D /* TracingFeatureMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingFeatureMocks.swift; sourceTree = ""; }; D25CFAA129C8644E00E3A43D /* Casting+Tracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Casting+Tracing.swift"; sourceTree = ""; }; D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogTrace.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2645,7 +2695,6 @@ D295A16429F299C9007C0E9A /* URLSessionInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionInterceptor.swift; sourceTree = ""; }; D29732462A5C108700827599 /* DDScriptMessageHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDScriptMessageHandler.swift; sourceTree = ""; }; D29732472A5C108700827599 /* MessageEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageEmitter.swift; sourceTree = ""; }; - D29732482A5C108700827599 /* WebViewMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebViewMessage.swift; sourceTree = ""; }; D297324F2A5C109A00827599 /* MessageEmitterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageEmitterTests.swift; sourceTree = ""; }; D29732502A5C109A00827599 /* WebViewTrackingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebViewTrackingTests.swift; sourceTree = ""; }; D29889C72734136200A4D1A9 /* RUMViewsHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewsHandlerTests.swift; sourceTree = ""; }; @@ -2678,6 +2727,8 @@ D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDHTTPHeadersWriter+apiTests.m"; sourceTree = ""; }; D2BCB11E29D30AF000737A9A /* URLSessionRUMResourcesHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionRUMResourcesHandler.swift; sourceTree = ""; }; D2BCB12129D34A5F00737A9A /* URLSessionRUMResourcesHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionRUMResourcesHandlerTests.swift; sourceTree = ""; }; + D2BCB2A02B7B8107005C2AAB /* WKWebViewRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKWebViewRecorder.swift; sourceTree = ""; }; + D2BCB2A22B7B9683005C2AAB /* WKWebViewRecorderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKWebViewRecorderTests.swift; sourceTree = ""; }; D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzler.swift; sourceTree = ""; }; D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzlerTests.swift; sourceTree = ""; }; D2BEEDB12B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzler.swift; sourceTree = ""; }; @@ -3143,7 +3194,6 @@ 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */, D29732462A5C108700827599 /* DDScriptMessageHandler.swift */, D29732472A5C108700827599 /* MessageEmitter.swift */, - D29732482A5C108700827599 /* WebViewMessage.swift */, ); name = DatadogWebViewTracking; path = ../DatadogWebViewTracking/Sources; @@ -3153,7 +3203,6 @@ isa = PBXGroup; children = ( D297324F2A5C109A00827599 /* MessageEmitterTests.swift */, - 61AE740F2AD6EE4E008DB9BB /* WebViewMessageTests.swift */, D29732502A5C109A00827599 /* WebViewTrackingTests.swift */, ); name = DatadogWebViewTrackingTests; @@ -3172,12 +3221,12 @@ 61054E012A6EE0A400AAA894 /* DatadogSessionReplay */ = { isa = PBXGroup; children = ( + 61054E0C2A6EE10A00AAA894 /* SessionReplay.swift */, + 61054E0B2A6EE10A00AAA894 /* SessionReplayConfiguration.swift */, 61054E3B2A6EE10A00AAA894 /* Feature */, 61054E482A6EE10A00AAA894 /* Processor */, 61054E0D2A6EE10A00AAA894 /* Recorder */, - 61054E0C2A6EE10A00AAA894 /* SessionReplay.swift */, - 61054E0B2A6EE10A00AAA894 /* SessionReplayConfiguration.swift */, - 61054E542A6EE10A00AAA894 /* Utilities */, + 61054E132A6EE10A00AAA894 /* Utilities */, A7B932F62B1F6A0A00AE6477 /* Models */, 61054E032A6EE10A00AAA894 /* Writers */, ); @@ -3188,13 +3237,13 @@ 61054E022A6EE0DB00AAA894 /* DatadogSessionReplayTests */ = { isa = PBXGroup; children = ( + 61054F482A6EE1B900AAA894 /* SessionReplayTests.swift */, + 61054F3D2A6EE1B900AAA894 /* SessionReplayConfigurationTests.swift */, 61054F882A6EE1BA00AAA894 /* Feature */, 61054F922A6EE1BA00AAA894 /* Helpers */, 61054F7D2A6EE1BA00AAA894 /* Mocks */, 61054F4E2A6EE1BA00AAA894 /* Processor */, 61054F592A6EE1BA00AAA894 /* Recorder */, - 61054F3D2A6EE1B900AAA894 /* SessionReplayConfigurationTests.swift */, - 61054F482A6EE1B900AAA894 /* SessionReplayTests.swift */, 61054F3E2A6EE1B900AAA894 /* Utilities */, 61054F492A6EE1BA00AAA894 /* Writer */, ); @@ -3215,13 +3264,13 @@ 61054E0D2A6EE10A00AAA894 /* Recorder */ = { isa = PBXGroup; children = ( - 61054E0E2A6EE10A00AAA894 /* WindowObserver */, + 61054E192A6EE10A00AAA894 /* RecordingCoordinator.swift */, 61054E112A6EE10A00AAA894 /* Recorder.swift */, 61054E122A6EE10A00AAA894 /* PrivacyLevel.swift */, - 61054E132A6EE10A00AAA894 /* Utilities */, - 61054E192A6EE10A00AAA894 /* RecordingCoordinator.swift */, + 61054E0E2A6EE10A00AAA894 /* WindowObserver */, 61054E1A2A6EE10A00AAA894 /* TouchSnapshotProducer */, 61054E212A6EE10A00AAA894 /* ViewTreeSnapshotProducer */, + 61054E542A6EE10A00AAA894 /* Utilities */, ); path = Recorder; sourceTree = ""; @@ -3238,13 +3287,14 @@ 61054E132A6EE10A00AAA894 /* Utilities */ = { isa = PBXGroup; children = ( - 61054E142A6EE10A00AAA894 /* ImageDataProvider.swift */, + 61054E142A6EE10A00AAA894 /* UIImage+SessionReplay.swift */, 61054E152A6EE10A00AAA894 /* UIKitExtensions.swift */, 61054E162A6EE10A00AAA894 /* CFType+Safety.swift */, 61054E172A6EE10A00AAA894 /* SystemColors.swift */, 61054E182A6EE10A00AAA894 /* CGRect+ContentFrame.swift */, ); - path = Utilities; + name = Utilities; + path = Recorder/Utilities; sourceTree = ""; }; 61054E1A2A6EE10A00AAA894 /* TouchSnapshotProducer */ = { @@ -3297,6 +3347,7 @@ 61054E282A6EE10A00AAA894 /* UIDatePickerRecorder.swift */, 61054E292A6EE10A00AAA894 /* UITextViewRecorder.swift */, 61054E2A2A6EE10A00AAA894 /* UIImageViewRecorder.swift */, + A70ADCD12B583B1300321BC9 /* UIImageResource.swift */, 61054E2B2A6EE10A00AAA894 /* UIViewRecorder.swift */, 61054E2C2A6EE10A00AAA894 /* UINavigationBarRecorder.swift */, 61054E2D2A6EE10A00AAA894 /* UITextFieldRecorder.swift */, @@ -3309,6 +3360,7 @@ 61054E342A6EE10A00AAA894 /* UITabBarRecorder.swift */, 61054E352A6EE10A00AAA894 /* UISegmentRecorder.swift */, 61054E362A6EE10A00AAA894 /* UnsupportedViewRecorder.swift */, + D2BCB2A02B7B8107005C2AAB /* WKWebViewRecorder.swift */, ); path = NodeRecorders; sourceTree = ""; @@ -3341,8 +3393,6 @@ isa = PBXGroup; children = ( 61054E432A6EE10A00AAA894 /* SegmentJSON.swift */, - 61054E442A6EE10A00AAA894 /* EnrichedRecordJSON.swift */, - 61054E452A6EE10A00AAA894 /* SegmentJSONBuilder.swift */, ); path = JSON; sourceTree = ""; @@ -3407,13 +3457,13 @@ children = ( 61054E552A6EE10A00AAA894 /* CGRectExtensions.swift */, 61054E562A6EE10A00AAA894 /* UIImage+Scaling.swift */, - 61054E572A6EE10A00AAA894 /* Cache.swift */, 61054E582A6EE10A00AAA894 /* Queue.swift */, 61054E592A6EE10A00AAA894 /* Errors.swift */, 61054E5A2A6EE10A00AAA894 /* Colors.swift */, 61054E5B2A6EE10A00AAA894 /* Schedulers */, ); - path = Utilities; + name = Utilities; + path = ../Utilities; sourceTree = ""; }; 61054E5B2A6EE10A00AAA894 /* Schedulers */ = { @@ -3428,7 +3478,6 @@ 61054F3E2A6EE1B900AAA894 /* Utilities */ = { isa = PBXGroup; children = ( - 61054F3F2A6EE1B900AAA894 /* CacheTests.swift */, 61054F402A6EE1B900AAA894 /* UIImage+ScalingTests.swift */, 61054F412A6EE1B900AAA894 /* CGRectExtensionsTests.swift */, 61054F422A6EE1B900AAA894 /* ColorsTests.swift */, @@ -3454,19 +3503,10 @@ A71013D52B178FAD00101E60 /* ResourcesWriterTests.swift */, 61054F4A2A6EE1BA00AAA894 /* RecordsWriterTests.swift */, 61054F4B2A6EE1BA00AAA894 /* SRCompressionTests.swift */, - 61054F4C2A6EE1BA00AAA894 /* Models */, ); path = Writer; sourceTree = ""; }; - 61054F4C2A6EE1BA00AAA894 /* Models */ = { - isa = PBXGroup; - children = ( - 61054F4D2A6EE1BA00AAA894 /* EnrichedRecordTests.swift */, - ); - path = Models; - sourceTree = ""; - }; 61054F4E2A6EE1BA00AAA894 /* Processor */ = { isa = PBXGroup; children = ( @@ -3530,7 +3570,6 @@ isa = PBXGroup; children = ( 61054F5D2A6EE1BA00AAA894 /* UIKitExtensionsTests.swift */, - 61054F5E2A6EE1BA00AAA894 /* ImageDataProviderTests.swift */, 61054F5F2A6EE1BA00AAA894 /* CGRect+ContentFrameTests.swift */, ); path = Utilties; @@ -3592,6 +3631,8 @@ 61054F762A6EE1BA00AAA894 /* UIImageViewWireframesBuilderTests.swift */, 61054F772A6EE1BA00AAA894 /* UIPickerViewRecorderTests.swift */, 61054F782A6EE1BA00AAA894 /* UITextViewRecorderTests.swift */, + D2BCB2A22B7B9683005C2AAB /* WKWebViewRecorderTests.swift */, + A7F6512F2B7655DE004B0EDB /* UIImageResourceTests.swift */, ); path = NodeRecorders; sourceTree = ""; @@ -3606,7 +3647,6 @@ 61054F822A6EE1BA00AAA894 /* RecorderMocks.swift */, 61054F832A6EE1BA00AAA894 /* TestScheduler.swift */, 61054F842A6EE1BA00AAA894 /* QueueMocks.swift */, - 61054F852A6EE1BA00AAA894 /* MockImageDataProvider.swift */, 61054F862A6EE1BA00AAA894 /* SnapshotProducerMocks.swift */, 61054F872A6EE1BA00AAA894 /* RUMContextObserverMock.swift */, A74A72862B10CE4100771FEB /* ResourceMocks.swift */, @@ -3641,8 +3681,7 @@ 61054F8C2A6EE1BA00AAA894 /* JSON */ = { isa = PBXGroup; children = ( - 61054F8D2A6EE1BA00AAA894 /* SegmentJSONBuilderTests.swift */, - 61054F8E2A6EE1BA00AAA894 /* EnrichedRecordJSONTests.swift */, + D25C834B2B8657CF008E73B1 /* SegmentJSONTests.swift */, ); path = JSON; sourceTree = ""; @@ -4385,6 +4424,70 @@ path = CrashContext; sourceTree = ""; }; + 6167E6D12B7F8B1300C3CA2D /* AppHangs */ = { + isa = PBXGroup; + children = ( + 6167E6D22B7F8B3300C3CA2D /* AppHangsObserver.swift */, + 6167E6D52B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift */, + ); + path = AppHangs; + sourceTree = ""; + }; + 6167E6D82B80047900C3CA2D /* AppHangs */ = { + isa = PBXGroup; + children = ( + 6167E6D92B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift */, + ); + path = AppHangs; + sourceTree = ""; + }; + 6167E6DF2B81203A00C3CA2D /* Models */ = { + isa = PBXGroup; + children = ( + 6167E6E02B81204B00C3CA2D /* CrashReporting */, + ); + path = Models; + sourceTree = ""; + }; + 6167E6E02B81204B00C3CA2D /* CrashReporting */ = { + isa = PBXGroup; + children = ( + 6167E6E12B81207200C3CA2D /* DDCrashReport.swift */, + 6167E6E72B8122E900C3CA2D /* BacktraceReport.swift */, + 6167E6F52B81E94C00C3CA2D /* DDThread.swift */, + 6167E6F82B81E95900C3CA2D /* BinaryImage.swift */, + ); + path = CrashReporting; + sourceTree = ""; + }; + 6167E6FB2B81EBD100C3CA2D /* BacktraceReporting */ = { + isa = PBXGroup; + children = ( + 6167E6FC2B81EC0400C3CA2D /* BacktraceReporter.swift */, + 6167E6FF2B81EF7500C3CA2D /* BacktraceReportingFeature.swift */, + ); + path = BacktraceReporting; + sourceTree = ""; + }; + 6167E7162B837F4200C3CA2D /* FeatureModels */ = { + isa = PBXGroup; + children = ( + 6167E7172B837F6300C3CA2D /* CrashReporting */, + ); + path = FeatureModels; + sourceTree = ""; + }; + 6167E7172B837F6300C3CA2D /* CrashReporting */ = { + isa = PBXGroup; + children = ( + 6167E7282B84C11900C3CA2D /* DDCrashReportMocks.swift */, + 6167E7182B837F7A00C3CA2D /* BacktraceReportMocks.swift */, + 6167E7222B837FF100C3CA2D /* BinaryImageMocks.swift */, + 6167E71D2B837FB200C3CA2D /* DDThreadMocks.swift */, + ); + path = CrashReporting; + sourceTree = ""; + }; 616CCE11250A181C009FED46 /* Instrumentation */ = { isa = PBXGroup; children = ( @@ -4394,6 +4497,7 @@ 6141014D251A578D00E3C2D9 /* Actions */, 6157FA5C252767B3009A8A3B /* Resources */, 9E06058F26EF904200F5F935 /* LongTasks */, + 6167E6D12B7F8B1300C3CA2D /* AppHangs */, ); path = Instrumentation; sourceTree = ""; @@ -4474,6 +4578,7 @@ isa = PBXGroup; children = ( 6179DB552B6022EA00E9E04E /* SendingCrashReportTests.swift */, + 6167E7052B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift */, ); path = CrashReporting; sourceTree = ""; @@ -4897,6 +5002,7 @@ isa = PBXGroup; children = ( 61E8C5072B28898800E709B4 /* StartingRUMSessionTests.swift */, + 6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */, ); path = RUM; sourceTree = ""; @@ -4979,6 +5085,7 @@ 61F3CDA925121FA100C816E5 /* Views */, 6141014C251A577D00E3C2D9 /* Actions */, 613F23EF252B1287006CD2D7 /* Resources */, + 6167E6D82B80047900C3CA2D /* AppHangs */, 61C713BB2A3C95AD00FA735A /* RUMInstrumentationTests.swift */, ); path = Instrumentation; @@ -5215,6 +5322,14 @@ path = Swizzling; sourceTree = ""; }; + D21A94F02B838CCD00AC4256 /* Models */ = { + isa = PBXGroup; + children = ( + D25C834D2B88A261008E73B1 /* WebViewTracking */, + ); + path = Models; + sourceTree = ""; + }; D21AE6BA29E5ED7D0064BF29 /* Telemetry */ = { isa = PBXGroup; children = ( @@ -5244,6 +5359,7 @@ D23039A6298D513D001A1FA3 /* DatadogInternal */ = { isa = PBXGroup; children = ( + 6167E6DF2B81203A00C3CA2D /* Models */, D23039CA298D5235001A1FA3 /* Attributes */, D23039C3298D5235001A1FA3 /* Codable */, D23039DA298D5235001A1FA3 /* Concurrency */, @@ -5256,9 +5372,11 @@ D23039BF298D5235001A1FA3 /* MessageBus */, D23039AE298D5235001A1FA3 /* Storage */, D23039CD298D5235001A1FA3 /* Telemetry */, + 6167E6FB2B81EBD100C3CA2D /* BacktraceReporting */, D23039D1298D5235001A1FA3 /* Upload */, D2A783D329A53049003B03BB /* Utils */, D2160CE229C0DFED00FAA9A5 /* Swizzling */, + D21A94F02B838CCD00AC4256 /* Models */, D2EBEE1D29BA15BC00B15732 /* NetworkInstrumentation */, ); name = DatadogInternal; @@ -5424,6 +5542,7 @@ isa = PBXGroup; children = ( 6112B11325C84E7900B37771 /* CrashReportSender.swift */, + 6167E7022B81F2EB00C3CA2D /* BacktraceReporter.swift */, ); path = Integrations; sourceTree = ""; @@ -5453,6 +5572,7 @@ D2579546298ABB04008A1BE5 /* Mocks */ = { isa = PBXGroup; children = ( + 6167E7162B837F4200C3CA2D /* FeatureModels */, 6188900D2AC58B5D00D0B966 /* MessageReceiverMocks */, 61C713C52A3CA08B00FA735A /* CoreMocks */, 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */, @@ -5463,6 +5583,7 @@ D257954B298ABB04008A1BE5 /* FoundationMocks.swift */, D257954C298ABB04008A1BE5 /* AttributesMocks.swift */, D2DA23C6298D5AC000C6C7E6 /* TelemetryMocks.swift */, + 6167E7112B837F0B00C3CA2D /* BacktraceReportingMocks.swift */, D2DA23C9298D5C1300C6C7E6 /* UIKitMocks.swift */, D2A7840229A536AD003B03BB /* PrintFunctionMock.swift */, D24C9C5129A7BD12002057CF /* SamplerMock.swift */, @@ -5486,10 +5607,19 @@ D2F44FBB299AA36D0074B0D9 /* Decompression.swift */, 61133C462423990D00786299 /* TestsDirectory.swift */, 61C713D22A3DFB4900FA735A /* FuzzyHelpers.swift */, + 6167E72B2B84C72B00C3CA2D /* UIKitHelpers.swift */, ); path = Helpers; sourceTree = ""; }; + D25C834D2B88A261008E73B1 /* WebViewTracking */ = { + isa = PBXGroup; + children = ( + D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */, + ); + path = WebViewTracking; + sourceTree = ""; + }; D25EE93529C4C3C300CE3839 /* DatadogTrace */ = { isa = PBXGroup; children = ( @@ -5746,6 +5876,14 @@ path = Files; sourceTree = ""; }; + D2C5D5272B83FD3700B63F36 /* Models */ = { + isa = PBXGroup; + children = ( + 61AE740F2AD6EE4E008DB9BB /* WebViewMessageTests.swift */, + ); + path = Models; + sourceTree = ""; + }; D2DA238B298D588A00C6C7E6 /* DatadogInternalTests */ = { isa = PBXGroup; children = ( @@ -5761,6 +5899,7 @@ D2160CE729C0E00200FAA9A5 /* Swizzling */, D2EBEE3A29BA162900B15732 /* NetworkInstrumentation */, D263BCB129DB014900FA0E21 /* Extensions */, + D2C5D5272B83FD3700B63F36 /* Models */, ); name = DatadogInternalTests; path = ../DatadogInternal/Tests; @@ -5852,6 +5991,7 @@ children = ( 617699162A8608C20030022B /* Context */, 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */, + 6167E70D2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift */, D21C26D028A64599005DD405 /* MessageBusTests.swift */, ); path = DatadogCore; @@ -6458,8 +6598,6 @@ D2C1A51129C4C4EF00946C31 /* PBXTargetDependency */, ); name = "DatadogTrace iOS"; - packageProductDependencies = ( - ); productName = DatadogTrace; productReference = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; productType = "com.apple.product-type.framework"; @@ -6552,8 +6690,6 @@ D2C1A57729C4F30000946C31 /* PBXTargetDependency */, ); name = "DatadogTrace tvOS"; - packageProductDependencies = ( - ); productName = DatadogTrace; productReference = D2C1A55A29C4F2DF00946C31 /* DatadogTrace.framework */; productType = "com.apple.product-type.framework"; @@ -6830,8 +6966,6 @@ Base, ); mainGroup = 61133B78242393DE00786299; - packageReferences = ( - ); productRefGroup = 61133B83242393DE00786299 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -7355,7 +7489,6 @@ buildActionMask = 2147483647; files = ( 3C85D42129F7C5C900AFF894 /* WebViewTracking.swift in Sources */, - D297324D2A5C108700827599 /* WebViewMessage.swift in Sources */, D297324B2A5C108700827599 /* MessageEmitter.swift in Sources */, D29732492A5C108700827599 /* DDScriptMessageHandler.swift in Sources */, ); @@ -7367,7 +7500,6 @@ files = ( D29732532A5C109A00827599 /* WebViewTrackingTests.swift in Sources */, D29732512A5C109A00827599 /* MessageEmitterTests.swift in Sources */, - 61AE74132AD6EF49008DB9BB /* WebViewMessageTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -7447,6 +7579,7 @@ 614798962A459AA80095CB02 /* DDTraceTests.swift in Sources */, D25085102976E30000E931C3 /* DatadogRemoteFeatureMock.swift in Sources */, A7C816AB2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift in Sources */, + 6167E6DD2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */, 612C13D02AA772FA0086B5D1 /* SRRequestMatcher.swift in Sources */, 61A1A44929643254007909E7 /* DatadogCoreProxy.swift in Sources */, D2A1EE3B287EECC000D28DFB /* CarrierInfoPublisherTests.swift in Sources */, @@ -7483,6 +7616,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 */, @@ -7546,6 +7680,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 */, @@ -7624,9 +7759,8 @@ A7B932FB2B1F6A0A00AE6477 /* EnrichedRecord.swift in Sources */, 61054E8D2A6EE10A00AAA894 /* RUMContextReceiver.swift in Sources */, A7B932FD2B1F6A0A00AE6477 /* EnrichedResource.swift in Sources */, - 61054E922A6EE10A00AAA894 /* SegmentJSONBuilder.swift in Sources */, 61054E622A6EE10A00AAA894 /* RecordWriter.swift in Sources */, - 61054E692A6EE10A00AAA894 /* ImageDataProvider.swift in Sources */, + 61054E692A6EE10A00AAA894 /* UIImage+SessionReplay.swift in Sources */, 61054E782A6EE10A00AAA894 /* UIDatePickerRecorder.swift in Sources */, 61054E822A6EE10A00AAA894 /* UILabelRecorder.swift in Sources */, A73A54982B16406900E1F7E3 /* ResourcesFeature.swift in Sources */, @@ -7669,7 +7803,6 @@ 61054E952A6EE10A00AAA894 /* SnapshotProcessor.swift in Sources */, 61054E722A6EE10A00AAA894 /* TouchIdentifierGenerator.swift in Sources */, A7B932F52B1F694000AE6477 /* ResourcesProcessor.swift in Sources */, - 61054E912A6EE10A00AAA894 /* EnrichedRecordJSON.swift in Sources */, 61054E742A6EE10A00AAA894 /* ViewTreeSnapshotProducer.swift in Sources */, 61054E7E2A6EE10A00AAA894 /* NodeRecorder.swift in Sources */, 61054E6F2A6EE10A00AAA894 /* UIApplicationSwizzler.swift in Sources */, @@ -7679,6 +7812,7 @@ 61054E862A6EE10A00AAA894 /* UnsupportedViewRecorder.swift in Sources */, 61054E882A6EE10A00AAA894 /* ViewTreeRecordingContext.swift in Sources */, 61054E932A6EE10A00AAA894 /* MultipartFormData.swift in Sources */, + D2BCB2A12B7B8107005C2AAB /* WKWebViewRecorder.swift in Sources */, 61054E712A6EE10A00AAA894 /* TouchSnapshot.swift in Sources */, 61054E8A2A6EE10A00AAA894 /* WindowViewTreeSnapshotProducer.swift in Sources */, 61054E7A2A6EE10A00AAA894 /* UIImageViewRecorder.swift in Sources */, @@ -7687,10 +7821,10 @@ 61054EA02A6EE10B00AAA894 /* Colors.swift in Sources */, 61054E7F2A6EE10A00AAA894 /* UISliderRecorder.swift in Sources */, 61054E842A6EE10A00AAA894 /* UITabBarRecorder.swift in Sources */, - 61054E9D2A6EE10B00AAA894 /* Cache.swift in Sources */, 61054E682A6EE10A00AAA894 /* PrivacyLevel.swift in Sources */, 61054E8E2A6EE10A00AAA894 /* SRContextPublisher.swift in Sources */, 61054E732A6EE10A00AAA894 /* WindowTouchSnapshotProducer.swift in Sources */, + A70ADCD22B583B1300321BC9 /* UIImageResource.swift in Sources */, 61054E792A6EE10A00AAA894 /* UITextViewRecorder.swift in Sources */, 61054E9B2A6EE10B00AAA894 /* CGRectExtensions.swift in Sources */, ); @@ -7705,7 +7839,6 @@ 61054FB22A6EE1BA00AAA894 /* UILabelRecorderTests.swift in Sources */, 61054FCE2A6EE1BA00AAA894 /* RUMContextObserverMock.swift in Sources */, 61054FBA2A6EE1BA00AAA894 /* UIImageViewRecorderTests.swift in Sources */, - 61054FD22A6EE1BA00AAA894 /* EnrichedRecordJSONTests.swift in Sources */, 61054FC02A6EE1BA00AAA894 /* UITextViewRecorderTests.swift in Sources */, 61054F9A2A6EE1BA00AAA894 /* CFType+SafetyTests.swift in Sources */, 61054F9E2A6EE1BA00AAA894 /* SessionReplayTests.swift in Sources */, @@ -7715,9 +7848,9 @@ 61054FA82A6EE1BA00AAA894 /* RecordingCoordinatorTests.swift in Sources */, 61054FAD2A6EE1BA00AAA894 /* WindowTouchSnapshotProducerTests.swift in Sources */, 61054FD52A6EE1BA00AAA894 /* XCTAssertRectsEqual.swift in Sources */, - 61054F962A6EE1BA00AAA894 /* CacheTests.swift in Sources */, 61054FC12A6EE1BA00AAA894 /* ViewTreeRecorderTests.swift in Sources */, 61054F982A6EE1BA00AAA894 /* CGRectExtensionsTests.swift in Sources */, + D25C834C2B8657CF008E73B1 /* SegmentJSONTests.swift in Sources */, 61054FB42A6EE1BA00AAA894 /* UITabBarRecorderTests.swift in Sources */, 61054FA22A6EE1BA00AAA894 /* TextObfuscatorTests.swift in Sources */, A71013D62B178FAD00101E60 /* ResourcesWriterTests.swift in Sources */, @@ -7726,7 +7859,7 @@ 61054FCB2A6EE1BA00AAA894 /* QueueMocks.swift in Sources */, 61054FC42A6EE1BA00AAA894 /* RecorderTests.swift in Sources */, 61054FC22A6EE1BA00AAA894 /* ViewTreeSnapshotTests.swift in Sources */, - 61054FAB2A6EE1BA00AAA894 /* ImageDataProviderTests.swift in Sources */, + D2BCB2A32B7B9683005C2AAB /* WKWebViewRecorderTests.swift in Sources */, 61054FC62A6EE1BA00AAA894 /* CoreGraphicsMocks.swift in Sources */, 61054FCA2A6EE1BA00AAA894 /* TestScheduler.swift in Sources */, 61054FBD2A6EE1BA00AAA894 /* UIViewRecorderTests.swift in Sources */, @@ -7742,7 +7875,6 @@ 61054FB62A6EE1BA00AAA894 /* UnsupportedViewRecorderTests.swift in Sources */, 61054F9F2A6EE1BA00AAA894 /* RecordsWriterTests.swift in Sources */, 61054FB82A6EE1BA00AAA894 /* UIDatePickerRecorderTests.swift in Sources */, - 61054FD12A6EE1BA00AAA894 /* SegmentJSONBuilderTests.swift in Sources */, 61054FA32A6EE1BA00AAA894 /* Diff+SRWireframesTests.swift in Sources */, 61054FAF2A6EE1BA00AAA894 /* ViewTreeRecordingContextTests.swift in Sources */, 61054FC52A6EE1BA00AAA894 /* UIKitMocks.swift in Sources */, @@ -7755,13 +7887,12 @@ 61054FCF2A6EE1BA00AAA894 /* RUMContextReceiverTests.swift in Sources */, 61054FC92A6EE1BA00AAA894 /* RecorderMocks.swift in Sources */, 61054FBB2A6EE1BA00AAA894 /* UISwitchRecorderTests.swift in Sources */, + A7F651302B7655DE004B0EDB /* UIImageResourceTests.swift in Sources */, 61054F972A6EE1BA00AAA894 /* UIImage+ScalingTests.swift in Sources */, 61054FB02A6EE1BA00AAA894 /* ViewTreeSnapshotBuilderTests.swift in Sources */, 61054FD32A6EE1BA00AAA894 /* MultipartFormDataTests.swift in Sources */, - 61054FCC2A6EE1BA00AAA894 /* MockImageDataProvider.swift in Sources */, 61054FB12A6EE1BA00AAA894 /* NodeIDGeneratorTests.swift in Sources */, 61054F9C2A6EE1BA00AAA894 /* SwiftExtensionsTests.swift in Sources */, - 61054FA12A6EE1BA00AAA894 /* EnrichedRecordTests.swift in Sources */, 61054FA72A6EE1BA00AAA894 /* NodesFlattenerTests.swift in Sources */, 61054F9D2A6EE1BA00AAA894 /* MainThreadSchedulerTests.swift in Sources */, 61054FAA2A6EE1BA00AAA894 /* UIKitExtensionsTests.swift in Sources */, @@ -7852,6 +7983,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 */, @@ -7963,12 +8095,15 @@ 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 */, + D21A94F22B8397CA00AC4256 /* WebViewMessage.swift in Sources */, D23039EC298D5236001A1FA3 /* LaunchTime.swift in Sources */, D23039EE298D5236001A1FA3 /* FeatureMessageReceiver.swift in Sources */, D23039DE298D5235001A1FA3 /* Writer.swift in Sources */, @@ -7989,7 +8124,9 @@ 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 */, + 6167E6FD2B81EC0400C3CA2D /* BacktraceReporter.swift in Sources */, D23039DD298D5235001A1FA3 /* DD.swift in Sources */, D2160C9A29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, D2EBEE2229BA160F00B15732 /* TracePropagationHeadersReader.swift in Sources */, @@ -7999,6 +8136,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 */, @@ -8013,6 +8151,7 @@ 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 */, D2A783D429A5309F003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD72A543B3B00446CF9 /* Event.swift in Sources */, @@ -8026,6 +8165,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6167E6D42B7F8B3300C3CA2D /* AppHangsObserver.swift in Sources */, D23F8E5229DDCD28001CFAE8 /* UIViewControllerHandler.swift in Sources */, D23F8E5329DDCD28001CFAE8 /* RUMCommand.swift in Sources */, D23F8E5429DDCD28001CFAE8 /* ValuePublisher.swift in Sources */, @@ -8063,6 +8203,7 @@ D23F8E7329DDCD28001CFAE8 /* SwiftUIActionModifier.swift in Sources */, D23F8E7429DDCD28001CFAE8 /* RUMCommandSubscriber.swift in Sources */, D23F8E7529DDCD28001CFAE8 /* RUMUserActionScope.swift in Sources */, + 6167E6D72B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */, 61C713A42A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */, 3C0D5DED2A54405A00446CF9 /* RUMViewEventsFilter.swift in Sources */, D23F8E7629DDCD28001CFAE8 /* RUMConnectivityInfoProvider.swift in Sources */, @@ -8132,6 +8273,7 @@ D23F8EC029DDCD38001CFAE8 /* RUMEventSanitizerTests.swift in Sources */, 6176C1732ABDBA2E00131A70 /* MonitorTests.swift in Sources */, D23F8EC129DDCD38001CFAE8 /* RUMEventsMapperTests.swift in Sources */, + 6167E6DB2B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift in Sources */, 3C0D5DEA2A543EA300446CF9 /* RUMViewEventsFilterTests.swift in Sources */, D23F8EC429DDCD38001CFAE8 /* RUMCommandTests.swift in Sources */, ); @@ -8161,13 +8303,18 @@ D2579556298ABB04008A1BE5 /* FoundationMocks.swift in Sources */, D2579553298ABB04008A1BE5 /* DatadogContextMock.swift in Sources */, D24C9C6929A7CE06002057CF /* DDErrorMocks.swift in Sources */, + 6167E7142B837F0B00C3CA2D /* BacktraceReportingMocks.swift in Sources */, D2579558298ABB04008A1BE5 /* Encoding.swift in Sources */, + 6167E7202B837FB200C3CA2D /* DDThreadMocks.swift in Sources */, D2EBEE4829BA17C400B15732 /* NetworkInstrumentationMocks.swift in Sources */, 3C0D5DEF2A5442A900446CF9 /* EventMocks.swift in Sources */, D24C9C5529A7C5F3002057CF /* DateProvider.swift in Sources */, D2579559298ABB04008A1BE5 /* DDAssert.swift in Sources */, D2579552298ABB04008A1BE5 /* FileWriterMock.swift in Sources */, + 6167E72C2B84C72B00C3CA2D /* UIKitHelpers.swift in Sources */, + 6167E7252B837FF100C3CA2D /* BinaryImageMocks.swift in Sources */, 61AE74172AD7DA9B008DB9BB /* FeatureMessageMocks.swift in Sources */, + 6167E71B2B837F7A00C3CA2D /* BacktraceReportMocks.swift in Sources */, D2A7840329A536AD003B03BB /* PrintFunctionMock.swift in Sources */, D2A7840D29A53A4B003B03BB /* TestsDirectory.swift in Sources */, D2DA23CF298D5F2300C6C7E6 /* FeatureMessageReceiverMock.swift in Sources */, @@ -8178,6 +8325,7 @@ D2F44FBC299AA36D0074B0D9 /* Decompression.swift in Sources */, D24C9C5229A7BD12002057CF /* SamplerMock.swift in Sources */, D2579557298ABB04008A1BE5 /* AttributesMocks.swift in Sources */, + 6167E7292B84C11900C3CA2D /* DDCrashReportMocks.swift in Sources */, D2DA23C7298D5AC000C6C7E6 /* TelemetryMocks.swift in Sources */, D2DA23CA298D5C1300C6C7E6 /* UIKitMocks.swift in Sources */, 6188900F2AC58B8C00D0B966 /* TelemetryReceiverMock.swift in Sources */, @@ -8198,13 +8346,18 @@ D2579579298ABB83008A1BE5 /* FoundationMocks.swift in Sources */, D257957A298ABB83008A1BE5 /* DatadogContextMock.swift in Sources */, D24C9C6A29A7CE06002057CF /* DDErrorMocks.swift in Sources */, + 6167E7152B837F0B00C3CA2D /* BacktraceReportingMocks.swift in Sources */, D257957B298ABB83008A1BE5 /* Encoding.swift in Sources */, + 6167E7212B837FB200C3CA2D /* DDThreadMocks.swift in Sources */, D2EBEE4929BA17C400B15732 /* NetworkInstrumentationMocks.swift in Sources */, 3C0D5DF02A5442A900446CF9 /* EventMocks.swift in Sources */, D24C9C5629A7C5F3002057CF /* DateProvider.swift in Sources */, D257957C298ABB83008A1BE5 /* DDAssert.swift in Sources */, D257957D298ABB83008A1BE5 /* FileWriterMock.swift in Sources */, + 6167E72D2B84C72B00C3CA2D /* UIKitHelpers.swift in Sources */, + 6167E7262B837FF100C3CA2D /* BinaryImageMocks.swift in Sources */, 61AE74182AD7DA9B008DB9BB /* FeatureMessageMocks.swift in Sources */, + 6167E71C2B837F7A00C3CA2D /* BacktraceReportMocks.swift in Sources */, D2A7840429A536AD003B03BB /* PrintFunctionMock.swift in Sources */, D2A7840E29A53A4B003B03BB /* TestsDirectory.swift in Sources */, D2DA23D0298D5F2300C6C7E6 /* FeatureMessageReceiverMock.swift in Sources */, @@ -8215,6 +8368,7 @@ D2F44FBD299AA36D0074B0D9 /* Decompression.swift in Sources */, D24C9C5329A7BD12002057CF /* SamplerMock.swift in Sources */, D257957F298ABB83008A1BE5 /* AttributesMocks.swift in Sources */, + 6167E72A2B84C11900C3CA2D /* DDCrashReportMocks.swift in Sources */, D2DA23C8298D5AC000C6C7E6 /* TelemetryMocks.swift in Sources */, D2DA23CB298D5C1300C6C7E6 /* UIKitMocks.swift in Sources */, 618890102AC58B8C00D0B966 /* TelemetryReceiverMock.swift in Sources */, @@ -8303,6 +8457,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6167E6D32B7F8B3300C3CA2D /* AppHangsObserver.swift in Sources */, D29A9F8029DD85BB005C54A4 /* UIViewControllerHandler.swift in Sources */, D29A9F5929DD85BB005C54A4 /* RUMCommand.swift in Sources */, D29A9F8C29DD861C005C54A4 /* ValuePublisher.swift in Sources */, @@ -8340,6 +8495,7 @@ D29A9F8729DD85BB005C54A4 /* SwiftUIActionModifier.swift in Sources */, D29A9F5D29DD85BB005C54A4 /* RUMCommandSubscriber.swift in Sources */, D29A9F6529DD85BB005C54A4 /* RUMUserActionScope.swift in Sources */, + 6167E6D62B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */, 61C713A32A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */, 3C0D5DEC2A54405A00446CF9 /* RUMViewEventsFilter.swift in Sources */, D29A9F5829DD85BB005C54A4 /* RUMConnectivityInfoProvider.swift in Sources */, @@ -8409,6 +8565,7 @@ D29A9FA229DDB483005C54A4 /* RUMEventSanitizerTests.swift in Sources */, 6176C1722ABDBA2E00131A70 /* MonitorTests.swift in Sources */, D29A9FB929DDB483005C54A4 /* RUMEventsMapperTests.swift in Sources */, + 6167E6DA2B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift in Sources */, 3C0D5DE92A543EA200446CF9 /* RUMViewEventsFilterTests.swift in Sources */, D29A9FA729DDB483005C54A4 /* RUMCommandTests.swift in Sources */, ); @@ -8605,6 +8762,7 @@ D2CB6F0027C520D400A62B57 /* RUMSessionMatcher.swift in Sources */, A728ADB12934EB0C00397996 /* DDW3CHTTPHeadersWriter+apiTests.m in Sources */, D2CB6F0127C520D400A62B57 /* DatadogPrivateMocks.swift in Sources */, + 6167E6DE2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */, D26C49B02886DC7B00802B2D /* ApplicationStatePublisherTests.swift in Sources */, D24C9C7229A7D57A002057CF /* DirectoriesMock.swift in Sources */, 61DA8CB3286215DE0074A606 /* CryptographyTests.swift in Sources */, @@ -8622,6 +8780,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 */, @@ -8697,6 +8856,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 */, @@ -8754,6 +8914,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 */, @@ -8805,12 +8966,15 @@ 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 */, + D21A94F32B8397CA00AC4256 /* WebViewMessage.swift in Sources */, D2DA2364298D57AA00C6C7E6 /* LaunchTime.swift in Sources */, D2DA2365298D57AA00C6C7E6 /* FeatureMessageReceiver.swift in Sources */, D2DA2366298D57AA00C6C7E6 /* Writer.swift in Sources */, @@ -8831,7 +8995,9 @@ 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 */, + 6167E6FE2B81EC0400C3CA2D /* BacktraceReporter.swift in Sources */, D2DA2372298D57AA00C6C7E6 /* DD.swift in Sources */, D2160C9B29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, D2EBEE3029BA161100B15732 /* TracePropagationHeadersReader.swift in Sources */, @@ -8841,6 +9007,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 */, @@ -8855,6 +9022,7 @@ 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 */, D2A783D529A530A0003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD82A543B3B00446CF9 /* Event.swift in Sources */, @@ -8885,6 +9053,7 @@ D270CDE02B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, D2DA23A1298D58F400C6C7E6 /* ReadWriteLockTests.swift in Sources */, D2160CD829C0DF6700FAA9A5 /* FirstPartyHostsTests.swift in Sources */, + D2C5D5282B83FD5300B63F36 /* WebViewMessageTests.swift in Sources */, D20731CD29A52E8700ECBF94 /* SamplerTests.swift in Sources */, D2DA23A6298D58F400C6C7E6 /* AnyCoderTests.swift in Sources */, D2EBEE3E29BA163E00B15732 /* W3CHTTPHeadersReaderTests.swift in Sources */, @@ -8927,6 +9096,7 @@ D270CDE12B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, D2DA23B5298D59DC00C6C7E6 /* ReadWriteLockTests.swift in Sources */, D2160CD929C0DF6700FAA9A5 /* FirstPartyHostsTests.swift in Sources */, + D2C5D5292B83FD5400B63F36 /* WebViewMessageTests.swift in Sources */, D20731CE29A52E8700ECBF94 /* SamplerTests.swift in Sources */, D2160CEA29C0E00200FAA9A5 /* MethodSwizzlerTests.swift in Sources */, D2DA23B6298D59DC00C6C7E6 /* AnyCoderTests.swift in Sources */, diff --git a/Datadog/Example/Base.lproj/Main iOS.storyboard b/Datadog/Example/Base.lproj/Main iOS.storyboard index 66d65e05c9..d4a0c551e4 100644 --- a/Datadog/Example/Base.lproj/Main iOS.storyboard +++ b/Datadog/Example/Base.lproj/Main iOS.storyboard @@ -18,7 +18,7 @@ - + @@ -65,28 +65,8 @@ - - - - - - - - - - - - - - - + @@ -106,7 +86,7 @@ - + @@ -126,7 +106,7 @@ - + @@ -146,7 +126,7 @@ - + @@ -166,7 +146,7 @@ - + @@ -186,7 +166,7 @@ - + @@ -804,319 +784,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Datadog/Example/ExampleAppDelegate.swift b/Datadog/Example/ExampleAppDelegate.swift index b73529bf32..6113abc415 100644 --- a/Datadog/Example/ExampleAppDelegate.swift +++ b/Datadog/Example/ExampleAppDelegate.swift @@ -73,7 +73,11 @@ class ExampleAppDelegate: UIResponder, UIApplicationDelegate { RUM.enable( with: RUM.Configuration( applicationID: Environment.readRUMApplicationID(), - urlSessionTracking: .init(firstPartyHostsTracing: .trace(hosts: [], sampleRate: 100)), + urlSessionTracking: .init( + resourceAttributesProvider: { req, resp, data, err in + print("⭐️ [Attributes Provider] data: \(String(describing: data))") + return [:] + }), trackBackgroundEvents: true, customEndpoint: Environment.readCustomRUMURL(), telemetrySampleRate: 100 @@ -85,6 +89,7 @@ class ExampleAppDelegate: UIResponder, UIApplicationDelegate { OpenTelemetry.registerTracerProvider( tracerProvider: OTelTracerProvider() ) + Logs.addAttribute(forKey: "testing-attribute", value: "my-value") // Create Logger logger = Logger.create( @@ -121,7 +126,9 @@ class ExampleAppDelegate: UIResponder, UIApplicationDelegate { } func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - installConsoleOutputInterceptor() + if Environment.isRunningInteractive() { + installConsoleOutputInterceptor() + } return true } diff --git a/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift b/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift new file mode 100644 index 0000000000..d3e7144025 --- /dev/null +++ b/Datadog/IntegrationUnitTests/CrashReporting/GeneratingBacktraceTests.swift @@ -0,0 +1,103 @@ +/* + * 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 TestUtilities +import DatadogCrashReporting +@testable import DatadogInternal + +/// 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 testGeneratingBacktraceOfTheCurrentThread() throws { + // Given + CrashReporting.enable(in: core) + XCTAssertNotNil(core.get(feature: BacktraceReportingFeature.self), "`BacktraceReportingFeature` must be registered") + + // 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`" + ) + } + + func testGeneratingBacktraceOfTheMainThread() throws { + // Given + CrashReporting.enable(in: core) + + // When + XCTAssertTrue(Thread.current.isMainThread) + let threadID = Thread.currentThreadID + let backtrace = try XCTUnwrap(core.backtraceReporter.generateBacktrace(threadID: threadID)) + + // Then + XCTAssertFalse(backtrace.stack.isEmpty) + XCTAssertTrue(backtrace.stack.contains(uiKitLibraryName), "Main thread stack should include UIKit symbols") + } + + func testGeneratingBacktraceOfSecondaryThread() throws { + // Given + CrashReporting.enable(in: core) + + // When + let semaphore = DispatchSemaphore(value: 0) + var threadID: ThreadID? + + let thread = Thread { + XCTAssertFalse(Thread.current.isMainThread) + threadID = Thread.currentThreadID + semaphore.signal() + } + + thread.start() + XCTAssertEqual(semaphore.wait(timeout: .now() + 5), .success) + thread.cancel() + + let backtrace = try XCTUnwrap(core.backtraceReporter.generateBacktrace(threadID: threadID!)) + + // Then + XCTAssertFalse(backtrace.stack.isEmpty) + XCTAssertFalse(backtrace.stack.contains(uiKitLibraryName), "Secondary thread stack should NOT include UIKit symbols") + } +} diff --git a/Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift b/Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift index 1ccdfd40cc..34d59bc27f 100644 --- a/Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift +++ b/Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift @@ -48,7 +48,8 @@ class SendingCrashReportTests: XCTestCase { let crashReport: DDCrashReport = .mockRandomWith( context: .mockWith( trackingConsent: .granted, // CR from the app session that has enabled data collection - lastIsAppInForeground: true // CR occurred while the app was in the foreground + lastIsAppInForeground: true, // CR occurred while the app was in the foreground + lastLogAttributes: .init(mockRandomAttributes()) ) ) @@ -64,6 +65,7 @@ class SendingCrashReportTests: XCTestCase { XCTAssertEqual(log.error?.message, crashReport.message) XCTAssertEqual(log.error?.kind, crashReport.type) XCTAssertEqual(log.error?.stack, crashReport.stack) + XCTAssertFalse(log.attributes.userAttributes.isEmpty) XCTAssertNotNil(log.attributes.internalAttributes?[DDError.threads]) XCTAssertNotNil(log.attributes.internalAttributes?[DDError.binaryImages]) XCTAssertNotNil(log.attributes.internalAttributes?[DDError.meta]) diff --git a/Datadog/IntegrationUnitTests/Public/NetworkInstrumentationIntegrationTests.swift b/Datadog/IntegrationUnitTests/Public/NetworkInstrumentationIntegrationTests.swift index 0aaf1051f5..209309cf9a 100644 --- a/Datadog/IntegrationUnitTests/Public/NetworkInstrumentationIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/Public/NetworkInstrumentationIntegrationTests.swift @@ -8,6 +8,7 @@ import XCTest import TestUtilities import DatadogInternal +@testable import DatadogRUM @testable import DatadogTrace @testable import DatadogCore @@ -50,8 +51,9 @@ class NetworkInstrumentationIntegrationTests: XCTestCase { core.flushAndTearDown() core = nil } - + func testParentSpanPropagation() throws { + let expectation = expectation(description: "request completes") // Given let request: URLRequest = .mockWith(url: "https://www.example.com") let span = Tracer.shared(in: core).startRootSpan(operationName: "root") @@ -60,15 +62,16 @@ class NetworkInstrumentationIntegrationTests: XCTestCase { // When span.setActive() // start root span - + session .dataTask(with: request) { _,_,_ in span.finish() // finish root span + expectation.fulfill() } .resume() // Then - server.waitFor(requestsCompletion: 1) + waitForExpectations(timeout: 1) let matchers = try core.waitAndReturnSpanMatchers() let matcher1 = try XCTUnwrap(matchers.first) @@ -86,4 +89,116 @@ class NetworkInstrumentationIntegrationTests: XCTestCase { class MockDelegate: NSObject, URLSessionDataDelegate { } + + func testResourceAttributesProvider_givenURLSessionDataTaskRequest() { + core = DatadogCoreProxy( + context: .mockWith( + env: "test", + version: "1.1.1", + serverTimeOffset: 123 + ) + ) + + let providerExpectation = expectation(description: "provider called") + var providerDataCount = 0 + RUM.enable( + with: .init( + applicationID: .mockAny(), + urlSessionTracking: .init( + resourceAttributesProvider: { req, resp, data, err in + XCTAssertNotNil(data) + XCTAssertTrue(data!.count > 0) + providerDataCount = data!.count + providerExpectation.fulfill() + return [:] + }) + ), + in: core + ) + + URLSessionInstrumentation.enable( + with: .init( + delegateClass: InstrumentedSessionDelegate.self + ), + in: core + ) + + let session = URLSession( + configuration: .ephemeral, + delegate: InstrumentedSessionDelegate(), + delegateQueue: nil + ) + var request = URLRequest(url: URL(string: "https://www.datadoghq.com/")!) + request.httpMethod = "GET" + + let task = session.dataTask(with: request) + task.resume() + + wait(for: [providerExpectation], timeout: 10) + XCTAssertTrue(providerDataCount > 0) + } + + func testResourceAttributesProvider_givenURLSessionDataTaskRequestWithCompletionHandler() { + core = DatadogCoreProxy( + context: .mockWith( + env: "test", + version: "1.1.1", + serverTimeOffset: 123 + ) + ) + + let providerExpectation = expectation(description: "provider called") + var providerDataCount = 0 + var providerData: Data? + RUM.enable( + with: .init( + applicationID: .mockAny(), + urlSessionTracking: .init( + resourceAttributesProvider: { req, resp, data, err in + XCTAssertNotNil(data) + XCTAssertTrue(data!.count > 0) + providerDataCount = data!.count + data.map { providerData = $0 } + providerExpectation.fulfill() + return [:] + }) + ), + in: core + ) + + URLSessionInstrumentation.enable( + with: .init( + delegateClass: InstrumentedSessionDelegate.self + ), + in: core + ) + + let session = URLSession( + configuration: .ephemeral, + delegate: InstrumentedSessionDelegate(), + delegateQueue: nil + ) + let request = URLRequest(url: URL(string: "https://www.datadoghq.com/")!) + + let taskExpectation = self.expectation(description: "task completed") + var taskDataCount = 0 + var taskData: Data? + let task = session.dataTask(with: request) { data, _, _ in + XCTAssertNotNil(data) + XCTAssertTrue(data!.count > 0) + taskDataCount = data!.count + data.map { taskData = $0 } + taskExpectation.fulfill() + } + task.resume() + + wait(for: [providerExpectation, taskExpectation], timeout: 10) + XCTAssertEqual(providerDataCount, taskDataCount) + XCTAssertEqual(providerData, taskData) + } + + class InstrumentedSessionDelegate: NSObject, URLSessionDataDelegate { + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + } + } } diff --git a/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift b/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift index 7207d63571..e5b1c559cd 100644 --- a/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift @@ -8,9 +8,10 @@ import XCTest #if !os(tvOS) import WebKit -import DatadogLogs + +@testable import DatadogLogs @testable import DatadogRUM -import DatadogWebViewTracking +@testable import DatadogWebViewTracking class WebLogIntegrationTests: XCTestCase { // swiftlint:disable implicitly_unwrapped_optional @@ -136,4 +137,3 @@ class WebLogIntegrationTests: XCTestCase { } #endif - diff --git a/Datadog/IntegrationUnitTests/RUM/AppHangsMonitoringTests.swift b/Datadog/IntegrationUnitTests/RUM/AppHangsMonitoringTests.swift new file mode 100644 index 0000000000..4bdddc62eb --- /dev/null +++ b/Datadog/IntegrationUnitTests/RUM/AppHangsMonitoringTests.swift @@ -0,0 +1,149 @@ +/* + * 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 TestUtilities +import DatadogInternal +import DatadogCrashReporting +@testable import DatadogRUM + +/// Test case covering scenarios of App Hangs monitoring in RUM. +class AppHangsMonitoringTests: XCTestCase { + private var core: DatadogCoreProxy! // swiftlint:disable:this implicitly_unwrapped_optional + private var rumConfig = RUM.Configuration(applicationID: .mockAny()) + private var hangDuration: TimeInterval! // swiftlint:disable:this implicitly_unwrapped_optional + /// Use main queue mock, otherwise any `waitForExpectations(timeout:)` would be considered an app hang and may cause dead locks. + private let mainQueue = DispatchQueue(label: "main-queue-mock", qos: .userInteractive) + + private var expectedHangDurationRangeNs: ClosedRange { + let min = hangDuration.toInt64Nanoseconds / 2 // -50% margin + let max = hangDuration.toInt64Nanoseconds * 5 // +500% margin to avoid flakiness + return (min...max) + } + + override func setUp() { + rumConfig.mainQueue = mainQueue + rumConfig.appHangThreshold = 0.4 + hangDuration = rumConfig.appHangThreshold! * 1.25 + core = DatadogCoreProxy() + } + + override func tearDown() { + core.flushAndTearDown() + core = nil + } + + func testWhenMainThreadIsHangedInitially_itTracksAppHangError() throws { + // Given + mainQueue.sync { + RUM.enable(with: rumConfig, in: core) + + // When + Thread.sleep(forTimeInterval: hangDuration) // hang right after SDK is initialized + } + + // Then + try flushHangsMonitoring() + let errors = core.waitAndReturnEvents(ofFeature: RUMFeature.name, ofType: RUMErrorEvent.self) + let appHangError = try XCTUnwrap(errors.first) + let actualHangDuration = try XCTUnwrap(appHangError.freeze?.duration) + + XCTAssertEqual(appHangError.error.message, AppHangsObserver.Constants.appHangErrorMessage) + XCTAssertEqual(appHangError.error.type, AppHangsObserver.Constants.appHangErrorType) + XCTAssertEqual(appHangError.error.category, .appHang) + XCTAssertTrue(expectedHangDurationRangeNs.contains(actualHangDuration)) + } + + func testWhenMainThreadIsHangedAfterInit_itTracksAppHangError() throws { + // Given + mainQueue.sync { + RUM.enable(with: rumConfig, in: core) + } + + // When + mainQueue.sync { // hang in the main thread task that follows SDK is initialization + Thread.sleep(forTimeInterval: hangDuration) + } + + // Then + try flushHangsMonitoring() + let errors = core.waitAndReturnEvents(ofFeature: RUMFeature.name, ofType: RUMErrorEvent.self) + let appHangError = try XCTUnwrap(errors.first) + let actualHangDuration = try XCTUnwrap(appHangError.freeze?.duration) + + XCTAssertEqual(appHangError.error.message, AppHangsObserver.Constants.appHangErrorMessage) + XCTAssertEqual(appHangError.error.type, AppHangsObserver.Constants.appHangErrorType) + XCTAssertEqual(appHangError.error.category, .appHang) + XCTAssertTrue(expectedHangDurationRangeNs.contains(actualHangDuration)) + } + + func testGivenRUMAndCrashReportingEnabled_whenMainThreadHangs_thenAppHangErrorIncludesStackTrace() throws { + // Given (initialize SDK on the main thread) + oneOf([ // no matter of RUM or CR initialization order + { + RUM.enable(with: self.rumConfig, in: self.core) + CrashReporting.enable(in: self.core) + }, + { + CrashReporting.enable(in: self.core) + RUM.enable(with: self.rumConfig, in: self.core) + }, + ]) + + // When + mainQueue.sync { + Thread.sleep(forTimeInterval: hangDuration) + } + + // Then + try flushHangsMonitoring() + let errors = core.waitAndReturnEvents(ofFeature: RUMFeature.name, ofType: RUMErrorEvent.self) + let appHangError = try XCTUnwrap(errors.first) + let mainThreadStack = try XCTUnwrap(appHangError.error.stack) + + XCTAssertEqual(appHangError.error.message, AppHangsObserver.Constants.appHangErrorMessage) + XCTAssertEqual(appHangError.error.type, AppHangsObserver.Constants.appHangErrorType) + XCTAssertTrue(mainThreadStack.contains(uiKitLibraryName), "Main thread stack should include UIKit symbols") + XCTAssertEqual(appHangError.error.source, .source) + XCTAssertNotNil(appHangError.error.threads, "Other threads should be available") + XCTAssertNotNil(appHangError.error.binaryImages, "Binary Images should be available for symbolication") + } + + func testGivenOnlyRUMEnabled_whenMainThreadHangs_itTracksAppHangWithNoStackTrace() throws { + // Given + mainQueue.sync { + RUM.enable(with: rumConfig, in: core) + } + + // When + mainQueue.sync { + Thread.sleep(forTimeInterval: hangDuration) + } + + // Then + try flushHangsMonitoring() + let errors = core.waitAndReturnEvents(ofFeature: RUMFeature.name, ofType: RUMErrorEvent.self) + let appHangError = try XCTUnwrap(errors.first) + + XCTAssertEqual(appHangError.error.message, AppHangsObserver.Constants.appHangErrorMessage) + XCTAssertEqual(appHangError.error.type, AppHangsObserver.Constants.appHangErrorType) + XCTAssertEqual(appHangError.error.stack, AppHangsObserver.Constants.appHangNoStackErrorMessage) + XCTAssertEqual(appHangError.error.source, .source) + XCTAssertNil(appHangError.error.threads, "Threads should be unavailable as CrashReporting was not enabled") + XCTAssertNil(appHangError.error.binaryImages, "Binary Images should be unavailable as CrashReporting was not enabled") + } + + private func flushHangsMonitoring() throws { + mainQueue.sync {} // flush the mock main queue (by awaiting the next task after the hang) + + // Flush the watchdog thread (by awaiting on the real main thread), to make sure the thread is done with any hang processing + // and it is idle: + let hangObserver = try XCTUnwrap(core.get(feature: RUMFeature.self)?.instrumentation.appHangs) + hangObserver.flush() + + RUMMonitor.shared(in: core).dd.flush() // flush also RUMMonitor so it ends processing hangs flushed from `hangObserver` + } +} diff --git a/Datadog/IntegrationUnitTests/RUM/StartingRUMSessionTests.swift b/Datadog/IntegrationUnitTests/RUM/StartingRUMSessionTests.swift index 8dc3a52fc8..9eeb75343d 100644 --- a/Datadog/IntegrationUnitTests/RUM/StartingRUMSessionTests.swift +++ b/Datadog/IntegrationUnitTests/RUM/StartingRUMSessionTests.swift @@ -143,14 +143,14 @@ class StartingRUMSessionTests: XCTestCase { let rumTime = DateProviderMock() rumConfig.dateProvider = rumTime rumConfig.trackBackgroundEvents = .mockRandom() // no matter BET state - + // When rumTime.now = sdkInitTime RUM.enable(with: rumConfig, in: core) - + rumTime.now = firstRUMTime RUMMonitor.shared(in: core).startView(key: "key", name: "FirstView") - + // Then let session = try RUMSessionMatcher .groupMatchersBySessions(try core.waitAndReturnRUMEventMatchers()) @@ -158,7 +158,7 @@ class StartingRUMSessionTests: XCTestCase { XCTAssertEqual(session.views.count, 1) XCTAssertTrue(try session.has(sessionPrecondition: .backgroundLaunch), "Session must be marked as 'background launch'") - + let firstView = try XCTUnwrap(session.views.first) XCTAssertFalse(firstView.isApplicationLaunchView(), "Session should not begin with 'app launch' view") XCTAssertEqual(firstView.name, "FirstView") diff --git a/DatadogAlamofireExtension.podspec b/DatadogAlamofireExtension.podspec index 4bf631647c..99cc7240bf 100644 --- a/DatadogAlamofireExtension.podspec +++ b/DatadogAlamofireExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogAlamofireExtension" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "An Official Extensions of Datadog Swift SDK for Alamofire." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogCore.podspec b/DatadogCore.podspec index 0cef383dae..d4951acd15 100644 --- a/DatadogCore.podspec +++ b/DatadogCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogCore" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Official Datadog Swift SDK for iOS." s.homepage = "https://www.datadoghq.com" @@ -22,7 +22,10 @@ Pod::Spec.new do |s| s.source_files = ["DatadogCore/Sources/**/*.swift", "DatadogCore/Private/**/*.{h,m}"] - s.resource = "DatadogCore/Resources/PrivacyInfo.xcprivacy" + + s.resource_bundle = { + "DatadogPrivacyInfo" => "DatadogCore/Resources/PrivacyInfo.xcprivacy" + } s.dependency 'DatadogInternal', s.version.to_s diff --git a/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift b/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift index 9290d5d26e..c0e18ba95b 100644 --- a/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift +++ b/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift @@ -7,7 +7,7 @@ import Foundation import DatadogInternal -#if os(iOS) && !targetEnvironment(macCatalyst) +#if os(iOS) && !targetEnvironment(macCatalyst) && !(swift(>=5.9) && os(visionOS)) import CoreTelephony diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index 1a0a7f3121..9baa90efa2 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -229,16 +229,16 @@ extension DatadogCore: DatadogCoreProtocol { /// /// - Parameter feature: The Feature instance. func register(feature: T) throws where T: DatadogFeature { - let featureDirectories = try directory.getFeatureDirectories(forFeatureNamed: T.name) + 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 performancePreset: PerformancePreset + if let override = feature.performanceOverride { + performancePreset = performance.updated(with: override) + } else { + performancePreset = performance + } - if let feature = feature as? DatadogRemoteFeature { let storage = FeatureStorage( featureName: T.name, queue: readWriteQueue, @@ -314,11 +314,11 @@ internal struct DatadogCoreFeatureScope: FeatureScope { let contextProvider: DatadogContextProvider let storage: FeatureStorage - func eventWriteContext(bypassConsent: Bool, forceNewBatch: Bool, _ block: @escaping (DatadogContext, Writer) -> Void) { + func eventWriteContext(bypassConsent: Bool, _ block: @escaping (DatadogContext, Writer) -> Void) { // (on user thread) request SDK context context { context in // (on context thread) call the block - let writer = storage.writer(for: bypassConsent ? .granted : context.trackingConsent, forceNewBatch: forceNewBatch) + let writer = storage.writer(for: bypassConsent ? .granted : context.trackingConsent) block(context, writer) } } @@ -344,6 +344,7 @@ extension DatadogContextProvider { buildId: String?, variant: String?, source: String, + nativeSourceOverride: String?, sdkVersion: String, ciAppOrigin: String?, applicationName: String, @@ -370,6 +371,7 @@ extension DatadogContextProvider { applicationBundleIdentifier: applicationBundleIdentifier, sdkInitDate: dateProvider.now, device: device, + nativeSourceOverride: nativeSourceOverride, // this is a placeholder waiting for the `ApplicationStatePublisher` // to be initialized on the main thread, this value will be overrided // as soon as the subscription is made. @@ -386,8 +388,7 @@ extension DatadogContextProvider { } else { assign(reader: SCNetworkReachabilityReader(), to: \.networkConnectionInfo) } - - #if os(iOS) && !targetEnvironment(macCatalyst) + #if os(iOS) && !targetEnvironment(macCatalyst) && !(swift(>=5.9) && os(visionOS)) if #available(iOS 12, *) { subscribe(\.carrierInfo, to: iOS12CarrierInfoPublisher()) } else { diff --git a/DatadogCore/Sources/Core/Storage/FeatureStorage.swift b/DatadogCore/Sources/Core/Storage/FeatureStorage.swift index 2771a22d19..ee30072024 100644 --- a/DatadogCore/Sources/Core/Storage/FeatureStorage.swift +++ b/DatadogCore/Sources/Core/Storage/FeatureStorage.swift @@ -23,13 +23,12 @@ internal struct FeatureStorage { /// Telemetry interface. let telemetry: Telemetry - func writer(for trackingConsent: TrackingConsent, forceNewBatch: Bool) -> Writer { + func writer(for trackingConsent: TrackingConsent) -> Writer { switch trackingConsent { case .granted: return AsyncWriter( execute: FileWriter( orchestrator: authorizedFilesOrchestrator, - forceNewFile: forceNewBatch, encryption: encryption, telemetry: telemetry ), @@ -41,7 +40,6 @@ internal struct FeatureStorage { return AsyncWriter( execute: FileWriter( orchestrator: unauthorizedFilesOrchestrator, - forceNewFile: forceNewBatch, encryption: encryption, telemetry: telemetry ), diff --git a/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift b/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift index f438a94539..9831681030 100644 --- a/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift +++ b/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift @@ -10,7 +10,6 @@ import DatadogInternal internal protocol FilesOrchestratorType: AnyObject { var performance: StoragePerformancePreset { get } - func getNewWritableFile(writeSize: UInt64) throws -> WritableFile func getWritableFile(writeSize: UInt64) throws -> WritableFile func getReadableFiles(excludingFilesNamed excludedFileNames: Set, limit: Int) -> [ReadableFile] func delete(readableFile: ReadableFile, deletionReason: BatchDeletedMetric.RemovalReason) @@ -66,21 +65,6 @@ internal class FilesOrchestrator: FilesOrchestratorType { // MARK: - `WritableFile` orchestration - /// Creates and returns new writable file by ignoring the heuristic of reusing files. - /// - /// Note: the `getWritableFile(writeSize:)` should be preferred, unless the caller has specific reason for - /// enforcing new files to be created (e.g. batching is managed in upstream state). - /// - /// - Parameter writeSize: the size of data to be written - /// - Returns: `WritableFile` capable of writing data of given size - func getNewWritableFile(writeSize: UInt64) throws -> WritableFile { - try validate(writeSize: writeSize) - if let closedBatchName = lastWritableFileName { - sendBatchClosedMetric(fileName: closedBatchName, forcedNew: true) - } - return try createNewWritableFile(writeSize: writeSize) - } - /// Returns writable file accordingly to default heuristic of creating and reusing files. /// /// - Parameter writeSize: the size of data to be written @@ -94,7 +78,7 @@ internal class FilesOrchestrator: FilesOrchestratorType { return lastWritableFile } else { if let closedBatchName = lastWritableFileName { - sendBatchClosedMetric(fileName: closedBatchName, forcedNew: false) + sendBatchClosedMetric(fileName: closedBatchName) } return try createNewWritableFile(writeSize: writeSize) } @@ -263,8 +247,7 @@ internal class FilesOrchestrator: FilesOrchestratorType { /// Sends "Batch Closed" telemetry log. /// - Parameters: /// - fileName: The name of the batch that was closed. - /// - forcedNew: If the batch was closed due to default heuristic or because a new batch was forced by feature. - private func sendBatchClosedMetric(fileName: String, forcedNew: Bool) { + private func sendBatchClosedMetric(fileName: String) { guard let metricsData = metricsData else { return // do not track metrics for this orchestrator } @@ -280,8 +263,7 @@ internal class FilesOrchestrator: FilesOrchestratorType { BatchClosedMetric.uploaderWindowKey: performance.uploaderWindow.toMilliseconds, BatchClosedMetric.batchSizeKey: lastWritableFileApproximatedSize, BatchClosedMetric.batchEventsCountKey: lastWritableFileObjectsCount, - BatchClosedMetric.batchDurationKey: batchDuration.toMilliseconds, - BatchClosedMetric.forcedNewKey: forcedNew + BatchClosedMetric.batchDurationKey: batchDuration.toMilliseconds ] ) } diff --git a/DatadogCore/Sources/Core/Storage/Writing/FileWriter.swift b/DatadogCore/Sources/Core/Storage/Writing/FileWriter.swift index 11e230a60a..86f7ff5325 100644 --- a/DatadogCore/Sources/Core/Storage/Writing/FileWriter.swift +++ b/DatadogCore/Sources/Core/Storage/Writing/FileWriter.swift @@ -16,20 +16,16 @@ internal struct FileWriter: Writer { let orchestrator: FilesOrchestratorType /// Algorithm to encrypt written data. let encryption: DataEncryption? - /// If this writer should force creation of a new file for writing events. - let forceNewFile: Bool /// Telemetry interface. let telemetry: Telemetry init( orchestrator: FilesOrchestratorType, - forceNewFile: Bool, encryption: DataEncryption?, telemetry: Telemetry ) { self.orchestrator = orchestrator self.encryption = encryption - self.forceNewFile = forceNewFile self.telemetry = telemetry } @@ -55,7 +51,7 @@ internal struct FileWriter: Writer { // This is to avoid a situation where event is written to one file and event metadata to another. // If this happens, the reader will not be able to match event with its metadata. let writeSize = UInt64(encoded.count) - let file = try forceNewFile ? orchestrator.getNewWritableFile(writeSize: writeSize) : orchestrator.getWritableFile(writeSize: writeSize) + let file = try orchestrator.getWritableFile(writeSize: writeSize) try file.append(data: encoded) } catch { DD.logger.error("Failed to write data", error: error) diff --git a/DatadogCore/Sources/Datadog.swift b/DatadogCore/Sources/Datadog.swift index b03f11189e..272f53d588 100644 --- a/DatadogCore/Sources/Datadog.swift +++ b/DatadogCore/Sources/Datadog.swift @@ -361,11 +361,14 @@ public enum Datadog { trackingConsent: TrackingConsent, instanceName: String = CoreRegistry.defaultInstanceName ) -> DatadogCoreProtocol { - // TODO: RUMM-511 remove this warning #if targetEnvironment(macCatalyst) consolePrint("⚠️ Catalyst is not officially supported by Datadog SDK: some features may NOT be functional!", .warn) #endif + #if swift(>=5.9) && os(visionOS) + consolePrint("⚠️ VisionOS is not officially supported by Datadog SDK: some features may NOT be functional!", .warn) + #endif + do { return try initializeOrThrow( with: configuration, @@ -409,6 +412,7 @@ public enum Datadog { let variant = configuration.additionalConfiguration[CrossPlatformAttributes.variant] as? String let sdkVersion = configuration.additionalConfiguration[CrossPlatformAttributes.sdkVersion] as? String ?? __sdkVersion let buildId = configuration.additionalConfiguration[CrossPlatformAttributes.buildId] as? String + let nativeSourceType = configuration.additionalConfiguration[CrossPlatformAttributes.nativeSourceType] as? String let performance = PerformancePreset( batchSize: debug ? .small : configuration.batchSize, @@ -438,6 +442,7 @@ public enum Datadog { buildId: buildId, variant: variant, source: source, + nativeSourceOverride: nativeSourceType, sdkVersion: sdkVersion, ciAppOrigin: CITestIntegration.active?.origin, applicationName: bundleName ?? bundleType.rawValue, diff --git a/DatadogCore/Sources/Versioning.swift b/DatadogCore/Sources/Versioning.swift index 8f3980e704..47dbbed30c 100644 --- a/DatadogCore/Sources/Versioning.swift +++ b/DatadogCore/Sources/Versioning.swift @@ -1,3 +1,3 @@ // GENERATED FILE: Do not edit directly -internal let __sdkVersion = "2.7.0" +internal let __sdkVersion = "2.7.1" diff --git a/DatadogCore/Tests/Datadog/Core/FeatureTests.swift b/DatadogCore/Tests/Datadog/Core/FeatureTests.swift index 6a7f56ce53..b659feb597 100644 --- a/DatadogCore/Tests/Datadog/Core/FeatureTests.swift +++ b/DatadogCore/Tests/Datadog/Core/FeatureTests.swift @@ -38,9 +38,9 @@ class FeatureStorageTests: XCTestCase { func testWhenWritingEventsWithoutForcingNewBatch_itShouldWriteAllEventsToTheSameBatch() throws { // When - storage.writer(for: .granted, forceNewBatch: false).write(value: ["event1": "1"]) - storage.writer(for: .granted, forceNewBatch: false).write(value: ["event2": "2"]) - storage.writer(for: .granted, forceNewBatch: false).write(value: ["event3": "3"]) + storage.writer(for: .granted).write(value: ["event1": "1"]) + storage.writer(for: .granted).write(value: ["event2": "2"]) + storage.writer(for: .granted).write(value: ["event3": "3"]) // Then storage.setIgnoreFilesAgeWhenReading(to: true) @@ -52,32 +52,13 @@ class FeatureStorageTests: XCTestCase { XCTAssertTrue(storage.reader.readNextBatches(1).isEmpty, "There must be no other batches") } - func testWhenWritingEventsWithForcingNewBatch_itShouldWriteEachEventToSeparateBatch() throws { - // When - storage.writer(for: .granted, forceNewBatch: true).write(value: ["event1": "1"]) - storage.writer(for: .granted, forceNewBatch: true).write(value: ["event2": "2"]) - storage.writer(for: .granted, forceNewBatch: true).write(value: ["event3": "3"]) - - // Then - storage.setIgnoreFilesAgeWhenReading(to: true) - - let batches = storage.reader.readNextBatches(3) - XCTAssertEqual(batches.count, 3) - batches.forEach { batch in - XCTAssertEqual(batch.events.count, 1) - storage.reader.markBatchAsRead(batch) - } - - XCTAssertTrue(storage.reader.readNextBatches(1).isEmpty, "There must be no other batches") - } - // MARK: - Behaviours on tracking consent func testWhenWritingEventsInDifferentConsents_itOnlyReadsGrantedEvents() throws { // When - storage.writer(for: .granted, forceNewBatch: false).write(value: ["event.consent": "granted"]) - storage.writer(for: .pending, forceNewBatch: false).write(value: ["event.consent": "pending"]) - storage.writer(for: .notGranted, forceNewBatch: false).write(value: ["event.consent": "notGranted"]) + storage.writer(for: .granted).write(value: ["event.consent": "granted"]) + storage.writer(for: .pending).write(value: ["event.consent": "pending"]) + storage.writer(for: .notGranted).write(value: ["event.consent": "notGranted"]) // Then storage.setIgnoreFilesAgeWhenReading(to: true) @@ -91,9 +72,9 @@ class FeatureStorageTests: XCTestCase { func testGivenEventsWrittenInDifferentConsents_whenChangingConsentToGranted_itMakesPendingEventsReadable() throws { // Given - storage.writer(for: .granted, forceNewBatch: false).write(value: ["event.consent": "granted"]) - storage.writer(for: .pending, forceNewBatch: false).write(value: ["event.consent": "pending"]) - storage.writer(for: .notGranted, forceNewBatch: false).write(value: ["event.consent": "notGranted"]) + storage.writer(for: .granted).write(value: ["event.consent": "granted"]) + storage.writer(for: .pending).write(value: ["event.consent": "pending"]) + storage.writer(for: .notGranted).write(value: ["event.consent": "notGranted"]) // When storage.migrateUnauthorizedData(toConsent: .granted) @@ -114,9 +95,9 @@ class FeatureStorageTests: XCTestCase { func testGivenEventsWrittenInDifferentConsents_whenChangingConsentToNotGranted_itDeletesPendingEvents() throws { // Given - storage.writer(for: .granted, forceNewBatch: false).write(value: ["event.consent": "granted"]) - storage.writer(for: .pending, forceNewBatch: false).write(value: ["event.consent": "pending"]) - storage.writer(for: .notGranted, forceNewBatch: false).write(value: ["event.consent": "notGranted"]) + storage.writer(for: .granted).write(value: ["event.consent": "granted"]) + storage.writer(for: .pending).write(value: ["event.consent": "pending"]) + storage.writer(for: .notGranted).write(value: ["event.consent": "notGranted"]) // When storage.migrateUnauthorizedData(toConsent: .notGranted) diff --git a/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestrator+MetricsTests.swift b/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestrator+MetricsTests.swift index 7289d5fee7..4ee4310749 100644 --- a/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestrator+MetricsTests.swift +++ b/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestrator+MetricsTests.swift @@ -115,7 +115,7 @@ class FilesOrchestrator_MetricsTests: XCTestCase { // When: // - then request new batch, which triggers directory purging dateProvider.advance(bySeconds: expectedBatchAge) - _ = try orchestrator.getNewWritableFile(writeSize: 1) + _ = try orchestrator.getWritableFile(writeSize: 1) // Then let metric = try XCTUnwrap(telemetry.messages.firstMetric(named: "Batch Deleted")) @@ -161,39 +161,7 @@ class FilesOrchestrator_MetricsTests: XCTestCase { "uploader_window": storage.uploaderWindow.toMilliseconds, "batch_size": expectedWrites.reduce(0, +), "batch_events_count": expectedWrites.count, - "batch_duration": (storage.maxFileAgeForWrite + 1).toMilliseconds, - "forced_new": false - ]) - } - - func testWhenNewBatchIsForced_itSendsBatchClosedMetric() throws { - // Given - // - request batch to be created - // - request few writes on that batch - let orchestrator = createOrchestrator() - let expectedWrites: [UInt64] = [10, 5, 2] - try expectedWrites.forEach { writeSize in - _ = try orchestrator.getWritableFile(writeSize: writeSize) - } - let expectedBatchDuration = storage.maxFileAgeForWrite - 1 - - // When - // - wait less than allowed batch age for writes - // - then request new batch, which closes the previous one - dateProvider.advance(bySeconds: expectedBatchDuration) - _ = try orchestrator.getNewWritableFile(writeSize: 1) - - // Then - let metric = try XCTUnwrap(telemetry.messages.firstMetric(named: "Batch Closed")) - DDAssertReflectionEqual(metric.attributes, [ - "metric_type": "batch closed", - "track": "track name", - "consent": "consent value", - "uploader_window": storage.uploaderWindow.toMilliseconds, - "batch_size": expectedWrites.reduce(0, +), - "batch_events_count": expectedWrites.count, - "batch_duration": expectedBatchDuration.toMilliseconds, - "forced_new": true + "batch_duration": (storage.maxFileAgeForWrite + 1).toMilliseconds ]) } } diff --git a/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestratorTests.swift b/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestratorTests.swift index beed34cfa1..cb6f09d807 100644 --- a/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestratorTests.swift +++ b/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestratorTests.swift @@ -166,19 +166,6 @@ class FilesOrchestratorTests: XCTestCase { XCTAssertNil(try? orchestrator.directory.file(named: file2.name)) } - func testWhenNewWritableFileIsObtained_itAlwaysCreatesNewFile() throws { - let orchestrator = configureOrchestrator(using: RelativeDateProvider(advancingBySeconds: 0.001)) - - let file1 = try orchestrator.getNewWritableFile(writeSize: 1) - let file2 = try orchestrator.getNewWritableFile(writeSize: 1) - let file3 = try orchestrator.getNewWritableFile(writeSize: 1) - - XCTAssertEqual(try orchestrator.directory.files().count, 3) - XCTAssertNotEqual(file1.name, file2.name) - XCTAssertNotEqual(file2.name, file3.name) - XCTAssertNotEqual(file3.name, file1.name) - } - // MARK: - Readable file tests func testGivenNoReadableFiles_whenObtainingFiles_itReturnsEmpty() { diff --git a/DatadogCore/Tests/Datadog/Core/Persistence/Writing/FileWriterTests.swift b/DatadogCore/Tests/Datadog/Core/Persistence/Writing/FileWriterTests.swift index f83a330d72..1051eee461 100644 --- a/DatadogCore/Tests/Datadog/Core/Persistence/Writing/FileWriterTests.swift +++ b/DatadogCore/Tests/Datadog/Core/Persistence/Writing/FileWriterTests.swift @@ -31,7 +31,6 @@ class FileWriterTests: XCTestCase { dateProvider: SystemDateProvider(), telemetry: NOPTelemetry() ), - forceNewFile: false, encryption: nil, telemetry: NOPTelemetry() ) @@ -69,7 +68,6 @@ class FileWriterTests: XCTestCase { dateProvider: SystemDateProvider(), telemetry: NOPTelemetry() ), - forceNewFile: false, encryption: DataEncryptionMock( encrypt: { data in "encrypted".utf8Data + data + "encrypted".utf8Data @@ -111,7 +109,6 @@ class FileWriterTests: XCTestCase { dateProvider: SystemDateProvider(), telemetry: NOPTelemetry() ), - forceNewFile: false, encryption: nil, telemetry: NOPTelemetry() ) @@ -135,42 +132,6 @@ class FileWriterTests: XCTestCase { XCTAssertEqual(block?.data, #"{"key3":"value3"}"#.utf8Data) } - func testWhenForceNewBatchIsSet_itWritesDataToSeparateFilesInTLVFormat() throws { - let writer = FileWriter( - orchestrator: FilesOrchestrator( - directory: directory, - performance: PerformancePreset.mockAny(), - dateProvider: RelativeDateProvider(advancingBySeconds: 1), - telemetry: NOPTelemetry() - ), - forceNewFile: true, - encryption: nil, - telemetry: NOPTelemetry() - ) - - writer.write(value: ["key1": "value1"]) - writer.write(value: ["key2": "value2"]) - writer.write(value: ["key3": "value3"]) - - XCTAssertEqual(try directory.files().count, 3) - - let dataBlocks = try directory.files() - .sorted { $0.name < $1.name } // read files in their creation order - .map { try DataBlockReader(input: $0.stream()).all() } - - XCTAssertEqual(dataBlocks[0].count, 1) - XCTAssertEqual(dataBlocks[0][0].type, .event) - XCTAssertEqual(dataBlocks[0][0].data, #"{"key1":"value1"}"#.utf8Data) - - XCTAssertEqual(dataBlocks[1].count, 1) - XCTAssertEqual(dataBlocks[1][0].type, .event) - XCTAssertEqual(dataBlocks[1][0].data, #"{"key2":"value2"}"#.utf8Data) - - XCTAssertEqual(dataBlocks[2].count, 1) - XCTAssertEqual(dataBlocks[2][0].type, .event) - XCTAssertEqual(dataBlocks[2][0].data, #"{"key3":"value3"}"#.utf8Data) - } - func testGivenErrorVerbosity_whenIndividualDataExceedsMaxWriteSize_itDropsDataAndPrintsError() throws { let dd = DD.mockWith(logger: CoreLoggerMock()) defer { dd.reset() } @@ -190,7 +151,6 @@ class FileWriterTests: XCTestCase { dateProvider: SystemDateProvider(), telemetry: NOPTelemetry() ), - forceNewFile: false, encryption: nil, telemetry: NOPTelemetry() ) @@ -223,7 +183,6 @@ class FileWriterTests: XCTestCase { dateProvider: SystemDateProvider(), telemetry: NOPTelemetry() ), - forceNewFile: false, encryption: nil, telemetry: NOPTelemetry() ) @@ -245,7 +204,6 @@ class FileWriterTests: XCTestCase { dateProvider: SystemDateProvider(), telemetry: NOPTelemetry() ), - forceNewFile: false, encryption: nil, telemetry: NOPTelemetry() ) @@ -276,7 +234,6 @@ class FileWriterTests: XCTestCase { dateProvider: SystemDateProvider(), telemetry: NOPTelemetry() ), - forceNewFile: false, encryption: nil, telemetry: NOPTelemetry() ) @@ -339,7 +296,6 @@ class FileWriterTests: XCTestCase { dateProvider: SystemDateProvider(), telemetry: NOPTelemetry() ), - forceNewFile: false, encryption: DataEncryptionMock( encrypt: { _ in "foo".utf8Data } ), diff --git a/DatadogCore/Tests/Datadog/Core/Upload/DataUploadWorkerTests.swift b/DatadogCore/Tests/Datadog/Core/Upload/DataUploadWorkerTests.swift index 986af5967e..4937932e32 100644 --- a/DatadogCore/Tests/Datadog/Core/Upload/DataUploadWorkerTests.swift +++ b/DatadogCore/Tests/Datadog/Core/Upload/DataUploadWorkerTests.swift @@ -21,7 +21,6 @@ class DataUploadWorkerTests: XCTestCase { ) lazy var writer = FileWriter( orchestrator: orchestrator, - forceNewFile: false, encryption: nil, telemetry: NOPTelemetry() ) 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/DatadogCore/Tests/Datadog/DatadogConfigurationTests.swift b/DatadogCore/Tests/Datadog/DatadogConfigurationTests.swift index bfe7096583..3bcd82b16a 100644 --- a/DatadogCore/Tests/Datadog/DatadogConfigurationTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogConfigurationTests.swift @@ -369,12 +369,31 @@ class DatadogConfigurationTests: XCTestCase { var configuration = defaultConfig configuration.additionalConfiguration[CrossPlatformAttributes.buildId] = buildId + // When Datadog.initialize(with: configuration, trackingConsent: .mockRandom()) defer { Datadog.flushAndDeinitialize() } let core = try XCTUnwrap(CoreRegistry.default as? DatadogCore) let context = core.contextProvider.read() + // Then XCTAssertEqual(context.buildId, buildId) } + + func testGivenNativeSourceType_itSetsInContext() throws { + // Given + let nativeSourceType: String = .mockRandom() + var configuration = defaultConfig + configuration.additionalConfiguration[CrossPlatformAttributes.nativeSourceType] = nativeSourceType + + // When + Datadog.initialize(with: configuration, trackingConsent: .mockRandom()) + defer { Datadog.flushAndDeinitialize() } + + let core = try XCTUnwrap(CoreRegistry.default as? DatadogCore) + let context = core.contextProvider.read() + + // Then + XCTAssertEqual(context.nativeSourceOverride, nativeSourceType) + } } 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" + ) + } +} diff --git a/DatadogCore/Tests/Datadog/DatadogCore/DatadogCoreTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/DatadogCoreTests.swift index c718730dcd..abaa499abd 100644 --- a/DatadogCore/Tests/Datadog/DatadogCore/DatadogCoreTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogCore/DatadogCoreTests.swift @@ -132,57 +132,6 @@ class DatadogCoreTests: XCTestCase { XCTAssertEqual(requestBuilderSpy.requestParameters.count, 1, "It should send only one request") } - func testWhenWritingEventsWithForcingNewBatch_itUploadsEachEventInSeparateRequest() throws { - // Given - let core = DatadogCore( - directory: temporaryCoreDirectory, - dateProvider: RelativeDateProvider(advancingBySeconds: 0.01), - initialConsent: .granted, - performance: .mockRandom(), - httpClient: HTTPClientMock(), - encryption: nil, - contextProvider: .mockAny(), - applicationVersion: .mockAny(), - maxBatchesPerUpload: .mockRandom(min: 1, max: 100), - backgroundTasksEnabled: .mockAny() - ) - - let requestBuilderSpy = FeatureRequestBuilderSpy() - try core.register(feature: FeatureMock(requestBuilder: requestBuilderSpy)) - let scope = try XCTUnwrap(core.scope(for: FeatureMock.name)) - - // When - scope.eventWriteContext(forceNewBatch: true) { context, writer in - writer.write(value: FeatureMock.Event(event: "1")) - } - - scope.eventWriteContext(forceNewBatch: true) { context, writer in - writer.write(value: FeatureMock.Event(event: "2")) - } - - scope.eventWriteContext(forceNewBatch: true) { context, writer in - writer.write(value: FeatureMock.Event(event: "3")) - } - - // Then - core.flushAndTearDown() - - let uploadedEvents = requestBuilderSpy.requestParameters - .flatMap { $0.events } - .map { $0.data.utf8String } - - XCTAssertEqual( - uploadedEvents, - [ - #"{"event":"1"}"#, - #"{"event":"2"}"#, - #"{"event":"3"}"#, - ], - "It should upload all events" - ) - XCTAssertEqual(requestBuilderSpy.requestParameters.count, 3, "It should send 3 requests") - } - func testWhenFeatureBaggageIsUpdated_thenNewValueIsImmediatellyAvailable() throws { // Given let core = DatadogCore( @@ -197,7 +146,6 @@ class DatadogCoreTests: XCTestCase { maxBatchesPerUpload: .mockRandom(min: 1, max: 100), backgroundTasksEnabled: .mockAny() ) - defer { core.flushAndTearDown() } let feature = FeatureMock() try core.register(feature: feature) diff --git a/DatadogCore/Tests/Datadog/LoggerTests.swift b/DatadogCore/Tests/Datadog/LoggerTests.swift index 3a7c19f584..d7ebcdda09 100644 --- a/DatadogCore/Tests/Datadog/LoggerTests.swift +++ b/DatadogCore/Tests/Datadog/LoggerTests.swift @@ -39,6 +39,8 @@ class LoggerTests: XCTestCase { sdkVersion: "1.2.3", applicationBundleIdentifier: "com.datadoghq.ios-sdk", device: .mockWith( + name: "Device Name", + model: "Model Name", osName: "testOS", osVersion: "1.0", osBuildNumber: "FFFFFF", @@ -74,6 +76,9 @@ class LoggerTests: XCTestCase { "ddtags": "env:tests,version:1.0.0", "_dd": { "device": { + "brand": "Apple", + "name": "Device Name", + "model": "Model Name", "architecture": "testArch" } } @@ -206,6 +211,7 @@ class LoggerTests: XCTestCase { matcher.assertValue(forKeyPath: "error.stack", equals: "TestError(description: \"Test description\")") matcher.assertValue(forKeyPath: "error.message", equals: "TestError(description: \"Test description\")") matcher.assertValue(forKeyPath: "error.kind", equals: "TestError") + matcher.assertValue(forKeyPath: "error.source_type", equals: "ios") } } @@ -234,6 +240,38 @@ class LoggerTests: XCTestCase { logMatcher.assertValue(forKeyPath: "error.kind", equals: errorKind) logMatcher.assertValue(forKeyPath: "error.message", equals: errorMessage) logMatcher.assertValue(forKeyPath: "error.stack", equals: stackTrace) + logMatcher.assertValue(forKeyPath: "error.source_type", equals: "ios") + } + } + + func testLoggingErrorWithSourceType() throws { + core.context = .mockAny() + + let feature: LogsFeature = .mockAny() + try core.register(feature: feature) + + let logger = Logger.create(in: core) + let errorKind = String.mockRandom() + let errorMessage = String.mockRandom() + let stackTrace = String.mockRandom() + logger._internal.log(level: .info, + message: .mockAny(), + errorKind: errorKind, + errorMessage: errorMessage, + stackTrace: stackTrace, + attributes: [ + "_dd.error.source_type": "flutter" + ] + ) + + let logMatchers = try core.waitAndReturnLogMatchers() + let logMatcher = logMatchers.first + XCTAssertNotNil(logMatcher) + if let logMatcher = logMatcher { + logMatcher.assertValue(forKeyPath: "error.kind", equals: errorKind) + logMatcher.assertValue(forKeyPath: "error.message", equals: errorMessage) + logMatcher.assertValue(forKeyPath: "error.stack", equals: stackTrace) + logMatcher.assertValue(forKeyPath: "error.source_type", equals: "flutter") } } @@ -544,6 +582,45 @@ class LoggerTests: XCTestCase { logMatchers[2].assertTags(equal: ["env:tests", "version:1.2.3"]) } + func testSendingTagsWithVariant() throws { + core.context = .mockWith( + env: "tests", + version: "1.2.3", + variant: "integration" + ) + + let feature: LogsFeature = .mockAny() + try core.register(feature: feature) + + let logger = Logger.create(in: core) + + // add tag + logger.add(tag: "tag1") + + // send message + logger.info("info message 1") + + // add tag with key + logger.addTag(withKey: "tag2", value: "abcd") + + // send message + logger.info("info message 2") + + // remove tag with key + logger.removeTag(withKey: "tag2") + + // remove tag + logger.remove(tag: "tag1") + + // send message + logger.info("info message 3") + + let logMatchers = try core.waitAndReturnLogMatchers() + logMatchers[0].assertTags(equal: ["tag1", "env:tests", "version:1.2.3", "variant:integration"]) + logMatchers[1].assertTags(equal: ["tag1", "tag2:abcd", "env:tests", "version:1.2.3", "variant:integration"]) + logMatchers[2].assertTags(equal: ["env:tests", "version:1.2.3", "variant:integration"]) + } + // MARK: - Integration With RUM Feature func testGivenBundlingWithRUMEnabledAndRUMFeatureEnabled_whenSendingLogBeforeAnyUserActivity_itContainsSessionId() throws { diff --git a/DatadogCore/Tests/Datadog/Logs/CrashLogReceiverTests.swift b/DatadogCore/Tests/Datadog/Logs/CrashLogReceiverTests.swift index 10f641c7ca..3753902003 100644 --- a/DatadogCore/Tests/Datadog/Logs/CrashLogReceiverTests.swift +++ b/DatadogCore/Tests/Datadog/Logs/CrashLogReceiverTests.swift @@ -108,6 +108,27 @@ class CrashLogReceiverTests: XCTestCase { ) } + private func crashContextWith(lastLogAttributes: AnyCodable?) -> CrashContext { + return .mockWith( + serverTimeOffset: .mockRandom(), + service: .mockRandom(), + env: .mockRandom(), + version: .mockRandom(), + buildNumber: .mockRandom(), + device: .mockWith( + osName: .mockRandom(), + osVersion: .mockRandom(), + osBuildNumber: .mockRandom(), + architecture: .mockRandom() + ), + sdkVersion: .mockRandom(), + userInfo: Bool.random() ? .mockRandom() : .empty, + networkConnectionInfo: .mockRandom(), + carrierInfo: .mockRandom(), + lastLogAttributes: lastLogAttributes + ) + } + func testWhenSendingCrashReport_itEncodesErrorInformation() throws { // Given (CR with no link to RUM view) let crashContext = crashContextWith(lastRUMViewEvent: nil) // no RUM view information @@ -133,7 +154,8 @@ class CrashLogReceiverTests: XCTestCase { error: .init( kind: crashReport.type, message: crashReport.message, - stack: crashReport.stack + stack: crashReport.stack, + sourceType: "ios" ), serviceName: crashContext.service, environment: crashContext.env, @@ -143,7 +165,15 @@ class CrashLogReceiverTests: XCTestCase { applicationVersion: crashContext.version, applicationBuildNumber: crashContext.buildNumber, buildId: nil, - dd: .init(device: .init(architecture: crashContext.device.architecture)), + variant: core.context.variant, + dd: .init( + device: .init( + brand: crashContext.device.brand, + name: crashContext.device.name, + model: crashContext.device.model, + architecture: crashContext.device.architecture + ) + ), os: .init( name: crashContext.device.osName, version: crashContext.device.osVersion, @@ -176,11 +206,13 @@ class CrashLogReceiverTests: XCTestCase { func testWhenSendingCrashReportWithRUMContext_itEncodesErrorInformation() throws { // Given (CR with the link to RUM view) let crashContext = crashContextWith( - lastRUMViewEvent: AnyCodable([ // partial RUM view information, necessary for the link - "application": ["id": "rum-app-id"], - "session": ["id": "rum-session-id"], - "view": ["id": "rum-view-id"], - ]) + lastRUMViewEvent: AnyCodable( + [ // partial RUM view information, necessary for the link + "application": ["id": "rum-app-id"], + "session": ["id": "rum-session-id"], + "view": ["id": "rum-view-id"], + ] + ) ) // When @@ -201,6 +233,25 @@ class CrashLogReceiverTests: XCTestCase { } // swiftlint:enable multiline_literal_brackets + func testWhenSendingCrashReportWithSourceType_itEncodesSourceType() throws { + // Given (CR with the link to RUM view) + let crashContext = crashContextWith(lastRUMViewEvent: nil) + + // When + let core = PassthroughCoreMock( + context: .mockWith(nativeSourceOverride: "ios+il2cpp"), + messageReceiver: CrashLogReceiver(dateProvider: SystemDateProvider()) + ) + + let sender = MessageBusSender(core: core) + sender.send(report: crashReport, with: crashContext) + + // Then + let log = try XCTUnwrap(core.events(ofType: LogEvent.self).first) + + XCTAssertEqual(log.error?.sourceType, "ios+il2cpp") + } + func testWhenSendingCrashReportWithMalformedRUMContext_itSendsErrorTelemetry() throws { // Given (CR with the link to RUM view) let crashContext = crashContextWith( @@ -224,4 +275,29 @@ class CrashLogReceiverTests: XCTestCase { XCTAssertTrue(error.message.hasPrefix("Failed to decode crash message in `LogMessageReceiver`")) XCTAssertTrue(core.events(ofType: LogEvent.self).isEmpty, "It should send no log") } + + func testWhenSendingCrashContextWithLogAttributes_itSendsThemToLog() throws { + // Given + let stringAttribute: String = .mockRandom() + let boolAttribute: Bool = .mockRandom() + let crashContext = crashContextWith(lastLogAttributes: .init( + [ + "mock-string-attribute": stringAttribute, + "mock-bool-attribute": boolAttribute + ] as [String: Any] + )) + let core = PassthroughCoreMock( + messageReceiver: CrashLogReceiver(dateProvider: SystemDateProvider()) + ) + let sender = MessageBusSender(core: core) + + // When + sender.send(report: crashReport, with: crashContext) + + // Then + let log = try XCTUnwrap(core.events(ofType: LogEvent.self).first) + + XCTAssertEqual((log.attributes.userAttributes["mock-string-attribute"] as? AnyCodable)?.value as? String, stringAttribute) + XCTAssertEqual((log.attributes.userAttributes["mock-bool-attribute"] as? AnyCodable)?.value as? Bool, boolAttribute) + } } diff --git a/DatadogCore/Tests/Datadog/Mocks/CoreMocks.swift b/DatadogCore/Tests/Datadog/Mocks/CoreMocks.swift index 98395e89b3..d9aeb3fa17 100644 --- a/DatadogCore/Tests/Datadog/Mocks/CoreMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/CoreMocks.swift @@ -275,7 +275,6 @@ internal class NOPFilesOrchestrator: FilesOrchestratorType { var performance: StoragePerformancePreset { StoragePerformanceMock.noOp } - func getNewWritableFile(writeSize: UInt64) throws -> WritableFile { NOPFile() } func getWritableFile(writeSize: UInt64) throws -> WritableFile { NOPFile() } func getReadableFiles(excludingFilesNamed excludedFileNames: Set, limit: Int) -> [ReadableFile] { [] } func delete(readableFile: ReadableFile, deletionReason: BatchDeletedMetric.RemovalReason) { } diff --git a/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift index 67fa374c59..84cd7fab9a 100644 --- a/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift @@ -143,7 +143,8 @@ extension CrashContext { carrierInfo: CarrierInfo? = .mockAny(), lastRUMViewEvent: AnyCodable? = nil, lastRUMSessionState: AnyCodable? = nil, - lastIsAppInForeground: Bool = .mockAny() + lastIsAppInForeground: Bool = .mockAny(), + lastLogAttributes: AnyCodable? = nil ) -> Self { .init( serverTimeOffset: serverTimeOffset, @@ -160,7 +161,8 @@ extension CrashContext { carrierInfo: carrierInfo, lastRUMViewEvent: lastRUMViewEvent, lastRUMSessionState: lastRUMSessionState, - lastIsAppInForeground: lastIsAppInForeground + lastIsAppInForeground: lastIsAppInForeground, + lastLogAttributes: lastLogAttributes ) } @@ -180,7 +182,8 @@ extension CrashContext { carrierInfo: .mockRandom(), lastRUMViewEvent: AnyCodable(mockRandomAttributes()), lastRUMSessionState: AnyCodable(mockRandomAttributes()), - lastIsAppInForeground: .mockRandom() + lastIsAppInForeground: .mockRandom(), + lastLogAttributes: AnyCodable(mockRandomAttributes()) ) } @@ -197,7 +200,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/DatadogCore/Tests/Datadog/Mocks/DatadogInternal/DatadogCoreProxy.swift b/DatadogCore/Tests/Datadog/Mocks/DatadogInternal/DatadogCoreProxy.swift index 542f11b838..fd2e2afa24 100644 --- a/DatadogCore/Tests/Datadog/Mocks/DatadogInternal/DatadogCoreProxy.swift +++ b/DatadogCore/Tests/Datadog/Mocks/DatadogInternal/DatadogCoreProxy.swift @@ -110,9 +110,9 @@ private struct FeatureScopeProxy: FeatureScope { let proxy: FeatureScope let interceptor: FeatureScopeInterceptor - func eventWriteContext(bypassConsent: Bool, forceNewBatch: Bool, _ block: @escaping (DatadogContext, Writer) -> Void) { + func eventWriteContext(bypassConsent: Bool, _ block: @escaping (DatadogContext, Writer) -> Void) { interceptor.enter() - proxy.eventWriteContext(bypassConsent: bypassConsent, forceNewBatch: forceNewBatch) { context, writer in + proxy.eventWriteContext(bypassConsent: bypassConsent) { context, writer in block(context, interceptor.intercept(writer: writer)) interceptor.leave() } diff --git a/DatadogCore/Tests/Datadog/Mocks/LogsMocks.swift b/DatadogCore/Tests/Datadog/Mocks/LogsMocks.swift index 500d0ef8fd..0ba0fe633a 100644 --- a/DatadogCore/Tests/Datadog/Mocks/LogsMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/LogsMocks.swift @@ -102,6 +102,7 @@ extension LogEvent: AnyMockable, RandomMockable { applicationVersion: String = .mockAny(), applicationBuildNumber: String = .mockAny(), buildId: String? = .mockAny(), + variant: String? = .mockAny(), dd: LogEvent.Dd = .mockAny(), os: LogEvent.OperatingSystem = .mockAny(), userInfo: UserInfo = .mockAny(), @@ -123,6 +124,7 @@ extension LogEvent: AnyMockable, RandomMockable { applicationVersion: applicationVersion, applicationBuildNumber: applicationBuildNumber, buildId: buildId, + variant: variant, dd: dd, os: os, userInfo: userInfo, @@ -147,6 +149,7 @@ extension LogEvent: AnyMockable, RandomMockable { applicationVersion: .mockRandom(), applicationBuildNumber: .mockRandom(), buildId: .mockRandom(), + variant: .mockRandom(), dd: .mockRandom(), os: .mockRandom(), userInfo: .mockRandom(), @@ -227,12 +230,18 @@ extension LogEvent.OperatingSystem: AnyMockable, RandomMockable { extension LogEvent.DeviceInfo: AnyMockable, RandomMockable { public static func mockAny() -> LogEvent.DeviceInfo { return LogEvent.DeviceInfo( + brand: .mockAny(), + name: .mockAny(), + model: .mockAny(), architecture: .mockAny() ) } public static func mockRandom() -> LogEvent.DeviceInfo { return LogEvent.DeviceInfo( + brand: .mockRandom(), + name: .mockRandom(), + model: .mockRandom(), architecture: .mockRandom() ) } diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift index af2928cb53..a2d37b857e 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift @@ -385,11 +385,14 @@ extension RUMErrorEvent: RandomMockable { device: .mockRandom(), display: nil, error: .init( + binaryImages: nil, + category: nil, handling: nil, handlingStack: nil, id: .mockRandom(), isCrash: .random(), message: .mockRandom(), + meta: nil, resource: .init( method: .mockRandom(), provider: .init( @@ -403,8 +406,11 @@ extension RUMErrorEvent: RandomMockable { source: [.source, .network, .custom].randomElement()!, sourceType: .mockRandom(), stack: .mockRandom(), - type: .mockRandom() + threads: nil, + type: .mockRandom(), + wasTruncated: .mockRandom() ), + freeze: nil, os: .mockRandom(), service: .mockRandom(), session: .init( @@ -501,6 +507,7 @@ extension TelemetryConfigurationEvent: RandomMockable { actionNameAttribute: nil, allowFallbackToLocalStorage: nil, allowUntrustedEvents: nil, + appHangThreshold: .mockRandom(), backgroundTasksEnabled: .mockRandom(), batchProcessingLevel: .mockRandom(), batchSize: .mockAny(), diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index fbbba44fad..6c8b783896 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -258,7 +258,10 @@ extension RUMAddCurrentViewErrorCommand: AnyMockable, RandomMockable { attributes: [AttributeKey: AttributeValue] = [:] ) -> RUMAddCurrentViewErrorCommand { return RUMAddCurrentViewErrorCommand( - time: time, error: error, source: source, attributes: attributes + time: time, + error: error, + source: source, + attributes: attributes ) } @@ -271,7 +274,12 @@ extension RUMAddCurrentViewErrorCommand: AnyMockable, RandomMockable { attributes: [AttributeKey: AttributeValue] = [:] ) -> RUMAddCurrentViewErrorCommand { return RUMAddCurrentViewErrorCommand( - time: time, message: message, type: type, stack: stack, source: source, attributes: attributes + time: time, + message: message, + type: type, + stack: stack, + source: source, + attributes: attributes ) } } diff --git a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift index 1a0bd0a8da..1c95a58844 100644 --- a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift @@ -526,6 +526,41 @@ class CrashReportReceiverTests: XCTestCase { DDAssertJSONEqual(AnyEncodable(sendRUMErrorEvent.additionalAttributes?[DDError.wasTruncated]), crashReport.wasTruncated) } + func testGivenCrashDuringRUMSessionWithActiveViewAndOverridenSourceType_whenSendingRUMViewEvent_itSendsOverrideSourceType() throws { + core = PassthroughCoreMock( + context: .mockWith(nativeSourceOverride: "ios+il2cpp") + ) + let lastRUMViewEvent: RUMViewEvent = .mockRandomWith(crashCount: 0) + + // Given + let crashDate: Date = .mockDecember15th2019At10AMUTC() + let dateCorrectionOffset: TimeInterval = .mockRandom() + let crashReport: DDCrashReport = .mockWith(date: crashDate) + let crashContext: CrashContext = .mockWith( + serverTimeOffset: dateCorrectionOffset, + trackingConsent: .granted, + lastRUMViewEvent: AnyCodable(lastRUMViewEvent) // means there was a RUM session and it was sampled + ) + + let receiver: CrashReportReceiver = .mockWith( + dateProvider: RelativeDateProvider(using: crashDate), + sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) + trackBackgroundEvents: .mockRandom() // no matter BET + ) + + // When + XCTAssertTrue( + receiver.receive(message: .baggage( + key: MessageBusSender.MessageKeys.crash, + value: MessageBusSender.Crash(report: crashReport, context: crashContext) + ), from: core) + ) + + // Then + let sendRUMErrorEvent = core.events(ofType: RUMCrashEvent.self)[0] + XCTAssertEqual(sendRUMErrorEvent.model.error.sourceType, .iosIl2cpp, "Must send overridden sourceType") + } + // MARK: - Testing Uploaded Data - Crashes During RUM Session With No Active View func testGivenCrashDuringRUMSessionWithNoActiveView_whenSendingRUMViewEvent_itIsLinkedToPreviousRUMSessionAndIncludesErrorInformation() throws { @@ -659,6 +694,7 @@ class CrashReportReceiverTests: XCTestCase { XCTAssertNotNil(sentRUMError.additionalAttributes?[DDError.binaryImages], "It must contain crash details") XCTAssertNotNil(sentRUMError.additionalAttributes?[DDError.meta], "It must contain crash details") XCTAssertNotNil(sentRUMError.additionalAttributes?[DDError.wasTruncated], "It must contain crash details") + XCTAssertEqual(sentRUMError.model.error.category, .exception, "Crashes are considered exceptions") XCTAssertNil(sentRUMView.context, "It musn't contain context as there was no last active view") } @@ -679,6 +715,78 @@ class CrashReportReceiverTests: XCTestCase { ) } + func testGivenCrashDuringRUMSessionWithNoActiveViewAndOverriddenSourceType_whenSendingRUMViewEvent_itSendsOverridenSourceType() throws { + func test( + lastRUMSessionState: RUMSessionState, + launchInForeground: Bool, + backgroundEventsTrackingEnabled: Bool + ) throws { + let mockApplicationId: String = .mockRandom(among: .alphanumerics) + let core = PassthroughCoreMock( + context: .mockWith(nativeSourceOverride: "ios+il2cpp"), + messageReceiver: CrashReportReceiver.mockWith( + applicationID: mockApplicationId, + sessionSampler: .mockKeepAll(), + trackBackgroundEvents: backgroundEventsTrackingEnabled, + uuidGenerator: DefaultRUMUUIDGenerator() + ) + ) + + // Given + let crashDate: Date = .mockDecember15th2019At10AMUTC() + let crashReport: DDCrashReport = .mockWith( + date: crashDate, + type: .mockRandom() + ) + let dateCorrectionOffset: TimeInterval = .mockRandom() + let crashContext: CrashContext = .mockWith( + serverTimeOffset: dateCorrectionOffset, + service: .mockRandom(), + version: .mockRandom(), + buildNumber: .mockRandom(), + source: .mockRandom(), + trackingConsent: .granted, + userInfo: .mockRandom(), + networkConnectionInfo: .mockRandom(), + carrierInfo: .mockRandom(), + lastRUMViewEvent: nil, // means there was no active RUM view + lastRUMSessionState: AnyCodable(lastRUMSessionState), // means there was RUM session (sampled) + lastIsAppInForeground: launchInForeground + ) + + let receiver: CrashReportReceiver = .mockWith( + applicationID: .mockRandom(among: .alphanumerics), + dateProvider: RelativeDateProvider(using: crashDate), + sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) + trackBackgroundEvents: backgroundEventsTrackingEnabled + ) + + // When + XCTAssertTrue( + receiver.receive(message: .baggage( + key: MessageBusSender.MessageKeys.crash, + value: MessageBusSender.Crash(report: crashReport, context: crashContext) + ), from: core) + ) + + // Then + let sentRUMError = core.events(ofType: RUMCrashEvent.self)[0] + XCTAssertEqual(sentRUMError.model.error.sourceType, .iosIl2cpp, "Must send overridden sourceType") + } + + try test( + lastRUMSessionState: .mockWith(isInitialSession: true, hasTrackedAnyView: false), // when initial session with no views history + launchInForeground: true, // launch in foreground + backgroundEventsTrackingEnabled: .mockRandom() // no matter BET + ) + + try test( + lastRUMSessionState: .mockRandom(), // any sampled session + launchInForeground: false, // launch in background + backgroundEventsTrackingEnabled: true // BET enabled + ) + } + // MARK: - Testing Uploaded Data - Crashes During App Launch func testGivenCrashDuringAppLaunch_whenSending_itIsSendAsRUMErrorInNewRUMSession() throws { @@ -814,4 +922,75 @@ class CrashReportReceiverTests: XCTestCase { expectViewURL: RUMOffViewEventsHandlingRule.Constants.backgroundViewURL ) } + + func testGivenCrashDuringAppLaunchWithNativeSourceType_whenSending_itIsSendsWithNativeSourceType() throws { + func test( + launchInForeground: Bool, + backgroundEventsTrackingEnabled: Bool, + expectViewName expectedViewName: String, + expectViewURL expectedViewURL: String + ) throws { + let core = PassthroughCoreMock( + context: .mockWith(nativeSourceOverride: "ios+il2cpp"), + messageReceiver: CrashReportReceiver.mockWith( + applicationID: .mockRandom(among: .alphanumerics), + sessionSampler: .mockKeepAll(), + trackBackgroundEvents: backgroundEventsTrackingEnabled, + uuidGenerator: DefaultRUMUUIDGenerator() + ) + ) + + // Given + let crashDate: Date = .mockDecember15th2019At10AMUTC() + let crashReport: DDCrashReport = .mockWith( + date: crashDate, + type: .mockRandom() + ) + + let dateCorrectionOffset: TimeInterval = .mockRandom() + let crashContext: CrashContext = .mockWith( + serverTimeOffset: dateCorrectionOffset, + trackingConsent: .granted, + userInfo: .mockRandom(), + networkConnectionInfo: .mockRandom(), + carrierInfo: .mockRandom(), + lastRUMViewEvent: nil, // means there was no RUM session (it crashed during app launch) + lastRUMSessionState: nil, // means there was no RUM session (it crashed during app launch) + lastIsAppInForeground: launchInForeground + ) + + let receiver: CrashReportReceiver = .mockWith( + applicationID: .mockRandom(), + dateProvider: RelativeDateProvider(using: crashDate), + trackBackgroundEvents: backgroundEventsTrackingEnabled + ) + + // When + XCTAssertTrue( + receiver.receive(message: .baggage( + key: MessageBusSender.MessageKeys.crash, + value: MessageBusSender.Crash(report: crashReport, context: crashContext) + ), from: core) + ) + + // Then + let sentRUMError = core.events(ofType: RUMCrashEvent.self)[0] + + XCTAssertEqual(sentRUMError.model.error.sourceType, .iosIl2cpp, "Must send overridden sourceType") + } + + try test( + launchInForeground: true, // launch in foreground + backgroundEventsTrackingEnabled: .mockRandom(), // no matter BET + expectViewName: RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName, + expectViewURL: RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewURL + ) + + try test( + launchInForeground: false, // launch in background + backgroundEventsTrackingEnabled: true, // BET enabled + expectViewName: RUMOffViewEventsHandlingRule.Constants.backgroundViewName, + expectViewURL: RUMOffViewEventsHandlingRule.Constants.backgroundViewURL + ) + } } diff --git a/DatadogCore/Tests/Datadog/RUM/RUMEventOutputs/RUMEventFileOutputTests.swift b/DatadogCore/Tests/Datadog/RUM/RUMEventOutputs/RUMEventFileOutputTests.swift index ee5f966cbc..9b4deaf2f2 100644 --- a/DatadogCore/Tests/Datadog/RUM/RUMEventOutputs/RUMEventFileOutputTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/RUMEventOutputs/RUMEventFileOutputTests.swift @@ -37,7 +37,6 @@ class RUMEventFileOutputTests: XCTestCase { dateProvider: fileCreationDateProvider, telemetry: NOPTelemetry() ), - forceNewFile: false, encryption: nil, telemetry: NOPTelemetry() ) diff --git a/DatadogCore/Tests/Matchers/SRRequestMatcher.swift b/DatadogCore/Tests/Matchers/SRRequestMatcher.swift index 73ce71d106..59935c334b 100644 --- a/DatadogCore/Tests/Matchers/SRRequestMatcher.swift +++ b/DatadogCore/Tests/Matchers/SRRequestMatcher.swift @@ -61,43 +61,21 @@ internal struct SRRequestMatcher { self.multipartForm = try MultipartFormDataParser(data: multipartBody, boundary: multipartBoundary) } - /// The value of "segment" field in underlying multipart form. - func segment() throws -> String { try valueOfField(named: "segment") } - - /// The value of "application.id" field in underlying multipart form. - func applicationID() throws -> String { try valueOfField(named: "application.id") } - - /// The value of "session.id" field in underlying multipart form. - func sessionID() throws -> String { try valueOfField(named: "session.id") } - - /// The value of "view.id" field in underlying multipart form. - func viewID() throws -> String { try valueOfField(named: "view.id") } - - /// The value of "has_full_snapshot" field in underlying multipart form. - func hasFullSnapshot() throws -> String { try valueOfField(named: "has_full_snapshot") } - - /// The value of "records_count" field in underlying multipart form. - func recordsCount() throws -> String { try valueOfField(named: "records_count") } - - /// The value of "raw_segment_size" field in underlying multipart form. - func rawSegmentSize() throws -> String { try valueOfField(named: "raw_segment_size") } - - /// The value of "start" field in underlying multipart form. - func start() throws -> String { try valueOfField(named: "start") } - - /// The value of "end" field in underlying multipart form. - func end() throws -> String { try valueOfField(named: "end") } - - /// The value of "source" field in underlying multipart form. - func source() throws -> String { try valueOfField(named: "source") } + /// Returns the blob file. + func blob(_ transform: (Data) throws -> T) throws -> T { + let data = try dataOfFile(named: "blob", fieldName: "event", mimeType: "application/json") + return try transform(data) + } /// Data of "segment" file in underlying multipart form. - func segmentJSONData() throws -> Data { - let compressedData = try dataOfFile(named: try sessionID(), fieldName: "segment", mimeType: "application/octet-stream") + func segment(at index: Int) throws -> SRSegmentMatcher { + let compressedData = try dataOfFile(named: "file\(index)", fieldName: "segment", mimeType: "application/octet-stream") guard let data = zlib.decode(compressedData) else { throw SRRequestException.segmentException("Failed to decompress segment JSON data: \(compressedData)") } - return data + + let object = try data.toJSONObject() + return SRSegmentMatcher(object: object) } // MARK: - Querying Multipart Fields and Files diff --git a/DatadogCore/Tests/Matchers/SRSegmentMatcher.swift b/DatadogCore/Tests/Matchers/SRSegmentMatcher.swift index 7eaf49eb2e..92f73952cd 100644 --- a/DatadogCore/Tests/Matchers/SRSegmentMatcher.swift +++ b/DatadogCore/Tests/Matchers/SRSegmentMatcher.swift @@ -11,26 +11,6 @@ import TestUtilities /// /// See: ``DatadogSessionReplay.SRSegment`` to understand how underlying data is encoded. internal class SRSegmentMatcher: JSONObjectMatcher { - /// Creates matcher from Session Replay `URLRequest`. The `request` must be a valid Session Replay (multipart) request. - /// This method extracts SR segment from the "segment" file encoded in multipart request. Other multipart fields are ignored. - /// - /// - Parameter request: Session Replay request. - static func fromURLRequest(_ request: URLRequest) throws -> SRSegmentMatcher { - let requestMatcher = try SRRequestMatcher(request: request) - let segmentJSONObjectData = try requestMatcher.segmentJSONData() - return SRSegmentMatcher(jsonObject: try segmentJSONObjectData.toJSONObject()) - } - - /// Creates matcher from JSON-encoded SR segment. - /// - Parameter data: JSON-encoded SR segment data (not compressed). - static func fromJSONData(_ data: Data) throws -> SRSegmentMatcher { - return SRSegmentMatcher(jsonObject: try data.toJSONObject()) - } - - private init(jsonObject: [String: Any]) { - super.init(object: jsonObject) - } - /// Enumerates SR record types. /// Raw values correspond to record types defined in SR JSON schema. /// @@ -44,6 +24,39 @@ internal class SRSegmentMatcher: JSONObjectMatcher { case visualViewportRecord = 8 } + /// The value of "segment" field in underlying multipart form. + func segment() throws -> String { try value("segment") } + + /// The value of "application.id" field in underlying multipart form. + func applicationID() throws -> String { try value("application.id") } + + /// The value of "session.id" field in underlying multipart form. + func sessionID() throws -> String { try value("session.id") } + + /// The value of "view.id" field in underlying multipart form. + func viewID() throws -> String { try value("view.id") } + + /// The value of "has_full_snapshot" field in underlying multipart form. + func hasFullSnapshot() throws -> Bool { try value("has_full_snapshot") } + + /// The value of "records_count" field in underlying multipart form. + func recordsCount() throws -> Int { try value("records_count") } + + /// The value of "raw_segment_size" field in underlying multipart form. + func rawSegmentSize() throws -> Int { try value("raw_segment_size") } + + /// The value of "compressed_segment_size" field in underlying multipart form. + func compressedSegmentSize() throws -> Int { try value("compressed_segment_size") } + + /// The value of "start" field in underlying multipart form. + func start() throws -> Int { try value("start") } + + /// The value of "end" field in underlying multipart form. + func end() throws -> Int { try value("end") } + + /// The value of "source" field in underlying multipart form. + func source() throws -> String { try value("source") } + /// Returns an array of JSON object matchers for all records in this segment. func records() throws -> [JSONObjectMatcher] { try array("records").objects() diff --git a/DatadogCrashReporting.podspec b/DatadogCrashReporting.podspec index 02a21ff4d2..37ca29f26c 100644 --- a/DatadogCrashReporting.podspec +++ b/DatadogCrashReporting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogCrashReporting" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Official Datadog Crash Reporting SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift b/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift index 14da79cbc5..09b87afc7e 100644 --- a/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift +++ b/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift @@ -69,6 +69,9 @@ internal struct CrashContext: Codable, Equatable { /// The last _"Is app in foreground?"_ information from crashed app process. let lastIsAppInForeground: Bool + /// Last global log attributes, set with Logs.addAttribute / Logs.removeAttribute + var lastLogAttributes: AnyCodable? + // MARK: - Initialization init( @@ -86,7 +89,8 @@ internal struct CrashContext: Codable, Equatable { carrierInfo: CarrierInfo?, lastRUMViewEvent: AnyCodable?, lastRUMSessionState: AnyCodable?, - lastIsAppInForeground: Bool + lastIsAppInForeground: Bool, + lastLogAttributes: AnyCodable? ) { self.serverTimeOffset = serverTimeOffset self.service = service @@ -103,12 +107,14 @@ internal struct CrashContext: Codable, Equatable { self.lastRUMViewEvent = lastRUMViewEvent self.lastRUMSessionState = lastRUMSessionState self.lastIsAppInForeground = lastIsAppInForeground + self.lastLogAttributes = lastLogAttributes } init( _ context: DatadogContext, lastRUMViewEvent: AnyCodable?, - lastRUMSessionState: AnyCodable? + lastRUMSessionState: AnyCodable?, + lastLogAttributes: AnyCodable? ) { self.serverTimeOffset = context.serverTimeOffset self.service = context.service @@ -126,6 +132,7 @@ internal struct CrashContext: Codable, Equatable { self.lastRUMViewEvent = lastRUMViewEvent self.lastRUMSessionState = lastRUMSessionState + self.lastLogAttributes = lastLogAttributes } static func == (lhs: CrashContext, rhs: CrashContext) -> Bool { diff --git a/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift b/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift index c190edacd1..1e360acc4f 100644 --- a/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift +++ b/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift @@ -39,6 +39,10 @@ internal class CrashContextCoreProvider: CrashContextProvider { didSet { _context?.lastRUMSessionState = sessionState } } + private var logAttributes: AnyCodable? { + didSet { _context?.lastLogAttributes = logAttributes } + } + // MARK: - CrashContextProviderType var currentCrashContext: CrashContext? { @@ -64,6 +68,9 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { /// The key references RUM session state. /// The state associated with the key conforms to `Codable`. static let sessionState = "rum-session-state" + + /// This key references the global log attributes + static let logAttributes = "global-log-attributes" } func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { @@ -76,6 +83,8 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { resetRUMView(with: baggage, to: core) case .baggage(let label, let baggage) where label == RUMBaggageKeys.sessionState: updateSessionState(with: baggage, to: core) + case .baggage(let label, let baggage) where label == RUMBaggageKeys.logAttributes: + updateLogAttributes(with: baggage, to: core) default: return false } @@ -91,7 +100,8 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { let crashContext = CrashContext( context, lastRUMViewEvent: self.viewEvent, - lastRUMSessionState: self.sessionState + lastRUMSessionState: self.sessionState, + lastLogAttributes: self.logAttributes ) if crashContext != self._context { @@ -134,6 +144,17 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { } } } + + private func updateLogAttributes(with baggage: FeatureBaggage, to core: DatadogCoreProtocol) { + queue.async { [weak core] in + do { + self.logAttributes = try baggage.decode(type: AnyCodable.self) + } catch { + core?.telemetry + .error("Fails to decode log attributes from Crash Reporting", error: error) + } + } + } } extension CrashContextCoreProvider: Flushable { 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 04751077ad..0afde4208e 100644 --- a/DatadogCrashReporting/Sources/CrashReportingPlugin.swift +++ b/DatadogCrashReporting/Sources/CrashReportingPlugin.swift @@ -5,174 +5,11 @@ */ 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. /// /// 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/Sources/Integrations/BacktraceReporter.swift b/DatadogCrashReporting/Sources/Integrations/BacktraceReporter.swift new file mode 100644 index 0000000000..33abbc5d3b --- /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(threadID: ThreadID) -> DatadogInternal.BacktraceReport? { + do { + return try reporter.generateBacktrace(threadID: threadID) + } catch let error { + DD.logger.error("Encountered an error when generating backtrace for thread ID: \(threadID)", error: error) + return nil + } + } +} 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..819afa077d 100644 --- a/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift +++ b/DatadogCrashReporting/Sources/PLCrashReporterIntegration/PLCrashReporterIntegration.swift @@ -5,6 +5,7 @@ */ import Foundation +import DatadogInternal import CrashReporter internal extension PLCrashReporterConfig { @@ -57,4 +58,21 @@ internal final class PLCrashReporterIntegration: ThirdPartyCrashReporter { func purgePendingCrashReport() throws { try crashReporter.purgePendingCrashReportAndReturnError() } + + func generateBacktrace(threadID: ThreadID) throws -> BacktraceReport { + let liveReportData = crashReporter.generateLiveReport(withThread: threadID) + 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 d55a8c63c6..7f98f22381 100644 --- a/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift +++ b/DatadogCrashReporting/Sources/ThirdPartyCrashReporter.swift @@ -5,12 +5,15 @@ */ import Foundation +import DatadogInternal /// An interface of 3rd party crash reporter used by the DatadogCrashReporting. 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(threadID: ThreadID) throws -> BacktraceReport } 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/DatadogCrashReporting/Tests/Mocks.swift b/DatadogCrashReporting/Tests/Mocks.swift index cf298449ca..32a4ffdf40 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 @@ -19,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 @@ -46,29 +49,9 @@ internal class ThirdPartyCrashReporterMock: ThirdPartyCrashReporter { } hasPurgedPendingCrashReport = true } -} -internal extension DDCrashReport { - static func mockAny() -> DDCrashReport { - return DDCrashReport( - date: Date(), - type: "any", - message: "any", - stack: "any", - threads: [], - binaryImages: [], - meta: .init( - incidentIdentifier: "any", - process: "any", - parentProcess: "any", - path: "any", - codeType: "any", - exceptionType: "any", - exceptionCodes: "any" - ), - wasTruncated: false, - context: "any".data(using: .utf8) - ) + func generateBacktrace(threadID: ThreadID) throws -> BacktraceReport { + return generatedBacktrace } } diff --git a/DatadogInternal.podspec b/DatadogInternal.podspec index a498bc7009..590de8b3b4 100644 --- a/DatadogInternal.podspec +++ b/DatadogInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogInternal" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Datadog Internal Package. This module is not for public use." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogInternal/Sources/Attributes/Attributes.swift b/DatadogInternal/Sources/Attributes/Attributes.swift index aad368b5b5..74457c09f2 100644 --- a/DatadogInternal/Sources/Attributes/Attributes.swift +++ b/DatadogInternal/Sources/Attributes/Attributes.swift @@ -149,6 +149,10 @@ public struct CrossPlatformAttributes { /// It sets the GraphQL varibles as a JSON string if they were defined by the developer. /// Expects `String` value. public static let graphqlVariables = "_dd.graphql.variables" + + /// Override the `source_type` of errors reported by the native crash handler. This is used on + /// platforms that can supply extra steps or information on a native crash (such as Unity's IL2CPP) + public static let nativeSourceType = "_dd.native_source_type" } public struct LaunchArguments { diff --git a/DatadogInternal/Sources/BacktraceReporting/BacktraceReporter.swift b/DatadogInternal/Sources/BacktraceReporting/BacktraceReporter.swift new file mode 100644 index 0000000000..567cf93451 --- /dev/null +++ b/DatadogInternal/Sources/BacktraceReporting/BacktraceReporter.swift @@ -0,0 +1,93 @@ +/* + * 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 value identifying the thread for `BacktraceReport` generation. +public typealias ThreadID = thread_t + +public extension Thread { + /// Obtains the `ThreadID` of the caller thread. + /// + /// Should be used in conjunction with `BacktraceReporting.generateBacktrace(threadID:)` to generate backtrace of particular thread. + static var currentThreadID: ThreadID { pthread_mach_thread_np(pthread_self()) } +} + +/// A protocol for types capable of generating backtrace reports. +public protocol BacktraceReporting { + /// Generates a backtrace report for given thread ID. + /// + /// The thread given by `threadID` will be promoted in the main stack of returned `BacktraceReport` (`report.stack`). + /// + /// - Parameter threadID: An ID of the thread that backtrace generation should start on. + /// - Returns: A `BacktraceReport` starting on the given thread and containing information about all other threads + /// running in the process. Returns `nil` if the backtrace report cannot be generated. + func generateBacktrace(threadID: ThreadID) -> BacktraceReport? +} + +public extension BacktraceReporting { + /// Generates a backtrace report for current thread. + /// + /// The caller thread will be promoted in the main stack of returned `BacktraceReport` (`report.stack`). + /// + /// - Returns: A `BacktraceReport` starting on the current thread and containing information about all other threads + /// running in the process. Returns `nil` if the backtrace report cannot be generated. + func generateBacktrace() -> BacktraceReport? { + let callerThreadID = Thread.currentThreadID + return generateBacktrace(threadID: callerThreadID) + } +} + +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(threadID: ThreadID) -> 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.reporter.generateBacktrace(threadID: threadID) + } +} + +/// 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..2de09b2b52 --- /dev/null +++ b/DatadogInternal/Sources/BacktraceReporting/BacktraceReportingFeature.swift @@ -0,0 +1,22 @@ +/* + * 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. + let reporter: BacktraceReporting + + /// Creates `BacktraceReportingFeature`. + /// - Parameter reporter: An external implementation of a type capable of generating backtrace reports. + init(reporter: BacktraceReporting) { + self.reporter = reporter + } +} diff --git a/DatadogInternal/Sources/Context/DatadogContext.swift b/DatadogInternal/Sources/Context/DatadogContext.swift index 316f0b4959..83f97ea759 100644 --- a/DatadogInternal/Sources/Context/DatadogContext.swift +++ b/DatadogInternal/Sources/Context/DatadogContext.swift @@ -39,6 +39,9 @@ public struct DatadogContext { /// - See: Datadog [Reserved Attributes](https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#reserved-attributes). public let source: String + /// Denotes the source type for crashes. This is used for platforms that provide additional symbolication steps for native crashes. + public let nativeSourceOverride: String? + /// The version of Datadog iOS SDK. public let sdkVersion: String @@ -124,6 +127,7 @@ public struct DatadogContext { applicationBundleIdentifier: String, sdkInitDate: Date, device: DeviceInfo, + nativeSourceOverride: String? = nil, userInfo: UserInfo? = nil, trackingConsent: TrackingConsent = .pending, launchTime: LaunchTime? = nil, @@ -150,6 +154,7 @@ public struct DatadogContext { self.applicationBundleIdentifier = applicationBundleIdentifier self.sdkInitDate = sdkInitDate self.device = device + self.nativeSourceOverride = nativeSourceOverride self.userInfo = userInfo self.trackingConsent = trackingConsent self.launchTime = launchTime diff --git a/DatadogInternal/Sources/DatadogCoreProtocol.swift b/DatadogInternal/Sources/DatadogCoreProtocol.swift index 0e2f8cebc8..c8416030ea 100644 --- a/DatadogInternal/Sources/DatadogCoreProtocol.swift +++ b/DatadogInternal/Sources/DatadogCoreProtocol.swift @@ -214,12 +214,8 @@ public protocol FeatureScope { /// - bypassConsent: `true` to bypass the current core consent and write events as authorized. /// Default is `false`, setting `true` must still respect user's consent for /// collecting information. - /// - forceNewBatch: `true` to enforce that event will be written to a separate batch than previous events. - /// Default is `false`, which means the core uses its own heuristic to split events between - /// batches. This parameter can be leveraged in Features which require a clear separation - /// of group of events for preparing their upload (a single upload is always constructed from a single batch). /// - block: The block to execute; it is called on the context queue. - func eventWriteContext(bypassConsent: Bool, forceNewBatch: Bool, _ block: @escaping (DatadogContext, Writer) -> Void) + func eventWriteContext(bypassConsent: Bool, _ block: @escaping (DatadogContext, Writer) -> Void) /// Retrieve the core context. /// @@ -249,8 +245,8 @@ public extension FeatureScope { /// batches. This parameter can be leveraged in Features which require a clear separation /// of group of events for preparing their upload (a single upload is always constructed from a single batch). /// - block: The block to execute; it is called on the context queue. - func eventWriteContext(bypassConsent: Bool = false, forceNewBatch: Bool = false, _ block: @escaping (DatadogContext, Writer) -> Void) { - eventWriteContext(bypassConsent: bypassConsent, forceNewBatch: forceNewBatch, block) + func eventWriteContext(_ block: @escaping (DatadogContext, Writer) -> Void) { + eventWriteContext(bypassConsent: false, block) } } diff --git a/DatadogInternal/Sources/MessageBus/FeatureMessage.swift b/DatadogInternal/Sources/MessageBus/FeatureMessage.swift index 0df2797303..5e462c83a9 100644 --- a/DatadogInternal/Sources/MessageBus/FeatureMessage.swift +++ b/DatadogInternal/Sources/MessageBus/FeatureMessage.swift @@ -8,13 +8,23 @@ import Foundation /// The set of messages that can be transimtted on the Features message bus. public enum FeatureMessage { - /// A custom message with generic encodable - /// attributes. + /// A custom message with generic encodable attributes. + /// + /// A baggage can be used to transmit loosely-typed data structure using `Codable`. + /// The encoding/decoding processes will have an impact on performances, opt for a baggage + /// only if the data-structure is small. + /// + /// For large data type, use the `.value` case with shared type definition. case baggage( key: String, baggage: FeatureBaggage ) + /// A web-view message. + /// + /// Represent a Browser SDK event sent through the JS bridge. + case webview(WebViewMessage) + /// A core context message. /// /// The core will send updated context throught the bus. Do not send new context values @@ -30,6 +40,10 @@ public enum FeatureMessage { extension FeatureMessage { /// Creates a `.baggage` message with the given key and `Encodable` value. /// + /// A baggage can be used to transmit loosely-typed data structure using `Codable`. + /// The encoding/decoding processes will have an impact on performances, opt for a baggage + /// only if the data-structure is small. + /// /// - Parameters: /// - key: The baggage key. /// - baggage: The baggage value. @@ -41,9 +55,9 @@ extension FeatureMessage { /// Returns the baggage if the key matches the message. /// /// - Parameters: - /// - label: The requested baggage label. + /// - key: The requested baggage key. /// - type: The expected type of the baggage value. - /// - Returns: The decoded baggage value, or nil if the label doesn't match. + /// - Returns: The decoded baggage value, or nil if the key doesn't match. /// - Throws: A `DecodingError` if decoding fails. public func baggage(forKey key: String, type: Value.Type = Value.self) throws -> Value? where Value: Decodable { guard case let .baggage(messageKey, baggage) = self, messageKey == key else { diff --git a/DatadogInternal/Sources/Models/CrashReporting/BacktraceReport.swift b/DatadogInternal/Sources/Models/CrashReporting/BacktraceReport.swift new file mode 100644 index 0000000000..2fb0730ef5 --- /dev/null +++ b/DatadogInternal/Sources/Models/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/Models/CrashReporting/BinaryImage.swift b/DatadogInternal/Sources/Models/CrashReporting/BinaryImage.swift new file mode 100644 index 0000000000..14b31ba2b7 --- /dev/null +++ b/DatadogInternal/Sources/Models/CrashReporting/BinaryImage.swift @@ -0,0 +1,44 @@ +/* + * 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 + +/// 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" + } +} diff --git a/DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift b/DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift new file mode 100644 index 0000000000..2b4fa13a62 --- /dev/null +++ b/DatadogInternal/Sources/Models/CrashReporting/DDCrashReport.swift @@ -0,0 +1,101 @@ +/* + * 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. +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 { + /// 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 in `threads` was truncated due to stack trace 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/Models/CrashReporting/DDThread.swift b/DatadogInternal/Sources/Models/CrashReporting/DDThread.swift new file mode 100644 index 0000000000..e7f187fa3b --- /dev/null +++ b/DatadogInternal/Sources/Models/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" + } +} diff --git a/DatadogInternal/Sources/Models/WebViewTracking/WebViewMessage.swift b/DatadogInternal/Sources/Models/WebViewTracking/WebViewMessage.swift new file mode 100644 index 0000000000..7ba9ab1cef --- /dev/null +++ b/DatadogInternal/Sources/Models/WebViewTracking/WebViewMessage.swift @@ -0,0 +1,73 @@ +/* + * 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 web-view message is transmitted by the `DatadogWebViewTracking` module +/// on the message-bus. +/// +/// Such message is decoded from Browser SDK events sent over the JS bridge. +public enum WebViewMessage { + /// The Browser event types that can be transmitted over the bridge. + public enum EventType: String, Decodable { + case log + case rum + case view + case action + case resource + case error + case longTask = "long_task" + case record + } + + /// Raw event dictionary. + public typealias Event = [String: Any] + + public struct View: Decodable { + public let id: String + } + + /// A browser log event. + case log(Event) + /// A browser rum event. + case rum(Event) + /// A browser session-replay record. + case record(Event, View) +} + +extension WebViewMessage: Decodable { + enum CodingKeys: CodingKey { + case eventType + case event + case view + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let eventType = try container.decode(EventType.self, forKey: .eventType) + let event = try container.decode(AnyDecodable.self, forKey: .event) + + guard let event = event.value as? Event else { + throw DecodingError.typeMismatch( + Event.self, + DecodingError.Context( + codingPath: [CodingKeys.event], + debugDescription: "The Browser Record event is not a dictionary" + ) + ) + } + + switch eventType { + case .log: + self = .log(event) + case .rum, .view, .action, .resource, .error, .longTask: + self = .rum(event) + case .record: + let view = try container.decode(View.self, forKey: .view) + self = .record(event, view) + } + } +} diff --git a/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift b/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift index 28f7d97d8d..3b8e451db0 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift @@ -92,7 +92,7 @@ internal final class NetworkInstrumentationFeature: DatadogFeature { interceptDidFinishCollecting: { [weak self] session, task, metrics in self?.task(task, didFinishCollecting: metrics) - if #available(iOS 15, tvOS 15, *) { + if #available(iOS 15, tvOS 15, *), !task.dd.hasCompletion { // iOS 15 and above, didCompleteWithError is not called hence we use task state to detect task completion // while prior to iOS 15, task state doesn't change to completed hence we use didCompleteWithError to detect task completion self?.task(task, didCompleteWithError: task.error) @@ -113,6 +113,8 @@ internal final class NetworkInstrumentationFeature: DatadogFeature { try swizzler.swizzle( interceptCompletionHandler: { [weak self] task, _, error in self?.task(task, didCompleteWithError: error) + }, didReceive: { [weak self] task, data in + self?.task(task, didReceive: data) } ) } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/DatadogURLSessionDelegate.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/DatadogURLSessionDelegate.swift index 1462812d0b..ac1e33c77f 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/DatadogURLSessionDelegate.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/DatadogURLSessionDelegate.swift @@ -139,6 +139,7 @@ open class DatadogURLSessionDelegate: NSObject, URLSessionDataDelegate { try swizzler.swizzle( interceptCompletionHandler: { [weak self] task, _, error in self?.interceptor?.task(task, didCompleteWithError: error) + }, didReceive: { _, _ in } ) } catch { diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/NetworkInstrumentationSwizzler.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/NetworkInstrumentationSwizzler.swift index f694655db5..8c6c9d6dbf 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/NetworkInstrumentationSwizzler.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/NetworkInstrumentationSwizzler.swift @@ -23,9 +23,13 @@ internal final class NetworkInstrumentationSwizzler { /// Swizzles `URLSession.dataTask(with:completionHandler:)` methods (with `URL` and `URLRequest`). func swizzle( - interceptCompletionHandler: @escaping (URLSessionTask, Data?, Error?) -> Void + interceptCompletionHandler: @escaping (URLSessionTask, Data?, Error?) -> Void, + didReceive: @escaping (URLSessionTask, Data) -> Void ) throws { - try urlSessionSwizzler.swizzle(interceptCompletionHandler: interceptCompletionHandler) + try urlSessionSwizzler.swizzle( + interceptCompletionHandler: interceptCompletionHandler, + didReceive: didReceive + ) } /// Swizzles `URLSessionTask.resume()` method. diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionSwizzler.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionSwizzler.swift index 2143952fee..5e9450fa8e 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionSwizzler.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionSwizzler.swift @@ -18,19 +18,26 @@ internal final class URLSessionSwizzler { /// Swizzles `URLSession.dataTask(with:completionHandler:)` methods (with `URL` and `URLRequest`). func swizzle( - interceptCompletionHandler: @escaping (URLSessionTask, Data?, Error?) -> Void + interceptCompletionHandler: @escaping (URLSessionTask, Data?, Error?) -> Void, + didReceive: @escaping (URLSessionTask, Data) -> Void ) throws { lock.lock() defer { lock.unlock() } dataTaskURLRequestCompletionHandler = try DataTaskURLRequestCompletionHandler.build() - dataTaskURLRequestCompletionHandler?.swizzle(interceptCompletion: interceptCompletionHandler) + dataTaskURLRequestCompletionHandler?.swizzle( + interceptCompletion: interceptCompletionHandler, + didReceive: didReceive + ) if #available(iOS 13.0, *) { // Prior to iOS 13.0 the `URLSession.dataTask(with:url, completionHandler:handler)` makes an internal // call to `URLSession.dataTask(with:request, completionHandler:handler)`. To avoid duplicated call // to the callback, we don't apply below swizzling prior to iOS 13. dataTaskURLCompletionHandler = try DataTaskURLCompletionHandler.build() - dataTaskURLCompletionHandler?.swizzle(interceptCompletion: interceptCompletionHandler) + dataTaskURLCompletionHandler?.swizzle( + interceptCompletion: interceptCompletionHandler, + didReceive: didReceive + ) } } @@ -71,7 +78,8 @@ internal final class URLSessionSwizzler { } func swizzle( - interceptCompletion: @escaping (URLSessionTask, Data?, Error?) -> Void + interceptCompletion: @escaping (URLSessionTask, Data?, Error?) -> Void, + didReceive: @escaping (URLSessionTask, Data) -> Void ) { typealias Signature = @convention(block) (URLSession, URLRequest, CompletionHandler?) -> URLSessionDataTask swizzle(method) { previousImplementation -> Signature in @@ -87,13 +95,18 @@ internal final class URLSessionSwizzler { var _task: URLSessionDataTask? let task = previousImplementation(session, Self.selector, request) { data, response, error in - completionHandler(data, response, error) + if let task = _task, let data = data { + didReceive(task, data) + } if let task = _task { // sanity check, should always succeed interceptCompletion(task, data, error) } + + completionHandler(data, response, error) } _task = task + _task?.dd.hasCompletion = true return task } } @@ -121,7 +134,8 @@ internal final class URLSessionSwizzler { } func swizzle( - interceptCompletion: @escaping (URLSessionTask, Data?, Error?) -> Void + interceptCompletion: @escaping (URLSessionTask, Data?, Error?) -> Void, + didReceive: @escaping (URLSessionTask, Data) -> Void ) { typealias Signature = @convention(block) (URLSession, URL, CompletionHandler?) -> URLSessionDataTask swizzle(method) { previousImplementation -> Signature in @@ -137,6 +151,10 @@ internal final class URLSessionSwizzler { var _task: URLSessionDataTask? let task = previousImplementation(session, Self.selector, url) { data, response, error in + if let task = _task, let data = data { + didReceive(task, data) + } + completionHandler(data, response, error) if let task = _task { // sanity check, should always succeed @@ -144,6 +162,7 @@ internal final class URLSessionSwizzler { } } _task = task + _task?.dd.hasCompletion = true return task } } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift index 6c3f19035b..da1a44f42c 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift @@ -33,4 +33,20 @@ extension DatadogExtension where ExtendedType: URLSessionTask { return session.delegate } + + var hasCompletion: Bool { + get { + let value = objc_getAssociatedObject(type, &hasCompletionKey) as? Bool + return value == true + } + set { + if newValue { + objc_setAssociatedObject(type, &hasCompletionKey, true, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } else { + objc_setAssociatedObject(type, &hasCompletionKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } } + +private var hasCompletionKey: Void? diff --git a/DatadogInternal/Sources/Storage/PerformancePresetOverride.swift b/DatadogInternal/Sources/Storage/PerformancePresetOverride.swift index 2a746ddc54..3d0e965347 100644 --- a/DatadogInternal/Sources/Storage/PerformancePresetOverride.swift +++ b/DatadogInternal/Sources/Storage/PerformancePresetOverride.swift @@ -52,8 +52,8 @@ public struct PerformancePresetOverride { public init( maxFileSize: UInt64?, maxObjectSize: UInt64?, - meanFileAge: TimeInterval?, - uploadDelay: (initial: TimeInterval, range: Range, changeRate: Double)? + meanFileAge: TimeInterval? = nil, + uploadDelay: (initial: TimeInterval, range: Range, changeRate: Double)? = nil ) { self.maxFileSize = maxFileSize self.maxObjectSize = maxObjectSize diff --git a/DatadogInternal/Sources/Telemetry/Telemetry.swift b/DatadogInternal/Sources/Telemetry/Telemetry.swift index 96d39ecceb..a43f2c8e7a 100644 --- a/DatadogInternal/Sources/Telemetry/Telemetry.swift +++ b/DatadogInternal/Sources/Telemetry/Telemetry.swift @@ -10,6 +10,7 @@ public struct ConfigurationTelemetry: Equatable { public let actionNameAttribute: String? public let allowFallbackToLocalStorage: Bool? public let allowUntrustedEvents: Bool? + public let appHangThreshold: Int64? public let backgroundTasksEnabled: Bool? public let batchProcessingLevel: Int64? public let batchSize: Int64? @@ -174,12 +175,13 @@ extension Telemetry { /// Report a Configuration Telemetry. /// - /// The configuration can be partial, the telemtry should support accumulation of - /// configuration for lazy initialization of the SDK. + /// The configuration can be partial, the telemetry supports accumulation of + /// configuration for lazy initialization of different SDK features. public func configuration( actionNameAttribute: String? = nil, allowFallbackToLocalStorage: Bool? = nil, allowUntrustedEvents: Bool? = nil, + appHangThreshold: Int64? = nil, backgroundTasksEnabled: Bool? = nil, batchProcessingLevel: Int64? = nil, batchSize: Int64? = nil, @@ -231,6 +233,7 @@ extension Telemetry { actionNameAttribute: actionNameAttribute, allowFallbackToLocalStorage: allowFallbackToLocalStorage, allowUntrustedEvents: allowUntrustedEvents, + appHangThreshold: appHangThreshold, backgroundTasksEnabled: backgroundTasksEnabled, batchProcessingLevel: batchProcessingLevel, batchSize: batchSize, @@ -337,6 +340,7 @@ extension ConfigurationTelemetry { actionNameAttribute: other.actionNameAttribute ?? actionNameAttribute, allowFallbackToLocalStorage: other.allowFallbackToLocalStorage ?? allowFallbackToLocalStorage, allowUntrustedEvents: other.allowUntrustedEvents ?? allowUntrustedEvents, + appHangThreshold: other.appHangThreshold ?? appHangThreshold, backgroundTasksEnabled: other.backgroundTasksEnabled ?? backgroundTasksEnabled, batchProcessingLevel: other.batchProcessingLevel ?? batchProcessingLevel, batchSize: other.batchSize ?? batchSize, diff --git a/DatadogInternal/Sources/Utils/DDError.swift b/DatadogInternal/Sources/Utils/DDError.swift index 3cc9153add..1d4b35ffd1 100644 --- a/DatadogInternal/Sources/Utils/DDError.swift +++ b/DatadogInternal/Sources/Utils/DDError.swift @@ -24,11 +24,13 @@ public struct DDError: Equatable, Codable, PassthroughAnyCodable { public let type: String public let message: String public let stack: String + public let sourceType: String - public init(type: String, message: String, stack: String) { + public init(type: String, message: String, stack: String, sourceType: String = "ios") { self.type = type self.message = message self.stack = stack + self.sourceType = sourceType } } @@ -49,6 +51,8 @@ extension DDError { self.message = "\(swiftError)" self.stack = "\(swiftError)" } + + self.sourceType = "ios" } } diff --git a/DatadogInternal/Tests/Models/WebViewMessageTests.swift b/DatadogInternal/Tests/Models/WebViewMessageTests.swift new file mode 100644 index 0000000000..3731846afe --- /dev/null +++ b/DatadogInternal/Tests/Models/WebViewMessageTests.swift @@ -0,0 +1,49 @@ +/* + * 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 TestUtilities + +@testable import DatadogInternal + +class WebViewMessageTests: XCTestCase { + let decoder = JSONDecoder() + + func testParsingCorruptedEvent() throws { + let invalidJSON = "(^#$@#)".utf8Data + + XCTAssertThrowsError(try decoder.decode(WebViewMessage.self, from: invalidJSON)) { error in + XCTAssert(error is DecodingError) + } + } + + func testParsingInvalidEvent() { + let messageWithNoEventType = """ + { + "event": { + "date": 1635932927012, + "error": { + "origin": "console" + } + } + } + """.utf8Data + + let messageWithNoEvent = """ + { + "eventType": "log" + } + """.utf8Data + + XCTAssertThrowsError(try decoder.decode(WebViewMessage.self, from: messageWithNoEventType)) { error in + XCTAssert(error is DecodingError) + } + + XCTAssertThrowsError(try decoder.decode(WebViewMessage.self, from: messageWithNoEvent)) { error in + XCTAssert(error is DecodingError) + } + } +} diff --git a/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift index 7b45b3fc9a..1b2ac33c5b 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift @@ -628,7 +628,7 @@ class NetworkInstrumentationFeatureTests: XCTestCase { // Then waitForExpectations(timeout: 5, handler: nil) - _ = server.waitAndReturnRequests(count: 1) + _ = server.waitAndReturnRequests(count: 2) // release the delegate to unswizzle session.finishTasksAndInvalidate() @@ -677,7 +677,7 @@ class NetworkInstrumentationFeatureTests: XCTestCase { // Then waitForExpectations(timeout: 5, handler: nil) - _ = server.waitAndReturnRequests(count: 1) + _ = server.waitAndReturnRequests(count: 2) // release the delegate to unswizzle session.finishTasksAndInvalidate() diff --git a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionSwizzlerTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionSwizzlerTests.swift index 75671d8f53..cfb515f7ae 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionSwizzlerTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionSwizzlerTests.swift @@ -10,6 +10,9 @@ import XCTest class URLSessionSwizzlerTests: XCTestCase { func testSwizzling_dataTaskWithCompletion() throws { + let didReceive = expectation(description: "didReceive") + didReceive.expectedFulfillmentCount = 2 + let didInterceptCompletion = expectation(description: "interceptCompletion") didInterceptCompletion.expectedFulfillmentCount = 2 @@ -18,6 +21,8 @@ class URLSessionSwizzlerTests: XCTestCase { try swizzler.swizzle( interceptCompletionHandler: { _, _, _ in didInterceptCompletion.fulfill() + }, didReceive: { _, _ in + didReceive.fulfill() } ) @@ -30,6 +35,6 @@ class URLSessionSwizzlerTests: XCTestCase { session.dataTask(with: url) { _, _, _ in }.resume() // not intercepted session.dataTask(with: URLRequest(url: url)) { _, _, _ in }.resume() // not intercepted - wait(for: [didInterceptCompletion], timeout: 5) + wait(for: [didReceive, didInterceptCompletion], timeout: 5) } } diff --git a/DatadogInternal/Tests/Telemetry/TelemetryMocks.swift b/DatadogInternal/Tests/Telemetry/TelemetryMocks.swift index dee147c04a..550f776c4e 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryMocks.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryMocks.swift @@ -14,6 +14,7 @@ extension ConfigurationTelemetry { actionNameAttribute: .mockRandom(), allowFallbackToLocalStorage: .mockRandom(), allowUntrustedEvents: .mockRandom(), + appHangThreshold: .mockRandom(), backgroundTasksEnabled: .mockRandom(), batchProcessingLevel: .mockRandom(), batchSize: .mockRandom(), diff --git a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift index eab8b085b4..3bdfa2300c 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift @@ -201,6 +201,7 @@ class TelemetryTest: Telemetry { actionNameAttribute: configuration.actionNameAttribute, allowFallbackToLocalStorage: configuration.allowFallbackToLocalStorage, allowUntrustedEvents: configuration.allowUntrustedEvents, + appHangThreshold: configuration.appHangThreshold, backgroundTasksEnabled: configuration.backgroundTasksEnabled, batchProcessingLevel: configuration.batchProcessingLevel, batchSize: configuration.batchSize, diff --git a/DatadogLogs.podspec b/DatadogLogs.podspec index 9bedf1981d..61d4db1412 100644 --- a/DatadogLogs.podspec +++ b/DatadogLogs.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogLogs" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Datadog Logs Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogLogs/Sources/Feature/Baggages.swift b/DatadogLogs/Sources/Feature/Baggages.swift index a4e41c7da1..cdcaf9b160 100644 --- a/DatadogLogs/Sources/Feature/Baggages.swift +++ b/DatadogLogs/Sources/Feature/Baggages.swift @@ -22,6 +22,33 @@ internal struct ErrorMessage: Encodable { let attributes: AnyEncodable } +internal struct GlobalLogAttributes: Codable { + static let key = "global-log-attributes" + + let attributes: [AttributeKey: AttributeValue] + + init(attributes: [AttributeKey: AttributeValue]) { + self.attributes = attributes + } + + func encode(to encoder: Encoder) throws { + var dynamicContainer = encoder.container(keyedBy: DynamicCodingKey.self) + try attributes.forEach { + let key = DynamicCodingKey($0) + try dynamicContainer.encode(AnyEncodable($1), forKey: key) + } + } + + init(from decoder: Decoder) throws { + // Decode other properties into [String: Codable] dictionary: + let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self) + self.attributes = try dynamicContainer.allKeys + .reduce(into: [:]) { + $0[$1.stringValue] = try dynamicContainer.decode(AnyCodable.self, forKey: $1) + } + } +} + /// The Span context received from `DatadogCore`. internal struct SpanContext: Decodable { static let key = "span_context" diff --git a/DatadogLogs/Sources/Feature/LogsFeature.swift b/DatadogLogs/Sources/Feature/LogsFeature.swift index 9d72ca0abb..9eb782051b 100644 --- a/DatadogLogs/Sources/Feature/LogsFeature.swift +++ b/DatadogLogs/Sources/Feature/LogsFeature.swift @@ -16,6 +16,9 @@ internal struct LogsFeature: DatadogRemoteFeature { let logEventMapper: LogEventMapper? + @ReadWriteLock + private var attributes: [String: Encodable] = [:] + /// Time provider. let dateProvider: DateProvider @@ -51,4 +54,16 @@ internal struct LogsFeature: DatadogRemoteFeature { self.messageReceiver = messageReceiver self.dateProvider = dateProvider } + + internal func addAttribute(forKey key: AttributeKey, value: AttributeValue) { + _attributes.mutate { $0[key] = value } + } + + internal func removeAttribute(forKey key: AttributeKey) { + _attributes.mutate { $0.removeValue(forKey: key) } + } + + internal func getAttributes() -> [String: Encodable] { + return attributes + } } diff --git a/DatadogLogs/Sources/Feature/MessageReceivers.swift b/DatadogLogs/Sources/Feature/MessageReceivers.swift index daba70b374..64abca605c 100644 --- a/DatadogLogs/Sources/Feature/MessageReceivers.swift +++ b/DatadogLogs/Sources/Feature/MessageReceivers.swift @@ -14,9 +14,6 @@ internal enum LoggingMessageKeys { /// The key references a crash message. static let crash = "crash" - - /// The key references a browser log message. - static let browserLog = "browser-log" } /// Receiver to consume a Log message @@ -171,8 +168,24 @@ internal struct CrashLogReceiver: FeatureMessageReceiver { let view: View } + struct GlobalLogAttributes: Decodable { + let attributes: [AttributeKey: AttributeValue] + + init(from decoder: Decoder) throws { + // Decode other properties into [String: Codable] dictionary: + let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self) + self.attributes = try dynamicContainer.allKeys + .reduce(into: [:]) { + $0[$1.stringValue] = try dynamicContainer.decode(AnyCodable.self, forKey: $1) + } + } + } + /// The last RUM view in crashed app process. let lastRUMViewEvent: PartialRUMViewEvent? + + /// Last global log attributes + let lastLogAttributes: GlobalLogAttributes? } /// Time provider. @@ -197,14 +210,15 @@ internal struct CrashLogReceiver: FeatureMessageReceiver { return false } - private func send(report: CrashReport, with context: CrashContext, to core: DatadogCoreProtocol) -> Bool { + private func send(report: CrashReport, with crashContext: CrashContext, to core: DatadogCoreProtocol) -> Bool { // The `report.crashDate` uses system `Date` collected at the moment of crash, so we need to adjust it // to the server time before processing. Following use of the current correction is not ideal, but this is the best // approximation we can get. let date = (report.date ?? dateProvider.now) - .addingTimeInterval(context.serverTimeOffset) + .addingTimeInterval(crashContext.serverTimeOffset) var errorAttributes: [AttributeKey: AttributeValue] = [:] + // Set crash attributes for the error errorAttributes[DDError.threads] = report.threads errorAttributes[DDError.binaryImages] = report.binaryImages @@ -212,56 +226,64 @@ internal struct CrashLogReceiver: FeatureMessageReceiver { errorAttributes[DDError.wasTruncated] = report.wasTruncated // Set RUM context if available (so emergency error is linked to the RUM session in Datadog app) - errorAttributes[LogEvent.Attributes.RUM.applicationID] = context.lastRUMViewEvent?.application.id - errorAttributes[LogEvent.Attributes.RUM.sessionID] = context.lastRUMViewEvent?.session.id - errorAttributes[LogEvent.Attributes.RUM.viewID] = context.lastRUMViewEvent?.view.id - - let user = context.userInfo - let deviceInfo = context.device - - let event = LogEvent( - date: date, - status: .emergency, - message: report.message, - error: .init( - kind: report.type, - message: report.message, - stack: report.stack - ), - serviceName: context.service, - environment: context.env, - loggerName: "crash-reporter", - loggerVersion: context.sdkVersion, - threadName: nil, - applicationVersion: context.version, - applicationBuildNumber: context.buildNumber, - buildId: nil, - dd: .init( - device: .init(architecture: deviceInfo.architecture) - ), - os: .init( - name: context.device.osName, - version: context.device.osVersion, - build: context.device.osBuildNumber - ), - userInfo: .init( - id: user?.id, - name: user?.name, - email: user?.email, - extraInfo: user?.extraInfo ?? [:] - ), - networkConnectionInfo: context.networkConnectionInfo, - mobileCarrierInfo: context.carrierInfo, - attributes: .init( - userAttributes: [:], - internalAttributes: errorAttributes - ), - tags: nil - ) + errorAttributes[LogEvent.Attributes.RUM.applicationID] = crashContext.lastRUMViewEvent?.application.id + errorAttributes[LogEvent.Attributes.RUM.sessionID] = crashContext.lastRUMViewEvent?.session.id + errorAttributes[LogEvent.Attributes.RUM.viewID] = crashContext.lastRUMViewEvent?.view.id + + let user = crashContext.userInfo + let deviceInfo = crashContext.device + let userAttributes = crashContext.lastLogAttributes?.attributes // crash reporting is considering the user consent from previous session, if an event reached // the message bus it means that consent was granted and we can safely bypass current consent. - core.scope(for: LogsFeature.name)?.eventWriteContext(bypassConsent: true, forceNewBatch: false) { _, writer in + core.scope(for: LogsFeature.name)?.eventWriteContext(bypassConsent: true) { context, writer in + let event = LogEvent( + date: date, + status: .emergency, + message: report.message, + error: .init( + kind: report.type, + message: report.message, + stack: report.stack, + sourceType: context.nativeSourceOverride ?? "ios" + ), + serviceName: crashContext.service, + environment: crashContext.env, + loggerName: "crash-reporter", + loggerVersion: crashContext.sdkVersion, + threadName: nil, + applicationVersion: crashContext.version, + applicationBuildNumber: crashContext.buildNumber, + buildId: nil, + variant: context.variant, + dd: .init( + device: .init( + brand: deviceInfo.brand, + name: deviceInfo.name, + model: deviceInfo.model, + architecture: deviceInfo.architecture + ) + ), + os: .init( + name: crashContext.device.osName, + version: crashContext.device.osVersion, + build: crashContext.device.osBuildNumber + ), + userInfo: .init( + id: user?.id, + name: user?.name, + email: user?.email, + extraInfo: user?.extraInfo ?? [:] + ), + networkConnectionInfo: crashContext.networkConnectionInfo, + mobileCarrierInfo: crashContext.carrierInfo, + attributes: .init( + userAttributes: userAttributes ?? [:], + internalAttributes: errorAttributes + ), + tags: nil + ) + writer.write(value: event) } @@ -277,56 +299,45 @@ internal struct WebViewLogReceiver: FeatureMessageReceiver { /// - message: The Feature message /// - core: The core from which the message is transmitted. func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - do { - guard case let .baggage(label, baggage) = message, label == LoggingMessageKeys.browserLog else { - return false - } + guard case var .webview(.log(event)) = message else { + return false + } - guard var event = try baggage.encode() as? [String: Any?] else { - throw InternalError(description: "event is not a dictionary") - } + let versionKey = LogEventEncoder.StaticCodingKeys.applicationVersion.rawValue + let envKey = LogEventEncoder.StaticCodingKeys.environment.rawValue + let tagsKey = LogEventEncoder.StaticCodingKeys.tags.rawValue + let dateKey = LogEventEncoder.StaticCodingKeys.date.rawValue - let versionKey = LogEventEncoder.StaticCodingKeys.applicationVersion.rawValue - let envKey = LogEventEncoder.StaticCodingKeys.environment.rawValue - let tagsKey = LogEventEncoder.StaticCodingKeys.tags.rawValue - let dateKey = LogEventEncoder.StaticCodingKeys.date.rawValue + core.scope(for: LogsFeature.name)?.eventWriteContext { context, writer in + let ddTags = "\(versionKey):\(context.version),\(envKey):\(context.env)" - core.scope(for: LogsFeature.name)?.eventWriteContext { context, writer in - let ddTags = "\(versionKey):\(context.version),\(envKey):\(context.env)" + if let tags = event[tagsKey] as? String, !tags.isEmpty { + event[tagsKey] = "\(ddTags),\(tags)" + } else { + event[tagsKey] = ddTags + } - if let tags = event[tagsKey] as? String, !tags.isEmpty { - event[tagsKey] = "\(ddTags),\(tags)" - } else { - event[tagsKey] = ddTags - } + if let timestampInMs = event[dateKey] as? Int { + let serverTimeOffsetInMs = context.serverTimeOffset.toInt64Milliseconds + let correctedTimestamp = Int64(timestampInMs) + serverTimeOffsetInMs + event[dateKey] = correctedTimestamp + } - if let timestampInMs = event[dateKey] as? Int64 { - let serverTimeOffsetInMs = context.serverTimeOffset.toInt64Milliseconds - let correctedTimestamp = Int64(timestampInMs) + serverTimeOffsetInMs - event[dateKey] = correctedTimestamp + if let rum = context.baggages[RUMContext.key] { + do { + let rum = try rum.decode(type: RUMContext.self) + event[LogEvent.Attributes.RUM.applicationID] = rum.applicationID + event[LogEvent.Attributes.RUM.sessionID] = rum.sessionID + event[LogEvent.Attributes.RUM.viewID] = rum.viewID + event[LogEvent.Attributes.RUM.actionID] = rum.userActionID + } catch { + core.telemetry.error("Fails to decode RUM context from Logs in `WebViewLogReceiver`", error: error) } - - if let rum = context.baggages[RUMContext.key] { - do { - let rum = try rum.decode(type: RUMContext.self) - event[LogEvent.Attributes.RUM.applicationID] = rum.applicationID - event[LogEvent.Attributes.RUM.sessionID] = rum.sessionID - event[LogEvent.Attributes.RUM.viewID] = rum.viewID - event[LogEvent.Attributes.RUM.actionID] = rum.userActionID - } catch { - core.telemetry.error("Fails to decode RUM context from Logs in `WebViewLogReceiver`", error: error) - } - } - - writer.write(value: AnyEncodable(event)) } - return true - } catch { - core.telemetry - .error("Failed to decode browser log in `LogMessageReceiver`", error: error) + writer.write(value: AnyEncodable(event)) } - return false + return true } } diff --git a/DatadogLogs/Sources/Log/LogEventBuilder.swift b/DatadogLogs/Sources/Log/LogEventBuilder.swift index 0c47dbac2e..df5cf43e93 100644 --- a/DatadogLogs/Sources/Log/LogEventBuilder.swift +++ b/DatadogLogs/Sources/Log/LogEventBuilder.swift @@ -61,7 +61,8 @@ internal struct LogEventBuilder { .init( kind: $0.type, message: $0.message, - stack: $0.stack + stack: $0.stack, + sourceType: $0.sourceType ) }, serviceName: service, @@ -72,8 +73,14 @@ internal struct LogEventBuilder { applicationVersion: context.version, applicationBuildNumber: context.buildNumber, buildId: context.buildId, + variant: context.variant, dd: LogEvent.Dd( - device: LogEvent.DeviceInfo(architecture: context.device.architecture) + device: LogEvent.DeviceInfo( + brand: context.device.brand, + name: context.device.name, + model: context.device.model, + architecture: context.device.architecture + ) ), os: .init( name: context.device.osName, diff --git a/DatadogLogs/Sources/Log/LogEventEncoder.swift b/DatadogLogs/Sources/Log/LogEventEncoder.swift index 9ed13f106a..ae36897ea2 100644 --- a/DatadogLogs/Sources/Log/LogEventEncoder.swift +++ b/DatadogLogs/Sources/Log/LogEventEncoder.swift @@ -71,10 +71,21 @@ public struct LogEvent: Encodable { public var message: String? /// The Log error stack public var stack: String? + /// The Log error source_type. Used by cross platform SDKs + public var sourceType: String = "ios" } /// Device information. public struct DeviceInfo: Codable { + /// Device manufacturer name. Always'Apple' + public let brand: String + + /// Device marketing name, e.g. "iPhone", "iPad", "iPod touch". + public let name: String + + /// Device model name, e.g. "iPhone10,1", "iPhone13,2". + public let model: String + /// The architecture of the device public let architecture: String } @@ -119,6 +130,8 @@ public struct LogEvent: Encodable { public let applicationBuildNumber: String /// The id of the current build (used for some cross platform frameworks) public let buildId: String? + /// The variant of the current build (used in some cross platform frameworks) + public let variant: String? /// Datadog specific attributes public let dd: Dd /// The associated log error @@ -157,6 +170,7 @@ internal struct LogEventEncoder { case errorKind = "error.kind" case errorMessage = "error.message" case errorStack = "error.stack" + case errorSourceType = "error.source_type" // MARK: - Application info @@ -218,6 +232,7 @@ internal struct LogEventEncoder { try container.encode(someError.kind, forKey: .errorKind) try container.encode(someError.message, forKey: .errorMessage) try container.encode(someError.stack, forKey: .errorStack) + try container.encode(someError.sourceType, forKey: .errorSourceType) } // Encode logger info @@ -290,6 +305,9 @@ internal struct LogEventEncoder { var tags = log.tags ?? [] tags.append("env:\(log.environment)") // include default env tag tags.append("version:\(log.applicationVersion)") // include default version tag + if let variant = log.variant { + tags.append("variant:\(variant)") + } let tagsString = tags.joined(separator: ",") try container.encode(tagsString, forKey: .tags) } diff --git a/DatadogLogs/Sources/Logs.swift b/DatadogLogs/Sources/Logs.swift index 5025a16dc2..25252dd7b2 100644 --- a/DatadogLogs/Sources/Logs.swift +++ b/DatadogLogs/Sources/Logs.swift @@ -73,6 +73,43 @@ public enum Logs { consolePrint("\(error)", .error) } } + + /// Adds a custom attribute to all future logs sent by any logger created from the provided Core. + /// - Parameters: + /// - key: the attribute key. See `AttributeKey` documentation for information on nesting attributes with dot `.` syntax. + /// - value: the attribute value that conforms to `Encodable`. See `AttributeValue` documentation + /// for information on nested encoding containers limitation. + /// - core: the `DatadogCoreProtocol` to add the attribute to. + public static func addAttribute(forKey key: AttributeKey, value: AttributeValue, in core: DatadogCoreProtocol = CoreRegistry.default) { + guard let feature = core.get(feature: LogsFeature.self) else { + return + } + feature.addAttribute(forKey: key, value: value) + sendAttributesChanged(for: feature, in: core) + } + + /// Removes the custom attribute from all future logs sent any logger created from the provided Core. + /// + /// Previous logs won't lose this attribute if sent prior to this call. + /// - Parameters: + /// - key: the key of an attribute that will be removed. + /// - core: the `DatadogCoreProtocol` to remove the attribute from. + public static func removeAttribute(forKey key: AttributeKey, in core: DatadogCoreProtocol = CoreRegistry.default) { + guard let feature = core.get(feature: LogsFeature.self) else { + return + } + feature.removeAttribute(forKey: key) + sendAttributesChanged(for: feature, in: core) + } + + private static func sendAttributesChanged(for feature: LogsFeature, in core: DatadogCoreProtocol) { + core.send( + message: .baggage( + key: GlobalLogAttributes.key, + value: GlobalLogAttributes(attributes: feature.getAttributes()) + ) + ) + } } extension Logs.Configuration: InternalExtended { } diff --git a/DatadogLogs/Sources/RemoteLogger.swift b/DatadogLogs/Sources/RemoteLogger.swift index f311383a3b..2328ad9498 100644 --- a/DatadogLogs/Sources/RemoteLogger.swift +++ b/DatadogLogs/Sources/RemoteLogger.swift @@ -100,6 +100,8 @@ internal final class RemoteLogger: LoggerProtocol { return } + let globalAttributes = self.core.get(feature: LogsFeature.self)?.getAttributes() + // on user thread: let date = dateProvider.now let threadName = Thread.current.dd.name @@ -110,6 +112,12 @@ internal final class RemoteLogger: LoggerProtocol { let isCrash = logAttributes?.removeValue(forKey: CrossPlatformAttributes.errorLogIsCrash) as? Bool ?? false let userAttributes = self.attributes .merging(logAttributes ?? [:]) { $1 } // prefer message attributes + let combinedAttributes: [String: any Encodable] + if let globalAttributes = globalAttributes { + combinedAttributes = globalAttributes.merging(userAttributes) { $1 } + } else { + combinedAttributes = userAttributes + } // SDK context must be requested on the user thread to ensure that it provides values // that are up-to-date for the caller. @@ -155,7 +163,7 @@ internal final class RemoteLogger: LoggerProtocol { message: message, error: error, attributes: .init( - userAttributes: userAttributes, + userAttributes: combinedAttributes, internalAttributes: internalAttributes ), tags: tags, @@ -175,7 +183,7 @@ internal final class RemoteLogger: LoggerProtocol { message: log.error?.message ?? log.message, type: log.error?.kind, stack: log.error?.stack, - attributes: .init(userAttributes) + attributes: .init(combinedAttributes) ) ) ) @@ -187,12 +195,16 @@ internal final class RemoteLogger: LoggerProtocol { extension RemoteLogger: InternalLoggerProtocol { func log(level: LogLevel, message: String, errorKind: String?, errorMessage: String?, stackTrace: String?, attributes: [String: Encodable]?) { var ddError: DDError? + // Find and remove source_type if it's in the attributes + var logAttributes = attributes + let sourceType = logAttributes?.removeValue(forKey: CrossPlatformAttributes.errorSourceType) as? String + if errorKind != nil || errorMessage != nil || stackTrace != nil { // Cross platform frameworks don't necessarilly send all values for errors. Send empty strings // for any values that are empty. - ddError = DDError(type: errorKind ?? "", message: errorMessage ?? "", stack: stackTrace ?? "") + ddError = DDError(type: errorKind ?? "", message: errorMessage ?? "", stack: stackTrace ?? "", sourceType: sourceType ?? "ios") } - internalLog(level: level, message: message, error: ddError, attributes: attributes) + internalLog(level: level, message: message, error: ddError, attributes: logAttributes) } } diff --git a/DatadogLogs/Tests/Log/LogEventBuilderTests.swift b/DatadogLogs/Tests/Log/LogEventBuilderTests.swift index adb2d586bc..4929b6bf54 100644 --- a/DatadogLogs/Tests/Log/LogEventBuilderTests.swift +++ b/DatadogLogs/Tests/Log/LogEventBuilderTests.swift @@ -25,6 +25,8 @@ class LogEventBuilderTests: XCTestCase { let randomOsName: String = .mockRandom() let randomOsVersion: String = .mockRandom() let randomOsBuildNumber: String = .mockRandom() + let randomName: String = .mockRandom() + let randomModel: String = .mockRandom() let randomArchitecture: String = .mockRandom() // Given @@ -46,6 +48,8 @@ class LogEventBuilderTests: XCTestCase { context: .mockWith( serverTimeOffset: 0, device: .mockWith( + name: randomName, + model: randomModel, osName: randomOsName, osVersion: randomOsVersion, osBuildNumber: randomOsBuildNumber, @@ -61,11 +65,15 @@ class LogEventBuilderTests: XCTestCase { XCTAssertEqual(log.error?.kind, randomError.type) XCTAssertEqual(log.error?.message, randomError.message) XCTAssertEqual(log.error?.stack, randomError.stack) + XCTAssertEqual(log.error?.sourceType, "ios") XCTAssertEqual(log.attributes, randomAttributes) XCTAssertEqual(log.tags.map { Set($0) }, randomTags) XCTAssertEqual(log.serviceName, randomService) XCTAssertEqual(log.loggerName, randomLoggerName) XCTAssertEqual(log.threadName, randomThreadName) + XCTAssertEqual(log.dd.device.brand, "Apple") + XCTAssertEqual(log.dd.device.name, randomName) + XCTAssertEqual(log.dd.device.model, randomModel) XCTAssertEqual(log.dd.device.architecture, randomArchitecture) XCTAssertEqual(log.os.name, randomOsName) XCTAssertEqual(log.os.version, randomOsVersion) @@ -89,6 +97,8 @@ class LogEventBuilderTests: XCTestCase { let randomNetworkInfo: NetworkConnectionInfo = .mockRandom() let randomCarrierInfo: CarrierInfo = .mockRandom() let randomServerOffset: TimeInterval = .mockRandom(min: -10, max: 10) + let randomName: String = .mockRandom() + let randomModel: String = .mockRandom() let randomOSVersion: String = .mockRandom() let randomOSBuild: String = .mockRandom() @@ -99,6 +109,8 @@ class LogEventBuilderTests: XCTestCase { sdkVersion: randomSDKVersion, serverTimeOffset: randomServerOffset, device: .mockWith( + name: randomName, + model: randomModel, osVersion: randomOSVersion, osBuildNumber: randomOSBuild ), @@ -139,6 +151,9 @@ class LogEventBuilderTests: XCTestCase { DDAssertDictionariesEqual(log.userInfo.extraInfo, randomUserInfo.extraInfo) XCTAssertEqual(log.networkConnectionInfo, randomNetworkInfo) XCTAssertEqual(log.mobileCarrierInfo, randomCarrierInfo) + XCTAssertEqual(log.dd.device.brand, "Apple") + XCTAssertEqual(log.dd.device.name, randomName) + XCTAssertEqual(log.dd.device.model, randomModel) XCTAssertEqual(log.os.version, randomOSVersion) XCTAssertEqual(log.os.build, randomOSBuild) expectation.fulfill() diff --git a/DatadogLogs/Tests/LogsTests.swift b/DatadogLogs/Tests/LogsTests.swift index 8edbe8a51c..6d9135b41c 100644 --- a/DatadogLogs/Tests/LogsTests.swift +++ b/DatadogLogs/Tests/LogsTests.swift @@ -80,4 +80,82 @@ class LogsTests: XCTestCase { // Then XCTAssertTrue(config._internalEventMapper is LogEventMapperMock) } + + func testLogsAddAttributeForwardedToFeature() throws { + // Given + let core = FeatureRegistrationCoreMock() + let config = Logs.Configuration() + Logs.enable(with: config, in: core) + + // When + let attributeKey: String = .mockRandom() + let attributeValue: String = .mockRandom() + Logs.addAttribute(forKey: attributeKey, value: attributeValue, in: core) + + // Then + let feature = try XCTUnwrap(core.get(feature: LogsFeature.self)) + XCTAssertEqual(feature.getAttributes()[attributeKey] as? String, attributeValue) + } + + func testLogsRemoveAttributeForwardedToFeature() throws { + // Given + let core = FeatureRegistrationCoreMock() + let config = Logs.Configuration() + Logs.enable(with: config, in: core) + let attributeKey: String = .mockRandom() + let attributeValue: String = .mockRandom() + Logs.addAttribute(forKey: attributeKey, value: attributeValue, in: core) + + // When + Logs.removeAttribute(forKey: attributeKey, in: core) + + // Then + let feature = try XCTUnwrap(core.get(feature: LogsFeature.self)) + XCTAssertNil(feature.getAttributes()[attributeKey]) + } + + func testItSendsGlobalLogUpdates_whenAddAttribute() throws { + // Given + let mockMessageReciever = FeatureMessageReceiverMock() + let core = SingleFeatureCoreMock( + messageReceiver: mockMessageReciever + ) + let config = Logs.Configuration() + Logs.enable(with: config, in: core) + + // When + let attributeKey: String = .mockRandom() + let attributeValue: String = .mockRandom() + Logs.addAttribute(forKey: attributeKey, value: attributeValue, in: core) + + // Then + let logMessage: [FeatureMessage] = mockMessageReciever.messages.filter { $0.asBaggage?.key == GlobalLogAttributes.key } + XCTAssertEqual(logMessage.count, 1) + let message = try XCTUnwrap(logMessage.first) + let baggage: GlobalLogAttributes = try XCTUnwrap(message.baggage(forKey: GlobalLogAttributes.key)) + XCTAssertEqual((baggage.attributes[attributeKey] as? AnyCodable)?.value as? String, attributeValue) + } + + func testItSendsGlobalLogUpdates_whenRemovettribute() throws { + // Given + let mockMessageReciever = FeatureMessageReceiverMock() + let core = SingleFeatureCoreMock( + messageReceiver: mockMessageReciever + ) + let config = Logs.Configuration() + Logs.enable(with: config, in: core) + let attributeKey: String = .mockRandom() + let attributeValue: String = .mockRandom() + Logs.addAttribute(forKey: attributeKey, value: attributeValue, in: core) + + // When + Logs.removeAttribute(forKey: attributeKey, in: core) + + // Then + let logMessage: [FeatureMessage] = mockMessageReciever.messages.filter { $0.asBaggage?.key == GlobalLogAttributes.key } + XCTAssertEqual(logMessage.count, 2) + let message = try XCTUnwrap(logMessage.last) + let baggage: GlobalLogAttributes = try XCTUnwrap(message.baggage(forKey: GlobalLogAttributes.key)) + XCTAssertNil(baggage.attributes[attributeKey]) + } } diff --git a/DatadogLogs/Tests/Mocks/LoggingFeatureMocks.swift b/DatadogLogs/Tests/Mocks/LoggingFeatureMocks.swift index 123730f97a..d7c0f9b1ff 100644 --- a/DatadogLogs/Tests/Mocks/LoggingFeatureMocks.swift +++ b/DatadogLogs/Tests/Mocks/LoggingFeatureMocks.swift @@ -118,6 +118,7 @@ extension LogEvent: AnyMockable, RandomMockable { applicationVersion: String = .mockAny(), applicationBuildNumber: String = .mockAny(), buildId: String? = .mockAny(), + variant: String? = .mockAny(), dd: LogEvent.Dd = .mockAny(), os: LogEvent.OperatingSystem = .mockAny(), userInfo: UserInfo = .mockAny(), @@ -139,6 +140,7 @@ extension LogEvent: AnyMockable, RandomMockable { applicationVersion: applicationVersion, applicationBuildNumber: applicationBuildNumber, buildId: nil, + variant: variant, dd: dd, os: os, userInfo: userInfo, @@ -163,6 +165,7 @@ extension LogEvent: AnyMockable, RandomMockable { applicationVersion: .mockRandom(), applicationBuildNumber: .mockRandom(), buildId: .mockRandom(), + variant: .mockRandom(), dd: .mockRandom(), os: .mockRandom(), userInfo: .mockRandom(), @@ -225,12 +228,18 @@ extension LogEvent.Dd: AnyMockable, RandomMockable { extension LogEvent.DeviceInfo: AnyMockable, RandomMockable { public static func mockAny() -> LogEvent.DeviceInfo { return LogEvent.DeviceInfo( + brand: .mockAny(), + name: .mockAny(), + model: .mockAny(), architecture: .mockAny() ) } public static func mockRandom() -> LogEvent.DeviceInfo { return LogEvent.DeviceInfo( + brand: .mockRandom(), + name: .mockRandom(), + model: .mockRandom(), architecture: .mockRandom() ) } diff --git a/DatadogLogs/Tests/RemoteLoggerTests.swift b/DatadogLogs/Tests/RemoteLoggerTests.swift index 5ccd0c643f..b21166d942 100644 --- a/DatadogLogs/Tests/RemoteLoggerTests.swift +++ b/DatadogLogs/Tests/RemoteLoggerTests.swift @@ -14,9 +14,17 @@ private class ErrorMessageReceiverMock: FeatureMessageReceiver { struct ErrorMessage: Decodable { /// The Log error message let message: String + /// The Log error type + let type: String? + /// The Log error stack + let stack: String? + /// The Log error stack + let source: String + /// The Log attributes + let attributes: [String: AnyCodable] } - var errors: [String] = [] + var errors: [ErrorMessage] = [] /// Adds RUM Error with given message and stack to current RUM View. func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { @@ -26,7 +34,7 @@ private class ErrorMessageReceiverMock: FeatureMessageReceiver { return false } - self.errors.append(error.message) + self.errors.append(error) return true } @@ -57,7 +65,7 @@ class RemoteLoggerTests: XCTestCase { waitForExpectations(timeout: 0.5, handler: nil) XCTAssertEqual(messageReceiver.errors.count, 1) - XCTAssertEqual(messageReceiver.errors.first, "Error message") + XCTAssertEqual(messageReceiver.errors.first?.message, "Error message") } func testItDoesNotSendErrorAlongWithCrossPlatformCrashLog() throws { @@ -86,6 +94,142 @@ class RemoteLoggerTests: XCTestCase { XCTAssertEqual(messageReceiver.errors.count, 0) } + // MARK: - Attributes + + func testWhenFeatureHasAttributes_itSendsAttributesOnLog() throws { + // Given + let logsFeature = LogsFeature.mockAny() + let core = SingleFeatureCoreMock( + feature: logsFeature, + expectation: expectation(description: "Send log") + ) + let logger = RemoteLogger( + core: core, + configuration: .mockAny(), + dateProvider: RelativeDateProvider(), + rumContextIntegration: false, + activeSpanIntegration: false + ) + + // When + let attributeKey = String.mockRandom() + let attributeValue = String.mockRandom() + logsFeature.addAttribute(forKey: attributeKey, value: attributeValue) + + logger.info("Information message") + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + + let logs = core.events(ofType: LogEvent.self) + XCTAssertEqual(logs.count, 1) + + let log = try XCTUnwrap(logs.first) + XCTAssertEqual(log.attributes.userAttributes[attributeKey] as? String, attributeValue) + } + + func testWhenFeatureHasAttributes_andLoggerHasAttributes_itSendsLoggerAttributesOnLog() throws { + // Given + let logsFeature = LogsFeature.mockAny() + let core = SingleFeatureCoreMock( + feature: logsFeature, + expectation: expectation(description: "Send log") + ) + let logger = RemoteLogger( + core: core, + configuration: .mockAny(), + dateProvider: RelativeDateProvider(), + rumContextIntegration: false, + activeSpanIntegration: false + ) + + // When + let attributeKey = String.mockRandom() + let featureAttributeValue = String.mockRandom() + let loggerAttributeValue = String.mockRandom() + logsFeature.addAttribute(forKey: attributeKey, value: featureAttributeValue) + logger.addAttribute(forKey: attributeKey, value: loggerAttributeValue) + + logger.info("Information message") + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + + let logs = core.events(ofType: LogEvent.self) + XCTAssertEqual(logs.count, 1) + + let log = try XCTUnwrap(logs.first) + XCTAssertEqual(log.attributes.userAttributes[attributeKey] as? String, loggerAttributeValue) + } + + func testWhenFeatureHasAttributes_andLogCallHasAttributes_itSendsLogCallAttributesOnLog() throws { + // Given + let logsFeature = LogsFeature.mockAny() + let core = SingleFeatureCoreMock( + feature: logsFeature, + expectation: expectation(description: "Send log") + ) + let logger = RemoteLogger( + core: core, + configuration: .mockAny(), + dateProvider: RelativeDateProvider(), + rumContextIntegration: false, + activeSpanIntegration: false + ) + + // When + let attributeKey = String.mockRandom() + let featureAttributeValue = String.mockRandom() + logsFeature.addAttribute(forKey: attributeKey, value: featureAttributeValue) + + let logCallValue = String.mockRandom() + logger.info("Information message", attributes: [attributeKey: logCallValue]) + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + + let logs = core.events(ofType: LogEvent.self) + XCTAssertEqual(logs.count, 1) + + let log = try XCTUnwrap(logs.first) + XCTAssertEqual(log.attributes.userAttributes[attributeKey] as? String, logCallValue) + } + + func testItSendsGlobalAttributesErrorAlongWithErrorLog() throws { + // Given + let messageReceiver = ErrorMessageReceiverMock() + + let logsFeature = LogsFeature.mockAny() + let core = SingleFeatureCoreMock( + feature: logsFeature, + expectation: expectation(description: "Send error"), + messageReceiver: messageReceiver + ) + + let logger = RemoteLogger( + core: core, + configuration: .mockAny(), + dateProvider: RelativeDateProvider(), + rumContextIntegration: false, + activeSpanIntegration: false + ) + + // When + let attributeKey = String.mockRandom() + let attributeValue = String.mockRandom() + logsFeature.addAttribute(forKey: attributeKey, value: attributeValue) + + // When + logger.error("Error message") + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + + XCTAssertEqual(messageReceiver.errors.count, 1) + let error = try XCTUnwrap(messageReceiver.errors.first) + XCTAssertEqual(error.attributes[attributeKey]?.value as? String, attributeValue) + } + // MARK: - RUM Integration func testWhenRUMIntegrationIsEnabled_itSendsLogWithRUMContext() throws { diff --git a/DatadogLogs/Tests/WebViewLogReceiverTests.swift b/DatadogLogs/Tests/WebViewLogReceiverTests.swift index 4266cd6c89..3f96513647 100644 --- a/DatadogLogs/Tests/WebViewLogReceiverTests.swift +++ b/DatadogLogs/Tests/WebViewLogReceiverTests.swift @@ -11,6 +11,49 @@ import DatadogInternal @testable import DatadogLogs class WebViewLogReceiverTests: XCTestCase { + func testParsingLogEvent() throws { + // Given + let data = """ + { + "eventType": "log", + "event": { + "date": 1635932927012, + "error": { + "origin": "console" + }, + "message": "console error: error", + "session_id": "0110cab4-7471-480e-aa4e-7ce039ced355", + "status": "error", + "view": { + "referrer": "", + "url": "https://datadoghq.dev/browser-sdk-test-playground" + } + }, + "tags": [ + "browser_sdk_version:3.6.13" + ] + } + """.utf8Data + + // When + let decoder = JSONDecoder() + let message = try decoder.decode(WebViewMessage.self, from: data) + + guard case let .log(event) = message else { + return XCTFail("not a log message") + } + + // Then + let json = JSONObjectMatcher(object: event) + XCTAssertEqual(try json.value("date"), 1_635_932_927_012) + XCTAssertEqual(try json.value("error.origin"), "console") + XCTAssertEqual(try json.value("message"), "console error: error") + XCTAssertEqual(try json.value("session_id"), "0110cab4-7471-480e-aa4e-7ce039ced355") + XCTAssertEqual(try json.value("status"), "error") + XCTAssertEqual(try json.value("view.referrer"), "") + XCTAssertEqual(try json.value("view.url"), "https://datadoghq.dev/browser-sdk-test-playground") + } + func testReceiveEvent() throws { // Given let messageReceiver = WebViewLogReceiver() @@ -19,15 +62,12 @@ class WebViewLogReceiverTests: XCTestCase { expectation: expectation(description: "Send Event") ) - // When let value: String = .mockRandom() + // When XCTAssert( messageReceiver.receive( - message: .baggage( - key: LoggingMessageKeys.browserLog, - value: AnyEncodable([ "test": value ]) - ), + message: .webview(.log(["test": value])), from: core ) ) @@ -78,10 +118,7 @@ class WebViewLogReceiverTests: XCTestCase { // When XCTAssert( messageReceiver.receive( - message: .baggage( - key: LoggingMessageKeys.browserLog, - value: AnyEncodable(webLogEvent) - ), + message: .webview(.log(webLogEvent)), from: core ) ) @@ -129,10 +166,7 @@ class WebViewLogReceiverTests: XCTestCase { // When XCTAssert( messageReceiver.receive( - message: .baggage( - key: LoggingMessageKeys.browserLog, - value: AnyEncodable([ "test": "value" ]) - ), + message: .webview(.log(["test": "value"])), from: core ) ) @@ -162,10 +196,7 @@ class WebViewLogReceiverTests: XCTestCase { // When XCTAssert( messageReceiver.receive( - message: .baggage( - key: LoggingMessageKeys.browserLog, - value: AnyEncodable([ "test": "value" ]) - ), + message: .webview(.log(["test": "value"])), from: core ) ) @@ -201,10 +232,7 @@ class WebViewLogReceiverTests: XCTestCase { // When XCTAssert( messageReceiver.receive( - message: .baggage( - key: LoggingMessageKeys.browserLog, - value: AnyEncodable([ "test": "value" ]) - ), + message: .webview(.log(["test": "value"])), from: core ) ) diff --git a/DatadogObjc.podspec b/DatadogObjc.podspec index ba86e67b3c..59b6874786 100644 --- a/DatadogObjc.podspec +++ b/DatadogObjc.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogObjc" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Official Datadog Objective-C SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift index b18e9c6268..b803a616f4 100644 --- a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift +++ b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift @@ -1058,6 +1058,10 @@ public class DDRUMErrorEvent: NSObject { root.swiftModel.featureFlags != nil ? DDRUMErrorEventFeatureFlags(root: root) : nil } + @objc public var freeze: DDRUMErrorEventFreeze? { + root.swiftModel.freeze != nil ? DDRUMErrorEventFreeze(root: root) : nil + } + @objc public var os: DDRUMErrorEventRUMOperatingSystem? { root.swiftModel.os != nil ? DDRUMErrorEventRUMOperatingSystem(root: root) : nil } @@ -1595,6 +1599,14 @@ public class DDRUMErrorEventError: NSObject { self.root = root } + @objc public var binaryImages: [DDRUMErrorEventErrorBinaryImages]? { + root.swiftModel.error.binaryImages?.map { DDRUMErrorEventErrorBinaryImages(swiftModel: $0) } + } + + @objc public var category: DDRUMErrorEventErrorCategory { + .init(swift: root.swiftModel.error.category) + } + @objc public var causes: [DDRUMErrorEventErrorCauses]? { set { root.swiftModel.error.causes = newValue?.map { $0.swiftModel } } get { root.swiftModel.error.causes?.map { DDRUMErrorEventErrorCauses(swiftModel: $0) } } @@ -1626,6 +1638,10 @@ public class DDRUMErrorEventError: NSObject { get { root.swiftModel.error.message } } + @objc public var meta: DDRUMErrorEventErrorMeta? { + root.swiftModel.error.meta != nil ? DDRUMErrorEventErrorMeta(root: root) : nil + } + @objc public var resource: DDRUMErrorEventErrorResource? { root.swiftModel.error.resource != nil ? DDRUMErrorEventErrorResource(root: root) : nil } @@ -1643,9 +1659,77 @@ public class DDRUMErrorEventError: NSObject { get { root.swiftModel.error.stack } } + @objc public var threads: [DDRUMErrorEventErrorThreads]? { + root.swiftModel.error.threads?.map { DDRUMErrorEventErrorThreads(swiftModel: $0) } + } + @objc public var type: String? { root.swiftModel.error.type } + + @objc public var wasTruncated: NSNumber? { + root.swiftModel.error.wasTruncated as NSNumber? + } +} + +@objc +public class DDRUMErrorEventErrorBinaryImages: NSObject { + internal var swiftModel: RUMErrorEvent.Error.BinaryImages + internal var root: DDRUMErrorEventErrorBinaryImages { self } + + internal init(swiftModel: RUMErrorEvent.Error.BinaryImages) { + self.swiftModel = swiftModel + } + + @objc public var arch: String? { + root.swiftModel.arch + } + + @objc public var isSystem: NSNumber { + root.swiftModel.isSystem as NSNumber + } + + @objc public var loadAddress: String? { + root.swiftModel.loadAddress + } + + @objc public var maxAddress: String? { + root.swiftModel.maxAddress + } + + @objc public var name: String { + root.swiftModel.name + } + + @objc public var uuid: String { + root.swiftModel.uuid + } +} + +@objc +public enum DDRUMErrorEventErrorCategory: Int { + internal init(swift: RUMErrorEvent.Error.Category?) { + switch swift { + case nil: self = .none + case .aNR?: self = .aNR + case .appHang?: self = .appHang + case .exception?: self = .exception + } + } + + internal var toSwift: RUMErrorEvent.Error.Category? { + switch self { + case .none: return nil + case .aNR: return .aNR + case .appHang: return .appHang + case .exception: return .exception + } + } + + case none + case aNR + case appHang + case exception } @objc @@ -1737,6 +1821,43 @@ public enum DDRUMErrorEventErrorHandling: Int { case unhandled } +@objc +public class DDRUMErrorEventErrorMeta: NSObject { + internal let root: DDRUMErrorEvent + + internal init(root: DDRUMErrorEvent) { + self.root = root + } + + @objc public var codeType: String? { + root.swiftModel.error.meta!.codeType + } + + @objc public var exceptionCodes: String? { + root.swiftModel.error.meta!.exceptionCodes + } + + @objc public var exceptionType: String? { + root.swiftModel.error.meta!.exceptionType + } + + @objc public var incidentIdentifier: String? { + root.swiftModel.error.meta!.incidentIdentifier + } + + @objc public var parentProcess: String? { + root.swiftModel.error.meta!.parentProcess + } + + @objc public var path: String? { + root.swiftModel.error.meta!.path + } + + @objc public var process: String? { + root.swiftModel.error.meta!.process + } +} + @objc public class DDRUMErrorEventErrorResource: NSObject { internal let root: DDRUMErrorEvent @@ -1773,6 +1894,9 @@ public enum DDRUMErrorEventErrorResourceRUMMethod: Int { case .put: self = .put case .delete: self = .delete case .patch: self = .patch + case .trace: self = .trace + case .options: self = .options + case .connect: self = .connect } } @@ -1784,6 +1908,9 @@ public enum DDRUMErrorEventErrorResourceRUMMethod: Int { case .put: return .put case .delete: return .delete case .patch: return .patch + case .trace: return .trace + case .options: return .options + case .connect: return .connect } } @@ -1793,6 +1920,9 @@ public enum DDRUMErrorEventErrorResourceRUMMethod: Int { case put case delete case patch + case trace + case options + case connect } @objc @@ -1924,6 +2054,9 @@ public enum DDRUMErrorEventErrorSourceType: Int { case .reactNative?: self = .reactNative case .flutter?: self = .flutter case .roku?: self = .roku + case .ndk?: self = .ndk + case .iosIl2cpp?: self = .iosIl2cpp + case .ndkIl2cpp?: self = .ndkIl2cpp } } @@ -1936,6 +2069,9 @@ public enum DDRUMErrorEventErrorSourceType: Int { case .reactNative: return .reactNative case .flutter: return .flutter case .roku: return .roku + case .ndk: return .ndk + case .iosIl2cpp: return .iosIl2cpp + case .ndkIl2cpp: return .ndkIl2cpp } } @@ -1946,6 +2082,35 @@ public enum DDRUMErrorEventErrorSourceType: Int { case reactNative case flutter case roku + case ndk + case iosIl2cpp + case ndkIl2cpp +} + +@objc +public class DDRUMErrorEventErrorThreads: NSObject { + internal var swiftModel: RUMErrorEvent.Error.Threads + internal var root: DDRUMErrorEventErrorThreads { self } + + internal init(swiftModel: RUMErrorEvent.Error.Threads) { + self.swiftModel = swiftModel + } + + @objc public var crashed: NSNumber { + root.swiftModel.crashed as NSNumber + } + + @objc public var name: String { + root.swiftModel.name + } + + @objc public var stack: String { + root.swiftModel.stack + } + + @objc public var state: String? { + root.swiftModel.state + } } @objc @@ -1961,6 +2126,19 @@ public class DDRUMErrorEventFeatureFlags: NSObject { } } +@objc +public class DDRUMErrorEventFreeze: NSObject { + internal let root: DDRUMErrorEvent + + internal init(root: DDRUMErrorEvent) { + self.root = root + } + + @objc public var duration: NSNumber { + root.swiftModel.freeze!.duration as NSNumber + } +} + @objc public class DDRUMErrorEventRUMOperatingSystem: NSObject { internal let root: DDRUMErrorEvent @@ -3772,6 +3950,9 @@ public enum DDRUMResourceEventResourceRUMMethod: Int { case .put?: self = .put case .delete?: self = .delete case .patch?: self = .patch + case .trace?: self = .trace + case .options?: self = .options + case .connect?: self = .connect } } @@ -3784,6 +3965,9 @@ public enum DDRUMResourceEventResourceRUMMethod: Int { case .put: return .put case .delete: return .delete case .patch: return .patch + case .trace: return .trace + case .options: return .options + case .connect: return .connect } } @@ -3794,6 +3978,9 @@ public enum DDRUMResourceEventResourceRUMMethod: Int { case put case delete case patch + case trace + case options + case connect } @objc @@ -5396,169 +5583,942 @@ public class DDRUMViewEventViewResource: NSObject { } @objc -public class DDTelemetryErrorEvent: NSObject { - internal var swiftModel: TelemetryErrorEvent - internal var root: DDTelemetryErrorEvent { self } +public class DDRUMVitalEvent: NSObject { + internal var swiftModel: RUMVitalEvent + internal var root: DDRUMVitalEvent { self } - internal init(swiftModel: TelemetryErrorEvent) { + internal init(swiftModel: RUMVitalEvent) { self.swiftModel = swiftModel } - @objc public var dd: DDTelemetryErrorEventDD { - DDTelemetryErrorEventDD(root: root) + @objc public var dd: DDRUMVitalEventDD { + DDRUMVitalEventDD(root: root) } - @objc public var action: DDTelemetryErrorEventAction? { - root.swiftModel.action != nil ? DDTelemetryErrorEventAction(root: root) : nil + @objc public var application: DDRUMVitalEventApplication { + DDRUMVitalEventApplication(root: root) } - @objc public var application: DDTelemetryErrorEventApplication? { - root.swiftModel.application != nil ? DDTelemetryErrorEventApplication(root: root) : nil + @objc public var buildId: String? { + root.swiftModel.buildId + } + + @objc public var buildVersion: String? { + root.swiftModel.buildVersion + } + + @objc public var ciTest: DDRUMVitalEventRUMCITest? { + root.swiftModel.ciTest != nil ? DDRUMVitalEventRUMCITest(root: root) : nil + } + + @objc public var connectivity: DDRUMVitalEventRUMConnectivity? { + root.swiftModel.connectivity != nil ? DDRUMVitalEventRUMConnectivity(root: root) : nil + } + + @objc public var container: DDRUMVitalEventContainer? { + root.swiftModel.container != nil ? DDRUMVitalEventContainer(root: root) : nil + } + + @objc public var context: DDRUMVitalEventRUMEventAttributes? { + root.swiftModel.context != nil ? DDRUMVitalEventRUMEventAttributes(root: root) : nil } @objc public var date: NSNumber { root.swiftModel.date as NSNumber } - @objc public var experimentalFeatures: [String]? { - root.swiftModel.experimentalFeatures + @objc public var device: DDRUMVitalEventRUMDevice? { + root.swiftModel.device != nil ? DDRUMVitalEventRUMDevice(root: root) : nil } - @objc public var service: String { + @objc public var display: DDRUMVitalEventDisplay? { + root.swiftModel.display != nil ? DDRUMVitalEventDisplay(root: root) : nil + } + + @objc public var os: DDRUMVitalEventRUMOperatingSystem? { + root.swiftModel.os != nil ? DDRUMVitalEventRUMOperatingSystem(root: root) : nil + } + + @objc public var service: String? { root.swiftModel.service } - @objc public var session: DDTelemetryErrorEventSession? { - root.swiftModel.session != nil ? DDTelemetryErrorEventSession(root: root) : nil + @objc public var session: DDRUMVitalEventSession { + DDRUMVitalEventSession(root: root) } - @objc public var source: DDTelemetryErrorEventSource { + @objc public var source: DDRUMVitalEventSource { .init(swift: root.swiftModel.source) } - @objc public var telemetry: DDTelemetryErrorEventTelemetry { - DDTelemetryErrorEventTelemetry(root: root) + @objc public var synthetics: DDRUMVitalEventRUMSyntheticsTest? { + root.swiftModel.synthetics != nil ? DDRUMVitalEventRUMSyntheticsTest(root: root) : nil } @objc public var type: String { root.swiftModel.type } - @objc public var version: String { + @objc public var usr: DDRUMVitalEventRUMUser? { + root.swiftModel.usr != nil ? DDRUMVitalEventRUMUser(root: root) : nil + } + + @objc public var version: String? { root.swiftModel.version } - @objc public var view: DDTelemetryErrorEventView? { - root.swiftModel.view != nil ? DDTelemetryErrorEventView(root: root) : nil + @objc public var view: DDRUMVitalEventView { + DDRUMVitalEventView(root: root) + } + + @objc public var vital: DDRUMVitalEventVital { + DDRUMVitalEventVital(root: root) } } @objc -public class DDTelemetryErrorEventDD: NSObject { - internal let root: DDTelemetryErrorEvent +public class DDRUMVitalEventDD: NSObject { + internal let root: DDRUMVitalEvent - internal init(root: DDTelemetryErrorEvent) { + internal init(root: DDRUMVitalEvent) { self.root = root } + @objc public var browserSdkVersion: String? { + root.swiftModel.dd.browserSdkVersion + } + + @objc public var configuration: DDRUMVitalEventDDConfiguration? { + root.swiftModel.dd.configuration != nil ? DDRUMVitalEventDDConfiguration(root: root) : nil + } + @objc public var formatVersion: NSNumber { root.swiftModel.dd.formatVersion as NSNumber } + + @objc public var session: DDRUMVitalEventDDSession? { + root.swiftModel.dd.session != nil ? DDRUMVitalEventDDSession(root: root) : nil + } } @objc -public class DDTelemetryErrorEventAction: NSObject { - internal let root: DDTelemetryErrorEvent +public class DDRUMVitalEventDDConfiguration: NSObject { + internal let root: DDRUMVitalEvent - internal init(root: DDTelemetryErrorEvent) { + internal init(root: DDRUMVitalEvent) { self.root = root } - @objc public var id: String { - root.swiftModel.action!.id + @objc public var sessionReplaySampleRate: NSNumber? { + root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber? + } + + @objc public var sessionSampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionSampleRate as NSNumber } } @objc -public class DDTelemetryErrorEventApplication: NSObject { - internal let root: DDTelemetryErrorEvent +public class DDRUMVitalEventDDSession: NSObject { + internal let root: DDRUMVitalEvent - internal init(root: DDTelemetryErrorEvent) { + internal init(root: DDRUMVitalEvent) { self.root = root } - @objc public var id: String { - root.swiftModel.application!.id + @objc public var plan: DDRUMVitalEventDDSessionPlan { + .init(swift: root.swiftModel.dd.session!.plan) + } + + @objc public var sessionPrecondition: DDRUMVitalEventDDSessionRUMSessionPrecondition { + .init(swift: root.swiftModel.dd.session!.sessionPrecondition) } } @objc -public class DDTelemetryErrorEventSession: NSObject { - internal let root: DDTelemetryErrorEvent - - internal init(root: DDTelemetryErrorEvent) { - self.root = root +public enum DDRUMVitalEventDDSessionPlan: Int { + internal init(swift: RUMVitalEvent.DD.Session.Plan?) { + switch swift { + case nil: self = .none + case .plan1?: self = .plan1 + case .plan2?: self = .plan2 + } } - @objc public var id: String { - root.swiftModel.session!.id + internal var toSwift: RUMVitalEvent.DD.Session.Plan? { + switch self { + case .none: return nil + case .plan1: return .plan1 + case .plan2: return .plan2 + } } + + case none + case plan1 + case plan2 } @objc -public enum DDTelemetryErrorEventSource: Int { - internal init(swift: TelemetryErrorEvent.Source) { +public enum DDRUMVitalEventDDSessionRUMSessionPrecondition: Int { + internal init(swift: RUMSessionPrecondition?) { switch swift { - case .android: self = .android - case .ios: self = .ios - case .browser: self = .browser - case .flutter: self = .flutter - case .reactNative: self = .reactNative - case .unity: self = .unity + case nil: self = .none + case .userAppLaunch?: self = .userAppLaunch + case .inactivityTimeout?: self = .inactivityTimeout + case .maxDuration?: self = .maxDuration + case .backgroundLaunch?: self = .backgroundLaunch + case .prewarm?: self = .prewarm + case .fromNonInteractiveSession?: self = .fromNonInteractiveSession + case .explicitStop?: self = .explicitStop } } - internal var toSwift: TelemetryErrorEvent.Source { + internal var toSwift: RUMSessionPrecondition? { switch self { - case .android: return .android - case .ios: return .ios - case .browser: return .browser - case .flutter: return .flutter - case .reactNative: return .reactNative - case .unity: return .unity + case .none: return nil + case .userAppLaunch: return .userAppLaunch + case .inactivityTimeout: return .inactivityTimeout + case .maxDuration: return .maxDuration + case .backgroundLaunch: return .backgroundLaunch + case .prewarm: return .prewarm + case .fromNonInteractiveSession: return .fromNonInteractiveSession + case .explicitStop: return .explicitStop } } - case android - case ios - case browser - case flutter - case reactNative - case unity + case none + case userAppLaunch + case inactivityTimeout + case maxDuration + case backgroundLaunch + case prewarm + case fromNonInteractiveSession + case explicitStop } @objc -public class DDTelemetryErrorEventTelemetry: NSObject { - internal let root: DDTelemetryErrorEvent +public class DDRUMVitalEventApplication: NSObject { + internal let root: DDRUMVitalEvent - internal init(root: DDTelemetryErrorEvent) { + internal init(root: DDRUMVitalEvent) { self.root = root } - @objc public var error: DDTelemetryErrorEventTelemetryError? { - root.swiftModel.telemetry.error != nil ? DDTelemetryErrorEventTelemetryError(root: root) : nil + @objc public var id: String { + root.swiftModel.application.id } +} - @objc public var message: String { - root.swiftModel.telemetry.message - } +@objc +public class DDRUMVitalEventRUMCITest: NSObject { + internal let root: DDRUMVitalEvent - @objc public var status: String { - root.swiftModel.telemetry.status + internal init(root: DDRUMVitalEvent) { + self.root = root } - @objc public var type: String? { - root.swiftModel.telemetry.type + @objc public var testExecutionId: String { + root.swiftModel.ciTest!.testExecutionId + } +} + +@objc +public class DDRUMVitalEventRUMConnectivity: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var cellular: DDRUMVitalEventRUMConnectivityCellular? { + root.swiftModel.connectivity!.cellular != nil ? DDRUMVitalEventRUMConnectivityCellular(root: root) : nil + } + + @objc public var effectiveType: DDRUMVitalEventRUMConnectivityEffectiveType { + .init(swift: root.swiftModel.connectivity!.effectiveType) + } + + @objc public var interfaces: [Int]? { + root.swiftModel.connectivity!.interfaces?.map { DDRUMVitalEventRUMConnectivityInterfaces(swift: $0).rawValue } + } + + @objc public var status: DDRUMVitalEventRUMConnectivityStatus { + .init(swift: root.swiftModel.connectivity!.status) + } +} + +@objc +public class DDRUMVitalEventRUMConnectivityCellular: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var carrierName: String? { + root.swiftModel.connectivity!.cellular!.carrierName + } + + @objc public var technology: String? { + root.swiftModel.connectivity!.cellular!.technology + } +} + +@objc +public enum DDRUMVitalEventRUMConnectivityEffectiveType: Int { + internal init(swift: RUMConnectivity.EffectiveType?) { + switch swift { + case nil: self = .none + case .slow2g?: self = .slow2g + case .effectiveType2g?: self = .effectiveType2g + case .effectiveType3g?: self = .effectiveType3g + case .effectiveType4g?: self = .effectiveType4g + } + } + + internal var toSwift: RUMConnectivity.EffectiveType? { + switch self { + case .none: return nil + case .slow2g: return .slow2g + case .effectiveType2g: return .effectiveType2g + case .effectiveType3g: return .effectiveType3g + case .effectiveType4g: return .effectiveType4g + } + } + + case none + case slow2g + case effectiveType2g + case effectiveType3g + case effectiveType4g +} + +@objc +public enum DDRUMVitalEventRUMConnectivityInterfaces: Int { + internal init(swift: RUMConnectivity.Interfaces?) { + switch swift { + case nil: self = .none + case .bluetooth?: self = .bluetooth + case .cellular?: self = .cellular + case .ethernet?: self = .ethernet + case .wifi?: self = .wifi + case .wimax?: self = .wimax + case .mixed?: self = .mixed + case .other?: self = .other + case .unknown?: self = .unknown + case .interfacesNone?: self = .interfacesNone + } + } + + internal var toSwift: RUMConnectivity.Interfaces? { + switch self { + case .none: return nil + case .bluetooth: return .bluetooth + case .cellular: return .cellular + case .ethernet: return .ethernet + case .wifi: return .wifi + case .wimax: return .wimax + case .mixed: return .mixed + case .other: return .other + case .unknown: return .unknown + case .interfacesNone: return .interfacesNone + } + } + + case none + case bluetooth + case cellular + case ethernet + case wifi + case wimax + case mixed + case other + case unknown + case interfacesNone +} + +@objc +public enum DDRUMVitalEventRUMConnectivityStatus: Int { + internal init(swift: RUMConnectivity.Status) { + switch swift { + case .connected: self = .connected + case .notConnected: self = .notConnected + case .maybe: self = .maybe + } + } + + internal var toSwift: RUMConnectivity.Status { + switch self { + case .connected: return .connected + case .notConnected: return .notConnected + case .maybe: return .maybe + } + } + + case connected + case notConnected + case maybe +} + +@objc +public class DDRUMVitalEventContainer: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var source: DDRUMVitalEventContainerSource { + .init(swift: root.swiftModel.container!.source) + } + + @objc public var view: DDRUMVitalEventContainerView { + DDRUMVitalEventContainerView(root: root) + } +} + +@objc +public enum DDRUMVitalEventContainerSource: Int { + internal init(swift: RUMVitalEvent.Container.Source) { + switch swift { + case .android: self = .android + case .ios: self = .ios + case .browser: self = .browser + case .flutter: self = .flutter + case .reactNative: self = .reactNative + case .roku: self = .roku + case .unity: self = .unity + } + } + + internal var toSwift: RUMVitalEvent.Container.Source { + switch self { + case .android: return .android + case .ios: return .ios + case .browser: return .browser + case .flutter: return .flutter + case .reactNative: return .reactNative + case .roku: return .roku + case .unity: return .unity + } + } + + case android + case ios + case browser + case flutter + case reactNative + case roku + case unity +} + +@objc +public class DDRUMVitalEventContainerView: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var id: String { + root.swiftModel.container!.view.id + } +} + +@objc +public class DDRUMVitalEventRUMEventAttributes: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var contextInfo: [String: Any] { + root.swiftModel.context!.contextInfo.castToObjectiveC() + } +} + +@objc +public class DDRUMVitalEventRUMDevice: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var architecture: String? { + root.swiftModel.device!.architecture + } + + @objc public var brand: String? { + root.swiftModel.device!.brand + } + + @objc public var model: String? { + root.swiftModel.device!.model + } + + @objc public var name: String? { + root.swiftModel.device!.name + } + + @objc public var type: DDRUMVitalEventRUMDeviceRUMDeviceType { + .init(swift: root.swiftModel.device!.type) + } +} + +@objc +public enum DDRUMVitalEventRUMDeviceRUMDeviceType: Int { + internal init(swift: RUMDevice.RUMDeviceType) { + switch swift { + case .mobile: self = .mobile + case .desktop: self = .desktop + case .tablet: self = .tablet + case .tv: self = .tv + case .gamingConsole: self = .gamingConsole + case .bot: self = .bot + case .other: self = .other + } + } + + internal var toSwift: RUMDevice.RUMDeviceType { + switch self { + case .mobile: return .mobile + case .desktop: return .desktop + case .tablet: return .tablet + case .tv: return .tv + case .gamingConsole: return .gamingConsole + case .bot: return .bot + case .other: return .other + } + } + + case mobile + case desktop + case tablet + case tv + case gamingConsole + case bot + case other +} + +@objc +public class DDRUMVitalEventDisplay: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var viewport: DDRUMVitalEventDisplayViewport? { + root.swiftModel.display!.viewport != nil ? DDRUMVitalEventDisplayViewport(root: root) : nil + } +} + +@objc +public class DDRUMVitalEventDisplayViewport: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var height: NSNumber { + root.swiftModel.display!.viewport!.height as NSNumber + } + + @objc public var width: NSNumber { + root.swiftModel.display!.viewport!.width as NSNumber + } +} + +@objc +public class DDRUMVitalEventRUMOperatingSystem: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var build: String? { + root.swiftModel.os!.build + } + + @objc public var name: String { + root.swiftModel.os!.name + } + + @objc public var version: String { + root.swiftModel.os!.version + } + + @objc public var versionMajor: String { + root.swiftModel.os!.versionMajor + } +} + +@objc +public class DDRUMVitalEventSession: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var hasReplay: NSNumber? { + root.swiftModel.session.hasReplay as NSNumber? + } + + @objc public var id: String { + root.swiftModel.session.id + } + + @objc public var type: DDRUMVitalEventSessionRUMSessionType { + .init(swift: root.swiftModel.session.type) + } +} + +@objc +public enum DDRUMVitalEventSessionRUMSessionType: Int { + internal init(swift: RUMSessionType) { + switch swift { + case .user: self = .user + case .synthetics: self = .synthetics + case .ciTest: self = .ciTest + } + } + + internal var toSwift: RUMSessionType { + switch self { + case .user: return .user + case .synthetics: return .synthetics + case .ciTest: return .ciTest + } + } + + case user + case synthetics + case ciTest +} + +@objc +public enum DDRUMVitalEventSource: Int { + internal init(swift: RUMVitalEvent.Source?) { + switch swift { + case nil: self = .none + case .android?: self = .android + case .ios?: self = .ios + case .browser?: self = .browser + case .flutter?: self = .flutter + case .reactNative?: self = .reactNative + case .roku?: self = .roku + case .unity?: self = .unity + } + } + + internal var toSwift: RUMVitalEvent.Source? { + switch self { + case .none: return nil + case .android: return .android + case .ios: return .ios + case .browser: return .browser + case .flutter: return .flutter + case .reactNative: return .reactNative + case .roku: return .roku + case .unity: return .unity + } + } + + case none + case android + case ios + case browser + case flutter + case reactNative + case roku + case unity +} + +@objc +public class DDRUMVitalEventRUMSyntheticsTest: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var injected: NSNumber? { + root.swiftModel.synthetics!.injected as NSNumber? + } + + @objc public var resultId: String { + root.swiftModel.synthetics!.resultId + } + + @objc public var testId: String { + root.swiftModel.synthetics!.testId + } +} + +@objc +public class DDRUMVitalEventRUMUser: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var email: String? { + root.swiftModel.usr!.email + } + + @objc public var id: String? { + root.swiftModel.usr!.id + } + + @objc public var name: String? { + root.swiftModel.usr!.name + } + + @objc public var usrInfo: [String: Any] { + root.swiftModel.usr!.usrInfo.castToObjectiveC() + } +} + +@objc +public class DDRUMVitalEventView: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var id: String { + root.swiftModel.view.id + } + + @objc public var name: String? { + set { root.swiftModel.view.name = newValue } + get { root.swiftModel.view.name } + } + + @objc public var referrer: String? { + set { root.swiftModel.view.referrer = newValue } + get { root.swiftModel.view.referrer } + } + + @objc public var url: String { + set { root.swiftModel.view.url = newValue } + get { root.swiftModel.view.url } + } +} + +@objc +public class DDRUMVitalEventVital: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var custom: [String: NSNumber]? { + root.swiftModel.vital.custom as [String: NSNumber]? + } + + @objc public var id: String { + root.swiftModel.vital.id + } + + @objc public var name: String? { + root.swiftModel.vital.name + } + + @objc public var type: DDRUMVitalEventVitalVitalType { + .init(swift: root.swiftModel.vital.type) + } +} + +@objc +public enum DDRUMVitalEventVitalVitalType: Int { + internal init(swift: RUMVitalEvent.Vital.VitalType) { + switch swift { + case .duration: self = .duration + } + } + + internal var toSwift: RUMVitalEvent.Vital.VitalType { + switch self { + case .duration: return .duration + } + } + + case duration +} + +@objc +public class DDTelemetryErrorEvent: NSObject { + internal var swiftModel: TelemetryErrorEvent + internal var root: DDTelemetryErrorEvent { self } + + internal init(swiftModel: TelemetryErrorEvent) { + self.swiftModel = swiftModel + } + + @objc public var dd: DDTelemetryErrorEventDD { + DDTelemetryErrorEventDD(root: root) + } + + @objc public var action: DDTelemetryErrorEventAction? { + root.swiftModel.action != nil ? DDTelemetryErrorEventAction(root: root) : nil + } + + @objc public var application: DDTelemetryErrorEventApplication? { + root.swiftModel.application != nil ? DDTelemetryErrorEventApplication(root: root) : nil + } + + @objc public var date: NSNumber { + root.swiftModel.date as NSNumber + } + + @objc public var experimentalFeatures: [String]? { + root.swiftModel.experimentalFeatures + } + + @objc public var service: String { + root.swiftModel.service + } + + @objc public var session: DDTelemetryErrorEventSession? { + root.swiftModel.session != nil ? DDTelemetryErrorEventSession(root: root) : nil + } + + @objc public var source: DDTelemetryErrorEventSource { + .init(swift: root.swiftModel.source) + } + + @objc public var telemetry: DDTelemetryErrorEventTelemetry { + DDTelemetryErrorEventTelemetry(root: root) + } + + @objc public var type: String { + root.swiftModel.type + } + + @objc public var version: String { + root.swiftModel.version + } + + @objc public var view: DDTelemetryErrorEventView? { + root.swiftModel.view != nil ? DDTelemetryErrorEventView(root: root) : nil + } +} + +@objc +public class DDTelemetryErrorEventDD: NSObject { + internal let root: DDTelemetryErrorEvent + + internal init(root: DDTelemetryErrorEvent) { + self.root = root + } + + @objc public var formatVersion: NSNumber { + root.swiftModel.dd.formatVersion as NSNumber + } +} + +@objc +public class DDTelemetryErrorEventAction: NSObject { + internal let root: DDTelemetryErrorEvent + + internal init(root: DDTelemetryErrorEvent) { + self.root = root + } + + @objc public var id: String { + root.swiftModel.action!.id + } +} + +@objc +public class DDTelemetryErrorEventApplication: NSObject { + internal let root: DDTelemetryErrorEvent + + internal init(root: DDTelemetryErrorEvent) { + self.root = root + } + + @objc public var id: String { + root.swiftModel.application!.id + } +} + +@objc +public class DDTelemetryErrorEventSession: NSObject { + internal let root: DDTelemetryErrorEvent + + internal init(root: DDTelemetryErrorEvent) { + self.root = root + } + + @objc public var id: String { + root.swiftModel.session!.id + } +} + +@objc +public enum DDTelemetryErrorEventSource: Int { + internal init(swift: TelemetryErrorEvent.Source) { + switch swift { + case .android: self = .android + case .ios: self = .ios + case .browser: self = .browser + case .flutter: self = .flutter + case .reactNative: self = .reactNative + case .unity: self = .unity + } + } + + internal var toSwift: TelemetryErrorEvent.Source { + switch self { + case .android: return .android + case .ios: return .ios + case .browser: return .browser + case .flutter: return .flutter + case .reactNative: return .reactNative + case .unity: return .unity + } + } + + case android + case ios + case browser + case flutter + case reactNative + case unity +} + +@objc +public class DDTelemetryErrorEventTelemetry: NSObject { + internal let root: DDTelemetryErrorEvent + + internal init(root: DDTelemetryErrorEvent) { + self.root = root + } + + @objc public var error: DDTelemetryErrorEventTelemetryError? { + root.swiftModel.telemetry.error != nil ? DDTelemetryErrorEventTelemetryError(root: root) : nil + } + + @objc public var message: String { + root.swiftModel.telemetry.message + } + + @objc public var status: String { + root.swiftModel.telemetry.status + } + + @objc public var type: String? { + root.swiftModel.telemetry.type } } @@ -5951,6 +6911,10 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { root.swiftModel.telemetry.configuration.allowUntrustedEvents as NSNumber? } + @objc public var appHangThreshold: NSNumber? { + root.swiftModel.telemetry.configuration.appHangThreshold as NSNumber? + } + @objc public var backgroundTasksEnabled: NSNumber? { root.swiftModel.telemetry.configuration.backgroundTasksEnabled as NSNumber? } @@ -6129,6 +7093,11 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { get { root.swiftModel.telemetry.configuration.trackViewsManually as NSNumber? } } + @objc public var unityVersion: String? { + set { root.swiftModel.telemetry.configuration.unityVersion = newValue } + get { root.swiftModel.telemetry.configuration.unityVersion } + } + @objc public var useAllowedTracingOrigins: NSNumber? { root.swiftModel.telemetry.configuration.useAllowedTracingOrigins as NSNumber? } @@ -6303,4 +7272,4 @@ public class DDTelemetryConfigurationEventView: NSObject { // swiftlint:enable force_unwrapping -// Generated from https://github.com/DataDog/rum-events-format/tree/389581be98dcf8efbfcfe7bffaa32d53f960fb6f +// Generated from https://github.com/DataDog/rum-events-format/tree/63842bcc15dec58b68fc0a57260715f8dfcde330 diff --git a/DatadogRUM.podspec b/DatadogRUM.podspec index b2c0fdd342..70693960bc 100644 --- a/DatadogRUM.podspec +++ b/DatadogRUM.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogRUM" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Datadog Real User Monitoring Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogRUM/Sources/DataModels/RUMDataModels.swift b/DatadogRUM/Sources/DataModels/RUMDataModels.swift index c499e6e7a8..efbae887d6 100644 --- a/DatadogRUM/Sources/DataModels/RUMDataModels.swift +++ b/DatadogRUM/Sources/DataModels/RUMDataModels.swift @@ -495,6 +495,9 @@ public struct RUMErrorEvent: RUMDataModel { /// Feature flags properties public internal(set) var featureFlags: FeatureFlags? + /// Properties of App Hang and ANR errors + public let freeze: Freeze? + /// Operating system properties public let os: RUMOperatingSystem? @@ -537,6 +540,7 @@ public struct RUMErrorEvent: RUMDataModel { case display = "display" case error = "error" case featureFlags = "feature_flags" + case freeze = "freeze" case os = "os" case service = "service" case session = "session" @@ -685,6 +689,12 @@ public struct RUMErrorEvent: RUMDataModel { /// Error properties public struct Error: Codable { + /// Description of each binary image (native libraries; for Android: .so files) loaded or referenced by the process/application. + public let binaryImages: [BinaryImages]? + + /// The specific category of the error. It provides a high-level grouping for different types of errors. + public let category: Category? + /// Causes of the error public var causes: [Causes]? @@ -706,6 +716,9 @@ public struct RUMErrorEvent: RUMDataModel { /// Error message public var message: String + /// Platform-specific metadata of the error event. + public let meta: Meta? + /// Resource properties of the error public var resource: Resource? @@ -718,10 +731,18 @@ public struct RUMErrorEvent: RUMDataModel { /// Stacktrace of the error public var stack: String? + /// Description of each thread in the process when error happened. + public let threads: [Threads]? + /// The type of the error public let type: String? + /// A boolean value saying if any of the stack traces was truncated due to minification. + public let wasTruncated: Bool? + enum CodingKeys: String, CodingKey { + case binaryImages = "binary_images" + case category = "category" case causes = "causes" case fingerprint = "fingerprint" case handling = "handling" @@ -729,11 +750,51 @@ public struct RUMErrorEvent: RUMDataModel { case id = "id" case isCrash = "is_crash" case message = "message" + case meta = "meta" case resource = "resource" case source = "source" case sourceType = "source_type" case stack = "stack" + case threads = "threads" case type = "type" + case wasTruncated = "was_truncated" + } + + /// Description of the binary image (native library; for Android: .so file) loaded or referenced by the process/application. + public struct BinaryImages: Codable { + /// CPU architecture from the library. + public let arch: String? + + /// Determines if it's a system or user library. + public let isSystem: Bool + + /// Library's load address (hexadecimal). + public let loadAddress: String? + + /// Max value from the library address range (hexadecimal). + public let maxAddress: String? + + /// Name of the library. + public let name: String + + /// Build UUID that uniquely identifies the binary image. + public let uuid: String + + enum CodingKeys: String, CodingKey { + case arch = "arch" + case isSystem = "is_system" + case loadAddress = "load_address" + case maxAddress = "max_address" + case name = "name" + case uuid = "uuid" + } + } + + /// The specific category of the error. It provides a high-level grouping for different types of errors. + public enum Category: String, Codable { + case aNR = "ANR" + case appHang = "App Hang" + case exception = "Exception" } /// Properties for one of the error causes @@ -776,6 +837,40 @@ public struct RUMErrorEvent: RUMDataModel { case unhandled = "unhandled" } + /// Platform-specific metadata of the error event. + public struct Meta: Codable { + /// The CPU architecture of the process that crashed. + public let codeType: String? + + /// CPU specific information about the exception encoded into 64-bit hexadecimal number preceded by the signal code. + public let exceptionCodes: String? + + /// The name of the corresponding BSD termination signal. (in case of iOS crash) + public let exceptionType: String? + + /// A client-generated 16-byte UUID of the incident. + public let incidentIdentifier: String? + + /// Parent process information. + public let parentProcess: String? + + /// The location of the executable. + public let path: String? + + /// The name of the crashed process. + public let process: String? + + enum CodingKeys: String, CodingKey { + case codeType = "code_type" + case exceptionCodes = "exception_codes" + case exceptionType = "exception_type" + case incidentIdentifier = "incident_identifier" + case parentProcess = "parent_process" + case path = "path" + case process = "process" + } + } + /// Resource properties of the error public struct Resource: Codable { /// HTTP method of the resource @@ -854,6 +949,31 @@ public struct RUMErrorEvent: RUMDataModel { case reactNative = "react-native" case flutter = "flutter" case roku = "roku" + case ndk = "ndk" + case iosIl2cpp = "ios+il2cpp" + case ndkIl2cpp = "ndk+il2cpp" + } + + /// Description of the thread in the process when error happened. + public struct Threads: Codable { + /// Tells if the thread crashed. + public let crashed: Bool + + /// Name of the thread (e.g. 'Thread 0'). + public let name: String + + /// Unsymbolicated stack trace of the given thread. + public let stack: String + + /// Platform-specific state of the thread when its state was captured (CPU registers dump for iOS, thread state enum for Android, etc.). + public let state: String? + + enum CodingKeys: String, CodingKey { + case crashed = "crashed" + case name = "name" + case stack = "stack" + case state = "state" + } } } @@ -862,6 +982,16 @@ public struct RUMErrorEvent: RUMDataModel { public internal(set) var featureFlagsInfo: [String: Encodable] } + /// Properties of App Hang and ANR errors + public struct Freeze: Codable { + /// Duration of the main thread freeze (in ns) + public let duration: Int64 + + enum CodingKeys: String, CodingKey { + case duration = "duration" + } + } + /// Session properties public struct Session: Codable { /// Whether this session has a replay @@ -2491,6 +2621,299 @@ extension RUMViewEvent.FeatureFlags { } } +/// Schema of all properties of a Vital event +public struct RUMVitalEvent: RUMDataModel { + /// Internal properties + public let dd: DD + + /// Application properties + public let application: Application + + /// Generated unique ID of the application build. Unlike version or build_version this field is not meant to be coming from the user, but rather generated by the tooling for each build. + public let buildId: String? + + /// The build version for this application + public let buildVersion: String? + + /// CI Visibility properties + public let ciTest: RUMCITest? + + /// Device connectivity properties + public let connectivity: RUMConnectivity? + + /// View Container properties (view wrapping the current view) + public let container: Container? + + /// User provided context + public internal(set) var context: RUMEventAttributes? + + /// Start of the event in ms from epoch + public let date: Int64 + + /// Device properties + public let device: RUMDevice? + + /// Display properties + public let display: Display? + + /// Operating system properties + public let os: RUMOperatingSystem? + + /// The service name for this application + public let service: String? + + /// Session properties + public let session: Session + + /// The source of this event + public let source: Source? + + /// Synthetics properties + public let synthetics: RUMSyntheticsTest? + + /// RUM event type + public let type: String = "vital" + + /// User properties + public internal(set) var usr: RUMUser? + + /// The version for this application + public let version: String? + + /// View properties + public var view: View + + /// Vital properties + public let vital: Vital + + enum CodingKeys: String, CodingKey { + case dd = "_dd" + case application = "application" + case buildId = "build_id" + case buildVersion = "build_version" + case ciTest = "ci_test" + case connectivity = "connectivity" + case container = "container" + case context = "context" + case date = "date" + case device = "device" + case display = "display" + case os = "os" + case service = "service" + case session = "session" + case source = "source" + case synthetics = "synthetics" + case type = "type" + case usr = "usr" + case version = "version" + case view = "view" + case vital = "vital" + } + + /// Internal properties + public struct DD: Codable { + /// Browser SDK version + public let browserSdkVersion: String? + + /// Subset of the SDK configuration options in use during its execution + public let configuration: Configuration? + + /// Version of the RUM event format + public let formatVersion: Int64 = 2 + + /// Session-related internal properties + public let session: Session? + + enum CodingKeys: String, CodingKey { + case browserSdkVersion = "browser_sdk_version" + case configuration = "configuration" + case formatVersion = "format_version" + case session = "session" + } + + /// Subset of the SDK configuration options in use during its execution + public struct Configuration: Codable { + /// The percentage of sessions with RUM & Session Replay pricing tracked + public let sessionReplaySampleRate: Double? + + /// The percentage of sessions tracked + public let sessionSampleRate: Double + + enum CodingKeys: String, CodingKey { + case sessionReplaySampleRate = "session_replay_sample_rate" + case sessionSampleRate = "session_sample_rate" + } + } + + /// Session-related internal properties + public struct Session: Codable { + /// Session plan: 1 is the plan without replay, 2 is the plan with replay (deprecated) + public let plan: Plan? + + /// The precondition that led to the creation of the session + public let sessionPrecondition: RUMSessionPrecondition? + + enum CodingKeys: String, CodingKey { + case plan = "plan" + case sessionPrecondition = "session_precondition" + } + + /// Session plan: 1 is the plan without replay, 2 is the plan with replay (deprecated) + public enum Plan: Int, Codable { + case plan1 = 1 + case plan2 = 2 + } + } + } + + /// Application properties + public struct Application: Codable { + /// UUID of the application + public let id: String + + enum CodingKeys: String, CodingKey { + case id = "id" + } + } + + /// View Container properties (view wrapping the current view) + public struct Container: Codable { + /// Source of the parent view + public let source: Source + + /// Attributes of the view's container + public let view: View + + enum CodingKeys: String, CodingKey { + case source = "source" + case view = "view" + } + + /// Source of the parent view + public enum Source: String, Codable { + case android = "android" + case ios = "ios" + case browser = "browser" + case flutter = "flutter" + case reactNative = "react-native" + case roku = "roku" + case unity = "unity" + } + + /// Attributes of the view's container + public struct View: Codable { + /// ID of the parent view + public let id: String + + enum CodingKeys: String, CodingKey { + case id = "id" + } + } + } + + /// Display properties + public struct Display: Codable { + /// The viewport represents the rectangular area that is currently being viewed. Content outside the viewport is not visible onscreen until scrolled into view. + public let viewport: Viewport? + + enum CodingKeys: String, CodingKey { + case viewport = "viewport" + } + + /// The viewport represents the rectangular area that is currently being viewed. Content outside the viewport is not visible onscreen until scrolled into view. + public struct Viewport: Codable { + /// Height of the viewport (in pixels) + public let height: Double + + /// Width of the viewport (in pixels) + public let width: Double + + enum CodingKeys: String, CodingKey { + case height = "height" + case width = "width" + } + } + } + + /// Session properties + public struct Session: Codable { + /// Whether this session has a replay + public let hasReplay: Bool? + + /// UUID of the session + public let id: String + + /// Type of the session + public let type: RUMSessionType + + enum CodingKeys: String, CodingKey { + case hasReplay = "has_replay" + case id = "id" + case type = "type" + } + } + + /// The source of this event + public enum Source: String, Codable { + case android = "android" + case ios = "ios" + case browser = "browser" + case flutter = "flutter" + case reactNative = "react-native" + case roku = "roku" + case unity = "unity" + } + + /// View properties + public struct View: Codable { + /// UUID of the view + public let id: String + + /// User defined name of the view + public var name: String? + + /// URL that linked to the initial view of the page + public var referrer: String? + + /// URL of the view + public var url: String + + enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case referrer = "referrer" + case url = "url" + } + } + + /// Vital properties + public struct Vital: Codable { + /// User custom vital. + public let custom: [String: Double]? + + /// UUID of the vital + public let id: String + + /// Name of the vital, as it is also used as facet path for its value, it must contain only letters, digits, or the characters - _ . @ $ + public let name: String? + + /// Type of the vital + public let type: VitalType + + enum CodingKeys: String, CodingKey { + case custom = "custom" + case id = "id" + case name = "name" + case type = "type" + } + + /// Type of the vital + public enum VitalType: String, Codable { + case duration = "duration" + } + } +} + /// Schema of all properties of a telemetry error event public struct TelemetryErrorEvent: RUMDataModel { /// Internal properties @@ -2935,6 +3358,9 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// Whether untrusted events are allowed public let allowUntrustedEvents: Bool? + /// The threshold used for iOS App Hangs monitoring (in milliseconds) + public let appHangThreshold: Int64? + /// Whether UIApplication background tasks are enabled public let backgroundTasksEnabled: Bool? @@ -3052,6 +3478,9 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// Whether the RUM views creation is handled manually public var trackViewsManually: Bool? + /// The version of Unity used in a Unity application + public var unityVersion: String? + /// Whether the allowed tracing origins list is used (deprecated in favor of use_allowed_tracing_urls) public let useAllowedTracingOrigins: Bool? @@ -3095,6 +3524,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case actionNameAttribute = "action_name_attribute" case allowFallbackToLocalStorage = "allow_fallback_to_local_storage" case allowUntrustedEvents = "allow_untrusted_events" + case appHangThreshold = "app_hang_threshold" case backgroundTasksEnabled = "background_tasks_enabled" case batchProcessingLevel = "batch_processing_level" case batchSize = "batch_size" @@ -3134,6 +3564,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case trackSessionAcrossSubdomains = "track_session_across_subdomains" case trackUserInteractions = "track_user_interactions" case trackViewsManually = "track_views_manually" + case unityVersion = "unity_version" case useAllowedTracingOrigins = "use_allowed_tracing_origins" case useAllowedTracingUrls = "use_allowed_tracing_urls" case useBeforeSend = "use_before_send" @@ -3565,6 +3996,9 @@ public enum RUMMethod: String, Codable { case put = "PUT" case delete = "DELETE" case patch = "PATCH" + case trace = "TRACE" + case options = "OPTIONS" + case connect = "CONNECT" } -// Generated from https://github.com/DataDog/rum-events-format/tree/389581be98dcf8efbfcfe7bffaa32d53f960fb6f +// Generated from https://github.com/DataDog/rum-events-format/tree/63842bcc15dec58b68fc0a57260715f8dfcde330 diff --git a/DatadogRUM/Sources/DataModels/RUMDataModelsMapping.swift b/DatadogRUM/Sources/DataModels/RUMDataModelsMapping.swift index e1f14665f1..0f956b9c2a 100644 --- a/DatadogRUM/Sources/DataModels/RUMDataModelsMapping.swift +++ b/DatadogRUM/Sources/DataModels/RUMDataModelsMapping.swift @@ -5,6 +5,7 @@ */ import Foundation +import DatadogInternal /* Collection of mappings from various types to `RUMDataModel` format. */ @@ -78,3 +79,27 @@ internal extension RUMViewEvent { return Metadata(id: view.id, documentVersion: dd.documentVersion) } } + +internal extension DDThread { + var toRUMDataFormat: RUMErrorEvent.Error.Threads { + return .init( + crashed: crashed, + name: name, + stack: stack, + state: nil + ) + } +} + +internal extension BinaryImage { + var toRUMDataFormat: RUMErrorEvent.Error.BinaryImages { + return .init( + arch: architecture, + isSystem: isSystemLibrary, + loadAddress: loadAddress, + maxAddress: maxAddress, + name: libraryName, + uuid: uuid + ) + } +} diff --git a/DatadogRUM/Sources/Debugging/RUMDebugging.swift b/DatadogRUM/Sources/Debugging/RUMDebugging.swift index 7a8bc6cbf1..28d8b94b8f 100644 --- a/DatadogRUM/Sources/Debugging/RUMDebugging.swift +++ b/DatadogRUM/Sources/Debugging/RUMDebugging.swift @@ -36,7 +36,7 @@ internal class RUMDebugging { // MARK: - Initialization - #if !os(tvOS) + #if !os(tvOS) && !(swift(>=5.9) && os(visionOS)) init() { DispatchQueue.main.async { UIDevice.current.beginGeneratingDeviceOrientationNotifications() @@ -109,7 +109,7 @@ internal class RUMDebugging { canvas.addSubview(view) } if canvas.superview == nil, - let someWindow = UIApplication.dd.managedShared?.keyWindow { + let someWindow = UIApplication.dd.managedShared?.windows.first(where: { $0.isKeyWindow }) { canvas.frame.size = someWindow.bounds.size someWindow.addSubview(canvas) } diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index d88d1e290e..fdb2d0aaa9 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -80,7 +80,11 @@ internal final class RUMFeature: DatadogRemoteFeature { uiKitRUMViewsPredicate: configuration.uiKitViewsPredicate, uiKitRUMActionsPredicate: configuration.uiKitActionsPredicate, longTaskThreshold: configuration.longTaskThreshold, - dateProvider: configuration.dateProvider + appHangThreshold: configuration.appHangThreshold, + mainQueue: configuration.mainQueue, + dateProvider: configuration.dateProvider, + backtraceReporter: core.backtraceReporter, + telemetry: core.telemetry ) self.requestBuilder = RequestBuilder( customIntakeURL: configuration.customEndpoint, @@ -122,6 +126,7 @@ internal final class RUMFeature: DatadogRemoteFeature { // Send configuration telemetry: core.telemetry.configuration( + appHangThreshold: configuration.appHangThreshold?.toInt64Milliseconds, mobileVitalsUpdatePeriod: configuration.vitalsUpdateFrequency?.timeInterval.toInt64Milliseconds, sessionSampleRate: Int64(withNoOverflow: configuration.sessionSampleRate), telemetrySampleRate: Int64(withNoOverflow: configuration.telemetrySampleRate), diff --git a/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsObserver.swift b/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsObserver.swift new file mode 100644 index 0000000000..fddaa01c03 --- /dev/null +++ b/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsObserver.swift @@ -0,0 +1,85 @@ +/* + * 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 +import DatadogInternal + +internal class AppHangsObserver: RUMCommandPublisher { + enum Constants { + /// The standardized `error.message` for RUM errors describing an app hang. + static let appHangErrorMessage = "App Hang" + + /// The standardized `error.type` for RUM errors describing an app hang. + static let appHangErrorType = "AppHang" + + /// The standardized `error.stack` when a backtrace couldn't be generated. + static let appHangNoStackErrorMessage = "Stack trace was not generated because `DatadogCrashReporting` had not been enabled." + } + + /// Watchdog thread that monitors the main queue for App Hangs. + private let watchdogThread: AppHangsWatchdogThread + /// Weak reference to RUM monitor for sending App Hang events. + private(set) weak var subscriber: RUMCommandSubscriber? + + init( + appHangThreshold: TimeInterval, + observedQueue: DispatchQueue, + backtraceReporter: BacktraceReporting, + dateProvider: DateProvider, + telemetry: Telemetry + ) { + watchdogThread = AppHangsWatchdogThread( + appHangThreshold: appHangThreshold, + queue: observedQueue, + dateProvider: dateProvider, + backtraceReporter: backtraceReporter, + telemetry: telemetry + ) + watchdogThread.onHangEnded = { [weak self] appHang in + // called on watchdog thread + self?.report(appHang: appHang) + } + } + + func start() { + watchdogThread.start() + } + + func stop() { + watchdogThread.cancel() + } + + func publish(to subscriber: RUMCommandSubscriber) { + self.subscriber = subscriber + } + + private func report(appHang: AppHang) { + let command = RUMAddCurrentViewAppHangCommand( + time: appHang.date, + attributes: [:], + message: Constants.appHangErrorMessage, + type: Constants.appHangErrorType, + stack: appHang.backtrace?.stack ?? Constants.appHangNoStackErrorMessage, + threads: appHang.backtrace?.threads, + binaryImages: appHang.backtrace?.binaryImages, + isStackTraceTruncated: appHang.backtrace?.wasTruncated, + hangDuration: appHang.duration + ) + + subscriber?.process(command: command) + } +} + +extension AppHangsObserver { + /// Awaits the processing of pending app hang. + /// + /// Note: This method is synchronous and will block the caller thread, in worst case up for `appHangThreshold`. + func flush() { + let semaphore = DispatchSemaphore(value: 0) + watchdogThread.onBeforeSleep = { semaphore.signal() } + semaphore.wait() + } +} diff --git a/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsWatchdogThread.swift b/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsWatchdogThread.swift new file mode 100644 index 0000000000..2b5d0de892 --- /dev/null +++ b/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsWatchdogThread.swift @@ -0,0 +1,166 @@ +/* + * 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 +import DatadogInternal + +internal struct AppHang { + /// The date of hang end. + let date: Date + /// The duration of the hang. + let duration: TimeInterval + /// The snapshot of all running threads during the hang. + /// Might be unavailable if `BacktraceReportingFeature` is not available in core. + let backtrace: BacktraceReport? +} + +internal final class AppHangsWatchdogThread: Thread { + enum Constants { + /// The "idle" interval for sleeping the watchdog thread before scheduling the next task on the main queue, represented as a percentage of the `appHangThreshold`. + /// + /// Introducing even a small sleep significantly reduces CPU consumption by the watchdog thread (nearly 0%). No sleep would result in close to 100% CPU usage. + /// + /// `tolerance` defines the margin of error in hang monitoring. The "measured hang duration" may differ from `0` to `tolerance` % when compared to the "actual hang duration". + /// This means some actual hangs very close to `appHangThreshold` might be considered false negatives and not reported. + /// + /// **Example:** + /// If `appHangThreshold` is 2 seconds and tolerance is 2.5%, the watchdog thread will idle for 50 milliseconds before taking each sample of the main thread. + /// Because the sleep might start right at the moment of hang beginning on the main thread, the watchdog thread will miss the first 50 milliseconds of hang duration. + /// If the actual hang lasts exactly 2 seconds, the watchdog will measure it as 1950 milliseconds. As a result, the SDK will not report it as it falls under `appHangThreshold`. + static let tolerance: Double = 0.025 // 2.5% + } + + /// The minimal duration of the main thread hang to consider it an App Hang. + private let appHangThreshold: TimeInterval + /// The "idle" interval for watchdog thread operations. Reduces the pressure on CPU. + private let idleInterval: TimeInterval + /// Semaphore used to block this thread until main thread responds. + private let mainThreadTask = DispatchSemaphore(value: 0) + /// The queue to observe by this thread (main queue). + private let mainQueue: DispatchQueue + /// SDK date provider. + private let dateProvider: DateProvider + /// Backtrace reporter for hang's stack trace generation. + private let backtraceReporter: BacktraceReporting + /// An identifier of the main thread required for backtrace generation. + /// Because backtrace is generated from the watchdog thread, we must identify the main thread to be promoted in `BacktraceReport`. This value is + /// obtained at runtime, only once and it is cached for later use. + @ReadWriteLock + private var mainThreadID: ThreadID? = nil + /// Telemetry interface. + private let telemetry: Telemetry + /// Closure to be notified when App Hang ends. It will be executed on the watchdog thread. + @ReadWriteLock + internal var onHangEnded: ((AppHang) -> Void)? + /// A block called after this thread finished its pass and will become idle. + @ReadWriteLock + internal var onBeforeSleep: (() -> Void)? + + /// Creates an instance of an App Hang watchdog thread. + /// + /// This thread is not started by default and requires manual invocation of `.start()`. + /// + /// - Parameters: + /// - appHangThreshold: Minimum duration of the `queue` hang to consider it an App Hang. + /// - queue: The queue to observe for hangs. (main queue) + /// - dateProvider: Date provider. + /// - backtraceReporter: Backtrace reporter for hang's stack trace generation. + /// - telemetry: The handler to report issues through RUM Telemetry. + init( + appHangThreshold: TimeInterval, + queue: DispatchQueue, + dateProvider: DateProvider, + backtraceReporter: BacktraceReporting, + telemetry: Telemetry + ) { + self.appHangThreshold = appHangThreshold + self.idleInterval = appHangThreshold * Constants.tolerance + self.mainQueue = queue + self.dateProvider = dateProvider + self.backtraceReporter = backtraceReporter + self.telemetry = telemetry + + super.init() + self.name = "com.datadoghq.app-hang-watchdog" + + if Thread.isMainThread { + // When initialization happens on the main thread, we can get its `ThreadID` right away, so startup hangs are covered + self.mainThreadID = Thread.currentThreadID + } else { + // Otherwise, schedule async task to capture the main `ThreadID`. This task will execute before any other `mainThreadTask` + // scheduled by watchdog thread (in `main()`), making sure the thread ID is available as soon as possible. + self.mainQueue.async { [weak self] in + self?.mainThreadID = Thread.currentThreadID + } + } + } + + override func main() { + let mainThreadTask = self.mainThreadTask + + while !isCancelled { + defer { + // Notify that thread finished its next pass and will be put to IDLE + onBeforeSleep?() + // Sleep (become idle) to reduce the CPU consumption by watchdog thread: + Thread.sleep(forTimeInterval: idleInterval) + } + + let waitStart: DispatchTime = .now() + + // Schedule task on the main thread to measure how fast it responds + mainQueue.async { + mainThreadTask.signal() + } + + // Await task completion + let result = mainThreadTask.wait(timeout: waitStart + appHangThreshold) + + if result == .success { + // This is predominant case of "no hang" situation. The main thread executed the task way below hang threshold. + continue + } // Otherwise, `result == .timeOut`, meaning that the main thread task is delayed to more than hang threshold. + + // We expect to be here roughtly `~appHangThreshold` after `wait()`. If this code is executed well after (50% margin), + // assume that the thread was suspended and app just woke up. In such case, ignore the hang as likely false-positive. + if interval(from: waitStart, to: .now()) > appHangThreshold * 1.5 { + DD.logger.debug("Ignoring likely false-positive App Hang") + continue // ignore likely false-positive + } + + // Capture the stack trace of all running threads with promoting the main thread stack. + guard let mainThreadID = mainThreadID else { + telemetry.error("Failed to determine main thread ID for backtrace generation") + continue // unexpected + } + let backtrace = backtraceReporter.generateBacktrace(threadID: mainThreadID) + + // Previous wait timed out, so wait again for the task completion, this time infinitely until the hang ends. + mainThreadTask.wait() + + // The hang has finished. + let hangDuration = interval(from: waitStart, to: .now()) + + if hangDuration > 30 { // sanity-check + // If the hang duration is irrealistically long (way more than assumed iOS watchdog termination limit: ~10s), send telemetry. + // This could be another false-positive caused by thread suspension between the two `wait()` calls. + telemetry.debug("Detected an App Hang with an unusually long duration", attributes: ["hang_duration": hangDuration]) + continue + } + + let appHang = AppHang( + date: dateProvider.now, + duration: hangDuration, + backtrace: backtrace + ) + onHangEnded?(appHang) + } + } + + private func interval(from t1: DispatchTime, to t2: DispatchTime) -> TimeInterval { + TimeInterval(t2.uptimeNanoseconds - t1.uptimeNanoseconds) / 1_000_000_000 + } +} diff --git a/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift b/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift index 22a322a8ed..af98bc1a3b 100644 --- a/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift +++ b/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift @@ -9,6 +9,13 @@ import DatadogInternal /// Bundles RUM instrumentation components. internal final class RUMInstrumentation: RUMCommandPublisher { + private enum Constants { + /// Minimum allowed value for long task threshold configuration. + static let minLongTaskThreshold: TimeInterval = 0 + /// Minimum allowed value for app hang threshold configuration. + static let minAppHangThreshold: TimeInterval = 0.1 + } + /// Swizzles `UIViewController` for intercepting its lifecycle callbacks. /// It is `nil` (no swizzling) if RUM View instrumentaiton is not enabled. let viewControllerSwizzler: UIViewControllerSwizzler? @@ -26,13 +33,20 @@ internal final class RUMInstrumentation: RUMCommandPublisher { /// Instruments RUM Long Tasks. It is `nil` if long tasks tracking is not enabled. let longTasks: LongTaskObserver? + /// Instruments App Hangs. It is `nil` if hangs monitoring is not enabled. + let appHangs: AppHangsObserver? + // MARK: - Initialization init( uiKitRUMViewsPredicate: UIKitRUMViewsPredicate?, uiKitRUMActionsPredicate: UIKitRUMActionsPredicate?, longTaskThreshold: TimeInterval?, - dateProvider: DateProvider + appHangThreshold: TimeInterval?, + mainQueue: DispatchQueue, + dateProvider: DateProvider, + backtraceReporter: BacktraceReporting, + telemetry: Telemetry ) { // Always create views handler (we can't know if it will be used by SwiftUI instrumentation) // and only swizzle `UIViewController` if UIKit instrumentation is configured: @@ -43,8 +57,9 @@ internal final class RUMInstrumentation: RUMCommandPublisher { var actionsHandler: UIKitRUMUserActionsHandler? = nil var uiApplicationSwizzler: UIApplicationSwizzler? = nil - // Create long tasks observer only if configured: + // Create long tasks and app hang observers only if configured: var longTasks: LongTaskObserver? = nil + var appHangs: AppHangsObserver? = nil do { if uiKitRUMViewsPredicate != nil { @@ -71,8 +86,27 @@ internal final class RUMInstrumentation: RUMCommandPublisher { ) } - if let threshold = longTaskThreshold, threshold > 0 { - longTasks = LongTaskObserver(threshold: threshold, dateProvider: dateProvider) + if let longTaskThreshold = longTaskThreshold { + if longTaskThreshold > Constants.minLongTaskThreshold { + longTasks = LongTaskObserver(threshold: longTaskThreshold, dateProvider: dateProvider) + } else { + DD.logger.error("`RUM.Configuration.longTaskThreshold` cannot be less than 0s. Long Tasks monitoring will be disabled.") + } + } + + if var appHangThreshold = appHangThreshold { + if appHangThreshold < Constants.minAppHangThreshold { + appHangThreshold = Constants.minAppHangThreshold + DD.logger.warn("`RUM.Configuration.appHangThreshold` cannot be less than \(Constants.minAppHangThreshold)s. A value of \(Constants.minAppHangThreshold)s will be used.") + } + + appHangs = AppHangsObserver( + appHangThreshold: appHangThreshold, + observedQueue: mainQueue, + backtraceReporter: backtraceReporter, + dateProvider: dateProvider, + telemetry: telemetry + ) } self.viewsHandler = viewsHandler @@ -80,11 +114,13 @@ internal final class RUMInstrumentation: RUMCommandPublisher { self.actionsHandler = actionsHandler self.uiApplicationSwizzler = uiApplicationSwizzler self.longTasks = longTasks + self.appHangs = appHangs // Enable configured instrumentations: self.viewControllerSwizzler?.swizzle() self.uiApplicationSwizzler?.swizzle() self.longTasks?.start() + self.appHangs?.start() } deinit { @@ -92,11 +128,13 @@ internal final class RUMInstrumentation: RUMCommandPublisher { viewControllerSwizzler?.unswizzle() uiApplicationSwizzler?.unswizzle() longTasks?.stop() + appHangs?.stop() } func publish(to subscriber: RUMCommandSubscriber) { viewsHandler.publish(to: subscriber) actionsHandler?.publish(to: subscriber) longTasks?.publish(to: subscriber) + appHangs?.publish(to: subscriber) } } diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index ac88d96f75..185a4dc2af 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -165,7 +165,12 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { // RUMM-2516 if a cross-platform crash was reported, do not send its native version if let lastRUMViewEvent = context.lastRUMViewEvent { if lastRUMViewEvent.view.crash?.count ?? 0 < 1 { - sendCrashReportLinkedToLastViewInPreviousSession(report, lastRUMViewEventInPreviousSession: lastRUMViewEvent, using: adjustedCrashTimings, to: core) + sendCrashReportLinkedToLastViewInPreviousSession( + report, + lastRUMViewEventInPreviousSession: lastRUMViewEvent, + using: adjustedCrashTimings, + to: core + ) } else { DD.logger.debug("There was a crash in previous session, but it is ignored due to another crash already present in the last view.") return false @@ -196,8 +201,8 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { // We know it is too late for sending RUM view to previous RUM session as it is now stale on backend. // To avoid inconsistency, we only send the RUM error. DD.logger.debug("Sending crash as RUM error.") - let rumError = createRUMError(from: crashReport, and: lastRUMViewEvent, crashDate: crashTimings.realCrashDate) - core.scope(for: RUMFeature.name)?.eventWriteContext(bypassConsent: true) { _, writer in + core.scope(for: RUMFeature.name)?.eventWriteContext(bypassConsent: true) { context, writer in + let rumError = createRUMError(from: crashReport, and: lastRUMViewEvent, crashDate: crashTimings.realCrashDate, sourceType: context.nativeSourceOverride) writer.write(value: rumError) } } @@ -313,11 +318,12 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { private func send(crashReport: CrashReport, to rumView: RUMViewEvent, using realCrashDate: Date, to core: DatadogCoreProtocol) { DD.logger.debug("Updating RUM view with crash report.") let updatedRUMView = updateRUMViewWithNewError(rumView, crashDate: realCrashDate) - let rumError = createRUMError(from: crashReport, and: updatedRUMView, crashDate: realCrashDate) // crash reporting is considering the user consent from previous session, if an event reached // the message bus it means that consent was granted and we can safely bypass current consent. - core.scope(for: RUMFeature.name)?.eventWriteContext(bypassConsent: true) { _, writer in + core.scope(for: RUMFeature.name)?.eventWriteContext(bypassConsent: true) { context, writer in + let rumError = createRUMError(from: crashReport, and: updatedRUMView, crashDate: realCrashDate, sourceType: context.nativeSourceOverride) + writer.write(value: rumError) writer.write(value: updatedRUMView) } @@ -326,7 +332,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { // MARK: - Building RUM events /// Creates RUM error based on the session information from `lastRUMViewEvent` and `DDCrashReport` details. - private func createRUMError(from crashReport: CrashReport, and lastRUMView: RUMViewEvent, crashDate: Date) -> RUMCrashEvent { + private func createRUMError(from crashReport: CrashReport, and lastRUMView: RUMViewEvent, crashDate: Date, sourceType: String?) -> RUMCrashEvent { let errorType = crashReport.type let errorMessage = crashReport.message let errorStackTrace = crashReport.stack @@ -337,6 +343,13 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { errorAttributes[DDError.meta] = crashReport.meta errorAttributes[DDError.wasTruncated] = crashReport.wasTruncated + let rumSourceType: RUMErrorSourceType + if let sourceType = sourceType { + rumSourceType = RUMErrorSourceType(rawValue: sourceType) ?? .ios + } else { + rumSourceType = .ios + } + let event = RUMErrorEvent( dd: .init( browserSdkVersion: nil, @@ -358,17 +371,23 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { device: lastRUMView.device, display: nil, error: .init( + binaryImages: nil, + category: .exception, // crashes are categorised as "Exception" handling: nil, handlingStack: nil, id: nil, isCrash: true, message: errorMessage, + meta: nil, resource: nil, source: .source, - sourceType: .ios, + sourceType: rumSourceType, stack: errorStackTrace, - type: errorType + threads: nil, + type: errorType, + wasTruncated: nil ), + freeze: nil, os: lastRUMView.os, service: lastRUMView.service, session: .init( diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index c6c5cd7533..26bbfcdb7b 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -260,6 +260,7 @@ private extension TelemetryConfigurationEvent.Telemetry.Configuration { actionNameAttribute: nil, allowFallbackToLocalStorage: nil, allowUntrustedEvents: nil, + appHangThreshold: configuration.appHangThreshold, backgroundTasksEnabled: configuration.backgroundTasksEnabled, batchProcessingLevel: configuration.batchProcessingLevel, batchSize: configuration.batchSize, diff --git a/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift b/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift index 1139914de6..72967a56f1 100644 --- a/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift +++ b/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift @@ -11,12 +11,6 @@ internal typealias JSON = [String: Any] /// Receiver to consume a RUM event coming from Browser SDK. internal final class WebViewEventReceiver: FeatureMessageReceiver { - /// Defines keys referencing Browser event message on the bus. - enum MessageKeys { - /// The key references a browser event message. - static let browserEvent = "browser-rum-event" - } - /// Subscriber that can process a `RUMKeepSessionAliveCommand`. let commandSubscriber: RUMCommandSubscriber @@ -37,23 +31,12 @@ internal final class WebViewEventReceiver: FeatureMessageReceiver { } func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - do { - guard case let .baggage(label, baggage) = message, label == MessageKeys.browserEvent else { - return false - } - - guard let event = try baggage.encode() as? JSON else { - throw InternalError(description: "Event is not a dictionary") - } - - write(event: event, to: core) - return true - } catch { - core.telemetry - .error("Fails to decode browser event from RUM", error: error) + guard case let .webview(.rum(event)) = message else { + return false } - return false + write(event: event, to: core) + return true } /// Writes a Browser RUM event to the core. @@ -80,7 +63,7 @@ internal final class WebViewEventReceiver: FeatureMessageReceiver { let rum: RUMCoreContext = try rumBaggage.decode() var event = event - if let date = event["date"] as? Int64 { + if let date = event["date"] as? Int { let viewID = (event["view"] as? JSON)?["id"] as? String let serverTimeOffsetInMs = self.getOffsetInMs(viewID: viewID, context: context) let correctedDate = Int64(date) + serverTimeOffsetInMs diff --git a/DatadogRUM/Sources/RUMConfiguration.swift b/DatadogRUM/Sources/RUMConfiguration.swift index e1c4824ca3..7eea40eb45 100644 --- a/DatadogRUM/Sources/RUMConfiguration.swift +++ b/DatadogRUM/Sources/RUMConfiguration.swift @@ -118,6 +118,20 @@ extension RUM { /// Default: `0.1`. public var longTaskThreshold: TimeInterval? + /// Enables App Hangs monitoring with the given threshold (in seconds). + /// + /// Only App Hangs that last more than this threshold will be reported. The minimal allowed value for this option is `0.1` seconds. + /// To disable hangs monitoring, set this parameter to `nil`. + /// + /// - Note: Be cautious when setting the threshold to very small values, as it may lead to excessive reporting of hangs. + /// The SDK implements a secondary thread for monitoring App Hangs. To reduce CPU utilization, it tracks hangs with a tolerance of 2.5%, meaning that + /// some hangs lasting very close to this threshold may not be reported. + /// + /// - Note: App Hangs monitoring requires Datadog Crash Reporting to be enabled. Otherwise stack trace will be not reported in App Hang errors. + /// + /// - Default: `nil` (hangs monitoring disabled). + public var appHangThreshold: TimeInterval? + /// Sets the preferred frequency for collecting RUM vitals. /// /// To disable RUM vitals monitoring, set `nil`. @@ -221,6 +235,8 @@ extension RUM { /// attributes for RUM resource based on the provided request, response, data, and error. /// Keep the implementation fast and do not make any assumptions on the thread used to run it. /// + /// Note: This is not supported for async-await APIs. + /// /// Default: `nil`. public var resourceAttributesProvider: RUM.ResourceAttributesProvider? @@ -256,6 +272,8 @@ extension RUM { internal var traceIDGenerator: TraceIDGenerator = DefaultTraceIDGenerator() internal var dateProvider: DateProvider = SystemDateProvider() + /// The main queue, subject to App Hangs monitoring. + internal var mainQueue: DispatchQueue = .main internal var debugSDK: Bool = ProcessInfo.processInfo.arguments.contains(LaunchArguments.Debug) internal var debugViews: Bool = ProcessInfo.processInfo.arguments.contains("DD_DEBUG_RUM") @@ -306,6 +324,7 @@ extension RUM.Configuration { /// - trackFrustrations: Determines whether automatic tracking of user frustrations should be enabled. Default: `true`. /// - trackBackgroundEvents: Determines whether RUM events should be tracked when no view is active. Default: `false`. /// - longTaskThreshold: The threshold for RUM long tasks tracking (in seconds). Default: `0.1`. + /// - appHangThreshold: The threshold for App Hangs monitoring (in seconds). Default: `nil`. /// - vitalsUpdateFrequency: The preferred frequency for collecting RUM vitals. Default: `.average`. /// - viewEventMapper: Custom mapper for RUM view events. Default: `nil`. /// - resourceEventMapper: Custom mapper for RUM resource events. Default: `nil`. @@ -324,6 +343,7 @@ extension RUM.Configuration { trackFrustrations: Bool = true, trackBackgroundEvents: Bool = false, longTaskThreshold: TimeInterval? = 0.1, + appHangThreshold: TimeInterval? = nil, vitalsUpdateFrequency: VitalsFrequency? = .average, viewEventMapper: RUM.ViewEventMapper? = nil, resourceEventMapper: RUM.ResourceEventMapper? = nil, @@ -342,6 +362,7 @@ extension RUM.Configuration { self.trackFrustrations = trackFrustrations self.trackBackgroundEvents = trackBackgroundEvents self.longTaskThreshold = longTaskThreshold + self.appHangThreshold = appHangThreshold self.vitalsUpdateFrequency = vitalsUpdateFrequency self.viewEventMapper = viewEventMapper self.resourceEventMapper = resourceEventMapper diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index 7ba3cdd1ab..b9439a0c16 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -63,14 +63,14 @@ internal extension RUMResourceType { internal typealias RUMErrorSourceType = RUMErrorEvent.Error.SourceType internal extension RUMErrorSourceType { - static func extract(from attributes: inout [AttributeKey: AttributeValue]) -> Self { + static func extract(from attributes: inout [AttributeKey: AttributeValue]) -> RUMErrorSourceType? { return (attributes.removeValue(forKey: CrossPlatformAttributes.errorSourceType)) .flatMap({ $0.decoded() }) .flatMap { return RUMErrorEvent.Error.SourceType(rawValue: $0) - } ?? .ios + } } } @@ -106,6 +106,9 @@ internal enum RUMInternalErrorSource: String, Decodable { } } +/// A mobile-specific category of the error. It provides a high-level grouping for different types of errors. +internal typealias RUMErrorCategory = RUMErrorEvent.Error.Category + internal class Monitor: RUMCommandSubscriber { let scopes: RUMApplicationScope let dateProvider: DateProvider diff --git a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift index c40648f56c..e2cf62c36b 100644 --- a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift +++ b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift @@ -87,25 +87,51 @@ internal struct RUMStopViewCommand: RUMCommand, RUMViewScopePropagatableAttribut let identity: ViewIdentifier } -internal struct RUMAddCurrentViewErrorCommand: RUMCommand { +/// Any error command, like exception or App Hang. +internal protocol RUMErrorCommand: RUMCommand { + /// The error message. + var message: String { get } + /// Error type. + var type: String? { get } + /// Error stacktrace. + var stack: String? { get } + /// Mobile-specific category of the error to empower high-level grouping of different types of errors. + var category: RUMErrorCategory { get } + /// Whether this error has crashed the host application + var isCrash: Bool? { get } + /// The origin of this error. + var source: RUMInternalErrorSource { get } + /// The platform type of the error (iOS, React Native, ...) + var errorSourceType: RUMErrorEvent.Error.SourceType { get } + /// An information about the threads currently running in the process. + var threads: [DDThread]? { get } + /// The list of binary images referenced from `stack` and `threads`. + var binaryImages: [BinaryImage]? { get } + /// Indicates whether any stack trace information in `stack` or `threads` was truncated due to stack trace minimization. + var isStackTraceTruncated: Bool? { get } +} + +/// Adds exception error to current view. +/// +/// Using this command results with classifying the error as "Exception" in Datadog app (`@error.category: Exception`). +internal struct RUMAddCurrentViewErrorCommand: RUMErrorCommand { var time: Date var attributes: [AttributeKey: AttributeValue] let canStartBackgroundView = true // yes, we want to track errors in "Background" view let isUserInteraction = false // an error is not an interactive event - /// The error message. let message: String - /// Error type. let type: String? - /// Error stacktrace. let stack: String? - /// Whether this error crashed the host application + let category: RUMErrorCategory = .exception let isCrash: Bool? - /// The origin of this error. let source: RUMInternalErrorSource - /// The platform type of the error (iOS, React Native, ...) let errorSourceType: RUMErrorEvent.Error.SourceType + let threads: [DDThread]? + let binaryImages: [BinaryImage]? + let isStackTraceTruncated: Bool? + /// Constructor dedicated to errors defined by message, type and stack. init( time: Date, message: String, @@ -114,35 +140,95 @@ internal struct RUMAddCurrentViewErrorCommand: RUMCommand { source: RUMInternalErrorSource, attributes: [AttributeKey: AttributeValue] ) { - self.time = time - self.source = source - self.attributes = attributes - self.message = message - self.type = type - self.stack = stack - - self.errorSourceType = RUMErrorSourceType.extract(from: &self.attributes) - self.isCrash = self.attributes.removeValue(forKey: CrossPlatformAttributes.errorIsCrash)?.decoded() + self.init( + time: time, + message: message, + type: type, + stack: stack, + source: source, + isCrash: nil, + threads: nil, + binaryImages: nil, + isStackTraceTruncated: nil, + attributes: attributes + ) } + /// Constructor dedicated to errors defined by `Error` object. init( time: Date, error: Error, source: RUMInternalErrorSource, attributes: [AttributeKey: AttributeValue] ) { + let dderror = DDError(error: error) + self.init( + time: time, + message: dderror.message, + type: dderror.type, + stack: dderror.stack, + source: source, + isCrash: nil, + threads: nil, + binaryImages: nil, + isStackTraceTruncated: nil, + attributes: attributes + ) + } + + /// Broad constructor for all kinds of errors. + init( + time: Date, + message: String, + type: String?, + stack: String?, + source: RUMInternalErrorSource, + isCrash: Bool?, + threads: [DDThread]?, + binaryImages: [BinaryImage]?, + isStackTraceTruncated: Bool?, + attributes: [AttributeKey: AttributeValue] + ) { + var attributes = attributes + let isCrossPlatformCrash: Bool? = attributes.removeValue(forKey: CrossPlatformAttributes.errorIsCrash)?.decoded() + let crossPlatformSourceType = RUMErrorSourceType.extract(from: &attributes) + self.time = time - self.source = source self.attributes = attributes + self.message = message + self.type = type + self.stack = stack + self.isCrash = isCrossPlatformCrash ?? isCrash + self.source = source + self.errorSourceType = crossPlatformSourceType ?? .ios + self.threads = threads + self.binaryImages = binaryImages + self.isStackTraceTruncated = isStackTraceTruncated + } +} - let dderror = DDError(error: error) - self.message = dderror.message - self.type = dderror.type - self.stack = dderror.stack +/// Adds App Hang error to current view. +/// +/// Using this command results with classifying the error as "App Hang" in Datadog app (`@error.category: App Hang`). +internal struct RUMAddCurrentViewAppHangCommand: RUMErrorCommand { + var time: Date + var attributes: [AttributeKey: AttributeValue] + let canStartBackgroundView = false // no, we don't want to track App Hangs in "Background" view + let isUserInteraction = false // an error is not an interactive event - self.errorSourceType = RUMErrorSourceType.extract(from: &self.attributes) - self.isCrash = self.attributes.removeValue(forKey: CrossPlatformAttributes.errorIsCrash) as? Bool - } + let message: String + let type: String? + let stack: String? + let category: RUMErrorCategory = .appHang + let isCrash: Bool? = false + let source: RUMInternalErrorSource = .source + let errorSourceType: RUMErrorEvent.Error.SourceType = .ios + let threads: [DDThread]? + let binaryImages: [BinaryImage]? + let isStackTraceTruncated: Bool? + + /// The duration of hang. + let hangDuration: TimeInterval } internal struct RUMAddViewTimingCommand: RUMCommand, RUMViewScopePropagatableAttributes { @@ -256,7 +342,7 @@ internal struct RUMStopResourceWithErrorCommand: RUMResourceCommand { // The stack will be meaningless in most cases as it will go down to the networking code: self.stack = nil - self.errorSourceType = RUMErrorSourceType.extract(from: &self.attributes) + self.errorSourceType = RUMErrorSourceType.extract(from: &self.attributes) ?? .ios } init( @@ -279,7 +365,7 @@ internal struct RUMStopResourceWithErrorCommand: RUMResourceCommand { // The stack will give the networking error (`NSError`) description in most cases: self.stack = dderror.stack - self.errorSourceType = RUMErrorSourceType.extract(from: &self.attributes) + self.errorSourceType = RUMErrorSourceType.extract(from: &self.attributes) ?? .ios } } diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift index fd350ff75a..0880eec843 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift @@ -276,11 +276,14 @@ internal class RUMResourceScope: RUMScope { device: .init(context: context, telemetry: dependencies.telemetry), display: nil, error: .init( + binaryImages: nil, + category: .exception, // resource errors are categorised as "Exception" handling: nil, handlingStack: nil, id: nil, isCrash: false, message: command.errorMessage, + meta: nil, resource: .init( method: resourceHTTPMethod, provider: errorEventProvider, @@ -290,8 +293,11 @@ internal class RUMResourceScope: RUMScope { source: command.errorSource.toRUMDataFormat, sourceType: command.errorSourceType, stack: command.stack, - type: command.errorType + threads: nil, + type: command.errorType, + wasTruncated: nil ), + freeze: nil, os: .init(context: context), service: context.service, session: .init( diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift index 87be30bcce..aafe5021a2 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift @@ -116,7 +116,7 @@ internal class RUMUserActionScope: RUMScope, RUMContextProvider { case is RUMStopResourceWithErrorCommand: activeResourcesCount -= 1 errorsCount += 1 - case is RUMAddCurrentViewErrorCommand: + case is RUMErrorCommand: errorsCount += 1 case is RUMAddLongTaskCommand: // TODO: RUMM-1616 this command is ignored if arrived after 100ms diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index e0b5a743e8..cae78801a6 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -219,7 +219,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { } // Error command - case let command as RUMAddCurrentViewErrorCommand where isActiveView: + case let command as RUMErrorCommand where isActiveView: sendErrorEvent(on: command, context: context, writer: writer) case let command as RUMAddLongTaskCommand where isActiveView: @@ -439,7 +439,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { attributes.merge(rumCommandAttributes: command.attributes) } - let isCrash = (command as? RUMAddCurrentViewErrorCommand).map { $0.isCrash ?? false } ?? false + let isCrash = (command as? RUMErrorCommand).map { $0.isCrash ?? false } ?? false // RUMM-1779 Keep view active as long as we have ongoing resources let isActive = isActiveView || !resourceScopes.isEmpty // RUMM-2079 `time_spent` can't be lower than 1ns @@ -558,7 +558,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { } } - private func sendErrorEvent(on command: RUMAddCurrentViewErrorCommand, context: DatadogContext, writer: Writer) { + private func sendErrorEvent(on command: RUMErrorCommand, context: DatadogContext, writer: Writer) { errorsCount += 1 let errorEvent = RUMErrorEvent( @@ -584,19 +584,27 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { device: .init(context: context, telemetry: dependencies.telemetry), display: nil, error: .init( + binaryImages: command.binaryImages?.compactMap { $0.toRUMDataFormat }, + category: command.category, causes: nil, handling: nil, handlingStack: nil, id: nil, isCrash: command.isCrash ?? false, message: command.message, + meta: nil, resource: nil, source: command.source.toRUMDataFormat, sourceType: command.errorSourceType, stack: command.stack, - type: command.type + threads: command.threads?.compactMap { $0.toRUMDataFormat }, + type: command.type, + wasTruncated: command.isStackTraceTruncated ), featureFlags: .init(featureFlagsInfo: featureFlags), + freeze: (command as? RUMAddCurrentViewAppHangCommand).map { appHangCommand in + .init(duration: appHangCommand.hangDuration.toInt64Nanoseconds) + }, os: .init(context: context), service: context.service, session: .init( diff --git a/DatadogRUM/Sources/RUMVitals/VitalInfoSampler.swift b/DatadogRUM/Sources/RUMVitals/VitalInfoSampler.swift index d251af28ce..4284679bb2 100644 --- a/DatadogRUM/Sources/RUMVitals/VitalInfoSampler.swift +++ b/DatadogRUM/Sources/RUMVitals/VitalInfoSampler.swift @@ -41,12 +41,22 @@ internal final class VitalInfoSampler { private var timer: Timer? + private static var maximumFramesPerSecond: Double { + get { + #if swift(>=5.9) && os(visionOS) + return 120.0 // Hardcoded to enable VisionOS compilation + #else + return Double(UIScreen.main.maximumFramesPerSecond) + #endif + } + } + init( cpuReader: SamplingBasedVitalReader, memoryReader: SamplingBasedVitalReader, refreshRateReader: ContinuousVitalReader, frequency: TimeInterval, - maximumRefreshRate: Double = Double(UIScreen.main.maximumFramesPerSecond) + maximumRefreshRate: Double = VitalInfoSampler.maximumFramesPerSecond ) { self.cpuReader = cpuReader diff --git a/DatadogRUM/Sources/RUMVitals/VitalRefreshRateReader.swift b/DatadogRUM/Sources/RUMVitals/VitalRefreshRateReader.swift index ea44a787cb..5ee6ee52af 100644 --- a/DatadogRUM/Sources/RUMVitals/VitalRefreshRateReader.swift +++ b/DatadogRUM/Sources/RUMVitals/VitalRefreshRateReader.swift @@ -171,7 +171,11 @@ extension FrameInfoProvider { extension CADisplayLink: FrameInfoProvider { var maximumDeviceFramesPerSecond: Int { + #if swift(>=5.9) && os(visionOS) + 120 + #else UIScreen.main.maximumFramesPerSecond + #endif } var currentFrameTimestamp: CFTimeInterval { diff --git a/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsWatchdogThreadTests.swift b/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsWatchdogThreadTests.swift new file mode 100644 index 0000000000..f43788b6bc --- /dev/null +++ b/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsWatchdogThreadTests.swift @@ -0,0 +1,119 @@ +/* + * 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 TestUtilities +import DatadogInternal +@testable import DatadogRUM + +class AppHangsWatchdogThreadTests: XCTestCase { + func testWhenQueueHangsAboveThreshold_itReportsAppHangs() { + let trackHangs = expectation(description: "track 3 App Hangs") + trackHangs.expectedFulfillmentCount = 3 + + // Given + let appHangThreshold: TimeInterval = 0.1 + let hangDuration: TimeInterval = appHangThreshold * 2 + let queue = DispatchQueue(label: "main-queue", qos: .userInteractive) + + let watchdogThread = AppHangsWatchdogThread( + appHangThreshold: appHangThreshold, + queue: queue, + dateProvider: SystemDateProvider(), + backtraceReporter: BacktraceReporterMock(), + telemetry: TelemetryMock() + ) + watchdogThread.onHangEnded = { _ in + trackHangs.fulfill() + } + watchdogThread.start() + + // When (multiple hangs above threshold) + queue.async { + Thread.sleep(forTimeInterval: hangDuration) + queue.async { // async from queue so watchdog thread can interleve with its own tasks + Thread.sleep(forTimeInterval: hangDuration) + queue.async { + Thread.sleep(forTimeInterval: hangDuration) + } + } + } + + // Then + waitForExpectations(timeout: hangDuration * 10) + watchdogThread.cancel() + } + + func testWhenQueueHangsBelowThreshold_itDoesNotReportAppHangs() { + let doNotTrackHangs = invertedExpectation(description: "track no App Hangs") + + // Given + let appHangThreshold: TimeInterval = 0.5 + let hangDuration: TimeInterval = appHangThreshold * 0.1 + let queue = DispatchQueue(label: "main-queue", qos: .userInteractive) + + let watchdogThread = AppHangsWatchdogThread( + appHangThreshold: appHangThreshold, + queue: queue, + dateProvider: SystemDateProvider(), + backtraceReporter: BacktraceReporterMock(), + telemetry: TelemetryMock() + ) + watchdogThread.onHangEnded = { _ in + doNotTrackHangs.fulfill() + } + watchdogThread.start() + + // When (multiple hangs below threshold) + queue.async { + Thread.sleep(forTimeInterval: hangDuration) + queue.async { // async from queue so watchdog thread can interleve with its own tasks + Thread.sleep(forTimeInterval: hangDuration) + queue.async { + Thread.sleep(forTimeInterval: hangDuration) + } + } + } + + // Then + waitForExpectations(timeout: hangDuration * 10) + watchdogThread.cancel() + } + + func testItTracksHangDateStackAndDuration() { + let trackHang = expectation(description: "track App Hang") + + // Given + let appHangThreshold: TimeInterval = 0.5 + let hangDuration: TimeInterval = appHangThreshold * 2 + let queue = DispatchQueue(label: "main-queue", qos: .userInteractive) + + let watchdogThread = AppHangsWatchdogThread( + appHangThreshold: appHangThreshold, + queue: queue, + dateProvider: DateProviderMock(now: .mockDecember15th2019At10AMUTC()), + backtraceReporter: BacktraceReporterMock(backtrace: .mockWith(stack: "Main thread stack")), + telemetry: TelemetryMock() + ) + watchdogThread.onHangEnded = { hang in + XCTAssertEqual(hang.date, .mockDecember15th2019At10AMUTC()) + XCTAssertEqual(hang.backtrace?.stack, "Main thread stack") + XCTAssertGreaterThanOrEqual(hang.duration, hangDuration * (1 - AppHangsWatchdogThread.Constants.tolerance)) + XCTAssertLessThanOrEqual(hang.duration, hangDuration * (1 + AppHangsWatchdogThread.Constants.tolerance)) + trackHang.fulfill() + } + watchdogThread.start() + + // When + queue.async { + Thread.sleep(forTimeInterval: hangDuration) + } + + // Then + waitForExpectations(timeout: hangDuration * 10) + watchdogThread.cancel() + } +} diff --git a/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift b/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift index ea6471f8d9..73ed980d84 100644 --- a/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift +++ b/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift @@ -18,7 +18,11 @@ class RUMInstrumentationTests: XCTestCase { uiKitRUMViewsPredicate: UIKitRUMViewsPredicateMock(), uiKitRUMActionsPredicate: nil, longTaskThreshold: nil, - dateProvider: SystemDateProvider() + appHangThreshold: .mockAny(), + mainQueue: .main, + dateProvider: SystemDateProvider(), + backtraceReporter: BacktraceReporterMock(), + telemetry: NOPTelemetry() ) // Then @@ -37,7 +41,11 @@ class RUMInstrumentationTests: XCTestCase { uiKitRUMViewsPredicate: nil, uiKitRUMActionsPredicate: UIKitRUMActionsPredicateMock(), longTaskThreshold: nil, - dateProvider: SystemDateProvider() + appHangThreshold: .mockAny(), + mainQueue: .main, + dateProvider: SystemDateProvider(), + backtraceReporter: BacktraceReporterMock(), + telemetry: NOPTelemetry() ) // Then @@ -53,7 +61,11 @@ class RUMInstrumentationTests: XCTestCase { uiKitRUMViewsPredicate: nil, uiKitRUMActionsPredicate: nil, longTaskThreshold: 0.5, - dateProvider: SystemDateProvider() + appHangThreshold: .mockAny(), + mainQueue: .main, + dateProvider: SystemDateProvider(), + backtraceReporter: BacktraceReporterMock(), + telemetry: NOPTelemetry() ) // Then @@ -72,7 +84,11 @@ class RUMInstrumentationTests: XCTestCase { uiKitRUMViewsPredicate: nil, uiKitRUMActionsPredicate: nil, longTaskThreshold: .mockRandom(min: -100, max: 0), - dateProvider: SystemDateProvider() + appHangThreshold: .mockAny(), + mainQueue: .main, + dateProvider: SystemDateProvider(), + backtraceReporter: BacktraceReporterMock(), + telemetry: NOPTelemetry() ) // Then @@ -87,7 +103,11 @@ class RUMInstrumentationTests: XCTestCase { uiKitRUMViewsPredicate: UIKitRUMViewsPredicateMock(), uiKitRUMActionsPredicate: UIKitRUMActionsPredicateMock(), longTaskThreshold: 0.5, - dateProvider: SystemDateProvider() + appHangThreshold: .mockAny(), + mainQueue: .main, + dateProvider: SystemDateProvider(), + backtraceReporter: BacktraceReporterMock(), + telemetry: NOPTelemetry() ) let subscriber = RUMCommandSubscriberMock() diff --git a/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift b/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift index da1deb5ee4..4a9c5424a1 100644 --- a/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift @@ -16,14 +16,112 @@ class WebViewEventReceiverTests: XCTestCase { /// Creates message sent from `DatadogWebViewTracking`. private func webViewTrackingMessage(with webEvent: JSON) -> FeatureMessage { - return .baggage( - key: WebViewEventReceiver.MessageKeys.browserEvent, - value: AnyEncodable(webEvent) - ) + return .webview(.rum(webEvent)) } // MARK: - Handling `FeatureMessage` + func testParsingViewEvent() throws { + // Given + let data = """ + { + "eventType": "view", + "event": { + "application": { + "id": "xxx" + }, + "date": 1635933113708, + "service": "super", + "session": { + "id": "0110cab4-7471-480e-aa4e-7ce039ced355", + "type": "user" + }, + "type": "view", + "view": { + "action": { + "count": 0 + }, + "cumulative_layout_shift": 0, + "dom_complete": 152800000, + "dom_content_loaded": 118300000, + "dom_interactive": 116400000, + "error": { + "count": 0 + }, + "first_contentful_paint": 121300000, + "id": "64308fd4-83f9-48cb-b3e1-1e91f6721230", + "in_foreground_periods": [], + "is_active": true, + "largest_contentful_paint": 121299000, + "load_event": 152800000, + "loading_time": 152800000, + "loading_type": "initial_load", + "long_task": { + "count": 0 + }, + "referrer": "", + "resource": { + "count": 3 + }, + "time_spent": 3120000000, + "url": "http://localhost:8080/test.html" + }, + "_dd": { + "document_version": 2, + "drift": 0, + "format_version": 2, + "session": { + "plan": 2 + } + } + }, + "tags": [ + "browser_sdk_version:3.6.13" + ] + } + """.utf8Data + + // When + let decoder = JSONDecoder() + let message = try decoder.decode(WebViewMessage.self, from: data) + + guard case let .rum(event) = message else { + return XCTFail("not a rum message") + } + + // Then + let json = JSONObjectMatcher(object: event) // only partial matching + XCTAssertEqual(try json.value("application.id"), "xxx") + XCTAssertEqual(try json.value("date"), 1_635_933_113_708) + XCTAssertEqual(try json.value("service"), "super") + XCTAssertEqual(try json.value("session.id"), "0110cab4-7471-480e-aa4e-7ce039ced355") + XCTAssertEqual(try json.value("session.type"), "user") + XCTAssertEqual(try json.value("type"), "view") + XCTAssertEqual(try json.value("view.action.count"), 0) + XCTAssertEqual(try json.value("view.cumulative_layout_shift"), 0) + XCTAssertEqual(try json.value("view.dom_complete"), 152_800_000) + XCTAssertEqual(try json.value("view.dom_content_loaded"), 118_300_000) + XCTAssertEqual(try json.value("view.dom_interactive"), 116_400_000) + XCTAssertEqual(try json.value("view.error.count"), 0) + XCTAssertEqual(try json.value("view.first_contentful_paint"), 121_300_000) + XCTAssertEqual(try json.value("view.id"), "64308fd4-83f9-48cb-b3e1-1e91f6721230") + XCTAssertEqual(try json.array("view.in_foreground_periods").count, 0) + XCTAssertEqual(try json.value("view.is_active"), true) + XCTAssertEqual(try json.value("view.largest_contentful_paint"), 121_299_000) + XCTAssertEqual(try json.value("view.load_event"), 152_800_000) + XCTAssertEqual(try json.value("view.loading_time"), 152_800_000) + XCTAssertEqual(try json.value("view.loading_type"), "initial_load") + XCTAssertEqual(try json.value("view.long_task.count"), 0) + XCTAssertEqual(try json.value("view.referrer"), "") + XCTAssertEqual(try json.value("view.resource.count"), 3) + XCTAssertEqual(try json.value("view.time_spent"), 3_120_000_000) + XCTAssertEqual(try json.value("view.url"), "http://localhost:8080/test.html") + XCTAssertEqual(try json.value("_dd.document_version"), 2) + XCTAssertEqual(try json.value("_dd.drift"), 0) + XCTAssertEqual(try json.value("_dd.format_version"), 2) + XCTAssertEqual(try json.value("_dd.session.plan"), 2) + } + func testWhenReceivingWebViewTrackingMessageWithValidEvent_itAcknowledgesTheMessageAndKeepsRUMSessionAlive() throws { let core = PassthroughCoreMock() let commandsSubscriberMock = RUMCommandSubscriberMock() @@ -44,26 +142,6 @@ class WebViewEventReceiverTests: XCTestCase { XCTAssertEqual(command.time, .mockDecember15th2019At10AMUTC()) } - func testWhenReceivingWebViewTrackingMessageWithInvalidEvent_itRejectsTheMessageAndSendsErrorTelemetry() throws { - let telemetryReceiver = TelemetryReceiverMock() - let core = PassthroughCoreMock(messageReceiver: telemetryReceiver) - - // Given - let receiver = WebViewEventReceiver( - dateProvider: DateProviderMock(), - commandSubscriber: RUMCommandSubscriberMock() - ) - - // When - let invalidMessage: FeatureMessage = .baggage(key: WebViewEventReceiver.MessageKeys.browserEvent, value: "not a JSON object") - let result = receiver.receive(message: invalidMessage, from: core) - - // Then - XCTAssertFalse(result, "It must reject the message") - let errorTelemetry = try XCTUnwrap(telemetryReceiver.messages.firstError(), "It must send error telemetry") - XCTAssertEqual(errorTelemetry.message, "Fails to decode browser event from RUM - Event is not a dictionary") - } - func testWhenReceivingOtherMessage_itRejectsIt() throws { let core = PassthroughCoreMock() diff --git a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift index a523f86be6..4152a03a37 100644 --- a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift @@ -398,11 +398,14 @@ extension RUMErrorEvent: RandomMockable { device: .mockRandom(), display: nil, error: .init( + binaryImages: nil, + category: nil, handling: nil, handlingStack: nil, id: .mockRandom(), isCrash: .random(), message: .mockRandom(), + meta: nil, resource: .init( method: .mockRandom(), provider: .init( @@ -416,8 +419,11 @@ extension RUMErrorEvent: RandomMockable { source: [.source, .network, .custom].randomElement()!, sourceType: .mockRandom(), stack: .mockRandom(), - type: .mockRandom() + threads: nil, + type: .mockRandom(), + wasTruncated: .mockRandom() ), + freeze: nil, os: .mockRandom(), service: .mockRandom(), session: .init( @@ -514,6 +520,7 @@ extension TelemetryConfigurationEvent: RandomMockable { actionNameAttribute: nil, allowFallbackToLocalStorage: nil, allowUntrustedEvents: nil, + appHangThreshold: .mockRandom(), backgroundTasksEnabled: .mockRandom(), batchProcessingLevel: .mockRandom(), batchSize: .mockAny(), diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index 0435472a1a..84ed5682ff 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -278,7 +278,10 @@ extension RUMAddCurrentViewErrorCommand: AnyMockable, RandomMockable { attributes: [AttributeKey: AttributeValue] = [:] ) -> RUMAddCurrentViewErrorCommand { return RUMAddCurrentViewErrorCommand( - time: time, error: error, source: source, attributes: attributes + time: time, + error: error, + source: source, + attributes: attributes ) } @@ -291,7 +294,56 @@ extension RUMAddCurrentViewErrorCommand: AnyMockable, RandomMockable { attributes: [AttributeKey: AttributeValue] = [:] ) -> RUMAddCurrentViewErrorCommand { return RUMAddCurrentViewErrorCommand( - time: time, message: message, type: type, stack: stack, source: source, attributes: attributes + time: time, + message: message, + type: type, + stack: stack, + source: source, + attributes: attributes + ) + } +} + +extension RUMAddCurrentViewAppHangCommand: AnyMockable, RandomMockable { + public static func mockAny() -> RUMAddCurrentViewAppHangCommand { + return .mockWith() + } + + public static func mockRandom() -> RUMAddCurrentViewAppHangCommand { + return RUMAddCurrentViewAppHangCommand( + time: .mockRandom(), + attributes: mockRandomAttributes(), + message: .mockRandom(), + type: .mockRandom(), + stack: .mockRandom(), + threads: .mockRandom(), + binaryImages: .mockRandom(), + isStackTraceTruncated: .mockRandom(), + hangDuration: .mockRandom() + ) + } + + public static func mockWith( + time: Date = .mockAny(), + attributes: [AttributeKey: AttributeValue] = [:], + message: String = .mockAny(), + type: String? = .mockAny(), + stack: String? = .mockAny(), + threads: [DDThread]? = .mockAny(), + binaryImages: [BinaryImage]? = .mockAny(), + isStackTraceTruncated: Bool? = .mockAny(), + hangDuration: TimeInterval = .mockAny() + ) -> RUMAddCurrentViewAppHangCommand { + return RUMAddCurrentViewAppHangCommand( + time: time, + attributes: attributes, + message: message, + type: type, + stack: stack, + threads: threads, + binaryImages: binaryImages, + isStackTraceTruncated: isStackTraceTruncated, + hangDuration: hangDuration ) } } diff --git a/DatadogRUM/Tests/RUMConfigurationTests.swift b/DatadogRUM/Tests/RUMConfigurationTests.swift index e15a9c0285..080087df69 100644 --- a/DatadogRUM/Tests/RUMConfigurationTests.swift +++ b/DatadogRUM/Tests/RUMConfigurationTests.swift @@ -22,6 +22,7 @@ class RUMConfigurationTests: XCTestCase { XCTAssertTrue(config.trackFrustrations) XCTAssertFalse(config.trackBackgroundEvents) XCTAssertEqual(config.longTaskThreshold, 0.1) + XCTAssertNil(config.appHangThreshold) XCTAssertEqual(config.vitalsUpdateFrequency, .average) XCTAssertNil(config.viewEventMapper) XCTAssertNil(config.resourceEventMapper) diff --git a/DatadogRUM/Tests/RUMMonitor/RUMCommandTests.swift b/DatadogRUM/Tests/RUMMonitor/RUMCommandTests.swift index 6bb6091752..303ea21a5d 100644 --- a/DatadogRUM/Tests/RUMMonitor/RUMCommandTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/RUMCommandTests.swift @@ -24,19 +24,34 @@ class RUMCommandTests: XCTestCase { ) func testWhenRUMAddCurrentViewErrorCommand_isBuildWithErrorObject() { - var command = RUMAddCurrentViewErrorCommand(time: .mockAny(), error: SwiftError(), source: .source, attributes: [:]) + var command = RUMAddCurrentViewErrorCommand( + time: .mockAny(), + error: SwiftError(), + source: .source, + attributes: [:] + ) XCTAssertEqual(command.type, "SwiftError") XCTAssertEqual(command.message, "error description") XCTAssertEqual(command.stack, "error description") - command = RUMAddCurrentViewErrorCommand(time: .mockAny(), error: SwiftEnumeratedError.errorLabel, source: .source, attributes: [:]) + command = RUMAddCurrentViewErrorCommand( + time: .mockAny(), + error: SwiftEnumeratedError.errorLabel, + source: .source, + attributes: [:] + ) XCTAssertEqual(command.type, "SwiftEnumeratedError") XCTAssertEqual(command.message, "errorLabel") XCTAssertEqual(command.stack, "errorLabel") - command = RUMAddCurrentViewErrorCommand(time: .mockAny(), error: nsError, source: .source, attributes: [:]) + command = RUMAddCurrentViewErrorCommand( + time: .mockAny(), + error: nsError, + source: .source, + attributes: [:] + ) XCTAssertEqual(command.type, "custom-domain - 10") XCTAssertEqual(command.message, "error description") diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift index e53b102ae0..4154f1e4f8 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift @@ -559,6 +559,7 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.error.message, "network issue explanation") XCTAssertEqual(event.error.source, .network) XCTAssertEqual(event.error.stack, "network issue explanation") + XCTAssertEqual(event.error.category, .exception) XCTAssertEqual(event.error.resource?.method, .post) XCTAssertEqual(event.error.type, "ErrorMock") XCTAssertNil(event.error.resource?.provider) @@ -621,6 +622,7 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.error.message, "network issue explanation") XCTAssertEqual(event.error.source, .network) XCTAssertEqual(event.error.stack, "network issue explanation") + XCTAssertEqual(event.error.category, .exception) XCTAssertEqual(event.error.resource?.method, .post) XCTAssertEqual(event.error.type, "ErrorMock") XCTAssertNil(event.error.resource?.provider) @@ -685,6 +687,7 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.error.message, "network issue explanation") XCTAssertEqual(event.error.source, .network) XCTAssertEqual(event.error.stack, "network issue explanation") + XCTAssertEqual(event.error.category, .exception) XCTAssertEqual(event.error.resource?.method, .post) XCTAssertEqual(event.error.type, "ErrorMock") XCTAssertNil(event.error.resource?.provider) diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift index b509034c49..7cd6b94753 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -1330,6 +1330,7 @@ class RUMViewScopeTests: XCTestCase { XCTAssertNil(error.connectivity) XCTAssertEqual(error.error.type, "abc") XCTAssertEqual(error.error.message, "view error") + XCTAssertEqual(error.error.category, .exception) XCTAssertEqual(error.error.source, .source) XCTAssertEqual(error.error.sourceType, .ios) XCTAssertNil(error.error.stack) @@ -1420,7 +1421,7 @@ class RUMViewScopeTests: XCTestCase { currentTime.addTimeInterval(1) - let customSourceType = String.mockAnySource() + let customSourceType = String.mockAnySourceType() let expectedSourceType = RUMErrorSourceType.init(rawValue: customSourceType) XCTAssertTrue( scope.process( @@ -1439,6 +1440,7 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(error.error.sourceType, expectedSourceType) XCTAssertTrue(error.error.isCrash ?? false) XCTAssertEqual(error.source, expectedSource) + XCTAssertEqual(error.error.category, .exception) XCTAssertEqual(error.service, "test-service") let viewUpdate = try XCTUnwrap(writer.events(ofType: RUMViewEvent.self).last) @@ -1544,6 +1546,65 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(viewUpdate.view.error.count, 1, "Failed Resource should be counted as Error") } + // MARK: - App Hangs + + func testWhenViewAppHangIsTracked_itSendsErrorEventAndViewUpdateEvent() throws { + var currentTime: Date = .mockDecember15th2019At10AMUTC() + let hangDuration: TimeInterval = .mockRandom(min: 1, max: 10) + let scope = RUMViewScope( + isInitialView: .mockRandom(), + parent: parent, + dependencies: .mockAny(), + identity: .mockViewIdentifier(), + path: "UIViewController", + name: "ViewName", + attributes: [:], + customTimings: [:], + startTime: currentTime, + serverTimeOffset: .zero + ) + + XCTAssertTrue( + scope.process( + command: RUMStartViewCommand.mockWith(time: currentTime, identity: .mockViewIdentifier()), + context: context, + writer: writer + ) + ) + + currentTime.addTimeInterval(1) + + XCTAssertTrue( + scope.process( + command: RUMAddCurrentViewAppHangCommand.mockWith( + time: currentTime, + message: "App Hang", + type: "AppHang", + stack: "", + hangDuration: hangDuration + ), + context: context, + writer: writer + ) + ) + + let error = try XCTUnwrap(writer.events(ofType: RUMErrorEvent.self).last) + XCTAssertEqual(error.date, Date.mockDecember15th2019At10AMUTC(addingTimeInterval: 1).timeIntervalSince1970.toInt64Milliseconds) + XCTAssertEqual(error.view.url, "UIViewController") + XCTAssertEqual(error.view.name, "ViewName") + XCTAssertEqual(error.error.message, "App Hang") + XCTAssertEqual(error.error.type, "AppHang") + XCTAssertEqual(error.error.stack, "") + XCTAssertEqual(error.error.category, .appHang) + XCTAssertEqual(error.error.source, .source) + XCTAssertEqual(error.error.sourceType, .ios) + XCTAssertTrue(error.error.isCrash == false) + XCTAssertEqual(error.freeze?.duration, hangDuration.toInt64Nanoseconds) + + let viewUpdate = try XCTUnwrap(writer.events(ofType: RUMViewEvent.self).last) + XCTAssertEqual(viewUpdate.view.error.count, 1) + } + // MARK: - Long tasks func testWhenLongTaskIsAdded_itSendsLongTaskEventAndViewUpdateEvent() throws { diff --git a/DatadogRUM/Tests/RUMTests.swift b/DatadogRUM/Tests/RUMTests.swift index 0b7efa1364..41930c0e38 100644 --- a/DatadogRUM/Tests/RUMTests.swift +++ b/DatadogRUM/Tests/RUMTests.swift @@ -101,6 +101,7 @@ class RUMTests: XCTestCase { config.uiKitViewsPredicate = UIKitRUMViewsPredicateMock() config.uiKitActionsPredicate = UIKitRUMActionsPredicateMock() config.longTaskThreshold = 0.5 + config.appHangThreshold = 2 // When RUM.enable(with: config, in: core) @@ -111,6 +112,7 @@ class RUMTests: XCTestCase { XCTAssertIdentical(monitor, rum.instrumentation.viewsHandler.subscriber) XCTAssertIdentical(monitor, (rum.instrumentation.actionsHandler as? UIKitRUMUserActionsHandler)?.subscriber) XCTAssertIdentical(monitor, rum.instrumentation.longTasks?.subscriber) + XCTAssertIdentical(monitor, rum.instrumentation.appHangs?.subscriber) } func testWhenEnabledWithNoInstrumentations() throws { @@ -118,6 +120,7 @@ class RUMTests: XCTestCase { config.uiKitViewsPredicate = nil config.uiKitActionsPredicate = nil config.longTaskThreshold = nil + config.appHangThreshold = nil // When RUM.enable(with: config, in: core) @@ -132,6 +135,35 @@ class RUMTests: XCTestCase { ) XCTAssertNil(rum.instrumentation.actionsHandler) XCTAssertNil(rum.instrumentation.longTasks) + XCTAssertNil(rum.instrumentation.appHangs) + } + + func testWhenEnabledWithInvalidLongTasksThreshold() throws { + let dd = DD.mockWith(logger: CoreLoggerMock()) + defer { dd.reset() } + + // Given + config.longTaskThreshold = -5 + + // When + RUM.enable(with: config, in: core) + + // Then + XCTAssertEqual(dd.logger.errorLog?.message, "`RUM.Configuration.longTaskThreshold` cannot be less than 0s. Long Tasks monitoring will be disabled.") + } + + func testWhenEnabledWithInvalidAppHangThreshold() throws { + let dd = DD.mockWith(logger: CoreLoggerMock()) + defer { dd.reset() } + + // Given + config.appHangThreshold = .mockRandom(min: -10, max: 0.0999) + + // When + RUM.enable(with: config, in: core) + + // Then + XCTAssertEqual(dd.logger.warnLog?.message, "`RUM.Configuration.appHangThreshold` cannot be less than 0.1s. A value of 0.1s will be used.") } func testWhenEnabledWithURLSessionTracking() throws { diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index 71ac821988..737cbdf5d2 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogSDK" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Official Datadog Swift SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKAlamofireExtension.podspec b/DatadogSDKAlamofireExtension.podspec index 3524f2bd9a..24bf73a84f 100644 --- a/DatadogSDKAlamofireExtension.podspec +++ b/DatadogSDKAlamofireExtension.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKAlamofireExtension" s.module_name = "DatadogAlamofireExtension" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "An Official Extensions of Datadog Swift SDK for Alamofire." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKCrashReporting.podspec b/DatadogSDKCrashReporting.podspec index 2aa5ee6c5f..9cce870352 100644 --- a/DatadogSDKCrashReporting.podspec +++ b/DatadogSDKCrashReporting.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKCrashReporting" s.module_name = "DatadogCrashReporting" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Official Datadog Crash Reporting SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKObjc.podspec b/DatadogSDKObjc.podspec index 76cc481b14..609e0c934d 100644 --- a/DatadogSDKObjc.podspec +++ b/DatadogSDKObjc.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKObjc" s.module_name = "DatadogObjc" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Official Datadog Objective-C SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSessionReplay.podspec b/DatadogSessionReplay.podspec index 586c5ec59a..cc263b1d7a 100644 --- a/DatadogSessionReplay.podspec +++ b/DatadogSessionReplay.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogSessionReplay" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Official Datadog Session Replay SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/Contents.json b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/Contents.json index b43d7f752a..491b6d58cb 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/Contents.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/Contents.json @@ -1,21 +1,15 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "dd_logo.jpg", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" + "filename" : "login_logo.pdf", + "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" } } diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/dd_logo.jpg b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/dd_logo.jpg deleted file mode 100644 index 403084aeae30b8effe8f397afc647b6824852525..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7073 zcmbt%1yodB_xFVXMnFJPq#J3aq=xR!p+UO4qy#|}iJ?P6T3~=tIs}w13F$_8>d!Nu#H4Km+^-nOS(aN~o(V|A*+m;Mw?}=m0Rn z@h8@QDgRH8*p^lv7RUr2APog9T-`m9SR9E}eLP+N;8-LkvamI?MB;oTW_L#>h{QvG z7p)Jb&ULOMa*b0O-s2_s6UN08R&h z+xPeP*SYuiw|M{nngoC!iU0V!BqH0nhm^L3W?ldQwa6Zm&bykq zoBg#82sxr!TLZvRApqd$0{|hi*I0)Czxu!Xjg0*#K9Dy70NVZlpxh4tAJYJU3ArAg z(fte{1)zh_(9l5W$N?Q49RvLE0T@Ym*w~m4@$m=<@bU2R2}vj(6B3aTP(UE000c(G_{$0fKn0Rj-_CsL!Vk%5-a-<0u#76Ndd=D%MC=U%Z4al%@SvN;LJ? zX>Egnn+C_TZVMO+Iu>_EqB&EIWpq;5Z1&1BYxY_eMNz2MG?(7bqbtKe5tKO$_eR4Q z@>gL{?AK$K9AaVRc&iR=IhK8DmfOTtDw>C%;6H1zNKy8;X=129{(efU|K&8(=6Slh zcI`1nV-y>Ir~R({jfR3>J%OvTsvC80(#aF%{WQr^)dltpzO*i-U+YU;<7fDzPu@Wx zu1j~Jxt#l4u8nxzQimy~gU&coFBzp%Iqt2?9-NgFY~XfZrVG(heQw zty`9NM!cA7t7;lsi4Z2zcolH8`ik#1hi_HkdCNg!UfW(@*GgxcCt zN-EeZ@mk@j7)Hk{g~mJtb$d#|Uaevn9>yI5w7PZG=fkZ5^pS~Kj$BxUx?2tF0y?G$ z&U?TmVDUCF_4hhvnV*khm%e8n41O#uI2a%piaE=RT55cP@o@>764<(+gi~>dokcx| z3$5N|0K|{=9fS$$&sr%0qfZ@-Y$N%IPb&q^PftAve{aIJ;RaN`9|r4sjCZxH>Nc-O z&mzl&7mLHX7+`rnIWuh7!Ggv(&k?R#ut?&wDz9yEC$>R0?-Q-O%3Hm%MdP?#hId-k znE@f8FHq71!MPue6IlK42sC-pnFNgn7cE{(krZpnhcdBM%?QU3catOVbkvO;b(4Sg zJC@-S<-fsygo0%A8?Aie46!DuUd#)*tf>Mk?EX=W0GW4-YEda8O?Bt!LiOHLkMCq6 zN8^rRFXVeepLoP@ilcbH^z~>qryxHCQeM4&tgfrolS~R{aK>xstrN5K&YAGu_AfTn zdXkVP`})z4U(&{FIHJJgz~0BsiTrWpIabjrTmROjqII@KB{_wXZ(?@E6o%-o5GhJmL%m~8VePP-l9F<_c@)OGuP-^*aFv347d?*GQ!Em*Fr1PFEAOi*OtxIg*AY?Qk_B02`hSY_CW@HC10&#l#gijF zDp2|&a86WI5TnhIkYF~<_R^?}SFmoONOS3w?YViPpmG4GS1zlq>Y!i5_UxGHx>ZAJ z?7HlW*cdvjxtQN~(aAF^lH1<&gWGk}F|VjA#)Q|VFKgH)eVd3?a65ln^hh4N zGe3SJiXmM1yMhvwdPttS1L|-7UB=v!e(lt#I3~nhJ~tavw7=O_!(fqpI_KDAV^&)F zcDB?DmRsLCTgkkVKpocBU6j)itzk+{u(|(ysNc5vS0%ANDS-fGhDaxI2D^)?9k*iM zeAZN>zl!aHQ1>rqbf8eDH`I^3X5jUOdF&92S;(DlcPlTr)} zcQoZD!&iGDeYqvdE!M2V#3`m~ob*s~ zR`R2r7bjT#y$Dt;%o1iA8Eou@9n^@l>nf@|ME#&jp??FJz`ViKgUh)XViJDFk7C)~ zx-1urv8SXus8zMrf&T7e$yd`3{Wk4Jw64M@s}PcPhla~A$|}`#3QK;wws)R;W20v! zPAaVx8pOOfoN-;*RE%=-tR49RS+?+cJHL&>#u*E)Rok{T+bsz6c>FBSf4G)h%0&NA zUGUAkjMO}}-S%0=QWGQliI-c2(0eJ~5T5QcihIDy|8wi}b053hPHv@Hhw<|$pWaQn zR}3C}lMq*l)-MbzNgLZ~P{ZoL>12%C85Jn(<3=Q=c{E?$s45K{3!q>qPU>3qxXW zt2U;m`-oqW$DR@7wRz)WH*bvWbwu6+ZCaFj=-GP;m&**0LWEmyAkq89)$bAK{U}PN zFGo8L6^DNj_N>3$9Fy}6-4^=qjd{v%1yl>w6u9psv(O|d-s(5Jg zqp&8X&UNOwP-~&aU2HK6!h-(qJfeyp7dUE@z3x)6t?BAf1Az=b6^?$fXmR!96x~T@ zj}aA(+Sj=dnbUB-Hs~;P!KEF0*6>X34NVvxOC3F8YyVXHVfG3PeDq<(b;X0Hyfw{n zJIn+^ROdUsTsX<0%Nwoq<28x4ExoR;TPZ9TUR*{|UW<}yXp8&yq;nf)#3|d0?Od~2 zKe*zM;xWU*;ryTz!{-Dlt>FXXj1F5!Pqfl&n%yYVb}6aTkx>JId9vug;$<5QTy{l+ zo!2bAhIJa9rQHbdwjzovg0eSS)lPZ4?Cz+wC%t&D;$xeYygF`%shVa#ne=INRH+PL z^jb|;Go>t@C44g|TJ}%vOpaso0Y3qI;ho~tI6#hSMnru#t=WxZWcxCfH~M;cpRad0 z2zk8h+?UDlc7gLY8dY`6qulJ?wzg0&1x$QzjYv>vH)QCaxtY-m(j@uh^PS4{7e`}D zmV_dD%j}9Tr(WaCx~ynDo3!-d;@_`61Gmc`=ejy6c#XJ6DzQ^!Y&Cq~z}#Xs@tzQG z*e5F)qno`mExD>O(vn4$A1Eo(Yu#eXMF#!Lc84^agV}1Y=Dy3q*v=9~pB?$eaD^M5 zm!EDXzDUok@ty8oi^+`kC{J;$V!D}ab3vc2o@L!lQbFV;9S%}Uj)dhU#oRO$DDYLE zmr+aSCS=xwtn_GtWrNhKsBM}#TD)sDHg6@gUyu0r(Whp9f&@Zu971Q}l3Gk-MC{I# zdoqluK6YMepSMBqk^^=4+(ZrREMO*^duBMVd3poJ%M*;pH?i9sr{|fm^l@4CqFm-0 zUwOPj%??vJdW+z=;7@c}qgKUQS@q(0fh8P9&uVK=Tl8j`Bfw=1jdOz@6T$9aY7Oda z!eOc#rnb`3No6&URU%Oxpckf=!TWLr($(8@$tx-!6O#Rg6QGgud_Fr&)W+Y!&X>k% z6qa>k(0ts!&D$uIH=^z3scIPnF;@OU5lx-cTw+*gZK!C^W|iiY2#pumS;$%cbwxAi z{+4^y$bZu?dh+=%Syi4sv<%JW2Ar8g2B+nUWP`pBezHAg@KjS4n_>$J`UpW##&UFx zdQG;`{)9o=rGCLuxJEi?!kC z;>wSX7J7riPSn&#Zji?>lqUpQP2cNGMfS>X_|_VdW=OXfT<(F~hL}$4Ya0^dwjgwN zpxTY=WELE5B>P)Gb|g5y?m-XvPDAoJu1L!F-4^#2T}Je?cZkf63Mn3wVO$X(Qz>@7 zkmep4BjiSkn3)PV9f9_ycdWq{WV=KaK0D~c3Ip$fiYvU&yPZ>o8yu@sya#-55lTm@ zQv!jBJlZ}jo4hL=Jni$$8(*T!h9@GetU7isEkAq(TObylWp~6idPYab2PPo>(kk&n zTbhdlLvs9hZ}d{t_nkG5;!I)^j12e4Iy?N6D^7Ih5>Q>{7sUx{^;aAFdKXiK(TwL z9=lNJEK%dzcfi`@7%6m^lNNA?m%!H_Hc0#nu7#%370|BUA#(j%=k3TQ9-*9*mrdAz zopF*5%#>15+v)^@yfz{AG|>@X;xckNG=|Cc{mw3)2{S*aVyvn@)F)?%zHx8-eyOiR z8PO*RvMWoBdoxr+61I?rJQ_wNG|IM&4G5sP_oQ=T`+##OiJ6@SMCE5!5xC~IFWbmS zA*h|(e8dma(p0#}L$|V2Enf|aOLXlurT6z@8&`a8E?6udX*b!q0#!t1JQ|C)cODw6 z#r))JA3F1hIgdO8-GQ9EnV`ZHv>p0-tq+n#eGr)UWfayYwZMsTqB+=wMZ5W!T+AV& zJHwSJS=Z@qsbEIculV?6^$Ay^&}i>3d=2`49iM((C5=fuQO;tMvV>6$oh8><;)gT` zxUt6m+P>aFvq&!9qiRn&p@ly@i_5vd+>FKPLq}Bcn2K z6>Hd$P}9(PT2L50JjnDjI+U3;k`l*h{209^f>}jmA16B4S?w;d^LOooT|d#V6t)bF zv3W}uo=>vUHr=)H%&PFg;JR z;!=L7x4JdxUA*RnzURmNI#x6(WTBet5${vt6ng;|Z|`sWg@tzebS|H^BbuRB{R1uu zf`d&vGj9WWh1@cP7N(^X=fJ&FZup`O-Wz2a_G8|ud&y}v{h%)8y^$4Y)~+$39haZv z$q)*xk@s7lYi1~y>I7y$8E&z6PdqA5Toz(%dR|Mus-YENOtA8vJnJ{T-56jXT` zFIGS5z1s;Vn+P#F;@|ju2dS`U6^S`n2vzp3o){#?-^A9@TK13+)Gq%6+yPXu7 zXcj}_E_wU5m3Q#xTDz<9ZfBc)LEK$X;CSB7ggT2rhrR1mVP^j23Okjie`UR}g?rN; z!QerLOnLW&dxGYW{3$Kl=GWX0vLXkv+dHB1lYZy?1LLE^w;XW?`}Y7{!Hs{_)A!Tr z*g>{zIv%1X9;)sTtmNd6!q-;33!g~7onxo`G=$d^@7`+6NXtkE|Gn?AN*1e&XqhI` z3%Kau`J4SB6-dG{cH5*|*|edRG4Go>hMBFmRY1!wh^pMY*lZFzPKSdXNAkSfl1`{8y|pctO@ zb7)CcEv`4R#OG?T9=BVqerG-Q9ym29`s}14l6X`l%0gyvu-k&}Ekc;RT?4g`oIis` zOfXoL@KKO1KQ#X3;kmk`ky?3FJ;27kYxYp*<^}RYCyw53D=!H_C5;$#Fa#P7#ud-1 zup$G>;$lFqm<9yL?SZDR7Y{~`)!)yUg_SqEq1-_5xEIO4+*!FAp0C?J_&4P9eW_J}63U#m;JDL>c9htYlO}3V#319dA zMhZUB;BQ9QPhY~ftO-Vyk1nxcstUac1&gx2gc%E%F}Y2Bega{6w3Dl#9I)%RiV3KS z;;3z3#AKz!PdVBr&Vds?vP}o}C00^;=NLM&?&AqDslFUXGjgaWi>pPY ze8W7)fxpEnvJWlZL6s}I@@dncSxL8(&TdO3`lB|M2Jjm09znUcuOMZT6w#KRV$(}`m7ttw;3)=>{&MaZxW*!vojKsxGos=*j|}gd@Q72 zOq;z%4;yt`g+lD*zqs3Dhw@3wz@Y!y6eop~+zf36g&8dLdcal%-?TT`5XHkM>{Xf# zpW&=;j-tQLCN;?W1Vj6bBmjzQW($Lj_;?b+-Q;dF^WU7!#U3^nuWY+rIXpg7^ozdU z(sZp@DB2Y=XY(#9u`;%#W@E6OtsT86&5T=i)~Zcw7!vP%v86pFIKIlkt6()>2&R}> z7C78Y9FSH0v7b@u7iemp->77V_+o{lY_oC^+RJb@sPlRmelkaan+89Ypl~Apbm6j7 zqH8og9$s~tN4g8v$Kk^IVx9f2sw0?k`BNm;V$wUIfqrfo4q^XI7KT?oOzSv$YGkL4 zv&zlGwsXuxW4xIMgpDt3T#S}I-oEpfIe;i0zn(5Oo@PQeDu_AZI$0YABd|*+Wb{CZG-_nl_40!5!w2!GrS)w{K%ob*>x>yc2cVbSJ z9&Zwb>2pi#WbcdxC8R975FPj)eWhTJpPNdX|1n}*#Fz-niCWbpjlwH@I=<|>FoE@A zj5~`8&6gmh=krs4AzRzo5p6bBLAj;Gy&6PHrTXT!2uY+NRj;1+OSc{q7#IpMsO3flq@bE&gvND$xwY-MmWj%pg|fsW4xOt|XcuV`?cWx-My= z@&D0gZMNzW7;f-uE|a${|7VJSRC$V}?0NixrIdm~O z{2T&q+kKvAhC1ltz$h##7cd~&UfNQJm0Se?8$zrVSTN1R`h-cCW}G3eWU=NTv8G;A zStQjXY3baz|B=_f6GQ?c0h*@~Pav;Ig9Wv}!Rb5$x0U67U{=t{w=nVXLw`EMr8T#u Q%MbAJQU5Cf_5IZU05zVYt^fc4 diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/login_logo.pdf b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Assets.xcassets/dd_logo.imageset/login_logo.pdf new file mode 100644 index 0000000000000000000000000000000000000000..19cf60f5499dd267b07af43c5c4deac22254bccd GIT binary patch literal 16615 zcmbWf1yo$i)-KvW<4$mG9D=*My9IZ52_D=D?jGEo0Kqj_aJS&@?*7Q$``rJWbMG7X zjn|{cTC2bMYSyf(HG0gNwZ28JC@N0JM9%_8-n(wg^`2_Qc>eB=p zE1Sv$TLYsyBRhv`4I`^Em9#31`Y)9UWJx+{c{(a;xe+C*Az&FRixMM1*a;@-TY1h| znsv!~d^O513>d(~BH9R?sh!C`cHgi5xbw${KW7e(KV^TenE%buPhT8NK@5t9=6@X> zP3@dPEPp$#Z0cn1;%IE@1Y-Xi5V5y&elK?d{pqOkpAP(E?@#%^I(bK4AOo# zDk=_Qkg|O@E&R_}`U(JCS)GbZkN1vIE9mF7MYH4oa3}WVbzm|IMfTM%`pILx&c652~ zvWUHny`zeQp|L55>u<|{IPZhNplJHucL!$|LmLo-ikzX7HHeAvZ-%g?v(tO2h`p_Y zz1=$(j`ROM!zT%vwd6D~V%Dz-J`AMtiH831R8g^xzEx6ni?n>cuQO}Vu+I{&-VPgD zr7+JAA{`#7v<719EWq_GGhg2jK{`h|uv=pyEWP1M%r}7lMTTCakDo}T1F?Uf`Kz)I zahDbsn|}&)dzQRh{)oPq!HY{Bca@RGFa>#+$B=JC7Qq{#=34?Y_0k=RLHkx!i4XW) zThzGy?{@3c&o?guE!hJa@MdIP-WP9kFYGTIx35WY3SVB?{qNLy$`(A|Vx}4mvwl}~ z=BW@ekDWAEX8Ar*LNq=x~PyPc%5VF6>Gp35+knjnNbnHX=5&iEDS_cp|ko>bJ#|WIs*D zJFzs-O`#t$lGPx(E^y*+5CK=y{W0z1)(kPBO-IIyC7nn0y@`PF+!*_8PoanA#aKzUIPPZ{Ndrd@WXJwnLKYBbAgCU(sKkq1M)*gO(}U30 zxJ4fz^V(94^5z$+60~DoSV+&(r7>Y=ypypY4*W~)kk<1h@}w7wu(9bSnH48Y;e`5D zq2yGA)g_ih?UPx4-{h@$8cxCFjK~TAV5kzjyV_?+$dWo0ZH^g74SFKs2^hfPHIzGu z;mkGsLZ-YwS-Nlv+3Bs;T}VzMQMp5wg0xP-feaLQpe{Bks1aKFC55ReDBH(A@jO)w zm#rEHy{-)Z;XZy>J%Wkmp-AHj50xv8FF15noV43ZNd^&Fqz%US-*3becNso*?AZ&!K*-$#||Ly2nuFjNe)`EjIMtk9CB?R@vLWwRc~(RFbY-+L>8U5hAW4RX|r># zSr0wU@o&ptD$l5#fC~xLoVWHW!DL^kzG7LFh|Y%4;vNgK&+&C>Dj!E0Zwmf^BOQ$R z%5HVb3GXrY9Qj55d7;9!ZXB#)uGq|c;^)IRslu;LnAa7S6TG25HfqNA_>Z%XoS#p% zIn9MXx`)?g`i>%}zb!M!9TVv-l_O8>&Hdz>P&TalHXA*|Y9vwQYi7}NkDpces4$i` z?w^lY6VjREWNN&_d*``@JJ=5K;aArEBK@wF#1L*8>9hcQefKhtgT*m*_Q~e4OIZ=6 z(@;;!?k(@#7;6;YXrS&jH)<`SJdlOxe8Sz1$PA?c5}AF>HSB}E!=}d2X#p_?Aue?8 zSodv%@PY_3WyPR#zegEI_LjbmFVDq_+2;k7_S^^+f0ANd*@{iW}U0P^Jdp1;d*f7Zey#t2RdmT2rdxK zonb(0S2%CAUAny--)4x{y~fSYpTMzDmu>Yt{jT`3yQ>j&L>(H1j=?NW5*p@*?06_5szuhF5a_`QO)M03K+lJ!A!hH6^Le=TJ^J~6|OMC0sExSVab-~#Yyp^ns-L>oh zWC*Z64Ku(+B}w%Ks(14lSoFZG8fmynp-g{8={_fIf;$Bh$q+jysT;!EOzppb>GxSy z_JY91MFl%TN#ZB~MAc=Bteevnrt)vI!v{o}ncnq+#Rg`J>_Wd>=TCpZ!+nL+za#PJ zOgXgt#y7r@<;Zpgu~~flqq%GR$lTfssk)B?{aO$y%e*9$Zq#OEbL=)`%WO5|a}Hxg zGaXFoy)RnV$_NUf88wIfbxvBlA-*J{H9Z12?N~O@frv5)jAUmkiY1s4R#_0y#Qqg% zvdnL}JIB6%;5_#mPe1#1(cIrGyNtze>q^R)<3=!M*F^4wF3f3}pG`1f(M17g-T37J zZf>mW$imD&vq#l5+AwF(vbhopRQu!wY0dGdS+4O>?A0tn&M+_ z;2EfGqJoij>iRM525_W+8T}k5kN>GzcT0}^ZF4c_{_~it>J>_?{?147hu*ZGWeM|- z@94$+Y*`L!?6TR14I;9weZ3B7Gjcw9$K+;%BsTE!#zou|q;i$y!<26h;VDHDG`qAK zY^hyGe4ChN+p3xxUG?1zA0D}u#`Nnxh61~85UuZpa?CCUIeIGU84rpEm%k6;!`$n% zJd*G?oD`KnP5Fw;MVzXW~mT&X?mtu{( zHxevY^eZ6bm<9XxT1*-zZLbz?!F+4=`F^4(FU$5!pww3))AH zOWwf!34_;%!L%z%u-#R*3(*HLTo54OH%y93U&z~p+VuQm>t^NGd=3bBj@QJ6wyiuA zg6E&1&$GIM=!qCE0uTtaCjCrb_{|-y>9#wtk1uV$f)m)q()7SGR+#uf0i2`r!N*Nu zmyHI={}plBE82;4$0Vuie1%yoq#X+?t*Pvo*1s@iu)d|>Vb+ME{G$^E$jctT@h57w zEQ1;+M!At|`HeaP0Fg)_J2d*OGG!QH1+ zyAy+SyTr8#Q z*6DZMcfQkvC{KiPnUt>n>EG-%Oh-L*OW5T~nSi%6DeVvdhk|AdJ1%~WH}$I|8VxB!;9#=x>_(W1bO{(9r*w`0LA_XG^N9XN_OSj zvR3=sx`Mi$z3UUkz}cH23A7$uF3b#%FZsmjk@_NkApW5`b@SKxrRsp&E?hWRhlthP z3E}6S^b_GD!4LQC{v8xE<@Y%7rh~sn>T112eREwXV4fB4ARX^p;6^j5Y&3qD>o+$- zRU(xGx6fxWe#0FOO#*#0;B?t^Toy<{+xC;I3`V&`LRXFX%TnWIV|c)Ab%WfAcg$C< z&X%%%(s>jAF>13ZFY`KU>zn@`XMd3VZ$076;&v$lUKDM7=O3&smFLbsoPr=0NCL=n zLw!iaO7-R*M;dKUM3vqeXB1*Pb418jCk;y`zxrT3eAOF0rMK@8tsgtF%QPl|bOFFC-;Nptnb8gSNwNF2+8g*;eFc%P!w+q#ul_IWVZ!^@OE||8r}5>JYy* zuAJfNzK%HU(Qy!=#C^TYWb$*JYX&#f)0UBZftz`FYVsr!MqQEiuRaV8J`!?DgMxGE z9t(Pj+u0>oFmsQR^b@&2){ua((Kh9ubVYIM0_BiJb_BiZcTs9*ev+I4QnH-E(__xU ziJyzM%ZQUllb$}^aZc|3nE^us_@Yj=7jRhV*!o*NSv|2B0|$c}(l7q1eSlIq%OP4m3yDQXa}i z0voYdtyr~KQYuij6&5+W_!^T(!bOgoKEU>a+Fua@X*8C!0R+r>zu_RLiQ`h!p%@FAIjxdLf=hlEEUb&x^Jw zarYyb7lT3&CQbtx+|&||X{cAESrTnR*ST^QXodf12(7rWZrF(5wN5%s1hDptLHKU0 zrMCCrW`>N7ivgU)#GZ#{ts{%QIBRZdM0_%~XLtux}^qFudXgxJ@QQk-8-5S0Zh z9LR#d<9|mrr-HhS(BS+wn%)PCT$il)`R7|H`mWQERBSW0WJC#%&Cm#hpIm(0CA?sk_Ehv(?Uk{EAH$v3(>JZ z9Y~rk@BFwXP{TyDz9f@VjA=y=z>W(kV=JK;XR$K^Gbj4JpZ_@-y8=;g$o=Y_$CjG1sUUXs%gsl)ymaPhJ^ze|TiEzlN3>h-g$vx&u z?FQbG;Fo(2b<%6;=T$7pJVhq=wMsZy970z!gtck?_>e+HbK8LiqXC#h-k zw5wJnpyIRX_&FDRgU%g}mTZ814|Sbvg>sd91r9IdVh%ITOjfL|bR#hQH3Y^ro<5*azVT<7VVh$(%=cZ? zx1elM>PY&y2flLm9duM9P2abmG@W-O~;=pq`26lmV+@-;WSG|jgUm4%wu zLoJOp8cPyRZBQm_DvlS=^GxRkc{T zzPxNYio+O~^U6go=Q=34OJTGV5B7moSE^vhN)8rOo zho+3aU&^xBc)4q$HEJm4-TsakfF*qq&HZ$K>!aeZ>Wbv7?1hw)#0edMM%{viP&Q5u z4qPJ7RFj1fmuGuhuQ}0_*_#Dmf6A5t!nDb7SEiyMWLeSp^<>#%#LZ6{NPAbIfncho z`EG)=!e<xr>Ua!J-6V6n>?i zRIaO3_cqE@g=`@_Qa1?%fPGPoiXBSArSE6~$JA8KIr{D+#twrLt3C@;s#ChSDWh!= zSLoV;9ieU#1L*!D6+1DQihJ780)wJyn>`D$_?U>2D&(^%QyV1TKVx%O`2KID%%Fr| za-$3l60G4@g&DCB_*}%F z8_Uh6lii7Gw|9p`^W8vf8yHgJD^$e!2iMMSMz=F(M;flt68i*$VEGmx>n%*|X(j2` zjDBj$zR66hib@Bb>Kef)QU7>GUG3MSNha!9lLqoqh&f<{se#^TG;tFxeFL?{87+`UEp@`r;m_Mg#R_P?M z;o}`ea@nY0I56;#Wb4HlT|U(;gfTY4=$Q=+=xai<8ET99QV5SA#;0?Q6Cwww+llOu zz~Fs+lncT}C!Y0Mb@&RdzokN66swdbDP`HL@U zfI-Yc@STO+3)6Dpp6sAkD!zWtCWTN>M+W^{P^D-%+B{>Ny8?~V$88BpjU#i`9*=W@ zbE$9>4~VpyZ*tvV)PorYoxPfS0%wJK-Yj}UO{Ax9{bR}yP0{xoQozWp-Y|Z}6saD8 znm+{CV>GzgkJS6>qMbHpbl7_Vhz-%om0)(x6D>8FfkeRG$E_KQT*WoXVU2_P4~o7B z;sN0%+ejFri8GqumjmnhIWm@mb*KBv5#k01Oy54v1&iC~@|4d6e8?dZiLIdT@uAk; zQ684e@kIt3$B9ec69s~IJgh2{#GA5_`oDe`<1ydX%$i31dV8Fa?Myc8AP#=g9?We* zrkRz126J=iYZf0*yMzNP>iLF)le{+r6n+6JC(vF=!2%y($qLmQ1O(Ub71kjTtb=EO zW3NEve448@ki#M6IikmpK4=)uHz2=Yh1xwKShjxmUDK?%K8e92%{^Ml@0}jtj4vdS znQFOrlHarXxhnIIc~zjAFN}J3sVO<|6*`r6RQi9<^Z&_=|4Ej!aWJv{D>MIB%KE=D z=uAw1r?LP4rO^K;VGhS2YU*lfY%1Ys_~k!R>!80=>;Ii52mOCYlke!B_?Od8%fq_X zBpSdb8bF64ojRlNXz)1j{Bm7i5|YjdN{qz{-f$8W965E079c_B8F4|(#xHc^O?%KY7Nx)@VB;c+Sb)PF$Qc^!?!!^2Y z%{Jr~G8gYOR= zdMi72@UM@i>t=Nrmad<9daus(pu;5I*mUU=NS|6Ex(KRBu9|c_)Nh~trD{mG>F_2L zQV!EU_+Y6&?n=e3Chx(me=$G7;+fB@yQZEZN*Uh2^u1`T?}MsrrB8P9u>-b6(-R->JbG_>0N_p>!$r+0DaS%zR3%>Q|%4bp5m?$4WBoC-n$G99)kqCVWiInkbuCs5ndE6F29sO+l=7ER}#VymbzgIt} z5t~QI;__)umupixOnjE^J^o?K=4+4_+?KvHaON3tE&H$((IlZ%i-_+FT{FjN2rLL` zKW_p=YKy0mPCta}5O`@4;xkWP-5o|4^%+BjqhWXqv@I}$r@a7V~ClMx7L$0;17QBaZVf7nMSG^*1In#g7})21{7L75Wo)A z#aU`Y`D71+G6Uzh(Am3!ZSHzb5^D}Y9*-g)KZRBF)B51?<`-*3pfSJA;?<^{Xq_ck z{4(2bwV^4iOgDRo0NC9RT=AX$v-fFGA5gPHlqwC`i#amgV^)R86MK_qP%EWbQo4 z0k>&k+Z_qWO^^h}0=f|K_p5NSP9(xw#Gcytq~|N~sRnfTK8)Y$XEFrH)>)iR?9q*+ zYNu8d&*o`wd_1rgw;5H{um$p?j(gZU%+i~0FztLp;1_V#veepJJM*ID%WW_$Q0HySi%60 z&`#tFSGS&K+0p${V*x$hR(UZ(}jY`xvjA*M=HHDx+Ak)U*xfcpU zpS|H(b-(-=!u<|d{?QI`<$G~z@6s~L5#j3VN^;8vy*1@j!Mo3rnv?gSUyW>dz^CmO zwb6ho+?8)GsvN-c z$VMP|NvVmCM8)J+)d`yqvUGp_C_(k0f9M*sc$zhLWqKHr?YJE4J&I}~(^x!__an=5y)Dee69ul6VBB)n_mT|-m|0`(#lS7ihv_x+yCO&BGd%a$6$I?>c zfPONRGsK7cx?tFvPw57*Ntj~o5s6jocZB0lch?xM3-jHz@RF99>b|y%BLmtfP zuaZCx`d!|=J3`kL!pY1*=?{MdQIJ;AqAlmu)wu;E{ko9-oPR>M>ubUL}Dw;ZEu# zJtgVwSn{`-BZZKslC)#ZV^~D339yNSZ+!QR*CjFmkBUs3| z2-`Ms@d;xj<)m2QyX5tBJkomGo!!ov94vbl#Q~F;m9q{4wd!UGcUm4!7gE>I+eRuk zF$D5l(011C!uxg;uQH>i1vmM_af{wFXET;U5Ima?xaoXB$I`C9R<@KI(kb=A;^3q+ z%Z%EHWY3t*6WHs+^GP-#w)6NTIci(#P)QYabvdVt%mYtyc64b1v&nX}3e!n!*X(gA z=OVEpM}Ww)e*ah+^@B5xB3!l6L}RUA3+FVjX}isGu`7WHc33WAKSie`i$&aQuLws* zuz9nWxv(@V$w5oCUcL=j+1#iq8}-JOKwZV2>%L06oS(g1#*N-RyvZUYIV(pk%BYb+~S8T{EdaZ*_q}fbMbUVZF z8FyD}_QH&eCR9yt5v=WKFWojA$#g$cJX!;5Wi9fNn9 zm*-Ogmx)@PvY^-W!22=`rOQ3%mXE*01RbeuRSksWt2X2@s8rOzs*YuYb2gRn3xu)( z9$3WV@<$us&Z&#Hp6AO2ThPA`sq=foS#-aVSA#A!F)SXhtdHa;f_%ZUjZ{5-Y~}P8 zo^GBM2*EnD-Ub^3QZ|tuZsCff{ocS{-;dRe9OlhEEc1RdD=&@(l(RXMvd=@??r7UZ zW)EMUHC1xVaqtx21${BMi(CWEgVOr~S^K`Ef4|}uoQA)hzR#WqlW%h^jp|=l9J2UFZ!y=A~fD($jCP7QnlAbes3l)9k3CdvOsGak|qGJo5FWe3na7WGtGR zrhn|Kr?y$xn|0(Y7bUcF&&d4JSEJ}G<1%9vm7V(PzzS?U`!i`i$y##gz^$}Tyow7r zMD_}OtB(-QRhW6mC=L7!$q5{kcWO7S!R8jDc*l!E4bSbQQeYz6BXlUVc}^NO)wRi; zZe(tc^=!}MUS!XEYJQpgwr+2AJ|DMo1iAV{;jy@7ekVO z7Gt07H8`ZNTZZTBH<(xl(Ja@O))3KW^8cr&F&tW6Y(aK7db{eyRN71Z-870b` z7Rpe4b#^Rj@@f|LT+(My)qzF2SSWH`sISA?B{Gz5y}r`d=9P0L@R4z^18PL}+$HW$ zf|F!5kU3IV*pPjYVQdUVt2Z_$sj35)TCu|lK+LZV0G;T!ZEJJ~SnMC;6+sdlI*o6f z^vLO#!hPZ;Hk9s5R{g6b1}svA1G7Vs7A7pH0=jkdS?r2zM8l5FiMmKb03E34oN z0>g3aEZ9{7WyUChSHqd7ME>{%*BSOAwVmVM%sLqsqQ~+@o4#!1iu(DMMQr{L4C)sl=( z@~J@gx7xw5F%h>pifJY!UdoJZCmUx_t3gm9H?tahc+kLNUeuLEV|B>EjoPz!(e~4* zS9vO@2jQCYR=T(zz*IO(v;`F0wrv}#Z5_uIdnaQ~(suWhkh{PVNHSt`u~@#+C_fWk z*l_-=Fb$J66#7{A)T5A4bcYhrAHJJp*EhP5tr z9x@-hq+1DCwMn~#%>LD zWa;M=D;O!x9H_{^}d~Vc0(4O&d(m z!Iz;lp^`_h#Zn@zHL8q~WR~8}QcXT%;es|o!t#nnrdiQ)OZ-bMrW4me?$Mxb<)`R^ zmj?qgOY7?$y32TxT6p(S-)293O%5?uWwoXxbvIm`dt3Joh@YZZFcv>Wd4+d=UYQev z%>wFcw4%8uC-RX$HTz~aA3B+SnM#U4Ys5E*j%HQB*G!1S)tutrDj8)0HB#{vyXIhg zSXN|^+6b*rnkl7bMNYCqI;R%|C($2LpM{+8#Snys^;iESr)J2{Rsau*LO3!V9yPc2AB ztBJteTb7v}*)sLJlt4M}3xy*VY_{(16L5`4sPjcr#RgS7#qZ}8P`KBvKYydT*k9H; zXz3b!@8w<$>_es-l3vcyJ4lncs;Jjf_Kh@V*Yz`!1|HvPSblO1wAwLWoOE6uMhs&6 zBui?ciuHBm+`#thOn+}EVNX1P+yKEd5f=+WLEIociqhuL=Yjr9UX4Hfiw9thGwI{EK@z`p zCbKV&3twq953^zXgyVi&*8a3&y7^U`+-If=j?bGfK5sJNno&$FUZN<^>`?rv*>m(>x3Tro?0tj$35Zt zymf5LQ#cOpMbsNTLE(fyRs^&b$K(Bl6HaNVP-mCHu~*WN#C+T+FH5ww3P8nmxFks+ zCp7y^b|5Aae_m-y>hTvoosG^i!&tO*rKLV~bN5eFJM)C3zM1T!%o<}R> zCWay+QfhU7Q{@spdj2`=mEbc5rE*{z_jhz63PPF>m%vTVlpG+g6`Zu7gNy-8#PwNv zzH!bP!kwY2{?8ziX8I0|9c@{kpJ3NL`vxsy&k}u&QQ3Y-DiOt8HaoouA)11ssW(-G z`TdC*ft359Yp6Vd7w@@zfT&%fPGK>h_1Jk)_ZVE>2megEA~=a{;k@G)`5{aoz*_?A zSpaek)(TfpI^Wm&t<4{lc$PS=qsX+qB}~ zBE3l@RSsUDn?17rPq`h;`MGM1UyKa>;|$t~y61&~<)4UfWrSG`CTGBR4gA@(rc@ja zi4~uoS#10)_rvikS_ib9VJ z0nwZz138Nj1F)i}g(wacz$j55;YTDf+cI8)T14DN47M-6rcTtCqS@88xv6%ad}-ls zUUHYra*o_KMt^wu?X-UtW<4HDmbb&O-MKBe<5i{3PvFiUV^6{WU@WVpi04Ob6eCo5 z_bn{oyZfmHP|yEF5Xy4jxvha!MAu*i0r2NA1R(|8nY3hFj^q1A{U-9cw6tTb zUfov2OEUIO#|Q42FW(hKNB5{b>wil7aVPjfaizQeQVX%2IPwT5_r`W^cfB<7mS-se zs@Zo{69~kMG>~DM;Jv~3=DI#^I^lJg)nr>3&1e5P)znNS$GlCKn?rwCj^1Lu+nqMb`;E(NtB1#%>n5#xZ$~}H-DNPtswXopX3^K% zu7Lx(jv0pPQBPMoqh;nV!e5+Tg9^oC?~(wyHLD&SNsMgcVr!Z=S$i|t@a@r z>#r`}I8DEHO%aYleYMck&AT?;zYuSHA=O{qadf>l`XGp`HA!fc-#s0i@a=w_{=%Lq zOx&%F6%ODOnI5?ckP}yn{g}0#9CibF8*G&!k(Ga4w$$k!zw{K5W1%75<-UG=QW42e z73qUFsaNJ6uZ`EfRG0pqtn=Fk-YY(T`PoT5^uR#cn&j@1kC#m{3j%zHJ(uZ@f|3NI z&fMqg}qn-#=X~o0uOb=J>;dFWd1!{a~&cvt`)`KrbtVzO})bgxC?Zm|p(yV6Ob)xM+p z6D5b8HIpZR_0gofjwG=Z->d8Pdy!us=yhz4<)((|)n$pfUk0(xUw*k3(c`-ifViI- zQU+hdE0AXKth+-ntanq7pQ7%^T-o|1DYnB-ec6|HZ*MTP1FF?~GXn3W0?Bz?abRu} z95SBhv%ub}cHOd!ueiChURu-jpDUVeVDFdG*Suqq?H#qH(_^}bI5cXZq6MOFfq)0F zrtI|>tke<2F4FH;FOaN<#Ef<^7aEVRjZ=5}l@ZVeWie-Of}`#9T?cP~FcB0LRQG6K z`!x-~58Vy)hXH?Wt_3Z?)1M$=y# zNPjk!*x6XQ{?|^^zl%!#(P}XLqa=}kmy|FF{gJ@fy(=$3|LhwnOT5cM{%^{d%w)Gu zDvOw*;>{%t-)oS|7facobUgP|t`I+~83qbKX0Ql2c1I&*#R|Ea@ zs9Oi^!*K5%I5r9xk_K4UhM5^q;$9Sd=DD3OEv-bdzwY*w-p{0UiMCUb!fEc!H;#lu zgbWG*CpOE@>qCLH1D>c-*N4OW1{}IhqGk?ZV#dsc&8r~U>aDi9E#9SR9ZXH3*v`e( z9K&g2&5CSQTumq6$d3f(>ZFpdo9BB$o3?+FN(b!RQB6`~;}gfWYu zJWO8(-aJ z$FJ)Vq8YLLs_m1-z{oXi(=6gnD)CQFH0O3L9y%A-2z^vu_6Iix2 zJ{&DFm6FfCsqukv0kw+^>!{i^{larx<3B*S$v~c%5*&4Ot-)mEBu}7!ybGJX)Om zwIG{raI{!x=Nvdze|ko6S_4FUzxP;T6$A+KlSF|?hM<=Ow-f}{3h^%h;DneKz}_QV z2f&QK#{^J5fZYK~2dLhK=m3x4Cm{$I(k;UXw(4&Q6L5}#ktb9Zi&=o2COn6Nrv>vx zL`abn9imSpEEXkR@L__Y1p8xPS+0m8-!-W-ZVQZV;A0N-1c(*t1XxF5ChgdK?=D|lA=)&2=ckpS#)?v0| zs{%G6&qucU9rYk?P+YjTeOyI93qbC6Iydqn?tt^f_a$75A?oj>jD^(#S_dE#Cxggr z$UdRq!tBAk1fl+9Rq`)Ua3%>s`O>RnMEyN_L8wg%pF%zXT`GhOOPP#1t=|8Wlog3C z1s_E_al#!hO;PjZFC`4qPlefn%lePUnf*P`bpaHVd+ zZWaIh8ns;UjryN_!BS%Bc}YfJjna+6i~={=`jp7q;{D?r(G*Gr4{D~!3yE=srwhMK zHPy@2bN@E|%}5}@QSdaKaL{*6wBLRWYf5gay+gHwzk|C|H>3>plhNRwMH#0sG9j`x zGUkePw|RVPe2Kl)sPW=)x2*9@eb%?_=g`BNmN1W|N2{BjL%zcV+;rRyTpV0g+!WS+Hk@?3^uhF< z^gY&rdJ8T1A3&`gt-T+5j7e#yBcII3>ms;vE2~b5=_dILR?7J*jZ5k@%=6{z#y;rA z(`$1GHR?2~xCtxBFlaF7S1X<6@+)OmW*2lybZYw!bxnYN8$<|YbeH6Y=XM%q8Rl;o zla|GmPkzkT%IE3lG;a7De!;(`O;Hn;DHSgjH7KXDFthr}pk=&%9Km|mf`YZ2t#u%; zW?56SLVPZVtB3cOSN@lb2O4<32#ttL%oR+1+7nt8x+7X5O=?X@4cq#|n#Z2sz3JAv z#@+)VR$In(V?`^wKPpS<)?7PJym``fYgRB@x<-;Rjony_=VLMKK-e|&ijH5T9kJNW} z$emGLO4`yodK{6PkJ@wXwe4M5;9GoYdZIy=L5`D%7)UHm*ux!%*ZZ1MtsbXd-57so zbY^u{%+Jjq#vjH1p~JPK+?(Ov@CE$E^>O8D{(SFo{z(DI39NwKhbsVz1J$7U;hAC4 zAYFhx-C*6V0YnT_`eZsYnBc`ca^t7xM6vtnhRNZ~t-GFpPVxl6w zt&%anrhZ98L}AKcx`_`;^oh+%$ccH1rHIL-XwYibZHq^0MS_a>Fp2OmJjq-gOYf#u zWJ0%&H{rHU=~;A=>yCdr{@5uU9uSa~v=ws5WR)G^JNJ%vh>Q3PWlOTtVo^x=XG8rT=zmVr*hQHB0Ga z>8NE;{eAtkFu7rP4UH}fA7wj34{JW7NXwAr?5XO9Ji*C0St=(@v)0t;SJ@lMNU{v_ zKu!2MtJ+m_3DfK$r4iKK>+SSyyJ3RCtHCE9HZwLi7xNC+*O2FKwo_}Q%HX-Gce$Xv zn{Ic@#!bgjj4jAka`Ajvd^pM!Z_O@zbw(*isiwogqV8pJo_Exm5YTxL9O4C7k{ zSGJqarEq`nMMM}@D7U&Ze@p%LRAyC8)#|W+O10iavt#K}`_r>(O1XIzmoATYrg!^E z$V=25VQW^h{;`kc!H+ZB_5+8ym*lO==nhEly+_?AhSl2^@15wkOgxAPsK-V6T>QlbpOTq zSG_+9<^R!1JAoMFOie5eh3(xz+J7X$?5rRT)^|a(h`r0ZaF_|iAY*Cb^e%w@$J)Q? znVJ4wBL25U5kqG~8+-G=CD=~?(q5~XIyza}+kqIE=vn{0{y$Ftap+xWZQ^45t}3Up zH8fUG0sX5A6C*tn+j}h?Dq$B(oA*v}i7|6>=um@{TnrtZJwWfO?7xKE?#|TjF24)V zo!=W0{iBGc66I!QWM*V!V`5=qXXRw((qd$!eE+`lbqKyE~ei!7+l~ zA3|gNUmp-V8ygE7$PDy1#>B?O^*%a(J|Me)Fh)iuuJYcJ_V40#GyC_u z>#r9INQu7RU%c#$tY%#A?wc7K8gj9jm~e7(nz6F5u`n95G8!_PGV{az-zo1({J-8r X@P2RnHE~SrOl%x* - + - + @@ -18,165 +18,138 @@ - - + + + + - - + + - - - - - - - - - - + + - + - - + + - - + + - - + + - - - - - - - - - + - - + + - - + + - - + + - - - - - - - - + - - + + - - + + - - + + - - - - - - - - - - + + - - - - - - - + + + + - - - - - - + + + - - + + + + + + + + + + + + + - - + - - - + + - - + @@ -184,7 +157,7 @@ - + @@ -199,7 +172,7 @@ - + @@ -210,15 +183,8 @@ - - - - - - - - - - - - - - - - - - + - - + + - - - - + @@ -280,28 +233,12 @@ - - - + - - + - - - - - - - - - - - - - @@ -309,19 +246,16 @@ - + - + - - - - + - + diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift index 175dcfe785..a551aeb4aa 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift @@ -31,7 +31,7 @@ final class SRSnapshotTests: SnapshotTestCase { func testBasicTexts() throws { show(fixture: .basicTexts) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -44,7 +44,7 @@ final class SRSnapshotTests: SnapshotTestCase { func testSliders() throws { show(fixture: .sliders) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -57,7 +57,7 @@ final class SRSnapshotTests: SnapshotTestCase { func testSegments() throws { show(fixture: .segments) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -70,7 +70,7 @@ final class SRSnapshotTests: SnapshotTestCase { func testPickers() throws { show(fixture: .pickers) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -83,7 +83,7 @@ final class SRSnapshotTests: SnapshotTestCase { func testSwitches() throws { show(fixture: .switches) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -96,7 +96,7 @@ final class SRSnapshotTests: SnapshotTestCase { func testTextFields() throws { show(fixture: .textFields) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -109,7 +109,7 @@ final class SRSnapshotTests: SnapshotTestCase { func testSteppers() throws { show(fixture: .steppers) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -124,7 +124,7 @@ final class SRSnapshotTests: SnapshotTestCase { vc1.set(date: .mockDecember15th2019At10AMUTC(), timeZone: .UTC) wait(seconds: 1.0) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -138,7 +138,7 @@ final class SRSnapshotTests: SnapshotTestCase { vc2.openCalendarPopover() wait(seconds: 1.0) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -151,7 +151,7 @@ final class SRSnapshotTests: SnapshotTestCase { vc3.set(date: .mockDecember15th2019At10AMUTC(), timeZone: .UTC) wait(seconds: 1.5) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -164,7 +164,7 @@ final class SRSnapshotTests: SnapshotTestCase { func testTimePickers() throws { show(fixture: .timePickersCountDown) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -177,7 +177,7 @@ final class SRSnapshotTests: SnapshotTestCase { vc1.set(date: .mockDecember15th2019At10AMUTC(), timeZone: .UTC) wait(seconds: 1.0) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -191,7 +191,7 @@ final class SRSnapshotTests: SnapshotTestCase { vc2.openTimePickerPopover() wait(seconds: 1.0) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -204,7 +204,7 @@ final class SRSnapshotTests: SnapshotTestCase { func testImages() throws { show(fixture: .images) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes([.allow, .mask]) { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, @@ -230,7 +230,7 @@ final class SRSnapshotTests: SnapshotTestCase { wait(seconds: 1.0) - try forEachPrivacyMode { privacyMode in + try forPrivacyModes { privacyMode in let image = try takeSnapshot(with: privacyMode) DDAssertSnapshotTest( newImage: image, diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/ImageRendering.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/ImageRendering.swift index 069f360401..4d1d20d1a6 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/ImageRendering.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/ImageRendering.swift @@ -36,13 +36,13 @@ internal struct WireframesRenderingDebugInfo { } /// Renders wireframes into image. -internal func renderImage(for wireframes: [SRWireframe]) -> (image: UIImage, debugInfo: WireframesRenderingDebugInfo) { +internal func renderImage(for wireframes: [SRWireframe], resources: [Resource]) -> (image: UIImage, debugInfo: WireframesRenderingDebugInfo) { precondition(!wireframes.isEmpty) - let frame = wireframes[0].toFrame() + let frame = wireframes[0].toFrame(resources: resources) let canvas = FramerCanvas.create(size: CGSize(width: frame.width, height: frame.height)) let blueprint = Blueprint( id: "snapshot", - contents: wireframes.map { .frame($0.toFrame()) } + contents: wireframes.map { .frame($0.toFrame(resources: resources)) } ) canvas.draw(blueprint: blueprint) let debugInfo = WireframesRenderingDebugInfo(wireframes: wireframes, blueprint: blueprint) @@ -52,16 +52,22 @@ internal func renderImage(for wireframes: [SRWireframe]) -> (image: UIImage, deb // MARK: - Wireframes Rendering with Framer private extension SRWireframe { - func toFrame() -> BlueprintFrame { + func toFrame(resources: [Resource]) -> BlueprintFrame { switch self { case .shapeWireframe(let shape): return shape.toFrame() case .textWireframe(let text): return text.toFrame() case .imageWireframe(value: let image): - return image.toFrame() + return image.toFrame( + imageData: resources.first { + $0.calculateIdentifier() == image.resourceId + }?.calculateData() + ) case .placeholderWireframe(value: let placeholder): return placeholder.toFrame() + case .webviewWireframe(value: let webview): + return webview.toFrame() } } } @@ -93,6 +99,19 @@ private extension SRTextWireframe { } private extension SRImageWireframe { + func toFrame(imageData: Data?) -> BlueprintFrame { + BlueprintFrame( + x: CGFloat(x), + y: CGFloat(y), + width: CGFloat(width), + height: CGFloat(height), + style: frameStyle(border: border, style: shapeStyle), + content: frameContent(imageData: imageData) + ) + } +} + +private extension SRWebviewWireframe { func toFrame() -> BlueprintFrame { BlueprintFrame( x: CGFloat(x), @@ -100,7 +119,14 @@ private extension SRImageWireframe { width: CGFloat(width), height: CGFloat(height), style: frameStyle(border: border, style: shapeStyle), - content: frameContent(base64ImageString: base64) + content: frameContent( + text: "WKWebView", + textStyle: nil, + textPosition: SRTextPosition( + alignment: SRTextPosition.Alignment(horizontal: .center, vertical: .center), + padding: nil + ) + ) ) } } @@ -193,10 +219,8 @@ private func frameContent(text: String, textStyle: SRTextStyle?, textPosition: S ) } -private func frameContent(base64ImageString: String?) -> BlueprintFrame.Content { - let base64Data = base64ImageString?.data(using: .utf8) ?? Data() - let imageData = Data(base64Encoded: base64Data) ?? Data() - let image = UIImage(data: imageData, scale: UIScreen.main.scale) ?? UIImage() +private func frameContent(imageData: Data?) -> BlueprintFrame.Content { + let image = UIImage(data: imageData ?? Data(), scale: UIScreen.main.scale) ?? UIImage() let contentType: BlueprintFrame.Content.ContentType = .image(image: image) return .init(contentType: contentType) } diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift index feb592e706..9ab6d11380 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift @@ -37,7 +37,8 @@ internal class SnapshotTestCase: XCTestCase { /// Captures side-by-side snapshot of the app UI and recorded wireframes. func takeSnapshot(with privacyLevel: SessionReplay.Configuration.PrivacyLevel = defaultPrivacyLevel) throws -> UIImage { - let expectation = self.expectation(description: "Wait for wireframes") + let expectWireframes = self.expectation(description: "Wait for wireframes") + let expectResources = self.expectation(description: "Wait for resources") // Set up SR recorder: let snapshotProcessor = SnapshotProcessor( @@ -57,11 +58,18 @@ internal class SnapshotTestCase: XCTestCase { additionalNodeRecorders: [] ) - // Set up wireframes interception : + // Set up wireframes interception: var wireframes: [SRWireframe]? snapshotProcessor.interceptWireframes = { wireframes = $0 - expectation.fulfill() + expectWireframes.fulfill() + } + + // Set up resource interception: + var resources: [Resource]? + resourceProcessor.interceptResources = { + resources = $0 + expectResources.fulfill() } // Capture next record with mock RUM Context @@ -76,7 +84,7 @@ internal class SnapshotTestCase: XCTestCase { XCTFail("Recorded no wireframes.") return UIImage() } - let renderedWireframes = renderImage(for: wireframes) + let renderedWireframes = renderImage(for: wireframes, resources: resources ?? []) let appImage = app.keyWindow.map { renderImage(for: $0) } ?? UIImage() // Add XCTest attachements for debugging and troubleshooting: @@ -106,8 +114,9 @@ internal class SnapshotTestCase: XCTestCase { waitForExpectations(timeout: seconds * 2) } - func forEachPrivacyMode(do work: (SessionReplay.Configuration.PrivacyLevel) throws -> Void) rethrows { - let modes: [SessionReplay.Configuration.PrivacyLevel] = [.mask, .allow, .maskUserInput] + func forPrivacyModes( + _ modes: [SessionReplay.Configuration.PrivacyLevel] = [.mask, .allow, .maskUserInput], + do work: (SessionReplay.Configuration.PrivacyLevel) throws -> Void) rethrows { try modes.forEach { try work($0) } } } diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-allow-privacy.png.json index 93bfc077a2..015c9ab456 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"59df680724c228d86e10beff214ddb2768986c74"} \ No newline at end of file +{"hash":"b34ce4960d712438e9de17c80d793d2bcfc17b4c"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-mask-privacy.png.json index f3cc50fa0b..f250aaae2a 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"e8ae204cefdb861b8cd14add7f14d7065d238c84"} \ No newline at end of file +{"hash":"22aadbd4fc940da34f64a353b38fed482d692316"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-maskUserInput-privacy.png.json deleted file mode 100644 index 93bfc077a2..0000000000 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-maskUserInput-privacy.png.json +++ /dev/null @@ -1 +0,0 @@ -{"hash":"59df680724c228d86e10beff214ddb2768986c74"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSafari().png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSafari().png.json index 6b01ab1dec..e9f111485d 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSafari().png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSafari().png.json @@ -1 +1 @@ -{"hash":"c5342dd3aa490f6a8fe9000cf88ed5741cff6d35"} \ No newline at end of file +{"hash":"f9fc829946664037f512472627bd71d2328f439c"} \ No newline at end of file diff --git a/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/EnrichedRecordJSON.swift b/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/EnrichedRecordJSON.swift deleted file mode 100644 index a9b1e99242..0000000000 --- a/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/EnrichedRecordJSON.swift +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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. - */ - -#if os(iOS) -import Foundation - -internal typealias JSONObject = [String: Any] - -/// A mirror of `EnrichedRecord` but providing decoding capability. Unlike encodable `EnrichedRecord` -/// it can be both encoded and decoded with `Foundation.JSONSerialization`. -/// -/// `EnrichedRecordJSON` values are decoded from batched events (`[Data]`) upon request from `DatadogCore`. -/// They are mapped into one or many `SegmentJSONs`. Segments are then encoded in multipart-body of `URLRequests` -/// and sent by core on behalf of Session Replay. -/// -/// Except containing original records created by `Processor` (in `records: [JSONObject]`), `EnrichedRecordJSON` -/// offers a typed meta information that facilitates grouping records into segments. -internal struct EnrichedRecordJSON { - /// Records enriched with further information. - let records: [JSONObject] - - /// The RUM application ID common to all records. - let applicationID: String - /// The RUM session ID common to all records. - let sessionID: String - /// The RUM view ID common to all records. - let viewID: String - /// If there is a Full Snapshot among records. - let hasFullSnapshot: Bool - /// The timestamp of the earliest record. - let earliestTimestamp: Int64 - /// The timestamp of the latest record. - let latestTimestamp: Int64 - - init(jsonObjectData: Data) throws { - let jsonObject: JSONObject = try decode(jsonObjectData) - - self.records = try read(codingKey: .records, from: jsonObject) - self.applicationID = try read(codingKey: .applicationID, from: jsonObject) - self.sessionID = try read(codingKey: .sessionID, from: jsonObject) - self.viewID = try read(codingKey: .viewID, from: jsonObject) - self.hasFullSnapshot = try read(codingKey: .hasFullSnapshot, from: jsonObject) - self.earliestTimestamp = try read(codingKey: .earliestTimestamp, from: jsonObject) - self.latestTimestamp = try read(codingKey: .latestTimestamp, from: jsonObject) - } -} - -private func decode(_ data: Data) throws -> T { - guard let value = try JSONSerialization.jsonObject(with: data) as? T else { - throw InternalError(description: "Failed to decode \(type(of: T.self))") - } - return value -} - -private func read(codingKey: EnrichedRecord.CodingKeys, from object: JSONObject) throws -> T { - guard let value = object[codingKey.stringValue] as? T else { - throw InternalError(description: "Failed to read attribute at key path '\(codingKey.stringValue)'") - } - return value -} -#endif diff --git a/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/SegmentJSON.swift b/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/SegmentJSON.swift index 650cdb44ee..541e97fe38 100644 --- a/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/SegmentJSON.swift +++ b/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/SegmentJSON.swift @@ -7,6 +7,8 @@ #if os(iOS) import Foundation +internal typealias JSONObject = [String: Any] + /// A counterpart of `SRSegment`. Unlike codable `SRSegment` it can be encoded to JSON data /// with using anonymous `records: [JSONObject]` (the original `SRSegment` requires /// typed `[SRRecords]` which isn't possible to read unambiguously from event data stored in `DatadogCore`). @@ -14,6 +16,20 @@ import Foundation /// Can be considered a temporary solution until we find a way to decode `[SRRecords]` unambiguously /// through `Codable` interface. internal struct SegmentJSON { + private enum Constants { + /// The `timestamp` is common to all records. + /// see. https://github.com/DataDog/rum-events-format/blob/master/schemas/session-replay/common/_common-record-schema.json#L9 + static let timestampKey = "timestamp" + /// The `type` key can be used to identify the type of record. + static let typeKey = "type" + /// The constant type value for browser full snapshot is `2`. + /// see. https://github.com/DataDog/rum-events-format/blob/master/schemas/session-replay/browser/full-snapshot-record-schema.json#L14L19 + static let browserFullsnapshotValue = 2 + /// The constant type value for mobile full snapshot is `10`. + /// see. https://github.com/DataDog/rum-events-format/blob/master/schemas/session-replay/mobile/full-snapshot-record-schema.json#L14L19 + static let nativeFullsnapshotValue = 10 + } + /// The RUM application ID common to all records. let applicationID: String /// The RUM session ID common to all records. @@ -33,6 +49,74 @@ internal struct SegmentJSON { /// If there is a Full Snapshot among records. let hasFullSnapshot: Bool + init( + applicationID: String, + sessionID: String, + viewID: String, + source: String, + start: Int64, + end: Int64, + records: [JSONObject], + recordsCount: Int64, + hasFullSnapshot: Bool + ) { + self.applicationID = applicationID + self.sessionID = sessionID + self.viewID = viewID + self.source = source + self.start = start + self.end = end + self.records = records + self.recordsCount = recordsCount + self.hasFullSnapshot = hasFullSnapshot + } + + /// Creates a segment from raw segment. + /// + /// - Parameters: + /// - data: The raw segment data. + /// - source: The segment source. + init(_ data: Data, source: SRSegment.Source) throws { + let json: JSONObject = try decode(data) + + self.records = try read(codingKey: .records, from: json) + self.recordsCount = Int64(records.count) + + var hasFullSnapshot = false + var start: Int64 = .max + var end: Int64 = .min + + // loop through the records to compute the + // `start`, `end, and `has_full_snapshot` fieds + // of the segment + for record in records { + guard let timestamp = record[Constants.timestampKey] as? Int64 else { + // records must contain a timestamp + throw InternalError(description: "Record is missing timestamp") + } + + start = min(timestamp, start) + end = max(timestamp, end) + + guard let type = record[Constants.typeKey] as? Int64 else { + continue // ignore records with no type + } + + // check for native or browser full snapshot + if type == Constants.nativeFullsnapshotValue || type == Constants.browserFullsnapshotValue { + hasFullSnapshot = true + } + } + + self.applicationID = try read(codingKey: .applicationID, from: json) + self.sessionID = try read(codingKey: .sessionID, from: json) + self.viewID = try read(codingKey: .viewID, from: json) + self.hasFullSnapshot = hasFullSnapshot + self.start = start + self.end = end + self.source = source.rawValue + } + func toJSONObject() -> JSONObject { return [ segmentKey(.application): [applicationKey(.id): applicationID], @@ -52,4 +136,47 @@ private func segmentKey(_ codingKey: SRSegment.CodingKeys) -> String { codingKey private func applicationKey(_ codingKey: SRSegment.Application.CodingKeys) -> String { codingKey.stringValue } private func sessionKey(_ codingKey: SRSegment.Session.CodingKeys) -> String { codingKey.stringValue } private func viewKey(_ codingKey: SRSegment.View.CodingKeys) -> String { codingKey.stringValue } + +private func decode(_ data: Data) throws -> T { + guard let value = try JSONSerialization.jsonObject(with: data) as? T else { + throw InternalError(description: "Failed to decode \(type(of: T.self))") + } + return value +} + +private func read(codingKey: EnrichedRecord.CodingKeys, from object: JSONObject) throws -> T { + guard let value = object[codingKey.stringValue] as? T else { + throw InternalError(description: "Failed to read attribute at key path '\(codingKey.stringValue)'") + } + return value +} + +extension Array where Element == SegmentJSON { + /// Merges Segments from the same `view.id` + /// + /// - Returns: The new list of segments grouped by view id. + func merge() -> [SegmentJSON] { + var indexes: [String: Int] = [:] + return reduce(into: []) { segments, segment in + if let index = indexes[segment.viewID] { + let current = segments[index] + segments[index] = SegmentJSON( + applicationID: current.applicationID, + sessionID: current.sessionID, + viewID: current.viewID, + source: current.source, + start: Swift.min(current.start, segment.start), + end: Swift.max(current.end, segment.end), + records: current.records + segment.records, + recordsCount: current.recordsCount + segment.recordsCount, + hasFullSnapshot: current.hasFullSnapshot || segment.hasFullSnapshot + ) + } else { + indexes[segment.viewID] = segments.count + segments.append(segment) + } + } + } +} + #endif diff --git a/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/SegmentJSONBuilder.swift b/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/SegmentJSONBuilder.swift deleted file mode 100644 index a15b968501..0000000000 --- a/DatadogSessionReplay/Sources/Feature/RequestBuilders/JSON/SegmentJSONBuilder.swift +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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. - */ - -#if os(iOS) -import Foundation - -internal struct SegmentJSONBuilderError: Error, CustomStringConvertible { - let description: String -} - -internal struct SegmentJSONBuilder { - let source: SRSegment.Source - - /// Turns records into segments. - /// It expects all `records` to reference the same RUM view (otherwise it throws an error). - /// - /// - Parameter records: records to bundle within segment - /// - Returns: serializable segment - func createSegmentJSON(from records: [EnrichedRecordJSON]) throws -> SegmentJSON { - guard !records.isEmpty else { - throw SegmentJSONBuilderError( - description: "Records array must not be empty." - ) - } - - // Segment state: - var current = records[0] - var hasFullSnapshot = current.hasFullSnapshot - var startTime = current.earliestTimestamp - var endTime = current.latestTimestamp - - // Verify if all records share the same RUM context and record segment state: - for index in (1.. Bool { - return record1.viewID == record2.viewID - && record1.sessionID == record2.sessionID - && record1.applicationID == record2.applicationID - } - - private func createSegmentJSON( - records: [EnrichedRecordJSON], - hasFullSnapshot: Bool, - startTime: Int64, - endTime: Int64 - ) -> SegmentJSON { - let anyRecord = records[0] - let recordsJSONArray = records.flatMap { $0.records } - - return SegmentJSON( - applicationID: anyRecord.applicationID, - sessionID: anyRecord.sessionID, - viewID: anyRecord.viewID, - source: source.rawValue, - start: startTime, - end: endTime, - records: recordsJSONArray, - recordsCount: Int64(recordsJSONArray.count), - hasFullSnapshot: hasFullSnapshot - ) - } -} -#endif diff --git a/DatadogSessionReplay/Sources/Feature/RequestBuilders/Multipart/MultipartFormData.swift b/DatadogSessionReplay/Sources/Feature/RequestBuilders/Multipart/MultipartFormData.swift index aca471c407..5b1e4b729f 100644 --- a/DatadogSessionReplay/Sources/Feature/RequestBuilders/Multipart/MultipartFormData.swift +++ b/DatadogSessionReplay/Sources/Feature/RequestBuilders/Multipart/MultipartFormData.swift @@ -8,27 +8,29 @@ import Foundation internal protocol MultipartFormDataBuilder { - /// The boundary UUID of this multipart form. - var boundary: UUID { get } + /// The boundary of this multipart form. + var boundary: String { get } /// Adds a field. mutating func addFormField(name: String, value: String) /// Adds a file. mutating func addFormData(name: String, filename: String, data: Data, mimeType: String) /// Returns the entire multipart body data (as it should be applied to request). - var data: Data { get } + mutating func build() -> Data } /// A helper facilitating creation of `multipart/form-data` body. internal struct MultipartFormData: MultipartFormDataBuilder { - let boundary: UUID - private var body = Data() + private var body: Data - init(boundary: UUID) { - self.boundary = boundary + private(set) var boundary: String + + init(boundary: UUID = UUID()) { + self.body = Data() + self.boundary = boundary.uuidString } mutating func addFormField(name: String, value: String) { - body.append(string: "--\(boundary.uuidString)\r\n") + body.append(string: "--\(boundary)\r\n") body.append(string: "Content-Disposition: form-data; name=\"\(name)\"\r\n") body.append(string: "\r\n") body.append(string: value) @@ -36,7 +38,7 @@ internal struct MultipartFormData: MultipartFormDataBuilder { } mutating func addFormData(name: String, filename: String, data: Data, mimeType: String) { - body.append(string: "--\(boundary.uuidString)\r\n") + body.append(string: "--\(boundary)\r\n") body.append(string: "Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(filename)\"\r\n") body.append(string: "Content-Type: \(mimeType)\r\n") body.append(string: "\r\n") @@ -44,10 +46,15 @@ internal struct MultipartFormData: MultipartFormDataBuilder { body.append(string: "\r\n") } - var data: Data { - var data = body - data.append(string: "--\(boundary.uuidString)--") - return data + mutating func build() -> Data { + defer { + // reset builder + body = Data() + boundary = UUID().uuidString + } + + body.append(string: "--\(boundary)--") + return body } } diff --git a/DatadogSessionReplay/Sources/Feature/RequestBuilders/ResourceRequestBuilder.swift b/DatadogSessionReplay/Sources/Feature/RequestBuilders/ResourceRequestBuilder.swift index c27ec93183..c25ee42836 100644 --- a/DatadogSessionReplay/Sources/Feature/RequestBuilders/ResourceRequestBuilder.swift +++ b/DatadogSessionReplay/Sources/Feature/RequestBuilders/ResourceRequestBuilder.swift @@ -19,7 +19,7 @@ internal struct ResourceRequestBuilder: FeatureRequestBuilder { init( customUploadURL: URL?, telemetry: Telemetry, - multipartBuilder: MultipartFormDataBuilder = MultipartFormData(boundary: UUID()) + multipartBuilder: MultipartFormDataBuilder = MultipartFormData() ) { self.customUploadURL = customUploadURL self.telemetry = telemetry @@ -41,7 +41,7 @@ internal struct ResourceRequestBuilder: FeatureRequestBuilder { url: url(with: context), queryItems: [], headers: [ - .contentTypeHeader(contentType: .multipartFormData(boundary: multipart.boundary.uuidString)), + .contentTypeHeader(contentType: .multipartFormData(boundary: multipart.boundary)), .userAgentHeader(appName: context.applicationName, appVersion: context.version, device: context.device), .ddAPIKeyHeader(clientToken: context.clientToken), .ddEVPOriginHeader(source: context.source), @@ -69,7 +69,7 @@ internal struct ResourceRequestBuilder: FeatureRequestBuilder { ) } - return builder.uploadRequest(with: multipart.data, compress: true) + return builder.uploadRequest(with: multipart.build(), compress: true) } private func url(with context: DatadogContext) -> URL { diff --git a/DatadogSessionReplay/Sources/Feature/RequestBuilders/SegmentRequestBuilder.swift b/DatadogSessionReplay/Sources/Feature/RequestBuilders/SegmentRequestBuilder.swift index 1cda1ff4fd..e93efab853 100644 --- a/DatadogSessionReplay/Sources/Feature/RequestBuilders/SegmentRequestBuilder.swift +++ b/DatadogSessionReplay/Sources/Feature/RequestBuilders/SegmentRequestBuilder.swift @@ -16,33 +16,45 @@ internal struct SegmentRequestBuilder: FeatureRequestBuilder { /// Sends telemetry through sdk core. let telemetry: Telemetry /// Builds multipart form for request's body. - var multipartBuilder: MultipartFormDataBuilder = MultipartFormData(boundary: UUID()) + let multipartBuilder: MultipartFormDataBuilder + + init( + customUploadURL: URL?, + telemetry: Telemetry, + multipartBuilder: MultipartFormDataBuilder = MultipartFormData() + ) { + self.customUploadURL = customUploadURL + self.telemetry = telemetry + self.multipartBuilder = multipartBuilder + } func request(for events: [Event], with context: DatadogContext) throws -> URLRequest { - let fallbackSource: () -> SRSegment.Source = { - telemetry.error("[SR] Could not create segment source from provided string '\(context.source)'") - return .ios + guard !events.isEmpty else { + throw InternalError(description: "[SR] batch events must not be empty.") } - let source = SRSegment.Source(rawValue: context.source) ?? fallbackSource() - let segmentBuilder = SegmentJSONBuilder(source: source) + let source = SRSegment.Source(rawValue: context.source) ?? { + telemetry.error("[SR] Could not create segment source from provided string '\(context.source)'") + return .ios + }() // If we can't decode `events: [Data]` there is no way to recover, so we throw an // error to let the core delete the batch: - let records = try events.map { try EnrichedRecordJSON(jsonObjectData: $0.data) } - let segment = try segmentBuilder.createSegmentJSON(from: records) + let segments = try events + .map { try SegmentJSON($0.data, source: source) } + .merge() - return try createRequest(segment: segment, context: context) + return try createRequest(segments: segments, context: context) } - private func createRequest(segment: SegmentJSON, context: DatadogContext) throws -> URLRequest { + private func createRequest(segments: [SegmentJSON], context: DatadogContext) throws -> URLRequest { var multipart = multipartBuilder let builder = URLRequestBuilder( url: url(with: context), queryItems: [], headers: [ - .contentTypeHeader(contentType: .multipartFormData(boundary: multipart.boundary.uuidString)), + .contentTypeHeader(contentType: .multipartFormData(boundary: multipart.boundary)), .userAgentHeader(appName: context.applicationName, appVersion: context.version, device: context.device), .ddAPIKeyHeader(clientToken: context.clientToken), .ddEVPOriginHeader(source: context.source), @@ -52,36 +64,41 @@ internal struct SegmentRequestBuilder: FeatureRequestBuilder { telemetry: telemetry ) - // Session Replay BE accepts compressed segment data followed by newline character (before compression): - var segmentData = try JSONSerialization.data(withJSONObject: segment.toJSONObject()) - segmentData.append(SegmentRequestBuilder.newlineByte) - let compressedSegmentData = try SRCompression.compress(data: segmentData) + let metadata = try segments.enumerated().map { index, segment in + var json = segment.toJSONObject() + // Session Replay BE accepts compressed segment data followed by newline character (before compression): + let data = try JSONSerialization.data(withJSONObject: json) + SegmentRequestBuilder.newlineByte + let compressedData = try SRCompression.compress(data: data) + // Compressed segment is sent within multipart form data - with some of segment (metadata) + // attributes listed as form fields: + multipart.addFormData( + name: "segment", + filename: "file\(index)", + data: compressedData, + mimeType: "application/octet-stream" + ) + // Remove the 'records' for the metadata + json["records"] = nil + json["raw_segment_size"] = data.count + json["compressed_segment_size"] = compressedData.count + return json + } - // Compressed segment is sent within multipart form data - with some of segment (metadata) - // attributes listed as form fields: + let data = try JSONSerialization.data(withJSONObject: metadata) multipart.addFormData( - name: "segment", - filename: segment.sessionID, - data: compressedSegmentData, - mimeType: "application/octet-stream" + name: "event", + filename: "blob", + data: data, + mimeType: "application/json" ) - multipart.addFormField(name: "segment", value: segment.sessionID) - multipart.addFormField(name: "application.id", value: segment.applicationID) - multipart.addFormField(name: "session.id", value: segment.sessionID) - multipart.addFormField(name: "view.id", value: segment.viewID) - multipart.addFormField(name: "has_full_snapshot", value: segment.hasFullSnapshot ? "true" : "false") - multipart.addFormField(name: "records_count", value: "\(segment.recordsCount)") - multipart.addFormField(name: "raw_segment_size", value: "\(compressedSegmentData.count)") - multipart.addFormField(name: "start", value: "\(segment.start)") - multipart.addFormField(name: "end", value: "\(segment.end)") - multipart.addFormField(name: "source", value: "\(context.source)") // Data is already compressed, so request building request w/o compression: - return builder.uploadRequest(with: multipart.data, compress: false) + return builder.uploadRequest(with: multipart.build(), compress: false) } private func url(with context: DatadogContext) -> URL { customUploadURL ?? context.site.endpoint.appendingPathComponent("api/v2/replay") } } + #endif diff --git a/DatadogSessionReplay/Sources/Feature/ResourcesFeature.swift b/DatadogSessionReplay/Sources/Feature/ResourcesFeature.swift index b0e94e04c1..b3e11e1cbc 100644 --- a/DatadogSessionReplay/Sources/Feature/ResourcesFeature.swift +++ b/DatadogSessionReplay/Sources/Feature/ResourcesFeature.swift @@ -12,6 +12,7 @@ internal class ResourcesFeature: DatadogRemoteFeature { static var name = "session-replay-resources" let messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver() + let performanceOverride: PerformancePresetOverride? let requestBuilder: FeatureRequestBuilder @@ -23,6 +24,10 @@ internal class ResourcesFeature: DatadogRemoteFeature { customUploadURL: configuration.customEndpoint, telemetry: core.telemetry ) + self.performanceOverride = PerformancePresetOverride( + maxFileSize: SessionReplay.maxObjectSize, + maxObjectSize: SessionReplay.maxObjectSize + ) } } #endif diff --git a/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift b/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift index e4f1de5d2c..8faf090742 100644 --- a/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift +++ b/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift @@ -26,21 +26,20 @@ internal class SessionReplayFeature: DatadogRemoteFeature { core: DatadogCoreProtocol, configuration: SessionReplay.Configuration ) throws { - let queue = BackgroundAsyncQueue(named: "com.datadoghq.session-replay.processor") + let processorsQueue = BackgroundAsyncQueue(named: "com.datadoghq.session-replay.processors") let snapshotProcessor = SnapshotProcessor( - queue: queue, + queue: processorsQueue, recordWriter: RecordWriter(core: core), srContextPublisher: SRContextPublisher(core: core), telemetry: core.telemetry ) - // RUM-2154 Disabled until prod backend is ready - _ = ResourceProcessor( - queue: queue, + let resourceProcessor = ResourceProcessor( + queue: processorsQueue, resourcesWriter: ResourcesWriter(core: core) ) let recorder = try Recorder( snapshotProcessor: snapshotProcessor, - resourceProcessor: nil, + resourceProcessor: resourceProcessor, telemetry: core.telemetry, additionalNodeRecorders: configuration._additionalNodeRecorders ) @@ -61,8 +60,8 @@ internal class SessionReplayFeature: DatadogRemoteFeature { telemetry: core.telemetry ) self.performanceOverride = PerformancePresetOverride( - maxFileSize: 10.MB.asUInt64(), - maxObjectSize: 10.MB.asUInt64(), + maxFileSize: SessionReplay.maxObjectSize, + maxObjectSize: SessionReplay.maxObjectSize, meanFileAge: 2, // vs 5s with `batchSize: .small` - see `DatadogCore.PerformancePreset` uploadDelay: ( initial: 2, // vs 5s with `uploadFrequency: .frequent` diff --git a/DatadogSessionReplay/Sources/Models/EnrichedRecord.swift b/DatadogSessionReplay/Sources/Models/EnrichedRecord.swift index 8a06a3e36f..60a277ca49 100644 --- a/DatadogSessionReplay/Sources/Models/EnrichedRecord.swift +++ b/DatadogSessionReplay/Sources/Models/EnrichedRecord.swift @@ -16,73 +16,28 @@ import Foundation /// `EnrichedRecord` conforms to `Encodable` so it can be encoded by `DatadogCore`. /// For decoding `EnrichedRecord` information, see `EnrichedRecordJSON` type. internal struct EnrichedRecord: Encodable { - /// Records enriched with further information. - let records: [SRRecord] - /// The RUM application ID of all records. let applicationID: String /// The RUM session ID of all records. let sessionID: String /// The RUM view ID of all records. let viewID: String - /// If there is a Full Snapshot among records. - let hasFullSnapshot: Bool - /// The timestamp of the earliest record. - let earliestTimestamp: Int64 - /// The timestamp of the latest record. - let latestTimestamp: Int64 + /// Records enriched with further information. + let records: [SRRecord] enum CodingKeys: String, CodingKey { case records case applicationID case sessionID case viewID - case hasFullSnapshot - case earliestTimestamp - case latestTimestamp } init(context: Recorder.Context, records: [SRRecord]) { - self.records = records self.applicationID = context.applicationID self.sessionID = context.sessionID self.viewID = context.viewID - - var hasFullSnapshot = false - var earliestTimestamp: Int64 = .max - var latestTimestamp: Int64 = .min - - for record in records { - hasFullSnapshot = hasFullSnapshot || record.isFullSnapshot - earliestTimestamp = min(record.timestamp, earliestTimestamp) - latestTimestamp = max(record.timestamp, latestTimestamp) - } - - self.hasFullSnapshot = hasFullSnapshot - self.earliestTimestamp = earliestTimestamp - self.latestTimestamp = latestTimestamp + self.records = records } } -// MARK: - Convenience - -extension SRRecord { - var isFullSnapshot: Bool { - switch self { - case .fullSnapshotRecord: return true - default: return false - } - } - - var timestamp: Int64 { - switch self { - case .fullSnapshotRecord(let record): return record.timestamp - case .incrementalSnapshotRecord(let record): return record.timestamp - case .metaRecord(let record): return record.timestamp - case .focusRecord(let record): return record.timestamp - case .viewEndRecord(let record): return record.timestamp - case .visualViewportRecord(let record): return record.timestamp - } - } -} #endif diff --git a/DatadogSessionReplay/Sources/Models/SRDataModels.swift b/DatadogSessionReplay/Sources/Models/SRDataModels.swift index 2358cdad62..61a2806adc 100644 --- a/DatadogSessionReplay/Sources/Models/SRDataModels.swift +++ b/DatadogSessionReplay/Sources/Models/SRDataModels.swift @@ -437,6 +437,53 @@ public struct SRPlaceholderWireframe: Codable, Hashable { } } +/// Schema of all properties of a WebviewWireframe. +@_spi(Internal) +public struct SRWebviewWireframe: Codable, Hashable { + /// The border properties of this wireframe. The default value is null (no-border). + public let border: SRShapeBorder? + + /// Schema of clipping information for a Wireframe. + public let clip: SRContentClip? + + /// The height in pixels of the UI element, normalized based on the device pixels per inch density (DPI). Example: if a device has a DPI = 2, the height of all UI elements is divided by 2 to get a normalized height. + public let height: Int64 + + /// Defines the unique ID of the wireframe. This is persistent throughout the view lifetime. + public let id: Int64 + + /// The style of this wireframe. + public let shapeStyle: SRShapeStyle? + + /// Unique Id of the slot containing this webview. + public let slotId: String + + /// The type of the wireframe. + public let type: String = "webview" + + /// The width in pixels of the UI element, normalized based on the device pixels per inch density (DPI). Example: if a device has a DPI = 2, the width of all UI elements is divided by 2 to get a normalized width. + public let width: Int64 + + /// The position in pixels on X axis of the UI element in absolute coordinates. The anchor point is always the top-left corner of the wireframe. + public let x: Int64 + + /// The position in pixels on Y axis of the UI element in absolute coordinates. The anchor point is always the top-left corner of the wireframe. + public let y: Int64 + + enum CodingKeys: String, CodingKey { + case border = "border" + case clip = "clip" + case height = "height" + case id = "id" + case shapeStyle = "shapeStyle" + case slotId = "slotId" + case type = "type" + case width = "width" + case x = "x" + case y = "y" + } +} + /// Schema of a Wireframe type. @_spi(Internal) public enum SRWireframe: Codable { @@ -444,6 +491,7 @@ public enum SRWireframe: Codable { case textWireframe(value: SRTextWireframe) case imageWireframe(value: SRImageWireframe) case placeholderWireframe(value: SRPlaceholderWireframe) + case webviewWireframe(value: SRWebviewWireframe) // MARK: - Codable @@ -460,6 +508,8 @@ public enum SRWireframe: Codable { try container.encode(value) case .placeholderWireframe(let value): try container.encode(value) + case .webviewWireframe(let value): + try container.encode(value) } } @@ -483,6 +533,10 @@ public enum SRWireframe: Codable { self = .placeholderWireframe(value: value) return } + if let value = try? container.decode(SRWebviewWireframe.self) { + self = .webviewWireframe(value: value) + return + } let error = DecodingError.Context( codingPath: container.codingPath, debugDescription: """ @@ -649,6 +703,7 @@ public struct SRIncrementalSnapshotRecord: Codable { case shapeWireframeUpdate(value: ShapeWireframeUpdate) case imageWireframeUpdate(value: ImageWireframeUpdate) case placeholderWireframeUpdate(value: PlaceholderWireframeUpdate) + case webviewWireframeUpdate(value: WebviewWireframeUpdate) // MARK: - Codable @@ -665,6 +720,8 @@ public struct SRIncrementalSnapshotRecord: Codable { try container.encode(value) case .placeholderWireframeUpdate(let value): try container.encode(value) + case .webviewWireframeUpdate(let value): + try container.encode(value) } } @@ -688,6 +745,10 @@ public struct SRIncrementalSnapshotRecord: Codable { self = .placeholderWireframeUpdate(value: value) return } + if let value = try? container.decode(WebviewWireframeUpdate.self) { + self = .webviewWireframeUpdate(value: value) + return + } let error = DecodingError.Context( codingPath: container.codingPath, debugDescription: """ @@ -893,6 +954,53 @@ public struct SRIncrementalSnapshotRecord: Codable { case y = "y" } } + + /// Schema of all properties of a WebviewWireframeUpdate. + @_spi(Internal) + public struct WebviewWireframeUpdate: Codable { + /// The border properties of this wireframe. The default value is null (no-border). + public let border: SRShapeBorder? + + /// Schema of clipping information for a Wireframe. + public let clip: SRContentClip? + + /// The height in pixels of the UI element, normalized based on the device pixels per inch density (DPI). Example: if a device has a DPI = 2, the height of all UI elements is divided by 2 to get a normalized height. + public let height: Int64? + + /// Defines the unique ID of the wireframe. This is persistent throughout the view lifetime. + public let id: Int64 + + /// The style of this wireframe. + public let shapeStyle: SRShapeStyle? + + /// Unique Id of the slot containing this webview. + public let slotId: String + + /// The type of the wireframe. + public let type: String = "webview" + + /// The width in pixels of the UI element, normalized based on the device pixels per inch density (DPI). Example: if a device has a DPI = 2, the width of all UI elements is divided by 2 to get a normalized width. + public let width: Int64? + + /// The position in pixels on X axis of the UI element in absolute coordinates. The anchor point is always the top-left corner of the wireframe. + public let x: Int64? + + /// The position in pixels on Y axis of the UI element in absolute coordinates. The anchor point is always the top-left corner of the wireframe. + public let y: Int64? + + enum CodingKeys: String, CodingKey { + case border = "border" + case clip = "clip" + case height = "height" + case id = "id" + case shapeStyle = "shapeStyle" + case slotId = "slotId" + case type = "type" + case width = "width" + case x = "x" + case y = "y" + } + } } } @@ -1007,6 +1115,9 @@ public struct SRMetaRecord: Codable { /// The data contained by this record. public let data: Data + /// Unique ID of the slot that generated this record. + public let slotId: String? + /// Defines the UTC time in milliseconds when this Record was performed. public let timestamp: Int64 @@ -1015,6 +1126,7 @@ public struct SRMetaRecord: Codable { enum CodingKeys: String, CodingKey { case data = "data" + case slotId = "slotId" case timestamp = "timestamp" case type = "type" } @@ -1044,6 +1156,9 @@ public struct SRMetaRecord: Codable { public struct SRFocusRecord: Codable { public let data: Data + /// Unique ID of the slot that generated this record. + public let slotId: String? + /// Defines the UTC time in milliseconds when this Record was performed. public let timestamp: Int64 @@ -1052,6 +1167,7 @@ public struct SRFocusRecord: Codable { enum CodingKeys: String, CodingKey { case data = "data" + case slotId = "slotId" case timestamp = "timestamp" case type = "type" } @@ -1070,6 +1186,9 @@ public struct SRFocusRecord: Codable { /// Schema of a Record which signifies that view lifecycle ended. @_spi(Internal) public struct SRViewEndRecord: Codable { + /// Unique ID of the slot that generated this record. + public let slotId: String? + /// Defines the UTC time in milliseconds when this Record was performed. public let timestamp: Int64 @@ -1077,6 +1196,7 @@ public struct SRViewEndRecord: Codable { public let type: Int64 = 7 enum CodingKeys: String, CodingKey { + case slotId = "slotId" case timestamp = "timestamp" case type = "type" } @@ -1087,6 +1207,9 @@ public struct SRViewEndRecord: Codable { public struct SRVisualViewportRecord: Codable { public let data: Data + /// Unique ID of the slot that generated this record. + public let slotId: String? + /// Defines the UTC time in milliseconds when this Record was performed. public let timestamp: Int64 @@ -1095,6 +1218,7 @@ public struct SRVisualViewportRecord: Codable { enum CodingKeys: String, CodingKey { case data = "data" + case slotId = "slotId" case timestamp = "timestamp" case type = "type" } @@ -1198,4 +1322,4 @@ public enum SRRecord: Codable { } } #endif -// Generated from https://github.com/DataDog/rum-events-format/tree/5a79d9a36b6e76493420792055fc50aed780569b +// Generated from https://github.com/DataDog/rum-events-format/tree/c3747b3facf75e51cbad4c32f77ec3894f5a7249 diff --git a/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift b/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift index aeaf6b5f86..032ec09dc7 100644 --- a/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift +++ b/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift @@ -20,6 +20,8 @@ extension SRWireframe: Diffable { return wireframe.id case .placeholderWireframe(let wireframe): return wireframe.id + case .webviewWireframe(let wireframe): + return wireframe.id } } @@ -71,6 +73,8 @@ extension SRWireframe: MutableWireframe { return try this.mutations(from: otherWireframe) case .placeholderWireframe(let this): return try this.mutations(from: otherWireframe) + case .webviewWireframe(let this): + return try this.mutations(from: otherWireframe) } } } @@ -175,6 +179,31 @@ extension SRTextWireframe: MutableWireframe { } } +extension SRWebviewWireframe: MutableWireframe { + func mutations(from otherWireframe: SRWireframe) throws -> WireframeMutation { + guard case .webviewWireframe(let other) = otherWireframe else { + throw WireframeMutationError.typeMismatch + } + guard other.id == id, other.slotId == slotId else { + throw WireframeMutationError.idMismatch + } + + return .webviewWireframeUpdate( + value: .init( + border: use(border, ifDifferentThan: other.border), + clip: use(clip, ifDifferentThan: other.clip), + height: use(height, ifDifferentThan: other.height), + id: id, + shapeStyle: use(shapeStyle, ifDifferentThan: other.shapeStyle), + slotId: slotId, + width: use(width, ifDifferentThan: other.width), + x: use(x, ifDifferentThan: other.x), + y: use(y, ifDifferentThan: other.y) + ) + ) + } +} + extension SRImageWireframe { @_spi(Internal) public static func == (lhs: SRImageWireframe, rhs: SRImageWireframe) -> Bool { diff --git a/DatadogSessionReplay/Sources/Processor/ResourcesProcessor.swift b/DatadogSessionReplay/Sources/Processor/ResourcesProcessor.swift index c47a51e858..3119972432 100644 --- a/DatadogSessionReplay/Sources/Processor/ResourcesProcessor.swift +++ b/DatadogSessionReplay/Sources/Processor/ResourcesProcessor.swift @@ -13,22 +13,36 @@ internal protocol ResourceProcessing { } internal class ResourceProcessor: ResourceProcessing { + /// Interception callback for snapshot tests. + /// Only available in Debug configuration, solely made for testing purpose. + var interceptResources: (([Resource]) -> Void)? = nil + private let queue: Queue private let resourcesWriter: ResourcesWriting + private var processedIdentifiers = Set() + func process(resources: [Resource], context: EnrichedResource.Context) { - guard !resources.isEmpty else { - return - } - queue.run { [resourcesWriter] in - resourcesWriter.write( - resources: resources.map { - EnrichedResource( - identifier: $0.calculateIdentifier(), + interceptResources?(resources) + queue.run { [weak self] in + let resources = resources + .compactMap { + let identifier = $0.calculateIdentifier() + let isProcessed = self?.processedIdentifiers.contains(identifier) == true + if !isProcessed { + self?.processedIdentifiers.insert(identifier) + } + return !isProcessed ? EnrichedResource( + identifier: identifier, data: $0.calculateData(), context: context - ) + ) : nil } + guard !resources.isEmpty else { + return + } + self?.resourcesWriter.write( + resources: resources ) } } diff --git a/DatadogSessionReplay/Sources/Processor/SRDataModelsBuilder/RecordsBuilder.swift b/DatadogSessionReplay/Sources/Processor/SRDataModelsBuilder/RecordsBuilder.swift index 8777d561e1..b9bc276995 100644 --- a/DatadogSessionReplay/Sources/Processor/SRDataModelsBuilder/RecordsBuilder.swift +++ b/DatadogSessionReplay/Sources/Processor/SRDataModelsBuilder/RecordsBuilder.swift @@ -30,6 +30,7 @@ internal class RecordsBuilder { href: nil, width: Int64(withNoOverflow: snapshot.viewportSize.width) ), + slotId: nil, timestamp: snapshot.date.timeIntervalSince1970.toInt64Milliseconds ) return .metaRecord(value: record) @@ -40,6 +41,7 @@ internal class RecordsBuilder { func createFocusRecord(from snapshot: ViewTreeSnapshot) -> SRRecord { let record = SRFocusRecord( data: SRFocusRecord.Data(hasFocus: true), + slotId: nil, timestamp: snapshot.date.timeIntervalSince1970.toInt64Milliseconds ) return .focusRecord(value: record) diff --git a/DatadogSessionReplay/Sources/Processor/SRDataModelsBuilder/WireframesBuilder.swift b/DatadogSessionReplay/Sources/Processor/SRDataModelsBuilder/WireframesBuilder.swift index 2731e32117..993167f904 100644 --- a/DatadogSessionReplay/Sources/Processor/SRDataModelsBuilder/WireframesBuilder.swift +++ b/DatadogSessionReplay/Sources/Processor/SRDataModelsBuilder/WireframesBuilder.swift @@ -69,7 +69,7 @@ public class SessionReplayWireframesBuilder { } public func createImageWireframe( - imageResource: ImageResource, + resourceId: String, id: WireframeID, frame: CGRect, mimeType: String = "png", @@ -81,14 +81,14 @@ public class SessionReplayWireframesBuilder { opacity: CGFloat? = nil ) -> SRWireframe { let wireframe = SRImageWireframe( - base64: imageResource.base64, + base64: nil, // field deprecated - we should use resource endpoint instead border: createShapeBorder(borderColor: borderColor, borderWidth: borderWidth), clip: clip, height: Int64(withNoOverflow: frame.height), id: id, isEmpty: false, // field deprecated - we should use placeholder wireframe instead mimeType: mimeType, - resourceId: imageResource.identifier, + resourceId: resourceId, shapeStyle: createShapeStyle(backgroundColor: backgroundColor, cornerRadius: cornerRadius, opacity: opacity), width: Int64(withNoOverflow: frame.width), x: Int64(withNoOverflow: frame.minX), @@ -177,6 +177,32 @@ public class SessionReplayWireframesBuilder { return .placeholderWireframe(value: wireframe) } + public func createWebViewWireframe( + id: Int64, + frame: CGRect, + slotId: String, + clip: SRContentClip? = nil, + borderColor: CGColor? = nil, + borderWidth: CGFloat? = nil, + backgroundColor: CGColor? = nil, + cornerRadius: CGFloat? = nil, + opacity: CGFloat? = nil + ) -> SRWireframe { + let wireframe = SRWebviewWireframe( + border: createShapeBorder(borderColor: borderColor, borderWidth: borderWidth), + clip: clip, + height: Int64(withNoOverflow: frame.height), + id: id, + shapeStyle: createShapeStyle(backgroundColor: backgroundColor, cornerRadius: cornerRadius, opacity: opacity), + slotId: slotId, + width: Int64(withNoOverflow: frame.size.width), + x: Int64(withNoOverflow: frame.minX), + y: Int64(withNoOverflow: frame.minY) + ) + + return .webviewWireframe(value: wireframe) + } + // MARK: - Private private func createShapeBorder(borderColor: CGColor?, borderWidth: CGFloat?) -> SRShapeBorder? { diff --git a/DatadogSessionReplay/Sources/Processor/SnapshotProcessor.swift b/DatadogSessionReplay/Sources/Processor/SnapshotProcessor.swift index f5eab564b0..d2d136173c 100644 --- a/DatadogSessionReplay/Sources/Processor/SnapshotProcessor.swift +++ b/DatadogSessionReplay/Sources/Processor/SnapshotProcessor.swift @@ -50,11 +50,9 @@ internal class SnapshotProcessor: SnapshotProcessing { /// Wireframes from last "full snapshot" or "incremental snapshot" record. private var lastWireframes: [SRWireframe]? = nil - #if DEBUG /// Interception callback for snapshot tests. /// Only available in Debug configuration, solely made for testing purpose. var interceptWireframes: (([SRWireframe]) -> Void)? = nil - #endif private var srContextPublisher: SRContextPublisher @@ -76,7 +74,7 @@ internal class SnapshotProcessor: SnapshotProcessing { // MARK: - Processing func process(viewTreeSnapshot: ViewTreeSnapshot, touchSnapshot: TouchSnapshot?) { - queue.run { self.processSync(viewTreeSnapshot: viewTreeSnapshot, touchSnapshot: touchSnapshot) } + queue.run { [weak self] in self?.processSync(viewTreeSnapshot: viewTreeSnapshot, touchSnapshot: touchSnapshot) } } private func processSync(viewTreeSnapshot: ViewTreeSnapshot, touchSnapshot: TouchSnapshot?) { @@ -85,9 +83,7 @@ internal class SnapshotProcessor: SnapshotProcessing { .map { node in node.wireframesBuilder } .flatMap { nodeBuilder in nodeBuilder.buildWireframes(with: wireframesBuilder) } - #if DEBUG interceptWireframes?(wireframes) - #endif var records: [SRRecord] = [] // Create records for describing UI: diff --git a/DatadogSessionReplay/Sources/Recorder/Recorder.swift b/DatadogSessionReplay/Sources/Recorder/Recorder.swift index 3f1f42d4da..dae35d1a02 100644 --- a/DatadogSessionReplay/Sources/Recorder/Recorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/Recorder.swift @@ -61,13 +61,13 @@ public class Recorder: Recording { /// Turns view tree snapshots into data models that will be uploaded to SR BE. private let snapshotProcessor: SnapshotProcessing /// Processes resources on a background thread. - private let resourceProcessor: ResourceProcessing? // RUM-2154 Optional until prod backend is ready + private let resourceProcessor: ResourceProcessing /// Sends telemetry through sdk core. private let telemetry: Telemetry convenience init( snapshotProcessor: SnapshotProcessing, - resourceProcessor: ResourceProcessing?, + resourceProcessor: ResourceProcessing, telemetry: Telemetry, additionalNodeRecorders: [NodeRecorder] ) throws { @@ -95,7 +95,7 @@ public class Recorder: Recording { viewTreeSnapshotProducer: ViewTreeSnapshotProducer, touchSnapshotProducer: TouchSnapshotProducer, snapshotProcessor: SnapshotProcessing, - resourceProcessor: ResourceProcessing?, + resourceProcessor: ResourceProcessing, telemetry: Telemetry ) { self.uiApplicationSwizzler = uiApplicationSwizzler @@ -124,7 +124,7 @@ public class Recorder: Recording { let touchSnapshot = touchSnapshotProducer.takeSnapshot(context: recorderContext) snapshotProcessor.process(viewTreeSnapshot: viewTreeSnapshot, touchSnapshot: touchSnapshot) - resourceProcessor?.process( + resourceProcessor.process( resources: viewTreeSnapshot.resources, context: .init(recorderContext.applicationID) ) diff --git a/DatadogSessionReplay/Sources/Recorder/Utilities/ImageDataProvider.swift b/DatadogSessionReplay/Sources/Recorder/Utilities/ImageDataProvider.swift deleted file mode 100644 index 0c4c38e255..0000000000 --- a/DatadogSessionReplay/Sources/Recorder/Utilities/ImageDataProvider.swift +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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. - */ - -#if os(iOS) -import Foundation -import UIKit - -@_spi(Internal) -public struct ImageResource { - let identifier: String - let base64: String -} - -internal protocol ImageDataProviding { - func contentBase64String( - of image: UIImage? - ) -> ImageResource? - - func contentBase64String( - of image: UIImage?, - tintColor: UIColor? - ) -> ImageResource? -} - -internal final class ImageDataProvider: ImageDataProviding { - private var cache: Cache - - private let desiredMaxBytesSize: Int - - internal init( - cache: Cache = .init(), - desiredMaxBytesSize: Int = 15.KB - ) { - self.cache = cache - self.desiredMaxBytesSize = desiredMaxBytesSize - } - - func contentBase64String( - of image: UIImage?, - tintColor: UIColor? - ) -> ImageResource? { - autoreleasepool { () -> ImageResource? in - guard var image = image else { - return nil - } - var identifier = image.srIdentifier - if let tintColorIdentifier = tintColor?.srIdentifier { - identifier += tintColorIdentifier - } - if let base64EncodedImage = cache[identifier] { - return ImageResource(identifier: identifier, base64: base64EncodedImage) - } else { - if #available(iOS 13.0, *), let tintColor = tintColor { - image = image.withTintColor(tintColor) - } - let base64EncodedImage = image.scaledDownToApproximateSize(desiredMaxBytesSize).base64EncodedString() - cache[identifier, base64EncodedImage.count] = base64EncodedImage - return ImageResource(identifier: identifier, base64: base64EncodedImage) - } - } - } - - func contentBase64String(of image: UIImage?) -> ImageResource? { - contentBase64String(of: image, tintColor: nil) - } -} - -fileprivate extension CGSize { - static func <= (lhs: CGSize, rhs: CGSize) -> Bool { - return lhs.width <= rhs.width && lhs.height <= rhs.height - } -} - -private var recordedKey: UInt8 = 22 -extension UIImage { - var srIdentifier: String { - return customHash - } - - var recorded: Bool { - get { - return objc_getAssociatedObject(self, &recordedKey) as? Bool ?? false - } - set { - objc_setAssociatedObject(self, &recordedKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } -} - -extension UIColor { - var srIdentifier: String { - return "\(hash)" - } -} - -import CryptoKit - -private var customHashKey: UInt8 = 11 -fileprivate extension UIImage { - var customHash: String { - if let hash = objc_getAssociatedObject(self, &customHashKey) as? String { - return hash - } - - let hash = computeHash() - objc_setAssociatedObject(self, &customHashKey, hash, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - return hash - } - - private func computeHash() -> String { - guard let imageData = self.pngData() else { - return "" - } - if #available(iOS 13.0, *) { - return Insecure.MD5.hash(data: imageData).map { String(format: "%02hhx", $0) }.joined() - } else { - return "\(hash)" - } - } -} -#endif diff --git a/DatadogSessionReplay/Sources/Recorder/Utilities/UIImage+SessionReplay.swift b/DatadogSessionReplay/Sources/Recorder/Utilities/UIImage+SessionReplay.swift new file mode 100644 index 0000000000..a4d8ba3b9f --- /dev/null +++ b/DatadogSessionReplay/Sources/Recorder/Utilities/UIImage+SessionReplay.swift @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#if os(iOS) +import Foundation +import UIKit +import DatadogInternal +import CryptoKit + +private var srIdentifierKey: UInt8 = 11 +extension UIImage: DatadogExtended {} +extension DatadogExtension where ExtendedType: UIImage { + var srIdentifier: String { + if let hash = objc_getAssociatedObject(self, &srIdentifierKey) as? String { + return hash + } else { + let hash = computeHash() + objc_setAssociatedObject(self, &srIdentifierKey, hash, .OBJC_ASSOCIATION_RETAIN) + return hash + } + } + + private func computeHash() -> String { + guard let imageData = type.pngData() else { + return "" + } + if #available(iOS 13.0, *) { + return Insecure.MD5.hash(data: imageData).map { String(format: "%02hhx", $0) }.joined() + } else { + return "\(type.hash)" + } + } +} + +extension UIColor { + var srIdentifier: String { + return "\(hash)" + } +} +#endif diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageResource.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageResource.swift new file mode 100644 index 0000000000..6aaf01730d --- /dev/null +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageResource.swift @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#if os(iOS) +import UIKit + +internal struct UIImageResource { + private let image: UIImage + private let tintColor: UIColor? + + internal init(image: UIImage, tintColor: UIColor?) { + self.image = image + self.tintColor = tintColor + } +} + +extension UIImageResource: Resource { + func calculateIdentifier() -> String { + var identifier = image.dd.srIdentifier + if let tintColorIdentifier = tintColor?.srIdentifier { + identifier += tintColorIdentifier + } + return identifier + } + + func calculateData() -> Data { + guard let tintColor = tintColor else { + return image.scaledDownToApproximateSize(SessionReplay.maxObjectSize) + } + if #available(iOS 13.0, *), image.isSymbolImage { + return image.withTintColor(tintColor) + .scaledDownToApproximateSize(SessionReplay.maxObjectSize) + } else { + return manuallyTintedImageData() + } + } + + /// Provides mitigation for template images that fail to tint programatically outside of `UIImageView` or other system container. + private func manuallyTintedImageData() -> Data { + return image + .scaledDownToApproximateSize(SessionReplay.maxObjectSize, tint: tintColor) + } +} +#endif diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorder.swift index 8329de7d66..e3e2994f3d 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorder.swift @@ -7,34 +7,6 @@ #if os(iOS) import UIKit -internal struct UIImageResource { - internal let image: UIImage - internal let tintColor: UIColor? - - internal init(image: UIImage, tintColor: UIColor?) { - self.image = image - self.tintColor = tintColor - } -} - -extension UIImageResource: Resource { - func calculateIdentifier() -> String { - var identifier = image.srIdentifier - if let tintColorIdentifier = tintColor?.srIdentifier { - identifier += tintColorIdentifier - } - return identifier - } - - func calculateData() -> Data { - var image = self.image - if #available(iOS 13.0, *), let tintColor = tintColor { - image = image.withTintColor(tintColor) - } - return image.scaledDownToApproximateSize(1.MB) // Intake limit is 10MB - to be adjusted in RUM-2153 - } -} - internal struct UIImageViewRecorder: NodeRecorder { internal let identifier = UUID() @@ -92,31 +64,24 @@ internal struct UIImageViewRecorder: NodeRecorder { } let shouldRecordImage = shouldRecordImagePredicate(imageView) let tintColor = tintColorProvider(imageView) + let imageResource = imageView.image + .map { image in + UIImageResource(image: image, tintColor: tintColor) + } let builder = UIImageViewWireframesBuilder( wireframeID: ids[0], imageWireframeID: ids[1], attributes: attributes, contentFrame: contentFrame, clipsToBounds: imageView.clipsToBounds, - image: imageView.image, - imageDataProvider: context.imageDataProvider, - tintColor: tintColor, + imageResource: shouldRecordImage ? imageResource : nil, shouldRecordImage: shouldRecordImage ) let node = Node(viewAttributes: attributes, wireframesBuilder: builder) return SpecificElement( subtreeStrategy: .record, nodes: [node], - resources: [imageView.image] - .filter { image in - defer { - image?.recorded = shouldRecordImage - } - return image?.recorded == false && shouldRecordImage - } - .compactMap { image in - image.map { UIImageResource(image: $0, tintColor: tintColor) } - } + resources: [imageResource].filter { _ in shouldRecordImage }.compactMap { $0 } ) } } @@ -136,11 +101,7 @@ internal struct UIImageViewWireframesBuilder: NodeWireframesBuilder { let clipsToBounds: Bool - let image: UIImage? - - let imageDataProvider: ImageDataProviding - - let tintColor: UIColor? + let imageResource: UIImageResource? let shouldRecordImage: Bool @@ -179,18 +140,11 @@ internal struct UIImageViewWireframesBuilder: NodeWireframesBuilder { opacity: attributes.alpha ) ] - var imageResource: ImageResource? - if shouldRecordImage { - imageResource = imageDataProvider.contentBase64String( - of: image, - tintColor: tintColor - ) - } if let contentFrame = contentFrame { if let imageResource = imageResource { wireframes.append( builder.createImageWireframe( - imageResource: imageResource, + resourceId: imageResource.calculateIdentifier(), id: imageWireframeID, frame: contentFrame, clip: clipsToBounds ? clip : nil diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorder.swift new file mode 100644 index 0000000000..0f4e48aac2 --- /dev/null +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorder.swift @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#if os(iOS) +import UIKit +import WebKit + +internal class WKWebViewRecorder: NodeRecorder { + let identifier = UUID() + + func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { + guard let webView = view as? WKWebView else { + return nil + } + + guard attributes.isVisible else { + return InvisibleElement.constant + } + + let builder = WKWebViewWireframesBuilder( + wireframeID: context.ids.nodeID(view: view, nodeRecorder: self), + slotID: webView.configuration.userContentController.hash, + attributes: attributes + ) + + let node = Node(viewAttributes: attributes, wireframesBuilder: builder) + return SpecificElement(subtreeStrategy: .ignore, nodes: [node]) + } +} + +internal struct WKWebViewWireframesBuilder: NodeWireframesBuilder { + let wireframeID: WireframeID + /// The slot identifier of the webview controller. + let slotID: Int + /// Attributes of the `UIView`. + let attributes: ViewAttributes + + var wireframeRect: CGRect { + attributes.frame + } + + func buildWireframes(with builder: WireframesBuilder) -> [SRWireframe] { + return [ + builder.createWebViewWireframe( + id: wireframeID, + frame: wireframeRect, + slotId: String(slotID), + borderColor: attributes.layerBorderColor, + borderWidth: attributes.layerBorderWidth, + backgroundColor: attributes.backgroundColor, + cornerRadius: attributes.layerCornerRadius, + opacity: attributes.alpha + ) + ] + } +} +#endif diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeRecordingContext.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeRecordingContext.swift index 27e0ab1b60..46b5b1c7e5 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeRecordingContext.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeRecordingContext.swift @@ -20,8 +20,6 @@ public struct SessionReplayViewTreeRecordingContext { let coordinateSpace: UICoordinateSpace /// Generates stable IDs for traversed views. public let ids: NodeIDGenerator - /// Provides base64 image data with a built in caching mechanism. - let imageDataProvider: ImageDataProviding /// Variable view controller related context var viewControllerContext: ViewControllerContext = .init() } diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift index d32f29909d..fd0e65f8b0 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift @@ -16,8 +16,6 @@ internal struct ViewTreeSnapshotBuilder { let viewTreeRecorder: ViewTreeRecorder /// Generates stable IDs for traversed views. let idsGenerator: NodeIDGenerator - /// Provides base64 image data with a built in caching mechanism. - let imageDataProvider: ImageDataProviding /// Builds the `ViewTreeSnapshot` for given root view. /// @@ -30,8 +28,7 @@ internal struct ViewTreeSnapshotBuilder { let context = ViewTreeRecordingContext( recorder: recorderContext, coordinateSpace: rootView, - ids: idsGenerator, - imageDataProvider: imageDataProvider + ids: idsGenerator ) let recording = viewTreeRecorder.record(rootView, in: context) let snapshot = ViewTreeSnapshot( @@ -49,8 +46,7 @@ extension ViewTreeSnapshotBuilder { init(additionalNodeRecorders: [NodeRecorder]) { self.init( viewTreeRecorder: ViewTreeRecorder(nodeRecorders: createDefaultNodeRecorders() + additionalNodeRecorders), - idsGenerator: NodeIDGenerator(), - imageDataProvider: ImageDataProvider() + idsGenerator: NodeIDGenerator() ) } } diff --git a/DatadogSessionReplay/Sources/SessionReplay.swift b/DatadogSessionReplay/Sources/SessionReplay.swift index 538d53738e..c0054d6872 100644 --- a/DatadogSessionReplay/Sources/SessionReplay.swift +++ b/DatadogSessionReplay/Sources/SessionReplay.swift @@ -29,6 +29,8 @@ public enum SessionReplay { } } + internal static let maxObjectSize = 10.MB.asUInt64() + internal static func enableOrThrow( with configuration: SessionReplay.Configuration, in core: DatadogCoreProtocol ) throws { diff --git a/DatadogSessionReplay/Sources/Utilities/Cache.swift b/DatadogSessionReplay/Sources/Utilities/Cache.swift deleted file mode 100644 index ef6d369d3e..0000000000 --- a/DatadogSessionReplay/Sources/Utilities/Cache.swift +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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. - */ - -#if os(iOS) -import Foundation - -internal final class Cache { - private let wrapped = NSCache() - private let dateProvider: () -> Date - private let entryLifetime: TimeInterval - private let keyTracker = KeyTracker() - - init(dateProvider: @escaping () -> Date = Date.init, - entryLifetime: TimeInterval = 10.minutes, - totalBytesLimit: Int = 5.MB, - maximumEntryCount: Int = 50 - ) { - self.dateProvider = dateProvider - self.entryLifetime = entryLifetime - wrapped.countLimit = maximumEntryCount - wrapped.totalCostLimit = totalBytesLimit - wrapped.delegate = keyTracker - } - - func insert(_ value: Value, forKey key: Key, size: Int = 0) { - let date = dateProvider().addingTimeInterval(entryLifetime) - let entry = Entry(key: key, value: value, expirationDate: date) - wrapped.setObject(entry, forKey: WrappedKey(key), cost: size) - keyTracker.keys.insert(key) - } - - func value(forKey key: Key) -> Value? { - guard let entry = wrapped.object(forKey: WrappedKey(key)) else { - return nil - } - - guard dateProvider() < entry.expirationDate else { - removeValue(forKey: key) - return nil - } - entry.expirationDate = entry.expirationDate.addingTimeInterval(entryLifetime) - - return entry.value - } - - func removeValue(forKey key: Key) { - wrapped.removeObject(forKey: WrappedKey(key)) - } -} - -private extension Cache { - final class WrappedKey: NSObject { - let key: Key - - init(_ key: Key) { self.key = key } - - override var hash: Int { return key.hashValue } - - override func isEqual(_ object: Any?) -> Bool { - guard let value = object as? WrappedKey else { - return false - } - - return value.key == key - } - } -} - -private extension Cache { - final class Entry { - let key: Key - let value: Value - var expirationDate: Date - - init(key: Key, value: Value, expirationDate: Date) { - self.key = key - self.value = value - self.expirationDate = expirationDate - } - } -} - -private extension Cache { - final class KeyTracker: NSObject, NSCacheDelegate { - var keys = Set() - - func cache(_ cache: NSCache, - willEvictObject object: Any) { - guard let entry = object as? Entry else { - return - } - - keys.remove(entry.key) - } - } -} - -extension Cache { - subscript(key: Key, size: Int = 0) -> Value? { - get { return value(forKey: key) } - set { - guard let value = newValue else { - removeValue(forKey: key) - return - } - - insert(value, forKey: key, size: size) - } - } -} -#endif diff --git a/DatadogSessionReplay/Sources/Utilities/Queue.swift b/DatadogSessionReplay/Sources/Utilities/Queue.swift index d0ffebe4db..8f95975d10 100644 --- a/DatadogSessionReplay/Sources/Utilities/Queue.swift +++ b/DatadogSessionReplay/Sources/Utilities/Queue.swift @@ -23,7 +23,7 @@ internal class BackgroundAsyncQueue: Queue { private let queue: DispatchQueue init(named queueName: String) { - self.queue = DispatchQueue(label: queueName, qos: .utility) + self.queue = DispatchQueue(label: queueName, qos: .utility, autoreleaseFrequency: .workItem) } func run(_ block: @escaping () -> Void) { diff --git a/DatadogSessionReplay/Sources/Utilities/UIImage+Scaling.swift b/DatadogSessionReplay/Sources/Utilities/UIImage+Scaling.swift index 1ab532fd4c..5655da6e32 100644 --- a/DatadogSessionReplay/Sources/Utilities/UIImage+Scaling.swift +++ b/DatadogSessionReplay/Sources/Utilities/UIImage+Scaling.swift @@ -26,17 +26,17 @@ extension UIImage { /// if let imageData = originalImage?.scaledDownToApproximateSize(desiredSizeInBytes) { /// // Use the scaled down image data. /// } - func scaledDownToApproximateSize(_ desiredSizeInBytes: Int) -> Data { + func scaledDownToApproximateSize(_ desiredSizeInBytes: UInt64, tint: UIColor? = nil) -> Data { guard let imageData = pngData() else { return Data() } - guard imageData.count > desiredSizeInBytes else { + guard tint != nil || imageData.count > desiredSizeInBytes else { return imageData } // Initial scale is approximatation based on the average side of square for given size ratio. // When running experiments it appeared to be closer to desired scale than using just a size ratio. - let initialScale = sqrt(CGFloat(desiredSizeInBytes) / CGFloat(imageData.count)) - var scaledImage = scaledImage(by: initialScale) + let initialScale = min(1, sqrt(CGFloat(desiredSizeInBytes) / CGFloat(imageData.count))) + var scaledImage = scaledImage(by: initialScale, tint: tint) var scale: Double = 1 let maxIterations = 20 @@ -48,7 +48,7 @@ extension UIImage { return scaledImageData } scale *= 0.9 - scaledImage = scaledImage.scaledImage(by: scale) + scaledImage = scaledImage.scaledImage(by: scale, tint: tint) } guard let scaledImageData = scaledImage.pngData() else { return imageData @@ -63,13 +63,18 @@ extension UIImage { /// /// This private helper function takes a CGFloat percentage as input and scales the image accordingly. /// It ensures that the resulting image has a size proportional to the original one, maintaining its aspect ratio. - private func scaledImage(by percentage: CGFloat) -> UIImage { + private func scaledImage(by percentage: CGFloat, tint: UIColor?) -> UIImage { guard percentage > 0 else { return UIImage() } let newSize = CGSize(width: size.width * percentage, height: size.height * percentage) UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0) - draw(in: CGRect(origin: .zero, size: newSize)) + let drawRect = CGRect(origin: .zero, size: newSize) + if let tint = tint { + tint.setFill() + UIRectFill(drawRect) + } + draw(in: drawRect, blendMode: .destinationIn, alpha: 1.0) let scaledImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() diff --git a/DatadogSessionReplay/Sources/Writers/RecordWriter.swift b/DatadogSessionReplay/Sources/Writers/RecordWriter.swift index 42923bf279..0c7997d89c 100644 --- a/DatadogSessionReplay/Sources/Writers/RecordWriter.swift +++ b/DatadogSessionReplay/Sources/Writers/RecordWriter.swift @@ -18,31 +18,14 @@ internal class RecordWriter: RecordWriting { /// An instance of SDK core the SR feature is registered to. private weak var core: DatadogCoreProtocol? - /// The `viewID` of last group of records written to core. If that ID changes, we request the core - /// to write new events to separate batch, so we receive them separately in `RequestBuilder`. - /// - /// This is to fulfill the SR payload requirement that each view needs to be send in separate segment. - private var lastViewID: String? - - init( - core: DatadogCoreProtocol, - lastViewID: String? = nil - ) { + init(core: DatadogCoreProtocol) { self.core = core - self.lastViewID = lastViewID } // MARK: - Writing func write(nextRecord: EnrichedRecord) { - let forceNewBatch = lastViewID != nextRecord.viewID - lastViewID = nextRecord.viewID - - guard let scope = core?.scope(for: SessionReplayFeature.name) else { - return - } - - scope.eventWriteContext(bypassConsent: false, forceNewBatch: forceNewBatch) { _, recordWriter in + core?.scope(for: SessionReplayFeature.name)?.eventWriteContext { _, recordWriter in recordWriter.write(value: nextRecord) } } diff --git a/DatadogSessionReplay/Sources/Writers/ResourcesWriter.swift b/DatadogSessionReplay/Sources/Writers/ResourcesWriter.swift index 08c467126f..a0c597abcc 100644 --- a/DatadogSessionReplay/Sources/Writers/ResourcesWriter.swift +++ b/DatadogSessionReplay/Sources/Writers/ResourcesWriter.swift @@ -30,7 +30,7 @@ internal class ResourcesWriter: ResourcesWriting { guard let scope = core?.scope(for: ResourcesFeature.name) else { return } - scope.eventWriteContext(bypassConsent: false, forceNewBatch: false) { _, recordWriter in + scope.eventWriteContext { _, recordWriter in resources.forEach { recordWriter.write(value: $0) } diff --git a/DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/EnrichedRecordJSONTests.swift b/DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/EnrichedRecordJSONTests.swift deleted file mode 100644 index 53124b10b7..0000000000 --- a/DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/EnrichedRecordJSONTests.swift +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 -@testable import DatadogSessionReplay -@testable import TestUtilities - -class EnrichedRecordJSONTests: XCTestCase { - func testWhenDecodingEnrichedRecordData_itHasTheSameInformationAvailable() throws { - // Given - let enrichedRecords = EnrichedRecord( - context: .mockRandom(), - records: .mockRandom() - ) - let data = try encode(enrichedRecords) - - // When - let someRecords = try EnrichedRecordJSON(jsonObjectData: data) - - // Then - XCTAssertEqual(someRecords.applicationID, enrichedRecords.applicationID) - XCTAssertEqual(someRecords.sessionID, enrichedRecords.sessionID) - XCTAssertEqual(someRecords.viewID, enrichedRecords.viewID) - XCTAssertEqual(someRecords.records.count, enrichedRecords.records.count) - XCTAssertEqual(someRecords.hasFullSnapshot, enrichedRecords.hasFullSnapshot) - XCTAssertEqual(someRecords.earliestTimestamp, enrichedRecords.earliestTimestamp) - XCTAssertEqual(someRecords.latestTimestamp, enrichedRecords.latestTimestamp) - - let expected = try encode(enrichedRecords.records) - let actual = try encode(someRecords.records) - XCTAssertEqual(expected, actual, "Serialized records data must be equal in both `EnrichedRecord` and `EnrichedRecordJSON`") - } - - func testWhenDecodingMalformedData_itThrows() { - // Given - let malrofmedData1 = "[]".data(using: .utf8)! - let malrofmedData2 = "{}".data(using: .utf8)! - - // When & Then - XCTAssertThrowsError(try EnrichedRecordJSON(jsonObjectData: malrofmedData1)) { error in - let description = (error as! InternalError).description - XCTAssertTrue(description.hasPrefix("Failed to decode Dictionary.Type")) - } - XCTAssertThrowsError(try EnrichedRecordJSON(jsonObjectData: malrofmedData2)) { error in - let description = (error as! InternalError).description - XCTAssertTrue(description.hasPrefix("Failed to read attribute at key path")) - } - } - - // MARK: - Encoding helpers - - private func encode(_ value: T) throws -> Data { - let encoder = JSONEncoder() - encoder.outputFormatting = .sortedKeys - return try encoder.encode(value) - } - - private func encode(_ jsonArray: [JSONObject]) throws -> Data { - return try JSONSerialization.data( - withJSONObject: jsonArray, - options: [.sortedKeys] - ) - } -} diff --git a/DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/SegmentJSONBuilderTests.swift b/DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/SegmentJSONBuilderTests.swift deleted file mode 100644 index 0e983cdffe..0000000000 --- a/DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/SegmentJSONBuilderTests.swift +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 -@_spi(Internal) -@testable import DatadogSessionReplay -@testable import TestUtilities - -class SegmentJSONBuilderTests: XCTestCase { - private let builder = SegmentJSONBuilder(source: .mockRandom()) - - func testGivenSRSegmentWithRecords_whenCreatingSegmentJSON_itEcodesToTheSameJSON() throws { - // Given - let expectedSegment = generateSegment(maxRecordsCount: 50) - let records = try generateEnrichedRecordJSONs(for: expectedSegment) - - // When - let actualSegment = try builder.createSegmentJSON(from: records) - - // Then - XCTAssertEqual( - try prettyJSONString(for: expectedSegment), - try prettyJSONString(for: actualSegment), - "`SegmentJSON` must encode the same JSON string as `SRSegment: Codable`" - ) - } - - func testWhenBuildingSegmentWithNoRecords_itThrows() { - // When - XCTAssertThrowsError(try builder.createSegmentJSON(from: [])) { error in - // Then - XCTAssertEqual((error as? SegmentJSONBuilderError)?.description, "Records array must not be empty.") - } - } - - func testWhenBuildingSegmentWithInvalidRecords_itThrows() throws { - // Given - let segment1 = generateSegment(maxRecordsCount: 25) - let segment2 = generateSegment(maxRecordsCount: 25) - let records = try generateEnrichedRecordJSONs(for: segment1) + generateEnrichedRecordJSONs(for: segment2) - - // When - XCTAssertThrowsError(try builder.createSegmentJSON(from: records)) { error in - // Then - XCTAssertEqual((error as? SegmentJSONBuilderError)?.description, "All records must reference the same RUM context.") - } - } - - // MARK: - Fuzzy Helpers - - private func generateSegment(maxRecordsCount: Int64 = 100) -> SRSegment { - let recordsCount: Int64 = .mockRandom(min: 1, max: maxRecordsCount) - let records: [SRRecord] = (0..)!, - hasFullSnapshot: records.contains { $0.isFullSnapshot }, - indexInView: nil, - records: records, - recordsCount: recordsCount, - session: .init(id: .mockRandom()), - source: builder.source, - start: timestamps.min(by: <)!, - view: .init(id: .mockRandom()) - ) - } - - private func generateEnrichedRecordJSONs(for segment: SRSegment) throws -> [EnrichedRecordJSON] { - let context = Recorder.Context( - privacy: .mockRandom(), - rumContext: RUMContext( - applicationID: segment.application.id, - sessionID: segment.session.id, - viewID: segment.view.id, - viewServerTimeOffset: 0 - ) - ) - return try segment.records - // To make it more challenging for tested `SegmentJSONBuilder`, we chunk records in - // expected `segment`, so they are spread among many enriched records: - .chunkedRandomly(numberOfChunks: .random(in: 1...segment.records.count)) - .map { EnrichedRecord(context: context, records: $0) } - // Encode `EnrichedRecords` into `Data`, just like it happens in `DatadogCore` when - // writting them into batches: - .map { try encode($0) } - // Decode it back to `EnrichedRecordJSON` just like it happens when preparing - // upload requests for SR: - .map { try EnrichedRecordJSON(jsonObjectData: $0) } - } - - private func encode(_ value: T) throws -> Data { - let encoder = JSONEncoder() - encoder.outputFormatting = [.sortedKeys, .prettyPrinted] - return try encoder.encode(value) - } - - private func encode(_ segment: SegmentJSON) throws -> Data { - return try JSONSerialization.data( - withJSONObject: segment.toJSONObject(), - options: [.sortedKeys, .prettyPrinted] - ) - } - - private func prettyJSONString(for value: T) throws -> String { - return String(data: try encode(value), encoding: .utf8) ?? "" - } - - private func prettyJSONString(for segment: SegmentJSON) throws -> String { - return String(data: try encode(segment), encoding: .utf8) ?? "" - } -} diff --git a/DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/SegmentJSONTests.swift b/DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/SegmentJSONTests.swift new file mode 100644 index 0000000000..9178928af9 --- /dev/null +++ b/DatadogSessionReplay/Tests/Feature/RequestBuilder/JSON/SegmentJSONTests.swift @@ -0,0 +1,183 @@ +/* + * 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 TestUtilities +import DatadogInternal + +@_spi(Internal) +@testable import DatadogSessionReplay + +class SegmentJSONTests: XCTestCase { + func testGivenSRSegmentWithRecords_whenCreatingSegmentJSON_itEcodesToTheSameJSON() throws { + // Given + let source: SRSegment.Source = .mockRandom() + let segment1 = generateSegment(maxRecordsCount: 50, source: source) + let segment2 = generateSegment(maxRecordsCount: 50, source: source) + let segments1 = try generateEnrichedRecordJSONs(for: segment1) + let segments2 = try generateEnrichedRecordJSONs(for: segment2) + + // When + let segments = (segments1 + segments2).merge().map { $0.toJSONObject() } + + // Then + DDAssertJSONEqual(AnyEncodable(segments.first), segment1) + DDAssertJSONEqual(AnyEncodable(segments.last), segment2) + } + + func testWhenDecodingEnrichedRecordData_itHasTheSameInformationAvailable() throws { + // Given + let enrichedRecords = EnrichedRecord( + context: .mockRandom(), + records: .mockRandom() + ) + let data = try encode(enrichedRecords) + + // When + let segment = try SegmentJSON(data, source: .mockAny()) + + // Then + XCTAssertEqual(segment.applicationID, enrichedRecords.applicationID) + XCTAssertEqual(segment.sessionID, enrichedRecords.sessionID) + XCTAssertEqual(segment.viewID, enrichedRecords.viewID) + XCTAssertEqual(segment.records.count, enrichedRecords.records.count) + XCTAssertEqual(segment.start, enrichedRecords.records.map({ $0.timestamp }).min()) + XCTAssertEqual(segment.end, enrichedRecords.records.map({ $0.timestamp }).max()) + + let expected = try encode(enrichedRecords.records) + let actual = try encode(segment.records) + XCTAssertEqual(expected, actual, "Serialized records data must be equal in both `EnrichedRecord` and `EnrichedRecordJSON`") + } + + func testWhenDecodingMalformedData_itThrows() { + // Given + let malrofmedData1 = "[]".data(using: .utf8)! + let malrofmedData2 = "{}".data(using: .utf8)! + + // When & Then + XCTAssertThrowsError(try SegmentJSON(malrofmedData1, source: .mockRandom())) { error in + let description = (error as! DatadogSessionReplay.InternalError).description + XCTAssertTrue(description.hasPrefix("Failed to decode Dictionary.Type")) + } + XCTAssertThrowsError(try SegmentJSON(malrofmedData2, source: .mockRandom())) { error in + let description = (error as! DatadogSessionReplay.InternalError).description + XCTAssertTrue(description.hasPrefix("Failed to read attribute at key path")) + } + } + + func testItHasFullSnapshot() throws { + // Given + let encoder = JSONEncoder() + let recordsWithFSR: [SRRecord] = .mockRandom() + [.fullSnapshotRecord(value: .mockRandom())] + let recordsWithNoFSR: [SRRecord] = .mockRandom().filter { !$0.isFullSnapshotRecord } + + let enrichedRecords1 = EnrichedRecord( + context: .mockAny(), + records: recordsWithFSR + ) + let enrichedRecords2 = EnrichedRecord( + context: .mockAny(), + records: recordsWithNoFSR + ) + + let data1 = try encoder.encode(enrichedRecords1) + let data2 = try encoder.encode(enrichedRecords2) + + // When + let segment1 = try SegmentJSON(data1, source: .mockAny()) + let segment2 = try SegmentJSON(data2, source: .mockAny()) + + // Then + XCTAssertTrue(segment1.hasFullSnapshot) + XCTAssertFalse(segment2.hasFullSnapshot) + } + + func testItComputesEarliestAndLatestTimestamps() throws { + // Given + let encoder = JSONEncoder() + + let record = EnrichedRecord( + context: .mockAny(), + records: .mockRandom(count: .mockRandom(min: 1, max: 100)) + ) + + let data = try encoder.encode(record) + + // When + let segment = try SegmentJSON(data, source: .mockAny()) + + // Then + XCTAssertEqual(segment.start, record.records.map({ $0.timestamp }).min()) + XCTAssertEqual(segment.end, record.records.map({ $0.timestamp }).max()) + } + + // MARK: - Fuzzy Helpers + + private func generateSegment(maxRecordsCount: Int64 = 100, source: SRSegment.Source = .mockRandom()) -> SRSegment { + let recordsCount: Int64 = .mockRandom(min: 1, max: maxRecordsCount) + let records: [SRRecord] = (0..)!, + hasFullSnapshot: hasFullSnapshot, + indexInView: nil, + records: records, + recordsCount: recordsCount, + session: .init(id: .mockRandom()), + source: source, + start: timestamps.min(by: <)!, + view: .init(id: .mockRandom()) + ) + } + + private func generateEnrichedRecordJSONs(for segment: SRSegment) throws -> [SegmentJSON] { + let context = Recorder.Context( + privacy: .mockRandom(), + rumContext: RUMContext( + applicationID: segment.application.id, + sessionID: segment.session.id, + viewID: segment.view.id, + viewServerTimeOffset: 0 + ) + ) + + let encoder = JSONEncoder() + return try segment.records + // To make it more challenging for tested `SegmentJSONBuilder`, we chunk records in + // expected `segment`, so they are spread among many enriched records: + .chunkedRandomly(numberOfChunks: .random(in: 1...segment.records.count)) + .map { EnrichedRecord(context: context, records: $0) } + // Encode `EnrichedRecords` into `Data`, just like it happens in `DatadogCore` when + // writting them into batches: + .map { try encoder.encode($0) } + // Decode it back to `EnrichedRecordJSON` just like it happens when preparing + // upload requests for SR: + .map { try SegmentJSON($0, source: segment.source) } + } + + // MARK: - Encoding helpers + + private func encode(_ value: T) throws -> Data { + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + return try encoder.encode(value) + } + + private func encode(_ jsonArray: [JSONObject]) throws -> Data { + return try JSONSerialization.data( + withJSONObject: jsonArray, + options: [.sortedKeys] + ) + } +} diff --git a/DatadogSessionReplay/Tests/Feature/RequestBuilder/Multipart/MultipartFormDataTests.swift b/DatadogSessionReplay/Tests/Feature/RequestBuilder/Multipart/MultipartFormDataTests.swift index 5569dca4d3..25ed57a054 100644 --- a/DatadogSessionReplay/Tests/Feature/RequestBuilder/Multipart/MultipartFormDataTests.swift +++ b/DatadogSessionReplay/Tests/Feature/RequestBuilder/Multipart/MultipartFormDataTests.swift @@ -32,7 +32,7 @@ class MultipartFormDataTests: XCTestCase { ) // Then - let actualDataString = try XCTUnwrap(String(data: multipart.data, encoding: .utf8)) + let actualDataString = try XCTUnwrap(String(data: multipart.build(), encoding: .utf8)) let expectedDataString = """ --12345678-0000-0000-0000-000000000000 Content-Disposition: form-data; name="field1" diff --git a/DatadogSessionReplay/Tests/Feature/RequestBuilder/ResourceRequestBuilderTests.swift b/DatadogSessionReplay/Tests/Feature/RequestBuilder/ResourceRequestBuilderTests.swift index 73c4c731bb..fb91980bf8 100644 --- a/DatadogSessionReplay/Tests/Feature/RequestBuilder/ResourceRequestBuilderTests.swift +++ b/DatadogSessionReplay/Tests/Feature/RequestBuilder/ResourceRequestBuilderTests.swift @@ -135,7 +135,7 @@ class ResourceRequestBuilderTests: XCTestCase { // Then let contentType = try XCTUnwrap(request.allHTTPHeaderFields?["Content-Type"]) - XCTAssertTrue(contentType.matches(regex: "multipart/form-data; boundary=\(multipartSpy.boundary.uuidString)")) + XCTAssertTrue(contentType.matches(regex: "multipart/form-data; boundary=\(multipartSpy.boundary)")) for i in 0.. DatadogSessionReplay.ImageResource? { - return .init(identifier: identifier, base64: contentBase64String) - } - - func contentBase64String(of image: UIImage?, tintColor: UIColor?) -> DatadogSessionReplay.ImageResource? { - return .init(identifier: identifier, base64: contentBase64String) - } - - init( - contentBase64String: String = "mock_base64_string", - identifier: String = "mock_identifier" - ) { - self.contentBase64String = contentBase64String - self.identifier = identifier - } -} - -internal func mockRandomImageDataProvider() -> ImageDataProviding { - return MockImageDataProvider(contentBase64String: .mockRandom()) -} diff --git a/DatadogSessionReplay/Tests/Mocks/MultipartBuilderSpy.swift b/DatadogSessionReplay/Tests/Mocks/MultipartBuilderSpy.swift index 320d3cb6e5..66425ef305 100644 --- a/DatadogSessionReplay/Tests/Mocks/MultipartBuilderSpy.swift +++ b/DatadogSessionReplay/Tests/Mocks/MultipartBuilderSpy.swift @@ -12,10 +12,13 @@ class MultipartBuilderSpy: MultipartFormDataBuilder { var formFiles: [(filename: String, data: Data, mimeType: String)] = [] var returnedData: Data = .mockRandom() - var boundary = UUID() + let boundary: String = UUID().uuidString + func addFormField(name: String, value: String) { formFields[name] = value } + func addFormData(name: String, filename: String, data: Data, mimeType: String) { formFiles.append((filename: filename, data: data, mimeType: mimeType)) } - var data: Data { returnedData } + + func build() -> Data { returnedData } } diff --git a/DatadogSessionReplay/Tests/Mocks/RecorderMocks.swift b/DatadogSessionReplay/Tests/Mocks/RecorderMocks.swift index 6699a4b168..8196086683 100644 --- a/DatadogSessionReplay/Tests/Mocks/RecorderMocks.swift +++ b/DatadogSessionReplay/Tests/Mocks/RecorderMocks.swift @@ -291,6 +291,12 @@ struct MockResource: Resource, AnyMockable, RandomMockable { } } +extension UIImageResource: RandomMockable { + public static func mockRandom() -> UIImageResource { + return .init(image: .mockRandom(), tintColor: .mockRandom()) + } +} + extension Collection where Element == Resource { static func mockAny() -> [Resource] { return [MockResource].mockAny() @@ -338,22 +344,19 @@ extension ViewTreeRecordingContext: AnyMockable, RandomMockable { return .init( recorder: .mockRandom(), coordinateSpace: UIView.mockRandom(), - ids: NodeIDGenerator(), - imageDataProvider: mockRandomImageDataProvider() + ids: NodeIDGenerator() ) } static func mockWith( recorder: Recorder.Context = .mockAny(), coordinateSpace: UICoordinateSpace = UIView.mockAny(), - ids: NodeIDGenerator = NodeIDGenerator(), - imageDataProvider: ImageDataProviding = MockImageDataProvider() + ids: NodeIDGenerator = NodeIDGenerator() ) -> ViewTreeRecordingContext { return .init( recorder: recorder, coordinateSpace: coordinateSpace, - ids: ids, - imageDataProvider: imageDataProvider + ids: ids ) } } diff --git a/DatadogSessionReplay/Tests/Mocks/SRDataModelsMocks.swift b/DatadogSessionReplay/Tests/Mocks/SRDataModelsMocks.swift index 53b48fbc09..8565242b77 100644 --- a/DatadogSessionReplay/Tests/Mocks/SRDataModelsMocks.swift +++ b/DatadogSessionReplay/Tests/Mocks/SRDataModelsMocks.swift @@ -485,6 +485,7 @@ extension SRVisualViewportRecord: AnyMockable, RandomMockable { public static func mockRandom() -> SRVisualViewportRecord { return SRVisualViewportRecord( data: .mockRandom(), + slotId: .mockRandom(), timestamp: .mockRandom() ) } @@ -495,6 +496,7 @@ extension SRVisualViewportRecord: AnyMockable, RandomMockable { ) -> SRVisualViewportRecord { return SRVisualViewportRecord( data: data, + slotId: .mockRandom(), timestamp: timestamp ) } @@ -545,6 +547,7 @@ extension SRViewEndRecord: AnyMockable, RandomMockable { public static func mockRandom() -> SRViewEndRecord { return SRViewEndRecord( + slotId: .mockRandom(), timestamp: .mockRandom() ) } @@ -553,6 +556,7 @@ extension SRViewEndRecord: AnyMockable, RandomMockable { timestamp: Int64 = .mockAny() ) -> SRViewEndRecord { return SRViewEndRecord( + slotId: .mockRandom(), timestamp: timestamp ) } @@ -566,6 +570,7 @@ extension SRFocusRecord: AnyMockable, RandomMockable { public static func mockRandom() -> SRFocusRecord { return SRFocusRecord( data: .mockRandom(), + slotId: .mockRandom(), timestamp: .mockRandom() ) } @@ -576,6 +581,7 @@ extension SRFocusRecord: AnyMockable, RandomMockable { ) -> SRFocusRecord { return SRFocusRecord( data: data, + slotId: .mockRandom(), timestamp: timestamp ) } @@ -609,6 +615,7 @@ extension SRMetaRecord: AnyMockable, RandomMockable { public static func mockRandom() -> SRMetaRecord { return SRMetaRecord( data: .mockRandom(), + slotId: .mockRandom(), timestamp: .mockRandom() ) } @@ -619,6 +626,7 @@ extension SRMetaRecord: AnyMockable, RandomMockable { ) -> SRMetaRecord { return SRMetaRecord( data: data, + slotId: .mockRandom(), timestamp: timestamp ) } diff --git a/DatadogSessionReplay/Tests/Processor/ResourceProcessorTests.swift b/DatadogSessionReplay/Tests/Processor/ResourceProcessorTests.swift index 60bc728849..f53a334c96 100644 --- a/DatadogSessionReplay/Tests/Processor/ResourceProcessorTests.swift +++ b/DatadogSessionReplay/Tests/Processor/ResourceProcessorTests.swift @@ -62,4 +62,36 @@ class ResourceProcessorTests: XCTestCase { XCTAssertTrue(writer.resources.isEmpty) } + + func testItRemovedDuplicates() { + let writer = ResourceWriterMock() + let processor = ResourceProcessor( + queue: NoQueue(), + resourcesWriter: writer + ) + + let resource1: MockResource = .mockRandom() + let resource2: MockResource = .mockRandom() + let context: EnrichedResource.Context = .mockRandom() + + processor.process(resources: [resource1, resource2], context: context) + processor.process(resources: [resource1, resource2], context: context) + + XCTAssertEqual(writer.resources.count, 1) + XCTAssertEqual( + writer.resources[0], + [ + EnrichedResource( + identifier: resource1.calculateIdentifier(), + data: resource1.calculateData(), + context: context + ), + EnrichedResource( + identifier: resource2.calculateIdentifier(), + data: resource2.calculateData(), + context: context + ), + ] + ) + } } diff --git a/DatadogSessionReplay/Tests/Processor/SnapshotProcessorTests.swift b/DatadogSessionReplay/Tests/Processor/SnapshotProcessorTests.swift index 172c3ec9a7..9e30dcb07b 100644 --- a/DatadogSessionReplay/Tests/Processor/SnapshotProcessorTests.swift +++ b/DatadogSessionReplay/Tests/Processor/SnapshotProcessorTests.swift @@ -48,13 +48,11 @@ class SnapshotProcessorTests: XCTestCase { XCTAssertEqual(enrichedRecord.applicationID, rum.applicationID) XCTAssertEqual(enrichedRecord.sessionID, rum.sessionID) XCTAssertEqual(enrichedRecord.viewID, rum.viewID) - XCTAssertEqual(enrichedRecord.earliestTimestamp, time.timeIntervalSince1970.toInt64Milliseconds) - XCTAssertEqual(enrichedRecord.latestTimestamp, time.timeIntervalSince1970.toInt64Milliseconds) XCTAssertEqual(enrichedRecord.records.count, 3, "Segment must start with 'meta' → 'focus' → 'full snapshot' records") XCTAssertTrue(enrichedRecord.records[0].isMetaRecord) XCTAssertTrue(enrichedRecord.records[1].isFocusRecord) - XCTAssertTrue(enrichedRecord.records[2].isFullSnapshotRecord && enrichedRecord.hasFullSnapshot) + XCTAssertTrue(enrichedRecord.records[2].isFullSnapshotRecord) XCTAssertEqual(core.recordsCountByViewID, ["abc": 3]) } @@ -90,7 +88,7 @@ class SnapshotProcessorTests: XCTestCase { XCTAssertEqual(enrichedRecords[0].records.count, 3, "Segment must start with 'meta' → 'focus' → 'full snapshot' records") XCTAssertTrue(enrichedRecords[0].records[0].isMetaRecord) XCTAssertTrue(enrichedRecords[0].records[1].isFocusRecord) - XCTAssertTrue(enrichedRecords[0].records[2].isFullSnapshotRecord && enrichedRecords[0].hasFullSnapshot) + XCTAssertTrue(enrichedRecords[0].records[2].isFullSnapshotRecord) XCTAssertEqual(enrichedRecords[1].records.count, 1, "It should follow with 'incremental snapshot' record") XCTAssertTrue(enrichedRecords[1].records[0].isIncrementalSnapshotRecord) @@ -102,10 +100,6 @@ class SnapshotProcessorTests: XCTestCase { XCTAssertEqual(enrichedRecord.applicationID, rum.applicationID) XCTAssertEqual(enrichedRecord.sessionID, rum.sessionID) XCTAssertEqual(enrichedRecord.viewID, rum.viewID) - - let expectedTime = time.addingTimeInterval(TimeInterval(index)) - XCTAssertEqual(enrichedRecord.earliestTimestamp, expectedTime.timeIntervalSince1970.toInt64Milliseconds) - XCTAssertEqual(enrichedRecord.latestTimestamp, expectedTime.timeIntervalSince1970.toInt64Milliseconds) } XCTAssertEqual(core.recordsCountByViewID, ["abc": 5]) @@ -138,10 +132,10 @@ class SnapshotProcessorTests: XCTestCase { XCTAssertEqual(enrichedRecords[0].records.count, 3, "Segment must start with 'meta' → 'focus' → 'full snapshot' records") XCTAssertTrue(enrichedRecords[0].records[0].isMetaRecord) XCTAssertTrue(enrichedRecords[0].records[1].isFocusRecord) - XCTAssertTrue(enrichedRecords[0].records[2].isFullSnapshotRecord && enrichedRecords[0].hasFullSnapshot) + XCTAssertTrue(enrichedRecords[0].records[2].isFullSnapshotRecord) XCTAssertEqual(enrichedRecords[1].records.count, 2, "It should follow with 'full snapshot' → 'incremental snapshot' records") - XCTAssertTrue(enrichedRecords[1].records[0].isFullSnapshotRecord && enrichedRecords[1].hasFullSnapshot) + XCTAssertTrue(enrichedRecords[1].records[0].isFullSnapshotRecord) XCTAssertTrue(enrichedRecords[1].records[1].isIncrementalSnapshotRecord) XCTAssertEqual(enrichedRecords[1].records[1].incrementalSnapshot?.viewportResizeData?.height, 100) XCTAssertEqual(enrichedRecords[1].records[1].incrementalSnapshot?.viewportResizeData?.width, 200) @@ -178,7 +172,7 @@ class SnapshotProcessorTests: XCTestCase { XCTAssertEqual(enrichedRecords[0].records.count, 3, "Segment must start with 'meta' → 'focus' → 'full snapshot' records") XCTAssertTrue(enrichedRecords[0].records[0].isMetaRecord) XCTAssertTrue(enrichedRecords[0].records[1].isFocusRecord) - XCTAssertTrue(enrichedRecords[0].records[2].isFullSnapshotRecord && enrichedRecords[0].hasFullSnapshot) + XCTAssertTrue(enrichedRecords[0].records[2].isFullSnapshotRecord) XCTAssertEqual(enrichedRecords[1].records.count, 1, "It should follow with 'incremental snapshot' record") XCTAssertTrue(enrichedRecords[1].records[0].isIncrementalSnapshotRecord) @@ -186,7 +180,7 @@ class SnapshotProcessorTests: XCTestCase { XCTAssertEqual(enrichedRecords[2].records.count, 3, "Next segment must start with 'meta' → 'focus' → 'full snapshot' records") XCTAssertTrue(enrichedRecords[2].records[0].isMetaRecord) XCTAssertTrue(enrichedRecords[2].records[1].isFocusRecord) - XCTAssertTrue(enrichedRecords[2].records[2].isFullSnapshotRecord && enrichedRecords[2].hasFullSnapshot) + XCTAssertTrue(enrichedRecords[2].records[2].isFullSnapshotRecord) XCTAssertEqual(enrichedRecords[3].records.count, 1, "Next segment should follow with 'incremental snapshot' record") XCTAssertTrue(enrichedRecords[3].records[0].isIncrementalSnapshotRecord) @@ -224,14 +218,12 @@ class SnapshotProcessorTests: XCTestCase { XCTAssertEqual(enrichedRecord.applicationID, rum.applicationID) XCTAssertEqual(enrichedRecord.sessionID, rum.sessionID) XCTAssertEqual(enrichedRecord.viewID, rum.viewID) - XCTAssertEqual(enrichedRecord.earliestTimestamp, earliestTouchTime.timeIntervalSince1970.toInt64Milliseconds) - XCTAssertEqual(enrichedRecord.latestTimestamp, snapshotTime.timeIntervalSince1970.toInt64Milliseconds) XCTAssertEqual(enrichedRecord.records.count, 13) XCTAssertTrue( enrichedRecord.records[0].isMetaRecord && enrichedRecord.records[1].isFocusRecord && - enrichedRecord.records[2].isFullSnapshotRecord && enrichedRecord.hasFullSnapshot, + enrichedRecord.records[2].isFullSnapshotRecord, "Segment must start with 'meta' → 'focus' → 'full snapshot' records" ) @@ -273,7 +265,7 @@ class SnapshotProcessorTests: XCTestCase { XCTAssertEqual(enrichedRecords[0].records.count, 3, "Segment must start with 'meta' → 'focus' → 'full snapshot' records") XCTAssertTrue(enrichedRecords[0].records[0].isMetaRecord) XCTAssertTrue(enrichedRecords[0].records[1].isFocusRecord) - XCTAssertTrue(enrichedRecords[0].records[2].isFullSnapshotRecord && enrichedRecords[0].hasFullSnapshot) + XCTAssertTrue(enrichedRecords[0].records[2].isFullSnapshotRecord) XCTAssertEqual(enrichedRecords[1].records.count, 1, "It should follow with 'incremental snapshot' record") XCTAssertTrue(enrichedRecords[1].records[0].isIncrementalSnapshotRecord) diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageResourceTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageResourceTests.swift new file mode 100644 index 0000000000..5d13f75ada --- /dev/null +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageResourceTests.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 +@testable import DatadogSessionReplay + +// Soft checks, because different iOS versions may produce different image data or hashing +final class UIImageResourceTests: XCTestCase { + func testWhenUIImageResourceIsInitializedWithEmptyImage() { + let imageResource = UIImageResource(image: UIImage(), tintColor: nil) + + XCTAssertEqual(imageResource.calculateIdentifier(), "") + XCTAssertEqual(imageResource.calculateData(), Data()) + } + + func testWhenUIImageResourceIsInitializedWithoutTintColor() { + let image = createSinglePixelImage() + let imageResource = UIImageResource(image: image, tintColor: nil) + + XCTAssertNotEqual(imageResource.calculateIdentifier(), "") + XCTAssertGreaterThan(imageResource.calculateData().count, 0) + } + + func testWhenUIImageResourceIsInitializedWithTintColor() { + let image = createSinglePixelImage() + let tintColor = UIColor.red + let imageResource = UIImageResource(image: image, tintColor: tintColor) + + XCTAssertNotEqual(imageResource.calculateIdentifier(), "") + XCTAssertGreaterThan(imageResource.calculateData().count, 0) + } + + @available(iOS 13.0, *) + func testWhenUIImageResourceIsInitializedWithSystemIconWithoutTintColor() { + let image = UIImage(systemName: "circle.fill")! + let imageResource = UIImageResource(image: image, tintColor: nil) + + XCTAssertNotEqual(imageResource.calculateIdentifier(), "") + XCTAssertGreaterThan(imageResource.calculateData().count, 0) + } + + @available(iOS 13.0, *) + func testWhenUIImageResourceIsInitializedWithSystemIconWithTintColor() { + let image = UIImage(systemName: "circle.fill")! + let tintColor = UIColor.red + let imageResource = UIImageResource(image: image, tintColor: tintColor) + + XCTAssertNotEqual(imageResource.calculateIdentifier(), "") + XCTAssertGreaterThan(imageResource.calculateData().count, 0) + } + + private func createSinglePixelImage() -> UIImage { + UIGraphicsBeginImageContext(CGSize(width: 1, height: 1)) + let image = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return image + } +} diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorderTests.swift index 74200a0b3c..ac9166b1f6 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorderTests.swift @@ -65,7 +65,6 @@ class UIImageViewRecorderTests: XCTestCase { let builder = try XCTUnwrap(semantics.nodes.first?.wireframesBuilder as? UIImageViewWireframesBuilder) XCTAssertFalse(builder.shouldRecordImage) XCTAssertNil(semantics.resources.first as? UIImage) - XCTAssertEqual(imageView.image?.recorded, false) } func testWhenShouldRecordImagePredicateReturnsTrue() throws { @@ -81,21 +80,6 @@ class UIImageViewRecorderTests: XCTestCase { let builder = try XCTUnwrap(semantics.nodes.first?.wireframesBuilder as? UIImageViewWireframesBuilder) XCTAssertTrue(builder.shouldRecordImage) XCTAssertNotNil(semantics.resources.first) - XCTAssertEqual(imageView.image?.recorded, true) - } - - func testWhenTintColorIsProvided() throws { - // When - let recorder = UIImageViewRecorder(tintColorProvider: { _ in return .red }) - imageView.image = UIImage() - viewAttributes = .mock(fixture: .visible()) - - // Then - let semantics = try XCTUnwrap(recorder.semantics(of: imageView, with: viewAttributes, in: .mockAny())) - XCTAssertTrue(semantics is SpecificElement) - XCTAssertEqual(semantics.subtreeStrategy, .record, "Image view's subtree should be recorded") - let builder = try XCTUnwrap(semantics.nodes.first?.wireframesBuilder as? UIImageViewWireframesBuilder) - XCTAssertEqual(builder.tintColor, .red) } func testWhenViewIsNotOfExpectedType() { diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewWireframesBuilderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewWireframesBuilderTests.swift index 326e9ddb6f..b0a016fa9f 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewWireframesBuilderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewWireframesBuilderTests.swift @@ -25,9 +25,7 @@ class UIImageViewWireframesBuilderTests: XCTestCase { attributes: ViewAttributes.mock(fixture: .visible(.someAppearance)), contentFrame: CGRect(x: 10, y: 10, width: 200, height: 200), clipsToBounds: true, - image: .mockRandom(), - imageDataProvider: MockImageDataProvider(), - tintColor: UIColor.mockRandom(), + imageResource: .mockRandom(), shouldRecordImage: true ) @@ -43,7 +41,7 @@ class UIImageViewWireframesBuilderTests: XCTestCase { if case let .imageWireframe(imageWireframe) = wireframes[1] { XCTAssertEqual(imageWireframe.id, imageWireframeID) - XCTAssertEqual(imageWireframe.base64, "mock_base64_string") + XCTAssertNil(imageWireframe.base64) // deprecated field } else { XCTFail("Second wireframe needs to be imageWireframe case") } @@ -58,9 +56,7 @@ class UIImageViewWireframesBuilderTests: XCTestCase { attributes: ViewAttributes.mock(fixture: .visible(.someAppearance)), contentFrame: CGRect(x: 10, y: 10, width: 200, height: 200), clipsToBounds: true, - image: .mockRandom(), - imageDataProvider: mockRandomImageDataProvider(), - tintColor: UIColor.mockRandom(), + imageResource: nil, shouldRecordImage: false ) diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorderTests.swift new file mode 100644 index 0000000000..f03be46989 --- /dev/null +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorderTests.swift @@ -0,0 +1,84 @@ +/* + * 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 WebKit +import TestUtilities + +@_spi(Internal) +@testable import DatadogSessionReplay + +class WKWebViewRecorderTests: XCTestCase { + private let recorder = WKWebViewRecorder() + /// The web-view under test. + private let webView = WKWebView() + /// `ViewAttributes` simulating common attributes of web-view's `UIView`. + private var viewAttributes: ViewAttributes = .mockAny() + + func testWhenViewIsNotOfExpectedType() { + // Given + let view = UILabel() + + // Then + XCTAssertNil(recorder.semantics(of: view, with: .mockAny(), in: .mockAny())) + } + + func testWhenWebViewIsNotVisible() throws { + // Given + let viewAttributes: ViewAttributes = .mock(fixture: .invisible) + + // When + let semantics = try XCTUnwrap(recorder.semantics(of: webView, with: viewAttributes, in: .mockAny())) + + // Then + XCTAssertTrue(semantics is InvisibleElement) + } + + func testWhenWebViewIsVisible() throws { + // Given + let viewAttributes: ViewAttributes = .mock(fixture: .visible()) + + // When + let semantics = try XCTUnwrap(recorder.semantics(of: webView, with: viewAttributes, in: .mockAny()) as? SpecificElement) + + // Then + XCTAssertEqual(semantics.subtreeStrategy, .ignore, "WebView's subtree should not be recorded") + + let builder = try XCTUnwrap(semantics.nodes.first?.wireframesBuilder as? WKWebViewWireframesBuilder) + XCTAssertEqual(builder.attributes, viewAttributes) + } + + func testWebViewWireframeBuilder() throws { + // Given + let id: WireframeID = .mockRandom() + let slotId: Int = .mockRandom() + let attributes: ViewAttributes = .mock(fixture: .visible()) + + let builder = WKWebViewWireframesBuilder( + wireframeID: id, + slotID: slotId, + attributes: attributes + ) + + // When + let wireframes = builder.buildWireframes(with: WireframesBuilder()) + + // Then + XCTAssertEqual(wireframes.count, 1) + + guard case let .webviewWireframe(wireframe) = wireframes.first else { + return XCTFail("First wireframe needs to be webviewWireframe case") + } + + XCTAssertEqual(wireframe.id, id) + XCTAssertEqual(wireframe.slotId, String(slotId)) + XCTAssertNil(wireframe.clip) + XCTAssertEqual(wireframe.x, Int64(withNoOverflow: attributes.frame.minX)) + XCTAssertEqual(wireframe.y, Int64(withNoOverflow: attributes.frame.minY)) + XCTAssertEqual(wireframe.width, Int64(withNoOverflow: attributes.frame.width)) + XCTAssertEqual(wireframe.height, Int64(withNoOverflow: attributes.frame.height)) + } +} diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilderTests.swift index ddd6f9d6b8..2e1842ab34 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilderTests.swift @@ -17,8 +17,7 @@ class ViewTreeSnapshotBuilderTests: XCTestCase { let nodeRecorder = NodeRecorderMock(resultForView: { _ in nil }) let builder = ViewTreeSnapshotBuilder( viewTreeRecorder: ViewTreeRecorder(nodeRecorders: [nodeRecorder]), - idsGenerator: NodeIDGenerator(), - imageDataProvider: MockImageDataProvider() + idsGenerator: NodeIDGenerator() ) // When @@ -39,8 +38,7 @@ class ViewTreeSnapshotBuilderTests: XCTestCase { let nodeRecorder = NodeRecorderMock(resultForView: { _ in nil }) let builder = ViewTreeSnapshotBuilder( viewTreeRecorder: ViewTreeRecorder(nodeRecorders: [nodeRecorder]), - idsGenerator: NodeIDGenerator(), - imageDataProvider: MockImageDataProvider() + idsGenerator: NodeIDGenerator() ) // When diff --git a/DatadogSessionReplay/Tests/Utilities/CacheTests.swift b/DatadogSessionReplay/Tests/Utilities/CacheTests.swift deleted file mode 100644 index 17954b4344..0000000000 --- a/DatadogSessionReplay/Tests/Utilities/CacheTests.swift +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 -@testable import DatadogSessionReplay -@testable import TestUtilities - -class CacheTests: XCTestCase { - func test_insertAndValueForKey() { - let cache = Cache() - cache.insert(1, forKey: "one") - XCTAssertEqual(cache.value(forKey: "one"), 1) - } - - func test_removeValueForKey() { - let cache = Cache() - cache.insert(1, forKey: "one") - cache.removeValue(forKey: "one") - XCTAssertNil(cache.value(forKey: "one")) - } - - func test_subscript() { - let cache = Cache() - cache["one"] = 1 - XCTAssertEqual(cache["one"], 1) - - cache["one"] = nil - XCTAssertNil(cache["one"]) - } - - func test_expiration() { - let cache = Cache(dateProvider: { - return Date(timeIntervalSinceReferenceDate: 0) - }, entryLifetime: 0) - - cache.insert(1, forKey: "one") - XCTAssertNil(cache.value(forKey: "one")) - } - - func test_bytesLimit() { - let cache = Cache(dateProvider: { - return Date(timeIntervalSinceReferenceDate: 0) - }, totalBytesLimit: 1) - - cache.insert(1, forKey: "one", size: 1) - cache.insert(1, forKey: "two", size: 1) - XCTAssertNil(cache.value(forKey: "one")) - XCTAssertNotNil(cache.value(forKey: "two")) - } - - func test_countLimit() { - let cache = Cache(maximumEntryCount: 1) - - cache.insert(1, forKey: "one") - cache.insert(2, forKey: "two") - cache.insert(3, forKey: "three") - XCTAssertNil(cache.value(forKey: "one")) - XCTAssertNil(cache.value(forKey: "two")) - XCTAssertEqual(cache.value(forKey: "three"), 3) - } -} diff --git a/DatadogSessionReplay/Tests/Utilities/UIImage+ScalingTests.swift b/DatadogSessionReplay/Tests/Utilities/UIImage+ScalingTests.swift index f6a5f5b6e7..d5ef20fa53 100644 --- a/DatadogSessionReplay/Tests/Utilities/UIImage+ScalingTests.swift +++ b/DatadogSessionReplay/Tests/Utilities/UIImage+ScalingTests.swift @@ -15,7 +15,7 @@ class UIImageScalingTests: XCTestCase { let dataSize = pngData.count let maxSize = dataSize + 100 - let scaledData = image.scaledDownToApproximateSize(maxSize) + let scaledData = image.scaledDownToApproximateSize(UInt64(maxSize)) XCTAssertEqual(scaledData, pngData) } @@ -25,7 +25,7 @@ class UIImageScalingTests: XCTestCase { let dataSize = pngData.count let maxSize = dataSize - 100 - let scaledData = image.scaledDownToApproximateSize(maxSize) + let scaledData = image.scaledDownToApproximateSize(UInt64(maxSize)) XCTAssertTrue(scaledData.count < dataSize) } } diff --git a/DatadogSessionReplay/Tests/Writer/Models/EnrichedRecordTests.swift b/DatadogSessionReplay/Tests/Writer/Models/EnrichedRecordTests.swift deleted file mode 100644 index 3b726427f5..0000000000 --- a/DatadogSessionReplay/Tests/Writer/Models/EnrichedRecordTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 -@testable import TestUtilities -@_spi(Internal) -@testable import DatadogSessionReplay - -class EnrichedRecordTests: XCTestCase { - func testItHasFullSnapshot() { - // Given - let recordsWithFSR: [SRRecord] = .mockRandom() + [.fullSnapshotRecord(value: .mockRandom())] - let recordsWithNoFSR: [SRRecord] = .mockRandom().filter { !$0.isFullSnapshotRecord } - - // When - let enrichedRecords1 = EnrichedRecord( - context: .mockAny(), - records: recordsWithFSR - ) - let enrichedRecords2 = EnrichedRecord( - context: .mockAny(), - records: recordsWithNoFSR - ) - - // Then - XCTAssertTrue(enrichedRecords1.hasFullSnapshot) - XCTAssertFalse(enrichedRecords2.hasFullSnapshot) - } - - func testItComputesEarliestAndLatestTimestamps() { - // Given - let records: [SRRecord] = .mockRandom(count: .mockRandom(min: 1, max: 100)) - - // When - let enrichedRecords = EnrichedRecord( - context: .mockAny(), - records: records - ) - - // Then - XCTAssertEqual(enrichedRecords.earliestTimestamp, records.map({ $0.timestamp }).reduce(.max, min)) - XCTAssertEqual(enrichedRecords.latestTimestamp, records.map({ $0.timestamp }).reduce(.min, max)) - } -} diff --git a/DatadogSessionReplay/Tests/Writer/RecordsWriterTests.swift b/DatadogSessionReplay/Tests/Writer/RecordsWriterTests.swift index 42c938fbac..9f0a249c18 100644 --- a/DatadogSessionReplay/Tests/Writer/RecordsWriterTests.swift +++ b/DatadogSessionReplay/Tests/Writer/RecordsWriterTests.swift @@ -40,22 +40,5 @@ class RecordsWriterTests: XCTestCase { XCTAssertEqual(core.events(ofType: EnrichedRecord.self).count, 0) } - - func testWhenSucceedingRecordsDescribeDifferentRUMViews_itWritesThemToSeparateBatches() throws { - // Given - let forceNewBatchExpectation = expectation(description: "Should force new batch on view-id change") - forceNewBatchExpectation.expectedFulfillmentCount = 2 - let core = PassthroughCoreMock(forceNewBatchExpectation: forceNewBatchExpectation) - - // When - let writer = RecordWriter(core: core) - - // Then - writer.write(nextRecord: EnrichedRecord(context: .mockWith(rumContext: .mockWith(viewID: "view1")), records: .mockRandom())) - writer.write(nextRecord: EnrichedRecord(context: .mockWith(rumContext: .mockWith(viewID: "view2")), records: .mockRandom())) - - XCTAssertEqual(core.events(ofType: EnrichedRecord.self).count, 2) - waitForExpectations(timeout: 0.5, handler: nil) - } } // swiftlint:enable empty_xctest_method diff --git a/DatadogTrace.podspec b/DatadogTrace.podspec index 523cd3ebed..3c2799fe54 100644 --- a/DatadogTrace.podspec +++ b/DatadogTrace.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogTrace" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Datadog Trace Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogTrace/Sources/Span/SpanWriteContext.swift b/DatadogTrace/Sources/Span/SpanWriteContext.swift index 5d3caefba5..5755ef29fc 100644 --- a/DatadogTrace/Sources/Span/SpanWriteContext.swift +++ b/DatadogTrace/Sources/Span/SpanWriteContext.swift @@ -41,7 +41,7 @@ internal final class LazySpanWriteContext: SpanWriteContext { } // Ignore the current context and use the one captured at initialization: - scope.eventWriteContext(bypassConsent: false, forceNewBatch: false) { _, writer in + scope.eventWriteContext { _, writer in guard let context = self.context else { return // unexpected } diff --git a/DatadogTrace/Tests/Span/SpanWriteContextTests.swift b/DatadogTrace/Tests/Span/SpanWriteContextTests.swift index b366ed0631..3fc4f04ef4 100644 --- a/DatadogTrace/Tests/Span/SpanWriteContextTests.swift +++ b/DatadogTrace/Tests/Span/SpanWriteContextTests.swift @@ -34,8 +34,7 @@ class SpanWriteContextTests: XCTestCase { func testWhenWritingEvent_itRespectsCoreConsentAndBatching() { let core = PassthroughCoreMock( expectation: expectation(description: "write event to core"), - bypassConsentExpectation: invertedExpectation(description: "do not bypass consent"), - forceNewBatchExpectation: invertedExpectation(description: "do not force new batch") + bypassConsentExpectation: invertedExpectation(description: "do not bypass consent") ) // Given diff --git a/DatadogWebViewTracking.podspec b/DatadogWebViewTracking.podspec index 2e01ec0040..13e200d5cd 100644 --- a/DatadogWebViewTracking.podspec +++ b/DatadogWebViewTracking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogWebViewTracking" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Datadog WebView Tracking Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogWebViewTracking/Sources/DDScriptMessageHandler.swift b/DatadogWebViewTracking/Sources/DDScriptMessageHandler.swift index 98dc702099..aaba9df396 100644 --- a/DatadogWebViewTracking/Sources/DDScriptMessageHandler.swift +++ b/DatadogWebViewTracking/Sources/DDScriptMessageHandler.swift @@ -28,14 +28,11 @@ internal class DDScriptMessageHandler: NSObject, WKScriptMessageHandler { _ userContentController: WKUserContentController, didReceive message: WKScriptMessage ) { + let hash = userContentController.hash // message.body must be called within UI thread - let messageBody = message.body + let body = message.body queue.async { - do { - try self.emitter.send(body: messageBody) - } catch { - DD.logger.error("Encountered an error when receiving web view event", error: error) - } + self.emitter.send(body: body, slotId: String(hash)) } } } diff --git a/DatadogWebViewTracking/Sources/MessageEmitter.swift b/DatadogWebViewTracking/Sources/MessageEmitter.swift index 3df123079b..464996fd16 100644 --- a/DatadogWebViewTracking/Sources/MessageEmitter.swift +++ b/DatadogWebViewTracking/Sources/MessageEmitter.swift @@ -4,19 +4,21 @@ * Copyright 2019-Present Datadog, Inc. */ +import Foundation import DatadogInternal +/// Errors that can be thrown when parsing a WebView message +internal enum WebViewMessageError: Error, Equatable { + case dataSerialization(message: String) + case invalidMessage(description: String) +} + /// A type forwarding type-less messages received from Datadog Browser SDK to either `DatadogRUM` or `DatadogLogs`. internal final class MessageEmitter: InternalExtension.AbstractMessageEmitter { - enum MessageKeys { - static let browserLog = "browser-log" - static let browserRUMEvent = "browser-rum-event" - } - - /// Log events sampler. - let logsSampler: Sampler /// The core for events forwarding. private weak var core: DatadogCoreProtocol? + /// Log events sampler. + let logsSampler: Sampler init( logsSampler: Sampler, @@ -28,29 +30,60 @@ internal final class MessageEmitter: InternalExtension.Abstract /// Sends a bag of data to the message bus /// - Parameter body: The data to send, it must be parsable to `WebViewMessage` - override func send(body: Any) throws { - let message = try WebViewMessage(body: body) - send(message: message) - } - - /// Sends a message to the message bus - /// - Parameter message: The message to send - private func send(message: WebViewMessage) { + override func send(body: Any, slotId: String? = nil) { guard let core = core else { return DD.logger.debug("Core must not be nil when using WebViewTracking") } - switch message { - case let .log(event): - if logsSampler.sample() { - core.send(message: .baggage(key: MessageKeys.browserLog, value: AnyEncodable(event)), else: { - DD.logger.warn("A WebView log is lost because Logging is disabled in the SDK") - }) + do { + guard let body = body as? String else { + throw WebViewMessageError.invalidMessage(description: String(describing: body)) + } + + guard let data = body.data(using: .utf8) else { + throw WebViewMessageError.dataSerialization(message: body) } - case let .rum(event): - core.send(message: .baggage(key: MessageKeys.browserRUMEvent, value: AnyEncodable(event)), else: { - DD.logger.warn("A WebView RUM event is lost because RUM is disabled in the SDK") - }) + + let decoder = JSONDecoder() + let event = try decoder.decode(WebViewMessage.self, from: data) + + switch event { + case .log: + send(log: event, in: core) + case .rum: + send(rum: event, in: core) + case let .record(event, view): + send(record: event, view: view, slotId: slotId, in: core) + } + } catch { + DD.logger.error("Encountered an error when receiving web view event", error: error) + core.telemetry.error("Encountered an error when receiving web view event", error: error) + } + } + + private func send(log message: WebViewMessage, in core: DatadogCoreProtocol) { + guard logsSampler.sample() else { + return } + + core.send(message: .webview(message), else: { + DD.logger.warn("A WebView log is lost because Logging is disabled in the SDK") + }) + } + + private func send(rum message: WebViewMessage, in core: DatadogCoreProtocol) { + core.send(message: .webview(message), else: { + DD.logger.warn("A WebView RUM event is lost because RUM is disabled in the SDK") + }) + } + + private func send(record event: WebViewMessage.Event, view: WebViewMessage.View, slotId: String?, in core: DatadogCoreProtocol) { + var event = event + // inject the slotId + event["slotId"] = slotId + + core.send(message: .webview(.record(event, view)), else: { + DD.logger.warn("A WebView Replay record is lost because Session Replay is disabled in the SDK") + }) } } diff --git a/DatadogWebViewTracking/Sources/WebViewMessage.swift b/DatadogWebViewTracking/Sources/WebViewMessage.swift deleted file mode 100644 index 6501b924fd..0000000000 --- a/DatadogWebViewTracking/Sources/WebViewMessage.swift +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 typealias JSON = [String: Any] - -/// Intermediate type to parse WebView messages and send them to the message bus -internal enum WebViewMessage { - /// A log message with a JSON payload - case log(JSON) - - /// A RUM event with a JSON payload - case rum(JSON) -} - -/// Errors that can be thrown when parsing a WebView message -internal enum WebViewMessageError: Error, Equatable { - case dataSerialization(message: String) - case JSONDeserialization(rawJSONDescription: String) - case invalidMessage(description: String) - case missingKey(key: String) -} - -extension WebViewMessage { - internal enum Keys { - static let eventType = "eventType" - static let event = "event" - } - - private enum EventTypes { - static let log = "log" - } - - /// Parses a bag of data to a `WebViewTrackingMessage` - /// - Parameter body: Unstructured bag of data - internal init(body: Any) throws { - guard let message = body as? String else { - throw WebViewMessageError.invalidMessage(description: String(describing: body)) - } - - let eventJSON = try WebViewMessage.parse(message) - - guard let type = eventJSON[Keys.eventType] as? String else { - throw WebViewMessageError.missingKey(key: Keys.eventType) - } - - guard let event = eventJSON[Keys.event] as? JSON else { - throw WebViewMessageError.missingKey(key: Keys.event) - } - - switch type { - case EventTypes.log: - self = .log(event) - default: - self = .rum(event) - } - } - - private static func parse(_ message: String) throws -> JSON { - guard let data = message.data(using: .utf8) else { - throw WebViewMessageError.dataSerialization(message: message) - } - let rawJSON = try JSONSerialization.jsonObject(with: data, options: []) - guard let json = rawJSON as? JSON else { - throw WebViewMessageError.JSONDeserialization(rawJSONDescription: String(describing: rawJSON)) - } - return json - } -} diff --git a/DatadogWebViewTracking/Sources/WebViewTracking.swift b/DatadogWebViewTracking/Sources/WebViewTracking.swift index 514e2e41fb..167c04156c 100644 --- a/DatadogWebViewTracking/Sources/WebViewTracking.swift +++ b/DatadogWebViewTracking/Sources/WebViewTracking.swift @@ -136,7 +136,7 @@ extension InternalExtension where ExtendedType == WebViewTracking { /// Sends a web-view message. /// /// - Parameter message: The message to send - public func send(body: Any) throws {} + public func send(body: Any, slotId: String? = nil) {} } /// Creates a web-view message emitter for cross-platform. diff --git a/DatadogWebViewTracking/Tests/MessageEmitterTests.swift b/DatadogWebViewTracking/Tests/MessageEmitterTests.swift index 10c0a392b6..cab31fec2c 100644 --- a/DatadogWebViewTracking/Tests/MessageEmitterTests.swift +++ b/DatadogWebViewTracking/Tests/MessageEmitterTests.swift @@ -7,6 +7,7 @@ import XCTest import TestUtilities import DatadogInternal + @testable import DatadogWebViewTracking class MessageEmitterTests: XCTestCase { @@ -14,7 +15,6 @@ class MessageEmitterTests: XCTestCase { func testGivenSampleRate100_whenReceivingLogEvent_itForwardsToLogs() throws { let sampler = Sampler(samplingRate: 100) - let eventType = "log" // Given let receiverMock = FeatureMessageReceiverMock() @@ -22,9 +22,9 @@ class MessageEmitterTests: XCTestCase { let emitter = MessageEmitter(logsSampler: sampler, core: core) // When - try emitter.send(body: """ + emitter.send(body: """ { - "eventType": "\(eventType)", + "eventType": "log", "event": { "attribute1": 123, "attribute2": "foo", @@ -34,9 +34,12 @@ class MessageEmitterTests: XCTestCase { """) // Then - let messageKey = MessageEmitter.MessageKeys.browserLog - let message = try XCTUnwrap(receiverMock.messages.firstBaggage(withKey: messageKey)) - let json = JSONObjectMatcher(object: try message.encode() as! JSON) + let message = try XCTUnwrap(receiverMock.messages.firstWebViewMessage) + guard case let .log(event) = message else { + return XCTFail("not a log message") + } + + let json = JSONObjectMatcher(object: event) XCTAssertEqual(try json.value("attribute1"), 123) XCTAssertEqual(try json.value("attribute2"), "foo") XCTAssertEqual(try json.array("attribute3").values(), ["foo", "bar", "bizz"]) @@ -52,7 +55,7 @@ class MessageEmitterTests: XCTestCase { let emitter = MessageEmitter(logsSampler: sampler, core: core) // When - try emitter.send(body: """ + emitter.send(body: """ { "eventType": "\(eventType)", "event": { @@ -64,22 +67,19 @@ class MessageEmitterTests: XCTestCase { """) // Then - let messageKey = MessageEmitter.MessageKeys.browserLog - XCTAssertNil(receiverMock.messages.firstBaggage(withKey: messageKey)) + XCTAssertNil(receiverMock.messages.firstWebViewMessage) } func testWhenReceivingEventOtherThanLog_itForwardsToRUM() throws { - let eventType: String = .mockRandom(otherThan: ["log"]) - // Given let receiverMock = FeatureMessageReceiverMock() let core = PassthroughCoreMock(messageReceiver: receiverMock) let emitter = MessageEmitter(logsSampler: .mockRandom(), core: core) // When - try emitter.send(body: """ + emitter.send(body: """ { - "eventType": "\(eventType)", + "eventType": "rum", "event": { "attribute1": 123, "attribute2": "foo", @@ -89,9 +89,12 @@ class MessageEmitterTests: XCTestCase { """) // Then - let messageKey = MessageEmitter.MessageKeys.browserRUMEvent - let message = try XCTUnwrap(receiverMock.messages.firstBaggage(withKey: messageKey)) - let json = JSONObjectMatcher(object: try message.encode() as! JSON) + let message = try XCTUnwrap(receiverMock.messages.firstWebViewMessage) + guard case let .rum(event) = message else { + return XCTFail("not a log message") + } + + let json = JSONObjectMatcher(object: event) XCTAssertEqual(try json.value("attribute1"), 123) XCTAssertEqual(try json.value("attribute2"), "foo") XCTAssertEqual(try json.array("attribute3").values(), ["foo", "bar", "bizz"]) @@ -99,16 +102,21 @@ class MessageEmitterTests: XCTestCase { // MARK: - Parsing - func testWhenMessageIsInvalid_itThrows() { - let bridge = MessageEmitter(logsSampler: .mockAny(), core: PassthroughCoreMock()) + func testWhenMessageIsInvalid_itReportTheError() throws { + let dd = DD.mockWith(logger: CoreLoggerMock()) + defer { dd.reset() } + + // Given + let telemetry = TelemetryReceiverMock() + let core = PassthroughCoreMock(messageReceiver: telemetry) + let bridge = MessageEmitter(logsSampler: .mockAny(), core: core) - let messageInvalidJSON = """ - { 123: foobar } - """ + // When + bridge.send(body: 123) - XCTAssertThrowsError( - try bridge.send(body: messageInvalidJSON), - "Non-string keys (123) should throw" - ) + // Then + XCTAssertEqual(dd.logger.errorLog?.message, "Encountered an error when receiving web view event") + XCTAssertEqual(dd.logger.errorLog?.error?.message, #"invalidMessage(description: "123")"#) + XCTAssertEqual(telemetry.messages.first?.asError?.message, #"Encountered an error when receiving web view event - invalidMessage(description: "123")"#) } } diff --git a/DatadogWebViewTracking/Tests/WebViewMessageTests.swift b/DatadogWebViewTracking/Tests/WebViewMessageTests.swift deleted file mode 100644 index ff5a1494a5..0000000000 --- a/DatadogWebViewTracking/Tests/WebViewMessageTests.swift +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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 TestUtilities -@testable import DatadogWebViewTracking - -class WebViewMessageTests: XCTestCase { - func testParsingLogEvent() throws { - // Given - let eventString = """ - { - "eventType": "log", - "event": { - "date": 1635932927012, - "error": { - "origin": "console" - }, - "message": "console error: error", - "session_id": "0110cab4-7471-480e-aa4e-7ce039ced355", - "status": "error", - "view": { - "referrer": "", - "url": "https://datadoghq.dev/browser-sdk-test-playground" - } - }, - "tags": [ - "browser_sdk_version:3.6.13" - ] - } - """ - - // When - let message = try WebViewMessage(body: eventString) - - // Then - XCTAssertTrue(message.isLogEvent) - let event = JSONObjectMatcher(object: message.json) - XCTAssertEqual(try event.value("date"), 1_635_932_927_012) - XCTAssertEqual(try event.value("error.origin"), "console") - XCTAssertEqual(try event.value("message"), "console error: error") - XCTAssertEqual(try event.value("session_id"), "0110cab4-7471-480e-aa4e-7ce039ced355") - XCTAssertEqual(try event.value("status"), "error") - XCTAssertEqual(try event.value("view.referrer"), "") - XCTAssertEqual(try event.value("view.url"), "https://datadoghq.dev/browser-sdk-test-playground") - } - - func testParsingRUMEvent() throws { - // Given - let eventString = """ - { - "eventType": "view", - "event": { - "application": { - "id": "xxx" - }, - "date": 1635933113708, - "service": "super", - "session": { - "id": "0110cab4-7471-480e-aa4e-7ce039ced355", - "type": "user" - }, - "type": "view", - "view": { - "action": { - "count": 0 - }, - "cumulative_layout_shift": 0, - "dom_complete": 152800000, - "dom_content_loaded": 118300000, - "dom_interactive": 116400000, - "error": { - "count": 0 - }, - "first_contentful_paint": 121300000, - "id": "64308fd4-83f9-48cb-b3e1-1e91f6721230", - "in_foreground_periods": [], - "is_active": true, - "largest_contentful_paint": 121299000, - "load_event": 152800000, - "loading_time": 152800000, - "loading_type": "initial_load", - "long_task": { - "count": 0 - }, - "referrer": "", - "resource": { - "count": 3 - }, - "time_spent": 3120000000, - "url": "http://localhost:8080/test.html" - }, - "_dd": { - "document_version": 2, - "drift": 0, - "format_version": 2, - "session": { - "plan": 2 - } - } - }, - "tags": [ - "browser_sdk_version:3.6.13" - ] - } - """ - - // When - let message = try WebViewMessage(body: eventString) - - // Then - XCTAssertTrue(message.isRUMEvent) - let event = JSONObjectMatcher(object: message.json) // only partial matching - XCTAssertEqual(try event.value("application.id"), "xxx") - XCTAssertEqual(try event.value("date"), 1_635_933_113_708) - XCTAssertEqual(try event.value("service"), "super") - XCTAssertEqual(try event.value("session.id"), "0110cab4-7471-480e-aa4e-7ce039ced355") - XCTAssertEqual(try event.value("session.type"), "user") - XCTAssertEqual(try event.value("type"), "view") - XCTAssertEqual(try event.value("view.action.count"), 0) - XCTAssertEqual(try event.value("view.cumulative_layout_shift"), 0) - XCTAssertEqual(try event.value("view.dom_complete"), 152_800_000) - XCTAssertEqual(try event.value("view.dom_content_loaded"), 118_300_000) - XCTAssertEqual(try event.value("view.dom_interactive"), 116_400_000) - XCTAssertEqual(try event.value("view.error.count"), 0) - XCTAssertEqual(try event.value("view.first_contentful_paint"), 121_300_000) - XCTAssertEqual(try event.value("view.id"), "64308fd4-83f9-48cb-b3e1-1e91f6721230") - XCTAssertEqual(try event.array("view.in_foreground_periods").count, 0) - XCTAssertEqual(try event.value("view.is_active"), true) - XCTAssertEqual(try event.value("view.largest_contentful_paint"), 121_299_000) - XCTAssertEqual(try event.value("view.load_event"), 152_800_000) - XCTAssertEqual(try event.value("view.loading_time"), 152_800_000) - XCTAssertEqual(try event.value("view.loading_type"), "initial_load") - XCTAssertEqual(try event.value("view.long_task.count"), 0) - XCTAssertEqual(try event.value("view.referrer"), "") - XCTAssertEqual(try event.value("view.resource.count"), 3) - XCTAssertEqual(try event.value("view.time_spent"), 3_120_000_000) - XCTAssertEqual(try event.value("view.url"), "http://localhost:8080/test.html") - XCTAssertEqual(try event.value("_dd.document_version"), 2) - XCTAssertEqual(try event.value("_dd.drift"), 0) - XCTAssertEqual(try event.value("_dd.format_version"), 2) - XCTAssertEqual(try event.value("_dd.session.plan"), 2) - } - - func testParsingCorruptedEvent() { - let invalidJSON = "(^#$@#)" - - XCTAssertThrowsError(try WebViewMessage(body: invalidJSON)) { error in - XCTAssertEqual((error as NSError).domain, NSCocoaErrorDomain) - XCTAssertEqual((error as NSError).code, NSPropertyListReadCorruptError) - } - } - - func testParsingInvalidEvent() { - let messageWithNoEventType = """ - { - "event": { - "date": 1635932927012, - "error": { - "origin": "console" - } - } - } - """ - let messageWithNoEvent = """ - { - "eventType": "log" - } - """ - - XCTAssertThrowsError(try WebViewMessage(body: messageWithNoEventType)) { error in - XCTAssertEqual(error as? WebViewMessageError, .missingKey(key: "eventType")) - } - XCTAssertThrowsError(try WebViewMessage(body: messageWithNoEvent)) { error in - XCTAssertEqual(error as? WebViewMessageError, .missingKey(key: "event")) - } - } -} - -// MARK: - Convenience - -internal extension WebViewMessage { - var isLogEvent: Bool { - switch self { - case .log: return true - default: return false - } - } - - var isRUMEvent: Bool { - switch self { - case .rum: return true - default: return false - } - } - - var json: JSON { - switch self { - case let .log(json): return json - case let .rum(json): return json - } - } -} diff --git a/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift b/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift index 73ccf37944..b44a5b4fc0 100644 --- a/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift +++ b/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift @@ -134,49 +134,20 @@ class WebViewTrackingTests: XCTestCase { XCTAssertEqual(controller.messageHandlers.count, componentCount) } - func testItLogsInvalidWebMessages() throws { - let dd = DD.mockWith(logger: CoreLoggerMock()) - defer { dd.reset() } - - let controller = DDUserContentController() - WebViewTracking.enable( - tracking: controller, - hosts: ["datadoghq.com"], - hostsSanitizer: HostsSanitizerMock(), - logsSampleRate: 100, - in: PassthroughCoreMock() - ) - - let messageHandler = try XCTUnwrap(controller.messageHandlers.first?.handler) as? DDScriptMessageHandler - // non-string body is passed - messageHandler?.userContentController(controller, didReceive: MockScriptMessage(body: 123)) - messageHandler?.queue.sync { } - - XCTAssertEqual(dd.logger.errorLog?.message, "Encountered an error when receiving web view event") - XCTAssertEqual(dd.logger.errorLog?.error?.message, #"invalidMessage(description: "123")"#) - } - func testSendingWebEvents() throws { let logMessageExpectation = expectation(description: "Log message received") - let rumMessageExpectation = expectation(description: "RUM message received") let core = PassthroughCoreMock( messageReceiver: FeatureMessageReceiverMock { message in switch message { - case .baggage(let label, let baggage) where label == MessageEmitter.MessageKeys.browserLog: - let event = try? baggage.encode() as? JSON - XCTAssertEqual(event?["date"] as? Int64, 1_635_932_927_012) - XCTAssertEqual(event?["message"] as? String, "console error: error") - XCTAssertEqual(event?["status"] as? String, "error") - XCTAssertEqual(event?["view"] as? [String: String], ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"]) - XCTAssertEqual(event?["error"] as? [String: String], ["origin": "console"]) - XCTAssertEqual(event?["session_id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") + case let .webview(.log(event)): + let matcher = JSONObjectMatcher(object: event) + XCTAssertEqual(try? matcher.value("date"), 1_635_932_927_012) + XCTAssertEqual(try? matcher.value("message"), "console error: error") + XCTAssertEqual(try? matcher.value("status"), "error") + XCTAssertEqual(try? matcher.value("view"), ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"]) + XCTAssertEqual(try? matcher.value("error"), ["origin": "console"]) + XCTAssertEqual(try? matcher.value("session_id"), "0110cab4-7471-480e-aa4e-7ce039ced355") logMessageExpectation.fulfill() - case .baggage(let label, let baggage) where label == MessageEmitter.MessageKeys.browserRUMEvent: - let event = try? baggage.encode() as? JSON - XCTAssertEqual((event?["view"] as? JSON)?["id"] as? String, "64308fd4-83f9-48cb-b3e1-1e91f6721230") - rumMessageExpectation.fulfill() - case .baggage(let label, let baggage): - XCTFail("Unexpected custom message received: label: \(label), baggage: \(baggage)") case .context: break default: @@ -217,8 +188,36 @@ class WebViewTrackingTests: XCTestCase { } """) messageHandler?.userContentController(controller, didReceive: webLogMessage) + waitForExpectations(timeout: 1) + } + + func testSendingWebRUMEvent() throws { + let rumMessageExpectation = expectation(description: "RUM message received") + let core = PassthroughCoreMock( + messageReceiver: FeatureMessageReceiverMock { message in + switch message { + case let .webview(.rum(event)): + let matcher = JSONObjectMatcher(object: event) + XCTAssertEqual(try? matcher.value("view.id"), "64308fd4-83f9-48cb-b3e1-1e91f6721230") + rumMessageExpectation.fulfill() + case .context: + break + default: + XCTFail("Unexpected message received: \(message)") + } + } + ) + + let controller = DDUserContentController() + WebViewTracking.enable( + tracking: controller, + hosts: ["datadoghq.com"], + hostsSanitizer: HostsSanitizerMock(), + logsSampleRate: 100, + in: core + ) - messageHandler?.queue.sync {} + let messageHandler = try XCTUnwrap(controller.messageHandlers.first?.handler) as? DDScriptMessageHandler let webRUMMessage = MockScriptMessage(body: """ { "eventType": "view", @@ -279,6 +278,50 @@ class WebViewTrackingTests: XCTestCase { messageHandler?.userContentController(controller, didReceive: webRUMMessage) waitForExpectations(timeout: 1) } + + func testSendingWebRecordEvent() throws { + let recordMessageExpectation = expectation(description: "Record message received") + let controller = DDUserContentController() + + let core = PassthroughCoreMock( + messageReceiver: FeatureMessageReceiverMock { message in + switch message { + case let .webview(.record(event, view)): + XCTAssertEqual(view.id, "64308fd4-83f9-48cb-b3e1-1e91f6721230") + let matcher = JSONObjectMatcher(object: event) + XCTAssertEqual(try? matcher.value("date"), 1_635_932_927_012) + XCTAssertEqual(try? matcher.value("slotId"), "\(controller.hash)") + recordMessageExpectation.fulfill() + case .context: + break + default: + XCTFail("Unexpected message received: \(message)") + } + } + ) + + WebViewTracking.enable( + tracking: controller, + hosts: ["datadoghq.com"], + hostsSanitizer: HostsSanitizerMock(), + logsSampleRate: 100, + in: core + ) + + let messageHandler = try XCTUnwrap(controller.messageHandlers.first?.handler) as? DDScriptMessageHandler + let webLogMessage = MockScriptMessage(body: """ + { + "eventType": "record", + "event": { + "date": 1635932927012 + }, + "view": { "id": "64308fd4-83f9-48cb-b3e1-1e91f6721230" } + } + """) + + messageHandler?.userContentController(controller, didReceive: webLogMessage) + waitForExpectations(timeout: 1) + } } #endif diff --git a/IntegrationTests/IntegrationScenarios/Scenarios/Core/StopCoreScenarioTests.swift b/IntegrationTests/IntegrationScenarios/Scenarios/Core/StopCoreScenarioTests.swift index a52b5e956e..8ce5a835dd 100644 --- a/IntegrationTests/IntegrationScenarios/Scenarios/Core/StopCoreScenarioTests.swift +++ b/IntegrationTests/IntegrationScenarios/Scenarios/Core/StopCoreScenarioTests.swift @@ -78,7 +78,7 @@ class StopCoreScenarioTests: IntegrationTests, LoggingCommonAsserts, TracingComm try assertLoggingDataWasCollected(by: loggingServerSession) try assertTracingDataWasCollected(by: tracingServerSession) - try assertFirstRUMSessionWasCollected(by: rumServerSession) + try assertRUMSessionWasCollected(by: rumServerSession) server.clearAllRequests() // Stop the core and replay the scenario @@ -100,7 +100,7 @@ class StopCoreScenarioTests: IntegrationTests, LoggingCommonAsserts, TracingComm try assertLoggingDataWasCollected(by: loggingServerSession) try assertTracingDataWasCollected(by: tracingServerSession) - try assertFirstRUMSessionWasCollected(by: rumServerSession) + try assertRUMSessionWasCollected(by: rumServerSession) } /// Plays following scenario for started application: @@ -148,10 +148,10 @@ class StopCoreScenarioTests: IntegrationTests, LoggingCommonAsserts, TracingComm XCTAssertEqual(try spanMatcher.operationName(), "test span") } - private func assertFirstRUMSessionWasCollected(by serverSession: ServerSession) throws { + private func assertRUMSessionWasCollected(by serverSession: ServerSession) throws { // Get RUM Sessions with expected number of View visits let recordedRequests = try serverSession.pullRecordedRequests(timeout: dataDeliveryTimeout) { requests in - try RUMSessionMatcher.singleSession(from: requests)?.views.count == 4 + try RUMSessionMatcher.singleSession(from: requests)?.actionEventMatchers.count == 8 } assertRUM(requests: recordedRequests) diff --git a/IntegrationTests/IntegrationScenarios/Scenarios/CrashReporting/CrashReportingWithLoggingScenarioTests.swift b/IntegrationTests/IntegrationScenarios/Scenarios/CrashReporting/CrashReportingWithLoggingScenarioTests.swift index 50d3b9c24e..83c2489434 100644 --- a/IntegrationTests/IntegrationScenarios/Scenarios/CrashReporting/CrashReportingWithLoggingScenarioTests.swift +++ b/IntegrationTests/IntegrationScenarios/Scenarios/CrashReporting/CrashReportingWithLoggingScenarioTests.swift @@ -76,6 +76,11 @@ class CrashReportingWithLoggingScenarioTests: IntegrationTests, LoggingCommonAss XCTFail("Unsupported architecture") #endif + crashLog.assertAttributes(equal: [ + "global-attribute": "string-a", + "global-attribute-2": 1_150 + ]) + crashLog.assertValue( forKeyPath: LogMatcher.JSONKey.errorStack, isTypeOf: String.self diff --git a/IntegrationTests/IntegrationScenarios/Scenarios/Logging/LoggingScenarioTests.swift b/IntegrationTests/IntegrationScenarios/Scenarios/Logging/LoggingScenarioTests.swift index a4d7e99bd4..f926668a9f 100644 --- a/IntegrationTests/IntegrationScenarios/Scenarios/Logging/LoggingScenarioTests.swift +++ b/IntegrationTests/IntegrationScenarios/Scenarios/Logging/LoggingScenarioTests.swift @@ -21,7 +21,7 @@ class LoggingScenarioTests: IntegrationTests, LoggingCommonAsserts { // Get expected number of `LogMatchers` let recordedRequests = try loggingServerSession.pullRecordedRequests(timeout: dataDeliveryTimeout) { requests in - try LogMatcher.from(requests: requests).count >= 6 + try LogMatcher.from(requests: requests).count >= 7 } let logMatchers = try LogMatcher.from(requests: recordedRequests) @@ -46,6 +46,16 @@ class LoggingScenarioTests: IntegrationTests, LoggingCommonAsserts { logMatchers[5].assertStatus(equals: "critical") logMatchers[5].assertMessage(equals: "critical message") + logMatchers[6].assertStatus(equals: "notice") + logMatchers[6].assertMessage(equals: "notice message with global") + + logMatchers[6].assertAttributes(equal: [ + "global-attribute-1": "global value", + "global-attribute-2": 1540 + // Don't check "attribute" because local attributes should override + ]) + + logMatchers.forEach { matcher in matcher.assertDate(matches: { Date().timeIntervalSince($0) < dataDeliveryTimeout * 2 }) matcher.assertService(equals: "ui-tests-service-name") diff --git a/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRCommonAsserts.swift b/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRCommonAsserts.swift index 8a2ebee98c..f63ec6918f 100644 --- a/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRCommonAsserts.swift +++ b/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRCommonAsserts.swift @@ -52,3 +52,12 @@ extension SRRequestMatcher { try SRRequestMatcher(body: request.httpBody, headers: request.httpHeaders) } } + +extension SRSegmentMatcher { + static func segmentsCount(from requests: [Request]) throws -> Int { + try SRRequestMatcher.from(requests: requests).reduce(0) { total, request in + let count = try request.blob { try JSONSerialization.jsonObject(with: $0, options: []) as? [Any] }?.count ?? 0 + return total + count + } + } +} diff --git a/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift b/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift index eb7fc03298..066b8abb20 100644 --- a/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift +++ b/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift @@ -24,6 +24,8 @@ class SRMultipleViewsRecordingScenarioTests: IntegrationTests, RUMCommonAsserts, /// These are **"minimum"** values, computed from a baseline run. Exact values may cause flakiness as number of SR records /// will highly depend on the performance of app, Simulator and CI. private struct Baseline { + /// Number of all SR segments to pass the test. + static let totalSegmentsCount = 15 /// Number of all SR records to pass the test. static let totalRecordsCount = 35 /// Number of all "full snapshot" records to pass the test. @@ -70,24 +72,24 @@ class SRMultipleViewsRecordingScenarioTests: IntegrationTests, RUMCommonAsserts, // Get RUM and SR raw requests from mock server: // - pull RUM data until the "end view" event is fetched - // - pull SR dat aright after - we know it is delivered faster than RUM so we don't need to await any longer - let rumSessionHasEndedCondition: ([Request]) throws -> Bool = { try RUMSessionMatcher.singleSession(from: $0)?.hasEnded() ?? false } - let rawRUMRequests = try rumEndpoint.pullRecordedRequests(timeout: dataDeliveryTimeout, until: rumSessionHasEndedCondition) - let rawSRRequests = try srEndpoint.getRecordedRequests() - + // - pull SR data until receiving expected count of segments + let rawRUMRequests = try rumEndpoint.pullRecordedRequests(timeout: dataDeliveryTimeout, until: { + try RUMSessionMatcher.singleSession(from: $0)?.hasEnded() ?? false + }) + + let rawSRRequests = try srEndpoint.pullRecordedRequests(timeout: dataDeliveryTimeout, until: { + try SRSegmentMatcher.segmentsCount(from: $0) == Baseline.totalSegmentsCount + }) + assertRUM(requests: rawRUMRequests) assertSR(requests: rawSRRequests) // Map raw requests into RUM Session and SR request matchers: let rumSession = try XCTUnwrap(RUMSessionMatcher.singleSession(from: rawRUMRequests)) let srRequests = try SRRequestMatcher.from(requests: rawSRRequests) - - // Read SR segments from SR requests (one request = one segment): - let segments = try srRequests.map { try SRSegmentMatcher.fromJSONData($0.segmentJSONData()) } - + XCTAssertFalse(rumSession.views.isEmpty, "There should be some RUM session") XCTAssertFalse(srRequests.isEmpty, "There should be some SR requests") - XCTAssertFalse(segments.isEmpty, "There should be some SR segments") sendCIAppLog(rumSession) // Validate if RUM session links to SR replay through `has_replay` flag in RUM events. @@ -95,30 +97,44 @@ class SRMultipleViewsRecordingScenarioTests: IntegrationTests, RUMCommonAsserts, // RUM events will not have `has_replay: true`. For that reason, we only do broad assertion on "most" events. let rumEventsWithReplay = try rumSession.allEvents.filter { try $0.sessionHasReplay() == true } XCTAssertGreaterThan(Double(rumEventsWithReplay.count) / Double(rumSession.allEvents.count), 0.5, "Most RUM events must have `has_replay` flag set to `true`") - - // Validate SR (multipart) requests. - for request in srRequests { - // - Each request must reference RUM session: - XCTAssertEqual(try request.applicationID(), rumSession.applicationID, "SR request must reference RUM application") - XCTAssertEqual(try request.sessionID(), rumSession.sessionID, "SR request must reference RUM session") - XCTAssertTrue(rumSession.containsView(with: try request.viewID()), "SR request must reference a known view ID from RUM session") - - // - Other, broad checks: - XCTAssertGreaterThan(Int(try request.recordsCount()) ?? 0, 0, "SR request must include some records") - XCTAssertGreaterThan(Int(try request.rawSegmentSize()) ?? 0, 0, "SR request must include non-empty segment information") - XCTAssertEqual(try request.source(), "ios") - } - - // Validate SR segments. - for segment in segments { - // - Each segment must reference RUM session: - XCTAssertEqual(try segment.value("application.id"), rumSession.applicationID, "Segment must be linked to RUM application") - XCTAssertEqual(try segment.value("session.id"), rumSession.sessionID, "Segment must be linked to RUM session") - XCTAssertTrue(rumSession.containsView(with: try segment.value("view.id")), "Segment must be linked to RUM view") - - // - Other, broad checks: - XCTAssertGreaterThan(try segment.value("records_count") as Int, 0, "Segment must include some records") - XCTAssertEqual(try segment.value("records_count") as Int, try segment.array("records").count, "Records count must be consistent") + + // Read and validate SR segments from SR requests. + let segments: [SRSegmentMatcher] = try srRequests.reduce([]) { segments, request in + + // Read the metadata from request blob file + let blob = try request.blob { data in + // Resource request will have non-array blob file + let array = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] + let matcher = JSONArrrayMatcher(array: array ?? []) + return try matcher.values().map(SRSegmentMatcher.init(object:)) + } + + return try segments + blob.enumerated().map { index, metadata in + // - Each request must reference RUM session: + XCTAssertEqual(try metadata.applicationID(), rumSession.applicationID, "SR request must reference RUM application") + XCTAssertEqual(try metadata.sessionID(), rumSession.sessionID, "SR request must reference RUM session") + XCTAssertTrue(rumSession.containsView(with: try metadata.viewID()), "SR request must reference a known view ID from RUM session") + + let segment = try request.segment(at: index) + + // - Each segment must reference RUM session: + try XCTAssertTrue(rumSession.containsView(with: segment.viewID()), "Segment must be linked to RUM view") + try XCTAssertEqual(segment.applicationID(), metadata.applicationID(), "Segment must be linked to RUM application") + try XCTAssertEqual(segment.sessionID(), metadata.sessionID(), "Segment must be linked to RUM session") + try XCTAssertEqual(segment.viewID(), metadata.viewID(), "Segment must be linked to RUM view") + try XCTAssertEqual(segment.hasFullSnapshot(), metadata.hasFullSnapshot()) + try XCTAssertEqual(segment.recordsCount(), metadata.recordsCount()) + try XCTAssertEqual(segment.start(), metadata.start()) + try XCTAssertEqual(segment.end(), metadata.end()) + try XCTAssertEqual(segment.source(), metadata.source()) + + // - Other, broad checks: + XCTAssertThrowsError(try metadata.records()) + try XCTAssertGreaterThan(metadata.rawSegmentSize(), metadata.compressedSegmentSize()) + try XCTAssertGreaterThan(segment.recordsCount(), 0, "Segment must include some records") + try XCTAssertEqual(segment.recordsCount(), segment.records().count, "Records count must be consistent") + return segment + } } // Validate SR records. diff --git a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj index 69929664de..9d2c26b486 100644 --- a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj +++ b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj @@ -825,6 +825,7 @@ 61441BFF24616DE9003D8BB8 /* Frameworks */, 61441C0024616DE9003D8BB8 /* Resources */, D240687A27CF982B00C04F44 /* Embed Frameworks */, + 5DAD8F2510CB09A5BA7723B9 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -846,6 +847,7 @@ 61441C2624616F1D003D8BB8 /* Sources */, 61441C2724616F1D003D8BB8 /* Frameworks */, 61441C2824616F1D003D8BB8 /* Resources */, + 7F6DA13A8C70117DE98BAFD7 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -959,6 +961,40 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 5DAD8F2510CB09A5BA7723B9 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 7F6DA13A8C70117DE98BAFD7 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS-IntegrationScenarios/Pods-Runner iOS-IntegrationScenarios-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS-IntegrationScenarios/Pods-Runner iOS-IntegrationScenarios-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner iOS-IntegrationScenarios/Pods-Runner iOS-IntegrationScenarios-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 8CA44A44BB7E45A38F2BEC24 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/IntegrationTests/Runner/Scenarios/CrashReporting/CrashReporting/CrashReportingViewController.swift b/IntegrationTests/Runner/Scenarios/CrashReporting/CrashReporting/CrashReportingViewController.swift index 800595b2c4..c3c8131483 100644 --- a/IntegrationTests/Runner/Scenarios/CrashReporting/CrashReporting/CrashReportingViewController.swift +++ b/IntegrationTests/Runner/Scenarios/CrashReporting/CrashReporting/CrashReportingViewController.swift @@ -6,6 +6,7 @@ import UIKit import DatadogCore +import DatadogLogs internal class CrashReportingViewController: UIViewController { @IBOutlet weak var sendingCrashReportLabel: UILabel! @@ -16,6 +17,9 @@ internal class CrashReportingViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + Logs.addAttribute(forKey: "global-attribute", value: "string-a") + Logs.addAttribute(forKey: "global-attribute-2", value: 1_150) + let testScenario = (appConfiguration.testScenario as! CrashReportingBaseScenario) sendingCrashReportLabel.isHidden = !testScenario.hadPendingCrashReportDataOnStartup diff --git a/IntegrationTests/Runner/Scenarios/Logging/ManualInstrumentation/SendLogsFixtureViewController.swift b/IntegrationTests/Runner/Scenarios/Logging/ManualInstrumentation/SendLogsFixtureViewController.swift index a5c215ff03..8c57e9b899 100644 --- a/IntegrationTests/Runner/Scenarios/Logging/ManualInstrumentation/SendLogsFixtureViewController.swift +++ b/IntegrationTests/Runner/Scenarios/Logging/ManualInstrumentation/SendLogsFixtureViewController.swift @@ -5,6 +5,7 @@ */ import UIKit +import DatadogLogs internal class SendLogsFixtureViewController: UIViewController { override func viewDidLoad() { @@ -24,5 +25,11 @@ internal class SendLogsFixtureViewController: UIViewController { logger?.warn("warn message", attributes: ["attribute": "value"]) logger?.error("error message", attributes: ["attribute": "value"]) logger?.critical("critical message", attributes: ["attribute": "value"]) + + Logs.addAttribute(forKey: "global-attribute-1", value: "global value") + Logs.addAttribute(forKey: "global-attribute-2", value: 1_540) + Logs.addAttribute(forKey: "attribute", value: 20) + + logger?.notice("notice message with global", attributes: ["attribute": "value"]) } } diff --git a/SUPPORTED-VERSIONS.md b/SUPPORTED-VERSIONS.md index 0e4acb16d1..69d08a31e2 100644 --- a/SUPPORTED-VERSIONS.md +++ b/SUPPORTED-VERSIONS.md @@ -5,10 +5,14 @@ | **iOS** | ✅ | `11+` | | **tvOS** | ✅ | `11+` | | **iPadOS** | ✅ | `11+` | +| **VisionOS** | ⚠️ | `1.1+` | | **watchOS**| ❌ | `n/a` | | **macOS** | ❌ | `n/a` | | **Linux** | ❌ | `n/a` | +## VisionOS +VisionOS is not officially supported by Datadog SDK. Some features may not be fully functional. + ## Xcode SDK is built using the most recent version of Xcode, but we make sure that it's backward compatible with the [lowest supported Xcode version for AppStore submission](https://developer.apple.com/news/?id=jd9wcyov). diff --git a/TestUtilities.podspec b/TestUtilities.podspec index 3b96736e17..07d45d040b 100644 --- a/TestUtilities.podspec +++ b/TestUtilities.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "TestUtilities" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "Datadog Testing Utilities. This module is for internal testing and should not be published." s.homepage = "https://www.datadoghq.com" diff --git a/TestUtilities/Helpers/UIKitHelpers.swift b/TestUtilities/Helpers/UIKitHelpers.swift new file mode 100644 index 0000000000..b528abecc4 --- /dev/null +++ b/TestUtilities/Helpers/UIKitHelpers.swift @@ -0,0 +1,16 @@ +/* + * 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 UIKit + +/// The library name for UIKit framework as it will appear in unsymbolicated stack trace. +/// +/// This name may differ between OS runtimes. +public var uiKitLibraryName: String { + let uiKitBundleURL = Bundle(for: UIViewController.self).bundleURL + let uiKitFrameworkName = uiKitBundleURL.lastPathComponent // 'UIKitCore.framework' on iOS 12+; 'UIKit.framework' on iOS 11 + return String(uiKitFrameworkName.dropLast(".framework".count)) +} diff --git a/TestUtilities/Mocks/BacktraceReportingMocks.swift b/TestUtilities/Mocks/BacktraceReportingMocks.swift new file mode 100644 index 0000000000..da7d328bd9 --- /dev/null +++ b/TestUtilities/Mocks/BacktraceReportingMocks.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 Foundation +import DatadogInternal + +public struct BacktraceReporterMock: BacktraceReporting { + public var backtrace: BacktraceReport? + + public init(backtrace: BacktraceReport? = .mockAny()) { + self.backtrace = backtrace + } + + public func generateBacktrace(threadID: ThreadID) -> BacktraceReport? { + return backtrace + } +} diff --git a/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift b/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift index b09814f17b..6ded3cfbf6 100644 --- a/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift +++ b/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift @@ -51,10 +51,6 @@ open class PassthroughCoreMock: DatadogCoreProtocol, FeatureScope { /// is executed with `bypassConsent` parameter set to `true`. public var bypassConsentExpectation: XCTestExpectation? - /// Test expectation that will be fullfilled when the `eventWriteContext` closure - /// is executed with `forceNewBatch` parameter set to `true`. - public var forceNewBatchExpectation: XCTestExpectation? - /// Creates a Passthrough core mock. /// /// - Parameters: @@ -63,20 +59,16 @@ open class PassthroughCoreMock: DatadogCoreProtocol, FeatureScope { /// is invoked. /// - bypassConsentExpectation: The test exepection to fullfill when `eventWriteContext` /// is invoked with `bypassConsent` parameter set to `true`. - /// - forceNewBatchExpectation: The test exepection to fullfill when `eventWriteContext` - /// is invoked with `forceNewBatch` parameter set to `true`. public required init( context: DatadogContext = .mockAny(), expectation: XCTestExpectation? = nil, bypassConsentExpectation: XCTestExpectation? = nil, - forceNewBatchExpectation: XCTestExpectation? = nil, messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver() ) { self.context = context self.expectation = expectation self.bypassConsentExpectation = bypassConsentExpectation - self.forceNewBatchExpectation = forceNewBatchExpectation self.messageReceiver = messageReceiver messageReceiver.receive(message: .context(context), from: self) @@ -112,15 +104,11 @@ open class PassthroughCoreMock: DatadogCoreProtocol, FeatureScope { /// Execute `block` with the current context and a `writer` to record events. /// /// - Parameter block: The block to execute. - public func eventWriteContext(bypassConsent: Bool, forceNewBatch: Bool, _ block: @escaping (DatadogContext, Writer) -> Void) { + public func eventWriteContext(bypassConsent: Bool, _ block: @escaping (DatadogContext, Writer) -> Void) { if bypassConsent { bypassConsentExpectation?.fulfill() } - if forceNewBatch { - forceNewBatchExpectation?.fulfill() - } - block(context, writer) expectation?.fulfill() } diff --git a/TestUtilities/Mocks/CoreMocks/SingleFeatureCoreMock.swift b/TestUtilities/Mocks/CoreMocks/SingleFeatureCoreMock.swift index 28c2b723dc..68192ead8d 100644 --- a/TestUtilities/Mocks/CoreMocks/SingleFeatureCoreMock.swift +++ b/TestUtilities/Mocks/CoreMocks/SingleFeatureCoreMock.swift @@ -40,14 +40,11 @@ public final class SingleFeatureCoreMock: PassthroughCoreMock where Fea /// is invoked. /// - bypassConsentExpectation: The test exepection to fullfill when `eventWriteContext` /// is invoked with `bypassConsent` parameter set to `true`. - /// - forceNewBatchExpectation: The test exepection to fullfill when `eventWriteContext` - /// is invoked with `forceNewBatch` parameter set to `true`. public required init( context: DatadogContext = .mockAny(), feature: Feature? = nil, expectation: XCTestExpectation? = nil, bypassConsentExpectation: XCTestExpectation? = nil, - forceNewBatchExpectation: XCTestExpectation? = nil, messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver() ) { self.feature = feature @@ -56,7 +53,6 @@ public final class SingleFeatureCoreMock: PassthroughCoreMock where Fea context: context, expectation: expectation, bypassConsentExpectation: bypassConsentExpectation, - forceNewBatchExpectation: forceNewBatchExpectation, messageReceiver: messageReceiver ) } @@ -69,13 +65,10 @@ public final class SingleFeatureCoreMock: PassthroughCoreMock where Fea /// is invoked. /// - bypassConsentExpectation: The test exepection to fullfill when `eventWriteContext` /// is invoked with `bypassConsent` parameter set to `true`. - /// - forceNewBatchExpectation: The test exepection to fullfill when `eventWriteContext` - /// is invoked with `forceNewBatch` parameter set to `true`. public required init( context: DatadogContext = .mockAny(), expectation: XCTestExpectation? = nil, bypassConsentExpectation: XCTestExpectation? = nil, - forceNewBatchExpectation: XCTestExpectation? = nil, messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver() ) { self.feature = nil @@ -84,7 +77,6 @@ public final class SingleFeatureCoreMock: PassthroughCoreMock where Fea context: context, expectation: expectation, bypassConsentExpectation: bypassConsentExpectation, - forceNewBatchExpectation: forceNewBatchExpectation, messageReceiver: messageReceiver ) } diff --git a/TestUtilities/Mocks/DatadogContextMock.swift b/TestUtilities/Mocks/DatadogContextMock.swift index f70fd5925c..a31d66566e 100644 --- a/TestUtilities/Mocks/DatadogContextMock.swift +++ b/TestUtilities/Mocks/DatadogContextMock.swift @@ -26,6 +26,7 @@ extension DatadogContext: AnyMockable { applicationName: String = .mockAny(), applicationBundleIdentifier: String = .mockAny(), sdkInitDate: Date = Date(), + nativeSourceOverride: String? = nil, device: DeviceInfo = .mockAny(), userInfo: UserInfo = .mockAny(), trackingConsent: TrackingConsent = .pending, @@ -54,6 +55,7 @@ extension DatadogContext: AnyMockable { applicationBundleIdentifier: applicationBundleIdentifier, sdkInitDate: sdkInitDate, device: device, + nativeSourceOverride: nativeSourceOverride, userInfo: userInfo, trackingConsent: trackingConsent, launchTime: launchTime, @@ -323,7 +325,11 @@ extension TrackingConsent { extension String { public static func mockAnySource() -> String { - return ["ios", "android", "browser", "ios", "react-native", "flutter"].randomElement()! + return ["ios", "android", "browser", "ios", "react-native", "flutter", "unity"].randomElement()! + } + + public static func mockAnySourceType() -> String { + return ["ios", "android", "browser", "react-native", "flutter", "roku", "ndk", "ios+il2cpp", "ndk+il2cpp"].randomElement()! } } diff --git a/TestUtilities/Mocks/FeatureMessageMocks.swift b/TestUtilities/Mocks/FeatureMessageMocks.swift index 6617a50822..9fb5074462 100644 --- a/TestUtilities/Mocks/FeatureMessageMocks.swift +++ b/TestUtilities/Mocks/FeatureMessageMocks.swift @@ -13,6 +13,11 @@ public extension Array where Element == FeatureMessage { return compactMap({ $0.asBaggage }).filter({ $0.key == key }).first?.baggage } + /// Unpacks the first "baggage message" with given key in this array. + var firstWebViewMessage: WebViewMessage? { + return lazy.compactMap { $0.asWebViewMessage }.first + } + /// Unpacks the first "context message" in this array. func firstContext() -> DatadogContext? { return compactMap({ $0.asContext }).first @@ -33,6 +38,14 @@ public extension FeatureMessage { return (key: key, baggage: baggage) } + /// Extracts baggage attributes from feature message. + var asWebViewMessage: WebViewMessage? { + guard case let .webview(message) = self else { + return nil + } + return message + } + /// Extracts context from feature message. var asContext: DatadogContext? { guard case let .context(context) = self else { diff --git a/TestUtilities/Mocks/FeatureModels/CrashReporting/BacktraceReportMocks.swift b/TestUtilities/Mocks/FeatureModels/CrashReporting/BacktraceReportMocks.swift new file mode 100644 index 0000000000..a3c9ddfcf4 --- /dev/null +++ b/TestUtilities/Mocks/FeatureModels/CrashReporting/BacktraceReportMocks.swift @@ -0,0 +1,36 @@ +/* + * 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 + +extension BacktraceReport: AnyMockable, RandomMockable { + public static func mockAny() -> BacktraceReport { + return .mockWith() + } + + public static func mockRandom() -> BacktraceReport { + return BacktraceReport( + stack: .mockRandom(), + threads: .mockRandom(), + binaryImages: .mockRandom(), + wasTruncated: .mockRandom() + ) + } + + public static func mockWith( + stack: String = .mockAny(), + threads: [DDThread] = .mockAny(), + binaryImages: [BinaryImage] = .mockAny(), + wasTruncated: Bool = .mockAny() + ) -> BacktraceReport { + return BacktraceReport( + stack: stack, + threads: threads, + binaryImages: binaryImages, + wasTruncated: wasTruncated + ) + } +} diff --git a/TestUtilities/Mocks/FeatureModels/CrashReporting/BinaryImageMocks.swift b/TestUtilities/Mocks/FeatureModels/CrashReporting/BinaryImageMocks.swift new file mode 100644 index 0000000000..06fd7b8f7c --- /dev/null +++ b/TestUtilities/Mocks/FeatureModels/CrashReporting/BinaryImageMocks.swift @@ -0,0 +1,42 @@ +/* + * 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 + +extension BinaryImage: AnyMockable, RandomMockable { + public static func mockAny() -> BinaryImage { + return .mockWith() + } + + public static func mockRandom() -> BinaryImage { + return BinaryImage( + libraryName: .mockRandom(), + uuid: .mockRandom(), + architecture: .mockRandom(), + isSystemLibrary: .mockRandom(), + loadAddress: .mockRandom(), + maxAddress: .mockRandom() + ) + } + + public static func mockWith( + libraryName: String = .mockAny(), + uuid: String = .mockAny(), + architecture: String = .mockAny(), + isSystemLibrary: Bool = .mockAny(), + loadAddress: String = .mockAny(), + maxAddress: String = .mockAny() + ) -> BinaryImage { + return BinaryImage( + libraryName: libraryName, + uuid: uuid, + architecture: architecture, + isSystemLibrary: isSystemLibrary, + loadAddress: loadAddress, + maxAddress: maxAddress + ) + } +} diff --git a/TestUtilities/Mocks/FeatureModels/CrashReporting/DDCrashReportMocks.swift b/TestUtilities/Mocks/FeatureModels/CrashReporting/DDCrashReportMocks.swift new file mode 100644 index 0000000000..420e09c9ae --- /dev/null +++ b/TestUtilities/Mocks/FeatureModels/CrashReporting/DDCrashReportMocks.swift @@ -0,0 +1,90 @@ +/* + * 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 +import DatadogInternal + +extension DDCrashReport: AnyMockable, RandomMockable { + public static func mockAny() -> DDCrashReport { + return .mockWith() + } + + public static func mockRandom() -> DDCrashReport { + return DDCrashReport( + date: .mockRandom(), + type: .mockRandom(), + message: .mockRandom(), + stack: .mockRandom(), + threads: .mockRandom(), + binaryImages: .mockRandom(), + meta: .mockRandom(), + wasTruncated: .mockRandom(), + context: .mockRandom() + ) + } + + public static func mockWith( + date: Date? = .mockAny(), + type: String = .mockAny(), + message: String = .mockAny(), + stack: String = .mockAny(), + threads: [DDThread] = .mockAny(), + binaryImages: [BinaryImage] = .mockAny(), + meta: Meta = .mockAny(), + wasTruncated: Bool = .mockAny(), + context: Data? = .mockAny() + ) -> DDCrashReport { + return DDCrashReport( + date: date, + type: type, + message: message, + stack: stack, + threads: threads, + binaryImages: binaryImages, + meta: meta, + wasTruncated: wasTruncated, + context: context + ) + } +} + +extension DDCrashReport.Meta: AnyMockable, RandomMockable { + public static func mockAny() -> DDCrashReport.Meta { + return .mockWith() + } + + public static func mockRandom() -> DDCrashReport.Meta { + return DDCrashReport.Meta( + incidentIdentifier: .mockRandom(), + process: .mockRandom(), + parentProcess: .mockRandom(), + path: .mockRandom(), + codeType: .mockRandom(), + exceptionType: .mockRandom(), + exceptionCodes: .mockRandom() + ) + } + + public static func mockWith( + incidentIdentifier: String? = .mockAny(), + process: String? = .mockAny(), + parentProcess: String? = .mockAny(), + path: String? = .mockAny(), + codeType: String? = .mockAny(), + exceptionType: String? = .mockAny(), + exceptionCodes: String? = .mockAny() + ) -> DDCrashReport.Meta { + return DDCrashReport.Meta( + incidentIdentifier: incidentIdentifier, + process: process, + parentProcess: parentProcess, + path: path, + codeType: codeType, + exceptionType: exceptionType, + exceptionCodes: exceptionCodes + ) + } +} diff --git a/TestUtilities/Mocks/FeatureModels/CrashReporting/DDThreadMocks.swift b/TestUtilities/Mocks/FeatureModels/CrashReporting/DDThreadMocks.swift new file mode 100644 index 0000000000..4782ef08a2 --- /dev/null +++ b/TestUtilities/Mocks/FeatureModels/CrashReporting/DDThreadMocks.swift @@ -0,0 +1,36 @@ +/* + * 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 + +extension DDThread: AnyMockable, RandomMockable { + public static func mockAny() -> DDThread { + return .mockWith() + } + + public static func mockRandom() -> DDThread { + return DDThread( + name: .mockRandom(), + stack: .mockRandom(), + crashed: .mockRandom(), + state: .mockRandom() + ) + } + + public static func mockWith( + name: String = .mockAny(), + stack: String = .mockAny(), + crashed: Bool = .mockAny(), + state: String? = .mockAny() + ) -> DDThread { + return DDThread( + name: name, + stack: stack, + crashed: crashed, + state: state + ) + } +} diff --git a/dependency-manager-tests/carthage/App/PrivacyInfo.xcprivacy b/dependency-manager-tests/carthage/App/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..0c67376eba --- /dev/null +++ b/dependency-manager-tests/carthage/App/PrivacyInfo.xcprivacy @@ -0,0 +1,5 @@ + + + + + diff --git a/dependency-manager-tests/carthage/App/ViewController.swift b/dependency-manager-tests/carthage/App/ViewController.swift index bbc2992938..c84b0fe3fd 100644 --- a/dependency-manager-tests/carthage/App/ViewController.swift +++ b/dependency-manager-tests/carthage/App/ViewController.swift @@ -41,13 +41,6 @@ internal class ViewController: UIViewController { RUM.enable(with: .init(applicationID: "app-id")) RUMMonitor.shared().startView(viewController: self) - // DDURLSessionDelegate APIs must be visible: - _ = DDURLSessionDelegate() - _ = DatadogURLSessionDelegate() - class CustomDelegate: NSObject, __URLSessionDelegateProviding { - var ddURLSessionDelegate: DatadogURLSessionDelegate { DatadogURLSessionDelegate() } - } - // Trace APIs must be visible: Trace.enable() diff --git a/dependency-manager-tests/carthage/CTProject.xcodeproj/project.pbxproj b/dependency-manager-tests/carthage/CTProject.xcodeproj/project.pbxproj index 8f31380c2a..137e0053b9 100644 --- a/dependency-manager-tests/carthage/CTProject.xcodeproj/project.pbxproj +++ b/dependency-manager-tests/carthage/CTProject.xcodeproj/project.pbxproj @@ -32,6 +32,8 @@ D20D6FE929F6C2ED00D2886E /* DatadogTrace.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D2966C2329CA1C5300FC6B3C /* DatadogTrace.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D20D6FEA29F6C2F200D2886E /* DatadogRUM.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D20D6FE329F6C2D600D2886E /* DatadogRUM.xcframework */; }; D20D6FEB29F6C2F200D2886E /* DatadogRUM.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D20D6FE329F6C2D600D2886E /* DatadogRUM.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D22919292B76525C00C38A18 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D22919282B76525C00C38A18 /* PrivacyInfo.xcprivacy */; }; + D229192A2B76525C00C38A18 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D22919282B76525C00C38A18 /* PrivacyInfo.xcprivacy */; }; D2675BF32A019CF500190669 /* DatadogCrashReporting.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 615D9E6A2604B5B1006DC6D1 /* DatadogCrashReporting.xcframework */; }; D2675BF42A019CF500190669 /* DatadogCrashReporting.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 615D9E6A2604B5B1006DC6D1 /* DatadogCrashReporting.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D2675BF52A019CF600190669 /* DatadogInternal.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D240FC8B2995183D00D9F099 /* DatadogInternal.xcframework */; }; @@ -155,6 +157,7 @@ 9E9D5E8625F90FC6002F12A0 /* DatadogObjc.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogObjc.xcframework; path = Carthage/Build/DatadogObjc.xcframework; sourceTree = ""; }; 9E9D5E8725F90FC6002F12A0 /* DatadogCore.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogCore.xcframework; path = Carthage/Build/DatadogCore.xcframework; sourceTree = ""; }; D20D6FE329F6C2D600D2886E /* DatadogRUM.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogRUM.xcframework; path = Carthage/Build/DatadogRUM.xcframework; sourceTree = ""; }; + D22919282B76525C00C38A18 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; D240FC8B2995183D00D9F099 /* DatadogInternal.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogInternal.xcframework; path = Carthage/Build/DatadogInternal.xcframework; sourceTree = ""; }; D26F741729ACC61E00D25622 /* DatadogLogs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogLogs.xcframework; path = Carthage/Build/DatadogLogs.xcframework; sourceTree = ""; }; D290BA2D27CD09740019936D /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -271,6 +274,7 @@ 61C3641A243752A500C4D4E6 /* SceneDelegate.swift */, 61C3641C243752A500C4D4E6 /* ViewController.swift */, 61C36426243752A600C4D4E6 /* Info.plist */, + D22919282B76525C00C38A18 /* PrivacyInfo.xcprivacy */, ); path = App; sourceTree = ""; @@ -481,6 +485,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D22919292B76525C00C38A18 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -502,6 +507,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D229192A2B76525C00C38A18 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/dependency-manager-tests/cocoapods/App/PrivacyInfo.xcprivacy b/dependency-manager-tests/cocoapods/App/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..0c67376eba --- /dev/null +++ b/dependency-manager-tests/cocoapods/App/PrivacyInfo.xcprivacy @@ -0,0 +1,5 @@ + + + + + diff --git a/dependency-manager-tests/cocoapods/App/ViewController.swift b/dependency-manager-tests/cocoapods/App/ViewController.swift index 283d3d7be3..e14ceb32ca 100644 --- a/dependency-manager-tests/cocoapods/App/ViewController.swift +++ b/dependency-manager-tests/cocoapods/App/ViewController.swift @@ -41,13 +41,6 @@ internal class ViewController: UIViewController { RUM.enable(with: .init(applicationID: "app-id")) RUMMonitor.shared().startView(viewController: self) - // DDURLSessionDelegate APIs must be visible: - _ = DDURLSessionDelegate() - _ = DatadogURLSessionDelegate() - class CustomDelegate: NSObject, __URLSessionDelegateProviding { - var ddURLSessionDelegate: DatadogURLSessionDelegate { DatadogURLSessionDelegate() } - } - // Trace APIs must be visible: Trace.enable() diff --git a/dependency-manager-tests/spm/App/PrivacyInfo.xcprivacy b/dependency-manager-tests/spm/App/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..0c67376eba --- /dev/null +++ b/dependency-manager-tests/spm/App/PrivacyInfo.xcprivacy @@ -0,0 +1,5 @@ + + + + + diff --git a/dependency-manager-tests/spm/App/ViewController.swift b/dependency-manager-tests/spm/App/ViewController.swift index 1c092f9e6e..a1b1e258c4 100644 --- a/dependency-manager-tests/spm/App/ViewController.swift +++ b/dependency-manager-tests/spm/App/ViewController.swift @@ -39,13 +39,6 @@ internal class ViewController: UIViewController { RUM.enable(with: .init(applicationID: "app-id")) RUMMonitor.shared().startView(viewController: self) - // DDURLSessionDelegate APIs must be visible: - _ = DDURLSessionDelegate() - _ = DatadogURLSessionDelegate() - class CustomDelegate: NSObject, __URLSessionDelegateProviding { - var ddURLSessionDelegate: DatadogURLSessionDelegate { DatadogURLSessionDelegate() } - } - // Trace APIs must be visible: Trace.enable() diff --git a/dependency-manager-tests/spm/SPMProject.xcodeproj.src/project.pbxproj b/dependency-manager-tests/spm/SPMProject.xcodeproj.src/project.pbxproj index 6f010b51f8..f4720f51a7 100644 --- a/dependency-manager-tests/spm/SPMProject.xcodeproj.src/project.pbxproj +++ b/dependency-manager-tests/spm/SPMProject.xcodeproj.src/project.pbxproj @@ -3,20 +3,19 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 3CB7A04C29F6ABDA007A27ED /* DatadogWebViewTracking in Frameworks */ = {isa = PBXBuildFile; productRef = 3CB7A04B29F6ABDA007A27ED /* DatadogWebViewTracking */; }; - 61253AF62A6FEAEF00A15E18 /* DatadogSessionReplay in Frameworks */ = {isa = PBXBuildFile; productRef = 61253AF52A6FEAEF00A15E18 /* DatadogSessionReplay */; }; - 612C055B2A93C4DF00A1DA8C /* DatadogObjc in Frameworks */ = {isa = PBXBuildFile; productRef = 612C055A2A93C4DF00A1DA8C /* DatadogObjc */; }; - 612C055D2A93C51300A1DA8C /* DatadogObjc in Frameworks */ = {isa = PBXBuildFile; productRef = 612C055C2A93C51300A1DA8C /* DatadogObjc */; }; 61C363DA24374D5F00C4D4E6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C363D924374D5F00C4D4E6 /* AppDelegate.swift */; }; 61C363DC24374D5F00C4D4E6 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C363DB24374D5F00C4D4E6 /* SceneDelegate.swift */; }; 61C363DE24374D5F00C4D4E6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C363DD24374D5F00C4D4E6 /* ViewController.swift */; }; 61C363F124374D6100C4D4E6 /* AppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C363F024374D6100C4D4E6 /* AppTests.swift */; }; 61C363FC24374D6100C4D4E6 /* AppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C363FB24374D6100C4D4E6 /* AppUITests.swift */; }; 9E73374026B0123500917C24 /* DatadogCrashReporting in Frameworks */ = {isa = PBXBuildFile; productRef = 9E73373F26B0123500917C24 /* DatadogCrashReporting */; }; + D22919312B76589B00C38A18 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D22919302B76589B00C38A18 /* PrivacyInfo.xcprivacy */; }; + D22919322B76589B00C38A18 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D22919302B76589B00C38A18 /* PrivacyInfo.xcprivacy */; }; D2344E1F29ACDE61007F5BD2 /* DatadogLogs in Frameworks */ = {isa = PBXBuildFile; productRef = D2344E1E29ACDE61007F5BD2 /* DatadogLogs */; }; D2344E2129ACDE68007F5BD2 /* DatadogLogs in Frameworks */ = {isa = PBXBuildFile; productRef = D2344E2029ACDE68007F5BD2 /* DatadogLogs */; }; D23BF5F327CCCC3300BB4CCD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C363DD24374D5F00C4D4E6 /* ViewController.swift */; }; @@ -25,6 +24,9 @@ D23BF5F727CCCC3300BB4CCD /* DatadogCrashReporting in Frameworks */ = {isa = PBXBuildFile; productRef = D23BF5F027CCCC3300BB4CCD /* DatadogCrashReporting */; }; D23BF60427CCCCF800BB4CCD /* AppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C363FB24374D6100C4D4E6 /* AppUITests.swift */; }; D23BF61027CCCD5700BB4CCD /* AppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C363F024374D6100C4D4E6 /* AppTests.swift */; }; + D266C6872B766095008866F9 /* DatadogObjc in Frameworks */ = {isa = PBXBuildFile; productRef = D266C6862B766095008866F9 /* DatadogObjc */; }; + D266C6892B766095008866F9 /* DatadogSessionReplay in Frameworks */ = {isa = PBXBuildFile; productRef = D266C6882B766095008866F9 /* DatadogSessionReplay */; }; + D266C68B2B76609F008866F9 /* DatadogObjc in Frameworks */ = {isa = PBXBuildFile; productRef = D266C68A2B76609F008866F9 /* DatadogObjc */; }; D26A32D72A4C892A00903514 /* DatadogCore in Frameworks */ = {isa = PBXBuildFile; productRef = D26A32D62A4C892A00903514 /* DatadogCore */; }; D26A32D92A4C893300903514 /* DatadogCore in Frameworks */ = {isa = PBXBuildFile; productRef = D26A32D82A4C893300903514 /* DatadogCore */; }; D2966C2E29CA1F2D00FC6B3C /* DatadogTrace in Frameworks */ = {isa = PBXBuildFile; productRef = D2966C2D29CA1F2D00FC6B3C /* DatadogTrace */; }; @@ -101,6 +103,7 @@ 61C363FD24374D6100C4D4E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61CE5FD52461D3C2005EA621 /* Datadog.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.xcconfig; sourceTree = ""; }; 61CE5FD62461D3C2005EA621 /* Datadog.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.local.xcconfig; sourceTree = ""; }; + D22919302B76589B00C38A18 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; D23BF5FE27CCCC3300BB4CCD /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; D23BF60A27CCCCF800BB4CCD /* App tvOS UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "App tvOS UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D23BF61627CCCD5700BB4CCD /* App tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "App tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -112,11 +115,11 @@ buildActionMask = 2147483647; files = ( D26A32D72A4C892A00903514 /* DatadogCore in Frameworks */, + D266C6892B766095008866F9 /* DatadogSessionReplay in Frameworks */, D2966C2E29CA1F2D00FC6B3C /* DatadogTrace in Frameworks */, + D266C6872B766095008866F9 /* DatadogObjc in Frameworks */, D2344E1F29ACDE61007F5BD2 /* DatadogLogs in Frameworks */, - 61253AF62A6FEAEF00A15E18 /* DatadogSessionReplay in Frameworks */, D2CE76BC29F6B9EE00D79713 /* DatadogRUM in Frameworks */, - 612C055B2A93C4DF00A1DA8C /* DatadogObjc in Frameworks */, 3CB7A04C29F6ABDA007A27ED /* DatadogWebViewTracking in Frameworks */, 9E73374026B0123500917C24 /* DatadogCrashReporting in Frameworks */, ); @@ -141,7 +144,7 @@ buildActionMask = 2147483647; files = ( D2966C3029CA1F3300FC6B3C /* DatadogTrace in Frameworks */, - 612C055D2A93C51300A1DA8C /* DatadogObjc in Frameworks */, + D266C68B2B76609F008866F9 /* DatadogObjc in Frameworks */, D2344E2129ACDE68007F5BD2 /* DatadogLogs in Frameworks */, D2CE76BA29F6B9E300D79713 /* DatadogRUM in Frameworks */, D26A32D92A4C893300903514 /* DatadogCore in Frameworks */, @@ -198,6 +201,7 @@ 61C363DB24374D5F00C4D4E6 /* SceneDelegate.swift */, 61C363DD24374D5F00C4D4E6 /* ViewController.swift */, 61C363E724374D6000C4D4E6 /* Info.plist */, + D22919302B76589B00C38A18 /* PrivacyInfo.xcprivacy */, ); path = App; sourceTree = ""; @@ -262,8 +266,8 @@ D2CE76BB29F6B9EE00D79713 /* DatadogRUM */, 3CB7A04B29F6ABDA007A27ED /* DatadogWebViewTracking */, D26A32D62A4C892A00903514 /* DatadogCore */, - 61253AF52A6FEAEF00A15E18 /* DatadogSessionReplay */, - 612C055A2A93C4DF00A1DA8C /* DatadogObjc */, + D266C6862B766095008866F9 /* DatadogObjc */, + D266C6882B766095008866F9 /* DatadogSessionReplay */, ); productName = SPMProject; productReference = 61C363D624374D5F00C4D4E6 /* App.app */; @@ -326,7 +330,7 @@ D2966C2F29CA1F3300FC6B3C /* DatadogTrace */, D2CE76B929F6B9E300D79713 /* DatadogRUM */, D26A32D82A4C893300903514 /* DatadogCore */, - 612C055C2A93C51300A1DA8C /* DatadogObjc */, + D266C68A2B76609F008866F9 /* DatadogObjc */, ); productName = SPMProject; productReference = D23BF5FE27CCCC3300BB4CCD /* App.app */; @@ -428,6 +432,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D22919312B76589B00C38A18 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -449,6 +454,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D22919322B76589B00C38A18 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -725,7 +731,6 @@ PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SUPPORTS_MACCATALYST = YES; - SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -748,7 +753,6 @@ PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SUPPORTS_MACCATALYST = YES; - SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -867,7 +871,6 @@ PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SDKROOT = appletvos; - SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; SWIFT_VERSION = 5.0; }; name = Debug; @@ -889,7 +892,6 @@ PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SDKROOT = appletvos; - SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; SWIFT_VERSION = 5.0; }; name = Release; @@ -1083,21 +1085,6 @@ package = 9E73373E26B0123500917C24 /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; productName = DatadogWebViewTracking; }; - 61253AF52A6FEAEF00A15E18 /* DatadogSessionReplay */ = { - isa = XCSwiftPackageProductDependency; - package = 9E73373E26B0123500917C24 /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; - productName = DatadogSessionReplay; - }; - 612C055A2A93C4DF00A1DA8C /* DatadogObjc */ = { - isa = XCSwiftPackageProductDependency; - package = 9E73373E26B0123500917C24 /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; - productName = DatadogObjc; - }; - 612C055C2A93C51300A1DA8C /* DatadogObjc */ = { - isa = XCSwiftPackageProductDependency; - package = 9E73373E26B0123500917C24 /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; - productName = DatadogObjc; - }; 9E73373F26B0123500917C24 /* DatadogCrashReporting */ = { isa = XCSwiftPackageProductDependency; package = 9E73373E26B0123500917C24 /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; @@ -1118,6 +1105,21 @@ package = D23BF5F127CCCC3300BB4CCD /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; productName = DatadogCrashReporting; }; + D266C6862B766095008866F9 /* DatadogObjc */ = { + isa = XCSwiftPackageProductDependency; + package = 9E73373E26B0123500917C24 /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; + productName = DatadogObjc; + }; + D266C6882B766095008866F9 /* DatadogSessionReplay */ = { + isa = XCSwiftPackageProductDependency; + package = 9E73373E26B0123500917C24 /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; + productName = DatadogSessionReplay; + }; + D266C68A2B76609F008866F9 /* DatadogObjc */ = { + isa = XCSwiftPackageProductDependency; + package = 9E73373E26B0123500917C24 /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; + productName = DatadogObjc; + }; D26A32D62A4C892A00903514 /* DatadogCore */ = { isa = XCSwiftPackageProductDependency; package = 9E73373E26B0123500917C24 /* XCRemoteSwiftPackageReference "dd-sdk-ios" */; diff --git a/dependency-manager-tests/xcframeworks/App/PrivacyInfo.xcprivacy b/dependency-manager-tests/xcframeworks/App/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..0c67376eba --- /dev/null +++ b/dependency-manager-tests/xcframeworks/App/PrivacyInfo.xcprivacy @@ -0,0 +1,5 @@ + + + + + diff --git a/dependency-manager-tests/xcframeworks/App/ViewController.swift b/dependency-manager-tests/xcframeworks/App/ViewController.swift index dc68d88a39..29a41b0a9d 100644 --- a/dependency-manager-tests/xcframeworks/App/ViewController.swift +++ b/dependency-manager-tests/xcframeworks/App/ViewController.swift @@ -41,13 +41,6 @@ internal class ViewController: UIViewController { RUM.enable(with: .init(applicationID: "app-id")) RUMMonitor.shared().startView(viewController: self) - // DDURLSessionDelegate APIs must be visible: - _ = DDURLSessionDelegate() - _ = DatadogURLSessionDelegate() - class CustomDelegate: NSObject, __URLSessionDelegateProviding { - var ddURLSessionDelegate: DatadogURLSessionDelegate { DatadogURLSessionDelegate() } - } - // Trace APIs must be visible: Trace.enable() diff --git a/dependency-manager-tests/xcframeworks/XCProject.xcodeproj/project.pbxproj b/dependency-manager-tests/xcframeworks/XCProject.xcodeproj/project.pbxproj index 3794d78cde..e5fcf1f276 100644 --- a/dependency-manager-tests/xcframeworks/XCProject.xcodeproj/project.pbxproj +++ b/dependency-manager-tests/xcframeworks/XCProject.xcodeproj/project.pbxproj @@ -33,6 +33,8 @@ D2966C2A29CA1E1600FC6B3C /* DatadogTrace.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D2966C2829CA1E1600FC6B3C /* DatadogTrace.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D2966C2B29CA1E1C00FC6B3C /* DatadogTrace.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2966C2829CA1E1600FC6B3C /* DatadogTrace.xcframework */; }; D2966C2C29CA1E1C00FC6B3C /* DatadogTrace.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D2966C2829CA1E1600FC6B3C /* DatadogTrace.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D2C2E9C52B76593F0076B3AB /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D2C2E9C42B76593F0076B3AB /* PrivacyInfo.xcprivacy */; }; + D2C2E9C62B76593F0076B3AB /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D2C2E9C42B76593F0076B3AB /* PrivacyInfo.xcprivacy */; }; D2EBEDA929B7862C00B15732 /* DatadogLogs.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2EBEDA829B7862C00B15732 /* DatadogLogs.xcframework */; }; D2EBEDAA29B7862C00B15732 /* DatadogLogs.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D2EBEDA829B7862C00B15732 /* DatadogLogs.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D2EBEDAB29B7863B00B15732 /* DatadogLogs.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2EBEDA829B7862C00B15732 /* DatadogLogs.xcframework */; }; @@ -154,6 +156,7 @@ D290BA3927CD09A20019936D /* App tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "App tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D290BA4527CD09C70019936D /* App tvOS UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "App tvOS UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D2966C2829CA1E1600FC6B3C /* DatadogTrace.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogTrace.xcframework; path = "dd-sdk-ios/build/xcframeworks/DatadogTrace.xcframework"; sourceTree = ""; }; + D2C2E9C42B76593F0076B3AB /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; D2EBEDA829B7862C00B15732 /* DatadogLogs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogLogs.xcframework; path = "dd-sdk-ios/build/xcframeworks/DatadogLogs.xcframework"; sourceTree = ""; }; D2EBEDAD29B7867600B15732 /* DatadogInternal.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogInternal.xcframework; path = "dd-sdk-ios/build/xcframeworks/DatadogInternal.xcframework"; sourceTree = ""; }; D2F09A2429F6C65A0036B910 /* DatadogRUM.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DatadogRUM.xcframework; path = "dd-sdk-ios/build/xcframeworks/DatadogRUM.xcframework"; sourceTree = ""; }; @@ -271,6 +274,7 @@ 61C3641A243752A500C4D4E6 /* SceneDelegate.swift */, 61C3641C243752A500C4D4E6 /* ViewController.swift */, 61C36426243752A600C4D4E6 /* Info.plist */, + D2C2E9C42B76593F0076B3AB /* PrivacyInfo.xcprivacy */, ); path = App; sourceTree = ""; @@ -481,6 +485,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D2C2E9C52B76593F0076B3AB /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -502,6 +507,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D2C2E9C62B76593F0076B3AB /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/tools/lint/sources.swiftlint.yml b/tools/lint/sources.swiftlint.yml index 35decd7eb8..b9e873f2c2 100644 --- a/tools/lint/sources.swiftlint.yml +++ b/tools/lint/sources.swiftlint.yml @@ -75,10 +75,10 @@ attributes: custom_rules: todo_without_jira: # enforces that all TODO comments must be followed by JIRA reference name: "TODO without JIRA" - regex: "(TODO|TO DO|FIX|FIXME|FIX ME|todo)(?!:? RUMM-[0-9]{2,})" # "TODO: RUMM-123", "TODO RUMM-123", "FIX RUMM-123", etc. + regex: "(TODO|TO DO|FIX|FIXME|FIX ME|todo)(?!:? (RUMM|RUM)-[0-9]{2,})" # "TODO: RUM-123", "TODO RUM-123", "FIX RUM-123", etc. match_kinds: - comment - message: "All TODOs must be followed by JIRA reference, for example: \"TODO: RUMM-123\"" + message: "All TODOs must be followed by JIRA reference, for example: \"TODO: RUM-123\"" severity: error unsafe_uiapplication_shared: # prevents from using `UIApplication.shared` API included: Sources diff --git a/tools/lint/tests.swiftlint.yml b/tools/lint/tests.swiftlint.yml index a7d9d3b151..2876b3acbc 100644 --- a/tools/lint/tests.swiftlint.yml +++ b/tools/lint/tests.swiftlint.yml @@ -68,10 +68,10 @@ attributes: custom_rules: todo_without_jira: # enforces that all TODO comments must be followed by JIRA reference name: "TODO without JIRA" - regex: "(TODO|TO DO|FIX|FIXME|FIX ME|todo)(?!:? RUMM-[0-9]{2,})" # "TODO: RUMM-123", "TODO RUMM-123", "FIX RUMM-123", etc. + regex: "(TODO|TO DO|FIX|FIXME|FIX ME|todo)(?!:? (RUMM|RUM)-[0-9]{2,})" # "TODO: RUM-123", "TODO RUM-123", "FIX RUM-123", etc. match_kinds: - comment - message: "All TODOs must be followed by JIRA reference, for example: \"TODO: RUMM-123\"" + message: "All TODOs must be followed by JIRA reference, for example: \"TODO: RUM-123\"" severity: error unmanaged_deallocation: # prevents from unmanaged singletons deallocation with `.instance = nil` name: "Unmanaged singleton deallocation: `.instance = nil`" diff --git a/tools/rum-models-generator/Sources/CodeDecoration/SRCodeDecorator.swift b/tools/rum-models-generator/Sources/CodeDecoration/SRCodeDecorator.swift index 2b60dff8c6..2a33691685 100644 --- a/tools/rum-models-generator/Sources/CodeDecoration/SRCodeDecorator.swift +++ b/tools/rum-models-generator/Sources/CodeDecoration/SRCodeDecorator.swift @@ -22,6 +22,7 @@ public class SRCodeDecorator: SwiftCodeDecorator { "SRTextWireframe", "SRImageWireframe", "SRPlaceholderWireframe", + "SRWebviewWireframe", // For convenience, make fat `*Record` structures to be root types: "SRFullSnapshotRecord", "SRIncrementalSnapshotRecord", diff --git a/tools/sr-snapshots/Sources/Git/GitClient.swift b/tools/sr-snapshots/Sources/Git/GitClient.swift index 5cae2f977a..fd9878d435 100644 --- a/tools/sr-snapshots/Sources/Git/GitClient.swift +++ b/tools/sr-snapshots/Sources/Git/GitClient.swift @@ -112,7 +112,7 @@ public class GitHubGitClient: GitClient { } print("ℹ️ Adding a commit:") try cli.shell("cd \(repoDirectory.path()) && git add -A") - try cli.shell("cd \(repoDirectory.path()) && git commit -m '\(message)'") + try cli.shell("cd \(repoDirectory.path()) && git commit -S -m '\(message)'") } public func push() throws { From ec37f70e19558d042500a11e5cc5722b436024d2 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 11 Mar 2024 22:18:24 +0100 Subject: [PATCH 044/153] RUM-1836 feat(otel-tracer): send telemetry for Tracer API usage --- .../Sources/Telemetry/Telemetry.swift | 8 +++ .../Tests/Telemetry/TelemetryMocks.swift | 2 + .../Tests/Telemetry/TelemetryTests.swift | 2 + .../Sources/RUM/RUMDataModels+objc.swift | 60 ++++++++++++++++++- .../Sources/DataModels/RUMDataModels.swift | 38 +++++++++++- .../Integrations/TelemetryReceiver.swift | 2 + .../OpenTelemetry/OTelTracerProvider.swift | 3 + DatadogTrace/Sources/Tracer.swift | 3 + 8 files changed, 116 insertions(+), 2 deletions(-) diff --git a/DatadogInternal/Sources/Telemetry/Telemetry.swift b/DatadogInternal/Sources/Telemetry/Telemetry.swift index a43f2c8e7a..1f53cfcf25 100644 --- a/DatadogInternal/Sources/Telemetry/Telemetry.swift +++ b/DatadogInternal/Sources/Telemetry/Telemetry.swift @@ -30,6 +30,8 @@ public struct ConfigurationTelemetry: Equatable { public let startSessionReplayRecordingManually: Bool? public let telemetryConfigurationSampleRate: Int64? public let telemetrySampleRate: Int64? + public let tracerAPI: String? + public let tracerAPIVersion: String? public let traceSampleRate: Int64? public let trackBackgroundEvents: Bool? public let trackCrossPlatformLongTasks: Bool? @@ -201,6 +203,8 @@ extension Telemetry { startSessionReplayRecordingManually: Bool? = nil, telemetryConfigurationSampleRate: Int64? = nil, telemetrySampleRate: Int64? = nil, + tracerAPI: String? = nil, + tracerAPIVersion: String? = nil, traceSampleRate: Int64? = nil, trackBackgroundEvents: Bool? = nil, trackCrossPlatformLongTasks: Bool? = nil, @@ -253,6 +257,8 @@ extension Telemetry { startSessionReplayRecordingManually: startSessionReplayRecordingManually, telemetryConfigurationSampleRate: telemetryConfigurationSampleRate, telemetrySampleRate: telemetrySampleRate, + tracerAPI: tracerAPI, + tracerAPIVersion: tracerAPIVersion, traceSampleRate: traceSampleRate, trackBackgroundEvents: trackBackgroundEvents, trackCrossPlatformLongTasks: trackCrossPlatformLongTasks, @@ -360,6 +366,8 @@ extension ConfigurationTelemetry { startSessionReplayRecordingManually: other.startSessionReplayRecordingManually ?? startSessionReplayRecordingManually, telemetryConfigurationSampleRate: other.telemetryConfigurationSampleRate ?? telemetryConfigurationSampleRate, telemetrySampleRate: other.telemetrySampleRate ?? telemetrySampleRate, + tracerAPI: other.tracerAPI ?? tracerAPI, + tracerAPIVersion: other.tracerAPIVersion ?? tracerAPIVersion, traceSampleRate: other.traceSampleRate ?? traceSampleRate, trackBackgroundEvents: other.trackBackgroundEvents ?? trackBackgroundEvents, trackCrossPlatformLongTasks: other.trackCrossPlatformLongTasks ?? trackCrossPlatformLongTasks, diff --git a/DatadogInternal/Tests/Telemetry/TelemetryMocks.swift b/DatadogInternal/Tests/Telemetry/TelemetryMocks.swift index 550f776c4e..b3022ba71b 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryMocks.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryMocks.swift @@ -34,6 +34,8 @@ extension ConfigurationTelemetry { startSessionReplayRecordingManually: .mockRandom(), telemetryConfigurationSampleRate: .mockRandom(), telemetrySampleRate: .mockRandom(), + tracerAPI: .mockRandom(), + tracerAPIVersion: .mockRandom(), traceSampleRate: .mockRandom(), trackBackgroundEvents: .mockRandom(), trackCrossPlatformLongTasks: .mockRandom(), diff --git a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift index 3bdfa2300c..d8458d41e3 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift @@ -221,6 +221,8 @@ class TelemetryTest: Telemetry { startSessionReplayRecordingManually: configuration.startSessionReplayRecordingManually, telemetryConfigurationSampleRate: configuration.telemetryConfigurationSampleRate, telemetrySampleRate: configuration.telemetrySampleRate, + tracerAPI: configuration.tracerAPI, + tracerAPIVersion: configuration.tracerAPIVersion, traceSampleRate: configuration.traceSampleRate, trackBackgroundEvents: configuration.trackBackgroundEvents, trackCrossPlatformLongTasks: configuration.trackCrossPlatformLongTasks, diff --git a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift index b803a616f4..1ee249d943 100644 --- a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift +++ b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift @@ -1612,6 +1612,10 @@ public class DDRUMErrorEventError: NSObject { get { root.swiftModel.error.causes?.map { DDRUMErrorEventErrorCauses(swiftModel: $0) } } } + @objc public var csp: DDRUMErrorEventErrorCSP? { + root.swiftModel.error.csp != nil ? DDRUMErrorEventErrorCSP(root: root) : nil + } + @objc public var fingerprint: String? { set { root.swiftModel.error.fingerprint = newValue } get { root.swiftModel.error.fingerprint } @@ -1798,6 +1802,42 @@ public enum DDRUMErrorEventErrorCausesSource: Int { case report } +@objc +public class DDRUMErrorEventErrorCSP: NSObject { + internal let root: DDRUMErrorEvent + + internal init(root: DDRUMErrorEvent) { + self.root = root + } + + @objc public var disposition: DDRUMErrorEventErrorCSPDisposition { + .init(swift: root.swiftModel.error.csp!.disposition) + } +} + +@objc +public enum DDRUMErrorEventErrorCSPDisposition: Int { + internal init(swift: RUMErrorEvent.Error.CSP.Disposition?) { + switch swift { + case nil: self = .none + case .enforce?: self = .enforce + case .report?: self = .report + } + } + + internal var toSwift: RUMErrorEvent.Error.CSP.Disposition? { + switch self { + case .none: return nil + case .enforce: return .enforce + case .report: return .report + } + } + + case none + case enforce + case report +} + @objc public enum DDRUMErrorEventErrorHandling: Int { internal init(swift: RUMErrorEvent.Error.Handling?) { @@ -6931,6 +6971,10 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { root.swiftModel.telemetry.configuration.batchUploadFrequency as NSNumber? } + @objc public var compressIntakeRequests: NSNumber? { + root.swiftModel.telemetry.configuration.compressIntakeRequests as NSNumber? + } + @objc public var dartVersion: String? { set { root.swiftModel.telemetry.configuration.dartVersion = newValue } get { root.swiftModel.telemetry.configuration.dartVersion } @@ -7019,6 +7063,16 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { root.swiftModel.telemetry.configuration.traceSampleRate as NSNumber? } + @objc public var tracerApi: String? { + set { root.swiftModel.telemetry.configuration.tracerApi = newValue } + get { root.swiftModel.telemetry.configuration.tracerApi } + } + + @objc public var tracerApiVersion: String? { + set { root.swiftModel.telemetry.configuration.tracerApiVersion = newValue } + get { root.swiftModel.telemetry.configuration.tracerApiVersion } + } + @objc public var trackBackgroundEvents: NSNumber? { set { root.swiftModel.telemetry.configuration.trackBackgroundEvents = newValue?.boolValue } get { root.swiftModel.telemetry.configuration.trackBackgroundEvents as NSNumber? } @@ -7093,6 +7147,10 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { get { root.swiftModel.telemetry.configuration.trackViewsManually as NSNumber? } } + @objc public var trackingConsent: String? { + root.swiftModel.telemetry.configuration.trackingConsent + } + @objc public var unityVersion: String? { set { root.swiftModel.telemetry.configuration.unityVersion = newValue } get { root.swiftModel.telemetry.configuration.unityVersion } @@ -7272,4 +7330,4 @@ public class DDTelemetryConfigurationEventView: NSObject { // swiftlint:enable force_unwrapping -// Generated from https://github.com/DataDog/rum-events-format/tree/63842bcc15dec58b68fc0a57260715f8dfcde330 +// Generated from https://github.com/DataDog/rum-events-format/tree/29008e6da435c4573160a2f549a4d657c7315d31 diff --git a/DatadogRUM/Sources/DataModels/RUMDataModels.swift b/DatadogRUM/Sources/DataModels/RUMDataModels.swift index efbae887d6..b874911e3d 100644 --- a/DatadogRUM/Sources/DataModels/RUMDataModels.swift +++ b/DatadogRUM/Sources/DataModels/RUMDataModels.swift @@ -698,6 +698,9 @@ public struct RUMErrorEvent: RUMDataModel { /// Causes of the error public var causes: [Causes]? + /// Content Security Violation properties + public let csp: CSP? + /// Fingerprint used for Error Tracking custom grouping public var fingerprint: String? @@ -744,6 +747,7 @@ public struct RUMErrorEvent: RUMDataModel { case binaryImages = "binary_images" case category = "category" case causes = "causes" + case csp = "csp" case fingerprint = "fingerprint" case handling = "handling" case handlingStack = "handling_stack" @@ -831,6 +835,22 @@ public struct RUMErrorEvent: RUMDataModel { } } + /// Content Security Violation properties + public struct CSP: Codable { + /// In the context of CSP errors, indicates how the violated policy is configured to be treated by the user agent. + public let disposition: Disposition? + + enum CodingKeys: String, CodingKey { + case disposition = "disposition" + } + + /// In the context of CSP errors, indicates how the violated policy is configured to be treated by the user agent. + public enum Disposition: String, Codable { + case enforce = "enforce" + case report = "report" + } + } + /// Whether the error has been handled manually in the source code or not public enum Handling: String, Codable { case handled = "handled" @@ -3373,6 +3393,9 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// The upload frequency of batches (in milliseconds) public let batchUploadFrequency: Int64? + /// Whether intake requests are compressed + public let compressIntakeRequests: Bool? + /// The version of Dart used in a Flutter application public var dartVersion: String? @@ -3433,6 +3456,12 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// The percentage of requests traced public let traceSampleRate: Int64? + /// The tracer API used by the SDK. Possible values: 'Datadog', 'OpenTelemetry', 'OpenTracing' + public var tracerApi: String? + + /// The version of the tracer API used by the SDK. Eg. '0.1.0' + public var tracerApiVersion: String? + /// Whether RUM events are tracked when the application is in Background public var trackBackgroundEvents: Bool? @@ -3478,6 +3507,9 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// Whether the RUM views creation is handled manually public var trackViewsManually: Bool? + /// The initial tracking consent value + public let trackingConsent: String? + /// The version of Unity used in a Unity application public var unityVersion: String? @@ -3529,6 +3561,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case batchProcessingLevel = "batch_processing_level" case batchSize = "batch_size" case batchUploadFrequency = "batch_upload_frequency" + case compressIntakeRequests = "compress_intake_requests" case dartVersion = "dart_version" case defaultPrivacyLevel = "default_privacy_level" case forwardConsoleLogs = "forward_console_logs" @@ -3549,6 +3582,8 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case telemetryConfigurationSampleRate = "telemetry_configuration_sample_rate" case telemetrySampleRate = "telemetry_sample_rate" case traceSampleRate = "trace_sample_rate" + case tracerApi = "tracer_api" + case tracerApiVersion = "tracer_api_version" case trackBackgroundEvents = "track_background_events" case trackCrossPlatformLongTasks = "track_cross_platform_long_tasks" case trackErrors = "track_errors" @@ -3564,6 +3599,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case trackSessionAcrossSubdomains = "track_session_across_subdomains" case trackUserInteractions = "track_user_interactions" case trackViewsManually = "track_views_manually" + case trackingConsent = "tracking_consent" case unityVersion = "unity_version" case useAllowedTracingOrigins = "use_allowed_tracing_origins" case useAllowedTracingUrls = "use_allowed_tracing_urls" @@ -4001,4 +4037,4 @@ public enum RUMMethod: String, Codable { case connect = "CONNECT" } -// Generated from https://github.com/DataDog/rum-events-format/tree/63842bcc15dec58b68fc0a57260715f8dfcde330 +// Generated from https://github.com/DataDog/rum-events-format/tree/29008e6da435c4573160a2f549a4d657c7315d31 diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 26bbfcdb7b..004dade6b0 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -284,6 +284,8 @@ private extension TelemetryConfigurationEvent.Telemetry.Configuration { telemetryConfigurationSampleRate: nil, telemetrySampleRate: configuration.telemetrySampleRate, traceSampleRate: configuration.traceSampleRate, + tracerApi: configuration.tracerAPI, + tracerApiVersion: configuration.tracerAPIVersion, trackBackgroundEvents: configuration.trackBackgroundEvents, trackCrossPlatformLongTasks: configuration.trackCrossPlatformLongTasks, trackErrors: configuration.trackErrors, diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift b/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift index 0214d21b45..303b405cd6 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelTracerProvider.swift @@ -61,6 +61,9 @@ public class OTelTracerProvider: OpenTelemetryApi.TracerProvider { ) } + // Send tracer API usage to telemetry + core?.telemetry.configuration(tracerAPI: "OpenTelemetry", tracerAPIVersion: OpenTelemetry.version) + return feature.tracer } catch { consolePrint("\(error)", .error) diff --git a/DatadogTrace/Sources/Tracer.swift b/DatadogTrace/Sources/Tracer.swift index 83dd84a639..05147d18f0 100644 --- a/DatadogTrace/Sources/Tracer.swift +++ b/DatadogTrace/Sources/Tracer.swift @@ -76,6 +76,9 @@ public class Tracer { ) } + // Send tracer API usage to telemetry + core.telemetry.configuration(tracerAPI: "OpenTracing") + return feature.tracer } catch { consolePrint("\(error)", .error) From 4ea1bb63623b980a8a68b930f05e784c4effa36a Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 12 Mar 2024 14:42:51 +0100 Subject: [PATCH 045/153] fix generated code default values --- DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift | 3 +++ DatadogRUM/Sources/Integrations/CrashReportReceiver.swift | 1 + DatadogRUM/Sources/Integrations/TelemetryReceiver.swift | 2 ++ DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift | 1 + DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift | 1 + DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift | 3 +++ 6 files changed, 11 insertions(+) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift index a2d37b857e..7eb5398f72 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift @@ -387,6 +387,7 @@ extension RUMErrorEvent: RandomMockable { error: .init( binaryImages: nil, category: nil, + csp: nil, handling: nil, handlingStack: nil, id: .mockRandom(), @@ -512,6 +513,7 @@ extension TelemetryConfigurationEvent: RandomMockable { batchProcessingLevel: .mockRandom(), batchSize: .mockAny(), batchUploadFrequency: .mockAny(), + compressIntakeRequests: nil, defaultPrivacyLevel: .mockAny(), forwardConsoleLogs: nil, forwardErrorsToLogs: nil, @@ -544,6 +546,7 @@ extension TelemetryConfigurationEvent: RandomMockable { trackResources: .mockRandom(), trackSessionAcrossSubdomains: nil, trackViewsManually: nil, + trackingConsent: nil, useAllowedTracingOrigins: .mockRandom(), useAllowedTracingUrls: nil, useBeforeSend: nil, diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index 185a4dc2af..b457b8e6c4 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -373,6 +373,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { error: .init( binaryImages: nil, category: .exception, // crashes are categorised as "Exception" + csp: nil, handling: nil, handlingStack: nil, id: nil, diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 004dade6b0..6cb75a8a85 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -265,6 +265,7 @@ private extension TelemetryConfigurationEvent.Telemetry.Configuration { batchProcessingLevel: configuration.batchProcessingLevel, batchSize: configuration.batchSize, batchUploadFrequency: configuration.batchUploadFrequency, + compressIntakeRequests: nil, dartVersion: configuration.dartVersion, defaultPrivacyLevel: nil, forwardConsoleLogs: nil, @@ -300,6 +301,7 @@ private extension TelemetryConfigurationEvent.Telemetry.Configuration { trackResources: nil, trackSessionAcrossSubdomains: nil, trackViewsManually: configuration.trackViewsManually, + trackingConsent: nil, useAllowedTracingOrigins: nil, useAllowedTracingUrls: nil, useBeforeSend: nil, diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift index 0880eec843..6492e520d4 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift @@ -278,6 +278,7 @@ internal class RUMResourceScope: RUMScope { error: .init( binaryImages: nil, category: .exception, // resource errors are categorised as "Exception" + csp: nil, handling: nil, handlingStack: nil, id: nil, diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index cae78801a6..88d23d76f0 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -587,6 +587,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { binaryImages: command.binaryImages?.compactMap { $0.toRUMDataFormat }, category: command.category, causes: nil, + csp: nil, handling: nil, handlingStack: nil, id: nil, diff --git a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift index 4152a03a37..38745e381c 100644 --- a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift @@ -400,6 +400,7 @@ extension RUMErrorEvent: RandomMockable { error: .init( binaryImages: nil, category: nil, + csp: nil, handling: nil, handlingStack: nil, id: .mockRandom(), @@ -525,6 +526,7 @@ extension TelemetryConfigurationEvent: RandomMockable { batchProcessingLevel: .mockRandom(), batchSize: .mockAny(), batchUploadFrequency: .mockRandom(), + compressIntakeRequests: nil, defaultPrivacyLevel: .mockRandom(), forwardConsoleLogs: nil, forwardErrorsToLogs: nil, @@ -557,6 +559,7 @@ extension TelemetryConfigurationEvent: RandomMockable { trackResources: .mockRandom(), trackSessionAcrossSubdomains: nil, trackViewsManually: nil, + trackingConsent: nil, useAllowedTracingOrigins: .mockRandom(), useAllowedTracingUrls: nil, useBeforeSend: nil, From 2bdd636fccad49c5405e9c01ab36e6d82d0131a9 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 12 Mar 2024 17:28:29 +0100 Subject: [PATCH 046/153] build using default branch --- DatadogObjc/Sources/RUM/RUMDataModels+objc.swift | 2 +- DatadogRUM/Sources/DataModels/RUMDataModels.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift index 1ee249d943..50c08bca9b 100644 --- a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift +++ b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift @@ -7330,4 +7330,4 @@ public class DDTelemetryConfigurationEventView: NSObject { // swiftlint:enable force_unwrapping -// Generated from https://github.com/DataDog/rum-events-format/tree/29008e6da435c4573160a2f549a4d657c7315d31 +// Generated from https://github.com/DataDog/rum-events-format/tree/61560c6502ebf333e71631ee35d00b9c09aadf8e diff --git a/DatadogRUM/Sources/DataModels/RUMDataModels.swift b/DatadogRUM/Sources/DataModels/RUMDataModels.swift index b874911e3d..d79d0ea277 100644 --- a/DatadogRUM/Sources/DataModels/RUMDataModels.swift +++ b/DatadogRUM/Sources/DataModels/RUMDataModels.swift @@ -4037,4 +4037,4 @@ public enum RUMMethod: String, Codable { case connect = "CONNECT" } -// Generated from https://github.com/DataDog/rum-events-format/tree/29008e6da435c4573160a2f549a4d657c7315d31 +// Generated from https://github.com/DataDog/rum-events-format/tree/61560c6502ebf333e71631ee35d00b9c09aadf8e From 564e6fbb07198f849a64d7c3918dc09af9c69720 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 4 Apr 2024 14:20:59 +0200 Subject: [PATCH 047/153] RUM-3853 feat(otel-tracer): make DDSpan active when OtelSpanBuilder starts a span as active --- DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift index 21139bc754..7e06cd577e 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift @@ -138,6 +138,7 @@ internal class OTelSpanBuilder: OpenTelemetryApi.SpanBuilder { if active { OpenTelemetry.instance.contextProvider.setActiveSpan(createdSpan) + createdSpan.ddSpan.setActive() } return createdSpan From 5c4669074c7d145a3ce88e78390eec2db7da17b5 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 4 Apr 2024 14:37:19 +0200 Subject: [PATCH 048/153] merge conflict change --- .../Example/Base.lproj/Main iOS.storyboard | 348 +++++++++++++++++- 1 file changed, 340 insertions(+), 8 deletions(-) diff --git a/Datadog/Example/Base.lproj/Main iOS.storyboard b/Datadog/Example/Base.lproj/Main iOS.storyboard index d4a0c551e4..66d65e05c9 100644 --- a/Datadog/Example/Base.lproj/Main iOS.storyboard +++ b/Datadog/Example/Base.lproj/Main iOS.storyboard @@ -18,7 +18,7 @@ - + @@ -65,9 +65,29 @@ - + + + + + + + + + + + + + + + @@ -86,7 +106,7 @@ - + @@ -106,7 +126,7 @@ - + @@ -126,7 +146,7 @@ - + @@ -146,7 +166,7 @@ - + @@ -166,7 +186,7 @@ - + @@ -784,7 +804,319 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a54ccfcefd8f40c20f6577a062bdb7f7f59d257c Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Apr 2024 16:04:01 +0200 Subject: [PATCH 049/153] RUM-3853 feat(otel-tracer): add test cases for setting active span --- .../Tests/Datadog/Tracing/OTelSpanTests.swift | 41 +++++++++++++++ .../Tests/OpenTelemetry/OTelSpanTests.swift | 51 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift index c039c91b2e..2e7c0c7fed 100644 --- a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift +++ b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift @@ -58,6 +58,47 @@ final class OTelSpanTests: XCTestCase { ] DDAssertJSONEqual(AnyEncodable(expectedAttributes), AnyEncodable(logs[0].attributes.userAttributes)) } + + func testContextProviderSetActive_givenParentSpan() throws { + let core = DatadogCoreProxy() + defer { core.flushAndTearDown() } + + Trace.enable(in: core) + + // Given + OpenTelemetry.registerTracerProvider( + tracerProvider: OTelTracerProvider(in: core) + ) + + let tracer = OpenTelemetry + .instance + .tracerProvider + .get(instrumentationName: "", instrumentationVersion: nil) + + let parentSpan = tracer + .spanBuilder(spanName: "ParentSpan") + .startSpan() + + // When + OpenTelemetry.instance.contextProvider.setActiveSpan(parentSpan) + + let childSpan = tracer + .spanBuilder(spanName: "ChildSpan") + .startSpan() + + childSpan.end() + parentSpan.end() + + // Then + let spans = try core.waitAndReturnSpanMatchers() + XCTAssertEqual(spans.count, 2) + + let childSpanMatcher = spans[0] + let parentSpanMatcher = spans[1] + + XCTAssertEqual(try parentSpanMatcher.traceID(), try childSpanMatcher.traceID()) + XCTAssertEqual(try parentSpanMatcher.spanID(), try childSpanMatcher.parentSpanID()) + } } extension Dictionary where Key == String, Value == OpenTelemetryApi.AttributeValue { diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 88dbc07953..32adfb4a56 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -249,6 +249,57 @@ final class OTelSpanTests: XCTestCase { XCTAssertEqual(child.parentID, nil) } + func testSetActive_givenParentSpan() { + let writeSpanExpectation = expectation(description: "write span event") + writeSpanExpectation.expectedFulfillmentCount = 2 + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let parentSpan = tracer.spanBuilder(spanName: "Parent").setActive(true).startSpan() + let childSpan = tracer.spanBuilder(spanName: "Child").startSpan() + + // When + childSpan.end() + parentSpan.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 2) + let child = recordedSpans.first! + let parent = recordedSpans.last! + XCTAssertEqual(child.traceID, parent.traceID) + XCTAssertEqual(parent.parentID, nil) + XCTAssertEqual(child.parentID, parent.spanID) + } + + func testParentIds_givenDisjointSpans() { + let writeSpanExpectation = expectation(description: "write span event") + writeSpanExpectation.expectedFulfillmentCount = 2 + let core = PassthroughCoreMock(expectation: writeSpanExpectation) + + // Given + let tracer: DatadogTracer = .mockWith(core: core) + let span1 = tracer.spanBuilder(spanName: "Span1").startSpan() + let span2 = tracer.spanBuilder(spanName: "Span2").startSpan() + + // When + span2.end() + span1.end() + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + let recordedSpans = core.spans() + XCTAssertEqual(recordedSpans.count, 2) + let span1Recorded = recordedSpans.first! + let span2Recorded = recordedSpans.last! + + XCTAssertEqual(span1Recorded.parentID, nil) + XCTAssertEqual(span2Recorded.parentID, nil) + XCTAssertNotEqual(span1Recorded.traceID, span2Recorded.traceID) + } + func testSetAttribute() { let writeSpanExpectation = expectation(description: "write span event") let core = PassthroughCoreMock(expectation: writeSpanExpectation) From 44b3fdc7a7e68f5468da9bb71dda3e67f23c6733 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Apr 2024 16:10:28 +0200 Subject: [PATCH 050/153] RUM-3853 feat(otel-tracer): simplify asserts --- DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift index 32adfb4a56..cc70869a47 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanTests.swift @@ -195,7 +195,7 @@ final class OTelSpanTests: XCTestCase { XCTAssertEqual(recordedSpans.count, 2) let child = recordedSpans.first! let parent = recordedSpans.last! - XCTAssertEqual(parent.parentID, nil) + XCTAssertNil(parent.parentID) XCTAssertEqual(child.parentID, parent.spanID) } @@ -220,7 +220,7 @@ final class OTelSpanTests: XCTestCase { XCTAssertEqual(recordedSpans.count, 2) let child = recordedSpans.first! let parent = recordedSpans.last! - XCTAssertEqual(parent.parentID, nil) + XCTAssertNil(parent.parentID) XCTAssertEqual(child.parentID, parent.spanID) } @@ -245,7 +245,7 @@ final class OTelSpanTests: XCTestCase { XCTAssertEqual(recordedSpans.count, 2) let child = recordedSpans.first! let parent = recordedSpans.last! - XCTAssertEqual(parent.parentID, nil) + XCTAssertNil(parent.parentID) XCTAssertEqual(child.parentID, nil) } @@ -270,7 +270,7 @@ final class OTelSpanTests: XCTestCase { let child = recordedSpans.first! let parent = recordedSpans.last! XCTAssertEqual(child.traceID, parent.traceID) - XCTAssertEqual(parent.parentID, nil) + XCTAssertNil(parent.parentID) XCTAssertEqual(child.parentID, parent.spanID) } From 2b06c3ba5045e6d37fdcdbb893ac6f5661a6915e Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Apr 2024 16:33:02 +0200 Subject: [PATCH 051/153] RUM-3853 feat(otel-tracer): add more interesting use case in example app --- .../Debugging/DebugOTelTracingViewController.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Datadog/Example/Debugging/DebugOTelTracingViewController.swift b/Datadog/Example/Debugging/DebugOTelTracingViewController.swift index 2a615cd1da..76d071f81b 100644 --- a/Datadog/Example/Debugging/DebugOTelTracingViewController.swift +++ b/Datadog/Example/Debugging/DebugOTelTracingViewController.swift @@ -7,6 +7,7 @@ import UIKit import DatadogCore import DatadogTrace +import OpenTelemetryApi class DebugOTelTracingViewController: UIViewController { @IBOutlet weak var serviceNameTextField: UITextField! @@ -82,12 +83,12 @@ class DebugOTelTracingViewController: UIViewController { let rootSpan = otelTracer .spanBuilder(spanName: spanName) + .setActive(true) .startSpan() wait(seconds: 0.5) self.queue2.sync { let child1 = otelTracer.spanBuilder(spanName: "otel child operation 1") - .setParent(rootSpan) .startSpan() wait(seconds: 0.5) child1.end() @@ -109,6 +110,12 @@ class DebugOTelTracingViewController: UIViewController { grandChild.end() } + OpenTelemetry.instance.contextProvider.setActiveSpan(child2) + let child2Child = otelTracer.spanBuilder(spanName: "otel child2 child") + .startSpan() + wait(seconds: 0.5) + child2Child.end() + child2.end() } From 2de48d897339850dc23d4426b4c5eb7624acf8ae Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Apr 2024 17:33:22 +0200 Subject: [PATCH 052/153] remove from context when ending the span --- DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 767a1958f1..0cdcdcfe7f 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -215,6 +215,7 @@ internal class OTelSpan: OpenTelemetryApi.Span { } ddSpan.finish(at: time) + OpenTelemetry.instance.contextProvider.removeContextForSpan(self) } var description: String { From ec4b42c0d99f6af5d3be2dedcb4bfa1f9f9515cd Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Apr 2024 17:07:36 +0200 Subject: [PATCH 053/153] feat(otel-tracer): enable testing SDK --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 435a742937..8380ce6829 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ all: dependencies templates # The release version of `dd-sdk-swift-testing` to use for tests instrumentation. DD_SDK_SWIFT_TESTING_VERSION = 2.3.2 -DD_DISABLE_TEST_INSTRUMENTING = true +DD_DISABLE_TEST_INSTRUMENTING = false define DD_SDK_TESTING_XCCONFIG_CI DD_SDK_TESTING_PATH=$$(DD_SDK_TESTING_OVERRIDE_PATH:default=$$(SRCROOT)/../instrumented-tests/)\n From eb673a579a050df5ddfee76e2f4db034847c6be4 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 5 Apr 2024 17:12:40 +0200 Subject: [PATCH 054/153] disable test that cause symbol conflict --- .../xcshareddata/xcschemes/DatadogCore iOS.xcscheme | 5 +++++ .../xcshareddata/xcschemes/DatadogCore tvOS.xcscheme | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme index db28c5f907..d8628af635 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme @@ -196,6 +196,11 @@ BlueprintName = "DatadogCoreTests iOS" ReferencedContainer = "container:Datadog.xcodeproj"> + + + + diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme index b30d9a0578..cc51d46853 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme @@ -182,6 +182,11 @@ BlueprintName = "DatadogCoreTests tvOS" ReferencedContainer = "container:Datadog.xcodeproj"> + + + + From 8af7a8d4c68cac8021d5c29cd9264b47402196eb Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 11 Apr 2024 15:02:06 +0200 Subject: [PATCH 055/153] RUM-3853 feat(otel-tracer): remove support for event API --- DatadogCore/Tests/Datadog/LoggerTests.swift | 40 +++++++++++++++++++ .../Sources/OpenTelemetry/OTelSpan.swift | 30 ++------------ 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/DatadogCore/Tests/Datadog/LoggerTests.swift b/DatadogCore/Tests/Datadog/LoggerTests.swift index d7ebcdda09..5362b9456e 100644 --- a/DatadogCore/Tests/Datadog/LoggerTests.swift +++ b/DatadogCore/Tests/Datadog/LoggerTests.swift @@ -7,6 +7,7 @@ import XCTest import TestUtilities import DatadogInternal +import OpenTelemetryApi @testable import DatadogLogs @testable import DatadogTrace @@ -875,6 +876,45 @@ class LoggerTests: XCTestCase { logMatchers[1].assertNoValue(forKey: "dd.span_id") } + func testGivenBundlingWithTraceEnabledAndOpenTelemetryTracerRegistered_whenSendingLog_itContainsActiveSpanAttributes() throws { + core.context = .mockAny() + + Logs.enable(in: core) + Trace.enable(in: core) + + // given + let logger = Logger.create(in: core) + OpenTelemetry.registerTracerProvider( + tracerProvider: OTelTracerProvider(in: core) + ) + + let tracer = OpenTelemetry + .instance + .tracerProvider + .get(instrumentationName: "", instrumentationVersion: nil) + + // when + let span = tracer.spanBuilder(spanName: "span") + .setActive(true) + .startSpan() + logger.info("info message 1") + span.end() + logger.info("info message 2") + + // then + let logMatchers = try core.waitAndReturnLogMatchers() + logMatchers[0].assertValue( + forKeyPath: "dd.trace_id", + equals: String(span.context.traceId.toDatadog()) + ) + logMatchers[0].assertValue( + forKeyPath: "dd.span_id", + equals: String(span.context.spanId.toDatadog()) + ) + logMatchers[1].assertNoValue(forKey: "dd.trace_id") + logMatchers[1].assertNoValue(forKey: "dd.span_id") + } + // MARK: - Log Dates Correction func testGivenTimeDifferenceBetweenDeviceAndServer_whenCollectingLogs_thenLogDateUsesServerTime() throws { diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 0cdcdcfe7f..8a44ee2819 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -136,42 +136,20 @@ internal class OTelSpan: OpenTelemetryApi.Span { ) } - /// Sends a span event which is akin to a log in Datadog - /// - Parameter name: name of the event func addEvent(name: String) { - addEvent(name: name, timestamp: .init()) + // not supported } - /// Sends a span event which is akin to a log in Datadog - /// - Parameters: - /// - name: name of the event - /// - timestamp: timestamp of the event func addEvent(name: String, timestamp: Date) { - addEvent(name: name, attributes: .init(), timestamp: timestamp) + // not supported } - /// Sends a span event which is akin to a log in Datadog - /// - Parameters: - /// - name: name of the event - /// - attributes: attributes of the event - /// - timestamp: timestamp of the event func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue]) { - addEvent(name: name, attributes: attributes, timestamp: .init()) + // not supported } - /// Sends a span event which is akin to a log in Datadog - /// - Parameters: - /// - name: name of the event - /// - attributes: attributes of the event - /// - timestamp: timestamp of the event func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue], timestamp: Date) { - guard isRecording else { - return - } - - // There is no need to lock here, because `DDSpan` is thread-safe - - ddSpan.log(message: name, fields: attributes.tags, timestamp: timestamp) + // not supported } func end() { From 64905004789af03d3b6f977c57116736e7dde11a Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 16 Apr 2024 15:54:27 +0200 Subject: [PATCH 056/153] log warning --- DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 8a44ee2819..52af970ffe 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -137,19 +137,19 @@ internal class OTelSpan: OpenTelemetryApi.Span { } func addEvent(name: String) { - // not supported + DD.logger.warn("\(#function) is not yet supported in `DatadogTrace`") } func addEvent(name: String, timestamp: Date) { - // not supported + DD.logger.warn("\(#function) is not yet supported in `DatadogTrace`") } func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue]) { - // not supported + DD.logger.warn("\(#function) is not yet supported in `DatadogTrace`") } func addEvent(name: String, attributes: [String: OpenTelemetryApi.AttributeValue], timestamp: Date) { - // not supported + DD.logger.warn("\(#function) is not yet supported in `DatadogTrace`") } func end() { From 703bdef4dc0e5fbe82565a3c3ff2c0a476cd619f Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 16 Apr 2024 17:56:52 +0200 Subject: [PATCH 057/153] feat(otel-tracer): add send span links in example app --- .../Example/Base.lproj/Main iOS.storyboard | 56 ++++++++++++++++--- .../DebugOTelTracingViewController.swift | 20 +++++++ DatadogInternal/Sources/Upload/Event.swift | 6 ++ 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/Datadog/Example/Base.lproj/Main iOS.storyboard b/Datadog/Example/Base.lproj/Main iOS.storyboard index 66d65e05c9..5de238d791 100644 --- a/Datadog/Example/Base.lproj/Main iOS.storyboard +++ b/Datadog/Example/Base.lproj/Main iOS.storyboard @@ -1,9 +1,9 @@ - + - + @@ -1063,28 +1063,67 @@ + + + + + + + + + + + + + + + + + - + - + - + @@ -1109,6 +1148,7 @@ + @@ -2060,13 +2100,13 @@ - + - + diff --git a/Datadog/Example/Debugging/DebugOTelTracingViewController.swift b/Datadog/Example/Debugging/DebugOTelTracingViewController.swift index 76d071f81b..ae5970e6a7 100644 --- a/Datadog/Example/Debugging/DebugOTelTracingViewController.swift +++ b/Datadog/Example/Debugging/DebugOTelTracingViewController.swift @@ -17,6 +17,7 @@ class DebugOTelTracingViewController: UIViewController { @IBOutlet weak var sendSingleSpanButton: UIButton! @IBOutlet weak var complexSpanOperationNameTextField: UITextField! @IBOutlet weak var sendComplexSpanButton: UIButton! + @IBOutlet weak var sendSpanLinksButton: UIButton! @IBOutlet weak var consoleTextView: UITextView! private let queue1 = DispatchQueue(label: "com.datadoghq.debug-tracing1") @@ -123,6 +124,25 @@ class DebugOTelTracingViewController: UIViewController { rootSpan.end() } } + + // MARK: - Sending span links + + @IBAction func didTabSendSpanLinks(_ sender: Any) { + sendSpanLinksButton.disableFor(seconds: 1) + queue1.async { + let span1 = otelTracer.spanBuilder(spanName: "span 1") + .startSpan() + wait(seconds: 0.5) + + let span2 = otelTracer.spanBuilder(spanName: "span 2") + .addLink(spanContext: span1.context) + .startSpan() + wait(seconds: 0.5) + span2.end() + + span1.end() + } + } } private func wait(seconds: TimeInterval) { diff --git a/DatadogInternal/Sources/Upload/Event.swift b/DatadogInternal/Sources/Upload/Event.swift index 6f05f8dc9a..f2ff635f0b 100644 --- a/DatadogInternal/Sources/Upload/Event.swift +++ b/DatadogInternal/Sources/Upload/Event.swift @@ -22,3 +22,9 @@ public struct Event: Equatable { self.metadata = metadata } } + +extension Event: CustomDebugStringConvertible { + public var debugDescription: String { + return .init(data: data, encoding: .utf8) ?? "" + } +} From 31fdeb5af3422c620c2c99fb3d62e595c43ccd51 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 15 Apr 2024 18:43:45 +0200 Subject: [PATCH 058/153] RUM-3185 feat(otel-tracer): use DatadogSDKTesting as SPM dependency --- Datadog/Datadog.xcodeproj/project.pbxproj | 41 +++++++++++++++++++ .../xcschemes/DatadogCore iOS.xcscheme | 5 --- .../xcschemes/DatadogCore tvOS.xcscheme | 5 --- .../Tests/Datadog/Tracing/OTelSpanTests.swift | 18 +------- Makefile | 17 -------- 5 files changed, 42 insertions(+), 44 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index aaae6e765a..b7b96600f3 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -80,6 +80,8 @@ 3CCCA5C52ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */; }; 3CCCA5C72ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */; }; 3CCCA5C82ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */; }; + 3CDA3F7E2BCD866D005D2C13 /* DatadogSDKTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 3CDA3F7D2BCD866D005D2C13 /* DatadogSDKTesting */; }; + 3CDA3F802BCD8687005D2C13 /* DatadogSDKTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 3CDA3F7F2BCD8687005D2C13 /* DatadogSDKTesting */; }; 3CE11A1129F7BE0900202522 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; 3CE11A1229F7BE0900202522 /* DatadogWebViewTracking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3CF673362B4807490016CE17 /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF673352B4807490016CE17 /* OTelSpanTests.swift */; }; @@ -3004,6 +3006,7 @@ files = ( D26F741129ACBDA100D25622 /* DatadogInternal.framework in Frameworks */, D2579592298ABCED008A1BE5 /* XCTest.framework in Frameworks */, + 3CDA3F7E2BCD866D005D2C13 /* DatadogSDKTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3013,6 +3016,7 @@ files = ( D26F741229ACBDAD00D25622 /* DatadogInternal.framework in Frameworks */, D230399E298D50F1001A1FA3 /* XCTest.framework in Frameworks */, + 3CDA3F802BCD8687005D2C13 /* DatadogSDKTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6560,6 +6564,9 @@ 3C41694229FBF6100042B9D2 /* PBXTargetDependency */, ); name = "TestUtilities iOS"; + packageProductDependencies = ( + 3CDA3F7D2BCD866D005D2C13 /* DatadogSDKTesting */, + ); productName = TestUtilities; productReference = D257953E298ABA65008A1BE5 /* TestUtilities.framework */; productType = "com.apple.product-type.framework"; @@ -6579,6 +6586,9 @@ D26F741529ACBDAD00D25622 /* PBXTargetDependency */, ); name = "TestUtilities tvOS"; + packageProductDependencies = ( + 3CDA3F7F2BCD8687005D2C13 /* DatadogSDKTesting */, + ); productName = TestUtilities; productReference = D257958B298ABB83008A1BE5 /* TestUtilities.framework */; productType = "com.apple.product-type.framework"; @@ -6598,6 +6608,8 @@ D2C1A51129C4C4EF00946C31 /* PBXTargetDependency */, ); name = "DatadogTrace iOS"; + packageProductDependencies = ( + ); productName = DatadogTrace; productReference = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; productType = "com.apple.product-type.framework"; @@ -6616,6 +6628,8 @@ D25EE93E29C4C3C300CE3839 /* PBXTargetDependency */, ); name = "DatadogTraceTests iOS"; + packageProductDependencies = ( + ); productName = DatadogTraceTests; productReference = D25EE93B29C4C3C300CE3839 /* DatadogTraceTests iOS.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -6966,6 +6980,9 @@ Base, ); mainGroup = 61133B78242393DE00786299; + packageReferences = ( + 3CDA3F6C2BCD8429005D2C13 /* XCRemoteSwiftPackageReference "dd-sdk-swift-testing" */, + ); productRefGroup = 61133B83242393DE00786299 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -13140,6 +13157,30 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 3CDA3F6C2BCD8429005D2C13 /* XCRemoteSwiftPackageReference "dd-sdk-swift-testing" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/DataDog/dd-sdk-swift-testing.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.4.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 3CDA3F7D2BCD866D005D2C13 /* DatadogSDKTesting */ = { + isa = XCSwiftPackageProductDependency; + package = 3CDA3F6C2BCD8429005D2C13 /* XCRemoteSwiftPackageReference "dd-sdk-swift-testing" */; + productName = DatadogSDKTesting; + }; + 3CDA3F7F2BCD8687005D2C13 /* DatadogSDKTesting */ = { + isa = XCSwiftPackageProductDependency; + package = 3CDA3F6C2BCD8429005D2C13 /* XCRemoteSwiftPackageReference "dd-sdk-swift-testing" */; + productName = DatadogSDKTesting; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 61133B79242393DE00786299 /* Project object */; } diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme index d8628af635..db28c5f907 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme @@ -196,11 +196,6 @@ BlueprintName = "DatadogCoreTests iOS" ReferencedContainer = "container:Datadog.xcodeproj"> - - - - diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme index cc51d46853..b30d9a0578 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme @@ -182,11 +182,6 @@ BlueprintName = "DatadogCoreTests tvOS" ReferencedContainer = "container:Datadog.xcodeproj"> - - - - diff --git a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift index 2e7c0c7fed..6590c9f589 100644 --- a/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift +++ b/DatadogCore/Tests/Datadog/Tracing/OTelSpanTests.swift @@ -40,23 +40,7 @@ final class OTelSpanTests: XCTestCase { // Then let logs: [LogEvent] = core.waitAndReturnEvents(ofFeature: LogsFeature.name, ofType: LogEvent.self) - XCTAssertEqual(logs.count, 1) - - let expectedAttributes: [String: Encodable] = [ - "string": "value", - "bool": "true", - "int": "2", - "double": "2.0", - "stringArray.0": "value1", - "stringArray.1": "value2", - "boolArray.0": "true", - "boolArray.1": "false", - "intArray.0": "1", - "intArray.1": "2", - "doubleArray.0": "1.0", - "doubleArray.1": "2.0" - ] - DDAssertJSONEqual(AnyEncodable(expectedAttributes), AnyEncodable(logs[0].attributes.userAttributes)) + XCTAssertEqual(logs.count, 0) } func testContextProviderSetActive_givenParentSpan() throws { diff --git a/Makefile b/Makefile index 8380ce6829..d2191b26d0 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,6 @@ all: dependencies templates -# The release version of `dd-sdk-swift-testing` to use for tests instrumentation. -DD_SDK_SWIFT_TESTING_VERSION = 2.3.2 -DD_DISABLE_TEST_INSTRUMENTING = false - define DD_SDK_TESTING_XCCONFIG_CI -DD_SDK_TESTING_PATH=$$(DD_SDK_TESTING_OVERRIDE_PATH:default=$$(SRCROOT)/../instrumented-tests/)\n -FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]=$$(inherited) $$(DD_SDK_TESTING_PATH)/DatadogSDKTesting.xcframework/ios-arm64_x86_64-simulator/\n -LD_RUNPATH_SEARCH_PATHS[sdk=iphonesimulator*]=$$(inherited) $$(DD_SDK_TESTING_PATH)/DatadogSDKTesting.xcframework/ios-arm64_x86_64-simulator/\n -FRAMEWORK_SEARCH_PATHS[sdk=appletvsimulator*]=$$(inherited) $$(DD_SDK_TESTING_PATH)/DatadogSDKTesting.xcframework/tvos-arm64_x86_64-simulator/\n -LD_RUNPATH_SEARCH_PATHS[sdk=appletvsimulator*]=$$(inherited) $$(DD_SDK_TESTING_PATH)/DatadogSDKTesting.xcframework/tvos-arm64_x86_64-simulator/\n -OTHER_LDFLAGS[sdk=iphonesimulator*]=$$(inherited) -framework DatadogSDKTesting\n -OTHER_LDFLAGS[sdk=appletvsimulator*]=$$(inherited) -framework DatadogSDKTesting\n DD_TEST_RUNNER=1\n DD_SDK_SWIFT_TESTING_SERVICE=dd-sdk-ios\n DD_SDK_SWIFT_TESTING_APIKEY=${DD_SDK_SWIFT_TESTING_APIKEY}\n @@ -72,12 +61,6 @@ ifeq (${ci}, true) @echo $$DD_SDK_DATADOG_XCCONFIG_CI > xcconfigs/Datadog.local.xcconfig; ifndef DD_DISABLE_TEST_INSTRUMENTING @echo $$DD_SDK_TESTING_XCCONFIG_CI > xcconfigs/DatadogSDKTesting.local.xcconfig; - @rm -rf instrumented-tests/DatadogSDKTesting.xcframework - @rm -rf instrumented-tests/DatadogSDKTesting.zip - @rm -rf instrumented-tests/LICENSE - @gh release download ${DD_SDK_SWIFT_TESTING_VERSION} -D instrumented-tests -R https://github.com/DataDog/dd-sdk-swift-testing -p "DatadogSDKTesting.zip" - @unzip -q instrumented-tests/DatadogSDKTesting.zip -d instrumented-tests - @[ -e "instrumented-tests/DatadogSDKTesting.xcframework" ] && echo "DatadogSDKTesting.xcframework - OK" || { echo "DatadogSDKTesting.xcframework - missing"; exit 1; } endif endif From aa91513f61d1a5775eaf08954c047aa70f2c5f31 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 22 Apr 2024 13:30:25 +0200 Subject: [PATCH 059/153] feat(otel-tracer): adapt otel implementation with develop branch --- DatadogCore/Tests/Datadog/LoggerTests.swift | 4 ++-- DatadogRUM/Sources/FatalErrorBuilder.swift | 1 + DatadogTrace/Sources/DDSpan.swift | 2 +- DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift | 6 +----- .../Sources/OpenTelemetry/OTelTraceId+Datadog.swift | 5 +---- DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift | 4 ++-- .../Tests/OpenTelemetry/OTelTraceId+DatadogTests.swift | 3 ++- DatadogTrace/Tests/Span/SpanEventBuilderTests.swift | 4 ++-- 8 files changed, 12 insertions(+), 17 deletions(-) diff --git a/DatadogCore/Tests/Datadog/LoggerTests.swift b/DatadogCore/Tests/Datadog/LoggerTests.swift index 5dbbcf6f6d..2e0caaf7eb 100644 --- a/DatadogCore/Tests/Datadog/LoggerTests.swift +++ b/DatadogCore/Tests/Datadog/LoggerTests.swift @@ -905,11 +905,11 @@ class LoggerTests: XCTestCase { let logMatchers = try core.waitAndReturnLogMatchers() logMatchers[0].assertValue( forKeyPath: "dd.trace_id", - equals: String(span.context.traceId.toDatadog()) + equals: span.context.traceId.toDatadog().toString(representation: .hexadecimal) ) logMatchers[0].assertValue( forKeyPath: "dd.span_id", - equals: String(span.context.spanId.toDatadog()) + equals: span.context.spanId.toDatadog().toString(representation: .decimal) ) logMatchers[1].assertNoValue(forKey: "dd.trace_id") logMatchers[1].assertNoValue(forKey: "dd.span_id") diff --git a/DatadogRUM/Sources/FatalErrorBuilder.swift b/DatadogRUM/Sources/FatalErrorBuilder.swift index 446bd224c8..9a41aa0fa0 100644 --- a/DatadogRUM/Sources/FatalErrorBuilder.swift +++ b/DatadogRUM/Sources/FatalErrorBuilder.swift @@ -76,6 +76,7 @@ internal struct FatalErrorBuilder { case .hang: return .appHang } }(), + csp: nil, handling: nil, handlingStack: nil, id: nil, diff --git a/DatadogTrace/Sources/DDSpan.swift b/DatadogTrace/Sources/DDSpan.swift index 3e7a51aee2..ee2e4da69e 100644 --- a/DatadogTrace/Sources/DDSpan.swift +++ b/DatadogTrace/Sources/DDSpan.swift @@ -125,7 +125,7 @@ internal final class DDSpan: OTSpan { if let activity = activityReference { ddTracer.removeSpan(activityReference: activity) } - sendSpan(finishTime: time, sampler: ddTracer.sampler) + sendSpan(finishTime: time, sampler: ddTracer.localTraceSampler) } // MARK: - Writing SpanEvent diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift index 7e06cd577e..c1746cb59f 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpanBuilder.swift @@ -116,11 +116,7 @@ internal class OTelSpanBuilder: OpenTelemetryApi.SpanBuilder { traceState: traceState ) - guard let core = tracer.core else { - return NOPOTelSpan() - } - - let writer = LazySpanWriteContext(core: core) + let writer = LazySpanWriteContext(featureScope: tracer.featureScope) let createdSpan = OTelSpan( attributes: attributes, diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelTraceId+Datadog.swift b/DatadogTrace/Sources/OpenTelemetry/OTelTraceId+Datadog.swift index 9f34b8cf8d..4034fbfa9a 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelTraceId+Datadog.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelTraceId+Datadog.swift @@ -12,9 +12,6 @@ extension OpenTelemetryApi.TraceId { /// Converts OpenTelemetry `TraceId` to Datadog `TraceID`. /// - Returns: Datadog `TraceID` with only higher order bits considered. func toDatadog() -> TraceID { - var data = Data(count: 16) - self.copyBytesTo(dest: &data, destOffset: 0) - let integerLiteral = UInt64(bigEndian: data.withUnsafeBytes { $0.load(as: UInt64.self) }) - return .init(integerLiteral: integerLiteral) + return .init(idHi: self.idHi, idLo: self.idLo) } } diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift index a07166c76d..8409016197 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelSpanLinkTests.swift @@ -43,7 +43,7 @@ final class OTelSpanLinkTests: XCTestCase { let encoded = try encoder.encode(spanLink) let decoded = try JSONDecoder().decode([String: AnyDecodable].self, from: encoded) - XCTAssertEqual(decoded["trace_id"]?.value as? String, "00000000000000000000000000000065") + XCTAssertEqual(decoded["trace_id"]?.value as? String, "00000000000000650000000000000066") XCTAssertEqual(decoded["span_id"]?.value as? String, "0000000000000067") XCTAssertEqual(decoded["attributes"]?.value as? [String: String], ["foo": "bar"]) XCTAssertEqual(decoded["tracestate"]?.value as? String, "foo=bar,bar=baz") @@ -72,7 +72,7 @@ final class OTelSpanLinkTests: XCTestCase { let encoded = try encoder.encode(spanLink) let decoded = try JSONDecoder().decode([String: AnyDecodable].self, from: encoded) - XCTAssertEqual(decoded["trace_id"]?.value as? String, "00000000000000000000000000000065") + XCTAssertEqual(decoded["trace_id"]?.value as? String, "00000000000000650000000000000066") XCTAssertEqual(decoded["span_id"]?.value as? String, "0000000000000067") XCTAssertNil(decoded["attributes"]?.value) XCTAssertNil(decoded["tracestate"]?.value) diff --git a/DatadogTrace/Tests/OpenTelemetry/OTelTraceId+DatadogTests.swift b/DatadogTrace/Tests/OpenTelemetry/OTelTraceId+DatadogTests.swift index f5bcc823bc..29e04f896f 100644 --- a/DatadogTrace/Tests/OpenTelemetry/OTelTraceId+DatadogTests.swift +++ b/DatadogTrace/Tests/OpenTelemetry/OTelTraceId+DatadogTests.swift @@ -15,6 +15,7 @@ class OTelTraceIdDatadogTests: XCTestCase { func testToDatadog_onlyHigherOrderBitsAreConsidered() { let otelId = TraceId.random() let ddId = otelId.toDatadog() - XCTAssertEqual(otelId.rawHigherLong, ddId.rawValue) + XCTAssertEqual(otelId.idLo, ddId.idLo) + XCTAssertEqual(otelId.idHi, ddId.idHi) } } diff --git a/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift b/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift index 25ecab72a6..e0ad6e459b 100644 --- a/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift +++ b/DatadogTrace/Tests/Span/SpanEventBuilderTests.swift @@ -605,10 +605,10 @@ class SpanEventBuilderTests: XCTestCase { // Then XCTAssertEqual(span.tags.count, 1) - let expectedTags = "[{\"trace_id\":\"00000000000000000000000000000065\",\"span_id\":\"0000000000000067\",\"tracestate\":\"foo=bar,bar=baz\",\"flags\":1,\"attributes\":{\"foo\":\"bar\"}}]" + let expectedTags = "[{\"attributes\":{\"foo\":\"bar\"},\"flags\":1,\"span_id\":\"0000000000000067\",\"trace_id\":\"00000000000000650000000000000066\",\"tracestate\":\"foo=bar,bar=baz\"}]" let actualTags = span.tags["_dd.span_links"] - DDAssertJSONStringEqual(expectedTags, actualTags!) + DDAssertJSONEqual(expectedTags, actualTags!) } // MARK: - RUM context enrichment From 218e29b617867f167f12c99ef4bd1d3f2175ad25 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 22 Apr 2024 15:17:37 +0200 Subject: [PATCH 060/153] feat(otel-tracer): use 1.6.0 version of otel library --- Cartfile | 2 +- Cartfile.resolved | 2 +- DatadogTrace.podspec | 2 +- Package.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cartfile b/Cartfile index caef0c626f..c66e01f370 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ github "microsoft/plcrashreporter" ~> 1.11.1 -binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/main/OpenTelemetryApi.json" ~> 1.9.1 +binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/main/OpenTelemetryApi.json" ~> 1.6.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index 28cfe79a44..4c54255812 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/main/OpenTelemetryApi.json" "1.9.1" +binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/main/OpenTelemetryApi.json" "1.6.0" github "microsoft/plcrashreporter" "1.11.1" diff --git a/DatadogTrace.podspec b/DatadogTrace.podspec index 52a5d12c9e..1830bb740d 100644 --- a/DatadogTrace.podspec +++ b/DatadogTrace.podspec @@ -23,5 +23,5 @@ Pod::Spec.new do |s| s.source_files = ["DatadogTrace/Sources/**/*.swift"] s.dependency 'DatadogInternal', s.version.to_s - s.dependency 'OpenTelemetrySwiftApi', '1.9.1' + s.dependency 'OpenTelemetrySwiftApi', '1.6.0' end diff --git a/Package.swift b/Package.swift index 36d53a79dd..48b341d488 100644 --- a/Package.swift +++ b/Package.swift @@ -45,7 +45,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/microsoft/plcrashreporter.git", from: "1.11.1"), - .package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", from: "1.8.0") + .package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", from: "1.6.0") ], targets: [ .target( From b17e6868ab856b316237307c5ed2ecabb871120f Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 22 Apr 2024 15:21:31 +0200 Subject: [PATCH 061/153] pin to specific version --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 48b341d488..72b8111698 100644 --- a/Package.swift +++ b/Package.swift @@ -45,7 +45,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/microsoft/plcrashreporter.git", from: "1.11.1"), - .package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", from: "1.6.0") + .package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", exact: "1.6.0") ], targets: [ .target( From 39f21037540fad0beb6d75761efbe35339882902 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 22 Apr 2024 15:35:27 +0200 Subject: [PATCH 062/153] merge leftover --- .../AppHangs/AppHangsObserver.swift | 85 ------------------- 1 file changed, 85 deletions(-) delete mode 100644 DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsObserver.swift diff --git a/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsObserver.swift b/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsObserver.swift deleted file mode 100644 index fddaa01c03..0000000000 --- a/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsObserver.swift +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 -import DatadogInternal - -internal class AppHangsObserver: RUMCommandPublisher { - enum Constants { - /// The standardized `error.message` for RUM errors describing an app hang. - static let appHangErrorMessage = "App Hang" - - /// The standardized `error.type` for RUM errors describing an app hang. - static let appHangErrorType = "AppHang" - - /// The standardized `error.stack` when a backtrace couldn't be generated. - static let appHangNoStackErrorMessage = "Stack trace was not generated because `DatadogCrashReporting` had not been enabled." - } - - /// Watchdog thread that monitors the main queue for App Hangs. - private let watchdogThread: AppHangsWatchdogThread - /// Weak reference to RUM monitor for sending App Hang events. - private(set) weak var subscriber: RUMCommandSubscriber? - - init( - appHangThreshold: TimeInterval, - observedQueue: DispatchQueue, - backtraceReporter: BacktraceReporting, - dateProvider: DateProvider, - telemetry: Telemetry - ) { - watchdogThread = AppHangsWatchdogThread( - appHangThreshold: appHangThreshold, - queue: observedQueue, - dateProvider: dateProvider, - backtraceReporter: backtraceReporter, - telemetry: telemetry - ) - watchdogThread.onHangEnded = { [weak self] appHang in - // called on watchdog thread - self?.report(appHang: appHang) - } - } - - func start() { - watchdogThread.start() - } - - func stop() { - watchdogThread.cancel() - } - - func publish(to subscriber: RUMCommandSubscriber) { - self.subscriber = subscriber - } - - private func report(appHang: AppHang) { - let command = RUMAddCurrentViewAppHangCommand( - time: appHang.date, - attributes: [:], - message: Constants.appHangErrorMessage, - type: Constants.appHangErrorType, - stack: appHang.backtrace?.stack ?? Constants.appHangNoStackErrorMessage, - threads: appHang.backtrace?.threads, - binaryImages: appHang.backtrace?.binaryImages, - isStackTraceTruncated: appHang.backtrace?.wasTruncated, - hangDuration: appHang.duration - ) - - subscriber?.process(command: command) - } -} - -extension AppHangsObserver { - /// Awaits the processing of pending app hang. - /// - /// Note: This method is synchronous and will block the caller thread, in worst case up for `appHangThreshold`. - func flush() { - let semaphore = DispatchSemaphore(value: 0) - watchdogThread.onBeforeSleep = { semaphore.signal() } - semaphore.wait() - } -} From 382bb8a25334f0113b90f89be020077e8bb13f9d Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 23 Apr 2024 10:18:54 +0200 Subject: [PATCH 063/153] update package managers test with start and finish spans --- .../carthage/App/ViewController.swift | 19 ++++++++++++++++++- .../cocoapods/App/ViewController.swift | 18 +++++++++++++++++- .../CPProject.xcodeproj/project.pbxproj | 19 ++++++++++++++++++- .../cocoapods/Podfile.src | 1 + .../spm/App/ViewController.swift | 15 ++++++++++++++- .../xcframeworks/App/ViewController.swift | 18 +++++++++++++++++- 6 files changed, 85 insertions(+), 5 deletions(-) diff --git a/dependency-manager-tests/carthage/App/ViewController.swift b/dependency-manager-tests/carthage/App/ViewController.swift index c84b0fe3fd..29cbd2ddf1 100644 --- a/dependency-manager-tests/carthage/App/ViewController.swift +++ b/dependency-manager-tests/carthage/App/ViewController.swift @@ -14,6 +14,7 @@ import DatadogCrashReporting #if os(iOS) import DatadogSessionReplay #endif +import OpenTelemetryApi internal class ViewController: UIViewController { private var logger: LoggerProtocol! // swiftlint:disable:this implicitly_unwrapped_optional @@ -44,8 +45,24 @@ internal class ViewController: UIViewController { // Trace APIs must be visible: Trace.enable() + // Register tracer provider + OpenTelemetry.registerTracerProvider( + tracerProvider: OTelTracerProvider() + ) + logger.info("It works") - _ = Tracer.shared().startSpan(operationName: "this too") + + let otSpan = Tracer.shared().startSpan(operationName: "OT Span") + otSpan.finish() + + // otel tracer + let tracer = OpenTelemetry + .instance + .tracerProvider + .get(instrumentationName: "", instrumentationVersion: nil) + let otelSpan = tracer.spanBuilder(spanName: "OTel span").startSpan() + otelSpan.end() + #if os(iOS) // Session Replay API must be visible: diff --git a/dependency-manager-tests/cocoapods/App/ViewController.swift b/dependency-manager-tests/cocoapods/App/ViewController.swift index e14ceb32ca..80617a6935 100644 --- a/dependency-manager-tests/cocoapods/App/ViewController.swift +++ b/dependency-manager-tests/cocoapods/App/ViewController.swift @@ -14,6 +14,7 @@ import DatadogCrashReporting import DatadogSessionReplay // it should compile for iOS and tvOS, but APIs are only available on iOS import DatadogObjc import Alamofire +import OpenTelemetryApi internal class ViewController: UIViewController { private var logger: LoggerProtocol! // swiftlint:disable:this implicitly_unwrapped_optional @@ -44,9 +45,24 @@ internal class ViewController: UIViewController { // Trace APIs must be visible: Trace.enable() + // Register tracer provider + OpenTelemetry.registerTracerProvider( + tracerProvider: OTelTracerProvider() + ) + logger.info("It works") - _ = Tracer.shared().startSpan(operationName: "this too") + let otSpan = Tracer.shared().startSpan(operationName: "OT Span") + otSpan.finish() + + // otel tracer + let tracer = OpenTelemetry + .instance + .tracerProvider + .get(instrumentationName: "", instrumentationVersion: nil) + let otelSpan = tracer.spanBuilder(spanName: "OTel span").startSpan() + otelSpan.end() + #if os(iOS) SessionReplay.enable(with: .init(replaySampleRate: 0)) #endif diff --git a/dependency-manager-tests/cocoapods/CPProject.xcodeproj/project.pbxproj b/dependency-manager-tests/cocoapods/CPProject.xcodeproj/project.pbxproj index e166bc8509..f7443e781d 100644 --- a/dependency-manager-tests/cocoapods/CPProject.xcodeproj/project.pbxproj +++ b/dependency-manager-tests/cocoapods/CPProject.xcodeproj/project.pbxproj @@ -448,7 +448,7 @@ 61B8C30026E0E278006EDF53 /* Frameworks */, 61B8C30126E0E278006EDF53 /* Resources */, 31384BB2056A865F3ED23F38 /* [CP] Embed Pods Frameworks */, - A242A29C234C1DB8379AA6B1 /* [CP] Copy Pods Resources */, + CE173FAE86F3C55AD4217B8F /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -920,6 +920,23 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-App Static tvOS/Pods-Common-App Static tvOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + CE173FAE86F3C55AD4217B8F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Dynamic iOS/Pods-Common-App Dynamic iOS-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-App Dynamic iOS/Pods-Common-App Dynamic iOS-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-App Dynamic iOS/Pods-Common-App Dynamic iOS-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; D235937827C8EB0500BF32D7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/dependency-manager-tests/cocoapods/Podfile.src b/dependency-manager-tests/cocoapods/Podfile.src index da859a6469..fc2dbb0d17 100644 --- a/dependency-manager-tests/cocoapods/Podfile.src +++ b/dependency-manager-tests/cocoapods/Podfile.src @@ -1,4 +1,5 @@ abstract_target 'Common' do + pod 'OpenTelemetrySwiftApi', '1.6.0' pod 'DatadogInternal', :git => 'GIT_REMOTE', :GIT_REFERENCE pod 'DatadogCore', :git => 'GIT_REMOTE', :GIT_REFERENCE pod 'DatadogLogs', :git => 'GIT_REMOTE', :GIT_REFERENCE diff --git a/dependency-manager-tests/spm/App/ViewController.swift b/dependency-manager-tests/spm/App/ViewController.swift index a1b1e258c4..03f9610de3 100644 --- a/dependency-manager-tests/spm/App/ViewController.swift +++ b/dependency-manager-tests/spm/App/ViewController.swift @@ -12,6 +12,7 @@ import DatadogRUM import DatadogCrashReporting import DatadogSessionReplay // it should compile for iOS and tvOS, but APIs are only available on iOS import DatadogObjc +import OpenTelemetryApi internal class ViewController: UIViewController { private var logger: LoggerProtocol! // swiftlint:disable:this implicitly_unwrapped_optional @@ -41,9 +42,21 @@ internal class ViewController: UIViewController { // Trace APIs must be visible: Trace.enable() + OpenTelemetry.registerTracerProvider( + tracerProvider: OTelTracerProvider() + ) logger.info("It works") - _ = Tracer.shared().startSpan(operationName: "this too") + let otSpan = Tracer.shared().startSpan(operationName: "OT Span") + otSpan.finish() + + // otel tracer + let tracer = OpenTelemetry + .instance + .tracerProvider + .get(instrumentationName: "", instrumentationVersion: nil) + let otelSpan = tracer.spanBuilder(spanName: "OTel span").startSpan() + otelSpan.end() #if os(iOS) // Session Replay API must be visible: diff --git a/dependency-manager-tests/xcframeworks/App/ViewController.swift b/dependency-manager-tests/xcframeworks/App/ViewController.swift index 29a41b0a9d..b5501cc721 100644 --- a/dependency-manager-tests/xcframeworks/App/ViewController.swift +++ b/dependency-manager-tests/xcframeworks/App/ViewController.swift @@ -14,6 +14,7 @@ import DatadogCrashReporting #if os(iOS) import DatadogSessionReplay #endif +import OpenTelemetryApi internal class ViewController: UIViewController { private var logger: LoggerProtocol! // swiftlint:disable:this implicitly_unwrapped_optional @@ -44,8 +45,23 @@ internal class ViewController: UIViewController { // Trace APIs must be visible: Trace.enable() + // Register tracer provider + OpenTelemetry.registerTracerProvider( + tracerProvider: OTelTracerProvider() + ) + logger.info("It works") - _ = Tracer.shared().startSpan(operationName: "this too") + + let otSpan = Tracer.shared().startSpan(operationName: "OT Span") + otSpan.finish() + + // otel tracer + let tracer = OpenTelemetry + .instance + .tracerProvider + .get(instrumentationName: "", instrumentationVersion: nil) + let otelSpan = tracer.spanBuilder(spanName: "OTel Span").startSpan() + otelSpan.end() #if os(iOS) SessionReplay.enable(with: .init(replaySampleRate: 0)) From 5e71a572a117f30421d50bf0d579f7f717fc64c8 Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Tue, 9 Apr 2024 15:40:06 -0400 Subject: [PATCH 064/153] feat: Support adding binary images to non-crashes This adds a cross platform attribute `_dd.error.include_binary_images` that will attach binary images to error reports by creating a backtrace and pulling the binary images from it. It contains a TODO to replace backtrace generation with a different method of binary image attachment at a future time. --- DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift | 4 ++++ DatadogInternal/Sources/Attributes/Attributes.swift | 3 +++ DatadogLogs/Sources/Feature/MessageReceivers.swift | 1 + DatadogLogs/Sources/Log/LogEventBuilder.swift | 3 +++ DatadogLogs/Sources/Log/LogEventEncoder.swift | 6 ++++++ DatadogLogs/Sources/RemoteLogger.swift | 8 ++++++++ DatadogLogs/Tests/Log/LogEventBuilderTests.swift | 6 ++++++ DatadogRUM/Sources/Feature/RUMFeature.swift | 1 + .../RUMMonitor/Scopes/RUMScopeDependencies.swift | 1 + .../Sources/RUMMonitor/Scopes/RUMViewScope.swift | 11 ++++++++++- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 4 ++++ 11 files changed, 47 insertions(+), 1 deletion(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index 78bd435fd5..09e8841401 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -713,6 +713,7 @@ extension RUMScopeDependencies { firstPartyHosts: FirstPartyHosts = .init([:]), eventBuilder: RUMEventBuilder = RUMEventBuilder(eventsMapper: .mockNoOp()), rumUUIDGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), + backtraceReporter: BacktraceReporting = BacktraceReporterMock(backtrace: nil), ciTest: RUMCITest? = nil, syntheticsTest: RUMSyntheticsTest? = nil, vitalsReaders: VitalsReaders? = nil, @@ -728,6 +729,7 @@ extension RUMScopeDependencies { firstPartyHosts: firstPartyHosts, eventBuilder: eventBuilder, rumUUIDGenerator: rumUUIDGenerator, + backtraceReporter: backtraceReporter, ciTest: ciTest, syntheticsTest: syntheticsTest, vitalsReaders: vitalsReaders, @@ -745,6 +747,7 @@ extension RUMScopeDependencies { firstPartyHosts: FirstPartyHosts? = nil, eventBuilder: RUMEventBuilder? = nil, rumUUIDGenerator: RUMUUIDGenerator? = nil, + backtraceReporter: BacktraceReporting? = nil, ciTest: RUMCITest? = nil, syntheticsTest: RUMSyntheticsTest? = nil, vitalsReaders: VitalsReaders? = nil, @@ -760,6 +763,7 @@ extension RUMScopeDependencies { firstPartyHosts: firstPartyHosts ?? self.firstPartyHosts, eventBuilder: eventBuilder ?? self.eventBuilder, rumUUIDGenerator: rumUUIDGenerator ?? self.rumUUIDGenerator, + backtraceReporter: backtraceReporter ?? self.backtraceReporter, ciTest: ciTest ?? self.ciTest, syntheticsTest: syntheticsTest ?? self.syntheticsTest, vitalsReaders: vitalsReaders ?? self.vitalsReaders, diff --git a/DatadogInternal/Sources/Attributes/Attributes.swift b/DatadogInternal/Sources/Attributes/Attributes.swift index 74457c09f2..5ee57a30f0 100644 --- a/DatadogInternal/Sources/Attributes/Attributes.swift +++ b/DatadogInternal/Sources/Attributes/Attributes.swift @@ -153,6 +153,9 @@ public struct CrossPlatformAttributes { /// Override the `source_type` of errors reported by the native crash handler. This is used on /// platforms that can supply extra steps or information on a native crash (such as Unity's IL2CPP) public static let nativeSourceType = "_dd.native_source_type" + + /// Add "binary images" to the reportted error to assist with symbolication. Used by Unity for IL2CPP symbolicaiton + public static let includeBinaryImages = "_dd.error.include_binary_images" } public struct LaunchArguments { diff --git a/DatadogLogs/Sources/Feature/MessageReceivers.swift b/DatadogLogs/Sources/Feature/MessageReceivers.swift index 8377c83e7b..a74bc715db 100644 --- a/DatadogLogs/Sources/Feature/MessageReceivers.swift +++ b/DatadogLogs/Sources/Feature/MessageReceivers.swift @@ -69,6 +69,7 @@ internal struct LogMessageReceiver: FeatureMessageReceiver { message: log.message, error: log.error, errorFingerprint: nil, + binaryImages: nil, attributes: .init( userAttributes: log.userAttributes ?? [:], internalAttributes: log.internalAttributes diff --git a/DatadogLogs/Sources/Log/LogEventBuilder.swift b/DatadogLogs/Sources/Log/LogEventBuilder.swift index 34c67853e5..89bd6a72c2 100644 --- a/DatadogLogs/Sources/Log/LogEventBuilder.swift +++ b/DatadogLogs/Sources/Log/LogEventBuilder.swift @@ -33,6 +33,8 @@ internal struct LogEventBuilder { /// - level: the severity level of the log /// - message: the message of the log /// - error: eventual error to associate with log + /// - errorFingerprint: the custom fingerprint for this log + /// - binaryImages: binary images needed to symbolicate the error /// - attributes: attributes to associate with log (user and internal attributes, separate) /// - tags: tags to associate with log /// - context: SDK context from the moment of creating log @@ -46,6 +48,7 @@ internal struct LogEventBuilder { message: String, error: DDError?, errorFingerprint: String?, + binaryImages: [BinaryImage]?, attributes: LogEvent.Attributes, tags: Set, context: DatadogContext, diff --git a/DatadogLogs/Sources/Log/LogEventEncoder.swift b/DatadogLogs/Sources/Log/LogEventEncoder.swift index 14396b49bc..0d1942e392 100644 --- a/DatadogLogs/Sources/Log/LogEventEncoder.swift +++ b/DatadogLogs/Sources/Log/LogEventEncoder.swift @@ -75,6 +75,8 @@ public struct LogEvent: Encodable { public var sourceType: String = "ios" /// The custom fingerprint supplied for this error, if any public var fingerprint: String? + /// Binary images needed to decode the provided stack (if any) + public var binaryImages: [BinaryImage]? } /// Device information. @@ -174,6 +176,7 @@ internal struct LogEventEncoder { case errorStack = "error.stack" case errorSourceType = "error.source_type" case errorFingerprint = "error.fingerprint" + case errorBinaryImages = "error.binary_images" // MARK: - Application info @@ -237,6 +240,9 @@ internal struct LogEventEncoder { try container.encode(someError.stack, forKey: .errorStack) try container.encode(someError.sourceType, forKey: .errorSourceType) try container.encode(someError.fingerprint, forKey: .errorFingerprint) + if let binaryImages = someError.binaryImages { + try container.encode(someError.binaryImages, forKey: .errorBinaryImages) + } } // Encode logger info diff --git a/DatadogLogs/Sources/RemoteLogger.swift b/DatadogLogs/Sources/RemoteLogger.swift index e3803d7aff..d76e82f526 100644 --- a/DatadogLogs/Sources/RemoteLogger.swift +++ b/DatadogLogs/Sources/RemoteLogger.swift @@ -151,6 +151,13 @@ internal final class RemoteLogger: LoggerProtocol { } } + // When binary images are requested, add them + var binaryImages: [BinaryImage]? + if let addBinaryImages = logAttributes?.removeValue(forKey: CrossPlatformAttributes.includeBinaryImages) { + // Don't try to get binary images if we already have them. + binaryImages = try? self.core?.backtraceReporter.generateBacktrace()?.binaryImages + } + let builder = LogEventBuilder( service: self.configuration.service ?? context.service, loggerName: self.configuration.name, @@ -164,6 +171,7 @@ internal final class RemoteLogger: LoggerProtocol { message: message, error: error, errorFingerprint: errorFingerprint, + binaryImages: binaryImages, attributes: .init( userAttributes: combinedAttributes, internalAttributes: internalAttributes diff --git a/DatadogLogs/Tests/Log/LogEventBuilderTests.swift b/DatadogLogs/Tests/Log/LogEventBuilderTests.swift index f8d9de0ca1..df8c6c1dd5 100644 --- a/DatadogLogs/Tests/Log/LogEventBuilderTests.swift +++ b/DatadogLogs/Tests/Log/LogEventBuilderTests.swift @@ -45,6 +45,7 @@ class LogEventBuilderTests: XCTestCase { message: randomMessage, error: randomError, errorFingerprint: randomErrorFingerprint, + binaryImages: .mockAny(), attributes: randomAttributes, tags: randomTags, context: .mockWith( @@ -137,6 +138,7 @@ class LogEventBuilderTests: XCTestCase { message: .mockAny(), error: .mockAny(), errorFingerprint: .mockAny(), + binaryImages: .mockAny(), attributes: .mockAny(), tags: .mockAny(), context: randomSDKContext, @@ -187,6 +189,7 @@ class LogEventBuilderTests: XCTestCase { message: .mockAny(), error: .mockAny(), errorFingerprint: .mockAny(), + binaryImages: .mockAny(), attributes: .mockAny(), tags: .mockAny(), context: randomSDKContext, @@ -214,6 +217,7 @@ class LogEventBuilderTests: XCTestCase { message: .mockAny(), error: .mockAny(), errorFingerprint: .mockAny(), + binaryImages: .mockAny(), attributes: .mockAny(), tags: .mockAny(), context: .mockWith( @@ -255,6 +259,7 @@ class LogEventBuilderTests: XCTestCase { message: "original message", error: .mockAny(), errorFingerprint: .mockAny(), + binaryImages: .mockAny(), attributes: .mockAny(), tags: .mockAny(), context: .mockAny(), @@ -289,6 +294,7 @@ class LogEventBuilderTests: XCTestCase { message: .mockAny(), error: .mockAny(), errorFingerprint: .mockAny(), + binaryImages: .mockAny(), attributes: .mockAny(), tags: .mockAny(), context: .mockAny(), diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index d6e4fd0680..18eb267621 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -55,6 +55,7 @@ internal final class RUMFeature: DatadogRemoteFeature { eventsMapper: eventsMapper ), rumUUIDGenerator: configuration.uuidGenerator, + backtraceReporter: core.backtraceReporter, ciTest: configuration.ciTestExecutionID.map { RUMCITest(testExecutionId: $0) }, syntheticsTest: { if let testId = configuration.syntheticsTestId, let resultId = configuration.syntheticsResultId { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift index f0258a1486..5f5c5c3637 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift @@ -36,6 +36,7 @@ internal struct RUMScopeDependencies { let firstPartyHosts: FirstPartyHosts? let eventBuilder: RUMEventBuilder let rumUUIDGenerator: RUMUUIDGenerator + let backtraceReporter: BacktraceReporting? /// Integration with CIApp tests. It contains the CIApp test context when active. let ciTest: RUMCITest? let syntheticsTest: RUMSyntheticsTest? diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index 1a98a5e1d2..caef19bc8f 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -553,6 +553,15 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { var commandAttributes = command.attributes let errorFingerprint = commandAttributes.removeValue(forKey: RUM.Attributes.errorFingerprint) as? String + var binaryImages = command.binaryImages?.compactMap { $0.toRUMDataFormat } + if let addBinaryImages = commandAttributes.removeValue(forKey: CrossPlatformAttributes.includeBinaryImages) { + // Don't try to get binary images if we already have them. + if binaryImages == nil { + // TODO: RUM-000 Replace full backtrace reporter with simpler binary image fetcher + binaryImages = try? dependencies.backtraceReporter?.generateBacktrace()?.binaryImages.compactMap { $0.toRUMDataFormat } + } + } + let errorEvent = RUMErrorEvent( dd: .init( browserSdkVersion: nil, @@ -576,7 +585,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { device: .init(context: context, telemetry: dependencies.telemetry), display: nil, error: .init( - binaryImages: command.binaryImages?.compactMap { $0.toRUMDataFormat }, + binaryImages: binaryImages, category: command.category, causes: nil, fingerprint: errorFingerprint, diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index 6ebaef88d3..057b72170d 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -767,6 +767,7 @@ extension RUMScopeDependencies { firstPartyHosts: FirstPartyHosts = .init([:]), eventBuilder: RUMEventBuilder = RUMEventBuilder(eventsMapper: .mockNoOp()), rumUUIDGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), + backtraceReporter: BacktraceReporting = BacktraceReporterMock(backtrace: nil), ciTest: RUMCITest? = nil, syntheticsTest: RUMSyntheticsTest? = nil, vitalsReaders: VitalsReaders? = nil, @@ -782,6 +783,7 @@ extension RUMScopeDependencies { firstPartyHosts: firstPartyHosts, eventBuilder: eventBuilder, rumUUIDGenerator: rumUUIDGenerator, + backtraceReporter: backtraceReporter, ciTest: ciTest, syntheticsTest: syntheticsTest, vitalsReaders: vitalsReaders, @@ -799,6 +801,7 @@ extension RUMScopeDependencies { firstPartyHosts: FirstPartyHosts? = nil, eventBuilder: RUMEventBuilder? = nil, rumUUIDGenerator: RUMUUIDGenerator? = nil, + backtraceReporter: BacktraceReporting? = nil, ciTest: RUMCITest? = nil, syntheticsTest: RUMSyntheticsTest? = nil, vitalsReaders: VitalsReaders? = nil, @@ -814,6 +817,7 @@ extension RUMScopeDependencies { firstPartyHosts: firstPartyHosts ?? self.firstPartyHosts, eventBuilder: eventBuilder ?? self.eventBuilder, rumUUIDGenerator: rumUUIDGenerator ?? self.rumUUIDGenerator, + backtraceReporter: backtraceReporter ?? self.backtraceReporter, ciTest: ciTest ?? self.ciTest, syntheticsTest: syntheticsTest ?? self.syntheticsTest, vitalsReaders: vitalsReaders ?? self.vitalsReaders, From 00fd8f7366e80cb2fed92bcc883f41026cdaed9e Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Fri, 12 Apr 2024 16:14:47 -0400 Subject: [PATCH 065/153] Add test and fix issues in log binary image attachement --- .../Tests/Datadog/Mocks/LogsMocks.swift | 6 +- .../Models/CrashReporting/BinaryImage.swift | 2 + DatadogLogs/Sources/Feature/LogsFeature.swift | 12 +++- DatadogLogs/Sources/Log/LogEventBuilder.swift | 3 +- DatadogLogs/Sources/Log/LogEventEncoder.swift | 2 +- DatadogLogs/Sources/RemoteLogger.swift | 20 ++++-- .../Tests/Mocks/LoggingFeatureMocks.swift | 6 +- DatadogLogs/Tests/RemoteLoggerTests.swift | 32 +++++++++ .../Scopes/RUMScopeDependencies.swift | 2 + .../RUMMonitor/Scopes/RUMViewScope.swift | 4 +- .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 68 +++++++++++++++++++ 11 files changed, 141 insertions(+), 16 deletions(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/LogsMocks.swift b/DatadogCore/Tests/Datadog/Mocks/LogsMocks.swift index 080a7e8bd6..0ef6a290df 100644 --- a/DatadogCore/Tests/Datadog/Mocks/LogsMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/LogsMocks.swift @@ -26,13 +26,15 @@ extension LogsFeature { logEventMapper: LogEventMapper? = nil, requestBuilder: FeatureRequestBuilder = RequestBuilder(), messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver(), - dateProvider: DateProvider = SystemDateProvider() + dateProvider: DateProvider = SystemDateProvider(), + backtraceReporter: BacktraceReporting? = nil ) -> Self { return .init( logEventMapper: logEventMapper, requestBuilder: requestBuilder, messageReceiver: messageReceiver, - dateProvider: dateProvider + dateProvider: dateProvider, + backtraceReporter: backtraceReporter ) } } diff --git a/DatadogInternal/Sources/Models/CrashReporting/BinaryImage.swift b/DatadogInternal/Sources/Models/CrashReporting/BinaryImage.swift index e22ef97166..7d53e3be5d 100644 --- a/DatadogInternal/Sources/Models/CrashReporting/BinaryImage.swift +++ b/DatadogInternal/Sources/Models/CrashReporting/BinaryImage.swift @@ -42,3 +42,5 @@ public struct BinaryImage: Codable, PassthroughAnyCodable { case maxAddress = "max_address" } } + +extension BinaryImage: Equatable {} diff --git a/DatadogLogs/Sources/Feature/LogsFeature.swift b/DatadogLogs/Sources/Feature/LogsFeature.swift index 5e5ab84279..ffd1fa1794 100644 --- a/DatadogLogs/Sources/Feature/LogsFeature.swift +++ b/DatadogLogs/Sources/Feature/LogsFeature.swift @@ -16,6 +16,8 @@ internal struct LogsFeature: DatadogRemoteFeature { let logEventMapper: LogEventMapper? + let backtraceReporter: BacktraceReporting? + @ReadWriteLock private var attributes: [String: Encodable] = [:] @@ -26,7 +28,8 @@ internal struct LogsFeature: DatadogRemoteFeature { logEventMapper: LogEventMapper?, dateProvider: DateProvider, customIntakeURL: URL? = nil, - telemetry: Telemetry = NOPTelemetry() + telemetry: Telemetry = NOPTelemetry(), + backtraceReporter: BacktraceReporting? = nil ) { self.init( logEventMapper: logEventMapper, @@ -39,7 +42,8 @@ internal struct LogsFeature: DatadogRemoteFeature { CrashLogReceiver(dateProvider: dateProvider, logEventMapper: logEventMapper), WebViewLogReceiver() ), - dateProvider: dateProvider + dateProvider: dateProvider, + backtraceReporter: backtraceReporter ) } @@ -47,12 +51,14 @@ internal struct LogsFeature: DatadogRemoteFeature { logEventMapper: LogEventMapper?, requestBuilder: FeatureRequestBuilder, messageReceiver: FeatureMessageReceiver, - dateProvider: DateProvider + dateProvider: DateProvider, + backtraceReporter: BacktraceReporting? ) { self.logEventMapper = logEventMapper self.requestBuilder = requestBuilder self.messageReceiver = messageReceiver self.dateProvider = dateProvider + self.backtraceReporter = backtraceReporter } internal func addAttribute(forKey key: AttributeKey, value: AttributeValue) { diff --git a/DatadogLogs/Sources/Log/LogEventBuilder.swift b/DatadogLogs/Sources/Log/LogEventBuilder.swift index 89bd6a72c2..0d1361df0e 100644 --- a/DatadogLogs/Sources/Log/LogEventBuilder.swift +++ b/DatadogLogs/Sources/Log/LogEventBuilder.swift @@ -67,7 +67,8 @@ internal struct LogEventBuilder { message: $0.message, stack: $0.stack, sourceType: $0.sourceType, - fingerprint: errorFingerprint + fingerprint: errorFingerprint, + binaryImages: binaryImages ) }, serviceName: service, diff --git a/DatadogLogs/Sources/Log/LogEventEncoder.swift b/DatadogLogs/Sources/Log/LogEventEncoder.swift index 0d1942e392..1db44a5459 100644 --- a/DatadogLogs/Sources/Log/LogEventEncoder.swift +++ b/DatadogLogs/Sources/Log/LogEventEncoder.swift @@ -241,7 +241,7 @@ internal struct LogEventEncoder { try container.encode(someError.sourceType, forKey: .errorSourceType) try container.encode(someError.fingerprint, forKey: .errorFingerprint) if let binaryImages = someError.binaryImages { - try container.encode(someError.binaryImages, forKey: .errorBinaryImages) + try container.encode(binaryImages, forKey: .errorBinaryImages) } } diff --git a/DatadogLogs/Sources/RemoteLogger.swift b/DatadogLogs/Sources/RemoteLogger.swift index d76e82f526..035a9b15ee 100644 --- a/DatadogLogs/Sources/RemoteLogger.swift +++ b/DatadogLogs/Sources/RemoteLogger.swift @@ -100,7 +100,9 @@ internal final class RemoteLogger: LoggerProtocol { return } - let globalAttributes = self.core?.get(feature: LogsFeature.self)?.getAttributes() + let logsFeature = self.core?.get(feature: LogsFeature.self) + + let globalAttributes = logsFeature?.getAttributes() // on user thread: let date = dateProvider.now @@ -111,6 +113,7 @@ internal final class RemoteLogger: LoggerProtocol { var logAttributes = attributes let isCrash = logAttributes?.removeValue(forKey: CrossPlatformAttributes.errorLogIsCrash) as? Bool ?? false let errorFingerprint = logAttributes?.removeValue(forKey: Logs.Attributes.errorFingerprint) as? String + let addBinaryImages = logAttributes?.removeValue(forKey: CrossPlatformAttributes.includeBinaryImages) as? Bool ?? false let userAttributes = self.attributes .merging(logAttributes ?? [:]) { $1 } // prefer message attributes let combinedAttributes: [String: any Encodable] @@ -153,9 +156,9 @@ internal final class RemoteLogger: LoggerProtocol { // When binary images are requested, add them var binaryImages: [BinaryImage]? - if let addBinaryImages = logAttributes?.removeValue(forKey: CrossPlatformAttributes.includeBinaryImages) { - // Don't try to get binary images if we already have them. - binaryImages = try? self.core?.backtraceReporter.generateBacktrace()?.binaryImages + if addBinaryImages { + // TODO: RUM-4072 Replace full backtrace reporter with simpler binary image fetcher + binaryImages = try? logsFeature?.backtraceReporter?.generateBacktrace()?.binaryImages } let builder = LogEventBuilder( @@ -186,6 +189,13 @@ internal final class RemoteLogger: LoggerProtocol { return } + // Bit of a hack - add the `includeBinaryImages` attribute back in if it was in the original log + // TODO: RUM-4072 This double generates a backtrace - this should be optimized to use cached binary images instead + var rumAttributes: [String: Encodable] = combinedAttributes + if addBinaryImages { + rumAttributes[CrossPlatformAttributes.includeBinaryImages] = true + } + self.core?.send( message: .baggage( key: ErrorMessage.key, @@ -193,7 +203,7 @@ internal final class RemoteLogger: LoggerProtocol { message: log.error?.message ?? log.message, type: log.error?.kind, stack: log.error?.stack, - attributes: .init(combinedAttributes) + attributes: .init(rumAttributes) ) ) ) diff --git a/DatadogLogs/Tests/Mocks/LoggingFeatureMocks.swift b/DatadogLogs/Tests/Mocks/LoggingFeatureMocks.swift index 865fb5fb31..8040d4f5e3 100644 --- a/DatadogLogs/Tests/Mocks/LoggingFeatureMocks.swift +++ b/DatadogLogs/Tests/Mocks/LoggingFeatureMocks.swift @@ -42,13 +42,15 @@ extension LogsFeature { sampler: Sampler = .mockKeepAll(), requestBuilder: FeatureRequestBuilder = RequestBuilder(), messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver(), - dateProvider: DateProvider = SystemDateProvider() + dateProvider: DateProvider = SystemDateProvider(), + backtraceReporter: BacktraceReporting = BacktraceReporterMock(backtrace: nil) ) -> Self { return .init( logEventMapper: logEventMapper, requestBuilder: requestBuilder, messageReceiver: messageReceiver, - dateProvider: dateProvider + dateProvider: dateProvider, + backtraceReporter: backtraceReporter ) } } diff --git a/DatadogLogs/Tests/RemoteLoggerTests.swift b/DatadogLogs/Tests/RemoteLoggerTests.swift index b799049e16..7456d6f2e6 100644 --- a/DatadogLogs/Tests/RemoteLoggerTests.swift +++ b/DatadogLogs/Tests/RemoteLoggerTests.swift @@ -260,6 +260,38 @@ class RemoteLoggerTests: XCTestCase { XCTAssertEqual(log.error?.fingerprint, randomErrorFingerprint) } + func testWhenAttributesContainIncludeBinaryImages_itAddsBinaryImagesToLogEvent() throws { + let stubBacktrace: BacktraceReport = .mockRandom() + let logsFeature = LogsFeature.mockWith( + backtraceReporter: BacktraceReporterMock(backtrace: stubBacktrace) + ) + let core = SingleFeatureCoreMock( + feature: logsFeature, + expectation: expectation(description: "Send log") + ) + let logger = RemoteLogger( + core: core, + configuration: .mockAny(), + dateProvider: RelativeDateProvider(), + rumContextIntegration: false, + activeSpanIntegration: false + ) + + // When + logger.error("Information message", error: ErrorMock(), attributes: [CrossPlatformAttributes.includeBinaryImages: true]) + + // Then + waitForExpectations(timeout: 0.5, handler: nil) + + let logs = core.events(ofType: LogEvent.self) + XCTAssertEqual(logs.count, 1) + + let log = try XCTUnwrap(logs.first) + XCTAssertNil(log.attributes.userAttributes[CrossPlatformAttributes.includeBinaryImages]) + XCTAssertNotNil(log.error?.binaryImages) + XCTAssertEqual(log.error?.binaryImages, stubBacktrace.binaryImages) + } + // MARK: - RUM Integration func testWhenRUMIntegrationIsEnabled_itSendsLogWithRUMContext() throws { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift index 5f5c5c3637..38add82707 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift @@ -58,6 +58,7 @@ internal struct RUMScopeDependencies { firstPartyHosts: FirstPartyHosts?, eventBuilder: RUMEventBuilder, rumUUIDGenerator: RUMUUIDGenerator, + backtraceReporter: BacktraceReporting?, ciTest: RUMCITest?, syntheticsTest: RUMSyntheticsTest?, vitalsReaders: VitalsReaders?, @@ -72,6 +73,7 @@ internal struct RUMScopeDependencies { self.firstPartyHosts = firstPartyHosts self.eventBuilder = eventBuilder self.rumUUIDGenerator = rumUUIDGenerator + self.backtraceReporter = backtraceReporter self.ciTest = ciTest self.syntheticsTest = syntheticsTest self.vitalsReaders = vitalsReaders diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index caef19bc8f..3bc9eb304a 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -554,10 +554,10 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { let errorFingerprint = commandAttributes.removeValue(forKey: RUM.Attributes.errorFingerprint) as? String var binaryImages = command.binaryImages?.compactMap { $0.toRUMDataFormat } - if let addBinaryImages = commandAttributes.removeValue(forKey: CrossPlatformAttributes.includeBinaryImages) { + if commandAttributes.removeValue(forKey: CrossPlatformAttributes.includeBinaryImages) != nil { // Don't try to get binary images if we already have them. if binaryImages == nil { - // TODO: RUM-000 Replace full backtrace reporter with simpler binary image fetcher + // TODO: RUM-4072 Replace full backtrace reporter with simpler binary image fetcher binaryImages = try? dependencies.backtraceReporter?.generateBacktrace()?.binaryImages.compactMap { $0.toRUMDataFormat } } } diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift index 0634533f61..a55bd179bd 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -1557,6 +1557,74 @@ class RUMViewScopeTests: XCTestCase { XCTAssertNil(error.context!.contextInfo[RUM.Attributes.errorFingerprint]) } + func testGivenStartedView_whenErrorWithIncludeBinaryImagesAttributesIsAdded_itAddsBinaryImagesToError() throws { + // Given + let mockBacktrace: BacktraceReport = .mockRandom() + let hasReplay: Bool = .mockRandom() + var context = self.context + context.baggages = try .mockSessionReplayAttributes(hasReplay: hasReplay) + var currentTime: Date = .mockDecember15th2019At10AMUTC() + + let scope = RUMViewScope( + isInitialView: .mockRandom(), + parent: parent, + dependencies: .mockWith( + backtraceReporter: BacktraceReporterMock(backtrace: mockBacktrace) + ), + identity: .mockViewIdentifier(), + path: "UIViewController", + name: "ViewName", + attributes: [:], + customTimings: [:], + startTime: currentTime, + serverTimeOffset: .zero + ) + + XCTAssertTrue( + scope.process( + command: RUMStartViewCommand.mockWith(time: currentTime, attributes: [:], identity: .mockViewIdentifier()), + context: context, + writer: writer + ) + ) + + currentTime.addTimeInterval(1) + + // When + XCTAssertTrue( + scope.process( + command: RUMAddCurrentViewErrorCommand.mockWithErrorMessage( + time: currentTime, + message: "view error", + source: .source, + stack: nil, + attributes: [ + CrossPlatformAttributes.includeBinaryImages: true + ] + ), + context: context, + writer: writer + ) + ) + + // Then + let error = try XCTUnwrap(writer.events(ofType: RUMErrorEvent.self).last) + XCTAssertNil(error.context?.contextInfo[CrossPlatformAttributes.includeBinaryImages]) + XCTAssertNotNil(error.error.binaryImages) + XCTAssertEqual(error.error.binaryImages?.count, mockBacktrace.binaryImages.count) + for i in 0.. Date: Wed, 1 May 2024 16:54:01 -0400 Subject: [PATCH 066/153] Refactor binary images for non-crashes Use a separate BinaryImage structure for encoding in Logs. Send BinaryImages through feature baggage instead of re-fetching when Logs trigger a RUM error. --- DatadogLogs/Sources/Feature/Baggages.swift | 4 +++ DatadogLogs/Sources/Log/LogEventBuilder.swift | 19 +++++++++++- DatadogLogs/Sources/Log/LogEventEncoder.swift | 30 +++++++++++++++++++ DatadogLogs/Sources/RemoteLogger.swift | 11 ++----- DatadogLogs/Tests/RemoteLoggerTests.swift | 12 +++++++- .../Integrations/ErrorMessageReceiver.swift | 11 +++++-- .../Sources/RUMMonitorProtocol+Internal.swift | 26 ++++++++++++++++ .../ErrorMessageReceiverTests.swift | 26 +++++++++++++--- 8 files changed, 122 insertions(+), 17 deletions(-) diff --git a/DatadogLogs/Sources/Feature/Baggages.swift b/DatadogLogs/Sources/Feature/Baggages.swift index 1654768cbb..0f820e1d5f 100644 --- a/DatadogLogs/Sources/Feature/Baggages.swift +++ b/DatadogLogs/Sources/Feature/Baggages.swift @@ -10,6 +10,8 @@ import DatadogInternal /// Error message sent from Logs on the message-bus. internal struct ErrorMessage: Encodable { static let key = "error" + /// The time of the log + let time: Date /// The Log error message let message: String /// The Log error type @@ -20,6 +22,8 @@ internal struct ErrorMessage: Encodable { let source: String = "logger" /// The Log attributes let attributes: AnyEncodable + /// Binary images if need to decode the stack trace + let binaryImages: [BinaryImage]? } internal struct GlobalLogAttributes: Codable { diff --git a/DatadogLogs/Sources/Log/LogEventBuilder.swift b/DatadogLogs/Sources/Log/LogEventBuilder.swift index 0d1361df0e..f582829e07 100644 --- a/DatadogLogs/Sources/Log/LogEventBuilder.swift +++ b/DatadogLogs/Sources/Log/LogEventBuilder.swift @@ -68,7 +68,7 @@ internal struct LogEventBuilder { stack: $0.stack, sourceType: $0.sourceType, fingerprint: errorFingerprint, - binaryImages: binaryImages + binaryImages: binaryImages?.toLogDataFormat ) }, serviceName: service, @@ -121,3 +121,20 @@ internal extension LogLevel { } } } + +internal extension BinaryImage { + var toLogDataFormat: LogEvent.Error.BinaryImage { + return .init( + arch: architecture, + isSystem: isSystemLibrary, + loadAddress: loadAddress, + maxAddress: maxAddress, + name: libraryName, + uuid: uuid + ) + } +} + +internal extension Array where Element == BinaryImage { + var toLogDataFormat: [LogEvent.Error.BinaryImage] { map { $0.toLogDataFormat } } +} diff --git a/DatadogLogs/Sources/Log/LogEventEncoder.swift b/DatadogLogs/Sources/Log/LogEventEncoder.swift index 1db44a5459..4d130bef22 100644 --- a/DatadogLogs/Sources/Log/LogEventEncoder.swift +++ b/DatadogLogs/Sources/Log/LogEventEncoder.swift @@ -65,6 +65,36 @@ public struct LogEvent: Encodable { /// Error description associated with a log event. public struct Error { + /// Description of BinaryImage (used for symbolicaiton of stack traces) + public struct BinaryImage: Codable { + /// CPU architecture from the library. + public let arch: String? + + /// Determines if it's a system or user library. + public let isSystem: Bool + + /// Library's load address (hexadecimal). + public let loadAddress: String? + + /// Max value from the library address range (hexadecimal). + public let maxAddress: String? + + /// Name of the library. + public let name: String + + /// Build UUID that uniquely identifies the binary image. + public let uuid: String + + enum CodingKeys: String, CodingKey { + case arch = "arch" + case isSystem = "is_system" + case loadAddress = "load_address" + case maxAddress = "max_address" + case name = "name" + case uuid = "uuid" + } + } + /// The Log error kind public var kind: String? /// The Log error message diff --git a/DatadogLogs/Sources/RemoteLogger.swift b/DatadogLogs/Sources/RemoteLogger.swift index 035a9b15ee..1506defdf7 100644 --- a/DatadogLogs/Sources/RemoteLogger.swift +++ b/DatadogLogs/Sources/RemoteLogger.swift @@ -189,21 +189,16 @@ internal final class RemoteLogger: LoggerProtocol { return } - // Bit of a hack - add the `includeBinaryImages` attribute back in if it was in the original log - // TODO: RUM-4072 This double generates a backtrace - this should be optimized to use cached binary images instead - var rumAttributes: [String: Encodable] = combinedAttributes - if addBinaryImages { - rumAttributes[CrossPlatformAttributes.includeBinaryImages] = true - } - self.core?.send( message: .baggage( key: ErrorMessage.key, value: ErrorMessage( + time: date, message: log.error?.message ?? log.message, type: log.error?.kind, stack: log.error?.stack, - attributes: .init(rumAttributes) + attributes: .init(combinedAttributes), + binaryImages: binaryImages ) ) ) diff --git a/DatadogLogs/Tests/RemoteLoggerTests.swift b/DatadogLogs/Tests/RemoteLoggerTests.swift index 7456d6f2e6..9896fb042d 100644 --- a/DatadogLogs/Tests/RemoteLoggerTests.swift +++ b/DatadogLogs/Tests/RemoteLoggerTests.swift @@ -289,7 +289,17 @@ class RemoteLoggerTests: XCTestCase { let log = try XCTUnwrap(logs.first) XCTAssertNil(log.attributes.userAttributes[CrossPlatformAttributes.includeBinaryImages]) XCTAssertNotNil(log.error?.binaryImages) - XCTAssertEqual(log.error?.binaryImages, stubBacktrace.binaryImages) + XCTAssertEqual(log.error?.binaryImages?.count, stubBacktrace.binaryImages.count) + for i in 0.. Date: Thu, 9 May 2024 18:14:07 +0200 Subject: [PATCH 067/153] Refactor: replace RUM monitor `queue` with `@ReadWriteLock` It will simplify the upcoming work on sending view updates on RUM attributes change (RUM-3588). - It simplifies internal state managemen in RUM.Monitor. - It reduces the number of threads consumed by RUM. - It has no performance impact as the queue was called with `sync {}` for processing events. --- .../RUM/AppHangsMonitoringTests.swift | 2 - .../Tests/Datadog/RUM/RUMMonitorTests.swift | 2 +- .../Sources/Debugging/RUMDebugging.swift | 28 +++-- DatadogRUM/Sources/Feature/RUMFeature.swift | 2 +- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 106 ++++++++---------- .../Tests/RUMMonitor/MonitorTests.swift | 4 - 6 files changed, 64 insertions(+), 80 deletions(-) diff --git a/Datadog/IntegrationUnitTests/RUM/AppHangsMonitoringTests.swift b/Datadog/IntegrationUnitTests/RUM/AppHangsMonitoringTests.swift index d577de054f..f026948ca6 100644 --- a/Datadog/IntegrationUnitTests/RUM/AppHangsMonitoringTests.swift +++ b/Datadog/IntegrationUnitTests/RUM/AppHangsMonitoringTests.swift @@ -143,7 +143,5 @@ class AppHangsMonitoringTests: XCTestCase { // and it is idle: let hangObserver = try XCTUnwrap(core.get(feature: RUMFeature.self)?.instrumentation.appHangs) hangObserver.flush() - - RUMMonitor.shared(in: core).dd.flush() // flush also RUMMonitor so it ends processing hangs flushed from `hangObserver` } } diff --git a/DatadogCore/Tests/Datadog/RUM/RUMMonitorTests.swift b/DatadogCore/Tests/Datadog/RUM/RUMMonitorTests.swift index e10170d75e..a6b53b363e 100644 --- a/DatadogCore/Tests/Datadog/RUM/RUMMonitorTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/RUMMonitorTests.swift @@ -1220,7 +1220,7 @@ class RUMMonitorTests: XCTestCase { case 10: monitor.addAction(type: .tap, name: .mockRandom()) case 11: monitor.addAttribute(forKey: String.mockRandom(), value: String.mockRandom()) case 12: monitor.removeAttribute(forKey: String.mockRandom()) - case 13: monitor.dd.debug = .mockRandom() + case 13: monitor.debug = .mockRandom() default: break } } diff --git a/DatadogRUM/Sources/Debugging/RUMDebugging.swift b/DatadogRUM/Sources/Debugging/RUMDebugging.swift index 28d8b94b8f..539171757d 100644 --- a/DatadogRUM/Sources/Debugging/RUMDebugging.swift +++ b/DatadogRUM/Sources/Debugging/RUMDebugging.swift @@ -28,11 +28,8 @@ private struct RUMDebugInfo { } internal class RUMDebugging { - private lazy var canvas: UIView = { - let view = RUMDebugView(frame: .zero) - view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - return view - }() + /// An overlay view renderd on top of the app content. It is created lazily on first draw. + private var canvas: UIView? = nil // MARK: - Initialization @@ -52,9 +49,8 @@ internal class RUMDebugging { } deinit { - let canvas = self.canvas - DispatchQueue.main.async { - canvas.removeFromSuperview() + DispatchQueue.main.async { [weak canvas] in + canvas?.removeFromSuperview() UIDevice.current.endGeneratingDeviceOrientationNotifications() } @@ -69,9 +65,8 @@ internal class RUMDebugging { } deinit { - let canvas = self.canvas - DispatchQueue.main.async { - canvas.removeFromSuperview() + DispatchQueue.main.async { [weak canvas] in + canvas?.removeFromSuperview() } } #endif @@ -91,6 +86,15 @@ internal class RUMDebugging { // MARK: - Private private func renderOnMainThread(rumDebugInfo: RUMDebugInfo) { + if canvas == nil { + canvas = RUMDebugView(frame: .zero) + canvas?.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } + + guard let canvas = canvas else { + return + } + canvas.subviews.forEach { view in view.removeFromSuperview() } @@ -118,7 +122,7 @@ internal class RUMDebugging { @objc private func updateLayout() { - canvas.subviews.forEach { $0.setNeedsLayout() } + canvas?.subviews.forEach { $0.setNeedsLayout() } } } diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index d6e4fd0680..50886c9444 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -159,7 +159,7 @@ extension RUMFeature: Flushable { /// /// **blocks the caller thread** func flush() { - monitor.flush() + instrumentation.appHangs?.flush() } } diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index 049f74b273..1995313fcb 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -114,13 +114,11 @@ internal class Monitor: RUMCommandSubscriber { let featureScope: FeatureScope let scopes: RUMApplicationScope let dateProvider: DateProvider - let queue = DispatchQueue( - label: "com.datadoghq.rum-monitor", - target: .global(qos: .userInteractive) - ) + @ReadWriteLock private(set) var debugging: RUMDebugging? = nil + @ReadWriteLock private var attributes: [AttributeKey: AttributeValue] = [:] init( @@ -135,38 +133,34 @@ internal class Monitor: RUMCommandSubscriber { func process(command: RUMCommand) { // process command in event context featureScope.eventWriteContext { context, writer in - self.queue.sync { - let transformedCommand = self.transform(command: command) + let transformedCommand = self.transform(command: command) - _ = self.scopes.process(command: transformedCommand, context: context, writer: writer) + _ = self.scopes.process(command: transformedCommand, context: context, writer: writer) - if let debugging = self.debugging { - debugging.debug(applicationScope: self.scopes) - } + if let debugging = self.debugging { + debugging.debug(applicationScope: self.scopes) } } // update the core context with rum context featureScope.set( - baggage: { - self.queue.sync { () -> RUMCoreContext? in - let context = self.scopes.activeSession?.viewScopes.last?.context ?? - self.scopes.activeSession?.context ?? - self.scopes.context - - guard context.sessionID != .nullUUID else { - // if Session was sampled or not yet started - return nil - } - - return RUMCoreContext( - applicationID: context.rumApplicationID, - sessionID: context.sessionID.rawValue.uuidString.lowercased(), - viewID: context.activeViewID?.rawValue.uuidString.lowercased(), - userActionID: context.activeUserActionID?.rawValue.uuidString.lowercased(), - viewServerTimeOffset: self.scopes.activeSession?.viewScopes.last?.serverTimeOffset - ) + baggage: { () -> RUMCoreContext? in + let context = self.scopes.activeSession?.viewScopes.last?.context ?? + self.scopes.activeSession?.context ?? + self.scopes.context + + guard context.sessionID != .nullUUID else { + // if Session was sampled or not yet started + return nil } + + return RUMCoreContext( + applicationID: context.rumApplicationID, + sessionID: context.sessionID.rawValue.uuidString.lowercased(), + viewID: context.activeViewID?.rawValue.uuidString.lowercased(), + userActionID: context.activeUserActionID?.rawValue.uuidString.lowercased(), + viewServerTimeOffset: self.scopes.activeSession?.viewScopes.last?.serverTimeOffset + ) }, forKey: RUMFeature.name ) @@ -201,37 +195,30 @@ extension Monitor: RUMMonitorProtocol { // MARK: - attributes func addAttribute(forKey key: AttributeKey, value: AttributeValue) { - queue.async { - self.attributes[key] = value - } + attributes[key] = value } func removeAttribute(forKey key: AttributeKey) { - queue.async { - self.attributes[key] = nil - } + attributes[key] = nil } // MARK: - session func currentSessionID(completion: @escaping (String?) -> Void) { - // Even though we're not writing anything, need to get the write context - // to make sure we're returning the correct sessionId after all other - // events have processed. - featureScope.eventWriteContext { _, _ in - self.queue.sync { - guard let sessionId = self.scopes.activeSession?.sessionUUID else { - completion(nil) - return - } - - var sessionIdValue: String? = nil - if sessionId != RUMUUID.nullUUID { - sessionIdValue = sessionId.rawValue.uuidString - } + // Synchronise it through the context thread to make sure we return the correct + // sessionID after all other events have been processed (also on the context thread): + featureScope.context { _ in + guard let sessionId = self.scopes.activeSession?.sessionUUID else { + completion(nil) + return + } - completion(sessionIdValue) + var sessionIdValue: String? = nil + if sessionId != RUMUUID.nullUUID { + sessionIdValue = sessionId.rawValue.uuidString } + + completion(sessionIdValue) } } @@ -500,15 +487,19 @@ extension Monitor: RUMMonitorProtocol { var debug: Bool { set { - queue.async { - self.debugging = newValue ? RUMDebugging() : nil - self.debugging?.debug(applicationScope: self.scopes) + debugging = newValue ? RUMDebugging() : nil + + // Synchronise `debug(applicationScope:)` through the context thread to make sure it can safely + // read `scopes` after all events have been processed (also on the context thread): + featureScope.context { [weak self] _ in + guard let self = self else { + return + } + debugging?.debug(applicationScope: scopes) } } get { - queue.sync { - self.debugging != nil - } + debugging != nil } } } @@ -540,9 +531,4 @@ extension Monitor { ) ) } - - /// Completes all asynchronous operations with blocking the caller thread. - func flush() { - queue.sync { } - } } diff --git a/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift b/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift index d0cefe3f7f..5ceb74335b 100644 --- a/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift @@ -22,7 +22,6 @@ class MonitorTests: XCTestCase { dateProvider: DateProviderMock() ) monitor.startView(key: "foo") - monitor.flush() // Then let expectedContext = monitor.currentRUMContext @@ -43,7 +42,6 @@ class MonitorTests: XCTestCase { dateProvider: DateProviderMock() ) monitor.startView(key: "foo") - monitor.flush() // Then XCTAssertNil(featureScope.contextMock.baggages[RUMFeature.name]) @@ -59,7 +57,6 @@ class MonitorTests: XCTestCase { dateProvider: DateProviderMock() ) monitor.startView(viewController: vc) - monitor.flush() // Then XCTAssertEqual(monitor.scopes.sessionScopes.first?.viewScopes.first?.viewName, "SomeViewController") @@ -76,7 +73,6 @@ class MonitorTests: XCTestCase { dateProvider: DateProviderMock() ) monitor.startView(viewController: vc, name: "Some View") - monitor.flush() // Then XCTAssertEqual(monitor.scopes.sessionScopes.first?.viewScopes.first?.viewName, "Some View") From 10c975cd7c86daa7aeb8d6129540ec50be45aacd Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 10 May 2024 11:34:14 +0200 Subject: [PATCH 068/153] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 865fc1a850..bfafd67a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- [FEATURE] `DatadogTrace` now supports OpenTelemetry. See [#1828][] + # 2.11.0 / 08-05-2024 - [FEATURE] `DatadogTrace` now supports head-based sampling. See [#1794][] @@ -660,6 +662,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1721]: https://github.com/DataDog/dd-sdk-ios/pull/1721 [#1803]: https://github.com/DataDog/dd-sdk-ios/pull/1803 [#1807]: https://github.com/DataDog/dd-sdk-ios/pull/1807 +[#1828]: https://github.com/DataDog/dd-sdk-ios/pull/1828 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu From 27ebec3eea1269591467834aeb0d2933bd0995fb Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 10 May 2024 11:44:52 +0200 Subject: [PATCH 069/153] CR feedback - weakify `self` in Monitor closures --- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 30 +++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index 1995313fcb..3be8f76b3a 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -132,22 +132,30 @@ internal class Monitor: RUMCommandSubscriber { func process(command: RUMCommand) { // process command in event context - featureScope.eventWriteContext { context, writer in - let transformedCommand = self.transform(command: command) + featureScope.eventWriteContext { [weak self] context, writer in + guard let self = self else { + return + } - _ = self.scopes.process(command: transformedCommand, context: context, writer: writer) + let transformedCommand = transform(command: command) - if let debugging = self.debugging { - debugging.debug(applicationScope: self.scopes) + _ = scopes.process(command: transformedCommand, context: context, writer: writer) + + if let debugging = debugging { + debugging.debug(applicationScope: scopes) } } // update the core context with rum context featureScope.set( - baggage: { () -> RUMCoreContext? in - let context = self.scopes.activeSession?.viewScopes.last?.context ?? - self.scopes.activeSession?.context ?? - self.scopes.context + baggage: { [weak self] () -> RUMCoreContext? in + guard let self = self else { + return nil + } + + let context = scopes.activeSession?.viewScopes.last?.context ?? + scopes.activeSession?.context ?? + scopes.context guard context.sessionID != .nullUUID else { // if Session was sampled or not yet started @@ -207,8 +215,8 @@ extension Monitor: RUMMonitorProtocol { func currentSessionID(completion: @escaping (String?) -> Void) { // Synchronise it through the context thread to make sure we return the correct // sessionID after all other events have been processed (also on the context thread): - featureScope.context { _ in - guard let sessionId = self.scopes.activeSession?.sessionUUID else { + featureScope.context { [weak self] _ in + guard let sessionId = self?.scopes.activeSession?.sessionUUID else { completion(nil) return } From d912e4c0dee7f196e3c27e98f4acfff5e5427c09 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 10 May 2024 12:21:09 +0200 Subject: [PATCH 070/153] fix merge issues --- DatadogCore/Tests/Datadog/DatadogCore/DatadogCoreTests.swift | 1 + DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift | 4 +++- DatadogTrace/Tests/TraceTests.swift | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DatadogCore/Tests/Datadog/DatadogCore/DatadogCoreTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/DatadogCoreTests.swift index bd3fbb0b13..9a51a52d7f 100644 --- a/DatadogCore/Tests/Datadog/DatadogCore/DatadogCoreTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogCore/DatadogCoreTests.swift @@ -146,6 +146,7 @@ class DatadogCoreTests: XCTestCase { maxBatchesPerUpload: .mockRandom(min: 1, max: 100), backgroundTasksEnabled: .mockAny() ) + defer { core.flushAndTearDown() } let feature = FeatureMock() try core.register(feature: feature) diff --git a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift index 52af970ffe..c8dd3292fa 100644 --- a/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift +++ b/DatadogTrace/Sources/OpenTelemetry/OTelSpan.swift @@ -126,7 +126,9 @@ internal class OTelSpan: OpenTelemetryApi.Span { traceID: context.traceId.toDatadog(), spanID: context.spanId.toDatadog(), parentSpanID: parentSpanID?.toDatadog(), - baggageItems: .init() + baggageItems: .init(), + sampleRate: tracer.localTraceSampler.samplingRate, + isKept: tracer.localTraceSampler.sample() ), operationName: name, startTime: startTime, diff --git a/DatadogTrace/Tests/TraceTests.swift b/DatadogTrace/Tests/TraceTests.swift index 02175173c4..186922841f 100644 --- a/DatadogTrace/Tests/TraceTests.swift +++ b/DatadogTrace/Tests/TraceTests.swift @@ -6,7 +6,6 @@ import XCTest import TestUtilities -import OpenTelemetryApi @testable import DatadogInternal @testable import DatadogTrace From eebf3476d89a9296779d39d08631f3750f78af6e Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 10 May 2024 13:00:43 +0200 Subject: [PATCH 071/153] Fix RUMContextMocks.swift reference --- Datadog/Datadog.xcodeproj/project.pbxproj | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 63157d5dea..bfb6a8ea8f 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 3C32359E2B55386C000B4258 /* OTelSpanLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359C2B55386C000B4258 /* OTelSpanLink.swift */; }; 3C3235A02B55387A000B4258 /* OTelSpanLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */; }; 3C3235A12B55387A000B4258 /* OTelSpanLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */; }; + 3C33E4072BEE35A8003B2988 /* RUMContextMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C33E4062BEE35A7003B2988 /* RUMContextMocks.swift */; }; 3C41693C29FBF4D50042B9D2 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; 3C5D63692B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */; }; 3C5D636A2B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */; }; @@ -82,6 +83,10 @@ 3CCCA5C52ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */; }; 3CCCA5C72ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */; }; 3CCCA5C82ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */; }; + 3CCECDAF2BC688120013C125 /* SpanIDGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCECDAE2BC688120013C125 /* SpanIDGeneratorTests.swift */; }; + 3CCECDB02BC688120013C125 /* SpanIDGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCECDAE2BC688120013C125 /* SpanIDGeneratorTests.swift */; }; + 3CCECDB22BC68A0A0013C125 /* SpanIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCECDB12BC68A0A0013C125 /* SpanIDTests.swift */; }; + 3CCECDB32BC68A0A0013C125 /* SpanIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCECDB12BC68A0A0013C125 /* SpanIDTests.swift */; }; 3CDA3F7E2BCD866D005D2C13 /* DatadogSDKTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 3CDA3F7D2BCD866D005D2C13 /* DatadogSDKTesting */; }; 3CDA3F802BCD8687005D2C13 /* DatadogSDKTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 3CDA3F7F2BCD8687005D2C13 /* DatadogSDKTesting */; }; 3CE11A1129F7BE0900202522 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; @@ -90,12 +95,6 @@ 3CF673372B4807490016CE17 /* OTelSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF673352B4807490016CE17 /* OTelSpanTests.swift */; }; 3CFF5D492B555F4F00FC483A /* OTelTracerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFF5D482B555F4F00FC483A /* OTelTracerProvider.swift */; }; 3CFF5D4A2B555F4F00FC483A /* OTelTracerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFF5D482B555F4F00FC483A /* OTelTracerProvider.swift */; }; - 3CCECDAF2BC688120013C125 /* SpanIDGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCECDAE2BC688120013C125 /* SpanIDGeneratorTests.swift */; }; - 3CCECDB02BC688120013C125 /* SpanIDGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCECDAE2BC688120013C125 /* SpanIDGeneratorTests.swift */; }; - 3CCECDB22BC68A0A0013C125 /* SpanIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCECDB12BC68A0A0013C125 /* SpanIDTests.swift */; }; - 3CCECDB32BC68A0A0013C125 /* SpanIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCECDB12BC68A0A0013C125 /* SpanIDTests.swift */; }; - 3CE11A1129F7BE0900202522 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; - 3CE11A1229F7BE0900202522 /* DatadogWebViewTracking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 49274906288048B500ECD49B /* InternalProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49274903288048AA00ECD49B /* InternalProxyTests.swift */; }; 49274907288048B800ECD49B /* InternalProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49274903288048AA00ECD49B /* InternalProxyTests.swift */; }; 49D8C0B72AC5D2160075E427 /* RUM+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D8C0B62AC5D2160075E427 /* RUM+Internal.swift */; }; @@ -2035,6 +2034,7 @@ 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OpenTelemetryApi.xcframework; path = ../Carthage/Build/OpenTelemetryApi.xcframework; sourceTree = ""; }; 3C32359C2B55386C000B4258 /* OTelSpanLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanLink.swift; sourceTree = ""; }; 3C32359F2B55387A000B4258 /* OTelSpanLinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanLinkTests.swift; sourceTree = ""; }; + 3C33E4062BEE35A7003B2988 /* RUMContextMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMContextMocks.swift; sourceTree = ""; }; 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+Datadog.swift"; sourceTree = ""; }; 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+DatadogTests.swift"; sourceTree = ""; }; 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpan.swift; sourceTree = ""; }; @@ -2046,11 +2046,9 @@ 3C6C7FF42B459AB3006F5CBC /* OTelSpanTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanTests.swift; sourceTree = ""; }; 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewTracking.swift; sourceTree = ""; }; 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsSanitizerMock.swift; sourceTree = ""; }; + 3C9B27242B9F174700569C07 /* SpanID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanID.swift; sourceTree = ""; }; 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NOPOTelSpan.swift; sourceTree = ""; }; 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NOPOTelSpanBuilder.swift; sourceTree = ""; }; - 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewTracking.swift; sourceTree = ""; }; - 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsSanitizerMock.swift; sourceTree = ""; }; - 3C9B27242B9F174700569C07 /* SpanID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanID.swift; sourceTree = ""; }; 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionInstrumentation.swift; sourceTree = ""; }; 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionTask+Tracking.swift"; sourceTree = ""; }; 3CC6AD172B4F07DC00015B18 /* OTelAttributeValue+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelAttributeValue+Datadog.swift"; sourceTree = ""; }; @@ -3787,6 +3785,7 @@ 61054F7D2A6EE1BA00AAA894 /* Mocks */ = { isa = PBXGroup; children = ( + 3C33E4062BEE35A7003B2988 /* RUMContextMocks.swift */, 61054F7E2A6EE1BA00AAA894 /* UIKitMocks.swift */, 61054F7F2A6EE1BA00AAA894 /* CoreGraphicsMocks.swift */, 61054F802A6EE1BA00AAA894 /* SRDataModelsMocks.swift */, @@ -3795,7 +3794,6 @@ 61054F832A6EE1BA00AAA894 /* TestScheduler.swift */, 61054F842A6EE1BA00AAA894 /* QueueMocks.swift */, 61054F862A6EE1BA00AAA894 /* SnapshotProducerMocks.swift */, - 3C2B89412BE53D6200043847 /* RUMContextMocks.swift */, 61054F872A6EE1BA00AAA894 /* RUMContextObserverMock.swift */, A74A72862B10CE4100771FEB /* ResourceMocks.swift */, A74A72882B10D95D00771FEB /* MultipartBuilderSpy.swift */, @@ -8188,7 +8186,7 @@ 61054F992A6EE1BA00AAA894 /* ColorsTests.swift in Sources */, 61054FBF2A6EE1BA00AAA894 /* UIPickerViewRecorderTests.swift in Sources */, 61054FAE2A6EE1BA00AAA894 /* TouchIdentifierGeneratorTests.swift in Sources */, - 3C2B89422BE53D6200043847 /* RUMContextMocks.swift in Sources */, + 3C33E4072BEE35A8003B2988 /* RUMContextMocks.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 63539d81d65ca2917986ae6e89328e63686d1644 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 10 May 2024 13:04:04 +0200 Subject: [PATCH 072/153] remove unused import --- DatadogTrace/Sources/Trace.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DatadogTrace/Sources/Trace.swift b/DatadogTrace/Sources/Trace.swift index 1ee3d6d4a5..e523314b50 100644 --- a/DatadogTrace/Sources/Trace.swift +++ b/DatadogTrace/Sources/Trace.swift @@ -6,7 +6,6 @@ import Foundation import DatadogInternal -import OpenTelemetryApi /// An entry point to Datadog Trace feature. public enum Trace { From 158d9331a1589d376622d4292a4f23b344c7e3c6 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 10 May 2024 15:43:31 +0200 Subject: [PATCH 073/153] another merge conflict --- dependency-manager-tests/spm/App/ViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependency-manager-tests/spm/App/ViewController.swift b/dependency-manager-tests/spm/App/ViewController.swift index c76cb5a8e7..eafeaf9d5e 100644 --- a/dependency-manager-tests/spm/App/ViewController.swift +++ b/dependency-manager-tests/spm/App/ViewController.swift @@ -8,6 +8,7 @@ import UIKit import DatadogRUM import DatadogObjc import DatadogSessionReplay // it should compile for iOS and tvOS, but APIs are only available on iOS +import DatadogTrace import OpenTelemetryApi internal class ViewController: UIViewController { @@ -25,7 +26,6 @@ internal class ViewController: UIViewController { tracerProvider: OTelTracerProvider() ) - logger.info("It works") let otSpan = Tracer.shared().startSpan(operationName: "OT Span") otSpan.finish() From 94366569d8dfc15957901aed0626e34fd7b71bb2 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 10 May 2024 11:44:52 +0200 Subject: [PATCH 074/153] RUM-3588 Add unit tests for existing global RUM attributes behavious --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 + DatadogRUM/Sources/RUMMonitor/Monitor.swift | 30 +- .../Monitor+GlobalAttributesTests.swift | 419 ++++++++++++++++++ 3 files changed, 444 insertions(+), 11 deletions(-) create mode 100644 DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 407bcaca8e..a6c0654462 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -489,6 +489,8 @@ 61C713D12A3DEFF900FA735A /* FeatureRegistrationCoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713CF2A3DEFF900FA735A /* FeatureRegistrationCoreMock.swift */; }; 61C713D32A3DFB4900FA735A /* FuzzyHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713D22A3DFB4900FA735A /* FuzzyHelpers.swift */; }; 61C713D42A3DFB4900FA735A /* FuzzyHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713D22A3DFB4900FA735A /* FuzzyHelpers.swift */; }; + 61CE2E5F2BF2177100EC7D42 /* Monitor+GlobalAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CE2E5E2BF2177100EC7D42 /* Monitor+GlobalAttributesTests.swift */; }; + 61CE2E602BF2177100EC7D42 /* Monitor+GlobalAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CE2E5E2BF2177100EC7D42 /* Monitor+GlobalAttributesTests.swift */; }; 61CE585A2B48174D00479510 /* SpanWriteContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CE58592B48174D00479510 /* SpanWriteContext.swift */; }; 61CE585B2B48174D00479510 /* SpanWriteContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CE58592B48174D00479510 /* SpanWriteContext.swift */; }; 61D03BE0273404E700367DE0 /* RUMDataModels+objcTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D03BDF273404E700367DE0 /* RUMDataModels+objcTests.swift */; }; @@ -2450,6 +2452,7 @@ 61C713C92A3DC22700FA735A /* RUMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMTests.swift; sourceTree = ""; }; 61C713CF2A3DEFF900FA735A /* FeatureRegistrationCoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureRegistrationCoreMock.swift; sourceTree = ""; }; 61C713D22A3DFB4900FA735A /* FuzzyHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyHelpers.swift; sourceTree = ""; }; + 61CE2E5E2BF2177100EC7D42 /* Monitor+GlobalAttributesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Monitor+GlobalAttributesTests.swift"; sourceTree = ""; }; 61CE58592B48174D00479510 /* SpanWriteContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanWriteContext.swift; sourceTree = ""; }; 61D03BDF273404E700367DE0 /* RUMDataModels+objcTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUMDataModels+objcTests.swift"; sourceTree = ""; }; 61D3E0C8277B23F0008BE766 /* KronosInternetAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KronosInternetAddress.swift; sourceTree = ""; }; @@ -4679,6 +4682,7 @@ 618DCFDE24C75FD300589570 /* RUMScopeTests.swift */, 618715F624DC0CDE00FC0F69 /* RUMCommandTests.swift */, 6176C1712ABDBA2E00131A70 /* MonitorTests.swift */, + 61CE2E5E2BF2177100EC7D42 /* Monitor+GlobalAttributesTests.swift */, ); path = RUMMonitor; sourceTree = ""; @@ -8428,6 +8432,7 @@ D23F8EAB29DDCD38001CFAE8 /* RUMDataModelMocks.swift in Sources */, D23F8EAC29DDCD38001CFAE8 /* RUMDataModelsMappingTests.swift in Sources */, D23F8EAD29DDCD38001CFAE8 /* RUMEventBuilderTests.swift in Sources */, + 61CE2E602BF2177100EC7D42 /* Monitor+GlobalAttributesTests.swift in Sources */, D23F8EAE29DDCD38001CFAE8 /* DDTAssertValidRUMUUID.swift in Sources */, D23F8EAF29DDCD38001CFAE8 /* RUMScopeTests.swift in Sources */, D23F8EB029DDCD38001CFAE8 /* SessionReplayDependencyTests.swift in Sources */, @@ -8719,6 +8724,7 @@ D29A9FC629DDBA8A005C54A4 /* RUMDataModelMocks.swift in Sources */, D29A9FD529DDC624005C54A4 /* RUMDataModelsMappingTests.swift in Sources */, D29A9FBE29DDB483005C54A4 /* RUMEventBuilderTests.swift in Sources */, + 61CE2E5F2BF2177100EC7D42 /* Monitor+GlobalAttributesTests.swift in Sources */, D29A9FCC29DDBCC5005C54A4 /* DDTAssertValidRUMUUID.swift in Sources */, D29A9FB329DDB483005C54A4 /* RUMScopeTests.swift in Sources */, D29A9FAE29DDB483005C54A4 /* SessionReplayDependencyTests.swift in Sources */, diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index 1995313fcb..3be8f76b3a 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -132,22 +132,30 @@ internal class Monitor: RUMCommandSubscriber { func process(command: RUMCommand) { // process command in event context - featureScope.eventWriteContext { context, writer in - let transformedCommand = self.transform(command: command) + featureScope.eventWriteContext { [weak self] context, writer in + guard let self = self else { + return + } - _ = self.scopes.process(command: transformedCommand, context: context, writer: writer) + let transformedCommand = transform(command: command) - if let debugging = self.debugging { - debugging.debug(applicationScope: self.scopes) + _ = scopes.process(command: transformedCommand, context: context, writer: writer) + + if let debugging = debugging { + debugging.debug(applicationScope: scopes) } } // update the core context with rum context featureScope.set( - baggage: { () -> RUMCoreContext? in - let context = self.scopes.activeSession?.viewScopes.last?.context ?? - self.scopes.activeSession?.context ?? - self.scopes.context + baggage: { [weak self] () -> RUMCoreContext? in + guard let self = self else { + return nil + } + + let context = scopes.activeSession?.viewScopes.last?.context ?? + scopes.activeSession?.context ?? + scopes.context guard context.sessionID != .nullUUID else { // if Session was sampled or not yet started @@ -207,8 +215,8 @@ extension Monitor: RUMMonitorProtocol { func currentSessionID(completion: @escaping (String?) -> Void) { // Synchronise it through the context thread to make sure we return the correct // sessionID after all other events have been processed (also on the context thread): - featureScope.context { _ in - guard let sessionId = self.scopes.activeSession?.sessionUUID else { + featureScope.context { [weak self] _ in + guard let sessionId = self?.scopes.activeSession?.sessionUUID else { completion(nil) return } diff --git a/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift new file mode 100644 index 0000000000..3a70e39efa --- /dev/null +++ b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift @@ -0,0 +1,419 @@ +/* + * 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 TestUtilities +import DatadogInternal +@testable import DatadogRUM + +class Monitor_GlobalAttributesTests: XCTestCase { + private let featureScope = FeatureScopeMock() + private var monitor: Monitor! // swiftlint:disable:this implicitly_unwrapped_optional + + override func setUp() { + monitor = Monitor( + dependencies: .mockWith(featureScope: featureScope), + dateProvider: SystemDateProvider() + ) + } + + override func tearDown() { + monitor = nil + } + + // MARK: - Changing Global Attributes After SDK Init + + func testAddingGlobalAttributeAfterSDKInit() throws { + // When + monitor.notifySDKInit() + monitor.addAttribute(forKey: "attribute", value: "value") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName) + XCTAssertNil(lastView.attribute(forKey: "attribute")) + } + + func testAddingGlobalAttributeAfterSDKInit_thenStartingView() throws { + // Given + monitor.notifySDKInit() + monitor.addAttribute(forKey: "attribute", value: "value") + + // When + monitor.startView(key: "View") + + // Then + let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self) + let appLaunchView = try XCTUnwrap(viewEvents.last(where: { $0.view.name == RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName })) + XCTAssertEqual(appLaunchView.attribute(forKey: "attribute"), "value") + } + + func testAddingGlobalAttributeAfterSDKInit_thenSendingInteractiveEvent() throws { + // Given + monitor.notifySDKInit() + monitor.addAttribute(forKey: "attribute", value: "value") + + // When + monitor.addAction(type: .custom, name: "custom action") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName) + XCTAssertNil(lastView.attribute(forKey: "attribute")) + } + + func testAddingGlobalAttributesAfterSDKInit_thenRemovingAttribute() throws { + // Given + monitor.notifySDKInit() + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + + // When + monitor.removeAttribute(forKey: "attribute1") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName) + XCTAssertNil(lastView.attribute(forKey: "attribute1")) + XCTAssertNil(lastView.attribute(forKey: "attribute2")) + } + + func testAddingGlobalAttributeAfterSDKInit_thenRemovingAttributeAndStartingView() throws { + // Given + monitor.notifySDKInit() + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + + // When + monitor.removeAttribute(forKey: "attribute1") + monitor.startView(key: "View") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, "View") + XCTAssertNil(lastView.attribute(forKey: "attribute1")) + XCTAssertEqual(lastView.attribute(forKey: "attribute2"), "value2") + } + + func testAddingGlobalAttributeAfterSDKInit_thenRemovingAttributeAndStartingAndStoppingView() throws { + // Given + monitor.notifySDKInit() + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + + // When + monitor.removeAttribute(forKey: "attribute1") + monitor.startView(key: "View") + monitor.stopView(key: "View") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, "View") + XCTAssertNil(lastView.attribute(forKey: "attribute1")) + XCTAssertEqual(lastView.attribute(forKey: "attribute2"), "value2") + } + + // MARK: - Changing Global Attributes After Starting View + + func testAddingGlobalAttributeAfterViewIsStarted() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "View") + + // When + monitor.addAttribute(forKey: "attribute", value: "value") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, "View") + XCTAssertNil(lastView.attribute(forKey: "attribute")) + } + + func testAddingGlobalAttributeAfterViewIsStarted_thenSendingInteractiveEvent() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "View") + monitor.addAttribute(forKey: "attribute", value: "value") + + // When + monitor.addAction(type: .custom, name: "custom action") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, "View") + XCTAssertNil(lastView.attribute(forKey: "attribute")) + } + + func testAddingGlobalAttributesAfterViewIsStarted_thenRemovingAttribute() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "View") + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + + // When + monitor.removeAttribute(forKey: "attribute1") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, "View") + XCTAssertNil(lastView.attribute(forKey: "attribute1")) + XCTAssertNil(lastView.attribute(forKey: "attribute2")) + } + + func testAddingGlobalAttributesAfterViewIsStarted_thenRemovingAttributeAndStoppingView() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "View") + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + + // When + monitor.removeAttribute(forKey: "attribute1") + monitor.stopView(key: "View") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, "View") + XCTAssertNil(lastView.attribute(forKey: "attribute1")) + XCTAssertEqual(lastView.attribute(forKey: "attribute2"), "value2") + } + + func testAddingGlobalAttributeAfterViewIsStarted_thenStartingNextViewsWhileAddingAttributes() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "View1") + monitor.addAttribute(forKey: "attribute1", value: "value1") + + // When + monitor.startView(key: "View2") + monitor.addAttribute(forKey: "attribute2", value: "value2") + monitor.startView(key: "View3") + monitor.addAttribute(forKey: "attribute3", value: "value3") + + // Then + let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self) + let lastView1 = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "View1" })) + let lastView2 = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "View2" })) + let lastView3 = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "View3" })) + + XCTAssertEqual(lastView1.attribute(forKey: "attribute1"), "value1") + XCTAssertNil(lastView1.attribute(forKey: "attribute2")) + XCTAssertNil(lastView1.attribute(forKey: "attribute3")) + + XCTAssertEqual(lastView2.attribute(forKey: "attribute1"), "value1") + XCTAssertEqual(lastView2.attribute(forKey: "attribute2"), "value2") + XCTAssertNil(lastView2.attribute(forKey: "attribute3")) + + XCTAssertEqual(lastView3.attribute(forKey: "attribute1"), "value1") + XCTAssertEqual(lastView3.attribute(forKey: "attribute2"), "value2") + XCTAssertNil(lastView2.attribute(forKey: "attribute3")) + } + + func testAddingGlobalAttributesAfterViewIsStarted_thenStartingNextViewsWhileRemovingAttributes() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "View1") + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + monitor.addAttribute(forKey: "attribute3", value: "value3") + + // When + monitor.startView(key: "View2") + monitor.removeAttribute(forKey: "attribute1") + monitor.startView(key: "View3") + monitor.removeAttribute(forKey: "attribute2") + + // Then + let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self) + let lastView1 = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "View1" })) + let lastView2 = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "View2" })) + let lastView3 = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "View3" })) + + XCTAssertEqual(lastView1.attribute(forKey: "attribute1"), "value1") + XCTAssertEqual(lastView1.attribute(forKey: "attribute2"), "value2") + XCTAssertEqual(lastView1.attribute(forKey: "attribute3"), "value3") + + XCTAssertEqual(lastView2.attribute(forKey: "attribute1"), "value1") + XCTAssertEqual(lastView2.attribute(forKey: "attribute2"), "value2") + XCTAssertEqual(lastView2.attribute(forKey: "attribute3"), "value3") + + XCTAssertNil(lastView3.attribute(forKey: "attribute1")) + XCTAssertEqual(lastView3.attribute(forKey: "attribute2"), "value2") + XCTAssertEqual(lastView3.attribute(forKey: "attribute3"), "value3") + } + + // MARK: - Changing Global Attributes While There Is An Inactive View + + func testAddingGlobalAttributeWhileInactiveView_thenImplicitlyStoppingInactiveView() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "InactiveView") + monitor.startResource(resourceKey: "pending resource", url: .mockAny()) + monitor.startView(key: "ActiveView") + + // When + monitor.addAttribute(forKey: "attribute", value: "value") + monitor.stopResource(resourceKey: "pending resource", response: .mockAny()) + + // Then + let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self) + let lastInactiveView = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "InactiveView" })) + let lastActiveView = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "ActiveView" })) + + XCTAssertEqual(lastInactiveView.view.resource.count, 1) + XCTAssertNil(lastInactiveView.attribute(forKey: "attribute")) + XCTAssertNil(lastActiveView.attribute(forKey: "attribute")) + } + + func testAddingGlobalAttributesWhileInactiveView_thenRemovingAttributeAndImplicitlyStoppingInactiveView() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "InactiveView") + monitor.startResource(resourceKey: "pending resource", url: .mockAny()) + monitor.startView(key: "ActiveView") + + // When + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + monitor.removeAttribute(forKey: "attribute1") + monitor.stopResource(resourceKey: "pending resource", response: .mockAny()) + + // Then + let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self) + let lastInactiveView = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "InactiveView" })) + let lastActiveView = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "ActiveView" })) + + XCTAssertEqual(lastInactiveView.view.resource.count, 1) + XCTAssertNil(lastInactiveView.attribute(forKey: "attribute1")) + XCTAssertNil(lastInactiveView.attribute(forKey: "attribute2")) + XCTAssertNil(lastActiveView.attribute(forKey: "attribute1")) + XCTAssertNil(lastActiveView.attribute(forKey: "attribute2")) + } + + func testAddingGlobalAttributesWhileInactiveView_thenRemovingAttributeAndImplicitlyStoppingInactiveViewAndStoppingActiveView() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "InactiveView") + monitor.startResource(resourceKey: "pending resource", url: .mockAny()) + monitor.startView(key: "ActiveView") + + // When + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + monitor.removeAttribute(forKey: "attribute1") + monitor.stopResource(resourceKey: "pending resource", response: .mockAny()) + monitor.stopView(key: "ActiveView") + + // Then + let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self) + let lastInactiveView = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "InactiveView" })) + let lastActiveView = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "ActiveView" })) + + XCTAssertEqual(lastInactiveView.view.resource.count, 1) + XCTAssertNil(lastInactiveView.attribute(forKey: "attribute1")) + XCTAssertNil(lastInactiveView.attribute(forKey: "attribute2")) + XCTAssertNil(lastActiveView.attribute(forKey: "attribute1")) + XCTAssertEqual(lastActiveView.attribute(forKey: "attribute2"), "value2") + } + + // MARK: - Including Up-to-date Global Attributes In Events + + func testAddingGlobalAttributeToActiveView_thenCollectingViewEvents() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "ActiveView") + monitor.addAttribute(forKey: "attribute", value: "value") + + // When + monitor.addError(message: "error event") + monitor.addAction(type: .custom, name: "custom action event") + monitor.startResource(resourceKey: "resource", url: .mockAny()) + monitor.stopResource(resourceKey: "resource", response: .mockAny()) + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + let errorEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMErrorEvent.self).last) + let actionEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMActionEvent.self).last) + let resourceEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMResourceEvent.self).last) + + XCTAssertEqual(lastView.view.error.count, 1) + XCTAssertEqual(lastView.view.action.count, 1) + XCTAssertEqual(lastView.view.resource.count, 1) + XCTAssertNil(lastView.attribute(forKey: "attribute")) + XCTAssertEqual(errorEvent.context?.contextInfo["attribute"] as? String, "value") + XCTAssertEqual(actionEvent.context?.contextInfo["attribute"] as? String, "value") + XCTAssertEqual(resourceEvent.context?.contextInfo["attribute"] as? String, "value") + } + + func testAddingGlobalAttributesToActiveView_thenRemovingAttributeAndCollectingViewEvents() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "ActiveView") + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + + // When + monitor.removeAttribute(forKey: "attribute1") + monitor.addError(message: "error event") + monitor.addAction(type: .custom, name: "custom action event") + monitor.startResource(resourceKey: "resource", url: .mockAny()) + monitor.stopResource(resourceKey: "resource", response: .mockAny()) + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + let errorEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMErrorEvent.self).last) + let actionEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMActionEvent.self).last) + let resourceEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMResourceEvent.self).last) + + XCTAssertEqual(lastView.view.error.count, 1) + XCTAssertEqual(lastView.view.action.count, 1) + XCTAssertEqual(lastView.view.resource.count, 1) + XCTAssertNil(lastView.attribute(forKey: "attribute1")) + XCTAssertNil(lastView.attribute(forKey: "attribute2")) + XCTAssertNil(errorEvent.context?.contextInfo["attribute1"]) + XCTAssertEqual(errorEvent.context?.contextInfo["attribute2"] as? String, "value2") + XCTAssertNil(actionEvent.context?.contextInfo["attribute1"]) + XCTAssertEqual(actionEvent.context?.contextInfo["attribute2"] as? String, "value2") + XCTAssertNil(resourceEvent.context?.contextInfo["attribute1"]) + XCTAssertEqual(resourceEvent.context?.contextInfo["attribute2"] as? String, "value2") + } + + func testAddingGlobalAttributesToActiveView_thenCollectingViewTimingsAndRemovingAttribute() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "ActiveView") + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + + // When + monitor.addTiming(name: "timing1") + monitor.removeAttribute(forKey: "attribute1") + monitor.addTiming(name: "timing2") + + // Then + let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self).filter { $0.view.name == "ActiveView" } + let viewAfterFirstTiming = try XCTUnwrap(viewEvents.last(where: { $0.view.customTimings?.count == 1 })) + let viewAfterSecondTiming = try XCTUnwrap(viewEvents.last(where: { $0.view.customTimings?.count == 2 })) + + XCTAssertEqual(viewAfterFirstTiming.attribute(forKey: "attribute1"), "value1") + XCTAssertEqual(viewAfterFirstTiming.attribute(forKey: "attribute2"), "value2") + XCTAssertEqual(viewAfterSecondTiming.attribute(forKey: "attribute2"), "value2") + XCTAssertEqual(viewAfterSecondTiming.attribute(forKey: "attribute2"), "value2") + } +} + +// MARK: - Helpers + +private extension RUMViewEvent { + func attribute(forKey key: String) -> Any? { + return context?.contextInfo[key] + } + + func attribute(forKey key: String) -> T? { + return context?.contextInfo[key] as? T + } +} From 6ccf29a7c5d77eba47ecaa3747ffae346fdde5d0 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 15 May 2024 11:03:51 +0200 Subject: [PATCH 075/153] Remove WKWebView instance in tests --- .../Public/WebLogIntegrationTests.swift | 16 +++++++++++----- .../RUM/WebEventIntegrationTests.swift | 16 +++++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift b/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift index e5b1c559cd..8d14130b91 100644 --- a/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift @@ -7,7 +7,8 @@ import XCTest #if !os(tvOS) -import WebKit + +import DatadogInternal @testable import DatadogLogs @testable import DatadogRUM @@ -29,10 +30,15 @@ class WebLogIntegrationTests: XCTestCase { ) controller = WKUserContentControllerMock() - let configuration = WKWebViewConfiguration() - configuration.userContentController = controller - let webView = WKWebView(frame: .zero, configuration: configuration) - WebViewTracking.enable(webView: webView, in: core) + + WebViewTracking.enable( + tracking: controller, + hosts: [], + hostsSanitizer: HostsSanitizer(), + logsSampleRate: 100, + sessionReplayConfiguration: nil, + in: core + ) } override func tearDown() { diff --git a/Datadog/IntegrationUnitTests/RUM/WebEventIntegrationTests.swift b/Datadog/IntegrationUnitTests/RUM/WebEventIntegrationTests.swift index 9507cc345d..34b8fb595c 100644 --- a/Datadog/IntegrationUnitTests/RUM/WebEventIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/RUM/WebEventIntegrationTests.swift @@ -6,9 +6,10 @@ import XCTest #if !os(tvOS) -import WebKit +import DatadogInternal import TestUtilities + @testable import DatadogRUM @testable import DatadogWebViewTracking @@ -28,10 +29,15 @@ class WebEventIntegrationTests: XCTestCase { ) controller = WKUserContentControllerMock() - let configuration = WKWebViewConfiguration() - configuration.userContentController = controller - let webView = WKWebView(frame: .zero, configuration: configuration) - WebViewTracking.enable(webView: webView, in: core) + + WebViewTracking.enable( + tracking: controller, + hosts: [], + hostsSanitizer: HostsSanitizer(), + logsSampleRate: 100, + sessionReplayConfiguration: nil, + in: core + ) } override func tearDown() { From 95cdd811aaef15032becedb933718c553f37e61d Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Mon, 13 May 2024 11:55:26 +0200 Subject: [PATCH 076/153] RUM-3535 feat: add support for trace context injection configuration to allow selective injection --- Datadog/Datadog.xcodeproj/project.pbxproj | 12 ++++ .../xcschemes/DatadogCore iOS.xcscheme | 14 ----- .../xcschemes/DatadogCore tvOS.xcscheme | 20 ------- ...ugManualTraceInjectionViewController.swift | 23 +++++-- .../Trace/HeadBasedSamplingTests.swift | 36 +++++------ DatadogCore/Tests/Datadog/TracerTests.swift | 8 +-- .../TracingURLSessionHandlerTests.swift | 3 +- .../Tests/DatadogObjc/DDTracerTests.swift | 50 ++++++++++++---- .../DDB3HTTPHeadersWriter+apiTests.m | 6 +- .../DDHTTPHeadersWriter+apiTests.m | 4 +- .../DDW3CHTTPHeadersWriter+apiTests.m | 4 +- .../B3/B3HTTPHeadersWriter.swift | 60 +++++++++++-------- .../Datadog/HTTPHeadersWriter.swift | 22 ++++--- .../TraceContextInjection.swift | 16 +++++ .../W3C/W3CHTTPHeadersWriter.swift | 56 ++++++++++------- .../B3HTTPHeadersReaderTests.swift | 4 +- .../B3HTTPHeadersWriterTests.swift | 30 ++++++---- .../HTTPHeadersReaderTests.swift | 6 +- .../HTTPHeadersWriterTests.swift | 12 ++-- .../W3CHTTPHeadersReaderTests.swift | 4 +- .../W3CHTTPHeadersWriterTests.swift | 12 ++-- .../B3HTTPHeadersWriter+objc.swift | 9 ++- .../Propagation/HTTPHeadersWriter+objc.swift | 11 +++- .../TraceContextInjection+objc.swift | 27 +++++++++ .../W3CHTTPHeadersWriter+objc.swift | 9 ++- DatadogRUM/Sources/Feature/RUMFeature.swift | 8 +-- .../URLSessionRUMResourcesHandler.swift | 20 +++++-- DatadogRUM/Sources/RUM+Internal.swift | 10 ++-- DatadogRUM/Sources/RUMConfiguration.swift | 14 ++++- .../URLSessionRUMResourcesHandlerTests.swift | 35 +++++++---- .../TracingURLSessionHandler.swift | 19 ++++-- DatadogTrace/Sources/Trace.swift | 10 +++- DatadogTrace/Sources/TraceConfiguration.swift | 14 ++++- DatadogTrace/Tests/DDNoopTracerTests.swift | 5 +- .../Tests/TracingURLSessionHandlerTests.swift | 26 ++++---- 35 files changed, 399 insertions(+), 220 deletions(-) create mode 100644 DatadogInternal/Sources/NetworkInstrumentation/TraceContextInjection.swift create mode 100644 DatadogObjc/Sources/Tracing/Propagation/TraceContextInjection+objc.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index bfb6a8ea8f..f87844c7e5 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -67,6 +67,10 @@ 3C9B27252B9F174700569C07 /* SpanID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9B27242B9F174700569C07 /* SpanID.swift */; }; 3C9B27262B9F174700569C07 /* SpanID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9B27242B9F174700569C07 /* SpanID.swift */; }; 3C9C6BB429F7C0C000581C43 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23039A5298D513C001A1FA3 /* DatadogInternal.framework */; }; + 3CA8525F2BF2073800B52CBA /* TraceContextInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA8525E2BF2073800B52CBA /* TraceContextInjection.swift */; }; + 3CA852602BF2073800B52CBA /* TraceContextInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA8525E2BF2073800B52CBA /* TraceContextInjection.swift */; }; + 3CA852642BF2148200B52CBA /* TraceContextInjection+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA852612BF2147600B52CBA /* TraceContextInjection+objc.swift */; }; + 3CA852652BF2148400B52CBA /* TraceContextInjection+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA852612BF2147600B52CBA /* TraceContextInjection+objc.swift */; }; 3CB012DD2B482E0400557951 /* NOPOTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */; }; 3CB012DE2B482E0400557951 /* NOPOTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */; }; 3CB012DF2B482E0400557951 /* NOPOTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */; }; @@ -2047,6 +2051,8 @@ 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewTracking.swift; sourceTree = ""; }; 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsSanitizerMock.swift; sourceTree = ""; }; 3C9B27242B9F174700569C07 /* SpanID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanID.swift; sourceTree = ""; }; + 3CA8525E2BF2073800B52CBA /* TraceContextInjection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceContextInjection.swift; sourceTree = ""; }; + 3CA852612BF2147600B52CBA /* TraceContextInjection+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TraceContextInjection+objc.swift"; sourceTree = ""; }; 3CB012DB2B482E0400557951 /* NOPOTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NOPOTelSpan.swift; sourceTree = ""; }; 3CB012DC2B482E0400557951 /* NOPOTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NOPOTelSpanBuilder.swift; sourceTree = ""; }; 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionInstrumentation.swift; sourceTree = ""; }; @@ -4386,6 +4392,7 @@ isa = PBXGroup; children = ( 616AAA6C2BDA674C00AB9DAD /* TraceSamplingStrategy+objc.swift */, + 3CA852612BF2147600B52CBA /* TraceContextInjection+objc.swift */, 6132BF4B24A49C8F00D7BD17 /* HTTPHeadersWriter+objc.swift */, A79B0F5E292BA435008742B3 /* B3HTTPHeadersWriter+objc.swift */, A728ADAA2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift */, @@ -6168,6 +6175,7 @@ D2160C9829C0DE5700FAA9A5 /* NetworkInstrumentationFeature.swift */, D2160CEC29C0E0E600FAA9A5 /* DatadogURLSessionHandler.swift */, 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */, + 3CA8525E2BF2073800B52CBA /* TraceContextInjection.swift */, D2EBEDCC29B893D800B15732 /* TraceID.swift */, 3C9B27242B9F174700569C07 /* SpanID.swift */, D2160C9429C0DE5600FAA9A5 /* FirstPartyHosts.swift */, @@ -8016,6 +8024,7 @@ A79B0F66292BD7CA008742B3 /* B3HTTPHeadersWriter+objc.swift in Sources */, 3CCCA5C42ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift in Sources */, 61133C0E2423983800786299 /* Datadog+objc.swift in Sources */, + 3CA852642BF2148200B52CBA /* TraceContextInjection+objc.swift in Sources */, 61133C102423983800786299 /* Logs+objc.swift in Sources */, 615A4A8324A3431600233986 /* Trace+objc.swift in Sources */, 6132BF4C24A49C8F00D7BD17 /* HTTPHeadersWriter+objc.swift in Sources */, @@ -8429,6 +8438,7 @@ D2EBEE2729BA160F00B15732 /* B3HTTPHeadersWriter.swift in Sources */, D23039E2298D5236001A1FA3 /* UserInfo.swift in Sources */, D23039FB298D5236001A1FA3 /* URLRequestBuilder.swift in Sources */, + 3CA8525F2BF2073800B52CBA /* TraceContextInjection.swift in Sources */, D23039F6298D5236001A1FA3 /* Attributes.swift in Sources */, D20731CB29A52E6000ECBF94 /* Sampler.swift in Sources */, D2EBEE2029BA160F00B15732 /* TracePropagationHeadersWriter.swift in Sources */, @@ -9229,6 +9239,7 @@ D2CB6FA127C5217A00A62B57 /* HTTPHeadersWriter+objc.swift in Sources */, 616AAA6E2BDA674C00AB9DAD /* TraceSamplingStrategy+objc.swift in Sources */, D2CB6FA227C5217A00A62B57 /* DDSpan+objc.swift in Sources */, + 3CA852652BF2148400B52CBA /* TraceContextInjection+objc.swift in Sources */, D2CB6FA327C5217A00A62B57 /* OTSpan+objc.swift in Sources */, D2CB6FA427C5217A00A62B57 /* DDURLSessionDelegate+objc.swift in Sources */, D2CB6FA527C5217A00A62B57 /* RUM+objc.swift in Sources */, @@ -9350,6 +9361,7 @@ D2EBEE3529BA161100B15732 /* B3HTTPHeadersWriter.swift in Sources */, D2DA2376298D57AA00C6C7E6 /* UserInfo.swift in Sources */, D2DA2377298D57AA00C6C7E6 /* URLRequestBuilder.swift in Sources */, + 3CA852602BF2073800B52CBA /* TraceContextInjection.swift in Sources */, D2DA2378298D57AA00C6C7E6 /* Attributes.swift in Sources */, D20731CC29A52E6000ECBF94 /* Sampler.swift in Sources */, D2EBEE2E29BA161100B15732 /* TracePropagationHeadersWriter.swift in Sources */, diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme index 3992068e2e..20bcde93a6 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme @@ -196,20 +196,6 @@ BlueprintName = "DatadogCoreTests iOS" ReferencedContainer = "container:Datadog.xcodeproj"> - - - - - - - - - - diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme index d512262352..b30d9a0578 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme @@ -182,26 +182,6 @@ BlueprintName = "DatadogCoreTests tvOS" ReferencedContainer = "container:Datadog.xcodeproj"> - - - - - - - - - - - - - - diff --git a/Datadog/Example/Debugging/DebugManualTraceInjectionViewController.swift b/Datadog/Example/Debugging/DebugManualTraceInjectionViewController.swift index 44cf533a20..3a6fcaf15c 100644 --- a/Datadog/Example/Debugging/DebugManualTraceInjectionViewController.swift +++ b/Datadog/Example/Debugging/DebugManualTraceInjectionViewController.swift @@ -107,19 +107,34 @@ internal struct DebugManualTraceInjectionView: View { switch selectedTraceHeaderType { case .datadog: - let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: sampleRate)) + let writer = HTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: sampleRate), + traceContextInjection: .all + ) Tracer.shared().inject(spanContext: span.context, writer: writer) writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } case .w3c: - let writer = W3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: sampleRate), tracestate: [:]) + let writer = W3CHTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: sampleRate), + tracestate: [:], + traceContextInjection: .all + ) Tracer.shared().inject(spanContext: span.context, writer: writer) writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } case .b3Single: - let writer = B3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: sampleRate), injectEncoding: .single) + let writer = B3HTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: sampleRate), + injectEncoding: .single, + traceContextInjection: .all + ) Tracer.shared().inject(spanContext: span.context, writer: writer) writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } case .b3Multiple: - let writer = B3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: sampleRate), injectEncoding: .multiple) + let writer = B3HTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: sampleRate), + injectEncoding: .multiple, + traceContextInjection: .all + ) Tracer.shared().inject(spanContext: span.context, writer: writer) writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } } diff --git a/Datadog/IntegrationUnitTests/Trace/HeadBasedSamplingTests.swift b/Datadog/IntegrationUnitTests/Trace/HeadBasedSamplingTests.swift index 6183c5e7d1..7048d9ce06 100644 --- a/Datadog/IntegrationUnitTests/Trace/HeadBasedSamplingTests.swift +++ b/Datadog/IntegrationUnitTests/Trace/HeadBasedSamplingTests.swift @@ -130,7 +130,6 @@ class HeadBasedSamplingTests: XCTestCase { XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.samplingPriorityField), "1") } - // TODO: RUM-3535 Enable this test when trace context injection control is implemented func testSendingDroppedDistributedTraceWithNoParent_throughURLSessionInstrumentationAPI() throws { /* This is the situation where distributed trace starts with the span created with DatadogTrace network @@ -161,8 +160,8 @@ class HeadBasedSamplingTests: XCTestCase { XCTAssertFalse(span.isKept, "Span must be dropped") // Then - let expectedTraceIDField = span.traceID.idLoHex - let expectedSpanIDField = String(span.spanID, representation: .hexadecimal) + let expectedTraceIDField = span.traceID.toString(representation: .decimal) + let expectedSpanIDField = span.spanID.toString(representation: .decimal) let expectedTagsField = "_dd.p.tid=\(span.traceID.idHiHex)" XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.traceIDField), expectedTraceIDField) XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.parentSpanIDField), expectedSpanIDField) @@ -218,7 +217,6 @@ class HeadBasedSamplingTests: XCTestCase { XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.samplingPriorityField), "1") } - // TODO: RUM-3535 Enable this test when trace context injection control is implemented func testSendingDroppedDistributedTraceWithParent_throughURLSessionInstrumentationAPI() throws { /* This is the situation where distributed trace starts with an active local span and is continued with the span @@ -250,16 +248,16 @@ class HeadBasedSamplingTests: XCTestCase { let activeSpan = try XCTUnwrap(spanEvents.first(where: { $0.operationName == "active.span" })) let urlsessionSpan = try XCTUnwrap(spanEvents.first(where: { $0.operationName == "urlsession.request" })) - XCTAssertEqual(activeSpan.samplingRate, 1, "Span must use local trace sample rate") - XCTAssertTrue(activeSpan.isKept, "Span must be sampled") - XCTAssertEqual(urlsessionSpan.samplingRate, 1, "Span must use local trace sample rate") - XCTAssertTrue(urlsessionSpan.isKept, "Span must be sampled") + XCTAssertEqual(activeSpan.samplingRate, 0, "Span must use local trace sample rate") + XCTAssertFalse(activeSpan.isKept, "Span must not be sampled") + XCTAssertEqual(urlsessionSpan.samplingRate, 0, "Span must use local trace sample rate") + XCTAssertFalse(urlsessionSpan.isKept, "Span must not be sampled") XCTAssertEqual(urlsessionSpan.traceID, activeSpan.traceID) XCTAssertEqual(urlsessionSpan.parentID, activeSpan.spanID) // Then - let expectedTraceIDField = activeSpan.traceID.idLoHex - let expectedSpanIDField = String(urlsessionSpan.spanID, representation: .hexadecimal) + let expectedTraceIDField = activeSpan.traceID.toString(representation: .decimal) + let expectedSpanIDField = urlsessionSpan.spanID.toString(representation: .decimal) let expectedTagsField = "_dd.p.tid=\(activeSpan.traceID.idHiHex)" XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.traceIDField), expectedTraceIDField) XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.parentSpanIDField), expectedSpanIDField) @@ -285,7 +283,7 @@ class HeadBasedSamplingTests: XCTestCase { // When var request: URLRequest = .mockAny() - let writer = HTTPHeadersWriter(samplingStrategy: .headBased) + let writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .all) let span = Tracer.shared(in: core).startSpan(operationName: "network.span") Tracer.shared(in: core).inject(spanContext: span.context, writer: writer) writer.traceHeaderFields.forEach { field, value in request.setValue(value, forHTTPHeaderField: field) } @@ -308,7 +306,6 @@ class HeadBasedSamplingTests: XCTestCase { XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.tagsField), expectedTagsField) } - // TODO: RUM-3535 Enable this test when trace context injection control is implemented func testSendingDroppedDistributedTraceWithNoParent_throughTracerAPI() throws { /* This is the situation where distributed trace starts with the span created with Datadog tracer: @@ -325,7 +322,7 @@ class HeadBasedSamplingTests: XCTestCase { // When var request: URLRequest = .mockAny() - let writer = HTTPHeadersWriter(samplingStrategy: .headBased) + let writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .all) let span = Tracer.shared(in: core).startSpan(operationName: "network.span") Tracer.shared(in: core).inject(spanContext: span.context, writer: writer) writer.traceHeaderFields.forEach { field, value in request.setValue(value, forHTTPHeaderField: field) } @@ -338,8 +335,8 @@ class HeadBasedSamplingTests: XCTestCase { XCTAssertFalse(networkSpan.isKept, "Span must be dropped") // Then - let expectedTraceIDField = networkSpan.traceID.idLoHex - let expectedSpanIDField = String(networkSpan.spanID, representation: .hexadecimal) + let expectedTraceIDField = networkSpan.traceID.toString(representation: .decimal) + let expectedSpanIDField = networkSpan.spanID.toString(representation: .decimal) let expectedTagsField = "_dd.p.tid=\(networkSpan.traceID.idHiHex)" XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.traceIDField), expectedTraceIDField) XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.parentSpanIDField), expectedSpanIDField) @@ -365,7 +362,7 @@ class HeadBasedSamplingTests: XCTestCase { // When var request: URLRequest = .mockAny() - let writer = HTTPHeadersWriter(samplingStrategy: .headBased) + let writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .all) let parentSpan = Tracer.shared(in: core).startSpan(operationName: "active.span").setActive() let span = Tracer.shared(in: core).startSpan(operationName: "network.span") Tracer.shared(in: core).inject(spanContext: span.context, writer: writer) @@ -395,7 +392,6 @@ class HeadBasedSamplingTests: XCTestCase { XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.samplingPriorityField), "1") } - // TODO: RUM-3535 Enable this test when trace context injection control is implemented func testSendingDroppedDistributedTraceWithParent_throughTracerAPI() throws { /* This is the situation where distributed trace starts with an active local span and is continued with the span @@ -414,7 +410,7 @@ class HeadBasedSamplingTests: XCTestCase { // When var request: URLRequest = .mockAny() - let writer = HTTPHeadersWriter(samplingStrategy: .headBased) + let writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .all) let parentSpan = Tracer.shared(in: core).startSpan(operationName: "active.span").setActive() let span = Tracer.shared(in: core).startSpan(operationName: "network.span") Tracer.shared(in: core).inject(spanContext: span.context, writer: writer) @@ -435,8 +431,8 @@ class HeadBasedSamplingTests: XCTestCase { XCTAssertEqual(networkSpan.parentID, activeSpan.spanID) // Then - let expectedTraceIDField = activeSpan.traceID.idLoHex - let expectedSpanIDField = String(networkSpan.spanID, representation: .hexadecimal) + let expectedTraceIDField = activeSpan.traceID.toString(representation: .decimal) + let expectedSpanIDField = networkSpan.spanID.toString(representation: .decimal) let expectedTagsField = "_dd.p.tid=\(activeSpan.traceID.idHiHex)" XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.traceIDField), expectedTraceIDField) XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.parentSpanIDField), expectedSpanIDField) diff --git a/DatadogCore/Tests/Datadog/TracerTests.swift b/DatadogCore/Tests/Datadog/TracerTests.swift index c5a1841143..ae7d7fb263 100644 --- a/DatadogCore/Tests/Datadog/TracerTests.swift +++ b/DatadogCore/Tests/Datadog/TracerTests.swift @@ -732,7 +732,7 @@ class TracerTests: XCTestCase { let injectedContext = tracer.startSpan(operationName: .mockAny()).context // When - let writer = HTTPHeadersWriter(samplingStrategy: .headBased) + let writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .all) tracer.inject(spanContext: injectedContext, writer: writer) let reader = HTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) @@ -753,7 +753,7 @@ class TracerTests: XCTestCase { let injectedContext = tracer.startSpan(operationName: .mockAny()).context // When - let writer = B3HTTPHeadersWriter(samplingStrategy: .headBased, injectEncoding: .single) + let writer = B3HTTPHeadersWriter(samplingStrategy: .headBased, injectEncoding: .single, traceContextInjection: .all) tracer.inject(spanContext: injectedContext, writer: writer) let reader = B3HTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) @@ -774,7 +774,7 @@ class TracerTests: XCTestCase { let injectedContext = tracer.startSpan(operationName: .mockAny()).context // When - let writer = B3HTTPHeadersWriter(samplingStrategy: .headBased, injectEncoding: .multiple) + let writer = B3HTTPHeadersWriter(samplingStrategy: .headBased, injectEncoding: .multiple, traceContextInjection: .all) tracer.inject(spanContext: injectedContext, writer: writer) let reader = B3HTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) @@ -795,7 +795,7 @@ class TracerTests: XCTestCase { let injectedContext = tracer.startSpan(operationName: .mockAny()).context // When - let writer = W3CHTTPHeadersWriter(samplingStrategy: .headBased) + let writer = W3CHTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .all) tracer.inject(spanContext: injectedContext, writer: writer) let reader = W3CHTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) diff --git a/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift b/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift index c16950c36d..8b88bf6b4f 100644 --- a/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift +++ b/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift @@ -39,7 +39,8 @@ class TracingURLSessionHandlerTests: XCTestCase { distributedTraceSampler: .mockKeepAll(), firstPartyHosts: .init([ "www.example.com": [.datadog] - ]) + ]), + traceContextInjection: .all ) } diff --git a/DatadogCore/Tests/DatadogObjc/DDTracerTests.swift b/DatadogCore/Tests/DatadogObjc/DDTracerTests.swift index 22c6a1cb70..7119f7d66b 100644 --- a/DatadogCore/Tests/DatadogObjc/DDTracerTests.swift +++ b/DatadogCore/Tests/DatadogObjc/DDTracerTests.swift @@ -203,7 +203,10 @@ class DDTracerTests: XCTestCase { swiftSpanContext: DDSpanContext.mockWith(traceID: .init(idHi: 10, idLo: 100), spanID: 200) ) - let objcWriter = DDHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100)) + let objcWriter = DDHTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: 100), + traceContextInjection: .all + ) try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) let expectedHTTPHeaders = [ @@ -222,13 +225,13 @@ class DDTracerTests: XCTestCase { swiftSpanContext: DDSpanContext.mockWith(traceID: .init(idHi: 10, idLo: 100), spanID: 200) ) - let objcWriter = DDHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0)) + let objcWriter = DDHTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: 0), + traceContextInjection: .sampled + ) try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) - let expectedHTTPHeaders = [ - "x-datadog-sampling-priority": "0", - ] - XCTAssertEqual(objcWriter.traceHeaderFields, expectedHTTPHeaders) + XCTAssertEqual(objcWriter.traceHeaderFields, [:]) } func testInjectingSpanContextToInvalidCarrierOrFormat() throws { @@ -236,7 +239,10 @@ class DDTracerTests: XCTestCase { let objcTracer = DDTracer.shared() let objcSpanContext = DDSpanContextObjc(swiftSpanContext: DDSpanContext.mockWith(traceID: .init(idHi: 10, idLo: 100), spanID: 200)) - let objcValidWriter = DDHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100)) + let objcValidWriter = DDHTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: 100), + traceContextInjection: .all + ) let objcInvalidFormat = "foo" XCTAssertThrowsError( try objcTracer.inject(objcSpanContext, format: objcInvalidFormat, carrier: objcValidWriter) @@ -256,7 +262,10 @@ class DDTracerTests: XCTestCase { swiftSpanContext: DDSpanContext.mockWith(traceID: .init(idHi: 10, idLo: 100), spanID: 200) ) - let objcWriter = DDB3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100)) + let objcWriter = DDB3HTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: 100), + traceContextInjection: .all + ) try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) let expectedHTTPHeaders = [ @@ -272,7 +281,10 @@ class DDTracerTests: XCTestCase { swiftSpanContext: DDSpanContext.mockWith(traceID: .init(idHi: 10, idLo: 100), spanID: 200) ) - let objcWriter = DDB3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0)) + let objcWriter = DDB3HTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: 0), + traceContextInjection: .all + ) try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) let expectedHTTPHeaders = [ @@ -286,7 +298,10 @@ class DDTracerTests: XCTestCase { let objcTracer = DDTracer.shared() let objcSpanContext = DDSpanContextObjc(swiftSpanContext: DDSpanContext.mockWith(traceID: .init(idHi: 10, idLo: 100), spanID: 200)) - let objcValidWriter = DDB3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100)) + let objcValidWriter = DDB3HTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: 100), + traceContextInjection: .all + ) let objcInvalidFormat = "foo" XCTAssertThrowsError( try objcTracer.inject(objcSpanContext, format: objcInvalidFormat, carrier: objcValidWriter) @@ -306,7 +321,10 @@ class DDTracerTests: XCTestCase { swiftSpanContext: DDSpanContext.mockWith(traceID: .init(idHi: 10, idLo: 100), spanID: 200) ) - let objcWriter = DDW3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100)) + let objcWriter = DDW3CHTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: 100), + traceContextInjection: .all + ) try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) let expectedHTTPHeaders = [ @@ -323,7 +341,10 @@ class DDTracerTests: XCTestCase { swiftSpanContext: DDSpanContext.mockWith(traceID: .init(idHi: 10, idLo: 100), spanID: 200) ) - let objcWriter = DDW3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0)) + let objcWriter = DDW3CHTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: 0), + traceContextInjection: .all + ) try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) let expectedHTTPHeaders = [ @@ -338,7 +359,10 @@ class DDTracerTests: XCTestCase { let objcTracer = DDTracer.shared() let objcSpanContext = DDSpanContextObjc(swiftSpanContext: DDSpanContext.mockWith(traceID: .init(idHi: 10, idLo: 100), spanID: 200)) - let objcValidWriter = DDW3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100)) + let objcValidWriter = DDW3CHTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: 100), + traceContextInjection: .all + ) let objcInvalidFormat = "foo" XCTAssertThrowsError( try objcTracer.inject(objcSpanContext, format: objcInvalidFormat, carrier: objcValidWriter) diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDB3HTTPHeadersWriter+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDB3HTTPHeadersWriter+apiTests.m index e315e3ec02..35094c851d 100644 --- a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDB3HTTPHeadersWriter+apiTests.m +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDB3HTTPHeadersWriter+apiTests.m @@ -20,9 +20,11 @@ @implementation DDB3HTTPHeadersWriter_apiTests - (void)testInitWithSamplingRate { [[DDB3HTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy headBased] - injectEncoding:DDInjectEncodingSingle]; + injectEncoding:DDInjectEncodingSingle + traceContextInjection:DDTraceContextInjectionAll]; [[DDB3HTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy customWithSampleRate:50] - injectEncoding:DDInjectEncodingMultiple]; + injectEncoding:DDInjectEncodingMultiple + traceContextInjection:DDTraceContextInjectionAll]; } #pragma clang diagnostic pop diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDHTTPHeadersWriter+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDHTTPHeadersWriter+apiTests.m index 7c0f0e9ffc..1b03444744 100644 --- a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDHTTPHeadersWriter+apiTests.m +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDHTTPHeadersWriter+apiTests.m @@ -19,8 +19,8 @@ @implementation DDHTTPHeadersWriter_apiTests #pragma clang diagnostic ignored "-Wunused-value" - (void)testInitWithSamplingRate { - [[DDHTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy headBased]]; - [[DDHTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy customWithSampleRate:50]]; + [[DDHTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy headBased] traceContextInjection:DDTraceContextInjectionAll]; + [[DDHTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy customWithSampleRate:50] traceContextInjection:DDTraceContextInjectionAll]; } #pragma clang diagnostic pop diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDW3CHTTPHeadersWriter+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDW3CHTTPHeadersWriter+apiTests.m index 1c5486d0ae..ddaaa620f2 100644 --- a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDW3CHTTPHeadersWriter+apiTests.m +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDW3CHTTPHeadersWriter+apiTests.m @@ -19,8 +19,8 @@ @implementation DDW3CHTTPHeadersWriter_apiTests #pragma clang diagnostic ignored "-Wunused-value" - (void)testInitWithSamplingRate { - [[DDW3CHTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy headBased]]; - [[DDW3CHTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy customWithSampleRate:50]]; + [[DDW3CHTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy headBased] traceContextInjection:DDTraceContextInjectionAll]; + [[DDW3CHTTPHeadersWriter alloc] initWithSamplingStrategy:[DDTraceSamplingStrategy customWithSampleRate:50] traceContextInjection:DDTraceContextInjectionAll]; } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/B3/B3HTTPHeadersWriter.swift b/DatadogInternal/Sources/NetworkInstrumentation/B3/B3HTTPHeadersWriter.swift index 7f34da3e7a..6f1cd301f9 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/B3/B3HTTPHeadersWriter.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/B3/B3HTTPHeadersWriter.swift @@ -59,6 +59,9 @@ public class B3HTTPHeadersWriter: TracePropagationHeadersWriter { private let samplingStrategy: TraceSamplingStrategy + /// Defines whether the trace context should be injected into all requests or only sampled ones. + private let traceContextInjection: TraceContextInjection + /// The telemetry header encoding used by the writer. private let injectEncoding: InjectEncoding @@ -85,7 +88,8 @@ public class B3HTTPHeadersWriter: TracePropagationHeadersWriter { ) { self.init( samplingStrategy: .custom(sampleRate: sampleRate), - injectEncoding: injectEncoding + injectEncoding: injectEncoding, + traceContextInjection: .all ) } @@ -93,12 +97,15 @@ public class B3HTTPHeadersWriter: TracePropagationHeadersWriter { /// /// - Parameter samplingStrategy: The strategy for sampling trace propagation headers. /// - Parameter injectEncoding: The B3 header encoding type, with `.single` as the default. + /// - Parameter traceContextInjection: The trace context injection strategy, with `.all` as the default. public init( samplingStrategy: TraceSamplingStrategy, - injectEncoding: InjectEncoding = .single + injectEncoding: InjectEncoding = .single, + traceContextInjection: TraceContextInjection = .all ) { self.samplingStrategy = samplingStrategy self.injectEncoding = injectEncoding + self.traceContextInjection = traceContextInjection } /// Writes the trace ID, span ID, and optional parent span ID into the trace propagation headers. @@ -112,30 +119,35 @@ public class B3HTTPHeadersWriter: TracePropagationHeadersWriter { typealias Constants = B3HTTPHeaders.Constants - switch injectEncoding { - case .multiple: - traceHeaderFields = [ - B3HTTPHeaders.Multiple.sampledField: sampled ? Constants.sampledValue : Constants.unsampledValue - ] - - if sampled { - traceHeaderFields[B3HTTPHeaders.Multiple.traceIDField] = String(traceContext.traceID, representation: .hexadecimal32Chars) - traceHeaderFields[B3HTTPHeaders.Multiple.spanIDField] = String(traceContext.spanID, representation: .hexadecimal16Chars) - traceHeaderFields[B3HTTPHeaders.Multiple.parentSpanIDField] = traceContext.parentSpanID.map { String($0, representation: .hexadecimal16Chars) } - } - case .single: - if sampled { - traceHeaderFields[B3HTTPHeaders.Single.b3Field] = [ - String(traceContext.traceID, representation: .hexadecimal32Chars), - String(traceContext.spanID, representation: .hexadecimal16Chars), - sampled ? Constants.sampledValue : Constants.unsampledValue, - traceContext.parentSpanID.map { String($0, representation: .hexadecimal16Chars) } + switch (traceContextInjection, sampled) { + case (.all, _), (.sampled, true): + switch injectEncoding { + case .multiple: + traceHeaderFields = [ + B3HTTPHeaders.Multiple.sampledField: sampled ? Constants.sampledValue : Constants.unsampledValue ] - .compactMap { $0 } - .joined(separator: Constants.b3Separator) - } else { - traceHeaderFields[B3HTTPHeaders.Single.b3Field] = Constants.unsampledValue + + if sampled { + traceHeaderFields[B3HTTPHeaders.Multiple.traceIDField] = String(traceContext.traceID, representation: .hexadecimal32Chars) + traceHeaderFields[B3HTTPHeaders.Multiple.spanIDField] = String(traceContext.spanID, representation: .hexadecimal16Chars) + traceHeaderFields[B3HTTPHeaders.Multiple.parentSpanIDField] = traceContext.parentSpanID.map { String($0, representation: .hexadecimal16Chars) } + } + case .single: + if sampled { + traceHeaderFields[B3HTTPHeaders.Single.b3Field] = [ + String(traceContext.traceID, representation: .hexadecimal32Chars), + String(traceContext.spanID, representation: .hexadecimal16Chars), + sampled ? Constants.sampledValue : Constants.unsampledValue, + traceContext.parentSpanID.map { String($0, representation: .hexadecimal16Chars) } + ] + .compactMap { $0 } + .joined(separator: Constants.b3Separator) + } else { + traceHeaderFields[B3HTTPHeaders.Single.b3Field] = Constants.unsampledValue + } } + case (.sampled, false): + break } } } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/Datadog/HTTPHeadersWriter.swift b/DatadogInternal/Sources/NetworkInstrumentation/Datadog/HTTPHeadersWriter.swift index b634f27d54..1d38b2e43f 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/Datadog/HTTPHeadersWriter.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/Datadog/HTTPHeadersWriter.swift @@ -35,6 +35,7 @@ public class HTTPHeadersWriter: TracePropagationHeadersWriter { public private(set) var traceHeaderFields: [String: String] = [:] private let samplingStrategy: TraceSamplingStrategy + private let traceContextInjection: TraceContextInjection /// Initializes the headers writer. /// @@ -49,14 +50,19 @@ public class HTTPHeadersWriter: TracePropagationHeadersWriter { /// - Parameter sampleRate: The sampling rate applied for headers injection, with 20% as the default. @available(*, deprecated, message: "This will be removed in future versions of the SDK. Use `init(samplingStrategy: .custom(sampleRate:))` instead.") public convenience init(sampleRate: Float = 20) { - self.init(samplingStrategy: .custom(sampleRate: sampleRate)) + self.init(samplingStrategy: .custom(sampleRate: sampleRate), traceContextInjection: .all) } /// Initializes the headers writer. /// /// - Parameter samplingStrategy: The strategy for sampling trace propagation headers. - public init(samplingStrategy: TraceSamplingStrategy) { + /// - Parameter traceContextInjection: The strategy for injecting trace context into requests. + public init( + samplingStrategy: TraceSamplingStrategy, + traceContextInjection: TraceContextInjection + ) { self.samplingStrategy = samplingStrategy + self.traceContextInjection = traceContextInjection } /// Writes the trace ID, span ID, and optional parent span ID into the trace propagation headers. @@ -68,14 +74,16 @@ public class HTTPHeadersWriter: TracePropagationHeadersWriter { let sampler = samplingStrategy.sampler(for: traceContext) let sampled = sampler.sample() - traceHeaderFields = [ - TracingHTTPHeaders.samplingPriorityField: sampled ? "1" : "0" - ] - - if sampled { + switch (traceContextInjection, sampled) { + case (.all, _), (.sampled, true): + traceHeaderFields = [ + TracingHTTPHeaders.samplingPriorityField: sampled ? "1" : "0" + ] traceHeaderFields[TracingHTTPHeaders.traceIDField] = String(traceContext.traceID.idLo) traceHeaderFields[TracingHTTPHeaders.parentSpanIDField] = String(traceContext.spanID, representation: .decimal) traceHeaderFields[TracingHTTPHeaders.tagsField] = "_dd.p.tid=\(traceContext.traceID.idHiHex)" + case (.sampled, false): + break } } } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/TraceContextInjection.swift b/DatadogInternal/Sources/NetworkInstrumentation/TraceContextInjection.swift new file mode 100644 index 0000000000..b890b32ceb --- /dev/null +++ b/DatadogInternal/Sources/NetworkInstrumentation/TraceContextInjection.swift @@ -0,0 +1,16 @@ +/* + * 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 + +/// Defines whether the trace context should be injected into all requests or only sampled ones. +public enum TraceContextInjection { + /// Injects trace context into all requests irrespective of the sampling decision. + case all + + /// Injects trace context only into sampled requests. + case sampled +} diff --git a/DatadogInternal/Sources/NetworkInstrumentation/W3C/W3CHTTPHeadersWriter.swift b/DatadogInternal/Sources/NetworkInstrumentation/W3C/W3CHTTPHeadersWriter.swift index 036f07feca..8b88138414 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/W3C/W3CHTTPHeadersWriter.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/W3C/W3CHTTPHeadersWriter.swift @@ -39,6 +39,7 @@ public class W3CHTTPHeadersWriter: TracePropagationHeadersWriter { private let tracestate: [String: String] private let samplingStrategy: TraceSamplingStrategy + private let traceContextInjection: TraceContextInjection /// Initializes the headers writer. /// @@ -55,16 +56,22 @@ public class W3CHTTPHeadersWriter: TracePropagationHeadersWriter { /// - Parameter tracestate: The tracestate to be injected. @available(*, deprecated, message: "This will be removed in future versions of the SDK. Use `init(samplingStrategy: .custom(sampleRate:))` instead.") public convenience init(sampleRate: Float = 20, tracestate: [String: String] = [:]) { - self.init(samplingStrategy: .custom(sampleRate: sampleRate), tracestate: tracestate) + self.init(samplingStrategy: .custom(sampleRate: sampleRate), tracestate: tracestate, traceContextInjection: .all) } /// Initializes the headers writer. /// /// - Parameter samplingStrategy: The strategy for sampling trace propagation headers. /// - Parameter tracestate: The tracestate to be injected. - public init(samplingStrategy: TraceSamplingStrategy, tracestate: [String: String] = [:]) { + /// - Parameter traceContextInjection: The strategy for injecting trace context into requests. + public init( + samplingStrategy: TraceSamplingStrategy, + tracestate: [String: String] = [:], + traceContextInjection: TraceContextInjection = .all + ) { self.samplingStrategy = samplingStrategy self.tracestate = tracestate + self.traceContextInjection = traceContextInjection } /// Writes the trace ID, span ID, and optional parent span ID into the trace propagation headers. @@ -78,28 +85,33 @@ public class W3CHTTPHeadersWriter: TracePropagationHeadersWriter { let sampler = samplingStrategy.sampler(for: traceContext) let sampled = sampler.sample() - traceHeaderFields[W3CHTTPHeaders.traceparent] = [ - Constants.version, - String(traceContext.traceID, representation: .hexadecimal32Chars), - String(traceContext.spanID, representation: .hexadecimal16Chars), - sampled ? Constants.sampledValue : Constants.unsampledValue - ] - .joined(separator: Constants.separator) + switch (traceContextInjection, sampled) { + case (.all, _), (.sampled, true): + traceHeaderFields[W3CHTTPHeaders.traceparent] = [ + Constants.version, + String(traceContext.traceID, representation: .hexadecimal32Chars), + String(traceContext.spanID, representation: .hexadecimal16Chars), + sampled ? Constants.sampledValue : Constants.unsampledValue + ] + .joined(separator: Constants.separator) - // while merging, the tracestate values from the tracestate property take precedence - // over the ones from the trace context - let tracestate: [String: String] = [ - Constants.sampling: "\(sampled ? 1 : 0)", - Constants.parentId: String(traceContext.spanID, representation: .hexadecimal16Chars) - ].merging(tracestate) { old, new in - return new - } + // while merging, the tracestate values from the tracestate property take precedence + // over the ones from the trace context + let tracestate: [String: String] = [ + Constants.sampling: "\(sampled ? 1 : 0)", + Constants.parentId: String(traceContext.spanID, representation: .hexadecimal16Chars) + ].merging(tracestate) { old, new in + return new + } - let ddtracestate = tracestate - .map { "\($0.key)\(Constants.tracestateKeyValueSeparator)\($0.value)" } - .sorted() - .joined(separator: Constants.tracestatePairSeparator) + let ddtracestate = tracestate + .map { "\($0.key)\(Constants.tracestateKeyValueSeparator)\($0.value)" } + .sorted() + .joined(separator: Constants.tracestatePairSeparator) - traceHeaderFields[W3CHTTPHeaders.tracestate] = "\(Constants.dd)=\(ddtracestate)" + traceHeaderFields[W3CHTTPHeaders.tracestate] = "\(Constants.dd)=\(ddtracestate)" + case (.sampled, false): + break + } } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersReaderTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersReaderTests.swift index cec2091c1c..4969ac0831 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersReaderTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersReaderTests.swift @@ -81,7 +81,7 @@ class B3HTTPHeadersReaderTests: XCTestCase { func testReadingSampledTraceContext() { let encoding: B3HTTPHeadersWriter.InjectEncoding = [.multiple, .single].randomElement()! - let writer = B3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100), injectEncoding: encoding) + let writer = B3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100), injectEncoding: encoding, traceContextInjection: .all) writer.write(traceContext: .mockRandom()) let reader = B3HTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) @@ -91,7 +91,7 @@ class B3HTTPHeadersReaderTests: XCTestCase { func testReadingNotSampledTraceContext() { let encoding: B3HTTPHeadersWriter.InjectEncoding = [.multiple, .single].randomElement()! - let writer = B3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), injectEncoding: encoding) + let writer = B3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), injectEncoding: encoding, traceContextInjection: .all) writer.write(traceContext: .mockRandom()) let reader = B3HTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) diff --git a/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersWriterTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersWriterTests.swift index 7491012888..74bad2fb05 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersWriterTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersWriterTests.swift @@ -12,7 +12,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testWritingSampledTraceContext_withSingleEncoding_andAutoSamplingStrategy() { let writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .single + injectEncoding: .single, + traceContextInjection: .all ) writer.write( @@ -31,7 +32,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testWritingDroppedTraceContext_withSingleEncoding_andAutoSamplingStrategy() { let writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .single + injectEncoding: .single, + traceContextInjection: .all ) writer.write( @@ -50,7 +52,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testWritingSampledTraceContext_withSingleEncoding_andCustomSamplingStrategy() { let writer = B3HTTPHeadersWriter( samplingStrategy: .custom(sampleRate: 100), - injectEncoding: .single + injectEncoding: .single, + traceContextInjection: .all ) writer.write( @@ -69,7 +72,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testWritingDroppedTraceContext_withSingleEncoding_andCustomSamplingStrategy() { let writer = B3HTTPHeadersWriter( samplingStrategy: .custom(sampleRate: 0), - injectEncoding: .single + injectEncoding: .single, + traceContextInjection: .all ) writer.write( @@ -88,7 +92,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testItWritesSingleHeaderWithoutOptionalValues() { let writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .single + injectEncoding: .single, + traceContextInjection: .all ) writer.write( @@ -106,7 +111,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testWritingSampledTraceContext_withMultipleEncoding_andAutoSamplingStrategy() { let writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .multiple + injectEncoding: .multiple, + traceContextInjection: .all ) writer.write( @@ -128,7 +134,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testWritingDroppedTraceContext_withMultipleEncoding_andAutoSamplingStrategy() { let writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .multiple + injectEncoding: .multiple, + traceContextInjection: .all ) writer.write( @@ -150,7 +157,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testWritingSampledTraceContext_withMultipleEncoding_andCustomSamplingStrategy() { let writer = B3HTTPHeadersWriter( samplingStrategy: .custom(sampleRate: 100), - injectEncoding: .multiple + injectEncoding: .multiple, + traceContextInjection: .all ) writer.write( @@ -172,7 +180,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testWritingDroppedTraceContext_withMultipleEncoding_andCustomSamplingStrategy() { let writer = B3HTTPHeadersWriter( samplingStrategy: .custom(sampleRate: 0), - injectEncoding: .multiple + injectEncoding: .multiple, + traceContextInjection: .all ) writer.write( @@ -194,7 +203,8 @@ class B3HTTPHeadersWriterTests: XCTestCase { func testItWritesMultipleHeaderWithoutOptionalValues() { let writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .multiple + injectEncoding: .multiple, + traceContextInjection: .all ) writer.write( diff --git a/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersReaderTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersReaderTests.swift index 880aa4f9d3..952a191685 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersReaderTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersReaderTests.swift @@ -10,7 +10,7 @@ import TestUtilities class HTTPHeadersReaderTests: XCTestCase { func testReadingSampledTraceContext() { - let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100)) + let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100), traceContextInjection: .all) writer.write(traceContext: .mockRandom()) let reader = HTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) @@ -19,11 +19,11 @@ class HTTPHeadersReaderTests: XCTestCase { } func testReadingNotSampledTraceContext() { - let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0)) + let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), traceContextInjection: .sampled) writer.write(traceContext: .mockRandom()) let reader = HTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) XCTAssertNil(reader.read(), "When not sampled, it should return no trace context") - XCTAssertEqual(reader.sampled, false) + XCTAssertNil(reader.sampled) } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersWriterTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersWriterTests.swift index c104331c82..278d244c9d 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersWriterTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersWriterTests.swift @@ -10,7 +10,7 @@ import DatadogInternal class HTTPHeadersWriterTests: XCTestCase { func testWritingSampledTraceContext_withAutoSamplingStrategy() { - let writer = HTTPHeadersWriter(samplingStrategy: .headBased) + let writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .all) writer.write( traceContext: .mockWith( @@ -28,7 +28,7 @@ class HTTPHeadersWriterTests: XCTestCase { } func testWritingDroppedTraceContext_withAutoSamplingStrategy() { - let writer = HTTPHeadersWriter(samplingStrategy: .headBased) + let writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .sampled) writer.write( traceContext: .mockWith( @@ -39,14 +39,14 @@ class HTTPHeadersWriterTests: XCTestCase { ) let headers = writer.traceHeaderFields - XCTAssertEqual(headers[TracingHTTPHeaders.samplingPriorityField], "0") + XCTAssertNil(headers[TracingHTTPHeaders.samplingPriorityField]) XCTAssertNil(headers[TracingHTTPHeaders.traceIDField]) XCTAssertNil(headers[TracingHTTPHeaders.parentSpanIDField]) XCTAssertNil(headers[TracingHTTPHeaders.tagsField]) } func testWritingSampledTraceContext_withCustomSamplingStrategy() { - let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100)) + let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100), traceContextInjection: .all) writer.write( traceContext: .mockWith( @@ -64,7 +64,7 @@ class HTTPHeadersWriterTests: XCTestCase { } func testWritingDroppedTraceContext_withCustomSamplingStrategy() { - let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0)) + let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), traceContextInjection: .sampled) writer.write( traceContext: .mockWith( @@ -75,7 +75,7 @@ class HTTPHeadersWriterTests: XCTestCase { ) let headers = writer.traceHeaderFields - XCTAssertEqual(headers[TracingHTTPHeaders.samplingPriorityField], "0") + XCTAssertNil(headers[TracingHTTPHeaders.samplingPriorityField]) XCTAssertNil(headers[TracingHTTPHeaders.traceIDField]) XCTAssertNil(headers[TracingHTTPHeaders.parentSpanIDField]) XCTAssertNil(headers[TracingHTTPHeaders.tagsField]) diff --git a/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersReaderTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersReaderTests.swift index 6778803612..999cfe1c6f 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersReaderTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersReaderTests.swift @@ -27,7 +27,7 @@ class W3CHTTPHeadersReaderTests: XCTestCase { } func testReadingSampledTraceContext() { - let writer = W3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100)) + let writer = W3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 100), traceContextInjection: .all) writer.write(traceContext: .mockRandom()) let reader = W3CHTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) @@ -36,7 +36,7 @@ class W3CHTTPHeadersReaderTests: XCTestCase { } func testReadingNotSampledTraceContext() { - let writer = W3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0)) + let writer = W3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), traceContextInjection: .all) writer.write(traceContext: .mockRandom()) let reader = W3CHTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) diff --git a/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersWriterTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersWriterTests.swift index 931714ca3e..4f6df1631f 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersWriterTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersWriterTests.swift @@ -14,7 +14,8 @@ class W3CHTTPHeadersWriterTests: XCTestCase { samplingStrategy: .headBased, tracestate: [ W3CHTTPHeaders.Constants.origin: W3CHTTPHeaders.Constants.originRUM - ] + ], + traceContextInjection: .all ) writer.write( @@ -35,7 +36,8 @@ class W3CHTTPHeadersWriterTests: XCTestCase { samplingStrategy: .headBased, tracestate: [ W3CHTTPHeaders.Constants.origin: W3CHTTPHeaders.Constants.originRUM - ] + ], + traceContextInjection: .all ) writer.write( @@ -57,7 +59,8 @@ class W3CHTTPHeadersWriterTests: XCTestCase { samplingStrategy: .custom(sampleRate: 100), tracestate: [ W3CHTTPHeaders.Constants.origin: W3CHTTPHeaders.Constants.originRUM - ] + ], + traceContextInjection: .all ) writer.write( @@ -78,7 +81,8 @@ class W3CHTTPHeadersWriterTests: XCTestCase { samplingStrategy: .custom(sampleRate: 0), tracestate: [ W3CHTTPHeaders.Constants.origin: W3CHTTPHeaders.Constants.originRUM - ] + ], + traceContextInjection: .all ) writer.write( diff --git a/DatadogObjc/Sources/Tracing/Propagation/B3HTTPHeadersWriter+objc.swift b/DatadogObjc/Sources/Tracing/Propagation/B3HTTPHeadersWriter+objc.swift index b55ab262c9..d125e09d72 100644 --- a/DatadogObjc/Sources/Tracing/Propagation/B3HTTPHeadersWriter+objc.swift +++ b/DatadogObjc/Sources/Tracing/Propagation/B3HTTPHeadersWriter+objc.swift @@ -53,18 +53,21 @@ public class DDB3HTTPHeadersWriter: NSObject { ) { swiftB3HTTPHeadersWriter = B3HTTPHeadersWriter( samplingStrategy: .custom(sampleRate: sampleRate), - injectEncoding: .init(injectEncoding) + injectEncoding: .init(injectEncoding), + traceContextInjection: .all ) } @objc public init( samplingStrategy: DDTraceSamplingStrategy, - injectEncoding: DDInjectEncoding = .single + injectEncoding: DDInjectEncoding = .single, + traceContextInjection: DDTraceContextInjection ) { swiftB3HTTPHeadersWriter = B3HTTPHeadersWriter( samplingStrategy: samplingStrategy.swiftType, - injectEncoding: .init(injectEncoding) + injectEncoding: .init(injectEncoding), + traceContextInjection: traceContextInjection.swiftType ) } } diff --git a/DatadogObjc/Sources/Tracing/Propagation/HTTPHeadersWriter+objc.swift b/DatadogObjc/Sources/Tracing/Propagation/HTTPHeadersWriter+objc.swift index 24d759f25f..c5e9ccbf1e 100644 --- a/DatadogObjc/Sources/Tracing/Propagation/HTTPHeadersWriter+objc.swift +++ b/DatadogObjc/Sources/Tracing/Propagation/HTTPHeadersWriter+objc.swift @@ -25,14 +25,19 @@ public class DDHTTPHeadersWriter: NSObject { @available(*, deprecated, message: "This will be removed in future versions of the SDK. Use `init(samplingStrategy: .custom(sampleRate:))` instead.") public init(sampleRate: Float = 20) { swiftHTTPHeadersWriter = HTTPHeadersWriter( - samplingStrategy: .custom(sampleRate: sampleRate) + samplingStrategy: .custom(sampleRate: sampleRate), + traceContextInjection: .all ) } @objc - public init(samplingStrategy: DDTraceSamplingStrategy) { + public init( + samplingStrategy: DDTraceSamplingStrategy, + traceContextInjection: DDTraceContextInjection + ) { swiftHTTPHeadersWriter = HTTPHeadersWriter( - samplingStrategy: samplingStrategy.swiftType + samplingStrategy: samplingStrategy.swiftType, + traceContextInjection: traceContextInjection.swiftType ) } } diff --git a/DatadogObjc/Sources/Tracing/Propagation/TraceContextInjection+objc.swift b/DatadogObjc/Sources/Tracing/Propagation/TraceContextInjection+objc.swift new file mode 100644 index 0000000000..829e5f92ac --- /dev/null +++ b/DatadogObjc/Sources/Tracing/Propagation/TraceContextInjection+objc.swift @@ -0,0 +1,27 @@ +/* + * 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 +import DatadogInternal + +/// Defines whether the trace context should be injected into all requests or only sampled ones. +@objc +public enum DDTraceContextInjection: Int { + internal var swiftType: DatadogInternal.TraceContextInjection { + switch self { + case .all: + return .all + case .sampled: + return .sampled + } + } + + /// Injects trace context into all requests irrespective of the sampling decision. + case all + + /// Injects trace context only into sampled requests. + case sampled +} diff --git a/DatadogObjc/Sources/Tracing/Propagation/W3CHTTPHeadersWriter+objc.swift b/DatadogObjc/Sources/Tracing/Propagation/W3CHTTPHeadersWriter+objc.swift index 1ef16bc401..13841a2c7f 100644 --- a/DatadogObjc/Sources/Tracing/Propagation/W3CHTTPHeadersWriter+objc.swift +++ b/DatadogObjc/Sources/Tracing/Propagation/W3CHTTPHeadersWriter+objc.swift @@ -26,17 +26,20 @@ public class DDW3CHTTPHeadersWriter: NSObject { public init(sampleRate: Float = 20) { swiftW3CHTTPHeadersWriter = W3CHTTPHeadersWriter( samplingStrategy: .custom(sampleRate: sampleRate), - tracestate: [:] + tracestate: [:], + traceContextInjection: .all ) } @objc public init( - samplingStrategy: DDTraceSamplingStrategy + samplingStrategy: DDTraceSamplingStrategy, + traceContextInjection: DDTraceContextInjection ) { swiftW3CHTTPHeadersWriter = W3CHTTPHeadersWriter( samplingStrategy: samplingStrategy.swiftType, - tracestate: [:] + tracestate: [:], + traceContextInjection: traceContextInjection.swiftType ) } } diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index 50886c9444..ed6f92bac0 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -43,9 +43,9 @@ internal final class RUMFeature: DatadogRemoteFeature { trackFrustrations: configuration.trackFrustrations, firstPartyHosts: { switch configuration.urlSessionTracking?.firstPartyHostsTracing { - case let .trace(hosts, _): + case let .trace(hosts, _, _): return FirstPartyHosts(hosts) - case let .traceWithHeaders(hostsWithHeaders, _): + case let .traceWithHeaders(hostsWithHeaders, _, _): return FirstPartyHosts(hostsWithHeaders) case .none: return nil @@ -166,8 +166,8 @@ extension RUMFeature: Flushable { private extension RUM.Configuration.URLSessionTracking.FirstPartyHostsTracing { var sampleRate: Float { switch self { - case .trace(_, let sampleRate): return sampleRate - case .traceWithHeaders(_, let sampleRate): return sampleRate + case .trace(_, let sampleRate, _): return sampleRate + case .traceWithHeaders(_, let sampleRate, _): return sampleRate } } } diff --git a/DatadogRUM/Sources/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift b/DatadogRUM/Sources/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift index ab39b9c1cf..8c30d73983 100644 --- a/DatadogRUM/Sources/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift +++ b/DatadogRUM/Sources/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift @@ -15,17 +15,21 @@ internal struct DistributedTracing { let spanIDGenerator: SpanIDGenerator /// First party hosts defined by the user. let firstPartyHosts: FirstPartyHosts + /// Trace context injection configuration to determine whether the trace context should be injected or not. + let traceContextInjection: TraceContextInjection init( sampler: Sampler, firstPartyHosts: FirstPartyHosts, traceIDGenerator: TraceIDGenerator, - spanIDGenerator: SpanIDGenerator + spanIDGenerator: SpanIDGenerator, + traceContextInjection: TraceContextInjection ) { self.sampler = sampler self.traceIDGenerator = traceIDGenerator self.spanIDGenerator = spanIDGenerator self.firstPartyHosts = firstPartyHosts + self.traceContextInjection = traceContextInjection } } @@ -161,25 +165,31 @@ extension DistributedTracing { let writer: TracePropagationHeadersWriter switch $0 { case .datadog: - writer = HTTPHeadersWriter(samplingStrategy: .headBased) + writer = HTTPHeadersWriter( + samplingStrategy: .headBased, + traceContextInjection: traceContextInjection + ) // To make sure the generated traces from RUM don’t affect APM Index Spans counts. request.setValue("rum", forHTTPHeaderField: TracingHTTPHeaders.originField) case .b3: writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .single + injectEncoding: .single, + traceContextInjection: traceContextInjection ) case .b3multi: writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .multiple + injectEncoding: .multiple, + traceContextInjection: traceContextInjection ) case .tracecontext: writer = W3CHTTPHeadersWriter( samplingStrategy: .headBased, tracestate: [ W3CHTTPHeaders.Constants.origin: W3CHTTPHeaders.Constants.originRUM - ] + ], + traceContextInjection: traceContextInjection ) } diff --git a/DatadogRUM/Sources/RUM+Internal.swift b/DatadogRUM/Sources/RUM+Internal.swift index 19c6286454..6c88d1f00a 100644 --- a/DatadogRUM/Sources/RUM+Internal.swift +++ b/DatadogRUM/Sources/RUM+Internal.swift @@ -49,19 +49,21 @@ extension InternalExtension where ExtendedType == RUM { // If first party hosts are configured, enable distributed tracing: switch configuration.firstPartyHostsTracing { - case let .trace(hosts, sampleRate): + case let .trace(hosts, sampleRate, traceContextInjection): distributedTracing = DistributedTracing( sampler: Sampler(samplingRate: rumConfiguration.debugSDK ? 100 : sampleRate), firstPartyHosts: FirstPartyHosts(hosts), traceIDGenerator: rumConfiguration.traceIDGenerator, - spanIDGenerator: rumConfiguration.spanIDGenerator + spanIDGenerator: rumConfiguration.spanIDGenerator, + traceContextInjection: traceContextInjection ) - case let .traceWithHeaders(hostsWithHeaders, sampleRate): + case let .traceWithHeaders(hostsWithHeaders, sampleRate, traceContextInjection): distributedTracing = DistributedTracing( sampler: Sampler(samplingRate: rumConfiguration.debugSDK ? 100 : sampleRate), firstPartyHosts: FirstPartyHosts(hostsWithHeaders), traceIDGenerator: rumConfiguration.traceIDGenerator, - spanIDGenerator: rumConfiguration.spanIDGenerator + spanIDGenerator: rumConfiguration.spanIDGenerator, + traceContextInjection: traceContextInjection ) case .none: distributedTracing = nil diff --git a/DatadogRUM/Sources/RUMConfiguration.swift b/DatadogRUM/Sources/RUMConfiguration.swift index bc8d6a05d2..d5921f5db4 100644 --- a/DatadogRUM/Sources/RUMConfiguration.swift +++ b/DatadogRUM/Sources/RUMConfiguration.swift @@ -294,13 +294,23 @@ extension RUM.Configuration.URLSessionTracking { /// - Parameters: /// - hosts: The set of hosts to inject tracing headers. Note: Hosts must not include the "http(s)://" prefix. /// - sampleRate: The sampling rate for tracing. Must be a value between `0.0` and `100.0`. Default: `20`. - case trace(hosts: Set, sampleRate: Float = 20) + /// - traceControlInjection: The strategy for injecting trace context into requests. Default: `.all`. + case trace( + hosts: Set, + sampleRate: Float = 20, + traceControlInjection: TraceContextInjection = .all + ) /// Trace given hosts with using custom tracing headers. /// /// - `hostsWithHeaders` - Dictionary of hosts and tracing header types to use. Note: Hosts must not include "http(s)://" prefix. /// - `sampleRate` - The sampling rate for tracing. Must be a value between `0.0` and `100.0`. Default: `20`. - case traceWithHeaders(hostsWithHeaders: [String: Set], sampleRate: Float = 20) + /// - `traceControlInjection` - The strategy for injecting trace context into requests. Default: `.all`. + case traceWithHeaders( + hostsWithHeaders: [String: Set], + sampleRate: Float = 20, + traceControlInjection: TraceContextInjection = .all + ) } /// Configuration for automatic RUM resources tracking. diff --git a/DatadogRUM/Tests/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift b/DatadogRUM/Tests/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift index d4d5a7c5aa..b8ffaad2db 100644 --- a/DatadogRUM/Tests/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift +++ b/DatadogRUM/Tests/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift @@ -35,7 +35,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockKeepAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .all ) ) @@ -66,7 +67,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockKeepAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .all ) ) @@ -94,7 +96,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockKeepAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .all ) ) @@ -125,7 +128,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockKeepAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .all ) ) @@ -153,7 +157,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockRejectAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .sampled ) ) @@ -166,7 +171,7 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.originField), "rum") XCTAssertNil(request.value(forHTTPHeaderField: TracingHTTPHeaders.traceIDField)) XCTAssertNil(request.value(forHTTPHeaderField: TracingHTTPHeaders.parentSpanIDField)) - XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.samplingPriorityField), "0") + XCTAssertNil(request.value(forHTTPHeaderField: TracingHTTPHeaders.samplingPriorityField)) XCTAssertNil(traceContext, "It must return no trace context") } @@ -178,7 +183,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockRejectAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .all ) ) @@ -201,7 +207,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockRejectAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .all ) ) @@ -227,7 +234,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockRejectAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .all ) ) @@ -250,7 +258,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockKeepAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .all ) ) @@ -332,7 +341,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: Sampler(samplingRate: Float(traceSamplingRate)), firstPartyHosts: .init(), traceIDGenerator: DefaultTraceIDGenerator(), - spanIDGenerator: DefaultSpanIDGenerator() + spanIDGenerator: DefaultSpanIDGenerator(), + traceContextInjection: .all ) ) @@ -515,7 +525,8 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { sampler: .mockKeepAll(), firstPartyHosts: .init(), traceIDGenerator: RelativeTracingUUIDGenerator(startingFrom: .init(idHi: 10, idLo: 100)), - spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0) + spanIDGenerator: RelativeSpanIDGenerator(startingFrom: 100, advancingByCount: 0), + traceContextInjection: .all ) ) let request: URLRequest = .mockWith(httpMethod: "GET") diff --git a/DatadogTrace/Sources/Integrations/TracingURLSessionHandler.swift b/DatadogTrace/Sources/Integrations/TracingURLSessionHandler.swift index 9dec872ec5..8a30ed8e0a 100644 --- a/DatadogTrace/Sources/Integrations/TracingURLSessionHandler.swift +++ b/DatadogTrace/Sources/Integrations/TracingURLSessionHandler.swift @@ -14,6 +14,8 @@ internal struct TracingURLSessionHandler: DatadogURLSessionHandler { let distributedTraceSampler: Sampler /// First party hosts defined by the user. let firstPartyHosts: FirstPartyHosts + /// Trace context injection configuration to determine whether the trace context should be injected or not. + let traceContextInjection: TraceContextInjection weak var tracer: DatadogTracer? @@ -21,12 +23,14 @@ internal struct TracingURLSessionHandler: DatadogURLSessionHandler { tracer: DatadogTracer, contextReceiver: ContextMessageReceiver, distributedTraceSampler: Sampler, - firstPartyHosts: FirstPartyHosts + firstPartyHosts: FirstPartyHosts, + traceContextInjection: TraceContextInjection ) { self.tracer = tracer self.contextReceiver = contextReceiver self.distributedTraceSampler = distributedTraceSampler self.firstPartyHosts = firstPartyHosts + self.traceContextInjection = traceContextInjection } func modify(request: URLRequest, headerTypes: Set) -> (URLRequest, TraceContext?) { @@ -55,21 +59,24 @@ internal struct TracingURLSessionHandler: DatadogURLSessionHandler { let writer: TracePropagationHeadersWriter switch $0 { case .datadog: - writer = HTTPHeadersWriter(samplingStrategy: .headBased) + writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: traceContextInjection) case .b3: writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .single + injectEncoding: .single, + traceContextInjection: traceContextInjection ) case .b3multi: writer = B3HTTPHeadersWriter( samplingStrategy: .headBased, - injectEncoding: .multiple + injectEncoding: .multiple, + traceContextInjection: traceContextInjection ) case .tracecontext: writer = W3CHTTPHeadersWriter( samplingStrategy: .headBased, - tracestate: [:] + tracestate: [:], + traceContextInjection: traceContextInjection ) } @@ -84,7 +91,7 @@ internal struct TracingURLSessionHandler: DatadogURLSessionHandler { } } - return (request, (hasSetAnyHeader && injectedSpanContext.isKept) ? injectedSpanContext : nil) + return (request, hasSetAnyHeader ? injectedSpanContext : nil) } func interceptionDidStart(interception: DatadogInternal.URLSessionTaskInterception) { diff --git a/DatadogTrace/Sources/Trace.swift b/DatadogTrace/Sources/Trace.swift index e523314b50..24717180ae 100644 --- a/DatadogTrace/Sources/Trace.swift +++ b/DatadogTrace/Sources/Trace.swift @@ -43,21 +43,25 @@ public enum Trace { if let firstPartyHostsTracing = configuration.urlSessionTracking?.firstPartyHostsTracing { let distributedTraceSampler: Sampler let firstPartyHosts: FirstPartyHosts + let traceContextInjection: TraceContextInjection switch firstPartyHostsTracing { - case let .trace(hosts, sampleRate): + case let .trace(hosts, sampleRate, injection): distributedTraceSampler = Sampler(samplingRate: configuration.debugSDK ? 100 : sampleRate) firstPartyHosts = FirstPartyHosts(hosts) - case let .traceWithHeaders(hostsWithHeaders, sampleRate): + traceContextInjection = injection + case let .traceWithHeaders(hostsWithHeaders, sampleRate, injection): distributedTraceSampler = Sampler(samplingRate: configuration.debugSDK ? 100 : sampleRate) firstPartyHosts = FirstPartyHosts(hostsWithHeaders) + traceContextInjection = injection } let urlSessionHandler = TracingURLSessionHandler( tracer: trace.tracer, contextReceiver: trace.contextReceiver, distributedTraceSampler: distributedTraceSampler, - firstPartyHosts: firstPartyHosts + firstPartyHosts: firstPartyHosts, + traceContextInjection: traceContextInjection ) try core.register(urlSessionHandler: urlSessionHandler) diff --git a/DatadogTrace/Sources/TraceConfiguration.swift b/DatadogTrace/Sources/TraceConfiguration.swift index 0de05d1825..9a21d8b612 100644 --- a/DatadogTrace/Sources/TraceConfiguration.swift +++ b/DatadogTrace/Sources/TraceConfiguration.swift @@ -104,13 +104,23 @@ extension Trace { /// - Parameters: /// - hosts: The set of hosts to inject tracing headers. Note: Hosts must not include the "http(s)://" prefix. /// - sampleRate: The sampling rate for tracing. Must be a value between `0.0` and `100.0`. Default: `20`. - case trace(hosts: Set, sampleRate: Float = 20) + /// - traceControlInjection: The strategy for injecting trace context into requests. Default: `.all`. + case trace( + hosts: Set, + sampleRate: Float = 20, + traceControlInjection: TraceContextInjection = .all + ) /// Trace given hosts with using custom tracing headers. /// /// - `hostsWithHeaders` - Dictionary of hosts and tracing header types to use. Note: Hosts must not include "http(s)://" prefix. /// - `sampleRate` - The sampling rate for tracing. Must be a value between `0.0` and `100.0`. Default: `20`. - case traceWithHeaders(hostsWithHeaders: [String: Set], sampleRate: Float = 20) + /// - traceControlInjection: The strategy for injecting trace context into requests. Default: `.all`. + case traceWithHeaders( + hostsWithHeaders: [String: Set], + sampleRate: Float = 20, + traceControlInjection: TraceContextInjection = .all + ) } /// Configuration for automatic network requests tracing. diff --git a/DatadogTrace/Tests/DDNoopTracerTests.swift b/DatadogTrace/Tests/DDNoopTracerTests.swift index ecea81ad79..a2ed90e489 100644 --- a/DatadogTrace/Tests/DDNoopTracerTests.swift +++ b/DatadogTrace/Tests/DDNoopTracerTests.swift @@ -20,7 +20,10 @@ class DDNoopTracerTests: XCTestCase { // When let context = DDSpanContext.mockAny() - noop.inject(spanContext: context, writer: HTTPHeadersWriter(samplingStrategy: .headBased)) + noop.inject( + spanContext: context, + writer: HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .all) + ) _ = noop.extract(reader: HTTPHeadersReader(httpHeaderFields: [:])) let root = noop.startRootSpan(operationName: "root operation").setActive() let child = noop.startSpan(operationName: "child operation") diff --git a/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift b/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift index e52be47ae0..57353102fa 100644 --- a/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift +++ b/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift @@ -34,7 +34,8 @@ class TracingURLSessionHandlerTests: XCTestCase { distributedTraceSampler: .mockKeepAll(), firstPartyHosts: .init([ "www.example.com": [.datadog] - ]) + ]), + traceContextInjection: .all ) } @@ -49,7 +50,8 @@ class TracingURLSessionHandlerTests: XCTestCase { tracer: tracer, contextReceiver: ContextMessageReceiver(), distributedTraceSampler: .mockKeepAll(), - firstPartyHosts: .init() + firstPartyHosts: .init(), + traceContextInjection: .all ) // When @@ -89,7 +91,8 @@ class TracingURLSessionHandlerTests: XCTestCase { tracer: tracer, contextReceiver: ContextMessageReceiver(), distributedTraceSampler: .mockKeepAll(), - firstPartyHosts: .init() + firstPartyHosts: .init(), + traceContextInjection: .all ) // When @@ -137,7 +140,8 @@ class TracingURLSessionHandlerTests: XCTestCase { tracer: tracer, contextReceiver: ContextMessageReceiver(), distributedTraceSampler: .mockRejectAll(), - firstPartyHosts: .init() + firstPartyHosts: .init(), + traceContextInjection: .sampled ) // When @@ -153,13 +157,13 @@ class TracingURLSessionHandlerTests: XCTestCase { XCTAssertNil(request.value(forHTTPHeaderField: TracingHTTPHeaders.traceIDField)) XCTAssertNil(request.value(forHTTPHeaderField: TracingHTTPHeaders.parentSpanIDField)) - XCTAssertEqual(request.value(forHTTPHeaderField: TracingHTTPHeaders.samplingPriorityField), "0") + XCTAssertNil(request.value(forHTTPHeaderField: TracingHTTPHeaders.samplingPriorityField)) XCTAssertNil(request.value(forHTTPHeaderField: B3HTTPHeaders.Multiple.traceIDField)) XCTAssertNil(request.value(forHTTPHeaderField: B3HTTPHeaders.Multiple.spanIDField)) XCTAssertNil(request.value(forHTTPHeaderField: B3HTTPHeaders.Multiple.parentSpanIDField)) - XCTAssertEqual(request.value(forHTTPHeaderField: B3HTTPHeaders.Multiple.sampledField), "0") - XCTAssertEqual(request.value(forHTTPHeaderField: B3HTTPHeaders.Single.b3Field), "0") - XCTAssertEqual(request.value(forHTTPHeaderField: W3CHTTPHeaders.traceparent), "00-000000000000000a0000000000000064-0000000000000064-00") + XCTAssertNil(request.value(forHTTPHeaderField: B3HTTPHeaders.Multiple.sampledField)) + XCTAssertNil(request.value(forHTTPHeaderField: B3HTTPHeaders.Single.b3Field)) + XCTAssertNil(request.value(forHTTPHeaderField: W3CHTTPHeaders.traceparent)) XCTAssertNil(traceContext, "It must return no trace context") } @@ -170,7 +174,8 @@ class TracingURLSessionHandlerTests: XCTestCase { tracer: tracer, contextReceiver: ContextMessageReceiver(), distributedTraceSampler: .mockKeepAll(), - firstPartyHosts: .init() + firstPartyHosts: .init(), + traceContextInjection: .all ) let span = tracer.startRootSpan(operationName: "root") @@ -383,7 +388,8 @@ class TracingURLSessionHandlerTests: XCTestCase { tracer: .mockWith(core: core), contextReceiver: receiver, distributedTraceSampler: .mockKeepAll(), - firstPartyHosts: .init() + firstPartyHosts: .init(), + traceContextInjection: .all ) core.context.applicationStateHistory = .mockAppInForeground() From fc186c8e80e5677695dc480bcbf54753b29c404e Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Tue, 14 May 2024 16:21:10 +0200 Subject: [PATCH 077/153] RUM-3535 update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfafd67a66..56fad4d7b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - [FEATURE] `DatadogTrace` now supports OpenTelemetry. See [#1828][] +- [FEATURE] Support for trace context injection configuration to allow selective injection. See [#1835][] # 2.11.0 / 08-05-2024 @@ -663,6 +664,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1803]: https://github.com/DataDog/dd-sdk-ios/pull/1803 [#1807]: https://github.com/DataDog/dd-sdk-ios/pull/1807 [#1828]: https://github.com/DataDog/dd-sdk-ios/pull/1828 +[#1835]: https://github.com/DataDog/dd-sdk-ios/pull/1835 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu From 4e2e9075b4661c1af06da28abb471483222279a5 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 15 May 2024 14:50:42 +0200 Subject: [PATCH 078/153] RUM-3535 fix linter --- .../Sources/Tracing/Propagation/B3HTTPHeadersWriter+objc.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogObjc/Sources/Tracing/Propagation/B3HTTPHeadersWriter+objc.swift b/DatadogObjc/Sources/Tracing/Propagation/B3HTTPHeadersWriter+objc.swift index d125e09d72..3919197e4d 100644 --- a/DatadogObjc/Sources/Tracing/Propagation/B3HTTPHeadersWriter+objc.swift +++ b/DatadogObjc/Sources/Tracing/Propagation/B3HTTPHeadersWriter+objc.swift @@ -62,7 +62,7 @@ public class DDB3HTTPHeadersWriter: NSObject { public init( samplingStrategy: DDTraceSamplingStrategy, injectEncoding: DDInjectEncoding = .single, - traceContextInjection: DDTraceContextInjection + traceContextInjection: DDTraceContextInjection = .all ) { swiftB3HTTPHeadersWriter = B3HTTPHeadersWriter( samplingStrategy: samplingStrategy.swiftType, From c158008773af33162bf2d9d6f031adf3f53741b7 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 15 May 2024 17:26:26 +0200 Subject: [PATCH 079/153] Remove flaky assertion due to upload at init --- DatadogCore/Tests/Datadog/DatadogTests.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/DatadogCore/Tests/Datadog/DatadogTests.swift b/DatadogCore/Tests/Datadog/DatadogTests.swift index ea194d4c4f..d1eb48fb65 100644 --- a/DatadogCore/Tests/Datadog/DatadogTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogTests.swift @@ -330,6 +330,7 @@ class DatadogTests: XCTestCase { // mock data is written only after this operation completes - otherwise, migration may delete mocked files. core.readWriteQueue.sync {} + // Given let featureDirectories: [FeatureDirectories] = [ try core.directory.getFeatureDirectories(forFeatureNamed: "logging"), try core.directory.getFeatureDirectories(forFeatureNamed: "tracing"), @@ -338,10 +339,6 @@ class DatadogTests: XCTestCase { let allDirectories: [Directory] = featureDirectories.flatMap { [$0.authorized, $0.unauthorized] } try allDirectories.forEach { directory in _ = try directory.createFile(named: .mockRandom()) } - // Given - let numberOfFiles = try allDirectories.reduce(0, { acc, nextDirectory in return try acc + nextDirectory.files().count }) - XCTAssertEqual(numberOfFiles, 4, "Each feature stores 2 files - one authorised and one unauthorised") - // When Datadog.clearAllData() From 935314ecf7e13dd3d8bec9d896306c20e9bd1c59 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 26 Apr 2024 14:38:42 +0200 Subject: [PATCH 080/153] RUM-2814 Add E2E test project --- E2ETests/E2ETests.xcodeproj/project.pbxproj | 481 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + E2ETests/Makefile | 32 ++ E2ETests/Runner/AppDelegate.swift | 29 ++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + E2ETests/Runner/Assets.xcassets/Contents.json | 6 + .../Runner/Base.lproj/LaunchScreen.storyboard | 25 + E2ETests/Runner/Base.lproj/Main.storyboard | 24 + E2ETests/Runner/Info.plist | 25 + E2ETests/Runner/SceneDelegate.swift | 47 ++ E2ETests/Runner/ViewController.swift | 15 + E2ETests/exportOptions.plist | 19 + E2ETests/xcconfigs/Runner.xcconfig | 3 + E2ETests/xcconfigs/Synthetics.xcconfig | 6 + Gemfile.lock | 9 +- Makefile | 3 + tools/code-sign.sh | 75 +++ 19 files changed, 835 insertions(+), 3 deletions(-) create mode 100644 E2ETests/E2ETests.xcodeproj/project.pbxproj create mode 100644 E2ETests/E2ETests.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 E2ETests/E2ETests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 E2ETests/Makefile create mode 100644 E2ETests/Runner/AppDelegate.swift create mode 100644 E2ETests/Runner/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 E2ETests/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 E2ETests/Runner/Assets.xcassets/Contents.json create mode 100644 E2ETests/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 E2ETests/Runner/Base.lproj/Main.storyboard create mode 100644 E2ETests/Runner/Info.plist create mode 100644 E2ETests/Runner/SceneDelegate.swift create mode 100644 E2ETests/Runner/ViewController.swift create mode 100644 E2ETests/exportOptions.plist create mode 100644 E2ETests/xcconfigs/Runner.xcconfig create mode 100644 E2ETests/xcconfigs/Synthetics.xcconfig create mode 100755 tools/code-sign.sh diff --git a/E2ETests/E2ETests.xcodeproj/project.pbxproj b/E2ETests/E2ETests.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..5c55d89011 --- /dev/null +++ b/E2ETests/E2ETests.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + D292E3BB2BD7BCDF0083453D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */; }; + D292E3BD2BD7BCDF0083453D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292E3BC2BD7BCDF0083453D /* SceneDelegate.swift */; }; + D292E3BF2BD7BCDF0083453D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292E3BE2BD7BCDF0083453D /* ViewController.swift */; }; + D292E3C22BD7BCDF0083453D /* Base in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C12BD7BCDF0083453D /* Base */; }; + D292E3C42BD7BCE00083453D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C32BD7BCE00083453D /* Assets.xcassets */; }; + D292E3C72BD7BCE00083453D /* Base in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C62BD7BCE00083453D /* Base */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + D2720E2B2BD8FE2F008ADF5D /* E2E.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = E2E.local.xcconfig; sourceTree = ""; }; + D2720E2D2BD8FE2F008ADF5D /* Runner.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Runner.xcconfig; sourceTree = ""; }; + D2720E2E2BD8FE2F008ADF5D /* Synthetics.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Synthetics.xcconfig; sourceTree = ""; }; + D292E3B82BD7BCDF0083453D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + D292E3BC2BD7BCDF0083453D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + D292E3BE2BD7BCDF0083453D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + D292E3C12BD7BCDF0083453D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + D292E3C32BD7BCE00083453D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D292E3C62BD7BCE00083453D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + D292E3C82BD7BCE00083453D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D292E3B52BD7BCDF0083453D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D2720E2F2BD8FE2F008ADF5D /* xcconfigs */ = { + isa = PBXGroup; + children = ( + D2720E2B2BD8FE2F008ADF5D /* E2E.local.xcconfig */, + D2720E2D2BD8FE2F008ADF5D /* Runner.xcconfig */, + D2720E2E2BD8FE2F008ADF5D /* Synthetics.xcconfig */, + ); + path = xcconfigs; + sourceTree = ""; + }; + D292E3782BD7BA830083453D = { + isa = PBXGroup; + children = ( + D2720E2F2BD8FE2F008ADF5D /* xcconfigs */, + D292E3B92BD7BCDF0083453D /* Runner */, + D292E3822BD7BA830083453D /* Products */, + ); + sourceTree = ""; + }; + D292E3822BD7BA830083453D /* Products */ = { + isa = PBXGroup; + children = ( + D292E3B82BD7BCDF0083453D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + D292E3B92BD7BCDF0083453D /* Runner */ = { + isa = PBXGroup; + children = ( + D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */, + D292E3BC2BD7BCDF0083453D /* SceneDelegate.swift */, + D292E3BE2BD7BCDF0083453D /* ViewController.swift */, + D292E3C02BD7BCDF0083453D /* Main.storyboard */, + D292E3C32BD7BCE00083453D /* Assets.xcassets */, + D292E3C52BD7BCE00083453D /* LaunchScreen.storyboard */, + D292E3C82BD7BCE00083453D /* Info.plist */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D292E3B72BD7BCDF0083453D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = D292E3CB2BD7BCE00083453D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + D292E3B42BD7BCDF0083453D /* Sources */, + D292E3B52BD7BCDF0083453D /* Frameworks */, + D292E3B62BD7BCDF0083453D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = D292E3B82BD7BCDF0083453D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D292E3792BD7BA830083453D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1530; + LastUpgradeCheck = 1530; + TargetAttributes = { + D292E3B72BD7BCDF0083453D = { + CreatedOnToolsVersion = 15.3; + }; + }; + }; + buildConfigurationList = D292E37C2BD7BA830083453D /* Build configuration list for PBXProject "E2ETests" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D292E3782BD7BA830083453D; + productRefGroup = D292E3822BD7BA830083453D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D292E3B72BD7BCDF0083453D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D292E3B62BD7BCDF0083453D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D292E3C42BD7BCE00083453D /* Assets.xcassets in Resources */, + D292E3C72BD7BCE00083453D /* Base in Resources */, + D292E3C22BD7BCDF0083453D /* Base in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D292E3B42BD7BCDF0083453D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D292E3BF2BD7BCDF0083453D /* ViewController.swift in Sources */, + D292E3BB2BD7BCDF0083453D /* AppDelegate.swift in Sources */, + D292E3BD2BD7BCDF0083453D /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + D292E3C02BD7BCDF0083453D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D292E3C12BD7BCDF0083453D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + D292E3C52BD7BCE00083453D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D292E3C62BD7BCE00083453D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + D2720E302BD8FE9A008ADF5D /* Synthetics */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Synthetics; + }; + D2720E312BD8FE9A008ADF5D /* Synthetics */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D2720E2E2BD8FE2F008ADF5D /* Synthetics.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CURRENT_PROJECT_VERSION = 1; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = JKFCB4CN7C; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Datadog E2E Runner"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.datadoghq.e2e.Runner; + PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Datadog E2E Runner"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Synthetics; + }; + D292E3A92BD7BA840083453D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + D292E3AA2BD7BA840083453D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D292E3C92BD7BCE00083453D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D2720E2D2BD8FE2F008ADF5D /* Runner.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = JKFCB4CN7C; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Datadog E2E Runner"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.datadoghq.e2e.Runner; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D292E3CA2BD7BCE00083453D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D2720E2D2BD8FE2F008ADF5D /* Runner.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = JKFCB4CN7C; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Datadog E2E Runner"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.datadoghq.e2e.Runner; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D292E37C2BD7BA830083453D /* Build configuration list for PBXProject "E2ETests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D292E3A92BD7BA840083453D /* Debug */, + D292E3AA2BD7BA840083453D /* Release */, + D2720E302BD8FE9A008ADF5D /* Synthetics */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D292E3CB2BD7BCE00083453D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D292E3C92BD7BCE00083453D /* Debug */, + D292E3CA2BD7BCE00083453D /* Release */, + D2720E312BD8FE9A008ADF5D /* Synthetics */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D292E3792BD7BA830083453D /* Project object */; +} diff --git a/E2ETests/E2ETests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/E2ETests/E2ETests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/E2ETests/E2ETests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/E2ETests/E2ETests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/E2ETests/E2ETests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/E2ETests/E2ETests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/E2ETests/Makefile b/E2ETests/Makefile new file mode 100644 index 0000000000..ca304e03ac --- /dev/null +++ b/E2ETests/Makefile @@ -0,0 +1,32 @@ +all: archive upload + +dependencies: + @echo "⚙️ Installing datadog-ci..." + @npm install -g @datadog/datadog-ci + @echo "⚙️ Installing xcbeautify..." + @brew install xcbeautify + +archive: + xcrun agvtool new-version "$(git rev-parse --short HEAD)" + + set -o pipefail && xcodebuild \ + -project E2ETests.xcodeproj \ + -scheme Runner \ + -sdk iphoneos \ + -configuration Synthetics \ + -destination generic/platform=iOS \ + -archivePath .build/Runner.xcarchive \ + archive | xcbeautify + + set -o pipefail && xcodebuild -exportArchive \ + -archivePath .build/Runner.xcarchive \ + -exportOptionsPlist exportOptions.plist \ + -exportPath .build \ + | xcbeautify + +upload: + datadog-ci synthetics upload-application \ + --mobileApp ".build/Runner.ipa" \ + --mobileApplicationId "${S8S_APPLICATION_ID}" \ + --versionName "$(shell agvtool vers -terse)" \ + --latest diff --git a/E2ETests/Runner/AppDelegate.swift b/E2ETests/Runner/AppDelegate.swift new file mode 100644 index 0000000000..4830af750c --- /dev/null +++ b/E2ETests/Runner/AppDelegate.swift @@ -0,0 +1,29 @@ +/* + * 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 UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } +} diff --git a/E2ETests/Runner/Assets.xcassets/AccentColor.colorset/Contents.json b/E2ETests/Runner/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000000..eb87897008 --- /dev/null +++ b/E2ETests/Runner/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/E2ETests/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/E2ETests/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..13613e3ee1 --- /dev/null +++ b/E2ETests/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/E2ETests/Runner/Assets.xcassets/Contents.json b/E2ETests/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/E2ETests/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/E2ETests/Runner/Base.lproj/LaunchScreen.storyboard b/E2ETests/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..865e9329f3 --- /dev/null +++ b/E2ETests/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/E2ETests/Runner/Base.lproj/Main.storyboard b/E2ETests/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..25a763858e --- /dev/null +++ b/E2ETests/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/E2ETests/Runner/Info.plist b/E2ETests/Runner/Info.plist new file mode 100644 index 0000000000..dd3c9afdae --- /dev/null +++ b/E2ETests/Runner/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/E2ETests/Runner/SceneDelegate.swift b/E2ETests/Runner/SceneDelegate.swift new file mode 100644 index 0000000000..651237222f --- /dev/null +++ b/E2ETests/Runner/SceneDelegate.swift @@ -0,0 +1,47 @@ +/* + * 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 UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } +} diff --git a/E2ETests/Runner/ViewController.swift b/E2ETests/Runner/ViewController.swift new file mode 100644 index 0000000000..01ab50c77e --- /dev/null +++ b/E2ETests/Runner/ViewController.swift @@ -0,0 +1,15 @@ +/* + * 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 UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } +} diff --git a/E2ETests/exportOptions.plist b/E2ETests/exportOptions.plist new file mode 100644 index 0000000000..9128ee3dcf --- /dev/null +++ b/E2ETests/exportOptions.plist @@ -0,0 +1,19 @@ + + + + + distributionBundleIdentifier + com.datadoghq.e2e.Runner + method + development + provisioningProfiles + + com.datadoghq.e2e.Runner + Datadog E2E Runner + + signingCertificate + Apple Development: Robot Bitrise (9HKDHCMCGH) + teamID + JKFCB4CN7C + + diff --git a/E2ETests/xcconfigs/Runner.xcconfig b/E2ETests/xcconfigs/Runner.xcconfig new file mode 100644 index 0000000000..38cb31dfe3 --- /dev/null +++ b/E2ETests/xcconfigs/Runner.xcconfig @@ -0,0 +1,3 @@ +RUM_APPLICATION_ID= // the RUM Application ID obtained on datadoghq.com +DATADOG_CLIENT_TOKEN= // the Client Token, generated for RUM_APPLICATION_ID +#include? "E2E.local.xcconfig" diff --git a/E2ETests/xcconfigs/Synthetics.xcconfig b/E2ETests/xcconfigs/Synthetics.xcconfig new file mode 100644 index 0000000000..f56ebc4bef --- /dev/null +++ b/E2ETests/xcconfigs/Synthetics.xcconfig @@ -0,0 +1,6 @@ +#include "Runner.xcconfig" + +CODE_SIGN_STYLE = Manual +CODE_SIGN_IDENTITY = Apple Development: Robot Bitrise (9HKDHCMCGH) +DEVELOPMENT_TEAM = JKFCB4CN7C +PROVISIONING_PROFILE_SPECIFIER = Datadog E2E Runner diff --git a/Gemfile.lock b/Gemfile.lock index c19c05ccfe..ddb0016f8e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,12 +3,13 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.0.8) + activesupport (6.1.7.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.4) + zeitwerk (~> 2.3) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) @@ -65,7 +66,7 @@ GEM i18n (1.14.1) concurrent-ruby (~> 1.0) json (2.6.3) - minitest (5.20.0) + minitest (5.22.3) molinillo (0.8.0) nanaimo (0.3.0) nap (1.1.0) @@ -84,10 +85,12 @@ GEM colored2 (~> 3.1) nanaimo (~> 0.3.0) rexml (~> 3.2.4) + zeitwerk (2.6.13) PLATFORMS arm64-darwin-21 universal-darwin-22 + universal-darwin-23 x86_64-linux DEPENDENCIES diff --git a/Makefile b/Makefile index 6e70c0e1d6..4852b8ad31 100644 --- a/Makefile +++ b/Makefile @@ -194,3 +194,6 @@ bump: git add . ; \ git commit -m "Bumped version to $$version"; \ echo Bumped version to $$version + +e2e-upload: + ./tools/code-sign.sh -- $(MAKE) -C E2ETests diff --git a/tools/code-sign.sh b/tools/code-sign.sh new file mode 100755 index 0000000000..9fa51f295b --- /dev/null +++ b/tools/code-sign.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +function usage() { + cat << EOF +OVERVIEW: Install Apple certificate and provisioning profile to build and sign. + +EXAMPLE: $(basename "${BASH_SOURCE[0]}") -- make export + +USAGE: $(basename "${BASH_SOURCE[0]}") [--p12 ] [--p12-password ] [--provisioning-profile] -- + +OPTIONS: + +-h, --help Print this help and exit. +--p12 Path to Apple signing 'p12' certificate. env P12_PATH +--p12-password The password for yotheur Apple signing certificate. env P12_PASSWORD +--provisioning-profile Path to Apple provisioning profile. env PP_PATH + +EOF + exit +} + +# read cmd arguments +while :; do + case $1 in + --p12) P12_PATH=$2 + shift + ;; + --p12-password) P12_PASSWORD=$2 + shift + ;; + --provisioning-profile) PP_PATH=$2 + shift + ;; + -h|--help) usage + shift + ;; + --) shift + CMD=$@ + break + ;; + *) break + esac + shift +done + +if [ -z "$P12_PATH" ] || [ -z "$P12_PASSWORD" ] || [ -z "$PP_PATH" ] || [ -z "$CMD" ]; then usage; fi + +# Ensure we do not leak any secrets +set +x + +KEYCHAIN=datadog.keychain +KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" +PROFILE=datadog.mobileprovision + +# apply provisioning profile +mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles +cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE + +# create temporary keychain +security delete-keychain $KEYCHAIN || : +security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN +security set-keychain-settings -lut 21600 $KEYCHAIN +security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN + +# import certificate to keychain +security import $P12_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN +security list-keychain -d user -s $KEYCHAIN "login.keychain" "System.keychain" +security set-key-partition-list -S apple-tool:,apple: -s -k $KEYCHAIN_PASSWORD $KEYCHAIN >/dev/null 2>&1 + +# run command with certificate and provisioning profile available +exec $CMD + +# clean up keychain and provisioning profile +security delete-keychain $KEYCHAIN +rm ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE From 4795478021be8cf839342595743ff1eead5217b2 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 26 Apr 2024 15:44:14 +0200 Subject: [PATCH 081/153] Add run_e2e_s8s_upload bitrise step --- E2ETests/Makefile | 6 ++---- Makefile | 3 --- bitrise.yml | 31 +++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/E2ETests/Makefile b/E2ETests/Makefile index ca304e03ac..43af3e81f3 100644 --- a/E2ETests/Makefile +++ b/E2ETests/Makefile @@ -1,13 +1,11 @@ -all: archive upload +all: dependencies archive upload dependencies: @echo "⚙️ Installing datadog-ci..." @npm install -g @datadog/datadog-ci - @echo "⚙️ Installing xcbeautify..." - @brew install xcbeautify archive: - xcrun agvtool new-version "$(git rev-parse --short HEAD)" + xcrun agvtool new-version "$(shell git rev-parse --short HEAD)" set -o pipefail && xcodebuild \ -project E2ETests.xcodeproj \ diff --git a/Makefile b/Makefile index 4852b8ad31..6e70c0e1d6 100644 --- a/Makefile +++ b/Makefile @@ -194,6 +194,3 @@ bump: git add . ; \ git commit -m "Bumped version to $$version"; \ echo Bumped version to $$version - -e2e-upload: - ./tools/code-sign.sh -- $(MAKE) -C E2ETests diff --git a/bitrise.yml b/bitrise.yml index 718c34f719..450cf18c1a 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -143,6 +143,7 @@ workflows: - run_linter - run_unit_tests - run_integration_tests + - run_e2e_s8s_upload - run_smoke_tests - run_tools_tests @@ -634,3 +635,33 @@ workflows: cd tools/distribution && make venv/bin/python3 release.py "$GIT_TAG" --only-cocoapods + + run_e2e_s8s_upload: + description: |- + Upload E2E application to Synthetics. + steps: + - script: + title: Upload E2E application to Synthetics. + inputs: + - content: |- + #!/usr/bin/env bash + set -e + + # prepare certificate + P12_PATH=e2e_cert.p12 + P12_PASSWORD=$E2E_CERTIFICATE_P12_PASSWORD + echo $E2E_CERTIFICATE_P12_BASE64 | base64 --decode -o $P12_PATH + + # prepare provisioning profile + PP_PATH=e2e.mobileprovision + echo $E2E_PROVISIONING_PROFILE_BASE64 | base64 --decode -o $PP_PATH + + # prepare xcconfig + echo $E2E_XCCONFIG_BASE64 | base64 --decode -o E2ETests/xcconfigs/E2E.local.xcconfig + + # prepare for synthetics upload + export DATADOG_API_KEY=$E2E_S8S_API_KEY + export DATADOG_APP_KEY=$E2E_S8S_APPLICATION_KEY + export S8S_APPLICATION_ID=$E2E_S8S_APPLICATION_ID + + source ./tools/code-sign.sh -- make -C E2ETests From 68998d1c129abc73f115fc3495fd17a7dcc20051 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 30 Apr 2024 11:34:13 +0200 Subject: [PATCH 082/153] Add make e2e-upload --- Makefile | 3 +++ bitrise.yml | 8 ++++---- tools/code-sign.sh | 20 ++++++++++++-------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 6e70c0e1d6..4852b8ad31 100644 --- a/Makefile +++ b/Makefile @@ -194,3 +194,6 @@ bump: git add . ; \ git commit -m "Bumped version to $$version"; \ echo Bumped version to $$version + +e2e-upload: + ./tools/code-sign.sh -- $(MAKE) -C E2ETests diff --git a/bitrise.yml b/bitrise.yml index 450cf18c1a..7937f685ae 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -648,12 +648,12 @@ workflows: set -e # prepare certificate - P12_PATH=e2e_cert.p12 - P12_PASSWORD=$E2E_CERTIFICATE_P12_PASSWORD + export P12_PATH=e2e_cert.p12 + export P12_PASSWORD=$E2E_CERTIFICATE_P12_PASSWORD echo $E2E_CERTIFICATE_P12_BASE64 | base64 --decode -o $P12_PATH # prepare provisioning profile - PP_PATH=e2e.mobileprovision + export PP_PATH=e2e.mobileprovision echo $E2E_PROVISIONING_PROFILE_BASE64 | base64 --decode -o $PP_PATH # prepare xcconfig @@ -664,4 +664,4 @@ workflows: export DATADOG_APP_KEY=$E2E_S8S_APPLICATION_KEY export S8S_APPLICATION_ID=$E2E_S8S_APPLICATION_ID - source ./tools/code-sign.sh -- make -C E2ETests + make e2e-upload diff --git a/tools/code-sign.sh b/tools/code-sign.sh index 9fa51f295b..4b7b2aa8b4 100755 --- a/tools/code-sign.sh +++ b/tools/code-sign.sh @@ -46,15 +46,19 @@ done if [ -z "$P12_PATH" ] || [ -z "$P12_PASSWORD" ] || [ -z "$PP_PATH" ] || [ -z "$CMD" ]; then usage; fi # Ensure we do not leak any secrets -set +x +set +x -e KEYCHAIN=datadog.keychain KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" PROFILE=datadog.mobileprovision -# apply provisioning profile -mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles -cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE +cleanup() { + rm -f ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE + security delete-keychain $KEYCHAIN +} + +# clean up keychain and provisioning profile on exit +trap cleanup EXIT # create temporary keychain security delete-keychain $KEYCHAIN || : @@ -67,9 +71,9 @@ security import $P12_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN security list-keychain -d user -s $KEYCHAIN "login.keychain" "System.keychain" security set-key-partition-list -S apple-tool:,apple: -s -k $KEYCHAIN_PASSWORD $KEYCHAIN >/dev/null 2>&1 +# apply provisioning profile +mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles +cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE + # run command with certificate and provisioning profile available exec $CMD - -# clean up keychain and provisioning profile -security delete-keychain $KEYCHAIN -rm ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE From 1eb1f5a5c3852bdcd90ef7025777b50ff070e7c0 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 30 Apr 2024 18:02:13 +0200 Subject: [PATCH 083/153] Add scenario skeleton --- E2ETests/E2ETests.xcodeproj/project.pbxproj | 110 +++++++++++++++--- .../contents.xcworkspacedata | 7 ++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++ E2ETests/Runner/AppConfiguration.swift | 53 +++++++++ E2ETests/Runner/AppDelegate.swift | 24 ++-- E2ETests/Runner/Base.lproj/Main.storyboard | 24 ---- E2ETests/Runner/Info.plist | 28 +++-- .../Runner/Scenarios/DefaultScenario.swift | 60 ++++++++++ E2ETests/Runner/Scenarios/Scenario.swift | 23 ++++ E2ETests/Runner/SceneDelegate.swift | 47 -------- 10 files changed, 264 insertions(+), 120 deletions(-) create mode 100644 E2ETests/E2ETests.xcworkspace/contents.xcworkspacedata create mode 100644 E2ETests/E2ETests.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 E2ETests/Runner/AppConfiguration.swift delete mode 100644 E2ETests/Runner/Base.lproj/Main.storyboard create mode 100644 E2ETests/Runner/Scenarios/DefaultScenario.swift create mode 100644 E2ETests/Runner/Scenarios/Scenario.swift delete mode 100644 E2ETests/Runner/SceneDelegate.swift diff --git a/E2ETests/E2ETests.xcodeproj/project.pbxproj b/E2ETests/E2ETests.xcodeproj/project.pbxproj index 5c55d89011..31b19a34d9 100644 --- a/E2ETests/E2ETests.xcodeproj/project.pbxproj +++ b/E2ETests/E2ETests.xcodeproj/project.pbxproj @@ -8,25 +8,36 @@ /* Begin PBXBuildFile section */ D292E3BB2BD7BCDF0083453D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */; }; - D292E3BD2BD7BCDF0083453D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292E3BC2BD7BCDF0083453D /* SceneDelegate.swift */; }; D292E3BF2BD7BCDF0083453D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292E3BE2BD7BCDF0083453D /* ViewController.swift */; }; - D292E3C22BD7BCDF0083453D /* Base in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C12BD7BCDF0083453D /* Base */; }; D292E3C42BD7BCE00083453D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C32BD7BCE00083453D /* Assets.xcassets */; }; D292E3C72BD7BCE00083453D /* Base in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C62BD7BCE00083453D /* Base */; }; + D2A6109B2BE11A74000AA6AB /* DatadogCore in Frameworks */ = {isa = PBXBuildFile; productRef = D2A6109A2BE11A74000AA6AB /* DatadogCore */; }; + D2A6109D2BE11A74000AA6AB /* DatadogCrashReporting in Frameworks */ = {isa = PBXBuildFile; productRef = D2A6109C2BE11A74000AA6AB /* DatadogCrashReporting */; }; + D2A6109F2BE11A74000AA6AB /* DatadogLogs in Frameworks */ = {isa = PBXBuildFile; productRef = D2A6109E2BE11A74000AA6AB /* DatadogLogs */; }; + D2A610A12BE11A74000AA6AB /* DatadogObjc in Frameworks */ = {isa = PBXBuildFile; productRef = D2A610A02BE11A74000AA6AB /* DatadogObjc */; }; + D2A610A32BE11A74000AA6AB /* DatadogRUM in Frameworks */ = {isa = PBXBuildFile; productRef = D2A610A22BE11A74000AA6AB /* DatadogRUM */; }; + D2A610A52BE11A74000AA6AB /* DatadogSessionReplay in Frameworks */ = {isa = PBXBuildFile; productRef = D2A610A42BE11A74000AA6AB /* DatadogSessionReplay */; }; + D2A610A72BE11A74000AA6AB /* DatadogTrace in Frameworks */ = {isa = PBXBuildFile; productRef = D2A610A62BE11A74000AA6AB /* DatadogTrace */; }; + D2A610A92BE11A74000AA6AB /* DatadogWebViewTracking in Frameworks */ = {isa = PBXBuildFile; productRef = D2A610A82BE11A74000AA6AB /* DatadogWebViewTracking */; }; + D2A610AD2BE125F0000AA6AB /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A610AC2BE125F0000AA6AB /* AppConfiguration.swift */; }; + D2A610B02BE12739000AA6AB /* Scenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A610AF2BE12739000AA6AB /* Scenario.swift */; }; + D2A610B22BE14D01000AA6AB /* DefaultScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A610B12BE14D01000AA6AB /* DefaultScenario.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + D20B99882BE101690074F98E /* dd-sdk-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "dd-sdk-ios"; path = ..; sourceTree = ""; }; D2720E2B2BD8FE2F008ADF5D /* E2E.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = E2E.local.xcconfig; sourceTree = ""; }; D2720E2D2BD8FE2F008ADF5D /* Runner.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Runner.xcconfig; sourceTree = ""; }; D2720E2E2BD8FE2F008ADF5D /* Synthetics.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Synthetics.xcconfig; sourceTree = ""; }; D292E3B82BD7BCDF0083453D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - D292E3BC2BD7BCDF0083453D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; D292E3BE2BD7BCDF0083453D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - D292E3C12BD7BCDF0083453D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; D292E3C32BD7BCE00083453D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; D292E3C62BD7BCE00083453D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; D292E3C82BD7BCE00083453D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D2A610AC2BE125F0000AA6AB /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; + D2A610AF2BE12739000AA6AB /* Scenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scenario.swift; sourceTree = ""; }; + D2A610B12BE14D01000AA6AB /* DefaultScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultScenario.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -34,12 +45,28 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D2A6109D2BE11A74000AA6AB /* DatadogCrashReporting in Frameworks */, + D2A610A52BE11A74000AA6AB /* DatadogSessionReplay in Frameworks */, + D2A610A32BE11A74000AA6AB /* DatadogRUM in Frameworks */, + D2A610A12BE11A74000AA6AB /* DatadogObjc in Frameworks */, + D2A6109F2BE11A74000AA6AB /* DatadogLogs in Frameworks */, + D2A6109B2BE11A74000AA6AB /* DatadogCore in Frameworks */, + D2A610A92BE11A74000AA6AB /* DatadogWebViewTracking in Frameworks */, + D2A610A72BE11A74000AA6AB /* DatadogTrace in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + D20B99872BE101690074F98E /* Frameworks */ = { + isa = PBXGroup; + children = ( + D20B99882BE101690074F98E /* dd-sdk-ios */, + ); + name = Frameworks; + sourceTree = ""; + }; D2720E2F2BD8FE2F008ADF5D /* xcconfigs */ = { isa = PBXGroup; children = ( @@ -56,6 +83,7 @@ D2720E2F2BD8FE2F008ADF5D /* xcconfigs */, D292E3B92BD7BCDF0083453D /* Runner */, D292E3822BD7BA830083453D /* Products */, + D20B99872BE101690074F98E /* Frameworks */, ); sourceTree = ""; }; @@ -71,9 +99,9 @@ isa = PBXGroup; children = ( D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */, - D292E3BC2BD7BCDF0083453D /* SceneDelegate.swift */, + D2A610AC2BE125F0000AA6AB /* AppConfiguration.swift */, D292E3BE2BD7BCDF0083453D /* ViewController.swift */, - D292E3C02BD7BCDF0083453D /* Main.storyboard */, + D2A610AE2BE12724000AA6AB /* Scenarios */, D292E3C32BD7BCE00083453D /* Assets.xcassets */, D292E3C52BD7BCE00083453D /* LaunchScreen.storyboard */, D292E3C82BD7BCE00083453D /* Info.plist */, @@ -81,6 +109,15 @@ path = Runner; sourceTree = ""; }; + D2A610AE2BE12724000AA6AB /* Scenarios */ = { + isa = PBXGroup; + children = ( + D2A610AF2BE12739000AA6AB /* Scenario.swift */, + D2A610B12BE14D01000AA6AB /* DefaultScenario.swift */, + ); + path = Scenarios; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -97,6 +134,16 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + D2A6109A2BE11A74000AA6AB /* DatadogCore */, + D2A6109C2BE11A74000AA6AB /* DatadogCrashReporting */, + D2A6109E2BE11A74000AA6AB /* DatadogLogs */, + D2A610A02BE11A74000AA6AB /* DatadogObjc */, + D2A610A22BE11A74000AA6AB /* DatadogRUM */, + D2A610A42BE11A74000AA6AB /* DatadogSessionReplay */, + D2A610A62BE11A74000AA6AB /* DatadogTrace */, + D2A610A82BE11A74000AA6AB /* DatadogWebViewTracking */, + ); productName = Runner; productReference = D292E3B82BD7BCDF0083453D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -141,7 +188,6 @@ files = ( D292E3C42BD7BCE00083453D /* Assets.xcassets in Resources */, D292E3C72BD7BCE00083453D /* Base in Resources */, - D292E3C22BD7BCDF0083453D /* Base in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -154,21 +200,15 @@ files = ( D292E3BF2BD7BCDF0083453D /* ViewController.swift in Sources */, D292E3BB2BD7BCDF0083453D /* AppDelegate.swift in Sources */, - D292E3BD2BD7BCDF0083453D /* SceneDelegate.swift in Sources */, + D2A610AD2BE125F0000AA6AB /* AppConfiguration.swift in Sources */, + D2A610B22BE14D01000AA6AB /* DefaultScenario.swift in Sources */, + D2A610B02BE12739000AA6AB /* Scenario.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - D292E3C02BD7BCDF0083453D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - D292E3C12BD7BCDF0083453D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; D292E3C52BD7BCE00083453D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -249,7 +289,6 @@ INFOPLIST_KEY_CFBundleDisplayName = "Datadog E2E Runner"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -401,7 +440,6 @@ INFOPLIST_KEY_CFBundleDisplayName = "Datadog E2E Runner"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -434,7 +472,6 @@ INFOPLIST_KEY_CFBundleDisplayName = "Datadog E2E Runner"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -476,6 +513,41 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + D2A6109A2BE11A74000AA6AB /* DatadogCore */ = { + isa = XCSwiftPackageProductDependency; + productName = DatadogCore; + }; + D2A6109C2BE11A74000AA6AB /* DatadogCrashReporting */ = { + isa = XCSwiftPackageProductDependency; + productName = DatadogCrashReporting; + }; + D2A6109E2BE11A74000AA6AB /* DatadogLogs */ = { + isa = XCSwiftPackageProductDependency; + productName = DatadogLogs; + }; + D2A610A02BE11A74000AA6AB /* DatadogObjc */ = { + isa = XCSwiftPackageProductDependency; + productName = DatadogObjc; + }; + D2A610A22BE11A74000AA6AB /* DatadogRUM */ = { + isa = XCSwiftPackageProductDependency; + productName = DatadogRUM; + }; + D2A610A42BE11A74000AA6AB /* DatadogSessionReplay */ = { + isa = XCSwiftPackageProductDependency; + productName = DatadogSessionReplay; + }; + D2A610A62BE11A74000AA6AB /* DatadogTrace */ = { + isa = XCSwiftPackageProductDependency; + productName = DatadogTrace; + }; + D2A610A82BE11A74000AA6AB /* DatadogWebViewTracking */ = { + isa = XCSwiftPackageProductDependency; + productName = DatadogWebViewTracking; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = D292E3792BD7BA830083453D /* Project object */; } diff --git a/E2ETests/E2ETests.xcworkspace/contents.xcworkspacedata b/E2ETests/E2ETests.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..65c2900989 --- /dev/null +++ b/E2ETests/E2ETests.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/E2ETests/E2ETests.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/E2ETests/E2ETests.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/E2ETests/E2ETests.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/E2ETests/Runner/AppConfiguration.swift b/E2ETests/Runner/AppConfiguration.swift new file mode 100644 index 0000000000..b83d8c91d4 --- /dev/null +++ b/E2ETests/Runner/AppConfiguration.swift @@ -0,0 +1,53 @@ +/* + * 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 +import DatadogInternal +import DatadogCore + +struct TestInfo: Decodable { + let mobileIntegrationOrg: OrgInfo + let sessionReplayOrg: OrgInfo + + enum CodingKeys: String, CodingKey { + case mobileIntegrationOrg = "MobileIntegration" + case sessionReplayOrg = "SessionReplayIntegration" + } +} + +extension TestInfo { + init(bundle: Bundle = .main) throws { + let decoder = AnyDecoder() + let obj = bundle.object(forInfoDictionaryKey: "DatadogConfiguration") + self = try decoder.decode(from: obj) + } +} + +struct OrgInfo: Decodable { + let clientToken: String + let applicationID: String + let site: DatadogSite? + let env: String? + + enum CodingKeys: String, CodingKey { + case clientToken = "ClientToken" + case applicationID = "ApplicationID" + case site = "Site" + case env = "Environment" + } +} + +extension DatadogSite: Decodable {} + +extension Datadog.Configuration { + static func e2e(org: OrgInfo) -> Self { + .init( + clientToken: org.clientToken, + env: org.env ?? "e2e", + site: org.site ?? .us1 + ) + } +} diff --git a/E2ETests/Runner/AppDelegate.swift b/E2ETests/Runner/AppDelegate.swift index 4830af750c..c7d7af206c 100644 --- a/E2ETests/Runner/AppDelegate.swift +++ b/E2ETests/Runner/AppDelegate.swift @@ -8,22 +8,16 @@ import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - // MARK: UISceneSession Lifecycle + var window: UIWindow? - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + let info = try! TestInfo() + let scenario: Scenario = ProcessInfo.processInfo.environment["E2E_SCENARIO"] + .flatMap(Scenarios.init(rawValue:)) ?? DefaultScenario() - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + window = UIWindow(frame: UIScreen.main.bounds) + window?.rootViewController = scenario.start(info: info) + window?.makeKeyAndVisible() + return true } } diff --git a/E2ETests/Runner/Base.lproj/Main.storyboard b/E2ETests/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index 25a763858e..0000000000 --- a/E2ETests/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/E2ETests/Runner/Info.plist b/E2ETests/Runner/Info.plist index dd3c9afdae..c474919156 100644 --- a/E2ETests/Runner/Info.plist +++ b/E2ETests/Runner/Info.plist @@ -2,23 +2,21 @@ - UIApplicationSceneManifest + DatadogConfiguration - UIApplicationSupportsMultipleScenes - - UISceneConfigurations + MobileIntegration - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - + ClientToken + $(MI_CLIENT_TOKEN) + ApplicationID + $(MI_RUM_APPLICATION_ID) + + SessionReplayIntegration + + ClientToken + $(SR_CLIENT_TOKEN) + ApplicationID + $(SR_RUM_APPLICATION_ID) diff --git a/E2ETests/Runner/Scenarios/DefaultScenario.swift b/E2ETests/Runner/Scenarios/DefaultScenario.swift new file mode 100644 index 0000000000..a2ea3b8a60 --- /dev/null +++ b/E2ETests/Runner/Scenarios/DefaultScenario.swift @@ -0,0 +1,60 @@ +/* + * 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 +import UIKit +import SwiftUI + +struct DefaultScenario: Scenario { + func start(info: TestInfo) -> UIViewController { + UIHostingController(rootView: ContentView(info: info)) + } + + struct ContentView: View { + let info: TestInfo + + var body: some View { + NavigationView { + List(Scenarios.allCases, id: \.rawValue) { scenario in + NavigationLink { + ScenarioView(info: info, scenario: scenario) + } label: { + Text(scenario.rawValue) + } + } + .navigationBarTitle("Scenarios") + } + } + } + + struct ScenarioView: UIViewControllerRepresentable { + let info: TestInfo + let scenario: Scenario + + func makeUIViewController(context: Context) -> UIViewController { + scenario.start(info: info) + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } + } +} + +#Preview { + DefaultScenario.ContentView(info: TestInfo( + mobileIntegrationOrg: OrgInfo( + clientToken: "", + applicationID: "", + site: nil, + env: nil + ), + sessionReplayOrg: OrgInfo( + clientToken: "", + applicationID: "", + site: nil, + env: nil + ) + )) +} diff --git a/E2ETests/Runner/Scenarios/Scenario.swift b/E2ETests/Runner/Scenarios/Scenario.swift new file mode 100644 index 0000000000..3126a85ff5 --- /dev/null +++ b/E2ETests/Runner/Scenarios/Scenario.swift @@ -0,0 +1,23 @@ +/* + * 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 +import UIKit + +protocol Scenario { + func start(info: TestInfo) -> UIViewController +} + +enum Scenarios: String, Scenario, CaseIterable { + case sessionReplayWebView + + func start(info: TestInfo) -> UIViewController { + switch self { + case .sessionReplayWebView: + return UIViewController() + } + } +} diff --git a/E2ETests/Runner/SceneDelegate.swift b/E2ETests/Runner/SceneDelegate.swift deleted file mode 100644 index 651237222f..0000000000 --- a/E2ETests/Runner/SceneDelegate.swift +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). - } - - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - } -} From 3d57b7769c3245a82a0ee722048436ebadb8d41a Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 30 Apr 2024 18:08:29 +0200 Subject: [PATCH 084/153] Add session replay webview scenario --- E2ETests/E2ETests.xcodeproj/project.pbxproj | 20 ++++- .../xcshareddata/xcschemes/Runner.xcscheme | 85 +++++++++++++++++++ .../contents.xcworkspacedata | 7 -- .../xcshareddata/IDEWorkspaceChecks.plist | 8 -- E2ETests/Runner/AppConfiguration.swift | 52 +++++++----- E2ETests/Runner/AppDelegate.swift | 6 +- E2ETests/Runner/Info.plist | 18 +--- .../Runner/Scenarios/DefaultScenario.swift | 26 +++--- E2ETests/Runner/Scenarios/Scenario.swift | 50 ++++++++++- .../SessionReplayWebViewController.swift | 41 +++++++++ .../SessionReplayWebViewScenario.swift | 38 +++++++++ E2ETests/Runner/ViewController.swift | 15 ---- E2ETests/xcconfigs/Runner.xcconfig | 4 +- 13 files changed, 279 insertions(+), 91 deletions(-) create mode 100644 E2ETests/E2ETests.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 E2ETests/E2ETests.xcworkspace/contents.xcworkspacedata delete mode 100644 E2ETests/E2ETests.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewController.swift create mode 100644 E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift delete mode 100644 E2ETests/Runner/ViewController.swift diff --git a/E2ETests/E2ETests.xcodeproj/project.pbxproj b/E2ETests/E2ETests.xcodeproj/project.pbxproj index 31b19a34d9..9dd59d490b 100644 --- a/E2ETests/E2ETests.xcodeproj/project.pbxproj +++ b/E2ETests/E2ETests.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ D292E3BB2BD7BCDF0083453D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */; }; - D292E3BF2BD7BCDF0083453D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292E3BE2BD7BCDF0083453D /* ViewController.swift */; }; D292E3C42BD7BCE00083453D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C32BD7BCE00083453D /* Assets.xcassets */; }; D292E3C72BD7BCE00083453D /* Base in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C62BD7BCE00083453D /* Base */; }; D2A6109B2BE11A74000AA6AB /* DatadogCore in Frameworks */ = {isa = PBXBuildFile; productRef = D2A6109A2BE11A74000AA6AB /* DatadogCore */; }; @@ -22,6 +21,8 @@ D2A610AD2BE125F0000AA6AB /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A610AC2BE125F0000AA6AB /* AppConfiguration.swift */; }; D2A610B02BE12739000AA6AB /* Scenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A610AF2BE12739000AA6AB /* Scenario.swift */; }; D2A610B22BE14D01000AA6AB /* DefaultScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A610B12BE14D01000AA6AB /* DefaultScenario.swift */; }; + D2C028A02BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C0289F2BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift */; }; + D2C028A22BE14FD300B5D7D3 /* SessionReplayWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C028A12BE14FD200B5D7D3 /* SessionReplayWebViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -31,13 +32,14 @@ D2720E2E2BD8FE2F008ADF5D /* Synthetics.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Synthetics.xcconfig; sourceTree = ""; }; D292E3B82BD7BCDF0083453D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - D292E3BE2BD7BCDF0083453D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; D292E3C32BD7BCE00083453D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; D292E3C62BD7BCE00083453D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; D292E3C82BD7BCE00083453D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D2A610AC2BE125F0000AA6AB /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; D2A610AF2BE12739000AA6AB /* Scenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scenario.swift; sourceTree = ""; }; D2A610B12BE14D01000AA6AB /* DefaultScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultScenario.swift; sourceTree = ""; }; + D2C0289F2BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayWebViewScenario.swift; sourceTree = ""; }; + D2C028A12BE14FD200B5D7D3 /* SessionReplayWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionReplayWebViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -100,7 +102,6 @@ children = ( D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */, D2A610AC2BE125F0000AA6AB /* AppConfiguration.swift */, - D292E3BE2BD7BCDF0083453D /* ViewController.swift */, D2A610AE2BE12724000AA6AB /* Scenarios */, D292E3C32BD7BCE00083453D /* Assets.xcassets */, D292E3C52BD7BCE00083453D /* LaunchScreen.storyboard */, @@ -114,10 +115,20 @@ children = ( D2A610AF2BE12739000AA6AB /* Scenario.swift */, D2A610B12BE14D01000AA6AB /* DefaultScenario.swift */, + D2C0289E2BE14F1B00B5D7D3 /* SessionReplayWebView */, ); path = Scenarios; sourceTree = ""; }; + D2C0289E2BE14F1B00B5D7D3 /* SessionReplayWebView */ = { + isa = PBXGroup; + children = ( + D2C0289F2BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift */, + D2C028A12BE14FD200B5D7D3 /* SessionReplayWebViewController.swift */, + ); + path = SessionReplayWebView; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -198,9 +209,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D292E3BF2BD7BCDF0083453D /* ViewController.swift in Sources */, D292E3BB2BD7BCDF0083453D /* AppDelegate.swift in Sources */, D2A610AD2BE125F0000AA6AB /* AppConfiguration.swift in Sources */, + D2C028A02BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift in Sources */, + D2C028A22BE14FD300B5D7D3 /* SessionReplayWebViewController.swift in Sources */, D2A610B22BE14D01000AA6AB /* DefaultScenario.swift in Sources */, D2A610B02BE12739000AA6AB /* Scenario.swift in Sources */, ); diff --git a/E2ETests/E2ETests.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/E2ETests/E2ETests.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000..c51a66b77b --- /dev/null +++ b/E2ETests/E2ETests.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/E2ETests/E2ETests.xcworkspace/contents.xcworkspacedata b/E2ETests/E2ETests.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 65c2900989..0000000000 --- a/E2ETests/E2ETests.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/E2ETests/E2ETests.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/E2ETests/E2ETests.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d..0000000000 --- a/E2ETests/E2ETests.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/E2ETests/Runner/AppConfiguration.swift b/E2ETests/Runner/AppConfiguration.swift index b83d8c91d4..28167174c9 100644 --- a/E2ETests/Runner/AppConfiguration.swift +++ b/E2ETests/Runner/AppConfiguration.swift @@ -8,13 +8,30 @@ import Foundation import DatadogInternal import DatadogCore +/// Test info reads configuration from `Info.plist`. +/// +/// The expected format is as follow: +/// +/// +/// DatadogConfiguration +/// +/// ClientToken +/// $(CLIENT_TOKEN) +/// ApplicationID +/// $(RUM_APPLICATION_ID) +/// +/// struct TestInfo: Decodable { - let mobileIntegrationOrg: OrgInfo - let sessionReplayOrg: OrgInfo + let clientToken: String + let applicationID: String + let site: DatadogSite? + let env: String? enum CodingKeys: String, CodingKey { - case mobileIntegrationOrg = "MobileIntegration" - case sessionReplayOrg = "SessionReplayIntegration" + case clientToken = "ClientToken" + case applicationID = "ApplicationID" + case site = "Site" + case env = "Environment" } } @@ -26,28 +43,25 @@ extension TestInfo { } } -struct OrgInfo: Decodable { - let clientToken: String - let applicationID: String - let site: DatadogSite? - let env: String? - - enum CodingKeys: String, CodingKey { - case clientToken = "ClientToken" - case applicationID = "ApplicationID" - case site = "Site" - case env = "Environment" +extension TestInfo { + static var empty: Self { + .init( + clientToken: "", + applicationID: "", + site: nil, + env: nil + ) } } extension DatadogSite: Decodable {} extension Datadog.Configuration { - static func e2e(org: OrgInfo) -> Self { + static func e2e(info: TestInfo) -> Self { .init( - clientToken: org.clientToken, - env: org.env ?? "e2e", - site: org.site ?? .us1 + clientToken: info.clientToken, + env: info.env ?? "e2e", + site: info.site ?? .us1 ) } } diff --git a/E2ETests/Runner/AppDelegate.swift b/E2ETests/Runner/AppDelegate.swift index c7d7af206c..9b992f4fe0 100644 --- a/E2ETests/Runner/AppDelegate.swift +++ b/E2ETests/Runner/AppDelegate.swift @@ -11,9 +11,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - let info = try! TestInfo() - let scenario: Scenario = ProcessInfo.processInfo.environment["E2E_SCENARIO"] - .flatMap(Scenarios.init(rawValue:)) ?? DefaultScenario() + let info = try! TestInfo() // crash if test info are missing or malformed + + let scenario: Scenario = SyntheticScenario() ?? DefaultScenario() window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = scenario.start(info: info) diff --git a/E2ETests/Runner/Info.plist b/E2ETests/Runner/Info.plist index c474919156..1787b852a0 100644 --- a/E2ETests/Runner/Info.plist +++ b/E2ETests/Runner/Info.plist @@ -4,20 +4,10 @@ DatadogConfiguration - MobileIntegration - - ClientToken - $(MI_CLIENT_TOKEN) - ApplicationID - $(MI_RUM_APPLICATION_ID) - - SessionReplayIntegration - - ClientToken - $(SR_CLIENT_TOKEN) - ApplicationID - $(SR_RUM_APPLICATION_ID) - + ClientToken + $(CLIENT_TOKEN) + ApplicationID + $(RUM_APPLICATION_ID) diff --git a/E2ETests/Runner/Scenarios/DefaultScenario.swift b/E2ETests/Runner/Scenarios/DefaultScenario.swift index a2ea3b8a60..d6760fed82 100644 --- a/E2ETests/Runner/Scenarios/DefaultScenario.swift +++ b/E2ETests/Runner/Scenarios/DefaultScenario.swift @@ -8,6 +8,9 @@ import Foundation import UIKit import SwiftUI +/// The default scenario will present the list of Synthetic scenarios to run in development mode. +/// To skip this screen, you can set the `E2E_SCENARIO` environment variable with the name +/// the desired scenario. struct DefaultScenario: Scenario { func start(info: TestInfo) -> UIViewController { UIHostingController(rootView: ContentView(info: info)) @@ -18,7 +21,7 @@ struct DefaultScenario: Scenario { var body: some View { NavigationView { - List(Scenarios.allCases, id: \.rawValue) { scenario in + List(SyntheticScenario.allCases, id: \.rawValue) { scenario in NavigationLink { ScenarioView(info: info, scenario: scenario) } label: { @@ -42,19 +45,10 @@ struct DefaultScenario: Scenario { } } -#Preview { - DefaultScenario.ContentView(info: TestInfo( - mobileIntegrationOrg: OrgInfo( - clientToken: "", - applicationID: "", - site: nil, - env: nil - ), - sessionReplayOrg: OrgInfo( - clientToken: "", - applicationID: "", - site: nil, - env: nil - ) - )) +#if DEBUG +struct DefaultScenario_Previews: PreviewProvider { + static var previews: some View { + DefaultScenario.ContentView(info: .empty) + } } +#endif diff --git a/E2ETests/Runner/Scenarios/Scenario.swift b/E2ETests/Runner/Scenarios/Scenario.swift index 3126a85ff5..77e7639ed0 100644 --- a/E2ETests/Runner/Scenarios/Scenario.swift +++ b/E2ETests/Runner/Scenarios/Scenario.swift @@ -7,17 +7,61 @@ import Foundation import UIKit +/// A `Scenario` is the entry-point of the E2E runner application. +/// +/// The compliant objects are responsible for initialization the SDK, enabling +/// Feature, and create the root view-controller. protocol Scenario { + /// Starts the scenario. + /// + /// Starting the scenario should intialize the SDK and enable Features based on + /// the provided ``TestInfo`` and scenarios needs. + /// + /// The returned view-controller will be used as the root view controller of the + /// application window. + /// + /// - Parameter info: The test info for configuring the SDK. + /// - Returns: The root view-controller. func start(info: TestInfo) -> UIViewController } -enum Scenarios: String, Scenario, CaseIterable { +/// A Synthetic scenario can be initialized by defining a Synthetic Test Variable +/// named `E2E_SCENARIO`. +/// +/// see. https://docs.datadoghq.com/mobile_app_testing/mobile_app_tests/#variables +enum SyntheticScenario: String, CaseIterable { case sessionReplayWebView + + /// Creates the scenario defined by the`E2E_SCENARIO` environment variable. + /// + /// - Parameter processInfo: The process info holding the environment variables. + init?(processInfo: ProcessInfo = .processInfo) { + guard + processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == nil, // skip SwiftUI preview + let rawValue = processInfo.environment["E2E_SCENARIO"], + let scenario = Self(rawValue: rawValue) + else { + return nil + } - func start(info: TestInfo) -> UIViewController { + self = scenario + } + + /// Returns the scenario defined by the environment variable. + var scenario: Scenario { switch self { case .sessionReplayWebView: - return UIViewController() + return SessionReplayWebViewScenario() } } } + +extension SyntheticScenario: Scenario { + /// Starts the underlying scenario. + /// + /// - Parameter info: The test info for configuring the SDK. + /// - Returns: The root view-controller. + func start(info: TestInfo) -> UIViewController { + scenario.start(info: info) + } +} diff --git a/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewController.swift b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewController.swift new file mode 100644 index 0000000000..31b7c06b60 --- /dev/null +++ b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewController.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 UIKit +import WebKit +import DatadogWebViewTracking + +class SessionReplayWebViewController: UIViewController, WKUIDelegate { + var webView: WKWebView! + + override func loadView() { + let configuration = WKWebViewConfiguration() + webView = WKWebView(frame: .zero, configuration: configuration) + webView.uiDelegate = self + view = webView + } + + override func viewDidLoad() { + super.viewDidLoad() + WebViewTracking.enable( + webView: webView, + hosts: ["datadoghq.dev"], + sessionReplayConfiguration: WebViewTracking.SessionReplayConfiguration( + privacyLevel: .allow + ) + ) + + let url = URL(string: "https://datadoghq.dev/browser-sdk-test-playground/webview-support")! + let request = URLRequest(url: url) + webView.load(request) + } + + func load(url string: String) { + let url = URL(string: string)! + let request = URLRequest(url: url) + webView.load(request) + } +} diff --git a/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift new file mode 100644 index 0000000000..4704adbe68 --- /dev/null +++ b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift @@ -0,0 +1,38 @@ +/* + * 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 +import UIKit + +import DatadogCore +import DatadogRUM +import DatadogSessionReplay + +struct SessionReplayWebViewScenario: Scenario { + func start(info: TestInfo) -> UIViewController { + Datadog.initialize( + with: .e2e(info: info), + trackingConsent: .granted + ) + + RUM.enable( + with: RUM.Configuration( + applicationID: info.applicationID, + uiKitViewsPredicate: DefaultUIKitRUMViewsPredicate(), + uiKitActionsPredicate: DefaultUIKitRUMActionsPredicate() + ) + ) + + SessionReplay.enable( + with: SessionReplay.Configuration( + replaySampleRate: 100, + defaultPrivacyLevel: .allow + ) + ) + + return SessionReplayWebViewController() + } +} diff --git a/E2ETests/Runner/ViewController.swift b/E2ETests/Runner/ViewController.swift deleted file mode 100644 index 01ab50c77e..0000000000 --- a/E2ETests/Runner/ViewController.swift +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } -} diff --git a/E2ETests/xcconfigs/Runner.xcconfig b/E2ETests/xcconfigs/Runner.xcconfig index 38cb31dfe3..3dce833ee6 100644 --- a/E2ETests/xcconfigs/Runner.xcconfig +++ b/E2ETests/xcconfigs/Runner.xcconfig @@ -1,3 +1,3 @@ -RUM_APPLICATION_ID= // the RUM Application ID obtained on datadoghq.com -DATADOG_CLIENT_TOKEN= // the Client Token, generated for RUM_APPLICATION_ID +CLIENT_TOKEN=// the Client Token on Mobile Integration Org +RUM_APPLICATION_ID=// the RUM Application ID on Mobile Integration Org #include? "E2E.local.xcconfig" From 8818b9e1056f9f028b4b90bcc1efbbcaeed66609 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 6 May 2024 17:04:17 +0200 Subject: [PATCH 085/153] Add README --- .../E2ETests.xcodeproj/.xcodesamplecode.plist | 1 + E2ETests/E2ETests.xcodeproj/project.pbxproj | 2 + E2ETests/README.md | 88 +++++++++++++++++++ E2ETests/Runner/Scenarios/Scenario.swift | 3 +- 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 E2ETests/E2ETests.xcodeproj/.xcodesamplecode.plist create mode 100644 E2ETests/README.md diff --git a/E2ETests/E2ETests.xcodeproj/.xcodesamplecode.plist b/E2ETests/E2ETests.xcodeproj/.xcodesamplecode.plist new file mode 100644 index 0000000000..4bc741ca64 --- /dev/null +++ b/E2ETests/E2ETests.xcodeproj/.xcodesamplecode.plist @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/E2ETests/E2ETests.xcodeproj/project.pbxproj b/E2ETests/E2ETests.xcodeproj/project.pbxproj index 9dd59d490b..fa231743bf 100644 --- a/E2ETests/E2ETests.xcodeproj/project.pbxproj +++ b/E2ETests/E2ETests.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ D2A610B12BE14D01000AA6AB /* DefaultScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultScenario.swift; sourceTree = ""; }; D2C0289F2BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayWebViewScenario.swift; sourceTree = ""; }; D2C028A12BE14FD200B5D7D3 /* SessionReplayWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionReplayWebViewController.swift; sourceTree = ""; }; + D2C028A32BE9282400B5D7D3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -82,6 +83,7 @@ D292E3782BD7BA830083453D = { isa = PBXGroup; children = ( + D2C028A32BE9282400B5D7D3 /* README.md */, D2720E2F2BD8FE2F008ADF5D /* xcconfigs */, D292E3B92BD7BCDF0083453D /* Runner */, D292E3822BD7BA830083453D /* Products */, diff --git a/E2ETests/README.md b/E2ETests/README.md new file mode 100644 index 0000000000..0253082a79 --- /dev/null +++ b/E2ETests/README.md @@ -0,0 +1,88 @@ +# End to End Tests + +E2E Test scenarios are run in [Synthetics for Mobile](https://docs.datadoghq.com/mobile_app_testing/) and [Monitors](https://docs.datadoghq.com/monitors/) will assert the proper propagation of data. + + +## CI + +CI will continuously build, sign, and upload a Runner application to Synthetics which will run predefined Tests daily. + +### Build + +Before building the application, the `E2ETests/xcconfigs/E2E.local.xcconfig` configuration file must be present with the `Mobile - Integration Org` client token and RUM application ID, these values are sensitive and must be securely stored. + +```ini +CLIENT_TOKEN= +RUM_APPLICATION_ID= +``` + +> [!TIP] +> Files can be base64 encoded and stored in secret variables. To copy the encoded file content, you can do: +> ```bash +> cat E2ETests/xcconfigs/E2E.local.xcconfig | base64 | pbcopy +> ``` + +### Sign + +To sign the Runner application, the Certificate and Provision Profile defined in [Synthetics.xcconfig](xcconfigs/Synthetics.xcconfig) and in [exportOptions.plist](exportOptions.plist) needs to be installed on the build machine, these files are sensitive and must be securely stored. Make sure to update both files when updating the Certificate and Provisioning Profile, otherwise signing will fail. + +> [!NOTE] +> Certificate & Provisioning Profile could be downloaded using [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi) instead of stored CI secrets. But we don't have the tooling in place. + +### Upload + +The application version (build number) will be set to the commit SHA of the current job, and the build will be uploaded to Synthetics using the [datadog-ci](https://github.com/DataDog/datadog-ci) CLI. This step expects environment variables to authenticate with the `Mobile - Integration Org`: + +```bash +export DATADOG_API_KEY= +export DATADOG_APP_KEY= +export S8S_APPLICATION_ID= +``` + +## Development + +Each scenario is independent and can be considered as an app within the Runner. + +### Create a Scenario + +A scenario must comply with the [`Scenario`](Runner/Scenarios/Scenario.swift) protocol. Upon start, a scenario will initialize the SDK, enable Features, and return a root view-controller. + +Here is a simple example of a scenario using Logs: +```swift +import Foundation +import UIKit + +import DatadogCore +import DatadogLogs + +struct SessionReplayWebViewScenario: Scenario { + + func start(info: TestInfo) -> UIViewController { + + Datadog.initialize( + with: .e2e(info: info), // SDK init with the e2e configuration + trackingConsent: .granted + ) + + Logs.enable() + + return LoggerViewController() + } +} +``` + +The test should then be added to the [`SyntheticScenario`](Runner/Scenarios/Scenario.swift) enumeration so it can be selected, either manually or by setting the `E2E_SCENARIO` environment variable. + + +### Adding the Test in Synthetics + +When creating a test in Synthetics, make sure to **always run on the latest version**. + +You can skip the Scenario selection screen by setting the `Process Arguments` of the Synthetic test: +```json +{ + "E2E_SCENARIO": "" +} +``` + +The test's name must match the [`SyntheticScenario`](Runner/Scenarios/Scenario.swift) enum case. \ No newline at end of file diff --git a/E2ETests/Runner/Scenarios/Scenario.swift b/E2ETests/Runner/Scenarios/Scenario.swift index 77e7639ed0..6e6e034966 100644 --- a/E2ETests/Runner/Scenarios/Scenario.swift +++ b/E2ETests/Runner/Scenarios/Scenario.swift @@ -30,8 +30,9 @@ protocol Scenario { /// /// see. https://docs.datadoghq.com/mobile_app_testing/mobile_app_tests/#variables enum SyntheticScenario: String, CaseIterable { + /// The `Session Replay WebView` Synthetics Test. id: `ks5-ba9-ck5` case sessionReplayWebView - + /// Creates the scenario defined by the`E2E_SCENARIO` environment variable. /// /// - Parameter processInfo: The process info holding the environment variables. From 628d0fe9cf837031426cc7a85b38a3cfb9d0029d Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 7 May 2024 14:18:33 +0200 Subject: [PATCH 086/153] Apply suggestions from code review Co-authored-by: Ursula Chen <58821586+urseberry@users.noreply.github.com> --- E2ETests/README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/E2ETests/README.md b/E2ETests/README.md index 0253082a79..a147d43449 100644 --- a/E2ETests/README.md +++ b/E2ETests/README.md @@ -1,15 +1,15 @@ # End to End Tests -E2E Test scenarios are run in [Synthetics for Mobile](https://docs.datadoghq.com/mobile_app_testing/) and [Monitors](https://docs.datadoghq.com/monitors/) will assert the proper propagation of data. +[Synthetics for Mobile](https://docs.datadoghq.com/mobile_app_testing/) runs E2E test scenarios. [Monitors](https://docs.datadoghq.com/monitors/) assert the proper propagation of data. ## CI -CI will continuously build, sign, and upload a Runner application to Synthetics which will run predefined Tests daily. +CI continuously builds, signs, and uploads a runner application to Synthetics which runs predefined tests daily. ### Build -Before building the application, the `E2ETests/xcconfigs/E2E.local.xcconfig` configuration file must be present with the `Mobile - Integration Org` client token and RUM application ID, these values are sensitive and must be securely stored. +Before building the application, the `E2ETests/xcconfigs/E2E.local.xcconfig` configuration file must be present and contain the `Mobile - Integration Org` client token and RUM application ID. These values are sensitive and must be securely stored. ```ini CLIENT_TOKEN= @@ -24,14 +24,14 @@ RUM_APPLICATION_ID= ### Sign -To sign the Runner application, the Certificate and Provision Profile defined in [Synthetics.xcconfig](xcconfigs/Synthetics.xcconfig) and in [exportOptions.plist](exportOptions.plist) needs to be installed on the build machine, these files are sensitive and must be securely stored. Make sure to update both files when updating the Certificate and Provisioning Profile, otherwise signing will fail. +To sign the runner application, the certificate and provision profile defined in [Synthetics.xcconfig](xcconfigs/Synthetics.xcconfig) and in [exportOptions.plist](exportOptions.plist) needs to be installed on the build machine. These files are sensitive and must be securely stored. Make sure to update both files when updating the certificate and provisioning profile, otherwise signing fails. > [!NOTE] > Certificate & Provisioning Profile could be downloaded using [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi) instead of stored CI secrets. But we don't have the tooling in place. ### Upload -The application version (build number) will be set to the commit SHA of the current job, and the build will be uploaded to Synthetics using the [datadog-ci](https://github.com/DataDog/datadog-ci) CLI. This step expects environment variables to authenticate with the `Mobile - Integration Org`: +The application version (build number) is set to the commit SHA of the current job, and the build is uploaded to Synthetics using the [datadog-ci](https://github.com/DataDog/datadog-ci) CLI. This step expects environment variables to authenticate with the `Mobile - Integration Org`: ```bash export DATADOG_API_KEY= @@ -41,11 +41,11 @@ export S8S_APPLICATION_ID= ## Development -Each scenario is independent and can be considered as an app within the Runner. +Each scenario is independent and can be considered as an app within the runner. -### Create a Scenario +### Create a scenario -A scenario must comply with the [`Scenario`](Runner/Scenarios/Scenario.swift) protocol. Upon start, a scenario will initialize the SDK, enable Features, and return a root view-controller. +A scenario must comply with the [`Scenario`](Runner/Scenarios/Scenario.swift) protocol. Upon start, a scenario initializes the SDK, enables features, and returns a root view-controller. Here is a simple example of a scenario using Logs: ```swift @@ -74,11 +74,11 @@ struct SessionReplayWebViewScenario: Scenario { The test should then be added to the [`SyntheticScenario`](Runner/Scenarios/Scenario.swift) enumeration so it can be selected, either manually or by setting the `E2E_SCENARIO` environment variable. -### Adding the Test in Synthetics +### Adding the test in synthetics -When creating a test in Synthetics, make sure to **always run on the latest version**. +**Note:** When creating a test in Synthetics, make sure to always run on the _latest version_. -You can skip the Scenario selection screen by setting the `Process Arguments` of the Synthetic test: +You can skip the scenario selection screen by setting the **Process Arguments** of the Synthetic test: ```json { "E2E_SCENARIO": "" From 63399ed5bb755e1bd4720869028c9c3e43b4d53b Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 7 May 2024 15:08:48 +0200 Subject: [PATCH 087/153] Update Scenario.swift --- E2ETests/Runner/Scenarios/Scenario.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/E2ETests/Runner/Scenarios/Scenario.swift b/E2ETests/Runner/Scenarios/Scenario.swift index 6e6e034966..45b6156be9 100644 --- a/E2ETests/Runner/Scenarios/Scenario.swift +++ b/E2ETests/Runner/Scenarios/Scenario.swift @@ -9,13 +9,13 @@ import UIKit /// A `Scenario` is the entry-point of the E2E runner application. /// -/// The compliant objects are responsible for initialization the SDK, enabling -/// Feature, and create the root view-controller. +/// The compliant objects are responsible for initializing the SDK, enabling +/// Features, and create the root view-controller. protocol Scenario { /// Starts the scenario. /// /// Starting the scenario should intialize the SDK and enable Features based on - /// the provided ``TestInfo`` and scenarios needs. + /// the provided ``TestInfo`` and scenario's needs. /// /// The returned view-controller will be used as the root view controller of the /// application window. @@ -25,10 +25,8 @@ protocol Scenario { func start(info: TestInfo) -> UIViewController } -/// A Synthetic scenario can be initialized by defining a Synthetic Test Variable +/// A Synthetic scenario can be initialized by defining a Synthetic Test Process Argument /// named `E2E_SCENARIO`. -/// -/// see. https://docs.datadoghq.com/mobile_app_testing/mobile_app_tests/#variables enum SyntheticScenario: String, CaseIterable { /// The `Session Replay WebView` Synthetics Test. id: `ks5-ba9-ck5` case sessionReplayWebView From 2b7efd61c5f9ed58f68fa0a46fad508490fcdd3d Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 7 May 2024 15:12:45 +0200 Subject: [PATCH 088/153] Remove assets --- E2ETests/E2ETests.xcodeproj/project.pbxproj | 19 -------------- .../AccentColor.colorset/Contents.json | 11 -------- .../AppIcon.appiconset/Contents.json | 13 ---------- E2ETests/Runner/Assets.xcassets/Contents.json | 6 ----- .../Runner/Base.lproj/LaunchScreen.storyboard | 25 ------------------- 5 files changed, 74 deletions(-) delete mode 100644 E2ETests/Runner/Assets.xcassets/AccentColor.colorset/Contents.json delete mode 100644 E2ETests/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 E2ETests/Runner/Assets.xcassets/Contents.json delete mode 100644 E2ETests/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/E2ETests/E2ETests.xcodeproj/project.pbxproj b/E2ETests/E2ETests.xcodeproj/project.pbxproj index fa231743bf..6f840fc721 100644 --- a/E2ETests/E2ETests.xcodeproj/project.pbxproj +++ b/E2ETests/E2ETests.xcodeproj/project.pbxproj @@ -8,8 +8,6 @@ /* Begin PBXBuildFile section */ D292E3BB2BD7BCDF0083453D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */; }; - D292E3C42BD7BCE00083453D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C32BD7BCE00083453D /* Assets.xcassets */; }; - D292E3C72BD7BCE00083453D /* Base in Resources */ = {isa = PBXBuildFile; fileRef = D292E3C62BD7BCE00083453D /* Base */; }; D2A6109B2BE11A74000AA6AB /* DatadogCore in Frameworks */ = {isa = PBXBuildFile; productRef = D2A6109A2BE11A74000AA6AB /* DatadogCore */; }; D2A6109D2BE11A74000AA6AB /* DatadogCrashReporting in Frameworks */ = {isa = PBXBuildFile; productRef = D2A6109C2BE11A74000AA6AB /* DatadogCrashReporting */; }; D2A6109F2BE11A74000AA6AB /* DatadogLogs in Frameworks */ = {isa = PBXBuildFile; productRef = D2A6109E2BE11A74000AA6AB /* DatadogLogs */; }; @@ -32,8 +30,6 @@ D2720E2E2BD8FE2F008ADF5D /* Synthetics.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Synthetics.xcconfig; sourceTree = ""; }; D292E3B82BD7BCDF0083453D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - D292E3C32BD7BCE00083453D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - D292E3C62BD7BCE00083453D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; D292E3C82BD7BCE00083453D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D2A610AC2BE125F0000AA6AB /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; D2A610AF2BE12739000AA6AB /* Scenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scenario.swift; sourceTree = ""; }; @@ -105,8 +101,6 @@ D292E3BA2BD7BCDF0083453D /* AppDelegate.swift */, D2A610AC2BE125F0000AA6AB /* AppConfiguration.swift */, D2A610AE2BE12724000AA6AB /* Scenarios */, - D292E3C32BD7BCE00083453D /* Assets.xcassets */, - D292E3C52BD7BCE00083453D /* LaunchScreen.storyboard */, D292E3C82BD7BCE00083453D /* Info.plist */, ); path = Runner; @@ -199,8 +193,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D292E3C42BD7BCE00083453D /* Assets.xcassets in Resources */, - D292E3C72BD7BCE00083453D /* Base in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -222,17 +214,6 @@ }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXVariantGroup section */ - D292E3C52BD7BCE00083453D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - D292E3C62BD7BCE00083453D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ D2720E302BD8FE9A008ADF5D /* Synthetics */ = { isa = XCBuildConfiguration; diff --git a/E2ETests/Runner/Assets.xcassets/AccentColor.colorset/Contents.json b/E2ETests/Runner/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897008..0000000000 --- a/E2ETests/Runner/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/E2ETests/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/E2ETests/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 13613e3ee1..0000000000 --- a/E2ETests/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/E2ETests/Runner/Assets.xcassets/Contents.json b/E2ETests/Runner/Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596a7..0000000000 --- a/E2ETests/Runner/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/E2ETests/Runner/Base.lproj/LaunchScreen.storyboard b/E2ETests/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 865e9329f3..0000000000 --- a/E2ETests/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - From 02aa88c7cc9c5e0d34f18b8c5269136580fbc2ac Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 13 May 2024 10:03:41 +0200 Subject: [PATCH 089/153] Update E2ETests/README.md Co-authored-by: Ursula Chen <58821586+urseberry@users.noreply.github.com> --- E2ETests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/E2ETests/README.md b/E2ETests/README.md index a147d43449..a72bc9f1ed 100644 --- a/E2ETests/README.md +++ b/E2ETests/README.md @@ -27,7 +27,7 @@ RUM_APPLICATION_ID= To sign the runner application, the certificate and provision profile defined in [Synthetics.xcconfig](xcconfigs/Synthetics.xcconfig) and in [exportOptions.plist](exportOptions.plist) needs to be installed on the build machine. These files are sensitive and must be securely stored. Make sure to update both files when updating the certificate and provisioning profile, otherwise signing fails. > [!NOTE] -> Certificate & Provisioning Profile could be downloaded using [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi) instead of stored CI secrets. But we don't have the tooling in place. +> Datadog does not have the tooling in place for you to download the Certificate & Provisioning Profile using the [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi) instead of stored CI secrets. ### Upload From 71e6383c0bede2863e11f57d9efb2a5494544be3 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 13 May 2024 11:41:44 +0200 Subject: [PATCH 090/153] Revert "Update E2ETests/README.md" This reverts commit 7db7b2c24302ed8ef94e9d5fb039b3f32adb9b5d. --- E2ETests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/E2ETests/README.md b/E2ETests/README.md index a72bc9f1ed..3016f9448f 100644 --- a/E2ETests/README.md +++ b/E2ETests/README.md @@ -27,7 +27,7 @@ RUM_APPLICATION_ID= To sign the runner application, the certificate and provision profile defined in [Synthetics.xcconfig](xcconfigs/Synthetics.xcconfig) and in [exportOptions.plist](exportOptions.plist) needs to be installed on the build machine. These files are sensitive and must be securely stored. Make sure to update both files when updating the certificate and provisioning profile, otherwise signing fails. > [!NOTE] -> Datadog does not have the tooling in place for you to download the Certificate & Provisioning Profile using the [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi) instead of stored CI secrets. +> Certificate & Provisioning Profile are also available through the [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi). But we don't have the tooling in place. ### Upload From a729c60c267b47fe3b0027e321ceca0db7227e07 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 13 May 2024 11:51:43 +0200 Subject: [PATCH 091/153] Update E2ETests/Runner/Scenarios/Scenario.swift Co-authored-by: Maciek Grzybowski --- E2ETests/Runner/Scenarios/Scenario.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/E2ETests/Runner/Scenarios/Scenario.swift b/E2ETests/Runner/Scenarios/Scenario.swift index 45b6156be9..40e1b2dbfb 100644 --- a/E2ETests/Runner/Scenarios/Scenario.swift +++ b/E2ETests/Runner/Scenarios/Scenario.swift @@ -27,6 +27,8 @@ protocol Scenario { /// A Synthetic scenario can be initialized by defining a Synthetic Test Process Argument /// named `E2E_SCENARIO`. +/// +/// Note: The raw value of enum case must match the test name defined in Synthetics. enum SyntheticScenario: String, CaseIterable { /// The `Session Replay WebView` Synthetics Test. id: `ks5-ba9-ck5` case sessionReplayWebView From 0da8f2cbd602882e1ecdf77fca527a0f83b120fd Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 15 May 2024 15:21:17 +0200 Subject: [PATCH 092/153] Apply CR suggestions --- .../xcshareddata/xcschemes/Runner.xcscheme | 10 ++++++++++ E2ETests/Runner/AppConfiguration.swift | 16 ++++++++++------ E2ETests/Runner/Info.plist | 12 ++++++++---- E2ETests/xcconfigs/Runner.xcconfig | 9 +++++++-- bitrise.yml | 2 +- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/E2ETests/E2ETests.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/E2ETests/E2ETests.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c51a66b77b..dc55baf0ee 100644 --- a/E2ETests/E2ETests.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/E2ETests/E2ETests.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -50,6 +50,16 @@ ReferencedContainer = "container:E2ETests.xcodeproj"> + + + + + + $(CLIENT_TOKEN) /// ApplicationID /// $(RUM_APPLICATION_ID) +/// Environment +/// $(DD_ENV) +/// Site +/// $(DD_SITE) /// /// struct TestInfo: Decodable { let clientToken: String let applicationID: String - let site: DatadogSite? - let env: String? + let site: DatadogSite + let env: String enum CodingKeys: String, CodingKey { case clientToken = "ClientToken" @@ -48,8 +52,8 @@ extension TestInfo { .init( clientToken: "", applicationID: "", - site: nil, - env: nil + site: .us1, + env: "e2e" ) } } @@ -60,8 +64,8 @@ extension Datadog.Configuration { static func e2e(info: TestInfo) -> Self { .init( clientToken: info.clientToken, - env: info.env ?? "e2e", - site: info.site ?? .us1 + env: info.env, + site: info.site ) } } diff --git a/E2ETests/Runner/Info.plist b/E2ETests/Runner/Info.plist index 1787b852a0..c5dc3c6338 100644 --- a/E2ETests/Runner/Info.plist +++ b/E2ETests/Runner/Info.plist @@ -4,10 +4,14 @@ DatadogConfiguration - ClientToken - $(CLIENT_TOKEN) - ApplicationID - $(RUM_APPLICATION_ID) + ClientToken + $(CLIENT_TOKEN) + ApplicationID + $(RUM_APPLICATION_ID) + Environment + $(DD_ENV) + Site + $(DD_SITE) diff --git a/E2ETests/xcconfigs/Runner.xcconfig b/E2ETests/xcconfigs/Runner.xcconfig index 3dce833ee6..f5667fcc7a 100644 --- a/E2ETests/xcconfigs/Runner.xcconfig +++ b/E2ETests/xcconfigs/Runner.xcconfig @@ -1,3 +1,8 @@ -CLIENT_TOKEN=// the Client Token on Mobile Integration Org -RUM_APPLICATION_ID=// the RUM Application ID on Mobile Integration Org +CLIENT_TOKEN = // the Client Token on Mobile Integration Org +RUM_APPLICATION_ID = // the RUM Application ID on Mobile Integration Org + +DD_ENV[config=*] = e2e +DD_ENV[config=Debug] = development +DD_SITE = us1 + #include? "E2E.local.xcconfig" diff --git a/bitrise.yml b/bitrise.yml index 7937f685ae..12551498de 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -41,6 +41,7 @@ workflows: after_run: - _make_dependencies - run_conditioned_workflows + - run_e2e_s8s_upload - _deploy_artifacts - _notify_failure_on_slack @@ -143,7 +144,6 @@ workflows: - run_linter - run_unit_tests - run_integration_tests - - run_e2e_s8s_upload - run_smoke_tests - run_tools_tests From f1eef07f22fa7f44f4e0fd68dd10aaaec6d365f8 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Thu, 16 May 2024 12:34:13 +0200 Subject: [PATCH 093/153] Prevent webview leak in tests --- .../SessionReplay/WebRecordIntegrationTests.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Datadog/IntegrationUnitTests/SessionReplay/WebRecordIntegrationTests.swift b/Datadog/IntegrationUnitTests/SessionReplay/WebRecordIntegrationTests.swift index 1738ccd2fe..80619be8f5 100644 --- a/Datadog/IntegrationUnitTests/SessionReplay/WebRecordIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/SessionReplay/WebRecordIntegrationTests.swift @@ -15,7 +15,8 @@ import TestUtilities class WebRecordIntegrationTests: XCTestCase { // swiftlint:disable implicitly_unwrapped_optional - private var core: DatadogCoreProxy! // swiftlint:disable:this implicitly_unwrapped_optional + private var core: DatadogCoreProxy! + private var webView: WKWebView! private var controller: WKUserContentControllerMock! // swiftlint:enable implicitly_unwrapped_optional @@ -30,20 +31,22 @@ class WebRecordIntegrationTests: XCTestCase { controller = WKUserContentControllerMock() let configuration = WKWebViewConfiguration() + configuration.processPool = WKProcessPool() // do not share cookies between instances: prevent leak configuration.userContentController = controller - let webView = WKWebView(frame: .zero, configuration: configuration) + webView = WKWebView(frame: .zero, configuration: configuration) WebViewTracking.enable(webView: webView, in: core) } override func tearDown() { + WebViewTracking.disable(webView: webView) core.flushAndTearDown() core = nil + webView = nil controller = nil } func testWebRecordIntegration() throws { // Given - let webView = WKWebView() let randomApplicationID: String = .mockRandom() let randomUUID: UUID = .mockRandom() let randomBrowserViewID: UUID = .mockRandom() From 7d69e12e9a70b827e4c4ce9df392bdfcf8597982 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 16 May 2024 13:51:49 +0200 Subject: [PATCH 094/153] RUM-3588 CR feedback - add tests for updating attributes --- .../Monitor+GlobalAttributesTests.swift | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift index 3a70e39efa..6d1d46f6cb 100644 --- a/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift @@ -116,6 +116,24 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(lastView.attribute(forKey: "attribute2"), "value2") } + func testUpdatingGlobalAttributesAfterSDKInit_thenStartingAndStoppingView() throws { + // Given + monitor.notifySDKInit() + + // When + monitor.addAttribute(forKey: "attribute", value: "value") + monitor.addAttribute(forKey: "attribute", value: "new-value") + monitor.removeAttribute(forKey: "unknown-attribute") + monitor.startView(key: "View") + monitor.stopView(key: "View") + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + XCTAssertEqual(lastView.view.name, "View") + XCTAssertEqual(lastView.attribute(forKey: "attribute"), "new-value") + XCTAssertEqual(lastView.numberOfAttributes, 1) + } + // MARK: - Changing Global Attributes After Starting View func testAddingGlobalAttributeAfterViewIsStarted() throws { @@ -246,6 +264,39 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(lastView3.attribute(forKey: "attribute3"), "value3") } + func testAddingGlobalAttributesAfterViewIsStarted_thenStartingNextViewsWhileUpdatingAttributes() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "View1") + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + monitor.addAttribute(forKey: "attribute3", value: "value3") + + // When + monitor.startView(key: "View2") + monitor.addAttribute(forKey: "attribute1", value: "new-value1") + monitor.startView(key: "View3") + monitor.addAttribute(forKey: "attribute2", value: "new-value1") + + // Then + let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self) + let lastView1 = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "View1" })) + let lastView2 = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "View2" })) + let lastView3 = try XCTUnwrap(viewEvents.last(where: { $0.view.name == "View3" })) + + XCTAssertEqual(lastView1.attribute(forKey: "attribute1"), "value1") + XCTAssertEqual(lastView1.attribute(forKey: "attribute2"), "value2") + XCTAssertEqual(lastView1.attribute(forKey: "attribute3"), "value3") + + XCTAssertEqual(lastView2.attribute(forKey: "attribute1"), "new-value1") + XCTAssertEqual(lastView2.attribute(forKey: "attribute2"), "value2") + XCTAssertEqual(lastView2.attribute(forKey: "attribute3"), "value3") + + XCTAssertEqual(lastView3.attribute(forKey: "attribute1"), "new-value1") + XCTAssertEqual(lastView3.attribute(forKey: "attribute2"), "value2") + XCTAssertEqual(lastView3.attribute(forKey: "attribute3"), "value3") + } + // MARK: - Changing Global Attributes While There Is An Inactive View func testAddingGlobalAttributeWhileInactiveView_thenImplicitlyStoppingInactiveView() throws { @@ -382,6 +433,34 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(resourceEvent.context?.contextInfo["attribute2"] as? String, "value2") } + func testUpdatingGlobalAttributeInActiveView_thenCollectingViewEvents() throws { + // Given + monitor.notifySDKInit() + monitor.startView(key: "ActiveView") + monitor.addAttribute(forKey: "attribute", value: "value") + monitor.addAttribute(forKey: "attribute", value: "new-value") + + // When + monitor.addError(message: "error event") + monitor.addAction(type: .custom, name: "custom action event") + monitor.startResource(resourceKey: "resource", url: .mockAny()) + monitor.stopResource(resourceKey: "resource", response: .mockAny()) + + // Then + let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) + let errorEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMErrorEvent.self).last) + let actionEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMActionEvent.self).last) + let resourceEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMResourceEvent.self).last) + + XCTAssertEqual(lastView.view.error.count, 1) + XCTAssertEqual(lastView.view.action.count, 1) + XCTAssertEqual(lastView.view.resource.count, 1) + XCTAssertNil(lastView.attribute(forKey: "attribute")) + XCTAssertEqual(errorEvent.context?.contextInfo["attribute"] as? String, "new-value") + XCTAssertEqual(actionEvent.context?.contextInfo["attribute"] as? String, "new-value") + XCTAssertEqual(resourceEvent.context?.contextInfo["attribute"] as? String, "new-value") + } + func testAddingGlobalAttributesToActiveView_thenCollectingViewTimingsAndRemovingAttribute() throws { // Given monitor.notifySDKInit() @@ -416,4 +495,8 @@ private extension RUMViewEvent { func attribute(forKey key: String) -> T? { return context?.contextInfo[key] as? T } + + var numberOfAttributes: Int { + return context?.contextInfo.count ?? 0 + } } From 205b9a0e65921dff8b5e63fcd652fafc3ac63252 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 17 May 2024 10:22:24 +0200 Subject: [PATCH 095/153] Update bitrise.yml Co-authored-by: Maciek Grzybowski --- bitrise.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/bitrise.yml b/bitrise.yml index 12551498de..36b7a16629 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -642,6 +642,7 @@ workflows: steps: - script: title: Upload E2E application to Synthetics. + run_if: '{{enveq "BITRISE_GIT_BRANCH" "develop"}}' inputs: - content: |- #!/usr/bin/env bash From c416abf9eb8a02f8186a6bc68037c314bb0b8b48 Mon Sep 17 00:00:00 2001 From: Pedro Lousada Date: Wed, 8 May 2024 11:07:28 +0100 Subject: [PATCH 096/153] [RUM-2911] Update RUM model --- .../Datadog/Mocks/RUMDataModelMocks.swift | 6 ++ .../Sources/RUM/RUMDataModels+objc.swift | 82 ++++++++++++++++++- .../Sources/DataModels/RUMDataModels.swift | 48 ++++++++++- DatadogRUM/Sources/FatalErrorBuilder.swift | 1 + .../Integrations/TelemetryReceiver.swift | 1 + .../RUMMonitor/Scopes/RUMResourceScope.swift | 5 ++ .../RUMMonitor/Scopes/RUMViewScope.swift | 1 + .../Tests/Mocks/RUMDataModelMocks.swift | 6 ++ 8 files changed, 147 insertions(+), 3 deletions(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift index 39bef14c16..cbc9b059bc 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift @@ -246,9 +246,11 @@ extension RUMResourceEvent: RandomMockable { os: .mockRandom(), resource: .init( connect: .init(duration: .mockRandom(), start: .mockRandom()), + decodedBodySize: nil, dns: .init(duration: .mockRandom(), start: .mockRandom()), download: .init(duration: .mockRandom(), start: .mockRandom()), duration: .mockRandom(), + encodedBodySize: nil, firstByte: .init(duration: .mockRandom(), start: .mockRandom()), id: .mockRandom(), method: .mockRandom(), @@ -258,9 +260,11 @@ extension RUMResourceEvent: RandomMockable { type: Bool.random() ? .firstParty : nil ), redirect: .init(duration: .mockRandom(), start: .mockRandom()), + renderBlockingStatus: nil, size: .mockRandom(), ssl: .init(duration: .mockRandom(), start: .mockRandom()), statusCode: .mockRandom(), + transferSize: nil, type: [.native, .image].randomElement()!, url: .mockRandom() ), @@ -408,6 +412,7 @@ extension RUMErrorEvent: RandomMockable { sourceType: .mockRandom(), stack: .mockRandom(), threads: nil, + timeSinceAppStart: nil, type: .mockRandom(), wasTruncated: .mockRandom() ), @@ -518,6 +523,7 @@ extension TelemetryConfigurationEvent: RandomMockable { storeContextsAcrossPages: nil, telemetryConfigurationSampleRate: .mockRandom(), telemetrySampleRate: .mockRandom(), + telemetryUsageSampleRate: nil, traceSampleRate: .mockRandom(), trackBackgroundEvents: .mockRandom(), trackCrossPlatformLongTasks: .mockRandom(), diff --git a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift index 50c08bca9b..3ca5d74aa9 100644 --- a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift +++ b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift @@ -1667,6 +1667,10 @@ public class DDRUMErrorEventError: NSObject { root.swiftModel.error.threads?.map { DDRUMErrorEventErrorThreads(swiftModel: $0) } } + @objc public var timeSinceAppStart: NSNumber? { + root.swiftModel.error.timeSinceAppStart as NSNumber? + } + @objc public var type: String? { root.swiftModel.error.type } @@ -3803,6 +3807,10 @@ public class DDRUMResourceEventResource: NSObject { root.swiftModel.resource.connect != nil ? DDRUMResourceEventResourceConnect(root: root) : nil } + @objc public var decodedBodySize: NSNumber? { + root.swiftModel.resource.decodedBodySize as NSNumber? + } + @objc public var dns: DDRUMResourceEventResourceDNS? { root.swiftModel.resource.dns != nil ? DDRUMResourceEventResourceDNS(root: root) : nil } @@ -3815,6 +3823,10 @@ public class DDRUMResourceEventResource: NSObject { root.swiftModel.resource.duration as NSNumber? } + @objc public var encodedBodySize: NSNumber? { + root.swiftModel.resource.encodedBodySize as NSNumber? + } + @objc public var firstByte: DDRUMResourceEventResourceFirstByte? { root.swiftModel.resource.firstByte != nil ? DDRUMResourceEventResourceFirstByte(root: root) : nil } @@ -3839,6 +3851,10 @@ public class DDRUMResourceEventResource: NSObject { root.swiftModel.resource.redirect != nil ? DDRUMResourceEventResourceRedirect(root: root) : nil } + @objc public var renderBlockingStatus: DDRUMResourceEventResourceRenderBlockingStatus { + .init(swift: root.swiftModel.resource.renderBlockingStatus) + } + @objc public var size: NSNumber? { root.swiftModel.resource.size as NSNumber? } @@ -3851,6 +3867,10 @@ public class DDRUMResourceEventResource: NSObject { root.swiftModel.resource.statusCode as NSNumber? } + @objc public var transferSize: NSNumber? { + root.swiftModel.resource.transferSize as NSNumber? + } + @objc public var type: DDRUMResourceEventResourceResourceType { .init(swift: root.swiftModel.resource.type) } @@ -4120,6 +4140,29 @@ public class DDRUMResourceEventResourceRedirect: NSObject { } } +@objc +public enum DDRUMResourceEventResourceRenderBlockingStatus: Int { + internal init(swift: RUMResourceEvent.Resource.RenderBlockingStatus?) { + switch swift { + case nil: self = .none + case .blocking?: self = .blocking + case .nonBlocking?: self = .nonBlocking + } + } + + internal var toSwift: RUMResourceEvent.Resource.RenderBlockingStatus? { + switch self { + case .none: return nil + case .blocking: return .blocking + case .nonBlocking: return .nonBlocking + } + } + + case none + case blocking + case nonBlocking +} + @objc public class DDRUMResourceEventResourceSSL: NSObject { internal let root: DDRUMResourceEvent @@ -7059,6 +7102,15 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { root.swiftModel.telemetry.configuration.telemetrySampleRate as NSNumber? } + @objc public var telemetryUsageSampleRate: NSNumber? { + root.swiftModel.telemetry.configuration.telemetryUsageSampleRate as NSNumber? + } + + @objc public var traceContextInjection: DDTelemetryConfigurationEventTelemetryConfigurationTraceContextInjection { + set { root.swiftModel.telemetry.configuration.traceContextInjection = newValue.toSwift } + get { .init(swift: root.swiftModel.telemetry.configuration.traceContextInjection) } + } + @objc public var traceSampleRate: NSNumber? { root.swiftModel.telemetry.configuration.traceSampleRate as NSNumber? } @@ -7189,6 +7241,11 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { root.swiftModel.telemetry.configuration.usePartitionedCrossSiteSessionCookie as NSNumber? } + @objc public var usePciIntake: NSNumber? { + set { root.swiftModel.telemetry.configuration.usePciIntake = newValue?.boolValue } + get { root.swiftModel.telemetry.configuration.usePciIntake as NSNumber? } + } + @objc public var useProxy: NSNumber? { set { root.swiftModel.telemetry.configuration.useProxy = newValue?.boolValue } get { root.swiftModel.telemetry.configuration.useProxy as NSNumber? } @@ -7286,6 +7343,29 @@ public enum DDTelemetryConfigurationEventTelemetryConfigurationSelectedTracingPr case tracecontext } +@objc +public enum DDTelemetryConfigurationEventTelemetryConfigurationTraceContextInjection: Int { + internal init(swift: TelemetryConfigurationEvent.Telemetry.Configuration.TraceContextInjection?) { + switch swift { + case nil: self = .none + case .all?: self = .all + case .sampled?: self = .sampled + } + } + + internal var toSwift: TelemetryConfigurationEvent.Telemetry.Configuration.TraceContextInjection? { + switch self { + case .none: return nil + case .all: return .all + case .sampled: return .sampled + } + } + + case none + case all + case sampled +} + @objc public enum DDTelemetryConfigurationEventTelemetryConfigurationViewTrackingStrategy: Int { internal init(swift: TelemetryConfigurationEvent.Telemetry.Configuration.ViewTrackingStrategy?) { @@ -7330,4 +7410,4 @@ public class DDTelemetryConfigurationEventView: NSObject { // swiftlint:enable force_unwrapping -// Generated from https://github.com/DataDog/rum-events-format/tree/61560c6502ebf333e71631ee35d00b9c09aadf8e +// Generated from https://github.com/DataDog/rum-events-format/tree/0455e104863c0f67c3bf69899c7d5da1ba6f0ebb diff --git a/DatadogRUM/Sources/DataModels/RUMDataModels.swift b/DatadogRUM/Sources/DataModels/RUMDataModels.swift index d79d0ea277..e9dc6dfe1c 100644 --- a/DatadogRUM/Sources/DataModels/RUMDataModels.swift +++ b/DatadogRUM/Sources/DataModels/RUMDataModels.swift @@ -737,6 +737,9 @@ public struct RUMErrorEvent: RUMDataModel { /// Description of each thread in the process when error happened. public let threads: [Threads]? + /// Time since application start when error happened (in milliseconds) + public let timeSinceAppStart: Int64? + /// The type of the error public let type: String? @@ -760,6 +763,7 @@ public struct RUMErrorEvent: RUMDataModel { case sourceType = "source_type" case stack = "stack" case threads = "threads" + case timeSinceAppStart = "time_since_app_start" case type = "type" case wasTruncated = "was_truncated" } @@ -1643,6 +1647,9 @@ public struct RUMResourceEvent: RUMDataModel { /// Connect phase properties public let connect: Connect? + /// Size in octet of the resource after removing any applied encoding + public let decodedBodySize: Int64? + /// DNS phase properties public let dns: DNS? @@ -1652,6 +1659,9 @@ public struct RUMResourceEvent: RUMDataModel { /// Duration of the resource public let duration: Int64? + /// Size in octet of the resource before removing any applied content encodings + public let encodedBodySize: Int64? + /// First Byte phase properties public let firstByte: FirstByte? @@ -1670,6 +1680,9 @@ public struct RUMResourceEvent: RUMDataModel { /// Redirect phase properties public let redirect: Redirect? + /// Render blocking status of the resource + public let renderBlockingStatus: RenderBlockingStatus? + /// Size in octet of the resource response body public let size: Int64? @@ -1679,6 +1692,9 @@ public struct RUMResourceEvent: RUMDataModel { /// HTTP status code of the resource public let statusCode: Int64? + /// Size in octet of the fetched resource + public let transferSize: Int64? + /// Resource type public let type: ResourceType @@ -1687,18 +1703,22 @@ public struct RUMResourceEvent: RUMDataModel { enum CodingKeys: String, CodingKey { case connect = "connect" + case decodedBodySize = "decoded_body_size" case dns = "dns" case download = "download" case duration = "duration" + case encodedBodySize = "encoded_body_size" case firstByte = "first_byte" case graphql = "graphql" case id = "id" case method = "method" case provider = "provider" case redirect = "redirect" + case renderBlockingStatus = "render_blocking_status" case size = "size" case ssl = "ssl" case statusCode = "status_code" + case transferSize = "transfer_size" case type = "type" case url = "url" } @@ -1838,6 +1858,12 @@ public struct RUMResourceEvent: RUMDataModel { } } + /// Render blocking status of the resource + public enum RenderBlockingStatus: String, Codable { + case blocking = "blocking" + case nonBlocking = "non-blocking" + } + /// SSL phase properties public struct SSL: Codable { /// Duration in ns of the resource ssl phase @@ -3384,7 +3410,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// Whether UIApplication background tasks are enabled public let backgroundTasksEnabled: Bool? - /// Maximum number of batches processed sequencially without a delay + /// Maximum number of batches processed sequentially without a delay public let batchProcessingLevel: Int64? /// The window duration for batches sent by the SDK (in milliseconds) @@ -3453,6 +3479,12 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// The percentage of telemetry events sent public let telemetrySampleRate: Int64? + /// The percentage of telemetry usage events sent after being sampled by telemetry_sample_rate + public let telemetryUsageSampleRate: Int64? + + /// The opt-in configuration to add trace context + public var traceContextInjection: TraceContextInjection? + /// The percentage of requests traced public let traceSampleRate: Int64? @@ -3537,6 +3569,9 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// Whether a partitioned secure cross-site session cookie is used public let usePartitionedCrossSiteSessionCookie: Bool? + /// Whether logs are sent to the PCI-compliant intake + public var usePciIntake: Bool? + /// Whether a proxy is used public var useProxy: Bool? @@ -3581,6 +3616,8 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case storeContextsAcrossPages = "store_contexts_across_pages" case telemetryConfigurationSampleRate = "telemetry_configuration_sample_rate" case telemetrySampleRate = "telemetry_sample_rate" + case telemetryUsageSampleRate = "telemetry_usage_sample_rate" + case traceContextInjection = "trace_context_injection" case traceSampleRate = "trace_sample_rate" case tracerApi = "tracer_api" case tracerApiVersion = "tracer_api_version" @@ -3609,6 +3646,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case useFirstPartyHosts = "use_first_party_hosts" case useLocalEncryption = "use_local_encryption" case usePartitionedCrossSiteSessionCookie = "use_partitioned_cross_site_session_cookie" + case usePciIntake = "use_pci_intake" case useProxy = "use_proxy" case useSecureSessionCookie = "use_secure_session_cookie" case useTracing = "use_tracing" @@ -3707,6 +3745,12 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case tracecontext = "tracecontext" } + /// The opt-in configuration to add trace context + public enum TraceContextInjection: String, Codable { + case all = "all" + case sampled = "sampled" + } + /// View tracking strategy public enum ViewTrackingStrategy: String, Codable { case activityViewTrackingStrategy = "ActivityViewTrackingStrategy" @@ -4037,4 +4081,4 @@ public enum RUMMethod: String, Codable { case connect = "CONNECT" } -// Generated from https://github.com/DataDog/rum-events-format/tree/61560c6502ebf333e71631ee35d00b9c09aadf8e +// Generated from https://github.com/DataDog/rum-events-format/tree/0455e104863c0f67c3bf69899c7d5da1ba6f0ebb diff --git a/DatadogRUM/Sources/FatalErrorBuilder.swift b/DatadogRUM/Sources/FatalErrorBuilder.swift index 9a41aa0fa0..476b8ca2d0 100644 --- a/DatadogRUM/Sources/FatalErrorBuilder.swift +++ b/DatadogRUM/Sources/FatalErrorBuilder.swift @@ -93,6 +93,7 @@ internal struct FatalErrorBuilder { sourceType: context.nativeSourceOverride.map { RUMErrorSourceType(rawValue: $0) } ?? .ios, stack: errorStack, threads: errorThreads, + timeSinceAppStart: nil, type: errorType, wasTruncated: errorWasTruncated ), diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 59b98fcc37..72fdf516b5 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -309,6 +309,7 @@ private extension TelemetryConfigurationEvent.Telemetry.Configuration { storeContextsAcrossPages: nil, telemetryConfigurationSampleRate: nil, telemetrySampleRate: configuration.telemetrySampleRate, + telemetryUsageSampleRate: nil, traceSampleRate: configuration.traceSampleRate, tracerApi: configuration.tracerAPI, tracerApiVersion: configuration.tracerAPIVersion, diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift index f13c8fac84..59ef18d853 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift @@ -198,6 +198,7 @@ internal class RUMResourceScope: RUMScope { start: metric.start.timeIntervalSince(resourceStartTime).toInt64Nanoseconds ) }, + decodedBodySize: nil, dns: resourceMetrics?.dns.map { metric in .init( duration: metric.duration.toInt64Nanoseconds, @@ -211,6 +212,7 @@ internal class RUMResourceScope: RUMScope { ) }, duration: resolveResourceDuration(resourceDuration), + encodedBodySize: nil, firstByte: resourceMetrics?.firstByte.map { metric in .init( duration: metric.duration.toInt64Nanoseconds, @@ -227,6 +229,7 @@ internal class RUMResourceScope: RUMScope { start: metric.start.timeIntervalSince(resourceStartTime).toInt64Nanoseconds ) }, + renderBlockingStatus: nil, size: size ?? 0, ssl: resourceMetrics?.ssl.map { metric in .init( @@ -235,6 +238,7 @@ internal class RUMResourceScope: RUMScope { ) }, statusCode: command.httpStatusCode?.toInt64 ?? 0, + transferSize: nil, type: resourceType, url: resourceURL ), @@ -310,6 +314,7 @@ internal class RUMResourceScope: RUMScope { sourceType: command.errorSourceType, stack: command.stack, threads: nil, + timeSinceAppStart: nil, type: command.errorType, wasTruncated: nil ), diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index a630ec285d..3b0f5266df 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -606,6 +606,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { sourceType: command.errorSourceType, stack: command.stack, threads: command.threads?.compactMap { $0.toRUMDataFormat }, + timeSinceAppStart: nil, type: command.type, wasTruncated: command.isStackTraceTruncated ), diff --git a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift index ee1edb00ef..ab08fe5a1b 100644 --- a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift @@ -259,9 +259,11 @@ extension RUMResourceEvent: RandomMockable { os: .mockRandom(), resource: .init( connect: .init(duration: .mockRandom(), start: .mockRandom()), + decodedBodySize: nil, dns: .init(duration: .mockRandom(), start: .mockRandom()), download: .init(duration: .mockRandom(), start: .mockRandom()), duration: .mockRandom(), + encodedBodySize: nil, firstByte: .init(duration: .mockRandom(), start: .mockRandom()), id: .mockRandom(), method: .mockRandom(), @@ -271,9 +273,11 @@ extension RUMResourceEvent: RandomMockable { type: Bool.random() ? .firstParty : nil ), redirect: .init(duration: .mockRandom(), start: .mockRandom()), + renderBlockingStatus: nil, size: .mockRandom(), ssl: .init(duration: .mockRandom(), start: .mockRandom()), statusCode: .mockRandom(), + transferSize: nil, type: [.native, .image].randomElement()!, url: .mockRandom() ), @@ -421,6 +425,7 @@ extension RUMErrorEvent: RandomMockable { sourceType: .mockRandom(), stack: .mockRandom(), threads: nil, + timeSinceAppStart: nil, type: .mockRandom(), wasTruncated: .mockRandom() ), @@ -531,6 +536,7 @@ extension TelemetryConfigurationEvent: RandomMockable { storeContextsAcrossPages: nil, telemetryConfigurationSampleRate: .mockRandom(), telemetrySampleRate: .mockRandom(), + telemetryUsageSampleRate: nil, traceSampleRate: .mockRandom(), trackBackgroundEvents: .mockRandom(), trackCrossPlatformLongTasks: .mockRandom(), From 15c1f95bdf4165c65cc3950166d7004e2d7c0bba Mon Sep 17 00:00:00 2001 From: Pedro Lousada Date: Thu, 9 May 2024 09:24:50 +0100 Subject: [PATCH 097/153] [RUM-2911] Fix precision loss in app start timing --- DatadogCore/Private/ObjcAppLaunchHandler.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogCore/Private/ObjcAppLaunchHandler.m b/DatadogCore/Private/ObjcAppLaunchHandler.m index 1b2a2dd2dd..23868d0b05 100644 --- a/DatadogCore/Private/ObjcAppLaunchHandler.m +++ b/DatadogCore/Private/ObjcAppLaunchHandler.m @@ -127,7 +127,7 @@ int processStartTimeIntervalSinceReferenceDate(NSTimeInterval *timeInterval) { // The process' start time is provided relative to 1 Jan 1970 struct timeval startTime = kip.kp_proc.p_starttime; - NSTimeInterval processStartTime = startTime.tv_sec + startTime.tv_usec / USEC_PER_SEC; + NSTimeInterval processStartTime = startTime.tv_sec + (1.0 * startTime.tv_usec) / USEC_PER_SEC; // Convert to time since 1 Jan 2001 to align with CFAbsoluteTimeGetCurrent() *timeInterval = processStartTime - kCFAbsoluteTimeIntervalSince1970; return res; From ff3a7ced0c06dbd76f2fde01b315dccbe819f88c Mon Sep 17 00:00:00 2001 From: Pedro Lousada Date: Thu, 9 May 2024 16:25:45 +0100 Subject: [PATCH 098/153] [RUM-2911] Add time since app start to RUM errors --- .../CrashContext/CrashContextTests.swift | 13 +++++++ .../Mocks/CrashReportingFeatureMocks.swift | 9 +++-- .../Sources/CrashContext/CrashContext.swift | 12 +++++- .../Sources/CrashReportingFeature.swift | 16 ++++++++ DatadogRUM/Sources/FatalErrorBuilder.swift | 10 ++++- .../AppHangs/FatalAppHangsHandler.swift | 7 +++- .../Integrations/CrashReportReceiver.swift | 29 ++++++++++----- .../RUMMonitor/Scopes/RUMResourceScope.swift | 6 ++- .../RUMMonitor/Scopes/RUMViewScope.swift | 6 ++- .../Scopes/RUMResourceScopeTests.swift | 37 +++++++++++++++++++ 10 files changed, 127 insertions(+), 18 deletions(-) diff --git a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift index 03d157fa49..e1f0027fd3 100644 --- a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift +++ b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift @@ -126,4 +126,17 @@ class CrashContextTests: XCTestCase { let deserializedContext = try decoder.decode(CrashContext.self, from: serializedContext) XCTAssertEqual(deserializedContext.lastIsAppInForeground, randomIsAppInForeground) } + + func testGivenContextWithAppLaunchDate_whenItGetsEncoded_thenTheValueIsPreservedAfterDecoding() throws { + let randomDate: Date = .mockRandom() + // Given + let context: CrashContext = .mockWith(appLaunchDate: randomDate) + + // When + let serializedContext = try encoder.encode(context) + + // Then + let deserializedContext = try decoder.decode(CrashContext.self, from: serializedContext) + XCTAssertEqual(deserializedContext.appLaunchDate, randomDate) + } } diff --git a/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift index 84cd7fab9a..87bee83467 100644 --- a/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift @@ -144,7 +144,8 @@ extension CrashContext { lastRUMViewEvent: AnyCodable? = nil, lastRUMSessionState: AnyCodable? = nil, lastIsAppInForeground: Bool = .mockAny(), - lastLogAttributes: AnyCodable? = nil + lastLogAttributes: AnyCodable? = nil, + appLaunchDate: Date? = .mockRandomInThePast() ) -> Self { .init( serverTimeOffset: serverTimeOffset, @@ -162,7 +163,8 @@ extension CrashContext { lastRUMViewEvent: lastRUMViewEvent, lastRUMSessionState: lastRUMSessionState, lastIsAppInForeground: lastIsAppInForeground, - lastLogAttributes: lastLogAttributes + lastLogAttributes: lastLogAttributes, + appLaunchDate: appLaunchDate ) } @@ -183,7 +185,8 @@ extension CrashContext { lastRUMViewEvent: AnyCodable(mockRandomAttributes()), lastRUMSessionState: AnyCodable(mockRandomAttributes()), lastIsAppInForeground: .mockRandom(), - lastLogAttributes: AnyCodable(mockRandomAttributes()) + lastLogAttributes: AnyCodable(mockRandomAttributes()), + appLaunchDate: .mockRandomInThePast() ) } diff --git a/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift b/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift index 09b87afc7e..5500df09b3 100644 --- a/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift +++ b/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift @@ -13,6 +13,9 @@ import DatadogInternal /// Note: as it gets saved along with the crash report during process interruption, it's good /// to keep this data well-packed and as small as possible. internal struct CrashContext: Codable, Equatable { + /// The Application Launch Date + var appLaunchDate: Date? + /// Interval between device and server time. /// /// The value can change as the device continue to sync with the server. @@ -90,7 +93,8 @@ internal struct CrashContext: Codable, Equatable { lastRUMViewEvent: AnyCodable?, lastRUMSessionState: AnyCodable?, lastIsAppInForeground: Bool, - lastLogAttributes: AnyCodable? + lastLogAttributes: AnyCodable?, + appLaunchDate: Date? ) { self.serverTimeOffset = serverTimeOffset self.service = service @@ -108,6 +112,7 @@ internal struct CrashContext: Codable, Equatable { self.lastRUMSessionState = lastRUMSessionState self.lastIsAppInForeground = lastIsAppInForeground self.lastLogAttributes = lastLogAttributes + self.appLaunchDate = appLaunchDate } init( @@ -133,6 +138,8 @@ internal struct CrashContext: Codable, Equatable { self.lastRUMViewEvent = lastRUMViewEvent self.lastRUMSessionState = lastRUMSessionState self.lastLogAttributes = lastLogAttributes + + self.appLaunchDate = context.launchTime?.launchDate } static func == (lhs: CrashContext, rhs: CrashContext) -> Bool { @@ -148,6 +155,7 @@ internal struct CrashContext: Codable, Equatable { lhs.lastIsAppInForeground == rhs.lastIsAppInForeground && lhs.userInfo?.id == rhs.userInfo?.id && lhs.userInfo?.name == rhs.userInfo?.name && - lhs.userInfo?.email == rhs.userInfo?.email + lhs.userInfo?.email == rhs.userInfo?.email && + lhs.appLaunchDate == rhs.appLaunchDate } } diff --git a/DatadogCrashReporting/Sources/CrashReportingFeature.swift b/DatadogCrashReporting/Sources/CrashReportingFeature.swift index 8d34e42b33..bc23767d06 100644 --- a/DatadogCrashReporting/Sources/CrashReportingFeature.swift +++ b/DatadogCrashReporting/Sources/CrashReportingFeature.swift @@ -113,6 +113,22 @@ internal final class CrashReportingFeature: DatadogFeature { private func decode(crashContextData: Data) -> CrashContext? { do { + crashContextDecoder.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + let dateFormatter = ISO8601DateFormatter() + dateFormatter.formatOptions = [ + .withFullDate, + .withFullTime, + .withDashSeparatorInDate, + .withFractionalSeconds + ] + + guard let date = dateFormatter.date(from: dateString) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date format: Requires ISO8601.") + } + return date + } return try crashContextDecoder.decode(CrashContext.self, from: crashContextData) } catch { DD.logger.error( diff --git a/DatadogRUM/Sources/FatalErrorBuilder.swift b/DatadogRUM/Sources/FatalErrorBuilder.swift index 476b8ca2d0..3800f8ecf2 100644 --- a/DatadogRUM/Sources/FatalErrorBuilder.swift +++ b/DatadogRUM/Sources/FatalErrorBuilder.swift @@ -40,9 +40,17 @@ internal struct FatalErrorBuilder { let errorBinaryImages: [RUMErrorEvent.Error.BinaryImages]? let errorWasTruncated: Bool? let errorMeta: RUMErrorEvent.Error.Meta? + var timeSinceAppStart: TimeInterval? /// Creates RUM error linked to given view. func createRUMError(with lastRUMView: RUMViewEvent) -> RUMErrorEvent { + var timeSinceAppStart: TimeInterval? = nil + if let appStartTiming = timeSinceAppStart { + //Bind timing to 0..max int64 + let timeInterval = min(max(0, appStartTiming), Double(Int64.max)) + timeSinceAppStart = timeInterval > 0 ? timeInterval : nil + } + let event = RUMErrorEvent( dd: .init( browserSdkVersion: nil, @@ -93,7 +101,7 @@ internal struct FatalErrorBuilder { sourceType: context.nativeSourceOverride.map { RUMErrorSourceType(rawValue: $0) } ?? .ios, stack: errorStack, threads: errorThreads, - timeSinceAppStart: nil, + timeSinceAppStart: timeSinceAppStart.map { Int64($0) }, type: errorType, wasTruncated: errorWasTruncated ), diff --git a/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift b/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift index ac5df9068c..3afb06ae46 100644 --- a/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift +++ b/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift @@ -92,6 +92,10 @@ internal final class FatalAppHangsHandler { let realErrorDate = fatalHang.hang.startDate.addingTimeInterval(fatalHang.serverTimeOffset) let realDateNow = dateProvider.now.addingTimeInterval(context.serverTimeOffset) + var timeSinceAppStart: TimeInterval? = nil + if let startTime = context.launchTime?.launchDate { + timeSinceAppStart = realErrorDate.timeIntervalSince(startTime) * 1_000 + } let builder = FatalErrorBuilder( context: context, @@ -103,7 +107,8 @@ internal final class FatalAppHangsHandler { errorThreads: fatalHang.hang.backtraceResult.threads?.toRUMDataFormat, errorBinaryImages: fatalHang.hang.backtraceResult.binaryImages?.toRUMDataFormat, errorWasTruncated: fatalHang.hang.backtraceResult.wasTruncated, - errorMeta: nil + errorMeta: nil, + timeSinceAppStart: timeSinceAppStart ) let error = builder.createRUMError(with: fatalHang.lastRUMView) let view = builder.updateRUMViewWithError(fatalHang.lastRUMView) diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index 80d0e1fc07..7421bd2cf4 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -23,6 +23,8 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { } struct CrashContext: Decodable { + /// The Application Launch time since epich + let appLaunchDate: Date? /// Interval between device and server time. let serverTimeOffset: TimeInterval /// The name of the service that data is generated from. @@ -66,6 +68,8 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { let realCrashDate: Date /// Current time, adjusted with NTP correction. let realDateNow: Date + /// Time between crash and application launch + let timeSinceAppStart: TimeInterval? } /// RUM feature scope. @@ -127,10 +131,16 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { let currentTimeCorrection = context.serverTimeOffset let crashDate = report.date ?? dateProvider.now + var timeSinceAppStart: TimeInterval? = nil + if let startDate = context.appLaunchDate { + timeSinceAppStart = crashDate.timeIntervalSince(startDate) * 1_000 + } + let adjustedCrashTimings = AdjustedCrashTimings( crashDate: crashDate, realCrashDate: crashDate.addingTimeInterval(currentTimeCorrection), - realDateNow: dateProvider.now.addingTimeInterval(currentTimeCorrection) + realDateNow: dateProvider.now.addingTimeInterval(currentTimeCorrection), + timeSinceAppStart: timeSinceAppStart ) // RUMM-2516 if a cross-platform crash was reported, do not send its native version @@ -165,13 +175,13 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { using crashTimings: AdjustedCrashTimings ) { if crashTimings.realDateNow.timeIntervalSince(crashTimings.realCrashDate) < FatalErrorBuilder.Constants.viewEventAvailabilityThreshold { - send(crashReport: crashReport, to: lastRUMViewEvent, using: crashTimings.realCrashDate) + send(crashReport: crashReport, to: lastRUMViewEvent, using: crashTimings) } else { // We know it is too late for sending RUM view to previous RUM session as it is now stale on backend. // To avoid inconsistency, we only send the RUM error. DD.logger.debug("Sending crash as RUM error.") featureScope.eventWriteContext(bypassConsent: true) { context, writer in - let builder = createFatalErrorBuilder(context: context, crash: crashReport, crashDate: crashTimings.realCrashDate) + let builder = createFatalErrorBuilder(context: context, crash: crashReport, crashDate: crashTimings.realCrashDate, timeSinceAppStart: crashTimings.timeSinceAppStart) let rumError = builder.createRUMError(with: lastRUMViewEvent) if let mappedError = self.eventsMapper.map(event: rumError) { @@ -231,7 +241,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { } if let newRUMView = newRUMView { - send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate) + send(crashReport: crashReport, to: newRUMView, using: crashTimings) } } @@ -284,18 +294,18 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { } if let newRUMView = newRUMView { - send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate) + send(crashReport: crashReport, to: newRUMView, using: crashTimings) } } /// Sends given `CrashReport` by linking it to given `rumView` and updating view counts accordingly. - private func send(crashReport: DDCrashReport, to rumView: RUMViewEvent, using realCrashDate: Date) { + private func send(crashReport: DDCrashReport, to rumView: RUMViewEvent, using crashTimings: AdjustedCrashTimings) { DD.logger.debug("Updating RUM view with crash report.") // crash reporting is considering the user consent from previous session, if an event reached // the message bus it means that consent was granted and we can safely bypass current consent. featureScope.eventWriteContext(bypassConsent: true) { context, writer in - let builder = createFatalErrorBuilder(context: context, crash: crashReport, crashDate: realCrashDate) + let builder = createFatalErrorBuilder(context: context, crash: crashReport, crashDate: crashTimings.realCrashDate, timeSinceAppStart: crashTimings.timeSinceAppStart) let updatedRUMView = builder.updateRUMViewWithError(rumView) let rumError = builder.createRUMError(with: updatedRUMView) @@ -313,7 +323,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { // MARK: - Building RUM events - private func createFatalErrorBuilder(context: DatadogContext, crash: DDCrashReport, crashDate: Date) -> FatalErrorBuilder { + private func createFatalErrorBuilder(context: DatadogContext, crash: DDCrashReport, crashDate: Date, timeSinceAppStart: TimeInterval?) -> FatalErrorBuilder { return FatalErrorBuilder( context: context, error: .crash, @@ -324,7 +334,8 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { errorThreads: crash.threads.toRUMDataFormat, errorBinaryImages: crash.binaryImages.toRUMDataFormat, errorWasTruncated: crash.wasTruncated, - errorMeta: crash.meta.toRUMDataFormat + errorMeta: crash.meta.toRUMDataFormat, + timeSinceAppStart: timeSinceAppStart ) } diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift index 59ef18d853..1b7886e0e6 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift @@ -270,6 +270,10 @@ internal class RUMResourceScope: RUMScope { attributes.merge(rumCommandAttributes: command.attributes) let errorFingerprint = attributes.removeValue(forKey: RUM.Attributes.errorFingerprint) as? String + var timeSinceAppStart: Int64? = nil + if let startTime = context.launchTime?.launchDate { + timeSinceAppStart = Int64(command.time.timeIntervalSince(startTime) * 1_000) + } let errorEvent = RUMErrorEvent( dd: .init( @@ -314,7 +318,7 @@ internal class RUMResourceScope: RUMScope { sourceType: command.errorSourceType, stack: command.stack, threads: nil, - timeSinceAppStart: nil, + timeSinceAppStart: timeSinceAppStart, type: command.errorType, wasTruncated: nil ), diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index 3b0f5266df..a769db9464 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -557,6 +557,10 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { var commandAttributes = command.attributes let errorFingerprint = commandAttributes.removeValue(forKey: RUM.Attributes.errorFingerprint) as? String + var timeSinceAppStart: Int64? = nil + if let startTime = context.launchTime?.launchDate { + timeSinceAppStart = Int64(command.time.timeIntervalSince(startTime) * 1_000) + } var binaryImages = command.binaryImages?.compactMap { $0.toRUMDataFormat } if commandAttributes.removeValue(forKey: CrossPlatformAttributes.includeBinaryImages) != nil { @@ -606,7 +610,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { sourceType: command.errorSourceType, stack: command.stack, threads: command.threads?.compactMap { $0.toRUMDataFormat }, - timeSinceAppStart: nil, + timeSinceAppStart: timeSinceAppStart, type: command.type, wasTruncated: command.isStackTraceTruncated ), diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift index 055574d884..7ca6c3e04f 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift @@ -1174,6 +1174,43 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.error.sourceType, .reactNative) } + func testGivenStartedResource_whenResourceLoadingEndsWithError_itSendsErrorEventWithTimeSinceAppStart() throws { + let currentTime: Date = .mockDecember15th2019At10AMUTC() + + // Given + let appLauchTimeDiff = Int64.random(in: 10..<1_000_000) + let customContext: DatadogContext = .mockWith(launchTime: .mockWith( + launchTime: .mockAny(), + launchDate: currentTime, + isActivePrewarm: .mockAny() + ) ) + + let scope = RUMResourceScope.mockWith( + context: rumContext, + dependencies: dependencies, + resourceKey: "/resource/1", + startTime: currentTime, + url: "https://foo.com/resource/1", + httpMethod: .post + ) + + // When + XCTAssertFalse( + scope.process( + command: RUMStopResourceWithErrorCommand.mockWithErrorMessage( + resourceKey: "/resource/1", + time: currentTime.addingTimeInterval(Double(appLauchTimeDiff)) + ), + context: customContext, + writer: writer + ) + ) + + // Then + let event = try XCTUnwrap(writer.events(ofType: RUMErrorEvent.self).first) + XCTAssertEqual(event.error.timeSinceAppStart, appLauchTimeDiff * 1_000) + } + // MARK: - Events sending callbacks func testGivenResourceScopeWithDefaultEventsMapper_whenSendingEvents_thenEventSentCallbacksAreCalled() throws { From bc3610a7fa32ccfe21874a0bc62673c61e2c26ca Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 10 May 2024 11:03:34 +0200 Subject: [PATCH 099/153] RUM-2911 CR feedback - test `CrashContext` coding against the actual encoder and decoder + simplify ISO8601 date coding --- .../CrashContext/CrashContextTests.swift | 13 +++++-- .../Sources/CrashReportingFeature.swift | 36 +++++++++---------- .../Sources/Utils/DateFormatting.swift | 1 + 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift index e1f0027fd3..2809429e98 100644 --- a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift +++ b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift @@ -12,8 +12,10 @@ import TestUtilities @testable import DatadogCrashReporting class CrashContextTests: XCTestCase { - private let encoder = JSONEncoder() - private let decoder = JSONDecoder() + /// This must be the exact encoder used to encode `CrashContext` in production code. + private let encoder = CrashReportingFeature.crashContextEncoder + /// This must be the exact decoder used to decode `CrashContext` in production code. + private let decoder = CrashReportingFeature.crashContextDecoder func testGivenContextWithTrackingConsentSet_whenItGetsEncoded_thenTheValueIsPreservedAfterDecoding() throws { let randomConsent: TrackingConsent = .mockRandom() @@ -129,6 +131,7 @@ class CrashContextTests: XCTestCase { func testGivenContextWithAppLaunchDate_whenItGetsEncoded_thenTheValueIsPreservedAfterDecoding() throws { let randomDate: Date = .mockRandom() + // Given let context: CrashContext = .mockWith(appLaunchDate: randomDate) @@ -137,6 +140,10 @@ class CrashContextTests: XCTestCase { // Then let deserializedContext = try decoder.decode(CrashContext.self, from: serializedContext) - XCTAssertEqual(deserializedContext.appLaunchDate, randomDate) + XCTAssertEqual( + deserializedContext.appLaunchDate!.timeIntervalSince1970, + randomDate.timeIntervalSince1970, + accuracy: 0.001 // assert with ms precision as we encode dates as ISO8601 string which is lossfull + ) } } diff --git a/DatadogCrashReporting/Sources/CrashReportingFeature.swift b/DatadogCrashReporting/Sources/CrashReportingFeature.swift index bc23767d06..fe99801329 100644 --- a/DatadogCrashReporting/Sources/CrashReportingFeature.swift +++ b/DatadogCrashReporting/Sources/CrashReportingFeature.swift @@ -90,13 +90,25 @@ internal final class CrashReportingFeature: DatadogFeature { /// Note: this `JSONEncoder` must have the same configuration as the `JSONEncoder` used later for writing payloads to uploadable files. /// Otherwise the format of data read and uploaded from crash report context will be different than the format of data retrieved from the user /// and written directly to uploadable file. - private let crashContextEncoder: JSONEncoder = .dd.default() + internal static let crashContextEncoder: JSONEncoder = .dd.default() /// JSON decoder used for reading `CrashContext` from JSON `Data` injected to crash report. - private let crashContextDecoder = JSONDecoder() + /// Note: it must follow a configuration that enables reading data encoded with `crashContextEncoder`. + internal static let crashContextDecoder: JSONDecoder = { + var decoder = JSONDecoder() + decoder.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + guard let date = iso8601DateFormatter.date(from: dateString) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date format: Requires ISO8601.") + } + return date + } + return decoder + }() private func encode(crashContext: CrashContext) -> Data? { do { - return try crashContextEncoder.encode(crashContext) + return try CrashReportingFeature.crashContextEncoder.encode(crashContext) } catch { DD.logger.error( """ @@ -113,23 +125,7 @@ internal final class CrashReportingFeature: DatadogFeature { private func decode(crashContextData: Data) -> CrashContext? { do { - crashContextDecoder.dateDecodingStrategy = .custom { decoder in - let container = try decoder.singleValueContainer() - let dateString = try container.decode(String.self) - let dateFormatter = ISO8601DateFormatter() - dateFormatter.formatOptions = [ - .withFullDate, - .withFullTime, - .withDashSeparatorInDate, - .withFractionalSeconds - ] - - guard let date = dateFormatter.date(from: dateString) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date format: Requires ISO8601.") - } - return date - } - return try crashContextDecoder.decode(CrashContext.self, from: crashContextData) + return try CrashReportingFeature.crashContextDecoder.decode(CrashContext.self, from: crashContextData) } catch { DD.logger.error( """ diff --git a/DatadogInternal/Sources/Utils/DateFormatting.swift b/DatadogInternal/Sources/Utils/DateFormatting.swift index 44b33a08cb..7dc4f94cf5 100644 --- a/DatadogInternal/Sources/Utils/DateFormatting.swift +++ b/DatadogInternal/Sources/Utils/DateFormatting.swift @@ -8,6 +8,7 @@ import Foundation public protocol DateFormatterType { func string(from date: Date) -> String + func date(from string: String) -> Date? } extension ISO8601DateFormatter: DateFormatterType {} From 306ae6963d2defe5db34bc064dc12c7bfa2855ea Mon Sep 17 00:00:00 2001 From: Pedro Lousada Date: Fri, 10 May 2024 13:58:24 +0100 Subject: [PATCH 100/153] RUM-2911 Address PR feedback --- DatadogCore/Private/ObjcAppLaunchHandler.m | 1 + DatadogRUM/Sources/FatalErrorBuilder.swift | 8 ++-- .../Integrations/CrashReportReceiver.swift | 4 +- .../RUMMonitor/Scopes/RUMResourceScope.swift | 2 +- .../RUMMonitor/Scopes/RUMViewScope.swift | 2 +- .../Scopes/RUMResourceScopeTests.swift | 6 +-- .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 44 +++++++++++++++++++ 7 files changed, 56 insertions(+), 11 deletions(-) diff --git a/DatadogCore/Private/ObjcAppLaunchHandler.m b/DatadogCore/Private/ObjcAppLaunchHandler.m index 23868d0b05..3239d61391 100644 --- a/DatadogCore/Private/ObjcAppLaunchHandler.m +++ b/DatadogCore/Private/ObjcAppLaunchHandler.m @@ -127,6 +127,7 @@ int processStartTimeIntervalSinceReferenceDate(NSTimeInterval *timeInterval) { // The process' start time is provided relative to 1 Jan 1970 struct timeval startTime = kip.kp_proc.p_starttime; + // Multiplication with 1.0 ensure we don't round to 0 with integer division NSTimeInterval processStartTime = startTime.tv_sec + (1.0 * startTime.tv_usec) / USEC_PER_SEC; // Convert to time since 1 Jan 2001 to align with CFAbsoluteTimeGetCurrent() *timeInterval = processStartTime - kCFAbsoluteTimeIntervalSince1970; diff --git a/DatadogRUM/Sources/FatalErrorBuilder.swift b/DatadogRUM/Sources/FatalErrorBuilder.swift index 3800f8ecf2..9ab5327eb4 100644 --- a/DatadogRUM/Sources/FatalErrorBuilder.swift +++ b/DatadogRUM/Sources/FatalErrorBuilder.swift @@ -44,11 +44,11 @@ internal struct FatalErrorBuilder { /// Creates RUM error linked to given view. func createRUMError(with lastRUMView: RUMViewEvent) -> RUMErrorEvent { - var timeSinceAppStart: TimeInterval? = nil + var msSinceAppStart: Int64? = nil if let appStartTiming = timeSinceAppStart { //Bind timing to 0..max int64 - let timeInterval = min(max(0, appStartTiming), Double(Int64.max)) - timeSinceAppStart = timeInterval > 0 ? timeInterval : nil + let timeInterval = max(0, appStartTiming.toInt64Milliseconds) + msSinceAppStart = timeInterval > 0 ? timeInterval : nil } let event = RUMErrorEvent( @@ -101,7 +101,7 @@ internal struct FatalErrorBuilder { sourceType: context.nativeSourceOverride.map { RUMErrorSourceType(rawValue: $0) } ?? .ios, stack: errorStack, threads: errorThreads, - timeSinceAppStart: timeSinceAppStart.map { Int64($0) }, + timeSinceAppStart: msSinceAppStart, type: errorType, wasTruncated: errorWasTruncated ), diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index 7421bd2cf4..00f7c2e241 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -23,7 +23,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { } struct CrashContext: Decodable { - /// The Application Launch time since epich + /// The Application launch date let appLaunchDate: Date? /// Interval between device and server time. let serverTimeOffset: TimeInterval @@ -133,7 +133,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { let crashDate = report.date ?? dateProvider.now var timeSinceAppStart: TimeInterval? = nil if let startDate = context.appLaunchDate { - timeSinceAppStart = crashDate.timeIntervalSince(startDate) * 1_000 + timeSinceAppStart = crashDate.timeIntervalSince(startDate) } let adjustedCrashTimings = AdjustedCrashTimings( diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift index 1b7886e0e6..41cd687c0d 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift @@ -272,7 +272,7 @@ internal class RUMResourceScope: RUMScope { let errorFingerprint = attributes.removeValue(forKey: RUM.Attributes.errorFingerprint) as? String var timeSinceAppStart: Int64? = nil if let startTime = context.launchTime?.launchDate { - timeSinceAppStart = Int64(command.time.timeIntervalSince(startTime) * 1_000) + timeSinceAppStart = command.time.timeIntervalSince(startTime).toInt64Milliseconds } let errorEvent = RUMErrorEvent( diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index a769db9464..f1b2c11268 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -559,7 +559,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { let errorFingerprint = commandAttributes.removeValue(forKey: RUM.Attributes.errorFingerprint) as? String var timeSinceAppStart: Int64? = nil if let startTime = context.launchTime?.launchDate { - timeSinceAppStart = Int64(command.time.timeIntervalSince(startTime) * 1_000) + timeSinceAppStart = command.time.timeIntervalSince(startTime).toInt64Milliseconds } var binaryImages = command.binaryImages?.compactMap { $0.toRUMDataFormat } diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift index 7ca6c3e04f..5adb8dc624 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMResourceScopeTests.swift @@ -1178,7 +1178,7 @@ class RUMResourceScopeTests: XCTestCase { let currentTime: Date = .mockDecember15th2019At10AMUTC() // Given - let appLauchTimeDiff = Int64.random(in: 10..<1_000_000) + let appLauchToErrorTimeDiff = Int64.random(in: 10..<1_000_000) let customContext: DatadogContext = .mockWith(launchTime: .mockWith( launchTime: .mockAny(), launchDate: currentTime, @@ -1199,7 +1199,7 @@ class RUMResourceScopeTests: XCTestCase { scope.process( command: RUMStopResourceWithErrorCommand.mockWithErrorMessage( resourceKey: "/resource/1", - time: currentTime.addingTimeInterval(Double(appLauchTimeDiff)) + time: currentTime.addingTimeInterval(Double(appLauchToErrorTimeDiff)) ), context: customContext, writer: writer @@ -1208,7 +1208,7 @@ class RUMResourceScopeTests: XCTestCase { // Then let event = try XCTUnwrap(writer.events(ofType: RUMErrorEvent.self).first) - XCTAssertEqual(event.error.timeSinceAppStart, appLauchTimeDiff * 1_000) + XCTAssertEqual(event.error.timeSinceAppStart, appLauchToErrorTimeDiff * 1_000) } // MARK: - Events sending callbacks diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift index a55bd179bd..d61d3255fd 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -1668,6 +1668,50 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(viewUpdate.view.error.count, 1, "Failed Resource should be counted as Error") } + func testWhenViewErrorIsAdded_itSendsErrorWithCorrectTimeSinceAppStart() throws { + var context = self.context + var currentTime: Date = .mockDecember15th2019At10AMUTC() + let appLauchToErrorTimeDiff = Int64.random(in: 10..<1_000_000) + + context.launchTime = .mockWith( + launchTime: .mockAny(), + launchDate: currentTime, + isActivePrewarm: .mockAny() + ) + + let scope = RUMViewScope( + isInitialView: .mockRandom(), + parent: parent, + dependencies: .mockAny(), + identity: .mockViewIdentifier(), + path: "UIViewController", + name: "ViewName", + attributes: [:], + customTimings: [:], + startTime: currentTime, + serverTimeOffset: .zero + ) + + XCTAssertTrue( + scope.process( + command: RUMStartViewCommand.mockWith(time: currentTime, attributes: ["foo": "bar"], identity: .mockViewIdentifier()), + context: context, + writer: writer + ) + ) + + XCTAssertTrue( + scope.process( + command: RUMAddCurrentViewErrorCommand.mockWithErrorMessage(time: currentTime.addingTimeInterval(Double(appLauchToErrorTimeDiff)), message: "view error", source: .source, stack: nil), + context: context, + writer: writer + ) + ) + + let error = try XCTUnwrap(writer.events(ofType: RUMErrorEvent.self).last) + XCTAssertEqual(error.error.timeSinceAppStart, appLauchToErrorTimeDiff * 1_000) + } + // MARK: - App Hangs func testWhenViewAppHangIsTracked_itSendsErrorEventAndViewUpdateEvent() throws { From 33fc0fea2915652fc7a1eddf93231b6f97666ad3 Mon Sep 17 00:00:00 2001 From: Pedro Lousada Date: Wed, 15 May 2024 13:23:26 +0100 Subject: [PATCH 101/153] RUM-2911 Improve FatalErrorBuilder time calculation's readability --- DatadogRUM/Sources/FatalErrorBuilder.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/DatadogRUM/Sources/FatalErrorBuilder.swift b/DatadogRUM/Sources/FatalErrorBuilder.swift index 9ab5327eb4..98911fa78c 100644 --- a/DatadogRUM/Sources/FatalErrorBuilder.swift +++ b/DatadogRUM/Sources/FatalErrorBuilder.swift @@ -44,12 +44,7 @@ internal struct FatalErrorBuilder { /// Creates RUM error linked to given view. func createRUMError(with lastRUMView: RUMViewEvent) -> RUMErrorEvent { - var msSinceAppStart: Int64? = nil - if let appStartTiming = timeSinceAppStart { - //Bind timing to 0..max int64 - let timeInterval = max(0, appStartTiming.toInt64Milliseconds) - msSinceAppStart = timeInterval > 0 ? timeInterval : nil - } + let msSinceAppStart = timeSinceAppStart.map { max(0, $0.toInt64Milliseconds) } let event = RUMErrorEvent( dd: .init( From 47f079d405a048970c25104c9326f129579a97fc Mon Sep 17 00:00:00 2001 From: Pedro Lousada Date: Wed, 15 May 2024 13:23:49 +0100 Subject: [PATCH 102/153] RUM-2911 Use correct startTime for fatal App Hangs --- DatadogRUM/Sources/Instrumentation/AppHangs/AppHang.swift | 2 ++ .../Instrumentation/AppHangs/FatalAppHangsHandler.swift | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/DatadogRUM/Sources/Instrumentation/AppHangs/AppHang.swift b/DatadogRUM/Sources/Instrumentation/AppHangs/AppHang.swift index 4002c1a9a7..d9a69075ee 100644 --- a/DatadogRUM/Sources/Instrumentation/AppHangs/AppHang.swift +++ b/DatadogRUM/Sources/Instrumentation/AppHangs/AppHang.swift @@ -42,4 +42,6 @@ internal struct FatalAppHang: Codable { let lastRUMView: RUMViewEvent /// The user's consent at the moment of hang's recording. let trackingConsent: TrackingConsent + /// The App Launch date at the moment of hang's recording. + let appLaunchDate: Date? } diff --git a/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift b/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift index 3afb06ae46..54ce41d884 100644 --- a/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift +++ b/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift @@ -41,7 +41,8 @@ internal final class FatalAppHangsHandler { hang: hang, serverTimeOffset: context.serverTimeOffset, lastRUMView: lastRUMView, - trackingConsent: context.trackingConsent + trackingConsent: context.trackingConsent, + appLaunchDate: context.launchTime?.launchDate ) dataStore.setValue(fatalHang, forKey: .fatalAppHangKey) } @@ -92,11 +93,13 @@ internal final class FatalAppHangsHandler { let realErrorDate = fatalHang.hang.startDate.addingTimeInterval(fatalHang.serverTimeOffset) let realDateNow = dateProvider.now.addingTimeInterval(context.serverTimeOffset) + var timeSinceAppStart: TimeInterval? = nil - if let startTime = context.launchTime?.launchDate { + if let startTime = fatalHang.appLaunchDate { timeSinceAppStart = realErrorDate.timeIntervalSince(startTime) * 1_000 } + let builder = FatalErrorBuilder( context: context, error: .hang, From c229bf78691f2000cb7144363b61b4797af337b3 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 16 May 2024 11:52:00 +0200 Subject: [PATCH 103/153] RUM-2911 Fix unit conversion and add test for Fatal App Hang's `timeSinceAppStart` --- .../AppHangs/FatalAppHangsHandler.swift | 7 +--- .../AppHangs/AppHangsMonitorTests.swift | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift b/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift index 54ce41d884..d8a2a46323 100644 --- a/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift +++ b/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift @@ -93,12 +93,7 @@ internal final class FatalAppHangsHandler { let realErrorDate = fatalHang.hang.startDate.addingTimeInterval(fatalHang.serverTimeOffset) let realDateNow = dateProvider.now.addingTimeInterval(context.serverTimeOffset) - - var timeSinceAppStart: TimeInterval? = nil - if let startTime = fatalHang.appLaunchDate { - timeSinceAppStart = realErrorDate.timeIntervalSince(startTime) * 1_000 - } - + let timeSinceAppStart = fatalHang.appLaunchDate.map { realErrorDate.timeIntervalSince($0) } let builder = FatalErrorBuilder( context: context, diff --git a/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsMonitorTests.swift b/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsMonitorTests.swift index 0aabdba81c..9cac5af72d 100644 --- a/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsMonitorTests.swift +++ b/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsMonitorTests.swift @@ -414,4 +414,38 @@ class AppHangsMonitorTests: XCTestCase { DDAssertJSONEqual(errorEvent.error.binaryImages, hangBacktrace.binaryImages.toRUMDataFormat) XCTAssertEqual(errorEvent.error.wasTruncated, hangBacktrace.wasTruncated) } + + func testWhenSendingRUMErrorEvent_itIncludesTimeSinceAppLaunch() throws { + let appLaunchDate: Date = .mockDecember15th2019At10AMUTC() + let hangTimeSinceAppStart: TimeInterval = .mockRandom(min: 1, max: 10) + let hang: AppHang = .mockWith(startDate: appLaunchDate.addingTimeInterval(hangTimeSinceAppStart)) + + // Given (track hang in previous app session) + featureScope.contextMock.trackingConsent = .granted + featureScope.contextMock.launchTime = .mockWith(launchDate: appLaunchDate) + featureScope.contextMock.serverTimeOffset = 0 + monitor.start() + fatalErrorContext.view = .mockRandom() + watchdogThread.delegate?.hangStarted(hang) + monitor.stop() + + // When (app is restarted) + let appRestartDate = appLaunchDate.addingTimeInterval(.mockRandom(min: 10, max: 100)) + featureScope.contextMock.launchTime = .mockWith(launchDate: appRestartDate) + featureScope.contextMock.serverTimeOffset = .mockRandom(min: 0, max: 100) + let monitor = AppHangsMonitor( + featureScope: featureScope, + watchdogThread: watchdogThread, + fatalErrorContext: fatalErrorContext, + processID: UUID(), // different process + dateProvider: DateProviderMock(now: appRestartDate) + ) + monitor.start() + defer { monitor.stop() } + + // Then + let errorEvent = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMErrorEvent.self).first) + XCTAssertEqual(errorEvent.error.category, .appHang) + XCTAssertEqual(errorEvent.error.timeSinceAppStart, hangTimeSinceAppStart.toInt64Milliseconds) + } } From 1c252a027ab961a0fc2c79f9198303f519645a22 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 17 May 2024 11:33:44 +0200 Subject: [PATCH 104/153] RUM-3183 Remove usage of unsafe `request.allHTTPHeaderFields` As observed in https://github.com/DataDog/dd-sdk-ios/issues/1638, accessing `request.allHTTPHeaderFields` is not safe and leads to crashes with undefined root cause. To avoid this, instead we use `request.value(forHTTPHeaderField:)` to only read headers known by the SDK. --- CHANGELOG.md | 2 + Datadog/Datadog.xcodeproj/project.pbxproj | 12 +++++ .../NetworkInstrumentationFeature.swift | 2 +- .../URLSession/ImmutableRequest.swift | 44 ++++++++++++++++++ .../URLSessionTaskInterception.swift | 24 ---------- .../ImmutableRequestTests.swift | 45 +++++++++++++++++++ 6 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 DatadogInternal/Sources/NetworkInstrumentation/URLSession/ImmutableRequest.swift create mode 100644 DatadogInternal/Tests/NetworkInstrumentation/ImmutableRequestTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index bfafd67a66..02819d4575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - [FEATURE] `DatadogTrace` now supports OpenTelemetry. See [#1828][] +- [FIX] Fix crash on accessing request.allHTTPHeaderFields. See [#1843][] # 2.11.0 / 08-05-2024 @@ -657,6 +658,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1774]: https://github.com/DataDog/dd-sdk-ios/pull/1774 [#1763]: https://github.com/DataDog/dd-sdk-ios/pull/1763 [#1767]: https://github.com/DataDog/dd-sdk-ios/pull/1767 +[#1843]: https://github.com/DataDog/dd-sdk-ios/pull/1843 [#1798]: https://github.com/DataDog/dd-sdk-ios/pull/1798 [#1776]: https://github.com/DataDog/dd-sdk-ios/pull/1776 [#1721]: https://github.com/DataDog/dd-sdk-ios/pull/1721 diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 01b4722d3d..67222b829b 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -338,6 +338,8 @@ 614798A22A45A48F0095CB02 /* DatadogTrace.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2C1A55A29C4F2DF00946C31 /* DatadogTrace.framework */; }; 614798A32A45A4980095CB02 /* DatadogTrace.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; }; 6147E3B3270486920092BC9F /* TraceConfigurationE2ETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6147E3B2270486920092BC9F /* TraceConfigurationE2ETests.swift */; }; + 614A708E2BF754D800D9AF42 /* ImmutableRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614A708D2BF754D700D9AF42 /* ImmutableRequest.swift */; }; + 614A708F2BF754D800D9AF42 /* ImmutableRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614A708D2BF754D700D9AF42 /* ImmutableRequest.swift */; }; 614B78ED296D7B63009C6B92 /* DatadogCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */; }; 614B78EE296D7B63009C6B92 /* DatadogCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */; }; 614B78F1296D7B63009C6B92 /* LowPowerModePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614B78EC296D7B63009C6B92 /* LowPowerModePublisherTests.swift */; }; @@ -349,6 +351,8 @@ 615192CE2BD6948B0005A782 /* HTTPHeadersWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615192CC2BD6948B0005A782 /* HTTPHeadersWriterTests.swift */; }; 615192D02BD6B7C90005A782 /* DatadogTracer+InjectAndExtract.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615192CF2BD6B7C90005A782 /* DatadogTracer+InjectAndExtract.swift */; }; 615192D12BD6B7C90005A782 /* DatadogTracer+InjectAndExtract.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615192CF2BD6B7C90005A782 /* DatadogTracer+InjectAndExtract.swift */; }; + 6156A9072BF75A7C00DF66C3 /* ImmutableRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6156A9062BF75A7C00DF66C3 /* ImmutableRequestTests.swift */; }; + 6156A9082BF75A7C00DF66C3 /* ImmutableRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6156A9062BF75A7C00DF66C3 /* ImmutableRequestTests.swift */; }; 61570005246AADFA00E96950 /* DatadogObjc.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61133BF0242397DA00786299 /* DatadogObjc.framework */; }; 615A4A8324A3431600233986 /* Trace+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615A4A8224A3431600233986 /* Trace+objc.swift */; }; 615A4A8924A34FD700233986 /* DDTracerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615A4A8824A34FD700233986 /* DDTracerTests.swift */; }; @@ -2319,6 +2323,7 @@ 61494CB024C839460082C633 /* RUMResourceScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMResourceScope.swift; sourceTree = ""; }; 61494CB424C864680082C633 /* RUMResourceScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMResourceScopeTests.swift; sourceTree = ""; }; 61494CB924CB126F0082C633 /* RUMUserActionScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMUserActionScope.swift; sourceTree = ""; }; + 614A708D2BF754D700D9AF42 /* ImmutableRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImmutableRequest.swift; sourceTree = ""; }; 614B0A4A24EBC43D00A2A780 /* RUMUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMUser.swift; sourceTree = ""; }; 614B0A4E24EBDC6B00A2A780 /* RUMConnectivityInfoProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMConnectivityInfoProvider.swift; sourceTree = ""; }; 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatadogCoreTests.swift; sourceTree = ""; }; @@ -2332,6 +2337,7 @@ 615519252461BCE7002A85CF /* Datadog.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.xcconfig; sourceTree = ""; }; 615519262461BCE7002A85CF /* Datadog.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Datadog.local.xcconfig; sourceTree = ""; }; 61569894256D0E9A00C6AADA /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; + 6156A9062BF75A7C00DF66C3 /* ImmutableRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImmutableRequestTests.swift; sourceTree = ""; }; 6156CB8D24DDA1B5008CB2B2 /* RUMContextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMContextProvider.swift; sourceTree = ""; }; 615950EA291C029700470E0C /* SessionReplayDependencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayDependencyTests.swift; sourceTree = ""; }; 615950ED291C058F00470E0C /* SessionReplayDependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayDependency.swift; sourceTree = ""; }; @@ -5517,6 +5523,7 @@ D2160CC029C0DED100FAA9A5 /* URLSession */ = { isa = PBXGroup; children = ( + 614A708D2BF754D700D9AF42 /* ImmutableRequest.swift */, D295A16429F299C9007C0E9A /* URLSessionInterceptor.swift */, D2160CC129C0DED100FAA9A5 /* URLSessionTaskInterception.swift */, 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */, @@ -6209,6 +6216,7 @@ D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */, D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */, D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */, + 6156A9062BF75A7C00DF66C3 /* ImmutableRequestTests.swift */, ); path = NetworkInstrumentation; sourceTree = ""; @@ -8404,6 +8412,7 @@ D23039F5298D5236001A1FA3 /* AnyEncodable.swift in Sources */, D2303A00298D5236001A1FA3 /* DatadogExtended.swift in Sources */, D23039E6298D5236001A1FA3 /* Sysctl.swift in Sources */, + 614A708E2BF754D800D9AF42 /* ImmutableRequest.swift in Sources */, D2160CF429C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */, D23039E1298D5236001A1FA3 /* AppState.swift in Sources */, D2DE63532A30A7CA00441A54 /* CoreRegistry.swift in Sources */, @@ -9327,6 +9336,7 @@ D2DA2369298D57AA00C6C7E6 /* AnyEncodable.swift in Sources */, D2DA236A298D57AA00C6C7E6 /* DatadogExtended.swift in Sources */, D2DA236B298D57AA00C6C7E6 /* Sysctl.swift in Sources */, + 614A708F2BF754D800D9AF42 /* ImmutableRequest.swift in Sources */, D2160CF529C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */, D2DA236C298D57AA00C6C7E6 /* AppState.swift in Sources */, D2DE63542A30A7CA00441A54 /* CoreRegistry.swift in Sources */, @@ -9409,6 +9419,7 @@ D2216EC32A96649500ADAEC8 /* FeatureBaggageTests.swift in Sources */, D2160CDC29C0DF6700FAA9A5 /* HostsSanitizerTests.swift in Sources */, 615192CD2BD6948B0005A782 /* HTTPHeadersWriterTests.swift in Sources */, + 6156A9072BF75A7C00DF66C3 /* ImmutableRequestTests.swift in Sources */, D2F44FB8299AA1DA0074B0D9 /* DataCompressionTests.swift in Sources */, D2160CE029C0DF6700FAA9A5 /* URLSessionDelegateAsSuperclassTests.swift in Sources */, D2EBEE3B29BA163E00B15732 /* B3HTTPHeadersReaderTests.swift in Sources */, @@ -9456,6 +9467,7 @@ D2216EC42A96649700ADAEC8 /* FeatureBaggageTests.swift in Sources */, D2160CDD29C0DF6700FAA9A5 /* HostsSanitizerTests.swift in Sources */, 615192CE2BD6948B0005A782 /* HTTPHeadersWriterTests.swift in Sources */, + 6156A9082BF75A7C00DF66C3 /* ImmutableRequestTests.swift in Sources */, D2F44FB9299AA1DB0074B0D9 /* DataCompressionTests.swift in Sources */, D2160CE129C0DF6700FAA9A5 /* URLSessionDelegateAsSuperclassTests.swift in Sources */, D2EBEE3F29BA163F00B15732 /* B3HTTPHeadersReaderTests.swift in Sources */, diff --git a/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift b/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift index c1f8f81495..4f1ddb2d04 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift @@ -197,7 +197,7 @@ extension NetworkInstrumentationFeature { interception.register(trace: traceContext) } - if let origin = request.allHTTPHeaderFields?[TracingHTTPHeaders.originField] { + if let origin = request.knownHTTPHeaderFields[TracingHTTPHeaders.originField] { interception.register(origin: origin) } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/ImmutableRequest.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/ImmutableRequest.swift new file mode 100644 index 0000000000..9c3077233b --- /dev/null +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/ImmutableRequest.swift @@ -0,0 +1,44 @@ +/* + * 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 + +/// An immutable version of `URLRequest`. +/// +/// Introduced in response to concerns raised in https://github.com/DataDog/dd-sdk-ios/issues/1638 +/// it makes a copy of request attributes, safeguarding against potential thread safety issues arising from concurrent +/// mutations (see more context in https://github.com/DataDog/dd-sdk-ios/pull/1767 ). +public struct ImmutableRequest { + /// The URL of the request. + public let url: URL? + /// The HTTP method of the request. + public let httpMethod: String? + /// Known HTTP header fields of the request. + public let knownHTTPHeaderFields: [String: String] + /// A reference to the original `URLRequest` object provided during initialization. Direct use is discouraged + /// due to thread safety concerns. Instead, necessary attributes should be accessed through `ImmutableRequest` fields. + public let unsafeOriginal: URLRequest + + public init(request: URLRequest) { + self.url = request.url + self.httpMethod = request.httpMethod + + // As observed in https://github.com/DataDog/dd-sdk-ios/issues/1638, accessing `request.allHTTPHeaderFields` is not + // safe and leads to crashes with undefined root cause. To avoid this, instead we use `request.value(forHTTPHeaderField:)` + // to only read headers known by the SDK. + var knownHTTPHeaderFields: [String: String] = [:] + addHeaderIfExists(request: request, field: TracingHTTPHeaders.originField, to: &knownHTTPHeaderFields) + + self.knownHTTPHeaderFields = knownHTTPHeaderFields + self.unsafeOriginal = request + } +} + +private func addHeaderIfExists(request: URLRequest, field: String, to knownHeaders: inout [String: String]) { + if let value = request.value(forHTTPHeaderField: field) { + knownHeaders[field] = value + } +} diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskInterception.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskInterception.swift index 22486f7b3b..17bfb3b7bb 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskInterception.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskInterception.swift @@ -81,30 +81,6 @@ public struct ResourceCompletion { } } -/// An immutable version of `URLRequest`. -/// -/// Introduced in response to concerns raised in https://github.com/DataDog/dd-sdk-ios/issues/1638 -/// it makes a copy of request attributes, safeguarding against potential thread safety issues arising from concurrent -/// mutations (see more context in https://github.com/DataDog/dd-sdk-ios/pull/1767 ). -public struct ImmutableRequest { - /// The URL of the request. - public let url: URL? - /// The HTTP method of the request. - public let httpMethod: String? - /// The HTTP header fields of the request. - public let allHTTPHeaderFields: [String: String]? - /// A reference to the original `URLRequest` object provided during initialization. Direct use is discouraged - /// due to thread safety concerns. Instead, necessary attributes should be accessed through `ImmutableRequest` fields. - public let unsafeOriginal: URLRequest - - public init(request: URLRequest) { - self.url = request.url - self.httpMethod = request.httpMethod - self.allHTTPHeaderFields = request.allHTTPHeaderFields - self.unsafeOriginal = request - } -} - /// Encapsulates key metrics retrieved either from `URLSessionTaskMetrics` or any other relevant data source. /// Reference: https://developer.apple.com/documentation/foundation/urlsessiontasktransactionmetrics public struct ResourceMetrics { diff --git a/DatadogInternal/Tests/NetworkInstrumentation/ImmutableRequestTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/ImmutableRequestTests.swift new file mode 100644 index 0000000000..7399d01cab --- /dev/null +++ b/DatadogInternal/Tests/NetworkInstrumentation/ImmutableRequestTests.swift @@ -0,0 +1,45 @@ +/* + * 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 TestUtilities +import DatadogInternal + +class ImmutableRequestTests: XCTestCase { + func testReadingURL() { + let original: URLRequest = .mockWith(url: "https://example.com") + let immutable = ImmutableRequest(request: original) + XCTAssertEqual(immutable.url, original.url) + } + + func testReadingHTTPMethod() { + let original: URLRequest = .mockWith(httpMethod: .mockRandom()) + let immutable = ImmutableRequest(request: original) + XCTAssertEqual(immutable.httpMethod, original.httpMethod) + } + + func testReadingKnownHeaders() { + let original: URLRequest = .mockWith( + headers: [ + TracingHTTPHeaders.originField: .mockRandom(length: 128), + ] + ) + let immutable = ImmutableRequest(request: original) + XCTAssertEqual(immutable.knownHTTPHeaderFields[TracingHTTPHeaders.originField], original.allHTTPHeaderFields?[TracingHTTPHeaders.originField]) + } + + func testIgnoringUnknownHeaders() { + let original: URLRequest = .mockWith(headers: .mockRandom()) + let immutable = ImmutableRequest(request: original) + XCTAssertTrue(immutable.knownHTTPHeaderFields.isEmpty) + } + + func testPreservingUnsafeOriginal() { + let original: URLRequest = .mockAny() + let immutable = ImmutableRequest(request: original) + XCTAssertEqual(immutable.unsafeOriginal, original) + } +} From b3440bca26069118120d97450e7678e4549f5f9c Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 17 May 2024 11:53:21 +0200 Subject: [PATCH 105/153] RUM-4440 Upgrade PLCR to 1.11.2 --- Cartfile | 2 +- Cartfile.resolved | 2 +- DatadogCrashReporting.podspec | 2 +- DatadogSDKCrashReporting.podspec | 2 +- Package.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cartfile b/Cartfile index c66e01f370..31328b20d2 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "microsoft/plcrashreporter" ~> 1.11.1 +github "microsoft/plcrashreporter" ~> 1.11.2 binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/main/OpenTelemetryApi.json" ~> 1.6.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index 4c54255812..613647a075 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ binary "https://raw.githubusercontent.com/DataDog/opentelemetry-swift-packages/main/OpenTelemetryApi.json" "1.6.0" -github "microsoft/plcrashreporter" "1.11.1" +github "microsoft/plcrashreporter" "1.11.2" diff --git a/DatadogCrashReporting.podspec b/DatadogCrashReporting.podspec index 2e80366d7f..052300a37f 100644 --- a/DatadogCrashReporting.podspec +++ b/DatadogCrashReporting.podspec @@ -23,7 +23,7 @@ Pod::Spec.new do |s| s.source_files = "DatadogCrashReporting/Sources/**/*.swift" s.dependency 'DatadogInternal', s.version.to_s - s.dependency 'PLCrashReporter', '~> 1.11.1' + s.dependency 'PLCrashReporter', '~> 1.11.2' s.resource_bundle = { "DatadogCrashReporting" => "DatadogCrashReporting/Resources/PrivacyInfo.xcprivacy" diff --git a/DatadogSDKCrashReporting.podspec b/DatadogSDKCrashReporting.podspec index f2b9780122..01ceed8132 100644 --- a/DatadogSDKCrashReporting.podspec +++ b/DatadogSDKCrashReporting.podspec @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.source_files = "DatadogCrashReporting/Sources/**/*.swift" s.dependency 'DatadogInternal', s.version.to_s - s.dependency 'PLCrashReporter', '~> 1.11.1' + s.dependency 'PLCrashReporter', '~> 1.11.2' s.resource_bundle = { "DatadogCrashReporting" => "DatadogCrashReporting/Resources/PrivacyInfo.xcprivacy" diff --git a/Package.swift b/Package.swift index c2676e8eaf..17943de813 100644 --- a/Package.swift +++ b/Package.swift @@ -45,7 +45,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/microsoft/plcrashreporter.git", from: "1.11.1"), + .package(url: "https://github.com/microsoft/plcrashreporter.git", from: "1.11.2"), .package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", exact: "1.6.0") ], targets: [ From cd7248e297e80771205dffeeac889a40b8d696e5 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 17 May 2024 12:51:58 +0200 Subject: [PATCH 106/153] RUM-789 Fix `batch_size` reported in configuration telemetry --- DatadogCore/Sources/Datadog.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogCore/Sources/Datadog.swift b/DatadogCore/Sources/Datadog.swift index d7f32e57b4..1c9f2f77b0 100644 --- a/DatadogCore/Sources/Datadog.swift +++ b/DatadogCore/Sources/Datadog.swift @@ -474,7 +474,7 @@ public enum Datadog { core.telemetry.configuration( backgroundTasksEnabled: configuration.backgroundTasksEnabled, batchProcessingLevel: Int64(exactly: configuration.batchProcessingLevel.maxBatchesPerUpload), - batchSize: Int64(exactly: performance.maxFileSize), + batchSize: performance.uploaderWindow.toInt64Milliseconds, batchUploadFrequency: performance.minUploadDelay.toInt64Milliseconds, useLocalEncryption: configuration.encryption != nil, useProxy: configuration.proxyConfiguration != nil From 7bf12f6e5c718cad6e3f582d4606f6138d59aeba Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 17 May 2024 13:01:11 +0200 Subject: [PATCH 107/153] RUM-3535 export DatadogInternal.TraceContextInjection --- DatadogRUM/Sources/RUMConfiguration.swift | 1 + DatadogTrace/Sources/TraceConfiguration.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/DatadogRUM/Sources/RUMConfiguration.swift b/DatadogRUM/Sources/RUMConfiguration.swift index d5921f5db4..ffff00b710 100644 --- a/DatadogRUM/Sources/RUMConfiguration.swift +++ b/DatadogRUM/Sources/RUMConfiguration.swift @@ -13,6 +13,7 @@ import DatadogInternal @_exported import typealias DatadogInternal.DDURLSessionDelegate @_exported import protocol DatadogInternal.__URLSessionDelegateProviding @_exported import enum DatadogInternal.URLSessionInstrumentation +@_exported import enum DatadogInternal.TraceContextInjection // swiftlint:enable duplicate_imports extension RUM { diff --git a/DatadogTrace/Sources/TraceConfiguration.swift b/DatadogTrace/Sources/TraceConfiguration.swift index 9a21d8b612..abb12e66e2 100644 --- a/DatadogTrace/Sources/TraceConfiguration.swift +++ b/DatadogTrace/Sources/TraceConfiguration.swift @@ -18,6 +18,7 @@ import DatadogInternal @_exported import class DatadogInternal.B3HTTPHeadersWriter @_exported import class DatadogInternal.W3CHTTPHeadersWriter @_exported import enum DatadogInternal.TraceSamplingStrategy +@_exported import enum DatadogInternal.TraceContextInjection // swiftlint:enable duplicate_imports extension Trace { From a4c8f01d911204d21425e13edf7c5098d342c7cd Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 17 May 2024 13:02:43 +0200 Subject: [PATCH 108/153] RUM-3535 fix test names --- .../Tests/NetworkInstrumentation/HTTPHeadersWriterTests.swift | 4 ++-- .../NetworkInstrumentation/W3CHTTPHeadersWriterTests.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersWriterTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersWriterTests.swift index 278d244c9d..1a526f6ae8 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersWriterTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersWriterTests.swift @@ -9,7 +9,7 @@ import TestUtilities import DatadogInternal class HTTPHeadersWriterTests: XCTestCase { - func testWritingSampledTraceContext_withAutoSamplingStrategy() { + func testWritingSampledTraceContext_withHeadBasedSamplingStrategy() { let writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .all) writer.write( @@ -27,7 +27,7 @@ class HTTPHeadersWriterTests: XCTestCase { XCTAssertEqual(headers[TracingHTTPHeaders.tagsField], "_dd.p.tid=4d2") } - func testWritingDroppedTraceContext_withAutoSamplingStrategy() { + func testWritingDroppedTraceContext_withHeadBasedSamplingStrategy() { let writer = HTTPHeadersWriter(samplingStrategy: .headBased, traceContextInjection: .sampled) writer.write( diff --git a/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersWriterTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersWriterTests.swift index 4f6df1631f..598de0fb94 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersWriterTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersWriterTests.swift @@ -9,7 +9,7 @@ import TestUtilities import DatadogInternal class W3CHTTPHeadersWriterTests: XCTestCase { - func testWritingSampledTraceContext_withAutoSamplingStrategy() { + func testWritingSampledTraceContext_withHeadBasedSamplingStrategy() { let writer = W3CHTTPHeadersWriter( samplingStrategy: .headBased, tracestate: [ @@ -31,7 +31,7 @@ class W3CHTTPHeadersWriterTests: XCTestCase { XCTAssertEqual(headers[W3CHTTPHeaders.tracestate], "dd=o:rum;p:0000000000000929;s:1") } - func testWritingDroppedTraceContext_withAutoSamplingStrategy() { + func testWritingDroppedTraceContext_withHeadBasedSamplingStrategy() { let writer = W3CHTTPHeadersWriter( samplingStrategy: .headBased, tracestate: [ From 6e9c8f074107ff5200ce2661790dbab9ba308bfe Mon Sep 17 00:00:00 2001 From: Pedro Lousada Date: Fri, 17 May 2024 12:04:49 +0100 Subject: [PATCH 109/153] RUM-2911 Fix warning in RUMViewScopeTests.swift --- DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift index d61d3255fd..b515d39f98 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -1670,7 +1670,7 @@ class RUMViewScopeTests: XCTestCase { func testWhenViewErrorIsAdded_itSendsErrorWithCorrectTimeSinceAppStart() throws { var context = self.context - var currentTime: Date = .mockDecember15th2019At10AMUTC() + let currentTime: Date = .mockDecember15th2019At10AMUTC() let appLauchToErrorTimeDiff = Int64.random(in: 10..<1_000_000) context.launchTime = .mockWith( From 3eda7075a8ed395aad3de446e9a923e62bf87a53 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 17 May 2024 13:18:52 +0200 Subject: [PATCH 110/153] RUM-3535 add test cases for readers --- .../B3HTTPHeadersReaderTests.swift | 12 +++++++++++- .../HTTPHeadersReaderTests.swift | 11 ++++++++++- .../W3CHTTPHeadersReaderTests.swift | 11 ++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersReaderTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersReaderTests.swift index 4969ac0831..8bfcab0ca9 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersReaderTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/B3HTTPHeadersReaderTests.swift @@ -89,7 +89,7 @@ class B3HTTPHeadersReaderTests: XCTestCase { XCTAssertEqual(reader.sampled, true) } - func testReadingNotSampledTraceContext() { + func testReadingNotSampledTraceContext_givenTraceContextInjectionIsAll() { let encoding: B3HTTPHeadersWriter.InjectEncoding = [.multiple, .single].randomElement()! let writer = B3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), injectEncoding: encoding, traceContextInjection: .all) writer.write(traceContext: .mockRandom()) @@ -98,4 +98,14 @@ class B3HTTPHeadersReaderTests: XCTestCase { XCTAssertNil(reader.read(), "When not sampled, it should return no trace context") XCTAssertEqual(reader.sampled, false) } + + func testReadingNotSampledTraceContext_givenTraceContextInjectionIsSampled() { + let encoding: B3HTTPHeadersWriter.InjectEncoding = [.multiple, .single].randomElement()! + let writer = B3HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), injectEncoding: encoding, traceContextInjection: .sampled) + writer.write(traceContext: .mockRandom()) + + let reader = B3HTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) + XCTAssertNil(reader.read(), "When not sampled, it should return no trace context") + XCTAssertNil(reader.sampled) + } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersReaderTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersReaderTests.swift index 952a191685..fc313b729e 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersReaderTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/HTTPHeadersReaderTests.swift @@ -18,7 +18,16 @@ class HTTPHeadersReaderTests: XCTestCase { XCTAssertEqual(reader.sampled, true) } - func testReadingNotSampledTraceContext() { + func testReadingNotSampledTraceContext_givenTraceContextInjectionIsAll() { + let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), traceContextInjection: .all) + writer.write(traceContext: .mockRandom()) + + let reader = HTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) + XCTAssertNotNil(reader.read(), "When not sampled, it should return no trace context") + XCTAssertEqual(reader.sampled, false) + } + + func testReadingNotSampledTraceContext_givenTraceContextInjectionIsSampled() { let writer = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), traceContextInjection: .sampled) writer.write(traceContext: .mockRandom()) diff --git a/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersReaderTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersReaderTests.swift index 999cfe1c6f..ffb69e103d 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersReaderTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/W3CHTTPHeadersReaderTests.swift @@ -35,7 +35,7 @@ class W3CHTTPHeadersReaderTests: XCTestCase { XCTAssertEqual(reader.sampled, true) } - func testReadingNotSampledTraceContext() { + func testReadingNotSampledTraceContext_givenTraceContextInjectionIsAll() { let writer = W3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), traceContextInjection: .all) writer.write(traceContext: .mockRandom()) @@ -43,4 +43,13 @@ class W3CHTTPHeadersReaderTests: XCTestCase { XCTAssertNil(reader.read(), "When not sampled, it should return no trace context") XCTAssertEqual(reader.sampled, false) } + + func testReadingNotSampledTraceContext_givenTraceContextInjectionIsSampled() { + let writer = W3CHTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 0), traceContextInjection: .sampled) + writer.write(traceContext: .mockRandom()) + + let reader = W3CHTTPHeadersReader(httpHeaderFields: writer.traceHeaderFields) + XCTAssertNil(reader.read(), "When not sampled, it should not return trace context") + XCTAssertNil(reader.sampled) + } } From dd422b5abe7cbd0d8b57b17c50dc9122cb5955c9 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 17 May 2024 13:33:22 +0200 Subject: [PATCH 111/153] chore: add support for Package.resolved V3 --- tools/distribution/dogfood.py | 4 +- .../src/dogfood/package_resolved.py | 44 +++++++++++++ .../tests/dogfood/test_package_resolved.py | 63 +++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/tools/distribution/dogfood.py b/tools/distribution/dogfood.py index 0b5bca1365..9820e80bee 100755 --- a/tools/distribution/dogfood.py +++ b/tools/distribution/dogfood.py @@ -29,9 +29,9 @@ def dogfood(dry_run: bool, repository_url: str, repository_name: str, repository dd_sdk_ios_package = PackageResolvedFile(path=f'{dd_sdk_package_path}/Package.resolved') dd_sdk_ios_package.print() - if dd_sdk_ios_package.version > 2: + if dd_sdk_ios_package.version > 3: raise Exception( - f'`dogfood.py` expects the `package.resolved` in `dd-sdk-ios` to use version <= 2 ' + + f'`dogfood.py` expects the `package.resolved` in `dd-sdk-ios` to use version <= 3 ' + f'but version {dd_sdk_ios_package.version} was detected. Update `dogfood.py` to use this version.' ) diff --git a/tools/distribution/src/dogfood/package_resolved.py b/tools/distribution/src/dogfood/package_resolved.py index a47dc2f8d6..41c84b7fce 100644 --- a/tools/distribution/src/dogfood/package_resolved.py +++ b/tools/distribution/src/dogfood/package_resolved.py @@ -88,6 +88,8 @@ def __init__(self, path: str): self.wrapped = PackageResolvedContentV1(self.path, self.packages) elif self.version == 2: self.wrapped = PackageResolvedContentV2(self.path, self.packages) + elif self.version == 3: + self.wrapped = PackageResolvedContentV3(self.path, self.packages) else: raise Exception( f'{path} uses version {self.version} but `PackageResolvedFile` only supports ' + @@ -132,6 +134,15 @@ def read_dependency_ids(self) -> [PackageID]: def read_dependency(self, package_id: PackageID) -> dict: return self.wrapped.read_dependency(package_id) + def origin_hash(self) -> str: + if self.version == 3: + return self.wrapped.origin_hash() + else: + raise Exception( + f'{self.path} does not contain `originHash` property. ' + + f'Check if the `package.resolved` file is in version 3.' + ) + class PackageResolvedContentV1(PackageResolvedContent): """ @@ -346,3 +357,36 @@ def __get_package(self, package_id: PackageID): package_pin_index = package_pins[0] return self.packages['pins'][package_pin_index] + + +class PackageResolvedContentV3(PackageResolvedContentV2): + """ + Example of `package.resolved` in version `2` looks this:: + + { + "originHash" : "b47de6af98c4a9811a8d2af11d70b960dfc66b7c8e4944b35bb74c8f8bb9c487", + "pins" : [ + { + "identity" : "dd-sdk-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/DataDog/dd-sdk-ios", + "state" : { + "branch" : "dogfooding", + "revision" : "6f662103771eb4523164e64f7f936bf9276f6bd0" + } + }, + ... + ] + "version" : 3 + } + + In v3 new property origin hash has been added which SHA256 hash of the repository content. + Check https://github.com/apple/swift-package-manager/blob/e5123e483c18bff7afdc3b2029f9e1924779bbc8/Sources/Workspace/Workspace%2BDependencies.swift#L280 + """ + + def __init__(self, path: str, json_content: dict): + self.path = path + self.packages = json_content + + def origin_hash(self): + return self.packages['originHash'] diff --git a/tools/distribution/tests/dogfood/test_package_resolved.py b/tools/distribution/tests/dogfood/test_package_resolved.py index c5641dfece..a6874a9d11 100644 --- a/tools/distribution/tests/dogfood/test_package_resolved.py +++ b/tools/distribution/tests/dogfood/test_package_resolved.py @@ -65,6 +65,33 @@ class PackageResolvedFileTestCase(unittest.TestCase): } ''' + v3_file_content = b''' + { + "originHash" : "ea83017c944c7850b8f60207e6143eb17cb6b5e6b734b3fa08787a5d920dba7b", + "pins" : [ + { + "identity" : "a", + "kind" : "remoteSourceControl", + "location" : "https://github.com/A-org/a", + "state" : { + "branch" : "a-branch", + "revision" : "a-revision" + } + }, + { + "identity" : "b", + "kind" : "remoteSourceControl", + "location" : "https://github.com/B-org/b.git", + "state" : { + "revision" : "b-revision", + "version" : "1.0.0" + } + } + ], + "version" : 3 + } + ''' + def test_it_reads_version1_files(self): with NamedTemporaryFile() as file: file.write(self.v1_file_content) @@ -264,3 +291,39 @@ def test_v2_package_id_from_repository_url(self): self.assertEqual('abc', v2_package_id_from_repository_url(repository_url='https://github.com/A-org/abc')) self.assertEqual('abc', v2_package_id_from_repository_url(repository_url='git@github.com:DataDog/abc.git')) self.assertEqual('abc', v2_package_id_from_repository_url(repository_url='git@github.com:DataDog/abc')) + + def test_it_reads_version3_files(self): + with NamedTemporaryFile() as file: + file.write(self.v3_file_content) + file.seek(0) + + package_resolved = PackageResolvedFile(path=file.name) + self.assertTrue(package_resolved.has_dependency(package_id=PackageID(v1=None, v2='a'))) + self.assertTrue(package_resolved.has_dependency(package_id=PackageID(v1=None, v2='b'))) + self.assertFalse(package_resolved.has_dependency(package_id=PackageID(v1=None, v2='c'))) + self.assertListEqual( + [PackageID(v1=None, v2='a'), PackageID(v1=None, v2='b')], + package_resolved.read_dependency_ids() + ) + self.assertDictEqual( + { + 'identity': 'a', + 'kind': 'remoteSourceControl', + 'location': 'https://github.com/A-org/a', + 'state': {'branch': 'a-branch', 'revision': 'a-revision'} + }, + package_resolved.read_dependency(package_id=PackageID(v1=None, v2='a')) + ) + self.assertDictEqual( + { + 'identity': 'b', + 'kind': 'remoteSourceControl', + 'location': 'https://github.com/B-org/b.git', + 'state': {'revision': 'b-revision', 'version': '1.0.0'} + }, + package_resolved.read_dependency(PackageID(v1=None, v2='b')) + ) + self.assertEqual( + "ea83017c944c7850b8f60207e6143eb17cb6b5e6b734b3fa08787a5d920dba7b", + package_resolved.origin_hash() + ) \ No newline at end of file From 44cc1e30541306e3a2817d819492938899f23ff5 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 17 May 2024 13:35:09 +0200 Subject: [PATCH 112/153] fix exception --- tools/distribution/src/dogfood/package_resolved.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/distribution/src/dogfood/package_resolved.py b/tools/distribution/src/dogfood/package_resolved.py index 41c84b7fce..c2567a9af7 100644 --- a/tools/distribution/src/dogfood/package_resolved.py +++ b/tools/distribution/src/dogfood/package_resolved.py @@ -93,7 +93,7 @@ def __init__(self, path: str): else: raise Exception( f'{path} uses version {self.version} but `PackageResolvedFile` only supports ' + - f'versions `1` and `2`. Update `PackageResolvedFile` to support new version.' + f'versions `1`, `2` and `3`. Update `PackageResolvedFile` to support new version.' ) def save(self): From fa2c237cf1f0cf92d9821dbbb07bd88c973317ec Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 17 May 2024 13:35:59 +0200 Subject: [PATCH 113/153] cleanup --- tools/distribution/src/dogfood/package_resolved.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/distribution/src/dogfood/package_resolved.py b/tools/distribution/src/dogfood/package_resolved.py index c2567a9af7..8381d81177 100644 --- a/tools/distribution/src/dogfood/package_resolved.py +++ b/tools/distribution/src/dogfood/package_resolved.py @@ -384,9 +384,5 @@ class PackageResolvedContentV3(PackageResolvedContentV2): Check https://github.com/apple/swift-package-manager/blob/e5123e483c18bff7afdc3b2029f9e1924779bbc8/Sources/Workspace/Workspace%2BDependencies.swift#L280 """ - def __init__(self, path: str, json_content: dict): - self.path = path - self.packages = json_content - def origin_hash(self): return self.packages['originHash'] From 494a0b5a6a156ca4ebebf8e3e11f9df54580663f Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 17 May 2024 17:18:09 +0200 Subject: [PATCH 114/153] chore: fix Swift 5.7 issue with scoping --- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index 3be8f76b3a..198547a434 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -137,12 +137,12 @@ internal class Monitor: RUMCommandSubscriber { return } - let transformedCommand = transform(command: command) + let transformedCommand = self.transform(command: command) - _ = scopes.process(command: transformedCommand, context: context, writer: writer) + _ = self.scopes.process(command: transformedCommand, context: context, writer: writer) - if let debugging = debugging { - debugging.debug(applicationScope: scopes) + if let debugging = self.debugging { + debugging.debug(applicationScope: self.scopes) } } @@ -153,9 +153,9 @@ internal class Monitor: RUMCommandSubscriber { return nil } - let context = scopes.activeSession?.viewScopes.last?.context ?? - scopes.activeSession?.context ?? - scopes.context + let context = self.scopes.activeSession?.viewScopes.last?.context ?? + self.scopes.activeSession?.context ?? + self.scopes.context guard context.sessionID != .nullUUID else { // if Session was sampled or not yet started @@ -503,7 +503,7 @@ extension Monitor: RUMMonitorProtocol { guard let self = self else { return } - debugging?.debug(applicationScope: scopes) + self.debugging?.debug(applicationScope: self.scopes) } } get { From a8a357bf36eb645cc24a7f3d4e51ab4529370aec Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 17 May 2024 17:44:27 +0200 Subject: [PATCH 115/153] fix: s8s context --- .../Sources/RUMMonitor/Scopes/RUMApplicationScope.swift | 2 +- DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift | 7 +------ DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift index a9b5f32074..65e6dab356 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -46,7 +46,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { // Notify Synthetics if needed if dependencies.syntheticsTest != nil { - print("_dd.application.id=" + dependencies.rumApplicationID) + NSLog("_dd.application.id=" + dependencies.rumApplicationID) } } diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index a3d9041e78..5154f12e38 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -122,7 +122,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { // Notify Synthetics if needed if dependencies.syntheticsTest != nil && sessionUUID != .nullUUID { - print("_dd.session.id=" + sessionUUID.toRUMDataFormat) + NSLog("_dd.session.id=" + sessionUUID.toRUMDataFormat) } } @@ -283,11 +283,6 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { timestamp: startTime.timeIntervalSince1970.toInt64Milliseconds, hasReplay: hasReplay ) - - // Notify Synthetics if needed - if dependencies.syntheticsTest != nil && sessionUUID != .nullUUID { - print("_dd.view.id=" + id) - } } private func startApplicationLaunchView(on command: RUMApplicationStartCommand, context: DatadogContext, writer: Writer) { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index f1b2c11268..031fa23332 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -127,7 +127,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { // Notify Synthetics if needed if dependencies.syntheticsTest != nil && self.context.sessionID != .nullUUID { - print("_dd.view.id=" + self.viewUUID.toRUMDataFormat) + NSLog("_dd.view.id=" + self.viewUUID.toRUMDataFormat) } } From e3a6782047853cd114fded3860d1ac7cb21f82a5 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 09:56:30 +0200 Subject: [PATCH 116/153] Fixes for Swiftlint 0.55.1 --- DatadogCore/Sources/Core/Upload/URLSessionClient.swift | 6 ++---- DatadogCore/Sources/Kronos/KronosNTPClient.swift | 6 +++--- DatadogCore/Tests/Datadog/RUM/RUMMonitorTests.swift | 2 +- .../Sources/Integrations/WebViewEventReceiver.swift | 8 +++----- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/DatadogCore/Sources/Core/Upload/URLSessionClient.swift b/DatadogCore/Sources/Core/Upload/URLSessionClient.swift index 8725d896eb..1cf66dd5d9 100644 --- a/DatadogCore/Sources/Core/Upload/URLSessionClient.swift +++ b/DatadogCore/Sources/Core/Upload/URLSessionClient.swift @@ -20,10 +20,8 @@ internal class URLSessionClient: HTTPClient { // URLSession does not set the `Proxy-Authorization` header automatically when using a proxy // configuration. We manually set the HTTP basic authentication header. - if - let user = proxyConfiguration?[kCFProxyUsernameKey] as? String, - let password = proxyConfiguration?[kCFProxyPasswordKey] as? String - { + if let user = proxyConfiguration?[kCFProxyUsernameKey] as? String, + let password = proxyConfiguration?[kCFProxyPasswordKey] as? String { let authorization = basicHTTPAuthentication(username: user, password: password) configuration.httpAdditionalHeaders = ["Proxy-Authorization": authorization] } diff --git a/DatadogCore/Sources/Kronos/KronosNTPClient.swift b/DatadogCore/Sources/Kronos/KronosNTPClient.swift index 757459254c..17e9323c3e 100644 --- a/DatadogCore/Sources/Kronos/KronosNTPClient.swift +++ b/DatadogCore/Sources/Kronos/KronosNTPClient.swift @@ -44,7 +44,7 @@ internal final class KronosNTPClient { var servers: [KronosInternetAddress: [KronosNTPPacket]] = [:] var completed: Int = 0 - let queryIPAndStoreResult = { (address: KronosInternetAddress, totalQueries: Int) -> Void in + let queryIPAndStoreResult = { (address: KronosInternetAddress, totalQueries: Int) in self.query(ip: address, port: port, version: version, timeout: timeout, numberOfSamples: numberOfSamples) { packet in defer { completed += 1 @@ -106,8 +106,8 @@ internal final class KronosNTPClient { timer?.invalidate() guard let data = data, let PDU = try? KronosNTPPacket(data: data, destinationTime: destinationTime), - PDU.isValidResponse() else - { + PDU.isValidResponse() + else { completion(nil) return } diff --git a/DatadogCore/Tests/Datadog/RUM/RUMMonitorTests.swift b/DatadogCore/Tests/Datadog/RUM/RUMMonitorTests.swift index a6b53b363e..98a79ed1c7 100644 --- a/DatadogCore/Tests/Datadog/RUM/RUMMonitorTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/RUMMonitorTests.swift @@ -1435,7 +1435,7 @@ class RUMMonitorTests: XCTestCase { for matcher in matchers.filterTelemetry() { // Application Start/Launch happens too early to have attributes set. if (try? matcher.attribute(forKeyPath: "action.type")) == "application_start" || - (try? matcher.attribute(forKeyPath: "view.name")) == "ApplicationLaunch"{ + (try? matcher.attribute(forKeyPath: "view.name")) == "ApplicationLaunch" { continue } expectedAttributes.forEach { attrKey, attrValue in diff --git a/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift b/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift index a578ce340f..22e61805dd 100644 --- a/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift +++ b/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift @@ -76,11 +76,9 @@ internal final class WebViewEventReceiver: FeatureMessageReceiver { let rum: RUMCoreContext = try rumBaggage.decode() var event = event - if - let date = event["date"] as? Int, - let view = event["view"] as? JSON, - let id = view["id"] as? String - { + if let date = event["date"] as? Int, + let view = event["view"] as? JSON, + let id = view["id"] as? String { let correctedDate = Int64(date) + self.offset(forView: id, context: context) event["date"] = correctedDate From 974a75c982ef1d95b917e07986d0a666b5f85de4 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 10:30:55 +0200 Subject: [PATCH 117/153] RUM-347 Fix `batch_duration` in "Batch Closed" metrics --- .../Core/Storage/FilesOrchestrator.swift | 11 +++++++++-- .../FilesOrchestrator+MetricsTests.swift | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift b/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift index 8f587a376f..23b4443199 100644 --- a/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift +++ b/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift @@ -33,6 +33,9 @@ internal class FilesOrchestrator: FilesOrchestratorType { /// Tracks the size of last writable file by accumulating the total `writeSize:` requested in `getWritableFile(writeSize:)` /// This is approximated value as it assumes that all requested writes succed. The actual difference should be negligible. private var lastWritableFileApproximatedSize: UInt64 = 0 + /// Tracks the last date when writtable file was requested for write operation. + /// It is used to compute the "batch duration" from the moment file was created to the moment it was last written. + private var lastWritableFileLastWriteDate: Date? = nil /// Telemetry interface. let telemetry: Telemetry @@ -75,6 +78,7 @@ internal class FilesOrchestrator: FilesOrchestratorType { if let lastWritableFile = reuseLastWritableFileIfPossible(writeSize: writeSize) { // if last writable file can be reused lastWritableFileObjectsCount += 1 lastWritableFileApproximatedSize += writeSize + lastWritableFileLastWriteDate = dateProvider.now return lastWritableFile } else { if let closedBatchName = lastWritableFileName { @@ -103,6 +107,7 @@ internal class FilesOrchestrator: FilesOrchestratorType { lastWritableFileName = newFile.name lastWritableFileObjectsCount = 1 lastWritableFileApproximatedSize = writeSize + lastWritableFileLastWriteDate = dateProvider.now return newFile } @@ -251,8 +256,10 @@ internal class FilesOrchestrator: FilesOrchestratorType { guard let metricsData = metricsData else { return // do not track metrics for this orchestrator } - - let batchDuration = dateProvider.now.timeIntervalSince(fileCreationDateFrom(fileName: fileName)) + guard let lastWriteDate = lastWritableFileLastWriteDate else { + return // not reachable + } + let batchDuration = lastWriteDate.timeIntervalSince(fileCreationDateFrom(fileName: fileName)) telemetry.metric( name: BatchClosedMetric.name, diff --git a/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestrator+MetricsTests.swift b/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestrator+MetricsTests.swift index 4ee4310749..789f042cce 100644 --- a/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestrator+MetricsTests.swift +++ b/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestrator+MetricsTests.swift @@ -139,17 +139,24 @@ class FilesOrchestrator_MetricsTests: XCTestCase { func testWhenNewBatchIsStarted_itSendsBatchClosedMetric() throws { // Given // - request batch to be created - // - request few writes on that batch + // - request few writes on that batch, each after certain delay let orchestrator = createOrchestrator() let expectedWrites: [UInt64] = [10, 5, 2] - try expectedWrites.forEach { writeSize in - _ = try orchestrator.getWritableFile(writeSize: writeSize) - } + let expectedWriteDelays: [TimeInterval] = [ + storage.maxFileAgeForWrite * 0.25, + storage.maxFileAgeForWrite * 0.45, + ] + + _ = try orchestrator.getWritableFile(writeSize: expectedWrites[0]) + dateProvider.advance(bySeconds: expectedWriteDelays[0]) + _ = try orchestrator.getWritableFile(writeSize: expectedWrites[1]) + dateProvider.advance(bySeconds: expectedWriteDelays[1]) + _ = try orchestrator.getWritableFile(writeSize: expectedWrites[2]) // When // - wait more than allowed batch age for writes, so next batch request will create another batch // - then request another batch, which will close the previous one - dateProvider.advance(bySeconds: (storage.maxFileAgeForWrite + 1)) + dateProvider.advance(bySeconds: storage.maxFileAgeForWrite + 1) _ = try orchestrator.getWritableFile(writeSize: 1) // Then @@ -161,7 +168,7 @@ class FilesOrchestrator_MetricsTests: XCTestCase { "uploader_window": storage.uploaderWindow.toMilliseconds, "batch_size": expectedWrites.reduce(0, +), "batch_events_count": expectedWrites.count, - "batch_duration": (storage.maxFileAgeForWrite + 1).toMilliseconds + "batch_duration": expectedWriteDelays.reduce(0, +).toMilliseconds ]) } } From bb4b652b967495e8c053583049ba35ad153ef0c3 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 11:09:27 +0200 Subject: [PATCH 118/153] RUM-347 Update file orchestrator test to not depend on relative time provider as adding more calls to this provider for SDK metrics caused flakiness --- .../Datadog/Core/Persistence/FilesOrchestratorTests.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestratorTests.swift b/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestratorTests.swift index cb6f09d807..35ce6dc27d 100644 --- a/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestratorTests.swift +++ b/DatadogCore/Tests/Datadog/Core/Persistence/FilesOrchestratorTests.swift @@ -55,17 +55,20 @@ class FilesOrchestratorTests: XCTestCase { } func testWhenSameWritableFileWasUsedMaxNumberOfTimes_itCreatesNewFile() throws { - let orchestrator = configureOrchestrator(using: RelativeDateProvider(advancingBySeconds: 0.001)) + let dateProvider = DateProviderMock() + let orchestrator = configureOrchestrator(using: dateProvider) var previousFile: WritableFile = try orchestrator.getWritableFile(writeSize: 1) // first use of a new file var nextFile: WritableFile for _ in (0..<5) { for _ in (0 ..< performance.maxObjectsInFile).dropLast() { // skip first use + dateProvider.now.addTimeInterval(0.001) nextFile = try orchestrator.getWritableFile(writeSize: 1) XCTAssertEqual(nextFile.name, previousFile.name, "It should reuse the file \(performance.maxObjectsInFile) times") previousFile = nextFile } + dateProvider.now.addTimeInterval(0.001) nextFile = try orchestrator.getWritableFile(writeSize: 1) // first use of a new file XCTAssertNotEqual(nextFile.name, previousFile.name, "It should create a new file when previous one is used \(performance.maxObjectsInFile) times") previousFile = nextFile From 137c0152df65159c0439d81ece97d842200fc22f Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 11:24:08 +0200 Subject: [PATCH 119/153] [CIVIS-9813] fix for test sdk integration --- .../CITestIntegration.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/DatadogCore/Sources/FeaturesIntegration/CITestIntegration.swift b/DatadogCore/Sources/FeaturesIntegration/CITestIntegration.swift index b3a6075d74..ea08775908 100644 --- a/DatadogCore/Sources/FeaturesIntegration/CITestIntegration.swift +++ b/DatadogCore/Sources/FeaturesIntegration/CITestIntegration.swift @@ -20,12 +20,15 @@ internal class CITestIntegration { let testExecutionId: String /// Tag that must be added to spans and headers when running inside a CIApp test let origin = "ciapp-test" + // UUID for test process message channel + private let messageChannelUUID: String? private init?(processInfo: ProcessInfo = .processInfo) { guard let testID = processInfo.environment["CI_VISIBILITY_TEST_EXECUTION_ID"] else { return nil } self.testExecutionId = testID + self.messageChannelUUID = processInfo.environment["CI_VISIBILITY_MESSAGE_CHANNEL_UUID"] } /// Entry point for running all the tasks needed for CIApp integration @@ -38,7 +41,10 @@ internal class CITestIntegration { /// created in the CIApp framework private func notifyRUMSession() { let timeout: CFTimeInterval = 1.0 - guard let remotePort = CFMessagePortCreateRemote(nil, "DatadogTestingPort" as CFString) else { + + guard let remotePort = CFMessagePortCreateRemote( + nil, messagePortId(name: "DatadogTestingPort") + ) else { return } CFMessagePortSendRequest( @@ -65,10 +71,18 @@ internal class CITestIntegration { return nil } - guard let port = CFMessagePortCreateLocal(nil, "DatadogRUMTestingPort" as CFString, attributeCallback, nil, nil) else { + guard let port = CFMessagePortCreateLocal( + nil, messagePortId(name: "DatadogRUMTestingPort"), attributeCallback, nil, nil + ) else { return } let runLoopSource = CFMessagePortCreateRunLoopSource(nil, port, 0) CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, CFRunLoopMode.commonModes) } + + /// Creates a ID for message port. If UUID is provided joins name with UUID. + /// Fallbacks to name if UUID is nil for backward compatibility + private func messagePortId(name: String) -> CFString { + (messageChannelUUID.map { "\(name)-\($0)" } ?? name) as CFString + } } From 9cb2383a222001a1b4a93882f53e948214252497 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 15:59:37 +0200 Subject: [PATCH 120/153] RUM-3183 CR feedback - be explicit in reading request headers --- .../NetworkInstrumentationFeature.swift | 2 +- .../URLSession/ImmutableRequest.swift | 22 +++++-------------- .../URLSession/URLSessionInterceptor.swift | 2 +- .../Sources/Upload/URLRequestBuilder.swift | 4 +++- .../ImmutableRequestTests.swift | 13 ++++------- tools/lint/sources.swiftlint.yml | 9 ++++++++ 6 files changed, 24 insertions(+), 28 deletions(-) diff --git a/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift b/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift index 4f1ddb2d04..4ddaedbe3f 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift @@ -197,7 +197,7 @@ extension NetworkInstrumentationFeature { interception.register(trace: traceContext) } - if let origin = request.knownHTTPHeaderFields[TracingHTTPHeaders.originField] { + if let origin = request.ddOriginHeaderValue { interception.register(origin: origin) } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/ImmutableRequest.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/ImmutableRequest.swift index 9c3077233b..be7951f400 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/ImmutableRequest.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/ImmutableRequest.swift @@ -16,8 +16,8 @@ public struct ImmutableRequest { public let url: URL? /// The HTTP method of the request. public let httpMethod: String? - /// Known HTTP header fields of the request. - public let knownHTTPHeaderFields: [String: String] + /// The value of `x-datadog-origin` header (if any). + public let ddOriginHeaderValue: String? /// A reference to the original `URLRequest` object provided during initialization. Direct use is discouraged /// due to thread safety concerns. Instead, necessary attributes should be accessed through `ImmutableRequest` fields. public let unsafeOriginal: URLRequest @@ -25,20 +25,10 @@ public struct ImmutableRequest { public init(request: URLRequest) { self.url = request.url self.httpMethod = request.httpMethod - - // As observed in https://github.com/DataDog/dd-sdk-ios/issues/1638, accessing `request.allHTTPHeaderFields` is not - // safe and leads to crashes with undefined root cause. To avoid this, instead we use `request.value(forHTTPHeaderField:)` - // to only read headers known by the SDK. - var knownHTTPHeaderFields: [String: String] = [:] - addHeaderIfExists(request: request, field: TracingHTTPHeaders.originField, to: &knownHTTPHeaderFields) - - self.knownHTTPHeaderFields = knownHTTPHeaderFields + // RUM-3183: As observed in https://github.com/DataDog/dd-sdk-ios/issues/1638, accessing `request.allHTTPHeaderFields` is not + // safe and can lead to crashes with undefined root cause. To avoid issues we should prefer `request.value(forHTTPHeaderField:)` + // when interacting with `URLRequest`. + self.ddOriginHeaderValue = request.value(forHTTPHeaderField: TracingHTTPHeaders.originField) self.unsafeOriginal = request } } - -private func addHeaderIfExists(request: URLRequest, field: String, to knownHeaders: inout [String: String]) { - if let value = request.value(forHTTPHeaderField: field) { - knownHeaders[field] = value - } -} diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionInterceptor.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionInterceptor.swift index 6892509bfd..66c33ba24e 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionInterceptor.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionInterceptor.swift @@ -98,7 +98,7 @@ public struct URLSessionInterceptor { // MARK: - Private private func extractTraceID(from request: URLRequest) -> TraceID? { - guard let headers = request.allHTTPHeaderFields else { + guard let headers = request.allHTTPHeaderFields else { // swiftlint:disable:this unsafe_all_http_header_fields return nil } diff --git a/DatadogInternal/Sources/Upload/URLRequestBuilder.swift b/DatadogInternal/Sources/Upload/URLRequestBuilder.swift index 9709795ddb..38f6625fa5 100644 --- a/DatadogInternal/Sources/Upload/URLRequestBuilder.swift +++ b/DatadogInternal/Sources/Upload/URLRequestBuilder.swift @@ -145,7 +145,9 @@ public struct URLRequestBuilder { } } - request.allHTTPHeaderFields = headers + headers.forEach { field, value in + request.setValue(value, forHTTPHeaderField: field) + } return request } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/ImmutableRequestTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/ImmutableRequestTests.swift index 7399d01cab..d805579bcc 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/ImmutableRequestTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/ImmutableRequestTests.swift @@ -21,20 +21,15 @@ class ImmutableRequestTests: XCTestCase { XCTAssertEqual(immutable.httpMethod, original.httpMethod) } - func testReadingKnownHeaders() { + func testReadingDatadogOriginHeader() { + let expectedValue: String = .mockRandom(length: 128) let original: URLRequest = .mockWith( headers: [ - TracingHTTPHeaders.originField: .mockRandom(length: 128), + TracingHTTPHeaders.originField: expectedValue ] ) let immutable = ImmutableRequest(request: original) - XCTAssertEqual(immutable.knownHTTPHeaderFields[TracingHTTPHeaders.originField], original.allHTTPHeaderFields?[TracingHTTPHeaders.originField]) - } - - func testIgnoringUnknownHeaders() { - let original: URLRequest = .mockWith(headers: .mockRandom()) - let immutable = ImmutableRequest(request: original) - XCTAssertTrue(immutable.knownHTTPHeaderFields.isEmpty) + XCTAssertEqual(immutable.ddOriginHeaderValue, expectedValue) } func testPreservingUnsafeOriginal() { diff --git a/tools/lint/sources.swiftlint.yml b/tools/lint/sources.swiftlint.yml index 230c323139..9ff9dab540 100644 --- a/tools/lint/sources.swiftlint.yml +++ b/tools/lint/sources.swiftlint.yml @@ -89,6 +89,15 @@ custom_rules: - doccomment message: "`UIApplication.shared` is unavailable in some environments. Check `UIApplication.managedShared`." severity: error + unsafe_all_http_header_fields: # prevents from using `URLRequest.allHTTPHeaderFields` API + included: Sources + name: "Unsafe API: `URLRequest.allHTTPHeaderFields`" + regex: '\.allHTTPHeaderFields' + excluded_match_kinds: + - comment + - doccomment + message: "`URLRequest.allHTTPHeaderFields` is considered unsafe. Prefer using `URLRequest.value(forHTTPHeaderField:)`. See full context in https://github.com/DataDog/dd-sdk-ios/pull/1843" + severity: error required_reason_api_name: # prevents from declaring symbols that may conflict with Apple's Required Reason APIs included: Sources name: "Required Reason API Conflict" From add8e6da4fa85602966e514c3e84aa71faf15f96 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 16:40:59 +0200 Subject: [PATCH 121/153] RUM-4315 Add missing RUM APIs for objective-C --- .../Tests/DatadogObjc/DDRUMMonitorTests.swift | 68 +++++++++++++++++++ .../ObjcAPITests/DDRUMMonitor+apiTests.m | 7 ++ DatadogObjc/Sources/RUM/RUM+objc.swift | 20 ++++++ 3 files changed, 95 insertions(+) diff --git a/DatadogCore/Tests/DatadogObjc/DDRUMMonitorTests.swift b/DatadogCore/Tests/DatadogObjc/DDRUMMonitorTests.swift index 07e090358f..e2806786cf 100644 --- a/DatadogCore/Tests/DatadogObjc/DDRUMMonitorTests.swift +++ b/DatadogCore/Tests/DatadogObjc/DDRUMMonitorTests.swift @@ -160,6 +160,50 @@ class DDRUMMonitorTests: XCTestCase { XCTAssertTrue(DDRUMMonitor.shared().swiftRUMMonitor is Monitor) } + func testProvidingCurrentSessionID() throws { + let callSessionIDCallback = expectation(description: "call session ID callback") + var currentSessionID: String? = nil + + RUM.enable(with: config) + let objcRUMMonitor = DDRUMMonitor.shared() + objcRUMMonitor.currentSessionID { sessionID in + currentSessionID = sessionID + callSessionIDCallback.fulfill() + } + + waitForExpectations(timeout: 0.5) + let sessionID = try XCTUnwrap(currentSessionID) + XCTAssertTrue(sessionID.matches(regex: .uuidRegex)) + } + + func testStoppingSession() throws { + let callSessionIDCallback = expectation(description: "call session ID callback twice") + callSessionIDCallback.expectedFulfillmentCount = 2 + var sessionID1: String? = nil + var sessionID2: String? = nil + + // Given + RUM.enable(with: config) + let objcRUMMonitor = DDRUMMonitor.shared() + objcRUMMonitor.currentSessionID { sessionID in + sessionID1 = sessionID + callSessionIDCallback.fulfill() + } + + // When + objcRUMMonitor.stopSession() + objcRUMMonitor.startView(key: "key", name: "AnyView", attributes: [:]) + + // Then + objcRUMMonitor.currentSessionID { sessionID in + sessionID2 = sessionID + callSessionIDCallback.fulfill() + } + + waitForExpectations(timeout: 0.5) + XCTAssertNotEqual(try XCTUnwrap(sessionID1), try XCTUnwrap(sessionID2)) + } + func testSendingViewEvents() throws { RUM.enable(with: config) @@ -391,4 +435,28 @@ class DDRUMMonitorTests: XCTestCase { XCTAssertNil(try? viewEvents[0].attribute(forKeyPath: "context.global-attribute2") as String) XCTAssertEqual(try viewEvents[0].attribute(forKeyPath: "context.event-attribute1"), "foo1") } + + func testEvaluatingFeatureFlags() throws { + RUM.enable(with: config) + let objcRUMMonitor = DDRUMMonitor.shared() + + objcRUMMonitor.addFeatureFlagEvaluation(name: "flag1", value: "value1") + objcRUMMonitor.addFeatureFlagEvaluation(name: "flag2", value: true) + + let viewEvents = core.waitAndReturnEvents(ofFeature: RUMFeature.name, ofType: RUMViewEvent.self) + let lastView = try XCTUnwrap(viewEvents.last) + XCTAssertEqual(lastView.featureFlags!.featureFlagsInfo["flag1"] as? AnyEncodable, AnyEncodable("value1")) + XCTAssertEqual(lastView.featureFlags!.featureFlagsInfo["flag2"] as? AnyEncodable, AnyEncodable(true)) + } + + func testChangingDebugFlag() throws { + RUM.enable(with: config) + let objcRUMMonitor = DDRUMMonitor.shared() + + objcRUMMonitor.debug = true + XCTAssertTrue(objcRUMMonitor.swiftRUMMonitor.debug) + + objcRUMMonitor.debug = false + XCTAssertFalse(objcRUMMonitor.swiftRUMMonitor.debug) + } } diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUMMonitor+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUMMonitor+apiTests.m index 8c6eaa7586..d603998a99 100644 --- a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUMMonitor+apiTests.m +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUMMonitor+apiTests.m @@ -52,6 +52,9 @@ - (void)testDDRUMMonitorAPI { UIViewController *anyVC = [UIViewController new]; DDRUMMonitor *monitor = [DDRUMMonitor shared]; + [monitor currentSessionIDWithCompletion:^(NSString * _Nullable sessionID) {}]; + [monitor stopSession]; + [monitor startViewWithViewController:anyVC name:@"" attributes:@{}]; [monitor stopViewWithViewController:anyVC attributes:@{}]; [monitor startViewWithKey:@"" name:nil attributes:@{}]; @@ -72,6 +75,10 @@ - (void)testDDRUMMonitorAPI { [monitor stopActionWithType:DDRUMActionTypeSwipe name:nil attributes:@{}]; [monitor addActionWithType:DDRUMActionTypeTap name:@"" attributes:@{}]; [monitor addAttributeForKey:@"" value:@""]; + [monitor addFeatureFlagEvaluationWithName: @"name" value: @"value"]; + + [monitor setDebug:YES]; + [monitor setDebug:NO]; } #pragma clang diagnostic pop diff --git a/DatadogObjc/Sources/RUM/RUM+objc.swift b/DatadogObjc/Sources/RUM/RUM+objc.swift index 52f058c2c4..f86218a3d9 100644 --- a/DatadogObjc/Sources/RUM/RUM+objc.swift +++ b/DatadogObjc/Sources/RUM/RUM+objc.swift @@ -452,6 +452,16 @@ public class DDRUMMonitor: NSObject { DDRUMMonitor(swiftRUMMonitor: RUMMonitor.shared()) } + @objc + public func currentSessionID(completion: @escaping (String?) -> Void) { + swiftRUMMonitor.currentSessionID(completion: completion) + } + + @objc + public func stopSession() { + swiftRUMMonitor.stopSession() + } + @objc public func startView( viewController: UIViewController, @@ -633,4 +643,14 @@ public class DDRUMMonitor: NSObject { public func removeAttribute(forKey key: String) { swiftRUMMonitor.removeAttribute(forKey: key) } + + @objc + public func addFeatureFlagEvaluation(name: String, value: Any) { + swiftRUMMonitor.addFeatureFlagEvaluation(name: name, value: AnyEncodable(value)) + } + + @objc public var debug: Bool { + set { swiftRUMMonitor.debug = newValue } + get { swiftRUMMonitor.debug } + } } From ed9ab00707a0cf9173c4062b0cde539e0e668a05 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 16:44:18 +0200 Subject: [PATCH 122/153] RUM-4315 Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfafd67a66..e3017dcb27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - [FEATURE] `DatadogTrace` now supports OpenTelemetry. See [#1828][] +- [FEATURE] RUM "stop session", "get session ID" and "evaluate feature flag" APIs are now available for Obj-C. See [#1853][] # 2.11.0 / 08-05-2024 @@ -661,6 +662,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1776]: https://github.com/DataDog/dd-sdk-ios/pull/1776 [#1721]: https://github.com/DataDog/dd-sdk-ios/pull/1721 [#1803]: https://github.com/DataDog/dd-sdk-ios/pull/1803 +[#1853]: https://github.com/DataDog/dd-sdk-ios/pull/1853 [#1807]: https://github.com/DataDog/dd-sdk-ios/pull/1807 [#1828]: https://github.com/DataDog/dd-sdk-ios/pull/1828 [@00fa9a]: https://github.com/00FA9A From be82043ad485f4997344b74a0ff6dd522f74843e Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 17:37:23 +0200 Subject: [PATCH 123/153] RUM-4313 Add WebViewTracking for Obj-c --- Datadog/Datadog.xcodeproj/project.pbxproj | 8 ++ .../DatadogObjc/ObjcAPITests/DDRUM+apiTests.m | 1 - .../ObjcAPITests/DDWebViewTracking+apiTests.m | 50 ++++++++ .../Sources/WebViewTracking+objc.swift | 113 ++++++++++++++++++ 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDWebViewTracking+apiTests.m create mode 100644 DatadogWebViewTracking/Sources/WebViewTracking+objc.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 01b4722d3d..9076b7cc0c 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -410,6 +410,8 @@ 6172472725D673D7007085B3 /* CrashContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6172472625D673D7007085B3 /* CrashContextTests.swift */; }; 617247AF25DA9BEA007085B3 /* CrashReportingObjcHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 617247AE25DA9BEA007085B3 /* CrashReportingObjcHelpers.m */; }; 617247B825DAB0E2007085B3 /* DDCrashReportBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617247B725DAB0E2007085B3 /* DDCrashReportBuilder.swift */; }; + 6174D6042BFB9AB600EC7469 /* WebViewTracking+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */; }; + 6174D6062BFB9D6400EC7469 /* DDWebViewTracking+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */; }; 6175922B2A6FA8EE0073F431 /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; 6175922D2A6FADDD0073F431 /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; 6175C3512BCE66DB006FAAB0 /* TraceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */; }; @@ -2386,6 +2388,8 @@ 617247AD25DA9BEA007085B3 /* CrashReportingObjcHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CrashReportingObjcHelpers.h; sourceTree = ""; }; 617247AE25DA9BEA007085B3 /* CrashReportingObjcHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CrashReportingObjcHelpers.m; sourceTree = ""; }; 617247B725DAB0E2007085B3 /* DDCrashReportBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDCrashReportBuilder.swift; sourceTree = ""; }; + 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebViewTracking+objc.swift"; sourceTree = ""; }; + 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDWebViewTracking+apiTests.m"; sourceTree = ""; }; 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceContext.swift; sourceTree = ""; }; 617699172A860D9D0030022B /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; 6176991A2A86121B0030022B /* HTTPClientMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClientMock.swift; sourceTree = ""; }; @@ -3336,6 +3340,7 @@ isa = PBXGroup; children = ( 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */, + 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */, D29732462A5C108700827599 /* DDScriptMessageHandler.swift */, D29732472A5C108700827599 /* MessageEmitter.swift */, ); @@ -4993,6 +4998,7 @@ A728ADAD2934EB0300397996 /* DDW3CHTTPHeadersWriter+apiTests.m */, 3C1890132ABDE99200CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m */, A795069D2B974CAA00AC4814 /* DDSessionReplay+apiTests.m */, + 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */, ); path = ObjcAPITests; sourceTree = ""; @@ -7755,6 +7761,7 @@ files = ( 3C85D42129F7C5C900AFF894 /* WebViewTracking.swift in Sources */, D297324B2A5C108700827599 /* MessageEmitter.swift in Sources */, + 6174D6042BFB9AB600EC7469 /* WebViewTracking+objc.swift in Sources */, D29732492A5C108700827599 /* DDScriptMessageHandler.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -7848,6 +7855,7 @@ D29A9FDA29DDC6D0005C54A4 /* RUMEventFileOutputTests.swift in Sources */, D28F836829C9E71D00EF8EA2 /* DDSpanTests.swift in Sources */, 61B8BA91281812F60068AFF4 /* KronosInternetAddressTests.swift in Sources */, + 6174D6062BFB9D6400EC7469 /* DDWebViewTracking+apiTests.m in Sources */, 614798962A459AA80095CB02 /* DDTraceTests.swift in Sources */, D25085102976E30000E931C3 /* DatadogRemoteFeatureMock.swift in Sources */, 6167E6DD2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */, diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUM+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUM+apiTests.m index ab6b08ea25..4b144cc68c 100644 --- a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUM+apiTests.m +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUM+apiTests.m @@ -134,5 +134,4 @@ - (void)testDDRUMConfigurationAPI { #pragma clang diagnostic pop - @end diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDWebViewTracking+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDWebViewTracking+apiTests.m new file mode 100644 index 0000000000..f5082efaaa --- /dev/null +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDWebViewTracking+apiTests.m @@ -0,0 +1,50 @@ +/* +* 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 +@import DatadogWebViewTracking; +@import WebKit; + +@interface WebViewMock: WKWebView +@end + +@implementation WebViewMock +@end + +// MARK: - DDWebViewTracking tests + +@interface DDWebViewTracking_apiTests : XCTestCase +@end + +/* + * `WebViewTracking` APIs smoke tests - minimal assertions, mainly check if the interface is available to Objc. + */ +@implementation DDWebViewTracking_apiTests + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-value" + +- (void)testDDWebViewTrackingAPI { + WebViewMock *webView = [WebViewMock new]; + [DDWebViewTracking enableWithWebView:webView + hosts:[NSSet setWithArray:@[@"host1.com", @"host2.com"]] + logsSampleRate:100.0 + with:nil]; + [DDWebViewTracking disableWithWebView:webView]; +} + +- (void)testDDWebViewTrackingSessionReplayConfigurationAPI { + DDWebViewTrackingSessionReplayConfiguration *config = [[DDWebViewTrackingSessionReplayConfiguration alloc] init]; + XCTAssertEqual(config.privacyLevel, DDPrivacyLevelMask); + config.privacyLevel = DDPrivacyLevelAllow; + XCTAssertEqual(config.privacyLevel, DDPrivacyLevelAllow); + config.privacyLevel = DDPrivacyLevelMaskUserInput; + XCTAssertEqual(config.privacyLevel, DDPrivacyLevelMaskUserInput); +} + +#pragma clang diagnostic pop + +@end diff --git a/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift b/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift new file mode 100644 index 0000000000..cd5fc2e87a --- /dev/null +++ b/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift @@ -0,0 +1,113 @@ +/* + * 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 +#if os(tvOS) +#warning("Datadog WebView Tracking does not support tvOS") +#else +import WebKit +#endif + +@objc +public final class DDWebViewTracking: NSObject { + override private init() { } + + /// Enables SDK to correlate Datadog RUM events and Logs from the WebView with native RUM session. + /// + /// If the content loaded in WebView uses Datadog Browser SDK (`v4.2.0+`) and matches specified + /// `hosts`, web events will be correlated with the RUM session from native SDK. + /// + /// - Parameters: + /// - webView: The web-view to track. + /// - hosts: A set of hosts instrumented with Browser SDK to capture Datadog events from. + /// - logsSampleRate: The sampling rate for logs coming from the WebView. Must be a value between `0` and `100`, + /// where 0 means no logs will be sent and 100 means all will be uploaded. Default: `100`. + /// - sessionReplayConfiguration: Session Replay configuration to enable linking Web and Native replays. + /// - core: Datadog SDK core to use for tracking. + @objc + public static func enable( + webView: WKWebView, + hosts: Set = [], + logsSampleRate: Float = 100, + with configuration: DDWebViewTrackingSessionReplayConfiguration? = nil + ) { + WebViewTracking.enable( + webView: webView, + hosts: hosts, + logsSampleRate: logsSampleRate, + sessionReplayConfiguration: configuration?.toSwift + ) + } + + /// Disables Datadog iOS SDK and Datadog Browser SDK integration. + /// + /// Removes Datadog's ScriptMessageHandler and UserScript from the caller. + /// - Note: This method **must** be called when the webview can be deinitialized. + /// + /// - Parameter webView: The web-view to stop tracking. + @objc + public static func disable( + webView: WKWebView + ) { + WebViewTracking.disable(webView: webView) + } +} + +/// The Session Replay configuration to capture records coming from the web view. +/// +/// Setting the Session Replay configuration in `WebViewTracking` will enable transmitting replay data from +/// the Datadog Browser SDK installed in the web page. Datadog will then be able to combine the native +/// and web recordings in a single replay. +@objc +public final class DDWebViewTrackingSessionReplayConfiguration: NSObject { + /// Available privacy levels for content masking. + @objc + public enum DDPrivacyLevel: Int { + /// Record all content. + case allow + /// Mask all content. + case mask + /// Mask input elements, but record all other content. + case maskUserInput + + internal var toSwift: WebViewTracking.SessionReplayConfiguration.PrivacyLevel { + switch self { + case .allow: .allow + case .mask: .mask + case .maskUserInput: .maskUserInput + } + } + } + + /// The privacy level to use for the web view replay recording. + @objc public var privacyLevel: DDPrivacyLevel + + /// Creates Webview Session Replay configuration. + /// + /// - Parameters: + /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. + @objc + override public init() { + self.privacyLevel = .mask + } + + /// Creates Webview Session Replay configuration. + /// + /// - Parameters: + /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. + @objc + public init( + privacyLevel: DDPrivacyLevel + ) { + self.privacyLevel = privacyLevel + } + + internal var toSwift: WebViewTracking.SessionReplayConfiguration { + return .init( + privacyLevel: privacyLevel.toSwift + ) + } +} From 53536d562f4847840576ba11c51b6403b2d20181 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 17:41:13 +0200 Subject: [PATCH 124/153] RUM-4313 Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfafd67a66..22f93842b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - [FEATURE] `DatadogTrace` now supports OpenTelemetry. See [#1828][] +- [FEATURE] `DatadogWebViewTracking` is now available for Obj-C. See [#1854][] # 2.11.0 / 08-05-2024 @@ -662,6 +663,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1721]: https://github.com/DataDog/dd-sdk-ios/pull/1721 [#1803]: https://github.com/DataDog/dd-sdk-ios/pull/1803 [#1807]: https://github.com/DataDog/dd-sdk-ios/pull/1807 +[#1854]: https://github.com/DataDog/dd-sdk-ios/pull/1854 [#1828]: https://github.com/DataDog/dd-sdk-ios/pull/1828 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin From 1f8fc89160bbcd03ce0cce2c45289ea00d01d7fd Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 20 May 2024 17:48:32 +0200 Subject: [PATCH 125/153] RUM-4313 Help compiler infering return value --- DatadogWebViewTracking/Sources/WebViewTracking+objc.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift b/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift index cd5fc2e87a..9d554cedf6 100644 --- a/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift +++ b/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift @@ -75,9 +75,9 @@ public final class DDWebViewTrackingSessionReplayConfiguration: NSObject { internal var toSwift: WebViewTracking.SessionReplayConfiguration.PrivacyLevel { switch self { - case .allow: .allow - case .mask: .mask - case .maskUserInput: .maskUserInput + case .allow: return .allow + case .mask: return .mask + case .maskUserInput: return .maskUserInput } } } From 89135601ed1beffc8895101128c6c1787e14a638 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 14 May 2024 10:59:24 +0200 Subject: [PATCH 126/153] RUM-3588 Abstract `FatalErrorContextNotifier` and add tests --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 + .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 17 ++- DatadogRUM/Sources/Feature/RUMFeature.swift | 3 +- .../AppHangs/AppHangsMonitor.swift | 4 +- .../AppHangs/FatalAppHangsHandler.swift | 4 +- .../Instrumentation/RUMInstrumentation.swift | 2 +- .../Scopes/FatalErrorContextNotifier.swift | 14 +- .../Scopes/RUMScopeDependencies.swift | 7 +- .../RUMInstrumentationTests.swift | 14 +- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 21 +-- .../Monitor+GlobalAttributesTests.swift | 2 + .../FatalErrorContextNotifierTests.swift | 79 +++++++++++ .../Scopes/RUMSessionScopeTests.swift | 125 +++++++----------- .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 12 +- 14 files changed, 198 insertions(+), 112 deletions(-) create mode 100644 DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 01b4722d3d..8ca36402e4 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -266,6 +266,8 @@ 61133C702423993200786299 /* DatadogCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61133B82242393DE00786299 /* DatadogCore.framework */; }; 6115299725E3BEF9004F740E /* UIKitExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6115299625E3BEF9004F740E /* UIKitExtensionsTests.swift */; }; 611720D52524D9FB00634D9E /* DDURLSessionDelegate+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611720D42524D9FB00634D9E /* DDURLSessionDelegate+objc.swift */; }; + 61181CDC2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61181CDB2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift */; }; + 61181CDD2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61181CDB2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift */; }; 6121627C247D220500AC5D67 /* TracingWithLoggingIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61216279247D21FE00AC5D67 /* TracingWithLoggingIntegrationTests.swift */; }; 61216B762666DDA10089DCD1 /* LoggerConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61216B752666DDA10089DCD1 /* LoggerConfigurationTests.swift */; }; 61216B7B2667A9AE0089DCD1 /* LogsConfigurationE2ETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61216B7A2667A9AE0089DCD1 /* LogsConfigurationE2ETests.swift */; }; @@ -2250,6 +2252,7 @@ 611529A425E3DD51004F740E /* ValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValuePublisher.swift; sourceTree = ""; }; 611529AD25E3E429004F740E /* ValuePublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValuePublisherTests.swift; sourceTree = ""; }; 611720D42524D9FB00634D9E /* DDURLSessionDelegate+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DDURLSessionDelegate+objc.swift"; sourceTree = ""; }; + 61181CDB2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FatalErrorContextNotifierTests.swift; sourceTree = ""; }; 611F82022563C66100CB9BDB /* UIKitRUMViewsPredicateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitRUMViewsPredicateTests.swift; sourceTree = ""; }; 61216275247D1CD700AC5D67 /* TracingWithLoggingIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingWithLoggingIntegration.swift; sourceTree = ""; }; 61216279247D21FE00AC5D67 /* TracingWithLoggingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingWithLoggingIntegrationTests.swift; sourceTree = ""; }; @@ -4799,6 +4802,7 @@ 6198D27024C6E3B700493501 /* RUMViewScopeTests.swift */, 61494CB424C864680082C633 /* RUMResourceScopeTests.swift */, 617CD0DC24CEDDD300B0B557 /* RUMUserActionScopeTests.swift */, + 61181CDB2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift */, ); path = Scopes; sourceTree = ""; @@ -8561,6 +8565,7 @@ D23F8EB129DDCD38001CFAE8 /* RUMViewScopeTests.swift in Sources */, D224431029E977A100274EC7 /* TelemetryReceiverTests.swift in Sources */, D23F8EB229DDCD38001CFAE8 /* ValuePublisherTests.swift in Sources */, + 61181CDD2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift in Sources */, 61C713BD2A3C95AD00FA735A /* RUMInstrumentationTests.swift in Sources */, D23F8EB329DDCD38001CFAE8 /* ErrorMessageReceiverTests.swift in Sources */, 61C713C12A3C9DAD00FA735A /* RequestBuilderTests.swift in Sources */, @@ -8870,6 +8875,7 @@ D29A9FB829DDB483005C54A4 /* RUMViewScopeTests.swift in Sources */, D224430F29E9779F00274EC7 /* TelemetryReceiverTests.swift in Sources */, D29A9F9D29DDB483005C54A4 /* ValuePublisherTests.swift in Sources */, + 61181CDC2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift in Sources */, 61C713BC2A3C95AD00FA735A /* RUMInstrumentationTests.swift in Sources */, D29A9FBB29DDB483005C54A4 /* ErrorMessageReceiverTests.swift in Sources */, 61C713C02A3C9DAD00FA735A /* RequestBuilderTests.swift in Sources */, diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index 09e8841401..4632e569fd 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -699,6 +699,11 @@ func mockNoOpSessionListener() -> RUM.SessionListener { return { _, _ in } } +internal class FatalErrorContextNotifierMock: FatalErrorContextNotifying { + var sessionState: RUMSessionState? + var view: RUMViewEvent? +} + extension RUMScopeDependencies { static func mockAny() -> RUMScopeDependencies { return mockWith() @@ -718,7 +723,8 @@ extension RUMScopeDependencies { syntheticsTest: RUMSyntheticsTest? = nil, vitalsReaders: VitalsReaders? = nil, onSessionStart: @escaping RUM.SessionListener = mockNoOpSessionListener(), - viewCache: ViewCache = ViewCache() + viewCache: ViewCache = ViewCache(), + fatalErrorContext: FatalErrorContextNotifying = FatalErrorContextNotifierMock() ) -> RUMScopeDependencies { return RUMScopeDependencies( featureScope: featureScope, @@ -734,7 +740,8 @@ extension RUMScopeDependencies { syntheticsTest: syntheticsTest, vitalsReaders: vitalsReaders, onSessionStart: onSessionStart, - viewCache: viewCache + viewCache: viewCache, + fatalErrorContext: fatalErrorContext ) } @@ -752,7 +759,8 @@ extension RUMScopeDependencies { syntheticsTest: RUMSyntheticsTest? = nil, vitalsReaders: VitalsReaders? = nil, onSessionStart: RUM.SessionListener? = nil, - viewCache: ViewCache? = nil + viewCache: ViewCache? = nil, + fatalErrorContext: FatalErrorContextNotifying? = nil ) -> RUMScopeDependencies { return RUMScopeDependencies( featureScope: self.featureScope, @@ -768,7 +776,8 @@ extension RUMScopeDependencies { syntheticsTest: syntheticsTest ?? self.syntheticsTest, vitalsReaders: vitalsReaders ?? self.vitalsReaders, onSessionStart: onSessionStart ?? self.onSessionStart, - viewCache: viewCache ?? self.viewCache + viewCache: viewCache ?? self.viewCache, + fatalErrorContext: fatalErrorContext ?? self.fatalErrorContext ) } } diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index d168792d75..3cfe69739b 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -71,7 +71,8 @@ internal final class RUMFeature: DatadogRemoteFeature { ) }, onSessionStart: configuration.onSessionStart, - viewCache: ViewCache() + viewCache: ViewCache(), + fatalErrorContext: FatalErrorContextNotifier(messageBus: featureScope) ) self.monitor = Monitor( diff --git a/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsMonitor.swift b/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsMonitor.swift index d053d07e26..12c4b601af 100644 --- a/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsMonitor.swift +++ b/DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsMonitor.swift @@ -31,7 +31,7 @@ internal final class AppHangsMonitor { appHangThreshold: TimeInterval, observedQueue: DispatchQueue, backtraceReporter: BacktraceReporting, - fatalErrorContext: FatalErrorContextNotifier, + fatalErrorContext: FatalErrorContextNotifying, dateProvider: DateProvider, processID: UUID ) { @@ -53,7 +53,7 @@ internal final class AppHangsMonitor { init( featureScope: FeatureScope, watchdogThread: AppHangsObservingThread, - fatalErrorContext: FatalErrorContextNotifier, + fatalErrorContext: FatalErrorContextNotifying, processID: UUID, dateProvider: DateProvider ) { diff --git a/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift b/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift index d8a2a46323..fbe4152658 100644 --- a/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift +++ b/DatadogRUM/Sources/Instrumentation/AppHangs/FatalAppHangsHandler.swift @@ -11,7 +11,7 @@ internal final class FatalAppHangsHandler { /// RUM feature scope. private let featureScope: FeatureScope /// RUM context for fatal App Hangs monitoring. - private let fatalErrorContext: FatalErrorContextNotifier + private let fatalErrorContext: FatalErrorContextNotifying /// An ID of the current process. private let processID: UUID /// Device date provider. @@ -19,7 +19,7 @@ internal final class FatalAppHangsHandler { init( featureScope: FeatureScope, - fatalErrorContext: FatalErrorContextNotifier, + fatalErrorContext: FatalErrorContextNotifying, processID: UUID, dateProvider: DateProvider ) { diff --git a/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift b/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift index e295fabf5f..13fc7b0bf4 100644 --- a/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift +++ b/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift @@ -47,7 +47,7 @@ internal final class RUMInstrumentation: RUMCommandPublisher { mainQueue: DispatchQueue, dateProvider: DateProvider, backtraceReporter: BacktraceReporting, - fatalErrorContext: FatalErrorContextNotifier, + fatalErrorContext: FatalErrorContextNotifying, processID: UUID ) { // Always create views handler (we can't know if it will be used by SwiftUI instrumentation) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/FatalErrorContextNotifier.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/FatalErrorContextNotifier.swift index b14132ba39..d8a88ac03d 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/FatalErrorContextNotifier.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/FatalErrorContextNotifier.swift @@ -7,9 +7,19 @@ import Foundation import DatadogInternal -/// Manages RUM information necessary for building context for fatal errors such as Crashes or Fatal App Hangs. +/// A type managing RUM information necessary for building context of fatal errors such as Crashes or Fatal App Hangs. +internal protocol FatalErrorContextNotifying: AnyObject { + /// The state of the current RUM session. + /// Can be `nil` if no session was yet started. Never gets `nil` after starting first session. + var sessionState: RUMSessionState? { set get } + /// The active RUM view in current session. + /// Can be `nil` if no view is yet started. Will become `nil` if view was stopped without starting the new one. + var view: RUMViewEvent? { set get } +} + +/// Manages RUM information necessary for building context of fatal errors such as Crashes or Fatal App Hangs. /// It tracks value changes and notifies updates on message bus. -internal final class FatalErrorContextNotifier { +internal final class FatalErrorContextNotifier: FatalErrorContextNotifying { /// Message bus interface to send context updates to Crash Reporting. private let messageBus: MessageSending diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift index 38add82707..d325ac39ec 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift @@ -44,7 +44,7 @@ internal struct RUMScopeDependencies { let onSessionStart: RUM.SessionListener? let viewCache: ViewCache /// The RUM context necessary for tracking fatal errors like Crashes or fatal App Hangs. - let fatalErrorContext: FatalErrorContextNotifier + let fatalErrorContext: FatalErrorContextNotifying /// Telemetry endpoint. let telemetry: Telemetry let sessionType: RUMSessionType @@ -63,7 +63,8 @@ internal struct RUMScopeDependencies { syntheticsTest: RUMSyntheticsTest?, vitalsReaders: VitalsReaders?, onSessionStart: RUM.SessionListener?, - viewCache: ViewCache + viewCache: ViewCache, + fatalErrorContext: FatalErrorContextNotifying ) { self.featureScope = featureScope self.rumApplicationID = rumApplicationID @@ -79,7 +80,7 @@ internal struct RUMScopeDependencies { self.vitalsReaders = vitalsReaders self.onSessionStart = onSessionStart self.viewCache = viewCache - self.fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + self.fatalErrorContext = fatalErrorContext self.telemetry = featureScope.telemetry if ciTest != nil { diff --git a/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift b/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift index 476587d873..9cb84d1fb9 100644 --- a/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift +++ b/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift @@ -23,7 +23,7 @@ class RUMInstrumentationTests: XCTestCase { mainQueue: .main, dateProvider: SystemDateProvider(), backtraceReporter: BacktraceReporterMock(), - fatalErrorContext: .mockAny(), + fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny() ) @@ -48,7 +48,7 @@ class RUMInstrumentationTests: XCTestCase { mainQueue: .main, dateProvider: SystemDateProvider(), backtraceReporter: BacktraceReporterMock(), - fatalErrorContext: .mockAny(), + fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny() ) @@ -70,7 +70,7 @@ class RUMInstrumentationTests: XCTestCase { mainQueue: .main, dateProvider: SystemDateProvider(), backtraceReporter: BacktraceReporterMock(), - fatalErrorContext: .mockAny(), + fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny() ) @@ -95,7 +95,7 @@ class RUMInstrumentationTests: XCTestCase { mainQueue: .main, dateProvider: SystemDateProvider(), backtraceReporter: BacktraceReporterMock(), - fatalErrorContext: .mockAny(), + fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny() ) @@ -116,7 +116,7 @@ class RUMInstrumentationTests: XCTestCase { mainQueue: .main, dateProvider: SystemDateProvider(), backtraceReporter: BacktraceReporterMock(), - fatalErrorContext: .mockAny(), + fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny() ) @@ -137,7 +137,7 @@ class RUMInstrumentationTests: XCTestCase { mainQueue: .main, dateProvider: SystemDateProvider(), backtraceReporter: BacktraceReporterMock(), - fatalErrorContext: .mockAny(), + fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny() ) @@ -158,7 +158,7 @@ class RUMInstrumentationTests: XCTestCase { mainQueue: .main, dateProvider: SystemDateProvider(), backtraceReporter: BacktraceReporterMock(), - fatalErrorContext: .mockAny(), + fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny() ) let subscriber = RUMCommandSubscriberMock() diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index 057b72170d..895f090256 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -745,12 +745,9 @@ func mockNoOpSessionListener() -> RUM.SessionListener { return { _, _ in } } -extension FatalErrorContextNotifier: AnyMockable { - public static func mockAny() -> FatalErrorContextNotifier { - return FatalErrorContextNotifier( - messageBus: NOPFeatureScope() - ) - } +internal class FatalErrorContextNotifierMock: FatalErrorContextNotifying { + var sessionState: RUMSessionState? + var view: RUMViewEvent? } extension RUMScopeDependencies { @@ -772,7 +769,8 @@ extension RUMScopeDependencies { syntheticsTest: RUMSyntheticsTest? = nil, vitalsReaders: VitalsReaders? = nil, onSessionStart: @escaping RUM.SessionListener = mockNoOpSessionListener(), - viewCache: ViewCache = ViewCache() + viewCache: ViewCache = ViewCache(), + fatalErrorContext: FatalErrorContextNotifying = FatalErrorContextNotifierMock() ) -> RUMScopeDependencies { return RUMScopeDependencies( featureScope: featureScope, @@ -788,7 +786,8 @@ extension RUMScopeDependencies { syntheticsTest: syntheticsTest, vitalsReaders: vitalsReaders, onSessionStart: onSessionStart, - viewCache: viewCache + viewCache: viewCache, + fatalErrorContext: fatalErrorContext ) } @@ -806,7 +805,8 @@ extension RUMScopeDependencies { syntheticsTest: RUMSyntheticsTest? = nil, vitalsReaders: VitalsReaders? = nil, onSessionStart: RUM.SessionListener? = nil, - viewCache: ViewCache? = nil + viewCache: ViewCache? = nil, + fatalErrorContext: FatalErrorContextNotifying? = nil ) -> RUMScopeDependencies { return RUMScopeDependencies( featureScope: self.featureScope, @@ -822,7 +822,8 @@ extension RUMScopeDependencies { syntheticsTest: syntheticsTest ?? self.syntheticsTest, vitalsReaders: vitalsReaders ?? self.vitalsReaders, onSessionStart: onSessionStart ?? self.onSessionStart, - viewCache: viewCache ?? self.viewCache + viewCache: viewCache ?? self.viewCache, + fatalErrorContext: fatalErrorContext ?? self.fatalErrorContext ) } } diff --git a/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift index 6d1d46f6cb..ac9db1e10f 100644 --- a/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift @@ -483,6 +483,8 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(viewAfterSecondTiming.attribute(forKey: "attribute2"), "value2") XCTAssertEqual(viewAfterSecondTiming.attribute(forKey: "attribute2"), "value2") } + + // MARK: - Updating Fatal Error Context } // MARK: - Helpers diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift new file mode 100644 index 0000000000..6f855ed1f0 --- /dev/null +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift @@ -0,0 +1,79 @@ +/* + * 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 +import TestUtilities +@testable import DatadogRUM + +class FatalErrorContextNotifierTests: XCTestCase { + private let featureScope = FeatureScopeMock() + + // MARK: - Changing Session State + + func testWhenSessionStateIsSet_itSendsSessionStateMessage() throws { + // Given + let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + let newSessionState: RUMSessionState = .mockRandom() + + // When + fatalErrorContext.sessionState = newSessionState + + // Then + let messages = featureScope.messagesSent() + XCTAssertEqual(messages.count, 1) + let sessionStateMessage = try XCTUnwrap(messages.lastBaggage(withKey: RUMBaggageKeys.sessionState)) + XCTAssertEqual(newSessionState, try sessionStateMessage.decode()) + } + + func testWhenSessionStateIsReset_itDoesNotSendNextSessionStateMessage() throws { + // Given + let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + let originalSessionState: RUMSessionState = .mockRandom() + fatalErrorContext.sessionState = originalSessionState + + // When + fatalErrorContext.sessionState = nil + + // Then + let messages = featureScope.messagesSent() + XCTAssertEqual(messages.count, 1) + let sessionStateMessage = try XCTUnwrap(messages.lastBaggage(withKey: RUMBaggageKeys.sessionState)) + XCTAssertEqual(originalSessionState, try sessionStateMessage.decode()) + } + + // MARK: - Changing View State + + func testWhenViewIsSet_itSendsViewEventMessage() throws { + // Given + let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + let newViewEvent: RUMViewEvent = .mockRandom() + + // When + fatalErrorContext.view = newViewEvent + + // Then + let messages = featureScope.messagesSent() + XCTAssertEqual(messages.count, 1) + let viewEventMessage = try XCTUnwrap(messages.lastBaggage(withKey: RUMBaggageKeys.viewEvent)) + DDAssertJSONEqual(newViewEvent, try viewEventMessage.decode(type: RUMViewEvent.self)) + } + + func testWhenViewIsReset_itSendsViewResetMessage() throws { + // Given + let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + fatalErrorContext.view = .mockRandom() + + // When + fatalErrorContext.view = nil + + // Then + let messages = featureScope.messagesSent() + XCTAssertEqual(messages.count, 2) + let viewResetMessage = try XCTUnwrap(messages.lastBaggage(withKey: RUMBaggageKeys.viewReset)) + XCTAssertTrue(try viewResetMessage.decode(type: Bool.self)) + } +} diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift index 48d777a8ad..fef13b0671 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift @@ -336,10 +336,11 @@ class RUMSessionScopeTests: XCTestCase { XCTAssertEqual(scope.viewScopes.count, 0) } - // MARK: - Sending Messages Over Message Bus + // MARK: - Updating Fatal Error Context - func testWhenSessionScopeIsCreated_itSendsSessionStateMessage() throws { + func testWhenSessionScopeIsCreated_itUpdatesFatalErrorContextWithSessionState() throws { let featureScope = FeatureScopeMock() + let fatalErrorContext = FatalErrorContextNotifierMock() let randomIsInitialSession: Bool = .mockRandom() let randomIsReplayBeingRecorded: Bool? = .mockRandom() @@ -349,7 +350,8 @@ class RUMSessionScopeTests: XCTestCase { parent: parent, dependencies: .mockWith( featureScope: featureScope, - sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll() // no matter if sampled or not + sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter if sampled or not, + fatalErrorContext: fatalErrorContext ), hasReplay: randomIsReplayBeingRecorded ) @@ -361,13 +363,13 @@ class RUMSessionScopeTests: XCTestCase { hasTrackedAnyView: false, didStartWithReplay: randomIsReplayBeingRecorded ) - let baggageSent = try XCTUnwrap(featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.sessionState)) - let sessionStateSent: RUMSessionState = try baggageSent.decode() - XCTAssertEqual(sessionStateSent, expectedSessionState, "It must send 'session state' message") + let actualSessionState = try XCTUnwrap(fatalErrorContext.sessionState) + XCTAssertEqual(actualSessionState, expectedSessionState) } - func testWhenSessionScopeStartsAnyView_itSendsSessionStateMessage() throws { + func testWhenSessionScopeStartsAnyView_itUpdatesFatalErrorContextWithSessionState() throws { let featureScope = FeatureScopeMock() + let fatalErrorContext = FatalErrorContextNotifierMock() let randomIsInitialSession: Bool = .mockRandom() let randomIsReplayBeingRecorded: Bool? = .mockRandom() @@ -377,7 +379,10 @@ class RUMSessionScopeTests: XCTestCase { isInitialSession: randomIsInitialSession, parent: parent, startTime: sessionStartTime, - dependencies: .mockWith(featureScope: featureScope), + dependencies: .mockWith( + featureScope: featureScope, + fatalErrorContext: fatalErrorContext + ), hasReplay: randomIsReplayBeingRecorded ) @@ -393,20 +398,23 @@ class RUMSessionScopeTests: XCTestCase { hasTrackedAnyView: true, didStartWithReplay: randomIsReplayBeingRecorded ) - let baggageSent = try XCTUnwrap(featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.sessionState)) - let sessionStateSent: RUMSessionState = try baggageSent.decode() - XCTAssertEqual(sessionStateSent, expectedSessionState, "It must send 'session state' message") + let actualSessionState = try XCTUnwrap(fatalErrorContext.sessionState) + XCTAssertEqual(actualSessionState, expectedSessionState) } - func testWhenSessionScopeHasNoActiveView_itSendsViewEventMessages() throws { + func testWhenSessionScopeHasNoActiveView_itUpdatesFatalErrorContextWithView() throws { let featureScope = FeatureScopeMock() + let fatalErrorContext = FatalErrorContextNotifierMock() // Given let sessionStartTime = Date() let scope: RUMSessionScope = .mockWith( parent: parent, startTime: sessionStartTime, - dependencies: .mockWith(featureScope: featureScope) + dependencies: .mockWith( + featureScope: featureScope, + fatalErrorContext: fatalErrorContext + ) ) // When @@ -414,19 +422,42 @@ class RUMSessionScopeTests: XCTestCase { _ = scope.process(command: command, context: context, writer: writer) // Then - let viewEventBaggage: RUMViewEvent = try XCTUnwrap( - featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.viewEvent)?.decode() - ) - XCTAssertEqual(viewEventBaggage.view.name, "ActiveView", "It must send 'view event' message") + XCTAssertEqual(fatalErrorContext.view?.view.name, "ActiveView") // When _ = scope.process(command: RUMStopViewCommand.mockWith(time: sessionStartTime.addingTimeInterval(1), identity: .mockViewIdentifier()), context: context, writer: writer) // Then - let viewResetBaggage: Bool = try XCTUnwrap( - featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.viewReset)?.decode() + XCTAssertNil(fatalErrorContext.view) + } + + func testWhenSessionEnds_itUpdatesFatalErrorContextWithView() throws { + let featureScope = FeatureScopeMock() + let fatalErrorContext = FatalErrorContextNotifierMock() + + // Given + let scope: RUMSessionScope = .mockWith( + parent: parent, + startTime: Date(), + dependencies: .mockWith( + featureScope: featureScope, + fatalErrorContext: fatalErrorContext + ) ) - XCTAssertTrue(viewResetBaggage, "It must send 'view reset' message") + + let command = RUMStartViewCommand.mockWith(time: Date(), identity: .mockViewIdentifier(), name: "ActiveView") + + // When + _ = scope.process(command: command, context: context, writer: writer) + + // Then + XCTAssertEqual(fatalErrorContext.view?.view.name, "ActiveView") + + // When + _ = scope.process(command: RUMStopSessionCommand.mockWith(time: Date()), context: context, writer: writer) + + // Then + XCTAssertNil(fatalErrorContext.view) } // MARK: - Stopping Sessions @@ -557,60 +588,6 @@ class RUMSessionScopeTests: XCTestCase { XCTAssertFalse(result) } - func testWhenScopeEnded_itSendsViewEventMessages() throws { - let featureScope = FeatureScopeMock() - - // Given - let scope: RUMSessionScope = .mockWith( - parent: parent, - startTime: Date(), - dependencies: .mockWith(featureScope: featureScope) - ) - - let command = RUMStartViewCommand.mockWith(time: Date(), identity: .mockViewIdentifier(), name: "ActiveView") - // When - _ = scope.process(command: command, context: context, writer: writer) - // Then - let viewEventBaggage: RUMViewEvent = try XCTUnwrap( - featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.viewEvent)?.decode() - ) - XCTAssertEqual(viewEventBaggage.view.name, "ActiveView", "It must send 'view event' message") - - // When - _ = scope.process(command: RUMStopSessionCommand.mockWith(time: Date()), context: context, writer: writer) - - // Then - let viewResetBaggage: Bool = try XCTUnwrap( - featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.viewReset)?.decode() - ) - XCTAssertTrue(viewResetBaggage, "It must send 'view reset' message") - } - - func testWhenScopeEnded_itDoesNotSendViewResetMessage() { - let featureScope = FeatureScopeMock() - - // Given - let scope: RUMSessionScope = .mockWith( - parent: parent, - startTime: Date(), - dependencies: .mockWith(featureScope: featureScope) - ) - - let startViewCommand = RUMStartViewCommand.mockWith(time: Date(), identity: .mockViewIdentifier()) - _ = scope.process(command: startViewCommand, context: context, writer: writer) - let startResourceCommand = RUMStartResourceCommand.mockWith(time: Date()) - _ = scope.process(command: startResourceCommand, context: context, writer: writer) - - // When - _ = scope.process(command: RUMStopSessionCommand.mockWith(time: Date()), context: context, writer: writer) - let stopResourceCommand = RUMStopResourceCommand.mockWith(resourceKey: startResourceCommand.resourceKey, time: Date()) - _ = scope.process(command: stopResourceCommand, context: context, writer: writer) - - // Then - let viewResetMessages = featureScope.messagesSent().filter { $0.asBaggage?.key == RUMBaggageKeys.viewReset } - XCTAssertEqual(viewResetMessages.count, 1, "It must send only one 'view reset' message") - } - // MARK: - Usage func testGivenSessionWithNoActiveScope_whenReceivingRUMCommandOtherThanKeepSessionAliveCommand_itLogsWarning() throws { diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift index b515d39f98..33bd8a0ecb 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -2551,16 +2551,17 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(event.dd.documentVersion, 1, "It should record only one view update") } - // MARK: - Sending Messages Over Message Bus + // MARK: - Updating Fatal Error Context - func testWhenViewIsStarted_itSendsViewEventMessages() throws { + func testWhenViewIsStarted_itUpdatesFatalErrorContextWithView() throws { let featureScope = FeatureScopeMock() + let fatalErrorContext = FatalErrorContextNotifierMock() // Given let scope = RUMViewScope( isInitialView: .mockRandom(), parent: parent, - dependencies: .mockWith(featureScope: featureScope), + dependencies: .mockWith(featureScope: featureScope, fatalErrorContext: fatalErrorContext), identity: .mockViewIdentifier(), path: "UIViewController", name: "ViewController", @@ -2581,8 +2582,7 @@ class RUMViewScopeTests: XCTestCase { // Then let rumViewWritten = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last, "It should send view event") - let baggageSent = try XCTUnwrap(featureScope.messagesSent().firstBaggage(withKey: RUMBaggageKeys.viewEvent)) - let rumViewSent: RUMViewEvent = try baggageSent.decode() - DDAssertReflectionEqual(rumViewSent, rumViewWritten, "It must sent written event over message bus") + let rumViewInFatalErrorContext = try XCTUnwrap(fatalErrorContext.view) + DDAssertReflectionEqual(rumViewWritten, rumViewInFatalErrorContext, "It must update fatal error context with the view event written") } } From b0768424ab4896ada94d611c1757c714f4d730ed Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 14 May 2024 16:05:32 +0200 Subject: [PATCH 127/153] RUM-3588 Update RUM view in CrashContext after attributes change but do not send view update event (to avoid performance penalties such as adding backpressure to writer queue, increasing RUM directory size with more event writes and effectively extending the backlog of uploadable files for RUM) --- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 6 ++ .../Sources/RUMMonitor/RUMCommand.swift | 11 ++- .../RUMMonitor/Scopes/RUMViewScope.swift | 8 +- .../Monitor+GlobalAttributesTests.swift | 96 +++++++++++++++++-- 4 files changed, 113 insertions(+), 8 deletions(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index 198547a434..784cd50ae4 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -204,10 +204,16 @@ extension Monitor: RUMMonitorProtocol { func addAttribute(forKey key: AttributeKey, value: AttributeValue) { attributes[key] = value + process( + command: RUMUpdateViewAttributesCommand(time: dateProvider.now) + ) } func removeAttribute(forKey key: AttributeKey) { attributes[key] = nil + process( + command: RUMUpdateViewAttributesCommand(time: dateProvider.now) + ) } // MARK: - session diff --git a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift index ddee818abc..910ef5fb19 100644 --- a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift +++ b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift @@ -14,7 +14,7 @@ internal protocol RUMCommand { /// Attributes associated with the command. var attributes: [AttributeKey: AttributeValue] { set get } /// Whether or not receiving this command should start the "Background" view if no view is active - /// and ``Datadog.Configuration.Builder.trackBackgroundEvents(_:)`` is enabled. + /// and ``RUM.Configuration.trackBackgroundEvents`` is enabled. var canStartBackgroundView: Bool { get } /// Whether or not this command is considered a user intaraction var isUserInteraction: Bool { get } @@ -242,6 +242,15 @@ internal struct RUMAddViewTimingCommand: RUMCommand, RUMViewScopePropagatableAtt let timingName: String } +internal struct RUMUpdateViewAttributesCommand: RUMCommand { + let canStartBackgroundView = false + let isUserInteraction = false + + var time: Date + var attributes: [AttributeKey: AttributeValue] = [:] +} + + // MARK: - RUM Resource related commands internal protocol RUMResourceCommand: RUMCommand { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index 031fa23332..b030c10056 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -196,6 +196,8 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { case let command as RUMAddViewTimingCommand where isActiveView: customTimings[command.timingName] = command.time.timeIntervalSince(viewStartTime).toInt64Nanoseconds needsViewUpdate = true + case _ as RUMUpdateViewAttributesCommand where isActiveView: + needsViewUpdate = true // Resource commands case let command as RUMStartResourceCommand where isActiveView: @@ -437,6 +439,8 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { // RUMM-3133 Don't override View attributes with commands that are not view related. if command is RUMViewScopePropagatableAttributes { attributes.merge(rumCommandAttributes: command.attributes) + } else if command is RUMUpdateViewAttributesCommand { + attributes = command.attributes } let isCrash = (command as? RUMErrorCommand).map { $0.isCrash ?? false } ?? false @@ -543,7 +547,9 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { ) if let event = dependencies.eventBuilder.build(from: viewEvent) { - writer.write(value: event, metadata: event.metadata()) + if !(command is RUMUpdateViewAttributesCommand) { + writer.write(value: event, metadata: event.metadata()) + } // Update fatal error context with recent RUM view: dependencies.fatalErrorContext.view = event diff --git a/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift index ac9db1e10f..0061107817 100644 --- a/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift @@ -62,7 +62,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { // Then let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) XCTAssertEqual(lastView.view.name, RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName) - XCTAssertNil(lastView.attribute(forKey: "attribute")) + XCTAssertEqual(lastView.attribute(forKey: "attribute"), "value") } func testAddingGlobalAttributesAfterSDKInit_thenRemovingAttribute() throws { @@ -162,7 +162,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { // Then let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) XCTAssertEqual(lastView.view.name, "View") - XCTAssertNil(lastView.attribute(forKey: "attribute")) + XCTAssertEqual(lastView.attribute(forKey: "attribute"), "value") } func testAddingGlobalAttributesAfterViewIsStarted_thenRemovingAttribute() throws { @@ -255,7 +255,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(lastView1.attribute(forKey: "attribute2"), "value2") XCTAssertEqual(lastView1.attribute(forKey: "attribute3"), "value3") - XCTAssertEqual(lastView2.attribute(forKey: "attribute1"), "value1") + XCTAssertNil(lastView2.attribute(forKey: "attribute1")) XCTAssertEqual(lastView2.attribute(forKey: "attribute2"), "value2") XCTAssertEqual(lastView2.attribute(forKey: "attribute3"), "value3") @@ -394,7 +394,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(lastView.view.error.count, 1) XCTAssertEqual(lastView.view.action.count, 1) XCTAssertEqual(lastView.view.resource.count, 1) - XCTAssertNil(lastView.attribute(forKey: "attribute")) + XCTAssertEqual(lastView.attribute(forKey: "attribute"), "value") XCTAssertEqual(errorEvent.context?.contextInfo["attribute"] as? String, "value") XCTAssertEqual(actionEvent.context?.contextInfo["attribute"] as? String, "value") XCTAssertEqual(resourceEvent.context?.contextInfo["attribute"] as? String, "value") @@ -424,7 +424,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(lastView.view.action.count, 1) XCTAssertEqual(lastView.view.resource.count, 1) XCTAssertNil(lastView.attribute(forKey: "attribute1")) - XCTAssertNil(lastView.attribute(forKey: "attribute2")) + XCTAssertEqual(lastView.attribute(forKey: "attribute2"), "value2") XCTAssertNil(errorEvent.context?.contextInfo["attribute1"]) XCTAssertEqual(errorEvent.context?.contextInfo["attribute2"] as? String, "value2") XCTAssertNil(actionEvent.context?.contextInfo["attribute1"]) @@ -484,7 +484,91 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(viewAfterSecondTiming.attribute(forKey: "attribute2"), "value2") } - // MARK: - Updating Fatal Error Context + // MARK: - Updating Fatal Error Context With Global Attributes + + func testGivenSDKInitialized_whenGlobalAttributeIsAdded_thenFatalErrorContextIsUpdatedWithNewView() throws { + let fatalErrorContext = FatalErrorContextNotifierMock() + + // Given + monitor = Monitor( + dependencies: .mockWith(featureScope: featureScope, fatalErrorContext: fatalErrorContext), + dateProvider: SystemDateProvider() + ) + monitor.notifySDKInit() + + // When + monitor.addAttribute(forKey: "attribute", value: "value") + + // Then + let fatalErrorContextView = try XCTUnwrap(fatalErrorContext.view) + XCTAssertEqual(fatalErrorContextView.view.name, RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName) + XCTAssertEqual(fatalErrorContextView.attribute(forKey: "attribute"), "value") + } + + func testGivenSDKInitialized_whenGlobalAttributesAreAddedAndRemoved_thenFatalErrorContextIsUpdatedWithNewView() throws { + let fatalErrorContext = FatalErrorContextNotifierMock() + + // Given + monitor = Monitor( + dependencies: .mockWith(featureScope: featureScope, fatalErrorContext: fatalErrorContext), + dateProvider: SystemDateProvider() + ) + monitor.notifySDKInit() + + // When + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + monitor.removeAttribute(forKey: "attribute1") + + // Then + let fatalErrorContextView = try XCTUnwrap(fatalErrorContext.view) + XCTAssertEqual(fatalErrorContextView.view.name, RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName) + XCTAssertNil(fatalErrorContextView.attribute(forKey: "attribute1")) + XCTAssertEqual(fatalErrorContextView.attribute(forKey: "attribute2"), "value2") + } + + func testGivenSDKInitializedAndViewStarted_whenGlobalAttributeIsAdded_thenFatalErrorContextIsUpdatedWithNewView() throws { + let fatalErrorContext = FatalErrorContextNotifierMock() + + // Given + monitor = Monitor( + dependencies: .mockWith(featureScope: featureScope, fatalErrorContext: fatalErrorContext), + dateProvider: SystemDateProvider() + ) + monitor.notifySDKInit() + monitor.startView(key: "View") + + // When + monitor.addAttribute(forKey: "attribute", value: "value") + + // Then + let fatalErrorContextView = try XCTUnwrap(fatalErrorContext.view) + XCTAssertEqual(fatalErrorContextView.view.name, "View") + XCTAssertEqual(fatalErrorContextView.attribute(forKey: "attribute"), "value") + } + + func testGivenSDKInitializedAndViewStarted_whenGlobalAttributesAreAddedAndRemoved_thenFatalErrorContextIsUpdatedWithNewView() throws { + let fatalErrorContext = FatalErrorContextNotifierMock() + + // Given + monitor = Monitor( + dependencies: .mockWith(featureScope: featureScope, fatalErrorContext: fatalErrorContext), + dateProvider: SystemDateProvider() + ) + monitor.notifySDKInit() + monitor.startView(key: "View") + + // When + monitor.addAttribute(forKey: "attribute1", value: "value1") + monitor.addAttribute(forKey: "attribute2", value: "value2") + monitor.removeAttribute(forKey: "attribute1") + + // Then + let fatalErrorContextView = try XCTUnwrap(fatalErrorContext.view) + XCTAssertEqual(fatalErrorContextView.view.name, "View") + XCTAssertNil(fatalErrorContextView.attribute(forKey: "attribute1")) + XCTAssertEqual(fatalErrorContextView.attribute(forKey: "attribute2"), "value2") + } } // MARK: - Helpers From 3de34eefd59e80ecd424840b0c7c5a905643d415 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 14 May 2024 17:00:44 +0200 Subject: [PATCH 128/153] RUM-3588 Fix linter --- DatadogRUM/Sources/RUMMonitor/RUMCommand.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift index 910ef5fb19..8a5dff7b4e 100644 --- a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift +++ b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift @@ -250,7 +250,6 @@ internal struct RUMUpdateViewAttributesCommand: RUMCommand { var attributes: [AttributeKey: AttributeValue] = [:] } - // MARK: - RUM Resource related commands internal protocol RUMResourceCommand: RUMCommand { From dbade4aec61ea24f62548f5990d5d43cacaee04b Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 16 May 2024 10:54:00 +0200 Subject: [PATCH 129/153] RUM-3588 Send global RUM attributes direclty to CrashContext An alternative approach was sending attributes as part of last RUMView, however causing view updates on every attribute change is producing significant backpressure on RUM queue. --- Datadog/Datadog.xcodeproj/project.pbxproj | 24 +- .../CrashContextProviderTests.swift | 315 +++++++++++++----- .../CrashContext/CrashContextTests.swift | 34 ++ .../Mocks/CrashReportingFeatureMocks.swift | 19 +- .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 1 + .../Sources/CrashContext/CrashContext.swift | 23 +- .../CrashContext/CrashContextProvider.swift | 21 ++ .../Models/RUM/GlobalRUMAttributes.swift | 28 ++ DatadogLogs/Tests/LogsTests.swift | 2 +- .../Sources/Feature/RUMBaggageKeys.swift | 4 + DatadogRUM/Sources/RUMMonitor/Monitor.swift | 20 +- .../Sources/RUMMonitor/RUMCommand.swift | 8 - .../Scopes/FatalErrorContextNotifier.swift | 15 + .../RUMMonitor/Scopes/RUMViewScope.swift | 8 +- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 1 + .../Monitor+GlobalAttributesTests.swift | 68 +--- .../FatalErrorContextNotifierTests.swift | 17 + TestUtilities/Helpers/DDAssert.swift | 7 + 18 files changed, 433 insertions(+), 182 deletions(-) create mode 100644 DatadogInternal/Sources/Models/RUM/GlobalRUMAttributes.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 8ca36402e4..e9c6df8f01 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -469,6 +469,8 @@ 619CE75F2A458CE1005588CB /* TraceConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619CE75D2A458CE1005588CB /* TraceConfigurationTests.swift */; }; 619CE7612A458D66005588CB /* TraceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619CE7602A458D66005588CB /* TraceTests.swift */; }; 619CE7622A458D66005588CB /* TraceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619CE7602A458D66005588CB /* TraceTests.swift */; }; + 619F5CEC2BF5089C004BFE70 /* GlobalRUMAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619F5CEA2BF5089B004BFE70 /* GlobalRUMAttributes.swift */; }; + 619F5CED2BF508A4004BFE70 /* GlobalRUMAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619F5CEA2BF5089B004BFE70 /* GlobalRUMAttributes.swift */; }; 61A1A44929643254007909E7 /* DatadogCoreProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A1A44829643254007909E7 /* DatadogCoreProxy.swift */; }; 61A1A44A29643254007909E7 /* DatadogCoreProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A1A44829643254007909E7 /* DatadogCoreProxy.swift */; }; 61A2CC212A443D330000FF25 /* DDRUMConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A2CC202A443D330000FF25 /* DDRUMConfigurationTests.swift */; }; @@ -2450,6 +2452,7 @@ 61993672265BC029009D7EA8 /* E2ETests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = E2ETests.xcconfig; sourceTree = ""; }; 619CE75D2A458CE1005588CB /* TraceConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceConfigurationTests.swift; sourceTree = ""; }; 619CE7602A458D66005588CB /* TraceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceTests.swift; sourceTree = ""; }; + 619F5CEA2BF5089B004BFE70 /* GlobalRUMAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalRUMAttributes.swift; sourceTree = ""; }; 61A1A44829643254007909E7 /* DatadogCoreProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogCoreProxy.swift; sourceTree = ""; }; 61A2CC202A443D330000FF25 /* DDRUMConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDRUMConfigurationTests.swift; sourceTree = ""; }; 61A2CC232A44454D0000FF25 /* DDRUMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDRUMTests.swift; sourceTree = ""; }; @@ -4641,6 +4644,8 @@ 6167E6DF2B81203A00C3CA2D /* Models */ = { isa = PBXGroup; children = ( + 619F5CEB2BF5089B004BFE70 /* RUM */, + D25C834D2B88A261008E73B1 /* WebViewTracking */, 6167E6E02B81204B00C3CA2D /* CrashReporting */, ); path = Models; @@ -4956,6 +4961,14 @@ path = Reading; sourceTree = ""; }; + 619F5CEB2BF5089B004BFE70 /* RUM */ = { + isa = PBXGroup; + children = ( + 619F5CEA2BF5089B004BFE70 /* GlobalRUMAttributes.swift */, + ); + path = RUM; + sourceTree = ""; + }; 61AE74112AD6EE7E008DB9BB /* Matchers */ = { isa = PBXGroup; children = ( @@ -5551,14 +5564,6 @@ path = Swizzling; sourceTree = ""; }; - D21A94F02B838CCD00AC4256 /* Models */ = { - isa = PBXGroup; - children = ( - D25C834D2B88A261008E73B1 /* WebViewTracking */, - ); - path = Models; - sourceTree = ""; - }; D21AE6BA29E5ED7D0064BF29 /* Telemetry */ = { isa = PBXGroup; children = ( @@ -5606,7 +5611,6 @@ D23039D1298D5235001A1FA3 /* Upload */, D2A783D329A53049003B03BB /* Utils */, D2160CE229C0DFED00FAA9A5 /* Swizzling */, - D21A94F02B838CCD00AC4256 /* Models */, D2EBEE1D29BA15BC00B15732 /* NetworkInstrumentation */, ); name = DatadogInternal; @@ -8373,6 +8377,7 @@ D23039E9298D5236001A1FA3 /* TrackingConsent.swift in Sources */, D2EBEE2629BA160F00B15732 /* B3HTTPHeaders.swift in Sources */, D23354FC2A42E32000AFCAE2 /* InternalExtended.swift in Sources */, + 619F5CEC2BF5089C004BFE70 /* GlobalRUMAttributes.swift in Sources */, D23039F3298D5236001A1FA3 /* DynamicCodingKey.swift in Sources */, D2BEEDB22B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */, D23039FE298D5236001A1FA3 /* FeatureRequestBuilder.swift in Sources */, @@ -9298,6 +9303,7 @@ D2DA235A298D57AA00C6C7E6 /* TrackingConsent.swift in Sources */, D2EBEE3429BA161100B15732 /* B3HTTPHeaders.swift in Sources */, D23354FD2A42E32000AFCAE2 /* InternalExtended.swift in Sources */, + 619F5CED2BF508A4004BFE70 /* GlobalRUMAttributes.swift in Sources */, D2DA235B298D57AA00C6C7E6 /* DynamicCodingKey.swift in Sources */, D2BEEDB32B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */, D2DA235C298D57AA00C6C7E6 /* FeatureRequestBuilder.swift in Sources */, diff --git a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift index ea88b9a716..e19d3cdb70 100644 --- a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift +++ b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift @@ -12,6 +12,7 @@ import CoreTelephony import DatadogInternal import TestUtilities +@testable import DatadogLogs @testable import DatadogRUM @testable import DatadogCrashReporting @testable import DatadogCore @@ -19,122 +20,266 @@ import TestUtilities /// This suite tests if `CrashContextProvider` gets updated by different SDK components, each updating /// separate part of the `CrashContext` information. class CrashContextProviderTests: XCTestCase { - // MARK: - `DatadogContext` Integration + private let provider = CrashContextCoreProvider() - func testWhenTrackingConsentValueChangesInConsentProvider_thenCrashContextProviderNotifiesNewContext() { - let expectation = self.expectation(description: "Notify new crash context") + // MARK: - Receiving SDK Context + + func testWhenInitialSDKContextIsReceived_itNotifiesCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } + + // Given + let sdkContext: DatadogContext = .mockRandom() + + // When + XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) + + // Then + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: sdkContext) + } + + func testWhenNextSDKContextIsReceived_itNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } // Given - let crashContextProvider = CrashContextCoreProvider() - let core = PassthroughCoreMock(messageReceiver: crashContextProvider) - let context: DatadogContext = .mockRandom() + let nextSDKContext: DatadogContext = .mockRandom() // When - crashContextProvider.onCrashContextChange = { - XCTAssertEqual($0.serverTimeOffset, context.serverTimeOffset) - XCTAssertEqual($0.service, context.service) - XCTAssertEqual($0.env, context.env) - XCTAssertEqual($0.version, context.version) - XCTAssertEqual($0.buildNumber, context.buildNumber) - XCTAssertEqual($0.device.osVersion, context.device.osVersion) - XCTAssertEqual($0.sdkVersion, context.sdkVersion) - XCTAssertEqual($0.source, context.source) - XCTAssertEqual($0.trackingConsent, context.trackingConsent) - DDAssertReflectionEqual($0.userInfo, context.userInfo) - XCTAssertEqual($0.networkConnectionInfo, context.networkConnectionInfo) - XCTAssertEqual($0.carrierInfo, context.carrierInfo) - XCTAssertEqual($0.lastIsAppInForeground, context.applicationStateHistory.currentSnapshot.state.isRunningInForeground) - expectation.fulfill() - } - - core.send(message: .context(context)) + XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial + XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) // receive next // Then - crashContextProvider.flush() - waitForExpectations(timeout: 0.5, handler: nil) + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: nextSDKContext) } - // MARK: - `RUMViewEvent` Integration + // MARK: - Receiving RUM View - func testWhenNewRUMView_thenItNotifiesNewCrashContext() throws { - let expectation = self.expectation(description: "Notify new crash context") + func testWhenRUMViewIsReceivedAfterSDKContext_itNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } // Given - let crashContextProvider = CrashContextCoreProvider() - let core = PassthroughCoreMock(messageReceiver: crashContextProvider) + let sdkContext: DatadogContext = .mockRandom() + let rumView: RUMViewEvent = .mockRandom() - let viewEvent: RUMViewEvent = .mockRandom() + // When + XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.viewEvent, value: rumView), from: NOPDatadogCore())) + + // Then + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: sdkContext) + DDAssertJSONEqual(crashContext.lastRUMViewEvent, rumView, "Last RUM view must be available") + } + + func testWhenSDKContextIsReceivedAfterRUMView_itNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } + + // Given + let rumView: RUMViewEvent = .mockRandom() + let nextSDKContext: DatadogContext = .mockRandom() // When - crashContextProvider.onCrashContextChange = { - DDAssertJSONEqual($0.lastRUMViewEvent, viewEvent) - expectation.fulfill() - } + XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.viewEvent, value: rumView), from: NOPDatadogCore())) + XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) - core.send(message: .baggage(key: RUMBaggageKeys.viewEvent, value: viewEvent)) + // Then + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: nextSDKContext) + DDAssertJSONEqual(crashContext.lastRUMViewEvent, rumView, "Last RUM view must be available even after next SDK context update") + } + + // MARK: - Receiving RUM View Reset + + func testWhenRUMViewResetIsReceivedAfterRUMView_thenItNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } + + // Given + let sdkContext: DatadogContext = .mockRandom() + let rumView: RUMViewEvent = .mockRandom() + + // When + XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) // receive initial SDK context + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.viewEvent, value: rumView), from: NOPDatadogCore())) + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.viewReset, value: true), from: NOPDatadogCore())) // Then - crashContextProvider.flush() - waitForExpectations(timeout: 0.5, handler: nil) + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: sdkContext) + XCTAssertNil(crashContext.lastRUMViewEvent, "Last RUM view must reset") } - func testWhenRUMViewReset_thenItNotifiesNewCrashContext() throws { - let expectation = self.expectation(description: "Notify new crash context") - expectation.expectedFulfillmentCount = 2 + func testWhenSDKContextIsReceivedAfterRUMViewReset_thenItNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } // Given - let crashContextProvider = CrashContextCoreProvider() - let core = PassthroughCoreMock(messageReceiver: crashContextProvider) + let rumView: RUMViewEvent = .mockRandom() + let nextSDKContext: DatadogContext = .mockRandom() - var viewEvent: AnyCodable? = nil + // When + XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.viewEvent, value: rumView), from: NOPDatadogCore())) + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.viewReset, value: true), from: NOPDatadogCore())) + XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) + + // Then + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: nextSDKContext) + XCTAssertNil(crashContext.lastRUMViewEvent, "Last RUM view must reset even after next SDK context update") + } + + // MARK: - Receiving RUM Session State + + func testWhenRUMSessionStateIsReceivedAfterSDKContext_itNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } + + // Given + let sdkContext: DatadogContext = .mockRandom() + let rumSessionState: RUMSessionState = .mockRandom() // When - crashContextProvider.onCrashContextChange = { - viewEvent = $0.lastRUMViewEvent - expectation.fulfill() - } + XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) // receive initial SDK context + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.sessionState, value: rumSessionState), from: NOPDatadogCore())) - core.send(message: .baggage(key: RUMBaggageKeys.viewEvent, value: RUMViewEvent.mockRandom())) - crashContextProvider.flush() - XCTAssertNotNil(viewEvent) + // Then + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: sdkContext) + DDAssertJSONEqual(crashContext.lastRUMSessionState, rumSessionState, "Last RUM session state must be available") + } + + func testWhenSDKContextIsReceivedAfterRUMSessionState_itNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } + + // Given + let rumSessionState: RUMSessionState = .mockRandom() + let nextSDKContext: DatadogContext = .mockRandom() - core.send(message: .baggage(key: RUMBaggageKeys.viewReset, value: true)) + // When + XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.sessionState, value: rumSessionState), from: NOPDatadogCore())) + XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) // Then - crashContextProvider.flush() - waitForExpectations(timeout: 0.5, handler: nil) - XCTAssertNil(viewEvent) + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: nextSDKContext) + DDAssertJSONEqual(crashContext.lastRUMSessionState, rumSessionState, "Last RUM session state must be available even after next SDK context update") } - // MARK: - RUM Session State Integration + // MARK: - Receiving Global RUM Attributes - func testWhenNewRUMSessionStateIsSentThroughMessageBus_thenItNotifiesNewCrashContext() throws { - let expectation = self.expectation(description: "Notify new crash context") + func testWhenRUMAttributesAreReceivedAfterSDKContext_itNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } // Given - let crashContextProvider = CrashContextCoreProvider() - let core = PassthroughCoreMock(messageReceiver: crashContextProvider) + let sdkContext: DatadogContext = .mockRandom() + let rumAttributes = GlobalRUMAttributes(attributes: mockRandomAttributes()) - let sessionState: RUMSessionState = .mockRandom() + // When + XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) // receive initial SDK context + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.attributes, value: rumAttributes), from: NOPDatadogCore())) + + // Then + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: sdkContext) + DDAssertJSONEqual(crashContext.lastRUMAttributes, rumAttributes, "Last RUM attributes must be available") + } + + func testWhenSDKContextIsReceivedAfterRUMAttributes_itNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } + + // Given + let rumAttributes = GlobalRUMAttributes(attributes: mockRandomAttributes()) + let nextSDKContext: DatadogContext = .mockRandom() // When - crashContextProvider.onCrashContextChange = { - DDAssertJSONEqual($0.lastRUMSessionState, sessionState) - expectation.fulfill() - } + XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context + XCTAssertTrue(provider.receive(message: .baggage(key: RUMBaggageKeys.attributes, value: rumAttributes), from: NOPDatadogCore())) + XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) + + // Then + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: nextSDKContext) + DDAssertJSONEqual(crashContext.lastRUMAttributes, rumAttributes, "Last RUM attributes must be available even after next SDK context update") + } - core.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: sessionState)) + // MARK: - Receiving Global Log Attributes + + func testWhenLogAttributesAreReceivedAfterSDKContext_itNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } + + // Given + let sdkContext: DatadogContext = .mockRandom() + let logAttributes = AnyCodable(mockRandomAttributes()) + + // When + XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) // receive initial SDK context + XCTAssertTrue(provider.receive(message: .baggage(key: GlobalLogAttributes.key, value: logAttributes), from: NOPDatadogCore())) // Then - crashContextProvider.flush() - waitForExpectations(timeout: 0.5, handler: nil) + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: sdkContext) + DDAssertJSONEqual(crashContext.lastLogAttributes, logAttributes, "Last Log attributes must be available") + } + + func testWhenSDKContextIsReceivedAfterLogAttributes_itNotifiesNewCrashContext() throws { + var latestCrashContext: CrashContext? = nil + provider.onCrashContextChange = { latestCrashContext = $0 } + + // Given + let logAttributes = AnyCodable(mockRandomAttributes()) + let nextSDKContext: DatadogContext = .mockRandom() + + // When + XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context + XCTAssertTrue(provider.receive(message: .baggage(key: GlobalLogAttributes.key, value: logAttributes), from: NOPDatadogCore())) + XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) + + // Then + provider.flush() + let crashContext = try XCTUnwrap(latestCrashContext) + XCTAssertEqual(crashContext, provider.currentCrashContext) + DDAssert(crashContext: crashContext, includes: nextSDKContext) + DDAssertJSONEqual(crashContext.lastLogAttributes, logAttributes, "Last Log attributes must be available even after next SDK context update") } // MARK: - Thread safety func testWhenContextIsWrittenAndReadFromDifferentThreads_itRunsAllOperationsSafely() { let provider = CrashContextCoreProvider() - let core = PassthroughCoreMock(messageReceiver: provider) let viewEvent: RUMViewEvent = .mockRandom() let sessionState: RUMSessionState = .mockRandom() @@ -142,17 +287,33 @@ class CrashContextProviderTests: XCTestCase { callConcurrently( closures: [ { _ = provider.currentCrashContext }, - { core.send(message: .context(.mockRandom())) }, - { core.send(message: .baggage(key: RUMBaggageKeys.viewReset, value: true)) }, - { core.send(message: .baggage(key: RUMBaggageKeys.viewEvent, value: viewEvent)) }, - { core.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: sessionState)) }, + { _ = provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore()) }, + { _ = provider.receive(message: .baggage(key: RUMBaggageKeys.viewReset, value: true), from: NOPDatadogCore()) }, + { _ = provider.receive(message: .baggage(key: RUMBaggageKeys.viewEvent, value: viewEvent), from: NOPDatadogCore()) }, + { _ = provider.receive(message: .baggage(key: RUMBaggageKeys.sessionState, value: sessionState), from: NOPDatadogCore()) }, ], iterations: 50 ) + // swiftlint:enable opening_brace - // provider retains the core in its queue: - // flush to release the core. provider.flush() - // swiftlint:enable opening_brace + } + + // MARK: - Helpers + + private func DDAssert(crashContext: CrashContext, includes sdkContext: DatadogContext, file: StaticString = #filePath, line: UInt = #line) { + XCTAssertEqual(crashContext.serverTimeOffset, sdkContext.serverTimeOffset, file: file, line: line) + XCTAssertEqual(crashContext.service, sdkContext.service, file: file, line: line) + XCTAssertEqual(crashContext.env, sdkContext.env, file: file, line: line) + XCTAssertEqual(crashContext.version, sdkContext.version, file: file, line: line) + XCTAssertEqual(crashContext.buildNumber, sdkContext.buildNumber, file: file, line: line) + XCTAssertEqual(crashContext.device, sdkContext.device, file: file, line: line) + XCTAssertEqual(crashContext.sdkVersion, sdkContext.sdkVersion, file: file, line: line) + XCTAssertEqual(crashContext.source, sdkContext.source, file: file, line: line) + XCTAssertEqual(crashContext.trackingConsent, sdkContext.trackingConsent, file: file, line: line) + DDAssertReflectionEqual(crashContext.userInfo, sdkContext.userInfo, file: file, line: line) + XCTAssertEqual(crashContext.networkConnectionInfo, sdkContext.networkConnectionInfo, file: file, line: line) + XCTAssertEqual(crashContext.carrierInfo, sdkContext.carrierInfo, file: file, line: line) + XCTAssertEqual(crashContext.lastIsAppInForeground, sdkContext.applicationStateHistory.currentSnapshot.state.isRunningInForeground, file: file, line: line) } } diff --git a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift index 2809429e98..40e8fd2443 100644 --- a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift +++ b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextTests.swift @@ -66,6 +66,40 @@ class CrashContextTests: XCTestCase { ) } + func testGivenContextWithLastRUMAttributesSet_whenItGetsEncoded_thenTheValueIsPreservedAfterDecoding() throws { + let randomRUMAttributes = Bool.random() ? GlobalRUMAttributes(attributes: mockRandomAttributes()) : nil + + // Given + let context: CrashContext = .mockWith(lastRUMAttributes: randomRUMAttributes) + + // When + let serializedContext = try encoder.encode(context) + + // Then + let deserializedContext = try decoder.decode(CrashContext.self, from: serializedContext) + DDAssertJSONEqual( + deserializedContext.lastRUMAttributes, + randomRUMAttributes + ) + } + + func testGivenContextWithLastLogttributesSet_whenItGetsEncoded_thenTheValueIsPreservedAfterDecoding() throws { + let randomLogAttributes = Bool.random() ? AnyCodable(mockRandomAttributes()) : nil + + // Given + let context: CrashContext = .mockWith(lastLogAttributes: randomLogAttributes) + + // When + let serializedContext = try encoder.encode(context) + + // Then + let deserializedContext = try decoder.decode(CrashContext.self, from: serializedContext) + DDAssertJSONEqual( + deserializedContext.lastLogAttributes, + randomLogAttributes + ) + } + func testGivenContextWithUserInfoSet_whenItGetsEncoded_thenTheValueIsPreservedAfterDecoding() throws { let randomUserInfo: UserInfo = .mockRandom() diff --git a/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift index 87bee83467..97566f0494 100644 --- a/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/CrashReportingFeatureMocks.swift @@ -144,8 +144,9 @@ extension CrashContext { lastRUMViewEvent: AnyCodable? = nil, lastRUMSessionState: AnyCodable? = nil, lastIsAppInForeground: Bool = .mockAny(), - lastLogAttributes: AnyCodable? = nil, - appLaunchDate: Date? = .mockRandomInThePast() + appLaunchDate: Date? = .mockRandomInThePast(), + lastRUMAttributes: GlobalRUMAttributes? = nil, + lastLogAttributes: AnyCodable? = nil ) -> Self { .init( serverTimeOffset: serverTimeOffset, @@ -160,11 +161,12 @@ extension CrashContext { userInfo: userInfo, networkConnectionInfo: networkConnectionInfo, carrierInfo: carrierInfo, + lastIsAppInForeground: lastIsAppInForeground, + appLaunchDate: appLaunchDate, lastRUMViewEvent: lastRUMViewEvent, lastRUMSessionState: lastRUMSessionState, - lastIsAppInForeground: lastIsAppInForeground, - lastLogAttributes: lastLogAttributes, - appLaunchDate: appLaunchDate + lastRUMAttributes: lastRUMAttributes, + lastLogAttributes: lastLogAttributes ) } @@ -182,11 +184,12 @@ extension CrashContext { userInfo: .mockRandom(), networkConnectionInfo: .mockRandom(), carrierInfo: .mockRandom(), + lastIsAppInForeground: .mockRandom(), + appLaunchDate: .mockRandomInThePast(), lastRUMViewEvent: AnyCodable(mockRandomAttributes()), lastRUMSessionState: AnyCodable(mockRandomAttributes()), - lastIsAppInForeground: .mockRandom(), - lastLogAttributes: AnyCodable(mockRandomAttributes()), - appLaunchDate: .mockRandomInThePast() + lastRUMAttributes: GlobalRUMAttributes(attributes: mockRandomAttributes()), + lastLogAttributes: AnyCodable(mockRandomAttributes()) ) } diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index 4632e569fd..68e8750259 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -702,6 +702,7 @@ func mockNoOpSessionListener() -> RUM.SessionListener { internal class FatalErrorContextNotifierMock: FatalErrorContextNotifying { var sessionState: RUMSessionState? var view: RUMViewEvent? + var globalAttributes: [String: Encodable] = [:] } extension RUMScopeDependencies { diff --git a/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift b/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift index 5500df09b3..c55b307da6 100644 --- a/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift +++ b/DatadogCrashReporting/Sources/CrashContext/CrashContext.swift @@ -63,18 +63,21 @@ internal struct CrashContext: Codable, Equatable { /// not support telephony services. let carrierInfo: CarrierInfo? + /// The last _"Is app in foreground?"_ information from crashed app process. + let lastIsAppInForeground: Bool + /// The last RUM view in crashed app process. var lastRUMViewEvent: AnyCodable? /// State of the last RUM session in crashed app process. var lastRUMSessionState: AnyCodable? - /// The last _"Is app in foreground?"_ information from crashed app process. - let lastIsAppInForeground: Bool - /// Last global log attributes, set with Logs.addAttribute / Logs.removeAttribute var lastLogAttributes: AnyCodable? + /// Last global RUM attributes. It gets updated with adding or removing attributes on `RUMMonitor`. + var lastRUMAttributes: GlobalRUMAttributes? + // MARK: - Initialization init( @@ -90,11 +93,12 @@ internal struct CrashContext: Codable, Equatable { userInfo: UserInfo?, networkConnectionInfo: NetworkConnectionInfo?, carrierInfo: CarrierInfo?, + lastIsAppInForeground: Bool, + appLaunchDate: Date?, lastRUMViewEvent: AnyCodable?, lastRUMSessionState: AnyCodable?, - lastIsAppInForeground: Bool, - lastLogAttributes: AnyCodable?, - appLaunchDate: Date? + lastRUMAttributes: GlobalRUMAttributes?, + lastLogAttributes: AnyCodable? ) { self.serverTimeOffset = serverTimeOffset self.service = service @@ -108,17 +112,19 @@ internal struct CrashContext: Codable, Equatable { self.userInfo = userInfo self.networkConnectionInfo = networkConnectionInfo self.carrierInfo = carrierInfo + self.lastIsAppInForeground = lastIsAppInForeground + self.appLaunchDate = appLaunchDate self.lastRUMViewEvent = lastRUMViewEvent self.lastRUMSessionState = lastRUMSessionState - self.lastIsAppInForeground = lastIsAppInForeground + self.lastRUMAttributes = lastRUMAttributes self.lastLogAttributes = lastLogAttributes - self.appLaunchDate = appLaunchDate } init( _ context: DatadogContext, lastRUMViewEvent: AnyCodable?, lastRUMSessionState: AnyCodable?, + lastRUMAttributes: GlobalRUMAttributes?, lastLogAttributes: AnyCodable? ) { self.serverTimeOffset = context.serverTimeOffset @@ -137,6 +143,7 @@ internal struct CrashContext: Codable, Equatable { self.lastRUMViewEvent = lastRUMViewEvent self.lastRUMSessionState = lastRUMSessionState + self.lastRUMAttributes = lastRUMAttributes self.lastLogAttributes = lastLogAttributes self.appLaunchDate = context.launchTime?.launchDate diff --git a/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift b/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift index 1e360acc4f..71dbfc8378 100644 --- a/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift +++ b/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift @@ -43,6 +43,10 @@ internal class CrashContextCoreProvider: CrashContextProvider { didSet { _context?.lastLogAttributes = logAttributes } } + private var rumAttributes: GlobalRUMAttributes? { + didSet { _context?.lastRUMAttributes = rumAttributes } + } + // MARK: - CrashContextProviderType var currentCrashContext: CrashContext? { @@ -71,6 +75,9 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { /// This key references the global log attributes static let logAttributes = "global-log-attributes" + + /// The key referencing ``DatadogInternal.GlobalRUMAttributes`` value holding RUM global attributes. + static let rumAttributes = "global-rum-attributes" } func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { @@ -85,6 +92,8 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { updateSessionState(with: baggage, to: core) case .baggage(let label, let baggage) where label == RUMBaggageKeys.logAttributes: updateLogAttributes(with: baggage, to: core) + case .baggage(let label, let baggage) where label == RUMBaggageKeys.rumAttributes: + updateRUMAttributes(with: baggage, to: core) default: return false } @@ -101,6 +110,7 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { context, lastRUMViewEvent: self.viewEvent, lastRUMSessionState: self.sessionState, + lastRUMAttributes: self.rumAttributes, lastLogAttributes: self.logAttributes ) @@ -155,6 +165,17 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { } } } + + private func updateRUMAttributes(with baggage: FeatureBaggage, to core: DatadogCoreProtocol) { + queue.async { [weak core] in + do { + self.rumAttributes = try baggage.decode(type: GlobalRUMAttributes.self) + } catch { + core?.telemetry + .error("Fails to decode log attributes from Crash Reporting", error: error) + } + } + } } extension CrashContextCoreProvider: Flushable { diff --git a/DatadogInternal/Sources/Models/RUM/GlobalRUMAttributes.swift b/DatadogInternal/Sources/Models/RUM/GlobalRUMAttributes.swift new file mode 100644 index 0000000000..a4e03fdcaa --- /dev/null +++ b/DatadogInternal/Sources/Models/RUM/GlobalRUMAttributes.swift @@ -0,0 +1,28 @@ +/* + * 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 + +public struct GlobalRUMAttributes: Codable, PassthroughAnyCodable { + public let attributes: [AttributeKey: AttributeValue] + + public init(attributes: [AttributeKey: AttributeValue]) { + self.attributes = attributes + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DynamicCodingKey.self) + try attributes.forEach { + try container.encode(AnyEncodable($1), forKey: DynamicCodingKey($0)) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DynamicCodingKey.self) + attributes = try container.allKeys + .reduce(into: [:]) { acc, next in acc[next.stringValue] = try container.decode(AnyCodable.self, forKey: next) } + } +} diff --git a/DatadogLogs/Tests/LogsTests.swift b/DatadogLogs/Tests/LogsTests.swift index 6d9135b41c..72c202dfce 100644 --- a/DatadogLogs/Tests/LogsTests.swift +++ b/DatadogLogs/Tests/LogsTests.swift @@ -136,7 +136,7 @@ class LogsTests: XCTestCase { XCTAssertEqual((baggage.attributes[attributeKey] as? AnyCodable)?.value as? String, attributeValue) } - func testItSendsGlobalLogUpdates_whenRemovettribute() throws { + func testItSendsGlobalLogUpdates_whenRemoveAttribute() throws { // Given let mockMessageReciever = FeatureMessageReceiverMock() let core = SingleFeatureCoreMock( diff --git a/DatadogRUM/Sources/Feature/RUMBaggageKeys.swift b/DatadogRUM/Sources/Feature/RUMBaggageKeys.swift index 57dbbcd251..f95db7f82d 100644 --- a/DatadogRUM/Sources/Feature/RUMBaggageKeys.swift +++ b/DatadogRUM/Sources/Feature/RUMBaggageKeys.swift @@ -18,4 +18,8 @@ internal enum RUMBaggageKeys { /// The key references RUM session state. /// The state associated with the key conforms to `Codable`. static let sessionState = "rum-session-state" + + /// The key references ``DatadogInternal.GlobalRUMAttributes`` value holding RUM attributes. + /// It is sent after each change to RUM attributes in `RUMMonitor`. + static let attributes = "global-rum-attributes" } diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index 784cd50ae4..c8f6c2ac5d 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -119,7 +119,13 @@ internal class Monitor: RUMCommandSubscriber { private(set) var debugging: RUMDebugging? = nil @ReadWriteLock - private var attributes: [AttributeKey: AttributeValue] = [:] + private var attributes: [AttributeKey: AttributeValue] = [:] { + didSet { + fatalErrorContext.globalAttributes = attributes + } + } + + private let fatalErrorContext: FatalErrorContextNotifying init( dependencies: RUMScopeDependencies, @@ -128,9 +134,11 @@ internal class Monitor: RUMCommandSubscriber { self.featureScope = dependencies.featureScope self.scopes = RUMApplicationScope(dependencies: dependencies) self.dateProvider = dateProvider + self.fatalErrorContext = dependencies.fatalErrorContext } func process(command: RUMCommand) { + let start = Date() // process command in event context featureScope.eventWriteContext { [weak self] context, writer in guard let self = self else { @@ -144,6 +152,10 @@ internal class Monitor: RUMCommandSubscriber { if let debugging = self.debugging { debugging.debug(applicationScope: self.scopes) } + + let stop = Date() + let diffMs = stop.timeIntervalSince(start) * 1_000 + print("⭐️ RUM latency (ms): \(diffMs)") } // update the core context with rum context @@ -204,16 +216,10 @@ extension Monitor: RUMMonitorProtocol { func addAttribute(forKey key: AttributeKey, value: AttributeValue) { attributes[key] = value - process( - command: RUMUpdateViewAttributesCommand(time: dateProvider.now) - ) } func removeAttribute(forKey key: AttributeKey) { attributes[key] = nil - process( - command: RUMUpdateViewAttributesCommand(time: dateProvider.now) - ) } // MARK: - session diff --git a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift index 8a5dff7b4e..8c29297761 100644 --- a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift +++ b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift @@ -242,14 +242,6 @@ internal struct RUMAddViewTimingCommand: RUMCommand, RUMViewScopePropagatableAtt let timingName: String } -internal struct RUMUpdateViewAttributesCommand: RUMCommand { - let canStartBackgroundView = false - let isUserInteraction = false - - var time: Date - var attributes: [AttributeKey: AttributeValue] = [:] -} - // MARK: - RUM Resource related commands internal protocol RUMResourceCommand: RUMCommand { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/FatalErrorContextNotifier.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/FatalErrorContextNotifier.swift index d8a88ac03d..54fd14bef6 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/FatalErrorContextNotifier.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/FatalErrorContextNotifier.swift @@ -15,6 +15,9 @@ internal protocol FatalErrorContextNotifying: AnyObject { /// The active RUM view in current session. /// Can be `nil` if no view is yet started. Will become `nil` if view was stopped without starting the new one. var view: RUMViewEvent? { set get } + /// The current set of RUM attributes. + /// It gets updated with global attributes being added or removed in `RUMMonitor`. + var globalAttributes: [String: Encodable] { set get } } /// Manages RUM information necessary for building context of fatal errors such as Crashes or Fatal App Hangs. @@ -50,4 +53,16 @@ internal final class FatalErrorContextNotifier: FatalErrorContextNotifying { } } } + + @ReadWriteLock + var globalAttributes: [String: Encodable] = [:] { + didSet { + messageBus.send( + message: .baggage( + key: RUMBaggageKeys.attributes, + value: GlobalRUMAttributes(attributes: globalAttributes) + ) + ) + } + } } diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index b030c10056..031fa23332 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -196,8 +196,6 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { case let command as RUMAddViewTimingCommand where isActiveView: customTimings[command.timingName] = command.time.timeIntervalSince(viewStartTime).toInt64Nanoseconds needsViewUpdate = true - case _ as RUMUpdateViewAttributesCommand where isActiveView: - needsViewUpdate = true // Resource commands case let command as RUMStartResourceCommand where isActiveView: @@ -439,8 +437,6 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { // RUMM-3133 Don't override View attributes with commands that are not view related. if command is RUMViewScopePropagatableAttributes { attributes.merge(rumCommandAttributes: command.attributes) - } else if command is RUMUpdateViewAttributesCommand { - attributes = command.attributes } let isCrash = (command as? RUMErrorCommand).map { $0.isCrash ?? false } ?? false @@ -547,9 +543,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { ) if let event = dependencies.eventBuilder.build(from: viewEvent) { - if !(command is RUMUpdateViewAttributesCommand) { - writer.write(value: event, metadata: event.metadata()) - } + writer.write(value: event, metadata: event.metadata()) // Update fatal error context with recent RUM view: dependencies.fatalErrorContext.view = event diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index 895f090256..7266735fe3 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -748,6 +748,7 @@ func mockNoOpSessionListener() -> RUM.SessionListener { internal class FatalErrorContextNotifierMock: FatalErrorContextNotifying { var sessionState: RUMSessionState? var view: RUMViewEvent? + var globalAttributes: [String: Encodable] = [:] } extension RUMScopeDependencies { diff --git a/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift index 0061107817..207e85d524 100644 --- a/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift @@ -62,7 +62,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { // Then let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) XCTAssertEqual(lastView.view.name, RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName) - XCTAssertEqual(lastView.attribute(forKey: "attribute"), "value") + XCTAssertNil(lastView.attribute(forKey: "attribute")) } func testAddingGlobalAttributesAfterSDKInit_thenRemovingAttribute() throws { @@ -162,7 +162,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { // Then let lastView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last) XCTAssertEqual(lastView.view.name, "View") - XCTAssertEqual(lastView.attribute(forKey: "attribute"), "value") + XCTAssertNil(lastView.attribute(forKey: "attribute")) } func testAddingGlobalAttributesAfterViewIsStarted_thenRemovingAttribute() throws { @@ -255,7 +255,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(lastView1.attribute(forKey: "attribute2"), "value2") XCTAssertEqual(lastView1.attribute(forKey: "attribute3"), "value3") - XCTAssertNil(lastView2.attribute(forKey: "attribute1")) + XCTAssertEqual(lastView2.attribute(forKey: "attribute1"), "value1") XCTAssertEqual(lastView2.attribute(forKey: "attribute2"), "value2") XCTAssertEqual(lastView2.attribute(forKey: "attribute3"), "value3") @@ -394,7 +394,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(lastView.view.error.count, 1) XCTAssertEqual(lastView.view.action.count, 1) XCTAssertEqual(lastView.view.resource.count, 1) - XCTAssertEqual(lastView.attribute(forKey: "attribute"), "value") + XCTAssertNil(lastView.attribute(forKey: "attribute")) XCTAssertEqual(errorEvent.context?.contextInfo["attribute"] as? String, "value") XCTAssertEqual(actionEvent.context?.contextInfo["attribute"] as? String, "value") XCTAssertEqual(resourceEvent.context?.contextInfo["attribute"] as? String, "value") @@ -424,7 +424,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { XCTAssertEqual(lastView.view.action.count, 1) XCTAssertEqual(lastView.view.resource.count, 1) XCTAssertNil(lastView.attribute(forKey: "attribute1")) - XCTAssertEqual(lastView.attribute(forKey: "attribute2"), "value2") + XCTAssertNil(lastView.attribute(forKey: "attribute2")) XCTAssertNil(errorEvent.context?.contextInfo["attribute1"]) XCTAssertEqual(errorEvent.context?.contextInfo["attribute2"] as? String, "value2") XCTAssertNil(actionEvent.context?.contextInfo["attribute1"]) @@ -486,48 +486,7 @@ class Monitor_GlobalAttributesTests: XCTestCase { // MARK: - Updating Fatal Error Context With Global Attributes - func testGivenSDKInitialized_whenGlobalAttributeIsAdded_thenFatalErrorContextIsUpdatedWithNewView() throws { - let fatalErrorContext = FatalErrorContextNotifierMock() - - // Given - monitor = Monitor( - dependencies: .mockWith(featureScope: featureScope, fatalErrorContext: fatalErrorContext), - dateProvider: SystemDateProvider() - ) - monitor.notifySDKInit() - - // When - monitor.addAttribute(forKey: "attribute", value: "value") - - // Then - let fatalErrorContextView = try XCTUnwrap(fatalErrorContext.view) - XCTAssertEqual(fatalErrorContextView.view.name, RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName) - XCTAssertEqual(fatalErrorContextView.attribute(forKey: "attribute"), "value") - } - - func testGivenSDKInitialized_whenGlobalAttributesAreAddedAndRemoved_thenFatalErrorContextIsUpdatedWithNewView() throws { - let fatalErrorContext = FatalErrorContextNotifierMock() - - // Given - monitor = Monitor( - dependencies: .mockWith(featureScope: featureScope, fatalErrorContext: fatalErrorContext), - dateProvider: SystemDateProvider() - ) - monitor.notifySDKInit() - - // When - monitor.addAttribute(forKey: "attribute1", value: "value1") - monitor.addAttribute(forKey: "attribute2", value: "value2") - monitor.removeAttribute(forKey: "attribute1") - - // Then - let fatalErrorContextView = try XCTUnwrap(fatalErrorContext.view) - XCTAssertEqual(fatalErrorContextView.view.name, RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewName) - XCTAssertNil(fatalErrorContextView.attribute(forKey: "attribute1")) - XCTAssertEqual(fatalErrorContextView.attribute(forKey: "attribute2"), "value2") - } - - func testGivenSDKInitializedAndViewStarted_whenGlobalAttributeIsAdded_thenFatalErrorContextIsUpdatedWithNewView() throws { + func testGivenSDKInitialized_whenGlobalAttributeIsAdded_thenFatalErrorContextIsUpdatedWithNewAttributes() throws { let fatalErrorContext = FatalErrorContextNotifierMock() // Given @@ -536,18 +495,16 @@ class Monitor_GlobalAttributesTests: XCTestCase { dateProvider: SystemDateProvider() ) monitor.notifySDKInit() - monitor.startView(key: "View") // When monitor.addAttribute(forKey: "attribute", value: "value") // Then - let fatalErrorContextView = try XCTUnwrap(fatalErrorContext.view) - XCTAssertEqual(fatalErrorContextView.view.name, "View") - XCTAssertEqual(fatalErrorContextView.attribute(forKey: "attribute"), "value") + XCTAssertEqual(fatalErrorContext.globalAttributes["attribute"] as? String, "value") + XCTAssertEqual(fatalErrorContext.globalAttributes.count, 1) } - func testGivenSDKInitializedAndViewStarted_whenGlobalAttributesAreAddedAndRemoved_thenFatalErrorContextIsUpdatedWithNewView() throws { + func testGivenSDKInitialized_whenGlobalAttributesAreAddedAndRemoved_thenFatalErrorContextIsUpdatedWithNewAttributes() throws { let fatalErrorContext = FatalErrorContextNotifierMock() // Given @@ -556,7 +513,6 @@ class Monitor_GlobalAttributesTests: XCTestCase { dateProvider: SystemDateProvider() ) monitor.notifySDKInit() - monitor.startView(key: "View") // When monitor.addAttribute(forKey: "attribute1", value: "value1") @@ -564,10 +520,8 @@ class Monitor_GlobalAttributesTests: XCTestCase { monitor.removeAttribute(forKey: "attribute1") // Then - let fatalErrorContextView = try XCTUnwrap(fatalErrorContext.view) - XCTAssertEqual(fatalErrorContextView.view.name, "View") - XCTAssertNil(fatalErrorContextView.attribute(forKey: "attribute1")) - XCTAssertEqual(fatalErrorContextView.attribute(forKey: "attribute2"), "value2") + XCTAssertEqual(fatalErrorContext.globalAttributes["attribute2"] as? String, "value2") + XCTAssertEqual(fatalErrorContext.globalAttributes.count, 1) } } diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift index 6f855ed1f0..e025b0e144 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift @@ -76,4 +76,21 @@ class FatalErrorContextNotifierTests: XCTestCase { let viewResetMessage = try XCTUnwrap(messages.lastBaggage(withKey: RUMBaggageKeys.viewReset)) XCTAssertTrue(try viewResetMessage.decode(type: Bool.self)) } + + // MARK: - Changing Global Attributes + + func testWhenGlobalAttributesAreSet_itSendsAttributesMessage() throws { + // Given + let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + let newGlobalAttributes = mockRandomAttributes() + + // When + fatalErrorContext.globalAttributes = newGlobalAttributes + + // Then + let messages = featureScope.messagesSent() + XCTAssertEqual(messages.count, 1) + let attributesMessage = try XCTUnwrap(messages.lastBaggage(withKey: RUMBaggageKeys.attributes)) + DDAssertJSONEqual(newGlobalAttributes, try attributesMessage.decode(type: GlobalRUMAttributes.self).attributes) + } } diff --git a/TestUtilities/Helpers/DDAssert.swift b/TestUtilities/Helpers/DDAssert.swift index 3a5c829c6a..bdb60c31f4 100644 --- a/TestUtilities/Helpers/DDAssert.swift +++ b/TestUtilities/Helpers/DDAssert.swift @@ -8,6 +8,7 @@ */ import Foundation +import DatadogInternal import XCTest public enum DDAssertError: Error { @@ -147,6 +148,12 @@ private func _DDAssertJSONEqual(_ expression1: @autoclosure () throws -> T } } +public func DDAssertJSONEqual(_ expression1: @autoclosure () throws -> Any, _ expression2: @autoclosure () throws -> Any, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { + _DDEvaluateAssertion(message: message(), file: file, line: line) { + try _DDAssertJSONEqual(AnyCodable(expression1()), AnyCodable(expression2())) + } +} + public func DDAssertJSONEqual(_ expression1: @autoclosure () throws -> T, _ expression2: @autoclosure () throws -> U, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) where T: Encodable, U: Encodable { _DDEvaluateAssertion(message: message(), file: file, line: line) { try _DDAssertJSONEqual(expression1(), expression2()) From 7b55807a63d883a9d43a9fb6c1a4a7b82a672858 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 16 May 2024 17:46:37 +0200 Subject: [PATCH 130/153] RUM-3588 Write RUM attributes to RUM view and error created for crash report --- .../CrashReportReceiverTests.swift | 161 +++++++++++++++++- .../Integrations/CrashReportReceiver.swift | 15 +- 2 files changed, 173 insertions(+), 3 deletions(-) diff --git a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift index 73e174a98c..ec1a4990a5 100644 --- a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift @@ -401,7 +401,6 @@ class CrashReportReceiverTests: XCTestCase { DDAssertReflectionEqual(sendRUMViewEvent.os, lastRUMViewEvent.os) } - // func testGivenCrashDuringRUMSessionWithActiveView_whenSendingRUMErrorEvent_itIsLinkedToPreviousRUMSessionAndIncludesCrashInformation() throws { let lastRUMViewEvent: RUMViewEvent = .mockRandomWith(crashCount: 0) @@ -720,6 +719,41 @@ class CrashReportReceiverTests: XCTestCase { DDAssertJSONEqual(sendRUMErrorEvent.error.wasTruncated, crashReport.wasTruncated) } + func testGivenCrashDuringRUMSessionWithActiveViewAndLastRUMAttributesAvailable_itSendsEventsWithOverridingAttributes() throws { + let lastRUMViewEvent: RUMViewEvent = .mockRandomWith(crashCount: 0) + let lastRUMAttributes = GlobalRUMAttributes(attributes: mockRandomAttributes()) + + // Given + let crashDate: Date = .mockDecember15th2019At10AMUTC() + let crashReport: DDCrashReport = .mockWith(date: crashDate) + let crashContext: CrashContext = .mockWith( + trackingConsent: .granted, + lastRUMViewEvent: AnyCodable(lastRUMViewEvent), + lastRUMAttributes: lastRUMAttributes + ) + + let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, + dateProvider: RelativeDateProvider(using: crashDate), + sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) + trackBackgroundEvents: .mockRandom() // no matter BET + ) + + // When + XCTAssertTrue( + receiver.receive(message: .baggage( + key: MessageBusSender.MessageKeys.crash, + value: MessageBusSender.Crash(report: crashReport, context: crashContext) + ), from: NOPDatadogCore()) + ) + + // Then + let sentRUMViewAttributes = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self)[0].context?.contextInfo) + let sentRUMErrorAttributes = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMErrorEvent.self)[0].context?.contextInfo) + DDAssertJSONEqual(sentRUMViewAttributes, lastRUMAttributes.attributes) + DDAssertJSONEqual(sentRUMErrorAttributes, lastRUMAttributes.attributes) + } + // MARK: - Testing Uploaded Data - Crashes During RUM Session With No Active View func testGivenCrashDuringRUMSessionWithNoActiveView_whenSendingRUMViewEvent_itIsLinkedToPreviousRUMSessionAndIncludesErrorInformation() throws { @@ -1017,6 +1051,74 @@ class CrashReportReceiverTests: XCTestCase { ) } + func testGivenCrashDuringRUMSessionWithNoActiveViewAndLastRUMAttributesAvailable_itSendsEventsWithOverridingAttributes() throws { + func test( + lastRUMSessionState: RUMSessionState, + launchInForeground: Bool, + backgroundEventsTrackingEnabled: Bool + ) throws { + let featureScope = FeatureScopeMock() + + // Given + let lastRUMAttributes = GlobalRUMAttributes(attributes: mockRandomAttributes()) + let crashDate: Date = .mockDecember15th2019At10AMUTC() + let crashReport: DDCrashReport = .mockWith( + date: crashDate, + type: .mockRandom() + ) + let dateCorrectionOffset: TimeInterval = .mockRandom() + let crashContext: CrashContext = .mockWith( + serverTimeOffset: dateCorrectionOffset, + service: .mockRandom(), + version: .mockRandom(), + buildNumber: .mockRandom(), + source: .mockRandom(), + trackingConsent: .granted, + userInfo: .mockRandom(), + networkConnectionInfo: .mockRandom(), + carrierInfo: .mockRandom(), + lastRUMViewEvent: nil, // means there was no active RUM view + lastRUMSessionState: AnyCodable(lastRUMSessionState), // means there was RUM session (sampled) + lastIsAppInForeground: launchInForeground, + lastRUMAttributes: lastRUMAttributes + ) + + let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, + applicationID: .mockRandom(among: .alphanumerics), + dateProvider: RelativeDateProvider(using: crashDate), + sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) + trackBackgroundEvents: backgroundEventsTrackingEnabled + ) + + // When + XCTAssertTrue( + receiver.receive(message: .baggage( + key: MessageBusSender.MessageKeys.crash, + value: MessageBusSender.Crash(report: crashReport, context: crashContext) + ), from: NOPDatadogCore()) + ) + + // Then + let sentRUMViewAttributes = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self)[0].context?.contextInfo) + let sentRUMErrorAttributes = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMErrorEvent.self)[0].context?.contextInfo) + DDAssertJSONEqual(sentRUMViewAttributes, lastRUMAttributes.attributes) + DDAssertJSONEqual(sentRUMErrorAttributes, lastRUMAttributes.attributes) + } + + try test( + lastRUMSessionState: .mockWith(isInitialSession: true, hasTrackedAnyView: false), // when initial session with no views history + launchInForeground: true, // launch in foreground + backgroundEventsTrackingEnabled: .mockRandom() // no matter BET + ) + + try test( + lastRUMSessionState: .mockRandom(), // any sampled session + launchInForeground: false, // launch in background + backgroundEventsTrackingEnabled: true // BET enabled + ) + } + // MARK: - Testing Uploaded Data - Crashes During App Launch func testGivenCrashDuringAppLaunch_whenSending_itIsSendAsRUMErrorInNewRUMSession() throws { @@ -1316,4 +1418,61 @@ class CrashReportReceiverTests: XCTestCase { expectViewURL: RUMOffViewEventsHandlingRule.Constants.backgroundViewURL ) } + + func testGivenCrashDuringAppLaunchAndLastRUMAttributesAvailable_itSendsEventsWithOverridingAttributes() throws { + func test( + launchInForeground: Bool, + backgroundEventsTrackingEnabled: Bool + ) throws { + let featureScope = FeatureScopeMock() + let lastRUMAttributes = GlobalRUMAttributes(attributes: mockRandomAttributes()) + + // Given + let crashDate: Date = .mockDecember15th2019At10AMUTC() + let crashReport: DDCrashReport = .mockWith( + date: crashDate, + type: .mockRandom() + ) + + let crashContext: CrashContext = .mockWith( + trackingConsent: .granted, + lastRUMViewEvent: nil, // means there was no RUM session (it crashed during app launch) + lastRUMSessionState: nil, // means there was no RUM session (it crashed during app launch) + lastIsAppInForeground: launchInForeground, + lastRUMAttributes: lastRUMAttributes + ) + + let mappedViewName = String.mockRandom() + let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, + applicationID: .mockRandom(), + dateProvider: RelativeDateProvider(using: crashDate), + trackBackgroundEvents: backgroundEventsTrackingEnabled + ) + + // When + XCTAssertTrue( + receiver.receive(message: .baggage( + key: MessageBusSender.MessageKeys.crash, + value: MessageBusSender.Crash(report: crashReport, context: crashContext) + ), from: NOPDatadogCore()) + ) + + // Then + let sentRUMViewAttributes = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self)[0].context?.contextInfo) + let sentRUMErrorAttributes = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMErrorEvent.self)[0].context?.contextInfo) + DDAssertJSONEqual(sentRUMViewAttributes, lastRUMAttributes.attributes) + DDAssertJSONEqual(sentRUMErrorAttributes, lastRUMAttributes.attributes) + } + + try test( + launchInForeground: true, // launch in foreground + backgroundEventsTrackingEnabled: .mockRandom() // no matter BET + ) + + try test( + launchInForeground: false, // launch in background + backgroundEventsTrackingEnabled: true // BET enabled + ) + } } diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index 00f7c2e241..1af52bc244 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -43,6 +43,8 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { var lastRUMViewEvent: RUMViewEvent? /// State of the last RUM session in crashed app process. var lastRUMSessionState: RUMSessionState? + /// The last global RUM attributes in crashed app process. + var lastRUMAttributes: GlobalRUMAttributes? /// The last _"Is app in foreground?"_ information from crashed app process. let lastIsAppInForeground: Bool /// Network information. @@ -144,7 +146,13 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { ) // RUMM-2516 if a cross-platform crash was reported, do not send its native version - if let lastRUMViewEvent = context.lastRUMViewEvent { + if var lastRUMViewEvent = context.lastRUMViewEvent { + if let lastRUMAttributes = context.lastRUMAttributes { + // RUM-3588: If last RUM attributes are available, use them to replace view attributes as we know that + // global RUM attributes can be updated more often than attributes in `lastRUMView`. + // See https://github.com/DataDog/dd-sdk-ios/pull/1834 for more context. + lastRUMViewEvent.context?.contextInfo = lastRUMAttributes.attributes + } if lastRUMViewEvent.view.crash?.count ?? 0 < 1 { sendCrashReportLinkedToLastViewInPreviousSession( report, @@ -377,7 +385,10 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { carrierInfo: context.carrierInfo ), container: nil, - context: nil, + // RUM-3588: We know that last RUM view is not available, so we're creating a new one. No matter that, try using last + // RUM attributes if available. There is a chance of having them as global RUM attributes can be updated more often than RUM view. + // See https://github.com/DataDog/dd-sdk-ios/pull/1834 for more context. + context: context.lastRUMAttributes.map { .init(contextInfo: $0.attributes) }, date: startDate.timeIntervalSince1970.toInt64Milliseconds, device: .init(device: context.device, telemetry: featureScope.telemetry), display: nil, From 3fae9b86087f2bc84a652a4744c60a6ccdd5fb9a Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 16 May 2024 18:06:21 +0200 Subject: [PATCH 131/153] RUM-3588 Cleanup --- .../Datadog/RUM/Integrations/CrashReportReceiverTests.swift | 1 - DatadogRUM/Sources/RUMMonitor/Monitor.swift | 5 ----- 2 files changed, 6 deletions(-) diff --git a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift index ec1a4990a5..6de0000be1 100644 --- a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift @@ -1442,7 +1442,6 @@ class CrashReportReceiverTests: XCTestCase { lastRUMAttributes: lastRUMAttributes ) - let mappedViewName = String.mockRandom() let receiver: CrashReportReceiver = .mockWith( featureScope: featureScope, applicationID: .mockRandom(), diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index c8f6c2ac5d..4621b81e45 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -138,7 +138,6 @@ internal class Monitor: RUMCommandSubscriber { } func process(command: RUMCommand) { - let start = Date() // process command in event context featureScope.eventWriteContext { [weak self] context, writer in guard let self = self else { @@ -152,10 +151,6 @@ internal class Monitor: RUMCommandSubscriber { if let debugging = self.debugging { debugging.debug(applicationScope: self.scopes) } - - let stop = Date() - let diffMs = stop.timeIntervalSince(start) * 1_000 - print("⭐️ RUM latency (ms): \(diffMs)") } // update the core context with rum context From 6ef10e98ebead51e6a3ed6344e2f52f9d68266b5 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 16 May 2024 18:11:18 +0200 Subject: [PATCH 132/153] RUM-3588 Add integration test for adding global RUM attributes to crash error --- .../CrashReporting/SendingCrashReportTests.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift b/Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift index 1a048bd2b9..099d4ea2db 100644 --- a/Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift +++ b/Datadog/IntegrationUnitTests/CrashReporting/SendingCrashReportTests.swift @@ -45,13 +45,13 @@ class SendingCrashReportTests: XCTestCase { func testWhenSDKStartsWithPendingCrashReport_itSendsItAsLogAndRUMEvent() throws { // Given - let crashReport: DDCrashReport = .mockRandomWith( - context: .mockWith( - trackingConsent: .granted, // CR from the app session that has enabled data collection - lastIsAppInForeground: true, // CR occurred while the app was in the foreground - lastLogAttributes: .init(mockRandomAttributes()) - ) + let crashContext: CrashContext = .mockWith( + trackingConsent: .granted, // CR from the app session that has enabled data collection + lastIsAppInForeground: true, // CR occurred while the app was in the foreground + lastRUMAttributes: GlobalRUMAttributes(attributes: mockRandomAttributes()), + lastLogAttributes: .init(mockRandomAttributes()) ) + let crashReport: DDCrashReport = .mockRandomWith(context: crashContext) // When Logs.enable(with: .init(), in: core) @@ -66,6 +66,7 @@ class SendingCrashReportTests: XCTestCase { XCTAssertEqual(log.error?.kind, crashReport.type) XCTAssertEqual(log.error?.stack, crashReport.stack) XCTAssertFalse(log.attributes.userAttributes.isEmpty) + DDAssertJSONEqual(log.attributes.userAttributes, crashContext.lastLogAttributes!) XCTAssertNotNil(log.attributes.internalAttributes?[DDError.threads]) XCTAssertNotNil(log.attributes.internalAttributes?[DDError.binaryImages]) XCTAssertNotNil(log.attributes.internalAttributes?[DDError.meta]) @@ -80,6 +81,7 @@ class SendingCrashReportTests: XCTestCase { XCTAssertNotNil(rumEvent.error.binaryImages) XCTAssertNotNil(rumEvent.error.meta) XCTAssertNotNil(rumEvent.error.wasTruncated) + DDAssertJSONEqual(rumEvent.context!.contextInfo, crashContext.lastRUMAttributes!) } func testWhenSendingCrashReportAsLog_itIsLinkedToTheRUMSessionThatHasCrashed() throws { From eb610b93ada92598beba8744abcc63b657bd330c Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 16 May 2024 18:21:22 +0200 Subject: [PATCH 133/153] RUM-3588 Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfafd67a66..d2282ef10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- [IMPROVEMENT] Crash errors now include up-to-date global RUM attributes. See [#1834][] - [FEATURE] `DatadogTrace` now supports OpenTelemetry. See [#1828][] # 2.11.0 / 08-05-2024 @@ -659,6 +660,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1767]: https://github.com/DataDog/dd-sdk-ios/pull/1767 [#1798]: https://github.com/DataDog/dd-sdk-ios/pull/1798 [#1776]: https://github.com/DataDog/dd-sdk-ios/pull/1776 +[#1834]: https://github.com/DataDog/dd-sdk-ios/pull/1834 [#1721]: https://github.com/DataDog/dd-sdk-ios/pull/1721 [#1803]: https://github.com/DataDog/dd-sdk-ios/pull/1803 [#1807]: https://github.com/DataDog/dd-sdk-ios/pull/1807 From 99b72b8dfb9958004cfcc58870e58364a5e62ff8 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 21 May 2024 09:45:08 +0200 Subject: [PATCH 134/153] RUM-3588 CR feedback + rebase --- .../CrashContextProviderTests.swift | 1 + .../CrashContext/CrashContextProvider.swift | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift index e19d3cdb70..9f45e3b5b7 100644 --- a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift +++ b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift @@ -302,6 +302,7 @@ class CrashContextProviderTests: XCTestCase { // MARK: - Helpers private func DDAssert(crashContext: CrashContext, includes sdkContext: DatadogContext, file: StaticString = #filePath, line: UInt = #line) { + XCTAssertEqual(crashContext.appLaunchDate, sdkContext.launchTime?.launchDate, file: file, line: line) XCTAssertEqual(crashContext.serverTimeOffset, sdkContext.serverTimeOffset, file: file, line: line) XCTAssertEqual(crashContext.service, sdkContext.service, file: file, line: line) XCTAssertEqual(crashContext.env, sdkContext.env, file: file, line: line) diff --git a/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift b/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift index 71dbfc8378..6d71594ec5 100644 --- a/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift +++ b/DatadogCrashReporting/Sources/CrashContext/CrashContextProvider.swift @@ -105,7 +105,11 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { /// /// - Parameter context: The updated core context. private func update(context: DatadogContext) { - queue.async { + queue.async { [weak self] in + guard let self = self else { + return + } + let crashContext = CrashContext( context, lastRUMViewEvent: self.viewEvent, @@ -121,9 +125,9 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { } private func updateRUMView(with baggage: FeatureBaggage, to core: DatadogCoreProtocol) { - queue.async { [weak core] in + queue.async { [weak core, weak self] in do { - self.viewEvent = try baggage.decode(type: AnyCodable.self) + self?.viewEvent = try baggage.decode(type: AnyCodable.self) } catch { core?.telemetry .error("Fails to decode RUM view event from Crash Reporting", error: error) @@ -132,10 +136,10 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { } private func resetRUMView(with baggage: FeatureBaggage, to core: DatadogCoreProtocol) { - queue.async { [weak core] in + queue.async { [weak core, weak self] in do { if try baggage.decode(type: Bool.self) { - self.viewEvent = nil + self?.viewEvent = nil } } catch { core?.telemetry @@ -145,9 +149,9 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { } private func updateSessionState(with baggage: FeatureBaggage, to core: DatadogCoreProtocol) { - queue.async { [weak core] in + queue.async { [weak core, weak self] in do { - self.sessionState = try baggage.decode(type: AnyCodable.self) + self?.sessionState = try baggage.decode(type: AnyCodable.self) } catch { core?.telemetry .error("Fails to decode RUM session state from Crash Reporting", error: error) @@ -156,9 +160,9 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { } private func updateLogAttributes(with baggage: FeatureBaggage, to core: DatadogCoreProtocol) { - queue.async { [weak core] in + queue.async { [weak core, weak self] in do { - self.logAttributes = try baggage.decode(type: AnyCodable.self) + self?.logAttributes = try baggage.decode(type: AnyCodable.self) } catch { core?.telemetry .error("Fails to decode log attributes from Crash Reporting", error: error) @@ -167,9 +171,9 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { } private func updateRUMAttributes(with baggage: FeatureBaggage, to core: DatadogCoreProtocol) { - queue.async { [weak core] in + queue.async { [weak core, weak self] in do { - self.rumAttributes = try baggage.decode(type: GlobalRUMAttributes.self) + self?.rumAttributes = try baggage.decode(type: GlobalRUMAttributes.self) } catch { core?.telemetry .error("Fails to decode log attributes from Crash Reporting", error: error) From 43e8d441bac937b747d1237d2c8d64a7c0c950f3 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 21 May 2024 10:46:37 +0200 Subject: [PATCH 135/153] Explicitly ignore `telemetry/usage-schema.json` to unblock generatin RUM models --- .../Sources/CodeGeneration/Generate/JSONSchema.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/rum-models-generator/Sources/CodeGeneration/Generate/JSONSchema.swift b/tools/rum-models-generator/Sources/CodeGeneration/Generate/JSONSchema.swift index efddd5b759..afcce8ae7b 100644 --- a/tools/rum-models-generator/Sources/CodeGeneration/Generate/JSONSchema.swift +++ b/tools/rum-models-generator/Sources/CodeGeneration/Generate/JSONSchema.swift @@ -209,6 +209,12 @@ internal class JSONSchema: Decodable { let schema = try reader.read(url) merge(with: schema) } + + // TODO: RUM-4571 [iOS] Generate RUM models for Usage Telemetry + // With addition of the "usage telemetry" the RUM schema was updated with unsupported constructs. + // To avoid `🚧 Unimplemented: "Building `SwiftAssociatedTypeEnum` case label for JSONObject is not supported` failure + // here we explicitly ignore the "usage" sub-schema. + oneOf = oneOf?.filter { $0.ref != "telemetry/usage-schema.json" } } // MARK: - Schemas Merging From 421f9b908d1deea28b7cd070fe388476911c6847 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 21 May 2024 11:22:40 +0200 Subject: [PATCH 136/153] Fix shared type ambiguity for `telemetry.device` and `telemetry.os` models --- .../CodeDecoration/RUMCodeDecorator.swift | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tools/rum-models-generator/Sources/CodeDecoration/RUMCodeDecorator.swift b/tools/rum-models-generator/Sources/CodeDecoration/RUMCodeDecorator.swift index f551140b58..aa22d6ad24 100644 --- a/tools/rum-models-generator/Sources/CodeDecoration/RUMCodeDecorator.swift +++ b/tools/rum-models-generator/Sources/CodeDecoration/RUMCodeDecorator.swift @@ -26,6 +26,8 @@ public class RUMCodeDecorator: SwiftCodeDecorator { "RUMOperatingSystem", "RUMActionID", "RUMSessionPrecondition", + "RUMTelemetryDevice", + "RUMTelemetryOperatingSystem", ] ) } @@ -104,11 +106,25 @@ public class RUMCodeDecorator: SwiftCodeDecorator { } if fixedName == "Device" { - fixedName = "RUMDevice" + if context.parent?.typeName == "telemetry" { + // The `telemetry.device` added in https://github.com/DataDog/rum-events-format/pull/200 has different schema + // than `*.device` in common schema: https://github.com/DataDog/rum-events-format/blob/dcd62e58566b9d158c404f3588edc62c041262dd/schemas/rum/_common-schema.json#L264-L295 + // For that reason, we generate it under different name, so the `RUMTelemetryDevice` can be shared between telemetry events. + fixedName = "RUMTelemetryDevice" + } else { + fixedName = "RUMDevice" + } } if fixedName == "OS" { - fixedName = "RUMOperatingSystem" + if context.parent?.typeName == "telemetry" { + // The `telemetry.os` added in https://github.com/DataDog/rum-events-format/pull/200 has different schema + // than `*.os` in common schema: https://github.com/DataDog/rum-events-format/blob/dcd62e58566b9d158c404f3588edc62c041262dd/schemas/rum/_common-schema.json#L237-L262 + // For that reason, we generate it under different name, so the `RUMTelemetryOperatingSystem` can be shared between telemetry events. + fixedName = "RUMTelemetryOperatingSystem" + } else { + fixedName = "RUMOperatingSystem" + } } // Since https://github.com/DataDog/rum-events-format/pull/57 `action.id` can be either From a02c34d9ebb0aa628c4dcfe008e1e8e356118be5 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 21 May 2024 14:38:22 +0200 Subject: [PATCH 137/153] RUM-4313 CR feedback - apply common convention for Objc interface --- .../Sources/WebViewTracking+objc.swift | 121 +++++++++--------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift b/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift index 9d554cedf6..ebaad783a7 100644 --- a/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift +++ b/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift @@ -11,10 +11,69 @@ import Foundation import WebKit #endif -@objc -public final class DDWebViewTracking: NSObject { +@objc(DDWebViewTracking) +@_spi(objc) +public final class objc_WebViewTracking: NSObject { override private init() { } + /// The Session Replay configuration to capture records coming from the web view. + /// + /// Setting the Session Replay configuration in `WebViewTracking` will enable transmitting replay data from + /// the Datadog Browser SDK installed in the web page. Datadog will then be able to combine the native + /// and web recordings in a single replay. + @objc(DDWebViewTrackingSessionReplayConfiguration) + @_spi(objc) + public final class SessionReplayConfiguration: NSObject { + /// Available privacy levels for content masking. + @objc(DDPrivacyLevel) + @_spi(objc) + public enum PrivacyLevel: Int { + /// Record all content. + case allow + /// Mask all content. + case mask + /// Mask input elements, but record all other content. + case maskUserInput + + internal var toSwift: WebViewTracking.SessionReplayConfiguration.PrivacyLevel { + switch self { + case .allow: return .allow + case .mask: return .mask + case .maskUserInput: return .maskUserInput + } + } + } + + /// The privacy level to use for the web view replay recording. + @objc public var privacyLevel: PrivacyLevel + + /// Creates Webview Session Replay configuration. + /// + /// - Parameters: + /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. + @objc + override public init() { + self.privacyLevel = .mask + } + + /// Creates Webview Session Replay configuration. + /// + /// - Parameters: + /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. + @objc + public init( + privacyLevel: PrivacyLevel + ) { + self.privacyLevel = privacyLevel + } + + internal var toSwift: WebViewTracking.SessionReplayConfiguration { + return .init( + privacyLevel: privacyLevel.toSwift + ) + } + } + /// Enables SDK to correlate Datadog RUM events and Logs from the WebView with native RUM session. /// /// If the content loaded in WebView uses Datadog Browser SDK (`v4.2.0+`) and matches specified @@ -32,7 +91,7 @@ public final class DDWebViewTracking: NSObject { webView: WKWebView, hosts: Set = [], logsSampleRate: Float = 100, - with configuration: DDWebViewTrackingSessionReplayConfiguration? = nil + with configuration: SessionReplayConfiguration? = nil ) { WebViewTracking.enable( webView: webView, @@ -55,59 +114,3 @@ public final class DDWebViewTracking: NSObject { WebViewTracking.disable(webView: webView) } } - -/// The Session Replay configuration to capture records coming from the web view. -/// -/// Setting the Session Replay configuration in `WebViewTracking` will enable transmitting replay data from -/// the Datadog Browser SDK installed in the web page. Datadog will then be able to combine the native -/// and web recordings in a single replay. -@objc -public final class DDWebViewTrackingSessionReplayConfiguration: NSObject { - /// Available privacy levels for content masking. - @objc - public enum DDPrivacyLevel: Int { - /// Record all content. - case allow - /// Mask all content. - case mask - /// Mask input elements, but record all other content. - case maskUserInput - - internal var toSwift: WebViewTracking.SessionReplayConfiguration.PrivacyLevel { - switch self { - case .allow: return .allow - case .mask: return .mask - case .maskUserInput: return .maskUserInput - } - } - } - - /// The privacy level to use for the web view replay recording. - @objc public var privacyLevel: DDPrivacyLevel - - /// Creates Webview Session Replay configuration. - /// - /// - Parameters: - /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. - @objc - override public init() { - self.privacyLevel = .mask - } - - /// Creates Webview Session Replay configuration. - /// - /// - Parameters: - /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. - @objc - public init( - privacyLevel: DDPrivacyLevel - ) { - self.privacyLevel = privacyLevel - } - - internal var toSwift: WebViewTracking.SessionReplayConfiguration { - return .init( - privacyLevel: privacyLevel.toSwift - ) - } -} From 29af80b1f6462dbea68cc81ae5c6df01a69d8934 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 21 May 2024 14:40:50 +0200 Subject: [PATCH 138/153] RUM-4313 CR feedback - move WVT+objc to `ObjC` folder to conform with common convention --- Datadog/Datadog.xcodeproj/project.pbxproj | 10 +++++++++- .../Sources/{ => ObjC}/WebViewTracking+objc.swift | 0 2 files changed, 9 insertions(+), 1 deletion(-) rename DatadogWebViewTracking/Sources/{ => ObjC}/WebViewTracking+objc.swift (100%) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 9076b7cc0c..a392c6c783 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -3339,8 +3339,8 @@ 3CE11A3B29F7BEE700202522 /* DatadogWebViewTracking */ = { isa = PBXGroup; children = ( + 6174D6072BFCCDA400EC7469 /* ObjC */, 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */, - 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */, D29732462A5C108700827599 /* DDScriptMessageHandler.swift */, D29732472A5C108700827599 /* MessageEmitter.swift */, ); @@ -4747,6 +4747,14 @@ path = ../DatadogCrashReporting/Tests; sourceTree = ""; }; + 6174D6072BFCCDA400EC7469 /* ObjC */ = { + isa = PBXGroup; + children = ( + 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */, + ); + path = ObjC; + sourceTree = ""; + }; 617699162A8608C20030022B /* Context */ = { isa = PBXGroup; children = ( diff --git a/DatadogWebViewTracking/Sources/WebViewTracking+objc.swift b/DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift similarity index 100% rename from DatadogWebViewTracking/Sources/WebViewTracking+objc.swift rename to DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift From 79e67e860b4707d3f6bdd168a11d9bc6ee545e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Lilensten?= Date: Fri, 17 May 2024 10:06:09 +0200 Subject: [PATCH 139/153] refacto external links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Lilensten --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 145e0b182e..6d97ff462a 100644 --- a/README.md +++ b/README.md @@ -22,19 +22,19 @@ ### Log Collection -See the dedicated [Datadog iOS Log Collection](https://docs.datadoghq.com/logs/log_collection/ios) documentation to learn how to send logs from your iOS application to Datadog. +See the dedicated [Datadog iOS Log Collection][1] documentation to learn how to send logs from your iOS application to Datadog. ![Datadog iOS Log Collection](docs/images/logging.png) ### Trace Collection -See [Datadog iOS Trace Collection](https://docs.datadoghq.com/tracing/setup_overview/setup/ios) documentation to try it out. +See [Datadog iOS Trace Collection][2] documentation to try it out. ![Datadog iOS Log Collection](docs/images/tracing.png) ### RUM Events Collection -See [Datadog iOS RUM Collection](https://docs.datadoghq.com/real_user_monitoring/ios) documentation to try it out. +See [Datadog iOS RUM Collection][3] documentation to try it out. ![Datadog iOS RUM Collection](docs/images/rum.png) @@ -42,7 +42,7 @@ See [Datadog iOS RUM Collection](https://docs.datadoghq.com/real_user_monitoring ### Alamofire -If you use [Alamofire](https://github.com/Alamofire/Alamofire), review the [`Datadog Alamofire Extension` library](DatadogExtensions/Alamofire/) to learn how to automatically instrument requests with the Datadog iOS SDK. +If you use [Alamofire][4], review the [`Datadog Alamofire Extension` library](DatadogExtensions/Alamofire/) to learn how to automatically instrument requests with the Datadog iOS SDK. ## Contributing @@ -51,3 +51,9 @@ Pull requests are welcome. First, open an issue to discuss what you would like t ## License [Apache License, v2.0](LICENSE) + + +[1]: https://docs.datadoghq.com/logs/log_collection/ios +[2]: https://docs.datadoghq.com/tracing/setup_overview/setup/ios +[3]: https://docs.datadoghq.com/real_user_monitoring/ios +[4]: https://github.com/Alamofire/Alamofire \ No newline at end of file From df5873d61ec00699a349852090e647106efc06af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Lilensten?= Date: Fri, 17 May 2024 10:07:43 +0200 Subject: [PATCH 140/153] remove in-repo docs about webview and reference official docs in readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Lilensten --- README.md | 7 ++- docs/rum_collection/web_view_tracking.md | 69 ------------------------ 2 files changed, 6 insertions(+), 70 deletions(-) delete mode 100644 docs/rum_collection/web_view_tracking.md diff --git a/README.md b/README.md index 6d97ff462a..fb35816849 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,10 @@ See [Datadog iOS RUM Collection][3] documentation to try it out. ![Datadog iOS RUM Collection](docs/images/rum.png) +#### WebView Tracking + +RUM allows you to monitor web views and eliminate blind spots in your hybrid mobile applications. See [WebView Tracking][5] documentation to try it out. + ## Integrations ### Alamofire @@ -56,4 +60,5 @@ Pull requests are welcome. First, open an issue to discuss what you would like t [1]: https://docs.datadoghq.com/logs/log_collection/ios [2]: https://docs.datadoghq.com/tracing/setup_overview/setup/ios [3]: https://docs.datadoghq.com/real_user_monitoring/ios -[4]: https://github.com/Alamofire/Alamofire \ No newline at end of file +[4]: https://github.com/Alamofire/Alamofire +[5]: https://docs.datadoghq.com/real_user_monitoring/mobile_and_tv_monitoring/web_view_tracking?tab=ios \ No newline at end of file diff --git a/docs/rum_collection/web_view_tracking.md b/docs/rum_collection/web_view_tracking.md deleted file mode 100644 index bc40260e18..0000000000 --- a/docs/rum_collection/web_view_tracking.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -beta: true -dependencies: -- https://github.com/DataDog/dd-sdk-ios/blob/master/docs/web_view_tracking.md -title: iOS Web View Tracking -kind: documentation -description: Monitor web views in your hybrid iOS applications. -further_reading: -- link: "/real_user_monitoring/ios/" - tag: "Documentation" - text: "iOS Monitoring" -- link: "/real_user_monitoring/browser/" - tag: "Documentation" - text: "Browser Monitoring" ---- - -## Overview - -Real User Monitoring allows you to monitor web views and eliminate blind spots in your hybrid iOS and tvOS applications. - -You can perform the following: - -- Track user journeys across web and native components in mobile applications -- Scope the root cause of latency to web pages or native components in mobile applications -- Support users that have difficulty loading web pages on mobile devices - -## Setup - -### Prerequisites - -Set up the web page you want rendered on your mobile iOS and tvOS application with the RUM Browser SDK first. For more information, see [RUM Browser Monitoring][1]. - -### Instrument your web views - -The RUM iOS SDK provides APIs for you to control web view tracking. To add Web View Tracking, declare the following as an extension of `WKUserContentController`. - -`trackDatadogEvents(in hosts: Set)` -: Enables RUM event tracking in a web view for certain `hosts`. - -`stopTrackingDatadogEvents()` -: Disables RUM event tracking in a web view. When a web view is about to be de-allocated or you are done with the web view, call this API. - -For example: - -``` -import WebKit -import DatadogCore - -webView.configuration.userContentController.trackDatadogEvents(in: ["example.com"]) -``` - -## Access your web views - -Your web views appear as events and views in the [RUM Explorer][4] with associated `service` and `source` attributes. The `service` attribute indicates the web component the web view is generated from, and the `source` attribute denotes the mobile application's platform, such as iOS. - -Filter on your iOS and tvOS applications, and click a session. A side panel with a list of events in the session appears. - -{{< img src="real_user_monitoring/ios/ios-webview-tracking.png" alt="Webview events captured in a session in the RUM Explorer" style="width:100%;">}} - -Click **Open View waterfall** to navigate from the session to a resource waterfall visualization in the view's **Performance** tab. - -## Further Reading - -{{< partial name="whats-next/whats-next.html" >}} - -[1]: https://docs.datadoghq.com/real_user_monitoring/browser/#npm -[2]: https://github.com/DataDog/dd-sdk-ios/releases/tag/1.10.0-beta1 -[3]: https://docs.datadoghq.com/real_user_monitoring/ios/ -[4]: https://app.datadoghq.com/rum/explorer From 44fc881b5c6bf921e2034dd5fdd5820f5c9522b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Lilensten?= Date: Fri, 17 May 2024 10:10:21 +0200 Subject: [PATCH 141/153] remove in-repo docs about supported versions and reference official docs in readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maël Lilensten --- README.md | 6 ++++- SUPPORTED-VERSIONS.md | 63 ------------------------------------------- 2 files changed, 5 insertions(+), 64 deletions(-) delete mode 100644 SUPPORTED-VERSIONS.md diff --git a/README.md b/README.md index fb35816849..0a78f7067e 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,13 @@ Pull requests are welcome. First, open an issue to discuss what you would like t [Apache License, v2.0](LICENSE) +## Supported Versions + +See the [Supported Versions][6] documentation for more details. [1]: https://docs.datadoghq.com/logs/log_collection/ios [2]: https://docs.datadoghq.com/tracing/setup_overview/setup/ios [3]: https://docs.datadoghq.com/real_user_monitoring/ios [4]: https://github.com/Alamofire/Alamofire -[5]: https://docs.datadoghq.com/real_user_monitoring/mobile_and_tv_monitoring/web_view_tracking?tab=ios \ No newline at end of file +[5]: https://docs.datadoghq.com/real_user_monitoring/mobile_and_tv_monitoring/web_view_tracking?tab=ios +[6]: https://docs.datadoghq.com/real_user_monitoring/mobile_and_tv_monitoring/supported_versions/ios/ \ No newline at end of file diff --git a/SUPPORTED-VERSIONS.md b/SUPPORTED-VERSIONS.md deleted file mode 100644 index 6f56125caa..0000000000 --- a/SUPPORTED-VERSIONS.md +++ /dev/null @@ -1,63 +0,0 @@ -## Platforms - -| Platform | Supported | Version | -|------------|:---------:|-------:| -| **iOS** | ✅ | `11+` | -| **tvOS** | ✅ | `11+` | -| **iPadOS** | ✅ | `11+` | -| **macOS (Designed for iPad)** | ✅ | `11+` | -| **macOS (Catalyst)** | ⚠️ | `12+` | -| **macOS** | ⚠️ | `12+` | -| **visionOS** | ⚠️ | `1.0+` | -| **watchOS**| ❌ | `n/a` | -| **Linux** | ❌ | `n/a` | - -## VisionOS - -VisionOS is not officially supported by Datadog SDK. Some features may not be fully functional. Note that `DatadogCrashReporting` is not supported on VisionOS, due to lack of support on the [PLCrashReporter side](https://github.com/microsoft/plcrashreporter/issues/288). - -## MacOS - -MacOS is not officially supported by Datadog SDK. Some features may not be fully functional. Note that `DatadogRUM`, `DatadogSessionReplay` and `DatadogObjc` which heavily depend on `UIKit` do not build on macOS. - -## Catalyst - -We support Catalyst in build mode only, which means that macOS target will build, but functionalities for the SDK might not work for this target. - -## Xcode - -SDK is built using the most recent version of Xcode, but we make sure that it's backward compatible with the [lowest supported Xcode version for AppStore submission](https://developer.apple.com/news/?id=jd9wcyov). - -## Dependency Managers - -We currently support integration of the SDK using following dependency managers. -- [Swift Package Manager](https://docs.datadoghq.com/logs/log_collection/ios/?tab=swiftpackagemanagerspm) -- [Cocoapods](https://docs.datadoghq.com/logs/log_collection/ios/?tab=cocoapods) -- [Carthage](https://docs.datadoghq.com/logs/log_collection/ios/?tab=carthage) - -## Languages - -| Language | Version | -|-----------------|:------------:| -| **Swift** | `5.*` | -| **Objective-C** | `2.0` | - -## UI Framework Instrumentation - -| Framework | Automatic | Manual | -|-----------------|:------------:|:------:| -| **UIKit** | ✅ | ✅ | -| **SwiftUI** | ❌ | ✅ | - -## Networking Compatibility -| Framework | Automatic | Manual | -|-----------------|:------------:|:------:| -| **URLSession** | ✅ | ✅ | -|[**Alamofire 5+**](https://github.com/DataDog/dd-sdk-ios/tree/develop/DatadogExtensions/Alamofire) | ❌ | ✅ | -| **SwiftNIO** | ❌ | ❌ | - -*Note: Third party networking libraries can be instrumented by implementing custom `DDURLSessionDelegate`.* - -## Dependencies -The Datadog SDK depends on the following third-party library: -- [PLCrashReporter](https://github.com/microsoft/plcrashreporter) 1.11.1 From 2da8501d90bd05f5401327d823387f76242a8062 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 13:53:41 +0000 Subject: [PATCH 142/153] --- updated-dependencies: - dependency-name: rexml dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ddb0016f8e..c7d98e8fa9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,8 +72,10 @@ GEM nap (1.1.0) netrc (0.11.0) public_suffix (4.0.7) - rexml (3.2.5) + rexml (3.2.8) + strscan (>= 3.0.9) ruby-macho (2.5.1) + strscan (3.1.0) typhoeus (1.4.0) ethon (>= 0.9.0) tzinfo (2.0.6) From 961cda0c7af2ef737fa0549f6797cd97ca4b15ec Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 22 May 2024 10:41:55 +0200 Subject: [PATCH 143/153] Introduce `SDKMetrics` (folder) convention --- Datadog/Datadog.xcodeproj/project.pbxproj | 66 ++++++++++++++----- .../Core/Storage/FilesOrchestrator.swift | 4 +- .../BatchMetrics.swift} | 0 .../BatchMetricsTests.swift} | 2 +- .../MethodCalledMetric.swift} | 8 +-- .../Sources/SDKMetrics/SDKMetricFields.swift | 13 ++++ .../Sources/Telemetry/Telemetry.swift | 2 +- .../Tests/Telemetry/TelemetryTests.swift | 2 +- .../Integrations/TelemetryReceiverTests.swift | 2 +- 9 files changed, 68 insertions(+), 31 deletions(-) rename DatadogCore/Sources/{Utils/CoreMetrics.swift => SDKMetrics/BatchMetrics.swift} (100%) rename DatadogCore/Tests/Datadog/{Core/Utils/CoreMetricsTests.swift => SDKMetrics/BatchMetricsTests.swift} (97%) rename DatadogInternal/Sources/{Utils/SharedMetrics.swift => SDKMetrics/MethodCalledMetric.swift} (89%) create mode 100644 DatadogInternal/Sources/SDKMetrics/SDKMetricFields.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index c6b7038098..98514ae04c 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -307,8 +307,8 @@ 6133D2012A6EDB7700384BEF /* TestUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D257953E298ABA65008A1BE5 /* TestUtilities.framework */; }; 6133D20B2A6EDBC100384BEF /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; 61345613244756E300E7DA6B /* PerformancePresetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61345612244756E300E7DA6B /* PerformancePresetTests.swift */; }; - 6134CDB12A691E850061CCD9 /* CoreMetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6134CDB02A691E850061CCD9 /* CoreMetricsTests.swift */; }; - 6134CDB22A691E850061CCD9 /* CoreMetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6134CDB02A691E850061CCD9 /* CoreMetricsTests.swift */; }; + 6134CDB12A691E850061CCD9 /* BatchMetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6134CDB02A691E850061CCD9 /* BatchMetricsTests.swift */; }; + 6134CDB22A691E850061CCD9 /* BatchMetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6134CDB02A691E850061CCD9 /* BatchMetricsTests.swift */; }; 61363D9F24D99BAA0084CD6F /* DDErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61363D9E24D99BAA0084CD6F /* DDErrorTests.swift */; }; 6136CB4A2A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6136CB492A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift */; }; 6136CB4B2A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6136CB492A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift */; }; @@ -320,8 +320,8 @@ 613F9C192BAC3527007C7606 /* DatadogCore+FeatureDataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613F9C172BAC3527007C7606 /* DatadogCore+FeatureDataStoreTests.swift */; }; 613F9C1B2BB03188007C7606 /* FeatureScopeMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613F9C1A2BB03188007C7606 /* FeatureScopeMock.swift */; }; 613F9C1C2BB03188007C7606 /* FeatureScopeMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613F9C1A2BB03188007C7606 /* FeatureScopeMock.swift */; }; - 614396722A67D74F00197326 /* CoreMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614396712A67D74F00197326 /* CoreMetrics.swift */; }; - 614396732A67D74F00197326 /* CoreMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614396712A67D74F00197326 /* CoreMetrics.swift */; }; + 614396722A67D74F00197326 /* BatchMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614396712A67D74F00197326 /* BatchMetrics.swift */; }; + 614396732A67D74F00197326 /* BatchMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614396712A67D74F00197326 /* BatchMetrics.swift */; }; 61441C0524616DE9003D8BB8 /* ExampleAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61441C0424616DE9003D8BB8 /* ExampleAppDelegate.swift */; }; 61441C0C24616DE9003D8BB8 /* Main iOS.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 61441C0A24616DE9003D8BB8 /* Main iOS.storyboard */; }; 61441C0E24616DEC003D8BB8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 61441C0D24616DEC003D8BB8 /* Assets.xcassets */; }; @@ -414,6 +414,8 @@ 617247B825DAB0E2007085B3 /* DDCrashReportBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617247B725DAB0E2007085B3 /* DDCrashReportBuilder.swift */; }; 6174D6042BFB9AB600EC7469 /* WebViewTracking+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */; }; 6174D6062BFB9D6400EC7469 /* DDWebViewTracking+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */; }; + 6174D60C2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */; }; + 6174D60D2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */; }; 6175922B2A6FA8EE0073F431 /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; 6175922D2A6FADDD0073F431 /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; 6175C3512BCE66DB006FAAB0 /* TraceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */; }; @@ -656,8 +658,8 @@ A7EA11622AB0CE6C00C73970 /* DDUIKitRUMActionsPredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DA18062AB0CA4700F76337 /* DDUIKitRUMActionsPredicateTests.swift */; }; A7EA88562B17639A00FE2580 /* ResourcesWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7EA88552B17639A00FE2580 /* ResourcesWriter.swift */; }; A7F651302B7655DE004B0EDB /* UIImageResourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F6512F2B7655DE004B0EDB /* UIImageResourceTests.swift */; }; - A7FA98CE2BA1A6930018D6B5 /* SharedMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FA98CD2BA1A6930018D6B5 /* SharedMetrics.swift */; }; - A7FA98CF2BA1A6930018D6B5 /* SharedMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FA98CD2BA1A6930018D6B5 /* SharedMetrics.swift */; }; + A7FA98CE2BA1A6930018D6B5 /* MethodCalledMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FA98CD2BA1A6930018D6B5 /* MethodCalledMetric.swift */; }; + A7FA98CF2BA1A6930018D6B5 /* MethodCalledMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7FA98CD2BA1A6930018D6B5 /* MethodCalledMetric.swift */; }; D2056C212BBFE05A0085BC76 /* WireframesBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2056C202BBFE05A0085BC76 /* WireframesBuilderTests.swift */; }; D20605A3287464F40047275C /* ContextValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20605A2287464F40047275C /* ContextValuePublisher.swift */; }; D20605A4287464F40047275C /* ContextValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20605A2287464F40047275C /* ContextValuePublisher.swift */; }; @@ -2288,7 +2290,7 @@ 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogSessionReplay.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6133D2082A6EDB7700384BEF /* DatadogSessionReplayTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogSessionReplayTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 61345612244756E300E7DA6B /* PerformancePresetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformancePresetTests.swift; sourceTree = ""; }; - 6134CDB02A691E850061CCD9 /* CoreMetricsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMetricsTests.swift; sourceTree = ""; }; + 6134CDB02A691E850061CCD9 /* BatchMetricsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchMetricsTests.swift; sourceTree = ""; }; 61363D9E24D99BAA0084CD6F /* DDErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDErrorTests.swift; sourceTree = ""; }; 6136CB492A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FilesOrchestrator+MetricsTests.swift"; sourceTree = ""; }; 61378BA72555329E00F28837 /* DatadogTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DatadogTests.xcconfig; sourceTree = ""; }; @@ -2308,7 +2310,7 @@ 6141015A251A601D00E3C2D9 /* UIKitRUMUserActionsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitRUMUserActionsHandler.swift; sourceTree = ""; }; 61410166251A661D00E3C2D9 /* UIApplicationSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationSwizzlerTests.swift; sourceTree = ""; }; 61411B0F24EC15AC0012EAB2 /* Casting+RUM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Casting+RUM.swift"; sourceTree = ""; }; - 614396712A67D74F00197326 /* CoreMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMetrics.swift; sourceTree = ""; }; + 614396712A67D74F00197326 /* BatchMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchMetrics.swift; sourceTree = ""; }; 61441C0224616DE9003D8BB8 /* Example iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 61441C0424616DE9003D8BB8 /* ExampleAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleAppDelegate.swift; sourceTree = ""; }; 61441C0B24616DE9003D8BB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/Main iOS.storyboard"; sourceTree = ""; }; @@ -2395,6 +2397,7 @@ 617247B725DAB0E2007085B3 /* DDCrashReportBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDCrashReportBuilder.swift; sourceTree = ""; }; 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebViewTracking+objc.swift"; sourceTree = ""; }; 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDWebViewTracking+apiTests.m"; sourceTree = ""; }; + 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKMetricFields.swift; sourceTree = ""; }; 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceContext.swift; sourceTree = ""; }; 617699172A860D9D0030022B /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; 6176991A2A86121B0030022B /* HTTPClientMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClientMock.swift; sourceTree = ""; }; @@ -2665,7 +2668,7 @@ A7F773D32924EA2D00AC1A62 /* B3HTTPHeaders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = B3HTTPHeaders.swift; sourceTree = ""; }; A7F773DB29253F8B00AC1A62 /* B3HTTPHeadersWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = B3HTTPHeadersWriter.swift; sourceTree = ""; }; A7F773DC29253F8B00AC1A62 /* B3HTTPHeadersReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = B3HTTPHeadersReader.swift; sourceTree = ""; }; - A7FA98CD2BA1A6930018D6B5 /* SharedMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedMetrics.swift; sourceTree = ""; }; + A7FA98CD2BA1A6930018D6B5 /* MethodCalledMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodCalledMetric.swift; sourceTree = ""; }; B3BBBCB0265E71C600943419 /* VitalMemoryReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VitalMemoryReader.swift; sourceTree = ""; }; B3BBBCBB265E71D100943419 /* VitalMemoryReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VitalMemoryReaderTests.swift; sourceTree = ""; }; B3FC3C0626526EFF00DEED9E /* VitalInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VitalInfo.swift; sourceTree = ""; }; @@ -4014,6 +4017,7 @@ 61216277247D1F2100AC5D67 /* FeaturesIntegration */, 61133BB72423979B00786299 /* Utils */, 61D3E0C7277B237D008BE766 /* Kronos */, + 6174D6082BFDDD1E00EC7469 /* SDKMetrics */, ); name = DatadogCore; path = ../DatadogCore/Sources; @@ -4055,7 +4059,6 @@ 61C3638424361E9200C4D4E6 /* Globals.swift */, 6139CD702589FAFD007E8BB7 /* Retrying.swift */, 61DA8CAE28620C760074A606 /* Cryptography.swift */, - 614396712A67D74F00197326 /* CoreMetrics.swift */, ); path = Utils; sourceTree = ""; @@ -4178,6 +4181,7 @@ 61133C352423990D00786299 /* Utils */, 61BAD46826415FA2001886CA /* OpenTracing */, 61D3E0DD277B3D6E008BE766 /* Kronos */, + 6174D6092BFDDDE400EC7469 /* SDKMetrics */, ); path = Datadog; sourceTree = ""; @@ -4763,6 +4767,31 @@ path = ObjC; sourceTree = ""; }; + 6174D6082BFDDD1E00EC7469 /* SDKMetrics */ = { + isa = PBXGroup; + children = ( + 614396712A67D74F00197326 /* BatchMetrics.swift */, + ); + path = SDKMetrics; + sourceTree = ""; + }; + 6174D6092BFDDDE400EC7469 /* SDKMetrics */ = { + isa = PBXGroup; + children = ( + 6134CDB02A691E850061CCD9 /* BatchMetricsTests.swift */, + ); + path = SDKMetrics; + sourceTree = ""; + }; + 6174D60A2BFDDE1900EC7469 /* SDKMetrics */ = { + isa = PBXGroup; + children = ( + 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */, + A7FA98CD2BA1A6930018D6B5 /* MethodCalledMetric.swift */, + ); + path = SDKMetrics; + sourceTree = ""; + }; 617699162A8608C20030022B /* Context */ = { isa = PBXGroup; children = ( @@ -4876,7 +4905,6 @@ 9E58E8E224615EDA008E5063 /* JSONEncoderTests.swift */, 61363D9E24D99BAA0084CD6F /* DDErrorTests.swift */, 61DA8CB1286215DE0074A606 /* CryptographyTests.swift */, - 6134CDB02A691E850061CCD9 /* CoreMetricsTests.swift */, ); path = Utils; sourceTree = ""; @@ -5626,6 +5654,7 @@ D2A783D329A53049003B03BB /* Utils */, D2160CE229C0DFED00FAA9A5 /* Swizzling */, D2EBEE1D29BA15BC00B15732 /* NetworkInstrumentation */, + 6174D60A2BFDDE1900EC7469 /* SDKMetrics */, ); name = DatadogInternal; path = ../DatadogInternal/Sources; @@ -6083,7 +6112,6 @@ D23039DC298D5235001A1FA3 /* DDError.swift */, 61133BBA2423979B00786299 /* SwiftExtensions.swift */, D29A9F9429DDB1DB005C54A4 /* UIKitExtensions.swift */, - A7FA98CD2BA1A6930018D6B5 /* SharedMetrics.swift */, ); path = Utils; sourceTree = ""; @@ -7824,7 +7852,7 @@ D20605CB2875A83F0047275C /* ContextValueReader.swift in Sources */, 61D3E0DB277B23F1008BE766 /* KronosNSTimer+ClosureKit.swift in Sources */, D20605A92874C1CD0047275C /* NetworkConnectionInfoPublisher.swift in Sources */, - 614396722A67D74F00197326 /* CoreMetrics.swift in Sources */, + 614396722A67D74F00197326 /* BatchMetrics.swift in Sources */, D20605A6287476230047275C /* ServerOffsetPublisher.swift in Sources */, 613E792F2577B0F900DFCC17 /* Reader.swift in Sources */, 61D3E0D3277B23F1008BE766 /* KronosDNSResolver.swift in Sources */, @@ -7988,7 +8016,7 @@ A7CA21802BEBB1E800732571 /* AppBackgroundTaskCoordinatorTests.swift in Sources */, D20605C52875895E0047275C /* KronosClockMock.swift in Sources */, 61133C642423990D00786299 /* LoggerTests.swift in Sources */, - 6134CDB12A691E850061CCD9 /* CoreMetricsTests.swift in Sources */, + 6134CDB12A691E850061CCD9 /* BatchMetricsTests.swift in Sources */, D24C9C6029A7CB0A002057CF /* DatadogLogsFeatureTests.swift in Sources */, 617B953D24BF4D8F00E6F443 /* RUMMonitorTests.swift in Sources */, D244B3A3271EDACD003E1B29 /* SwiftUIExtensionsTests.swift in Sources */, @@ -8448,7 +8476,7 @@ D2EBEE2229BA160F00B15732 /* TracePropagationHeadersReader.swift in Sources */, D2303A02298D5236001A1FA3 /* ReadWriteLock.swift in Sources */, D2EBEE2429BA160F00B15732 /* W3CHTTPHeadersReader.swift in Sources */, - A7FA98CE2BA1A6930018D6B5 /* SharedMetrics.swift in Sources */, + A7FA98CE2BA1A6930018D6B5 /* MethodCalledMetric.swift in Sources */, D23039E8298D5236001A1FA3 /* DatadogContext.swift in Sources */, D23039FF298D5236001A1FA3 /* Foundation+Datadog.swift in Sources */, D2F8235329915E12003C7E99 /* DatadogSite.swift in Sources */, @@ -8470,6 +8498,7 @@ D23039E0298D5235001A1FA3 /* DatadogCoreProtocol.swift in Sources */, D23039FD298D5236001A1FA3 /* DataCompression.swift in Sources */, 6167E6F92B81E95900C3CA2D /* BinaryImage.swift in Sources */, + 6174D60C2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */, D23039F0298D5236001A1FA3 /* AnyEncoder.swift in Sources */, D2A783D429A5309F003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD72A543B3B00446CF9 /* Event.swift in Sources */, @@ -9043,7 +9072,7 @@ D2A7841029A53B2F003B03BB /* Directory.swift in Sources */, 61DA8CB028620C760074A606 /* Cryptography.swift in Sources */, D2DC4BF727F484AA00E4FB96 /* DataEncryption.swift in Sources */, - 614396732A67D74F00197326 /* CoreMetrics.swift in Sources */, + 614396732A67D74F00197326 /* BatchMetrics.swift in Sources */, D29294E1291D5ED500F8EFF9 /* ApplicationVersionPublisher.swift in Sources */, D20605A7287476230047275C /* ServerOffsetPublisher.swift in Sources */, D21C26C628A3B49C005DD405 /* FeatureStorage.swift in Sources */, @@ -9090,7 +9119,7 @@ D28F836929C9E71D00EF8EA2 /* DDSpanTests.swift in Sources */, 61B8BA92281812F60068AFF4 /* KronosInternetAddressTests.swift in Sources */, 3C1890162ABDE9C000CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m in Sources */, - 6134CDB22A691E850061CCD9 /* CoreMetricsTests.swift in Sources */, + 6134CDB22A691E850061CCD9 /* BatchMetricsTests.swift in Sources */, 61F930C62BA1C4EB005F0EE2 /* TLVBlockReaderTests.swift in Sources */, D22743E029DEB8B5001A7EF9 /* VitalCPUReaderTests.swift in Sources */, D2A1EE3C287EECC200D28DFB /* CarrierInfoPublisherTests.swift in Sources */, @@ -9374,7 +9403,7 @@ D2EBEE3029BA161100B15732 /* TracePropagationHeadersReader.swift in Sources */, D2DA2373298D57AA00C6C7E6 /* ReadWriteLock.swift in Sources */, D2EBEE3229BA161100B15732 /* W3CHTTPHeadersReader.swift in Sources */, - A7FA98CF2BA1A6930018D6B5 /* SharedMetrics.swift in Sources */, + A7FA98CF2BA1A6930018D6B5 /* MethodCalledMetric.swift in Sources */, D2DA2374298D57AA00C6C7E6 /* DatadogContext.swift in Sources */, D2DA2375298D57AA00C6C7E6 /* Foundation+Datadog.swift in Sources */, D2F8235429915E12003C7E99 /* DatadogSite.swift in Sources */, @@ -9396,6 +9425,7 @@ D2DA237C298D57AA00C6C7E6 /* DatadogCoreProtocol.swift in Sources */, D2DA237D298D57AA00C6C7E6 /* DataCompression.swift in Sources */, 6167E6FA2B81E95900C3CA2D /* BinaryImage.swift in Sources */, + 6174D60D2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */, D2DA237E298D57AA00C6C7E6 /* AnyEncoder.swift in Sources */, D2A783D529A530A0003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD82A543B3B00446CF9 /* Event.swift in Sources */, diff --git a/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift b/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift index 23b4443199..52be47d9df 100644 --- a/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift +++ b/DatadogCore/Sources/Core/Storage/FilesOrchestrator.swift @@ -234,7 +234,7 @@ internal class FilesOrchestrator: FilesOrchestratorType { telemetry.metric( name: BatchDeletedMetric.name, attributes: [ - BasicMetric.typeKey: BatchDeletedMetric.typeValue, + SDKMetricFields.typeKey: BatchDeletedMetric.typeValue, BatchMetric.trackKey: metricsData.trackName, BatchDeletedMetric.uploaderDelayKey: [ BatchDeletedMetric.uploaderDelayMinKey: metricsData.uploaderPerformance.minUploadDelay.toMilliseconds, @@ -264,7 +264,7 @@ internal class FilesOrchestrator: FilesOrchestratorType { telemetry.metric( name: BatchClosedMetric.name, attributes: [ - BasicMetric.typeKey: BatchClosedMetric.typeValue, + SDKMetricFields.typeKey: BatchClosedMetric.typeValue, BatchMetric.trackKey: metricsData.trackName, BatchMetric.consentKey: metricsData.consentLabel, BatchClosedMetric.uploaderWindowKey: performance.uploaderWindow.toMilliseconds, diff --git a/DatadogCore/Sources/Utils/CoreMetrics.swift b/DatadogCore/Sources/SDKMetrics/BatchMetrics.swift similarity index 100% rename from DatadogCore/Sources/Utils/CoreMetrics.swift rename to DatadogCore/Sources/SDKMetrics/BatchMetrics.swift diff --git a/DatadogCore/Tests/Datadog/Core/Utils/CoreMetricsTests.swift b/DatadogCore/Tests/Datadog/SDKMetrics/BatchMetricsTests.swift similarity index 97% rename from DatadogCore/Tests/Datadog/Core/Utils/CoreMetricsTests.swift rename to DatadogCore/Tests/Datadog/SDKMetrics/BatchMetricsTests.swift index 1de4a486b3..d02ee85b75 100644 --- a/DatadogCore/Tests/Datadog/Core/Utils/CoreMetricsTests.swift +++ b/DatadogCore/Tests/Datadog/SDKMetrics/BatchMetricsTests.swift @@ -7,7 +7,7 @@ import XCTest @testable import DatadogCore -class CoreMetricsTests: XCTestCase { +class BatchMetricsTests: XCTestCase { func testBatchRemovalReasonFormatting() { typealias RemovalReason = BatchDeletedMetric.RemovalReason diff --git a/DatadogInternal/Sources/Utils/SharedMetrics.swift b/DatadogInternal/Sources/SDKMetrics/MethodCalledMetric.swift similarity index 89% rename from DatadogInternal/Sources/Utils/SharedMetrics.swift rename to DatadogInternal/Sources/SDKMetrics/MethodCalledMetric.swift index 61bed76bb4..2d17c5c822 100644 --- a/DatadogInternal/Sources/Utils/SharedMetrics.swift +++ b/DatadogInternal/Sources/SDKMetrics/MethodCalledMetric.swift @@ -6,12 +6,6 @@ import Foundation -/// Common definitions for telemetries. -public enum BasicMetric { - /// Basic Metric type key. - public static let typeKey = "metric_type" -} - /// Definition of "Method Called" telemetry. public enum MethodCalledMetric { /// The name of this metric, included in telemetry log. @@ -57,6 +51,6 @@ public enum MethodCalledMetric { public extension [String: Encodable] { var isMethodCallAttributes: Bool { - self[BasicMetric.typeKey] as? String == MethodCalledMetric.typeValue + self[SDKMetricFields.typeKey] as? String == MethodCalledMetric.typeValue } } diff --git a/DatadogInternal/Sources/SDKMetrics/SDKMetricFields.swift b/DatadogInternal/Sources/SDKMetrics/SDKMetricFields.swift new file mode 100644 index 0000000000..b2b98b5470 --- /dev/null +++ b/DatadogInternal/Sources/SDKMetrics/SDKMetricFields.swift @@ -0,0 +1,13 @@ +/* + * 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 + +/// Common fields in SDK metrics. +public enum SDKMetricFields { + /// Metric type key. + public static let typeKey = "metric_type" +} diff --git a/DatadogInternal/Sources/Telemetry/Telemetry.swift b/DatadogInternal/Sources/Telemetry/Telemetry.swift index 0b868f2f1a..557ef3e4e5 100644 --- a/DatadogInternal/Sources/Telemetry/Telemetry.swift +++ b/DatadogInternal/Sources/Telemetry/Telemetry.swift @@ -128,7 +128,7 @@ public struct MethodCalledTrace { MethodCalledMetric.operationName: operationName, MethodCalledMetric.callerClass: callerClass, MethodCalledMetric.isSuccessful: isSuccessful, - BasicMetric.typeKey: MethodCalledMetric.typeValue + SDKMetricFields.typeKey: MethodCalledMetric.typeValue ] ) } diff --git a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift index c3eeae0a4a..306e7426ce 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift @@ -201,7 +201,7 @@ class TelemetryTests: XCTestCase { XCTAssertEqual(try XCTUnwrap(attributes[MethodCalledMetric.operationName] as? String), operationName) XCTAssertEqual(try XCTUnwrap(attributes[MethodCalledMetric.callerClass] as? String), callerClass) XCTAssertEqual(try XCTUnwrap(attributes[MethodCalledMetric.isSuccessful] as? Bool), isSuccessful) - XCTAssertEqual(try XCTUnwrap(attributes[BasicMetric.typeKey] as? String), MethodCalledMetric.typeValue) + XCTAssertEqual(try XCTUnwrap(attributes[SDKMetricFields.typeKey] as? String), MethodCalledMetric.typeValue) } } diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index afabbe8f3a..ae1f91bbcd 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -443,7 +443,7 @@ class TelemetryReceiverTests: XCTestCase { // Then let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first XCTAssertEqual(event?.telemetry.message, "[Mobile Metric] Method Called") - XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[BasicMetric.typeKey] as? String), MethodCalledMetric.typeValue) + XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[SDKMetricFields.typeKey] as? String), MethodCalledMetric.typeValue) XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.operationName] as? String), operationName) XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.callerClass] as? String), callerClass) XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.isSuccessful] as? Bool), isSuccessful) From 3145106853de5c675c327645820ef0f26a563a09 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 22 May 2024 17:55:31 +0200 Subject: [PATCH 144/153] fix: set traceContextInjection param for E2E test case --- Datadog/E2ETests/Tracing/TracerE2ETests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Datadog/E2ETests/Tracing/TracerE2ETests.swift b/Datadog/E2ETests/Tracing/TracerE2ETests.swift index c90a2176f4..f56bc03e76 100644 --- a/Datadog/E2ETests/Tracing/TracerE2ETests.swift +++ b/Datadog/E2ETests/Tracing/TracerE2ETests.swift @@ -51,7 +51,7 @@ class TracerE2ETests: E2ETests { /// ``` func test_trace_tracer_inject_span_context() { let anySpan = tracer.startSpan(operationName: .mockRandom()) // this span is never sent - let anyWriter = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 20)) + let anyWriter = HTTPHeadersWriter(samplingStrategy: .custom(sampleRate: 20), traceContextInjection: .all) measure(resourceName: DD.PerfSpanName.fromCurrentMethodName()) { tracer.inject(spanContext: anySpan.context, writer: anyWriter) From 248b36102cbba2a4d6a8740f30cf23caa8ec16a6 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 24 May 2024 09:54:40 +0200 Subject: [PATCH 145/153] refactor: Make `error.kind` and `error.stack` non-optional in SDK error telemetry this is to later aggregate SDK errors by their kind in "RUM Session Ended" telemetry --- Datadog/Datadog.xcodeproj/project.pbxproj | 18 +- ...ft => CoreTelemetryIntegrationTests.swift} | 4 + DatadogCore/Sources/Datadog+Internal.swift | 2 +- .../Datadog/OpenTracing/OTSpanTests.swift | 14 +- .../Datadog/RUM/TelemetryReceiverTests.swift | 2 +- .../Sources/Telemetry/Telemetry.swift | 34 +- .../Tests/Telemetry/TelemetryTests.swift | 316 +++++++----------- .../Integrations/TelemetryReceiver.swift | 2 +- .../Integrations/TelemetryReceiverTests.swift | 2 +- .../Tests/SessionReplayTests.swift | 7 +- TestUtilities/Helpers/ModuleName.swift | 16 + TestUtilities/Mocks/FeatureMessageMocks.swift | 9 +- TestUtilities/Mocks/TelemetryMocks.swift | 14 +- 13 files changed, 199 insertions(+), 241 deletions(-) rename Datadog/IntegrationUnitTests/Public/{TelemetryCoreIntegrationTests.swift => CoreTelemetryIntegrationTests.swift} (97%) create mode 100644 TestUtilities/Helpers/ModuleName.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 98514ae04c..16bb753457 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -223,8 +223,8 @@ 61054FD32A6EE1BA00AAA894 /* MultipartFormDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F902A6EE1BA00AAA894 /* MultipartFormDataTests.swift */; }; 61054FD42A6EE1BA00AAA894 /* SegmentRequestBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F912A6EE1BA00AAA894 /* SegmentRequestBuilderTests.swift */; }; 61054FD52A6EE1BA00AAA894 /* XCTAssertRectsEqual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61054F932A6EE1BA00AAA894 /* XCTAssertRectsEqual.swift */; }; - 610ABD4C2A6930CA00AFEA34 /* TelemetryCoreIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610ABD4B2A6930CA00AFEA34 /* TelemetryCoreIntegrationTests.swift */; }; - 610ABD4D2A6930CA00AFEA34 /* TelemetryCoreIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610ABD4B2A6930CA00AFEA34 /* TelemetryCoreIntegrationTests.swift */; }; + 610ABD4C2A6930CA00AFEA34 /* CoreTelemetryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610ABD4B2A6930CA00AFEA34 /* CoreTelemetryIntegrationTests.swift */; }; + 610ABD4D2A6930CA00AFEA34 /* CoreTelemetryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610ABD4B2A6930CA00AFEA34 /* CoreTelemetryIntegrationTests.swift */; }; 61112F8E2A4417D6006FFCA6 /* DDRUM+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 61112F8D2A4417D6006FFCA6 /* DDRUM+apiTests.m */; }; 61112F8F2A4417D6006FFCA6 /* DDRUM+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 61112F8D2A4417D6006FFCA6 /* DDRUM+apiTests.m */; }; 6111C58225C0081F00F5C4A2 /* RUMDataModels+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6111C58125C0081F00F5C4A2 /* RUMDataModels+objc.swift */; }; @@ -416,6 +416,8 @@ 6174D6062BFB9D6400EC7469 /* DDWebViewTracking+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */; }; 6174D60C2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */; }; 6174D60D2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */; }; + 6174D61D2C007B3300EC7469 /* ModuleName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D61C2C007B3300EC7469 /* ModuleName.swift */; }; + 6174D61E2C007B3300EC7469 /* ModuleName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D61C2C007B3300EC7469 /* ModuleName.swift */; }; 6175922B2A6FA8EE0073F431 /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; 6175922D2A6FADDD0073F431 /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; 6175C3512BCE66DB006FAAB0 /* TraceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */; }; @@ -2197,7 +2199,7 @@ 61054F902A6EE1BA00AAA894 /* MultipartFormDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormDataTests.swift; sourceTree = ""; }; 61054F912A6EE1BA00AAA894 /* SegmentRequestBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentRequestBuilderTests.swift; sourceTree = ""; }; 61054F932A6EE1BA00AAA894 /* XCTAssertRectsEqual.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTAssertRectsEqual.swift; sourceTree = ""; }; - 610ABD4B2A6930CA00AFEA34 /* TelemetryCoreIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryCoreIntegrationTests.swift; sourceTree = ""; }; + 610ABD4B2A6930CA00AFEA34 /* CoreTelemetryIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTelemetryIntegrationTests.swift; sourceTree = ""; }; 61112F8D2A4417D6006FFCA6 /* DDRUM+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDRUM+apiTests.m"; sourceTree = ""; }; 6111C58125C0081F00F5C4A2 /* RUMDataModels+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUMDataModels+objc.swift"; sourceTree = ""; }; 61122ECD25B1B74500F9C7F5 /* SpanSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanSanitizer.swift; sourceTree = ""; }; @@ -2398,6 +2400,7 @@ 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebViewTracking+objc.swift"; sourceTree = ""; }; 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDWebViewTracking+apiTests.m"; sourceTree = ""; }; 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKMetricFields.swift; sourceTree = ""; }; + 6174D61C2C007B3300EC7469 /* ModuleName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleName.swift; sourceTree = ""; }; 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceContext.swift; sourceTree = ""; }; 617699172A860D9D0030022B /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; 6176991A2A86121B0030022B /* HTTPClientMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClientMock.swift; sourceTree = ""; }; @@ -3883,7 +3886,7 @@ isa = PBXGroup; children = ( 6176991D2A8791880030022B /* Datadog+MultipleInstancesIntegrationTests.swift */, - 610ABD4B2A6930CA00AFEA34 /* TelemetryCoreIntegrationTests.swift */, + 610ABD4B2A6930CA00AFEA34 /* CoreTelemetryIntegrationTests.swift */, D20FD9D52ACC0934004D3569 /* WebLogIntegrationTests.swift */, D21831542B6A57530012B3A0 /* NetworkInstrumentationIntegrationTests.swift */, ); @@ -5886,6 +5889,7 @@ 61133C462423990D00786299 /* TestsDirectory.swift */, 61C713D22A3DFB4900FA735A /* FuzzyHelpers.swift */, 6167E72B2B84C72B00C3CA2D /* UIKitHelpers.swift */, + 6174D61C2C007B3300EC7469 /* ModuleName.swift */, ); path = Helpers; sourceTree = ""; @@ -8005,7 +8009,7 @@ 6128F57B2BA35D6200D35B08 /* FeatureDataStoreTests.swift in Sources */, D29A9FCE29DDC4BA005C54A4 /* RUMFeatureMocks.swift in Sources */, 61133C5B2423990D00786299 /* DirectoryTests.swift in Sources */, - 610ABD4C2A6930CA00AFEA34 /* TelemetryCoreIntegrationTests.swift in Sources */, + 610ABD4C2A6930CA00AFEA34 /* CoreTelemetryIntegrationTests.swift in Sources */, A79B0F64292BD074008742B3 /* DDB3HTTPHeadersWriter+apiTests.m in Sources */, D2B3F052282E827700C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */, D20605B92875729E0047275C /* ContextValuePublisherMock.swift in Sources */, @@ -8678,6 +8682,7 @@ D2A7840329A536AD003B03BB /* PrintFunctionMock.swift in Sources */, D2A7840D29A53A4B003B03BB /* TestsDirectory.swift in Sources */, D2DA23CF298D5F2300C6C7E6 /* FeatureMessageReceiverMock.swift in Sources */, + 6174D61D2C007B3300EC7469 /* ModuleName.swift in Sources */, D2160CF029C0EC4D00FAA9A5 /* SingleFeatureCoreMock.swift in Sources */, D2579554298ABB04008A1BE5 /* FeatureBaggageMock.swift in Sources */, 3C85D42C29F7C87D00AFF894 /* HostsSanitizerMock.swift in Sources */, @@ -8723,6 +8728,7 @@ D2A7840429A536AD003B03BB /* PrintFunctionMock.swift in Sources */, D2A7840E29A53A4B003B03BB /* TestsDirectory.swift in Sources */, D2DA23D0298D5F2300C6C7E6 /* FeatureMessageReceiverMock.swift in Sources */, + 6174D61E2C007B3300EC7469 /* ModuleName.swift in Sources */, D2160CF129C0EC4D00FAA9A5 /* SingleFeatureCoreMock.swift in Sources */, D257957E298ABB83008A1BE5 /* FeatureBaggageMock.swift in Sources */, 3C85D42D29F7C87D00AFF894 /* HostsSanitizerMock.swift in Sources */, @@ -9135,7 +9141,7 @@ D2CB6EE527C520D400A62B57 /* DataUploadConditionsTests.swift in Sources */, D2CB6EE627C520D400A62B57 /* DateFormattingTests.swift in Sources */, D2CB6EE727C520D400A62B57 /* FileTests.swift in Sources */, - 610ABD4D2A6930CA00AFEA34 /* TelemetryCoreIntegrationTests.swift in Sources */, + 610ABD4D2A6930CA00AFEA34 /* CoreTelemetryIntegrationTests.swift in Sources */, D2CB6EEA27C520D400A62B57 /* LogMatcher.swift in Sources */, D2CB6EEC27C520D400A62B57 /* CustomObjcViewController.m in Sources */, D2EFA876286E011900F1FAA6 /* DatadogContextProviderTests.swift in Sources */, diff --git a/Datadog/IntegrationUnitTests/Public/TelemetryCoreIntegrationTests.swift b/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift similarity index 97% rename from Datadog/IntegrationUnitTests/Public/TelemetryCoreIntegrationTests.swift rename to Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift index 59a47f41ae..9a0ee635a4 100644 --- a/Datadog/IntegrationUnitTests/Public/TelemetryCoreIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift @@ -31,7 +31,9 @@ class CoreTelemetryIntegrationTests: XCTestCase { // When core.telemetry.debug("Debug Telemetry", attributes: ["debug.attribute": 42]) + #sourceLocation(file: "File.swift", line: 42) core.telemetry.error("Error Telemetry") + #sourceLocation() core.telemetry.metric(name: "Metric Name", attributes: ["metric.attribute": 42]) core.telemetry.stopMethodCalled( core.telemetry.startMethodCalled(operationName: .mockRandom(), callerClass: .mockRandom()) @@ -50,6 +52,8 @@ class CoreTelemetryIntegrationTests: XCTestCase { let error = errorEvents[0] XCTAssertEqual(error.telemetry.message, "Error Telemetry") + XCTAssertEqual(error.telemetry.error?.kind, "\(moduleName())/FileError") + XCTAssertEqual(error.telemetry.error?.stack, "\(moduleName())/File.swift:42") let metric = debugEvents[1] XCTAssertEqual(metric.telemetry.message, "[Mobile Metric] Metric Name") diff --git a/DatadogCore/Sources/Datadog+Internal.swift b/DatadogCore/Sources/Datadog+Internal.swift index 4b8175faa1..ecf30fd50e 100644 --- a/DatadogCore/Sources/Datadog+Internal.swift +++ b/DatadogCore/Sources/Datadog+Internal.swift @@ -43,7 +43,7 @@ public struct _TelemetryProxy { /// See Telementry.error public func error(id: String, message: String, kind: String?, stack: String?) { - telemetry.error(id: id, message: message, kind: kind, stack: stack) + telemetry.error(id: id, message: message, kind: kind ?? "unknown", stack: stack ?? "unknown") } } diff --git a/DatadogCore/Tests/Datadog/OpenTracing/OTSpanTests.swift b/DatadogCore/Tests/Datadog/OpenTracing/OTSpanTests.swift index efdb4c04ca..5d8d0c4223 100644 --- a/DatadogCore/Tests/Datadog/OpenTracing/OTSpanTests.swift +++ b/DatadogCore/Tests/Datadog/OpenTracing/OTSpanTests.swift @@ -44,12 +44,6 @@ private extension Dictionary where Key == String, Value == Encodable { } class OTSpanTests: XCTestCase { - #if os(iOS) - private let testModuleName = "DatadogCoreTests_iOS" - #elseif os(tvOS) - private let testModuleName = "DatadogCoreTests_tvOS" - #endif - // MARK: - Test Error Conveniences func testWhenSettingErrorFromSwiftError_itLogsErrorFields() throws { @@ -71,7 +65,7 @@ class OTSpanTests: XCTestCase { XCTAssertEqual( try span.logs[0].otStack(), """ - \(testModuleName)/File.swift:42 + \(moduleName())/File.swift:42 swift error description """ ) @@ -123,7 +117,7 @@ class OTSpanTests: XCTestCase { XCTAssertEqual( try span.logs[0].otStack(), """ - \(testModuleName)/File.swift:42 + \(moduleName())/File.swift:42 Error Domain=DDSpan Code=1 "ns error description" UserInfo={NSLocalizedDescription=ns error description} """ ) @@ -147,7 +141,7 @@ class OTSpanTests: XCTestCase { XCTAssertEqual( try span.logs[0].otStack(), """ - \(testModuleName)/File.swift:42 + \(moduleName())/File.swift:42 """ ) } @@ -175,7 +169,7 @@ class OTSpanTests: XCTestCase { XCTAssertEqual( try span.logs[0].otStack(), """ - \(testModuleName)/File.swift:42 + \(moduleName())/File.swift:42 Thread 0 Crashed: 0 app 0x0000000102bc0d8c 0x102bb8000 + 36236 1 UIKitCore 0x00000001b513d9ac 0x1b4739000 + 10504620 diff --git a/DatadogCore/Tests/Datadog/RUM/TelemetryReceiverTests.swift b/DatadogCore/Tests/Datadog/RUM/TelemetryReceiverTests.swift index cbb5d5fc86..8713dbcd0d 100644 --- a/DatadogCore/Tests/Datadog/RUM/TelemetryReceiverTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/TelemetryReceiverTests.swift @@ -29,7 +29,7 @@ class TelemetryReceiverTests: XCTestCase { callConcurrently( closures: [ { core.telemetry.debug(id: .mockRandom(), message: "telemetry debug") }, - { core.telemetry.error(id: .mockRandom(), message: "telemetry error", kind: nil, stack: nil) }, + { core.telemetry.error(id: .mockRandom(), message: "telemetry error", kind: "error.kind", stack: "error.stack") }, { core.telemetry.configuration(batchSize: .mockRandom()) }, { core.set( diff --git a/DatadogInternal/Sources/Telemetry/Telemetry.swift b/DatadogInternal/Sources/Telemetry/Telemetry.swift index 557ef3e4e5..9aae9447b9 100644 --- a/DatadogInternal/Sources/Telemetry/Telemetry.swift +++ b/DatadogInternal/Sources/Telemetry/Telemetry.swift @@ -59,7 +59,7 @@ public struct ConfigurationTelemetry: Equatable { public enum TelemetryMessage { case debug(id: String, message: String, attributes: [String: Encodable]?) - case error(id: String, message: String, kind: String?, stack: String?) + case error(id: String, message: String, kind: String, stack: String) case configuration(ConfigurationTelemetry) case metric(name: String, attributes: [String: Encodable]) } @@ -152,7 +152,7 @@ extension Telemetry { /// - message: The error message. /// - kind: The kind of error. /// - stack: The stack trace. - public func error(id: String, message: String, kind: String?, stack: String?) { + public func error(id: String, message: String, kind: String, stack: String) { send(telemetry: .error(id: id, message: message, kind: kind, stack: stack)) } @@ -173,7 +173,7 @@ extension Telemetry { /// - attributes: Custom attributes attached to the log (optional). /// - file: The current file name. /// - line: The line number in file. - public func debug(_ message: String, attributes: [String: Encodable]? = nil, file: String = #file, line: Int = #line) { + public func debug(_ message: String, attributes: [String: Encodable]? = nil, file: String = #fileID, line: Int = #line) { debug(id: "\(file):\(line):\(message)", message: message, attributes: attributes) } @@ -181,13 +181,25 @@ extension Telemetry { /// /// - Parameters: /// - message: The error message. + /// - kind: The kind of error. /// - stack: The stack trace. /// - file: The current file name. /// - line: The line number in file. - /// - file: The current file name. - /// - line: The line number in file. - public func error(_ message: String, kind: String? = nil, stack: String? = nil, file: String = #file, line: Int = #line) { - error(id: "\(file):\(line):\(message)", message: message, kind: kind, stack: stack) + public func error(_ message: String, kind: String? = nil, stack: String? = nil, file: String = #fileID, line: Int = #line) { + func defaultKind(fileID: String) -> String { + // e.g. `DatadogInternal/Telemetry.swift` -> `DatadogInternal/TelemetryError` + return "\(fileID.hasSuffix(".swift") ? String(fileID.dropLast(6)) : fileID)Error" + } + func defaultStack(fileID: String, line: Int) -> String { + "\(fileID):\(line)" + } + + error( + id: "\(file):\(line):\(message)", + message: message, + kind: kind ?? defaultKind(fileID: file), + stack: stack ?? defaultStack(fileID: file, line: line) + ) } /// Collect execution error. @@ -196,7 +208,7 @@ extension Telemetry { /// - error: The error. /// - file: The current file name. /// - line: The line number in file. - public func error(_ error: DDError, file: String = #file, line: Int = #line) { + public func error(_ error: DDError, file: String = #fileID, line: Int = #line) { self.error(error.message, kind: error.type, stack: error.stack, file: file, line: line) } @@ -207,7 +219,7 @@ extension Telemetry { /// - error: The error. /// - file: The current file name. /// - line: The line number in file. - public func error(_ message: String, error: DDError, file: String = #file, line: Int = #line) { + public func error(_ message: String, error: DDError, file: String = #fileID, line: Int = #line) { self.error("\(message) - \(error.message)", kind: error.type, stack: error.stack, file: file, line: line) } @@ -217,7 +229,7 @@ extension Telemetry { /// - error: The error. /// - file: The current file name. /// - line: The line number in file. - public func error(_ error: Error, file: String = #file, line: Int = #line) { + public func error(_ error: Error, file: String = #fileID, line: Int = #line) { self.error(DDError(error: error), file: file, line: line) } @@ -228,7 +240,7 @@ extension Telemetry { /// - error: The error. /// - file: The current file name. /// - line: The line number in file. - public func error(_ message: String, error: Error, file: String = #file, line: Int = #line) { + public func error(_ message: String, error: Error, file: String = #fileID, line: Int = #line) { self.error(message, error: DDError(error: error), file: file, line: line) } diff --git a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift index 306e7426ce..2e1ef88b0d 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift @@ -5,267 +5,179 @@ */ import XCTest +import Foundation import TestUtilities - @testable import DatadogInternal class TelemetryTests: XCTestCase { - func testTelemetryDebug() { - // Given - class TelemetryTest: Telemetry { - var debug: (id: String, message: String, attributes: [String: Encodable]?)? - - func send(telemetry: DatadogInternal.TelemetryMessage) { - guard case let .debug(id, message, attributes) = telemetry else { - return - } - - debug = (id: id, message: message, attributes: attributes) - } - } + private let telemetry = TelemetryMock() - let telemetry = TelemetryTest() - - struct SwiftError: Error { - let description = "error description" - } + // MARK: - Debug Telemetry + func testSendingDebugTelemetry() throws { // When #sourceLocation(file: "File.swift", line: 1) telemetry.debug("debug message", attributes: ["foo": "bar"]) #sourceLocation() // Then - XCTAssertEqual(telemetry.debug?.id, "File.swift:1:debug message") - XCTAssertEqual(telemetry.debug?.message, "debug message") - XCTAssertEqual(telemetry.debug?.attributes as? [String: String], ["foo": "bar"]) + let debug = try XCTUnwrap(telemetry.messages.firstDebug()) + XCTAssertEqual(debug.id, "\(moduleName())/File.swift:1:debug message") + XCTAssertEqual(debug.message, "debug message") + XCTAssertEqual(debug.attributes as? [String: String], ["foo": "bar"]) + XCTAssertEqual(telemetry.messages.count, 1) } - func testTelemetryErrorFormatting() { - // Given - class TelemetryTest: Telemetry { - var error: (id: String, message: String, kind: String?, stack: String?)? - - func send(telemetry: DatadogInternal.TelemetryMessage) { - guard case let .error(id, message, kind, stack) = telemetry else { - return - } - - error = (id: id, message: message, kind: kind, stack: stack) - } - } - - let telemetry = TelemetryTest() - - struct SwiftError: Error { - let description = "error description" - } - - let swiftError = SwiftError() - - let nsError = NSError( - domain: "custom-domain", - code: 10, - userInfo: [ - NSLocalizedDescriptionKey: "error description" - ] - ) + // MARK: - Error Telemetry + func testSendingErrorTelemetry() throws { // When #sourceLocation(file: "File.swift", line: 1) - telemetry.error(swiftError) + telemetry.error("error message", kind: "error.kind", stack: "error.stack") #sourceLocation() // Then - XCTAssertEqual(telemetry.error?.id, #"File.swift:1:SwiftError(description: "error description")"#) - XCTAssertEqual(telemetry.error?.message, #"SwiftError(description: "error description")"#) - XCTAssertEqual(telemetry.error?.kind, "SwiftError") - XCTAssertEqual(telemetry.error?.stack, #"SwiftError(description: "error description")"#) + let error = try XCTUnwrap(telemetry.messages.firstError()) + XCTAssertEqual(error.id, "\(moduleName())/File.swift:1:error message") + XCTAssertEqual(error.message, "error message") + XCTAssertEqual(error.kind, "error.kind") + XCTAssertEqual(error.stack, "error.stack") + XCTAssertEqual(telemetry.messages.count, 1) + } + func testSendingErrorTelemetry_whenNoKindAndNoStack() throws { // When - #sourceLocation(file: "File.swift", line: 2) - telemetry.error(nsError) + #sourceLocation(file: "File.swift", line: 1) + telemetry.error("error message", kind: nil, stack: nil) #sourceLocation() // Then - XCTAssertEqual(telemetry.error?.id, "File.swift:2:error description") - XCTAssertEqual(telemetry.error?.message, "error description") - XCTAssertEqual(telemetry.error?.kind, "custom-domain - 10") - XCTAssertEqual( - telemetry.error?.stack, - """ - Error Domain=custom-domain Code=10 "error description" UserInfo={NSLocalizedDescription=error description} - """ - ) - - // When - telemetry.error("swift error", error: swiftError) - // Then - XCTAssertEqual(telemetry.error?.message, #"swift error - SwiftError(description: "error description")"#) - - // When - telemetry.error("ns error", error: nsError) - // Then - XCTAssertEqual(telemetry.error?.message, "ns error - error description") + let error = try XCTUnwrap(telemetry.messages.firstError()) + XCTAssertEqual(error.id, "\(moduleName())/File.swift:1:error message") + XCTAssertEqual(error.message, "error message") + XCTAssertEqual(error.kind, "\(moduleName())/FileError") + XCTAssertEqual(error.stack, "\(moduleName())/File.swift:1") + XCTAssertEqual(telemetry.messages.count, 1) } - func testTelemetryConfiguration() { + func testSendingErrorTelemetry_withSwiftError() throws { // Given - let expectedConfiguration: ConfigurationTelemetry = .mockRandom() - - let telemetry = TelemetryTest() + struct SwiftError: Error { + let description = "error description" + } + let swiftError = SwiftError() // When - telemetry.applyConfiguration(configuration: expectedConfiguration) + telemetry.error(swiftError) + telemetry.error("custom message", error: swiftError) // Then - XCTAssertEqual(telemetry.configuration, expectedConfiguration) + let errors = telemetry.messages.compactMap({ $0.asError }) + XCTAssertEqual(telemetry.messages.count, 2) + XCTAssertEqual(errors[0].message, #"SwiftError(description: "error description")"#) + XCTAssertEqual(errors[0].kind, "SwiftError") + XCTAssertEqual(errors[0].stack, #"SwiftError(description: "error description")"#) + XCTAssertEqual(errors[1].message, #"custom message - SwiftError(description: "error description")"#) + XCTAssertEqual(errors[1].kind, "SwiftError") + XCTAssertEqual(errors[1].stack, #"SwiftError(description: "error description")"#) } - func testTelemetryConfigurationMerge() { + func testSendingErrorTelemetry_withNSError() throws { // Given - let initialConfiguration: ConfigurationTelemetry = .mockRandom() - let expectedConfiguration: ConfigurationTelemetry = .mockRandom() - - let telemetry = TelemetryTest() + let nsError = NSError( + domain: "custom-domain", + code: 10, + userInfo: [NSLocalizedDescriptionKey: "error description"] + ) // When - telemetry.applyConfiguration(configuration: initialConfiguration) - telemetry.applyConfiguration(configuration: expectedConfiguration) + telemetry.error(nsError) + telemetry.error("custom message", error: nsError) // Then - XCTAssertEqual(telemetry.configuration, expectedConfiguration) - XCTAssertNotEqual(telemetry.configuration, initialConfiguration) + let errors = telemetry.messages.compactMap({ $0.asError }) + XCTAssertEqual(telemetry.messages.count, 2) + XCTAssertEqual(errors[0].message, "error description") + XCTAssertEqual(errors[0].kind, "custom-domain - 10") + XCTAssertEqual(errors[0].stack, #"Error Domain=custom-domain Code=10 "error description" UserInfo={NSLocalizedDescription=error description}"#) + XCTAssertEqual(errors[1].message, "custom message - error description") + XCTAssertEqual(errors[1].kind, "custom-domain - 10") + XCTAssertEqual(errors[1].stack, #"Error Domain=custom-domain Code=10 "error description" UserInfo={NSLocalizedDescription=error description}"#) } - func testWhenSendingTelemetryMessage_itForwardsToCore() throws { - // Given - class Receiver: FeatureMessageReceiver { - var telemetry: TelemetryMessage? - - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - guard case .telemetry(let telemetry) = message else { - return false - } - - self.telemetry = telemetry - return true - } - } - - let receiver = Receiver() - let core = PassthroughCoreMock(messageReceiver: receiver) + // MARK: - Configuration Telemetry + func testSendingConfigurationTelemetry() throws { // When - core.telemetry.debug("debug") + telemetry.configuration(batchSize: 123, batchUploadFrequency: 456) // only some values // Then - guard case .debug(_, let message, _) = receiver.telemetry else { - return XCTFail("A debug should be send to core.") - } - XCTAssertEqual(message, "debug") + let configuration = try XCTUnwrap(telemetry.messages.firstConfiguration()) + XCTAssertEqual(configuration.batchSize, 123) + XCTAssertEqual(configuration.batchUploadFrequency, 456) + } + + // MARK: - Metric Telemetry + func testSendingMetricTelemetry() throws { // When - core.telemetry.error("error") + telemetry.metric(name: "metric name", attributes: ["attribute": "value"]) // Then - guard case .error(_, let message, _, _) = receiver.telemetry else { - return XCTFail("An error should be send to core.") - } - XCTAssertEqual(message, "error") + let metric = try XCTUnwrap(telemetry.messages.compactMap({ $0.asMetric }).first) + XCTAssertEqual(metric.name, "metric name") + XCTAssertEqual(metric.attributes as? [String: String], ["attribute": "value"]) + } - // When - core.telemetry.configuration(batchSize: 0) + func testStartingMethodCalledMetricTrace_whenSampled() throws { + XCTAssertNotNil(telemetry.startMethodCalled(operationName: .mockAny(), callerClass: .mockAny(), samplingRate: 100)) + } - // Then - guard case .configuration(let configuration) = receiver.telemetry else { - return XCTFail("An error should be send to core.") - } - XCTAssertEqual(configuration.batchSize, 0) + func testStartingMethodCalledMetricTrace_whenNotSampled() throws { + XCTAssertNil(telemetry.startMethodCalled(operationName: .mockAny(), callerClass: .mockAny(), samplingRate: 0)) + } + + func testTrackingMethodCallMetricTelemetry() throws { + let operationName: String = .mockRandom() + let callerClass: String = .mockRandom() + let isSuccessful: Bool = .random() // When - let operationName = String.mockRandom() - let callerClass = String.mockRandom() - let isSuccessful = Bool.random() - core.telemetry.stopMethodCalled( - core.telemetry.startMethodCalled(operationName: operationName, callerClass: callerClass, samplingRate: 100), - isSuccessful: isSuccessful - ) + let metricTrace = telemetry.startMethodCalled(operationName: operationName, callerClass: callerClass, samplingRate: 100) + Thread.sleep(forTimeInterval: 0.05) + telemetry.stopMethodCalled(metricTrace, isSuccessful: isSuccessful) // Then - guard case .metric(let name, let attributes) = receiver.telemetry else { - return XCTFail("A debug should be send to core.") - } - XCTAssertEqual(name, MethodCalledMetric.name) - XCTAssertGreaterThan(try XCTUnwrap(attributes[MethodCalledMetric.executionTime] as? Int64), 0) - XCTAssertEqual(try XCTUnwrap(attributes[MethodCalledMetric.operationName] as? String), operationName) - XCTAssertEqual(try XCTUnwrap(attributes[MethodCalledMetric.callerClass] as? String), callerClass) - XCTAssertEqual(try XCTUnwrap(attributes[MethodCalledMetric.isSuccessful] as? Bool), isSuccessful) - XCTAssertEqual(try XCTUnwrap(attributes[SDKMetricFields.typeKey] as? String), MethodCalledMetric.typeValue) + let metric = try XCTUnwrap(telemetry.messages.firstMetric(named: MethodCalledMetric.name)) + XCTAssertEqual(metric.attributes[SDKMetricFields.typeKey] as? String, MethodCalledMetric.typeValue) + XCTAssertEqual(metric.attributes[MethodCalledMetric.operationName] as? String, operationName) + XCTAssertEqual(metric.attributes[MethodCalledMetric.callerClass] as? String, callerClass) + XCTAssertEqual(metric.attributes[MethodCalledMetric.isSuccessful] as? Bool, isSuccessful) + let executionTime = try XCTUnwrap(metric.attributes[MethodCalledMetric.executionTime] as? Int64) + XCTAssertGreaterThan(executionTime, 0) + XCTAssertLessThan(executionTime, TimeInterval(1).toInt64Nanoseconds) } -} -class TelemetryTest: Telemetry { - var configuration: ConfigurationTelemetry? + // MARK: - Integration with Core - func send(telemetry: DatadogInternal.TelemetryMessage) { - guard case .configuration(let configuration) = telemetry else { - return - } + func testWhenUsingCoreTelemetry_itSendsTelemetryToMessageReceiver() throws { + let receiver = FeatureMessageReceiverMock() + let core = PassthroughCoreMock(messageReceiver: receiver) - self.configuration = configuration - } + core.telemetry.debug("debug message") + XCTAssertEqual(receiver.messages.lastTelemetry?.asDebug?.message, "debug message") - internal func applyConfiguration(configuration: ConfigurationTelemetry) { - self.configuration( - actionNameAttribute: configuration.actionNameAttribute, - allowFallbackToLocalStorage: configuration.allowFallbackToLocalStorage, - allowUntrustedEvents: configuration.allowUntrustedEvents, - appHangThreshold: configuration.appHangThreshold, - backgroundTasksEnabled: configuration.backgroundTasksEnabled, - batchProcessingLevel: configuration.batchProcessingLevel, - batchSize: configuration.batchSize, - batchUploadFrequency: configuration.batchUploadFrequency, - dartVersion: configuration.dartVersion, - defaultPrivacyLevel: configuration.defaultPrivacyLevel, - forwardErrorsToLogs: configuration.forwardErrorsToLogs, - initializationType: configuration.initializationType, - mobileVitalsUpdatePeriod: configuration.mobileVitalsUpdatePeriod, - reactNativeVersion: configuration.reactNativeVersion, - reactVersion: configuration.reactVersion, - sessionReplaySampleRate: configuration.sessionReplaySampleRate, - sessionSampleRate: configuration.sessionSampleRate, - silentMultipleInit: configuration.silentMultipleInit, - startSessionReplayRecordingManually: configuration.startSessionReplayRecordingManually, - telemetryConfigurationSampleRate: configuration.telemetryConfigurationSampleRate, - telemetrySampleRate: configuration.telemetrySampleRate, - tracerAPI: configuration.tracerAPI, - tracerAPIVersion: configuration.tracerAPIVersion, - traceSampleRate: configuration.traceSampleRate, - trackBackgroundEvents: configuration.trackBackgroundEvents, - trackCrossPlatformLongTasks: configuration.trackCrossPlatformLongTasks, - trackErrors: configuration.trackErrors, - trackFlutterPerformance: configuration.trackFlutterPerformance, - trackFrustrations: configuration.trackFrustrations, - trackLongTask: configuration.trackLongTask, - trackNativeErrors: configuration.trackNativeErrors, - trackNativeLongTasks: configuration.trackNativeLongTasks, - trackNativeViews: configuration.trackNativeViews, - trackNetworkRequests: configuration.trackNetworkRequests, - trackResources: configuration.trackResources, - trackSessionAcrossSubdomains: configuration.trackSessionAcrossSubdomains, - trackUserInteractions: configuration.trackUserInteractions, - trackViewsManually: configuration.trackViewsManually, - unityVersion: configuration.unityVersion, - useAllowedTracingUrls: configuration.useAllowedTracingUrls, - useBeforeSend: configuration.useBeforeSend, - useExcludedActivityUrls: configuration.useExcludedActivityUrls, - useFirstPartyHosts: configuration.useFirstPartyHosts, - useLocalEncryption: configuration.useLocalEncryption, - useProxy: configuration.useProxy, - useSecureSessionCookie: configuration.useSecureSessionCookie, - useTracing: configuration.useTracing, - useWorkerUrl: configuration.useWorkerUrl - ) + core.telemetry.error("error message") + XCTAssertEqual(receiver.messages.lastTelemetry?.asError?.message, "error message") + + core.telemetry.configuration(batchSize: 123) + XCTAssertEqual(receiver.messages.lastTelemetry?.asConfiguration?.batchSize, 123) + + core.telemetry.metric(name: "metric name", attributes: [:]) + XCTAssertEqual(receiver.messages.lastTelemetry?.asMetric?.name, "metric name") + + let metricTrace = core.telemetry.startMethodCalled(operationName: .mockAny(), callerClass: .mockAny()) + core.telemetry.stopMethodCalled(metricTrace) + XCTAssertEqual(receiver.messages.lastTelemetry?.asMetric?.name, MethodCalledMetric.name) } } diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 72fdf516b5..1e90babbb3 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -137,7 +137,7 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// - message: Body of the log /// - kind: The error type or kind (or code in some cases). /// - stack: The stack trace or the complementary information about the error. - private func error(id: String, message: String, kind: String?, stack: String?) { + private func error(id: String, message: String, kind: String, stack: String) { let date = dateProvider.now record(event: id) { context, writer in diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index ae1f91bbcd..8dfb2a28ed 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -129,7 +129,7 @@ class TelemetryReceiverTests: XCTestCase { // When telemetry.debug(id: "0", message: "telemetry debug 0") - telemetry.error(id: "0", message: "telemetry debug 1", kind: nil, stack: nil) + telemetry.error(id: "0", message: "telemetry debug 1", kind: "error.kind", stack: "error.stack") telemetry.debug(id: "0", message: "telemetry debug 2") telemetry.debug(id: "1", message: "telemetry debug 3") diff --git a/DatadogSessionReplay/Tests/SessionReplayTests.swift b/DatadogSessionReplay/Tests/SessionReplayTests.swift index c724a8e392..0b5ccf70a9 100644 --- a/DatadogSessionReplay/Tests/SessionReplayTests.swift +++ b/DatadogSessionReplay/Tests/SessionReplayTests.swift @@ -84,10 +84,9 @@ class SessionReplayTests: XCTestCase { ) // Then - let message = messageReceiver.messages.firstTelemetry() - let configuration = message?.asConfiguration - XCTAssertEqual(configuration?.sessionReplaySampleRate, sampleRate) - XCTAssertEqual(configuration?.defaultPrivacyLevel, privacyLevel.rawValue) + let configuration = try XCTUnwrap(messageReceiver.messages.firstTelemetry?.asConfiguration) + XCTAssertEqual(configuration.sessionReplaySampleRate, sampleRate) + XCTAssertEqual(configuration.defaultPrivacyLevel, privacyLevel.rawValue) } func testWhenEnabledWithReplaySampleRate() throws { diff --git a/TestUtilities/Helpers/ModuleName.swift b/TestUtilities/Helpers/ModuleName.swift new file mode 100644 index 0000000000..c14ae9659e --- /dev/null +++ b/TestUtilities/Helpers/ModuleName.swift @@ -0,0 +1,16 @@ +/* + * 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 + +/// Returns the name of current module. +/// - Returns: The name of caller module. +public func moduleName(file: String = #fileID) -> String { + guard let url = URL(string: file) else { + return "unknown" + } + return url.pathComponents.first ?? "unknown" +} diff --git a/TestUtilities/Mocks/FeatureMessageMocks.swift b/TestUtilities/Mocks/FeatureMessageMocks.swift index 9bb295238a..d5841aa5a2 100644 --- a/TestUtilities/Mocks/FeatureMessageMocks.swift +++ b/TestUtilities/Mocks/FeatureMessageMocks.swift @@ -29,8 +29,13 @@ public extension Array where Element == FeatureMessage { } /// Unpacks the first "telemetry message" in this array. - func firstTelemetry() -> TelemetryMessage? { - return compactMap({ $0.asTelemetry }).first + var firstTelemetry: TelemetryMessage? { + compactMap({ $0.asTelemetry }).first + } + + /// Unpacks the last "telemetry message" in this array. + var lastTelemetry: TelemetryMessage? { + compactMap({ $0.asTelemetry }).last } } diff --git a/TestUtilities/Mocks/TelemetryMocks.swift b/TestUtilities/Mocks/TelemetryMocks.swift index 91c037985d..0801dc201d 100644 --- a/TestUtilities/Mocks/TelemetryMocks.swift +++ b/TestUtilities/Mocks/TelemetryMocks.swift @@ -73,7 +73,7 @@ public class TelemetryMock: Telemetry, CustomStringConvertible { let attributesString = attributes.map({ ", \($0)" }) ?? "" description.append("\n- [debug] \(message)" + attributesString) case .error(_, let message, let kind, let stack): - description.append("\n - [error] \(message), kind: \(kind ?? "nil"), stack: \(stack ?? "nil")") + description.append("\n - [error] \(message), kind: \(kind), stack: \(stack)") case .configuration(let configuration): description.append("\n- [configuration] \(configuration)") case let .metric(name, attributes): @@ -89,10 +89,20 @@ public extension Array where Element == TelemetryMessage { return compactMap({ $0.asMetric }).filter({ $0.name == metricName }).first } - /// Returns attributes of the first ERROR telemetry in this array. + /// Returns attributes of the first debug telemetry in this array. + func firstDebug() -> (id: String, message: String, attributes: [String: Encodable]?)? { + return compactMap { $0.asDebug }.first + } + + /// Returns attributes of the first error telemetry in this array. func firstError() -> (id: String, message: String, kind: String?, stack: String?)? { return compactMap { $0.asError }.first } + + /// Returns the first configuration telemetry in this array. + func firstConfiguration() -> ConfigurationTelemetry? { + return compactMap { $0.asConfiguration }.first + } } public extension TelemetryMessage { From 4409d80b0637dd16a05c42031140b6160dfef480 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 24 May 2024 12:34:43 +0200 Subject: [PATCH 146/153] RUM-3464 feat(oom): simulate OOM crash in Example app --- .../Example/Base.lproj/Main iOS.storyboard | 62 ++++++++++++++++--- ...gCrashReportingWithRUMViewController.swift | 18 ++++++ 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/Datadog/Example/Base.lproj/Main iOS.storyboard b/Datadog/Example/Base.lproj/Main iOS.storyboard index 5de238d791..fb7c848fb2 100644 --- a/Datadog/Example/Base.lproj/Main iOS.storyboard +++ b/Datadog/Example/Base.lproj/Main iOS.storyboard @@ -1,9 +1,9 @@ - + - + @@ -1167,7 +1167,7 @@ - + @@ -1316,11 +1316,57 @@ - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2100,13 +2146,13 @@ - + - + diff --git a/Datadog/Example/Debugging/DebugCrashReportingWithRUMViewController.swift b/Datadog/Example/Debugging/DebugCrashReportingWithRUMViewController.swift index 407866e60c..8c531f96ff 100644 --- a/Datadog/Example/Debugging/DebugCrashReportingWithRUMViewController.swift +++ b/Datadog/Example/Debugging/DebugCrashReportingWithRUMViewController.swift @@ -47,4 +47,22 @@ class DebugCrashReportingWithRUMViewController: UIViewController { self?.crash() } } + + // MARK: - OOM Crash + + @IBAction func didTapOOMCrash(_ sender: UIButton) { + DispatchQueue.main.async { + let megaByte = 1_024 * 1_024 + let memoryPageSize = NSPageSize() + let memoryPages = megaByte / memoryPageSize + + while true { + // Allocate one MB and set one element of each memory page to something. + let ptr = UnsafeMutablePointer.allocate(capacity: megaByte) + for i in 0.. Date: Fri, 24 May 2024 15:02:41 +0200 Subject: [PATCH 147/153] Update `testWhenSourceIsInvalid_itSendsErrorTelemetry()` after `error.kind` change --- .../RequestBuilder/SegmentRequestBuilderTests.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/DatadogSessionReplay/Tests/Feature/RequestBuilder/SegmentRequestBuilderTests.swift b/DatadogSessionReplay/Tests/Feature/RequestBuilder/SegmentRequestBuilderTests.swift index d468db3e88..86bacd905e 100644 --- a/DatadogSessionReplay/Tests/Feature/RequestBuilder/SegmentRequestBuilderTests.swift +++ b/DatadogSessionReplay/Tests/Feature/RequestBuilder/SegmentRequestBuilderTests.swift @@ -247,12 +247,13 @@ class SegmentRequestBuilderTests: XCTestCase { _ = try builder.request(for: mockEvents, with: .mockWith(source: "invalid source")) // Then - XCTAssertEqual( - telemetry.description, - """ - Telemetry logs: - - [error] [SR] Could not create segment source from provided string 'invalid source', kind: nil, stack: nil - """ + XCTAssertTrue( + telemetry.description.hasPrefix( + """ + Telemetry logs: + - [error] [SR] Could not create segment source from provided string 'invalid source' + """ + ) ) } } From 84f83efd18c980a74c4bd54c66aa04f6484282c7 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 29 May 2024 09:39:57 +0200 Subject: [PATCH 148/153] CR feedback - simplify `error.kind` format --- .../Public/CoreTelemetryIntegrationTests.swift | 2 +- DatadogInternal/Sources/Telemetry/Telemetry.swift | 12 ++---------- DatadogInternal/Tests/Telemetry/TelemetryTests.swift | 4 ++-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift b/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift index 9a0ee635a4..c527f02a9b 100644 --- a/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift @@ -52,7 +52,7 @@ class CoreTelemetryIntegrationTests: XCTestCase { let error = errorEvents[0] XCTAssertEqual(error.telemetry.message, "Error Telemetry") - XCTAssertEqual(error.telemetry.error?.kind, "\(moduleName())/FileError") + XCTAssertEqual(error.telemetry.error?.kind, "\(moduleName())/File.swift") XCTAssertEqual(error.telemetry.error?.stack, "\(moduleName())/File.swift:42") let metric = debugEvents[1] diff --git a/DatadogInternal/Sources/Telemetry/Telemetry.swift b/DatadogInternal/Sources/Telemetry/Telemetry.swift index 9aae9447b9..5f875d2eeb 100644 --- a/DatadogInternal/Sources/Telemetry/Telemetry.swift +++ b/DatadogInternal/Sources/Telemetry/Telemetry.swift @@ -186,19 +186,11 @@ extension Telemetry { /// - file: The current file name. /// - line: The line number in file. public func error(_ message: String, kind: String? = nil, stack: String? = nil, file: String = #fileID, line: Int = #line) { - func defaultKind(fileID: String) -> String { - // e.g. `DatadogInternal/Telemetry.swift` -> `DatadogInternal/TelemetryError` - return "\(fileID.hasSuffix(".swift") ? String(fileID.dropLast(6)) : fileID)Error" - } - func defaultStack(fileID: String, line: Int) -> String { - "\(fileID):\(line)" - } - error( id: "\(file):\(line):\(message)", message: message, - kind: kind ?? defaultKind(fileID: file), - stack: stack ?? defaultStack(fileID: file, line: line) + kind: kind ?? "\(file)", + stack: stack ?? "\(file):\(line)" ) } diff --git a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift index 2e1ef88b0d..f61db878ac 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift @@ -48,14 +48,14 @@ class TelemetryTests: XCTestCase { func testSendingErrorTelemetry_whenNoKindAndNoStack() throws { // When #sourceLocation(file: "File.swift", line: 1) - telemetry.error("error message", kind: nil, stack: nil) + telemetry.error("error message") #sourceLocation() // Then let error = try XCTUnwrap(telemetry.messages.firstError()) XCTAssertEqual(error.id, "\(moduleName())/File.swift:1:error message") XCTAssertEqual(error.message, "error message") - XCTAssertEqual(error.kind, "\(moduleName())/FileError") + XCTAssertEqual(error.kind, "\(moduleName())/File.swift") XCTAssertEqual(error.stack, "\(moduleName())/File.swift:1") XCTAssertEqual(telemetry.messages.count, 1) } From 386f0e8ad5cbc84627d7fae7294995161a696b26 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Wed, 29 May 2024 12:28:44 +0100 Subject: [PATCH 149/153] Reduce telemetry sampling for method called --- DatadogSessionReplay/Sources/Recorder/Recorder.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DatadogSessionReplay/Sources/Recorder/Recorder.swift b/DatadogSessionReplay/Sources/Recorder/Recorder.swift index bc15a36ca8..a216f7cf3b 100644 --- a/DatadogSessionReplay/Sources/Recorder/Recorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/Recorder.swift @@ -99,7 +99,7 @@ public class Recorder: Recording { snapshotProcessor: SnapshotProcessing, resourceProcessor: ResourceProcessing, telemetry: Telemetry, - methodCallTelemetrySamplingRate: Float = 5 + methodCallTelemetrySamplingRate: Float = 0.1 ) { self.uiApplicationSwizzler = uiApplicationSwizzler self.viewTreeSnapshotProducer = viewTreeSnapshotProducer @@ -123,7 +123,7 @@ public class Recorder: Recording { let methodCalledTrace = telemetry.startMethodCalled( operationName: MethodCallConstants.captureRecordOperationName, callerClass: MethodCallConstants.className, - samplingRate: methodCallTelemetrySamplingRate // Effectively 3% * 5% = 0.15% of calls + samplingRate: methodCallTelemetrySamplingRate // Effectively 3% * 0.1% = 0.003% of calls ) var isSuccessful = true do { From 873ba7afd71310eff81b3720a43fa1d941ec988a Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 29 May 2024 16:52:12 +0200 Subject: [PATCH 150/153] Fix accessing repository URL in `Package.resolved` (version 2 and 3) --- tools/distribution/dogfood.py | 2 +- tools/distribution/src/dogfood/package_resolved.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/distribution/dogfood.py b/tools/distribution/dogfood.py index 9820e80bee..98200ce9cd 100755 --- a/tools/distribution/dogfood.py +++ b/tools/distribution/dogfood.py @@ -73,7 +73,7 @@ def dogfood(dry_run: bool, repository_url: str, repository_name: str, repository else: package.add_dependency( package_id=dependency_id, - repository_url=dependency['repositoryURL'], + repository_url=dependency['location'], branch=dependency['state'].get('branch'), revision=dependency['state']['revision'], version=dependency['state'].get('version'), diff --git a/tools/distribution/src/dogfood/package_resolved.py b/tools/distribution/src/dogfood/package_resolved.py index 8381d81177..225e452ce9 100644 --- a/tools/distribution/src/dogfood/package_resolved.py +++ b/tools/distribution/src/dogfood/package_resolved.py @@ -361,7 +361,7 @@ def __get_package(self, package_id: PackageID): class PackageResolvedContentV3(PackageResolvedContentV2): """ - Example of `package.resolved` in version `2` looks this:: + Example of `package.resolved` in version `3` looks this:: { "originHash" : "b47de6af98c4a9811a8d2af11d70b960dfc66b7c8e4944b35bb74c8f8bb9c487", From bf157a1edaef108ee8aa064559328f3130ecb8ce Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 29 May 2024 17:06:47 +0200 Subject: [PATCH 151/153] Add smoke test for dogfooding automation --- bitrise.yml | 11 +++++++++++ tools/distribution/dogfood.py | 2 ++ 2 files changed, 13 insertions(+) diff --git a/bitrise.yml b/bitrise.yml index 36b7a16629..e1cb0a9620 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -536,6 +536,17 @@ workflows: set -e cd tools/distribution && make venv/bin/python3 -m pytest tests + - script: + title: Smoke test dogfooding (with dry-run) + run_if: '{{enveq "DD_RUN_TOOLS_TESTS" "1"}}' + inputs: + - content: |- + #!/usr/bin/env bash + set -e + + export DD_DRY_RUN=yes + cd tools/distribution && make + venv/bin/python3 dogfood.py - script: title: Run tests for nightly-unit-tests tool run_if: '{{enveq "DD_RUN_TOOLS_TESTS" "1"}}' diff --git a/tools/distribution/dogfood.py b/tools/distribution/dogfood.py index 98200ce9cd..1a9eb9dcf1 100755 --- a/tools/distribution/dogfood.py +++ b/tools/distribution/dogfood.py @@ -111,6 +111,8 @@ def dogfood(dry_run: bool, repository_url: str, repository_name: str, repository try: dry_run = os.environ.get('DD_DRY_RUN') == 'yes' + if dry_run: + print(f'ℹ️ Running in dry-run mode') skip_datadog_ios = os.environ.get('DD_SKIP_DATADOG_IOS') == 'yes' skip_shopist_ios = os.environ.get('DD_SKIP_SHOPIST_IOS') == 'yes' From f2e92acc3a8beca3cd45744d1ca5c8542446ffc4 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 31 May 2024 09:16:09 +0200 Subject: [PATCH 152/153] Mark 2.12.0 in CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce37982a50..052ae4f300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 2.12.0 / 03-06-2024 + - [IMPROVEMENT] Crash errors now include up-to-date global RUM attributes. See [#1834][] - [FEATURE] `DatadogTrace` now supports OpenTelemetry. See [#1828][] - [FIX] Fix crash on accessing request.allHTTPHeaderFields. See [#1843][] From bb051bc817da77642f7565afc5763a8c4c81e10d Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 31 May 2024 09:16:42 +0200 Subject: [PATCH 153/153] Bumped version to 2.12.0 --- DatadogAlamofireExtension.podspec | 2 +- DatadogCore.podspec | 2 +- DatadogCore/Sources/Versioning.swift | 2 +- DatadogCrashReporting.podspec | 2 +- DatadogInternal.podspec | 2 +- DatadogLogs.podspec | 2 +- DatadogObjc.podspec | 2 +- DatadogRUM.podspec | 2 +- DatadogSDK.podspec | 2 +- DatadogSDKAlamofireExtension.podspec | 2 +- DatadogSDKCrashReporting.podspec | 2 +- DatadogSDKObjc.podspec | 2 +- DatadogSessionReplay.podspec | 2 +- DatadogTrace.podspec | 2 +- DatadogWebViewTracking.podspec | 2 +- TestUtilities.podspec | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/DatadogAlamofireExtension.podspec b/DatadogAlamofireExtension.podspec index 139b4cad1c..26bd6e307b 100644 --- a/DatadogAlamofireExtension.podspec +++ b/DatadogAlamofireExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogAlamofireExtension" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "An Official Extensions of Datadog Swift SDK for Alamofire." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogCore.podspec b/DatadogCore.podspec index bc4dc1562d..db695ade56 100644 --- a/DatadogCore.podspec +++ b/DatadogCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogCore" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Official Datadog Swift SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogCore/Sources/Versioning.swift b/DatadogCore/Sources/Versioning.swift index a19f9e6a0f..faeed4bc02 100644 --- a/DatadogCore/Sources/Versioning.swift +++ b/DatadogCore/Sources/Versioning.swift @@ -1,3 +1,3 @@ // GENERATED FILE: Do not edit directly -internal let __sdkVersion = "2.11.0" +internal let __sdkVersion = "2.12.0" diff --git a/DatadogCrashReporting.podspec b/DatadogCrashReporting.podspec index 052300a37f..80d2677cce 100644 --- a/DatadogCrashReporting.podspec +++ b/DatadogCrashReporting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogCrashReporting" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Official Datadog Crash Reporting SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogInternal.podspec b/DatadogInternal.podspec index fd527361a1..8bf055e353 100644 --- a/DatadogInternal.podspec +++ b/DatadogInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogInternal" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Datadog Internal Package. This module is not for public use." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogLogs.podspec b/DatadogLogs.podspec index 45bbb80172..ece57e14c4 100644 --- a/DatadogLogs.podspec +++ b/DatadogLogs.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogLogs" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Datadog Logs Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogObjc.podspec b/DatadogObjc.podspec index 38856c6a3f..83342fd77b 100644 --- a/DatadogObjc.podspec +++ b/DatadogObjc.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogObjc" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Official Datadog Objective-C SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogRUM.podspec b/DatadogRUM.podspec index 26e513b5ce..f564323599 100644 --- a/DatadogRUM.podspec +++ b/DatadogRUM.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogRUM" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Datadog Real User Monitoring Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index 47155da357..4d33683889 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogSDK" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Official Datadog Swift SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKAlamofireExtension.podspec b/DatadogSDKAlamofireExtension.podspec index d8178d71c7..dc74781193 100644 --- a/DatadogSDKAlamofireExtension.podspec +++ b/DatadogSDKAlamofireExtension.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKAlamofireExtension" s.module_name = "DatadogAlamofireExtension" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "An Official Extensions of Datadog Swift SDK for Alamofire." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKCrashReporting.podspec b/DatadogSDKCrashReporting.podspec index 01ceed8132..e3fd72abd6 100644 --- a/DatadogSDKCrashReporting.podspec +++ b/DatadogSDKCrashReporting.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKCrashReporting" s.module_name = "DatadogCrashReporting" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Official Datadog Crash Reporting SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKObjc.podspec b/DatadogSDKObjc.podspec index 1ab703ae24..980f790bfa 100644 --- a/DatadogSDKObjc.podspec +++ b/DatadogSDKObjc.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKObjc" s.module_name = "DatadogObjc" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Official Datadog Objective-C SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSessionReplay.podspec b/DatadogSessionReplay.podspec index f6250e802c..92c1cc5593 100644 --- a/DatadogSessionReplay.podspec +++ b/DatadogSessionReplay.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogSessionReplay" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Official Datadog Session Replay SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogTrace.podspec b/DatadogTrace.podspec index 23e67659c2..ed7e11024f 100644 --- a/DatadogTrace.podspec +++ b/DatadogTrace.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogTrace" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Datadog Trace Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogWebViewTracking.podspec b/DatadogWebViewTracking.podspec index 72ad6990c5..4db4326cb6 100644 --- a/DatadogWebViewTracking.podspec +++ b/DatadogWebViewTracking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogWebViewTracking" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Datadog WebView Tracking Module." s.homepage = "https://www.datadoghq.com" diff --git a/TestUtilities.podspec b/TestUtilities.podspec index 58e9c927d9..59a059083a 100644 --- a/TestUtilities.podspec +++ b/TestUtilities.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "TestUtilities" - s.version = "2.11.0" + s.version = "2.12.0" s.summary = "Datadog Testing Utilities. This module is for internal testing and should not be published." s.homepage = "https://www.datadoghq.com"