diff --git a/CHANGELOG.md b/CHANGELOG.md index e182c8ac4d..8146f0a3e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - [IMPROVEMENT] Add a method for sending error attributes on logs as strings. - [IMPROVEMENT] Add manual Open Telemetry b3 headers injection. See [#1057][] - [IMPROVEMENT] Add automatic Open Telemetry b3 headers injection. See [#1061][] +- [IMPROVEMENT] Add manual and automatic W3C traceparent header injection. See [#1071][] # 1.13.0 / 08-11-2022 @@ -418,6 +419,7 @@ [#1045]: https://github.com/DataDog/dd-sdk-ios/pull/1045 [#1057]: https://github.com/DataDog/dd-sdk-ios/pull/1057 [#1061]: https://github.com/DataDog/dd-sdk-ios/pull/1061 +[#1071]: https://github.com/DataDog/dd-sdk-ios/pull/1071 [@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 2d549ea03b..265159ab5d 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -342,7 +342,6 @@ 61B038662527247800518F3C /* URLSessionInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E1337252340810098C6B0 /* URLSessionInterceptor.swift */; }; 61B0386C2527247B00518F3C /* TaskInterception.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61417DC52525CDDE00E2D55C /* TaskInterception.swift */; }; 61B03879252724AB00518F3C /* URLSessionInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03874252724AB00518F3C /* URLSessionInterceptorTests.swift */; }; - 61B0387A252724AB00518F3C /* FirstPartyURLsFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03875252724AB00518F3C /* FirstPartyURLsFilterTests.swift */; }; 61B0387B252724AB00518F3C /* URLSessionTracingHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03876252724AB00518F3C /* URLSessionTracingHandlerTests.swift */; }; 61B0387C252724AB00518F3C /* DDURLSessionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03877252724AB00518F3C /* DDURLSessionDelegateTests.swift */; }; 61B0387D252724AB00518F3C /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03878252724AB00518F3C /* URLSessionSwizzlerTests.swift */; }; @@ -558,13 +557,30 @@ 9EE5AD8226205B82001E699E /* DDNSURLSessionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EE5AD8126205B82001E699E /* DDNSURLSessionDelegateTests.swift */; }; 9EEA4871258B76A100EBDA9D /* Global+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEA4870258B76A100EBDA9D /* Global+objc.swift */; }; 9EF963E82537556300235F98 /* DDURLSessionDelegateAsSuperclassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF963E72537556300235F98 /* DDURLSessionDelegateAsSuperclassTests.swift */; }; - 9EFD112C24B32D29003A1A2B /* FirstPartyURLsFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EFD112B24B32D29003A1A2B /* FirstPartyURLsFilter.swift */; }; A71B1FFB292CE0B000C99F4F /* TracePropagationHeadersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71B1FFA292CE0B000C99F4F /* TracePropagationHeadersProvider.swift */; }; A71B1FFD292CE22F00C99F4F /* TracePropagationHeadersExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71B1FFC292CE22F00C99F4F /* TracePropagationHeadersExtractor.swift */; }; A71B1FFF292CF0A400C99F4F /* TracingHeaderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71B1FFE292CF0A400C99F4F /* TracingHeaderType.swift */; }; A71B2000292CF0A400C99F4F /* TracingHeaderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71B1FFE292CF0A400C99F4F /* TracingHeaderType.swift */; }; + A728AD9D2934CE4400397996 /* W3CHTTPHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728AD9C2934CE4400397996 /* W3CHTTPHeaders.swift */; }; + A728AD9F2934CE5000397996 /* W3CHTTPHeadersWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728AD9E2934CE5000397996 /* W3CHTTPHeadersWriter.swift */; }; + A728ADA12934CE5D00397996 /* W3CHTTPHeadersReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728ADA02934CE5D00397996 /* W3CHTTPHeadersReader.swift */; }; + A728ADA32934DB5000397996 /* W3CHTTPHeadersWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728ADA22934DB5000397996 /* W3CHTTPHeadersWriterTests.swift */; }; + A728ADA42934DB5600397996 /* W3CHTTPHeadersWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728ADA22934DB5000397996 /* W3CHTTPHeadersWriterTests.swift */; }; + A728ADA62934DF2400397996 /* W3CHTTPHeadersReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728ADA52934DF2400397996 /* W3CHTTPHeadersReaderTests.swift */; }; + A728ADA72934DF2800397996 /* W3CHTTPHeadersReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728ADA52934DF2400397996 /* W3CHTTPHeadersReaderTests.swift */; }; A728ADA82934E2F100397996 /* TracePropagationHeadersExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71B1FFC292CE22F00C99F4F /* TracePropagationHeadersExtractor.swift */; }; A728ADA92934E2F800397996 /* TracePropagationHeadersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71B1FFA292CE0B000C99F4F /* TracePropagationHeadersProvider.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 */; }; + A728ADB12934EB0C00397996 /* DDW3CHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A728ADAD2934EB0300397996 /* DDW3CHTTPHeadersWriter+apiTests.m */; }; + A7609F272940AB4B00020D85 /* FirstPartyHosts.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7609F262940AB4B00020D85 /* FirstPartyHosts.swift */; }; + A7609F282940AB4B00020D85 /* FirstPartyHosts.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7609F262940AB4B00020D85 /* FirstPartyHosts.swift */; }; + A762BDE429351A250058D8E7 /* FirstPartyHostsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A762BDE329351A250058D8E7 /* FirstPartyHostsTests.swift */; }; + A762BDE529351A250058D8E7 /* FirstPartyHostsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A762BDE329351A250058D8E7 /* FirstPartyHostsTests.swift */; }; + A793E6692948C785002A195D /* W3CHTTPHeadersWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728AD9E2934CE5000397996 /* W3CHTTPHeadersWriter.swift */; }; + A793E66A2948C796002A195D /* W3CHTTPHeadersReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728ADA02934CE5D00397996 /* W3CHTTPHeadersReader.swift */; }; + A793E66B2948C7B1002A195D /* W3CHTTPHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728AD9C2934CE4400397996 /* W3CHTTPHeaders.swift */; }; A79B0F5C292B7C24008742B3 /* OTelHTTPHeadersWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79B0F5A292B7C06008742B3 /* OTelHTTPHeadersWriterTests.swift */; }; A79B0F5D292B7C25008742B3 /* OTelHTTPHeadersWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79B0F5A292B7C06008742B3 /* OTelHTTPHeadersWriterTests.swift */; }; A79B0F61292BB071008742B3 /* OTelHTTPHeadersReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79B0F60292BB071008742B3 /* OTelHTTPHeadersReaderTests.swift */; }; @@ -806,7 +822,6 @@ D2CB6E5127C50EAE00A62B57 /* Warnings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C5A87D24509A0C00DA608C /* Warnings.swift */; }; D2CB6E5227C50EAE00A62B57 /* DataMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619E16E82578E73E00B2516B /* DataMigrator.swift */; }; D2CB6E5327C50EAE00A62B57 /* RUMUUIDGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618DCFD824C7269500589570 /* RUMUUIDGenerator.swift */; }; - D2CB6E5427C50EAE00A62B57 /* FirstPartyURLsFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EFD112B24B32D29003A1A2B /* FirstPartyURLsFilter.swift */; }; D2CB6E5527C50EAE00A62B57 /* KronosNSTimer+ClosureKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D3E0D1277B23F1008BE766 /* KronosNSTimer+ClosureKit.swift */; }; D2CB6E5627C50EAE00A62B57 /* TaskInterception.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61417DC52525CDDE00E2D55C /* TaskInterception.swift */; }; D2CB6E5727C50EAE00A62B57 /* HTTPHeadersReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E13A92524B8700098C6B0 /* HTTPHeadersReader.swift */; }; @@ -986,7 +1001,6 @@ D2CB6F2427C520D400A62B57 /* DDURLSessionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03877252724AB00518F3C /* DDURLSessionDelegateTests.swift */; }; D2CB6F2527C520D400A62B57 /* EquatableInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AADBDC263C7ECF008ABC6F /* EquatableInTests.swift */; }; D2CB6F2627C520D400A62B57 /* DataUploadDelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C312423990D00786299 /* DataUploadDelayTests.swift */; }; - D2CB6F2727C520D400A62B57 /* FirstPartyURLsFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03875252724AB00518F3C /* FirstPartyURLsFilterTests.swift */; }; D2CB6F2827C520D400A62B57 /* DataUploadWorkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C2F2423990D00786299 /* DataUploadWorkerTests.swift */; }; D2CB6F2927C520D400A62B57 /* DDGlobalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616B6683259CAE3300968EE8 /* DDGlobalTests.swift */; }; D2CB6F2A27C520D400A62B57 /* GlobalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E909F524A32D1C005EA2DE /* GlobalTests.swift */; }; @@ -1685,7 +1699,6 @@ 61AD4E3924534075006E34EA /* TracingFeatureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingFeatureTests.swift; sourceTree = ""; }; 61B03810252656F500518F3C /* URLSessionTracingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTracingHandler.swift; sourceTree = ""; }; 61B03874252724AB00518F3C /* URLSessionInterceptorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionInterceptorTests.swift; sourceTree = ""; }; - 61B03875252724AB00518F3C /* FirstPartyURLsFilterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstPartyURLsFilterTests.swift; sourceTree = ""; }; 61B03876252724AB00518F3C /* URLSessionTracingHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionTracingHandlerTests.swift; sourceTree = ""; }; 61B03877252724AB00518F3C /* DDURLSessionDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDURLSessionDelegateTests.swift; sourceTree = ""; }; 61B03878252724AB00518F3C /* URLSessionSwizzlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzlerTests.swift; sourceTree = ""; }; @@ -1892,10 +1905,18 @@ 9EF49F1624476FBD004F2CA0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9EF49F17244770AD004F2CA0 /* DatadogIntegrationTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DatadogIntegrationTests.xcconfig; sourceTree = ""; }; 9EF963E72537556300235F98 /* DDURLSessionDelegateAsSuperclassTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDURLSessionDelegateAsSuperclassTests.swift; sourceTree = ""; }; - 9EFD112B24B32D29003A1A2B /* FirstPartyURLsFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstPartyURLsFilter.swift; sourceTree = ""; }; A71B1FFA292CE0B000C99F4F /* TracePropagationHeadersProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracePropagationHeadersProvider.swift; sourceTree = ""; }; A71B1FFC292CE22F00C99F4F /* TracePropagationHeadersExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracePropagationHeadersExtractor.swift; sourceTree = ""; }; A71B1FFE292CF0A400C99F4F /* TracingHeaderType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingHeaderType.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 = ""; }; + A728ADA22934DB5000397996 /* W3CHTTPHeadersWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = W3CHTTPHeadersWriterTests.swift; sourceTree = ""; }; + A728ADA52934DF2400397996 /* W3CHTTPHeadersReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = W3CHTTPHeadersReaderTests.swift; sourceTree = ""; }; + A728ADAA2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "W3CHTTPHeadersWriter+objc.swift"; sourceTree = ""; }; + A728ADAD2934EB0300397996 /* DDW3CHTTPHeadersWriter+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDW3CHTTPHeadersWriter+apiTests.m"; sourceTree = ""; }; + A7609F262940AB4B00020D85 /* FirstPartyHosts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstPartyHosts.swift; sourceTree = ""; }; + A762BDE329351A250058D8E7 /* FirstPartyHostsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstPartyHostsTests.swift; sourceTree = ""; }; A79B0F5A292B7C06008742B3 /* OTelHTTPHeadersWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTelHTTPHeadersWriterTests.swift; sourceTree = ""; }; A79B0F5E292BA435008742B3 /* OTelHTTPHeadersWriter+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OTelHTTPHeadersWriter+objc.swift"; sourceTree = ""; }; A79B0F60292BB071008742B3 /* OTelHTTPHeadersReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTelHTTPHeadersReaderTests.swift; sourceTree = ""; }; @@ -2831,6 +2852,7 @@ children = ( 6132BF4B24A49C8F00D7BD17 /* HTTPHeadersWriter+objc.swift */, A79B0F5E292BA435008742B3 /* OTelHTTPHeadersWriter+objc.swift */, + A728ADAA2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift */, ); path = Propagation; sourceTree = ""; @@ -2982,7 +3004,6 @@ 613F23DC252B05BD006CD2D7 /* URLFiltering */ = { isa = PBXGroup; children = ( - 9EFD112B24B32D29003A1A2B /* FirstPartyURLsFilter.swift */, 6149FB392529D17F00EE387A /* InternalURLsFilter.swift */, ); path = URLFiltering; @@ -2991,7 +3012,6 @@ 613F23E2252B05D7006CD2D7 /* URLFiltering */ = { isa = PBXGroup; children = ( - 61B03875252724AB00518F3C /* FirstPartyURLsFilterTests.swift */, 6149FB402529DEBD00EE387A /* InternalURLsFilterTests.swift */, ); path = URLFiltering; @@ -3008,6 +3028,7 @@ 613F2404252B3791006CD2D7 /* Autoinstrumentation */ = { isa = PBXGroup; children = ( + A762BDE329351A250058D8E7 /* FirstPartyHostsTests.swift */, 61B03876252724AB00518F3C /* URLSessionTracingHandlerTests.swift */, ); path = Autoinstrumentation; @@ -3018,6 +3039,7 @@ children = ( A71B1FFE292CF0A400C99F4F /* TracingHeaderType.swift */, 61B03810252656F500518F3C /* URLSessionTracingHandler.swift */, + A7609F262940AB4B00020D85 /* FirstPartyHosts.swift */, ); path = AutoInstrumentation; sourceTree = ""; @@ -3642,6 +3664,7 @@ 61B5E42426DFAFBC000B0A5F /* DDGlobal+apiTests.m */, D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */, A79B0F63292BD074008742B3 /* DDOTelHTTPHeadersWriter+apiTests.m */, + A728ADAD2934EB0300397996 /* DDW3CHTTPHeadersWriter+apiTests.m */, ); path = ObjcAPITests; sourceTree = ""; @@ -3736,6 +3759,7 @@ 61C5A88224509A0C00DA608C /* Propagation */ = { isa = PBXGroup; children = ( + A728AD992934CE2800397996 /* W3C */, A7F773DA29253F6200AC1A62 /* OpenTelemetry */, A7F773D929253F5900AC1A62 /* OpenTracing */, A71B1FFA292CE0B000C99F4F /* TracePropagationHeadersProvider.swift */, @@ -4248,11 +4272,23 @@ path = DatadogIntegrationTests; sourceTree = ""; }; + A728AD992934CE2800397996 /* W3C */ = { + isa = PBXGroup; + children = ( + A728AD9C2934CE4400397996 /* W3CHTTPHeaders.swift */, + A728AD9E2934CE5000397996 /* W3CHTTPHeadersWriter.swift */, + A728ADA02934CE5D00397996 /* W3CHTTPHeadersReader.swift */, + ); + path = W3C; + sourceTree = ""; + }; A79B0F59292B7BF5008742B3 /* Propagation */ = { isa = PBXGroup; children = ( A79B0F5A292B7C06008742B3 /* OTelHTTPHeadersWriterTests.swift */, A79B0F60292BB071008742B3 /* OTelHTTPHeadersReaderTests.swift */, + A728ADA22934DB5000397996 /* W3CHTTPHeadersWriterTests.swift */, + A728ADA52934DF2400397996 /* W3CHTTPHeadersReaderTests.swift */, ); path = Propagation; sourceTree = ""; @@ -5488,6 +5524,7 @@ 6179FFD3254ADB1700556A0B /* ObjcAppLaunchHandler.m in Sources */, F637AED22697404200516F32 /* UIKitRUMUserActionsPredicate.swift in Sources */, D21C26DD28ACD371005DD405 /* FeatureBaggage.swift in Sources */, + A728ADA12934CE5D00397996 /* W3CHTTPHeadersReader.swift in Sources */, D29CDD3228211A2200F7DAA5 /* DataBlock.swift in Sources */, D2553829288F0B2400727FAD /* LowPowerModePublisher.swift in Sources */, 616C0A9E28573DFF00C13264 /* RUMOperatingSystemInfo.swift in Sources */, @@ -5532,7 +5569,6 @@ 61C5A88824509A0C00DA608C /* Warnings.swift in Sources */, 619E16E92578E73E00B2516B /* DataMigrator.swift in Sources */, 618DCFD924C7269500589570 /* RUMUUIDGenerator.swift in Sources */, - 9EFD112C24B32D29003A1A2B /* FirstPartyURLsFilter.swift in Sources */, 61D3E0DB277B23F1008BE766 /* KronosNSTimer+ClosureKit.swift in Sources */, 61B0386C2527247B00518F3C /* TaskInterception.swift in Sources */, 61B03892252724D900518F3C /* HTTPHeadersReader.swift in Sources */, @@ -5562,6 +5598,7 @@ 611529A525E3DD51004F740E /* ValuePublisher.swift in Sources */, 618DCFD724C7265300589570 /* RUMUUID.swift in Sources */, A71B1FFD292CE22F00C99F4F /* TracePropagationHeadersExtractor.swift in Sources */, + A728AD9D2934CE4400397996 /* W3CHTTPHeaders.swift in Sources */, 61B038662527247800518F3C /* URLSessionInterceptor.swift in Sources */, 61B03898252724DE00518F3C /* TracingHTTPHeaders.swift in Sources */, D2FB1254292E0E96005B13F8 /* TrackingConsentPublisher.swift in Sources */, @@ -5577,6 +5614,7 @@ 61D3E0D6277B23F1008BE766 /* KronosClock.swift in Sources */, D248ED452807193B00B315B4 /* RUMTelemetry.swift in Sources */, 613E793B2577B6EE00DFCC17 /* DataReader.swift in Sources */, + A728AD9F2934CE5000397996 /* W3CHTTPHeadersWriter.swift in Sources */, 61FD9FC92851E67100214BD9 /* Sysctl.swift in Sources */, 614B0A5324EBFE5500A2A780 /* DDRUMMonitor.swift in Sources */, 61133BE32423979B00786299 /* UserInfoProvider.swift in Sources */, @@ -5626,6 +5664,7 @@ 61E945E02869BEF500A946C4 /* DD.swift in Sources */, D24C27EA270C8BEE005DE596 /* DataCompression.swift in Sources */, 61133BE42423979B00786299 /* LogEventEncoder.swift in Sources */, + A7609F272940AB4B00020D85 /* FirstPartyHosts.swift in Sources */, B3FC3C0926526F0000DEED9E /* VitalInfo.swift in Sources */, 61DA8CAF28620C760074A606 /* Cryptography.swift in Sources */, 616F1FB32840125400651A3A /* RUMV2Configuration.swift in Sources */, @@ -5738,8 +5777,10 @@ 61DA8CB2286215DE0074A606 /* CryptographyTests.swift in Sources */, 615A4A8924A34FD700233986 /* DDTracerTests.swift in Sources */, 615A4A8724A3452800233986 /* DDTracerConfigurationTests.swift in Sources */, + A762BDE429351A250058D8E7 /* FirstPartyHostsTests.swift in Sources */, 613E81F725A743600084B751 /* RUMEventsMapperTests.swift in Sources */, 61EF78B7257E37D500EDCCB3 /* MoveDataMigratorTests.swift in Sources */, + A728ADA62934DF2400397996 /* W3CHTTPHeadersReaderTests.swift in Sources */, 61F1A621249A45E400075390 /* DDSpanContextTests.swift in Sources */, 61D03BE0273404E700367DE0 /* RUMDataModels+objcTests.swift in Sources */, E143CCAF27D236F600F4018A /* CITestIntegrationTests.swift in Sources */, @@ -5777,9 +5818,9 @@ 61DA8CAC2861C3720074A606 /* DirectoriesTests.swift in Sources */, 61AADBDD263C7ECF008ABC6F /* EquatableInTests.swift in Sources */, 61133C5E2423990D00786299 /* DataUploadDelayTests.swift in Sources */, - 61B0387A252724AB00518F3C /* FirstPartyURLsFilterTests.swift in Sources */, 61133C5C2423990D00786299 /* DataUploadWorkerTests.swift in Sources */, 616B6684259CAE3300968EE8 /* DDGlobalTests.swift in Sources */, + A728ADB02934EB0900397996 /* DDW3CHTTPHeadersWriter+apiTests.m in Sources */, 61E909F624A32D1C005EA2DE /* GlobalTests.swift in Sources */, 61FC5F3525CC1898006BB4DE /* CrashContextProviderTests.swift in Sources */, 49274906288048B500ECD49B /* InternalTelemetryTests.swift in Sources */, @@ -5870,6 +5911,7 @@ 9EF963E82537556300235F98 /* DDURLSessionDelegateAsSuperclassTests.swift in Sources */, 6141CE672806B41C00EBB879 /* RUMViewUpdatesThrottlerTests.swift in Sources */, 61F3CDAB25121FB500C816E5 /* UIViewControllerSwizzlerTests.swift in Sources */, + A728ADA32934DB5000397996 /* W3CHTTPHeadersWriterTests.swift in Sources */, 9E989A4225F640D100235FC3 /* AppStateListenerTests.swift in Sources */, D2FB1257292E0F0E005B13F8 /* TrackingConsentPublisherTests.swift in Sources */, D2EFA871286DFF7900F1FAA6 /* DatadogContextMock.swift in Sources */, @@ -5903,6 +5945,7 @@ 6111C58225C0081F00F5C4A2 /* RUMDataModels+objc.swift in Sources */, 6132BF4924A49B6800D7BD17 /* DDSpanContext+objc.swift in Sources */, 6132BF4224A38D2400D7BD17 /* OTTracer+objc.swift in Sources */, + A728ADAB2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift in Sources */, A79B0F66292BD7CA008742B3 /* OTelHTTPHeadersWriter+objc.swift in Sources */, 615A4A8524A3445700233986 /* TracerConfiguration+objc.swift in Sources */, 61133C0E2423983800786299 /* Datadog+objc.swift in Sources */, @@ -6183,6 +6226,7 @@ D2CBC25B2942009800134409 /* AnyCodable.swift in Sources */, D2CB6E3327C50EAE00A62B57 /* OTConstants.swift in Sources */, 61DA8CB628643B220074A606 /* InternalLogger.swift in Sources */, + A7609F282940AB4B00020D85 /* FirstPartyHosts.swift in Sources */, D2EFA869286DA85700F1FAA6 /* DatadogContextProvider.swift in Sources */, D2B3F04E282A85FD00C2B5EE /* DatadogCore.swift in Sources */, D2CB6E3527C50EAE00A62B57 /* SpanEventBuilder.swift in Sources */, @@ -6193,6 +6237,7 @@ D2CB6E3927C50EAE00A62B57 /* JSONEncoder.swift in Sources */, D2CB6E3C27C50EAE00A62B57 /* Retrying.swift in Sources */, D2CB6E3D27C50EAE00A62B57 /* FeaturesConfiguration.swift in Sources */, + A793E66B2948C7B1002A195D /* W3CHTTPHeaders.swift in Sources */, D2CB6E3E27C50EAE00A62B57 /* SpanEventMapper.swift in Sources */, D2CB6E3F27C50EAE00A62B57 /* ConsentProvider.swift in Sources */, D20605B32874E1660047275C /* CarrierInfoPublisher.swift in Sources */, @@ -6226,8 +6271,8 @@ D2CB6E5027C50EAE00A62B57 /* UIViewControllerHandler.swift in Sources */, D2CB6E5127C50EAE00A62B57 /* Warnings.swift in Sources */, D2CB6E5227C50EAE00A62B57 /* DataMigrator.swift in Sources */, + A793E6692948C785002A195D /* W3CHTTPHeadersWriter.swift in Sources */, D2CB6E5327C50EAE00A62B57 /* RUMUUIDGenerator.swift in Sources */, - D2CB6E5427C50EAE00A62B57 /* FirstPartyURLsFilter.swift in Sources */, D2CB6E5527C50EAE00A62B57 /* KronosNSTimer+ClosureKit.swift in Sources */, D2CB6E5627C50EAE00A62B57 /* TaskInterception.swift in Sources */, D2CBC2582942008800134409 /* AnyDecodable.swift in Sources */, @@ -6238,6 +6283,7 @@ D2CB6E5A27C50EAE00A62B57 /* URLSessionAutoInstrumentation.swift in Sources */, D2A1EE30287DA4F200D28DFB /* UserInfo.swift in Sources */, D2CB6E5B27C50EAE00A62B57 /* DDSpanContext.swift in Sources */, + A793E66A2948C796002A195D /* W3CHTTPHeadersReader.swift in Sources */, A7F773D52924EA2D00AC1A62 /* OTelHTTPHeaders.swift in Sources */, 6194E4BA28785BFD00EB6307 /* RemoteLogger.swift in Sources */, D2CB6E5C27C50EAE00A62B57 /* UIKitRUMUserActionsHandler.swift in Sources */, @@ -6403,6 +6449,7 @@ D2CB6EE427C520D400A62B57 /* FeatureTests.swift in Sources */, D2CB6EE527C520D400A62B57 /* DataUploadConditionsTests.swift in Sources */, D2CB6EE627C520D400A62B57 /* DateFormattingTests.swift in Sources */, + A728ADA72934DF2800397996 /* W3CHTTPHeadersReaderTests.swift in Sources */, D2CB6EE727C520D400A62B57 /* FileTests.swift in Sources */, D2CB6EE827C520D400A62B57 /* TracingFeatureTests.swift in Sources */, D2CB6EE927C520D400A62B57 /* SamplerTests.swift in Sources */, @@ -6430,6 +6477,7 @@ D2CB6EFE27C520D400A62B57 /* RUMMonitorConfigurationTests.swift in Sources */, D2CB6EFF27C520D400A62B57 /* AttributesMocks.swift in Sources */, D2CB6F0027C520D400A62B57 /* RUMSessionMatcher.swift in Sources */, + A728ADB12934EB0C00397996 /* DDW3CHTTPHeadersWriter+apiTests.m in Sources */, D2CB6F0127C520D400A62B57 /* DatadogPrivateMocks.swift in Sources */, D26C49B02886DC7B00802B2D /* ApplicationStatePublisherTests.swift in Sources */, D2CB6F0227C520D400A62B57 /* TracingFeatureMocks.swift in Sources */, @@ -6455,6 +6503,7 @@ D2CB6F1327C520D400A62B57 /* DDConfigurationTests.swift in Sources */, D2CB6F1427C520D400A62B57 /* UUID.swift in Sources */, D2CB6F1527C520D400A62B57 /* URLSessionRUMResourcesHandlerTests.swift in Sources */, + A762BDE529351A250058D8E7 /* FirstPartyHostsTests.swift in Sources */, D2CB6F1627C520D400A62B57 /* URLSessionInterceptorTests.swift in Sources */, D2CB6F1727C520D400A62B57 /* ObjcExceptionHandlerTests.swift in Sources */, D2CB6F1827C520D400A62B57 /* DatadogTestsObserver.swift in Sources */, @@ -6463,6 +6512,7 @@ D2A1EE39287EEB7600D28DFB /* NetworkConnectionInfoPublisherTests.swift in Sources */, D2CB6F1B27C520D400A62B57 /* RUMOffViewEventsHandlingRuleTests.swift in Sources */, D2CB6F1C27C520D400A62B57 /* DDNoopRUMMonitorTests.swift in Sources */, + A728ADA42934DB5600397996 /* W3CHTTPHeadersWriterTests.swift in Sources */, D2CB6F1D27C520D400A62B57 /* DataUploaderTests.swift in Sources */, D2CB6F1F27C520D400A62B57 /* XCTestCase.swift in Sources */, D2A1EE35287EB8DB00D28DFB /* ServerOffsetPublisherTests.swift in Sources */, @@ -6476,7 +6526,6 @@ 61DA8CAD2861C3720074A606 /* DirectoriesTests.swift in Sources */, D2CB6F2527C520D400A62B57 /* EquatableInTests.swift in Sources */, D2CB6F2627C520D400A62B57 /* DataUploadDelayTests.swift in Sources */, - D2CB6F2727C520D400A62B57 /* FirstPartyURLsFilterTests.swift in Sources */, D2CB6F2827C520D400A62B57 /* DataUploadWorkerTests.swift in Sources */, D2CB6F2927C520D400A62B57 /* DDGlobalTests.swift in Sources */, D2CB6F2A27C520D400A62B57 /* GlobalTests.swift in Sources */, @@ -6602,6 +6651,7 @@ D2CB6F9A27C5217A00A62B57 /* RUMDataModels+objc.swift in Sources */, D2CB6F9B27C5217A00A62B57 /* DDSpanContext+objc.swift in Sources */, D2CB6F9C27C5217A00A62B57 /* OTTracer+objc.swift in Sources */, + A728ADAC2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift in Sources */, A79B0F67292BD7CC008742B3 /* OTelHTTPHeadersWriter+objc.swift in Sources */, D2CB6F9D27C5217A00A62B57 /* TracerConfiguration+objc.swift in Sources */, D2CB6F9E27C5217A00A62B57 /* Datadog+objc.swift in Sources */, diff --git a/Datadog/Example/Scenarios/URLSession/URLSessionScenarios.swift b/Datadog/Example/Scenarios/URLSession/URLSessionScenarios.swift index fc5033e8c5..2c09598e66 100644 --- a/Datadog/Example/Scenarios/URLSession/URLSessionScenarios.swift +++ b/Datadog/Example/Scenarios/URLSession/URLSessionScenarios.swift @@ -162,11 +162,15 @@ class URLSessionBaseScenario: NSObject { delegate = DDURLSessionDelegate() case .directWithAdditionalFirstyPartyHosts: delegate = DDURLSessionDelegate( - additionalFirstPartyHosts: [ - customGETResourceURL.host!, - customPOSTRequest.url!.host!, - badResourceURL.host! + additionalFirstPartyHostsWithHeaderTypes: [ + customGETResourceURL.host, + customPOSTRequest.url?.host, + badResourceURL.host ] + .compactMap { $0 } + .reduce(into: [:], { partialResult, value in + partialResult[value] = [.datadog] // Prevents duplicates + }) ) case .inheritance: delegate = InheritedURLSessionDelegate() diff --git a/Sources/Datadog/Core/FeaturesConfiguration.swift b/Sources/Datadog/Core/FeaturesConfiguration.swift index 3fb316d01f..95ddc09bf1 100644 --- a/Sources/Datadog/Core/FeaturesConfiguration.swift +++ b/Sources/Datadog/Core/FeaturesConfiguration.swift @@ -69,14 +69,15 @@ internal struct FeaturesConfiguration { let backgroundEventTrackingEnabled: Bool let frustrationTrackingEnabled: Bool let onSessionStart: RUMSessionListener? - let firstPartyHosts: Set + let firstPartyHosts: FirstPartyHosts let vitalsFrequency: TimeInterval? let dateProvider: DateProvider } struct URLSessionAutoInstrumentation { - /// First party hosts defined by the user. - let userDefinedFirstPartyHosts: Set + /// First party hosts defined by the user with custom tracing header types. + let userDefinedFirstPartyHosts: FirstPartyHosts + /// URLs used internally by the SDK - they are not instrumented. let sdkInternalURLs: Set /// An optional RUM Resource attributes provider. @@ -90,8 +91,6 @@ internal struct FeaturesConfiguration { /// - if RUM instrumentation is enabled, it is used to sample traces generated by RUM BE, /// - if RUM is disabled, it is used to sample traces generated by the SDK. let tracingSampler: Sampler - - let tracingHeaderTypes: Set } struct CrashReporting { @@ -121,7 +120,7 @@ extension FeaturesConfiguration { /// /// Throws an error on invalid user input, i.e. broken custom URL. /// Prints a warning if configuration is inconsistent, i.e. RUM is enabled, but RUM Application ID was not specified. - init(configuration: Datadog.Configuration, appContext: AppContext, hostsSanitizer: HostsSanitizing = HostsSanitizer()) throws { + init(configuration: Datadog.Configuration, appContext: AppContext) throws { var logging: Logging? var tracing: Tracing? var rum: RUM? @@ -210,14 +209,6 @@ extension FeaturesConfiguration { ) } - var sanitizedHosts: Set = [] - if let firstPartyHosts = configuration.firstPartyHosts { - sanitizedHosts = hostsSanitizer.sanitized( - hosts: firstPartyHosts, - warningMessage: "The first party host configured for Datadog SDK is not valid" - ) - } - if configuration.rumEnabled { let instrumentation = RUM.Instrumentation( uiKitRUMViewsPredicate: configuration.rumUIKitViewsPredicate, @@ -241,7 +232,7 @@ extension FeaturesConfiguration { backgroundEventTrackingEnabled: configuration.rumBackgroundEventTrackingEnabled, frustrationTrackingEnabled: configuration.rumFrustrationSignalsTrackingEnabled, onSessionStart: configuration.rumSessionsListener, - firstPartyHosts: sanitizedHosts, + firstPartyHosts: configuration.firstPartyHosts ?? .init(), vitalsFrequency: configuration.mobileVitalsFrequency.timeInterval, dateProvider: dateProvider ) @@ -256,10 +247,10 @@ extension FeaturesConfiguration { } } - if configuration.firstPartyHosts != nil { + if let firstPartyHosts = configuration.firstPartyHosts { if configuration.tracingEnabled || configuration.rumEnabled { urlSessionAutoInstrumentation = URLSessionAutoInstrumentation( - userDefinedFirstPartyHosts: sanitizedHosts, + userDefinedFirstPartyHosts: firstPartyHosts, sdkInternalURLs: [ logsEndpoint.url, tracesEndpoint.url, @@ -268,8 +259,7 @@ extension FeaturesConfiguration { rumAttributesProvider: configuration.rumResourceAttributesProvider, instrumentTracing: configuration.tracingEnabled, instrumentRUM: configuration.rumEnabled, - tracingSampler: Sampler(samplingRate: debugOverride ? 100.0 : configuration.tracingSamplingRate), - tracingHeaderTypes: configuration.tracingHeaderTypes + tracingSampler: Sampler(samplingRate: debugOverride ? 100.0 : configuration.tracingSamplingRate) ) } else { let error = ProgrammerError( diff --git a/Sources/Datadog/Core/Utils/HostsSanitizer.swift b/Sources/Datadog/Core/Utils/HostsSanitizer.swift index 563cdec045..0160bd1738 100644 --- a/Sources/Datadog/Core/Utils/HostsSanitizer.swift +++ b/Sources/Datadog/Core/Utils/HostsSanitizer.swift @@ -8,42 +8,45 @@ import Foundation internal protocol HostsSanitizing { func sanitized(hosts: Set, warningMessage: String) -> Set + func sanitized( + hostsWithTracingHeaderTypes: [String: Set], + warningMessage: String + ) -> [String: Set] } internal struct HostsSanitizer: HostsSanitizing { - func sanitized(hosts: Set, warningMessage: String) -> Set { - let urlRegex = #"^(http|https)://(.*)"# - let hostRegex = #"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])$"# - let ipRegex = #"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"# - - var warnings: [String] = [] + private let urlRegex = #"^(http|https)://(.*)"# + private let hostRegex = #"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])$"# + private let ipRegex = #"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"# - let array: [String] = hosts.compactMap { host in - if host.range(of: urlRegex, options: .regularExpression) != nil { - // if an URL is given instead of the host, take its `host` part - if let sanitizedHost = URL(string: host)?.host { - warnings.append("'\(host)' is an url and will be sanitized to: '\(sanitizedHost)'.") - return sanitizedHost - } else { - warnings.append("'\(host)' is not a valid host name and will be dropped.") - return nil - } - } else if host.range(of: hostRegex, options: .regularExpression) != nil { - // if a valid host name is given, accept it - return host - } else if host.range(of: ipRegex, options: .regularExpression) != nil { - // if a valid IP address is given, accept it - return host - } else if host == "localhost" { - // if "localhost" given, accept it - return host + private func sanitize(host: String, warningMessage: String) -> (String?, String?) { + if host.range(of: urlRegex, options: .regularExpression) != nil { + // if an URL is given instead of the host, take its `host` part + if let sanitizedHost = URL(string: host)?.host { + let warning = "'\(host)' is an url and will be sanitized to: '\(sanitizedHost)'." + return (sanitizedHost, warning) } else { // otherwise, drop - warnings.append("'\(host)' is not a valid host name and will be dropped.") - return nil + let warning = "'\(host)' is not a valid host name and will be dropped." + return (nil, warning) } + } else if host.range(of: hostRegex, options: .regularExpression) != nil { + // if a valid host name is given, accept it + return (host, nil) + } else if host.range(of: ipRegex, options: .regularExpression) != nil { + // if a valid IP address is given, accept it + return (host, nil) + } else if host == "localhost" { + // if "localhost" given, accept it + return (host, nil) + } else { + // otherwise, drop + let warning = "'\(host)' is not a valid host name and will be dropped." + return (nil, warning) } + } + private func printWarnings(_ warningMessage: String, _ warnings: [String]) { warnings.forEach { warning in consolePrint( """ @@ -51,7 +54,43 @@ internal struct HostsSanitizer: HostsSanitizing { """ ) } + } + + func sanitized(hosts: Set, warningMessage: String) -> Set { + var warnings: [String] = [] + + let array: [String] = hosts.compactMap { host in + let (sanitizedHost, warning) = sanitize(host: host, warningMessage: warningMessage) + if let warning = warning { + warnings.append(warning) + } + return sanitizedHost + } + + printWarnings(warningMessage, warnings) return Set(array) } + + func sanitized( + hostsWithTracingHeaderTypes: [String: Set], + warningMessage: String + ) -> [String: Set] { + var warnings: [String] = [] + + let sanitized: [String: Set] = hostsWithTracingHeaderTypes.reduce(into: [:]) { partialResult, item in + let host = item.key + let (sanitizedHost, warning) = sanitize(host: host, warningMessage: warningMessage) + if let warning = warning { + warnings.append(warning) + } + if let sanitizedHost = sanitizedHost { + partialResult[sanitizedHost] = item.value + } + } + + printWarnings(warningMessage, warnings) + + return sanitized + } } diff --git a/Sources/Datadog/DDRUMMonitor.swift b/Sources/Datadog/DDRUMMonitor.swift index 8c809913b8..29a4a8df19 100644 --- a/Sources/Datadog/DDRUMMonitor.swift +++ b/Sources/Datadog/DDRUMMonitor.swift @@ -1,7 +1,7 @@ /* * 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. + * Copyright 2019-Present Datadog, Inc. */ import UIKit diff --git a/Sources/Datadog/DatadogConfiguration.swift b/Sources/Datadog/DatadogConfiguration.swift index fd4fc58dfd..13e227ad94 100644 --- a/Sources/Datadog/DatadogConfiguration.swift +++ b/Sources/Datadog/DatadogConfiguration.swift @@ -262,7 +262,7 @@ extension Datadog { private(set) var rumEndpoint: RUMEndpoint private(set) var serviceName: String? - private(set) var firstPartyHosts: Set? + private(set) var firstPartyHosts: FirstPartyHosts? var logEventMapper: LogEventMapper? private(set) var spanEventMapper: SpanEventMapper? private(set) var loggingSamplingRate: Float @@ -287,7 +287,6 @@ extension Datadog { private(set) var additionalConfiguration: [String: Any] private(set) var proxyConfiguration: [AnyHashable: Any]? private(set) var encryption: DataEncryption? - private(set) var tracingHeaderTypes: Set /// Creates the builder for configuring the SDK to work with RUM, Logging and Tracing features. /// - Parameter rumApplicationID: RUM Application ID obtained on Datadog website. @@ -363,8 +362,7 @@ extension Datadog { batchSize: .medium, uploadFrequency: .average, additionalConfiguration: [:], - proxyConfiguration: nil, - tracingHeaderTypes: .init([.dd]) + proxyConfiguration: nil ) } @@ -519,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,9 +528,50 @@ extension Datadog { /// /// **NOTE 2:** The `URLSession` instrumentation will NOT work without using `DDURLSessionDelegate`. /// + /// **NOTE 3:** If used simultaneously with `trackURLSession(firstPartyHostsWithHeaderTypes:)` it will merge first party hosts provided in both. + /// /// - Parameter firstPartyHosts: empty set by default public func trackURLSession(firstPartyHosts: Set = []) -> Builder { - configuration.firstPartyHosts = firstPartyHosts + return trackURLSession(firstPartyHostsWithHeaderTypes: firstPartyHosts.reduce(into: [:], { partialResult, host in + partialResult[host] = [.datadog] + })) + } + + /// 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": [.datadog]]`: + /// - 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": [.datadog]]]`: + /// - 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`. + /// + /// **NOTE 3:** If used simultaneously with `trackURLSession(firstPartyHosts:)` it will merge first party hosts provided in both. + /// + /// - Parameter firstPartyHostsWithHeaderTypes: Dictionary used to classify network requests as 1st-party + /// and determine the HTTP header types to use for Distributed Tracing. Key is a host and value is a set of tracing header types. + public func trackURLSession(firstPartyHostsWithHeaderTypes: [String: Set]) -> Builder { + configuration.firstPartyHosts += FirstPartyHosts(firstPartyHostsWithHeaderTypes) return self } diff --git a/Sources/Datadog/Logger.swift b/Sources/Datadog/Logger.swift index ab565e89d6..8f2a252789 100644 --- a/Sources/Datadog/Logger.swift +++ b/Sources/Datadog/Logger.swift @@ -274,7 +274,6 @@ public class Logger: LoggerProtocol { internal var sendLogsToDatadog = true internal var consoleLogFormat: ConsoleLogFormat? = nil internal var datadogReportingThreshold: LogLevel = .debug - internal var samplingRate: Float? /// Sets the service name that will appear in logs. /// - Parameter serviceName: the service name (default value is set to application bundle identifier) diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift index f60a2b211e..4583be071b 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift @@ -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 diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift index 424c6c937f..f36095f6c3 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift @@ -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, diff --git a/Sources/Datadog/RUM/RUMTelemetry.swift b/Sources/Datadog/RUM/RUMTelemetry.swift index dcd1dadbdd..9cbf65156d 100644 --- a/Sources/Datadog/RUM/RUMTelemetry.swift +++ b/Sources/Datadog/RUM/RUMTelemetry.swift @@ -256,7 +256,7 @@ private extension FeaturesConfiguration { useBeforeSend: nil, useCrossSiteSessionCookie: nil, useExcludedActivityUrls: nil, - useFirstPartyHosts: !(self.rum?.firstPartyHosts.isEmpty ?? true), + useFirstPartyHosts: !(self.rum?.firstPartyHosts.hosts.isEmpty ?? true), useLocalEncryption: self.common.encryption != nil, useProxy: self.common.proxyConfiguration != nil, useSecureSessionCookie: nil, diff --git a/Sources/Datadog/Tracing/AutoInstrumentation/FirstPartyHosts.swift b/Sources/Datadog/Tracing/AutoInstrumentation/FirstPartyHosts.swift new file mode 100644 index 0000000000..7e50149e2f --- /dev/null +++ b/Sources/Datadog/Tracing/AutoInstrumentation/FirstPartyHosts.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 + +/// A struct that represents a dictionary of host names and tracing header types. +internal struct FirstPartyHosts: Equatable { + fileprivate var hostsWithTracingHeaderTypes: [String: Set] + + var hosts: Set { + return Set(hostsWithTracingHeaderTypes.keys) + } + + /// Creates a `FirstPartyHosts` instance with the given dictionary of host names and tracing header types. + /// + /// - Parameter hostsWithTracingHeaderTypes: The dictionary of host names and tracing header types. + internal init(_ hostsWithTracingHeaderTypes: [String: Set] = [:]) { + self.init(hostsWithTracingHeaderTypes: hostsWithTracingHeaderTypes) + } + + internal init( + hostsWithTracingHeaderTypes: [String: Set], + hostsSanitizer: HostsSanitizing = HostsSanitizer() + ) { + self.hostsWithTracingHeaderTypes = hostsSanitizer.sanitized( + hostsWithTracingHeaderTypes: hostsWithTracingHeaderTypes, + warningMessage: "The first party host with header types configured for Datadog SDK is not valid" + ) + } + + /// The function takes a `URL` and returns a `Set` of matching values. + /// If one than more match is found it will return union of matching values. + func tracingHeaderTypes(for url: URL?) -> Set { + return hostsWithTracingHeaderTypes.compactMap { item -> Set? in + let regex = "^(.*\\.)*\(NSRegularExpression.escapedPattern(for: item.key))$" + if url?.host?.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 { + 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) + } +} + +internal func += (left: inout FirstPartyHosts?, right: FirstPartyHosts) { + left = FirstPartyHosts( + left?.hostsWithTracingHeaderTypes.merging(right.hostsWithTracingHeaderTypes, uniquingKeysWith: { left, right in + left.union(right) + }) ?? right.hostsWithTracingHeaderTypes + ) +} diff --git a/Sources/Datadog/Tracing/AutoInstrumentation/TracingHeaderType.swift b/Sources/Datadog/Tracing/AutoInstrumentation/TracingHeaderType.swift index 7bfda361f2..a360c187ad 100644 --- a/Sources/Datadog/Tracing/AutoInstrumentation/TracingHeaderType.swift +++ b/Sources/Datadog/Tracing/AutoInstrumentation/TracingHeaderType.swift @@ -8,11 +8,13 @@ import Foundation /// The type of the tracing header injected to requests. /// -/// - `dd` - [Datadog's `x-datadog-*` header](https://docs.datadoghq.com/real_user_monitoring/connect_rum_and_traces/?tab=browserrum#how-are-rum-resources-linked-to-traces). -/// - `b3s` - Open Telemetry B3 [Single header](https://github.com/openzipkin/b3-propagation#single-headers). -/// - `b3m` - Open Telemetry B3 [Multiple headers](https://github.com/openzipkin/b3-propagation#multiple-headers). -public enum TracingHeaderType { - case dd - case b3s - case b3m +/// - `datadog` - [Datadog's `x-datadog-*` header](https://docs.datadoghq.com/real_user_monitoring/connect_rum_and_traces/?tab=browserrum#how-are-rum-resources-linked-to-traces). +/// - `b3` - Open Telemetry B3 [Single header](https://github.com/openzipkin/b3-propagation#single-headers). +/// - `b3multi` - Open Telemetry B3 [Multiple headers](https://github.com/openzipkin/b3-propagation#multiple-headers). +/// - `tracecontext` - W3C [Trace Context header](https://www.w3.org/TR/trace-context/#tracestate-header) +public enum TracingHeaderType: Hashable { + case datadog + case b3 + case b3multi + case tracecontext } diff --git a/Sources/Datadog/Tracing/Propagation/OpenTelemetry/OTelHTTPHeadersWriter.swift b/Sources/Datadog/Tracing/Propagation/OpenTelemetry/OTelHTTPHeadersWriter.swift index 01367f8afa..97d6973e43 100644 --- a/Sources/Datadog/Tracing/Propagation/OpenTelemetry/OTelHTTPHeadersWriter.swift +++ b/Sources/Datadog/Tracing/Propagation/OpenTelemetry/OTelHTTPHeadersWriter.swift @@ -7,7 +7,7 @@ import Foundation /// The `OTelHTTPHeadersWriter` should be used to inject trace propagation headers to -/// the network requests send to the backend instrumented with Datadog APM. +/// the network requests send to the backend instrumented with Open Telemetry. /// The injected headers conform to [Open Telemetry](https://github.com/openzipkin/b3-propagation) standard. /// /// Usage: @@ -40,7 +40,7 @@ public class OTelHTTPHeadersWriter: OTHTTPHeadersWriter, TracePropagationHeaders } /// A dictionary with HTTP Headers required to propagate the trace started in the mobile app - /// to the backend instrumented with Datadog APM. + /// to the backend instrumented with Open Telemetry. /// /// Usage: /// diff --git a/Sources/Datadog/Tracing/Propagation/W3C/W3CHTTPHeaders.swift b/Sources/Datadog/Tracing/Propagation/W3C/W3CHTTPHeaders.swift new file mode 100644 index 0000000000..8e56def0e3 --- /dev/null +++ b/Sources/Datadog/Tracing/Propagation/W3C/W3CHTTPHeaders.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 Foundation + +/// W3C trace context headers as explained in +/// https://www.w3.org/TR/trace-context/#traceparent-header +internal enum W3CHTTPHeaders { + /// The traceparent header represents the incoming request in a tracing system in a common format, understood by all vendors. + /// It's following a convention of `{version-format}-{trace-id}-{parent-id}-{trace-flags}`. + /// + /// Here’s an example of a traceparent header. + /// `traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01` + /// + /// **version-format** + /// + /// The following version-format definition is used for version 00. + /// + /// **trace-id** + /// + /// This is the ID of the whole trace forest and is used to uniquely identify a distributed trace through a system. + /// It is represented as a 16-byte array, for example, `4bf92f3577b34da6a3ce929d0e0e4736`. + /// All bytes as zero (`00000000000000000000000000000000`) is considered an invalid value. + /// + /// **parent-id** + /// + /// This is the ID of this request as known by the caller + /// (in some tracing systems, this is known as the span-id, where a span is the execution of a client request). + /// It is represented as an 8-byte array, for example, `00f067aa0ba902b7`. + /// All bytes as zero (`0000000000000000`) is considered an invalid value. + /// + /// **trace-flags** + /// + /// The current version of this specification only supports a single flag called sampled. + /// The sampled flag can be used to ensure that information about requests that were marked + /// for recording by the caller will also be recorded by SaaS service downstream so that the caller + /// can troubleshoot the behavior of every recorded request. + static let traceparent = "traceparent" + + enum Constants { + static let version = "00" + static let sampledValue = "01" + static let unsampledValue = "00" + static let separator = "-" + } +} diff --git a/Sources/Datadog/Tracing/Propagation/W3C/W3CHTTPHeadersReader.swift b/Sources/Datadog/Tracing/Propagation/W3C/W3CHTTPHeadersReader.swift new file mode 100644 index 0000000000..8a4456967a --- /dev/null +++ b/Sources/Datadog/Tracing/Propagation/W3C/W3CHTTPHeadersReader.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 + +internal class W3CHTTPHeadersReader: OTHTTPHeadersReader, TracePropagationHeadersExtractor { + private let httpHeaderFields: [String: String] + private var baggageItemQueue: DispatchQueue? + + init(httpHeaderFields: [String: String]) { + self.httpHeaderFields = httpHeaderFields + } + + func use(baggageItemQueue: DispatchQueue) { + self.baggageItemQueue = baggageItemQueue + } + + func extract() -> OTSpanContext? { + guard let baggageItemQueue = baggageItemQueue else { + return nil + } + + guard let traceparentValue = httpHeaderFields[W3CHTTPHeaders.traceparent]?.components( + separatedBy: W3CHTTPHeaders.Constants.separator + ), + let traceID = TracingUUID(traceparentValue[safe: 1], .hexadecimal), + let spanID = TracingUUID(traceparentValue[safe: 2], .hexadecimal), + traceparentValue[safe: 3] != W3CHTTPHeaders.Constants.unsampledValue else { + return nil + } + return DDSpanContext( + traceID: traceID, + spanID: spanID, + parentSpanID: nil, + baggageItems: BaggageItems(targetQueue: baggageItemQueue, parentSpanItems: nil) + ) + } +} diff --git a/Sources/Datadog/Tracing/Propagation/W3C/W3CHTTPHeadersWriter.swift b/Sources/Datadog/Tracing/Propagation/W3C/W3CHTTPHeadersWriter.swift new file mode 100644 index 0000000000..38af4b4ecd --- /dev/null +++ b/Sources/Datadog/Tracing/Propagation/W3C/W3CHTTPHeadersWriter.swift @@ -0,0 +1,83 @@ +/* + * 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 + +/// The `W3CHTTPHeadersWriter` should be used to inject trace propagation headers to +/// the network requests send to the backend instrumented with W3C trace context. +/// The injected headers conform to [W3C](https://www.w3.org/TR/trace-context/) standard. +/// +/// Usage: +/// +/// var request = URLRequest(...) +/// +/// let writer = W3CHTTPHeadersWriter() +/// let span = Global.sharedTracer.startSpan("network request") +/// writer.inject(spanContext: span.context) +/// +/// writer.tracePropagationHTTPHeaders.forEach { (field, value) in +/// request.setValue(value, forHTTPHeaderField: field) +/// } +/// +/// // call span.finish() when the request completes +/// +/// +public class W3CHTTPHeadersWriter: OTHTTPHeadersWriter, TracePropagationHeadersProvider { + /// A dictionary with HTTP Headers required to propagate the trace started in the mobile app + /// to the backend instrumented with W3C trace context. + /// + /// Usage: + /// + /// writer.tracePropagationHTTPHeaders.forEach { (field, value) in + /// request.setValue(value, forHTTPHeaderField: field) + /// } + /// + public private(set) var tracePropagationHTTPHeaders: [String: String] = [:] + + /// The tracing sampler. + /// + /// This value will decide of the `FLAG_SAMPLED` header field value + /// and if `trace-id`, `span-id` are propagated. + private let sampler: Sampler + + /// Creates a `W3CHTTPHeadersWriter` to inject traces propagation headers + /// to network request. + /// + /// - Parameter samplingRate: Tracing sampling rate. 20% by default. + public init( + samplingRate: Float = 20 + ) { + self.sampler = Sampler(samplingRate: samplingRate) + } + + /// Creates a `W3CHTTPHeadersWriter` to inject traces propagation headers + /// to network request. + /// + /// - Parameter sampler: Tracing sampler responsible for randomizing the sample. + internal init( + sampler: Sampler + ) { + self.sampler = sampler + } + + public func inject(spanContext: OTSpanContext) { + guard let spanContext = spanContext.dd else { + return + } + + let samplingPriority = sampler.sample() + + typealias Constants = W3CHTTPHeaders.Constants + + tracePropagationHTTPHeaders[W3CHTTPHeaders.traceparent] = [ + Constants.version, + spanContext.traceID.toString(.hexadecimal32Chars), + spanContext.spanID.toString(.hexadecimal16Chars), + samplingPriority ? Constants.sampledValue : Constants.unsampledValue + ] + .joined(separator: Constants.separator) + } +} diff --git a/Sources/Datadog/URLSessionAutoInstrumentation/DDURLSessionDelegate.swift b/Sources/Datadog/URLSessionAutoInstrumentation/DDURLSessionDelegate.swift index 7be01696c4..988073d72d 100644 --- a/Sources/Datadog/URLSessionAutoInstrumentation/DDURLSessionDelegate.swift +++ b/Sources/Datadog/URLSessionAutoInstrumentation/DDURLSessionDelegate.swift @@ -32,19 +32,29 @@ open class DDURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDat core().v1.feature(URLSessionAutoInstrumentation.self) } - let firstPartyURLsFilter: FirstPartyURLsFilter + let firstPartyHosts: FirstPartyHosts private let core: () -> DatadogCoreProtocol @objc override public init() { core = { defaultDatadogCore } - firstPartyURLsFilter = FirstPartyURLsFilter(hosts: []) + firstPartyHosts = .init() super.init() } public convenience init(in core: DatadogCoreProtocol) { - self.init(in: core, additionalFirstPartyHosts: []) + self.init(in: core, additionalFirstPartyHostsWithHeaderTypes: [:]) + } + + /// 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(additionalFirstPartyHostsWithHeaderTypes: [String: Set]) { + self.init(in: defaultDatadogCore, additionalFirstPartyHostsWithHeaderTypes: additionalFirstPartyHostsWithHeaderTypes) } /// Automatically tracked hosts can be customized per instance with this initializer. @@ -52,10 +62,12 @@ open class DDURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDat /// **NOTE:** If `trackURLSession(firstPartyHosts:)` is never called, automatic tracking will **not** take place. /// /// - Parameter additionalFirstPartyHosts: these hosts are tracked **in addition to** what was - /// passed to `DatadogConfiguration.Builder` via `trackURLSession(firstPartyHosts:)` + /// passed to `DatadogConfiguration.Builder` via `trackURLSession(firstPartyHosts:)` @objc public convenience init(additionalFirstPartyHosts: Set) { - self.init(in: defaultDatadogCore, additionalFirstPartyHosts: additionalFirstPartyHosts) + self.init(additionalFirstPartyHostsWithHeaderTypes: additionalFirstPartyHosts.reduce(into: [:], { partialResult, host in + partialResult[host] = [.datadog] + })) } /// Automatically tracked hosts can be customized per instance with this initializer. @@ -64,9 +76,12 @@ open class DDURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDat /// - core: Datadog SDK core. /// - additionalFirstPartyHosts: additionalFirstPartyHosts: these hosts are tracked **in addition to** what was /// passed to `DatadogConfiguration.Builder` via `trackURLSession(firstPartyHosts:)` - public init(in core: @autoclosure @escaping () -> DatadogCoreProtocol, additionalFirstPartyHosts: Set) { + public init( + in core: @autoclosure @escaping () -> DatadogCoreProtocol, + additionalFirstPartyHostsWithHeaderTypes: [String: Set] + ) { self.core = core - self.firstPartyURLsFilter = FirstPartyURLsFilter(hosts: additionalFirstPartyHosts) + self.firstPartyHosts = FirstPartyHosts(additionalFirstPartyHostsWithHeaderTypes) super.init() } diff --git a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilter.swift b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilter.swift index 93930802b5..5826fde303 100644 --- a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilter.swift +++ b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilter.swift @@ -8,39 +8,26 @@ import Foundation /// Filters `URLs` which match the first party hosts given by the user. internal struct FirstPartyURLsFilter { - /// A regexp for matching hosts, e.g. when `hosts` is "example.com", it will match - /// "example.com", "api.example.com", but not "foo.com". - private let regex: String? + private let tracingHeaderTypesProvider: TracingHeaderTypesProvider - init(hosts: Set) { - if hosts.isEmpty { - self.regex = nil - } else { - // pattern = "^(.*\\.)*tracedHost1$|tracedHost2$|...$" - let escapedHosts = hosts - .map { "\(NSRegularExpression.escapedPattern(for: $0))$" } - .joined(separator: "|") - self.regex = "^(.*\\.)*\(escapedHosts)" - } + internal init(hosts: FirstPartyHosts) { + self.tracingHeaderTypesProvider = TracingHeaderTypesProvider(firstPartyHosts: hosts) } /// Returns `true` if given `URL` matches the first party hosts defined by the user; `false` otherwise. func isFirstParty(url: URL?) -> Bool { - guard let regex = self.regex, - let host = url?.host else { + guard let host = url?.host, let url = URL(string: host) else { return false } - return host.range(of: regex, options: .regularExpression) != nil + return !tracingHeaderTypesProvider.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), - let regex = self.regex, - let host = url.host else { + guard let url = URL(string: string) else { return false } - return host.range(of: regex, options: .regularExpression) != nil + return isFirstParty(url: url) } } diff --git a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift index 7399c27ae1..7737f3c36f 100644 --- a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift +++ b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift @@ -32,8 +32,8 @@ 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 /// Handles resources interception. @@ -48,8 +48,6 @@ public class URLSessionInterceptor: URLSessionInterceptorType { /// Tracing sampler used to sample traces generated by the SDK or RUM BE. internal let tracingSampler: Sampler - internal let tracingHeaderTypes: Set - // MARK: - Initialization convenience init( @@ -79,11 +77,10 @@ public class URLSessionInterceptor: URLSessionInterceptorType { configuration: FeaturesConfiguration.URLSessionAutoInstrumentation, handler: URLSessionInterceptionHandler ) { - self.defaultFirstPartyURLsFilter = FirstPartyURLsFilter(hosts: configuration.userDefinedFirstPartyHosts) + self.firstPartyHosts = configuration.userDefinedFirstPartyHosts self.internalURLsFilter = InternalURLsFilter(urls: configuration.sdkInternalURLs) self.handler = handler self.tracingSampler = configuration.tracingSampler - self.tracingHeaderTypes = configuration.tracingHeaderTypes if configuration.instrumentTracing { self.injectTracingHeadersToFirstPartyRequests = true @@ -122,7 +119,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType { } let isFirstPartyRequest = isFirstParty(request: request, for: session) if injectTracingHeadersToFirstPartyRequests && isFirstPartyRequest { - return injectSpanContext(into: request) + return injectSpanContext(into: request, session: session) } return request } @@ -143,7 +140,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType { ) self.interceptionByTask[task] = interception - if let spanContext = self.extractSpanContext(from: request) { + if let spanContext = self.extractSpanContext(from: request, session: session) { interception.register(spanContext: spanContext) } @@ -223,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) { @@ -237,7 +233,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType { // MARK: - SpanContext Injection & Extraction - private func injectSpanContext(into firstPartyRequest: URLRequest) -> URLRequest { + private func injectSpanContext(into firstPartyRequest: URLRequest, session: URLSession?) -> URLRequest { guard let tracer = Global.sharedTracer as? Tracer else { return firstPartyRequest } @@ -245,21 +241,27 @@ public class URLSessionInterceptor: URLSessionInterceptorType { let spanContext = tracer.createSpanContext() var newRequest = firstPartyRequest - tracingHeaderTypes.forEach { + let additionalFirstPartyHostsTracingHeaderTypes = (session?.delegate as? DDURLSessionDelegate)? + .firstPartyHosts.tracingHeaderTypes(for: newRequest.url) ?? .init() + let tracingHeaderTypes = firstPartyHosts.tracingHeaderTypes(for: newRequest.url) + + tracingHeaderTypes.union(additionalFirstPartyHostsTracingHeaderTypes).forEach { let writer: TracePropagationHeadersProvider & OTFormatWriter switch $0 { - case .dd: + case .datadog: writer = HTTPHeadersWriter(sampler: tracingSampler) - case .b3s: + case .b3: writer = OTelHTTPHeadersWriter( sampler: tracingSampler, injectEncoding: .single ) - case .b3m: + case .b3multi: writer = OTelHTTPHeadersWriter( sampler: tracingSampler, injectEncoding: .multiple ) + case .tracecontext: + writer = W3CHTTPHeadersWriter(sampler: tracingSampler) } tracer.inject(spanContext: spanContext, writer: writer) @@ -274,13 +276,25 @@ public class URLSessionInterceptor: URLSessionInterceptorType { return newRequest } - private func extractSpanContext(from request: URLRequest) -> DDSpanContext? { + private func extractSpanContext(from request: URLRequest, session: URLSession?) -> DDSpanContext? { guard let tracer = Global.sharedTracer as? Tracer, let headers = request.allHTTPHeaderFields else { return nil } - let reader = HTTPHeadersReader(httpHeaderFields: headers) + let additionalFirstPartyHostsTracingHeaderTypes = (session?.delegate as? DDURLSessionDelegate)? + .firstPartyHosts.tracingHeaderTypes(for: request.url) ?? .init() + let tracingHeaderTypes = firstPartyHosts.tracingHeaderTypes(for: request.url) + .union(additionalFirstPartyHostsTracingHeaderTypes) + + let reader: OTFormatReader + if tracingHeaderTypes.contains(.datadog) { + reader = HTTPHeadersReader(httpHeaderFields: headers) + } else if tracingHeaderTypes.contains(.b3) || tracingHeaderTypes.contains(.b3multi) { + reader = OTelHTTPHeadersReader(httpHeaderFields: headers) + } else { + reader = W3CHTTPHeadersReader(httpHeaderFields: headers) + } return tracer.extract(reader: reader) as? DDSpanContext } } diff --git a/Sources/DatadogObjc/DDURLSessionDelegate+objc.swift b/Sources/DatadogObjc/DDURLSessionDelegate+objc.swift index f421630962..c15cb5fd83 100644 --- a/Sources/DatadogObjc/DDURLSessionDelegate+objc.swift +++ b/Sources/DatadogObjc/DDURLSessionDelegate+objc.swift @@ -19,6 +19,15 @@ open class DDNSURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionD swiftDelegate = DDURLSessionDelegate() } + @objc + public init(additionalFirstPartyHostsWithHeaderTypes: [String: Set]) { + swiftDelegate = DDURLSessionDelegate( + additionalFirstPartyHostsWithHeaderTypes: additionalFirstPartyHostsWithHeaderTypes.mapValues { tracingHeaderTypes in + return Set(tracingHeaderTypes.map { $0.swiftType }) + } + ) + } + @objc public init(additionalFirstPartyHosts: Set) { swiftDelegate = DDURLSessionDelegate(additionalFirstPartyHosts: additionalFirstPartyHosts) diff --git a/Sources/DatadogObjc/DatadogConfiguration+objc.swift b/Sources/DatadogObjc/DatadogConfiguration+objc.swift index dd9bae5b6e..ebb11020c3 100644 --- a/Sources/DatadogObjc/DatadogConfiguration+objc.swift +++ b/Sources/DatadogObjc/DatadogConfiguration+objc.swift @@ -167,9 +167,10 @@ public class DDTracingHeaderType: NSObject { self.swiftType = swiftType } - @objc public static let dd = DDTracingHeaderType(.dd) - @objc public static let b3m = DDTracingHeaderType(.b3m) - @objc public static let b3s = DDTracingHeaderType(.b3s) + @objc public static let datadog = DDTracingHeaderType(.datadog) + @objc public static let b3multi = DDTracingHeaderType(.b3multi) + @objc public static let b3 = DDTracingHeaderType(.b3) + @objc public static let tracecontext = DDTracingHeaderType(.tracecontext) } @objc @@ -339,6 +340,13 @@ public class DDConfigurationBuilder: NSObject { _ = sdkBuilder.trackURLSession(firstPartyHosts: firstPartyHosts) } + @objc + public func trackURLSession(firstPartyHostsWithHeaderTypes: [String: Set]) { + _ = sdkBuilder.trackURLSession(firstPartyHostsWithHeaderTypes: firstPartyHostsWithHeaderTypes.mapValues { tracingHeaderTypes in + return Set(tracingHeaderTypes.map { $0.swiftType }) + }) + } + @objc public func set(serviceName: String) { _ = sdkBuilder.set(serviceName: serviceName) diff --git a/Sources/DatadogObjc/Tracer+objc.swift b/Sources/DatadogObjc/Tracer+objc.swift index 8c1876f023..68041b17d1 100644 --- a/Sources/DatadogObjc/Tracer+objc.swift +++ b/Sources/DatadogObjc/Tracer+objc.swift @@ -123,6 +123,14 @@ public class DDTracer: NSObject, DatadogObjc.OTTracer { spanContext: ddspanContext.swiftSpanContext, writer: objcWriter.swiftOTelHTTPHeadersWriter ) + } else if let objcWriter = carrier as? DDW3CHTTPHeadersWriter, format == OT.formatTextMap { + guard let ddspanContext = spanContext.dd else { + return + } + swiftTracer.inject( + spanContext: ddspanContext.swiftSpanContext, + writer: objcWriter.swiftW3CHTTPHeadersWriter + ) } else { let error = NSError( domain: "DDTracer", diff --git a/Sources/DatadogObjc/Tracing/Propagation/W3CHTTPHeadersWriter+objc.swift b/Sources/DatadogObjc/Tracing/Propagation/W3CHTTPHeadersWriter+objc.swift new file mode 100644 index 0000000000..dce732ea5a --- /dev/null +++ b/Sources/DatadogObjc/Tracing/Propagation/W3CHTTPHeadersWriter+objc.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 +import class Datadog.W3CHTTPHeadersWriter + +@objc +public class DDW3CHTTPHeadersWriter: NSObject { + let swiftW3CHTTPHeadersWriter: W3CHTTPHeadersWriter + + @objc public var tracePropagationHTTPHeaders: [String: String] { + swiftW3CHTTPHeadersWriter.tracePropagationHTTPHeaders + } + + @objc + public init(samplingRate: Float = 20) { + swiftW3CHTTPHeadersWriter = W3CHTTPHeadersWriter(samplingRate: samplingRate) + } +} diff --git a/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift b/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift index 11e0232987..1e0077bd5b 100644 --- a/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift +++ b/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift @@ -562,19 +562,6 @@ class FeaturesConfigurationTests: XCTestCase { XCTAssertEqual(custom.logging?.remoteLoggingSampler.samplingRate, 12.34) } - func testTracingHeaderType() throws { - let custom = try FeaturesConfiguration( - configuration: .mockWith( - tracingEnabled: true, - firstPartyHosts: .init(), - tracingHeaderTypes: .init(arrayLiteral: .dd) - ), - appContext: .mockAny() - ) - XCTAssertEqual(custom.urlSessionAutoInstrumentation?.tracingHeaderTypes.count, 1) - XCTAssertEqual(custom.urlSessionAutoInstrumentation?.tracingHeaderTypes.first, .dd) - } - // MARK: - URLSession Auto Instrumentation Configuration Tests func testURLSessionAutoInstrumentationConfiguration() throws { @@ -583,7 +570,10 @@ class FeaturesConfigurationTests: XCTestCase { let randomCustomTracesEndpoint: URL? = Bool.random() ? .mockRandom() : nil let randomCustomRUMEndpoint: URL? = Bool.random() ? .mockRandom() : nil - let firstPartyHosts: Set = ["example.com", "foo.eu"] + let firstPartyHosts: FirstPartyHosts = .init([ + "example.com": [.datadog], + "foo.eu": [.datadog] + ]) let expectedSDKInternalURLs: Set = [ randomCustomLogsEndpoint?.absoluteString ?? randomDatadogEndpoint.logsEndpoint.url, randomCustomTracesEndpoint?.absoluteString ?? randomDatadogEndpoint.tracesEndpoint.url, @@ -593,7 +583,7 @@ class FeaturesConfigurationTests: XCTestCase { func createConfiguration( tracingEnabled: Bool, rumEnabled: Bool, - firstPartyHosts: Set? + firstPartyHosts: FirstPartyHosts? ) throws -> FeaturesConfiguration { try FeaturesConfiguration( configuration: .mockWith( @@ -657,7 +647,7 @@ class FeaturesConfigurationTests: XCTestCase { configuration = try createConfiguration( tracingEnabled: true, rumEnabled: true, - firstPartyHosts: [] + firstPartyHosts: .init() ) XCTAssertNotNil( configuration.urlSessionAutoInstrumentation, @@ -671,7 +661,7 @@ class FeaturesConfigurationTests: XCTestCase { configuration: .mockWith( tracingEnabled: .random(), rumEnabled: true, - firstPartyHosts: ["foo.com"], + firstPartyHosts: .init(["foo.com": [.datadog]]), rumResourceAttributesProvider: { _, _, _, _ in [:] } ), appContext: .mockAny() @@ -680,7 +670,7 @@ class FeaturesConfigurationTests: XCTestCase { configuration: .mockWith( tracingEnabled: .random(), rumEnabled: true, - firstPartyHosts: ["foo.com"], + firstPartyHosts: .init(["foo.com": [.datadog]]), rumResourceAttributesProvider: nil ), appContext: .mockAny() @@ -755,7 +745,7 @@ class FeaturesConfigurationTests: XCTestCase { defer { consolePrint = { print($0) } } // Given - let firstPartyHosts: Set = ["first-party.com"] + let firstPartyHosts: FirstPartyHosts = .init(["first-party.com": [.datadog]]) // When let tracingEnabled = false @@ -763,7 +753,11 @@ class FeaturesConfigurationTests: XCTestCase { // Then let configuration = try FeaturesConfiguration( - configuration: .mockWith(tracingEnabled: tracingEnabled, rumEnabled: rumEnabled, firstPartyHosts: firstPartyHosts), + configuration: .mockWith( + tracingEnabled: tracingEnabled, + rumEnabled: rumEnabled, + firstPartyHosts: firstPartyHosts + ), appContext: .mockAny() ) @@ -782,25 +776,27 @@ class FeaturesConfigurationTests: XCTestCase { } func testWhenFirstPartyHostsAreProvided_itPassesThemToSanitizer() throws { - // When - let firstPartyHosts: Set = [ - "https://first-party.com", - "http://api.first-party.com", - "https://first-party.com/v2/api" - ] - - // Then + // Given let mockHostsSanitizer = MockHostsSanitizer() + let firstPartyHosts = FirstPartyHosts( + hostsWithTracingHeaderTypes: [ + "https://first-party.com": [.datadog], + "http://api.first-party.com": [.datadog], + "https://first-party.com/v2/api": [.datadog] + ], + hostsSanitizer: mockHostsSanitizer + ) + + // When _ = try FeaturesConfiguration( configuration: .mockWith(rumEnabled: true, firstPartyHosts: firstPartyHosts), - appContext: .mockAny(), - hostsSanitizer: mockHostsSanitizer + appContext: .mockAny() ) XCTAssertEqual(mockHostsSanitizer.sanitizations.count, 1) let sanitization = try XCTUnwrap(mockHostsSanitizer.sanitizations.first) - XCTAssertEqual(sanitization.hosts, firstPartyHosts) - XCTAssertEqual(sanitization.warningMessage, "The first party host configured for Datadog SDK is not valid") + XCTAssertEqual(sanitization.hosts, firstPartyHosts.hosts) + XCTAssertEqual(sanitization.warningMessage, "The first party host with header types configured for Datadog SDK is not valid") } // MARK: - Helpers diff --git a/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift b/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift index 80279df7d6..bbfc5b35fa 100644 --- a/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift +++ b/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift @@ -66,8 +66,6 @@ class DatadogConfigurationBuilderTests: XCTestCase { XCTAssertEqual(configuration.additionalConfiguration.count, 0) XCTAssertNil(configuration.encryption) XCTAssertNil(configuration.serverDateProvider) - XCTAssertEqual(configuration.tracingHeaderTypes.count, 1) - XCTAssertEqual(configuration.tracingHeaderTypes.first, .dd) } } @@ -99,6 +97,7 @@ class DatadogConfigurationBuilderTests: XCTestCase { .set(loggingSamplingRate: 66) .set(tracingSamplingRate: 75) .trackURLSession(firstPartyHosts: ["example.com"]) + .trackURLSession(firstPartyHostsWithHeaderTypes: ["example2.com": [.b3]]) .trackUIKitRUMViews(using: UIKitRUMViewsPredicateMock()) .trackUIKitRUMActions(using: UIKitRUMUserActionsPredicateMock()) .trackRUMLongTasks(threshold: 100.0) @@ -156,7 +155,7 @@ class DatadogConfigurationBuilderTests: XCTestCase { XCTAssertEqual(configuration.customLogsEndpoint, URL(string: "https://api.custom.logs/")!) XCTAssertEqual(configuration.customTracesEndpoint, URL(string: "https://api.custom.traces/")!) XCTAssertEqual(configuration.customRUMEndpoint, URL(string: "https://api.custom.rum/")!) - XCTAssertEqual(configuration.firstPartyHosts, ["example.com"]) + XCTAssertEqual(configuration.firstPartyHosts, .init(["example.com": [.datadog], "example2.com": [.b3]])) XCTAssertEqual(configuration.loggingSamplingRate, 66) XCTAssertEqual(configuration.tracingSamplingRate, 75) XCTAssertEqual(configuration.rumSessionsSamplingRate, 42.5) @@ -186,8 +185,6 @@ class DatadogConfigurationBuilderTests: XCTestCase { XCTAssertEqual(configuration.proxyConfiguration?[kCFProxyPasswordKey] as? String, "proxypass") XCTAssertTrue(configuration.encryption is DataEncryptionMock) XCTAssertTrue(configuration.serverDateProvider is ServerDateProviderMock) - XCTAssertEqual(configuration.tracingHeaderTypes.count, 1) - XCTAssertEqual(configuration.tracingHeaderTypes.first, .dd) // Aync mapper: configuration.logEventMapper?.map(event: .mockRandom()) { event in @@ -210,7 +207,7 @@ class DatadogConfigurationBuilderTests: XCTestCase { let configuration = builder.build() - XCTAssertEqual(configuration.firstPartyHosts, ["example.com"]) + XCTAssertEqual(configuration.firstPartyHosts, .init(["example.com": [.datadog]])) XCTAssertEqual(configuration.logsEndpoint, .eu1) XCTAssertEqual(configuration.tracesEndpoint, .eu1) XCTAssertEqual(configuration.rumEndpoint, .eu1) diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 829a0bf958..7e781a267a 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -49,7 +49,7 @@ extension Datadog.Configuration { tracesEndpoint: TracesEndpoint = .us1, rumEndpoint: RUMEndpoint = .us1, serviceName: String? = .mockAny(), - firstPartyHosts: Set? = nil, + firstPartyHosts: FirstPartyHosts? = nil, loggingSamplingRate: Float = 100.0, tracingSamplingRate: Float = 100.0, rumSessionsSamplingRate: Float = 100.0, @@ -65,8 +65,7 @@ extension Datadog.Configuration { uploadFrequency: UploadFrequency = .average, additionalConfiguration: [String: Any] = [:], proxyConfiguration: [AnyHashable: Any]? = nil, - internalMonitoringClientToken: String? = nil, - tracingHeaderTypes: Set = .init(arrayLiteral: .dd) + internalMonitoringClientToken: String? = nil ) -> Datadog.Configuration { return Datadog.Configuration( rumApplicationID: rumApplicationID, @@ -99,8 +98,7 @@ extension Datadog.Configuration { batchSize: batchSize, uploadFrequency: uploadFrequency, additionalConfiguration: additionalConfiguration, - proxyConfiguration: proxyConfiguration, - tracingHeaderTypes: tracingHeaderTypes + proxyConfiguration: proxyConfiguration ) } } @@ -295,7 +293,7 @@ extension FeaturesConfiguration.RUM { backgroundEventTrackingEnabled: Bool = false, frustrationTrackingEnabled: Bool = true, onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListener(), - firstPartyHosts: Set = [], + firstPartyHosts: FirstPartyHosts = .init(), vitalsFrequency: TimeInterval? = 0.5, dateProvider: DateProvider = SystemDateProvider() ) -> Self { @@ -339,13 +337,12 @@ extension FeaturesConfiguration.URLSessionAutoInstrumentation { static func mockAny() -> Self { mockWith() } static func mockWith( - userDefinedFirstPartyHosts: Set = [], + userDefinedFirstPartyHosts: FirstPartyHosts = .init(), sdkInternalURLs: Set = [], rumAttributesProvider: URLSessionRUMAttributesProvider? = nil, instrumentTracing: Bool = true, instrumentRUM: Bool = true, - tracingSampler: Sampler = .mockKeepAll(), - tracingHeaderTypes: Set = .init(arrayLiteral: .dd) + tracingSampler: Sampler = .mockKeepAll() ) -> Self { return .init( userDefinedFirstPartyHosts: userDefinedFirstPartyHosts, @@ -353,8 +350,7 @@ extension FeaturesConfiguration.URLSessionAutoInstrumentation { rumAttributesProvider: rumAttributesProvider, instrumentTracing: instrumentTracing, instrumentRUM: instrumentRUM, - tracingSampler: tracingSampler, - tracingHeaderTypes: tracingHeaderTypes + tracingSampler: tracingSampler ) } } @@ -1116,6 +1112,14 @@ class MockHostsSanitizer: HostsSanitizing { sanitizations.append((hosts: hosts, warningMessage: warningMessage)) return hosts } + + func sanitized( + hostsWithTracingHeaderTypes: [String: Set], + warningMessage: String + ) -> [String: Set] { + sanitizations.append((hosts: Set(hostsWithTracingHeaderTypes.keys), warningMessage: warningMessage)) + return hostsWithTracingHeaderTypes + } } // MARK: - Global Dependencies Mocks diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index 0c7173dca6..e5e1f2968e 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -649,7 +649,7 @@ extension RUMScopeDependencies { sessionSampler: Sampler = .mockKeepAll(), backgroundEventTrackingEnabled: Bool = .mockAny(), frustrationTrackingEnabled: Bool = true, - firstPartyURLsFilter: FirstPartyURLsFilter = FirstPartyURLsFilter(hosts: []), + firstPartyHosts: FirstPartyHosts = .init([:]), eventBuilder: RUMEventBuilder = RUMEventBuilder(eventsMapper: .mockNoOp()), rumUUIDGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), ciTest: RUMCITest? = nil, @@ -663,7 +663,7 @@ extension RUMScopeDependencies { sessionSampler: sessionSampler, backgroundEventTrackingEnabled: backgroundEventTrackingEnabled, frustrationTrackingEnabled: frustrationTrackingEnabled, - firstPartyURLsFilter: firstPartyURLsFilter, + firstPartyHosts: firstPartyHosts, eventBuilder: eventBuilder, rumUUIDGenerator: rumUUIDGenerator, ciTest: ciTest, @@ -679,7 +679,7 @@ extension RUMScopeDependencies { sessionSampler: Sampler? = nil, backgroundEventTrackingEnabled: Bool? = nil, frustrationTrackingEnabled: Bool? = nil, - firstPartyUrls: Set? = nil, + firstPartyHosts: FirstPartyHosts? = nil, eventBuilder: RUMEventBuilder? = nil, rumUUIDGenerator: RUMUUIDGenerator? = nil, ciTest: RUMCITest? = nil, @@ -693,7 +693,7 @@ extension RUMScopeDependencies { sessionSampler: sessionSampler ?? self.sessionSampler, backgroundEventTrackingEnabled: backgroundEventTrackingEnabled ?? self.backgroundEventTrackingEnabled, frustrationTrackingEnabled: frustrationTrackingEnabled ?? self.frustrationTrackingEnabled, - firstPartyURLsFilter: firstPartyUrls.map { .init(hosts: $0) } ?? self.firstPartyURLsFilter, + firstPartyHosts: firstPartyHosts ?? self.firstPartyHosts, eventBuilder: eventBuilder ?? self.eventBuilder, rumUUIDGenerator: rumUUIDGenerator ?? self.rumUUIDGenerator, ciTest: ciTest ?? self.ciTest, diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift index 45c6663f39..58b57bf993 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift @@ -17,7 +17,7 @@ class RUMResourceScopeTests: XCTestCase { ) private let dependencies: RUMScopeDependencies = .mockWith( - firstPartyURLsFilter: FirstPartyURLsFilter(hosts: ["firstparty.com"]) + firstPartyHosts: FirstPartyHosts(["firstparty.com": [.datadog]]) ) private let rumContext = RUMContext.mockWith( diff --git a/Tests/DatadogTests/Datadog/RUMMonitorTests.swift b/Tests/DatadogTests/Datadog/RUMMonitorTests.swift index 59e6d82185..f9bad1c52d 100644 --- a/Tests/DatadogTests/Datadog/RUMMonitorTests.swift +++ b/Tests/DatadogTests/Datadog/RUMMonitorTests.swift @@ -292,7 +292,7 @@ class RUMMonitorTests: XCTestCase { let rum: RUMFeature = .mockByRecordingRUMEventMatchers( configuration: .mockWith( // .mockRandom always uses foo.com - firstPartyHosts: ["foo.com"] + firstPartyHosts: .init(["foo.com": [.datadog]]) ) ) core.register(feature: rum) @@ -316,7 +316,7 @@ class RUMMonitorTests: XCTestCase { func testLoadingResourceWithURLString_thenMarksFirstPartyURLs() throws { let rum: RUMFeature = .mockByRecordingRUMEventMatchers( configuration: .mockWith( - firstPartyHosts: ["foo.com"] + firstPartyHosts: .init(["foo.com": [.datadog]]) ) ) core.register(feature: rum) diff --git a/Tests/DatadogTests/Datadog/TracerTests.swift b/Tests/DatadogTests/Datadog/TracerTests.swift index 9a8a1aa8e6..c9cbf4cb28 100644 --- a/Tests/DatadogTests/Datadog/TracerTests.swift +++ b/Tests/DatadogTests/Datadog/TracerTests.swift @@ -832,6 +832,60 @@ class TracerTests: XCTestCase { XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders) } + func testItInjectsSpanContextWithW3CHTTPHeadersWriter() { + let tracer: Tracer = .mockAny(in: PassthroughCoreMock()) + let spanContext1 = DDSpanContext(traceID: 1, spanID: 2, parentSpanID: 3, baggageItems: .mockAny()) + let spanContext2 = DDSpanContext(traceID: 4, spanID: 5, parentSpanID: 6, baggageItems: .mockAny()) + let spanContext3 = DDSpanContext(traceID: 77, spanID: 88, parentSpanID: nil, baggageItems: .mockAny()) + + let httpHeadersWriter = W3CHTTPHeadersWriter(sampler: .mockKeepAll()) + XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, [:]) + + // When + tracer.inject(spanContext: spanContext1, writer: httpHeadersWriter) + + // Then + let expectedHTTPHeaders1 = [ + "traceparent": "00-00000000000000000000000000000001-0000000000000002-01" + ] + XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders1) + + // When + tracer.inject(spanContext: spanContext2, writer: httpHeadersWriter) + + // Then + let expectedHTTPHeaders2 = [ + "traceparent": "00-00000000000000000000000000000004-0000000000000005-01" + ] + XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders2) + + // When + tracer.inject(spanContext: spanContext3, writer: httpHeadersWriter) + + // Then + let expectedHTTPHeaders3 = [ + "traceparent": "00-0000000000000000000000000000004d-0000000000000058-01" + ] + XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders3) + } + + func testItInjectsRejectedSpanContextWithW3CHTTPHeadersWriter() { + let tracer: Tracer = .mockAny(in: PassthroughCoreMock()) + let spanContext = DDSpanContext(traceID: 1, spanID: 2, parentSpanID: .mockAny(), baggageItems: .mockAny()) + + let httpHeadersWriter = W3CHTTPHeadersWriter(sampler: .mockRejectAll()) + XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, [:]) + + // When + tracer.inject(spanContext: spanContext, writer: httpHeadersWriter) + + // Then + let expectedHTTPHeaders = [ + "traceparent": "00-00000000000000000000000000000001-0000000000000002-00" + ] + XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders) + } + func testItInjectsRejectedSpanContextWithHTTPHeadersWriter() { let tracer: Tracer = .mockAny(in: PassthroughCoreMock()) let spanContext = DDSpanContext(traceID: 1, spanID: 2, parentSpanID: .mockAny(), baggageItems: .mockAny()) @@ -900,6 +954,23 @@ class TracerTests: XCTestCase { XCTAssertEqual(extractedSpanContext?.dd.parentSpanID, injectedSpanContext.dd.parentSpanID) } + func testItExtractsSpanContextWithW3CHTTPHeadersReader() { + let tracer: Tracer = .mockAny(in: PassthroughCoreMock()) + let injectedSpanContext = DDSpanContext(traceID: 1, spanID: 2, parentSpanID: 3, baggageItems: .mockAny()) + + let httpHeadersWriter = W3CHTTPHeadersWriter(sampler: .mockKeepAll()) + tracer.inject(spanContext: injectedSpanContext, writer: httpHeadersWriter) + + let httpHeadersReader = W3CHTTPHeadersReader( + httpHeaderFields: httpHeadersWriter.tracePropagationHTTPHeaders + ) + let extractedSpanContext = tracer.extract(reader: httpHeadersReader) + + XCTAssertEqual(extractedSpanContext?.dd.traceID, injectedSpanContext.dd.traceID) + XCTAssertEqual(extractedSpanContext?.dd.spanID, injectedSpanContext.dd.spanID) + XCTAssertNil(extractedSpanContext?.dd.parentSpanID) + } + // MARK: - Span Dates Correction func testGivenTimeDifferenceBetweenDeviceAndServer_whenCollectingSpans_thenSpanDateUsesServerTime() throws { diff --git a/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/FirstPartyHostsTests.swift b/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/FirstPartyHostsTests.swift new file mode 100644 index 0000000000..302566da63 --- /dev/null +++ b/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/FirstPartyHostsTests.swift @@ -0,0 +1,146 @@ +/* + * 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 Datadog + +class FirstPartyHostsTests: XCTestCase { + let hostsDictionary: [String: Set] = [ + "http://first-party.com/": [.tracecontext, .b3], + "https://first-party.com/": [.tracecontext, .b3], + "https://api.first-party.com/v2/users": [.tracecontext, .b3], + "https://www.first-party.com/": [.tracecontext, .b3], + "https://login:p4ssw0rd@first-party.com:999/": [.tracecontext, .b3], + "http://any-domain.eu/": [.tracecontext, .b3], + "https://any-domain.eu/": [.tracecontext, .b3], + "https://api.any-domain.eu/v2/users": [.tracecontext, .b3], + "https://www.any-domain.eu/": [.tracecontext, .b3], + "https://login:p4ssw0rd@www.any-domain.eu:999/": [.tracecontext, .b3], + "https://api.any-domain.org.eu/": [.tracecontext, .b3], + ] + + let otherHosts = [ + "http://third-party.com/", + "https://third-party.com/", + "https://api.third-party.com/v2/users", + "https://www.third-party.com/", + "https://login:p4ssw0rd@third-party.com:999/", + "http://any-domain.org/", + "https://any-domain.org/", + "https://api.any-domain.org/v2/users", + "https://www.any-domain.org/", + "https://login:p4ssw0rd@www.any-domain.org:999/", + "https://api.any-domain.eu.org/", + ] + + func testGivenEmptyDictionary_itReturnsDefaultTracingHeaderTypes() { + let headerTypesProvider = FirstPartyHosts() + (hostsDictionary.keys + otherHosts).forEach { fixture in + let url = URL(string: fixture) + XCTAssertEqual(headerTypesProvider.tracingHeaderTypes(for: url), .init()) + } + XCTAssertTrue(headerTypesProvider.hosts.isEmpty) + } + + func testGivenEmptyTracingHeaderTypes_itReturnsNoTracingHeaderTypes() { + let headerTypesProvider = FirstPartyHosts( + ["http://first-party.com/": .init()] + ) + XCTAssertEqual(headerTypesProvider.tracingHeaderTypes(for: URL(string: "http://first-party.com/")), .init()) + } + + func testGivenValidDictionary_itReturnsTracingHeaderTypes_forSubdomainURL() { + let firstPartyHosts = FirstPartyHosts([ + "first-party.com": .init([.b3multi]), + "example.com": [.datadog, .b3multi], + "subdomain.example.com": [.tracecontext], + "otherdomain.com": [.b3] + ]) + + XCTAssertEqual(firstPartyHosts.tracingHeaderTypes(for: URL(string: "http://example.com/path1")), [.datadog, .b3multi]) + XCTAssertEqual(firstPartyHosts.tracingHeaderTypes(for: URL(string: "https://subdomain.example.com/path2")), [.tracecontext, .datadog, .b3multi]) + XCTAssertEqual(firstPartyHosts.tracingHeaderTypes(for: URL(string: "http://otherdomain.com/path3")), [.b3]) + XCTAssertEqual(firstPartyHosts.tracingHeaderTypes(for: URL(string: "https://somedomain.com/path4")), []) + XCTAssertEqual(firstPartyHosts.tracingHeaderTypes(for: URL(string: "http://api.first-party.com")), [.b3multi]) + XCTAssertEqual(firstPartyHosts.tracingHeaderTypes(for: URL(string: "http://apifirst-party.com")), []) + XCTAssertEqual(firstPartyHosts.tracingHeaderTypes(for: URL(string: "https://api.first-party.com/v1/endpoint")), [.b3multi]) + } + + func testGivenValidDictionary_itReturnsCorrectTracingHeaderTypes() { + let headerTypesProvider = FirstPartyHosts(hostsDictionary) + hostsDictionary.keys.forEach { fixture in + let url = URL(string: fixture) + XCTAssertEqual(headerTypesProvider.tracingHeaderTypes(for: url), [.tracecontext, .b3]) + } + otherHosts.forEach { fixture in + let url = URL(string: fixture) + XCTAssertEqual(headerTypesProvider.tracingHeaderTypes(for: url), .init()) + } + } + + func testFalsePositiveURL_itReturnsEmptyTracingHeaderTypes() { + let filter = FirstPartyHosts( + hostsWithTracingHeaderTypes: ["example.com": [.datadog, .b3multi]] + ) + let url = URL(string: "http://foo.com/something.example.com") + + XCTAssertEqual(filter.tracingHeaderTypes(for: url), []) + } + + func testGivenFilterIsInitializedWithEmptySet_itNeverReturnsFirstParty() { + let filter = FirstPartyHosts([:]) + (hostsDictionary.keys + otherHosts).forEach { fixture in + let url = URL(string: fixture)! + XCTAssertFalse( + filter.isFirstParty(url: url), + "The url: `\(url)` should NOT be matched as first party." + ) + } + } + + func testGivenURLHostIsSubdomain_itIsConsideredFirstParty() { + let filter = FirstPartyHosts([ + "first-party.com": .init([.datadog]) + ]) + let url = URL(string: "https://api.first-party.com")! + XCTAssertTrue( + filter.isFirstParty(url: url), + "The url: `\(url)` should NOT be matched as first party." + ) + } + + func testGivenURLHostIsNotSubdomain_itIsNotConsideredFirstParty() { + let filter = FirstPartyHosts([ + "first-party.com": .init([.datadog]) + ]) + let urlString = "https://apifirst-party.com" + let url = URL(string: urlString)! + XCTAssertFalse( + filter.isFirstParty(url: url), + "The url: `\(url)` should NOT be matched as first party." + ) + XCTAssertFalse( + filter.isFirstParty(string: urlString), + "The url: `\(urlString)` should NOT be matched as first party." + ) + } + + func testGivenWRongURL_itIsNotConsideredFirstParty() { + let filter = FirstPartyHosts([ + "first-party.com": .init([.datadog]) + ]) + let badUrlString = "" + let badUrl = URL(string: badUrlString) + XCTAssertFalse( + filter.isFirstParty(url: badUrl), + "The url: `\(String(describing: badUrl))` should NOT be matched as first party." + ) + XCTAssertFalse( + filter.isFirstParty(string: badUrlString), + "The url: `\(badUrlString)` should NOT be matched as first party." + ) + } +} diff --git a/Tests/DatadogTests/Datadog/Tracing/Propagation/W3CHTTPHeadersReaderTests.swift b/Tests/DatadogTests/Datadog/Tracing/Propagation/W3CHTTPHeadersReaderTests.swift new file mode 100644 index 0000000000..1609e59b0f --- /dev/null +++ b/Tests/DatadogTests/Datadog/Tracing/Propagation/W3CHTTPHeadersReaderTests.swift @@ -0,0 +1,32 @@ +/* + * 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 Datadog + +class W3CHTTPHeadersReaderTests: XCTestCase { + func testW3CHTTPHeadersReaderreadsSingleHeader() { + let w3cHTTPHeadersReader = W3CHTTPHeadersReader(httpHeaderFields: ["traceparent": "00-4d2-929-01"]) + w3cHTTPHeadersReader.use(baggageItemQueue: .main) + + let spanContext = w3cHTTPHeadersReader.extract()?.dd + + XCTAssertEqual(spanContext?.traceID, TracingUUID(rawValue: 1_234)) + XCTAssertEqual(spanContext?.spanID, TracingUUID(rawValue: 2_345)) + XCTAssertNil(spanContext?.parentSpanID) + } + + func testW3CHTTPHeadersReaderreadsSingleHeaderWithSampling() { + let w3cHTTPHeadersReader = W3CHTTPHeadersReader(httpHeaderFields: ["traceparent": "00-0-0-00"]) + w3cHTTPHeadersReader.use(baggageItemQueue: .main) + + let spanContext = w3cHTTPHeadersReader.extract()?.dd + + XCTAssertNil(spanContext?.traceID) + XCTAssertNil(spanContext?.spanID) + XCTAssertNil(spanContext?.parentSpanID) + } +} diff --git a/Tests/DatadogTests/Datadog/Tracing/Propagation/W3CHTTPHeadersWriterTests.swift b/Tests/DatadogTests/Datadog/Tracing/Propagation/W3CHTTPHeadersWriterTests.swift new file mode 100644 index 0000000000..460dd5aee0 --- /dev/null +++ b/Tests/DatadogTests/Datadog/Tracing/Propagation/W3CHTTPHeadersWriterTests.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 XCTest +@testable import Datadog + +class W3CHTTPHeadersWriterTests: XCTestCase { + func testW3CHTTPHeadersWriterwritesSingleHeader() { + let sampler: Sampler = .mockKeepAll() + let context: OTSpanContext = DDSpanContext.mockWith( + traceID: .mock(1_234), + spanID: .mock(2_345), + parentSpanID: nil, + baggageItems: .mockAny() + ) + let w3cHTTPHeadersWriter = W3CHTTPHeadersWriter( + sampler: sampler + ) + w3cHTTPHeadersWriter.inject(spanContext: context) + + let headers = w3cHTTPHeadersWriter.tracePropagationHTTPHeaders + XCTAssertEqual(headers[W3CHTTPHeaders.traceparent], "00-000000000000000000000000000004d2-0000000000000929-01") + } + + func testW3CHTTPHeadersWriterwritesSingleHeaderWithSampling() { + let sampler: Sampler = .mockRejectAll() + let context: OTSpanContext = DDSpanContext.mockWith( + traceID: .mock(1_234), + spanID: .mock(2_345), + parentSpanID: .mock(5_678), + baggageItems: .mockAny() + ) + let w3cHTTPHeadersWriter = W3CHTTPHeadersWriter( + sampler: sampler + ) + w3cHTTPHeadersWriter.inject(spanContext: context) + + let headers = w3cHTTPHeadersWriter.tracePropagationHTTPHeaders + XCTAssertEqual(headers[W3CHTTPHeaders.traceparent], "00-000000000000000000000000000004d2-0000000000000929-00") + } +} diff --git a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilterTests.swift b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilterTests.swift index 86c0f610e1..87ab99ca8d 100644 --- a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilterTests.swift +++ b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilterTests.swift @@ -37,7 +37,7 @@ class FirstPartyURLsFilterTests: XCTestCase { ] func testWhenFilterIsInitializedWithEmptySet_itNeverReturnsFirstParty() { - let filter = FirstPartyURLsFilter(hosts: []) + let filter = FirstPartyURLsFilter(hosts: [:]) (fixtures1stParty + fixtures3rdParty).forEach { fixture in let url = URL(string: fixture)! XCTAssertFalse( @@ -51,7 +51,7 @@ class FirstPartyURLsFilterTests: XCTestCase { // NOTE: RUMM-722 why that for loop here? https://github.com/DataDog/dd-sdk-ios/pull/384 for _ in 0...5 { let filter = FirstPartyURLsFilter( - hosts: ["first-party.com", "eu"] + hosts: ["first-party.com": .init(.dd), "eu": .init(.dd)] ) fixtures1stParty.forEach { fixture in let url = URL(string: fixture)! @@ -67,7 +67,7 @@ class FirstPartyURLsFilterTests: XCTestCase { // NOTE: RUMM-722 why that for loop here? https://github.com/DataDog/dd-sdk-ios/pull/384 for _ in 0...5 { let filter = FirstPartyURLsFilter( - hosts: ["first-party.com", "eu"] + hosts: ["first-party.com": .init(.dd), "eu": .init(.b3m)] ) fixtures3rdParty.forEach { fixture in let url = URL(string: fixture)! @@ -78,4 +78,26 @@ class FirstPartyURLsFilterTests: XCTestCase { } } } + + func testWhenURLHostIsSubdomain_itIsConsideredFirstParty() { + let filter = FirstPartyURLsFilter( + hosts: ["first-party.com": .init(.dd)] + ) + let url = URL(string: "https://api.first-party.com")! + XCTAssertTrue( + filter.isFirstParty(url: url), + "The url: `\(url)` should NOT be matched as first party." + ) + } + + func testWhenURLHostIsNotSubdomain_itIsNotConsideredFirstParty() { + let filter = FirstPartyURLsFilter( + hosts: ["first-party.com": .init(.dd)] + ) + let url = URL(string: "https://apifirst-party.com")! + XCTAssertFalse( + filter.isFirstParty(url: url), + "The url: `\(url)` should NOT be matched as first party." + ) + } } diff --git a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift index cc3756883d..7a382ee9eb 100644 --- a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift +++ b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift @@ -117,7 +117,7 @@ class URLSessionInterceptorTests: XCTestCase { tracingSampler: Sampler = .mockKeepAll() ) -> FeaturesConfiguration.URLSessionAutoInstrumentation { return .mockWith( - userDefinedFirstPartyHosts: ["first-party.com"], + userDefinedFirstPartyHosts: .init(["first-party.com": [.datadog]]), sdkInternalURLs: ["https://dd.internal.com"], instrumentTracing: tracingInstrumentationEnabled, instrumentRUM: rumInstrumentationEnabled, @@ -138,7 +138,7 @@ class URLSessionInterceptorTests: XCTestCase { Global.sharedTracer = Tracer.mockAny(in: core) defer { Global.sharedTracer = DDNoopGlobals.tracer } let sessionWithCustomFirstPartyHosts = URLSession.mockWith( - DDURLSessionDelegate(additionalFirstPartyHosts: [alternativeFirstPartyRequest.url!.host!]) + DDURLSessionDelegate(additionalFirstPartyHostsWithHeaderTypes: [alternativeFirstPartyRequest.url!.host!: [.datadog]]) ) // When @@ -314,7 +314,7 @@ class URLSessionInterceptorTests: XCTestCase { Global.sharedTracer = Tracer.mockAny(in: core) defer { Global.sharedTracer = DDNoopGlobals.tracer } let sessionWithCustomFirstPartyHosts = URLSession.mockWith( - DDURLSessionDelegate(additionalFirstPartyHosts: [alternativeFirstPartyRequest.url!.host!]) + DDURLSessionDelegate(additionalFirstPartyHostsWithHeaderTypes: [alternativeFirstPartyRequest.url!.host!: [.datadog]]) ) let interceptedFirstPartyRequest = interceptor.modify(request: firstPartyRequest) diff --git a/Tests/DatadogTests/DatadogObjc/DDConfigurationTests.swift b/Tests/DatadogTests/DatadogObjc/DDConfigurationTests.swift index e6f97b9d3b..242771e15e 100644 --- a/Tests/DatadogTests/DatadogObjc/DDConfigurationTests.swift +++ b/Tests/DatadogTests/DatadogObjc/DDConfigurationTests.swift @@ -136,7 +136,13 @@ class DDConfigurationTests: XCTestCase { XCTAssertEqual(objcBuilder.build().sdkConfiguration.serviceName, "service-name") objcBuilder.trackURLSession(firstPartyHosts: ["example.com"]) - XCTAssertEqual(objcBuilder.build().sdkConfiguration.firstPartyHosts, ["example.com"]) + XCTAssertEqual(objcBuilder.build().sdkConfiguration.firstPartyHosts, .init(["example.com": [.datadog]])) + + objcBuilder.trackURLSession(firstPartyHostsWithHeaderTypes: ["example2.com": [.tracecontext]]) + XCTAssertEqual(objcBuilder.build().sdkConfiguration.firstPartyHosts, .init([ + "example2.com": [.tracecontext], + "example.com": [.datadog] + ])) objcBuilder.set(loggingSamplingRate: 66) XCTAssertEqual(objcBuilder.build().sdkConfiguration.loggingSamplingRate, 66) @@ -232,9 +238,6 @@ class DDConfigurationTests: XCTestCase { let serverDateProvider = ObjcServerDateProvider() objcBuilder.set(serverDateProvider: serverDateProvider) XCTAssertTrue((objcBuilder.build().sdkConfiguration.serverDateProvider as? DDServerDateProviderBridge)?.objcProvider === serverDateProvider) - - XCTAssertEqual(objcBuilder.build().sdkConfiguration.tracingHeaderTypes.count, 1) - XCTAssertEqual(objcBuilder.build().sdkConfiguration.tracingHeaderTypes.first, .dd) } func testScrubbingRUMEvents() { diff --git a/Tests/DatadogTests/DatadogObjc/DDNSURLSessionDelegateTests.swift b/Tests/DatadogTests/DatadogObjc/DDNSURLSessionDelegateTests.swift index 9d4d9f51f4..974a255d11 100644 --- a/Tests/DatadogTests/DatadogObjc/DDNSURLSessionDelegateTests.swift +++ b/Tests/DatadogTests/DatadogObjc/DDNSURLSessionDelegateTests.swift @@ -30,13 +30,13 @@ class DDNSURLSessionDelegateTests: XCTestCase { func testInit() { let delegate = DDNSURLSessionDelegate() let url = URL(string: "foo.com") - XCTAssertFalse(delegate.swiftDelegate.firstPartyURLsFilter.isFirstParty(url: url)) + XCTAssertFalse(delegate.swiftDelegate.firstPartyHosts.isFirstParty(url: url)) } func testInitWithAdditionalFirstPartyHosts() { - let delegate = DDNSURLSessionDelegate(additionalFirstPartyHosts: ["foo.com"]) + let delegate = DDNSURLSessionDelegate(additionalFirstPartyHostsWithHeaderTypes: ["foo.com": [.datadog]]) let url = URL(string: "http://foo.com") - XCTAssertTrue(delegate.swiftDelegate.firstPartyURLsFilter.isFirstParty(url: url)) + XCTAssertTrue(delegate.swiftDelegate.firstPartyHosts.isFirstParty(url: url)) } func testItForwardsCallsToSwiftDelegate() { diff --git a/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift b/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift index a6fbbf5ba7..03819a3fe4 100644 --- a/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift +++ b/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift @@ -289,6 +289,53 @@ class DDTracerTests: XCTestCase { ) } + func testInjectingSpanContextToValidCarrierAndFormatForW3C() throws { + let objcTracer = DDTracer(swiftTracer: Tracer.mockAny(in: core)) + let objcSpanContext = DDSpanContextObjc( + swiftSpanContext: DDSpanContext.mockWith(traceID: 1, spanID: 2) + ) + + let objcWriter = DDW3CHTTPHeadersWriter(samplingRate: 100) + try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) + + let expectedHTTPHeaders = [ + "traceparent": "00-00000000000000000000000000000001-0000000000000002-01" + ] + XCTAssertEqual(objcWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders) + } + + func testInjectingRejectedSpanContextToValidCarrierAndFormatForW3C() throws { + let objcTracer = DDTracer(swiftTracer: Tracer.mockAny(in: core)) + let objcSpanContext = DDSpanContextObjc( + swiftSpanContext: DDSpanContext.mockWith(traceID: 1, spanID: 2) + ) + + let objcWriter = DDW3CHTTPHeadersWriter(samplingRate: 0) + try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) + + let expectedHTTPHeaders = [ + "traceparent": "00-00000000000000000000000000000001-0000000000000002-00" + ] + XCTAssertEqual(objcWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders) + } + + func testInjectingSpanContextToInvalidCarrierOrFormatForW3C() throws { + let objcTracer = DDTracer(swiftTracer: Tracer.mockAny(in: core)) + let objcSpanContext = DDSpanContextObjc(swiftSpanContext: DDSpanContext.mockWith(traceID: 1, spanID: 2)) + + let objcValidWriter = DDW3CHTTPHeadersWriter(samplingRate: 100) + let objcInvalidFormat = "foo" + XCTAssertThrowsError( + try objcTracer.inject(objcSpanContext, format: objcInvalidFormat, carrier: objcValidWriter) + ) + + let objcInvalidWriter = NSObject() + let objcValidFormat = OT.formatTextMap + XCTAssertThrowsError( + try objcTracer.inject(objcSpanContext, format: objcValidFormat, carrier: objcInvalidWriter) + ) + } + // MARK: - Usage errors func testsWhenTagsDictionaryContainsInvalidKeys_thenThosesTagsAreDropped() throws { diff --git a/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDNSURLSessionDelegate+apiTests.m b/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDNSURLSessionDelegate+apiTests.m index 9def1ffc0b..81a35932b5 100644 --- a/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDNSURLSessionDelegate+apiTests.m +++ b/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDNSURLSessionDelegate+apiTests.m @@ -21,6 +21,9 @@ @implementation DDNSURLSessionDelegate_apiTests - (void)testDDNSURLSessionDelegateAPI { [[DDNSURLSessionDelegate alloc] init]; [[DDNSURLSessionDelegate alloc] initWithAdditionalFirstPartyHosts:[NSSet setWithArray:@[]]]; + [[DDNSURLSessionDelegate alloc] initWithAdditionalFirstPartyHostsWithHeaderTypes:@{ + @"host": [[NSSet alloc] initWithObjects:[DDTracingHeaderType datadog], nil] + }]; } #pragma clang diagnostic pop diff --git a/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDW3CHTTPHeadersWriter+apiTests.m b/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDW3CHTTPHeadersWriter+apiTests.m new file mode 100644 index 0000000000..1ad07d22a2 --- /dev/null +++ b/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDW3CHTTPHeadersWriter+apiTests.m @@ -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 +@import DatadogObjc; + +@interface DDW3CHTTPHeadersWriter_apiTests : XCTestCase +@end + +/* + * `DatadogObjc` APIs smoke tests - only check if the interface is available to Objc. + */ +@implementation DDW3CHTTPHeadersWriter_apiTests + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-value" + +- (void)testInitWithSamplingRate { + [[DDW3CHTTPHeadersWriter alloc] initWithSamplingRate:50]; +} + +#pragma clang diagnostic pop + +@end