From 69013778ba228c3b7d43d909f812d9deb9172043 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 20 Apr 2022 14:22:49 +0200 Subject: [PATCH 01/55] RUMM-2024 Replace internal monitoring with rum telemetry --- Datadog/Datadog.xcodeproj/project.pbxproj | 68 ---- Datadog/Example/AppConfiguration.swift | 5 - Makefile | 3 +- Sources/Datadog/Core/Feature.swift | 18 +- .../Datadog/Core/FeaturesConfiguration.swift | 25 -- .../Core/Persistence/FilesOrchestrator.swift | 14 +- .../Persistence/Migrating/DataMigrator.swift | 8 +- .../Migrating/DeleteAllDataMigrator.swift | 4 +- .../Migrating/MoveDataMigrator.swift | 4 +- .../Core/Persistence/Reading/FileReader.swift | 8 +- .../Core/Persistence/Writing/FileWriter.swift | 8 +- .../Core/System/Time/ServerDateProvider.swift | 7 +- Sources/Datadog/Core/Telemetry.swift | 9 + .../Core/Upload/DataUploadStatus.swift | 33 +- .../Core/Upload/DataUploadWorker.swift | 10 +- .../Datadog/Core/Upload/DataUploader.swift | 2 +- .../Datadog/Core/Upload/RequestBuilder.swift | 18 +- .../CrashReporting/CrashReporter.swift | 25 +- .../CrashReportingFeature.swift | 6 +- .../DDCrashReportingPluginType.swift | 7 - Sources/Datadog/Datadog.swift | 71 ++--- Sources/Datadog/DatadogConfiguration.swift | 9 +- .../WKUserContentController+Datadog.swift | 1 - .../WebView/WebLogEventConsumer.swift | 9 +- .../InternalMonitoring/InternalMonitor.swift | 14 - .../InternalMonitoringFeature.swift | 157 ---------- .../Kronos/KronosMonitor.swift | 294 ------------------ Sources/Datadog/Kronos/KronosClock.swift | 6 +- Sources/Datadog/Kronos/KronosNTPClient.swift | 19 +- Sources/Datadog/Logging/LoggingFeature.swift | 16 +- Sources/Datadog/RUM/RUMFeature.swift | 18 +- .../RUM/RUMVitals/VitalCPUReader.swift | 6 +- .../RUM/Scrubbing/RUMEventsMapper.swift | 4 +- Sources/Datadog/Tracer.swift | 3 +- .../Tracing/Span/SpanEventBuilder.swift | 6 +- Sources/Datadog/Tracing/TracingFeature.swift | 24 +- .../PLCrashReporterIntegration.swift | 56 ---- .../Core/FeaturesConfigurationTests.swift | 40 --- .../Core/Upload/DataUploadStatusTests.swift | 13 +- .../Core/Upload/DataUploadWorkerTests.swift | 14 +- .../CrashReporting/CrashReporterTests.swift | 18 +- Tests/DatadogTests/Datadog/DatadogTests.swift | 49 +-- .../Datadog/Mocks/CoreMocks.swift | 37 +-- .../Mocks/CrashReportingFeatureMocks.swift | 6 +- .../InternalMonitoringFeatureMocks.swift | 62 ---- .../Datadog/Mocks/LoggingFeatureMocks.swift | 17 + .../Datadog/Mocks/TracingFeatureMocks.swift | 15 +- .../WebView/WebLogEventConsumerTests.swift | 51 --- .../TestsObserver/DatadogTestsObserver.swift | 1 - 49 files changed, 221 insertions(+), 1097 deletions(-) delete mode 100644 Sources/Datadog/InternalMonitoring/InternalMonitor.swift delete mode 100644 Sources/Datadog/InternalMonitoring/InternalMonitoringFeature.swift delete mode 100644 Sources/Datadog/InternalMonitoring/Kronos/KronosMonitor.swift delete mode 100644 Tests/DatadogTests/Datadog/Mocks/InternalMonitoringFeatureMocks.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 8393e3f558..c24d02bd65 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -278,7 +278,6 @@ 617B954224BF4E7600E6F443 /* RUMMonitorConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617B954124BF4E7600E6F443 /* RUMMonitorConfigurationTests.swift */; }; 617CD0DD24CEDDD300B0B557 /* RUMUserActionScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617CD0DC24CEDDD300B0B557 /* RUMUserActionScopeTests.swift */; }; 617CEB392456BC3A00AD4669 /* TracingUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617CEB382456BC3A00AD4669 /* TracingUUID.swift */; }; - 61815C06278867D1004A666C /* KronosMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61815C05278867D1004A666C /* KronosMonitorTests.swift */; }; 618236892710560900125326 /* DebugWebviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618236882710560900125326 /* DebugWebviewViewController.swift */; }; 6182374325D3DFD5006A375B /* CrashReportingWithRUMIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6182374225D3DFD5006A375B /* CrashReportingWithRUMIntegrationTests.swift */; }; 6184751526EFCF1300C7C9C5 /* DatadogTestsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6184751426EFCF1300C7C9C5 /* DatadogTestsObserver.swift */; }; @@ -454,10 +453,6 @@ 61EF78B1257E2E7A00EDCCB3 /* MoveDataMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61EF78B0257E2E7A00EDCCB3 /* MoveDataMigrator.swift */; }; 61EF78B7257E37D500EDCCB3 /* MoveDataMigratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61EF78B6257E37D500EDCCB3 /* MoveDataMigratorTests.swift */; }; 61EF78C1257F842000EDCCB3 /* FeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61EF78C0257F842000EDCCB3 /* FeatureTests.swift */; }; - 61F1878425FA121F0022CE9A /* InternalMonitoringFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1878325FA121F0022CE9A /* InternalMonitoringFeature.swift */; }; - 61F1878D25FA33A90022CE9A /* InternalMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1878C25FA33A90022CE9A /* InternalMonitor.swift */; }; - 61F1879D25FA774C0022CE9A /* InternalMonitoringFeatureMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1879C25FA774C0022CE9A /* InternalMonitoringFeatureMocks.swift */; }; - 61F187FC25FA7DD60022CE9A /* InternalMonitoringFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F187FB25FA7DD60022CE9A /* InternalMonitoringFeatureTests.swift */; }; 61F1A61A2498A51700075390 /* CoreMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1A6192498A51700075390 /* CoreMocks.swift */; }; 61F1A621249A45E400075390 /* DDSpanContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1A620249A45E400075390 /* DDSpanContextTests.swift */; }; 61F1A623249B811200075390 /* Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1A622249B811200075390 /* Encoding.swift */; }; @@ -497,7 +492,6 @@ 61FF283024BC5E2D000B3D9B /* RUMEventFileOutputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FF282F24BC5E2D000B3D9B /* RUMEventFileOutputTests.swift */; }; 61FF416225EE5FF400CE35EC /* CrashReportingWithLoggingIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FF416125EE5FF400CE35EC /* CrashReportingWithLoggingIntegrationTests.swift */; }; 61FF9A4525AC5DEA001058CC /* RUMViewIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FF9A4425AC5DEA001058CC /* RUMViewIdentity.swift */; }; - 61FFFB89278457D400401A28 /* KronosMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FFFB88278457D300401A28 /* KronosMonitor.swift */; }; 9E26E6B924C87693000B3270 /* RUMDataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E26E6B824C87693000B3270 /* RUMDataModels.swift */; }; 9E2EF44F2694FA14008A7DAE /* VitalInfoSamplerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2EF44E2694FA14008A7DAE /* VitalInfoSamplerTests.swift */; }; 9E307C3224C8846D0039607E /* RUMDataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E26E6B824C87693000B3270 /* RUMDataModels.swift */; }; @@ -633,7 +627,6 @@ D2CB6E3727C50EAE00A62B57 /* UIKitRUMUserActionsPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F637AED12697404200516F32 /* UIKitRUMUserActionsPredicate.swift */; }; D2CB6E3827C50EAE00A62B57 /* DataFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AD4E3724531500006E34EA /* DataFormat.swift */; }; D2CB6E3927C50EAE00A62B57 /* JSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E58E8E024615C75008E5063 /* JSONEncoder.swift */; }; - D2CB6E3A27C50EAE00A62B57 /* InternalMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1878C25FA33A90022CE9A /* InternalMonitor.swift */; }; D2CB6E3B27C50EAE00A62B57 /* CodableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BA02423979B00786299 /* CodableValue.swift */; }; D2CB6E3C27C50EAE00A62B57 /* Retrying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6139CD702589FAFD007E8BB7 /* Retrying.swift */; }; D2CB6E3D27C50EAE00A62B57 /* FeaturesConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BBD19424ED4E9E0023E65F /* FeaturesConfiguration.swift */; }; @@ -650,7 +643,6 @@ D2CB6E4827C50EAE00A62B57 /* ServerDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BCB81E256EB77F0039887B /* ServerDateProvider.swift */; }; D2CB6E4927C50EAE00A62B57 /* AttributesSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61122ED925B1BA9700F9C7F5 /* AttributesSanitizer.swift */; }; D2CB6E4A27C50EAE00A62B57 /* RUMEventOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FF282524B8A248000B3D9B /* RUMEventOutput.swift */; }; - D2CB6E4B27C50EAE00A62B57 /* InternalMonitoringFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1878325FA121F0022CE9A /* InternalMonitoringFeature.swift */; }; D2CB6E4C27C50EAE00A62B57 /* RUMDataModelsMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618715F824DC13A100FC0F69 /* RUMDataModelsMapping.swift */; }; D2CB6E4D27C50EAE00A62B57 /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3638424361E9200C4D4E6 /* Globals.swift */; }; D2CB6E4E27C50EAE00A62B57 /* ActiveSpansPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D202E924C065CF00D1AF3A /* ActiveSpansPool.swift */; }; @@ -683,7 +675,6 @@ D2CB6E6927C50EAE00A62B57 /* KronosDNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D3E0C9277B23F0008BE766 /* KronosDNSResolver.swift */; }; D2CB6E6A27C50EAE00A62B57 /* InternalURLsFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6149FB392529D17F00EE387A /* InternalURLsFilter.swift */; }; D2CB6E6B27C50EAE00A62B57 /* ValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611529A425E3DD51004F740E /* ValuePublisher.swift */; }; - D2CB6E6C27C50EAE00A62B57 /* KronosMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FFFB88278457D300401A28 /* KronosMonitor.swift */; }; D2CB6E6D27C50EAE00A62B57 /* RUMUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618DCFD624C7265300589570 /* RUMUUID.swift */; }; D2CB6E6E27C50EAE00A62B57 /* URLSessionInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E1337252340810098C6B0 /* URLSessionInterceptor.swift */; }; D2CB6E6F27C50EAE00A62B57 /* TracingHTTPHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E13B02524B8F80098C6B0 /* TracingHTTPHeaders.swift */; }; @@ -867,7 +858,6 @@ D2CB6F2E27C520D400A62B57 /* RUMCommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618715F624DC0CDE00FC0F69 /* RUMCommandTests.swift */; }; D2CB6F2F27C520D400A62B57 /* LogUtilityOutputsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C3F2423990D00786299 /* LogUtilityOutputsTests.swift */; }; D2CB6F3027C520D400A62B57 /* DatadogExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C472423990D00786299 /* DatadogExtensions.swift */; }; - D2CB6F3127C520D400A62B57 /* InternalMonitoringFeatureMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F1879C25FA774C0022CE9A /* InternalMonitoringFeatureMocks.swift */; }; D2CB6F3227C520D400A62B57 /* JSONDataMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45BE624519A3700F2C652 /* JSONDataMatcher.swift */; }; D2CB6F3327C520D400A62B57 /* FilesOrchestratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C2A2423990D00786299 /* FilesOrchestratorTests.swift */; }; D2CB6F3427C520D400A62B57 /* TracingUUIDGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B558D32469CDD8001460D3 /* TracingUUIDGeneratorTests.swift */; }; @@ -919,11 +909,9 @@ D2CB6F6227C520D400A62B57 /* WebLogEventConsumerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */; }; D2CB6F6327C520D400A62B57 /* ConsentProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6114FE3A25768AA90084E372 /* ConsentProviderTests.swift */; }; D2CB6F6427C520D400A62B57 /* LoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C382423990D00786299 /* LoggerTests.swift */; }; - D2CB6F6527C520D400A62B57 /* KronosMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61815C05278867D1004A666C /* KronosMonitorTests.swift */; }; D2CB6F6627C520D400A62B57 /* RUMMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617B953C24BF4D8F00E6F443 /* RUMMonitorTests.swift */; }; D2CB6F6727C520D400A62B57 /* HostsSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */; }; D2CB6F6827C520D400A62B57 /* SwiftUIExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D244B3A2271EDACD003E1B29 /* SwiftUIExtensionsTests.swift */; }; - D2CB6F6927C520D400A62B57 /* InternalMonitoringFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F187FB25FA7DD60022CE9A /* InternalMonitoringFeatureTests.swift */; }; D2CB6F6A27C520D400A62B57 /* DDRUMMonitor+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 61B5E42026DF85C7000B0A5F /* DDRUMMonitor+apiTests.m */; }; D2CB6F6B27C520D400A62B57 /* RUMDebuggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61786F7624FCDE04009E6BAB /* RUMDebuggingTests.swift */; }; D2CB6F6C27C520D400A62B57 /* RUMInstrumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F9CA702512450B000A5E61 /* RUMInstrumentationTests.swift */; }; @@ -1466,7 +1454,6 @@ 617B954124BF4E7600E6F443 /* RUMMonitorConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMonitorConfigurationTests.swift; sourceTree = ""; }; 617CD0DC24CEDDD300B0B557 /* RUMUserActionScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMUserActionScopeTests.swift; sourceTree = ""; }; 617CEB382456BC3A00AD4669 /* TracingUUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingUUID.swift; sourceTree = ""; }; - 61815C05278867D1004A666C /* KronosMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KronosMonitorTests.swift; sourceTree = ""; }; 618236882710560900125326 /* DebugWebviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugWebviewViewController.swift; sourceTree = ""; }; 6182374225D3DFD5006A375B /* CrashReportingWithRUMIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportingWithRUMIntegrationTests.swift; sourceTree = ""; }; 6184751426EFCF1300C7C9C5 /* DatadogTestsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogTestsObserver.swift; sourceTree = ""; }; @@ -1652,10 +1639,6 @@ 61EF78C0257F842000EDCCB3 /* FeatureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureTests.swift; sourceTree = ""; }; 61F1872325F657630022CE9A /* DatadogIntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = DatadogIntegrationTests.xctestplan; sourceTree = ""; }; 61F1872B25F658260022CE9A /* DatadogCrashReportingIntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = DatadogCrashReportingIntegrationTests.xctestplan; sourceTree = ""; }; - 61F1878325FA121F0022CE9A /* InternalMonitoringFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalMonitoringFeature.swift; sourceTree = ""; }; - 61F1878C25FA33A90022CE9A /* InternalMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalMonitor.swift; sourceTree = ""; }; - 61F1879C25FA774C0022CE9A /* InternalMonitoringFeatureMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalMonitoringFeatureMocks.swift; sourceTree = ""; }; - 61F187FB25FA7DD60022CE9A /* InternalMonitoringFeatureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalMonitoringFeatureTests.swift; sourceTree = ""; }; 61F1A6192498A51700075390 /* CoreMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMocks.swift; sourceTree = ""; }; 61F1A620249A45E400075390 /* DDSpanContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDSpanContextTests.swift; sourceTree = ""; }; 61F1A622249B811200075390 /* Encoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encoding.swift; sourceTree = ""; }; @@ -1691,7 +1674,6 @@ 61FF282F24BC5E2D000B3D9B /* RUMEventFileOutputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMEventFileOutputTests.swift; sourceTree = ""; }; 61FF416125EE5FF400CE35EC /* CrashReportingWithLoggingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportingWithLoggingIntegrationTests.swift; sourceTree = ""; }; 61FF9A4425AC5DEA001058CC /* RUMViewIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewIdentity.swift; sourceTree = ""; }; - 61FFFB88278457D300401A28 /* KronosMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KronosMonitor.swift; sourceTree = ""; }; 9E0542CA25F8EBBE007A3D0B /* Kronos.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Kronos.xcframework; path = ../Carthage/Build/Kronos.xcframework; sourceTree = ""; }; 9E26E6B824C87693000B3270 /* RUMDataModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMDataModels.swift; sourceTree = ""; }; 9E2EF44E2694FA14008A7DAE /* VitalInfoSamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalInfoSamplerTests.swift; sourceTree = ""; }; @@ -2065,7 +2047,6 @@ 61E5332A24B75C3B003D6C4E /* RUM */, 61DE332425C826B6008E3EC2 /* CrashReporting */, 618E132A25233E890098C6B0 /* URLSessionAutoInstrumentation */, - 61F1878225FA10A30022CE9A /* InternalMonitoring */, 61216277247D1F2100AC5D67 /* FeaturesIntegration */, 61133BB72423979B00786299 /* Utils */, 61E909E524A24DD3005EA2DE /* OpenTracing */, @@ -2323,7 +2304,6 @@ 61E5332D24B75DC7003D6C4E /* RUM */, 61F2724725C9437C00D54BF8 /* CrashReporting */, 61B03872252724AB00518F3C /* URLSessionAutoInstrumentation */, - 61F187FA25FA7D820022CE9A /* InternalMonitoring */, 61216278247D20D500AC5D67 /* FeaturesIntegration */, 61133C352423990D00786299 /* Utils */, 61BAD46826415FA2001886CA /* OpenTracing */, @@ -2345,7 +2325,6 @@ 61F2723E25C86DA400D54BF8 /* CrashReportingFeatureMocks.swift */, 613E820425A879AF0084B751 /* RUMDataModelMocks.swift */, 61B038B92527257B00518F3C /* URLSessionAutoInstrumentationMocks.swift */, - 61F1879C25FA774C0022CE9A /* InternalMonitoringFeatureMocks.swift */, 61C3638224361BE200C4D4E6 /* DatadogPrivateMocks.swift */, 61F1A61B2498AD2C00075390 /* SystemFrameworks */, 614BF37D2670AE9D002379C8 /* AttributesMocks.swift */, @@ -3175,14 +3154,6 @@ path = UUIDs; sourceTree = ""; }; - 61815C04278867BB004A666C /* Kronos */ = { - isa = PBXGroup; - children = ( - 61815C05278867D1004A666C /* KronosMonitorTests.swift */, - ); - path = Kronos; - sourceTree = ""; - }; 6182374125D3DFB8006A375B /* CrashReporting */ = { isa = PBXGroup; children = ( @@ -3743,25 +3714,6 @@ path = Migrating; sourceTree = ""; }; - 61F1878225FA10A30022CE9A /* InternalMonitoring */ = { - isa = PBXGroup; - children = ( - 61F1878325FA121F0022CE9A /* InternalMonitoringFeature.swift */, - 61F1878C25FA33A90022CE9A /* InternalMonitor.swift */, - 61FFFB8A2784593B00401A28 /* Kronos */, - ); - path = InternalMonitoring; - sourceTree = ""; - }; - 61F187FA25FA7D820022CE9A /* InternalMonitoring */ = { - isa = PBXGroup; - children = ( - 61F187FB25FA7DD60022CE9A /* InternalMonitoringFeatureTests.swift */, - 61815C04278867BB004A666C /* Kronos */, - ); - path = InternalMonitoring; - sourceTree = ""; - }; 61F1A61B2498AD2C00075390 /* SystemFrameworks */ = { isa = PBXGroup; children = ( @@ -3932,14 +3884,6 @@ path = RUMEventOutputs; sourceTree = ""; }; - 61FFFB8A2784593B00401A28 /* Kronos */ = { - isa = PBXGroup; - children = ( - 61FFFB88278457D300401A28 /* KronosMonitor.swift */, - ); - path = Kronos; - sourceTree = ""; - }; 9E06058F26EF904200F5F935 /* LongTasks */ = { isa = PBXGroup; children = ( @@ -5025,7 +4969,6 @@ F637AED22697404200516F32 /* UIKitRUMUserActionsPredicate.swift in Sources */, 61AD4E3824531500006E34EA /* DataFormat.swift in Sources */, 9E58E8E124615C75008E5063 /* JSONEncoder.swift in Sources */, - 61F1878D25FA33A90022CE9A /* InternalMonitor.swift in Sources */, 61133BCA2423979B00786299 /* CodableValue.swift in Sources */, 6139CD712589FAFD007E8BB7 /* Retrying.swift in Sources */, 61BBD19524ED4E9E0023E65F /* FeaturesConfiguration.swift in Sources */, @@ -5044,7 +4987,6 @@ 61BCB81F256EB77F0039887B /* ServerDateProvider.swift in Sources */, 61122EDA25B1BA9700F9C7F5 /* AttributesSanitizer.swift in Sources */, 61FF282624B8A248000B3D9B /* RUMEventOutput.swift in Sources */, - 61F1878425FA121F0022CE9A /* InternalMonitoringFeature.swift in Sources */, 618715F924DC13A100FC0F69 /* RUMDataModelsMapping.swift in Sources */, 61C3638524361E9200C4D4E6 /* Globals.swift in Sources */, E1D202EA24C065CF00D1AF3A /* ActiveSpansPool.swift in Sources */, @@ -5078,7 +5020,6 @@ 6149FB3A2529D17F00EE387A /* InternalURLsFilter.swift in Sources */, D2DC4BF627F484AA00E4FB96 /* DataEncryption.swift in Sources */, 611529A525E3DD51004F740E /* ValuePublisher.swift in Sources */, - 61FFFB89278457D400401A28 /* KronosMonitor.swift in Sources */, 618DCFD724C7265300589570 /* RUMUUID.swift in Sources */, 61B038662527247800518F3C /* URLSessionInterceptor.swift in Sources */, 61B03898252724DE00518F3C /* TracingHTTPHeaders.swift in Sources */, @@ -5271,7 +5212,6 @@ 618715F724DC0CDE00FC0F69 /* RUMCommandTests.swift in Sources */, 61133C682423990D00786299 /* LogUtilityOutputsTests.swift in Sources */, 61133C6E2423990D00786299 /* DatadogExtensions.swift in Sources */, - 61F1879D25FA774C0022CE9A /* InternalMonitoringFeatureMocks.swift in Sources */, 61E45BE724519A3700F2C652 /* JSONDataMatcher.swift in Sources */, 61133C592423990D00786299 /* FilesOrchestratorTests.swift in Sources */, 61B558D42469CDD8001460D3 /* TracingUUIDGeneratorTests.swift in Sources */, @@ -5323,11 +5263,9 @@ 9EA8A7F82768A72B007D6FDB /* WebLogEventConsumerTests.swift in Sources */, 6114FE3B25768AA90084E372 /* ConsentProviderTests.swift in Sources */, 61133C642423990D00786299 /* LoggerTests.swift in Sources */, - 61815C06278867D1004A666C /* KronosMonitorTests.swift in Sources */, 617B953D24BF4D8F00E6F443 /* RUMMonitorTests.swift in Sources */, 9EA8A7F1275E1518007D6FDB /* HostsSanitizerTests.swift in Sources */, D244B3A3271EDACD003E1B29 /* SwiftUIExtensionsTests.swift in Sources */, - 61F187FC25FA7DD60022CE9A /* InternalMonitoringFeatureTests.swift in Sources */, 61B5E42126DF85C7000B0A5F /* DDRUMMonitor+apiTests.m in Sources */, 61786F7724FCDE05009E6BAB /* RUMDebuggingTests.swift in Sources */, 61F9CA712512450B000A5E61 /* RUMInstrumentationTests.swift in Sources */, @@ -5644,7 +5582,6 @@ D2CB6E3727C50EAE00A62B57 /* UIKitRUMUserActionsPredicate.swift in Sources */, D2CB6E3827C50EAE00A62B57 /* DataFormat.swift in Sources */, D2CB6E3927C50EAE00A62B57 /* JSONEncoder.swift in Sources */, - D2CB6E3A27C50EAE00A62B57 /* InternalMonitor.swift in Sources */, D2CB6E3B27C50EAE00A62B57 /* CodableValue.swift in Sources */, D2CB6E3C27C50EAE00A62B57 /* Retrying.swift in Sources */, D2CB6E3D27C50EAE00A62B57 /* FeaturesConfiguration.swift in Sources */, @@ -5663,7 +5600,6 @@ D2CB6E4827C50EAE00A62B57 /* ServerDateProvider.swift in Sources */, D2CB6E4927C50EAE00A62B57 /* AttributesSanitizer.swift in Sources */, D2CB6E4A27C50EAE00A62B57 /* RUMEventOutput.swift in Sources */, - D2CB6E4B27C50EAE00A62B57 /* InternalMonitoringFeature.swift in Sources */, D2CB6E4C27C50EAE00A62B57 /* RUMDataModelsMapping.swift in Sources */, D2CB6E4D27C50EAE00A62B57 /* Globals.swift in Sources */, D2CB6E4E27C50EAE00A62B57 /* ActiveSpansPool.swift in Sources */, @@ -5696,7 +5632,6 @@ D2CB6E6927C50EAE00A62B57 /* KronosDNSResolver.swift in Sources */, D2CB6E6A27C50EAE00A62B57 /* InternalURLsFilter.swift in Sources */, D2CB6E6B27C50EAE00A62B57 /* ValuePublisher.swift in Sources */, - D2CB6E6C27C50EAE00A62B57 /* KronosMonitor.swift in Sources */, D2CB6E6D27C50EAE00A62B57 /* RUMUUID.swift in Sources */, D2CB6E6E27C50EAE00A62B57 /* URLSessionInterceptor.swift in Sources */, D2CB6E6F27C50EAE00A62B57 /* TracingHTTPHeaders.swift in Sources */, @@ -5890,7 +5825,6 @@ D2CB6F2E27C520D400A62B57 /* RUMCommandTests.swift in Sources */, D2CB6F2F27C520D400A62B57 /* LogUtilityOutputsTests.swift in Sources */, D2CB6F3027C520D400A62B57 /* DatadogExtensions.swift in Sources */, - D2CB6F3127C520D400A62B57 /* InternalMonitoringFeatureMocks.swift in Sources */, D2CB6F3227C520D400A62B57 /* JSONDataMatcher.swift in Sources */, D2CB6F3327C520D400A62B57 /* FilesOrchestratorTests.swift in Sources */, D2CB6F3427C520D400A62B57 /* TracingUUIDGeneratorTests.swift in Sources */, @@ -5942,11 +5876,9 @@ D2CB6F6227C520D400A62B57 /* WebLogEventConsumerTests.swift in Sources */, D2CB6F6327C520D400A62B57 /* ConsentProviderTests.swift in Sources */, D2CB6F6427C520D400A62B57 /* LoggerTests.swift in Sources */, - D2CB6F6527C520D400A62B57 /* KronosMonitorTests.swift in Sources */, D2CB6F6627C520D400A62B57 /* RUMMonitorTests.swift in Sources */, D2CB6F6727C520D400A62B57 /* HostsSanitizerTests.swift in Sources */, D2CB6F6827C520D400A62B57 /* SwiftUIExtensionsTests.swift in Sources */, - D2CB6F6927C520D400A62B57 /* InternalMonitoringFeatureTests.swift in Sources */, D2CB6F6A27C520D400A62B57 /* DDRUMMonitor+apiTests.m in Sources */, D2CB6F6B27C520D400A62B57 /* RUMDebuggingTests.swift in Sources */, D2CB6F6C27C520D400A62B57 /* RUMInstrumentationTests.swift in Sources */, diff --git a/Datadog/Example/AppConfiguration.swift b/Datadog/Example/AppConfiguration.swift index 837e7c9d55..274d513f4f 100644 --- a/Datadog/Example/AppConfiguration.swift +++ b/Datadog/Example/AppConfiguration.swift @@ -51,11 +51,6 @@ struct ExampleAppConfiguration: AppConfiguration { _ = configuration.set(customRUMEndpoint: customRUMURL) } -#if DD_SDK_ENABLE_INTERNAL_MONITORING - _ = configuration - .enableInternalMonitoring(clientToken: Environment.readClientToken()) -#endif - if let testScenario = testScenario { // If the `Example` app was launched with test scenario ENV, apply the scenario configuration testScenario.configureSDK(builder: configuration) diff --git a/Makefile b/Makefile index 39d9f6f35f..99e8702b47 100644 --- a/Makefile +++ b/Makefile @@ -18,10 +18,9 @@ export DD_SDK_TESTING_XCCONFIG_CI define DD_SDK_BASE_XCCONFIG // Active compilation conditions - only enabled on local machine:\n -// - DD_SDK_ENABLE_INTERNAL_MONITORING - enables Internal Monitoring APIs\n // - DD_SDK_ENABLE_EXPERIMENTAL_APIS - enables APIs which are not available in released version of the SDK\n // - DD_SDK_COMPILED_FOR_TESTING - conditions the SDK code compiled for testing\n -SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) DD_SDK_ENABLE_INTERNAL_MONITORING DD_SDK_ENABLE_EXPERIMENTAL_APIS DD_SDK_COMPILED_FOR_TESTING\n +SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) DD_SDK_ENABLE_EXPERIMENTAL_APIS DD_SDK_COMPILED_FOR_TESTING\n \n // To build only active architecture for all configurations. This gives us ~10% build time gain\n // in targets which do not use 'Debug' configuration.\n diff --git a/Sources/Datadog/Core/Feature.swift b/Sources/Datadog/Core/Feature.swift index fdc9161742..d6d4d652d3 100644 --- a/Sources/Datadog/Core/Feature.swift +++ b/Sources/Datadog/Core/Feature.swift @@ -55,7 +55,7 @@ internal struct FeatureStorage { dataFormat: DataFormat, directories: FeatureDirectories, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { let readWriteQueue = DispatchQueue( label: "com.datadoghq.ios-sdk-\(featureName)-read-write", @@ -65,13 +65,13 @@ internal struct FeatureStorage { directory: directories.authorized, performance: commonDependencies.performance, dateProvider: commonDependencies.dateProvider, - internalMonitor: internalMonitor + telemetry: telemetry ) let unauthorizedFilesOrchestrator = FilesOrchestrator( directory: directories.unauthorized, performance: commonDependencies.performance, dateProvider: commonDependencies.dateProvider, - internalMonitor: internalMonitor + telemetry: telemetry ) let dataOrchestrator = DataOrchestrator( @@ -84,14 +84,14 @@ internal struct FeatureStorage { dataFormat: dataFormat, orchestrator: unauthorizedFilesOrchestrator, encryption: commonDependencies.encryption, - internalMonitor: internalMonitor + telemetry: telemetry ) let authorizedFileWriter = FileWriter( dataFormat: dataFormat, orchestrator: authorizedFilesOrchestrator, encryption: commonDependencies.encryption, - internalMonitor: internalMonitor + telemetry: telemetry ) let consentAwareDataWriter = ConsentAwareDataWriter( @@ -103,7 +103,7 @@ internal struct FeatureStorage { ), dataMigratorFactory: DataMigratorFactory( directories: directories, - internalMonitor: internalMonitor + telemetry: telemetry ) ) @@ -120,7 +120,7 @@ internal struct FeatureStorage { dataFormat: dataFormat, orchestrator: authorizedFilesOrchestrator, encryption: commonDependencies.encryption, - internalMonitor: internalMonitor + telemetry: telemetry ) ) @@ -170,7 +170,7 @@ internal struct FeatureUpload { storage: FeatureStorage, requestBuilder: RequestBuilder, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { let uploadQueue = DispatchQueue( label: "com.datadoghq.ios-sdk-\(featureName)-upload", @@ -195,7 +195,7 @@ internal struct FeatureUpload { uploadConditions: uploadConditions, delay: DataUploadDelay(performance: commonDependencies.performance), featureName: featureName, - internalMonitor: internalMonitor + telemetry: telemetry ) ) } diff --git a/Sources/Datadog/Core/FeaturesConfiguration.swift b/Sources/Datadog/Core/FeaturesConfiguration.swift index 417b8fa609..8d2fa66175 100644 --- a/Sources/Datadog/Core/FeaturesConfiguration.swift +++ b/Sources/Datadog/Core/FeaturesConfiguration.swift @@ -82,17 +82,6 @@ internal struct FeaturesConfiguration { let crashReportingPlugin: DDCrashReportingPluginType } - struct InternalMonitoring { - let common: Common - let sdkServiceName: String - let sdkEnvironment: String - /// Internal monitoring logger's name. - let loggerName = "im-logger" - let logsUploadURL: URL - /// The client token authorized for monitoring org (likely it's different than client token for other features). - let clientToken: String - } - /// Configuration common to all features. let common: Common /// Logging feature configuration or `nil` if the feature is disabled. @@ -105,8 +94,6 @@ internal struct FeaturesConfiguration { let urlSessionAutoInstrumentation: URLSessionAutoInstrumentation? /// Crash Reporting feature configuration or `nil` if the feature was not enabled. let crashReporting: CrashReporting? - /// Internal Monitoring feature configuration or `nil` if the feature was not enabled. - let internalMonitoring: InternalMonitoring? } extension FeaturesConfiguration { @@ -123,7 +110,6 @@ extension FeaturesConfiguration { var rum: RUM? var urlSessionAutoInstrumentation: URLSessionAutoInstrumentation? var crashReporting: CrashReporting? - var internalMonitoring: InternalMonitoring? var logsEndpoint = configuration.logsEndpoint var tracesEndpoint = configuration.tracesEndpoint @@ -281,23 +267,12 @@ extension FeaturesConfiguration { } } - if let internalMonitoringClientToken = configuration.internalMonitoringClientToken { - internalMonitoring = InternalMonitoring( - common: common, - sdkServiceName: "dd-sdk-ios", - sdkEnvironment: "prod", - logsUploadURL: try ifValid(endpointURLString: Datadog.Configuration.DatadogEndpoint.us1.logsEndpoint.url), - clientToken: try ifValid(clientToken: internalMonitoringClientToken) - ) - } - self.common = common self.logging = logging self.tracing = tracing self.rum = rum self.urlSessionAutoInstrumentation = urlSessionAutoInstrumentation self.crashReporting = crashReporting - self.internalMonitoring = internalMonitoring } } diff --git a/Sources/Datadog/Core/Persistence/FilesOrchestrator.swift b/Sources/Datadog/Core/Persistence/FilesOrchestrator.swift index 5510d41f59..725936b41b 100644 --- a/Sources/Datadog/Core/Persistence/FilesOrchestrator.swift +++ b/Sources/Datadog/Core/Persistence/FilesOrchestrator.swift @@ -19,18 +19,18 @@ internal class FilesOrchestrator { /// Tracks number of times the file at `lastWritableFileURL` was returned from `getWritableFile()`. /// This should correspond with number of objects stored in file, assuming that majority of writes succeed (the difference is negligible). private var lastWritableFileUsesCount: Int = 0 - private let internalMonitor: InternalMonitor? + private let telemetry: Telemetry? init( directory: Directory, performance: StoragePerformancePreset, dateProvider: DateProvider, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { self.directory = directory self.performance = performance self.dateProvider = dateProvider - self.internalMonitor = internalMonitor + self.telemetry = telemetry } // MARK: - `WritableFile` orchestration @@ -79,7 +79,7 @@ internal class FilesOrchestrator { return lastFile } } catch { - internalMonitor?.sdkLogger.warn("Failed to reuse last writable file", error: error) + telemetry?.error("Failed to reuse last writable file", error: error) } } @@ -113,7 +113,7 @@ internal class FilesOrchestrator { return fileIsOldEnough ? oldestFile : nil } catch { - internalMonitor?.sdkLogger.error("Failed to obtain readable file", error: error) + telemetry?.error("Failed to obtain readable file", error: error) return nil } } @@ -122,7 +122,7 @@ internal class FilesOrchestrator { do { try readableFile.delete() } catch { - internalMonitor?.sdkLogger.error("Failed to delete file", error: error) + telemetry?.error("Failed to delete file", error: error) } } @@ -130,7 +130,7 @@ internal class FilesOrchestrator { do { try directory.deleteAllFiles() } catch { - internalMonitor?.sdkLogger.error("Failed to delete all readable file", error: error) + telemetry?.error("Failed to delete all readable file", error: error) } } diff --git a/Sources/Datadog/Core/Persistence/Migrating/DataMigrator.swift b/Sources/Datadog/Core/Persistence/Migrating/DataMigrator.swift index b125ee6fe3..b62ca36f3c 100644 --- a/Sources/Datadog/Core/Persistence/Migrating/DataMigrator.swift +++ b/Sources/Datadog/Core/Persistence/Migrating/DataMigrator.swift @@ -12,13 +12,13 @@ internal protocol DataMigrator { internal struct DataMigratorFactory { /// Data directories for the feature. let directories: FeatureDirectories - var internalMonitor: InternalMonitor? = nil + var telemetry: Telemetry? = nil /// Resolves migrator to use when the SDK is started. func resolveInitialMigrator() -> DataMigrator { return DeleteAllDataMigrator( directory: directories.unauthorized, - internalMonitor: internalMonitor + telemetry: telemetry ) } @@ -28,13 +28,13 @@ internal struct DataMigratorFactory { case (.pending, .notGranted): return DeleteAllDataMigrator( directory: directories.unauthorized, - internalMonitor: internalMonitor + telemetry: telemetry ) case (.pending, .granted): return MoveDataMigrator( sourceDirectory: directories.unauthorized, destinationDirectory: directories.authorized, - internalMonitor: internalMonitor + telemetry: telemetry ) default: return nil diff --git a/Sources/Datadog/Core/Persistence/Migrating/DeleteAllDataMigrator.swift b/Sources/Datadog/Core/Persistence/Migrating/DeleteAllDataMigrator.swift index 5627fcf305..e26b46085f 100644 --- a/Sources/Datadog/Core/Persistence/Migrating/DeleteAllDataMigrator.swift +++ b/Sources/Datadog/Core/Persistence/Migrating/DeleteAllDataMigrator.swift @@ -9,13 +9,13 @@ import Foundation /// Data migrator which deletes all files in given directory. internal struct DeleteAllDataMigrator: DataMigrator { let directory: Directory - var internalMonitor: InternalMonitor? = nil + var telemetry: Telemetry? = nil func migrate() { do { try directory.deleteAllFiles() } catch { - internalMonitor?.sdkLogger.error( + telemetry?.error( "Failed to use `DeleteAllDataMigrator` in directory \(directory.url)", error: error ) } diff --git a/Sources/Datadog/Core/Persistence/Migrating/MoveDataMigrator.swift b/Sources/Datadog/Core/Persistence/Migrating/MoveDataMigrator.swift index a29ce60960..a9938dce3c 100644 --- a/Sources/Datadog/Core/Persistence/Migrating/MoveDataMigrator.swift +++ b/Sources/Datadog/Core/Persistence/Migrating/MoveDataMigrator.swift @@ -11,13 +11,13 @@ import Foundation internal struct MoveDataMigrator: DataMigrator { let sourceDirectory: Directory let destinationDirectory: Directory - var internalMonitor: InternalMonitor? = nil + var telemetry: Telemetry? = nil func migrate() { do { try sourceDirectory.moveAllFiles(to: destinationDirectory) } catch { - internalMonitor?.sdkLogger.error( + telemetry?.error( """ 🔥 Failed to use `MoveDataMigrator` for source directory \(sourceDirectory.url) and destination directory \(destinationDirectory.url) diff --git a/Sources/Datadog/Core/Persistence/Reading/FileReader.swift b/Sources/Datadog/Core/Persistence/Reading/FileReader.swift index 421dd4adc8..92dc3efcc4 100644 --- a/Sources/Datadog/Core/Persistence/Reading/FileReader.swift +++ b/Sources/Datadog/Core/Persistence/Reading/FileReader.swift @@ -13,7 +13,7 @@ internal final class FileReader: Reader { /// Orchestrator producing reference to readable file. private let orchestrator: FilesOrchestrator private let encryption: DataEncryption? - private let internalMonitor: InternalMonitor? + private let telemetry: Telemetry? /// Files marked as read. private var filesRead: Set = [] @@ -22,12 +22,12 @@ internal final class FileReader: Reader { dataFormat: DataFormat, orchestrator: FilesOrchestrator, encryption: DataEncryption? = nil, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { self.dataFormat = dataFormat self.orchestrator = orchestrator self.encryption = encryption - self.internalMonitor = internalMonitor + self.telemetry = telemetry } // MARK: - Reading batches @@ -42,7 +42,7 @@ internal final class FileReader: Reader { let batchData = dataFormat.prefixData + fileData + dataFormat.suffixData return Batch(data: batchData, file: file) } catch { - internalMonitor?.sdkLogger.error("Failed to read data from file", error: error) + telemetry?.error("Failed to read data from file", error: error) return nil } } diff --git a/Sources/Datadog/Core/Persistence/Writing/FileWriter.swift b/Sources/Datadog/Core/Persistence/Writing/FileWriter.swift index 3d374b6d1e..b61cab5f8f 100644 --- a/Sources/Datadog/Core/Persistence/Writing/FileWriter.swift +++ b/Sources/Datadog/Core/Persistence/Writing/FileWriter.swift @@ -15,19 +15,19 @@ internal final class FileWriter: Writer { /// JSON encoder used to encode data. private let jsonEncoder: JSONEncoder private let encryption: DataEncryption? - private let internalMonitor: InternalMonitor? + private let telemetry: Telemetry? init( dataFormat: DataFormat, orchestrator: FilesOrchestrator, encryption: DataEncryption? = nil, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { self.dataFormat = dataFormat self.orchestrator = orchestrator self.jsonEncoder = .default() self.encryption = encryption - self.internalMonitor = internalMonitor + self.telemetry = telemetry } // MARK: - Writing data @@ -45,7 +45,7 @@ internal final class FileWriter: Writer { try file.append(data: data) } catch { userLogger.error("🔥 Failed to write data: \(error)") - internalMonitor?.sdkLogger.error("Failed to write data to file", error: error) + telemetry?.error("Failed to write data to file", error: error) } } diff --git a/Sources/Datadog/Core/System/Time/ServerDateProvider.swift b/Sources/Datadog/Core/System/Time/ServerDateProvider.swift index 2e16bf3bcf..8333e7e934 100644 --- a/Sources/Datadog/Core/System/Time/ServerDateProvider.swift +++ b/Sources/Datadog/Core/System/Time/ServerDateProvider.swift @@ -20,11 +20,7 @@ internal class NTPServerDateProvider: ServerDateProvider { /// Server offset publisher. private let publisher: ValuePublisher = ValuePublisher(initialValue: nil) - /// Monitor collecting Kronos telemetry - only enabled if Internal Monitoring is configured. - private let kronosMonitor: KronosMonitor? - - init(kronosMonitor: KronosMonitor? = nil) { - self.kronosMonitor = kronosMonitor + init() { } /// Returns the server time offset or `nil` if not yet determined. @@ -36,7 +32,6 @@ internal class NTPServerDateProvider: ServerDateProvider { func synchronize(with pool: String, completion: @escaping (TimeInterval?) -> Void) { KronosClock.sync( from: pool, - monitor: kronosMonitor, first: { [weak self] _, offset in self?.publisher.publishAsync(offset) }, diff --git a/Sources/Datadog/Core/Telemetry.swift b/Sources/Datadog/Core/Telemetry.swift index 90e8abc81d..6a20a84b04 100644 --- a/Sources/Datadog/Core/Telemetry.swift +++ b/Sources/Datadog/Core/Telemetry.swift @@ -41,4 +41,13 @@ extension Telemetry { func error(_ message: String, stack: String? = nil) { error(message, kind: nil, stack: stack) } + + /// Collect execution error. + /// + /// - Parameters: + /// - message: The error message. + /// - stack: The stack trace. + func error(_ message: String, error: Error?) { + self.error(message, kind: "swift", stack: String(describing: error)) + } } diff --git a/Sources/Datadog/Core/Upload/DataUploadStatus.swift b/Sources/Datadog/Core/Upload/DataUploadStatus.swift index fef7923936..3a153ab288 100644 --- a/Sources/Datadog/Core/Upload/DataUploadStatus.swift +++ b/Sources/Datadog/Core/Upload/DataUploadStatus.swift @@ -63,7 +63,7 @@ internal struct DataUploadStatus { let userErrorMessage: String? /// An optional error logged to the Internal Monitoring feature (if it's enabled). - let internalMonitoringError: (message: String, error: Error?, attributes: [String: String]?)? + let telemetryError: (message: String, error: Error?)? } extension DataUploadStatus { @@ -76,7 +76,7 @@ extension DataUploadStatus { needsRetry: statusCode.needsRetry, userDebugDescription: "[response code: \(httpResponse.statusCode) (\(statusCode)), request ID: \(ddRequestID ?? "(???)")]", userErrorMessage: statusCode == .unauthorized ? "⚠️ The client token you provided seems to be invalid." : nil, - internalMonitoringError: createInternalMonitoringErrorIfNeeded(for: httpResponse.statusCode, requestID: ddRequestID) + telemetryError: createInternalMonitoringErrorIfNeeded(for: httpResponse.statusCode, requestID: ddRequestID) ) } @@ -85,18 +85,17 @@ extension DataUploadStatus { needsRetry: true, // retry this upload as it failed due to network transport isse userDebugDescription: "[error: \(DDError(error: networkError).message)]", // e.g. "[error: A data connection is not currently allowed]" userErrorMessage: nil, // nothing actionable for the user - internalMonitoringError: createInternalMonitoringErrorIfNeeded(for: networkError) + telemetryError: createInternalMonitoringErrorIfNeeded(for: networkError) ) } } -// MARK: - Internal Monitoring +// MARK: - Internal Telemtry -#if DD_SDK_ENABLE_INTERNAL_MONITORING /// Looks at the `statusCode` and produces error for Internal Monitoring feature if anything is going wrong. private func createInternalMonitoringErrorIfNeeded( for statusCode: Int, requestID: String? -) -> (message: String, error: Error?, attributes: [String: String]?)? { +) -> (message: String, error: Error?)? { guard let responseStatusCode = HTTPResponseStatusCode(rawValue: statusCode) else { // If status code is unexpected, do not produce an error for Internal Monitoring - otherwise monitoring may // become too verbose for old installations if we introduce a new status code in the API. @@ -113,9 +112,8 @@ private func createInternalMonitoringErrorIfNeeded( case .badRequest, .payloadTooLarge, .tooManyRequests, .requestTimeout: // These codes mean that something wrong is happening either in the SDK or on the server - produce an error. return ( - message: "Data upload finished with status code: \(statusCode)", - error: nil, - attributes: ["dd_request_id": requestID ?? "(???)"] + message: "Data upload finished with status code: \(statusCode), dd_request_id: \(requestID ?? "(???)")", + error: nil ) case .unexpected: return nil @@ -141,24 +139,11 @@ private let ignoredNSURLErrorCodes = Set([ /// Looks at the `networkError` and produces error for Internal Monitoring feature if anything is going wrong. private func createInternalMonitoringErrorIfNeeded( for networkError: Error -) -> (message: String, error: Error?, attributes: [String: String]?)? { +) -> (message: String, error: Error?)? { let nsError = networkError as NSError if nsError.domain == NSURLErrorDomain && !ignoredNSURLErrorCodes.contains(nsError.code) { - return (message: "Data upload finished with error", error: nsError, attributes: nil) + return (message: "Data upload finished with error", error: nsError) } else { return nil } } -#else -private func createInternalMonitoringErrorIfNeeded( - for statusCode: Int, requestID: String? -) -> (message: String, error: Error?, attributes: [String: String]?)? { - return nil -} - -private func createInternalMonitoringErrorIfNeeded( - for networkError: Error -) -> (message: String, error: Error?, attributes: [String: String]?)? { - return nil -} -#endif diff --git a/Sources/Datadog/Core/Upload/DataUploadWorker.swift b/Sources/Datadog/Core/Upload/DataUploadWorker.swift index f0fc4dd4d4..6de3c0d680 100644 --- a/Sources/Datadog/Core/Upload/DataUploadWorker.swift +++ b/Sources/Datadog/Core/Upload/DataUploadWorker.swift @@ -24,7 +24,7 @@ internal class DataUploadWorker: DataUploadWorkerType { /// Name of the feature this worker is performing uploads for. private let featureName: String /// A monitor reporting errors through Internal Monitoring feature (if enabled). - private let internalMonitor: InternalMonitor? + private let telemetry: Telemetry? /// Delay used to schedule consecutive uploads. private var delay: Delay @@ -39,7 +39,7 @@ internal class DataUploadWorker: DataUploadWorkerType { uploadConditions: DataUploadConditions, delay: Delay, featureName: String, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { self.queue = queue self.fileReader = fileReader @@ -47,7 +47,7 @@ internal class DataUploadWorker: DataUploadWorkerType { self.dataUploader = dataUploader self.delay = delay self.featureName = featureName - self.internalMonitor = internalMonitor + self.telemetry = telemetry let uploadWork = DispatchWorkItem { [weak self] in guard let self = self else { @@ -81,8 +81,8 @@ internal class DataUploadWorker: DataUploadWorkerType { } // Send internal monitoring error (if any and if Internal Monitoring is enabled) - if let sdkError = uploadStatus.internalMonitoringError { - self.internalMonitor?.sdkLogger.error(sdkError.message, error: sdkError.error, attributes: sdkError.attributes) + if let telemetryError = uploadStatus.telemetryError { + self.telemetry?.error(telemetryError.message, error: telemetryError.error) } } else { let batchLabel = nextBatch != nil ? "YES" : (isSystemReady ? "NO" : "NOT CHECKED") diff --git a/Sources/Datadog/Core/Upload/DataUploader.swift b/Sources/Datadog/Core/Upload/DataUploader.swift index aa8a1ca6c4..4cf496eb6a 100644 --- a/Sources/Datadog/Core/Upload/DataUploader.swift +++ b/Sources/Datadog/Core/Upload/DataUploader.swift @@ -14,7 +14,7 @@ internal protocol DataUploaderType { /// Synchronously uploads data to server using `HTTPClient`. internal final class DataUploader: DataUploaderType { /// An unreachable upload status - only meant to satisfy the compiler. - private static let unreachableUploadStatus = DataUploadStatus(needsRetry: false, userDebugDescription: "", userErrorMessage: nil, internalMonitoringError: nil) + private static let unreachableUploadStatus = DataUploadStatus(needsRetry: false, userDebugDescription: "", userErrorMessage: nil, telemetryError: nil) private let httpClient: HTTPClient private let requestBuilder: RequestBuilder diff --git a/Sources/Datadog/Core/Upload/RequestBuilder.swift b/Sources/Datadog/Core/Upload/RequestBuilder.swift index 20a284ec47..bd4fe380fa 100644 --- a/Sources/Datadog/Core/Upload/RequestBuilder.swift +++ b/Sources/Datadog/Core/Upload/RequestBuilder.swift @@ -101,7 +101,7 @@ internal struct RequestBuilder { /// Computed HTTP headers (their value is different in succeeding requests). private let computedHeaders: [String: () -> String] /// A monitor reporting errors through Internal Monitoring feature (if enabled). - private let internalMonitor: InternalMonitor? + private let telemetry: Telemetry? // MARK: - Initialization @@ -109,7 +109,7 @@ internal struct RequestBuilder { url: URL, queryItems: [QueryItem], headers: [HTTPHeader], - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) @@ -131,7 +131,7 @@ internal struct RequestBuilder { self.url = urlComponents?.url ?? url self.precomputedHeaders = precomputedHeaders self.computedHeaders = computedHeaders - self.internalMonitor = internalMonitor + self.telemetry = telemetry } /// Creates `URLRequest` for uploading given `data` to Datadog. @@ -149,12 +149,12 @@ internal struct RequestBuilder { request.httpBody = body } else { request.httpBody = data - internalMonitor?.sdkLogger.warn( - "Failed to compress request payload", - attributes: [ - "url": url, - "uncompressed-size": data.count - ] + telemetry?.error( + """ + Failed to compress request payload + - url: \(url) + - uncompressed-size: \(data.count) + """ ) } diff --git a/Sources/Datadog/CrashReporting/CrashReporter.swift b/Sources/Datadog/CrashReporting/CrashReporter.swift index 6e9c93ed94..3e1f86b7ba 100644 --- a/Sources/Datadog/CrashReporting/CrashReporter.swift +++ b/Sources/Datadog/CrashReporting/CrashReporter.swift @@ -17,6 +17,9 @@ internal class CrashReporter { let crashContextProvider: CrashContextProviderType + /// Telemetry interface + let telemetry: Telemetry? + // MARK: - Initialization convenience init?(crashReportingFeature: CrashReportingFeature) { @@ -54,14 +57,16 @@ internal class CrashReporter { rumSessionStateProvider: crashReportingFeature.rumSessionStateProvider, appStateListener: crashReportingFeature.appStateListener ), - loggingOrRUMIntegration: availableLoggingOrRUMIntegration + loggingOrRUMIntegration: availableLoggingOrRUMIntegration, + telemetry: crashReportingFeature.telemetry ) } init( crashReportingPlugin: DDCrashReportingPluginType, crashContextProvider: CrashContextProviderType, - loggingOrRUMIntegration: CrashReportingIntegration + loggingOrRUMIntegration: CrashReportingIntegration, + telemetry: Telemetry? ) { self.queue = DispatchQueue( label: "com.datadoghq.crash-reporter", @@ -70,6 +75,7 @@ internal class CrashReporter { self.plugin = crashReportingPlugin self.loggingOrRUMIntegration = loggingOrRUMIntegration self.crashContextProvider = crashContextProvider + self.telemetry = telemetry // Inject current `CrashContext` self.inject(currentCrashContext: crashContextProvider.currentCrashContext) @@ -94,10 +100,6 @@ internal class CrashReporter { } userLogger.debug("Loaded pending crash report") -#if DD_SDK_ENABLE_INTERNAL_MONITORING - InternalMonitoringFeature.instance?.monitor.sdkLogger - .debug("Loaded pending crash report", attributes: availableCrashReport.diagnosticInfo) -#endif guard let crashContext = availableCrashReport.context.flatMap({ self.decode(crashContextData: $0) }) else { // `CrashContext` is malformed and and cannot be read. Return `true` to let the crash reporter @@ -141,8 +143,8 @@ internal class CrashReporter { Error details: \(error) """ ) - InternalMonitoringFeature.instance?.monitor.sdkLogger - .error("Failed to encode crash report context", error: error) + + telemetry?.error("Failed to encode crash report context", error: error) return nil } } @@ -159,12 +161,7 @@ internal class CrashReporter { Error details: \(error) """ ) -#if DD_SDK_ENABLE_INTERNAL_MONITORING - let contextUTF8String = String(data: crashContextData, encoding: .utf8) - let attributes = ["context_utf8_string": contextUTF8String ?? "none"] - InternalMonitoringFeature.instance?.monitor.sdkLogger - .error("Failed to decode crash report context", error: error, attributes: attributes) -#endif + telemetry?.error("Failed to decode crash report context", error: error) return nil } } diff --git a/Sources/Datadog/CrashReporting/CrashReportingFeature.swift b/Sources/Datadog/CrashReporting/CrashReportingFeature.swift index 7122cba183..9564472f25 100644 --- a/Sources/Datadog/CrashReporting/CrashReportingFeature.swift +++ b/Sources/Datadog/CrashReporting/CrashReportingFeature.swift @@ -36,10 +36,13 @@ internal final class CrashReportingFeature { let rumSessionStateProvider: ValuePublisher /// Publishes changes to app "foreground" / "background" state. let appStateListener: AppStateListening + /// Telemetry interface + let telemetry: Telemetry? init( configuration: FeaturesConfiguration.CrashReporting, - commonDependencies: FeaturesCommonDependencies + commonDependencies: FeaturesCommonDependencies, + telemetry: Telemetry? ) { self.configuration = configuration self.consentProvider = commonDependencies.consentProvider @@ -49,6 +52,7 @@ internal final class CrashReportingFeature { self.rumViewEventProvider = ValuePublisher(initialValue: nil) // `nil` by default, because there cannot be any RUM view at this ponit self.rumSessionStateProvider = ValuePublisher(initialValue: nil) // `nil` by default, because there cannot be any RUM session at this ponit self.appStateListener = commonDependencies.appStateListener + self.telemetry = telemetry } internal func deinitialize() { diff --git a/Sources/Datadog/CrashReporting/DDCrashReportingPluginType.swift b/Sources/Datadog/CrashReporting/DDCrashReportingPluginType.swift index aae17e1a4b..306e350f8d 100644 --- a/Sources/Datadog/CrashReporting/DDCrashReportingPluginType.swift +++ b/Sources/Datadog/CrashReporting/DDCrashReportingPluginType.swift @@ -146,13 +146,6 @@ public class DDCrashReport: NSObject { /// The last context injected through `inject(context:)` internal let context: Data? -#if DD_SDK_ENABLE_INTERNAL_MONITORING - /// Additional diagnostic information about the crash report, collected for `DatadogCrashReporting` observability. - /// Available only if internal monitoring is enabled, disabled by default. - /// See: `Datadog.Configuration.Builder.enableInternalMonitoring(clientToken:)`. - public var diagnosticInfo: [String: Encodable] = [:] -#endif - public init( date: Date?, type: String, diff --git a/Sources/Datadog/Datadog.swift b/Sources/Datadog/Datadog.swift index b9f812100d..815f47dc10 100644 --- a/Sources/Datadog/Datadog.swift +++ b/Sources/Datadog/Datadog.swift @@ -172,19 +172,11 @@ public class Datadog { throw ProgrammerError(description: "SDK is already initialized.") } - let kronosMonitor: KronosMonitor? -#if DD_SDK_ENABLE_INTERNAL_MONITORING - // Collect Kronos telemetry only if internal monitoring is compiled and enabled - kronosMonitor = configuration.internalMonitoring != nil ? KronosInternalMonitor() : nil -#else - kronosMonitor = nil -#endif - let consentProvider = ConsentProvider(initialConsent: initialTrackingConsent) let dateProvider = SystemDateProvider() let dateCorrector = DateCorrector( deviceDateProvider: dateProvider, - serverDateProvider: NTPServerDateProvider(kronosMonitor: kronosMonitor) + serverDateProvider: NTPServerDateProvider() ) let userInfoProvider = UserInfoProvider() let networkConnectionInfoProvider = NetworkConnectionInfoProvider() @@ -205,8 +197,7 @@ public class Datadog { userLogger = createSDKUserLogger(configuration: internalLoggerConfiguration) // Then, initialize features: - - var internalMonitoring: InternalMonitoringFeature? + var telemetry: Telemetry? var logging: LoggingFeature? var tracing: TracingFeature? var rum: RUMFeature? @@ -231,12 +222,27 @@ public class Datadog { encryption: configuration.common.encryption ) - if let internalMonitoringConfiguration = configuration.internalMonitoring { - internalMonitoring = InternalMonitoringFeature( - logDirectories: try obtainInternalMonitoringFeatureLogDirectories(), - configuration: internalMonitoringConfiguration, - commonDependencies: commonDependencies + if let rumConfiguration = configuration.rum { + telemetry = RUMTelemetry( + sdkVersion: configuration.common.sdkVersion, + applicationID: rumConfiguration.applicationID, + dateProvider: dateProvider, + dateCorrector: dateCorrector ) + + rum = RUMFeature( + directories: try obtainRUMFeatureDirectories(), + configuration: rumConfiguration, + commonDependencies: commonDependencies, + telemetry: telemetry + ) + + if let instrumentationConfiguration = rumConfiguration.instrumentation { + rumInstrumentation = RUMInstrumentation( + configuration: instrumentationConfiguration, + dateProvider: dateProvider + ) + } } if let loggingConfiguration = configuration.logging { @@ -244,7 +250,7 @@ public class Datadog { directories: try obtainLoggingFeatureDirectories(), configuration: loggingConfiguration, commonDependencies: commonDependencies, - internalMonitor: internalMonitoring?.monitor + telemetry: telemetry ) } @@ -255,29 +261,15 @@ public class Datadog { commonDependencies: commonDependencies, loggingFeatureAdapter: logging.flatMap { LoggingForTracingAdapter(loggingFeature: $0) }, tracingUUIDGenerator: DefaultTracingUUIDGenerator(), - internalMonitor: internalMonitoring?.monitor - ) - } - - if let rumConfiguration = configuration.rum { - rum = RUMFeature( - directories: try obtainRUMFeatureDirectories(), - configuration: rumConfiguration, - commonDependencies: commonDependencies, - internalMonitor: internalMonitoring?.monitor + telemetry: telemetry ) - if let instrumentationConfiguration = rumConfiguration.instrumentation { - rumInstrumentation = RUMInstrumentation( - configuration: instrumentationConfiguration, - dateProvider: dateProvider - ) - } } if let crashReportingConfiguration = configuration.crashReporting { crashReporting = CrashReportingFeature( configuration: crashReportingConfiguration, - commonDependencies: commonDependencies + commonDependencies: commonDependencies, + telemetry: telemetry ) } @@ -288,8 +280,6 @@ public class Datadog { ) } - InternalMonitoringFeature.instance = internalMonitoring - LoggingFeature.instance = logging TracingFeature.instance = tracing RUMFeature.instance = rum @@ -314,12 +304,6 @@ public class Datadog { Global.crashReporter = CrashReporter(crashReportingFeature: crashReportingFeature) Global.crashReporter?.sendCrashReportIfFound() } - - // If Internal Monitoring is enabled and Kronos internal monitor is configured, - // export result of NTP sync to IM. - if let internalMonitoringFeature = InternalMonitoringFeature.instance { - kronosMonitor?.export(to: internalMonitoringFeature.monitor) - } } internal init( @@ -336,7 +320,6 @@ public class Datadog { LoggingFeature.instance?.storage.clearAllData() TracingFeature.instance?.storage.clearAllData() RUMFeature.instance?.storage.clearAllData() - InternalMonitoringFeature.instance?.logsStorage.clearAllData() } /// Flushes all authorised data for each feature, tears down and deinitializes the SDK. @@ -357,8 +340,6 @@ public class Datadog { LoggingFeature.instance?.deinitialize() TracingFeature.instance?.deinitialize() RUMFeature.instance?.deinitialize() - - InternalMonitoringFeature.instance?.deinitialize() CrashReportingFeature.instance?.deinitialize() RUMInstrumentation.instance?.deinitialize() diff --git a/Sources/Datadog/DatadogConfiguration.swift b/Sources/Datadog/DatadogConfiguration.swift index 4c1f0067f4..5aa2a02f13 100644 --- a/Sources/Datadog/DatadogConfiguration.swift +++ b/Sources/Datadog/DatadogConfiguration.swift @@ -269,9 +269,6 @@ extension Datadog { private(set) var proxyConfiguration: [AnyHashable: Any]? private(set) var encryption: DataEncryption? - /// The client token autorizing internal monitoring data to be sent to Datadog org. - private(set) var internalMonitoringClientToken: String? - /// Creates the builder for configuring the SDK to work with RUM, Logging and Tracing features. /// - Parameter rumApplicationID: RUM Application ID obtained on Datadog website. /// - Parameter clientToken: the client token (generated for the RUM Application) obtained on Datadog website. @@ -341,8 +338,7 @@ extension Datadog { batchSize: .medium, uploadFrequency: .average, additionalConfiguration: [:], - proxyConfiguration: nil, - internalMonitoringClientToken: nil + proxyConfiguration: nil ) } @@ -723,8 +719,9 @@ extension Datadog { /// SWIFT_ACTIVE_COMPILATION_CONDITIONS = DD_SDK_ENABLE_INTERNAL_MONITORING /// /// - Parameter clientToken: the client token authorised for a Datadog org which should receive the SDK telemetry + @available(*, deprecated, message: "Internal monitoring has been removed") public func enableInternalMonitoring(clientToken: String) -> Builder { - configuration.internalMonitoringClientToken = clientToken + // no-op return self } #endif diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 6992e1b2c2..c87fe08cdc 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -58,7 +58,6 @@ public extension WKUserContentController { if let loggingFeature = LoggingFeature.instance { logEventConsumer = DefaultWebLogEventConsumer( userLogsWriter: loggingFeature.storage.writer, - internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, dateCorrector: loggingFeature.dateCorrector, rumContextProvider: globalRUMMonitor?.contextProvider, applicationVersion: loggingFeature.configuration.common.applicationVersion, diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index 0f01e55b2c..176a131d53 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -18,7 +18,6 @@ internal class DefaultWebLogEventConsumer: WebLogEventConsumer { } private let userLogsWriter: Writer - private let internalLogsWriter: Writer? private let dateCorrector: DateCorrectorType private let rumContextProvider: RUMContextProvider? private let applicationVersion: String @@ -37,14 +36,12 @@ internal class DefaultWebLogEventConsumer: WebLogEventConsumer { init( userLogsWriter: Writer, - internalLogsWriter: Writer?, dateCorrector: DateCorrectorType, rumContextProvider: RUMContextProvider?, applicationVersion: String, environment: String ) { self.userLogsWriter = userLogsWriter - self.internalLogsWriter = internalLogsWriter self.dateCorrector = dateCorrector self.rumContextProvider = rumContextProvider self.applicationVersion = applicationVersion @@ -74,10 +71,6 @@ internal class DefaultWebLogEventConsumer: WebLogEventConsumer { let jsonData = try JSONSerialization.data(withJSONObject: mutableEvent, options: []) let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData) - if internalLog { - internalLogsWriter?.write(value: encodableEvent) - } else { - userLogsWriter.write(value: encodableEvent) - } + userLogsWriter.write(value: encodableEvent) } } diff --git a/Sources/Datadog/InternalMonitoring/InternalMonitor.swift b/Sources/Datadog/InternalMonitoring/InternalMonitor.swift deleted file mode 100644 index e50ba74b2e..0000000000 --- a/Sources/Datadog/InternalMonitoring/InternalMonitor.swift +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2019-2020 Datadog, Inc. - */ - -import Foundation - -/// Bundles internal monitoring tools, adding observability to the `dd-sdk-ios`. -internal struct InternalMonitor { - /// The logger sending SDK monitoring & observability logs to Datadog org. - /// **It should be used wisely to only log critical and important events**. - let sdkLogger: Logger -} diff --git a/Sources/Datadog/InternalMonitoring/InternalMonitoringFeature.swift b/Sources/Datadog/InternalMonitoring/InternalMonitoringFeature.swift deleted file mode 100644 index b202d6810e..0000000000 --- a/Sources/Datadog/InternalMonitoring/InternalMonitoringFeature.swift +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2019-2020 Datadog, Inc. - */ - -import Foundation - -/// Obtains subdirectories in `/Library/Caches` where internal monitoring data is stored. -internal func obtainInternalMonitoringFeatureLogDirectories() throws -> FeatureDirectories { - let version = "v1" - return FeatureDirectories( - unauthorized: try Directory(withSubdirectoryPath: "com.datadoghq.im-logs/intermediate-\(version)"), - authorized: try Directory(withSubdirectoryPath: "com.datadoghq.im-logs/\(version)") - ) -} - -/// This feature provides observability for internal events happening in the SDK. All data collected by this feature -/// is sent to Datadog org, not to the customer's org. This feature is opt-in and requires specific configuration to be enabled. -/// It is never enabled by default. We do not collect any internal monitoring data for those who didn't explicitly opt-in for that by contacting Datadog. -/// -/// This feature uses Datadog logs for sending observability data. -/// -/// The `InternalMonitoringFeature` facade creates and owns components enabling the feature. -/// Bundles dependencies for monitoring-related components created later at runtime (i.e. internal `Logger`). -internal final class InternalMonitoringFeature { - /// Single, shared instance of `InternalMonitoringFeature`. - internal static var instance: InternalMonitoringFeature? - - /// Tells if the feature was enabled by the user in the SDK configuration. - static var isEnabled: Bool { instance != nil } - - // MARK: - Components - - static let featureName = "internal-monitoring" - /// NOTE: any change to data format requires updating the directory url to be unique - static let logsDataFormat = DataFormat(prefix: "[", suffix: "]", separator: ",") - - /// Log files storage. - let logsStorage: FeatureStorage - /// Logs upload worker. - let logsUpload: FeatureUpload - - /// Monitor bundling internal monitoring tools for `dd-sdk-ios` observability in Datadog org. - let monitor: InternalMonitor - - // MARK: - Initialization - - static func createLogsStorage(directories: FeatureDirectories, commonDependencies: FeaturesCommonDependencies) -> FeatureStorage { - return FeatureStorage( - featureName: InternalMonitoringFeature.featureName, - dataFormat: InternalMonitoringFeature.logsDataFormat, - directories: directories, - commonDependencies: commonDependencies, - // (!) Do not inject monitoring bundle, otherwise the feature will be monitoring itself - // leading to infinite processing loops. - internalMonitor: nil - ) - } - - static func createLogsUpload( - storage: FeatureStorage, - configuration: FeaturesConfiguration.InternalMonitoring, - commonDependencies: FeaturesCommonDependencies - ) -> FeatureUpload { - return FeatureUpload( - featureName: InternalMonitoringFeature.featureName, - storage: storage, - requestBuilder: RequestBuilder( - url: configuration.logsUploadURL, - queryItems: [ - .ddsource(source: configuration.common.source) - ], - headers: [ - .contentTypeHeader(contentType: .applicationJSON), - .userAgentHeader( - appName: configuration.common.applicationName, - appVersion: configuration.common.applicationVersion, - device: commonDependencies.mobileDevice - ), - .ddAPIKeyHeader(clientToken: configuration.clientToken), - .ddEVPOriginHeader(source: configuration.common.origin ?? configuration.common.source), - .ddEVPOriginVersionHeader(sdkVersion: configuration.common.sdkVersion), - .ddRequestIDHeader(), - ], - // (!) Do not inject monitoring bundle, otherwise the feature will be monitoring itself - // leading to infinite processing loops. - internalMonitor: nil - ), - commonDependencies: commonDependencies, - // (!) Do not inject monitoring bundle, otherwise the feature will be monitoring itself - // leading to infinite processing loops. - internalMonitor: nil - ) - } - - convenience init( - logDirectories: FeatureDirectories, - configuration: FeaturesConfiguration.InternalMonitoring, - commonDependencies: FeaturesCommonDependencies - ) { - let storage = InternalMonitoringFeature.createLogsStorage(directories: logDirectories, commonDependencies: commonDependencies) - let upload = InternalMonitoringFeature.createLogsUpload(storage: storage, configuration: configuration, commonDependencies: commonDependencies) - self.init( - storage: storage, - upload: upload, - configuration: configuration, - commonDependencies: commonDependencies - ) - } - - init( - storage: FeatureStorage, - upload: FeatureUpload, - configuration: FeaturesConfiguration.InternalMonitoring, - commonDependencies: FeaturesCommonDependencies - ) { - // Initialize stacks - self.logsStorage = storage - self.logsUpload = upload - - // Initialize internal monitor - let internalLogger = Logger( - logBuilder: LogEventBuilder( - sdkVersion: configuration.common.sdkVersion, - applicationVersion: configuration.common.applicationVersion, - environment: configuration.sdkEnvironment, - serviceName: configuration.sdkServiceName, - loggerName: configuration.loggerName, - userInfoProvider: UserInfoProvider(), // no-op to not associate user info with internal logs - networkConnectionInfoProvider: commonDependencies.networkConnectionInfoProvider, - carrierInfoProvider: commonDependencies.carrierInfoProvider, - dateCorrector: commonDependencies.dateCorrector, - logEventMapper: nil - ), - logOutput: LogFileOutput( - fileWriter: storage.writer, - rumErrorsIntegration: nil - ), - dateProvider: commonDependencies.dateProvider, - identifier: configuration.loggerName, - rumContextIntegration: nil, - activeSpanIntegration: nil - ) - - internalLogger.addAttribute(forKey: "application.name", value: configuration.common.applicationName) - internalLogger.addAttribute(forKey: "application.bundle-id", value: configuration.common.applicationBundleIdentifier) - - self.monitor = InternalMonitor(sdkLogger: internalLogger) - } - - internal func deinitialize() { - logsStorage.flushAndTearDown() - logsUpload.flushAndTearDown() - InternalMonitoringFeature.instance = nil - } -} diff --git a/Sources/Datadog/InternalMonitoring/Kronos/KronosMonitor.swift b/Sources/Datadog/InternalMonitoring/Kronos/KronosMonitor.swift deleted file mode 100644 index 66e52c04e8..0000000000 --- a/Sources/Datadog/InternalMonitoring/Kronos/KronosMonitor.swift +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2019-2020 Datadog, Inc. - */ - -import Foundation -import Network - -/// Telemetry monitor for `KronosClock`. -internal protocol KronosMonitor { - // MARK: - Clock sync - - func notifySyncStart(from pool: String) - func notifySyncEnd(serverOffset: TimeInterval?) - - // MARK: - DNS resolution - - func notifyResolveDNS(to addresses: [KronosInternetAddress]) - - // MARK: - IP querying - - func notifyStartQuerying(ip address: KronosInternetAddress, numberOfSamples: Int) - func notifyReceivePacket(from address: KronosInternetAddress, isValidSample: Bool) - func notifyEndQuerying(ip address: KronosInternetAddress) - - // MARK: - Telemetry Export - - func export(to exporter: InternalMonitor) -} - -#if DD_SDK_ENABLE_INTERNAL_MONITORING - -/// `KronosMonitor` for diagnosing `KronosClock` in Internal Monitoring. Not used when Internal Monitoring is disabled (by default). -/// It is only implemented to collect extra telemetry for troubleshooting https://github.com/DataDog/dd-sdk-ios/issues/647 -internal class KronosInternalMonitor: KronosMonitor { - struct SyncResult: Encodable { - /// The pool addres used by Kronos. - let pool: String - /// The server offset reported by Kronos. - let serverOffset: TimeInterval? - /// Stats for each IP resolved by Kronos DNS. - let ips: [String: IP] - - struct IP: Encodable { - let address: String? - let connectionDuration: TimeInterval? - let connectionCheckResult: IPConnectionCheckResult? - let succeededPacketsCount: Int - let failedPacketsCount: Int - } - } - - /// Used to gather stats on each IP during Kronos sync. - private struct IPStats: Encodable { - var connectionStart: Date? = nil - var connectionEnd: Date? = nil - var succeededPacketsCount = 0 - var failedPacketsCount = 0 - var checkResult: IPConnectionCheckResult? = nil - } - - /// Queue for synchronising Kronos and `IPConnectionMonitor` callbacks. - private let queue: DispatchQueue - /// Dispatch group used to synchronize Kronos and `IPConnectionMonitor` tasks. - /// It notifies readiness of all recorded details (collected asynchronously) so the `SyncResult` can be built and sent to Datadog. - private let dispatchGroup = DispatchGroup() - - /// The address of the pool being synchronised by `KronosClock`. - private var pool: String? = nil - /// The final server offset retrieved from `KronosClock` (can be `nil` if anything went wrong). - private var serverOffset: TimeInterval? = nil - /// Stats for each IP resolved from the `pool`. - private var statsByIP: [KronosInternetAddress: IPStats] = [:] - /// Connection monitor checking additional reachability for each IP resolved from the `pool`. Only available from iOS 14.2. - private let connectionMonitor: IPConnectionMonitorType? - - /// Internal Monitor for sending the `result` to Datadog. - private var exporter: InternalMonitor? = nil - - convenience init() { - let queue = DispatchQueue(label: "com.datadoghq.kronos-monitor", qos: .utility) - if #available(iOS 14.2, tvOS 14.2, *) { - self.init( - queue: queue, - connectionMonitor: IPConnectionMonitor(queue: queue) - ) - } else { - self.init( - queue: queue, - connectionMonitor: nil - ) - } - } - - init(queue: DispatchQueue, connectionMonitor: IPConnectionMonitorType?) { - self.queue = queue - self.connectionMonitor = connectionMonitor - - self.dispatchGroup.enter() // await Kronos completion - self.dispatchGroup.enter() // await exporter registration - - self.dispatchGroup.notify(queue: self.queue) { [weak self] in - self?.sendTelemetryToDatadog() - } - } - - // MARK: - Clock sync - - func notifySyncStart(from pool: String) { - queue.async { - self.pool = pool - } - } - - func notifySyncEnd(serverOffset: TimeInterval?) { - queue.async { - self.serverOffset = serverOffset - self.dispatchGroup.leave() // notify Kronos completion - } - } - - // MARK: - Exporting - - func export(to exporter: InternalMonitor) { - queue.async { - self.exporter = exporter - self.dispatchGroup.leave() // notify exporter registration - } - } - - // MARK: - DNS resolution - - func notifyResolveDNS(to addresses: [KronosInternetAddress]) { - queue.async { - addresses.forEach { ip in - self.statsByIP[ip] = IPStats() - - if let connectionMonitor = self.connectionMonitor { - self.dispatchGroup.enter() // await connection check result - connectionMonitor.checkConnection(to: ip) { [weak self] result in // completion is called on `queue` - self?.statsByIP[ip]?.checkResult = result - self?.dispatchGroup.leave() // notify connection check result - } - } - } - } - } - - // MARK: - IP querying - - func notifyStartQuerying(ip address: KronosInternetAddress, numberOfSamples: Int) { - queue.async { - self.statsByIP[address]?.connectionStart = Date() - } - } - - func notifyReceivePacket(from address: KronosInternetAddress, isValidSample: Bool) { - queue.async { - if isValidSample { - self.statsByIP[address]?.succeededPacketsCount += 1 - } else { - self.statsByIP[address]?.failedPacketsCount += 1 - } - } - } - - func notifyEndQuerying(ip address: KronosInternetAddress) { - queue.async { - self.statsByIP[address]?.connectionEnd = Date() - } - } - - // MARK: - Exporting telemetry to Internal Monitoring - - private func sendTelemetryToDatadog() { - guard let sdkLogger = exporter?.sdkLogger else { - return // cannot happen - } - - guard let pool = self.pool else { - sdkLogger.debug("Kronos pool was not registered in `KronosMonitor`") // cannot happen, but log it for sanity - return - } - - var ips: [String: SyncResult.IP] = [:] - statsByIP.forEach { address, stats in - let key = "ip\(ips.count)" - ips[key] = SyncResult.IP( - address: address.host, - connectionDuration: stats.connectionStart.flatMap { stats.connectionEnd?.timeIntervalSince($0) }, - connectionCheckResult: stats.checkResult, - succeededPacketsCount: stats.succeededPacketsCount, - failedPacketsCount: stats.failedPacketsCount - ) - } - - let syncResult = SyncResult( - pool: pool, - serverOffset: serverOffset, - ips: ips - ) - - let ipsFailedDueToLocalNetworkDenied = syncResult.ips.values.filter { $0.connectionCheckResult?.isLocalNetworkDenied ?? false } - - if !ipsFailedDueToLocalNetworkDenied.isEmpty { - // Send error, as this indicates the issue reported in https://github.com/DataDog/dd-sdk-ios/issues/647. - sdkLogger.error("Kronos sync to \(pool) was blocked on trying to connect to local network", attributes: ["sync-result": syncResult]) - } else if syncResult.serverOffset != nil { - // Send info - everything went fine - sdkLogger.info("Kronos resolved \(pool) with receiving server offset", attributes: ["sync-result": syncResult]) - } else { - // Send info - something went wrong, but it could be due to network unreachability or other env factors - sdkLogger.info("Kronos resolved \(pool) but received no server offset", attributes: ["sync-result": syncResult]) - } - } -} - -// MARK: - IPConnectionMonitor - -internal struct IPConnectionCheckResult: Encodable { - let isLocalNetworkDenied: Bool - let details: String -} - -/// Checks connection to certain IP. Not used when Internal Monitoring is disabled (by default). -/// It is only implemented to collect extra telemetry for troubleshooting https://github.com/DataDog/dd-sdk-ios/issues/647 -internal protocol IPConnectionMonitorType { - func checkConnection(to ip: KronosInternetAddress, resultCallback: @escaping (IPConnectionCheckResult) -> Void) -} - -@available(iOS 14.2, tvOS 14.2, *) -internal class IPConnectionMonitor: IPConnectionMonitorType { - /// Timeout for checking each connection. - private let timeout: TimeInterval = 20 - - private let queue: DispatchQueue - private var pendingConnections: [NWConnection] = [] - - init(queue: DispatchQueue) { - self.queue = queue - } - - func checkConnection(to ip: KronosInternetAddress, resultCallback: @escaping (IPConnectionCheckResult) -> Void) { - guard let host = ip.host else { - return - } - - let connection = NWConnection(host: .init(host), port: 123, using: .udp) - queue.async { self.pendingConnections.append(connection) } - queue.asyncAfter(deadline: .now() + timeout) { [weak self] in - guard let self = self else { - return - } - - if self.pendingConnections.contains(where: { $0 === connection }) { - self.cancelCheck(for: connection) - } - } - - connection.pathUpdateHandler = { [weak self] latestPath in - let checkResult: IPConnectionCheckResult - - switch latestPath.status { - case .unsatisfied: - // Here we check if the connection won't lead to reaching host in local network - // Ref.: 'How do I use the unsatisfied reason property?' - // https://developer.apple.com/forums/thread/663769 - switch latestPath.unsatisfiedReason { - case .localNetworkDenied: checkResult = .init(isLocalNetworkDenied: true, details: "unsatisfied: localNetworkDenied") - case .notAvailable: checkResult = .init(isLocalNetworkDenied: false, details: "unsatisfied: notAvailable") - case .cellularDenied: checkResult = .init(isLocalNetworkDenied: false, details: "unsatisfied: cellularDenied") - case .wifiDenied: checkResult = .init(isLocalNetworkDenied: false, details: "unsatisfied: wifiDenied") - @unknown default: checkResult = .init(isLocalNetworkDenied: false, details: "unsatisfied: unknown") - } - case .satisfied: checkResult = .init(isLocalNetworkDenied: false, details: "satisfied") - case .requiresConnection: checkResult = .init(isLocalNetworkDenied: false, details: "requiresConnection") - @unknown default: checkResult = .init(isLocalNetworkDenied: false, details: "unknown") - } - - resultCallback(checkResult) - self?.cancelCheck(for: connection) - } - - connection.start(queue: queue) - } - - private func cancelCheck(for connection: NWConnection) { - connection.cancel() - pendingConnections = pendingConnections.filter { $0 !== connection } - } -} - -#endif diff --git a/Sources/Datadog/Kronos/KronosClock.swift b/Sources/Datadog/Kronos/KronosClock.swift index 2fc49ba1f6..5623ad0f6e 100644 --- a/Sources/Datadog/Kronos/KronosClock.swift +++ b/Sources/Datadog/Kronos/KronosClock.swift @@ -73,20 +73,17 @@ internal struct KronosClock { /// - parameter pool: NTP pool that will be resolved into multiple NTP servers that will be used for /// the synchronization. /// - parameter samples: The number of samples to be acquired from each server (default 4). - /// - parameter monitor: Monitor collecting Kronos telemetry - only enabled if Internal Monitoring is configured. /// - parameter first: A closure that will be called after the first valid date is calculated. /// - parameter completion: A closure that will be called after _all_ the NTP calls are finished. static func sync( from pool: String = "time.apple.com", samples: Int = 4, - monitor: KronosMonitor? = nil, first: ((Date, TimeInterval) -> Void)? = nil, completion: ((Date?, TimeInterval?) -> Void)? = nil ) { self.loadFromDefaults() - monitor?.notifySyncStart(from: pool) - KronosNTPClient().query(pool: pool, numberOfSamples: samples, monitor: monitor) { offset, done, total in + KronosNTPClient().query(pool: pool, numberOfSamples: samples) { offset, done, total in if let offset = offset { self.stableTime = KronosTimeFreeze(offset: offset) @@ -96,7 +93,6 @@ internal struct KronosClock { } if done == total { - monitor?.notifySyncEnd(serverOffset: offset) completion?(self.now, offset) } } diff --git a/Sources/Datadog/Kronos/KronosNTPClient.swift b/Sources/Datadog/Kronos/KronosNTPClient.swift index 6c2f3abe67..0854d789e7 100644 --- a/Sources/Datadog/Kronos/KronosNTPClient.swift +++ b/Sources/Datadog/Kronos/KronosNTPClient.swift @@ -31,7 +31,6 @@ internal final class KronosNTPClient { /// - parameter numberOfSamples: The number of samples to be acquired from each server (default 4). /// - parameter maximumServers: The maximum number of servers to be queried (default 5). /// - parameter timeout: The individual timeout for each of the NTP operations. - /// - parameter monitor: Monitor collecting Kronos telemetry - only enabled if Internal Monitoring is configured. /// - parameter completion: A closure that will be response PDU on success or nil on error. func query( pool: String = "time.apple.com", @@ -40,15 +39,13 @@ internal final class KronosNTPClient { numberOfSamples: Int = kDefaultSamples, maximumServers: Int = kMaximumNTPServers, timeout: CFTimeInterval = kronosDefaultTimeout, - monitor: KronosMonitor? = nil, progress: @escaping (TimeInterval?, Int, Int) -> Void ) { var servers: [KronosInternetAddress: [KronosNTPPacket]] = [:] var completed: Int = 0 let queryIPAndStoreResult = { (address: KronosInternetAddress, totalQueries: Int) -> Void in - monitor?.notifyStartQuerying(ip: address, numberOfSamples: numberOfSamples) - self.query(ip: address, port: port, version: version, timeout: timeout, numberOfSamples: numberOfSamples, monitor: monitor) { packet in + self.query(ip: address, port: port, version: version, timeout: timeout, numberOfSamples: numberOfSamples) { packet in defer { completed += 1 @@ -76,8 +73,6 @@ internal final class KronosNTPClient { let totalServers = min(addresses.count, maximumServers) let addressesToQuery = Array(addresses[0 ..< totalServers]) - monitor?.notifyResolveDNS(to: addressesToQuery) - for address in addressesToQuery { queryIPAndStoreResult(address, totalServers * numberOfSamples) } @@ -91,7 +86,6 @@ internal final class KronosNTPClient { /// - parameter version: NTP version to use (default 3). /// - parameter timeout: Timeout on socket operations. /// - parameter numberOfSamples: The number of samples to be acquired from the server (default 4). - /// - parameter monitor: Monitor collecting Kronos telemetry - only enabled if Internal Monitoring is configured. /// - parameter completion: A closure that will be response PDU on success or nil on error. func query( ip: KronosInternetAddress, @@ -99,7 +93,6 @@ internal final class KronosNTPClient { version: Int8 = 3, timeout: CFTimeInterval = kronosDefaultTimeout, numberOfSamples: Int = kDefaultSamples, - monitor: KronosMonitor? = nil, completion: @escaping (KronosNTPPacket?) -> Void ) { var timer: Timer? @@ -107,7 +100,7 @@ internal final class KronosNTPClient { defer { // If we still have samples left; we'll keep querying the same server if numberOfSamples > 1 { - self.query(ip: ip, port: port, version: version, timeout: timeout, numberOfSamples: numberOfSamples - 1, monitor: monitor, completion: completion) + self.query(ip: ip, port: port, version: version, timeout: timeout, numberOfSamples: numberOfSamples - 1, completion: completion) } } timer?.invalidate() @@ -115,18 +108,10 @@ internal final class KronosNTPClient { let data = data, let PDU = try? KronosNTPPacket(data: data, destinationTime: destinationTime), PDU.isValidResponse() else { - monitor?.notifyReceivePacket(from: ip, isValidSample: false) - if numberOfSamples == 1 { - monitor?.notifyEndQuerying(ip: ip) - } completion(nil) return } - monitor?.notifyReceivePacket(from: ip, isValidSample: true) - if numberOfSamples == 1 { - monitor?.notifyEndQuerying(ip: ip) - } completion(PDU) } diff --git a/Sources/Datadog/Logging/LoggingFeature.swift b/Sources/Datadog/Logging/LoggingFeature.swift index 27228fbe42..078e012fd1 100644 --- a/Sources/Datadog/Logging/LoggingFeature.swift +++ b/Sources/Datadog/Logging/LoggingFeature.swift @@ -52,14 +52,14 @@ internal final class LoggingFeature { static func createStorage( directories: FeatureDirectories, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) -> FeatureStorage { return FeatureStorage( featureName: LoggingFeature.featureName, dataFormat: LoggingFeature.dataFormat, directories: directories, commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) } @@ -67,7 +67,7 @@ internal final class LoggingFeature { storage: FeatureStorage, configuration: FeaturesConfiguration.Logging, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) -> FeatureUpload { return FeatureUpload( featureName: LoggingFeature.featureName, @@ -89,10 +89,10 @@ internal final class LoggingFeature { .ddEVPOriginVersionHeader(sdkVersion: configuration.common.sdkVersion), .ddRequestIDHeader(), ], - internalMonitor: internalMonitor + telemetry: telemetry ), commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) } @@ -100,18 +100,18 @@ internal final class LoggingFeature { directories: FeatureDirectories, configuration: FeaturesConfiguration.Logging, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { let storage = LoggingFeature.createStorage( directories: directories, commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) let upload = LoggingFeature.createUpload( storage: storage, configuration: configuration, commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) self.init( storage: storage, diff --git a/Sources/Datadog/RUM/RUMFeature.swift b/Sources/Datadog/RUM/RUMFeature.swift index 806d271630..2b9fc5aa70 100644 --- a/Sources/Datadog/RUM/RUMFeature.swift +++ b/Sources/Datadog/RUM/RUMFeature.swift @@ -64,14 +64,14 @@ internal final class RUMFeature { static func createStorage( directories: FeatureDirectories, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) -> FeatureStorage { return FeatureStorage( featureName: RUMFeature.featureName, dataFormat: RUMFeature.dataFormat, directories: directories, commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) } @@ -79,7 +79,7 @@ internal final class RUMFeature { storage: FeatureStorage, configuration: FeaturesConfiguration.RUM, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) -> FeatureUpload { return FeatureUpload( featureName: RUMFeature.featureName, @@ -109,10 +109,10 @@ internal final class RUMFeature { .ddEVPOriginVersionHeader(sdkVersion: configuration.common.sdkVersion), .ddRequestIDHeader(), ], - internalMonitor: internalMonitor + telemetry: telemetry ), commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) } @@ -120,7 +120,7 @@ internal final class RUMFeature { directories: FeatureDirectories, configuration: FeaturesConfiguration.RUM, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { let eventsMapper = RUMEventsMapper( viewEventMapper: configuration.viewEventMapper, @@ -128,18 +128,18 @@ internal final class RUMFeature { resourceEventMapper: configuration.resourceEventMapper, actionEventMapper: configuration.actionEventMapper, longTaskEventMapper: configuration.longTaskEventMapper, - internalMonitor: internalMonitor + telemetry: telemetry ) let storage = RUMFeature.createStorage( directories: directories, commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) let upload = RUMFeature.createUpload( storage: storage, configuration: configuration, commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) self.init( eventsMapper: eventsMapper, diff --git a/Sources/Datadog/RUM/RUMVitals/VitalCPUReader.swift b/Sources/Datadog/RUM/RUMVitals/VitalCPUReader.swift index bf353def01..b1985bd61a 100644 --- a/Sources/Datadog/RUM/RUMVitals/VitalCPUReader.swift +++ b/Sources/Datadog/RUM/RUMVitals/VitalCPUReader.swift @@ -67,9 +67,9 @@ internal class VitalCPUReader: SamplingBasedVitalReader { // as its Swift interface doesn't have integer values // // NOTE: RUMM-1276 consider using sdkLogger.errorOnce(...) to avoid flooding - InternalMonitoringFeature.instance?.monitor.sdkLogger.error( - "CPU Vital cannot be read! Error code: \(result)" - ) +// InternalMonitoringFeature.instance?.monitor.sdkLogger.error( +// "CPU Vital cannot be read! Error code: \(result)" +// ) return nil } diff --git a/Sources/Datadog/RUM/Scrubbing/RUMEventsMapper.swift b/Sources/Datadog/RUM/Scrubbing/RUMEventsMapper.swift index 7059542b75..de13698eaa 100644 --- a/Sources/Datadog/RUM/Scrubbing/RUMEventsMapper.swift +++ b/Sources/Datadog/RUM/Scrubbing/RUMEventsMapper.swift @@ -19,7 +19,7 @@ internal struct RUMEventsMapper { let resourceEventMapper: RUMResourceEventMapper? let actionEventMapper: RUMActionEventMapper? let longTaskEventMapper: RUMLongTaskEventMapper? - var internalMonitor: InternalMonitor? = nil + var telemetry: Telemetry? = nil // MARK: - EventMapper @@ -43,7 +43,7 @@ internal struct RUMEventsMapper { case let event as RUMLongTaskEvent: return map(event: event, using: longTaskEventMapper) as? T default: - internalMonitor?.sdkLogger.critical("No `RUMEventMapper` is registered for \(type(of: event))") + telemetry?.error("No `RUMEventMapper` is registered for \(type(of: event))") return event } } diff --git a/Sources/Datadog/Tracer.swift b/Sources/Datadog/Tracer.swift index 2221101f3c..44f5cb58c5 100644 --- a/Sources/Datadog/Tracer.swift +++ b/Sources/Datadog/Tracer.swift @@ -107,7 +107,8 @@ public class Tracer: OTTracer { dateCorrector: tracingFeature.dateCorrector, source: tracingFeature.configuration.common.source, origin: tracingFeature.configuration.common.origin , - eventsMapper: tracingFeature.configuration.spanEventMapper + eventsMapper: tracingFeature.configuration.spanEventMapper, + telemetry: tracingFeature.telemetry ), spanOutput: SpanFileOutput( fileWriter: tracingFeature.storage.writer, diff --git a/Sources/Datadog/Tracing/Span/SpanEventBuilder.swift b/Sources/Datadog/Tracing/Span/SpanEventBuilder.swift index 499cebcc57..e22bccda30 100644 --- a/Sources/Datadog/Tracing/Span/SpanEventBuilder.swift +++ b/Sources/Datadog/Tracing/Span/SpanEventBuilder.swift @@ -28,6 +28,8 @@ internal struct SpanEventBuilder { let origin: String? /// Span events mapper configured by the user, `nil` if not set. let eventsMapper: SpanEventMapper? + /// Telemetry interface + let telemetry: Telemetry? func createSpanEvent( traceID: TracingUUID, @@ -129,7 +131,7 @@ internal struct SpanEventBuilder { codingPath: [], debugDescription: "Failed to use temporary array container when encoding span tag '\(key)' to JSON string." ) - InternalMonitoringFeature.instance?.monitor.sdkLogger.error(encodingContext.debugDescription) + telemetry?.error(encodingContext.debugDescription) throw EncodingError.invalidValue(encodable.value, encodingContext) } @@ -143,7 +145,7 @@ internal struct SpanEventBuilder { codingPath: [], debugDescription: "Failed to read utf-8 JSON data when encoding span tag '\(key)' to JSON string." ) - InternalMonitoringFeature.instance?.monitor.sdkLogger.error(encodingContext.debugDescription) + telemetry?.error(encodingContext.debugDescription) throw EncodingError.invalidValue(encodable.value, encodingContext) } } catch let error { diff --git a/Sources/Datadog/Tracing/TracingFeature.swift b/Sources/Datadog/Tracing/TracingFeature.swift index e1a20d2a1c..e4dfaabb60 100644 --- a/Sources/Datadog/Tracing/TracingFeature.swift +++ b/Sources/Datadog/Tracing/TracingFeature.swift @@ -42,6 +42,7 @@ internal final class TracingFeature { let userInfoProvider: UserInfoProvider let networkConnectionInfoProvider: NetworkConnectionInfoProviderType let carrierInfoProvider: CarrierInfoProviderType + let telemetry: Telemetry? // MARK: - Components @@ -59,14 +60,14 @@ internal final class TracingFeature { static func createStorage( directories: FeatureDirectories, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) -> FeatureStorage { return FeatureStorage( featureName: TracingFeature.featureName, dataFormat: TracingFeature.dataFormat, directories: directories, commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) } @@ -74,7 +75,7 @@ internal final class TracingFeature { storage: FeatureStorage, configuration: FeaturesConfiguration.Tracing, commonDependencies: FeaturesCommonDependencies, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) -> FeatureUpload { return FeatureUpload( featureName: TracingFeature.featureName, @@ -94,10 +95,10 @@ internal final class TracingFeature { .ddEVPOriginVersionHeader(sdkVersion: configuration.common.sdkVersion), .ddRequestIDHeader(), ], - internalMonitor: internalMonitor + telemetry: telemetry ), commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) } @@ -107,18 +108,18 @@ internal final class TracingFeature { commonDependencies: FeaturesCommonDependencies, loggingFeatureAdapter: LoggingForTracingAdapter?, tracingUUIDGenerator: TracingUUIDGenerator, - internalMonitor: InternalMonitor? = nil + telemetry: Telemetry? = nil ) { let storage = TracingFeature.createStorage( directories: directories, commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) let upload = TracingFeature.createUpload( storage: storage, configuration: configuration, commonDependencies: commonDependencies, - internalMonitor: internalMonitor + telemetry: telemetry ) self.init( storage: storage, @@ -126,7 +127,8 @@ internal final class TracingFeature { configuration: configuration, commonDependencies: commonDependencies, loggingFeatureAdapter: loggingFeatureAdapter, - tracingUUIDGenerator: tracingUUIDGenerator + tracingUUIDGenerator: tracingUUIDGenerator, + telemetry: telemetry ) } @@ -136,7 +138,8 @@ internal final class TracingFeature { configuration: FeaturesConfiguration.Tracing, commonDependencies: FeaturesCommonDependencies, loggingFeatureAdapter: LoggingForTracingAdapter?, - tracingUUIDGenerator: TracingUUIDGenerator + tracingUUIDGenerator: TracingUUIDGenerator, + telemetry: Telemetry? ) { // Configuration self.configuration = configuration @@ -151,6 +154,7 @@ internal final class TracingFeature { self.userInfoProvider = commonDependencies.userInfoProvider self.networkConnectionInfoProvider = commonDependencies.networkConnectionInfoProvider self.carrierInfoProvider = commonDependencies.carrierInfoProvider + self.telemetry = telemetry // Initialize stacks self.storage = storage diff --git a/Sources/DatadogCrashReporting/PLCrashReporterIntegration/PLCrashReporterIntegration.swift b/Sources/DatadogCrashReporting/PLCrashReporterIntegration/PLCrashReporterIntegration.swift index 1290f309ac..5cd5f71621 100644 --- a/Sources/DatadogCrashReporting/PLCrashReporterIntegration/PLCrashReporterIntegration.swift +++ b/Sources/DatadogCrashReporting/PLCrashReporterIntegration/PLCrashReporterIntegration.swift @@ -47,11 +47,6 @@ internal final class PLCrashReporterIntegration: ThirdPartyCrashReporter { let crashReportData = try crashReporter.loadPendingCrashReportDataAndReturnError() let crashReport = try PLCrashReport(data: crashReportData) let ddCrashReport = try builder.createDDCrashReport(from: crashReport) -#if DD_SDK_ENABLE_INTERNAL_MONITORING - ddCrashReport.diagnosticInfo = [ - "diagnostic-info": PLCrashReportDiagnosticInfo(crashReport) - ] -#endif return ddCrashReport } @@ -63,54 +58,3 @@ internal final class PLCrashReporterIntegration: ThirdPartyCrashReporter { try crashReporter.purgePendingCrashReportAndReturnError() } } - -#if DD_SDK_ENABLE_INTERNAL_MONITORING -/// Diagnostic information about the crash report, collected for `DatadogCrashReporting` observability. -/// Available only if internal monitoring is enabled, disabled by default. -/// See: `Datadog.Configuration.Builder.enableInternalMonitoring(clientToken:)`. -private struct PLCrashReportDiagnosticInfo: Encodable { - private let numberOfImages: Int - private let numberOfSystemImages: Int - private let numberOfUserImages: Int - private let hasCrashDate: Bool - private let numberOfThreads: Int - private let numberOfStackFramesPerThread: [String: Int] - private let numberOfStackFramesInCrashedThread: Int - - init(_ crashReport: PLCrashReport) { - let images = crashReport.images?.compactMap { $0 as? PLCrashReportBinaryImageInfo } - self.numberOfImages = images?.count ?? -1 - - var userImagesCount = 0 - var systemImagesCount = 0 - images?.forEach { image in - if (image.imageName ?? "").hasPrefix("/private/var") { - // e.g. `/private/var/containers/Bundle/Application//Example.app/Frameworks/.framework/` - userImagesCount += 1 - } else { - // e.g. `/usr/lib/libobjc-trampolines.dylib` or `/System/Library/PrivateFrameworks/AssertionServices.framework/AssertionServices` - systemImagesCount += 1 - } - } - self.numberOfUserImages = userImagesCount - self.numberOfSystemImages = systemImagesCount - - self.hasCrashDate = crashReport.systemInfo?.timestamp != nil - - let threads = crashReport.threads?.compactMap { $0 as? PLCrashReportThreadInfo } - self.numberOfThreads = threads?.count ?? -1 - - var numberOfStackFramesInCrashedThread = -1 - var stackFramesByThread: [String: Int] = [:] - threads?.forEach { thread in - stackFramesByThread["thread-\(thread.threadNumber)"] = thread.stackFrames?.count ?? -1 - if thread.crashed { - numberOfStackFramesInCrashedThread = thread.stackFrames?.count ?? -1 - } - } - self.numberOfStackFramesPerThread = stackFramesByThread - self.numberOfStackFramesInCrashedThread = numberOfStackFramesInCrashedThread - } -} - -#endif diff --git a/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift b/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift index 5127fed9f8..7c2367ca8d 100644 --- a/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift +++ b/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift @@ -158,7 +158,6 @@ class FeaturesConfigurationTests: XCTestCase { XCTAssertEqual(configuration.logging?.clientToken, clientToken) XCTAssertEqual(configuration.tracing?.clientToken, clientToken) XCTAssertEqual(configuration.rum?.clientToken, clientToken) - XCTAssertNotEqual(configuration.internalMonitoring?.clientToken, clientToken) } func testEndpoint() throws { @@ -632,45 +631,6 @@ class FeaturesConfigurationTests: XCTestCase { ) } - // MARK: - Internal Monitoring Configuration Tests - - func testWhenInternalMonitoringIsDisabled() throws { - XCTAssertNil( - try FeaturesConfiguration(configuration: .mockWith(internalMonitoringClientToken: nil), appContext: .mockAny()).internalMonitoring, - "Feature configuration should not be available if the feature is disabled" - ) - } - - func testWhenInternalMonitoringClientTokenIsSet_thenInternalMonitoringConfigurationIsEnabled() throws { - // When - let internalMonitoringClientToken: String = .mockRandom(among: "abcdef") - let featuresClientToken: String = .mockRandom(among: "ghijkl") - let featuresConfiguration = try FeaturesConfiguration( - configuration: .mockWith( - clientToken: featuresClientToken, - loggingEnabled: true, - tracingEnabled: true, - rumEnabled: true, - internalMonitoringClientToken: internalMonitoringClientToken - ), - appContext: .mockAny() - ) - - // Then - let configuration = try XCTUnwrap(featuresConfiguration.internalMonitoring) - XCTAssertEqual(configuration.common, featuresConfiguration.common) - XCTAssertEqual(configuration.sdkServiceName, "dd-sdk-ios", "Internal monitoring data should be available under \"service:dd-sdk-ios\"") - XCTAssertEqual(configuration.sdkEnvironment, "prod", "Internal monitoring data should be available under \"env:prod\"") - XCTAssertEqual( - configuration.logsUploadURL.absoluteString, - "https://logs.browser-intake-datadoghq.com/api/v2/logs" - ) - XCTAssertEqual(configuration.clientToken, internalMonitoringClientToken, "Internal Monitoring must use monitoring token") - XCTAssertEqual(featuresConfiguration.logging!.clientToken, featuresClientToken, "Logging must use feature token") - XCTAssertEqual(featuresConfiguration.tracing!.clientToken, featuresClientToken, "Tracing must use feature token") - XCTAssertEqual(featuresConfiguration.rum!.clientToken, featuresClientToken, "RUM must use feature token") - } - // MARK: - Invalid Configurations func testWhenClientTokenIsInvalid_itThrowsProgrammerError() { diff --git a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift index 6051415da7..c0aa245674 100644 --- a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift @@ -122,9 +122,8 @@ class DataUploadStatusTests: XCTestCase { try alertingStatusCodes.forEach { statusCode in let requestID: String = .mockRandom() let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: requestID) - let error = try XCTUnwrap(status.internalMonitoringError, "Internal Monitoring error should be created for status code \(statusCode)") - XCTAssertEqual(error.message, "Data upload finished with status code: \(statusCode)") - XCTAssertEqual(error.attributes?["dd_request_id"], requestID) + let error = try XCTUnwrap(status.telemetryError, "Internal Monitoring error should be created for status code \(statusCode)") + XCTAssertEqual(error.message, "Data upload finished with status code: \(statusCode), dd_request_id: \(requestID)") } } @@ -132,7 +131,7 @@ class DataUploadStatusTests: XCTestCase { let clientIssueStatusCodes = Set(expectedStatusCodes).subtracting(Set(alertingStatusCodes)) clientIssueStatusCodes.forEach { statusCode in let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: nil) - XCTAssertNil(status.internalMonitoringError, "Internal Monitoring error should not be created for status code \(statusCode)") + XCTAssertNil(status.telemetryError, "Internal Monitoring error should not be created for status code \(statusCode)") } } @@ -140,7 +139,7 @@ class DataUploadStatusTests: XCTestCase { let unexpectedStatusCodes = Set((100...599)).subtracting(Set(expectedStatusCodes)) unexpectedStatusCodes.forEach { statusCode in let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: nil) - XCTAssertNil(status.internalMonitoringError) + XCTAssertNil(status.telemetryError) } } @@ -148,7 +147,7 @@ class DataUploadStatusTests: XCTestCase { let alertingNSURLErrorCode = NSURLErrorBadURL let status = DataUploadStatus(networkError: NSError(domain: NSURLErrorDomain, code: alertingNSURLErrorCode, userInfo: nil)) - let error = try XCTUnwrap(status.internalMonitoringError, "Internal Monitoring error should be created for NSURLError code: \(alertingNSURLErrorCode)") + let error = try XCTUnwrap(status.telemetryError, "Internal Monitoring error should be created for NSURLError code: \(alertingNSURLErrorCode)") XCTAssertEqual(error.message, "Data upload finished with error") let nsError = try XCTUnwrap(error.error) as NSError XCTAssertEqual(nsError.code, alertingNSURLErrorCode) @@ -157,6 +156,6 @@ class DataUploadStatusTests: XCTestCase { func testWhenUploadFinishesWithError_andErrorCodeMeansExternalFactors_itDoesNotCreateInternalMonitoringError() { let notAlertingNSURLErrorCode = NSURLErrorNetworkConnectionLost let status = DataUploadStatus(networkError: NSError(domain: NSURLErrorDomain, code: notAlertingNSURLErrorCode, userInfo: nil)) - XCTAssertNil(status.internalMonitoringError, "Internal Monitoring error should be created for NSURLError code: \(notAlertingNSURLErrorCode)") + XCTAssertNil(status.telemetryError, "Internal Monitoring error should be created for NSURLError code: \(notAlertingNSURLErrorCode)") } } diff --git a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift index 7e4438af3b..241afb1bf0 100644 --- a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift @@ -234,7 +234,7 @@ class DataUploadWorkerTests: XCTestCase { let mockUserLoggerOutput = LogOutputMock() userLogger = .mockWith(logOutput: mockUserLoggerOutput) - let mockSDKLoggerOutput = LogOutputMock() + let mockTelemetry = TelemetryMock() // Given writer.write(value: ["key": "value"]) @@ -254,9 +254,7 @@ class DataUploadWorkerTests: XCTestCase { uploadConditions: .alwaysUpload(), delay: DataUploadDelay(performance: UploadPerformanceMock.veryQuickInitialUpload), featureName: randomFeatureName, - internalMonitor: InternalMonitor( - sdkLogger: .mockWith(logOutput: mockSDKLoggerOutput) - ) + telemetry: mockTelemetry ) wait(for: [startUploadExpectation], timeout: 0.5) @@ -283,11 +281,11 @@ class DataUploadWorkerTests: XCTestCase { "An error should be printed to `userLogger`. All captured logs:\n\(mockUserLoggerOutput.dumpAllRecordedLogs())" ) - XCTAssertEqual(mockSDKLoggerOutput.allRecordedLogs.count, 1) + XCTAssertEqual(mockTelemetry.errors.count, 1) XCTAssertEqual( - mockSDKLoggerOutput.allRecordedLogs[0].message, - randomUploadStatus.internalMonitoringError?.message, - "An error should be send to `sdkLogger` for internal monitoring. All captured logs:\n\(mockSDKLoggerOutput.dumpAllRecordedLogs())" + mockTelemetry.errors.first?.message, + randomUploadStatus.telemetryError?.message, + "An error should be send to `sdkLogger` for internal monitoring. \(mockTelemetry)" ) } diff --git a/Tests/DatadogTests/Datadog/CrashReporting/CrashReporterTests.swift b/Tests/DatadogTests/Datadog/CrashReporting/CrashReporterTests.swift index 06c1e2a459..2bd0177ad5 100644 --- a/Tests/DatadogTests/Datadog/CrashReporting/CrashReporterTests.swift +++ b/Tests/DatadogTests/Datadog/CrashReporting/CrashReporterTests.swift @@ -25,7 +25,8 @@ class CrashReporterTests: XCTestCase { let crashReporter = CrashReporter( crashReportingPlugin: plugin, crashContextProvider: CrashContextProviderMock(), - loggingOrRUMIntegration: integration + loggingOrRUMIntegration: integration, + telemetry: nil ) // Then @@ -57,7 +58,8 @@ class CrashReporterTests: XCTestCase { let crashReporter = CrashReporter( crashReportingPlugin: plugin, crashContextProvider: CrashContextProviderMock(), - loggingOrRUMIntegration: integration + loggingOrRUMIntegration: integration, + telemetry: nil ) // Then @@ -84,7 +86,8 @@ class CrashReporterTests: XCTestCase { let crashReporter = CrashReporter( crashReportingPlugin: plugin, crashContextProvider: CrashContextProviderMock(), - loggingOrRUMIntegration: integration + loggingOrRUMIntegration: integration, + telemetry: nil ) // Then @@ -110,7 +113,8 @@ class CrashReporterTests: XCTestCase { let crashReporter = CrashReporter( crashReportingPlugin: plugin, crashContextProvider: CrashContextProviderMock(initialCrashContext: initialCrashContext), - loggingOrRUMIntegration: CrashReportingIntegrationMock() + loggingOrRUMIntegration: CrashReportingIntegrationMock(), + telemetry: nil ) try withExtendedLifetime(crashReporter) { @@ -133,7 +137,8 @@ class CrashReporterTests: XCTestCase { let crashReporter = CrashReporter( crashReportingPlugin: plugin, crashContextProvider: crashContextProvider, - loggingOrRUMIntegration: CrashReportingIntegrationMock() + loggingOrRUMIntegration: CrashReportingIntegrationMock(), + telemetry: nil ) try withExtendedLifetime(crashReporter) { @@ -174,7 +179,8 @@ class CrashReporterTests: XCTestCase { let crashReporter = CrashReporter( crashReportingPlugin: plugin, crashContextProvider: crashContextProvider, - loggingOrRUMIntegration: CrashReportingIntegrationMock() + loggingOrRUMIntegration: CrashReportingIntegrationMock(), + telemetry: nil ) // swiftlint:disable opening_brace diff --git a/Tests/DatadogTests/Datadog/DatadogTests.swift b/Tests/DatadogTests/Datadog/DatadogTests.swift index 92e072a361..2baf49f732 100644 --- a/Tests/DatadogTests/Datadog/DatadogTests.swift +++ b/Tests/DatadogTests/Datadog/DatadogTests.swift @@ -120,7 +120,6 @@ class DatadogTests: XCTestCase { XCTAssertFalse(CrashReportingFeature.isEnabled) XCTAssertNil(RUMInstrumentation.instance) XCTAssertNil(URLSessionAutoInstrumentation.instance) - XCTAssertNil(InternalMonitoringFeature.instance) // verify integrations: XCTAssertNotNil(TracingFeature.instance?.loggingFeatureAdapter) } @@ -132,7 +131,6 @@ class DatadogTests: XCTestCase { XCTAssertFalse(CrashReportingFeature.isEnabled) XCTAssertNotNil(RUMInstrumentation.instance) XCTAssertNil(URLSessionAutoInstrumentation.instance) - XCTAssertNil(InternalMonitoringFeature.instance) // verify integrations: XCTAssertNotNil(TracingFeature.instance?.loggingFeatureAdapter) } @@ -145,7 +143,6 @@ class DatadogTests: XCTestCase { XCTAssertFalse(CrashReportingFeature.isEnabled) XCTAssertNil(RUMInstrumentation.instance) XCTAssertNil(URLSessionAutoInstrumentation.instance) - XCTAssertNil(InternalMonitoringFeature.instance) // verify integrations: XCTAssertNil(TracingFeature.instance?.loggingFeatureAdapter) } @@ -157,7 +154,6 @@ class DatadogTests: XCTestCase { XCTAssertFalse(CrashReportingFeature.isEnabled) XCTAssertNotNil(RUMInstrumentation.instance) XCTAssertNil(URLSessionAutoInstrumentation.instance) - XCTAssertNil(InternalMonitoringFeature.instance) // verify integrations: XCTAssertNil(TracingFeature.instance?.loggingFeatureAdapter) } @@ -170,7 +166,6 @@ class DatadogTests: XCTestCase { XCTAssertFalse(CrashReportingFeature.isEnabled) XCTAssertNil(RUMInstrumentation.instance) XCTAssertNil(URLSessionAutoInstrumentation.instance) - XCTAssertNil(InternalMonitoringFeature.instance) } verify(configuration: rumBuilder.enableTracing(false).build()) { // verify features: @@ -180,7 +175,6 @@ class DatadogTests: XCTestCase { XCTAssertFalse(CrashReportingFeature.isEnabled) XCTAssertNotNil(RUMInstrumentation.instance) XCTAssertNil(URLSessionAutoInstrumentation.instance) - XCTAssertNil(InternalMonitoringFeature.instance) } verify(configuration: defaultBuilder.enableRUM(true).build()) { @@ -191,7 +185,6 @@ class DatadogTests: XCTestCase { XCTAssertFalse(CrashReportingFeature.isEnabled) XCTAssertNil(RUMInstrumentation.instance) XCTAssertNil(URLSessionAutoInstrumentation.instance) - XCTAssertNil(InternalMonitoringFeature.instance) // verify integrations: XCTAssertNotNil(TracingFeature.instance?.loggingFeatureAdapter) } @@ -203,7 +196,6 @@ class DatadogTests: XCTestCase { XCTAssertFalse(CrashReportingFeature.isEnabled) XCTAssertNil(RUMInstrumentation.instance) XCTAssertNil(URLSessionAutoInstrumentation.instance) - XCTAssertNil(InternalMonitoringFeature.instance) // verify integrations: XCTAssertNotNil(TracingFeature.instance?.loggingFeatureAdapter) } @@ -253,7 +245,6 @@ class DatadogTests: XCTestCase { Global.crashReporter?.loggingOrRUMIntegration is CrashReportingWithLoggingIntegration, "When only Logging feature is enabled, the Crash Reporter should send crash reports as Logs" ) - XCTAssertNil(InternalMonitoringFeature.instance) } verify( @@ -268,7 +259,6 @@ class DatadogTests: XCTestCase { Global.crashReporter?.loggingOrRUMIntegration is CrashReportingWithRUMIntegration, "When only RUM feature is enabled, the Crash Reporter should send crash reports as RUM Events" ) - XCTAssertNil(InternalMonitoringFeature.instance) } verify( @@ -283,7 +273,6 @@ class DatadogTests: XCTestCase { Global.crashReporter?.loggingOrRUMIntegration is CrashReportingWithRUMIntegration, "When both Logging and RUM features are enabled, the Crash Reporter should send crash reports as RUM Events" ) - XCTAssertNil(InternalMonitoringFeature.instance) } verify( @@ -298,35 +287,6 @@ class DatadogTests: XCTestCase { Global.crashReporter, "When both Logging and RUM are disabled, Crash Reporter should not be registered" ) - XCTAssertNil(InternalMonitoringFeature.instance) - } - - verify( - configuration: rumBuilder - .enableLogging(.random()) - .enableTracing(.random()) - .enableRUM(.random()) - .enableCrashReporting(using: CrashReportingPluginMock()) - .enableInternalMonitoring(clientToken: .mockAny()) - .build() - ) { - XCTAssertNotNil( - InternalMonitoringFeature.instance, - "When client token for internal monitoring is set, the Internal Monitoring feature should be enabled" - ) - } - verify( - configuration: rumBuilder - .enableLogging(.random()) - .enableTracing(.random()) - .enableRUM(.random()) - .enableCrashReporting(using: CrashReportingPluginMock()) - .build() - ) { - XCTAssertNil( - InternalMonitoringFeature.instance, - "When client token for internal monitoring is NOT set, the Internal Monitoring feature should be disabled" - ) } } @@ -467,7 +427,6 @@ class DatadogTests: XCTestCase { .enableLogging(true) .enableTracing(true) .enableRUM(true) - .enableInternalMonitoring(clientToken: "abc") .build() ) @@ -477,17 +436,14 @@ class DatadogTests: XCTestCase { let loggingWriter = try XCTUnwrap(LoggingFeature.instance?.storage.writer as? ConsentAwareDataWriter) let tracingWriter = try XCTUnwrap(TracingFeature.instance?.storage.writer as? ConsentAwareDataWriter) let rumWriter = try XCTUnwrap(RUMFeature.instance?.storage.writer as? ConsentAwareDataWriter) - let internalMonitoringWriter = try XCTUnwrap(InternalMonitoringFeature.instance?.logsStorage.writer as? ConsentAwareDataWriter) loggingWriter.queue.sync {} tracingWriter.queue.sync {} rumWriter.queue.sync {} - internalMonitoringWriter.queue.sync {} let featureDirectories: [FeatureDirectories] = [ try obtainLoggingFeatureDirectories(), try obtainTracingFeatureDirectories(), - try obtainRUMFeatureDirectories(), - try obtainInternalMonitoringFeatureLogDirectories() + try obtainRUMFeatureDirectories() ] let allDirectories: [Directory] = featureDirectories.flatMap { [$0.authorized, $0.unauthorized] } @@ -495,7 +451,7 @@ class DatadogTests: XCTestCase { // Given let numberOfFiles = try allDirectories.reduce(0, { acc, nextDirectory in return try acc + nextDirectory.files().count }) - XCTAssertEqual(numberOfFiles, 8, "Each feature stores 2 files - one authorised and one unauthorised") + XCTAssertEqual(numberOfFiles, 6, "Each feature stores 2 files - one authorised and one unauthorised") // When Datadog.clearAllData() @@ -504,7 +460,6 @@ class DatadogTests: XCTestCase { (LoggingFeature.instance?.storage.dataOrchestrator as? DataOrchestrator)?.queue.sync {} (TracingFeature.instance?.storage.dataOrchestrator as? DataOrchestrator)?.queue.sync {} (RUMFeature.instance?.storage.dataOrchestrator as? DataOrchestrator)?.queue.sync {} - (InternalMonitoringFeature.instance?.logsStorage.dataOrchestrator as? DataOrchestrator)?.queue.sync {} // Then let newNumberOfFiles = try allDirectories.reduce(0, { acc, nextDirectory in return try acc + nextDirectory.files().count }) diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 241e8d6498..28b1e226aa 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -87,8 +87,7 @@ extension Datadog.Configuration { batchSize: batchSize, uploadFrequency: uploadFrequency, additionalConfiguration: additionalConfiguration, - proxyConfiguration: proxyConfiguration, - internalMonitoringClientToken: internalMonitoringClientToken + proxyConfiguration: proxyConfiguration ) } } @@ -168,8 +167,7 @@ extension FeaturesConfiguration { tracing: Tracing? = .mockAny(), rum: RUM? = .mockAny(), crashReporting: CrashReporting = .mockAny(), - urlSessionAutoInstrumentation: URLSessionAutoInstrumentation? = .mockAny(), - internalMonitoring: InternalMonitoring? = nil + urlSessionAutoInstrumentation: URLSessionAutoInstrumentation? = .mockAny() ) -> Self { return .init( common: common, @@ -177,8 +175,7 @@ extension FeaturesConfiguration { tracing: tracing, rum: rum, urlSessionAutoInstrumentation: urlSessionAutoInstrumentation, - crashReporting: crashReporting, - internalMonitoring: internalMonitoring + crashReporting: crashReporting ) } } @@ -323,28 +320,6 @@ extension FeaturesConfiguration.URLSessionAutoInstrumentation { } } -extension FeaturesConfiguration.InternalMonitoring { - static func mockAny() -> Self { - return mockWith() - } - - static func mockWith( - common: FeaturesConfiguration.Common = .mockAny(), - sdkServiceName: String = .mockAny(), - sdkEnvironment: String = .mockAny(), - logsUploadURL: URL = .mockAny(), - clientToken: String = .mockAny() - ) -> Self { - return .init( - common: common, - sdkServiceName: sdkServiceName, - sdkEnvironment: sdkEnvironment, - logsUploadURL: logsUploadURL, - clientToken: clientToken - ) - } -} - extension AppContext { static func mockAny() -> AppContext { return mockWith() @@ -873,7 +848,7 @@ extension DataUploadStatus: RandomMockable { needsRetry: .random(), userDebugDescription: .mockRandom(), userErrorMessage: .mockRandom(), - internalMonitoringError: (.mockRandom(), ErrorMock(), .mockRandom()) + telemetryError: (.mockRandom(), ErrorMock()) ) } @@ -881,13 +856,13 @@ extension DataUploadStatus: RandomMockable { needsRetry: Bool = .mockAny(), userDebugDescription: String = .mockAny(), userErrorMessage: String? = nil, - internalMonitoringError: (message: String, error: Error?, attributes: [String: String]?)? = nil + telemetryError: (message: String, error: Error?)? = nil ) -> DataUploadStatus { return DataUploadStatus( needsRetry: needsRetry, userDebugDescription: userDebugDescription, userErrorMessage: userErrorMessage, - internalMonitoringError: internalMonitoringError + telemetryError: telemetryError ) } } diff --git a/Tests/DatadogTests/Datadog/Mocks/CrashReportingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CrashReportingFeatureMocks.swift index 5fc3cd8d13..16ce25c600 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CrashReportingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CrashReportingFeatureMocks.swift @@ -11,7 +11,8 @@ extension CrashReportingFeature { static func mockNoOp() -> CrashReportingFeature { return CrashReportingFeature( configuration: .mockWith(crashReportingPlugin: NoopCrashReportingPlugin()), - commonDependencies: .mockAny() + commonDependencies: .mockAny(), + telemetry: nil ) } @@ -21,7 +22,8 @@ extension CrashReportingFeature { ) -> CrashReportingFeature { return CrashReportingFeature( configuration: configuration, - commonDependencies: dependencies + commonDependencies: dependencies, + telemetry: nil ) } } diff --git a/Tests/DatadogTests/Datadog/Mocks/InternalMonitoringFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/InternalMonitoringFeatureMocks.swift deleted file mode 100644 index d963b628da..0000000000 --- a/Tests/DatadogTests/Datadog/Mocks/InternalMonitoringFeatureMocks.swift +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2019-2020 Datadog, Inc. - */ - -@testable import Datadog - -extension InternalMonitoringFeature { - /// Mocks the feature instance which performs uploads to `URLSession`. - /// Use `ServerMock` to inspect and assert recorded `URLRequests`. - static func mockWith( - logDirectories: FeatureDirectories, - configuration: FeaturesConfiguration.InternalMonitoring = .mockAny(), - dependencies: FeaturesCommonDependencies = .mockAny() - ) -> InternalMonitoringFeature { - return InternalMonitoringFeature( - logDirectories: logDirectories, - configuration: configuration, - commonDependencies: dependencies - ) - } - - /// Mocks the feature instance which performs uploads to mocked `DataUploadWorker`. - /// Use `InternalMonitoringFeature.waitAndReturnLogMatchers()` to inspect and assert recorded `Logs`. - static func mockByRecordingLogMatchers( - directories: FeatureDirectories, - configuration: FeaturesConfiguration.InternalMonitoring = .mockAny(), - dependencies: FeaturesCommonDependencies = .mockAny() - ) -> InternalMonitoringFeature { - // Create full feature: - let fullFeature = InternalMonitoringFeature( - logDirectories: directories, - configuration: configuration, - commonDependencies: dependencies.replacing( - dateProvider: SystemDateProvider() // replace date provider in mocked `Feature.Storage` - ) - ) - let uploadWorker = DataUploadWorkerMock() - let observedStorage = uploadWorker.observe(featureStorage: fullFeature.logsStorage) - // Replace by mocking the `FeatureUpload` and observing the `FeatureStorage`: - let mockedUpload = FeatureUpload(uploader: uploadWorker) - // Tear down the original upload - fullFeature.logsUpload.flushAndTearDown() - return InternalMonitoringFeature( - storage: observedStorage, - upload: mockedUpload, - configuration: configuration, - commonDependencies: dependencies - ) - } - - // MARK: - Expecting Logs Data - - static func waitAndReturnLogMatchers(count: UInt, file: StaticString = #file, line: UInt = #line) throws -> [LogMatcher] { - guard let uploadWorker = InternalMonitoringFeature.instance?.logsUpload.uploader as? DataUploadWorkerMock else { - preconditionFailure("Retrieving matchers requires that feature is mocked with `.mockByRecordingLogMatchers()`") - } - return try uploadWorker.waitAndReturnBatchedData(count: count, file: file, line: line) - .flatMap { batchData in try LogMatcher.fromArrayOfJSONObjectsData(batchData, file: file, line: line) } - } -} diff --git a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift index 6b3963aedb..98b151ba60 100644 --- a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift @@ -285,3 +285,20 @@ class LogOutputMock: LogOutput { .joined(separator: "\n") } } + +/// `Telemtry` recording received telemetry. +class TelemetryMock: Telemetry, CustomStringConvertible { + private(set) var debugs: [String] = [] + private(set) var errors: [(message: String, kind: String?, stack: String?)] = [] + private(set) var description: String = "Telemetry logs:" + + func debug(_ message: String) { + debugs.append(message) + description.append("\n- [debug] \(message)") + } + + func error(_ message: String, kind: String?, stack: String?) { + errors.append((message: message, kind: kind, stack: stack)) + description.append("\n - [error] \(message), kind: \(kind ?? "nil"), stack: \(stack ?? "nil")") + } +} diff --git a/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift index 90a2acaaaa..5f0735ce55 100644 --- a/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift @@ -15,7 +15,8 @@ extension TracingFeature { configuration: .mockAny(), commonDependencies: .mockAny(), loggingFeatureAdapter: nil, - tracingUUIDGenerator: DefaultTracingUUIDGenerator() + tracingUUIDGenerator: DefaultTracingUUIDGenerator(), + telemetry: nil ) } @@ -44,7 +45,8 @@ extension TracingFeature { configuration: FeaturesConfiguration.Tracing = .mockAny(), dependencies: FeaturesCommonDependencies = .mockAny(), loggingFeature: LoggingFeature? = nil, - tracingUUIDGenerator: TracingUUIDGenerator = DefaultTracingUUIDGenerator() + tracingUUIDGenerator: TracingUUIDGenerator = DefaultTracingUUIDGenerator(), + telemetry: Telemetry? = nil ) -> TracingFeature { // Get the full feature mock: let fullFeature: TracingFeature = .mockWith( @@ -68,7 +70,8 @@ extension TracingFeature { configuration: configuration, commonDependencies: dependencies, loggingFeatureAdapter: fullFeature.loggingFeatureAdapter, - tracingUUIDGenerator: fullFeature.tracingUUIDGenerator + tracingUUIDGenerator: fullFeature.tracingUUIDGenerator, + telemetry: telemetry ) } @@ -305,7 +308,8 @@ extension SpanEventBuilder { source: String = .mockAny(), origin: String? = nil, sdkVersion: String = .mockAny(), - eventsMapper: SpanEventMapper? = nil + eventsMapper: SpanEventMapper? = nil, + telemetry: Telemetry? = nil ) -> SpanEventBuilder { return SpanEventBuilder( sdkVersion: sdkVersion, @@ -317,7 +321,8 @@ extension SpanEventBuilder { dateCorrector: dateCorrector, source: source, origin: origin, - eventsMapper: eventsMapper + eventsMapper: eventsMapper, + telemetry: telemetry ) } } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index 9915d2237a..26cfdf5586 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -9,7 +9,6 @@ import XCTest class WebLogEventConsumerTests: XCTestCase { let mockUserLogsWriter = FileWriterMock() - let mockInternalLogsWriter = FileWriterMock() let mockDateCorrector = DateCorrectorMock() let mockContextProvider = RUMContextProviderMock(context: .mockWith(rumApplicationID: "123456")) @@ -21,7 +20,6 @@ class WebLogEventConsumerTests: XCTestCase { let environment = String.mockRandom() let eventConsumer = DefaultWebLogEventConsumer( userLogsWriter: mockUserLogsWriter, - internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, rumContextProvider: mockContextProvider, applicationVersion: applicationVersion, @@ -53,52 +51,6 @@ class WebLogEventConsumerTests: XCTestCase { let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) AssertDictionariesEqual(writtenJSON, expectedWebLogEvent) - - XCTAssertNil(mockInternalLogsWriter.dataWritten) - } - - func testWhenValidWebInternalLogEventPassed_itDecoratesAndPassesToWriter() throws { - let mockSessionID: UUID = .mockRandom() - mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) - mockDateCorrector.correctionOffset = 123 - let applicationVersion = String.mockRandom() - let environment = String.mockRandom() - let eventConsumer = DefaultWebLogEventConsumer( - userLogsWriter: mockUserLogsWriter, - internalLogsWriter: mockInternalLogsWriter, - dateCorrector: mockDateCorrector, - rumContextProvider: mockContextProvider, - applicationVersion: applicationVersion, - environment: environment - ) - - let webLogEvent: JSON = [ - "date": 1_635_932_927_012, - "error": ["origin": "console"], - "message": "console error: error", - "session_id": "0110cab4-7471-480e-aa4e-7ce039ced355", - "status": "error", - "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] - ] - let expectedWebLogEvent: JSON = [ - "date": 1_635_932_927_012 + 123.toInt64Milliseconds, - "error": ["origin": "console"], - "message": "console error: error", - "application_id": "123456", - "session_id": mockSessionID.uuidString.lowercased(), - "status": "error", - "ddtags": "version:\(applicationVersion),env:\(environment)", - "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] - ] - - try eventConsumer.consume(event: webLogEvent, internalLog: true) - - let data = try JSONEncoder().encode(mockInternalLogsWriter.dataWritten as? CodableValue) - let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) - - AssertDictionariesEqual(writtenJSON, expectedWebLogEvent) - - XCTAssertNil(mockUserLogsWriter.dataWritten) } func testWhenContextIsUnavailable_itPassesEventAsIs() throws { @@ -106,7 +58,6 @@ class WebLogEventConsumerTests: XCTestCase { let environment = String.mockRandom() let eventConsumer = DefaultWebLogEventConsumer( userLogsWriter: mockUserLogsWriter, - internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, rumContextProvider: nil, applicationVersion: applicationVersion, @@ -130,7 +81,5 @@ class WebLogEventConsumerTests: XCTestCase { let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) AssertDictionariesEqual(writtenJSON, expectedWebLogEvent) - - XCTAssertNil(mockInternalLogsWriter.dataWritten) } } diff --git a/Tests/DatadogTests/TestsObserver/DatadogTestsObserver.swift b/Tests/DatadogTests/TestsObserver/DatadogTestsObserver.swift index 44d75dd5a3..22a93e99f9 100644 --- a/Tests/DatadogTests/TestsObserver/DatadogTestsObserver.swift +++ b/Tests/DatadogTests/TestsObserver/DatadogTestsObserver.swift @@ -49,7 +49,6 @@ internal class DatadogTestsObserver: NSObject, XCTestObservation { && TracingFeature.instance == nil && RUMFeature.instance == nil && CrashReportingFeature.instance == nil - && InternalMonitoringFeature.instance == nil }, problem: "All features must not be initialized.", solution: """ From 3ffae875bc24b0585af9529bcf2843e886cf8612 Mon Sep 17 00:00:00 2001 From: Priyanshi Gupta Date: Mon, 25 Apr 2022 13:13:34 +0200 Subject: [PATCH 02/55] Update crash_reporting.md --- docs/rum_collection/crash_reporting.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/rum_collection/crash_reporting.md b/docs/rum_collection/crash_reporting.md index 8d54108dfd..db3b8475b1 100644 --- a/docs/rum_collection/crash_reporting.md +++ b/docs/rum_collection/crash_reporting.md @@ -181,6 +181,8 @@ To verify your iOS Crash Reporting and Error Tracking configuration, issue a cra 3. After the crash happens, restart your application and wait for the iOS SDK to upload the crash report in [**Error Tracking**][8]. +Note that RUM supports system symbol files for iOS v14+ arm64 and arm64e architecture. + ## Further Reading {{< partial name="whats-next/whats-next.html" >}} From 99f3e3e8c71efd4e01cb77bbe95c33c12b702752 Mon Sep 17 00:00:00 2001 From: Priyanshi Gupta Date: Mon, 25 Apr 2022 13:15:26 +0200 Subject: [PATCH 03/55] Update crash_reporting.md --- docs/rum_collection/crash_reporting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rum_collection/crash_reporting.md b/docs/rum_collection/crash_reporting.md index db3b8475b1..7cbc8e2066 100644 --- a/docs/rum_collection/crash_reporting.md +++ b/docs/rum_collection/crash_reporting.md @@ -181,7 +181,7 @@ To verify your iOS Crash Reporting and Error Tracking configuration, issue a cra 3. After the crash happens, restart your application and wait for the iOS SDK to upload the crash report in [**Error Tracking**][8]. -Note that RUM supports system symbol files for iOS v14+ arm64 and arm64e architecture. +Note that RUM supports symbolication of system symbol files for iOS v14+ arm64 and arm64e architecture. ## Further Reading From c59084e689f8b5b52b59841eeb4d090e5a6b97f6 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 25 Apr 2022 16:29:14 +0200 Subject: [PATCH 04/55] RUMM-2024 Remove request id from upload telemetry --- .../Core/Upload/DataUploadStatus.swift | 21 +++++++------------ .../Core/Upload/DataUploadWorker.swift | 4 ++-- .../Core/Upload/DataUploadStatusTests.swift | 7 +++---- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/Sources/Datadog/Core/Upload/DataUploadStatus.swift b/Sources/Datadog/Core/Upload/DataUploadStatus.swift index 3a153ab288..3e5a3e0895 100644 --- a/Sources/Datadog/Core/Upload/DataUploadStatus.swift +++ b/Sources/Datadog/Core/Upload/DataUploadStatus.swift @@ -76,7 +76,7 @@ extension DataUploadStatus { needsRetry: statusCode.needsRetry, userDebugDescription: "[response code: \(httpResponse.statusCode) (\(statusCode)), request ID: \(ddRequestID ?? "(???)")]", userErrorMessage: statusCode == .unauthorized ? "⚠️ The client token you provided seems to be invalid." : nil, - telemetryError: createInternalMonitoringErrorIfNeeded(for: httpResponse.statusCode, requestID: ddRequestID) + telemetryError: createTelemetryErrorIfNeeded(for: httpResponse.statusCode) ) } @@ -85,7 +85,7 @@ extension DataUploadStatus { needsRetry: true, // retry this upload as it failed due to network transport isse userDebugDescription: "[error: \(DDError(error: networkError).message)]", // e.g. "[error: A data connection is not currently allowed]" userErrorMessage: nil, // nothing actionable for the user - telemetryError: createInternalMonitoringErrorIfNeeded(for: networkError) + telemetryError: createTelemetryErrorIfNeeded(for: networkError) ) } } @@ -93,11 +93,9 @@ extension DataUploadStatus { // MARK: - Internal Telemtry /// Looks at the `statusCode` and produces error for Internal Monitoring feature if anything is going wrong. -private func createInternalMonitoringErrorIfNeeded( - for statusCode: Int, requestID: String? -) -> (message: String, error: Error?)? { +private func createTelemetryErrorIfNeeded(for statusCode: Int) -> (message: String, error: Error?)? { guard let responseStatusCode = HTTPResponseStatusCode(rawValue: statusCode) else { - // If status code is unexpected, do not produce an error for Internal Monitoring - otherwise monitoring may + // If status code is unexpected, do not produce an error for internal Telemetry - otherwise monitoring may // become too verbose for old installations if we introduce a new status code in the API. return nil } @@ -112,7 +110,7 @@ private func createInternalMonitoringErrorIfNeeded( case .badRequest, .payloadTooLarge, .tooManyRequests, .requestTimeout: // These codes mean that something wrong is happening either in the SDK or on the server - produce an error. return ( - message: "Data upload finished with status code: \(statusCode), dd_request_id: \(requestID ?? "(???)")", + message: "Data upload finished with status code: \(statusCode)", error: nil ) case .unexpected: @@ -137,13 +135,10 @@ private let ignoredNSURLErrorCodes = Set([ ]) /// Looks at the `networkError` and produces error for Internal Monitoring feature if anything is going wrong. -private func createInternalMonitoringErrorIfNeeded( - for networkError: Error -) -> (message: String, error: Error?)? { +private func createTelemetryErrorIfNeeded(for networkError: Error) -> (message: String, error: Error?)? { let nsError = networkError as NSError - if nsError.domain == NSURLErrorDomain && !ignoredNSURLErrorCodes.contains(nsError.code) { - return (message: "Data upload finished with error", error: nsError) - } else { + guard nsError.domain == NSURLErrorDomain, !ignoredNSURLErrorCodes.contains(nsError.code) else { return nil } + return (message: "Data upload finished with error", error: nsError) } diff --git a/Sources/Datadog/Core/Upload/DataUploadWorker.swift b/Sources/Datadog/Core/Upload/DataUploadWorker.swift index 6de3c0d680..aa41328676 100644 --- a/Sources/Datadog/Core/Upload/DataUploadWorker.swift +++ b/Sources/Datadog/Core/Upload/DataUploadWorker.swift @@ -23,7 +23,7 @@ internal class DataUploadWorker: DataUploadWorkerType { private let uploadConditions: DataUploadConditions /// Name of the feature this worker is performing uploads for. private let featureName: String - /// A monitor reporting errors through Internal Monitoring feature (if enabled). + /// A monitor reporting errors through internal telemetry feature (if enabled). private let telemetry: Telemetry? /// Delay used to schedule consecutive uploads. @@ -80,7 +80,7 @@ internal class DataUploadWorker: DataUploadWorkerType { userLogger.error(userErrorMessage) } - // Send internal monitoring error (if any and if Internal Monitoring is enabled) + // Send telemetry error (if enabled) if let telemetryError = uploadStatus.telemetryError { self.telemetry?.error(telemetryError.message, error: telemetryError.error) } diff --git a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift index c0aa245674..33935301e7 100644 --- a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift @@ -120,10 +120,9 @@ class DataUploadStatusTests: XCTestCase { func testWhenUploadFinishesWithResponse_andStatusCodeMeansSDKIssue_itCreatesInternalMonitoringError() throws { try alertingStatusCodes.forEach { statusCode in - let requestID: String = .mockRandom() - let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: requestID) - let error = try XCTUnwrap(status.telemetryError, "Internal Monitoring error should be created for status code \(statusCode)") - XCTAssertEqual(error.message, "Data upload finished with status code: \(statusCode), dd_request_id: \(requestID)") + let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: .mockRandom()) + let error = try XCTUnwrap(status.telemetryError, "Telemetry error should be created for status code \(statusCode)") + XCTAssertEqual(error.message, "Data upload finished with status code: \(statusCode)") } } From f7f3abbebba60b2065ff00f40ae8c4e2849b6752 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 25 Apr 2022 16:31:32 +0200 Subject: [PATCH 05/55] RUMM-2024 Replace Internal Monitoring with Telemetry in comments --- Sources/Datadog/Core/Upload/DataUploadStatus.swift | 8 ++++---- Sources/Datadog/Core/Upload/RequestBuilder.swift | 2 +- .../Datadog/Core/Upload/DataUploadStatusTests.swift | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Datadog/Core/Upload/DataUploadStatus.swift b/Sources/Datadog/Core/Upload/DataUploadStatus.swift index 3e5a3e0895..df32568f8e 100644 --- a/Sources/Datadog/Core/Upload/DataUploadStatus.swift +++ b/Sources/Datadog/Core/Upload/DataUploadStatus.swift @@ -62,7 +62,7 @@ internal struct DataUploadStatus { /// It is meant to indicate user action that must be taken to fix the upload issue (e.g. if the client token is invalid, it needs to be fixed). let userErrorMessage: String? - /// An optional error logged to the Internal Monitoring feature (if it's enabled). + /// An optional error logged to the internal Telemetry feature (if enabled). let telemetryError: (message: String, error: Error?)? } @@ -92,7 +92,7 @@ extension DataUploadStatus { // MARK: - Internal Telemtry -/// Looks at the `statusCode` and produces error for Internal Monitoring feature if anything is going wrong. +/// Looks at the `statusCode` and produces error for internal Telemetry feature if anything is going wrong. private func createTelemetryErrorIfNeeded(for statusCode: Int) -> (message: String, error: Error?)? { guard let responseStatusCode = HTTPResponseStatusCode(rawValue: statusCode) else { // If status code is unexpected, do not produce an error for internal Telemetry - otherwise monitoring may @@ -118,7 +118,7 @@ private func createTelemetryErrorIfNeeded(for statusCode: Int) -> (message: Stri } } -/// A list of known NSURLError codes which should not produce error in Internal Monitoring. +/// A list of known NSURLError codes which should not produce error in Telemetry. /// Receiving these codes doesn't mean SDK issue, but the network transportation scenario where the connection interrupted due to external factors. /// These list should evolve and we may want to add more codes in there. /// @@ -134,7 +134,7 @@ private let ignoredNSURLErrorCodes = Set([ NSURLErrorCannotConnectToHost, // -1004 ]) -/// Looks at the `networkError` and produces error for Internal Monitoring feature if anything is going wrong. +/// Looks at the `networkError` and produces error for internal Telemetry feature if anything is going wrong. private func createTelemetryErrorIfNeeded(for networkError: Error) -> (message: String, error: Error?)? { let nsError = networkError as NSError guard nsError.domain == NSURLErrorDomain, !ignoredNSURLErrorCodes.contains(nsError.code) else { diff --git a/Sources/Datadog/Core/Upload/RequestBuilder.swift b/Sources/Datadog/Core/Upload/RequestBuilder.swift index bd4fe380fa..03d66b0b1b 100644 --- a/Sources/Datadog/Core/Upload/RequestBuilder.swift +++ b/Sources/Datadog/Core/Upload/RequestBuilder.swift @@ -100,7 +100,7 @@ internal struct RequestBuilder { private let precomputedHeaders: [String: String] /// Computed HTTP headers (their value is different in succeeding requests). private let computedHeaders: [String: () -> String] - /// A monitor reporting errors through Internal Monitoring feature (if enabled). + /// A monitor reporting errors through internal telemetry feature. private let telemetry: Telemetry? // MARK: - Initialization diff --git a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift index 33935301e7..3d73d7abc7 100644 --- a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift @@ -130,7 +130,7 @@ class DataUploadStatusTests: XCTestCase { let clientIssueStatusCodes = Set(expectedStatusCodes).subtracting(Set(alertingStatusCodes)) clientIssueStatusCodes.forEach { statusCode in let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: nil) - XCTAssertNil(status.telemetryError, "Internal Monitoring error should not be created for status code \(statusCode)") + XCTAssertNil(status.telemetryError, "Telemetry error should not be created for status code \(statusCode)") } } @@ -146,7 +146,7 @@ class DataUploadStatusTests: XCTestCase { let alertingNSURLErrorCode = NSURLErrorBadURL let status = DataUploadStatus(networkError: NSError(domain: NSURLErrorDomain, code: alertingNSURLErrorCode, userInfo: nil)) - let error = try XCTUnwrap(status.telemetryError, "Internal Monitoring error should be created for NSURLError code: \(alertingNSURLErrorCode)") + let error = try XCTUnwrap(status.telemetryError, "Telemetry error should be created for NSURLError code: \(alertingNSURLErrorCode)") XCTAssertEqual(error.message, "Data upload finished with error") let nsError = try XCTUnwrap(error.error) as NSError XCTAssertEqual(nsError.code, alertingNSURLErrorCode) @@ -155,6 +155,6 @@ class DataUploadStatusTests: XCTestCase { func testWhenUploadFinishesWithError_andErrorCodeMeansExternalFactors_itDoesNotCreateInternalMonitoringError() { let notAlertingNSURLErrorCode = NSURLErrorNetworkConnectionLost let status = DataUploadStatus(networkError: NSError(domain: NSURLErrorDomain, code: notAlertingNSURLErrorCode, userInfo: nil)) - XCTAssertNil(status.telemetryError, "Internal Monitoring error should be created for NSURLError code: \(notAlertingNSURLErrorCode)") + XCTAssertNil(status.telemetryError, "Telemetry error should be created for NSURLError code: \(notAlertingNSURLErrorCode)") } } From 4bef992590a33a058148513065c2278d418fc576 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 25 Apr 2022 16:32:21 +0200 Subject: [PATCH 06/55] RUMM-2024 Report data compression failure as telemetry debug --- Sources/Datadog/Core/Upload/RequestBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Datadog/Core/Upload/RequestBuilder.swift b/Sources/Datadog/Core/Upload/RequestBuilder.swift index 03d66b0b1b..b256e897aa 100644 --- a/Sources/Datadog/Core/Upload/RequestBuilder.swift +++ b/Sources/Datadog/Core/Upload/RequestBuilder.swift @@ -149,7 +149,7 @@ internal struct RequestBuilder { request.httpBody = body } else { request.httpBody = data - telemetry?.error( + telemetry?.debug( """ Failed to compress request payload - url: \(url) From a3f21778a11b1e423333764a01c07dafc0863dd8 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 25 Apr 2022 16:33:13 +0200 Subject: [PATCH 07/55] RUMM-2024 Remove default nil telemetry in features --- Sources/Datadog/Core/Feature.swift | 4 ++-- Sources/Datadog/Logging/LoggingFeature.swift | 6 +++--- Sources/Datadog/RUM/RUMFeature.swift | 6 +++--- Sources/Datadog/Tracing/TracingFeature.swift | 6 +++--- .../DataStorage/LoggingStorageBenchmarkTests.swift | 3 ++- .../DataStorage/RUMStorageBenchmarkTests.swift | 3 ++- .../DataStorage/TracingStorageBenchmarkTests.swift | 3 ++- Tests/DatadogTests/Datadog/Core/FeatureTests.swift | 9 ++++++--- .../Datadog/Mocks/LoggingFeatureMocks.swift | 10 ++++++++-- Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift | 10 ++++++++-- .../Datadog/Mocks/TracingFeatureMocks.swift | 6 ++++-- 11 files changed, 43 insertions(+), 23 deletions(-) diff --git a/Sources/Datadog/Core/Feature.swift b/Sources/Datadog/Core/Feature.swift index d6d4d652d3..687c12d1cb 100644 --- a/Sources/Datadog/Core/Feature.swift +++ b/Sources/Datadog/Core/Feature.swift @@ -55,7 +55,7 @@ internal struct FeatureStorage { dataFormat: DataFormat, directories: FeatureDirectories, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) { let readWriteQueue = DispatchQueue( label: "com.datadoghq.ios-sdk-\(featureName)-read-write", @@ -170,7 +170,7 @@ internal struct FeatureUpload { storage: FeatureStorage, requestBuilder: RequestBuilder, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) { let uploadQueue = DispatchQueue( label: "com.datadoghq.ios-sdk-\(featureName)-upload", diff --git a/Sources/Datadog/Logging/LoggingFeature.swift b/Sources/Datadog/Logging/LoggingFeature.swift index 078e012fd1..f4cd34a5b5 100644 --- a/Sources/Datadog/Logging/LoggingFeature.swift +++ b/Sources/Datadog/Logging/LoggingFeature.swift @@ -52,7 +52,7 @@ internal final class LoggingFeature { static func createStorage( directories: FeatureDirectories, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) -> FeatureStorage { return FeatureStorage( featureName: LoggingFeature.featureName, @@ -67,7 +67,7 @@ internal final class LoggingFeature { storage: FeatureStorage, configuration: FeaturesConfiguration.Logging, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) -> FeatureUpload { return FeatureUpload( featureName: LoggingFeature.featureName, @@ -100,7 +100,7 @@ internal final class LoggingFeature { directories: FeatureDirectories, configuration: FeaturesConfiguration.Logging, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) { let storage = LoggingFeature.createStorage( directories: directories, diff --git a/Sources/Datadog/RUM/RUMFeature.swift b/Sources/Datadog/RUM/RUMFeature.swift index 2b9fc5aa70..11b0f14974 100644 --- a/Sources/Datadog/RUM/RUMFeature.swift +++ b/Sources/Datadog/RUM/RUMFeature.swift @@ -64,7 +64,7 @@ internal final class RUMFeature { static func createStorage( directories: FeatureDirectories, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) -> FeatureStorage { return FeatureStorage( featureName: RUMFeature.featureName, @@ -79,7 +79,7 @@ internal final class RUMFeature { storage: FeatureStorage, configuration: FeaturesConfiguration.RUM, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) -> FeatureUpload { return FeatureUpload( featureName: RUMFeature.featureName, @@ -120,7 +120,7 @@ internal final class RUMFeature { directories: FeatureDirectories, configuration: FeaturesConfiguration.RUM, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) { let eventsMapper = RUMEventsMapper( viewEventMapper: configuration.viewEventMapper, diff --git a/Sources/Datadog/Tracing/TracingFeature.swift b/Sources/Datadog/Tracing/TracingFeature.swift index e4dfaabb60..313fb85eaf 100644 --- a/Sources/Datadog/Tracing/TracingFeature.swift +++ b/Sources/Datadog/Tracing/TracingFeature.swift @@ -60,7 +60,7 @@ internal final class TracingFeature { static func createStorage( directories: FeatureDirectories, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) -> FeatureStorage { return FeatureStorage( featureName: TracingFeature.featureName, @@ -75,7 +75,7 @@ internal final class TracingFeature { storage: FeatureStorage, configuration: FeaturesConfiguration.Tracing, commonDependencies: FeaturesCommonDependencies, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) -> FeatureUpload { return FeatureUpload( featureName: TracingFeature.featureName, @@ -108,7 +108,7 @@ internal final class TracingFeature { commonDependencies: FeaturesCommonDependencies, loggingFeatureAdapter: LoggingForTracingAdapter?, tracingUUIDGenerator: TracingUUIDGenerator, - telemetry: Telemetry? = nil + telemetry: Telemetry? ) { let storage = TracingFeature.createStorage( directories: directories, diff --git a/Tests/DatadogBenchmarkTests/DataStorage/LoggingStorageBenchmarkTests.swift b/Tests/DatadogBenchmarkTests/DataStorage/LoggingStorageBenchmarkTests.swift index 03ac113734..08ab0e0686 100644 --- a/Tests/DatadogBenchmarkTests/DataStorage/LoggingStorageBenchmarkTests.swift +++ b/Tests/DatadogBenchmarkTests/DataStorage/LoggingStorageBenchmarkTests.swift @@ -24,7 +24,8 @@ class LoggingStorageBenchmarkTests: XCTestCase { unauthorized: obtainUniqueTemporaryDirectory(), authorized: directory ), - commonDependencies: .mockAny() + commonDependencies: .mockAny(), + telemetry: nil ) self.writer = storage.writer self.reader = storage.reader diff --git a/Tests/DatadogBenchmarkTests/DataStorage/RUMStorageBenchmarkTests.swift b/Tests/DatadogBenchmarkTests/DataStorage/RUMStorageBenchmarkTests.swift index f6c7cada5e..96d0b0a3a5 100644 --- a/Tests/DatadogBenchmarkTests/DataStorage/RUMStorageBenchmarkTests.swift +++ b/Tests/DatadogBenchmarkTests/DataStorage/RUMStorageBenchmarkTests.swift @@ -24,7 +24,8 @@ class RUMStorageBenchmarkTests: XCTestCase { unauthorized: obtainUniqueTemporaryDirectory(), authorized: directory ), - commonDependencies: .mockAny() + commonDependencies: .mockAny(), + telemetry: nil ) self.writer = storage.writer self.reader = storage.reader diff --git a/Tests/DatadogBenchmarkTests/DataStorage/TracingStorageBenchmarkTests.swift b/Tests/DatadogBenchmarkTests/DataStorage/TracingStorageBenchmarkTests.swift index 2c675a103c..58e3997f5d 100644 --- a/Tests/DatadogBenchmarkTests/DataStorage/TracingStorageBenchmarkTests.swift +++ b/Tests/DatadogBenchmarkTests/DataStorage/TracingStorageBenchmarkTests.swift @@ -24,7 +24,8 @@ class TracingStorageBenchmarkTests: XCTestCase { unauthorized: obtainUniqueTemporaryDirectory(), authorized: directory ), - commonDependencies: .mockAny() + commonDependencies: .mockAny(), + telemetry: nil ) self.writer = storage.writer self.reader = storage.reader diff --git a/Tests/DatadogTests/Datadog/Core/FeatureTests.swift b/Tests/DatadogTests/Datadog/Core/FeatureTests.swift index b2281c7bbd..7c5665bb28 100644 --- a/Tests/DatadogTests/Datadog/Core/FeatureTests.swift +++ b/Tests/DatadogTests/Datadog/Core/FeatureTests.swift @@ -26,7 +26,8 @@ class FeatureStorageTests: XCTestCase { featureName: .mockAny(), dataFormat: DataFormat(prefix: "", suffix: "", separator: "#"), directories: temporaryFeatureDirectories, - commonDependencies: .mockWith(consentProvider: consentProvider) + commonDependencies: .mockWith(consentProvider: consentProvider), + telemetry: nil ) // When @@ -65,7 +66,8 @@ class FeatureStorageTests: XCTestCase { featureName: .mockAny(), dataFormat: DataFormat(prefix: "", suffix: "", separator: "#"), directories: temporaryFeatureDirectories, - commonDependencies: .mockWith(consentProvider: .init(initialConsent: .granted)) + commonDependencies: .mockWith(consentProvider: .init(initialConsent: .granted)), + telemetry: nil ) // When @@ -95,7 +97,8 @@ class FeatureStorageTests: XCTestCase { featureName: .mockAny(), dataFormat: DataFormat(prefix: "", suffix: "", separator: "#"), directories: temporaryFeatureDirectories, - commonDependencies: .mockWith(consentProvider: .init(initialConsent: .granted)) + commonDependencies: .mockWith(consentProvider: .init(initialConsent: .granted)), + telemetry: nil ) // When diff --git a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift index 98b151ba60..b7d46b91d7 100644 --- a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift @@ -22,9 +22,15 @@ extension LoggingFeature { static func mockWith( directories: FeatureDirectories, configuration: FeaturesConfiguration.Logging = .mockAny(), - dependencies: FeaturesCommonDependencies = .mockAny() + dependencies: FeaturesCommonDependencies = .mockAny(), + telemetry: Telemetry? = nil ) -> LoggingFeature { - return LoggingFeature(directories: directories, configuration: configuration, commonDependencies: dependencies) + return LoggingFeature( + directories: directories, + configuration: configuration, + commonDependencies: dependencies, + telemetry: telemetry + ) } /// Mocks the feature instance which performs uploads to mocked `DataUploadWorker`. diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index da93bb3c62..4b6458bdee 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -28,9 +28,15 @@ extension RUMFeature { static func mockWith( directories: FeatureDirectories, configuration: FeaturesConfiguration.RUM = .mockAny(), - dependencies: FeaturesCommonDependencies = .mockAny() + dependencies: FeaturesCommonDependencies = .mockAny(), + telemetry: Telemetry? = nil ) -> RUMFeature { - return RUMFeature(directories: directories, configuration: configuration, commonDependencies: dependencies) + return RUMFeature( + directories: directories, + configuration: configuration, + commonDependencies: dependencies, + telemetry: telemetry + ) } /// Mocks the feature instance which performs uploads to mocked `DataUploadWorker`. diff --git a/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift index 5f0735ce55..4dd7aecc22 100644 --- a/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift @@ -27,14 +27,16 @@ extension TracingFeature { configuration: FeaturesConfiguration.Tracing = .mockAny(), dependencies: FeaturesCommonDependencies = .mockAny(), loggingFeature: LoggingFeature? = nil, - tracingUUIDGenerator: TracingUUIDGenerator = DefaultTracingUUIDGenerator() + tracingUUIDGenerator: TracingUUIDGenerator = DefaultTracingUUIDGenerator(), + telemetry: Telemetry? = nil ) -> TracingFeature { return TracingFeature( directories: directories, configuration: configuration, commonDependencies: dependencies, loggingFeatureAdapter: loggingFeature.flatMap { LoggingForTracingAdapter(loggingFeature: $0) }, - tracingUUIDGenerator: tracingUUIDGenerator + tracingUUIDGenerator: tracingUUIDGenerator, + telemetry: telemetry ) } From c26e9da73f49c04570bcea1d7065d42d5f2d918c Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 25 Apr 2022 16:33:43 +0200 Subject: [PATCH 08/55] RUMM-2024 Remove internal monitoring from configuration --- Sources/Datadog/DatadogConfiguration.swift | 23 ---------------------- 1 file changed, 23 deletions(-) diff --git a/Sources/Datadog/DatadogConfiguration.swift b/Sources/Datadog/DatadogConfiguration.swift index 5aa2a02f13..53c1eb3c1d 100644 --- a/Sources/Datadog/DatadogConfiguration.swift +++ b/Sources/Datadog/DatadogConfiguration.swift @@ -703,29 +703,6 @@ extension Datadog { return self } -#if DD_SDK_ENABLE_INTERNAL_MONITORING - // MARK: - Internal Monitoring Configuration - - /// Enables the internal monitoring feature. - /// - /// This feature provides an observability for the SDK performance. All telemetry collected by the internal monitoring feature is sent to - /// Datadog instance authorised for given `clientToken`, which can be a different org than the one configured for RUM, Tracing and Logging data. - /// - /// This feature is opt-in and requires specific configuration to be enabled. **Datadog does not collect any internal telemetry data by default.** - /// - /// To make this API visible, the `DD_SDK_ENABLE_INTERNAL_MONITORING` compiler flag must be defined in the "Active Compilation Conditions" Build Setting - /// or in the `.xcconfig` set for the build configuration: - /// - /// SWIFT_ACTIVE_COMPILATION_CONDITIONS = DD_SDK_ENABLE_INTERNAL_MONITORING - /// - /// - Parameter clientToken: the client token authorised for a Datadog org which should receive the SDK telemetry - @available(*, deprecated, message: "Internal monitoring has been removed") - public func enableInternalMonitoring(clientToken: String) -> Builder { - // no-op - return self - } -#endif - // MARK: - Features Common Configuration /// Sets the default service name associated with data send to Datadog. From 8f3a9587aa979eb1938706f537473a4dc54a3617 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 26 Apr 2022 11:08:17 +0200 Subject: [PATCH 09/55] RUMM-2024 Format telemetry error with DDError --- Sources/Datadog/Core/Telemetry.swift | 31 ++++++++++-- .../Datadog/RUM/RUMTelemetryTests.swift | 49 +++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/Sources/Datadog/Core/Telemetry.swift b/Sources/Datadog/Core/Telemetry.swift index 6a20a84b04..5490c0c5b7 100644 --- a/Sources/Datadog/Core/Telemetry.swift +++ b/Sources/Datadog/Core/Telemetry.swift @@ -42,12 +42,37 @@ extension Telemetry { error(message, kind: nil, stack: stack) } + /// Collect execution error. + /// + /// - Parameters: + /// - error: The error. + func error(_ error: DDError) { + self.error(error.message, kind: error.type, stack: error.stack) + } + /// Collect execution error. /// /// - Parameters: /// - message: The error message. - /// - stack: The stack trace. - func error(_ message: String, error: Error?) { - self.error(message, kind: "swift", stack: String(describing: error)) + /// - error: The error. + func error(_ message: String, error: DDError) { + self.error("\(message) - \(error.message)", kind: error.type, stack: error.stack) + } + + /// Collect execution error. + /// + /// - Parameters: + /// - error: The error. + func error(_ error: Error) { + self.error(DDError(error: error)) + } + + /// Collect execution error. + /// + /// - Parameters: + /// - message: The error message. + /// - error: The error. + func error(_ message: String, error: Error) { + self.error(message, error: DDError(error: error)) } } diff --git a/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift index 920b41f349..39abd8d320 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift @@ -98,4 +98,53 @@ class RUMTelemetryTests: XCTestCase { XCTAssertValidRumUUID(event.session?.id) } } + + func testTelemetryErrorFormatting() { + class TelemetryTest: Telemetry { + var record: (message: String, kind: String?, stack: String?)? + + func debug(_ message: String) { } + + func error(_ message: String, kind: String?, stack: String?) { + record = (message: message, kind: kind, stack: stack) + } + } + + let telemetry = TelemetryTest() + + struct SwiftError: Error { + let description = "error description" + } + + let swiftError = SwiftError() + + let nsError = NSError( + domain: "custom-domain", + code: 10, + userInfo: [ + NSLocalizedDescriptionKey: "error description" + ] + ) + + telemetry.error(swiftError) + XCTAssertEqual(telemetry.record?.message, #"SwiftError(description: "error description")"#) + XCTAssertEqual(telemetry.record?.kind, "SwiftError") + XCTAssertEqual(telemetry.record?.stack, #"SwiftError(description: "error description")"#) + + telemetry.error(nsError) + XCTAssertEqual(telemetry.record?.message, "error description") + XCTAssertEqual(telemetry.record?.kind, "custom-domain - 10") + XCTAssertEqual( + telemetry.record?.stack, + """ + Error Domain=custom-domain Code=10 "error description" UserInfo={NSLocalizedDescription=error description} + """ + ) + + telemetry.error("swift error", error: swiftError) + XCTAssertEqual(telemetry.record?.message, #"swift error - SwiftError(description: "error description")"#) + + telemetry.error("ns error", error: nsError) + XCTAssertEqual(telemetry.record?.message, "ns error - error description") + } } From 871a1c2087348e3cbb758125116fb20ab2a1c0f0 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Tue, 26 Apr 2022 13:08:50 +0200 Subject: [PATCH 10/55] RUMM-2105 RUMResourceScope converts neg duration to pos --- .../RUMMonitor/Scopes/RUMResourceScope.swift | 14 +++- .../Scopes/RUMResourceScopeTests.swift | 66 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift index 6fa4202130..9ffbdc5dcc 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift @@ -162,7 +162,7 @@ internal class RUMResourceScope: RUMScope { start: metric.start.timeIntervalSince(resourceStartTime).toInt64Nanoseconds ) }, - duration: resourceDuration.toInt64Nanoseconds, + duration: resolveResourceDuration(resourceDuration), firstByte: resourceMetrics?.firstByte.flatMap { metric in .init( duration: metric.duration.toInt64Nanoseconds, @@ -302,4 +302,16 @@ internal class RUMResourceScope: RUMScope { private func providerDomain(from url: String) -> String? { return URL(string: url)?.host ?? url } + + private func resolveResourceDuration(_ duration: TimeInterval) -> Int64 { + if duration <= 0.0 { + let negativeDurationWarningMessage = + """ + The computed duration for your resource: \(resourceURL) was 0 or negative. In order to keep the resource event we forced it to 1ns. + """ + userLogger.warn(negativeDurationWarningMessage) + return 1 // 1ns + } + return duration.toInt64Nanoseconds + } } diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift index 4026a5dab5..1486f51d3d 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift @@ -106,6 +106,72 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.service, randomServiceName) } + func testGivenStartedResource_whenResourceLoadingEndsWithNegativeDuration_itSendsResourceEventWithPositiveDuration() throws { + var currentTime: Date = .mockDecember15th2019At10AMUTC() + + // Given + let scope = RUMResourceScope.mockWith( + context: context, + dependencies: dependencies, + resourceKey: "/resource/1", + attributes: [:], + startTime: currentTime, + dateCorrection: .zero, + url: "https://foo.com/resource/1", + httpMethod: .post, + isFirstPartyResource: nil, + resourceKindBasedOnRequest: nil, + spanContext: .init(traceID: "100", spanID: "200") + ) + + currentTime.addTimeInterval(-1) + + // When + XCTAssertFalse( + scope.process( + command: RUMStopResourceCommand( + resourceKey: "/resource/1", + time: currentTime, + attributes: ["foo": "bar"], + kind: .image, + httpStatusCode: 200, + size: 1_024 + ) + ) + ) + + // Then + let event = try XCTUnwrap(output.recordedEvents(ofType: RUMResourceEvent.self).first) + XCTAssertEqual(event.date, Date.mockDecember15th2019At10AMUTC().timeIntervalSince1970.toInt64Milliseconds) + XCTAssertEqual(event.application.id, scope.context.rumApplicationID) + XCTAssertEqual(event.session.id, scope.context.sessionID.toRUMDataFormat) + XCTAssertEqual(event.session.type, .user) + XCTAssertEqual(event.source, .ios) + XCTAssertEqual(event.view.id, context.activeViewID?.toRUMDataFormat) + XCTAssertEqual(event.view.url, "FooViewController") + XCTAssertEqual(event.view.name, "FooViewName") + XCTAssertValidRumUUID(event.resource.id) + XCTAssertEqual(event.resource.type, .image) + XCTAssertEqual(event.resource.method, .post) + XCTAssertNil(event.resource.provider) + XCTAssertEqual(event.resource.url, "https://foo.com/resource/1") + XCTAssertEqual(event.resource.statusCode, 200) + XCTAssertEqual(event.resource.duration, 1) + XCTAssertEqual(event.resource.size, 1_024) + XCTAssertNil(event.resource.redirect) + XCTAssertNil(event.resource.dns) + XCTAssertNil(event.resource.connect) + XCTAssertNil(event.resource.ssl) + XCTAssertNil(event.resource.firstByte) + XCTAssertNil(event.resource.download) + XCTAssertEqual(try XCTUnwrap(event.action?.id), context.activeUserActionID?.toRUMDataFormat) + XCTAssertEqual(event.context?.contextInfo as? [String: String], ["foo": "bar"]) + XCTAssertEqual(event.dd.traceId, "100") + XCTAssertEqual(event.dd.spanId, "200") + XCTAssertEqual(event.dd.session?.plan, .plan1, "All RUM events should use RUM Lite plan") + XCTAssertEqual(event.service, randomServiceName) + } + func testGivenStartedResource_whenResourceLoadingEnds_itSendsResourceEventWithCustomSpanAndTraceId() throws { var currentTime: Date = .mockDecember15th2019At10AMUTC() From 7561e20b4cd15c709ac9c5b77ebfdb557bed3eaf Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 26 Apr 2022 14:52:35 +0200 Subject: [PATCH 11/55] RUMM-2024 Move data upload error handling to upload worker --- .../Core/Upload/DataUploadStatus.swift | 81 +++++++------ .../Core/Upload/DataUploadWorker.swift | 16 +-- .../Datadog/Core/Upload/DataUploader.swift | 2 +- .../Core/Upload/DataUploadStatusTests.swift | 68 ++++++----- .../Core/Upload/DataUploadWorkerTests.swift | 112 ++++++++++++++++-- .../Datadog/Mocks/CoreMocks.swift | 9 +- .../SystemFrameworks/FoundationMocks.swift | 10 ++ 7 files changed, 198 insertions(+), 100 deletions(-) diff --git a/Sources/Datadog/Core/Upload/DataUploadStatus.swift b/Sources/Datadog/Core/Upload/DataUploadStatus.swift index df32568f8e..9223f17912 100644 --- a/Sources/Datadog/Core/Upload/DataUploadStatus.swift +++ b/Sources/Datadog/Core/Upload/DataUploadStatus.swift @@ -58,12 +58,7 @@ internal struct DataUploadStatus { /// Upload status description printed to the console if SDK `.debug` verbosity is enabled. let userDebugDescription: String - /// An optional error printed to the console if SDK `.error` (or lower) verbosity is enabled. - /// It is meant to indicate user action that must be taken to fix the upload issue (e.g. if the client token is invalid, it needs to be fixed). - let userErrorMessage: String? - - /// An optional error logged to the internal Telemetry feature (if enabled). - let telemetryError: (message: String, error: Error?)? + let error: DataUploadError? } extension DataUploadStatus { @@ -75,8 +70,7 @@ extension DataUploadStatus { self.init( needsRetry: statusCode.needsRetry, userDebugDescription: "[response code: \(httpResponse.statusCode) (\(statusCode)), request ID: \(ddRequestID ?? "(???)")]", - userErrorMessage: statusCode == .unauthorized ? "⚠️ The client token you provided seems to be invalid." : nil, - telemetryError: createTelemetryErrorIfNeeded(for: httpResponse.statusCode) + error: DataUploadError(status: httpResponse.statusCode) ) } @@ -84,38 +78,17 @@ extension DataUploadStatus { self.init( needsRetry: true, // retry this upload as it failed due to network transport isse userDebugDescription: "[error: \(DDError(error: networkError).message)]", // e.g. "[error: A data connection is not currently allowed]" - userErrorMessage: nil, // nothing actionable for the user - telemetryError: createTelemetryErrorIfNeeded(for: networkError) + error: DataUploadError(networkError: networkError) ) } } -// MARK: - Internal Telemtry - -/// Looks at the `statusCode` and produces error for internal Telemetry feature if anything is going wrong. -private func createTelemetryErrorIfNeeded(for statusCode: Int) -> (message: String, error: Error?)? { - guard let responseStatusCode = HTTPResponseStatusCode(rawValue: statusCode) else { - // If status code is unexpected, do not produce an error for internal Telemetry - otherwise monitoring may - // become too verbose for old installations if we introduce a new status code in the API. - return nil - } +// MARK: - Data Upload Errors - switch responseStatusCode { - case .accepted, .unauthorized, .forbidden: - // These codes mean either success or the user configuration mistake - do not produce error. - return nil - case .internalServerError, .serviceUnavailable: - // These codes mean Datadog service issue - do not produce SDK error as this is already monitored by other means. - return nil - case .badRequest, .payloadTooLarge, .tooManyRequests, .requestTimeout: - // These codes mean that something wrong is happening either in the SDK or on the server - produce an error. - return ( - message: "Data upload finished with status code: \(statusCode)", - error: nil - ) - case .unexpected: - return nil - } +internal enum DataUploadError: Error, Equatable { + case unauthorized + case httpError(statusCode: Int) + case networkError(error: NSError) } /// A list of known NSURLError codes which should not produce error in Telemetry. @@ -134,11 +107,37 @@ private let ignoredNSURLErrorCodes = Set([ NSURLErrorCannotConnectToHost, // -1004 ]) -/// Looks at the `networkError` and produces error for internal Telemetry feature if anything is going wrong. -private func createTelemetryErrorIfNeeded(for networkError: Error) -> (message: String, error: Error?)? { - let nsError = networkError as NSError - guard nsError.domain == NSURLErrorDomain, !ignoredNSURLErrorCodes.contains(nsError.code) else { - return nil +extension DataUploadError { + init?(status code: Int) { + guard let responseStatusCode = HTTPResponseStatusCode(rawValue: code) else { + // If status code is unexpected, do not produce an error for internal Telemetry - otherwise monitoring may + // become too verbose for old installations if we introduce a new status code in the API. + return nil + } + + switch responseStatusCode { + case .accepted, .forbidden: + // These codes mean either success or the user configuration mistake - do not produce error. + return nil + case .unauthorized: + self = .unauthorized + case .internalServerError, .serviceUnavailable: + // These codes mean Datadog service issue - do not produce SDK error as this is already monitored by other means. + return nil + case .badRequest, .payloadTooLarge, .tooManyRequests, .requestTimeout: + // These codes mean that something wrong is happening either in the SDK or on the server - produce an error. + self = .httpError(statusCode: code) + case .unexpected: + return nil + } + } + + init?(networkError: Error) { + let nsError = networkError as NSError + guard nsError.domain == NSURLErrorDomain, !ignoredNSURLErrorCodes.contains(nsError.code) else { + return nil + } + + self = .networkError(error: nsError) } - return (message: "Data upload finished with error", error: nsError) } diff --git a/Sources/Datadog/Core/Upload/DataUploadWorker.swift b/Sources/Datadog/Core/Upload/DataUploadWorker.swift index aa41328676..77b5bbc537 100644 --- a/Sources/Datadog/Core/Upload/DataUploadWorker.swift +++ b/Sources/Datadog/Core/Upload/DataUploadWorker.swift @@ -75,14 +75,14 @@ internal class DataUploadWorker: DataUploadWorkerType { userLogger.debug(" → (\(self.featureName)) accepted, won't be retransmitted: \(uploadStatus.userDebugDescription)") } - // Print user error (if any) - if let userErrorMessage = uploadStatus.userErrorMessage { - userLogger.error(userErrorMessage) - } - - // Send telemetry error (if enabled) - if let telemetryError = uploadStatus.telemetryError { - self.telemetry?.error(telemetryError.message, error: telemetryError.error) + switch uploadStatus.error { + case .unauthorized: + userLogger.error("⚠️ The client token you provided seems to be invalid.") + case let .httpError(statusCode: statusCode): + self.telemetry?.error("Data upload finished with status code: \(statusCode)") + case let .networkError(error: error): + self.telemetry?.error("Data upload finished with error", error: error) + case .none: break } } else { let batchLabel = nextBatch != nil ? "YES" : (isSystemReady ? "NO" : "NOT CHECKED") diff --git a/Sources/Datadog/Core/Upload/DataUploader.swift b/Sources/Datadog/Core/Upload/DataUploader.swift index 4cf496eb6a..c2c8da8743 100644 --- a/Sources/Datadog/Core/Upload/DataUploader.swift +++ b/Sources/Datadog/Core/Upload/DataUploader.swift @@ -14,7 +14,7 @@ internal protocol DataUploaderType { /// Synchronously uploads data to server using `HTTPClient`. internal final class DataUploader: DataUploaderType { /// An unreachable upload status - only meant to satisfy the compiler. - private static let unreachableUploadStatus = DataUploadStatus(needsRetry: false, userDebugDescription: "", userErrorMessage: nil, telemetryError: nil) + private static let unreachableUploadStatus = DataUploadStatus(needsRetry: false, userDebugDescription: "", error: nil) private let httpClient: HTTPClient private let requestBuilder: RequestBuilder diff --git a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift index 3d73d7abc7..48bd4b0d90 100644 --- a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift @@ -89,72 +89,70 @@ class DataUploadStatusTests: XCTestCase { XCTAssertEqual(status.userDebugDescription, "[error: \(randomErrorDescription)]") } - // MARK: - Test `.userErrorMessage` + // MARK: - Test Upload Error - func testWhenUploadFinishesWithResponse_andStatusCodeIs401_itCreatesClientTokenErrorMessage() { + private let alertingStatusCodes: Set = [ + 400, // BAD REQUEST + 401, // UNAUTHORIZED + 413, // PAYLOAD TOO LARGE + 408, // REQUEST TIMEOUT + 429, // TOO MANY REQUESTS + ] + + func testWhenUploadFinishesWithResponse_andStatusCodeIs401_itCreatesError() { let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: 401), ddRequestID: nil) - XCTAssertEqual(status.userErrorMessage, "⚠️ The client token you provided seems to be invalid.") + XCTAssertEqual(status.error, .unauthorized) } - func testWhenUploadFinishesWithResponse_andStatusCodeIsDifferentThan401_itDoesNotCreateAnyUserErrorMessage() { - let statusCodes = Set((100...599)).subtracting([401]) - statusCodes.forEach { statusCode in + func testWhenUploadFinishesWithResponse_andStatusCodeIsDifferentThan401_itDoesNotCreateAnyError() { + Set((100...599)).subtracting(alertingStatusCodes).forEach { statusCode in let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: nil) - XCTAssertNil(status.userErrorMessage) + XCTAssertNil(status.error) } } - func testWhenUploadFinishesWithError_itDoesNotCreateAnyUserErrorMessage() { - let status = DataUploadStatus(networkError: ErrorMock(.mockRandom())) - XCTAssertNil(status.userErrorMessage) - } - - // MARK: - Test `.internalMonitoringError` + func testWhenUploadFinishesWithResponse_andStatusCodeMeansSDKIssue_itCreatesHTTPError() { + alertingStatusCodes.subtracting([401]).forEach { statusCode in + let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: .mockRandom()) - private let alertingStatusCodes = [ - 400, // BAD REQUEST - 413, // PAYLOAD TOO LARGE - 408, // REQUEST TIMEOUT - 429, // TOO MANY REQUESTS - ] + guard case let .httpError(statusCode: receivedStatusCode) = status.error else { + return XCTFail("Upload status error should be created for status code: \(statusCode)") + } - func testWhenUploadFinishesWithResponse_andStatusCodeMeansSDKIssue_itCreatesInternalMonitoringError() throws { - try alertingStatusCodes.forEach { statusCode in - let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: .mockRandom()) - let error = try XCTUnwrap(status.telemetryError, "Telemetry error should be created for status code \(statusCode)") - XCTAssertEqual(error.message, "Data upload finished with status code: \(statusCode)") + XCTAssertEqual(receivedStatusCode, statusCode) } } - func testWhenUploadFinishesWithResponse_andStatusCodeMeansClientIssue_itDoesNotCreateInternalMonitoringError() { + func testWhenUploadFinishesWithResponse_andStatusCodeMeansClientIssue_itDoesNotCreateHTTPError() { let clientIssueStatusCodes = Set(expectedStatusCodes).subtracting(Set(alertingStatusCodes)) clientIssueStatusCodes.forEach { statusCode in let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: nil) - XCTAssertNil(status.telemetryError, "Telemetry error should not be created for status code \(statusCode)") + XCTAssertNil(status.error, "Upload status error should not be created for status code \(statusCode)") } } - func testWhenUploadFinishesWithResponse_andUnexpectedStatusCodeMeansClientIssue_itDoesNotCreateInternalMonitoringError() { + func testWhenUploadFinishesWithResponse_andUnexpectedStatusCodeMeansClientIssue_itDoesNotCreateHTTPError() { let unexpectedStatusCodes = Set((100...599)).subtracting(Set(expectedStatusCodes)) unexpectedStatusCodes.forEach { statusCode in let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: nil) - XCTAssertNil(status.telemetryError) + XCTAssertNil(status.error) } } - func testWhenUploadFinishesWithError_andErrorCodeMeansSDKIssue_itCreatesInternalMonitoringError() throws { + func testWhenUploadFinishesWithError_andErrorCodeMeansSDKIssue_itCreatesNetworkError() throws { let alertingNSURLErrorCode = NSURLErrorBadURL let status = DataUploadStatus(networkError: NSError(domain: NSURLErrorDomain, code: alertingNSURLErrorCode, userInfo: nil)) - let error = try XCTUnwrap(status.telemetryError, "Telemetry error should be created for NSURLError code: \(alertingNSURLErrorCode)") - XCTAssertEqual(error.message, "Data upload finished with error") - let nsError = try XCTUnwrap(error.error) as NSError - XCTAssertEqual(nsError.code, alertingNSURLErrorCode) + guard case let .networkError(error: nserror) = status.error else { + return XCTFail("Upload status error should be created for NSURLError code: \(alertingNSURLErrorCode)") + } + + XCTAssertEqual(nserror.code, alertingNSURLErrorCode) } - func testWhenUploadFinishesWithError_andErrorCodeMeansExternalFactors_itDoesNotCreateInternalMonitoringError() { + func testWhenUploadFinishesWithError_andErrorCodeMeansExternalFactors_itDoesNotCreateNetworkError() { let notAlertingNSURLErrorCode = NSURLErrorNetworkConnectionLost let status = DataUploadStatus(networkError: NSError(domain: NSURLErrorDomain, code: notAlertingNSURLErrorCode, userInfo: nil)) - XCTAssertNil(status.telemetryError, "Telemetry error should be created for NSURLError code: \(notAlertingNSURLErrorCode)") + XCTAssertNil(status.error, "Upload status error should not be created for NSURLError code: \(notAlertingNSURLErrorCode)") } } diff --git a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift index 241afb1bf0..b04c3f0c50 100644 --- a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift @@ -227,15 +227,13 @@ class DataUploadWorkerTests: XCTestCase { // MARK: - Notifying Upload Progress - func testWhenDataIsBeingUploaded_itPrintsUploadProgressInformationAndSendsErrorsThroughInternalMonitoring() { + func testWhenDataIsBeingUploaded_itPrintsUploadProgressInformation() { let previousUserLogger = userLogger defer { userLogger = previousUserLogger } let mockUserLoggerOutput = LogOutputMock() userLogger = .mockWith(logOutput: mockUserLoggerOutput) - let mockTelemetry = TelemetryMock() - // Given writer.write(value: ["key": "value"]) @@ -253,8 +251,7 @@ class DataUploadWorkerTests: XCTestCase { dataUploader: mockDataUploader, uploadConditions: .alwaysUpload(), delay: DataUploadDelay(performance: UploadPerformanceMock.veryQuickInitialUpload), - featureName: randomFeatureName, - telemetry: mockTelemetry + featureName: randomFeatureName ) wait(for: [startUploadExpectation], timeout: 0.5) @@ -262,7 +259,7 @@ class DataUploadWorkerTests: XCTestCase { // Then let expectedSummary = randomUploadStatus.needsRetry ? "not delivered, will be retransmitted" : "accepted, won't be retransmitted" - XCTAssertEqual(mockUserLoggerOutput.allRecordedLogs.count, 3) + XCTAssertEqual(mockUserLoggerOutput.allRecordedLogs.count, 2) XCTAssertEqual( mockUserLoggerOutput.allRecordedLogs[0].message, @@ -275,17 +272,114 @@ class DataUploadWorkerTests: XCTestCase { " → (\(randomFeatureName)) \(expectedSummary): \(randomUploadStatus.userDebugDescription)", "Batch completion information should be printed to `userLogger`. All captured logs:\n\(mockUserLoggerOutput.dumpAllRecordedLogs())" ) + } + + func testWhenDataIsBeingUploaded_itPrintsUnauthoriseMessage_toUserLogger() { + let previousUserLogger = userLogger + defer { userLogger = previousUserLogger } + + let mockUserLoggerOutput = LogOutputMock() + userLogger = .mockWith(logOutput: mockUserLoggerOutput) + + // Given + writer.write(value: ["key": "value"]) + + let randomUploadStatus: DataUploadStatus = .mockWith(error: .unauthorized) + + // When + let startUploadExpectation = self.expectation(description: "Upload has started") + var mockDataUploader = DataUploaderMock(uploadStatus: randomUploadStatus) + mockDataUploader.onUpload = { startUploadExpectation.fulfill() } + + let worker = DataUploadWorker( + queue: uploaderQueue, + fileReader: reader, + dataUploader: mockDataUploader, + uploadConditions: .alwaysUpload(), + delay: DataUploadDelay(performance: UploadPerformanceMock.veryQuickInitialUpload), + featureName: .mockRandom() + ) + + wait(for: [startUploadExpectation], timeout: 0.5) + worker.cancelSynchronously() + + // Then + XCTAssertEqual(mockUserLoggerOutput.allRecordedLogs.count, 3) + XCTAssertEqual( mockUserLoggerOutput.allRecordedLogs[2].message, - randomUploadStatus.userErrorMessage, + "⚠️ The client token you provided seems to be invalid.", "An error should be printed to `userLogger`. All captured logs:\n\(mockUserLoggerOutput.dumpAllRecordedLogs())" ) + } + + func testWhenDataIsBeingUploaded_itPrintsHTTPErrorMessage_toTelemetry() { + // Given + let mockTelemetry = TelemetryMock() + + writer.write(value: ["key": "value"]) + let randomUploadStatus: DataUploadStatus = .mockWith(error: .httpError(statusCode: 500)) + + // When + let startUploadExpectation = self.expectation(description: "Upload has started") + var mockDataUploader = DataUploaderMock(uploadStatus: randomUploadStatus) + mockDataUploader.onUpload = { startUploadExpectation.fulfill() } + + let worker = DataUploadWorker( + queue: uploaderQueue, + fileReader: reader, + dataUploader: mockDataUploader, + uploadConditions: .alwaysUpload(), + delay: DataUploadDelay(performance: UploadPerformanceMock.veryQuickInitialUpload), + featureName: .mockRandom(), + telemetry: mockTelemetry + ) + wait(for: [startUploadExpectation], timeout: 0.5) + worker.cancelSynchronously() + + // Then XCTAssertEqual(mockTelemetry.errors.count, 1) + + XCTAssertEqual( + mockTelemetry.errors.first?.message, + "Data upload finished with status code: 500", + "An error should be send to internal telemetry. \(mockTelemetry)" + ) + } + + func testWhenDataIsBeingUploaded_itPrintsNetworkErrorMessage_toTelemetry() { + // Given + let mockTelemetry = TelemetryMock() + + writer.write(value: ["key": "value"]) + let randomUploadStatus: DataUploadStatus = .mockWith(error: .networkError(error: .mockAny())) + + // When + let startUploadExpectation = self.expectation(description: "Upload has started") + var mockDataUploader = DataUploaderMock(uploadStatus: randomUploadStatus) + mockDataUploader.onUpload = { startUploadExpectation.fulfill() } + + let worker = DataUploadWorker( + queue: uploaderQueue, + fileReader: reader, + dataUploader: mockDataUploader, + uploadConditions: .alwaysUpload(), + delay: DataUploadDelay(performance: UploadPerformanceMock.veryQuickInitialUpload), + featureName: .mockRandom(), + telemetry: mockTelemetry + ) + + wait(for: [startUploadExpectation], timeout: 0.5) + worker.cancelSynchronously() + + // Then + XCTAssertEqual(mockTelemetry.errors.count, 1) + XCTAssertEqual( mockTelemetry.errors.first?.message, - randomUploadStatus.telemetryError?.message, - "An error should be send to `sdkLogger` for internal monitoring. \(mockTelemetry)" + #"Data upload finished with error - Error Domain=abc Code=0 "(null)""#, + "An error should be send to internal telemetry. \(mockTelemetry)" ) } diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 28b1e226aa..3e3adbeef3 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -847,22 +847,19 @@ extension DataUploadStatus: RandomMockable { return DataUploadStatus( needsRetry: .random(), userDebugDescription: .mockRandom(), - userErrorMessage: .mockRandom(), - telemetryError: (.mockRandom(), ErrorMock()) + error: nil ) } static func mockWith( needsRetry: Bool = .mockAny(), userDebugDescription: String = .mockAny(), - userErrorMessage: String? = nil, - telemetryError: (message: String, error: Error?)? = nil + error: DataUploadError? = nil ) -> DataUploadStatus { return DataUploadStatus( needsRetry: needsRetry, userDebugDescription: userDebugDescription, - userErrorMessage: userErrorMessage, - telemetryError: telemetryError + error: error ) } } diff --git a/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift b/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift index 3cc6de5267..03c4519975 100644 --- a/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift @@ -348,6 +348,16 @@ struct FailingEncodableMock: Encodable { } } +extension NSError: AnyMockable, RandomMockable { + static func mockAny() -> Self { + .init(domain: .mockAny(), code: .mockAny()) + } + + static func mockRandom() -> Self { + .init(domain: .mockRandom(), code: .mockRandom()) + } +} + class BundleMock: Bundle { // swiftlint:disable identifier_name fileprivate var _bundlePath: String = .mockAny() From 16d4b54d9e1ef59066e5525bc07e819736b83759 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 26 Apr 2022 14:54:49 +0200 Subject: [PATCH 12/55] RUMM-2024 Remove internal monitoring test files --- .../InternalMonitoringFeatureTests.swift | 126 -------------- .../Kronos/KronosMonitorTests.swift | 156 ------------------ 2 files changed, 282 deletions(-) delete mode 100644 Tests/DatadogTests/Datadog/InternalMonitoring/InternalMonitoringFeatureTests.swift delete mode 100644 Tests/DatadogTests/Datadog/InternalMonitoring/Kronos/KronosMonitorTests.swift diff --git a/Tests/DatadogTests/Datadog/InternalMonitoring/InternalMonitoringFeatureTests.swift b/Tests/DatadogTests/Datadog/InternalMonitoring/InternalMonitoringFeatureTests.swift deleted file mode 100644 index 9d8bf0ed34..0000000000 --- a/Tests/DatadogTests/Datadog/InternalMonitoring/InternalMonitoringFeatureTests.swift +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2019-2020 Datadog, Inc. - */ - -import XCTest -@testable import Datadog - -class InternalMonitoringFeatureTests: XCTestCase { - override func setUp() { - super.setUp() - XCTAssertNil(Datadog.instance) - XCTAssertNil(InternalMonitoringFeature.instance) - temporaryFeatureDirectories.create() - } - - override func tearDown() { - XCTAssertNil(Datadog.instance) - XCTAssertNil(InternalMonitoringFeature.instance) - temporaryFeatureDirectories.delete() - super.tearDown() - } - - // MARK: - HTTP Message - - func testItUsesExpectedHTTPMessage() throws { - let randomApplicationName: String = .mockRandom(among: .alphanumerics) - let randomApplicationVersion: String = .mockRandom() - let randomSource: String = .mockRandom(among: .alphanumerics) - let randomOrigin: String = .mockRandom(among: .alphanumerics) - let randomSDKVersion: String = .mockRandom(among: .alphanumerics) - let randomUploadURL: URL = .mockRandom() - let randomClientToken: String = .mockRandom() - let randomDeviceModel: String = .mockRandom() - let randomDeviceOSName: String = .mockRandom() - let randomDeviceOSVersion: String = .mockRandom() - let randomEncryption: DataEncryption? = Bool.random() ? DataEncryptionMock() : nil - - let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) - - // Given - InternalMonitoringFeature.instance = .mockWith( - logDirectories: temporaryFeatureDirectories, - configuration: .mockWith( - common: .mockWith( - applicationName: randomApplicationName, - applicationVersion: randomApplicationVersion, - source: randomSource, - origin: randomOrigin, - sdkVersion: randomSDKVersion, - encryption: randomEncryption - ), - logsUploadURL: randomUploadURL, - clientToken: randomClientToken - ), - dependencies: .mockWith( - mobileDevice: .mockWith(model: randomDeviceModel, osName: randomDeviceOSName, osVersion: randomDeviceOSVersion) - ) - ) - defer { InternalMonitoringFeature.instance?.deinitialize() } - - // When - let sdkLogger = try XCTUnwrap(InternalMonitoringFeature.instance?.monitor.sdkLogger) - sdkLogger.debug(.mockAny()) - - // Then - let request = server.waitAndReturnRequests(count: 1)[0] - let requestURL = try XCTUnwrap(request.url) - XCTAssertEqual(request.httpMethod, "POST") - XCTAssertTrue(requestURL.absoluteString.starts(with: randomUploadURL.absoluteString + "?")) - XCTAssertEqual(requestURL.query, "ddsource=\(randomSource)") - XCTAssertEqual( - request.allHTTPHeaderFields?["User-Agent"], - """ - \(randomApplicationName)/\(randomApplicationVersion) CFNetwork (\(randomDeviceModel); \(randomDeviceOSName)/\(randomDeviceOSVersion)) - """ - ) - XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], "application/json") - XCTAssertEqual(request.allHTTPHeaderFields?["Content-Encoding"], "deflate") - XCTAssertEqual(request.allHTTPHeaderFields?["DD-API-KEY"], randomClientToken) - XCTAssertEqual(request.allHTTPHeaderFields?["DD-EVP-ORIGIN"], randomOrigin) - XCTAssertEqual(request.allHTTPHeaderFields?["DD-EVP-ORIGIN-VERSION"], randomSDKVersion) - XCTAssertEqual(request.allHTTPHeaderFields?["DD-REQUEST-ID"]?.matches(regex: .uuidRegex), true) - } - - // MARK: - Sending SDK Logs - - func testItSendsApplicationAndSDKInfoWithLogs() throws { - InternalMonitoringFeature.instance = .mockByRecordingLogMatchers( - directories: temporaryFeatureDirectories, - configuration: .mockWith( - common: .mockWith( - applicationName: "ApplicationName", - applicationVersion: "2.0.0", - applicationBundleIdentifier: "com.application.bundle.id", - serviceName: .mockRandom(), - environment: .mockRandom(), - sdkVersion: "1.2.3" - ), - sdkServiceName: "sdk-service-name", - sdkEnvironment: "sdk-environment" - ), - dependencies: .mockWith( - dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) - ) - ) - defer { InternalMonitoringFeature.instance?.deinitialize() } - - let sdkLogger = try XCTUnwrap(InternalMonitoringFeature.instance?.monitor.sdkLogger) - sdkLogger.error("internal error message") - - let logMatcher = try InternalMonitoringFeature.waitAndReturnLogMatchers(count: 1)[0] - - typealias Attribute = LogMatcher.JSONKey - logMatcher.assertValue(forKeyPath: Attribute.date, equals: "2019-12-15T10:00:00.000Z") - logMatcher.assertValue(forKeyPath: Attribute.message, equals: "internal error message") - logMatcher.assertValue(forKeyPath: Attribute.serviceName, equals: "sdk-service-name") - logMatcher.assertValue(forKeyPath: Attribute.loggerName, equals: "im-logger") - logMatcher.assertValue(forKeyPath: Attribute.loggerVersion, equals: "1.2.3") - logMatcher.assertTags(equal: ["env:sdk-environment"]) - logMatcher.assertValue(forKeyPath: Attribute.applicationVersion, equals: "2.0.0") - logMatcher.assertValue(forKeyPath: "application.name", equals: "ApplicationName") - logMatcher.assertValue(forKeyPath: "application.bundle-id", equals: "com.application.bundle.id") - } -} diff --git a/Tests/DatadogTests/Datadog/InternalMonitoring/Kronos/KronosMonitorTests.swift b/Tests/DatadogTests/Datadog/InternalMonitoring/Kronos/KronosMonitorTests.swift deleted file mode 100644 index 6d27b952f9..0000000000 --- a/Tests/DatadogTests/Datadog/InternalMonitoring/Kronos/KronosMonitorTests.swift +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2019-2020 Datadog, Inc. - */ - -import XCTest -@testable import Datadog - -private struct IPConnectionMonitorMock: IPConnectionMonitorType { - let queue: DispatchQueue - let results: [KronosInternetAddress: IPConnectionCheckResult] - let resultDelay: (KronosInternetAddress) -> TimeInterval - - func checkConnection(to ip: KronosInternetAddress, resultCallback: @escaping (IPConnectionCheckResult) -> Void) { - queue.asyncAfter(deadline: .now() + resultDelay(ip)) { - resultCallback(results[ip]!) - } - } -} - -class KronosMonitorTests: XCTestCase { - private let randomNTPPool: String = .mockRandom(among: .decimalDigits) + ".ntp.org" - private let ip1: KronosInternetAddress = .mockWith(ipString: "10.0.0.1") - private let ip2: KronosInternetAddress = .mockWith(ipString: "10.0.0.2") - private let ip3: KronosInternetAddress = .mockWith(ipString: "10.0.0.3") - - func testWhenNTPPoolIsResolvedToRemoteIPAddresses_andServerOffsetIsRetrieved_itChecksAllConnections_andSendsINFOLog() throws { - let (recordedLog, recordedSyncResult) = try simulateNTPSynchronisation( - to: randomNTPPool, - withDNSResolvingTo: [ - ip1: .mockWith(isLocalNetworkDenied: false), // remote IP - ip2: .mockWith(isLocalNetworkDenied: false), // remote IP - ip3: .mockWith(isLocalNetworkDenied: false), // remote IP - ], - andRetrievingServerOffset: .mockRandom() // server offset retrieved - ) - - XCTAssertEqual(recordedLog.status, .info, "It must send INFO log") - XCTAssertEqual(recordedLog.message, "Kronos resolved \(randomNTPPool) with receiving server offset") - - XCTAssertEqual(recordedSyncResult.ips.count, 3, "It must record connection status for all resolved IPs") - XCTAssertTrue(recordedSyncResult.ips.values.contains { $0.address == ip1.host! }, "It must check connection status for \(ip1.host!)") - XCTAssertTrue(recordedSyncResult.ips.values.contains { $0.address == ip2.host! }, "It must check connection status for \(ip2.host!)") - XCTAssertTrue(recordedSyncResult.ips.values.contains { $0.address == ip3.host! }, "It must check connection status for \(ip3.host!)") - XCTAssertFalse(recordedSyncResult.ips.values.contains { $0.connectionCheckResult!.isLocalNetworkDenied }, "It must report all IPs as remote") - } - - func testWhenNTPPoolIsResolvedToRemoteIPAddresses_butServerOffsetIsNotRetrieved_itChecksAllConnections_andSendsINFOLog() throws { - let (recordedLog, recordedSyncResult) = try simulateNTPSynchronisation( - to: randomNTPPool, - withDNSResolvingTo: [ - ip1: .mockWith(isLocalNetworkDenied: false), // remote IP - ip2: .mockWith(isLocalNetworkDenied: false), // remote IP - ip3: .mockWith(isLocalNetworkDenied: false), // remote IP - ], - andRetrievingServerOffset: nil // no server offset retrieved - ) - - XCTAssertEqual(recordedLog.status, .info, "It must send INFO log") - XCTAssertEqual(recordedLog.message, "Kronos resolved \(randomNTPPool) but received no server offset") - - XCTAssertEqual(recordedSyncResult.ips.count, 3, "It must record connection status for all resolved IPs") - XCTAssertTrue(recordedSyncResult.ips.values.contains { $0.address == ip1.host! }, "It must check connection status for \(ip1.host!)") - XCTAssertTrue(recordedSyncResult.ips.values.contains { $0.address == ip2.host! }, "It must check connection status for \(ip2.host!)") - XCTAssertTrue(recordedSyncResult.ips.values.contains { $0.address == ip3.host! }, "It must check connection status for \(ip3.host!)") - XCTAssertFalse(recordedSyncResult.ips.values.contains { $0.connectionCheckResult!.isLocalNetworkDenied }, "It must report all IPs as remote") - } - - func testWhenNTPPoolIsResolvedToLocalIPAddresses_itChecksAllConnections_andSendsERRORLog() throws { - let (recordedLog, recordedSyncResult) = try simulateNTPSynchronisation( - to: randomNTPPool, - withDNSResolvingTo: [ - ip1: .mockWith(isLocalNetworkDenied: false), // remote IP - ip2: .mockWith(isLocalNetworkDenied: false), // remote IP - ip3: .mockWith(isLocalNetworkDenied: true), // local IP, IRL this will trigger 'Local Network Permission' - ], - andRetrievingServerOffset: Bool.mockRandom() ? .mockRandom() : nil // no matter if retrieving offset - ) - - XCTAssertEqual(recordedLog.status, .error, "It must send ERROR log") - XCTAssertEqual(recordedLog.message, "Kronos sync to \(randomNTPPool) was blocked on trying to connect to local network") - - XCTAssertEqual(recordedSyncResult.ips.count, 3, "It must record connection status for all resolved IPs") - XCTAssertTrue(recordedSyncResult.ips.values.contains { $0.address == ip1.host! }, "It must check connection status for \(ip1.host!)") - XCTAssertTrue(recordedSyncResult.ips.values.contains { $0.address == ip2.host! }, "It must check connection status for \(ip2.host!)") - XCTAssertTrue(recordedSyncResult.ips.values.contains { $0.address == ip3.host! }, "It must check connection status for \(ip3.host!)") - XCTAssertTrue( - recordedSyncResult.ips.values.contains { $0.connectionCheckResult!.isLocalNetworkDenied } && - recordedSyncResult.ips.values.contains { !$0.connectionCheckResult!.isLocalNetworkDenied }, - "It must report both local and remote IPs" - ) - } - - /// Simulates `KronosClock` execution and returns telemetry uploaded to Datadog. - func simulateNTPSynchronisation( - to pool: String, - withDNSResolvingTo dnsResolution: [KronosInternetAddress: IPConnectionCheckResult], - andRetrievingServerOffset serverOffset: TimeInterval? - ) throws -> (log: LogEvent, syncResult: KronosInternalMonitor.SyncResult) { - let queue = DispatchQueue(label: "kronos-monitor-tests") - - // Given - let resolvedIPs = Array(dnsResolution.keys) - let mockIPConnectionMonitor = IPConnectionMonitorMock( - queue: queue, - results: dnsResolution, - resultDelay: { _ in .mockRandom(min: 0.1, max: 0.5) } // random delay for each connection - ) - let mockLogOutput = LogOutputMock() - let mockExporter = InternalMonitor( - sdkLogger: .mockWith(logOutput: mockLogOutput) - ) - - let monitor = KronosInternalMonitor(queue: queue, connectionMonitor: mockIPConnectionMonitor) - - // When - monitor.notifySyncStart(from: pool) - monitor.notifyResolveDNS(to: resolvedIPs) - resolvedIPs.forEach { ip in - monitor.notifyStartQuerying(ip: ip, numberOfSamples: 1) - monitor.notifyReceivePacket(from: ip, isValidSample: .mockRandom()) - monitor.notifyEndQuerying(ip: ip) - } - monitor.notifySyncEnd(serverOffset: serverOffset) - monitor.export(to: mockExporter) - - // Then - let expectation = self.expectation(description: "Send telemetry log") - mockLogOutput.onLogRecorded = { _ in expectation.fulfill() } - - waitForExpectations(timeout: 5, handler: nil) - - let recordedLog = try XCTUnwrap(mockLogOutput.recordedLog) - let recordedSyncResult = try XCTUnwrap(recordedLog.attributes.userAttributes["sync-result"] as? KronosInternalMonitor.SyncResult) - return (recordedLog, recordedSyncResult) - } -} - -// MARK: - Helpers - -private extension KronosInternetAddress { - static func mockWith(ipString: String) -> KronosInternetAddress { - var addr = sockaddr_in() - addr.sin_len = UInt8(MemoryLayout.size(ofValue: addr)) - addr.sin_family = sa_family_t(AF_INET) - addr.sin_addr.s_addr = inet_addr(ipString) - return .ipv4(addr) - } -} - -private extension IPConnectionCheckResult { - static func mockWith(isLocalNetworkDenied: Bool) -> IPConnectionCheckResult { - return .init(isLocalNetworkDenied: isLocalNetworkDenied, details: .mockRandom()) - } -} From fb8aff3aa598459b848b18c7bbfce27a9f1352ae Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 26 Apr 2022 14:57:25 +0200 Subject: [PATCH 13/55] RUMM-2024 Remove kronos monitor from e2e tests --- Datadog/E2ETests/NTP/KronosE2ETests.swift | 112 ---------------------- 1 file changed, 112 deletions(-) diff --git a/Datadog/E2ETests/NTP/KronosE2ETests.swift b/Datadog/E2ETests/NTP/KronosE2ETests.swift index a5b93cc633..86b7b998ed 100644 --- a/Datadog/E2ETests/NTP/KronosE2ETests.swift +++ b/Datadog/E2ETests/NTP/KronosE2ETests.swift @@ -10,7 +10,6 @@ class KronosE2ETests: E2ETests { /// The logger sending logs on Kronos execution. These logs are available in Mobile Integrations org. private var logger: Logger! // swiftlint:disable:this implicitly_unwrapped_optional /// The logger sending telemetry on internal Kronos execution. These logs are available in Mobile Integrations org. - private var telemetryLogger: Logger! // swiftlint:disable:this implicitly_unwrapped_optional private let queue = DispatchQueue(label: "kronos-monitor-queue") override func setUp() { @@ -19,33 +18,13 @@ class KronosE2ETests: E2ETests { .builder .set(loggerName: "kronos-e2e") .build() - telemetryLogger = Logger.builder - .set(loggerName: "kronos-e2e-internal-telemetry") - .sendNetworkInfo(true) - .build() } override func tearDown() { logger = nil - telemetryLogger = nil super.tearDown() } - /// Creates kronos monitor for checking connections to all IPs resolved from NTP pool and sending additional telemetry on their statuses. - private func createKronosMonitor() -> KronosMonitor? { - if #available(iOS 14.2, *) { - let monitor = KronosInternalMonitor( - queue: queue, - connectionMonitor: IPConnectionMonitor(queue: queue) - ) - // Here we redirect IM's logger to E2E Kronos logger (`telemetryLogger`) to send data to Mobile Integrations org, not IM's org - monitor.export(to: InternalMonitor(sdkLogger: telemetryLogger)) - return monitor - } else { - return nil - } - } - /// TODO: RUMM-1859: Add E2E tests for monitoring Kronos in nightly tests func test_kronos_clock_performs_sync_using_datadog_ntp_pool() { // E2E:wip /// The result of `KronosClock.sync()`. @@ -81,7 +60,6 @@ class KronosE2ETests: E2ETests { KronosClock.sync( from: pool, samples: numberOfSamplesForEachIP, - monitor: createKronosMonitor(), first: { date, offset in // this closure could not be called if all samples to all servers resulted with failure result.firstReceivedDate = date result.firstReceivedOffset = offset @@ -131,94 +109,4 @@ class KronosE2ETests: E2ETests { } } } - - /// TODO: RUMM-1859: Add E2E tests for monitoring Kronos in nightly tests - func test_kronos_ntp_client_queries_both_ipv4_and_ipv6_ips() { // E2E:wip - /// The result of `KronosNTPClient.query(pool:)`. - struct KronosNTPClientQueryResult { - /// Partial offsets received for each NTP packet sent to each resolved IP. - var receivedOffsets: [TimeInterval?] = [] - /// Expected number of NTP packets to send. - var expectedNumberOfSamples = 0 - /// Actual number of NTP packets that completed. - var numberOfCompletedSamples = 0 - } - - func performKronosNTPClientQuery() -> KronosNTPClientQueryResult { - let testTimeout: TimeInterval = 30 - let monitor = createKronosMonitor() - - // Given - let pool = "2.datadog.pool.ntp.org" // a pool resolved to multiple IPv4 and IPv6 addresses (e.g. 4 + 4) - let numberOfSamplesForEachIP = 2 // exchange only 2 samples with each resolved IP - to run test quick - - // Each IP (each server) is asked in parallel, but samples are obtained sequentially. - // Here we compute individual sample timeout, to ensure that all (parallel) servers complete querying their (sequential) samples - // below `testTimeout` with assuming -30% margin. This should guarantee no flakiness on test timeout. - let timeoutForEachSample = (testTimeout / Double(numberOfSamplesForEachIP)) * 0.7 - - // When - let completionExpectation = expectation(description: "It completes all samples for all IPs") - var result = KronosNTPClientQueryResult() - - monitor?.notifySyncStart(from: pool) // must be notified by hand because normally it's called from `KronosClock.sync()` - - KronosNTPClient() - .query( - pool: pool, - numberOfSamples: numberOfSamplesForEachIP, - maximumServers: .max, // query all resolved IPs in the pool - to include both IPv4 and IPv6 - timeout: timeoutForEachSample, - monitor: monitor - ) { offset, completed, total in - result.receivedOffsets.append(offset) - result.numberOfCompletedSamples = completed - result.expectedNumberOfSamples = total - - if completed == total { - monitor?.notifySyncEnd(serverOffset: offset) // must be notified by hand because normally it's called from `KronosClock.sync()` - completionExpectation.fulfill() - } - } - - // Then - - // We don't expect receiving timeout on `completionExpectation`. Number of samples and individual sample timeout - // is configured in a way that lets `KronosNTPClient` always fulfill the `completionExpectation`. - // In worst case, it can fulfill it, with recording only `nil` offsets, which will mean receiving timeouts - // or error on all NTP queries. - waitForExpectations(timeout: testTimeout) - - return result - } - - // Run test: - let result = measure(resourceName: DD.PerfSpanName.fromCurrentMethodName()) { - performKronosNTPClientQuery() - } - - // Report result: - if result.receivedOffsets.contains(where: { offset in offset != nil }) { - // We consider `KronosNTPClient.query(pool:)` result to be consistent if it received at least one offset. - let receivedOffsets: [String] = result.receivedOffsets.map { offset in - if let offset = offset { - return "\(offset)" - } else { - return "(nil)" - } - } - logger.info( - "KronosNTPClient.query(pool:) completed with consistent result receiving \(result.numberOfCompletedSamples)/\(result.expectedNumberOfSamples) NTP packets", - attributes: [ - "offsets_received": receivedOffsets - ] - ) - } else { - // Inconsistent result may correspond to flaky execution, e.g. if network was unreachable or if **all** NTP calls received timeout. - // We track inconsistent result as WARN log that will be watched by E2E monitor. - logger.warn( - "KronosNTPClient.query(pool:) completed with inconsistent result receiving \(result.numberOfCompletedSamples)/\(result.expectedNumberOfSamples) NTP packets" - ) - } - } } From f37d9e4e0c16b3d4ff59a27bb3149928f6a004de Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 26 Apr 2022 20:25:58 +0200 Subject: [PATCH 14/55] RUMM-1651 Fix GH asset validation for tvOS --- tools/distribution/src/release/assets/gh_asset.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/distribution/src/release/assets/gh_asset.py b/tools/distribution/src/release/assets/gh_asset.py index 417cf6620f..036b2122c1 100644 --- a/tools/distribution/src/release/assets/gh_asset.py +++ b/tools/distribution/src/release/assets/gh_asset.py @@ -55,7 +55,7 @@ def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool 'tvos-arm64/BCSymbolMaps/*.bcsymbolmap', 'tvos-arm64/dSYMs/*.dSYM', 'tvos-arm64/**/arm64.swiftinterface', - 'tvos-arm64/**/arm64-apple-ios.swiftinterface', + 'tvos-arm64/**/arm64-apple-tvos.swiftinterface', 'tvos-arm64_x86_64-simulator', 'tvos-arm64_x86_64-simulator/dSYMs/*.dSYM', @@ -97,7 +97,7 @@ def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool 'tvos-arm64/BCSymbolMaps/*.bcsymbolmap', 'tvos-arm64/dSYMs/*.dSYM', 'tvos-arm64/**/arm64.swiftinterface', - 'tvos-arm64/**/arm64-apple-ios.swiftinterface', + 'tvos-arm64/**/arm64-apple-tvos.swiftinterface', 'tvos-arm64_x86_64-simulator', 'tvos-arm64_x86_64-simulator/**/arm64.swiftinterface', @@ -136,7 +136,7 @@ def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool 'tvos-arm64', 'tvos-arm64/BCSymbolMaps/*.bcsymbolmap', 'tvos-arm64/**/arm64.swiftinterface', - 'tvos-arm64/**/arm64-apple-ios.swiftinterface', + 'tvos-arm64/**/arm64-apple-tvos.swiftinterface', 'tvos-arm64_x86_64-simulator', 'tvos-arm64_x86_64-simulator/dSYMs/*.dSYM', From b3d6bfbe6984635b30b84c59e3aed9bd95fe95fe Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 27 Apr 2022 10:35:54 +0200 Subject: [PATCH 15/55] RUMM-2152 SwiftUI linked weakly Pre-iOS 13 simulators crash due to dyld issues at runtime Source: https://developer.apple.com/forums/thread/126506 --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 8393e3f558..277804863f 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -512,6 +512,8 @@ 9E5B6D2E270C84B4002499B8 /* RUMMonitorE2ETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5B6D2D270C84B4002499B8 /* RUMMonitorE2ETests.swift */; }; 9E5B6D30270C85AB002499B8 /* RUMUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5B6D2F270C85AB002499B8 /* RUMUtils.swift */; }; 9E5B6D32270DE9E5002499B8 /* RUMTrackingConsentE2ETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5B6D31270DE9E5002499B8 /* RUMTrackingConsentE2ETests.swift */; }; + 9E5BD8042819742200CB568E /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61B03ECC274FF00E00EB1AE1 /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 9E5BD8062819742C00CB568E /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E5BD8052819742C00CB568E /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 9E64849D27031071007CCD7B /* RUMGlobalE2ETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E64849C27031071007CCD7B /* RUMGlobalE2ETests.swift */; }; 9E68FB55244707FD0013A8AA /* ObjcExceptionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E68FB53244707FD0013A8AA /* ObjcExceptionHandler.m */; }; 9E68FB56244707FD0013A8AA /* ObjcExceptionHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E68FB54244707FD0013A8AA /* ObjcExceptionHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1706,6 +1708,7 @@ 9E5B6D2D270C84B4002499B8 /* RUMMonitorE2ETests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMonitorE2ETests.swift; sourceTree = ""; }; 9E5B6D2F270C85AB002499B8 /* RUMUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMUtils.swift; sourceTree = ""; }; 9E5B6D31270DE9E5002499B8 /* RUMTrackingConsentE2ETests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMTrackingConsentE2ETests.swift; sourceTree = ""; }; + 9E5BD8052819742C00CB568E /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS15.4.sdk/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; }; 9E64849C27031071007CCD7B /* RUMGlobalE2ETests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMGlobalE2ETests.swift; sourceTree = ""; }; 9E68FB53244707FD0013A8AA /* ObjcExceptionHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjcExceptionHandler.m; sourceTree = ""; }; 9E68FB54244707FD0013A8AA /* ObjcExceptionHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjcExceptionHandler.h; sourceTree = ""; }; @@ -1804,6 +1807,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9E5BD8042819742200CB568E /* SwiftUI.framework in Frameworks */, D240687827CF982B00C04F44 /* CrashReporter.xcframework in Frameworks */, D240687B27CF982C00C04F44 /* Datadog.framework in Frameworks */, D240687D27CF982D00C04F44 /* DatadogCrashReporting.framework in Frameworks */, @@ -1882,6 +1886,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9E5BD8062819742C00CB568E /* SwiftUI.framework in Frameworks */, D240687027CF971C00C04F44 /* CrashReporter.xcframework in Frameworks */, D240687127CF971C00C04F44 /* Datadog.framework in Frameworks */, D240687227CF971C00C04F44 /* DatadogCrashReporting.framework in Frameworks */, @@ -2493,6 +2498,7 @@ isa = PBXGroup; children = ( 61B03ECC274FF00E00EB1AE1 /* SwiftUI.framework */, + 9E5BD8052819742C00CB568E /* SwiftUI.framework */, 614ED36B260352DC00C8C519 /* CrashReporter.xcframework */, 9E0542CA25F8EBBE007A3D0B /* Kronos.xcframework */, ); From 7f7e6d534b228b161b1b10b1792c904cd308299c Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 26 Apr 2022 15:20:36 +0200 Subject: [PATCH 16/55] RUMM-2153 Prevent Kronos logic from querying privte IPs --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 + .../Datadog/Kronos/KronosDNSResolver.swift | 1 + .../Kronos/KronosInternetAddress.swift | 46 +++++++ .../Kronos/KronosInternetAddressTests.swift | 120 ++++++++++++++++++ .../SystemFrameworks/FoundationMocks.swift | 6 +- 5 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 Tests/DatadogTests/Datadog/Kronos/KronosInternetAddressTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 8393e3f558..9eb20cfbf5 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -353,6 +353,8 @@ 61B7885D25C180CB002675B5 /* DatadogCrashReporting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61B7885425C180CB002675B5 /* DatadogCrashReporting.framework */; }; 61B7886225C180CB002675B5 /* DDCrashReportingPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B7886125C180CB002675B5 /* DDCrashReportingPluginTests.swift */; }; 61B7886425C180CB002675B5 /* DatadogCrashReporting.h in Headers */ = {isa = PBXBuildFile; fileRef = 61B7885625C180CB002675B5 /* DatadogCrashReporting.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 61B8BA91281812F60068AFF4 /* KronosInternetAddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B8BA90281812F60068AFF4 /* KronosInternetAddressTests.swift */; }; + 61B8BA92281812F60068AFF4 /* KronosInternetAddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B8BA90281812F60068AFF4 /* KronosInternetAddressTests.swift */; }; 61B9ED1C2461E12000C0DCFF /* SendLogsFixtureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B9ED1A2461E12000C0DCFF /* SendLogsFixtureViewController.swift */; }; 61B9ED1D2461E12000C0DCFF /* SendTracesFixtureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B9ED1B2461E12000C0DCFF /* SendTracesFixtureViewController.swift */; }; 61B9ED1F2461E57700C0DCFF /* UITestsHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B9ED1E2461E57700C0DCFF /* UITestsHelpers.swift */; }; @@ -1549,6 +1551,7 @@ 61B7885C25C180CB002675B5 /* DatadogCrashReportingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DatadogCrashReportingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61B7886125C180CB002675B5 /* DDCrashReportingPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDCrashReportingPluginTests.swift; sourceTree = ""; }; 61B7886325C180CB002675B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 61B8BA90281812F60068AFF4 /* KronosInternetAddressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KronosInternetAddressTests.swift; sourceTree = ""; }; 61B9ED1A2461E12000C0DCFF /* SendLogsFixtureViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendLogsFixtureViewController.swift; sourceTree = ""; }; 61B9ED1B2461E12000C0DCFF /* SendTracesFixtureViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendTracesFixtureViewController.swift; sourceTree = ""; }; 61B9ED1E2461E57700C0DCFF /* UITestsHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsHelpers.swift; sourceTree = ""; }; @@ -3603,6 +3606,7 @@ children = ( 61D3E0DF277B3D92008BE766 /* KronosNTPPacketTests.swift */, 61D3E0E2277B3D92008BE766 /* KronosTimeStorageTests.swift */, + 61B8BA90281812F60068AFF4 /* KronosInternetAddressTests.swift */, ); path = Kronos; sourceTree = ""; @@ -5182,6 +5186,7 @@ D248ED4A28081D7500B315B4 /* RUMTelemetryTests.swift in Sources */, 61B0387D252724AB00518F3C /* URLSessionSwizzlerTests.swift in Sources */, 617CD0DD24CEDDD300B0B557 /* RUMUserActionScopeTests.swift in Sources */, + 61B8BA91281812F60068AFF4 /* KronosInternetAddressTests.swift in Sources */, D29889C9273413ED00A4D1A9 /* RUMViewsHandlerTests.swift in Sources */, 61C5A8A024509C1100DA608C /* Casting+Tracing.swift in Sources */, 61133C662423990D00786299 /* LogSanitizerTests.swift in Sources */, @@ -5801,6 +5806,7 @@ D2CB6ED727C520D400A62B57 /* URLSessionSwizzlerTests.swift in Sources */, D248ED4B28081D7E00B315B4 /* RUMTelemetryTests.swift in Sources */, D2CB6ED827C520D400A62B57 /* RUMUserActionScopeTests.swift in Sources */, + 61B8BA92281812F60068AFF4 /* KronosInternetAddressTests.swift in Sources */, D2CB6ED927C520D400A62B57 /* RUMViewsHandlerTests.swift in Sources */, D2CB6EDA27C520D400A62B57 /* Casting+Tracing.swift in Sources */, D2CB6EDB27C520D400A62B57 /* LogSanitizerTests.swift in Sources */, diff --git a/Sources/Datadog/Kronos/KronosDNSResolver.swift b/Sources/Datadog/Kronos/KronosDNSResolver.swift index 042de51de6..2ff6cfab84 100644 --- a/Sources/Datadog/Kronos/KronosDNSResolver.swift +++ b/Sources/Datadog/Kronos/KronosDNSResolver.swift @@ -49,6 +49,7 @@ internal final class KronosDNSResolver { let IPs = (addresses.takeUnretainedValue() as NSArray) .compactMap { $0 as? NSData } .compactMap(KronosInternetAddress.init) + .filter { ip in !ip.isPrivate } // to avoid querying private IPs, see: https://github.com/DataDog/dd-sdk-ios/issues/647 resolver.completion?(IPs) retainedSelf.release() diff --git a/Sources/Datadog/Kronos/KronosInternetAddress.swift b/Sources/Datadog/Kronos/KronosInternetAddress.swift index 9c912a87ff..9c894cb669 100644 --- a/Sources/Datadog/Kronos/KronosInternetAddress.swift +++ b/Sources/Datadog/Kronos/KronosInternetAddress.swift @@ -43,6 +43,52 @@ internal enum KronosInternetAddress: Hashable { } } + /// If the address is reserved for private internets (local / private IP). + var isPrivate: Bool { + guard let host = host else { + return false + } + + switch self { + case .ipv6: + // Ref.: https://datatracker.ietf.org/doc/html/rfc4193#section-3 + // +--------+-+------------+-----------+----------------------------+ + // | 7 bits |1| 40 bits | 16 bits | 64 bits | + // +--------+-+------------+-----------+----------------------------+ + // | Prefix |L| Global ID | Subnet ID | Interface ID | + // +--------+-+------------+-----------+----------------------------+ + // + // Local IP is expected to have FC00::/7 prefix (7 bits) and L byte set to 1, + // which effectively means `fd` prefix for local IPs. + let localPrefix = "fd" + + // Ref.: https://datatracker.ietf.org/doc/html/rfc4291#section-2.4 + let multicastPrefix = "ff" + + let hostLowercased = host.lowercased() + return hostLowercased.starts(with: localPrefix) + || hostLowercased.starts(with: multicastPrefix) + case .ipv4: + // Ref.: https://datatracker.ietf.org/doc/html/rfc1918#section-3 + // Local IPs have predefined ranges: + // - class A: 10.0.0.0 — 10.255.255.255 + // - class B: 172.16.0.0 — 172.31.255.255 + // - class C: 192.168.0.0 — 192.168.255.255 + let classABCregex = #"^((10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.))"# + + // Ref.: https://datatracker.ietf.org/doc/html/rfc5771#section-3 + // Multicast address (range 224.0.0.0 - 239.255.255.255) are considered to be local network + // addresses too (https://developer.apple.com/forums/thread/663848) + // + let multicastRegex = #"^((22[4-9]\.)|(23[0-9]\.))"# + let broadcastIP = "255.255.255.255" + + return host.range(of: classABCregex, options: .regularExpression) != nil + || host.range(of: multicastRegex, options: .regularExpression) != nil + || host == broadcastIP + } + } + func hash(into hasher: inout Hasher) { hasher.combine(self.host) } diff --git a/Tests/DatadogTests/Datadog/Kronos/KronosInternetAddressTests.swift b/Tests/DatadogTests/Datadog/Kronos/KronosInternetAddressTests.swift new file mode 100644 index 0000000000..a432ac3b7a --- /dev/null +++ b/Tests/DatadogTests/Datadog/Kronos/KronosInternetAddressTests.swift @@ -0,0 +1,120 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +@testable import Datadog + +class KronosInternetAddressTests: XCTestCase { + func testIfIPv4AddressIsPrivate() throws { + let privateIPs: [KronosInternetAddress] = try (0..<50).flatMap { _ in + return [ + // random private IPs of class A: 10.0.0.0 — 10.255.255.255 + try .mockIPv4([10, .mockRandom(), .mockRandom(), .mockRandom()]), + // random private IPs of class B: 172.16.0.0 — 172.31.255.255 + try .mockIPv4([172, .mockRandom(min: 16, max: 31), .mockRandom(), .mockRandom()]), + // random private IPs of class C: 192.168.0.0 — 192.168.255.255 + try .mockIPv4([192, 168, .mockRandom(), .mockRandom()]), + // multicast IPs 224.0.0.0 - 239.255.255.255 + try .mockIPv4([.mockRandom(min: 224, max: 239), .mockRandom(), .mockRandom(), .mockRandom()]), + // broadcast IP 255.255.255.255 + try .mockIPv4([255, 255, 255, 255]), + ] + } + let publicIPs: [KronosInternetAddress] = try (0..<50).flatMap { _ in + return [ + try .mockIPv4([.mockRandom(otherThan: Set([10, 172, 192, 255] + (224...239))), .mockRandom(), .mockRandom(), .mockRandom()]), + try .mockIPv4([172, .mockRandom(min: 0, max: 15), .mockRandom(), .mockRandom()]), + try .mockIPv4([172, .mockRandom(min: 32, max: 255), .mockRandom(), .mockRandom()]), + try .mockIPv4([192, .mockRandom(otherThan: [168]), .mockRandom(), .mockRandom()]), + try .mockIPv4([255, .mockRandom(max: 254), .mockRandom(), .mockRandom()]), + try .mockIPv4([255, .mockRandom(), .mockRandom(max: 254), .mockRandom()]), + try .mockIPv4([255, .mockRandom(), .mockRandom(), .mockRandom(max: 254)]), + ] + } + + privateIPs.forEach { ip in + XCTAssertTrue(ip.isPrivate, "\(ip.host ?? "nil") should be private IP") + } + publicIPs.forEach { ip in + XCTAssertFalse(ip.isPrivate, "\(ip.host ?? "nil") should not be private IP") + } + } + + func testIfIPv6AddressIsPrivate() throws { + let privateIPs: [KronosInternetAddress] = try (0..<50).flatMap { _ in + return [ + // random private IP starting with `fd` prefix + try .mockIPv6([0xfd] + (0..<15).map({ _ in .mockRandom() })), + // random multicast IP starting with `ff` prefix + try .mockIPv6([0xff] + (0..<15).map({ _ in .mockRandom() })), + ] + } + let publicIPs: [KronosInternetAddress] = try (0..<50).flatMap { _ in + return [ + // first byte is mocked to avoid having `fd` or `ff` prefix + try .mockIPv6([.mockRandom(min: 0xf0, otherThan: [0xfd, 0xff])] + (0..<15).map({ _ in .mockRandom() })), + try .mockIPv6([.mockRandom(max: 0xfc, otherThan: [0xf])] + (0..<15).map({ _ in .mockRandom() })), + ] + } + + privateIPs.forEach { ip in + XCTAssertTrue(ip.isPrivate, "\(ip.host ?? "nil") should be private IP") + } + publicIPs.forEach { ip in + XCTAssertFalse(ip.isPrivate, "\(ip.host ?? "nil") should not be private IP") + } + } +} + +// MARK: - Mocks + +private extension KronosInternetAddress { + static func mockIPv4(_ bytes: [UInt8]) throws -> KronosInternetAddress { + precondition(bytes.count == 4, "Expected 4 bytes") + let numbers = bytes.map { String($0) } + let ipv4String = numbers.joined(separator: ".") // e.g. '192.168.1.1' + let address: KronosInternetAddress? = .mockWith(ipv4String: ipv4String) + return try XCTUnwrap(address, "\(ipv4String) is not a valid IPv4 string") + } + + static func mockIPv6(_ bytes: [UInt8]) throws -> KronosInternetAddress { + precondition(bytes.count == 16, "Expected 16 bytes") + let groups: [String] = (0..<8).map { idx in + let hexA = String(bytes[idx * 2], radix: 16) + let hexB = String(bytes[idx * 2 + 1], radix: 16) + return hexA + hexB + } + let ipv6String = groups.joined(separator: ":") // e.g. 'ab:ab:ab:ab:ab:ab:ab:ab' + let address: KronosInternetAddress? = .mockWith(ipv6String: ipv6String.randomcased()) + return try XCTUnwrap(address, "\(ipv6String) is not a valid IPv6 string") + } + + static func mockWith(ipv4String: String) -> KronosInternetAddress? { + var inaddr = in_addr() + guard ipv4String.withCString({ inet_pton(AF_INET, $0, &inaddr) }) == 1 else { + return nil // likely, not an IPv4 string + } + + var addr = sockaddr_in() + addr.sin_len = UInt8(MemoryLayout.size(ofValue: addr)) + addr.sin_family = sa_family_t(AF_INET) + addr.sin_addr = inaddr + return .ipv4(addr) + } + + static func mockWith(ipv6String: String) -> KronosInternetAddress? { + var inaddr = in6_addr() + guard ipv6String.withCString({ inet_pton(AF_INET6, $0, &inaddr) }) == 1 else { + return nil // likely, not an IPv6 string + } + + var addr = sockaddr_in6() + addr.sin6_len = UInt8(MemoryLayout.size(ofValue: addr)) + addr.sin6_family = sa_family_t(AF_INET6) + addr.sin6_addr = inaddr + return .ipv6(addr) + } +} diff --git a/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift b/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift index 3cc6de5267..2a25b94152 100644 --- a/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift @@ -270,8 +270,10 @@ extension FixedWidthInteger where Self: RandomMockable { return .random(in: min...max) } - static func mockRandom(min: Self = .min, max: Self = .max) -> Self { - return .random(in: min...max) + static func mockRandom(min: Self = .min, max: Self = .max, otherThan values: Set = []) -> Self { + var random: Self = .random(in: min...max) + while values.contains(random) { random = .random(in: min...max) } + return random } } From 25d9eaecac43a1384648fafc79bfcf05610b443e Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 27 Apr 2022 11:53:16 +0200 Subject: [PATCH 17/55] RUMM-2153 Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51d2595817..1f0fa32e76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +* [BUGFIX] Fix rare problem with bringing up the "Local Network Permission" alert. See [#830][] + # 1.11.0-beta1 / 04-26-2022 ### Changes @@ -347,6 +349,7 @@ [#795]: https://github.com/DataDog/dd-sdk-ios/issues/795 [#797]: https://github.com/DataDog/dd-sdk-ios/issues/797 [#815]: https://github.com/DataDog/dd-sdk-ios/issues/815 +[#830]: https://github.com/DataDog/dd-sdk-ios/issues/830 [@00FA9A]: https://github.com/00FA9A [@Britton-Earnin]: https://github.com/Britton-Earnin [@Hengyu]: https://github.com/Hengyu From ae560c22ab8ed75d3c14e483135f20b922a4baca Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Wed, 27 Apr 2022 17:02:45 -0400 Subject: [PATCH 18/55] Fix RUM events to support sending correctly configured `source` value Fixes RUMM-2162 PR Feedback Move source mocking, fix use of .init over constructors. --- Sources/Datadog/Datadog.swift | 1 + .../CrashReportingWithRUMIntegration.swift | 6 +- .../RUM/DataModels/RUMDataModelsMapping.swift | 12 ++ .../RUMMonitor/Scopes/RUMResourceScope.swift | 4 +- .../Scopes/RUMScopeDependencies.swift | 2 + .../Scopes/RUMUserActionScope.swift | 2 +- .../RUM/RUMMonitor/Scopes/RUMViewScope.swift | 8 +- Sources/Datadog/RUM/RUMTelemetry.swift | 7 +- ...rashReportingWithRUMIntegrationTests.swift | 8 + .../Datadog/Mocks/RUMDataModelMocks.swift | 6 + .../Datadog/Mocks/RUMFeatureMocks.swift | 6 + .../Scopes/RUMResourceScopeTests.swift | 82 ++++++++++ .../Scopes/RUMUserActionScopeTests.swift | 21 +++ .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 148 +++++++++++++++++- .../Datadog/RUM/RUMTelemetryTests.swift | 8 +- 15 files changed, 302 insertions(+), 19 deletions(-) diff --git a/Sources/Datadog/Datadog.swift b/Sources/Datadog/Datadog.swift index 815f47dc10..96da3b692b 100644 --- a/Sources/Datadog/Datadog.swift +++ b/Sources/Datadog/Datadog.swift @@ -226,6 +226,7 @@ public class Datadog { telemetry = RUMTelemetry( sdkVersion: configuration.common.sdkVersion, applicationID: rumConfiguration.applicationID, + source: rumConfiguration.common.source, dateProvider: dateProvider, dateCorrector: dateCorrector ) diff --git a/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift b/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift index 9c0f21daf6..9b56f14cee 100644 --- a/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift +++ b/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift @@ -253,7 +253,7 @@ internal struct CrashReportingWithRUMIntegration: CrashReportingIntegration { id: lastRUMView.session.id, type: lastRUMView.ciTest != nil ? .ciTest : .user ), - source: .ios, + source: lastRUMView.source?.toErrorEventSource ?? .ios, synthetics: nil, usr: lastRUMView.usr, version: lastRUMView.version, @@ -286,7 +286,7 @@ internal struct CrashReportingWithRUMIntegration: CrashReportingIntegration { date: crashDate.timeIntervalSince1970.toInt64Milliseconds - 1, // -1ms to put the crash after view in RUM session service: original.service, session: original.session, - source: .ios, + source: original.source ?? .ios, synthetics: nil, usr: original.usr, version: original.version, @@ -359,7 +359,7 @@ internal struct CrashReportingWithRUMIntegration: CrashReportingIntegration { id: sessionUUID.toRUMDataFormat, type: CITestIntegration.active != nil ? .ciTest : .user ), - source: .ios, + source: .init(rawValue: rumConfiguration.common.source) ?? .ios, synthetics: nil, usr: crashContext.lastUserInfo.flatMap { RUMUser(userInfo: $0) }, version: rumConfiguration.common.applicationVersion, diff --git a/Sources/Datadog/RUM/DataModels/RUMDataModelsMapping.swift b/Sources/Datadog/RUM/DataModels/RUMDataModelsMapping.swift index a9dd68c7ad..672d1a7f88 100644 --- a/Sources/Datadog/RUM/DataModels/RUMDataModelsMapping.swift +++ b/Sources/Datadog/RUM/DataModels/RUMDataModelsMapping.swift @@ -44,3 +44,15 @@ internal extension RUMUserActionType { } } } + +internal extension RUMViewEvent.Source { + var toErrorEventSource: RUMErrorEvent.Source { + switch self { + case .ios: return .ios + case .android: return .android + case .browser: return .browser + case .reactNative: return .reactNative + case .flutter: return .flutter + } + } +} diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift index 9ffbdc5dcc..5fa5113c4c 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift @@ -195,7 +195,7 @@ internal class RUMResourceScope: RUMScope { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .ios, + source: .init(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, @@ -253,7 +253,7 @@ internal class RUMResourceScope: RUMScope { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .ios, + source: .init(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift index 9ababa5e01..6563512a2f 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift @@ -22,6 +22,7 @@ internal struct RUMScopeDependencies { let serviceName: String let applicationVersion: String let sdkVersion: String + let source: String let eventBuilder: RUMEventBuilder let eventOutput: RUMEventOutput let rumUUIDGenerator: RUMUUIDGenerator @@ -59,6 +60,7 @@ internal extension RUMScopeDependencies { serviceName: rumFeature.configuration.common.serviceName, applicationVersion: rumFeature.configuration.common.applicationVersion, sdkVersion: rumFeature.configuration.common.sdkVersion, + source: rumFeature.configuration.common.source, eventBuilder: RUMEventBuilder( eventsMapper: rumFeature.eventsMapper ), diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift index d02e85d5b9..c13a6c77f9 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift @@ -159,7 +159,7 @@ internal class RUMUserActionScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .ios, + source: .init(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index 8addcee532..21e57cb9a0 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -348,7 +348,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .ios, + source: .init(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, @@ -398,7 +398,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .ios, + source: .init(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, @@ -489,7 +489,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .ios, + source: .init(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, @@ -532,7 +532,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .ios, + source: .init(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, diff --git a/Sources/Datadog/RUM/RUMTelemetry.swift b/Sources/Datadog/RUM/RUMTelemetry.swift index 77e625ad77..5694168b05 100644 --- a/Sources/Datadog/RUM/RUMTelemetry.swift +++ b/Sources/Datadog/RUM/RUMTelemetry.swift @@ -13,6 +13,7 @@ import Foundation internal final class RUMTelemetry: Telemetry { let sdkVersion: String let applicationID: String + let source: String let dateProvider: DateProvider let dateCorrector: DateCorrectorType @@ -26,11 +27,13 @@ internal final class RUMTelemetry: Telemetry { init( sdkVersion: String, applicationID: String, + source: String, dateProvider: DateProvider, dateCorrector: DateCorrectorType ) { self.sdkVersion = sdkVersion self.applicationID = applicationID + self.source = source self.dateProvider = dateProvider self.dateCorrector = dateCorrector } @@ -64,7 +67,7 @@ internal final class RUMTelemetry: Telemetry { date: date.timeIntervalSince1970.toInt64Milliseconds, service: "dd-sdk-ios", session: sessionId.map { .init(id: $0) }, - source: .ios, + source: .init(rawValue: self.source) ?? .ios, telemetry: .init(message: message), version: self.sdkVersion, view: viewId.map { .init(id: $0) } @@ -106,7 +109,7 @@ internal final class RUMTelemetry: Telemetry { date: date.timeIntervalSince1970.toInt64Milliseconds, service: "dd-sdk-ios", session: sessionId.map { .init(id: $0) }, - source: .ios, + source: .init(rawValue: self.source) ?? .ios, telemetry: .init(error: .init(kind: kind, stack: stack), message: message), version: self.sdkVersion, view: viewId.map { .init(id: $0) } diff --git a/Tests/DatadogTests/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegrationTests.swift b/Tests/DatadogTests/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegrationTests.swift index 38cb456027..a6d67978da 100644 --- a/Tests/DatadogTests/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegrationTests.swift +++ b/Tests/DatadogTests/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegrationTests.swift @@ -425,6 +425,7 @@ class CrashReportingWithRUMIntegrationTests: XCTestCase { let randomCarrierInfo: CarrierInfo = .mockRandom() let randomUserInfo: UserInfo = .mockRandom() let randomCrashType: String = .mockRandom() + let randomSource: String = .mockAnySource() func test( lastRUMSessionState: RUMSessionState, @@ -457,6 +458,9 @@ class CrashReportingWithRUMIntegrationTests: XCTestCase { dateProvider: RelativeDateProvider(using: crashDate), dateCorrector: DateCorrectorMock(correctionOffset: dateCorrectionOffset), rumConfiguration: .mockWith( + common: .mockWith( + source: randomSource + ), applicationID: randomRUMAppID, sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled), backgroundEventTrackingEnabled: backgroundEventsTrackingEnabled @@ -494,6 +498,7 @@ class CrashReportingWithRUMIntegrationTests: XCTestCase { XCTAssertEqual(sentRUMView.view.error.count, 0) XCTAssertEqual(sentRUMView.view.resource.count, 0) XCTAssertEqual(sentRUMView.view.action.count, 0) + XCTAssertEqual(sentRUMView.source, RUMViewEvent.Source(rawValue: randomSource)) XCTAssertEqual( sentRUMView.date, crashDate.addingTimeInterval(dateCorrectionOffset).timeIntervalSince1970.toInt64Milliseconds - 1, @@ -505,6 +510,8 @@ class CrashReportingWithRUMIntegrationTests: XCTestCase { XCTAssertEqual(sentRUMError.model.application.id, sentRUMView.application.id, "It must be linked to the same application as RUM view") XCTAssertEqual(sentRUMError.model.session.id, sentRUMView.session.id, "It must be linked to the same session as RUM view") XCTAssertEqual(sentRUMError.model.view.id, sentRUMView.view.id, "It must be linked to the RUM view") + XCTAssertEqual(sentRUMError.model.source, RUMErrorEvent.Source(rawValue: randomSource), "Must support configured sources") + XCTAssertEqual(sentRUMError.model.error.sourceType, .ios, "Must send .ios as the sourceType") XCTAssertEqual( sentRUMError.model.connectivity, RUMConnectivity(networkInfo: randomNetworkConnectionInfo, carrierInfo: randomCarrierInfo), @@ -655,6 +662,7 @@ class CrashReportingWithRUMIntegrationTests: XCTestCase { XCTAssertNotNil(sentRUMError.additionalAttributes?[DDError.binaryImages], "It must contain crash details") XCTAssertNotNil(sentRUMError.additionalAttributes?[DDError.meta], "It must contain crash details") XCTAssertNotNil(sentRUMError.additionalAttributes?[DDError.wasTruncated], "It must contain crash details") + XCTAssertEqual(sentRUMError.model.error.sourceType, .ios, "Must send .ios as the sourceType") } try test( diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMDataModelMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMDataModelMocks.swift index a7aec98a95..e100d2483e 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMDataModelMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMDataModelMocks.swift @@ -319,3 +319,9 @@ extension RUMLongTaskEvent: RandomMockable { ) } } + +extension String { + static func mockAnySource() -> String { + return ["ios", "android", "browser", "ios", "react-native", "flutter"].randomElement()! + } +} diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index 4b6458bdee..cfa848c482 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -102,12 +102,14 @@ extension RUMTelemetry: AnyMockable { static func mockWith( sdkVersion: String = .mockAny(), applicationID: String = .mockAny(), + source: String = .mockAnySource(), dateProvider: DateProvider = SystemDateProvider(), dateCorrector: DateCorrectorType = DateCorrectorMock() ) -> Self { .init( sdkVersion: sdkVersion, applicationID: applicationID, + source: source, dateProvider: dateProvider, dateCorrector: dateCorrector ) @@ -659,6 +661,7 @@ extension RUMScopeDependencies { serviceName: String = .mockAny(), applicationVersion: String = .mockAny(), sdkVersion: String = .mockAny(), + source: String = "ios", eventBuilder: RUMEventBuilder = RUMEventBuilder(eventsMapper: .mockNoOp()), eventOutput: RUMEventOutput = RUMEventOutputMock(), rumUUIDGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), @@ -680,6 +683,7 @@ extension RUMScopeDependencies { serviceName: serviceName, applicationVersion: applicationVersion, sdkVersion: sdkVersion, + source: source, eventBuilder: eventBuilder, eventOutput: eventOutput, rumUUIDGenerator: rumUUIDGenerator, @@ -707,6 +711,7 @@ extension RUMScopeDependencies { serviceName: String? = nil, applicationVersion: String? = nil, sdkVersion: String? = nil, + source: String? = nil, eventBuilder: RUMEventBuilder? = nil, eventOutput: RUMEventOutput? = nil, rumUUIDGenerator: RUMUUIDGenerator? = nil, @@ -728,6 +733,7 @@ extension RUMScopeDependencies { serviceName: serviceName ?? self.serviceName, applicationVersion: applicationVersion ?? self.applicationVersion, sdkVersion: sdkVersion ?? self.sdkVersion, + source: source ?? self.source, eventBuilder: eventBuilder ?? self.eventBuilder, eventOutput: eventOutput ?? self.eventOutput, rumUUIDGenerator: rumUUIDGenerator ?? self.rumUUIDGenerator, diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift index 1486f51d3d..4a10d0d41e 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift @@ -106,6 +106,48 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.service, randomServiceName) } + func testGivenConfiguredSoruce_whenResourceLoadingEnds_itSendsResourceEventWithCorrecSource() throws { + var currentTime: Date = .mockDecember15th2019At10AMUTC() + + // Given + let customSource = String.mockAnySource() + let scope = RUMResourceScope.mockWith( + context: context, + dependencies: dependencies.replacing( + source: customSource + ), + resourceKey: "/resource/1", + attributes: [:], + startTime: currentTime, + dateCorrection: .zero, + url: "https://foo.com/resource/1", + httpMethod: .post, + isFirstPartyResource: nil, + resourceKindBasedOnRequest: nil, + spanContext: .init(traceID: "100", spanID: "200") + ) + + currentTime.addTimeInterval(2) + + // When + XCTAssertFalse( + scope.process( + command: RUMStopResourceCommand( + resourceKey: "/resource/1", + time: currentTime, + attributes: ["foo": "bar"], + kind: .image, + httpStatusCode: 200, + size: 1_024 + ) + ) + ) + + // Then + let event = try XCTUnwrap(output.recordedEvents(ofType: RUMResourceEvent.self).first) + XCTAssertEqual(event.source, RUMResourceEvent.Source(rawValue: customSource)) + } + func testGivenStartedResource_whenResourceLoadingEndsWithNegativeDuration_itSendsResourceEventWithPositiveDuration() throws { var currentTime: Date = .mockDecember15th2019At10AMUTC() @@ -293,6 +335,46 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.service, randomServiceName) } + func testGivenConfiguredSource_whenResourceLoadingEndsWithError_itSendsErrorEventWithConfiguredSource() throws { + var currentTime: Date = .mockDecember15th2019At10AMUTC() + + // Given + let customSource = String.mockAnySource() + let scope = RUMResourceScope.mockWith( + context: context, + dependencies: dependencies.replacing( + source: customSource + ), + resourceKey: "/resource/1", + attributes: [:], + startTime: currentTime, + dateCorrection: .zero, + url: "https://foo.com/resource/1", + httpMethod: .post + ) + + currentTime.addTimeInterval(2) + + // When + XCTAssertFalse( + scope.process( + command: RUMStopResourceWithErrorCommand( + resourceKey: "/resource/1", + time: currentTime, + error: ErrorMock("network issue explanation"), + source: .network, + httpStatusCode: 500, + attributes: ["foo": "bar"] + ) + ) + ) + + // Then + let event = try XCTUnwrap(output.recordedEvents(ofType: RUMErrorEvent.self).first) + XCTAssertEqual(event.source, RUMErrorEvent.Source(rawValue: customSource)) + XCTAssertEqual(event.service, randomServiceName) + } + func testGivenStartedResource_whenResourceReceivesMetricsBeforeItEnds_itUsesMetricValuesInSentResourceEvent() throws { var currentTime: Date = .mockDecember15th2019At10AMUTC() diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScopeTests.swift index 40da80a1ef..57f9d807e9 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScopeTests.swift @@ -66,6 +66,27 @@ class RUMUserActionScopeTests: XCTestCase { XCTAssertEqual(recordedAction.service, randomServiceName) } + func testGivenCustomSource_whenActionIsSent_itSendsCustomSource() throws { + let customSource = String.mockAnySource() + let scope = RUMViewScope.mockWith( + parent: parent, + dependencies: dependencies.replacing( + source: customSource + ), + identity: mockView, + attributes: [:], + startTime: Date() + ) + XCTAssertTrue(scope.process(command: RUMStartViewCommand.mockWith(identity: mockView))) + let mockUserActionCmd = RUMAddUserActionCommand.mockAny() + XCTAssertTrue(scope.process(command: mockUserActionCmd)) + XCTAssertFalse(scope.process(command: RUMStopViewCommand.mockWith(identity: mockView))) + + let recordedActionEvents = try output.recordedEvents(ofType: RUMActionEvent.self) + let recordedAction = try XCTUnwrap(recordedActionEvents.last) + XCTAssertEqual(recordedAction.source, RUMActionEvent.Source(rawValue: customSource)) + } + // MARK: - Continuous User Action func testWhenContinuousUserActionEnds_itSendsActionEvent() throws { diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift index ba3d829abd..2f63207270 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -103,6 +103,34 @@ class RUMViewScopeTests: XCTestCase { XCTAssertNil(event.context?.contextInfo[RUMViewScope.Constants.activePrewarm]) } + func testWhenConfigurationSourceIsSet_applicationStartUsesTheConfigurationSource() throws { + // Given + let currentTime: Date = .mockDecember15th2019At10AMUTC() + let randomSource = String.mockAnySource() + let expectedSource = RUMActionEvent.Source(rawValue: randomSource) + let scope = RUMViewScope( + isInitialView: true, + parent: parent, + dependencies: dependencies.replacing( + launchTimeProvider: LaunchTimeProviderMock.mockWith(launchTime: 2),// 2 seconds + source: randomSource + ), + identity: mockView, + path: "UIViewController", + name: "ViewName", + attributes: [:], + customTimings: [:], + startTime: currentTime + ) + + // When + _ = scope.process(command: RUMCommandMock(time: currentTime)) + + // Then + let event = try XCTUnwrap(output.recordedEvents(ofType: RUMActionEvent.self).first) + XCTAssertEqual(event.source, expectedSource) + } + func testWhenActivePrewarm_itSendsApplicationStartAction_withoutLoadingTime() throws { // Given let scope: RUMViewScope = .mockWith( @@ -164,6 +192,31 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(event.service, randomServiceName) } + func testWhenInitialViewHasCconfiguredSource_itSendsViewUpdateEventWithConfiguredSource() throws { + // GIVEN + let currentTime: Date = .mockDecember15th2019At10AMUTC() + let randomSource = String.mockAnySource() + let expectedSource = RUMViewEvent.Source(rawValue: randomSource) + let scope = RUMViewScope( + isInitialView: true, + parent: parent, + dependencies: dependencies.replacing( + source: randomSource + ), + identity: mockView, + path: "UIViewController", + name: "ViewName", + attributes: [:], + customTimings: [:], + startTime: currentTime + ) + + _ = scope.process(command: RUMCommandMock(time: currentTime)) + + let event = try XCTUnwrap(output.recordedEvents(ofType: RUMViewEvent.self).first) + XCTAssertEqual(event.source, expectedSource) + } + func testWhenViewIsStarted_itSendsViewUpdateEvent() throws { let currentTime: Date = .mockDecember15th2019At10AMUTC() let isInitialView: Bool = .mockRandom() @@ -722,10 +775,55 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(viewUpdate.view.error.count, 1) } + func testWhenViewErrorIsAddedWithConfiguredSource_itSendsErrorEventWithCorrectSource() throws { + var currentTime: Date = .mockDecember15th2019At10AMUTC() + let configuredSource = String.mockAnySource() + let expectedSource = RUMErrorEvent.Source(rawValue: configuredSource) + let scope = RUMViewScope( + isInitialView: .mockRandom(), + parent: parent, + dependencies: dependencies.replacing( + source: configuredSource + ), + identity: mockView, + path: "UIViewController", + name: "ViewName", + attributes: [:], + customTimings: [:], + startTime: currentTime + ) + + XCTAssertTrue( + scope.process( + command: RUMStartViewCommand.mockWith(time: currentTime, attributes: ["foo": "bar"], identity: mockView) + ) + ) + + currentTime.addTimeInterval(1) + + XCTAssertTrue( + scope.process( + command: RUMAddCurrentViewErrorCommand.mockWithErrorMessage(time: currentTime, message: "view error", source: .source, stack: nil) + ) + ) + + let error = try XCTUnwrap(output.recordedEvents(ofType: RUMErrorEvent.self).last) + XCTAssertEqual(error.source, expectedSource) + // Configured source should not muck with sourceType, which is set seperately. + XCTAssertEqual(error.error.sourceType, .ios) + } + func testGivenStartedView_whenCrossPlatformErrorIsAdded_itSendsCorrectErrorEvent() throws { var currentTime: Date = .mockDecember15th2019At10AMUTC() - let scope: RUMViewScope = .mockWith(parent: parent, dependencies: dependencies) + let customSource = String.mockAnySource() + let expectedSource = RUMErrorEvent.Source(rawValue: customSource) + let scope: RUMViewScope = .mockWith( + parent: parent, + dependencies: dependencies.replacing( + source: customSource + ) + ) XCTAssertTrue( scope.process(command: RUMStartViewCommand.mockAny()) @@ -733,11 +831,13 @@ class RUMViewScopeTests: XCTestCase { currentTime.addTimeInterval(1) + let customSourceType = String.mockAnySource() + let expectedSourceType = RUMErrorSourceType.init(rawValue: customSourceType) XCTAssertTrue( scope.process( command: RUMAddCurrentViewErrorCommand.mockWithErrorMessage( attributes: [ - CrossPlatformAttributes.errorSourceType: "react-native", + CrossPlatformAttributes.errorSourceType: customSourceType, CrossPlatformAttributes.errorIsCrash: true ] ) @@ -745,14 +845,14 @@ class RUMViewScopeTests: XCTestCase { ) let error = try XCTUnwrap(output.recordedEvents(ofType: RUMErrorEvent.self).last) - XCTAssertEqual(error.error.sourceType, .reactNative) + XCTAssertEqual(error.error.sourceType, expectedSourceType) XCTAssertTrue(error.error.isCrash ?? false) - XCTAssertEqual(error.source, .ios) + XCTAssertEqual(error.source, expectedSource) XCTAssertEqual(error.service, randomServiceName) let viewUpdate = try XCTUnwrap(output.recordedEvents(ofType: RUMViewEvent.self).last) XCTAssertEqual(viewUpdate.view.error.count, 1) - XCTAssertEqual(viewUpdate.source, .ios) + XCTAssertEqual(viewUpdate.source, RUMViewEvent.Source(rawValue: customSource)) XCTAssertEqual(viewUpdate.service, randomServiceName) } @@ -845,6 +945,44 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(viewUpdate.view.longTask?.count, 1) } + func testWhenLongTaskIsAddedWithConfiguredSource_itSendsLongTaskEventWithConfiguredSource() throws { + let startViewDate: Date = .mockDecember15th2019At10AMUTC() + + let customSource = String.mockAnySource() + let expectedSource = RUMLongTaskEvent.Source(rawValue: customSource) + let scope = RUMViewScope( + isInitialView: .mockRandom(), + parent: parent, + dependencies: dependencies.replacing( + source: customSource + ), + identity: mockView, + path: "UIViewController", + name: "ViewName", + attributes: [:], + customTimings: [:], + startTime: startViewDate + ) + + XCTAssertTrue( + scope.process( + command: RUMStartViewCommand.mockWith(time: startViewDate, attributes: ["foo": "bar"], identity: mockView) + ) + ) + + let addLongTaskDate = startViewDate + 1.0 + let duration: TimeInterval = 1.0 + + XCTAssertTrue( + scope.process( + command: RUMAddLongTaskCommand(time: addLongTaskDate, attributes: ["foo": "bar"], duration: duration) + ) + ) + + let event = try XCTUnwrap(output.recordedEvents(ofType: RUMLongTaskEvent.self).last) + XCTAssertEqual(event.source, expectedSource) + } + // MARK: - Custom Timings Tracking func testGivenActiveView_whenCustomTimingIsRegistered_itSendsViewUpdateEvent() throws { diff --git a/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift index 39abd8d320..3a76d19ac4 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift @@ -27,7 +27,9 @@ class RUMTelemetryTests: XCTestCase { // MARK: - Sending Telemetry events func testSendTelemetryDebug() throws { + let configuredSource = String.mockAnySource() let telemetry: RUMTelemetry = .mockWith( + source: configuredSource, dateProvider: RelativeDateProvider( using: .init(timeIntervalSince1970: 0) ) @@ -41,13 +43,15 @@ class RUMTelemetryTests: XCTestCase { XCTAssertEqual(event.application?.id, telemetry.applicationID) XCTAssertEqual(event.version, telemetry.sdkVersion) XCTAssertEqual(event.service, "dd-sdk-ios") - XCTAssertEqual(event.source, .ios) + XCTAssertEqual(event.source, TelemetryDebugEvent.Source(rawValue: configuredSource)) XCTAssertEqual(event.telemetry.message, "Hello world!") } } func testSendTelemetryError() throws { + let configuredSource = String.mockAnySource() let telemetry: RUMTelemetry = .mockWith( + source: configuredSource, dateProvider: RelativeDateProvider( using: .init(timeIntervalSince1970: 0) ) @@ -60,7 +64,7 @@ class RUMTelemetryTests: XCTestCase { XCTAssertEqual(event.application?.id, telemetry.applicationID) XCTAssertEqual(event.version, telemetry.sdkVersion) XCTAssertEqual(event.service, "dd-sdk-ios") - XCTAssertEqual(event.source, .ios) + XCTAssertEqual(event.source, TelemetryErrorEvent.Source(rawValue: configuredSource)) XCTAssertEqual(event.telemetry.message, "Oops") XCTAssertEqual(event.telemetry.error?.kind, "OutOfMemory") XCTAssertEqual(event.telemetry.error?.stack, "a\nhay\nneedle\nstack") From 9f14e5046c39bfc115412bd7703769190d54c755 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 29 Apr 2022 09:50:19 +0200 Subject: [PATCH 19/55] RUMM-2162 Fix compilation issue for Xcode 12.x --- .../CrashReporting/CrashReportingWithRUMIntegration.swift | 2 +- .../Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift | 4 ++-- .../RUM/RUMMonitor/Scopes/RUMUserActionScope.swift | 2 +- Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift | 8 ++++---- Sources/Datadog/RUM/RUMTelemetry.swift | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift b/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift index 9b56f14cee..859328df64 100644 --- a/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift +++ b/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift @@ -359,7 +359,7 @@ internal struct CrashReportingWithRUMIntegration: CrashReportingIntegration { id: sessionUUID.toRUMDataFormat, type: CITestIntegration.active != nil ? .ciTest : .user ), - source: .init(rawValue: rumConfiguration.common.source) ?? .ios, + source: RUMViewEvent.Source(rawValue: rumConfiguration.common.source) ?? .ios, synthetics: nil, usr: crashContext.lastUserInfo.flatMap { RUMUser(userInfo: $0) }, version: rumConfiguration.common.applicationVersion, diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift index 5fa5113c4c..b68e0b022b 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift @@ -195,7 +195,7 @@ internal class RUMResourceScope: RUMScope { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .init(rawValue: dependencies.source) ?? .ios, + source: RUMResourceEvent.Source(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, @@ -253,7 +253,7 @@ internal class RUMResourceScope: RUMScope { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .init(rawValue: dependencies.source) ?? .ios, + source: RUMErrorEvent.Source(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift index c13a6c77f9..270d5e4320 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift @@ -159,7 +159,7 @@ internal class RUMUserActionScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .init(rawValue: dependencies.source) ?? .ios, + source: RUMActionEvent.Source(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index 21e57cb9a0..bc12080c7a 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -348,7 +348,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .init(rawValue: dependencies.source) ?? .ios, + source: RUMActionEvent.Source(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, @@ -398,7 +398,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .init(rawValue: dependencies.source) ?? .ios, + source: RUMViewEvent.Source(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, @@ -489,7 +489,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .init(rawValue: dependencies.source) ?? .ios, + source: RUMErrorEvent.Source(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, @@ -532,7 +532,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { id: context.sessionID.toRUMDataFormat, type: dependencies.ciTest != nil ? .ciTest : .user ), - source: .init(rawValue: dependencies.source) ?? .ios, + source: RUMLongTaskEvent.Source(rawValue: dependencies.source) ?? .ios, synthetics: nil, usr: dependencies.userInfoProvider.current, version: dependencies.applicationVersion, diff --git a/Sources/Datadog/RUM/RUMTelemetry.swift b/Sources/Datadog/RUM/RUMTelemetry.swift index 5694168b05..e041ebba61 100644 --- a/Sources/Datadog/RUM/RUMTelemetry.swift +++ b/Sources/Datadog/RUM/RUMTelemetry.swift @@ -67,7 +67,7 @@ internal final class RUMTelemetry: Telemetry { date: date.timeIntervalSince1970.toInt64Milliseconds, service: "dd-sdk-ios", session: sessionId.map { .init(id: $0) }, - source: .init(rawValue: self.source) ?? .ios, + source: TelemetryDebugEvent.Source(rawValue: self.source) ?? .ios, telemetry: .init(message: message), version: self.sdkVersion, view: viewId.map { .init(id: $0) } @@ -109,7 +109,7 @@ internal final class RUMTelemetry: Telemetry { date: date.timeIntervalSince1970.toInt64Milliseconds, service: "dd-sdk-ios", session: sessionId.map { .init(id: $0) }, - source: .init(rawValue: self.source) ?? .ios, + source: TelemetryErrorEvent.Source(rawValue: self.source) ?? .ios, telemetry: .init(error: .init(kind: kind, stack: stack), message: message), version: self.sdkVersion, view: viewId.map { .init(id: $0) } From ac1e03bd1413baedc14c2508122f921cd8ebcc6b Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 25 Apr 2022 15:15:21 +0200 Subject: [PATCH 20/55] RUMM-2027 Add RUM Telemetry sampling --- .../E2ETests/Helpers/DatadogE2EHelpers.swift | 2 +- Datadog/Example/AppConfiguration.swift | 1 + .../Datadog/Core/FeaturesConfiguration.swift | 2 + Sources/Datadog/Core/Telemetry.swift | 26 ++-- Sources/Datadog/Datadog.swift | 3 +- Sources/Datadog/DatadogConfiguration.swift | 11 ++ Sources/Datadog/RUM/RUMTelemetry.swift | 77 ++++++---- .../DatadogConfigurationBuilderTests.swift | 1 + .../Datadog/Mocks/CoreMocks.swift | 6 +- .../Datadog/Mocks/LoggingFeatureMocks.swift | 4 +- .../Datadog/Mocks/RUMFeatureMocks.swift | 10 +- .../Datadog/RUM/RUMTelemetryTests.swift | 131 +++++++++++++++++- .../Matchers/RUMEventMatcher.swift | 4 + 13 files changed, 233 insertions(+), 45 deletions(-) diff --git a/Datadog/E2ETests/Helpers/DatadogE2EHelpers.swift b/Datadog/E2ETests/Helpers/DatadogE2EHelpers.swift index 44e78f0e43..c8ebc3ba03 100644 --- a/Datadog/E2ETests/Helpers/DatadogE2EHelpers.swift +++ b/Datadog/E2ETests/Helpers/DatadogE2EHelpers.swift @@ -12,6 +12,6 @@ extension Datadog.Configuration { rumApplicationID: E2EConfig.readRUMApplicationID(), clientToken: E2EConfig.readClientToken(), environment: E2EConfig.readEnv() - ) + ).set(sampleTelemetry: 100) } } diff --git a/Datadog/Example/AppConfiguration.swift b/Datadog/Example/AppConfiguration.swift index 274d513f4f..8e289cdf20 100644 --- a/Datadog/Example/AppConfiguration.swift +++ b/Datadog/Example/AppConfiguration.swift @@ -40,6 +40,7 @@ struct ExampleAppConfiguration: AppConfiguration { .set(serviceName: serviceName) .set(batchSize: .small) .set(uploadFrequency: .frequent) + .set(sampleTelemetry: 100) if let customLogsURL = Environment.readCustomLogsURL() { _ = configuration.set(customLogsEndpoint: customLogsURL) diff --git a/Sources/Datadog/Core/FeaturesConfiguration.swift b/Sources/Datadog/Core/FeaturesConfiguration.swift index 8d2fa66175..079b2179af 100644 --- a/Sources/Datadog/Core/FeaturesConfiguration.swift +++ b/Sources/Datadog/Core/FeaturesConfiguration.swift @@ -51,6 +51,7 @@ internal struct FeaturesConfiguration { let clientToken: String let applicationID: String let sessionSampler: Sampler + let telemetrySampler: Sampler let uuidGenerator: RUMUUIDGenerator let viewEventMapper: RUMViewEventMapper? let resourceEventMapper: RUMResourceEventMapper? @@ -197,6 +198,7 @@ extension FeaturesConfiguration { clientToken: try ifValid(clientToken: configuration.clientToken), applicationID: rumApplicationID, sessionSampler: Sampler(samplingRate: debugOverride ? 100.0 : configuration.rumSessionsSamplingRate), + telemetrySampler: Sampler(samplingRate: configuration.rumTelemetrySamplingRate), uuidGenerator: DefaultRUMUUIDGenerator(), viewEventMapper: configuration.rumViewEventMapper, resourceEventMapper: configuration.rumResourceEventMapper, diff --git a/Sources/Datadog/Core/Telemetry.swift b/Sources/Datadog/Core/Telemetry.swift index 5490c0c5b7..776d593fd4 100644 --- a/Sources/Datadog/Core/Telemetry.swift +++ b/Sources/Datadog/Core/Telemetry.swift @@ -11,26 +11,30 @@ import Foundation internal protocol Telemetry { /// Collects debug information. /// - /// - Parameter message: The debug message. - func debug(_ message: String) + /// - Parameters: + /// - id: Identity of the debug log, this can be used to prevent duplicates. + /// - message: The debug message. + func debug(id: String, message: String) /// Collect execution error. /// /// - Parameters: + /// - id: Identity of the debug log, this can be used to prevent duplicates. /// - message: The error message. /// - kind: The kind of error. /// - stack: The stack trace. - func error(_ message: String, kind: String?, stack: String?) + func error(id: String, message: String, kind: String?, stack: String?) } extension Telemetry { - /// Collect execution error. + /// Collects debug information. /// /// - Parameters: - /// - message: The error message. - /// - kind: The kind of error. - func error(_ message: String, kind: String) { - error(message, kind: kind, stack: nil) + /// - message: The debug message. + /// - file: The current file name. + /// - line: The line number in file. + func debug(_ message: String, file: String = #fileID, line: Int = #line) { + debug(id: "\(file):\(line):\(message)", message: message) } /// Collect execution error. @@ -38,8 +42,10 @@ extension Telemetry { /// - Parameters: /// - message: The error message. /// - stack: The stack trace. - func error(_ message: String, stack: String? = nil) { - error(message, kind: nil, stack: stack) + /// - file: The current file name. + /// - line: The line number in file. + func error(_ message: String, kind: String? = nil, stack: String? = nil, file: String = #fileID, line: Int = #line) { + error(id: "\(file):\(line):\(message)", message: message, kind: kind, stack: stack) } /// Collect execution error. diff --git a/Sources/Datadog/Datadog.swift b/Sources/Datadog/Datadog.swift index 96da3b692b..1667781114 100644 --- a/Sources/Datadog/Datadog.swift +++ b/Sources/Datadog/Datadog.swift @@ -228,7 +228,8 @@ public class Datadog { applicationID: rumConfiguration.applicationID, source: rumConfiguration.common.source, dateProvider: dateProvider, - dateCorrector: dateCorrector + dateCorrector: dateCorrector, + sampler: rumConfiguration.telemetrySampler ) rum = RUMFeature( diff --git a/Sources/Datadog/DatadogConfiguration.swift b/Sources/Datadog/DatadogConfiguration.swift index 53c1eb3c1d..8fd7a93b1b 100644 --- a/Sources/Datadog/DatadogConfiguration.swift +++ b/Sources/Datadog/DatadogConfiguration.swift @@ -263,6 +263,7 @@ extension Datadog { private(set) var rumLongTaskEventMapper: RUMLongTaskEventMapper? private(set) var rumResourceAttributesProvider: URLSessionRUMAttributesProvider? private(set) var rumBackgroundEventTrackingEnabled: Bool + private(set) var rumTelemetrySamplingRate: Float private(set) var batchSize: BatchSize private(set) var uploadFrequency: UploadFrequency private(set) var additionalConfiguration: [String: Any] @@ -335,6 +336,7 @@ extension Datadog { rumErrorEventMapper: nil, rumResourceAttributesProvider: nil, rumBackgroundEventTrackingEnabled: false, + rumTelemetrySamplingRate: 20, batchSize: .medium, uploadFrequency: .average, additionalConfiguration: [:], @@ -686,6 +688,15 @@ extension Datadog { return self } + /// Sets the sampling rate for Internal Telemetry (info related to the work of the SDK internals). Default value is 20. + /// + /// - Parameter rate: the sampling rate must be a value between 0 and 100. A value of 0 + /// means no telemetry will be sent, 100 means all telemetry will be kept. + public func set(sampleTelemetry rate: Float) -> Builder { + configuration.rumTelemetrySamplingRate = rate + return self + } + // MARK: - Crash Reporting Configuration /// Enables the crash reporting feature. diff --git a/Sources/Datadog/RUM/RUMTelemetry.swift b/Sources/Datadog/RUM/RUMTelemetry.swift index 5694168b05..63306e674f 100644 --- a/Sources/Datadog/RUM/RUMTelemetry.swift +++ b/Sources/Datadog/RUM/RUMTelemetry.swift @@ -8,14 +8,27 @@ import Foundation /// Sends Telemetry events to RUM. /// -/// `RUMTelemetry` complies to `Telemetry` protocol allowing -/// sending telemetry events accross features. +/// `RUMTelemetry` complies to `Telemetry` protocol allowing sending telemetry +/// events accross features. +/// +/// Events are reported up to 100 per sessions with a sampling mechanism that is +/// configured at initialisation. Duplicates are discared. internal final class RUMTelemetry: Telemetry { + /// Maximium number of telemetry events allowed per user sessions. + static let MaxEventsPerSessions: Int = 100 + let sdkVersion: String let applicationID: String let source: String let dateProvider: DateProvider let dateCorrector: DateCorrectorType + let sampler: Sampler + + /// Keeps track of current session + private var currentSessionID: RUMUUID = .nullUUID + + /// Keeps track of event's ids recorded during a user session. + private var eventIDs: [String] = [] /// Creates a RUM Telemetry instance. /// @@ -24,18 +37,21 @@ internal final class RUMTelemetry: Telemetry { /// - applicationID: The application ID. /// - dateProvider: Current device time provider. /// - dateCorrector: Date correction for adjusting device time to server time. + /// - sampler: Telemetry events sampler. init( sdkVersion: String, applicationID: String, source: String, dateProvider: DateProvider, - dateCorrector: DateCorrectorType + dateCorrector: DateCorrectorType, + sampler: Sampler ) { self.sdkVersion = sdkVersion self.applicationID = applicationID self.source = source self.dateProvider = dateProvider self.dateCorrector = dateCorrector + self.sampler = sampler } /// Sends a `TelemetryDebugEvent` event. @@ -44,21 +60,16 @@ internal final class RUMTelemetry: Telemetry { /// The current RUM context info is applied if available, including session ID, view ID, /// and action ID. /// - /// - Parameter message: Body of the log - func debug(_ message: String) { - guard - let monitor = Global.rum as? RUMMonitor, - let writer = RUMFeature.instance?.storage.writer - else { - return - } - + /// - Parameters: + /// - id: Identity of the debug log, this can be used to prevent duplicates. + /// - message: The debug message. + func debug(id: String, message: String) { let date = dateCorrector.currentCorrection.applying(to: dateProvider.currentDate()) - monitor.contextProvider.async { context in + record(event: id) { context, writer in let actionId = context.activeUserActionID?.toRUMDataFormat let viewId = context.activeViewID?.toRUMDataFormat - let sessionId = context.sessionID == RUMUUID.nullUUID ? nil :context.sessionID.toRUMDataFormat + let sessionId = context.sessionID == RUMUUID.nullUUID ? nil : context.sessionID.toRUMDataFormat let event = TelemetryDebugEvent( dd: .init(), @@ -84,23 +95,17 @@ internal final class RUMTelemetry: Telemetry { /// and action ID. /// /// - Parameters: + /// - id: Identity of the debug log, this can be used to prevent duplicates. /// - message: Body of the log /// - kind: The error type or kind (or code in some cases). /// - stack: The stack trace or the complementary information about the error. - func error(_ message: String, kind: String?, stack: String?) { - guard - let monitor = Global.rum as? RUMMonitor, - let writer = RUMFeature.instance?.storage.writer - else { - return - } - + func error(id: String, message: String, kind: String?, stack: String?) { let date = dateCorrector.currentCorrection.applying(to: dateProvider.currentDate()) - monitor.contextProvider.async { context in + record(event: id) { context, writer in let actionId = context.activeUserActionID?.toRUMDataFormat let viewId = context.activeViewID?.toRUMDataFormat - let sessionId = context.sessionID == RUMUUID.nullUUID ? nil :context.sessionID.toRUMDataFormat + let sessionId = context.sessionID == RUMUUID.nullUUID ? nil : context.sessionID.toRUMDataFormat let event = TelemetryErrorEvent( dd: .init(), @@ -118,4 +123,28 @@ internal final class RUMTelemetry: Telemetry { writer.write(value: event) } } + + private func record(event id: String, operation: @escaping (RUMContext, Writer) -> Void) { + guard + sampler.sample(), + let monitor = Global.rum as? RUMMonitor, + let writer = RUMFeature.instance?.storage.writer + else { + return + } + + monitor.contextProvider.async { context in + // reset recorded events on session renewal + if context.sessionID != self.currentSessionID { + self.currentSessionID = context.sessionID + self.eventIDs = [] + } + + // record up de `MaxEventsPerSessions`, discard duplicates + if self.eventIDs.count < RUMTelemetry.MaxEventsPerSessions, !self.eventIDs.contains(id) { + self.eventIDs.append(id) + operation(context, writer) + } + } + } } diff --git a/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift b/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift index 2d81173565..7c939b747d 100644 --- a/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift +++ b/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift @@ -146,6 +146,7 @@ class DatadogConfigurationBuilderTests: XCTestCase { XCTAssertEqual(configuration.firstPartyHosts, ["example.com"]) XCTAssertEqual(configuration.rumSessionsSamplingRate, 42.5) XCTAssertNotNil(configuration.rumSessionsListener) + XCTAssertEqual(configuration.rumTelemetrySamplingRate, 20) XCTAssertTrue(configuration.rumUIKitViewsPredicate is UIKitRUMViewsPredicateMock) XCTAssertTrue(configuration.rumUIKitUserActionsPredicate is UIKitRUMUserActionsPredicateMock) XCTAssertEqual(configuration.rumLongTaskDurationThreshold, 100.0) diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 3e3adbeef3..713b11c2ce 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -55,6 +55,7 @@ extension Datadog.Configuration { rumLongTaskDurationThreshold: TimeInterval? = nil, rumResourceAttributesProvider: URLSessionRUMAttributesProvider? = nil, rumBackgroundEventTrackingEnabled: Bool = false, + rumTelemetrySamplingRate: Float = 100.0, batchSize: BatchSize = .medium, uploadFrequency: UploadFrequency = .average, additionalConfiguration: [String: Any] = [:], @@ -84,6 +85,7 @@ extension Datadog.Configuration { rumLongTaskDurationThreshold: rumLongTaskDurationThreshold, rumResourceAttributesProvider: rumResourceAttributesProvider, rumBackgroundEventTrackingEnabled: rumBackgroundEventTrackingEnabled, + rumTelemetrySamplingRate: rumTelemetrySamplingRate, batchSize: batchSize, uploadFrequency: uploadFrequency, additionalConfiguration: additionalConfiguration, @@ -257,6 +259,7 @@ extension FeaturesConfiguration.RUM { clientToken: String = .mockAny(), applicationID: String = .mockAny(), sessionSampler: Sampler = Sampler(samplingRate: 100), + telemetrySampler: Sampler = Sampler(samplingRate: 100), uuidGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), viewEventMapper: RUMViewEventMapper? = nil, resourceEventMapper: RUMResourceEventMapper? = nil, @@ -265,7 +268,7 @@ extension FeaturesConfiguration.RUM { longTaskEventMapper: RUMLongTaskEventMapper? = nil, instrumentation: FeaturesConfiguration.RUM.Instrumentation? = nil, backgroundEventTrackingEnabled: Bool = false, - onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListerner() + onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListener() ) -> Self { return .init( common: common, @@ -273,6 +276,7 @@ extension FeaturesConfiguration.RUM { clientToken: clientToken, applicationID: applicationID, sessionSampler: sessionSampler, + telemetrySampler: telemetrySampler, uuidGenerator: uuidGenerator, viewEventMapper: viewEventMapper, resourceEventMapper: resourceEventMapper, diff --git a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift index b7d46b91d7..c7f4d0b182 100644 --- a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift @@ -298,12 +298,12 @@ class TelemetryMock: Telemetry, CustomStringConvertible { private(set) var errors: [(message: String, kind: String?, stack: String?)] = [] private(set) var description: String = "Telemetry logs:" - func debug(_ message: String) { + func debug(id: String, message: String) { debugs.append(message) description.append("\n- [debug] \(message)") } - func error(_ message: String, kind: String?, stack: String?) { + func error(id: String, message: String, kind: String?, stack: String?) { errors.append((message: message, kind: kind, stack: stack)) description.append("\n - [error] \(message), kind: \(kind ?? "nil"), stack: \(stack ?? "nil")") } diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index cfa848c482..27eb7d51d4 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -104,14 +104,16 @@ extension RUMTelemetry: AnyMockable { applicationID: String = .mockAny(), source: String = .mockAnySource(), dateProvider: DateProvider = SystemDateProvider(), - dateCorrector: DateCorrectorType = DateCorrectorMock() + dateCorrector: DateCorrectorType = DateCorrectorMock(), + sampler: Sampler = .init(samplingRate: 100) ) -> Self { .init( sdkVersion: sdkVersion, applicationID: applicationID, source: source, dateProvider: dateProvider, - dateCorrector: dateCorrector + dateCorrector: dateCorrector, + sampler: sampler ) } } @@ -637,7 +639,7 @@ internal struct NoOpRUMViewUpdatesThrottler: RUMViewUpdatesThrottlerType { } } -func mockNoOpSessionListerner() -> RUMSessionListener { +func mockNoOpSessionListener() -> RUMSessionListener { return { _, _ in } } @@ -669,7 +671,7 @@ extension RUMScopeDependencies { crashContextIntegration: RUMWithCrashContextIntegration? = nil, ciTest: RUMCITest? = nil, viewUpdatesThrottlerFactory: @escaping () -> RUMViewUpdatesThrottlerType = { NoOpRUMViewUpdatesThrottler() }, - onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListerner() + onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListener() ) -> RUMScopeDependencies { return RUMScopeDependencies( rumApplicationID: rumApplicationID, diff --git a/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift index 3a76d19ac4..f262535a6a 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift @@ -27,6 +27,7 @@ class RUMTelemetryTests: XCTestCase { // MARK: - Sending Telemetry events func testSendTelemetryDebug() throws { + // Given let configuredSource = String.mockAnySource() let telemetry: RUMTelemetry = .mockWith( source: configuredSource, @@ -35,8 +36,10 @@ class RUMTelemetryTests: XCTestCase { ) ) + // When telemetry.debug("Hello world!") + // Then let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 1) try rumEventMatchers.lastRUMEvent(ofType: TelemetryDebugEvent.self).model(ofType: TelemetryDebugEvent.self) { event in XCTAssertEqual(event.date, 0) @@ -49,6 +52,7 @@ class RUMTelemetryTests: XCTestCase { } func testSendTelemetryError() throws { + // Given let configuredSource = String.mockAnySource() let telemetry: RUMTelemetry = .mockWith( source: configuredSource, @@ -56,8 +60,11 @@ class RUMTelemetryTests: XCTestCase { using: .init(timeIntervalSince1970: 0) ) ) + + // When telemetry.error("Oops", kind: "OutOfMemory", stack: "a\nhay\nneedle\nstack") + // Then let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 1) try rumEventMatchers.lastRUMEvent(ofType: TelemetryErrorEvent.self).model(ofType: TelemetryErrorEvent.self) { event in XCTAssertEqual(event.date, 0) @@ -72,12 +79,15 @@ class RUMTelemetryTests: XCTestCase { } func testSendTelemetryDebug_withRUMContext() throws { + // Given let telemetry: RUMTelemetry = .mockAny() + // When Global.rum.startView(viewController: mockView) Global.rum.startUserAction(type: .scroll, name: .mockAny()) telemetry.debug("telemetry debug") + // Then let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 3) try rumEventMatchers.lastRUMEvent(ofType: TelemetryDebugEvent.self).model(ofType: TelemetryDebugEvent.self) { event in XCTAssertEqual(event.telemetry.message, "telemetry debug") @@ -88,12 +98,15 @@ class RUMTelemetryTests: XCTestCase { } func testSendTelemetryError_withRUMContext() throws { + // Given let telemetry: RUMTelemetry = .mockAny() + // When Global.rum.startView(viewController: mockView) Global.rum.startUserAction(type: .scroll, name: .mockAny()) telemetry.error("telemetry error") + // Then let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 3) try rumEventMatchers.lastRUMEvent(ofType: TelemetryErrorEvent.self).model(ofType: TelemetryErrorEvent.self) { event in XCTAssertEqual(event.telemetry.message, "telemetry error") @@ -107,9 +120,9 @@ class RUMTelemetryTests: XCTestCase { class TelemetryTest: Telemetry { var record: (message: String, kind: String?, stack: String?)? - func debug(_ message: String) { } + func debug(id: String, message: String) { } - func error(_ message: String, kind: String?, stack: String?) { + func error(id: String, message: String, kind: String?, stack: String?) { record = (message: message, kind: kind, stack: stack) } } @@ -151,4 +164,118 @@ class RUMTelemetryTests: XCTestCase { telemetry.error("ns error", error: nsError) XCTAssertEqual(telemetry.record?.message, "ns error - error description") } + func testSendTelemetry_discardDuplicates() throws { + // Given + let telemetry: RUMTelemetry = .mockAny() + + // When + telemetry.debug(id: "0", message: "telemetry debug 0") + telemetry.error(id: "0", message: "telemetry debug 1", kind: nil, stack: nil) + telemetry.debug(id: "0", message: "telemetry debug 2") + telemetry.debug(id: "1", message: "telemetry debug 3") + + for _ in 0...10 { + // telemetry id is composed of the file, line number, and message + telemetry.debug("telemetry debug 4") + } + + for index in 5...10 { + // telemetry id is composed of the file, line number, and message + telemetry.debug("telemetry debug \(index)") + } + + telemetry.debug("telemetry debug 11") + + // Then + let events = try RUMFeature.waitAndReturnRUMEventMatchers(count: 10).compactMap(TelemetryDebugEvent.self) + XCTAssertEqual(events.count, 10) + XCTAssertEqual(events[0].telemetry.message, "telemetry debug 0") + XCTAssertEqual(events[1].telemetry.message, "telemetry debug 3") + XCTAssertEqual(events[2].telemetry.message, "telemetry debug 4") + XCTAssertEqual(events[3].telemetry.message, "telemetry debug 5") + XCTAssertEqual(events.last?.telemetry.message, "telemetry debug 11") + } + + func testSendTelemetry_toSessionLimit() throws { + // Given + let telemetry: RUMTelemetry = .mockAny() + + // When + // sends 101 telemetry events + for index in 0...RUMTelemetry.MaxEventsPerSessions { + telemetry.debug(id: "\(index)", message: "telemetry debug") + } + + // Then + let eventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 100) + let events = try eventMatchers.compactMap(TelemetryDebugEvent.self) + XCTAssertEqual(events.count, 100) + } + + func testSampledTelemetry_rejectAll() throws { + // Given + let telemetry: RUMTelemetry = .mockWith(sampler: .mockRejectAll()) + + // When + // sends 10 telemetry events + for index in 0..<10 { + telemetry.debug(id: "\(index)", message: "telemetry debug") + } + + // Then + let eventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 0) + let events = try eventMatchers.compactMap(TelemetryDebugEvent.self) + XCTAssertEqual(events.count, 0) + } + + func testSendTelemetry_resetAfterSessionExpire() throws { + // Given + let monitor = try XCTUnwrap(Global.rum as? RUMMonitor, "Global RUM monitor must be of type `RUMMonitor`") + let telemetry: RUMTelemetry = .mockAny() + var currentTime = Date() + + // When + let view = createMockViewInWindow() + telemetry.debug(id: "0", message: "telemetry debug") + monitor.process(command: RUMStartViewCommand.mockWith(time: currentTime, identity: view)) + + // push time forward by the max session duration: + currentTime.addTimeInterval(RUMSessionScope.Constants.sessionMaxDuration) + monitor.process(command: RUMAddUserActionCommand.mockWith(time: currentTime)) + telemetry.debug(id: "0", message: "telemetry debug") + + // Then + let eventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 2) + let events = try eventMatchers.compactMap(TelemetryDebugEvent.self) + XCTAssertEqual(events.count, 2) + } + + // MARK: - Thread safety + + func testSendTelemetryAndReset_onAnyThread() throws { + let monitor = try XCTUnwrap(Global.rum as? RUMMonitor, "Global RUM monitor must be of type `RUMMonitor`") + let telemetry: RUMTelemetry = .mockAny() + + let view = createMockViewInWindow() + monitor.process(command: RUMStartViewCommand.mockWith(time: .init(), identity: view)) + + // timeoffset will be incremented to force session renewal + let timeoffset = ValuePublisher(initialValue: RUMSessionScope.Constants.sessionMaxDuration) + + // swiftlint:disable opening_brace + callConcurrently( + closures: [ + { telemetry.debug(id: .mockRandom(), message: "telemetry debug") }, + { telemetry.error(id: .mockRandom(), message: "telemetry error", kind: nil, stack: nil) }, + { + let offset = timeoffset.currentValue + let time = Date(timeIntervalSinceNow: offset) + monitor.process(command: RUMAddUserActionCommand.mockWith(time: time)) + timeoffset.publishSync(offset + RUMSessionScope.Constants.sessionMaxDuration) + } + ], + iterations: 50 + ) + // swiftlint:enable opening_brace + } } diff --git a/Tests/DatadogTests/Matchers/RUMEventMatcher.swift b/Tests/DatadogTests/Matchers/RUMEventMatcher.swift index 6ef892f545..748dfaa374 100644 --- a/Tests/DatadogTests/Matchers/RUMEventMatcher.swift +++ b/Tests/DatadogTests/Matchers/RUMEventMatcher.swift @@ -138,6 +138,10 @@ extension Array where Element == RUMEventMatcher { return try filter { matcher in matcher.model(isTypeOf: type) } .forEach { matcher in body(try matcher.model()) } } + + func compactMap(_ type: DM.Type) throws -> [DM] { + return try filter { $0.model(isTypeOf: type) }.map { try $0.model() } + } } func XCTAssertValidRumUUID(_ string: String?, file: StaticString = #file, line: UInt = #line) { From fa7bb87f413c2b0d291deec0a138390c01dcee54 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 27 Apr 2022 17:45:46 +0200 Subject: [PATCH 21/55] RUMM-2027 Enable cpu vital telemetry --- Sources/Datadog/RUM/RUMFeature.swift | 2 +- Sources/Datadog/RUM/RUMVitals/VitalCPUReader.swift | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/Datadog/RUM/RUMFeature.swift b/Sources/Datadog/RUM/RUMFeature.swift index 11b0f14974..6c4aae13a8 100644 --- a/Sources/Datadog/RUM/RUMFeature.swift +++ b/Sources/Datadog/RUM/RUMFeature.swift @@ -147,7 +147,7 @@ internal final class RUMFeature { upload: upload, configuration: configuration, commonDependencies: commonDependencies, - vitalCPUReader: VitalCPUReader(), + vitalCPUReader: VitalCPUReader(telemetry: telemetry), vitalMemoryReader: VitalMemoryReader(), vitalRefreshRateReader: VitalRefreshRateReader(), onSessionStart: configuration.onSessionStart diff --git a/Sources/Datadog/RUM/RUMVitals/VitalCPUReader.swift b/Sources/Datadog/RUM/RUMVitals/VitalCPUReader.swift index b1985bd61a..a2ca302070 100644 --- a/Sources/Datadog/RUM/RUMVitals/VitalCPUReader.swift +++ b/Sources/Datadog/RUM/RUMVitals/VitalCPUReader.swift @@ -14,7 +14,13 @@ internal class VitalCPUReader: SamplingBasedVitalReader { private var totalInactiveTicks: natural_t = 0 private var utilizedTicksWhenResigningActive: natural_t? = nil - init(notificationCenter: NotificationCenter = .default) { + let telemetry: Telemetry? + + init( + notificationCenter: NotificationCenter = .default, + telemetry: Telemetry? = nil + ) { + self.telemetry = telemetry notificationCenter.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) notificationCenter.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) } @@ -65,11 +71,7 @@ internal class VitalCPUReader: SamplingBasedVitalReader { if result != KERN_SUCCESS { // in case of error, refer to `kern_return.h` (Objc) // as its Swift interface doesn't have integer values - // - // NOTE: RUMM-1276 consider using sdkLogger.errorOnce(...) to avoid flooding -// InternalMonitoringFeature.instance?.monitor.sdkLogger.error( -// "CPU Vital cannot be read! Error code: \(result)" -// ) + telemetry?.error("CPU Vital cannot be read! Error code: \(result)") return nil } From a2f4c3d56a21b979c09e9bc5a0bd16afb695efb9 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 2 May 2022 09:59:49 +0200 Subject: [PATCH 22/55] RUMM-2027 Replace telemetry events records with Set --- Sources/Datadog/RUM/RUMTelemetry.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Datadog/RUM/RUMTelemetry.swift b/Sources/Datadog/RUM/RUMTelemetry.swift index 63306e674f..70cc47abc9 100644 --- a/Sources/Datadog/RUM/RUMTelemetry.swift +++ b/Sources/Datadog/RUM/RUMTelemetry.swift @@ -28,7 +28,7 @@ internal final class RUMTelemetry: Telemetry { private var currentSessionID: RUMUUID = .nullUUID /// Keeps track of event's ids recorded during a user session. - private var eventIDs: [String] = [] + private var eventIDs: Set = [] /// Creates a RUM Telemetry instance. /// @@ -142,7 +142,7 @@ internal final class RUMTelemetry: Telemetry { // record up de `MaxEventsPerSessions`, discard duplicates if self.eventIDs.count < RUMTelemetry.MaxEventsPerSessions, !self.eventIDs.contains(id) { - self.eventIDs.append(id) + self.eventIDs.insert(id) operation(context, writer) } } From 506e1b4b92de4c13e3c499075af0d41045b6828c Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 2 May 2022 10:36:12 +0200 Subject: [PATCH 23/55] RUMM-2027 Fix default telemetry id --- Sources/Datadog/Core/Telemetry.swift | 30 ++++++++++++------- .../Datadog/RUM/RUMTelemetryTests.swift | 12 ++++++-- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Sources/Datadog/Core/Telemetry.swift b/Sources/Datadog/Core/Telemetry.swift index 776d593fd4..d8511b6de1 100644 --- a/Sources/Datadog/Core/Telemetry.swift +++ b/Sources/Datadog/Core/Telemetry.swift @@ -33,7 +33,7 @@ extension Telemetry { /// - message: The debug message. /// - file: The current file name. /// - line: The line number in file. - func debug(_ message: String, file: String = #fileID, line: Int = #line) { + func debug(_ message: String, file: String = #file, line: Int = #line) { debug(id: "\(file):\(line):\(message)", message: message) } @@ -44,7 +44,9 @@ extension Telemetry { /// - stack: The stack trace. /// - file: The current file name. /// - line: The line number in file. - func error(_ message: String, kind: String? = nil, stack: String? = nil, file: String = #fileID, line: Int = #line) { + /// - file: The current file name. + /// - line: The line number in file. + func error(_ message: String, kind: String? = nil, stack: String? = nil, file: String = #file, line: Int = #line) { error(id: "\(file):\(line):\(message)", message: message, kind: kind, stack: stack) } @@ -52,8 +54,10 @@ extension Telemetry { /// /// - Parameters: /// - error: The error. - func error(_ error: DDError) { - self.error(error.message, kind: error.type, stack: error.stack) + /// - file: The current file name. + /// - line: The line number in file. + func error(_ error: DDError, file: String = #file, line: Int = #line) { + self.error(error.message, kind: error.type, stack: error.stack, file: file, line: line) } /// Collect execution error. @@ -61,16 +65,20 @@ extension Telemetry { /// - Parameters: /// - message: The error message. /// - error: The error. - func error(_ message: String, error: DDError) { - self.error("\(message) - \(error.message)", kind: error.type, stack: error.stack) + /// - file: The current file name. + /// - line: The line number in file. + func error(_ message: String, error: DDError, file: String = #file, line: Int = #line) { + self.error("\(message) - \(error.message)", kind: error.type, stack: error.stack, file: file, line: line) } /// Collect execution error. /// /// - Parameters: /// - error: The error. - func error(_ error: Error) { - self.error(DDError(error: error)) + /// - file: The current file name. + /// - line: The line number in file. + func error(_ error: Error, file: String = #file, line: Int = #line) { + self.error(DDError(error: error), file: file, line: line) } /// Collect execution error. @@ -78,7 +86,9 @@ extension Telemetry { /// - Parameters: /// - message: The error message. /// - error: The error. - func error(_ message: String, error: Error) { - self.error(message, error: DDError(error: error)) + /// - file: The current file name. + /// - line: The line number in file. + func error(_ message: String, error: Error, file: String = #file, line: Int = #line) { + self.error(message, error: DDError(error: error), file: file, line: line) } } diff --git a/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift index f262535a6a..a001ea0103 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMTelemetryTests.swift @@ -118,12 +118,12 @@ class RUMTelemetryTests: XCTestCase { func testTelemetryErrorFormatting() { class TelemetryTest: Telemetry { - var record: (message: String, kind: String?, stack: String?)? + var record: (id: String, message: String, kind: String?, stack: String?)? func debug(id: String, message: String) { } func error(id: String, message: String, kind: String?, stack: String?) { - record = (message: message, kind: kind, stack: stack) + record = (id: id, message: message, kind: kind, stack: stack) } } @@ -143,12 +143,20 @@ class RUMTelemetryTests: XCTestCase { ] ) + #sourceLocation(file: "File.swift", line: 1) telemetry.error(swiftError) + #sourceLocation() + + XCTAssertEqual(telemetry.record?.id, #"File.swift:1:SwiftError(description: "error description")"#) XCTAssertEqual(telemetry.record?.message, #"SwiftError(description: "error description")"#) XCTAssertEqual(telemetry.record?.kind, "SwiftError") XCTAssertEqual(telemetry.record?.stack, #"SwiftError(description: "error description")"#) + #sourceLocation(file: "File.swift", line: 2) telemetry.error(nsError) + #sourceLocation() + + XCTAssertEqual(telemetry.record?.id, "File.swift:2:error description") XCTAssertEqual(telemetry.record?.message, "error description") XCTAssertEqual(telemetry.record?.kind, "custom-domain - 10") XCTAssertEqual( From 0c4fb8aae0f48c9621546729ac63411d884fb028 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Mon, 2 May 2022 15:29:48 +0200 Subject: [PATCH 24/55] RUMM-1227 DatadogSDKTesting is enabled for Crash tests --- ...gCrashReportingIntegrationTests.xctestplan | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Datadog/TargetSupport/DatadogIntegrationTests/DatadogCrashReportingIntegrationTests.xctestplan b/Datadog/TargetSupport/DatadogIntegrationTests/DatadogCrashReportingIntegrationTests.xctestplan index c070030cdf..26fbafb19f 100644 --- a/Datadog/TargetSupport/DatadogIntegrationTests/DatadogCrashReportingIntegrationTests.xctestplan +++ b/Datadog/TargetSupport/DatadogIntegrationTests/DatadogCrashReportingIntegrationTests.xctestplan @@ -25,6 +25,64 @@ } ] }, + "environmentVariableEntries" : [ + { + "key" : "DD_DISABLE_CRASH_HANDLER", + "value" : "1" + }, + { + "key" : "DD_TEST_RUNNER", + "value" : "$(DD_TEST_RUNNER)" + }, + { + "key" : "DD_API_KEY", + "value" : "$(DD_SDK_SWIFT_TESTING_APIKEY)" + }, + { + "key" : "DD_ENV", + "value" : "$(DD_SDK_SWIFT_TESTING_ENV)" + }, + { + "key" : "DD_SERVICE", + "value" : "$(DD_SDK_SWIFT_TESTING_SERVICE)" + }, + { + "key" : "DD_DISABLE_SDKIOS_INTEGRATION", + "value" : "1" + }, + { + "key" : "DD_DISABLE_HEADERS_INJECTION", + "value" : "1" + }, + { + "key" : "DD_ENABLE_RECORD_PAYLOAD", + "value" : "1" + }, + { + "key" : "SRCROOT", + "value" : "$(SRCROOT)" + }, + { + "key" : "BITRISE_SOURCE_DIR", + "value" : "$(BITRISE_SOURCE_DIR)" + }, + { + "key" : "BITRISE_TRIGGERED_WORKFLOW_ID", + "value" : "$(BITRISE_TRIGGERED_WORKFLOW_ID)" + }, + { + "key" : "BITRISE_BUILD_SLUG", + "value" : "$(BITRISE_BUILD_SLUG)" + }, + { + "key" : "BITRISE_BUILD_NUMBER", + "value" : "$(BITRISE_BUILD_NUMBER)" + }, + { + "key" : "BITRISE_BUILD_URL", + "value" : "$(BITRISE_BUILD_URL)" + } + ], "targetForVariableExpansion" : { "containerPath" : "container:Datadog.xcodeproj", "identifier" : "61441C2924616F1D003D8BB8", From e7b69690c2ecc8fef172bf29a484195ec63066ee Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 4 May 2022 16:32:05 +0200 Subject: [PATCH 25/55] Revert CHANGELOG --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f0fa32e76..51d2595817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ # Unreleased -* [BUGFIX] Fix rare problem with bringing up the "Local Network Permission" alert. See [#830][] - # 1.11.0-beta1 / 04-26-2022 ### Changes @@ -349,7 +347,6 @@ [#795]: https://github.com/DataDog/dd-sdk-ios/issues/795 [#797]: https://github.com/DataDog/dd-sdk-ios/issues/797 [#815]: https://github.com/DataDog/dd-sdk-ios/issues/815 -[#830]: https://github.com/DataDog/dd-sdk-ios/issues/830 [@00FA9A]: https://github.com/00FA9A [@Britton-Earnin]: https://github.com/Britton-Earnin [@Hengyu]: https://github.com/Hengyu From 04a96278a6ee4f0dce4919890989c0780ae0a88e Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Mon, 2 May 2022 14:36:25 -0400 Subject: [PATCH 26/55] Allow manually tracked resources to detect first party hosts First party hosts set by `trackUrlSession` only works for automatically instrumented URLSessions. This allows manual calls to startResourceLoading and stopResourceLoading to detect first party host calls as well. --- CHANGELOG.md | 2 + .../Datadog/Core/FeaturesConfiguration.swift | 19 ++++--- .../Scopes/RUMScopeDependencies.swift | 2 + Sources/Datadog/RUMMonitor.swift | 6 +-- .../URLFiltering/FirstPartyURLsFilter.swift | 11 ++++ .../Datadog/Mocks/CoreMocks.swift | 6 ++- .../Datadog/Mocks/RUMFeatureMocks.swift | 3 ++ .../Datadog/RUMMonitorTests.swift | 52 +++++++++++++++++++ 8 files changed, 90 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55f619e501..ab5e7f26a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Changes +* [IMPROVEMENT] Allow manually tracked resources in RUM Sessions to detect first party hosts. + # 1.11.0-beta2 / 05-04-2022 ### Changes diff --git a/Sources/Datadog/Core/FeaturesConfiguration.swift b/Sources/Datadog/Core/FeaturesConfiguration.swift index 079b2179af..ebc40fc1d3 100644 --- a/Sources/Datadog/Core/FeaturesConfiguration.swift +++ b/Sources/Datadog/Core/FeaturesConfiguration.swift @@ -62,6 +62,7 @@ internal struct FeaturesConfiguration { let instrumentation: Instrumentation? let backgroundEventTrackingEnabled: Bool let onSessionStart: RUMSessionListener? + let firstPartyHosts: Set } struct URLSessionAutoInstrumentation { @@ -184,6 +185,14 @@ 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, @@ -207,7 +216,8 @@ extension FeaturesConfiguration { longTaskEventMapper: configuration.rumLongTaskEventMapper, instrumentation: instrumentation, backgroundEventTrackingEnabled: configuration.rumBackgroundEventTrackingEnabled, - onSessionStart: configuration.rumSessionsListener + onSessionStart: configuration.rumSessionsListener, + firstPartyHosts: sanitizedHosts ) } else { let error = ProgrammerError( @@ -220,13 +230,10 @@ extension FeaturesConfiguration { } } - if let firstPartyHosts = configuration.firstPartyHosts { + if configuration.firstPartyHosts != nil { if configuration.tracingEnabled || configuration.rumEnabled { urlSessionAutoInstrumentation = URLSessionAutoInstrumentation( - userDefinedFirstPartyHosts: hostsSanitizer.sanitized( - hosts: firstPartyHosts, - warningMessage: "The first party host configured for Datadog SDK is not valid" - ), + userDefinedFirstPartyHosts: sanitizedHosts, sdkInternalURLs: [ logsEndpoint.url, tracesEndpoint.url, diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift index 6563512a2f..0dbcc265c7 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMScopeDependencies.swift @@ -23,6 +23,7 @@ internal struct RUMScopeDependencies { let applicationVersion: String let sdkVersion: String let source: String + let firstPartyURLsFilter: FirstPartyURLsFilter let eventBuilder: RUMEventBuilder let eventOutput: RUMEventOutput let rumUUIDGenerator: RUMUUIDGenerator @@ -61,6 +62,7 @@ internal extension RUMScopeDependencies { applicationVersion: rumFeature.configuration.common.applicationVersion, sdkVersion: rumFeature.configuration.common.sdkVersion, source: rumFeature.configuration.common.source, + firstPartyURLsFilter: FirstPartyURLsFilter(hosts: rumFeature.configuration.firstPartyHosts), eventBuilder: RUMEventBuilder( eventsMapper: rumFeature.eventsMapper ), diff --git a/Sources/Datadog/RUMMonitor.swift b/Sources/Datadog/RUMMonitor.swift index 7822becc9d..c396dca039 100644 --- a/Sources/Datadog/RUMMonitor.swift +++ b/Sources/Datadog/RUMMonitor.swift @@ -349,7 +349,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { url: request.url?.absoluteString ?? "unknown_url", httpMethod: RUMMethod(httpMethod: request.httpMethod), kind: RUMResourceType(request: request), - isFirstPartyRequest: nil, + isFirstPartyRequest: applicationScope.dependencies.firstPartyURLsFilter.isFirstParty(url: request.url), spanContext: nil ) ) @@ -368,7 +368,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { url: url.absoluteString, httpMethod: .get, kind: nil, - isFirstPartyRequest: nil, + isFirstPartyRequest: applicationScope.dependencies.firstPartyURLsFilter.isFirstParty(url: url), spanContext: nil ) ) @@ -388,7 +388,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { url: urlString, httpMethod: httpMethod, kind: nil, - isFirstPartyRequest: nil, + isFirstPartyRequest: applicationScope.dependencies.firstPartyURLsFilter.isFirstParty(string: urlString), spanContext: nil ) ) diff --git a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilter.swift b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilter.swift index 6c65ddb591..002eee8c59 100644 --- a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilter.swift +++ b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLFiltering/FirstPartyURLsFilter.swift @@ -32,4 +32,15 @@ internal struct FirstPartyURLsFilter { } return host.range(of: regex, options: .regularExpression) != nil } + + // 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 { + return false + } + return host.range(of: regex, options: .regularExpression) != nil + } } diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 713b11c2ce..7c6614ef69 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -268,7 +268,8 @@ extension FeaturesConfiguration.RUM { longTaskEventMapper: RUMLongTaskEventMapper? = nil, instrumentation: FeaturesConfiguration.RUM.Instrumentation? = nil, backgroundEventTrackingEnabled: Bool = false, - onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListener() + onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListener(), + firstPartyHosts: Set = [] ) -> Self { return .init( common: common, @@ -285,7 +286,8 @@ extension FeaturesConfiguration.RUM { longTaskEventMapper: longTaskEventMapper, instrumentation: instrumentation, backgroundEventTrackingEnabled: backgroundEventTrackingEnabled, - onSessionStart: onSessionStart + onSessionStart: onSessionStart, + firstPartyHosts: firstPartyHosts ) } } diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index 27eb7d51d4..becb99c0f4 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -686,6 +686,7 @@ extension RUMScopeDependencies { applicationVersion: applicationVersion, sdkVersion: sdkVersion, source: source, + firstPartyURLsFilter: FirstPartyURLsFilter(hosts: []), eventBuilder: eventBuilder, eventOutput: eventOutput, rumUUIDGenerator: rumUUIDGenerator, @@ -714,6 +715,7 @@ extension RUMScopeDependencies { applicationVersion: String? = nil, sdkVersion: String? = nil, source: String? = nil, + firstPartyUrls: Set? = nil, eventBuilder: RUMEventBuilder? = nil, eventOutput: RUMEventOutput? = nil, rumUUIDGenerator: RUMUUIDGenerator? = nil, @@ -736,6 +738,7 @@ extension RUMScopeDependencies { applicationVersion: applicationVersion ?? self.applicationVersion, sdkVersion: sdkVersion ?? self.sdkVersion, source: source ?? self.source, + firstPartyURLsFilter: firstPartyUrls != nil ? FirstPartyURLsFilter(hosts: firstPartyUrls!) : self.firstPartyURLsFilter, eventBuilder: eventBuilder ?? self.eventBuilder, eventOutput: eventOutput ?? self.eventOutput, rumUUIDGenerator: rumUUIDGenerator ?? self.rumUUIDGenerator, diff --git a/Tests/DatadogTests/Datadog/RUMMonitorTests.swift b/Tests/DatadogTests/Datadog/RUMMonitorTests.swift index 9c48f4ed7e..ab846180fd 100644 --- a/Tests/DatadogTests/Datadog/RUMMonitorTests.swift +++ b/Tests/DatadogTests/Datadog/RUMMonitorTests.swift @@ -262,6 +262,7 @@ class RUMMonitorTests: XCTestCase { let resourceEvent = session.viewVisits[0].resourceEvents[0] XCTAssertEqual(resourceEvent.resource.url, url.absoluteString) XCTAssertEqual(resourceEvent.resource.statusCode, 200) + XCTAssertNil(resourceEvent.resource.provider?.type) } func testStartingView_thenLoadingResourceWithURLString() throws { @@ -284,6 +285,57 @@ class RUMMonitorTests: XCTestCase { XCTAssertEqual(resourceEvent.resource.statusCode, 333) XCTAssertEqual(resourceEvent.resource.type, .beacon) XCTAssertEqual(resourceEvent.resource.method, .post) + XCTAssertNil(resourceEvent.resource.provider?.type) + } + + func testLoadingResourceWithURL_thenMarksFirstPartyURLs() throws { + RUMFeature.instance = .mockByRecordingRUMEventMatchers( + directories: temporaryFeatureDirectories, + configuration: .mockWith( + // .mockRandom always uses foo.com + firstPartyHosts: ["foo.com"] + ) + ) + defer { RUMFeature.instance?.deinitialize() } + + let monitor = try createTestableRUMMonitor() + setGlobalAttributes(of: monitor) + + let url: URL = .mockRandom() + monitor.startView(viewController: mockView) + monitor.startResourceLoading(resourceKey: "/resource/1", url: url) + monitor.stopResourceLoading(resourceKey: "/resource/1", response: .mockWith(statusCode: 200, mimeType: "image/png")) + + let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 4) + verifyGlobalAttributes(in: rumEventMatchers) + + let session = try XCTUnwrap(try RUMSessionMatcher.groupMatchersBySessions(rumEventMatchers).first) + let resourceEvent = session.viewVisits[0].resourceEvents[0] + XCTAssertEqual(resourceEvent.resource.provider?.type, RUMResourceEvent.Resource.Provider.ProviderType.firstParty) + } + + func testLoadingResourceWithURLString_thenMarksFirstPartyURLs() throws { + RUMFeature.instance = .mockByRecordingRUMEventMatchers( + directories: temporaryFeatureDirectories, + configuration: .mockWith( + firstPartyHosts: ["foo.com"] + ) + ) + defer { RUMFeature.instance?.deinitialize() } + + let monitor = try createTestableRUMMonitor() + setGlobalAttributes(of: monitor) + + monitor.startView(viewController: mockView) + monitor.startResourceLoading(resourceKey: "/resource/1", httpMethod: .post, urlString: "http://www.foo.com/some/url/string", attributes: [:]) + monitor.stopResourceLoading(resourceKey: "/resource/1", statusCode: 333, kind: .beacon) + + let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 4) + verifyGlobalAttributes(in: rumEventMatchers) + + let session = try XCTUnwrap(try RUMSessionMatcher.groupMatchersBySessions(rumEventMatchers).first) + let resourceEvent = session.viewVisits[0].resourceEvents[0] + XCTAssertEqual(resourceEvent.resource.provider?.type, RUMResourceEvent.Resource.Provider.ProviderType.firstParty) } func testStartingView_thenTappingButton() throws { From e3dad80223d1d8434a9a713ba64d11a129511e27 Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Wed, 4 May 2022 11:54:54 -0400 Subject: [PATCH 27/55] Remove isFirstPartRequest from the command Instead, use the firstPartyURLsFilter on the dependencies to set isFirstPartyResource as we're writing the resource. --- .../URLSessionRUMResourcesHandler.swift | 1 - .../Datadog/RUM/RUMMonitor/RUMCommand.swift | 2 -- .../RUM/RUMMonitor/Scopes/RUMViewScope.swift | 2 +- Sources/Datadog/RUMMonitor.swift | 3 -- .../Datadog/Mocks/RUMFeatureMocks.swift | 1 - .../URLSessionRUMResourcesHandlerTests.swift | 34 +------------------ 6 files changed, 2 insertions(+), 41 deletions(-) diff --git a/Sources/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift b/Sources/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift index a5f804d4e5..5b84dc9ce8 100644 --- a/Sources/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift +++ b/Sources/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift @@ -42,7 +42,6 @@ internal class URLSessionRUMResourcesHandler: URLSessionInterceptionHandler, RUM url: url, httpMethod: RUMMethod(httpMethod: interception.request.httpMethod), kind: RUMResourceType(request: interception.request), - isFirstPartyRequest: interception.isFirstPartyRequest, spanContext: interception.spanContext.flatMap { spanContext in .init( traceID: String(spanContext.traceID.rawValue), diff --git a/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift b/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift index 2b7072090b..2f7d6e52b8 100644 --- a/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift +++ b/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift @@ -157,8 +157,6 @@ internal struct RUMStartResourceCommand: RUMResourceCommand { let httpMethod: RUMMethod /// A type of the Resource if it's possible to determine on start (when the response MIME is not yet known). let kind: RUMResourceType? - /// Whether or not the resource url targets a first party host, if that information is available. - let isFirstPartyRequest: Bool? /// Span context passed to the RUM backend in order to generate the APM span for underlying resource. let spanContext: RUMSpanContext? } diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index bc12080c7a..9005d2a7d9 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -234,7 +234,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { dateCorrection: dateCorrection, url: command.url, httpMethod: command.httpMethod, - isFirstPartyResource: command.isFirstPartyRequest, + isFirstPartyResource: dependencies.firstPartyURLsFilter.isFirstParty(string: command.url), resourceKindBasedOnRequest: command.kind, spanContext: command.spanContext, onResourceEventSent: { [weak self] in diff --git a/Sources/Datadog/RUMMonitor.swift b/Sources/Datadog/RUMMonitor.swift index c396dca039..1d9d6ca8f3 100644 --- a/Sources/Datadog/RUMMonitor.swift +++ b/Sources/Datadog/RUMMonitor.swift @@ -349,7 +349,6 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { url: request.url?.absoluteString ?? "unknown_url", httpMethod: RUMMethod(httpMethod: request.httpMethod), kind: RUMResourceType(request: request), - isFirstPartyRequest: applicationScope.dependencies.firstPartyURLsFilter.isFirstParty(url: request.url), spanContext: nil ) ) @@ -368,7 +367,6 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { url: url.absoluteString, httpMethod: .get, kind: nil, - isFirstPartyRequest: applicationScope.dependencies.firstPartyURLsFilter.isFirstParty(url: url), spanContext: nil ) ) @@ -388,7 +386,6 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { url: urlString, httpMethod: httpMethod, kind: nil, - isFirstPartyRequest: applicationScope.dependencies.firstPartyURLsFilter.isFirstParty(string: urlString), spanContext: nil ) ) diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index becb99c0f4..80e196cdaa 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -362,7 +362,6 @@ extension RUMStartResourceCommand: AnyMockable, RandomMockable { url: url, httpMethod: httpMethod, kind: kind, - isFirstPartyRequest: isFirstPartyRequest, spanContext: spanContext ) } diff --git a/Tests/DatadogTests/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift b/Tests/DatadogTests/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift index 99360106a0..840e4ae03f 100644 --- a/Tests/DatadogTests/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift @@ -45,39 +45,7 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { XCTAssertNil(resourceStartCommand.spanContext) } - func testGivenTaskInterceptionForFirstPartyHost_whenInterceptionStarts_itStartsRUMResourceForFirstPartyHost() throws { - let receiveCommand = expectation(description: "Receive RUM command") - commandSubscriber.onCommandReceived = { _ in receiveCommand.fulfill() } - - // Given - let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: true) - - // When - handler.notify_taskInterceptionStarted(interception: taskInterception) - - // Then - waitForExpectations(timeout: 0.5, handler: nil) - - let resourceStartCommand = try XCTUnwrap(commandSubscriber.lastReceivedCommand as? RUMStartResourceCommand) - XCTAssertTrue(resourceStartCommand.isFirstPartyRequest!) - } - - func testGivenTaskInterceptionForThirdPartyHost_whenInterceptionStarts_itStartsRUMResourceForThirdPartyHost() throws { - let receiveCommand = expectation(description: "Receive RUM command") - commandSubscriber.onCommandReceived = { _ in receiveCommand.fulfill() } - - // Given - let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: false) - - // When - handler.notify_taskInterceptionStarted(interception: taskInterception) - - // Then - waitForExpectations(timeout: 0.5, handler: nil) - - let resourceStartCommand = try XCTUnwrap(commandSubscriber.lastReceivedCommand as? RUMStartResourceCommand) - XCTAssertFalse(resourceStartCommand.isFirstPartyRequest!) - } + func testGivenTaskInterceptionWithSpanContext_whenInterceptionStarts_itStartsRUMResource() throws { let receiveCommand = expectation(description: "Receive RUM command") From 3a5f4d05574a921cef736cbf916912a2ae41f8ae Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Thu, 5 May 2022 11:57:37 -0400 Subject: [PATCH 28/55] Swiftlint fixes --- .../Resources/URLSessionRUMResourcesHandlerTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/DatadogTests/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift b/Tests/DatadogTests/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift index 840e4ae03f..cbb3356741 100644 --- a/Tests/DatadogTests/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandlerTests.swift @@ -45,8 +45,6 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase { XCTAssertNil(resourceStartCommand.spanContext) } - - func testGivenTaskInterceptionWithSpanContext_whenInterceptionStarts_itStartsRUMResource() throws { let receiveCommand = expectation(description: "Receive RUM command") commandSubscriber.onCommandReceived = { _ in receiveCommand.fulfill() } From 3c6e01be8dbab84d64b3c64a7e5686ea63d0860e Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Thu, 5 May 2022 16:45:38 -0400 Subject: [PATCH 29/55] Move isFirstPartyResource check up to RUMResourceScope Checking first party resource a the last possible minute, avoiding passing it through multiple scopes. --- .../RUM/RUMMonitor/Scopes/RUMResourceScope.swift | 5 ++--- .../Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift | 1 - Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift | 4 ++-- .../RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift | 10 +++++----- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift index b68e0b022b..459e420a43 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift @@ -28,7 +28,7 @@ internal class RUMResourceScope: RUMScope { /// The HTTP method used to load this Resource. private var resourceHTTPMethod: RUMMethod /// Whether or not the Resource is provided by a first party host, if that information is available. - private let isFirstPartyResource: Bool? + private let isFirstPartyResource: Bool /// The Resource kind captured when starting the `URLRequest`. /// It may be `nil` if it's not possible to predict the kind from resource and the response MIME type is needed. private var resourceKindBasedOnRequest: RUMResourceType? @@ -54,7 +54,6 @@ internal class RUMResourceScope: RUMScope { dateCorrection: DateCorrection, url: String, httpMethod: RUMMethod, - isFirstPartyResource: Bool?, resourceKindBasedOnRequest: RUMResourceType?, spanContext: RUMSpanContext?, onResourceEventSent: @escaping () -> Void, @@ -69,7 +68,7 @@ internal class RUMResourceScope: RUMScope { self.resourceLoadingStartTime = startTime self.dateCorrection = dateCorrection self.resourceHTTPMethod = httpMethod - self.isFirstPartyResource = isFirstPartyResource + self.isFirstPartyResource = dependencies.firstPartyURLsFilter.isFirstParty(string: url) self.resourceKindBasedOnRequest = resourceKindBasedOnRequest self.spanContext = spanContext self.onResourceEventSent = onResourceEventSent diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index 9005d2a7d9..5951248e8d 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -234,7 +234,6 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { dateCorrection: dateCorrection, url: command.url, httpMethod: command.httpMethod, - isFirstPartyResource: dependencies.firstPartyURLsFilter.isFirstParty(string: command.url), resourceKindBasedOnRequest: command.kind, spanContext: command.spanContext, onResourceEventSent: { [weak self] in diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index 80e196cdaa..75a551fdfa 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -663,6 +663,7 @@ extension RUMScopeDependencies { applicationVersion: String = .mockAny(), sdkVersion: String = .mockAny(), source: String = "ios", + firstPartyURLsFilter: FirstPartyURLsFilter = FirstPartyURLsFilter(hosts: []), eventBuilder: RUMEventBuilder = RUMEventBuilder(eventsMapper: .mockNoOp()), eventOutput: RUMEventOutput = RUMEventOutputMock(), rumUUIDGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), @@ -685,7 +686,7 @@ extension RUMScopeDependencies { applicationVersion: applicationVersion, sdkVersion: sdkVersion, source: source, - firstPartyURLsFilter: FirstPartyURLsFilter(hosts: []), + firstPartyURLsFilter: firstPartyURLsFilter, eventBuilder: eventBuilder, eventOutput: eventOutput, rumUUIDGenerator: rumUUIDGenerator, @@ -870,7 +871,6 @@ extension RUMResourceScope { dateCorrection: dateCorrection, url: url, httpMethod: httpMethod, - isFirstPartyResource: isFirstPartyResource, resourceKindBasedOnRequest: resourceKindBasedOnRequest, spanContext: spanContext, onResourceEventSent: onResourceEventSent, diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift index 4a10d0d41e..d57e55574e 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift @@ -12,6 +12,7 @@ class RUMResourceScopeTests: XCTestCase { private let randomServiceName: String = .mockRandom() private lazy var dependencies: RUMScopeDependencies = .mockWith( serviceName: randomServiceName, + firstPartyURLsFilter: FirstPartyURLsFilter(hosts: ["firstparty.com"]), eventOutput: output ) private let context = RUMContext.mockWith( @@ -53,7 +54,6 @@ class RUMResourceScopeTests: XCTestCase { dateCorrection: .zero, url: "https://foo.com/resource/1", httpMethod: .post, - isFirstPartyResource: nil, resourceKindBasedOnRequest: nil, spanContext: .init(traceID: "100", spanID: "200") ) @@ -560,7 +560,7 @@ class RUMResourceScopeTests: XCTestCase { attributes: [:], startTime: currentTime, dateCorrection: .zero, - url: "https://foo.com/resource/1", + url: "https://firstparty.com/resource/1", httpMethod: .post, isFirstPartyResource: true, resourceKindBasedOnRequest: nil, @@ -581,7 +581,7 @@ class RUMResourceScopeTests: XCTestCase { let providerType = try XCTUnwrap(event.resource.provider?.type) let providerDomain = try XCTUnwrap(event.resource.provider?.domain) XCTAssertEqual(providerType, .firstParty) - XCTAssertEqual(providerDomain, "foo.com") + XCTAssertEqual(providerDomain, "firstparty.com") } func testGivenStartedThirdartyResource_whenResourceLoadingEnds_itSendsResourceEventWithoutResourceProvider() throws { @@ -627,7 +627,7 @@ class RUMResourceScopeTests: XCTestCase { attributes: [:], startTime: currentTime, dateCorrection: .zero, - url: "https://foo.com/resource/1", + url: "https://firstparty.com/resource/1", httpMethod: .post, isFirstPartyResource: true ) @@ -646,7 +646,7 @@ class RUMResourceScopeTests: XCTestCase { let providerType = try XCTUnwrap(event.error.resource?.provider?.type) let providerDomain = try XCTUnwrap(event.error.resource?.provider?.domain) XCTAssertEqual(providerType, .firstParty) - XCTAssertEqual(providerDomain, "foo.com") + XCTAssertEqual(providerDomain, "firstparty.com") XCTAssertEqual(event.error.sourceType, .ios) } From 020a51c30e2dca87ee00c688b1758be454588d8b Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Fri, 6 May 2022 11:53:56 +0200 Subject: [PATCH 30/55] RUMM-2129 Invalid token error message updated --- Sources/Datadog/Core/Upload/DataUploadStatus.swift | 4 ++-- Sources/Datadog/Core/Upload/DataUploadWorker.swift | 2 +- .../Datadog/Core/Upload/DataUploadStatusTests.swift | 3 ++- .../Datadog/Core/Upload/DataUploadWorkerTests.swift | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/Datadog/Core/Upload/DataUploadStatus.swift b/Sources/Datadog/Core/Upload/DataUploadStatus.swift index 9223f17912..cc841fa6d8 100644 --- a/Sources/Datadog/Core/Upload/DataUploadStatus.swift +++ b/Sources/Datadog/Core/Upload/DataUploadStatus.swift @@ -116,10 +116,10 @@ extension DataUploadError { } switch responseStatusCode { - case .accepted, .forbidden: + case .accepted: // These codes mean either success or the user configuration mistake - do not produce error. return nil - case .unauthorized: + case .unauthorized, .forbidden: self = .unauthorized case .internalServerError, .serviceUnavailable: // These codes mean Datadog service issue - do not produce SDK error as this is already monitored by other means. diff --git a/Sources/Datadog/Core/Upload/DataUploadWorker.swift b/Sources/Datadog/Core/Upload/DataUploadWorker.swift index 77b5bbc537..9c2aab3521 100644 --- a/Sources/Datadog/Core/Upload/DataUploadWorker.swift +++ b/Sources/Datadog/Core/Upload/DataUploadWorker.swift @@ -77,7 +77,7 @@ internal class DataUploadWorker: DataUploadWorkerType { switch uploadStatus.error { case .unauthorized: - userLogger.error("⚠️ The client token you provided seems to be invalid.") + userLogger.error("⚠️ Make sure that the provided token still exists and you're targeting the relevant Datadog site.") case let .httpError(statusCode: statusCode): self.telemetry?.error("Data upload finished with status code: \(statusCode)") case let .networkError(error: error): diff --git a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift index 48bd4b0d90..3faf70e08b 100644 --- a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadStatusTests.swift @@ -94,6 +94,7 @@ class DataUploadStatusTests: XCTestCase { private let alertingStatusCodes: Set = [ 400, // BAD REQUEST 401, // UNAUTHORIZED + 403, // FORBIDDEN 413, // PAYLOAD TOO LARGE 408, // REQUEST TIMEOUT 429, // TOO MANY REQUESTS @@ -112,7 +113,7 @@ class DataUploadStatusTests: XCTestCase { } func testWhenUploadFinishesWithResponse_andStatusCodeMeansSDKIssue_itCreatesHTTPError() { - alertingStatusCodes.subtracting([401]).forEach { statusCode in + alertingStatusCodes.subtracting([401, 403]).forEach { statusCode in let status = DataUploadStatus(httpResponse: .mockResponseWith(statusCode: statusCode), ddRequestID: .mockRandom()) guard case let .httpError(statusCode: receivedStatusCode) = status.error else { diff --git a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift index b04c3f0c50..ac00eda5cd 100644 --- a/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Upload/DataUploadWorkerTests.swift @@ -308,7 +308,7 @@ class DataUploadWorkerTests: XCTestCase { XCTAssertEqual( mockUserLoggerOutput.allRecordedLogs[2].message, - "⚠️ The client token you provided seems to be invalid.", + "⚠️ Make sure that the provided token still exists and you're targeting the relevant Datadog site.", "An error should be printed to `userLogger`. All captured logs:\n\(mockUserLoggerOutput.dumpAllRecordedLogs())" ) } From 54e12fd435c26e7e51ab5f926b62ec9ea6f9db4f Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Fri, 6 May 2022 08:34:50 -0400 Subject: [PATCH 31/55] Update Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift Co-authored-by: Maxime Epain --- Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index 75a551fdfa..b95888179b 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -738,7 +738,7 @@ extension RUMScopeDependencies { applicationVersion: applicationVersion ?? self.applicationVersion, sdkVersion: sdkVersion ?? self.sdkVersion, source: source ?? self.source, - firstPartyURLsFilter: firstPartyUrls != nil ? FirstPartyURLsFilter(hosts: firstPartyUrls!) : self.firstPartyURLsFilter, + firstPartyURLsFilter: firstPartyUrls.map { .init(hosts: $0) } ?? self.firstPartyURLsFilter, eventBuilder: eventBuilder ?? self.eventBuilder, eventOutput: eventOutput ?? self.eventOutput, rumUUIDGenerator: rumUUIDGenerator ?? self.rumUUIDGenerator, From 168e1e840827b4e6cfa8d4a99cac277b7f4bfabf Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Mon, 9 May 2022 14:22:47 +0200 Subject: [PATCH 32/55] Update Sources/Datadog/Core/Upload/DataUploadStatus.swift Co-authored-by: Maciek Grzybowski --- Sources/Datadog/Core/Upload/DataUploadStatus.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Datadog/Core/Upload/DataUploadStatus.swift b/Sources/Datadog/Core/Upload/DataUploadStatus.swift index cc841fa6d8..2af3a5e903 100644 --- a/Sources/Datadog/Core/Upload/DataUploadStatus.swift +++ b/Sources/Datadog/Core/Upload/DataUploadStatus.swift @@ -117,7 +117,6 @@ extension DataUploadError { switch responseStatusCode { case .accepted: - // These codes mean either success or the user configuration mistake - do not produce error. return nil case .unauthorized, .forbidden: self = .unauthorized From 37b082e579d67f9dc6abdff801ce5c371af1ab6d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Pinalie <2850825+jybp@users.noreply.github.com> Date: Wed, 11 May 2022 13:14:03 +0200 Subject: [PATCH 33/55] Update crash_reporting.md --- docs/rum_collection/crash_reporting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rum_collection/crash_reporting.md b/docs/rum_collection/crash_reporting.md index 8d54108dfd..d769009ae1 100644 --- a/docs/rum_collection/crash_reporting.md +++ b/docs/rum_collection/crash_reporting.md @@ -92,7 +92,7 @@ Depending on your setup, you may need to download dSYM files from App Store Conn ### Upload your dYSM file -By uploading your dYSM file to Datadog, you gain access to the file path, line number, and code snippet of each frame in an error's related stack trace. +By uploading your dYSM file to Datadog, you gain access to the file path and line number of each frame in an error's related stack trace. Once your application crashes and you restart the application, the iOS SDK uploads a crash report to Datadog. From 3dd4b3f836b4f30abef377868b16fdf4f5c30c1e Mon Sep 17 00:00:00 2001 From: Priyanshi Gupta Date: Wed, 11 May 2022 13:30:03 +0200 Subject: [PATCH 34/55] Update crash_reporting.md --- docs/rum_collection/crash_reporting.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/rum_collection/crash_reporting.md b/docs/rum_collection/crash_reporting.md index d769009ae1..27fb228b14 100644 --- a/docs/rum_collection/crash_reporting.md +++ b/docs/rum_collection/crash_reporting.md @@ -18,7 +18,7 @@ Enable iOS Crash Reporting and Error Tracking to get comprehensive crash reports - Symbolicated iOS crash reports - Trend analysis with iOS error tracking -In order to symbolicate your stack traces, find and upload your dYSM files to Datadog. Then, verify your configuration by running a test crash and restarting your application. +In order to symbolicate your stack traces, find and upload your .dSYM files to Datadog. Then, verify your configuration by running a test crash and restarting your application. Your crash reports appear in [**Error Tracking**][8]. @@ -77,18 +77,18 @@ Global.rum = RUMMonitor.initialize() ## Symbolicate crash reports -Crash reports are collected in a raw format and mostly contain memory addresses. To map these addresses into legible symbol information, Datadog requires dYSM files, which are generated in your application's build or distribution process. +Crash reports are collected in a raw format and mostly contain memory addresses. To map these addresses into legible symbol information, Datadog requires .dSYM files, which are generated in your application's build or distribution process. -### Find your dYSM file +### Find your .dSYM file -Every iOS application produces dYSM files for each application module. These files minimize an application's binary size and enable faster download speed. Each application version contains a set of dYSM files. +Every iOS application produces .dSYM files for each application module. These files minimize an application's binary size and enable faster download speed. Each application version contains a set of .dSYM files. -Depending on your setup, you may need to download dSYM files from App Store Connect or find them on your local machine. +Depending on your setup, you may need to download .dSYM files from App Store Connect or find them on your local machine. | Bitcode Enabled | Description | |-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Yes | dSYM files are available once [App Store Connect][6] completes processing your application's build. | -| No | Xcode exports dSYM files to `$DWARF_DSYM_FOLDER_PATH` at the end of your application's build. Ensure that the `DEBUG_INFORMATION_FORMAT` build setting is set to **DWARF with dYSM File**. By default, Xcode projects only set `DEBUG_INFORMATION_FORMAT` to **DWARF with dSYM File** for the Release project configuration. | +| No | Xcode exports dSYM files to `$DWARF_DSYM_FOLDER_PATH` at the end of your application's build. Ensure that the `DEBUG_INFORMATION_FORMAT` build setting is set to **DWARF with .d File**. By default, Xcode projects only set `DEBUG_INFORMATION_FORMAT` to **DWARF with dSYM File** for the Release project configuration. | ### Upload your dYSM file From 8abfde81bcf29e4473ded621a077534cff1d55e1 Mon Sep 17 00:00:00 2001 From: Priyanshi Gupta Date: Wed, 11 May 2022 13:33:19 +0200 Subject: [PATCH 35/55] Update crash_reporting.md --- docs/rum_collection/crash_reporting.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/rum_collection/crash_reporting.md b/docs/rum_collection/crash_reporting.md index 27fb228b14..e784a985e9 100644 --- a/docs/rum_collection/crash_reporting.md +++ b/docs/rum_collection/crash_reporting.md @@ -88,11 +88,11 @@ Depending on your setup, you may need to download .dSYM files from App Store Con | Bitcode Enabled | Description | |-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Yes | dSYM files are available once [App Store Connect][6] completes processing your application's build. | -| No | Xcode exports dSYM files to `$DWARF_DSYM_FOLDER_PATH` at the end of your application's build. Ensure that the `DEBUG_INFORMATION_FORMAT` build setting is set to **DWARF with .d File**. By default, Xcode projects only set `DEBUG_INFORMATION_FORMAT` to **DWARF with dSYM File** for the Release project configuration. | +| No | Xcode exports .dSYM files to `$DWARF_DSYM_FOLDER_PATH` at the end of your application's build. Ensure that the `DEBUG_INFORMATION_FORMAT` build setting is set to **DWARF with dSYM File**. By default, Xcode projects only set `DEBUG_INFORMATION_FORMAT` to **DWARF with dSYM File** for the Release project configuration. | -### Upload your dYSM file +### Upload your dSYM file -By uploading your dYSM file to Datadog, you gain access to the file path and line number of each frame in an error's related stack trace. +By uploading your .dSYM file to Datadog, you gain access to the file path and line number of each frame in an error's related stack trace. Once your application crashes and you restart the application, the iOS SDK uploads a crash report to Datadog. From 39c98614fe98f296689ce4342df834abf59d09e4 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 11 May 2022 14:52:38 +0200 Subject: [PATCH 36/55] Run integration workflow on benchmark tests change --- tools/ci/choose_workflows.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/ci/choose_workflows.py b/tools/ci/choose_workflows.py index 6c1dfc8734..a78d4cf659 100644 --- a/tools/ci/choose_workflows.py +++ b/tools/ci/choose_workflows.py @@ -42,6 +42,7 @@ def should_run_integration_tests(ctx: CIContext) -> bool: pr_path_prefixes=[ 'Datadog/Example/', 'Tests/DatadogIntegrationTests/', + 'Tests/DatadogBenchmarkTests/', ], pr_file_extensions=[] ) From 1e66ab6871dd91b2639e9c075a328b52f0abdc0c Mon Sep 17 00:00:00 2001 From: Austin Lai <76412946+alai97@users.noreply.github.com> Date: Wed, 11 May 2022 07:29:01 -0700 Subject: [PATCH 37/55] Copy Nit --- docs/rum_collection/crash_reporting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rum_collection/crash_reporting.md b/docs/rum_collection/crash_reporting.md index e784a985e9..f0a5ec303e 100644 --- a/docs/rum_collection/crash_reporting.md +++ b/docs/rum_collection/crash_reporting.md @@ -79,7 +79,7 @@ Global.rum = RUMMonitor.initialize() Crash reports are collected in a raw format and mostly contain memory addresses. To map these addresses into legible symbol information, Datadog requires .dSYM files, which are generated in your application's build or distribution process. -### Find your .dSYM file +### Find your dSYM file Every iOS application produces .dSYM files for each application module. These files minimize an application's binary size and enable faster download speed. Each application version contains a set of .dSYM files. From 6226b2cbfc6339cf617183a907c3148594bf162a Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Thu, 12 May 2022 10:45:56 +0200 Subject: [PATCH 38/55] RUMM-2166 Add tracing sampling rate --- .../Datadog/Core/FeaturesConfiguration.swift | 5 +- Sources/Datadog/DatadogConfiguration.swift | 11 ++++ .../URLSessionRUMResourcesHandler.swift | 6 +- .../Propagation/HTTPHeadersWriter.swift | 33 +++++++--- .../Propagation/TracingHTTPHeaders.swift | 6 +- .../Interception/URLSessionInterceptor.swift | 5 +- .../DatadogConfiguration+objc.swift | 5 ++ .../Propagation/HTTPHeadersWriter+objc.swift | 6 +- .../RUM/RUMResourcesScenarioTests.swift | 4 -- .../TracingURLSessionScenarioTests.swift | 1 - .../DatadogConfigurationBuilderTests.swift | 3 + .../Datadog/Mocks/CoreMocks.swift | 10 ++- Tests/DatadogTests/Datadog/TracerTests.swift | 19 +++++- .../URLSessionInterceptorTests.swift | 66 +++++++++++++++---- .../DatadogObjc/DDConfigurationTests.swift | 3 + .../DatadogObjc/DDTracerTests.swift | 16 ++++- 16 files changed, 154 insertions(+), 45 deletions(-) diff --git a/Sources/Datadog/Core/FeaturesConfiguration.swift b/Sources/Datadog/Core/FeaturesConfiguration.swift index 417b8fa609..143092d30c 100644 --- a/Sources/Datadog/Core/FeaturesConfiguration.swift +++ b/Sources/Datadog/Core/FeaturesConfiguration.swift @@ -75,6 +75,8 @@ internal struct FeaturesConfiguration { let instrumentTracing: Bool /// If the RUM instrumentation should be enabled. let instrumentRUM: Bool + // Tracing sampler + let samplingRate: Float } struct CrashReporting { @@ -246,7 +248,8 @@ extension FeaturesConfiguration { ], rumAttributesProvider: configuration.rumResourceAttributesProvider, instrumentTracing: configuration.tracingEnabled, - instrumentRUM: configuration.rumEnabled + instrumentRUM: configuration.rumEnabled, + samplingRate: debugOverride ? 100.0 : configuration.tracingSamplingRate ) } else { let error = ProgrammerError( diff --git a/Sources/Datadog/DatadogConfiguration.swift b/Sources/Datadog/DatadogConfiguration.swift index 4c1f0067f4..4f747a502e 100644 --- a/Sources/Datadog/DatadogConfiguration.swift +++ b/Sources/Datadog/DatadogConfiguration.swift @@ -251,6 +251,7 @@ extension Datadog { private(set) var firstPartyHosts: Set? private(set) var logEventMapper: LogEventMapper? private(set) var spanEventMapper: SpanEventMapper? + private(set) var tracingSamplingRate: Float private(set) var rumSessionsSamplingRate: Float private(set) var rumSessionsListener: RUMSessionListener? private(set) var rumUIKitViewsPredicate: UIKitRUMViewsPredicate? @@ -328,6 +329,7 @@ extension Datadog { serviceName: nil, firstPartyHosts: nil, spanEventMapper: nil, + tracingSamplingRate: 100.0, rumSessionsSamplingRate: 100.0, rumSessionsListener: nil, rumUIKitViewsPredicate: nil, @@ -500,6 +502,15 @@ extension Datadog { return self } + /// Sets the sampling rate for APM tracing. + /// + /// - Parameter apmTraceSamplingRate: the sampling rate must be a value between `0.0` and `100.0`. A value of `0.0` + /// means no trace will be kept, `100.0` means all traces will be kept (default value is `100.0`). + public func set(tracingSamplingRate: Float) -> Builder { + configuration.tracingSamplingRate = tracingSamplingRate + return self + } + // MARK: - RUM Configuration /// Enables or disables the RUM feature. diff --git a/Sources/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift b/Sources/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift index a5f804d4e5..1effa8be0a 100644 --- a/Sources/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift +++ b/Sources/Datadog/RUM/Instrumentation/Resources/URLSessionRUMResourcesHandler.swift @@ -43,10 +43,10 @@ internal class URLSessionRUMResourcesHandler: URLSessionInterceptionHandler, RUM httpMethod: RUMMethod(httpMethod: interception.request.httpMethod), kind: RUMResourceType(request: interception.request), isFirstPartyRequest: interception.isFirstPartyRequest, - spanContext: interception.spanContext.flatMap { spanContext in + spanContext: interception.spanContext.map { .init( - traceID: String(spanContext.traceID.rawValue), - spanID: String(spanContext.spanID.rawValue) + traceID: String($0.traceID.rawValue), + spanID: String($0.spanID.rawValue) ) } ) diff --git a/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift b/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift index 25d4d11d89..2386ca8bca 100644 --- a/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift +++ b/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift @@ -36,14 +36,19 @@ public class HTTPHeadersWriter: OTHTTPHeadersWriter { /// public private(set) var tracePropagationHTTPHeaders: [String: String] = [:] - /// Pre-computed headers with constant values. - private let constantHTTPHeaders: [String: String] + /// The tracing sampling rate. + /// + /// This value will decide of the `x-datadog-sampling-priority` + /// header field value and if `x-datadog-trace-id` and `x-datadog-parent-id` + /// are propagated. + private let samplingRate: Float - public init() { - constantHTTPHeaders = [ - TracingHTTPHeaders.ddSamplingPriority.field: TracingHTTPHeaders.ddSamplingPriority.value, - TracingHTTPHeaders.ddSampled.field: TracingHTTPHeaders.ddSampled.value, - ] + /// Creates a `HTTPHeadersWriter` to inject traces propagation headers + /// to network request. + /// + /// - Parameter samplingRate: Tracing sampling rate. 100% (keep all) by default. + public init(samplingRate: Float = 100) { + self.samplingRate = samplingRate } public func inject(spanContext: OTSpanContext) { @@ -51,8 +56,16 @@ public class HTTPHeadersWriter: OTHTTPHeadersWriter { return } - tracePropagationHTTPHeaders = constantHTTPHeaders - tracePropagationHTTPHeaders[TracingHTTPHeaders.traceIDField] = String(spanContext.traceID.rawValue) - tracePropagationHTTPHeaders[TracingHTTPHeaders.parentSpanIDField] = String(spanContext.spanID.rawValue) + let sampler = Sampler(samplingRate: samplingRate) + let samplingPriority = sampler.sample() + + tracePropagationHTTPHeaders = [ + TracingHTTPHeaders.samplingPriorityField: samplingPriority ? "1" : "0" + ] + + if samplingPriority { + tracePropagationHTTPHeaders[TracingHTTPHeaders.traceIDField] = String(spanContext.traceID.rawValue) + tracePropagationHTTPHeaders[TracingHTTPHeaders.parentSpanIDField] = String(spanContext.spanID.rawValue) + } } } diff --git a/Sources/Datadog/Tracing/Propagation/TracingHTTPHeaders.swift b/Sources/Datadog/Tracing/Propagation/TracingHTTPHeaders.swift index 7d01664de0..6629a4e7e7 100644 --- a/Sources/Datadog/Tracing/Propagation/TracingHTTPHeaders.swift +++ b/Sources/Datadog/Tracing/Propagation/TracingHTTPHeaders.swift @@ -20,11 +20,7 @@ internal struct TracingHTTPHeaders { /// To make sure that the Agent keeps the trace. /// It is used both in Tracing and RUM features. - static let ddSamplingPriority = (field: "x-datadog-sampling-priority", value: "1") - - /// Indicates this request is selected for sampling. - /// It is used both in Tracing and RUM features. - static let ddSampled = (field: "x-datadog-sampled", value: "1") + static let samplingPriorityField = "x-datadog-sampling-priority" /// To make sure the generated traces from RUM don’t affect APM Index Spans counts. /// **Note:** it is only added to requests that we create RUM Resource for (it is not injected when RUM feature is disabled and only Tracing is used). diff --git a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift index 71c14e3fbb..ecc900e5fb 100644 --- a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift +++ b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift @@ -44,6 +44,8 @@ public class URLSessionInterceptor: URLSessionInterceptorType { /// Additional header injected to intercepted 1st party requests. /// Set to `x-datadog-origin: rum` if both RUM and Tracing instrumentations are enabled and `nil` in all other cases. internal let additionalHeadersForFirstPartyRequests: [String: String]? + /// Tracing sampling rate + internal let samplingRate: Float // MARK: - Initialization @@ -73,6 +75,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType { self.defaultFirstPartyURLsFilter = FirstPartyURLsFilter(hosts: configuration.userDefinedFirstPartyHosts) self.internalURLsFilter = InternalURLsFilter(urls: configuration.sdkInternalURLs) self.handler = handler + self.samplingRate = configuration.samplingRate if configuration.instrumentTracing { self.injectTracingHeadersToFirstPartyRequests = true @@ -229,7 +232,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType { return firstPartyRequest } - let writer = HTTPHeadersWriter() + let writer = HTTPHeadersWriter(samplingRate: samplingRate) let spanContext = tracer.createSpanContext() tracer.inject(spanContext: spanContext, writer: writer) diff --git a/Sources/DatadogObjc/DatadogConfiguration+objc.swift b/Sources/DatadogObjc/DatadogConfiguration+objc.swift index 3cea5f4e4c..426931ddc5 100644 --- a/Sources/DatadogObjc/DatadogConfiguration+objc.swift +++ b/Sources/DatadogObjc/DatadogConfiguration+objc.swift @@ -284,6 +284,11 @@ public class DDConfigurationBuilder: NSObject { _ = sdkBuilder.set(serviceName: serviceName) } + @objc + public func set(tracingSamplingRate: Float) { + _ = sdkBuilder.set(tracingSamplingRate: tracingSamplingRate) + } + @objc public func set(rumSessionsSamplingRate: Float) { _ = sdkBuilder.set(rumSessionsSamplingRate: rumSessionsSamplingRate) diff --git a/Sources/DatadogObjc/Tracing/Propagation/HTTPHeadersWriter+objc.swift b/Sources/DatadogObjc/Tracing/Propagation/HTTPHeadersWriter+objc.swift index 43dc22e040..a82b407f14 100644 --- a/Sources/DatadogObjc/Tracing/Propagation/HTTPHeadersWriter+objc.swift +++ b/Sources/DatadogObjc/Tracing/Propagation/HTTPHeadersWriter+objc.swift @@ -9,12 +9,14 @@ import class Datadog.HTTPHeadersWriter @objc public class DDHTTPHeadersWriter: NSObject { - let swiftHTTPHeadersWriter = HTTPHeadersWriter() + let swiftHTTPHeadersWriter: HTTPHeadersWriter @objc public var tracePropagationHTTPHeaders: [String: String] { swiftHTTPHeadersWriter.tracePropagationHTTPHeaders } @objc - override public init() {} + public init(samplingRate: Float = 100) { + swiftHTTPHeadersWriter = HTTPHeadersWriter(samplingRate: samplingRate) + } } diff --git a/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMResourcesScenarioTests.swift b/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMResourcesScenarioTests.swift index d16512092d..0142657e0b 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMResourcesScenarioTests.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMResourcesScenarioTests.swift @@ -104,10 +104,6 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { firstPartyPOSTRequest.httpHeaders.contains("x-datadog-sampling-priority: 1"), "`x-datadog-sampling-priority: 1` header must be set for `firstPartyPOSTResourceURL`" ) - XCTAssertTrue( - firstPartyPOSTRequest.httpHeaders.contains("x-datadog-sampled: 1"), - "`x-datadog-sampled: 1` header must be set for `firstPartyPOSTResourceURL`" - ) XCTAssertTrue( firstPartyPOSTRequest.httpHeaders.contains("x-datadog-origin: rum"), "`x-datadog-origin: rum` header must be set for `firstPartyPOSTResourceURL`" diff --git a/Tests/DatadogIntegrationTests/Scenarios/Tracing/TracingURLSessionScenarioTests.swift b/Tests/DatadogIntegrationTests/Scenarios/Tracing/TracingURLSessionScenarioTests.swift index 71d66d8b76..ab4faeaff6 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/Tracing/TracingURLSessionScenarioTests.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/Tracing/TracingURLSessionScenarioTests.swift @@ -128,7 +128,6 @@ class TracingURLSessionScenarioTests: IntegrationTests, TracingCommonAsserts { "x-datadog-trace-id: \(try taskWithRequest.traceID().hexadecimalNumberToDecimal)", "x-datadog-parent-id: \(try taskWithRequest.spanID().hexadecimalNumberToDecimal)", "x-datadog-sampling-priority: 1", - "x-datadog-sampled: 1" ] expectedHeaders.forEach { expectedHeader in diff --git a/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift b/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift index 2d81173565..7e171a8653 100644 --- a/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift +++ b/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift @@ -45,6 +45,7 @@ class DatadogConfigurationBuilderTests: XCTestCase { XCTAssertNil(configuration.firstPartyHosts) XCTAssertNil(configuration.logEventMapper) XCTAssertNil(configuration.spanEventMapper) + XCTAssertEqual(configuration.tracingSamplingRate, 100.0) XCTAssertEqual(configuration.rumSessionsSamplingRate, 100.0) XCTAssertNil(configuration.rumSessionsListener) XCTAssertNil(configuration.rumUIKitViewsPredicate) @@ -88,6 +89,7 @@ class DatadogConfigurationBuilderTests: XCTestCase { .onRUMSessionStart { _, _ in } .setLogEventMapper { _ in mockLogEvent } .setSpanEventMapper { _ in mockSpanEvent } + .set(tracingSamplingRate: 75) .trackURLSession(firstPartyHosts: ["example.com"]) .trackUIKitRUMViews(using: UIKitRUMViewsPredicateMock()) .trackUIKitRUMActions(using: UIKitRUMUserActionsPredicateMock()) @@ -144,6 +146,7 @@ class DatadogConfigurationBuilderTests: XCTestCase { 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.tracingSamplingRate, 75) XCTAssertEqual(configuration.rumSessionsSamplingRate, 42.5) XCTAssertNotNil(configuration.rumSessionsListener) XCTAssertTrue(configuration.rumUIKitViewsPredicate is UIKitRUMViewsPredicateMock) diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 241e8d6498..1d083711bf 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -49,6 +49,7 @@ extension Datadog.Configuration { rumEndpoint: RUMEndpoint = .us1, serviceName: String? = .mockAny(), firstPartyHosts: Set? = nil, + tracingSamplingRate: Float = 100.0, rumSessionsSamplingRate: Float = 100.0, rumUIKitViewsPredicate: UIKitRUMViewsPredicate? = nil, rumUIKitUserActionsPredicate: UIKitRUMUserActionsPredicate? = nil, @@ -78,6 +79,7 @@ extension Datadog.Configuration { rumEndpoint: rumEndpoint, serviceName: serviceName, firstPartyHosts: firstPartyHosts, + tracingSamplingRate: tracingSamplingRate, rumSessionsSamplingRate: rumSessionsSamplingRate, rumUIKitViewsPredicate: rumUIKitViewsPredicate, rumUIKitUserActionsPredicate: rumUIKitUserActionsPredicate, @@ -259,7 +261,7 @@ extension FeaturesConfiguration.RUM { uploadURL: URL = .mockAny(), clientToken: String = .mockAny(), applicationID: String = .mockAny(), - sessionSampler: Sampler = Sampler(samplingRate: 100), + sessionSampler: Sampler = .mockKeepAll(), uuidGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), viewEventMapper: RUMViewEventMapper? = nil, resourceEventMapper: RUMResourceEventMapper? = nil, @@ -311,14 +313,16 @@ extension FeaturesConfiguration.URLSessionAutoInstrumentation { sdkInternalURLs: Set = [], rumAttributesProvider: URLSessionRUMAttributesProvider? = nil, instrumentTracing: Bool = true, - instrumentRUM: Bool = true + instrumentRUM: Bool = true, + samplingRate: Float = 100 ) -> Self { return .init( userDefinedFirstPartyHosts: userDefinedFirstPartyHosts, sdkInternalURLs: sdkInternalURLs, rumAttributesProvider: rumAttributesProvider, instrumentTracing: instrumentTracing, - instrumentRUM: instrumentRUM + instrumentRUM: instrumentRUM, + samplingRate: samplingRate ) } } diff --git a/Tests/DatadogTests/Datadog/TracerTests.swift b/Tests/DatadogTests/Datadog/TracerTests.swift index 7f4e5433e3..2e2bc67d6c 100644 --- a/Tests/DatadogTests/Datadog/TracerTests.swift +++ b/Tests/DatadogTests/Datadog/TracerTests.swift @@ -838,7 +838,6 @@ class TracerTests: XCTestCase { "x-datadog-trace-id": "1", "x-datadog-parent-id": "2", "x-datadog-sampling-priority": "1", - "x-datadog-sampled": "1", ] XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders1) @@ -850,11 +849,27 @@ class TracerTests: XCTestCase { "x-datadog-trace-id": "3", "x-datadog-parent-id": "4", "x-datadog-sampling-priority": "1", - "x-datadog-sampled": "1", ] XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders2) } + func testItInjectsRejectedSpanContextWithHTTPHeadersWriter() { + let tracer: Tracer = .mockAny() + let spanContext = DDSpanContext(traceID: 1, spanID: 2, parentSpanID: .mockAny(), baggageItems: .mockAny()) + + let httpHeadersWriter = HTTPHeadersWriter(samplingRate: 0) + XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, [:]) + + // When + tracer.inject(spanContext: spanContext, writer: httpHeadersWriter) + + // Then + let expectedHTTPHeaders = [ + "x-datadog-sampling-priority": "0", + ] + XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders) + } + func testItExtractsSpanContextWithHTTPHeadersReader() { let tracer: Tracer = .mockAny() let injectedSpanContext = DDSpanContext(traceID: 1, spanID: 2, parentSpanID: .mockAny(), baggageItems: .mockAny()) diff --git a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift index 94db9ad04b..785c2d6f92 100644 --- a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift +++ b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift @@ -99,20 +99,26 @@ class URLSessionInterceptorTests: XCTestCase { private func mockConfiguration( tracingInstrumentationEnabled: Bool, - rumInstrumentationEnabled: Bool + rumInstrumentationEnabled: Bool, + samplingRate: Float = 100 ) -> FeaturesConfiguration.URLSessionAutoInstrumentation { return .mockWith( userDefinedFirstPartyHosts: ["first-party.com"], sdkInternalURLs: ["https://dd.internal.com"], instrumentTracing: tracingInstrumentationEnabled, - instrumentRUM: rumInstrumentationEnabled + instrumentRUM: rumInstrumentationEnabled, + samplingRate: samplingRate ) } func testGivenTracingAndRUMInstrumentationEnabled_whenInterceptingRequests_itInjectsTracingContextToFirstPartyRequests() throws { // Given let interceptor = URLSessionInterceptor( - configuration: mockConfiguration(tracingInstrumentationEnabled: true, rumInstrumentationEnabled: true), + configuration: mockConfiguration( + tracingInstrumentationEnabled: true, + rumInstrumentationEnabled: true, + samplingRate: 100 + ), handler: handler ) Global.sharedTracer = Tracer.mockAny() @@ -133,30 +139,30 @@ class URLSessionInterceptorTests: XCTestCase { // Then XCTAssertNotNil(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.traceIDField]) XCTAssertNotNil(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.parentSpanIDField]) + XCTAssertEqual(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.samplingPriorityField], "1") XCTAssertEqual(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.ddOrigin.field], TracingHTTPHeaders.ddOrigin.value) assertRequestsEqual( interceptedFirstPartyRequest .removing(httpHeaderField: TracingHTTPHeaders.traceIDField) .removing(httpHeaderField: TracingHTTPHeaders.parentSpanIDField) - .removing(httpHeaderField: TracingHTTPHeaders.ddSamplingPriority.field) - .removing(httpHeaderField: TracingHTTPHeaders.ddSampled.field) + .removing(httpHeaderField: TracingHTTPHeaders.samplingPriorityField) .removing(httpHeaderField: TracingHTTPHeaders.ddOrigin.field), firstPartyRequest, - "The only modification of the original requests should be the addition of 5 tracing headers." + "The only modification of the original requests should be the addition of 4 tracing headers." ) XCTAssertNotNil(interceptedCustomFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.traceIDField]) XCTAssertNotNil(interceptedCustomFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.parentSpanIDField]) + XCTAssertEqual(interceptedCustomFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.samplingPriorityField], "1") XCTAssertEqual(interceptedCustomFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.ddOrigin.field], TracingHTTPHeaders.ddOrigin.value) assertRequestsEqual( interceptedCustomFirstPartyRequest .removing(httpHeaderField: TracingHTTPHeaders.traceIDField) .removing(httpHeaderField: TracingHTTPHeaders.parentSpanIDField) - .removing(httpHeaderField: TracingHTTPHeaders.ddSamplingPriority.field) - .removing(httpHeaderField: TracingHTTPHeaders.ddSampled.field) + .removing(httpHeaderField: TracingHTTPHeaders.samplingPriorityField) .removing(httpHeaderField: TracingHTTPHeaders.ddOrigin.field), alternativeFirstPartyRequest, - "The only modification of the original requests should be the addition of 5 tracing headers." + "The only modification of the original requests should be the addition of 4 tracing headers." ) assertRequestsEqual(thirdPartyRequest, interceptedThirdPartyRequest, "Intercepted 3rd party request should not be modified.") @@ -166,7 +172,11 @@ class URLSessionInterceptorTests: XCTestCase { func testGivenOnlyTracingInstrumentationEnabled_whenInterceptingRequests_itInjectsTracingContextToFirstPartyRequests() throws { // Given let interceptor = URLSessionInterceptor( - configuration: mockConfiguration(tracingInstrumentationEnabled: true, rumInstrumentationEnabled: false), + configuration: mockConfiguration( + tracingInstrumentationEnabled: true, + rumInstrumentationEnabled: false, + samplingRate: 100 + ), handler: handler ) Global.sharedTracer = Tracer.mockAny() @@ -180,13 +190,13 @@ class URLSessionInterceptorTests: XCTestCase { // Then XCTAssertNotNil(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.traceIDField]) XCTAssertNotNil(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.parentSpanIDField]) + XCTAssertEqual(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.samplingPriorityField], "1") XCTAssertNil(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.ddOrigin.field], "Origin header should not be added if RUM is disabled.") assertRequestsEqual( interceptedFirstPartyRequest .removing(httpHeaderField: TracingHTTPHeaders.traceIDField) .removing(httpHeaderField: TracingHTTPHeaders.parentSpanIDField) - .removing(httpHeaderField: TracingHTTPHeaders.ddSamplingPriority.field) - .removing(httpHeaderField: TracingHTTPHeaders.ddSampled.field), + .removing(httpHeaderField: TracingHTTPHeaders.samplingPriorityField), firstPartyRequest, "The only modification of the original requests should be the addition of 4 tracing headers." ) @@ -194,6 +204,38 @@ class URLSessionInterceptorTests: XCTestCase { assertRequestsEqual(internalRequest, interceptedInternalRequest, "Intercepted internal request should not be modified.") } + func testGivenTracingInstrumentationEnabled_whenInterceptingRequests_itInjectsSampledOutTracingContextToFirstPartyRequests() throws { + // Given + let interceptor = URLSessionInterceptor( + configuration: mockConfiguration( + tracingInstrumentationEnabled: true, + rumInstrumentationEnabled: false, + samplingRate: 0 + ), + handler: handler + ) + Global.sharedTracer = Tracer.mockAny() + defer { Global.sharedTracer = DDNoopGlobals.tracer } + + // When + let interceptedFirstPartyRequest = interceptor.modify(request: firstPartyRequest) + let interceptedThirdPartyRequest = interceptor.modify(request: thirdPartyRequest) + let interceptedInternalRequest = interceptor.modify(request: internalRequest) + + // Then + XCTAssertNil(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.traceIDField]) + XCTAssertNil(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.parentSpanIDField]) + XCTAssertEqual(interceptedFirstPartyRequest.allHTTPHeaderFields?[TracingHTTPHeaders.samplingPriorityField], "0") + assertRequestsEqual( + interceptedFirstPartyRequest + .removing(httpHeaderField: TracingHTTPHeaders.samplingPriorityField), + firstPartyRequest, + "The only modification of the original requests should be the addition of x-datadog-sampling-priority tracing headers." + ) + assertRequestsEqual(thirdPartyRequest, interceptedThirdPartyRequest, "Intercepted 3rd party request should not be modified.") + assertRequestsEqual(internalRequest, interceptedInternalRequest, "Intercepted internal request should not be modified.") + } + func testGivenOnlyRUMInstrumentationEnabled_whenInterceptingRequests_itDoesNotModifyThem() throws { // Given let interceptor = URLSessionInterceptor( diff --git a/Tests/DatadogTests/DatadogObjc/DDConfigurationTests.swift b/Tests/DatadogTests/DatadogObjc/DDConfigurationTests.swift index 0b17e03bec..3ef9a63a84 100644 --- a/Tests/DatadogTests/DatadogObjc/DDConfigurationTests.swift +++ b/Tests/DatadogTests/DatadogObjc/DDConfigurationTests.swift @@ -134,6 +134,9 @@ class DDConfigurationTests: XCTestCase { objcBuilder.trackURLSession(firstPartyHosts: ["example.com"]) XCTAssertEqual(objcBuilder.build().sdkConfiguration.firstPartyHosts, ["example.com"]) + objcBuilder.set(tracingSamplingRate: 75) + XCTAssertEqual(objcBuilder.build().sdkConfiguration.tracingSamplingRate, 75) + objcBuilder.trackUIKitRUMActions() XCTAssertTrue(objcBuilder.build().sdkConfiguration.rumUIKitUserActionsPredicate is DefaultUIKitRUMUserActionsPredicate) diff --git a/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift b/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift index cd53ef95e5..f12ac9c26a 100644 --- a/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift +++ b/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift @@ -236,7 +236,21 @@ class DDTracerTests: XCTestCase { "x-datadog-trace-id": "1", "x-datadog-parent-id": "2", "x-datadog-sampling-priority": "1", - "x-datadog-sampled": "1", + ] + XCTAssertEqual(objcWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders) + } + + func testInjectingRejectedSpanContextToValidCarrierAndFormat() throws { + let objcTracer = DDTracer(swiftTracer: Tracer.mockAny()) + let objcSpanContext = DDSpanContextObjc( + swiftSpanContext: DDSpanContext.mockWith(traceID: 1, spanID: 2) + ) + + let objcWriter = DDHTTPHeadersWriter(samplingRate: 0) + try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) + + let expectedHTTPHeaders = [ + "x-datadog-sampling-priority": "0", ] XCTAssertEqual(objcWriter.tracePropagationHTTPHeaders, expectedHTTPHeaders) } From 5c9fa6f7795b9fec34bdcfc726b2df5b72094258 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Thu, 12 May 2022 11:32:44 +0200 Subject: [PATCH 39/55] RUMM-2166 Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55f619e501..9d70580ec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Changes +* [IMPROVEMENT] Add tracing sampling rate. See [#851][] + # 1.11.0-beta2 / 05-04-2022 ### Changes @@ -358,6 +360,7 @@ [#815]: https://github.com/DataDog/dd-sdk-ios/issues/815 [#830]: https://github.com/DataDog/dd-sdk-ios/issues/830 [#832]: https://github.com/DataDog/dd-sdk-ios/issues/832 +[#851]: https://github.com/DataDog/dd-sdk-ios/issues/851 [@00FA9A]: https://github.com/00FA9A [@Britton-Earnin]: https://github.com/Britton-Earnin [@Hengyu]: https://github.com/Hengyu From cc59c8d6f85da8a3d04ea6e02f86b3ba9aaa2785 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 13 May 2022 14:41:41 +0200 Subject: [PATCH 40/55] RUMM-2166 Sample out placeholder trace in URLSessionTracingHandler --- .../Datadog/Core/FeaturesConfiguration.swift | 4 +-- Sources/Datadog/DatadogConfiguration.swift | 4 +-- .../URLSessionTracingHandler.swift | 9 ++++-- .../Propagation/HTTPHeadersWriter.swift | 20 ++++++++---- .../Interception/URLSessionInterceptor.swift | 13 +++++--- .../Datadog/Mocks/CoreMocks.swift | 4 +-- .../URLSessionTracingHandlerTests.swift | 32 ++++++++++++++++++- .../URLSessionInterceptorTests.swift | 10 +++--- .../ObjcAPITests/DDConfiguration+apiTests.m | 1 + 9 files changed, 71 insertions(+), 26 deletions(-) diff --git a/Sources/Datadog/Core/FeaturesConfiguration.swift b/Sources/Datadog/Core/FeaturesConfiguration.swift index 143092d30c..52ac949086 100644 --- a/Sources/Datadog/Core/FeaturesConfiguration.swift +++ b/Sources/Datadog/Core/FeaturesConfiguration.swift @@ -76,7 +76,7 @@ internal struct FeaturesConfiguration { /// If the RUM instrumentation should be enabled. let instrumentRUM: Bool // Tracing sampler - let samplingRate: Float + let tracingSampler: Sampler } struct CrashReporting { @@ -249,7 +249,7 @@ extension FeaturesConfiguration { rumAttributesProvider: configuration.rumResourceAttributesProvider, instrumentTracing: configuration.tracingEnabled, instrumentRUM: configuration.rumEnabled, - samplingRate: debugOverride ? 100.0 : configuration.tracingSamplingRate + tracingSampler: Sampler(samplingRate: debugOverride ? 100.0 : configuration.tracingSamplingRate) ) } else { let error = ProgrammerError( diff --git a/Sources/Datadog/DatadogConfiguration.swift b/Sources/Datadog/DatadogConfiguration.swift index 4f747a502e..9a05392fcb 100644 --- a/Sources/Datadog/DatadogConfiguration.swift +++ b/Sources/Datadog/DatadogConfiguration.swift @@ -502,9 +502,9 @@ extension Datadog { return self } - /// Sets the sampling rate for APM tracing. + /// Sets the sampling rate for APM traces created for auto-instrumented `URLSession` requests. /// - /// - Parameter apmTraceSamplingRate: the sampling rate must be a value between `0.0` and `100.0`. A value of `0.0` + /// - Parameter tracingSamplingRate: the sampling rate must be a value between `0.0` and `100.0`. A value of `0.0` /// means no trace will be kept, `100.0` means all traces will be kept (default value is `100.0`). public func set(tracingSamplingRate: Float) -> Builder { configuration.tracingSamplingRate = tracingSamplingRate diff --git a/Sources/Datadog/Tracing/AutoInstrumentation/URLSessionTracingHandler.swift b/Sources/Datadog/Tracing/AutoInstrumentation/URLSessionTracingHandler.swift index f656f3e5b7..4273024647 100644 --- a/Sources/Datadog/Tracing/AutoInstrumentation/URLSessionTracingHandler.swift +++ b/Sources/Datadog/Tracing/AutoInstrumentation/URLSessionTracingHandler.swift @@ -9,9 +9,12 @@ import Foundation internal class URLSessionTracingHandler: URLSessionInterceptionHandler { /// Listening to app state changes and use it to report `foreground_duration` let appStateListener: AppStateListening + /// The Tracing sampler. + let tracingSampler: Sampler - init(appStateListener: AppStateListening) { + init(appStateListener: AppStateListening, tracingSampler: Sampler) { self.appStateListener = appStateListener + self.tracingSampler = tracingSampler } // MARK: - URLSessionInterceptionHandler @@ -46,13 +49,15 @@ internal class URLSessionTracingHandler: URLSessionInterceptionHandler { operationName: "urlsession.request", startTime: resourceMetrics.fetch.start ) - } else { + } else if tracingSampler.sample() { // Span context may not be injected on iOS13+ if `URLSession.dataTask(...)` for `URL` // was used to create the session task. span = tracer.startSpan( operationName: "urlsession.request", startTime: resourceMetrics.fetch.start ) + } else { + return } let url = interception.request.url?.absoluteString ?? "unknown_url" diff --git a/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift b/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift index 2386ca8bca..10b6f0a037 100644 --- a/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift +++ b/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift @@ -36,19 +36,26 @@ public class HTTPHeadersWriter: OTHTTPHeadersWriter { /// public private(set) var tracePropagationHTTPHeaders: [String: String] = [:] - /// The tracing sampling rate. + /// The tracing sampler. /// - /// This value will decide of the `x-datadog-sampling-priority` - /// header field value and if `x-datadog-trace-id` and `x-datadog-parent-id` - /// are propagated. - private let samplingRate: Float + /// This value will decide of the `x-datadog-sampling-priority` header field value + /// and if `x-datadog-trace-id` and `x-datadog-parent-id` are propagated. + private let sampler: Sampler /// Creates a `HTTPHeadersWriter` to inject traces propagation headers /// to network request. /// /// - Parameter samplingRate: Tracing sampling rate. 100% (keep all) by default. public init(samplingRate: Float = 100) { - self.samplingRate = samplingRate + self.sampler = Sampler(samplingRate: samplingRate) + } + + /// Creates a `HTTPHeadersWriter` to inject traces propagation headers + /// to network request. + /// + /// - Parameter samplingRate: Tracing sampling rate. 100% (keep all) by default. + internal init(sampler: Sampler) { + self.sampler = sampler } public func inject(spanContext: OTSpanContext) { @@ -56,7 +63,6 @@ public class HTTPHeadersWriter: OTHTTPHeadersWriter { return } - let sampler = Sampler(samplingRate: samplingRate) let samplingPriority = sampler.sample() tracePropagationHTTPHeaders = [ diff --git a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift index ecc900e5fb..642e930e3b 100644 --- a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift +++ b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptor.swift @@ -44,8 +44,8 @@ public class URLSessionInterceptor: URLSessionInterceptorType { /// Additional header injected to intercepted 1st party requests. /// Set to `x-datadog-origin: rum` if both RUM and Tracing instrumentations are enabled and `nil` in all other cases. internal let additionalHeadersForFirstPartyRequests: [String: String]? - /// Tracing sampling rate - internal let samplingRate: Float + /// Tracing sampler + internal let tracingSampler: Sampler // MARK: - Initialization @@ -62,7 +62,10 @@ public class URLSessionInterceptor: URLSessionInterceptorType { rumAttributesProvider: configuration.rumAttributesProvider ) } else { - handler = URLSessionTracingHandler(appStateListener: appStateListener) + handler = URLSessionTracingHandler( + appStateListener: appStateListener, + tracingSampler: configuration.tracingSampler + ) } self.init(configuration: configuration, handler: handler) @@ -75,7 +78,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType { self.defaultFirstPartyURLsFilter = FirstPartyURLsFilter(hosts: configuration.userDefinedFirstPartyHosts) self.internalURLsFilter = InternalURLsFilter(urls: configuration.sdkInternalURLs) self.handler = handler - self.samplingRate = configuration.samplingRate + self.tracingSampler = configuration.tracingSampler if configuration.instrumentTracing { self.injectTracingHeadersToFirstPartyRequests = true @@ -232,7 +235,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType { return firstPartyRequest } - let writer = HTTPHeadersWriter(samplingRate: samplingRate) + let writer = HTTPHeadersWriter(sampler: tracingSampler) let spanContext = tracer.createSpanContext() tracer.inject(spanContext: spanContext, writer: writer) diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 1d083711bf..cec0c595dd 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -314,7 +314,7 @@ extension FeaturesConfiguration.URLSessionAutoInstrumentation { rumAttributesProvider: URLSessionRUMAttributesProvider? = nil, instrumentTracing: Bool = true, instrumentRUM: Bool = true, - samplingRate: Float = 100 + tracingSampler: Sampler = .mockKeepAll() ) -> Self { return .init( userDefinedFirstPartyHosts: userDefinedFirstPartyHosts, @@ -322,7 +322,7 @@ extension FeaturesConfiguration.URLSessionAutoInstrumentation { rumAttributesProvider: rumAttributesProvider, instrumentTracing: instrumentTracing, instrumentRUM: instrumentRUM, - samplingRate: samplingRate + tracingSampler: tracingSampler ) } } diff --git a/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/URLSessionTracingHandlerTests.swift b/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/URLSessionTracingHandlerTests.swift index 4f89720a2a..dd08f3c15d 100644 --- a/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/URLSessionTracingHandlerTests.swift +++ b/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/URLSessionTracingHandlerTests.swift @@ -16,7 +16,8 @@ class URLSessionTracingHandlerTests: XCTestCase { initialSnapshot: .init(state: .active, date: .mockDecember15th2019At10AMUTC()), recentDate: .mockDecember15th2019At10AMUTC() + 10 ) - ) + ), + tracingSampler: .mockKeepAll() ) override func setUp() { @@ -302,4 +303,33 @@ class URLSessionTracingHandlerTests: XCTestCase { XCTAssertEqual(recordedSpan.tags[DDTags.foregroundDuration], "10000000000") XCTAssertEqual(recordedSpan.tags[DDTags.isBackground], "false") } + + func testGivenRejectingHandler_itDoesNotRecordSpan() throws { + let spanNotSentExpectation = expectation(description: "Don't send span") + spanNotSentExpectation.isInverted = true + spanOutput.onSpanRecorded = { _ in spanNotSentExpectation.fulfill() } + + // Given + let handler = URLSessionTracingHandler( + appStateListener: AppStateListenerMock.mockAppInForeground(), + tracingSampler: .mockRejectAll() + ) + + let interception = TaskInterception(request: .mockAny(), isFirstParty: true) + interception.register(completion: .mockAny()) + interception.register( + metrics: .mockWith( + fetch: .init( + start: .mockDecember15th2019At10AMUTC(), + end: .mockDecember15th2019At10AMUTC(addingTimeInterval: 10) + ) + ) + ) + + // When + handler.notify_taskInterceptionCompleted(interception: interception) + + // Then + wait(for: [spanNotSentExpectation], timeout: 0.5) + } } diff --git a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift index 785c2d6f92..cf2126247a 100644 --- a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift +++ b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift @@ -100,14 +100,14 @@ class URLSessionInterceptorTests: XCTestCase { private func mockConfiguration( tracingInstrumentationEnabled: Bool, rumInstrumentationEnabled: Bool, - samplingRate: Float = 100 + tracingSampler: Sampler = .mockKeepAll() ) -> FeaturesConfiguration.URLSessionAutoInstrumentation { return .mockWith( userDefinedFirstPartyHosts: ["first-party.com"], sdkInternalURLs: ["https://dd.internal.com"], instrumentTracing: tracingInstrumentationEnabled, instrumentRUM: rumInstrumentationEnabled, - samplingRate: samplingRate + tracingSampler: tracingSampler ) } @@ -117,7 +117,7 @@ class URLSessionInterceptorTests: XCTestCase { configuration: mockConfiguration( tracingInstrumentationEnabled: true, rumInstrumentationEnabled: true, - samplingRate: 100 + tracingSampler: .mockKeepAll() ), handler: handler ) @@ -175,7 +175,7 @@ class URLSessionInterceptorTests: XCTestCase { configuration: mockConfiguration( tracingInstrumentationEnabled: true, rumInstrumentationEnabled: false, - samplingRate: 100 + tracingSampler: .mockKeepAll() ), handler: handler ) @@ -210,7 +210,7 @@ class URLSessionInterceptorTests: XCTestCase { configuration: mockConfiguration( tracingInstrumentationEnabled: true, rumInstrumentationEnabled: false, - samplingRate: 0 + tracingSampler: .mockRejectAll() ), handler: handler ) diff --git a/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDConfiguration+apiTests.m b/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDConfiguration+apiTests.m index ae0a88851f..86e655da4e 100644 --- a/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDConfiguration+apiTests.m +++ b/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDConfiguration+apiTests.m @@ -100,6 +100,7 @@ - (void)testDDConfigurationBuilderAPI { [builder setWithCustomTracesEndpoint:[NSURL new]]; [builder setWithCustomRUMEndpoint:[NSURL new]]; [builder trackURLSessionWithFirstPartyHosts:[NSSet setWithArray:@[]]]; + [builder setWithTracingSamplingRate:75]; [builder setWithServiceName:@""]; [builder setWithRumSessionsSamplingRate:50]; [builder setOnRUMSessionStart:^(NSString * _Nonnull sessionId, BOOL isDiscarded) {}]; From 99aefa9379e5d503bef9b6ca407e20293ad820e1 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 13 May 2022 14:42:23 +0200 Subject: [PATCH 41/55] RUMM-2166 Test DDHTTPHeadersWriter objc api --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 +++++ .../DDHTTPHeadersWriter+apiTests.m | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDHTTPHeadersWriter+apiTests.m diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index cd352e6dad..1ab6541d60 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -592,6 +592,8 @@ D28D5D5527C54B30008E72D0 /* DatadogCrashReporting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2CB6FD127C5348200A62B57 /* DatadogCrashReporting.framework */; }; D29889C9273413ED00A4D1A9 /* RUMViewsHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29889C72734136200A4D1A9 /* RUMViewsHandlerTests.swift */; }; D29D5A4D273BF8B400A687C1 /* SwiftUIActionModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29D5A4C273BF8B400A687C1 /* SwiftUIActionModifier.swift */; }; + D2B3F052282E827700C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; + D2B3F053282E827B00C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; D2CB6E0C27C50EAE00A62B57 /* Datadog.h in Headers */ = {isa = PBXBuildFile; fileRef = 61133B85242393DE00786299 /* Datadog.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2CB6E0D27C50EAE00A62B57 /* ObjcAppLaunchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 6179FFD1254ADB1100556A0B /* ObjcAppLaunchHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2CB6E0E27C50EAE00A62B57 /* ObjcExceptionHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E68FB54244707FD0013A8AA /* ObjcExceptionHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1764,6 +1766,7 @@ D2791EF827170A760046E07A /* RUMSwiftUIScenarioTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMSwiftUIScenarioTests.swift; sourceTree = ""; }; D29889C72734136200A4D1A9 /* RUMViewsHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewsHandlerTests.swift; sourceTree = ""; }; D29D5A4C273BF8B400A687C1 /* SwiftUIActionModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIActionModifier.swift; sourceTree = ""; }; + D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDHTTPHeadersWriter+apiTests.m"; sourceTree = ""; }; D2CB6ED127C50EAE00A62B57 /* Datadog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Datadog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D2CB6F8F27C520D400A62B57 /* DatadogTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DatadogTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D2CB6FB027C5217A00A62B57 /* DatadogObjc.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogObjc.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3433,6 +3436,7 @@ 61B5E42026DF85C7000B0A5F /* DDRUMMonitor+apiTests.m */, 61B5E42A26DFC433000B0A5F /* DDNSURLSessionDelegate+apiTests.m */, 61B5E42426DFAFBC000B0A5F /* DDGlobal+apiTests.m */, + D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */, ); path = ObjcAPITests; sourceTree = ""; @@ -5320,6 +5324,7 @@ 61E5332F24B75DE2003D6C4E /* RUMFeatureTests.swift in Sources */, E1D203FD24C1885C00D1AF3A /* ActiveSpansPoolTests.swift in Sources */, 6149FB412529DEBD00EE387A /* InternalURLsFilterTests.swift in Sources */, + D2B3F052282E827700C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */, 61133C562423990D00786299 /* CarrierInfoProviderTests.swift in Sources */, 618DCFDF24C75FD300589570 /* RUMScopeTests.swift in Sources */, 61C5A89E24509C1100DA608C /* WarningsTests.swift in Sources */, @@ -5940,6 +5945,7 @@ D2CB6F5427C520D400A62B57 /* RUMFeatureTests.swift in Sources */, D2CB6F5527C520D400A62B57 /* ActiveSpansPoolTests.swift in Sources */, D2CB6F5627C520D400A62B57 /* InternalURLsFilterTests.swift in Sources */, + D2B3F053282E827B00C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */, D2CB6F5727C520D400A62B57 /* CarrierInfoProviderTests.swift in Sources */, D2CB6F5827C520D400A62B57 /* RUMScopeTests.swift in Sources */, D2CB6F5927C520D400A62B57 /* WarningsTests.swift in Sources */, diff --git a/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDHTTPHeadersWriter+apiTests.m b/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDHTTPHeadersWriter+apiTests.m new file mode 100644 index 0000000000..f78e47ebc4 --- /dev/null +++ b/Tests/DatadogTests/DatadogObjc/ObjcAPITests/DDHTTPHeadersWriter+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-2020 Datadog, Inc. +*/ + +#import +@import DatadogObjc; + +@interface DDHTTPHeadersWriter_apiTests : XCTestCase +@end + +/* + * `DatadogObjc` APIs smoke tests - only check if the interface is available to Objc. + */ +@implementation DDHTTPHeadersWriter_apiTests + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-value" + +- (void)testInitWithSamplingRate { + [[DDHTTPHeadersWriter alloc] initWithSamplingRate:50]; +} + +#pragma clang diagnostic pop + +@end From 980c3a4490d668516bc24c987c889d0bb5cd1482 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 13 May 2022 15:16:31 +0200 Subject: [PATCH 42/55] RUMM-2166 Set default tracing sampling to 20% --- Datadog/Example/AppConfiguration.swift | 1 + Sources/Datadog/DatadogConfiguration.swift | 4 ++-- Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift | 4 ++-- .../Datadog/DatadogConfigurationBuilderTests.swift | 2 +- Tests/DatadogTests/Datadog/TracerTests.swift | 6 +++--- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Datadog/Example/AppConfiguration.swift b/Datadog/Example/AppConfiguration.swift index 837e7c9d55..a7362b8e9e 100644 --- a/Datadog/Example/AppConfiguration.swift +++ b/Datadog/Example/AppConfiguration.swift @@ -116,6 +116,7 @@ struct UITestsAppConfiguration: AppConfiguration { .set(serviceName: "ui-tests-service-name") .set(batchSize: .small) .set(uploadFrequency: .frequent) + .set(tracingSamplingRate: 100) let serverMockConfiguration = Environment.serverMockConfiguration() diff --git a/Sources/Datadog/DatadogConfiguration.swift b/Sources/Datadog/DatadogConfiguration.swift index 9a05392fcb..657294787b 100644 --- a/Sources/Datadog/DatadogConfiguration.swift +++ b/Sources/Datadog/DatadogConfiguration.swift @@ -329,7 +329,7 @@ extension Datadog { serviceName: nil, firstPartyHosts: nil, spanEventMapper: nil, - tracingSamplingRate: 100.0, + tracingSamplingRate: 20.0, rumSessionsSamplingRate: 100.0, rumSessionsListener: nil, rumUIKitViewsPredicate: nil, @@ -505,7 +505,7 @@ extension Datadog { /// Sets the sampling rate for APM traces created for auto-instrumented `URLSession` requests. /// /// - Parameter tracingSamplingRate: the sampling rate must be a value between `0.0` and `100.0`. A value of `0.0` - /// means no trace will be kept, `100.0` means all traces will be kept (default value is `100.0`). + /// means no trace will be kept, `100.0` means all traces will be kept (default value is `20.0`). public func set(tracingSamplingRate: Float) -> Builder { configuration.tracingSamplingRate = tracingSamplingRate return self diff --git a/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift b/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift index 10b6f0a037..726f7c3c31 100644 --- a/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift +++ b/Sources/Datadog/Tracing/Propagation/HTTPHeadersWriter.swift @@ -45,8 +45,8 @@ public class HTTPHeadersWriter: OTHTTPHeadersWriter { /// Creates a `HTTPHeadersWriter` to inject traces propagation headers /// to network request. /// - /// - Parameter samplingRate: Tracing sampling rate. 100% (keep all) by default. - public init(samplingRate: Float = 100) { + /// - Parameter samplingRate: Tracing sampling rate. 20% by default. + public init(samplingRate: Float = 20) { self.sampler = Sampler(samplingRate: samplingRate) } diff --git a/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift b/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift index 7e171a8653..4bb07eb7dc 100644 --- a/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift +++ b/Tests/DatadogTests/Datadog/DatadogConfigurationBuilderTests.swift @@ -45,7 +45,7 @@ class DatadogConfigurationBuilderTests: XCTestCase { XCTAssertNil(configuration.firstPartyHosts) XCTAssertNil(configuration.logEventMapper) XCTAssertNil(configuration.spanEventMapper) - XCTAssertEqual(configuration.tracingSamplingRate, 100.0) + XCTAssertEqual(configuration.tracingSamplingRate, 20.0) XCTAssertEqual(configuration.rumSessionsSamplingRate, 100.0) XCTAssertNil(configuration.rumSessionsListener) XCTAssertNil(configuration.rumUIKitViewsPredicate) diff --git a/Tests/DatadogTests/Datadog/TracerTests.swift b/Tests/DatadogTests/Datadog/TracerTests.swift index 2e2bc67d6c..c8f3378933 100644 --- a/Tests/DatadogTests/Datadog/TracerTests.swift +++ b/Tests/DatadogTests/Datadog/TracerTests.swift @@ -827,7 +827,7 @@ class TracerTests: XCTestCase { let spanContext1 = DDSpanContext(traceID: 1, spanID: 2, parentSpanID: .mockAny(), baggageItems: .mockAny()) let spanContext2 = DDSpanContext(traceID: 3, spanID: 4, parentSpanID: .mockAny(), baggageItems: .mockAny()) - let httpHeadersWriter = HTTPHeadersWriter() + let httpHeadersWriter = HTTPHeadersWriter(sampler: .mockKeepAll()) XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, [:]) // When @@ -857,7 +857,7 @@ class TracerTests: XCTestCase { let tracer: Tracer = .mockAny() let spanContext = DDSpanContext(traceID: 1, spanID: 2, parentSpanID: .mockAny(), baggageItems: .mockAny()) - let httpHeadersWriter = HTTPHeadersWriter(samplingRate: 0) + let httpHeadersWriter = HTTPHeadersWriter(sampler: .mockRejectAll()) XCTAssertEqual(httpHeadersWriter.tracePropagationHTTPHeaders, [:]) // When @@ -874,7 +874,7 @@ class TracerTests: XCTestCase { let tracer: Tracer = .mockAny() let injectedSpanContext = DDSpanContext(traceID: 1, spanID: 2, parentSpanID: .mockAny(), baggageItems: .mockAny()) - let httpHeadersWriter = HTTPHeadersWriter() + let httpHeadersWriter = HTTPHeadersWriter(sampler: .mockKeepAll()) tracer.inject(spanContext: injectedSpanContext, writer: httpHeadersWriter) let httpHeadersReader = HTTPHeadersReader( From 5b7c435f9a2c82dc0e0d9703b15cb48038dd6069 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 13 May 2022 15:37:03 +0200 Subject: [PATCH 43/55] RUMM-2166 Update documentation --- docs/rum_collection/advanced_configuration.md | 4 +++- docs/trace_collection.md | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/rum_collection/advanced_configuration.md b/docs/rum_collection/advanced_configuration.md index 3813840b9a..917c008cfc 100644 --- a/docs/rum_collection/advanced_configuration.md +++ b/docs/rum_collection/advanced_configuration.md @@ -431,7 +431,7 @@ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConf {{% /tab %}} {{< /tabs >}} -Also, you can configure first party hosts using `.trackURLSession(firstPartyHosts:)`. This classifies resources that match the given domain as "first party" in RUM and propagates tracing information to your backend (if you have enabled Tracing). +Also, you can configure first party hosts using `.trackURLSession(firstPartyHosts:)`. This classifies resources that match the given domain as "first party" in RUM and propagates tracing information to your backend (if you have enabled Tracing). Network traces are sampled with an adjustable sampling rate, a sampling of 20% is applied by default. For instance, you can configure `example.com` as the first party host and enable both RUM and Tracing features: @@ -444,6 +444,7 @@ Datadog.initialize( .builderUsing(/* ... */) .trackUIKitRUMViews() .trackURLSession(firstPartyHosts: ["example.com"]) + .set(tracingSamplingRate: 20) .build() ) @@ -469,6 +470,7 @@ DDConfigurationBuilder *builder = [DDConfiguration builderWithRumApplicationID:@ // ... [builder trackUIKitRUMViews]; [builder trackURLSessionWithFirstPartyHosts:[NSSet setWithArray:@[@"example.com"]]]; +[builder setWithTracingSamplingRate:20]; DDGlobal.rum = [[DDRUMMonitor alloc] init]; DDGlobal.sharedTracer = [[DDTracer alloc] initWithConfiguration:[DDTracerConfiguration new]]; diff --git a/docs/trace_collection.md b/docs/trace_collection.md index 9c239c6ebe..faccd135a3 100644 --- a/docs/trace_collection.md +++ b/docs/trace_collection.md @@ -335,7 +335,7 @@ span.log( {{% /tab %}} {{< /tabs >}} -8. (Optional) To distribute traces between your environments, for example frontend - backend, you can either do it manually or leverage our auto instrumentation. +8. (Optional) To distribute traces between your environments, for example frontend - backend, you can either do it manually or leverage our auto instrumentation. In both cases, network traces are sampled with an adjustable sampling rate, a sampling of 20% is applied by default. * To manually propagate the trace, inject the span context into `URLRequest` headers: @@ -346,7 +346,7 @@ var request: URLRequest = ... // the request to your API let span = Global.sharedTracer.startSpan(operationName: "network request") -let headersWriter = HTTPHeadersWriter() +let headersWriter = HTTPHeadersWriter(samplingRate: 20) Global.sharedTracer.inject(spanContext: span.context, writer: headersWriter) for (headerField, value) in headersWriter.tracePropagationHTTPHeaders { @@ -357,7 +357,7 @@ for (headerField, value) in headersWriter.tracePropagationHTTPHeaders { {{% tab "Objective-C" %}} ```objective-c id span = [DDGlobal.sharedTracer startSpan:@"network request"]; -DDHTTPHeadersWriter *headersWriter = [[DDHTTPHeadersWriter alloc] init]; +DDHTTPHeadersWriter *headersWriter = [[DDHTTPHeadersWriter alloc] initWithSamplingRate:20]; NSError *error = nil; [DDGlobal.sharedTracer inject:span.context @@ -385,6 +385,7 @@ Datadog.initialize( configuration: Datadog.Configuration .builderUsing(clientToken: "", environment: "") .trackURLSession(firstPartyHosts: ["example.com", "api.yourdomain.com"]) + .set(tracingSamplingRate: 20) .build() ) @@ -401,6 +402,7 @@ DDConfigurationBuilder *builder = [DDConfiguration builderWithClientToken:@""]; [builder trackURLSessionWithFirstPartyHosts:[NSSet setWithArray:@[@"example.com", @"api.yourdomain.com"]]]; +[builder setWithTracingSamplingRate:20]; [DDDatadog initializeWithAppContext:[DDAppContext new] trackingConsent:trackingConsent From 7366761642a2f09704b310ce99e96c4766be19fa Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 16 May 2022 09:32:08 +0200 Subject: [PATCH 44/55] RUMM-2166 Apply suggestions from doc review Co-authored-by: cswatt --- docs/rum_collection/advanced_configuration.md | 2 +- docs/trace_collection.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rum_collection/advanced_configuration.md b/docs/rum_collection/advanced_configuration.md index 917c008cfc..58c8c7ba82 100644 --- a/docs/rum_collection/advanced_configuration.md +++ b/docs/rum_collection/advanced_configuration.md @@ -431,7 +431,7 @@ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConf {{% /tab %}} {{< /tabs >}} -Also, you can configure first party hosts using `.trackURLSession(firstPartyHosts:)`. This classifies resources that match the given domain as "first party" in RUM and propagates tracing information to your backend (if you have enabled Tracing). Network traces are sampled with an adjustable sampling rate, a sampling of 20% is applied by default. +Also, you can configure first party hosts using `.trackURLSession(firstPartyHosts:)`. This classifies resources that match the given domain as "first party" in RUM and propagates tracing information to your backend (if you have enabled Tracing). Network traces are sampled with an adjustable sampling rate. A sampling of 20% is applied by default. For instance, you can configure `example.com` as the first party host and enable both RUM and Tracing features: diff --git a/docs/trace_collection.md b/docs/trace_collection.md index faccd135a3..e70d4d99dc 100644 --- a/docs/trace_collection.md +++ b/docs/trace_collection.md @@ -335,7 +335,7 @@ span.log( {{% /tab %}} {{< /tabs >}} -8. (Optional) To distribute traces between your environments, for example frontend - backend, you can either do it manually or leverage our auto instrumentation. In both cases, network traces are sampled with an adjustable sampling rate, a sampling of 20% is applied by default. +8. (Optional) To distribute traces between your environments, for example frontend - backend, you can either do it manually or leverage our auto instrumentation. In both cases, network traces are sampled with an adjustable sampling rate. A sampling of 20% is applied by default. * To manually propagate the trace, inject the span context into `URLRequest` headers: From f8b9787c4ad12cb26b0ce85f51e83625cd48a7eb Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 16 May 2022 13:47:38 +0200 Subject: [PATCH 45/55] RUMM-2166 Fix objc default racing sampling rate --- .../Tracing/Propagation/HTTPHeadersWriter+objc.swift | 2 +- Tests/DatadogTests/DatadogObjc/DDTracerTests.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/DatadogObjc/Tracing/Propagation/HTTPHeadersWriter+objc.swift b/Sources/DatadogObjc/Tracing/Propagation/HTTPHeadersWriter+objc.swift index a82b407f14..a48f942665 100644 --- a/Sources/DatadogObjc/Tracing/Propagation/HTTPHeadersWriter+objc.swift +++ b/Sources/DatadogObjc/Tracing/Propagation/HTTPHeadersWriter+objc.swift @@ -16,7 +16,7 @@ public class DDHTTPHeadersWriter: NSObject { } @objc - public init(samplingRate: Float = 100) { + public init(samplingRate: Float = 20) { swiftHTTPHeadersWriter = HTTPHeadersWriter(samplingRate: samplingRate) } } diff --git a/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift b/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift index f12ac9c26a..2d92688d2d 100644 --- a/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift +++ b/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift @@ -229,7 +229,7 @@ class DDTracerTests: XCTestCase { swiftSpanContext: DDSpanContext.mockWith(traceID: 1, spanID: 2) ) - let objcWriter = DDHTTPHeadersWriter() + let objcWriter = DDHTTPHeadersWriter(samplingRate: 100) try objcTracer.inject(objcSpanContext, format: OT.formatTextMap, carrier: objcWriter) let expectedHTTPHeaders = [ @@ -259,7 +259,7 @@ class DDTracerTests: XCTestCase { let objcTracer = DDTracer(swiftTracer: Tracer.mockAny()) let objcSpanContext = DDSpanContextObjc(swiftSpanContext: DDSpanContext.mockWith(traceID: 1, spanID: 2)) - let objcValidWriter = DDHTTPHeadersWriter() + let objcValidWriter = DDHTTPHeadersWriter(samplingRate: 100) let objcInvalidFormat = "foo" XCTAssertThrowsError( try objcTracer.inject(objcSpanContext, format: objcInvalidFormat, carrier: objcValidWriter) From fb6b99c44638eb238ca7ac651bf73108ba54073a Mon Sep 17 00:00:00 2001 From: Priyanshi Gupta Date: Wed, 4 May 2022 11:00:52 +0200 Subject: [PATCH 46/55] add background event tracking documentation --- docs/rum_collection/_index.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/rum_collection/_index.md b/docs/rum_collection/_index.md index 5315a6026f..03397f332e 100644 --- a/docs/rum_collection/_index.md +++ b/docs/rum_collection/_index.md @@ -299,6 +299,20 @@ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConf {{% /tab %}} {{< /tabs >}} +### Track background events + +To track events (crashes, network requests etc.) when your application is in the foreground, add during initialization while setting up Datadog configuration: + +{{< tabs >}} +{{% tab "Swift" %}} +```swift +.trackBackgroundEvents() + +``` + +

Note that tracking background events could lead to additional sessions that might impact billing. For questions contact support@datadoghq.com

+
+ ## iOS Crash Reporting and Error Tracking Crash Reporting and Error Tracking for iOS displays any issues and latest available errors. You can view error details and attributes including JSON in the RUM Explorer. From 381b83cdb0f21a999475dcecfd1c3f451d51d0a1 Mon Sep 17 00:00:00 2001 From: Priyanshi Gupta Date: Wed, 4 May 2022 11:04:57 +0200 Subject: [PATCH 47/55] Update _index.md --- docs/rum_collection/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rum_collection/_index.md b/docs/rum_collection/_index.md index 03397f332e..bc11fbe086 100644 --- a/docs/rum_collection/_index.md +++ b/docs/rum_collection/_index.md @@ -301,7 +301,7 @@ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConf ### Track background events -To track events (crashes, network requests etc.) when your application is in the foreground, add during initialization while setting up Datadog configuration: +To track events (crashes, network requests etc.) when your application is in the background, i.e. there is no active view, add the following snippet during initialization while setting up Datadog configuration: {{< tabs >}} {{% tab "Swift" %}} From 4ff3b8737ad6a88b58ec2b8b548a999dd2e846ad Mon Sep 17 00:00:00 2001 From: Priyanshi Gupta Date: Thu, 5 May 2022 13:07:44 +0200 Subject: [PATCH 48/55] update docs for background tracking - doc review Co-authored-by: Austin Lai <76412946+alai97@users.noreply.github.com> --- docs/rum_collection/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rum_collection/_index.md b/docs/rum_collection/_index.md index bc11fbe086..82045940b3 100644 --- a/docs/rum_collection/_index.md +++ b/docs/rum_collection/_index.md @@ -310,7 +310,7 @@ To track events (crashes, network requests etc.) when your application is in the ``` -

Note that tracking background events could lead to additional sessions that might impact billing. For questions contact support@datadoghq.com

+

Tracking background events may lead to additional sessions, which can impact billing. For questions, contact Datadog support.

## iOS Crash Reporting and Error Tracking From 850d44e04e8781829b83ec8c950d467ec3fa6236 Mon Sep 17 00:00:00 2001 From: Priyanshi Gupta Date: Thu, 5 May 2022 13:08:08 +0200 Subject: [PATCH 49/55] Update docs/rum_collection/_index.md Co-authored-by: Austin Lai <76412946+alai97@users.noreply.github.com> --- docs/rum_collection/_index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/rum_collection/_index.md b/docs/rum_collection/_index.md index 82045940b3..a1c910c36f 100644 --- a/docs/rum_collection/_index.md +++ b/docs/rum_collection/_index.md @@ -301,7 +301,9 @@ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConf ### Track background events -To track events (crashes, network requests etc.) when your application is in the background, i.e. there is no active view, add the following snippet during initialization while setting up Datadog configuration: +You can track events such as crashes and network requests when your application is in the background (for example, no active view is available). + +Add the following snippet during initialization in your Datadog configuration: {{< tabs >}} {{% tab "Swift" %}} From 345cfa5cb7a7093ffc95ccf03c711eb8c3fde294 Mon Sep 17 00:00:00 2001 From: Austin Lai <76412946+alai97@users.noreply.github.com> Date: Tue, 17 May 2022 08:26:23 -0700 Subject: [PATCH 50/55] Add Closing Tag Co-authored-by: Maciek Grzybowski --- docs/rum_collection/_index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/rum_collection/_index.md b/docs/rum_collection/_index.md index a1c910c36f..24cb87418b 100644 --- a/docs/rum_collection/_index.md +++ b/docs/rum_collection/_index.md @@ -311,7 +311,8 @@ Add the following snippet during initialization in your Datadog configuration: .trackBackgroundEvents() ``` - +{{% /tab %}} +{{< /tabs >}}

Tracking background events may lead to additional sessions, which can impact billing. For questions, contact Datadog support.

From f660be5d09a63fdbee9db7345285bf7ae48e9316 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 18 May 2022 14:05:57 +0200 Subject: [PATCH 51/55] Apply docs review Co-authored-by: May Lee --- docs/rum_collection/crash_reporting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rum_collection/crash_reporting.md b/docs/rum_collection/crash_reporting.md index 7cbc8e2066..aae6db215e 100644 --- a/docs/rum_collection/crash_reporting.md +++ b/docs/rum_collection/crash_reporting.md @@ -181,7 +181,7 @@ To verify your iOS Crash Reporting and Error Tracking configuration, issue a cra 3. After the crash happens, restart your application and wait for the iOS SDK to upload the crash report in [**Error Tracking**][8]. -Note that RUM supports symbolication of system symbol files for iOS v14+ arm64 and arm64e architecture. +**Note:** RUM supports symbolication of system symbol files for iOS v14+ arm64 and arm64e architecture. ## Further Reading From bfd6badfe6859fdd28ba0bc1c24a2b172ccb56a9 Mon Sep 17 00:00:00 2001 From: Austin Lai Date: Tue, 5 Apr 2022 13:27:48 -0700 Subject: [PATCH 52/55] DOCS-3292 Adds Mobile Vitals doc page in the iOS SDK repo. --- docs/README.md | 3 ++- docs/rum_collection/_index.md | 9 ++++---- docs/rum_mobile_vitals.md | 40 +++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 docs/rum_mobile_vitals.md diff --git a/docs/README.md b/docs/README.md index cb1341a48e..edaaba008d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,4 +9,5 @@ Find dedicated documentation for the following topics: * [Collecting and sending RUM events from your iOS application to Datadog](rum_collection.md). * [Data collected from your iOS application to Datadog](data_collected.md). * [Advanced configuration for iOS monitoring](advanced_configuration.md)). -* [Enable crash reporting and error tracking for your iOS applications](crash_reporting.md). \ No newline at end of file +* [Enable crash reporting and error tracking for your iOS applications](crash_reporting.md). +* [Comprehend your iOS application's health and performance with Mobile Vitals](rum_mobile_vitals.md). \ No newline at end of file diff --git a/docs/rum_collection/_index.md b/docs/rum_collection/_index.md index 24cb87418b..4c09b8a754 100644 --- a/docs/rum_collection/_index.md +++ b/docs/rum_collection/_index.md @@ -12,6 +12,9 @@ further_reading: - link: "/real_user_monitoring" tag: "Documentation" text: "Learn how to explore your RUM data" + - link: "/real_user_monitoring/ios/crash_reporting/" + tag: "Documentation" + text: "See your crash reports and error trends in RUM" --- Datadog Real User Monitoring (RUM) enables you to visualize and analyze the real-time performance and user journeys of your application's individual users. @@ -318,10 +321,7 @@ Add the following snippet during initialization in your Datadog configuration: ## iOS Crash Reporting and Error Tracking -Crash Reporting and Error Tracking for iOS displays any issues and latest available errors. You can view error details and attributes including JSON in the RUM Explorer. - -

Crash Reporting and Error Tracking is available in beta. To sign up, see Crash Reporting (beta).

-
+Crash Reporting and Error Tracking for iOS displays any issues and latest available errors. You can view error details and attributes including JSON in the [RUM Explorer][10]. ## Further Reading @@ -337,3 +337,4 @@ Crash Reporting and Error Tracking for iOS displays any issues and latest availa [7]: https://docs.datadoghq.com/account_management/api-app-keys/#client-tokens [8]: https://docs.datadoghq.com/real_user_monitoring/ios/advanced_configuration/#set-tracking-consent-gdpr-compliance [9]: https://docs.datadoghq.com/real_user_monitoring/ios/advanced_configuration/#initialization-parameters +[10]: https://docs.datadoghq.com/real_user_monitoring/explorer/ \ No newline at end of file diff --git a/docs/rum_mobile_vitals.md b/docs/rum_mobile_vitals.md new file mode 100644 index 0000000000..24275eb4d8 --- /dev/null +++ b/docs/rum_mobile_vitals.md @@ -0,0 +1,40 @@ +## Overview + +Real User Monitoring offers Mobile Vitals, a set of metrics inspired by [MetricKit][1], that can help compute insights about your mobile application's responsiveness, stability, and resource consumption. Mobile Vitals range from poor, moderate, to good. + +Mobile Vitals appear in your application's **Overview** tab and in the side panel under **Performance** > **Event Timings and Mobile Vitals** when you click on an individual view in the [RUM Explorer][2]. Click on a graph in **Mobile Vitals** to apply a filter by version or examine filtered sessions. + +{{< img src="real_user_monitoring/ios/ios_mobile_vitals.png" alt="Mobile Vitals in the Performance Tab" style="width:70%;">}} + +Understand your application's overall health and performance with the line graphs displaying metrics across various application versions. To filter on application version or see specific sessions and views, click on a graph. + +{{< img src="real_user_monitoring/ios/rum_explorer_mobile_vitals.png" alt="Event Timings and Mobile Vitals in the RUM Explorer" style="width:90%;">}} + +You can also select a view in the RUM Explorer and observe recommended benchmark ranges that directly correlate to your application's user experience in the session. Click on a metric such as **Refresh Rate Average** and click **Search Views With Poor Performance** to apply a filter in your search query and examine additional views. + +## Metrics + +The following metrics provide insight into your mobile application's performance. +| Measurement | Description | +|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Slow renders | To ensure a smooth, [jank-free][3] user experience, your application should render frames in under 60Hz.

RUM tracks the application’s [display refresh rate][4] using `@view.refresh_rate_average` and `@view.refresh_rate_min` view attributes.

With slow rendering, you can monitor which views are taking longer than 16ms or 60Hz to render.
**Note:** Refresh rates are normalized on a range of zero to 60fps. For example, if your application runs at 100fps on a device capable of rendering 120fps, Datadog reports 50fps in **Mobile Vitals**. | +| Frozen frames | Frames that take longer than 700ms to render appear as stuck and unresponsive in your application. These are classified as [frozen frames][5].

RUM tracks `long task` events with the duration for any task taking longer then 100ms to complete.

With frozen frames, you can monitor which views appear frozen (taking longer than 700ms to render) to your end users and eliminate jank in your application. | +| Application not responding | When the UI thread of an application is blocked for more than 5 seconds, an `Application Not Responding` ([ANR][6]) error triggers. If the application is in the foreground, the system displays a dialog modal to the user, allowing them to force quit the application.

RUM tracks ANR occurrences and captures the entire stack trace that blocks the main thread when it encounters an ANR. | +| Crash-free sessions by version | An [application crash][7] is reported due to an unexpected exit in the application typically caused by an unhandled exception or signal. Crash-free user sessions in your application directly correspond to your end user’s experience and overall satisfaction.

RUM tracks complete crash reports and presents trends over time with [Error Tracking][8].

With crash-free sessions, you can stay up to speed on industry benchmarks and ensure that your application is ranked highly on the Google Play Store. | +| CPU ticks per second | High CPU usage impacts the [battery life][9] on your users’ devices.

RUM tracks CPU ticks per second for each view and the CPU utilization over the course of a session. The recommended range is <40 for good and <60 for moderate.

You can see the top views with the most number of CPU ticks on average over a selected time period under **Mobile Vitals** in your application's Overview page. | +| Memory utilization | High memory usage can lead to [out-of-memory crashes][10], which causes a poor user experience.

RUM tracks the amount of physical memory used by your application in bytes for each view, over the course of a session. The recommended range is <200MB for good and <400MB for moderate.

You can see the top views with the most memory consumption on average over a selected time period under **Mobile Vitals** in your application's Overview page. | + +## Further Reading + +{{< partial name="whats-next/whats-next.html" >}} + +[1]: https://developer.apple.com/documentation/metrickit +[2]: https://app.datadoghq.com/rum/explorer +[3]: https://developer.android.com/topic/performance/vitals/render#common-jank +[4]: https://developer.android.com/guide/topics/media/frame-rate +[5]: https://developer.android.com/topic/performance/vitals/frozen +[6]: https://developer.android.com/topic/performance/vitals/anr +[7]: https://developer.apple.com/documentation/xcode/diagnosing-issues-using-crash-reports-and-device-logs +[8]: https://docs.datadoghq.com/real_user_monitoring/ios/crash_reporting/ +[9]: https://developer.apple.com/documentation/xcode/analyzing-your-app-s-battery-use/ +[10]: https://developer.android.com/reference/java/lang/OutOfMemoryError \ No newline at end of file From 4407653623937ead7d199e2f06935af1e2459e4c Mon Sep 17 00:00:00 2001 From: Austin Lai <76412946+alai97@users.noreply.github.com> Date: Wed, 4 May 2022 09:11:21 -0700 Subject: [PATCH 53/55] SME Review Co-authored-by: Maciek Grzybowski --- docs/rum_mobile_vitals.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/rum_mobile_vitals.md b/docs/rum_mobile_vitals.md index 24275eb4d8..d2eb93ed15 100644 --- a/docs/rum_mobile_vitals.md +++ b/docs/rum_mobile_vitals.md @@ -19,7 +19,6 @@ The following metrics provide insight into your mobile application's performance |--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Slow renders | To ensure a smooth, [jank-free][3] user experience, your application should render frames in under 60Hz.

RUM tracks the application’s [display refresh rate][4] using `@view.refresh_rate_average` and `@view.refresh_rate_min` view attributes.

With slow rendering, you can monitor which views are taking longer than 16ms or 60Hz to render.
**Note:** Refresh rates are normalized on a range of zero to 60fps. For example, if your application runs at 100fps on a device capable of rendering 120fps, Datadog reports 50fps in **Mobile Vitals**. | | Frozen frames | Frames that take longer than 700ms to render appear as stuck and unresponsive in your application. These are classified as [frozen frames][5].

RUM tracks `long task` events with the duration for any task taking longer then 100ms to complete.

With frozen frames, you can monitor which views appear frozen (taking longer than 700ms to render) to your end users and eliminate jank in your application. | -| Application not responding | When the UI thread of an application is blocked for more than 5 seconds, an `Application Not Responding` ([ANR][6]) error triggers. If the application is in the foreground, the system displays a dialog modal to the user, allowing them to force quit the application.

RUM tracks ANR occurrences and captures the entire stack trace that blocks the main thread when it encounters an ANR. | | Crash-free sessions by version | An [application crash][7] is reported due to an unexpected exit in the application typically caused by an unhandled exception or signal. Crash-free user sessions in your application directly correspond to your end user’s experience and overall satisfaction.

RUM tracks complete crash reports and presents trends over time with [Error Tracking][8].

With crash-free sessions, you can stay up to speed on industry benchmarks and ensure that your application is ranked highly on the Google Play Store. | | CPU ticks per second | High CPU usage impacts the [battery life][9] on your users’ devices.

RUM tracks CPU ticks per second for each view and the CPU utilization over the course of a session. The recommended range is <40 for good and <60 for moderate.

You can see the top views with the most number of CPU ticks on average over a selected time period under **Mobile Vitals** in your application's Overview page. | | Memory utilization | High memory usage can lead to [out-of-memory crashes][10], which causes a poor user experience.

RUM tracks the amount of physical memory used by your application in bytes for each view, over the course of a session. The recommended range is <200MB for good and <400MB for moderate.

You can see the top views with the most memory consumption on average over a selected time period under **Mobile Vitals** in your application's Overview page. | From d21193a10439e362dc319b49d8216c8c85e179c1 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 18 May 2022 14:56:07 +0200 Subject: [PATCH 54/55] Bumped version to 1.11.0-rc1 --- DatadogSDK.podspec | 2 +- DatadogSDKAlamofireExtension.podspec | 2 +- DatadogSDKCrashReporting.podspec | 4 ++-- DatadogSDKObjc.podspec | 4 ++-- Sources/Datadog/Versioning.swift | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index bac3b45595..d60ba16731 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDK" s.module_name = "Datadog" - s.version = "1.11.0-beta2" + s.version = "1.11.0-rc1" s.summary = "Official Datadog Swift SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKAlamofireExtension.podspec b/DatadogSDKAlamofireExtension.podspec index 4a1ca34f50..ab5bd6ff48 100644 --- a/DatadogSDKAlamofireExtension.podspec +++ b/DatadogSDKAlamofireExtension.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKAlamofireExtension" s.module_name = "DatadogAlamofireExtension" - s.version = "1.11.0-beta2" + s.version = "1.11.0-rc1" s.summary = "An Official Extensions of Datadog Swift SDK for Alamofire." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKCrashReporting.podspec b/DatadogSDKCrashReporting.podspec index af2393ecdb..9782ff6536 100644 --- a/DatadogSDKCrashReporting.podspec +++ b/DatadogSDKCrashReporting.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKCrashReporting" s.module_name = "DatadogCrashReporting" - s.version = "1.11.0-beta2" + s.version = "1.11.0-rc1" s.summary = "Official Datadog Crash Reporting SDK for iOS." s.homepage = "https://www.datadoghq.com" @@ -22,6 +22,6 @@ Pod::Spec.new do |s| s.static_framework = true s.source_files = "Sources/DatadogCrashReporting/**/*.swift" - s.dependency 'DatadogSDK', '1.11.0-beta2' + s.dependency 'DatadogSDK', '1.11.0-rc1' s.dependency 'PLCrashReporter', '~> 1.10.1' end diff --git a/DatadogSDKObjc.podspec b/DatadogSDKObjc.podspec index 9a135254e6..a4fc1a0913 100644 --- a/DatadogSDKObjc.podspec +++ b/DatadogSDKObjc.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKObjc" s.module_name = "DatadogObjc" - s.version = "1.11.0-beta2" + s.version = "1.11.0-rc1" s.summary = "Official Datadog Objective-C SDK for iOS." s.homepage = "https://www.datadoghq.com" @@ -21,5 +21,5 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/DataDog/dd-sdk-ios.git', :tag => s.version.to_s } s.source_files = "Sources/DatadogObjc/**/*.swift" - s.dependency 'DatadogSDK', '1.11.0-beta2' + s.dependency 'DatadogSDK', '1.11.0-rc1' end diff --git a/Sources/Datadog/Versioning.swift b/Sources/Datadog/Versioning.swift index d44b7a6db6..2206737b03 100644 --- a/Sources/Datadog/Versioning.swift +++ b/Sources/Datadog/Versioning.swift @@ -1,3 +1,3 @@ // GENERATED FILE: Do not edit directly -internal let __sdkVersion = "1.11.0-beta2" +internal let __sdkVersion = "1.11.0-rc1" From 83404267de239728a8efb4c772b04f096c15905c Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 18 May 2022 15:00:13 +0200 Subject: [PATCH 55/55] Update CHANGELOG --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66009e8f1b..90b176b4a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ### Changes -* [IMPROVEMENT] Allow manually tracked resources in RUM Sessions to detect first party hosts. +# 1.11.0-rc1 / 18-05-2022 + +### Changes + +* [IMPROVEMENT] Allow manually tracked resources in RUM Sessions to detect first party hosts. See [#837][] * [IMPROVEMENT] Add tracing sampling rate. See [#851][] # 1.11.0-beta2 / 05-04-2022 @@ -360,6 +364,7 @@ [#797]: https://github.com/DataDog/dd-sdk-ios/issues/797 [#815]: https://github.com/DataDog/dd-sdk-ios/issues/815 [#830]: https://github.com/DataDog/dd-sdk-ios/issues/830 +[#837]: https://github.com/DataDog/dd-sdk-ios/issues/837 [#832]: https://github.com/DataDog/dd-sdk-ios/issues/832 [#851]: https://github.com/DataDog/dd-sdk-ios/issues/851 [@00FA9A]: https://github.com/00FA9A