Skip to content

Commit 080d20c

Browse files
gh-worker-dd-mergequeue-cf854d[bot]maxepbarboraplasovska
authored
Merge pull request #2721 from DataDog/feature/header-capture
RUM-14563 HTTP header capture for RUM resource events Co-authored-by: maxep <maxime.epain@datadoghq.com> Co-authored-by: barboraplasovska <barbora.plasovska@datadoghq.com>
2 parents 6371ec5 + 09333c7 commit 080d20c

File tree

20 files changed

+1308
-13
lines changed

20 files changed

+1308
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Unreleased
22

33
- [IMPROVEMENT] Rename `DDRUMErrorEventErrorMeta` to `DDRUMErrorEventErrorMetaInfo`, add support of custom Objective-C runtime names for generated RUM models. See [#2705][]
4+
- [FEATURE] Add `trackResourceHeaders` configuration to capture HTTP request and response headers in RUM Resource events. See [#2721][]
45

56
# 3.7.0 / 18-02-2026
67

@@ -1060,6 +1061,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO
10601061
[#2676]: https://github.com/DataDog/dd-sdk-ios/pull/2676
10611062
[#2688]: https://github.com/DataDog/dd-sdk-ios/pull/2688
10621063
[#2705]: https://github.com/DataDog/dd-sdk-ios/pull/2705
1064+
[#2721]: https://github.com/DataDog/dd-sdk-ios/pull/2721
10631065

10641066
[@00fa9a]: https://github.com/00FA9A
10651067
[@britton-earnin]: https://github.com/Britton-Earnin

Datadog/Datadog.xcodeproj/project.pbxproj

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,10 @@
402402
261255882E2167E40015042B /* BaggageHeaderMerger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261255862E2167E40015042B /* BaggageHeaderMerger.swift */; };
403403
2612558A2E2167F10015042B /* BaggageHeaderMergerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261255892E2167F10015042B /* BaggageHeaderMergerTests.swift */; };
404404
2612558B2E2167F10015042B /* BaggageHeaderMergerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261255892E2167F10015042B /* BaggageHeaderMergerTests.swift */; };
405+
261255912E2167E40015042B /* HeaderProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261255902E2167E40015042B /* HeaderProcessor.swift */; };
406+
261255922E2167E40015042B /* HeaderProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261255902E2167E40015042B /* HeaderProcessor.swift */; };
407+
261255942E2167F10015042B /* HeaderProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261255932E2167F10015042B /* HeaderProcessorTests.swift */; };
408+
261255952E2167F10015042B /* HeaderProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261255932E2167F10015042B /* HeaderProcessorTests.swift */; };
405409
265496D32D81C5B10094B6E2 /* RUMAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265496D22D81C5AE0094B6E2 /* RUMAccount.swift */; };
406410
265496D42D81C5B10094B6E2 /* RUMAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265496D22D81C5AE0094B6E2 /* RUMAccount.swift */; };
407411
266BFA5E2D6F4E31003041A5 /* AccountInfoPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 266BFA5D2D6F4E2A003041A5 /* AccountInfoPublisherTests.swift */; };
@@ -2910,6 +2914,8 @@
29102914
1434A4652B7F8D880072E3BB /* DebugOTelTracingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugOTelTracingViewController.swift; sourceTree = "<group>"; };
29112915
261255862E2167E40015042B /* BaggageHeaderMerger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaggageHeaderMerger.swift; sourceTree = "<group>"; };
29122916
261255892E2167F10015042B /* BaggageHeaderMergerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaggageHeaderMergerTests.swift; sourceTree = "<group>"; };
2917+
261255902E2167E40015042B /* HeaderProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderProcessor.swift; sourceTree = "<group>"; };
2918+
261255932E2167F10015042B /* HeaderProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderProcessorTests.swift; sourceTree = "<group>"; };
29132919
265496D22D81C5AE0094B6E2 /* RUMAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMAccount.swift; sourceTree = "<group>"; };
29142920
266BFA5D2D6F4E2A003041A5 /* AccountInfoPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInfoPublisherTests.swift; sourceTree = "<group>"; };
29152921
2671348C2D688ACD0048CB54 /* AccountInfoPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInfoPublisher.swift; sourceTree = "<group>"; };
@@ -5907,12 +5913,21 @@
59075913
613F23EF252B1287006CD2D7 /* Resources */ = {
59085914
isa = PBXGroup;
59095915
children = (
5916+
261255972E2167F10015042B /* HeaderCapture */,
59105917
261255892E2167F10015042B /* BaggageHeaderMergerTests.swift */,
59115918
D2BCB12129D34A5F00737A9A /* URLSessionRUMResourcesHandlerTests.swift */,
59125919
);
59135920
path = Resources;
59145921
sourceTree = "<group>";
59155922
};
5923+
261255972E2167F10015042B /* HeaderCapture */ = {
5924+
isa = PBXGroup;
5925+
children = (
5926+
261255932E2167F10015042B /* HeaderProcessorTests.swift */,
5927+
);
5928+
path = HeaderCapture;
5929+
sourceTree = "<group>";
5930+
};
59165931
6141014C251A577D00E3C2D9 /* Actions */ = {
59175932
isa = PBXGroup;
59185933
children = (
@@ -6058,12 +6073,21 @@
60586073
6157FA5C252767B3009A8A3B /* Resources */ = {
60596074
isa = PBXGroup;
60606075
children = (
6076+
261255962E2167E40015042B /* HeaderCapture */,
60616077
261255862E2167E40015042B /* BaggageHeaderMerger.swift */,
60626078
D2BCB11E29D30AF000737A9A /* URLSessionRUMResourcesHandler.swift */,
60636079
);
60646080
path = Resources;
60656081
sourceTree = "<group>";
60666082
};
6083+
261255962E2167E40015042B /* HeaderCapture */ = {
6084+
isa = PBXGroup;
6085+
children = (
6086+
261255902E2167E40015042B /* HeaderProcessor.swift */,
6087+
);
6088+
path = HeaderCapture;
6089+
sourceTree = "<group>";
6090+
};
60676091
615950EC291C057D00470E0C /* Integrations */ = {
60686092
isa = PBXGroup;
60696093
children = (
@@ -10543,6 +10567,7 @@
1054310567
D23F8E7629DDCD28001CFAE8 /* RUMConnectivityInfoProvider.swift in Sources */,
1054410568
D23F8E7729DDCD28001CFAE8 /* UIKitRUMViewsPredicate.swift in Sources */,
1054510569
261255882E2167E40015042B /* BaggageHeaderMerger.swift in Sources */,
10570+
261255912E2167E40015042B /* HeaderProcessor.swift in Sources */,
1054610571
9629FFDE2D81C317008DFE39 /* SwiftUIViewPath.swift in Sources */,
1054710572
111201C12E93C13000375DA3 /* AppStateManager.swift in Sources */,
1054810573
111201C22E93C13000375DA3 /* AppStateInfo.swift in Sources */,
@@ -10613,6 +10638,7 @@
1061310638
D23F8EAE29DDCD38001CFAE8 /* DDTAssertValidRUMUUID.swift in Sources */,
1061410639
D23F8EAF29DDCD38001CFAE8 /* RUMScopeTests.swift in Sources */,
1061510640
2612558A2E2167F10015042B /* BaggageHeaderMergerTests.swift in Sources */,
10641+
261255942E2167F10015042B /* HeaderProcessorTests.swift in Sources */,
1061610642
A7E6EA842D314A9B00997201 /* AnonymousIdentifierManagerTests.swift in Sources */,
1061710643
D23F8EB029DDCD38001CFAE8 /* SessionReplayDependencyTests.swift in Sources */,
1061810644
61C713B72A3C600400FA735A /* RUMMonitorProtocol+ConvenienceTests.swift in Sources */,
@@ -11038,6 +11064,7 @@
1103811064
D29A9F5829DD85BB005C54A4 /* RUMConnectivityInfoProvider.swift in Sources */,
1103911065
D29A9F5E29DD85BB005C54A4 /* UIKitRUMViewsPredicate.swift in Sources */,
1104011066
261255872E2167E40015042B /* BaggageHeaderMerger.swift in Sources */,
11067+
261255922E2167E40015042B /* HeaderProcessor.swift in Sources */,
1104111068
9629FFDF2D81C317008DFE39 /* SwiftUIViewPath.swift in Sources */,
1104211069
111201C32E93C13000375DA3 /* AppStateManager.swift in Sources */,
1104311070
111201C42E93C13000375DA3 /* AppStateInfo.swift in Sources */,
@@ -11107,6 +11134,7 @@
1110711134
D29A9FCC29DDBCC5005C54A4 /* DDTAssertValidRUMUUID.swift in Sources */,
1110811135
D29A9FB329DDB483005C54A4 /* RUMScopeTests.swift in Sources */,
1110911136
2612558B2E2167F10015042B /* BaggageHeaderMergerTests.swift in Sources */,
11137+
261255952E2167F10015042B /* HeaderProcessorTests.swift in Sources */,
1111011138
A7E6EA852D314A9B00997201 /* AnonymousIdentifierManagerTests.swift in Sources */,
1111111139
D29A9FAE29DDB483005C54A4 /* SessionReplayDependencyTests.swift in Sources */,
1111211140
61C713B62A3C600400FA735A /* RUMMonitorProtocol+ConvenienceTests.swift in Sources */,

DatadogCore/Tests/Objc/DDRUMConfigurationTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,33 @@ class DDRUMConfigurationTests: XCTestCase {
7575
DDAssertReflectionEqual(swift.urlSessionTracking, .init(firstPartyHostsTracing: .traceWithHeaders(hostsWithHeaders: ["foo.com": [.b3, .datadog]], sampleRate: 99)))
7676
}
7777

78+
func testSetDDRUMURLSessionTrackingWithTrackResourceHeaders() {
79+
let tracking = objc_URLSessionTracking()
80+
81+
objc.setURLSessionTracking(tracking)
82+
DDAssertReflectionEqual(swift.urlSessionTracking, RUM.Configuration.URLSessionTracking())
83+
84+
tracking.setTrackResourceHeaders(.disabled)
85+
objc.setURLSessionTracking(tracking)
86+
DDAssertReflectionEqual(swift.urlSessionTracking, .init(trackResourceHeaders: .disabled))
87+
88+
tracking.setTrackResourceHeaders(.defaults)
89+
objc.setURLSessionTracking(tracking)
90+
DDAssertReflectionEqual(swift.urlSessionTracking, .init(trackResourceHeaders: .defaults))
91+
92+
tracking.setTrackResourceHeaders(.custom([.defaults]))
93+
objc.setURLSessionTracking(tracking)
94+
DDAssertReflectionEqual(swift.urlSessionTracking, .init(trackResourceHeaders: .custom([.defaults])))
95+
96+
tracking.setTrackResourceHeaders(.custom([.matchHeaders(["x-request-id", "x-trace-id"])]))
97+
objc.setURLSessionTracking(tracking)
98+
DDAssertReflectionEqual(swift.urlSessionTracking, .init(trackResourceHeaders: .custom([.matchHeaders(["x-request-id", "x-trace-id"])])))
99+
100+
tracking.setTrackResourceHeaders(.custom([.defaults, .matchHeaders(["x-request-id"])]))
101+
objc.setURLSessionTracking(tracking)
102+
DDAssertReflectionEqual(swift.urlSessionTracking, .init(trackResourceHeaders: .custom([.defaults, .matchHeaders(["x-request-id"])])))
103+
}
104+
78105
func testSetDDRUMURLSessionTrackingWithResourceAttributesProvider() {
79106
let tracking = objc_URLSessionTracking()
80107

DatadogInternal/Sources/Attributes/Attributes.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ public struct CrossPlatformAttributes {
163163
/// Custom value for Interaction To Next view.
164164
/// For Flutter this is the amount of time between an action occurring and the First Build Complete occurring on the next view.
165165
public static let customINVValue: String = "_dd.view.custom_inv_value"
166+
167+
/// Request headers passed from Cross-Platform SDK or captured from native URLSession interception.
168+
/// Used in RUM resources to transport request headers through the RUM command pipeline.
169+
/// Expects `[String: String]` value containing header keys and values.
170+
public static let requestHeaders = "_dd.request_headers"
171+
172+
/// Response headers passed from Cross-Platform SDK or captured from native URLSession interception.
173+
/// Used in RUM resources to transport response headers through the RUM command pipeline.
174+
/// Expects `[String: String]` value containing header keys and values.
175+
public static let responseHeaders = "_dd.response_headers"
166176
}
167177

168178
/// HTTP header names used to pass GraphQL metadata from the application to the SDK.

DatadogInternal/Sources/Models/RUM/RUMDataModels.swift

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3869,7 +3869,10 @@ public struct RUMResourceEvent: RUMDataModel {
38693869
public let renderBlockingStatus: RenderBlockingStatus?
38703870

38713871
/// Request properties
3872-
public let request: Request?
3872+
public var request: Request?
3873+
3874+
/// Response properties
3875+
public var response: Response?
38733876

38743877
/// Size in octet of the resource response body
38753878
public let size: Int64?
@@ -3909,6 +3912,7 @@ public struct RUMResourceEvent: RUMDataModel {
39093912
case redirect = "redirect"
39103913
case renderBlockingStatus = "render_blocking_status"
39113914
case request = "request"
3915+
case response = "response"
39123916
case size = "size"
39133917
case ssl = "ssl"
39143918
case statusCode = "status_code"
@@ -3937,6 +3941,7 @@ public struct RUMResourceEvent: RUMDataModel {
39373941
/// - redirect: Redirect phase properties
39383942
/// - renderBlockingStatus: Render blocking status of the resource
39393943
/// - request: Request properties
3944+
/// - response: Response properties
39403945
/// - size: Size in octet of the resource response body
39413946
/// - ssl: SSL phase properties
39423947
/// - statusCode: HTTP status code of the resource
@@ -3961,6 +3966,7 @@ public struct RUMResourceEvent: RUMDataModel {
39613966
redirect: Redirect? = nil,
39623967
renderBlockingStatus: RenderBlockingStatus? = nil,
39633968
request: Request? = nil,
3969+
response: Response? = nil,
39643970
size: Int64? = nil,
39653971
ssl: SSL? = nil,
39663972
statusCode: Int64? = nil,
@@ -3985,6 +3991,7 @@ public struct RUMResourceEvent: RUMDataModel {
39853991
self.redirect = redirect
39863992
self.renderBlockingStatus = renderBlockingStatus
39873993
self.request = request
3994+
self.response = response
39883995
self.size = size
39893996
self.ssl = ssl
39903997
self.statusCode = statusCode
@@ -4373,22 +4380,79 @@ public struct RUMResourceEvent: RUMDataModel {
43734380
/// Size in octet of the request body sent over the network (after encoding)
43744381
public let encodedBodySize: Int64?
43754382

4383+
/// HTTP headers of the resource request
4384+
public var headers: Headers?
4385+
43764386
public enum CodingKeys: String, CodingKey {
43774387
case decodedBodySize = "decoded_body_size"
43784388
case encodedBodySize = "encoded_body_size"
4389+
case headers = "headers"
43794390
}
43804391

43814392
/// Request properties
43824393
///
43834394
/// - Parameters:
43844395
/// - decodedBodySize: Size in octet of the request body before any encoding
43854396
/// - encodedBodySize: Size in octet of the request body sent over the network (after encoding)
4397+
/// - headers: HTTP headers of the resource request
43864398
public init(
43874399
decodedBodySize: Int64? = nil,
4388-
encodedBodySize: Int64? = nil
4400+
encodedBodySize: Int64? = nil,
4401+
headers: Headers? = nil
43894402
) {
43904403
self.decodedBodySize = decodedBodySize
43914404
self.encodedBodySize = encodedBodySize
4405+
self.headers = headers
4406+
}
4407+
4408+
/// HTTP headers of the resource request
4409+
public struct Headers: Codable {
4410+
public var headersInfo: [String: String]
4411+
4412+
/// HTTP headers of the resource request
4413+
///
4414+
/// - Parameters:
4415+
/// - headersInfo:
4416+
public init(
4417+
headersInfo: [String: String]
4418+
) {
4419+
self.headersInfo = headersInfo
4420+
}
4421+
}
4422+
}
4423+
4424+
/// Response properties
4425+
public struct Response: Codable {
4426+
/// HTTP headers of the resource response
4427+
public var headers: Headers?
4428+
4429+
public enum CodingKeys: String, CodingKey {
4430+
case headers = "headers"
4431+
}
4432+
4433+
/// Response properties
4434+
///
4435+
/// - Parameters:
4436+
/// - headers: HTTP headers of the resource response
4437+
public init(
4438+
headers: Headers? = nil
4439+
) {
4440+
self.headers = headers
4441+
}
4442+
4443+
/// HTTP headers of the resource response
4444+
public struct Headers: Codable {
4445+
public var headersInfo: [String: String]
4446+
4447+
/// HTTP headers of the resource response
4448+
///
4449+
/// - Parameters:
4450+
/// - headersInfo:
4451+
public init(
4452+
headersInfo: [String: String]
4453+
) {
4454+
self.headersInfo = headersInfo
4455+
}
43924456
}
43934457
}
43944458

@@ -4571,6 +4635,46 @@ public struct RUMResourceEvent: RUMDataModel {
45714635
}
45724636
}
45734637

4638+
extension RUMResourceEvent.Resource.Request.Headers {
4639+
public func encode(to encoder: Encoder) throws {
4640+
// Encode dynamic properties:
4641+
var dynamicContainer = encoder.container(keyedBy: DynamicCodingKey.self)
4642+
try headersInfo.forEach {
4643+
try dynamicContainer.encode($1, forKey: DynamicCodingKey($0))
4644+
}
4645+
}
4646+
4647+
public init(from decoder: Decoder) throws {
4648+
// Decode other properties into [String: String] dictionary:
4649+
let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self)
4650+
self.headersInfo = [:]
4651+
4652+
try dynamicContainer.allKeys.forEach {
4653+
self.headersInfo[$0.stringValue] = try dynamicContainer.decode(String.self, forKey: $0)
4654+
}
4655+
}
4656+
}
4657+
4658+
extension RUMResourceEvent.Resource.Response.Headers {
4659+
public func encode(to encoder: Encoder) throws {
4660+
// Encode dynamic properties:
4661+
var dynamicContainer = encoder.container(keyedBy: DynamicCodingKey.self)
4662+
try headersInfo.forEach {
4663+
try dynamicContainer.encode($1, forKey: DynamicCodingKey($0))
4664+
}
4665+
}
4666+
4667+
public init(from decoder: Decoder) throws {
4668+
// Decode other properties into [String: String] dictionary:
4669+
let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self)
4670+
self.headersInfo = [:]
4671+
4672+
try dynamicContainer.allKeys.forEach {
4673+
self.headersInfo[$0.stringValue] = try dynamicContainer.decode(String.self, forKey: $0)
4674+
}
4675+
}
4676+
}
4677+
45744678
/// The precondition that led to the creation of the session
45754679
public enum RUMSessionPrecondition: String, Codable {
45764680
case userAppLaunch = "user_app_launch"
@@ -11618,4 +11722,4 @@ extension TelemetryUsageEvent.Telemetry {
1161811722
}
1161911723
}
1162011724

11621-
// Generated from https://github.com/DataDog/rum-events-format/tree/df49e999b2444a66f3c37089db42e3c20ca5538d
11725+
// Generated from https://github.com/DataDog/rum-events-format/tree/302e837c3d49b38587fa58ef16f9aaa7d79be455

0 commit comments

Comments
 (0)