diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index c353036268..a2f790847f 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -214,6 +214,7 @@ 61940C7C25668EC600A20043 /* URLSessionInterceptionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61940C7B25668EC600A20043 /* URLSessionInterceptionHandler.swift */; }; 6198D27124C6E3B700493501 /* RUMViewScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6198D27024C6E3B700493501 /* RUMViewScopeTests.swift */; }; 61A763DC252DB2B3005A23F2 /* NSURLSessionBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 61A763DB252DB2B3005A23F2 /* NSURLSessionBridge.m */; }; + 61A9238E256FCAA2009B9667 /* DateCorrectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A9238D256FCAA2009B9667 /* DateCorrectionTests.swift */; }; 61AD4E182451C7FF006E34EA /* TracingFeatureMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AD4E172451C7FF006E34EA /* TracingFeatureMocks.swift */; }; 61AD4E3824531500006E34EA /* DataFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AD4E3724531500006E34EA /* DataFormat.swift */; }; 61AD4E3A24534075006E34EA /* TracingFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AD4E3924534075006E34EA /* TracingFeatureTests.swift */; }; @@ -242,6 +243,7 @@ 61BB2B1B244A185D009F3F56 /* PerformancePreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BB2B1A244A185D009F3F56 /* PerformancePreset.swift */; }; 61BBD19524ED4E9E0023E65F /* FeaturesConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BBD19424ED4E9E0023E65F /* FeaturesConfiguration.swift */; }; 61BBD19724ED50040023E65F /* FeaturesConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BBD19624ED50040023E65F /* FeaturesConfigurationTests.swift */; }; + 61BCB81F256EB77F0039887B /* ServerDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BCB81E256EB77F0039887B /* ServerDateProvider.swift */; }; 61C2C20724C098FC00C0321C /* RUMSessionScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C2C20624C098FC00C0321C /* RUMSessionScope.swift */; }; 61C2C20924C0C75500C0321C /* RUMSessionScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C2C20824C0C75500C0321C /* RUMSessionScopeTests.swift */; }; 61C2C20B24C1045300C0321C /* SendRUMFixture1ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C2C20A24C1045300C0321C /* SendRUMFixture1ViewController.swift */; }; @@ -256,6 +258,7 @@ 61C3E63924BF19B4008053F2 /* RUMContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3E63824BF19B4008053F2 /* RUMContext.swift */; }; 61C3E63B24BF1A4B008053F2 /* RUMCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3E63A24BF1A4B008053F2 /* RUMCommand.swift */; }; 61C3E63E24BF1B91008053F2 /* RUMApplicationScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3E63D24BF1B91008053F2 /* RUMApplicationScope.swift */; }; + 61C576C6256E65BD00295F7C /* DateCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C576C5256E65BD00295F7C /* DateCorrection.swift */; }; 61C5A88424509A0C00DA608C /* DDSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C5A87824509A0C00DA608C /* DDSpan.swift */; }; 61C5A88524509A0C00DA608C /* DDNoOps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C5A87924509A0C00DA608C /* DDNoOps.swift */; }; 61C5A88624509A0C00DA608C /* TracingUUIDGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C5A87B24509A0C00DA608C /* TracingUUIDGenerator.swift */; }; @@ -635,6 +638,7 @@ 61A763D9252DB2B3005A23F2 /* DatadogTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DatadogTests-Bridging-Header.h"; sourceTree = ""; }; 61A763DA252DB2B3005A23F2 /* NSURLSessionBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSURLSessionBridge.h; sourceTree = ""; }; 61A763DB252DB2B3005A23F2 /* NSURLSessionBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSURLSessionBridge.m; sourceTree = ""; }; + 61A9238D256FCAA2009B9667 /* DateCorrectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateCorrectionTests.swift; sourceTree = ""; }; 61AD4E172451C7FF006E34EA /* TracingFeatureMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingFeatureMocks.swift; sourceTree = ""; }; 61AD4E3724531500006E34EA /* DataFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataFormat.swift; sourceTree = ""; }; 61AD4E3924534075006E34EA /* TracingFeatureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingFeatureTests.swift; sourceTree = ""; }; @@ -656,6 +660,7 @@ 61BB2B1A244A185D009F3F56 /* PerformancePreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformancePreset.swift; sourceTree = ""; }; 61BBD19424ED4E9E0023E65F /* FeaturesConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturesConfiguration.swift; sourceTree = ""; }; 61BBD19624ED50040023E65F /* FeaturesConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturesConfigurationTests.swift; sourceTree = ""; }; + 61BCB81E256EB77F0039887B /* ServerDateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDateProvider.swift; sourceTree = ""; }; 61C2C20624C098FC00C0321C /* RUMSessionScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMSessionScope.swift; sourceTree = ""; }; 61C2C20824C0C75500C0321C /* RUMSessionScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMSessionScopeTests.swift; sourceTree = ""; }; 61C2C20A24C1045300C0321C /* SendRUMFixture1ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendRUMFixture1ViewController.swift; sourceTree = ""; }; @@ -670,6 +675,7 @@ 61C3E63824BF19B4008053F2 /* RUMContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMContext.swift; sourceTree = ""; }; 61C3E63A24BF1A4B008053F2 /* RUMCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMCommand.swift; sourceTree = ""; }; 61C3E63D24BF1B91008053F2 /* RUMApplicationScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMApplicationScope.swift; sourceTree = ""; }; + 61C576C5256E65BD00295F7C /* DateCorrection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateCorrection.swift; sourceTree = ""; }; 61C5A87824509A0C00DA608C /* DDSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDSpan.swift; sourceTree = ""; }; 61C5A87924509A0C00DA608C /* DDNoOps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDNoOps.swift; sourceTree = ""; }; 61C5A87B24509A0C00DA608C /* TracingUUIDGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TracingUUIDGenerator.swift; sourceTree = ""; }; @@ -923,7 +929,7 @@ 61133BA12423979B00786299 /* System */ = { isa = PBXGroup; children = ( - 61133BA82423979B00786299 /* DateProvider.swift */, + 61C576C4256E655600295F7C /* Time */, 61E36A10254B2280001AD6F2 /* LaunchTimeProvider.swift */, 61133BA22423979B00786299 /* CarrierInfoProvider.swift */, 61133BA32423979B00786299 /* MobileDevice.swift */, @@ -1149,6 +1155,7 @@ 61133C222423990D00786299 /* System */ = { isa = PBXGroup; children = ( + 61A9238C256FCA92009B9667 /* Time */, 614AD085254C3027004999A3 /* LaunchTimeProviderTests.swift */, 61133C232423990D00786299 /* MobileDeviceTests.swift */, 61133C242423990D00786299 /* NetworkConnectionInfoProviderTests.swift */, @@ -1777,6 +1784,14 @@ path = TapActionAutoInstrumentation; sourceTree = ""; }; + 61A9238C256FCA92009B9667 /* Time */ = { + isa = PBXGroup; + children = ( + 61A9238D256FCAA2009B9667 /* DateCorrectionTests.swift */, + ); + path = Time; + sourceTree = ""; + }; 61B03872252724AB00518F3C /* URLSessionAutoInstrumentation */ = { isa = PBXGroup; children = ( @@ -1837,6 +1852,16 @@ path = Scopes; sourceTree = ""; }; + 61C576C4256E655600295F7C /* Time */ = { + isa = PBXGroup; + children = ( + 61133BA82423979B00786299 /* DateProvider.swift */, + 61BCB81E256EB77F0039887B /* ServerDateProvider.swift */, + 61C576C5256E65BD00295F7C /* DateCorrection.swift */, + ); + path = Time; + sourceTree = ""; + }; 61C5A87724509A0C00DA608C /* Tracing */ = { isa = PBXGroup; children = ( @@ -2490,6 +2515,7 @@ buildActionMask = 2147483647; files = ( 61E917D12465423600E6C631 /* TracerConfiguration.swift in Sources */, + 61C576C6256E65BD00295F7C /* DateCorrection.swift in Sources */, 61E909ED24A24DD3005EA2DE /* OTSpan.swift in Sources */, 61133BDE2423979B00786299 /* CompilationConditions.swift in Sources */, 61E909F324A24DD3005EA2DE /* OTSpanContext.swift in Sources */, @@ -2524,6 +2550,7 @@ 61494CB124C839460082C633 /* RUMResourceScope.swift in Sources */, 61C2C20724C098FC00C0321C /* RUMSessionScope.swift in Sources */, 617CEB392456BC3A00AD4669 /* TracingUUID.swift in Sources */, + 61BCB81F256EB77F0039887B /* ServerDateProvider.swift in Sources */, 61FF282624B8A248000B3D9B /* RUMEventOutput.swift in Sources */, 618715F924DC13A100FC0F69 /* RUMDataModelsMapping.swift in Sources */, 61C3638524361E9200C4D4E6 /* Globals.swift in Sources */, @@ -2693,6 +2720,7 @@ 61C5A89624509BF600DA608C /* TracerTests.swift in Sources */, 61F1A61A2498A51700075390 /* CoreMocks.swift in Sources */, 61E45BD22450F65B00F2C652 /* SpanBuilderTests.swift in Sources */, + 61A9238E256FCAA2009B9667 /* DateCorrectionTests.swift in Sources */, 61E45BCF2450A6EC00F2C652 /* TracingUUIDTests.swift in Sources */, 614AD086254C3027004999A3 /* LaunchTimeProviderTests.swift in Sources */, 61133C482423990D00786299 /* DDDatadogTests.swift in Sources */, diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme index ed33b84776..05e8c28394 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -58,7 +58,7 @@ Date +} + +internal class DateCorrection: DateCorrectionType { + static let datadogNTPServers = [ + "0.datadog.pool.ntp.org", + "1.datadog.pool.ntp.org", + "2.datadog.pool.ntp.org", + "3.datadog.pool.ntp.org" + ] + private let deviceDateProvider: DateProvider + private let serverDateProvider: ServerDateProvider + + init(deviceDateProvider: DateProvider, serverDateProvider: ServerDateProvider) { + self.deviceDateProvider = deviceDateProvider + self.serverDateProvider = serverDateProvider + // swiftlint:disable trailing_closure + serverDateProvider.synchronize( + with: DateCorrection.datadogNTPServers.randomElement()!, // swiftlint:disable:this force_unwrapping + completion: { serverTime in + let deviceTime = deviceDateProvider.currentDate() + if let serverTime = serverTime { + let difference = (serverTime.timeIntervalSince(deviceTime) * 1_000).rounded() / 1_000 + userLogger.info( + """ + NTP time synchronization completed. + Server time will be used for signing events (current server time is \(serverTime); \(difference)s difference with device time). + """ + ) + } else { + userLogger.warn( + """ + NTP time synchronization failed. + Device time will be used for signing events (current device time is \(deviceTime)). + """ + ) + } + } + ) + // swiftlint:enable trailing_closure + } + + func toServerDate(deviceDate: Date) -> Date { + if let serverTime = serverDateProvider.currentDate() { + let deviceTime = deviceDateProvider.currentDate() + let timeDifference = serverTime.timeIntervalSince(deviceTime) + return deviceDate.addingTimeInterval(timeDifference) + } else { + return deviceDate + } + } +} diff --git a/Sources/Datadog/Core/System/DateProvider.swift b/Sources/Datadog/Core/System/Time/DateProvider.swift similarity index 85% rename from Sources/Datadog/Core/System/DateProvider.swift rename to Sources/Datadog/Core/System/Time/DateProvider.swift index c8191c1490..3316b5c042 100644 --- a/Sources/Datadog/Core/System/DateProvider.swift +++ b/Sources/Datadog/Core/System/Time/DateProvider.swift @@ -6,8 +6,9 @@ import Foundation -/// Interface for date provider used for files orchestration. +/// Provides current device time information. internal protocol DateProvider { + /// Current device time. func currentDate() -> Date } diff --git a/Sources/Datadog/Core/System/Time/ServerDateProvider.swift b/Sources/Datadog/Core/System/Time/ServerDateProvider.swift new file mode 100644 index 0000000000..4dddb51f02 --- /dev/null +++ b/Sources/Datadog/Core/System/Time/ServerDateProvider.swift @@ -0,0 +1,32 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import Foundation +import Kronos + +/// Abstract the monotonic clock synchronized with the server using NTP. +internal protocol ServerDateProvider { + /// Start the clock synchronisation with NTP server. + /// Calls the `completion` by passing it the server time when the synchronization succeeds or`nil` if it fails. + func synchronize(with ntpPool: String, completion: @escaping (Date?) -> Void) + /// Returns the server time or `nil` if not yet determined. + /// This time gets more precise while synchronization is pending. + func currentDate() -> Date? +} + +internal class NTPServerDateProvider: ServerDateProvider { + func synchronize(with ntpPool: String, completion: @escaping (Date?) -> Void) { + // swiftlint:disable trailing_closure multiline_arguments_brackets + Clock.sync(from: ntpPool, completion: { serverTime, _ in + completion(serverTime) + }) + // swiftlint:enable trailing_closure multiline_arguments_brackets + } + + func currentDate() -> Date? { + return Clock.now + } +} diff --git a/Sources/Datadog/Core/Upload/DataUploader.swift b/Sources/Datadog/Core/Upload/DataUploader.swift index 97b6386877..4398b7e50a 100644 --- a/Sources/Datadog/Core/Upload/DataUploader.swift +++ b/Sources/Datadog/Core/Upload/DataUploader.swift @@ -14,14 +14,6 @@ internal class UploadURLProvider { class QueryItemProvider { let value: () -> URLQueryItem - /// Creates `batch_time=...` query item adding current timestamp (in milliseconds) to the URL. - static func batchTime(using dateProvider: DateProvider) -> QueryItemProvider { - return QueryItemProvider { - let timestamp = dateProvider.currentDate().timeIntervalSince1970.toMilliseconds - return URLQueryItem(name: "batch_time", value: "\(timestamp)") - } - } - /// Creates `ddsource=ios` query item. static func ddsource() -> QueryItemProvider { let queryItem = URLQueryItem(name: "ddsource", value: Datadog.Constants.ddsource) @@ -41,7 +33,10 @@ internal class UploadURLProvider { var url: URL { var urlComponents = URLComponents(url: urlWithClientToken, resolvingAgainstBaseURL: false) - urlComponents?.queryItems = queryItemProviders.map { $0.value() } + + if !queryItemProviders.isEmpty { + urlComponents?.queryItems = queryItemProviders.map { $0.value() } + } guard let url = urlComponents?.url else { userLogger.error("🔥 Failed to create URL from \(urlWithClientToken) with \(queryItemProviders)") diff --git a/Sources/Datadog/Datadog.swift b/Sources/Datadog/Datadog.swift index 5f750056cf..35e47f39f5 100644 --- a/Sources/Datadog/Datadog.swift +++ b/Sources/Datadog/Datadog.swift @@ -5,7 +5,6 @@ */ import Foundation -import Kronos /// Datadog SDK configuration object. public class Datadog { @@ -48,10 +47,6 @@ public class Datadog { /// - appContext: context passing information about the app. /// - configuration: the SDK configuration obtained using `Datadog.Configuration.builderUsing(clientToken:)`. public static func initialize(appContext: AppContext, configuration: Configuration) { - // TODO: RUMM-655 This is only to check at runtime if `lyft/Kronos` is properly linked. Will be removed before - // merging to `master` branch. - _ = Clock.now - // TODO: RUMM-511 remove this warning #if targetEnvironment(macCatalyst) consolePrint("⚠️ Catalyst is not officially supported by Datadog SDK: some features may NOT be functional!") @@ -101,6 +96,10 @@ public class Datadog { } let dateProvider = SystemDateProvider() + let dateCorrection = DateCorrection( + deviceDateProvider: dateProvider, + serverDateProvider: NTPServerDateProvider() + ) let userInfoProvider = UserInfoProvider() let networkConnectionInfoProvider = NetworkConnectionInfoProvider() let carrierInfoProvider = CarrierInfoProvider() @@ -133,6 +132,7 @@ public class Datadog { httpClient: HTTPClient(), mobileDevice: MobileDevice.current, dateProvider: dateProvider, + dateCorrection: dateCorrection, userInfoProvider: userInfoProvider, networkConnectionInfoProvider: networkConnectionInfoProvider, carrierInfoProvider: carrierInfoProvider, diff --git a/Sources/Datadog/FeaturesIntegration/LoggingForTracingAdapter.swift b/Sources/Datadog/FeaturesIntegration/LoggingForTracingAdapter.swift index 5a2a1e6d95..8909dafd1f 100644 --- a/Sources/Datadog/FeaturesIntegration/LoggingForTracingAdapter.swift +++ b/Sources/Datadog/FeaturesIntegration/LoggingForTracingAdapter.swift @@ -26,7 +26,8 @@ internal struct LoggingForTracingAdapter { loggerName: "trace", userInfoProvider: tracingFeature.userInfoProvider, networkConnectionInfoProvider: tracerConfiguration.sendNetworkInfo ? tracingFeature.networkConnectionInfoProvider : nil, - carrierInfoProvider: tracerConfiguration.sendNetworkInfo ? tracingFeature.carrierInfoProvider : nil + carrierInfoProvider: tracerConfiguration.sendNetworkInfo ? tracingFeature.carrierInfoProvider : nil, + dateCorrection: loggingFeature.dateCorrection ), fileWriter: loggingFeature.storage.writer, diff --git a/Sources/Datadog/Logger.swift b/Sources/Datadog/Logger.swift index 7e7e884259..623d575e62 100644 --- a/Sources/Datadog/Logger.swift +++ b/Sources/Datadog/Logger.swift @@ -404,7 +404,8 @@ public class Logger { loggerName: resolveLoggerName(for: loggingFeature), userInfoProvider: loggingFeature.userInfoProvider, networkConnectionInfoProvider: sendNetworkInfo ? loggingFeature.networkConnectionInfoProvider : nil, - carrierInfoProvider: sendNetworkInfo ? loggingFeature.carrierInfoProvider : nil + carrierInfoProvider: sendNetworkInfo ? loggingFeature.carrierInfoProvider : nil, + dateCorrection: loggingFeature.dateCorrection ) switch (useFileOutput, useConsoleLogFormat) { diff --git a/Sources/Datadog/Logging/Log/LogBuilder.swift b/Sources/Datadog/Logging/Log/LogBuilder.swift index 9b63c7e1be..a719171ac1 100644 --- a/Sources/Datadog/Logging/Log/LogBuilder.swift +++ b/Sources/Datadog/Logging/Log/LogBuilder.swift @@ -22,10 +22,12 @@ internal struct LogBuilder { let networkConnectionInfoProvider: NetworkConnectionInfoProviderType? /// Shared mobile carrier info provider (or `nil` if disabled for given logger). let carrierInfoProvider: CarrierInfoProviderType? + /// Adjusts log's time (device time) to server time. + let dateCorrection: DateCorrectionType? func createLogWith(level: LogLevel, message: String, date: Date, attributes: LogAttributes, tags: Set) -> Log { return Log( - date: date, + date: dateCorrection?.toServerDate(deviceDate: date) ?? date, status: logStatus(for: level), message: message, serviceName: serviceName, diff --git a/Sources/Datadog/Logging/LoggingFeature.swift b/Sources/Datadog/Logging/LoggingFeature.swift index 7917401270..75b9026fb1 100644 --- a/Sources/Datadog/Logging/LoggingFeature.swift +++ b/Sources/Datadog/Logging/LoggingFeature.swift @@ -27,6 +27,7 @@ internal final class LoggingFeature { // MARK: - Dependencies let dateProvider: DateProvider + let dateCorrection: DateCorrectionType let userInfoProvider: UserInfoProvider let networkConnectionInfoProvider: NetworkConnectionInfoProviderType let carrierInfoProvider: CarrierInfoProviderType @@ -75,8 +76,7 @@ internal final class LoggingFeature { uploadURLProvider: UploadURLProvider( urlWithClientToken: configuration.uploadURLWithClientToken, queryItemProviders: [ - .ddsource(), - .batchTime(using: commonDependencies.dateProvider) + .ddsource() ] ), commonDependencies: commonDependencies @@ -109,6 +109,7 @@ internal final class LoggingFeature { // Bundle dependencies self.dateProvider = commonDependencies.dateProvider + self.dateCorrection = commonDependencies.dateCorrection self.userInfoProvider = commonDependencies.userInfoProvider self.networkConnectionInfoProvider = commonDependencies.networkConnectionInfoProvider self.carrierInfoProvider = commonDependencies.carrierInfoProvider diff --git a/Sources/Datadog/RUM/RUMFeature.swift b/Sources/Datadog/RUM/RUMFeature.swift index 2c7899e71a..06d72c150b 100644 --- a/Sources/Datadog/RUM/RUMFeature.swift +++ b/Sources/Datadog/RUM/RUMFeature.swift @@ -27,6 +27,7 @@ internal final class RUMFeature { // MARK: - Dependencies let dateProvider: DateProvider + let dateCorrection: DateCorrectionType let userInfoProvider: UserInfoProvider let networkConnectionInfoProvider: NetworkConnectionInfoProviderType let carrierInfoProvider: CarrierInfoProviderType @@ -77,7 +78,6 @@ internal final class RUMFeature { urlWithClientToken: configuration.uploadURLWithClientToken, queryItemProviders: [ .ddsource(), - .batchTime(using: commonDependencies.dateProvider), .ddtags( tags: [ "service:\(configuration.common.serviceName)", @@ -118,6 +118,7 @@ internal final class RUMFeature { // Bundle dependencies self.dateProvider = commonDependencies.dateProvider + self.dateCorrection = commonDependencies.dateCorrection self.userInfoProvider = commonDependencies.userInfoProvider self.networkConnectionInfoProvider = commonDependencies.networkConnectionInfoProvider self.carrierInfoProvider = commonDependencies.carrierInfoProvider diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift index 65f14c3d4c..0a4c7ddebc 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -14,6 +14,8 @@ internal struct RUMScopeDependencies { let eventBuilder: RUMEventBuilder let eventOutput: RUMEventOutput let rumUUIDGenerator: RUMUUIDGenerator + /// Adjusts RUM events time (device time) to server time. + let dateCorrection: DateCorrectionType } internal class RUMApplicationScope: RUMScope, RUMContextProvider { diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift index cf75cbd8a8..217e9b0165 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift @@ -98,7 +98,7 @@ internal class RUMResourceScope: RUMScope { } let eventData = RUMDataResource( - date: resourceStartTime.timeIntervalSince1970.toInt64Milliseconds, + date: dependencies.dateCorrection.toServerDate(deviceDate: resourceStartTime).timeIntervalSince1970.toInt64Milliseconds, application: .init(id: context.rumApplicationID), service: nil, session: .init(id: context.sessionID.toRUMDataFormat, type: .user), @@ -171,7 +171,7 @@ internal class RUMResourceScope: RUMScope { attributes.merge(rumCommandAttributes: command.attributes) let eventData = RUMDataError( - date: command.time.timeIntervalSince1970.toInt64Milliseconds, + date: dependencies.dateCorrection.toServerDate(deviceDate: command.time).timeIntervalSince1970.toInt64Milliseconds, application: .init(id: context.rumApplicationID), service: nil, session: .init(id: context.sessionID.toRUMDataFormat, type: .user), diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift index bd5dcc06bb..d18b42501e 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift @@ -111,7 +111,7 @@ internal class RUMUserActionScope: RUMScope, RUMContextProvider { } let eventData = RUMDataAction( - date: actionStartTime.timeIntervalSince1970.toInt64Milliseconds, + date: dependencies.dateCorrection.toServerDate(deviceDate: actionStartTime).timeIntervalSince1970.toInt64Milliseconds, application: .init(id: context.rumApplicationID), service: nil, session: .init(id: context.sessionID.toRUMDataFormat, type: .user), diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index 2881284572..e73780d0e0 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -211,7 +211,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { private func sendApplicationStartAction(on command: RUMCommand) { let eventData = RUMDataAction( - date: viewStartTime.timeIntervalSince1970.toInt64Milliseconds, + date: dependencies.dateCorrection.toServerDate(deviceDate: viewStartTime).timeIntervalSince1970.toInt64Milliseconds, application: .init(id: context.rumApplicationID), service: nil, session: .init(id: context.sessionID.toRUMDataFormat, type: .user), @@ -244,7 +244,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { attributes.merge(rumCommandAttributes: command.attributes) let eventData = RUMDataView( - date: viewStartTime.timeIntervalSince1970.toInt64Milliseconds, + date: dependencies.dateCorrection.toServerDate(deviceDate: viewStartTime).timeIntervalSince1970.toInt64Milliseconds, application: .init(id: context.rumApplicationID), service: nil, session: .init(id: context.sessionID.toRUMDataFormat, type: .user), @@ -279,7 +279,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { attributes.merge(rumCommandAttributes: command.attributes) let eventData = RUMDataError( - date: command.time.timeIntervalSince1970.toInt64Milliseconds, + date: dependencies.dateCorrection.toServerDate(deviceDate: command.time).timeIntervalSince1970.toInt64Milliseconds, application: .init(id: context.rumApplicationID), service: nil, session: .init(id: context.sessionID.toRUMDataFormat, type: .user), diff --git a/Sources/Datadog/RUMMonitor.swift b/Sources/Datadog/RUMMonitor.swift index be2a150930..64d9c01179 100644 --- a/Sources/Datadog/RUMMonitor.swift +++ b/Sources/Datadog/RUMMonitor.swift @@ -186,7 +186,8 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { eventOutput: RUMEventFileOutput( fileWriter: rumFeature.storage.writer ), - rumUUIDGenerator: DefaultRUMUUIDGenerator() + rumUUIDGenerator: DefaultRUMUUIDGenerator(), + dateCorrection: rumFeature.dateCorrection ), samplingRate: rumFeature.configuration.sessionSamplingRate ), diff --git a/Sources/Datadog/Tracer.swift b/Sources/Datadog/Tracer.swift index cb1298590a..536e7e3087 100644 --- a/Sources/Datadog/Tracer.swift +++ b/Sources/Datadog/Tracer.swift @@ -97,7 +97,8 @@ public class Tracer: OTTracer { serviceName: tracerConfiguration.serviceName ?? tracingFeature.configuration.common.serviceName, userInfoProvider: tracingFeature.userInfoProvider, networkConnectionInfoProvider: tracerConfiguration.sendNetworkInfo ? tracingFeature.networkConnectionInfoProvider : nil, - carrierInfoProvider: tracerConfiguration.sendNetworkInfo ? tracingFeature.carrierInfoProvider : nil + carrierInfoProvider: tracerConfiguration.sendNetworkInfo ? tracingFeature.carrierInfoProvider : nil, + dateCorrection: tracingFeature.dateCorrection ), fileWriter: tracingFeature.storage.writer ), diff --git a/Sources/Datadog/Tracing/Span/SpanBuilder.swift b/Sources/Datadog/Tracing/Span/SpanBuilder.swift index 36d4f9a1c0..f4100f2b6e 100644 --- a/Sources/Datadog/Tracing/Span/SpanBuilder.swift +++ b/Sources/Datadog/Tracing/Span/SpanBuilder.swift @@ -20,6 +20,8 @@ internal struct SpanBuilder { let networkConnectionInfoProvider: NetworkConnectionInfoProviderType? /// Shared mobile carrier info provider (or `nil` if disabled for given tracer). let carrierInfoProvider: CarrierInfoProviderType? + /// Adjusts span's time (device time) to server time. + let dateCorrection: DateCorrectionType /// Encodes tag `Span` tag values as JSON string private let tagsJSONEncoder: JSONEncoder = .default() @@ -46,7 +48,7 @@ internal struct SpanBuilder { operationName: ddspan.operationName, serviceName: serviceName, resource: tagsReducer.extractedResourceName ?? ddspan.operationName, - startTime: ddspan.startTime, + startTime: dateCorrection.toServerDate(deviceDate: ddspan.startTime), duration: finishTime.timeIntervalSince(ddspan.startTime), isError: tagsReducer.extractedIsError ?? false, tracerVersion: sdkVersion, diff --git a/Sources/Datadog/Tracing/TracingFeature.swift b/Sources/Datadog/Tracing/TracingFeature.swift index 6ca2d666ad..ee6cd1ba5a 100644 --- a/Sources/Datadog/Tracing/TracingFeature.swift +++ b/Sources/Datadog/Tracing/TracingFeature.swift @@ -33,6 +33,7 @@ internal final class TracingFeature { // MARK: - Dependencies let dateProvider: DateProvider + let dateCorrection: DateCorrectionType let tracingUUIDGenerator: TracingUUIDGenerator let userInfoProvider: UserInfoProvider let networkConnectionInfoProvider: NetworkConnectionInfoProviderType @@ -81,9 +82,7 @@ internal final class TracingFeature { ), uploadURLProvider: UploadURLProvider( urlWithClientToken: configuration.uploadURLWithClientToken, - queryItemProviders: [ - .batchTime(using: commonDependencies.dateProvider) - ] + queryItemProviders: [] ), commonDependencies: commonDependencies ) @@ -124,6 +123,7 @@ internal final class TracingFeature { // Bundle dependencies self.dateProvider = commonDependencies.dateProvider + self.dateCorrection = commonDependencies.dateCorrection self.tracingUUIDGenerator = tracingUUIDGenerator self.userInfoProvider = commonDependencies.userInfoProvider self.networkConnectionInfoProvider = commonDependencies.networkConnectionInfoProvider diff --git a/Sources/Datadog/Utils/InternalLoggers.swift b/Sources/Datadog/Utils/InternalLoggers.swift index 351cc5935a..0fd2b63bf2 100644 --- a/Sources/Datadog/Utils/InternalLoggers.swift +++ b/Sources/Datadog/Utils/InternalLoggers.swift @@ -51,7 +51,8 @@ internal func createSDKDeveloperLogger( loggerName: "sdk-developer", userInfoProvider: configuration.userInfoProvider, networkConnectionInfoProvider: configuration.networkConnectionInfoProvider, - carrierInfoProvider: configuration.carrierInfoProvider + carrierInfoProvider: configuration.carrierInfoProvider, + dateCorrection: nil ), format: .shortWith(prefix: "🐶 → "), timeZone: timeZone, @@ -91,7 +92,8 @@ internal func createSDKUserLogger( loggerName: "sdk-user", userInfoProvider: configuration.userInfoProvider, networkConnectionInfoProvider: configuration.networkConnectionInfoProvider, - carrierInfoProvider: configuration.carrierInfoProvider + carrierInfoProvider: configuration.carrierInfoProvider, + dateCorrection: nil ), format: .shortWith(prefix: "[DATADOG SDK] 🐶 → "), timeZone: timeZone, diff --git a/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift b/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift index 0524ca2cc8..947af313e2 100644 --- a/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift +++ b/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift @@ -6,6 +6,12 @@ @testable import Datadog +private struct DateCorrectionMock: DateCorrectionType { + func toServerDate(deviceDate: Date) -> Date { + return deviceDate + } +} + extension FeaturesCommonDependencies { static func mockAny() -> Self { return .init( @@ -13,6 +19,7 @@ extension FeaturesCommonDependencies { httpClient: HTTPClient(), mobileDevice: .current, dateProvider: SystemDateProvider(), + dateCorrection: DateCorrectionMock(), userInfoProvider: UserInfoProvider(), networkConnectionInfoProvider: NetworkConnectionInfoProvider(), carrierInfoProvider: CarrierInfoProvider(), diff --git a/Tests/DatadogBenchmarkTests/DataUpload/DataUploaderBenchmarkTests.swift b/Tests/DatadogBenchmarkTests/DataUpload/DataUploaderBenchmarkTests.swift index 9a4858c8ce..bc56aaf0a9 100644 --- a/Tests/DatadogBenchmarkTests/DataUpload/DataUploaderBenchmarkTests.swift +++ b/Tests/DatadogBenchmarkTests/DataUpload/DataUploaderBenchmarkTests.swift @@ -25,7 +25,7 @@ class DataUploaderBenchmarkTests: BenchmarkTests { /// `DataUploader` leaves no memory footprint (the memory peak after upload is less or equal `0kB`). func testUploadingDataToServer_leavesNoMemoryFootprint() throws { let dataUploader = DataUploader( - urlProvider: mockUniqueUploadURLProvider(), + urlProvider: mockUploadURLProvider(), httpClient: HTTPClient(), httpHeaders: HTTPHeaders(headers: []) ) @@ -42,17 +42,10 @@ class DataUploaderBenchmarkTests: BenchmarkTests { } } - /// Creates the `UploadURLProvider` giving an unique URL for each upload. - /// URLs are differentiated by the value of `batch` query item. - private func mockUniqueUploadURLProvider() -> UploadURLProvider { - struct BatchTimeProvider: DateProvider { - func currentDate() -> Date { - Date(timeIntervalSince1970: .random(in: 0..<1_000_000)) - } - } + private func mockUploadURLProvider() -> UploadURLProvider { return UploadURLProvider( urlWithClientToken: server.obtainUniqueRecordingSession().recordingURL, - queryItemProviders: [.batchTime(using: BatchTimeProvider())] + queryItemProviders: [.ddtags(tags: ["foo:bar"])] ) } } diff --git a/Tests/DatadogIntegrationTests/Scenarios/Logging/LoggingCommonAsserts.swift b/Tests/DatadogIntegrationTests/Scenarios/Logging/LoggingCommonAsserts.swift index 3df6e130d2..710bbe79ba 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/Logging/LoggingCommonAsserts.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/Logging/LoggingCommonAsserts.swift @@ -21,8 +21,8 @@ extension LoggingCommonAsserts { requests.forEach { request in XCTAssertEqual(request.httpMethod, "POST") - // Example path here: `/36882784-420B-494F-910D-CBAC5897A309/ui-tests-client-token?ddsource=ios&batch_time=1589969230153` - let pathRegex = #"^(.*)(/ui-tests-client-token\?ddsource=ios&batch_time=)([0-9]+)$"# + // Example path here: `/36882784-420B-494F-910D-CBAC5897A309/ui-tests-client-token?ddsource=ios` + let pathRegex = #"^(.*)(/ui-tests-client-token\?ddsource=ios)$"# XCTAssertTrue( request.path.matches(regex: pathRegex), """ diff --git a/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMCommonAsserts.swift b/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMCommonAsserts.swift index 4c5258db66..7d5dd96f60 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMCommonAsserts.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMCommonAsserts.swift @@ -21,8 +21,8 @@ extension RUMCommonAsserts { requests.forEach { request in XCTAssertEqual(request.httpMethod, "POST") - // Example path here: `/36882784-420B-494F-910D-CBAC5897A309/ui-tests-client-token?ddsource=ios&batch_time=1576404000000&ddtags=service:ui-tests-service-name,version:1.0,sdk_version:1.3.0-beta3,env:integration` - let pathRegex = #"^(.*)(\/ui-tests-client-token\?ddsource=ios&batch_time=)([0-9]+)(&ddtags=service:ui-tests-service-name,version:1.0,sdk_version:)([0-9].[0-9].[0-9]([-a-z0-9])*)(,env:integration)$"# + // Example path here: `/36882784-420B-494F-910D-CBAC5897A309/ui-tests-client-token?ddsource=ios&&ddtags=service:ui-tests-service-name,version:1.0,sdk_version:1.3.0-beta3,env:integration` + let pathRegex = #"^(.*)(\/ui-tests-client-token\?ddsource=ios&ddtags=service:ui-tests-service-name,version:1.0,sdk_version:)([0-9].[0-9].[0-9]([-a-z0-9])*)(,env:integration)$"# XCTAssertTrue( request.path.matches(regex: pathRegex), """ diff --git a/Tests/DatadogIntegrationTests/Scenarios/Tracing/TracingCommonAsserts.swift b/Tests/DatadogIntegrationTests/Scenarios/Tracing/TracingCommonAsserts.swift index 45c37675ed..6d521f7484 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/Tracing/TracingCommonAsserts.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/Tracing/TracingCommonAsserts.swift @@ -33,8 +33,8 @@ extension TracingCommonAsserts { requests.forEach { request in XCTAssertEqual(request.httpMethod, "POST") - // Example path here: `/36882784-420B-494F-910D-CBAC5897A309/ui-tests-client-token?batch_time=1589969230153` - let pathRegex = #"^(.*)(/ui-tests-client-token\?batch_time=)([0-9]+)$"# + // Example path here: `/36882784-420B-494F-910D-CBAC5897A309/ui-tests-client-token` + let pathRegex = #"^(.*)(/ui-tests-client-token)$"# XCTAssertTrue( request.path.matches(regex: pathRegex), """ diff --git a/Tests/DatadogTests/Datadog/Core/System/Time/DateCorrectionTests.swift b/Tests/DatadogTests/Datadog/Core/System/Time/DateCorrectionTests.swift new file mode 100644 index 0000000000..30884b3549 --- /dev/null +++ b/Tests/DatadogTests/Datadog/Core/System/Time/DateCorrectionTests.swift @@ -0,0 +1,165 @@ +/* + * 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 class ServerDateProviderMock: ServerDateProvider { + private(set) var synchronizedNTPPool: String? = nil + var serverTime: Date? = nil + + init(using serverTime: Date? = nil) { + self.serverTime = serverTime + } + + func synchronize(with ntpPool: String, completion: @escaping (Date?) -> Void) { + synchronizedNTPPool = ntpPool + completion(self.serverTime) + } + + func currentDate() -> Date? { + return serverTime + } +} + +class DateCorrectionTests: XCTestCase { + private var previousUserLogger: Logger! // swiftlint:disable:this implicitly_unwrapped_optional + private var userLogOutput: LogOutputMock! // swiftlint:disable:this implicitly_unwrapped_optional + + override func setUp() { + super.setUp() + self.previousUserLogger = userLogger + self.userLogOutput = LogOutputMock() + userLogger = .mockWith(logOutput: userLogOutput) + } + + override func tearDown() { + userLogger = self.previousUserLogger + super.tearDown() + } + + func testWhenInitialized_itSynchronizesWithOneOfDatadogNTPServers() { + let serverDateProvider = ServerDateProviderMock() + let deviceDateProvider = SystemDateProvider() + + var randomlyChoosenServers: Set = [] + + (0..<100).forEach { _ in + _ = DateCorrection(deviceDateProvider: deviceDateProvider, serverDateProvider: serverDateProvider) + randomlyChoosenServers.insert(serverDateProvider.synchronizedNTPPool!) + } + + let allAvailableServers = Set(DateCorrection.datadogNTPServers) + XCTAssertEqual(randomlyChoosenServers, allAvailableServers, "Each time Datadog NTP server should be picked randomly.") + } + + func testWhenNTPSynchronizationSucceeds_itPrintsInfoMessage() throws { + let serverDateProvider = ServerDateProviderMock(using: .mockDecember15th2019At10AMUTC()) + let deviceDateProvider = RelativeDateProvider(using: .mockDecember15th2019At10AMUTC(addingTimeInterval: 1)) + + // When + _ = DateCorrection(deviceDateProvider: deviceDateProvider, serverDateProvider: serverDateProvider) + + // Then + let log = try XCTUnwrap(userLogOutput.recordedLog) + XCTAssertEqual(log.level, .info) + XCTAssertEqual( + log.message, + """ + NTP time synchronization completed. + Server time will be used for signing events (current server time is 2019-12-15 10:00:00 +0000; -1.0s difference with device time). + """ + ) + } + + func testWhenNTPSynchronizationFails_itPrintsWarning() throws { + let serverDateProvider = ServerDateProviderMock(using: nil) + let deviceDateProvider = RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) + + // When + _ = DateCorrection(deviceDateProvider: deviceDateProvider, serverDateProvider: serverDateProvider) + + // Then + let log = try XCTUnwrap(userLogOutput.recordedLog) + XCTAssertEqual(log.level, .warn) + XCTAssertEqual( + log.message, + """ + NTP time synchronization failed. + Device time will be used for signing events (current device time is 2019-12-15 10:00:00 +0000). + """ + ) + } + + func testWhenServerTimeIsNotAvailable_itDoesNoCorrection() { + let serverDateProvider = ServerDateProviderMock(using: nil) + let deviceDateProvider = RelativeDateProvider(using: .mockAny()) + + // When + let correction = DateCorrection(deviceDateProvider: deviceDateProvider, serverDateProvider: serverDateProvider) + + // Then + let randomDeviceTime: Date = .mockRandomInThePast() + XCTAssertEqual(correction.toServerDate(deviceDate: randomDeviceTime), randomDeviceTime) + } + + func testWhenServerTimeIsAvailable_itCorrectsDatesByTimeDifference() { + let serverDateProvider = ServerDateProviderMock(using: .mockRandomInThePast()) + let deviceDateProvider = RelativeDateProvider(using: .mockRandomInThePast()) + + func currentTimeDifference() -> TimeInterval { + serverDateProvider.currentDate()!.timeIntervalSince(deviceDateProvider.currentDate()) + } + + // When + let correction = DateCorrection(deviceDateProvider: deviceDateProvider, serverDateProvider: serverDateProvider) + + // Then + XCTAssertTrue( + datesEqual( + correction.toServerDate(deviceDate: deviceDateProvider.currentDate()), + serverDateProvider.currentDate()! + ), + "The device current time should be corrected to the server time." + ) + + let randomDeviceTime: Date = .mockRandomInThePast() + XCTAssertTrue( + datesEqual( + correction.toServerDate(deviceDate: randomDeviceTime), + randomDeviceTime.addingTimeInterval(currentTimeDifference()) + ), + "Any device time should be corrected by the server-to-device time difference." + ) + + serverDateProvider.serverTime = .mockRandomInThePast() + XCTAssertTrue( + datesEqual( + correction.toServerDate(deviceDate: randomDeviceTime), + randomDeviceTime.addingTimeInterval(currentTimeDifference()) + ), + "When the server time goes on, any next correction should include new server-to-device time difference." + ) + } + + /// As we randomize dates in this tests, they must be compared using some granularity, otherwise comparison may fail due to precission error. + private func datesEqual(_ date1: Date, _ date2: Date) -> Bool { + let calendar = Calendar.current + return calendar.compare(date1, to: date2, toGranularity: .nanosecond) == .orderedSame + } + + // MARK: - Thread Safety + + func testRandomlyCallingCorrectionConcurrentlyDoesNotCrash() { + let serverDateProvider = ServerDateProviderMock(using: .mockRandomInThePast()) + let deviceDateProvider = RelativeDateProvider(using: .mockRandomInThePast()) + let correction = DateCorrection(deviceDateProvider: deviceDateProvider, serverDateProvider: serverDateProvider) + + DispatchQueue.concurrentPerform(iterations: 50) { iteration in + _ = correction.toServerDate(deviceDate: .mockRandomInThePast()) + } + } +} diff --git a/Tests/DatadogTests/Datadog/Core/Upload/DataUploaderTests.swift b/Tests/DatadogTests/Datadog/Core/Upload/DataUploaderTests.swift index b27fdc5104..fdb0a3627d 100644 --- a/Tests/DatadogTests/Datadog/Core/Upload/DataUploaderTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Upload/DataUploaderTests.swift @@ -15,36 +15,23 @@ class DataUploadURLProviderTests: XCTestCase { XCTAssertEqual(item.value().value, "ios") } - func testBatchTimeQueryItem() { - let dateProvider = RelativeDateProvider(using: Date.mockDecember15th2019At10AMUTC()) - let item: UploadURLProvider.QueryItemProvider = .batchTime(using: dateProvider) - - XCTAssertEqual(item.value().name, "batch_time") - XCTAssertEqual(item.value().value, "1576404000000") - dateProvider.advance(bySeconds: 9.999) - XCTAssertEqual(item.value().name, "batch_time") - XCTAssertEqual(item.value().value, "1576404009999") - } - func testItBuildsValidURLUsingNoQueryItems() throws { let urlProvider = UploadURLProvider( urlWithClientToken: URL(string: "https://api.example.com/v1/endpoint/abc")!, queryItemProviders: [] ) - XCTAssertEqual(urlProvider.url, URL(string: "https://api.example.com/v1/endpoint/abc?")) + XCTAssertEqual(urlProvider.url, URL(string: "https://api.example.com/v1/endpoint/abc")) } func testItBuildsValidURLUsingAllQueryItems() throws { - let dateProvider = RelativeDateProvider(using: Date.mockDecember15th2019At10AMUTC()) let urlProvider = UploadURLProvider( urlWithClientToken: URL(string: "https://api.example.com/v1/endpoint/abc")!, - queryItemProviders: [.ddsource(), .batchTime(using: dateProvider)] + queryItemProviders: [.ddsource(), .ddtags(tags: ["abc:def"])] ) - XCTAssertEqual(urlProvider.url, URL(string: "https://api.example.com/v1/endpoint/abc?ddsource=ios&batch_time=1576404000000")) - dateProvider.advance(bySeconds: 9.999) - XCTAssertEqual(urlProvider.url, URL(string: "https://api.example.com/v1/endpoint/abc?ddsource=ios&batch_time=1576404009999")) + XCTAssertEqual(urlProvider.url, URL(string: "https://api.example.com/v1/endpoint/abc?ddsource=ios&ddtags=abc:def")) + XCTAssertEqual(urlProvider.url, URL(string: "https://api.example.com/v1/endpoint/abc?ddsource=ios&ddtags=abc:def")) } func testItEscapesWhitespacesInQueryItems() throws { diff --git a/Tests/DatadogTests/Datadog/LoggerTests.swift b/Tests/DatadogTests/Datadog/LoggerTests.swift index 8d73022465..2aec882269 100644 --- a/Tests/DatadogTests/Datadog/LoggerTests.swift +++ b/Tests/DatadogTests/Datadog/LoggerTests.swift @@ -648,6 +648,33 @@ class LoggerTests: XCTestCase { logMatcher.assertNoValue(forKeyPath: LoggingWithActiveSpanIntegration.Attributes.spanID) } + // MARK: - Log Dates Correction + + func testGivenTimeDifferenceBetweenDeviceAndServer_whenCollectingLogs_thenLogDateUsesServerTime() throws { + // Given + let deviceTime: Date = .mockDecember15th2019At10AMUTC() + let serverTimeDifference = TimeInterval.random(in: -5..<5).rounded() // few seconds difference + + // When + LoggingFeature.instance = .mockByRecordingLogMatchers( + directory: temporaryDirectory, + dependencies: .mockWith( + dateProvider: RelativeDateProvider(using: deviceTime), + dateCorrection: DateCorrectionMock(correctionOffset: serverTimeDifference) + ) + ) + defer { LoggingFeature.instance = nil } + + let logger = Logger.builder.build() + logger.debug("message") + + // Then + let logMatchers = try LoggingFeature.waitAndReturnLogMatchers(count: 1) + logMatchers[0].assertDate { logDate in + logDate == deviceTime.addingTimeInterval(serverTimeDifference) + } + } + // MARK: - Thread safety func testRandomlyCallingDifferentAPIsConcurrentlyDoesNotCrash() { diff --git a/Tests/DatadogTests/Datadog/Logging/LoggingFeatureTests.swift b/Tests/DatadogTests/Datadog/Logging/LoggingFeatureTests.swift index 234c24d7c0..fd032af266 100644 --- a/Tests/DatadogTests/Datadog/Logging/LoggingFeatureTests.swift +++ b/Tests/DatadogTests/Datadog/Logging/LoggingFeatureTests.swift @@ -35,8 +35,7 @@ class LoggingFeatureTests: XCTestCase { ) ), dependencies: .mockWith( - mobileDevice: .mockWith(model: "iPhone", osName: "iOS", osVersion: "13.3.1"), - dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) + mobileDevice: .mockWith(model: "iPhone", osName: "iOS", osVersion: "13.3.1") ) ) defer { LoggingFeature.instance = nil } @@ -46,7 +45,7 @@ class LoggingFeatureTests: XCTestCase { let request = server.waitAndReturnRequests(count: 1)[0] XCTAssertEqual(request.httpMethod, "POST") - XCTAssertEqual(request.url?.query, "ddsource=ios&batch_time=1576404000000") + XCTAssertEqual(request.url?.query, "ddsource=ios") XCTAssertEqual(request.allHTTPHeaderFields?["User-Agent"], "FoobarApp/2.1.0 CFNetwork (iPhone; iOS/13.3.1)") XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], "application/json") } diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 0e0f8d7c7b..773ee171c0 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -277,6 +277,7 @@ extension FeaturesCommonDependencies { } ), dateProvider: DateProvider = SystemDateProvider(), + dateCorrection: DateCorrectionType = DateCorrectionMock(), userInfoProvider: UserInfoProvider = .mockAny(), networkConnectionInfoProvider: NetworkConnectionInfoProviderType = NetworkConnectionInfoProviderMock.mockWith( networkConnectionInfo: .mockWith( @@ -296,6 +297,7 @@ extension FeaturesCommonDependencies { httpClient: HTTPClient(session: .serverMockURLSession), mobileDevice: mobileDevice, dateProvider: dateProvider, + dateCorrection: dateCorrection, userInfoProvider: userInfoProvider, networkConnectionInfoProvider: networkConnectionInfoProvider, carrierInfoProvider: carrierInfoProvider, @@ -372,6 +374,15 @@ class RelativeDateProvider: DateProvider { } } +/// `DateCorrectionType` mock, correcting dates by adding predefined offset. +struct DateCorrectionMock: DateCorrectionType { + var correctionOffset: TimeInterval = 0 + + func toServerDate(deviceDate: Date) -> Date { + return deviceDate.addingTimeInterval(correctionOffset) + } +} + struct LaunchTimeProviderMock: LaunchTimeProviderType { var launchTime: TimeInterval? = nil } diff --git a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift index 7d18410947..9ec4f46419 100644 --- a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift @@ -135,7 +135,8 @@ extension LogBuilder { loggerName: String = .mockAny(), userInfoProvider: UserInfoProvider = .mockAny(), networkConnectionInfoProvider: NetworkConnectionInfoProviderType = NetworkConnectionInfoProviderMock.mockAny(), - carrierInfoProvider: CarrierInfoProviderType = CarrierInfoProviderMock.mockAny() + carrierInfoProvider: CarrierInfoProviderType = CarrierInfoProviderMock.mockAny(), + dateCorrection: DateCorrectionType? = nil ) -> LogBuilder { return LogBuilder( applicationVersion: applicationVersion, @@ -144,7 +145,8 @@ extension LogBuilder { loggerName: loggerName, userInfoProvider: userInfoProvider, networkConnectionInfoProvider: networkConnectionInfoProvider, - carrierInfoProvider: carrierInfoProvider + carrierInfoProvider: carrierInfoProvider, + dateCorrection: dateCorrection ) } } diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index 50c3bd7587..1681502598 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -325,7 +325,8 @@ extension RUMScopeDependencies { ), eventBuilder: RUMEventBuilder = RUMEventBuilder(), eventOutput: RUMEventOutput = RUMEventOutputMock(), - rumUUIDGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator() + rumUUIDGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), + dateCorrection: DateCorrectionType = DateCorrectionMock() ) -> RUMScopeDependencies { return RUMScopeDependencies( userInfoProvider: userInfoProvider, @@ -333,7 +334,8 @@ extension RUMScopeDependencies { connectivityInfoProvider: connectivityInfoProvider, eventBuilder: eventBuilder, eventOutput: eventOutput, - rumUUIDGenerator: rumUUIDGenerator + rumUUIDGenerator: rumUUIDGenerator, + dateCorrection: dateCorrection ) } } diff --git a/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift index 953f4331af..47b0ca50a3 100644 --- a/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/TracingFeatureMocks.swift @@ -193,7 +193,8 @@ extension SpanBuilder { serviceName: String = .mockAny(), userInfoProvider: UserInfoProvider = .mockAny(), networkConnectionInfoProvider: NetworkConnectionInfoProviderType = NetworkConnectionInfoProviderMock.mockAny(), - carrierInfoProvider: CarrierInfoProviderType = CarrierInfoProviderMock.mockAny() + carrierInfoProvider: CarrierInfoProviderType = CarrierInfoProviderMock.mockAny(), + dateCorrection: DateCorrectionType = DateCorrectionMock() ) -> SpanBuilder { return SpanBuilder( applicationVersion: applicationVersion, @@ -201,7 +202,8 @@ extension SpanBuilder { serviceName: serviceName, userInfoProvider: userInfoProvider, networkConnectionInfoProvider: networkConnectionInfoProvider, - carrierInfoProvider: carrierInfoProvider + carrierInfoProvider: carrierInfoProvider, + dateCorrection: dateCorrection ) } } diff --git a/Tests/DatadogTests/Datadog/RUM/RUMFeatureTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMFeatureTests.swift index 4b807be5c2..b377768c6d 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMFeatureTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMFeatureTests.swift @@ -37,8 +37,7 @@ class RUMFeatureTests: XCTestCase { ) ), dependencies: .mockWith( - mobileDevice: .mockWith(model: "iPhone", osName: "iOS", osVersion: "13.3.1"), - dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) + mobileDevice: .mockWith(model: "iPhone", osName: "iOS", osVersion: "13.3.1") ) ) defer { RUMFeature.instance = nil } @@ -53,7 +52,7 @@ class RUMFeatureTests: XCTestCase { XCTAssertEqual( request.url?.query, """ - ddsource=ios&batch_time=1576404000000&ddtags=service:service-name,version:2.1.0,sdk_version:\(sdkVersion),env:environment-name + ddsource=ios&ddtags=service:service-name,version:2.1.0,sdk_version:\(sdkVersion),env:environment-name """ ) XCTAssertEqual(request.allHTTPHeaderFields?["User-Agent"], "FoobarApp/2.1.0 CFNetwork (iPhone; iOS/13.3.1)") diff --git a/Tests/DatadogTests/Datadog/RUMMonitorTests.swift b/Tests/DatadogTests/Datadog/RUMMonitorTests.swift index b815d7c2a4..9bd5b6ae00 100644 --- a/Tests/DatadogTests/Datadog/RUMMonitorTests.swift +++ b/Tests/DatadogTests/Datadog/RUMMonitorTests.swift @@ -600,6 +600,69 @@ class RUMMonitorTests: XCTestCase { try XCTAssertEqual(lastViewUpdate.attribute(forKeyPath: "context.a3"), "foo3", "The attribute should be added") } + // MARK: - RUM Events Dates Correction + + func testGivenTimeDifferenceBetweenDeviceAndServer_whenCollectingRUMEvents_thenEventsDateUseServerTime() throws { + // Given + let deviceTime: Date = .mockDecember15th2019At10AMUTC() + var serverTimeDifference = TimeInterval.random(in: 600..<1_200).rounded() // 10 - 20 minutes difference + serverTimeDifference = serverTimeDifference * (Bool.random() ? 1 : -1) // positive or negative difference + let dateProvider = RelativeDateProvider( + startingFrom: deviceTime, + advancingBySeconds: 1 // short advancing, so all events will be collected less than a minute after `deviceTime` + ) + + // When + RUMFeature.instance = .mockByRecordingRUMEventMatchers( + directory: temporaryDirectory, + dependencies: .mockWith( + dateProvider: dateProvider, + dateCorrection: DateCorrectionMock(correctionOffset: serverTimeDifference) + ) + ) + defer { RUMFeature.instance = nil } + + let monitor = RUMMonitor.initialize() + + monitor.startView(viewController: mockView) + monitor.addUserAction(type: .tap, name: .mockAny()) + monitor.startResourceLoading(resourceKey: "/resource/1", request: .mockAny()) + monitor.stopResourceLoading(resourceKey: "/resource/1", response: .mockAny()) + monitor.startResourceLoading(resourceKey: "/resource/2", url: .mockAny()) + monitor.stopResourceLoadingWithError(resourceKey: "/resource/2", errorMessage: .mockAny()) + monitor.addError(message: .mockAny()) + + // Then + let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 10) + let session = try RUMSessionMatcher.groupMatchersBySessions(rumEventMatchers)[0] + + let viewEvents = session.viewVisits[0].viewEvents + let actionEvents = session.viewVisits[0].actionEvents + let resourceEvents = session.viewVisits[0].resourceEvents + let errorEvents = session.viewVisits[0].errorEvents + + XCTAssertGreaterThan(viewEvents.count, 0) + XCTAssertGreaterThan(actionEvents.count, 0) + XCTAssertGreaterThan(resourceEvents.count, 0) + XCTAssertGreaterThan(errorEvents.count, 0) + + // All RUM events should be send later than or equal this earliest server time + let earliestServerTime = deviceTime.addingTimeInterval(serverTimeDifference).timeIntervalSince1970.toInt64Milliseconds + + viewEvents.forEach { view in + XCTAssertGreaterThanOrEqual(view.date, earliestServerTime, "Event `date` should be adjusted to server time") + } + actionEvents.forEach { action in + XCTAssertGreaterThanOrEqual(action.date, earliestServerTime, "Event `date` should be adjusted to server time") + } + resourceEvents.forEach { resource in + XCTAssertGreaterThanOrEqual(resource.date, earliestServerTime, "Event `date` should be adjusted to server time") + } + errorEvents.forEach { error in + XCTAssertGreaterThanOrEqual(error.date, earliestServerTime, "Event `date` should be adjusted to server time") + } + } + // MARK: - Thread safety func testRandomlyCallingDifferentAPIsConcurrentlyDoesNotCrash() { diff --git a/Tests/DatadogTests/Datadog/TracerTests.swift b/Tests/DatadogTests/Datadog/TracerTests.swift index 8c1825b421..4633c3f468 100644 --- a/Tests/DatadogTests/Datadog/TracerTests.swift +++ b/Tests/DatadogTests/Datadog/TracerTests.swift @@ -784,6 +784,42 @@ class TracerTests: XCTestCase { XCTAssertNil(extractedSpanContext?.dd.parentSpanID) } + // MARK: - Span Dates Correction + + func testGivenTimeDifferenceBetweenDeviceAndServer_whenCollectingSpans_thenSpanDateUsesServerTime() throws { + // Given + let deviceTime: Date = .mockDecember15th2019At10AMUTC() + let serverTimeDifference = TimeInterval.random(in: -5..<5).rounded() // few seconds difference + + // When + TracingFeature.instance = .mockByRecordingSpanMatchers( + directory: temporaryDirectory, + dependencies: .mockWith( + dateProvider: RelativeDateProvider(using: deviceTime), + dateCorrection: DateCorrectionMock(correctionOffset: serverTimeDifference) + ) + ) + defer { TracingFeature.instance = nil } + + let tracer = Tracer.initialize(configuration: .init()) + + let span = tracer.startSpan(operationName: .mockAny()) + span.finish(at: deviceTime.addingTimeInterval(2)) // 2 seconds long span + + // Then + let spanMatcher = try TracingFeature.waitAndReturnSpanMatchers(count: 1)[0] + XCTAssertEqual( + try spanMatcher.startTime(), + deviceTime.addingTimeInterval(serverTimeDifference).timeIntervalSince1970.toNanoseconds, + "The `startTime` should be using server time." + ) + XCTAssertEqual( + try spanMatcher.duration(), + 2_000_000_000, + "The `duration` should remain unaffected." + ) + } + // MARK: - Thread safety func testRandomlyCallingDifferentAPIsConcurrentlyDoesNotCrash() { diff --git a/Tests/DatadogTests/Datadog/Tracing/TracingFeatureTests.swift b/Tests/DatadogTests/Datadog/Tracing/TracingFeatureTests.swift index de31aacaa6..3c1eadc692 100644 --- a/Tests/DatadogTests/Datadog/Tracing/TracingFeatureTests.swift +++ b/Tests/DatadogTests/Datadog/Tracing/TracingFeatureTests.swift @@ -35,8 +35,7 @@ class TracingFeatureTests: XCTestCase { ) ), dependencies: .mockWith( - mobileDevice: .mockWith(model: "iPhone", osName: "iOS", osVersion: "13.3.1"), - dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) + mobileDevice: .mockWith(model: "iPhone", osName: "iOS", osVersion: "13.3.1") ) ) defer { TracingFeature.instance = nil } @@ -48,7 +47,7 @@ class TracingFeatureTests: XCTestCase { let request = server.waitAndReturnRequests(count: 1)[0] XCTAssertEqual(request.httpMethod, "POST") - XCTAssertEqual(request.url?.query, "batch_time=1576404000000") + XCTAssertNil(request.url?.query) XCTAssertEqual(request.allHTTPHeaderFields?["User-Agent"], "FoobarApp/2.1.0 CFNetwork (iPhone; iOS/13.3.1)") XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], "text/plain;charset=UTF-8") } diff --git a/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift b/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift index 547261d65f..6a6b5d2784 100644 --- a/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift +++ b/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift @@ -158,6 +158,13 @@ internal class RUMSessionMatcher { return firstVisitTime < secondVisitTime } + // Sort view events in each visit by document version + visits.forEach { visit in + visit.viewEvents = visit.viewEvents.sorted { viewUpdate1, viewUpdate2 in + viewUpdate1.dd.documentVersion < viewUpdate2.dd.documentVersion + } + } + self.viewVisits = visitsEventOrderedByTime } }