Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

RUMM-2759 Automatic w3c headers injection #1071

Merged
merged 20 commits into from
Dec 19, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 12 additions & 30 deletions Datadog/Datadog.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions Datadog/Example/Scenarios/URLSession/URLSessionScenarios.swift
Original file line number Diff line number Diff line change
@@ -162,11 +162,11 @@ class URLSessionBaseScenario: NSObject {
delegate = DDURLSessionDelegate()
case .directWithAdditionalFirstyPartyHosts:
delegate = DDURLSessionDelegate(
additionalFirstPartyHosts: [
customGETResourceURL.host!: .init(.dd),
customPOSTRequest.url!.host!: .init(.dd),
badResourceURL.host!: .init(.dd)
]
additionalFirstPartyHosts: .init([
customGETResourceURL.host!: .init([.dd]),
customPOSTRequest.url!.host!: .init([.dd]),
badResourceURL.host!: .init([.dd])
])
)
case .inheritance:
delegate = InheritedURLSessionDelegate()
2 changes: 1 addition & 1 deletion Sources/Datadog/Core/FeaturesConfiguration.swift
Original file line number Diff line number Diff line change
@@ -209,7 +209,7 @@ extension FeaturesConfiguration {
)
}

var sanitizedHostsWithHeaderTypes: FirstPartyHosts = [:]
var sanitizedHostsWithHeaderTypes: FirstPartyHosts = .init()
if let firstPartyHosts = configuration.firstPartyHostsWithHeaderTypes {
sanitizedHostsWithHeaderTypes = hostsSanitizer.sanitized(
firstPartyHosts: firstPartyHosts,
4 changes: 2 additions & 2 deletions Sources/Datadog/Core/Utils/HostsSanitizer.swift
Original file line number Diff line number Diff line change
@@ -75,7 +75,7 @@ internal struct HostsSanitizer: HostsSanitizing {
func sanitized(firstPartyHosts: FirstPartyHosts, warningMessage: String) -> FirstPartyHosts {
var warnings: [String] = []

let sanitized: FirstPartyHosts = firstPartyHosts.reduce(into: [:]) { partialResult, item in
let sanitized: FirstPartyHosts = .init(firstPartyHosts.hostsDictionary.reduce(into: [:]) { partialResult, item in
let host = item.key
let (sanitizedHost, warning) = sanitize(host: host, warningMessage: warningMessage)
if let warning = warning {
@@ -84,7 +84,7 @@ internal struct HostsSanitizer: HostsSanitizing {
if let sanitizedHost = sanitizedHost {
partialResult[sanitizedHost] = item.value
}
}
})

printWarnings(warningMessage, warnings)

39 changes: 36 additions & 3 deletions Sources/Datadog/DatadogConfiguration.swift
Original file line number Diff line number Diff line change
@@ -517,6 +517,8 @@ extension Datadog {
/// If Tracing feature is enabled, the SDK will send tracing Span for each 1st-party request. It will also add extra HTTP headers to further propagate the trace - it means that
/// if your backend is instrumented with Datadog agent you will see the full trace (e.g.: client → server → database) in your dashboard, thanks to Datadog Distributed Tracing.
///
/// For more control over the kind of headers used for Distributed Tracing, see `trackURLSession(firstPartyHostsWithHeaderTypes:)`.
///
/// If both RUM and Tracing features are enabled, the SDK will be sending RUM Resources for 1st- and 3rd-party requests and tracing Spans for 1st-parties.
///
/// Until `trackURLSession()` is called, network requests monitoring is disabled.
@@ -528,11 +530,42 @@ extension Datadog {
///
/// - Parameter firstPartyHosts: empty set by default
public func trackURLSession(firstPartyHosts: Set<String> = []) -> Builder {
return trackURLSession(firstPartyHostsWithHeaderTypes: firstPartyHosts.reduce(into: [:], { partialResult, host in
partialResult[host] = .init(.dd)
}))
return trackURLSession(firstPartyHostsWithHeaderTypes: .init(firstPartyHosts.reduce(into: [:], { partialResult, host in
partialResult[host] = .init([.dd])
})))
}

/// The `trackURLSession(firstPartyHostsWithHeaderTypes:)` function is an alternate version of `trackURLSession(firstPartyHosts:)`
/// that allows for more fine-grained control over the HTTP headers used for Distributed Tracing.
///
/// Configures network requests monitoring for Tracing and RUM features. **It must be used together with** `DDURLSessionDelegate` set as the `URLSession` delegate.
///
/// If set, the SDK will intercept all network requests made by `URLSession` instances which use `DDURLSessionDelegate`.
///
/// Each request will be classified as 1st- or 3rd-party based on the host comparison, i.e.:
/// * if `firstPartyHostsWithHeaderTypes` is `["example.com": .init([.dd])]`:
/// - 1st-party URL examples: https://example.com/, https://api.example.com/v2/users
/// - 3rd-party URL examples: https://foo.com/
/// * if `firstPartyHostsWithHeaderTypes` is `["api.example.com": .init([.dd])]]`:
/// - 1st-party URL examples: https://api.example.com/, https://api.example.com/v2/users
/// - 3rd-party URL examples: https://example.com/, https://foo.com/
///
/// If RUM feature is enabled, the SDK will send RUM Resources for all intercepted requests.
///
/// If Tracing feature is enabled, the SDK will send tracing Span for each 1st-party request. It will also add extra HTTP headers to further propagate the trace - it means that
/// if your backend is instrumented with Datadog agent you will see the full trace (e.g.: client → server → database) in your dashboard, thanks to Datadog Distributed Tracing.
///
/// If both RUM and Tracing features are enabled, the SDK will be sending RUM Resources for 1st- and 3rd-party requests and tracing Spans for 1st-parties.
///
/// Until `trackURLSession()` is called, network requests monitoring is disabled.
///
/// **NOTE 1:** Enabling this option will install swizzlings on some methods of the `URLSession`. Refer to `URLSessionSwizzler.swift`
/// for implementation details.
///
/// **NOTE 2:** The `URLSession` instrumentation will NOT work without using `DDURLSessionDelegate`.
///
/// - Parameter firstPartyHostsWithHeaderTypes: Object used to classify network requests as 1st-party
/// and determine the HTTP header types to use for Distributed Tracing.
public func trackURLSession(firstPartyHostsWithHeaderTypes: FirstPartyHosts) -> Builder {
configuration.firstPartyHostsWithHeaderTypes = firstPartyHostsWithHeaderTypes
return self
Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ internal class RUMResourceScope: RUMScope {
self.resourceLoadingStartTime = startTime
self.serverTimeOffset = serverTimeOffset
self.resourceHTTPMethod = httpMethod
self.isFirstPartyResource = dependencies.firstPartyURLsFilter.isFirstParty(string: url)
self.isFirstPartyResource = dependencies.firstPartyHosts.isFirstParty(string: url)
self.resourceKindBasedOnRequest = resourceKindBasedOnRequest
self.spanContext = spanContext
self.onResourceEventSent = onResourceEventSent
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ internal struct RUMScopeDependencies {
let sessionSampler: Sampler
let backgroundEventTrackingEnabled: Bool
let frustrationTrackingEnabled: Bool
let firstPartyURLsFilter: FirstPartyURLsFilter
let firstPartyHosts: FirstPartyHosts
let eventBuilder: RUMEventBuilder
let rumUUIDGenerator: RUMUUIDGenerator
/// Integration with CIApp tests. It contains the CIApp test context when active.
@@ -45,7 +45,7 @@ internal extension RUMScopeDependencies {
sessionSampler: rumFeature.configuration.sessionSampler,
backgroundEventTrackingEnabled: rumFeature.configuration.backgroundEventTrackingEnabled,
frustrationTrackingEnabled: rumFeature.configuration.frustrationTrackingEnabled,
firstPartyURLsFilter: FirstPartyURLsFilter(hosts: rumFeature.configuration.firstPartyHosts),
firstPartyHosts: rumFeature.configuration.firstPartyHosts,
eventBuilder: RUMEventBuilder(
eventsMapper: RUMEventsMapper(
viewEventMapper: rumFeature.configuration.viewEventMapper,
2 changes: 1 addition & 1 deletion Sources/Datadog/RUM/RUMTelemetry.swift
Original file line number Diff line number Diff line change
@@ -256,7 +256,7 @@ private extension FeaturesConfiguration {
useBeforeSend: nil,
useCrossSiteSessionCookie: nil,
useExcludedActivityUrls: nil,
useFirstPartyHosts: !(self.rum?.firstPartyHosts.isEmpty ?? true),
useFirstPartyHosts: !(self.rum?.firstPartyHosts.hostsDictionary.isEmpty ?? true),
useLocalEncryption: self.common.encryption != nil,
useProxy: self.common.proxyConfiguration != nil,
useSecureSessionCookie: nil,
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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-2020 Datadog, Inc.
*/

import Foundation

/// A struct that represents a dictionary of host names and tracing header types.
public struct FirstPartyHosts: Equatable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public struct FirstPartyHosts: Equatable {
internal struct FirstPartyHosts: Equatable {

var hostsDictionary: [String: Set<TracingHeaderType>]

var hosts: Set<String> {
return Set(hostsDictionary.keys)
}

/// Creates a `FirstPartyHosts` instance with the given dictionary of host names and tracing header types.
///
/// - Parameter hostsDictionary: The dictionary of host names and tracing header types.
public init(_ hostsDictionary: [String: Set<TracingHeaderType>] = [:]) {
self.hostsDictionary = hostsDictionary
}

/// The function takes a `URL` and returns a `Set<TracingHeaderType>` of matching values.
/// If one than more match is found it will return union of matching values.
func tracingHeaderTypes(for url: URL?) -> Set<TracingHeaderType> {
return hostsDictionary.compactMap { item -> Set<TracingHeaderType>? in
let regex = "^(.*\\.)*[.]?\(NSRegularExpression.escapedPattern(for: item.key))$"
if url?.host?.range(of: regex, options: .regularExpression) != nil {
return item.value
}
if url?.absoluteString.range(of: regex, options: .regularExpression) != nil {
return item.value
}
return nil
}
.reduce(into: Set(), { partialResult, value in
partialResult.formUnion(value)
})
}

/// Returns `true` if given `URL` matches the first party hosts defined by the user; `false` otherwise.
func isFirstParty(url: URL?) -> Bool {
guard let host = url?.host, let url = URL(string: host) else {
return false
}
return !tracingHeaderTypes(for: url).isEmpty
}

// Returns `true` if given `String` can be parsed as a URL and matches the first
// party hosts defined by the user; `false` otherwise
func isFirstParty(string: String) -> Bool {
guard let url = URL(string: string) else {
return false
}
return isFirstParty(url: url)
}
}
Original file line number Diff line number Diff line change
@@ -18,17 +18,3 @@ public enum TracingHeaderType: Hashable {
case b3m
case w3c
}

public typealias FirstPartyHosts = [String: Set<TracingHeaderType>]

extension FirstPartyHosts {
var hosts: Set<String> {
return Set(keys)
}
}

public extension Set where Element == TracingHeaderType {
init(_ tracingHeaderType: TracingHeaderType) {
self.init(arrayLiteral: tracingHeaderType)
}
}
Original file line number Diff line number Diff line change
@@ -32,21 +32,29 @@ open class DDURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDat
core().v1.feature(URLSessionAutoInstrumentation.self)
}

let firstPartyURLsFilter: FirstPartyURLsFilter
let tracingHeaderTypesProvider: TracingHeaderTypesProvider
let firstPartyHosts: FirstPartyHosts

private let core: () -> DatadogCoreProtocol

@objc
override public init() {
core = { defaultDatadogCore }
firstPartyURLsFilter = FirstPartyURLsFilter(hosts: [:])
tracingHeaderTypesProvider = TracingHeaderTypesProvider(firstPartyHosts: [:])
firstPartyHosts = .init()
super.init()
}

public convenience init(in core: DatadogCoreProtocol) {
self.init(in: core, additionalFirstPartyHosts: [:])
self.init(in: core, additionalFirstPartyHosts: .init())
}

/// Automatically tracked hosts can be customized per instance with this initializer.
///
/// **NOTE:** If `trackURLSession(firstPartyHostsWithHeaderTypes:)` is never called, automatic tracking will **not** take place.
///
/// - Parameter additionalFirstPartyHostsWithHeaderTypes: these hosts are tracked **in addition to** what was
/// passed to `DatadogConfiguration.Builder` via `trackURLSession(firstPartyHostsWithHeaderTypes:)`
public convenience init(additionalFirstPartyHosts: FirstPartyHosts) {
self.init(in: defaultDatadogCore, additionalFirstPartyHosts: additionalFirstPartyHosts)
}

/// Automatically tracked hosts can be customized per instance with this initializer.
@@ -55,8 +63,11 @@ open class DDURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDat
///
/// - Parameter additionalFirstPartyHosts: these hosts are tracked **in addition to** what was
/// passed to `DatadogConfiguration.Builder` via `trackURLSession(firstPartyHosts:)`
public convenience init(additionalFirstPartyHosts: FirstPartyHosts) {
self.init(in: defaultDatadogCore, additionalFirstPartyHosts: additionalFirstPartyHosts)
@objc
public convenience init(additionalFirstPartyHosts: Set<String>) {
self.init(additionalFirstPartyHosts: .init(additionalFirstPartyHosts.reduce(into: [:], { partialResult, host in
partialResult[host] = .init(arrayLiteral: .dd)
})))
}

/// Automatically tracked hosts can be customized per instance with this initializer.
@@ -67,8 +78,7 @@ open class DDURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDat
/// passed to `DatadogConfiguration.Builder` via `trackURLSession(firstPartyHosts:)`
public init(in core: @autoclosure @escaping () -> DatadogCoreProtocol, additionalFirstPartyHosts: FirstPartyHosts) {
self.core = core
self.firstPartyURLsFilter = FirstPartyURLsFilter(hosts: additionalFirstPartyHosts)
self.tracingHeaderTypesProvider = TracingHeaderTypesProvider(firstPartyHosts: additionalFirstPartyHosts)
self.firstPartyHosts = additionalFirstPartyHosts
super.init()
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -32,13 +32,10 @@ public class URLSessionInterceptor: URLSessionInterceptorType {
return instrumentation?.interceptor as? URLSessionInterceptor
}

/// Filters first party `URLs` defined by the user.
private let defaultFirstPartyURLsFilter: FirstPartyURLsFilter
/// First party hosts defined by the user.
private let firstPartyHosts: FirstPartyHosts
/// Filters internal `URLs` used by the SDK.
private let internalURLsFilter: InternalURLsFilter

/// Returns tracing header types for given host.
private let tracingHeaderTypesProvider: TracingHeaderTypesProvider
/// Handles resources interception.
/// Depending on which instrumentation is enabled, this can be either RUM or Tracing handler sending respectively: RUM Resource or tracing Span.
internal let handler: URLSessionInterceptionHandler
@@ -80,11 +77,8 @@ public class URLSessionInterceptor: URLSessionInterceptorType {
configuration: FeaturesConfiguration.URLSessionAutoInstrumentation,
handler: URLSessionInterceptionHandler
) {
self.defaultFirstPartyURLsFilter = FirstPartyURLsFilter(hosts: configuration.userDefinedHostsWithHeaderTypes)
self.firstPartyHosts = configuration.userDefinedHostsWithHeaderTypes
self.internalURLsFilter = InternalURLsFilter(urls: configuration.sdkInternalURLs)
self.tracingHeaderTypesProvider = TracingHeaderTypesProvider(
firstPartyHosts: configuration.userDefinedHostsWithHeaderTypes
)
self.handler = handler
self.tracingSampler = configuration.tracingSampler

@@ -226,11 +220,10 @@ public class URLSessionInterceptor: URLSessionInterceptorType {

private func isFirstParty(request: URLRequest, for session: URLSession?) -> Bool {
guard let delegate = session?.delegate as? DDURLSessionDelegate else {
return defaultFirstPartyURLsFilter.isFirstParty(url: request.url)
return firstPartyHosts.isFirstParty(url: request.url)
}

return delegate.firstPartyURLsFilter.isFirstParty(url: request.url) ||
defaultFirstPartyURLsFilter.isFirstParty(url: request.url)
return delegate.firstPartyHosts.isFirstParty(url: request.url) || firstPartyHosts.isFirstParty(url: request.url)
}

private func finishInterception(task: URLSessionTask, interception: TaskInterception) {
@@ -249,8 +242,8 @@ public class URLSessionInterceptor: URLSessionInterceptorType {

var newRequest = firstPartyRequest
let additionalFirstPartyHostsTracingHeaderTypes = (session?.delegate as? DDURLSessionDelegate)?
.tracingHeaderTypesProvider.tracingHeaderTypes(for: newRequest.url) ?? .init()
let tracingHeaderTypes = tracingHeaderTypesProvider.tracingHeaderTypes(for: newRequest.url)
.firstPartyHosts.tracingHeaderTypes(for: newRequest.url) ?? .init()
let tracingHeaderTypes = firstPartyHosts.tracingHeaderTypes(for: newRequest.url)

tracingHeaderTypes.union(additionalFirstPartyHostsTracingHeaderTypes).forEach {
let writer: TracePropagationHeadersProvider & OTFormatWriter
@@ -290,8 +283,8 @@ public class URLSessionInterceptor: URLSessionInterceptorType {
}

let additionalFirstPartyHostsTracingHeaderTypes = (session?.delegate as? DDURLSessionDelegate)?
.tracingHeaderTypesProvider.tracingHeaderTypes(for: request.url) ?? .init()
let tracingHeaderTypes = tracingHeaderTypesProvider.tracingHeaderTypes(for: request.url)
.firstPartyHosts.tracingHeaderTypes(for: request.url) ?? .init()
let tracingHeaderTypes = firstPartyHosts.tracingHeaderTypes(for: request.url)
.union(additionalFirstPartyHostsTracingHeaderTypes)

let reader: OTFormatReader
Loading