From f9a68846145f2c3f0c796888e5e908f812f0abfc Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Mon, 4 May 2020 19:32:27 +0200 Subject: [PATCH] RUMM-437 Encoding doesn't ignore miliseconds anymore --- Datadog/Datadog.xcodeproj/project.pbxproj | 12 +++++++ .../Datadog/Core/Persistence/FileWriter.swift | 6 +--- .../Core/Utils/ISO8601DateFormatter.swift | 24 ++++++++++++++ Sources/Datadog/Core/Utils/JSONEncoder.swift | 18 ++++++++++ .../Logs/LogOutputs/LogConsoleOutput.swift | 21 +++++------- Sources/Datadog/Utils/InternalLoggers.swift | 4 +-- Tests/DatadogTests/Datadog/LoggerTests.swift | 6 ++-- .../LogOutputs/LogConsoleOutputTests.swift | 4 +-- .../Datadog/Utils/EncodingTests.swift | 33 +++++++++++++++++++ .../Datadog/Utils/InternalLoggersTests.swift | 14 ++++---- .../DatadogObjc/DDLoggerTests.swift | 4 +-- Tests/DatadogTests/Matchers/LogMatcher.swift | 6 +++- 12 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 Sources/Datadog/Core/Utils/ISO8601DateFormatter.swift create mode 100644 Sources/Datadog/Core/Utils/JSONEncoder.swift create mode 100644 Tests/DatadogTests/Datadog/Utils/EncodingTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 8f95efe0d6..cf58d75aae 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -97,6 +97,9 @@ 9E08587A242519FF001A3583 /* NetworkPathMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E085879242519FF001A3583 /* NetworkPathMonitor.swift */; }; 9E2FB2722447660E001C9B7B /* Datadog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61133B82242393DE00786299 /* Datadog.framework */; }; 9E36D92224373EA700BFBDB7 /* SwiftExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */; }; + 9E58E8DF24615B89008E5063 /* ISO8601DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E58E8DE24615B89008E5063 /* ISO8601DateFormatter.swift */; }; + 9E58E8E124615C75008E5063 /* JSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E58E8E024615C75008E5063 /* JSONEncoder.swift */; }; + 9E58E8E324615EDA008E5063 /* EncodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E58E8E224615EDA008E5063 /* EncodingTests.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 = (Private, ); }; }; 9EA6A539244897A900621535 /* LoggingIOBenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA6A538244897A900621535 /* LoggingIOBenchmarkTests.swift */; }; @@ -254,6 +257,9 @@ 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExtensionsTests.swift; sourceTree = ""; }; 9E4195742449D739000AB0DB /* app-target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "app-target.xcconfig"; sourceTree = ""; }; 9E4195752449D739000AB0DB /* unit-tests-target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "unit-tests-target.xcconfig"; sourceTree = ""; }; + 9E58E8DE24615B89008E5063 /* ISO8601DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ISO8601DateFormatter.swift; sourceTree = ""; }; + 9E58E8E024615C75008E5063 /* JSONEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONEncoder.swift; sourceTree = ""; }; + 9E58E8E224615EDA008E5063 /* EncodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodingTests.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 = ""; }; 9E9EB37624468CE90002C80B /* Datadog.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Datadog.modulemap; sourceTree = ""; }; @@ -372,6 +378,8 @@ isa = PBXGroup; children = ( 61133BA02423979B00786299 /* EncodableValue.swift */, + 9E58E8DE24615B89008E5063 /* ISO8601DateFormatter.swift */, + 9E58E8E024615C75008E5063 /* JSONEncoder.swift */, ); path = Utils; sourceTree = ""; @@ -627,6 +635,7 @@ children = ( 61133C362423990D00786299 /* InternalLoggersTests.swift */, 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */, + 9E58E8E224615EDA008E5063 /* EncodingTests.swift */, ); path = Utils; sourceTree = ""; @@ -1008,12 +1017,14 @@ 61133BCF2423979B00786299 /* FileWriter.swift in Sources */, 61133BCC2423979B00786299 /* MobileDevice.swift in Sources */, 9E08587A242519FF001A3583 /* NetworkPathMonitor.swift in Sources */, + 9E58E8E124615C75008E5063 /* JSONEncoder.swift in Sources */, 61133BCA2423979B00786299 /* EncodableValue.swift in Sources */, 9E68FB55244707FD0013A8AA /* ObjcExceptionHandler.m in Sources */, 61C3638524361E9200C4D4E6 /* Globals.swift in Sources */, 61133BE62423979B00786299 /* LogSanitizer.swift in Sources */, 61133BDF2423979B00786299 /* SwiftExtensions.swift in Sources */, 61133BEA2423979B00786299 /* LogConsoleOutput.swift in Sources */, + 9E58E8DF24615B89008E5063 /* ISO8601DateFormatter.swift in Sources */, 61133BE32423979B00786299 /* UserInfo.swift in Sources */, 61133BE02423979B00786299 /* Datadog.swift in Sources */, 61133BCB2423979B00786299 /* CarrierInfoProvider.swift in Sources */, @@ -1067,6 +1078,7 @@ 61133C6A2423990D00786299 /* DatadogTests.swift in Sources */, 61133C5E2423990D00786299 /* LogsUploadDelayTests.swift in Sources */, 61133C5C2423990D00786299 /* DataUploadWorkerTests.swift in Sources */, + 9E58E8E324615EDA008E5063 /* EncodingTests.swift in Sources */, 61133C692423990D00786299 /* LogFileOutputTests.swift in Sources */, 61133C682423990D00786299 /* LogUtilityOutputsTests.swift in Sources */, 61133C6E2423990D00786299 /* DatadogExtensions.swift in Sources */, diff --git a/Sources/Datadog/Core/Persistence/FileWriter.swift b/Sources/Datadog/Core/Persistence/FileWriter.swift index cd5f5114d5..229fbc325d 100644 --- a/Sources/Datadog/Core/Persistence/FileWriter.swift +++ b/Sources/Datadog/Core/Persistence/FileWriter.swift @@ -19,11 +19,7 @@ internal final class FileWriter { init(orchestrator: FilesOrchestrator, queue: DispatchQueue) { self.orchestrator = orchestrator self.queue = queue - self.jsonEncoder = JSONEncoder() - jsonEncoder.dateEncodingStrategy = .iso8601 - if #available(iOS 13.0, OSX 10.15, *) { - jsonEncoder.outputFormatting = [.withoutEscapingSlashes] - } + self.jsonEncoder = JSONEncoder.default() } // MARK: - Writing data diff --git a/Sources/Datadog/Core/Utils/ISO8601DateFormatter.swift b/Sources/Datadog/Core/Utils/ISO8601DateFormatter.swift new file mode 100644 index 0000000000..f5a0bb3d04 --- /dev/null +++ b/Sources/Datadog/Core/Utils/ISO8601DateFormatter.swift @@ -0,0 +1,24 @@ +/* + * 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 + +extension ISO8601DateFormatter { + static func `default`() -> ISO8601DateFormatter { + let formatter = ISO8601DateFormatter() + formatter.formatOptions.insert(.withFractionalSeconds) + return formatter + } + + static var encodingStrategy: JSONEncoder.DateEncodingStrategy { + let formatter = Self.default() + return .custom { date, encoder in + var container = encoder.singleValueContainer() + let formatted = formatter.string(from: date) + try container.encode(formatted) + } + } +} diff --git a/Sources/Datadog/Core/Utils/JSONEncoder.swift b/Sources/Datadog/Core/Utils/JSONEncoder.swift new file mode 100644 index 0000000000..108c3f1bb7 --- /dev/null +++ b/Sources/Datadog/Core/Utils/JSONEncoder.swift @@ -0,0 +1,18 @@ +/* + * 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 + +extension JSONEncoder { + static func `default`() -> JSONEncoder { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = ISO8601DateFormatter.encodingStrategy + if #available(iOS 13.0, OSX 10.15, *) { + encoder.outputFormatting = [.withoutEscapingSlashes] + } + return encoder + } +} diff --git a/Sources/Datadog/Logs/LogOutputs/LogConsoleOutput.swift b/Sources/Datadog/Logs/LogOutputs/LogConsoleOutput.swift index b54fefb1ec..4bbaf11149 100644 --- a/Sources/Datadog/Logs/LogOutputs/LogConsoleOutput.swift +++ b/Sources/Datadog/Logs/LogOutputs/LogConsoleOutput.swift @@ -14,11 +14,9 @@ internal protocol ConsoleLogFormatter { /// `LogOutput` which prints logs to console. internal struct LogConsoleOutput: LogOutput { /// Time formatter used for `.short` output format. - static func shortTimeFormatter(calendar: Calendar = .current, timeZone: TimeZone = .current) -> DateFormatter { - let formatter = DateFormatter() - formatter.calendar = calendar - formatter.timeZone = timeZone - formatter.dateFormat = "HH:mm:ss" + static func shortTimeFormatter(calendar: Calendar = .current, timeZone: TimeZone = .current) -> Formatter { + let formatter = ISO8601DateFormatter.default() + formatter.formatOptions = [.withFractionalSeconds, .withFullTime] return formatter } @@ -30,7 +28,7 @@ internal struct LogConsoleOutput: LogOutput { logBuilder: LogBuilder, format: Logger.Builder.ConsoleLogFormat, printingFunction: @escaping (String) -> Void = { consolePrint($0) }, - timeFormatter: DateFormatter = LogConsoleOutput.shortTimeFormatter() + timeFormatter: Formatter = LogConsoleOutput.shortTimeFormatter() ) { switch format { case .short: @@ -60,8 +58,7 @@ private struct JSONLogFormatter: ConsoleLogFormatter { private let prefix: String init(prefix: String = "") { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 + let encoder = JSONEncoder.default() encoder.outputFormatting = .prettyPrinted self.encoder = encoder self.prefix = prefix @@ -79,17 +76,17 @@ private struct JSONLogFormatter: ConsoleLogFormatter { /// Formats log as custom short string. private struct ShortLogFormatter: ConsoleLogFormatter { - private let timeFormatter: DateFormatter + private let timeFormatter: Formatter private let prefix: String - init(timeFormatter: DateFormatter, prefix: String = "") { + init(timeFormatter: Formatter, prefix: String = "") { self.timeFormatter = timeFormatter self.prefix = prefix } func format(log: Log) -> String { - let time = timeFormatter.string(from: log.date) + let time = timeFormatter.string(for: log.date) let status = log.status.rawValue.uppercased() - return "\(prefix)\(time) [\(status)] \(log.message)" + return "\(prefix)\(time ?? "null") [\(status)] \(log.message)" } } diff --git a/Sources/Datadog/Utils/InternalLoggers.swift b/Sources/Datadog/Utils/InternalLoggers.swift index 81ba5506a9..885b41a5bd 100644 --- a/Sources/Datadog/Utils/InternalLoggers.swift +++ b/Sources/Datadog/Utils/InternalLoggers.swift @@ -23,7 +23,7 @@ internal var userLogger = createSDKUserLogger() internal func createSDKDeveloperLogger( consolePrintFunction: @escaping (String) -> Void = { consolePrint($0) }, dateProvider: DateProvider = SystemDateProvider(), - timeFormatter: DateFormatter = LogConsoleOutput.shortTimeFormatter() + timeFormatter: Formatter = LogConsoleOutput.shortTimeFormatter() ) -> Logger? { if CompilationConditions.isSDKCompiledForDevelopment == false { return nil @@ -54,7 +54,7 @@ internal func createSDKDeveloperLogger( internal func createSDKUserLogger( consolePrintFunction: @escaping (String) -> Void = { consolePrint($0) }, dateProvider: DateProvider = SystemDateProvider(), - timeFormatter: DateFormatter = LogConsoleOutput.shortTimeFormatter() + timeFormatter: Formatter = LogConsoleOutput.shortTimeFormatter() ) -> Logger { guard let loggingFeature = LoggingFeature.instance else { return Logger(logOutput: NoOpLogOutput(), identifier: "no-op") diff --git a/Tests/DatadogTests/Datadog/LoggerTests.swift b/Tests/DatadogTests/Datadog/LoggerTests.swift index eeaa4db460..802162a64d 100644 --- a/Tests/DatadogTests/Datadog/LoggerTests.swift +++ b/Tests/DatadogTests/Datadog/LoggerTests.swift @@ -51,7 +51,7 @@ class LoggerTests: XCTestCase { "logger.name" : "com.datadoghq.ios-sdk", "logger.version": "\(sdkVersion)", "logger.thread_name" : "main", - "date" : "2019-12-15T10:00:00Z", + "date" : "2019-12-15T10:00:00.000Z", "application.version": "1.0.0" } """) @@ -310,8 +310,8 @@ class LoggerTests: XCTestCase { logMatcher.assertValue(forKey: "uint-8", equals: UInt8(10)) logMatcher.assertValue(forKey: "double", equals: 10.5) logMatcher.assertValue(forKey: "array-of-int", equals: [1, 2, 3]) - logMatcher.assertValue(forKeyPath: "dictionary-of-date.date1", equals: "2019-12-15T10:00:00Z") - logMatcher.assertValue(forKeyPath: "dictionary-of-date.date2", equals: "2019-12-15T11:00:00Z") + logMatcher.assertValue(forKeyPath: "dictionary-of-date.date1", equals: "2019-12-15T10:00:00.000Z") + logMatcher.assertValue(forKeyPath: "dictionary-of-date.date2", equals: "2019-12-15T11:00:00.000Z") logMatcher.assertValue(forKeyPath: "person.name", equals: "Adam") logMatcher.assertValue(forKeyPath: "person.age", equals: 30) logMatcher.assertValue(forKeyPath: "person.nationality", equals: "Polish") diff --git a/Tests/DatadogTests/Datadog/Logs/LogOutputs/LogConsoleOutputTests.swift b/Tests/DatadogTests/Datadog/Logs/LogOutputs/LogConsoleOutputTests.swift index f3bcf066e7..ae882035ac 100644 --- a/Tests/DatadogTests/Datadog/Logs/LogOutputs/LogConsoleOutputTests.swift +++ b/Tests/DatadogTests/Datadog/Logs/LogOutputs/LogConsoleOutputTests.swift @@ -19,7 +19,7 @@ class LogConsoleOutputTests: XCTestCase { timeFormatter: LogConsoleOutput.shortTimeFormatter(calendar: .gregorian, timeZone: .UTC) ) output1.writeLogWith(level: .info, message: "Info message.", attributes: [:], tags: []) - XCTAssertEqual(messagePrinted, "10:00:00 [INFO] Info message.") + XCTAssertEqual(messagePrinted, "10:00:00.000Z [INFO] Info message.") let output2 = LogConsoleOutput( logBuilder: .mockWith(date: .mockDecember15th2019At10AMUTC()), @@ -28,7 +28,7 @@ class LogConsoleOutputTests: XCTestCase { timeFormatter: LogConsoleOutput.shortTimeFormatter(calendar: .gregorian, timeZone: .UTC) ) output2.writeLogWith(level: .info, message: "Info message.", attributes: [:], tags: []) - XCTAssertEqual(messagePrinted, "🐶 10:00:00 [INFO] Info message.") + XCTAssertEqual(messagePrinted, "🐶 10:00:00.000Z [INFO] Info message.") } func testItPrintsLogsUsingJSONFormat() throws { diff --git a/Tests/DatadogTests/Datadog/Utils/EncodingTests.swift b/Tests/DatadogTests/Datadog/Utils/EncodingTests.swift new file mode 100644 index 0000000000..cb9f2b4575 --- /dev/null +++ b/Tests/DatadogTests/Datadog/Utils/EncodingTests.swift @@ -0,0 +1,33 @@ +/* + * 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 DateFormatterTests: XCTestCase { + func testISO8601FormatWithSubSecondPrecision() throws { + let dateFormatter = ISO8601DateFormatter.default() + + let knownDate: Date = .mockDecember15th2019At10AMUTC(addingTimeInterval: 0.123) + let formattedDate = dateFormatter.string(from: knownDate) + + XCTAssertEqual(formattedDate, "2019-12-15T10:00:00.123Z") + } +} + +class EncodingTests: XCTestCase { + func testEncodingDateWithSubSecondPrecision() throws { + let jsonEncoder = JSONEncoder.default() + + let knownDate: Date = .mockDecember15th2019At10AMUTC(addingTimeInterval: 0.123) + let encodedKnownDate = try jsonEncoder.encode(knownDate) + + let jsonDecoder = JSONDecoder() + let knownDateDecodedString = try jsonDecoder.decode(String.self, from: encodedKnownDate) + + XCTAssertEqual(knownDateDecodedString, "2019-12-15T10:00:00.123Z") + } +} diff --git a/Tests/DatadogTests/Datadog/Utils/InternalLoggersTests.swift b/Tests/DatadogTests/Datadog/Utils/InternalLoggersTests.swift index e13099950a..b124a6b477 100644 --- a/Tests/DatadogTests/Datadog/Utils/InternalLoggersTests.swift +++ b/Tests/DatadogTests/Datadog/Utils/InternalLoggersTests.swift @@ -44,12 +44,12 @@ class InternalLoggersTests: XCTestCase { } private let expectedMessages = [ - "[DATADOG SDK] 🐶 → 10:00:00 [DEBUG] message", - "[DATADOG SDK] 🐶 → 10:00:00 [INFO] message", - "[DATADOG SDK] 🐶 → 10:00:00 [NOTICE] message", - "[DATADOG SDK] 🐶 → 10:00:00 [WARN] message", - "[DATADOG SDK] 🐶 → 10:00:00 [ERROR] message", - "[DATADOG SDK] 🐶 → 10:00:00 [CRITICAL] message" + "[DATADOG SDK] 🐶 → 10:00:00.000Z [DEBUG] message", + "[DATADOG SDK] 🐶 → 10:00:00.000Z [INFO] message", + "[DATADOG SDK] 🐶 → 10:00:00.000Z [NOTICE] message", + "[DATADOG SDK] 🐶 → 10:00:00.000Z [WARN] message", + "[DATADOG SDK] 🐶 → 10:00:00.000Z [ERROR] message", + "[DATADOG SDK] 🐶 → 10:00:00.000Z [CRITICAL] message" ] func testUserLoggerDoesNothingWithDefaultVerbosityLevel() { @@ -120,6 +120,6 @@ class InternalLoggersTests: XCTestCase { developerLogger?.info("It works.") XCTAssertNotNil(developerLogger) - XCTAssertEqual(printedMessage, "🐶 → 10:00:00 [INFO] It works.") + XCTAssertEqual(printedMessage, "🐶 → 10:00:00.000Z [INFO] It works.") } } diff --git a/Tests/DatadogTests/DatadogObjc/DDLoggerTests.swift b/Tests/DatadogTests/DatadogObjc/DDLoggerTests.swift index 79240b9455..6476b38c9a 100644 --- a/Tests/DatadogTests/DatadogObjc/DDLoggerTests.swift +++ b/Tests/DatadogTests/DatadogObjc/DDLoggerTests.swift @@ -110,8 +110,8 @@ class DDLoggerTests: XCTestCase { logMatcher.assertValue(forKeyPath: "nsnull", isTypeOf: Optional.self) logMatcher.assertValue(forKey: "nsurl", equals: "http://apple.com") logMatcher.assertValue(forKey: "nsarray-of-int", equals: [1, 2, 3]) - logMatcher.assertValue(forKeyPath: "nsdictionary-of-date.date1", equals: "2019-12-15T10:00:00Z") - logMatcher.assertValue(forKeyPath: "nsdictionary-of-date.date2", equals: "2019-12-15T11:00:00Z") + logMatcher.assertValue(forKeyPath: "nsdictionary-of-date.date1", equals: "2019-12-15T10:00:00.000Z") + logMatcher.assertValue(forKeyPath: "nsdictionary-of-date.date2", equals: "2019-12-15T11:00:00.000Z") } } // swiftlint:enable multiline_arguments_brackets diff --git a/Tests/DatadogTests/Matchers/LogMatcher.swift b/Tests/DatadogTests/Matchers/LogMatcher.swift index b373f260e3..7978166e1b 100644 --- a/Tests/DatadogTests/Matchers/LogMatcher.swift +++ b/Tests/DatadogTests/Matchers/LogMatcher.swift @@ -9,7 +9,11 @@ import XCTest /// Provides set of assertions for Log JSON object. /// Note: this file is individually referenced by integration tests project, so no dependency on other source files should be introduced. struct LogMatcher { - private static let dateFormatter = ISO8601DateFormatter() + private static let dateFormatter: ISO8601DateFormatter = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions.insert(.withFractionalSeconds) + return formatter + }() /// Log JSON keys. struct JSONKey {