From ed023e3c7ecf1fee05ad6060b7c673857e7fe395 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Thu, 3 Oct 2024 12:44:15 +0200 Subject: [PATCH] RUM-6395: Add internal logging/telemetry APIs to ObjC --- CHANGELOG.md | 3 + Datadog/Datadog.xcodeproj/project.pbxproj | 18 ++++ .../DatadogObjc/DDInternalLoggerTests.swift | 89 +++++++++++++++++++ .../ObjcAPITests/DDInternalLogger+apiTests.m | 25 ++++++ .../Sources/DDInternalLogger+objc.swift | 42 +++++++++ api-surface-objc | 9 ++ 6 files changed, 186 insertions(+) create mode 100644 DatadogCore/Tests/DatadogObjc/DDInternalLoggerTests.swift create mode 100644 DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDInternalLogger+apiTests.m create mode 100644 DatadogObjc/Sources/DDInternalLogger+objc.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index d8a7aecbdb..764fd57a8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- [IMPROVEMENT] Add ObjC API for the internal logging/telemetry. See [#2073][] + # 2.18.0 / 25-09-2024 - [IMPROVEMENT] Add overwrite required (breaking) param to addViewLoadingTime & usage telemetry. See [#2040][] - [FEATURE] Prevent "show password" features from revealing sensitive texts in Session Replay. See [#2050][] @@ -774,6 +776,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#2043]: https://github.com/DataDog/dd-sdk-ios/pull/2043 [#2040]: https://github.com/DataDog/dd-sdk-ios/pull/2040 [#2050]: https://github.com/DataDog/dd-sdk-ios/pull/2050 +[#2073]: https://github.com/DataDog/dd-sdk-ios/pull/2073 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 260354e8cf..638d7a4191 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -1705,6 +1705,12 @@ E2AA55E82C32C6D9002FEF28 /* ApplicationNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */; }; E2AA55EA2C32C76A002FEF28 /* WatchKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */; }; E2AA55EC2C32C78B002FEF28 /* WatchKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */; }; + F603F1262CAE9F760088E6B7 /* DDInternalLogger+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = F603F1252CAE9F760088E6B7 /* DDInternalLogger+objc.swift */; }; + F603F1272CAE9F760088E6B7 /* DDInternalLogger+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = F603F1252CAE9F760088E6B7 /* DDInternalLogger+objc.swift */; }; + F603F12B2CAEA4FA0088E6B7 /* DDInternalLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F603F1282CAEA4E90088E6B7 /* DDInternalLoggerTests.swift */; }; + F603F12C2CAEA7180088E6B7 /* DDInternalLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F603F1282CAEA4E90088E6B7 /* DDInternalLoggerTests.swift */; }; + F603F1302CAEA7620088E6B7 /* DDInternalLogger+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F603F12D2CAEA7590088E6B7 /* DDInternalLogger+apiTests.m */; }; + F603F1312CAEA7630088E6B7 /* DDInternalLogger+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F603F12D2CAEA7590088E6B7 /* DDInternalLogger+apiTests.m */; }; F6E106542C75E0D000716DC6 /* LogsDataModels+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E106532C75E0D000716DC6 /* LogsDataModels+objc.swift */; }; F6E106552C75E0D000716DC6 /* LogsDataModels+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E106532C75E0D000716DC6 /* LogsDataModels+objc.swift */; }; /* End PBXBuildFile section */ @@ -3067,6 +3073,9 @@ E1D5AEA624B4D45A007F194B /* Versioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Versioning.swift; sourceTree = ""; }; E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationNotifications.swift; sourceTree = ""; }; E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchKitExtensions.swift; sourceTree = ""; }; + F603F1252CAE9F760088E6B7 /* DDInternalLogger+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DDInternalLogger+objc.swift"; sourceTree = ""; }; + F603F1282CAEA4E90088E6B7 /* DDInternalLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDInternalLoggerTests.swift; sourceTree = ""; }; + F603F12D2CAEA7590088E6B7 /* DDInternalLogger+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDInternalLogger+apiTests.m"; sourceTree = ""; }; F637AED12697404200516F32 /* UIKitRUMUserActionsPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitRUMUserActionsPredicate.swift; sourceTree = ""; }; F6E106532C75E0D000716DC6 /* LogsDataModels+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LogsDataModels+objc.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -4281,6 +4290,7 @@ 6111C58025C0080C00F5C4A2 /* RUM */, 6132BF4024A38D0600D7BD17 /* OpenTracing */, D2A434A72A8E3FFB0028E329 /* SessionReplay */, + F603F1252CAE9F760088E6B7 /* DDInternalLogger+objc.swift */, ); name = DatadogObjc; path = ../DatadogObjc/Sources; @@ -4319,6 +4329,7 @@ 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */, D2A434AD2A8E426C0028E329 /* DDSessionReplayTests.swift */, 61D03BDE273404BB00367DE0 /* RUM */, + F603F1282CAEA4E90088E6B7 /* DDInternalLoggerTests.swift */, ); path = DatadogObjc; sourceTree = ""; @@ -5240,6 +5251,7 @@ 3C1890132ABDE99200CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m */, A795069D2B974CAA00AC4814 /* DDSessionReplay+apiTests.m */, 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */, + F603F12D2CAEA7590088E6B7 /* DDInternalLogger+apiTests.m */, ); path = ObjcAPITests; sourceTree = ""; @@ -8189,6 +8201,7 @@ 61133C572423990D00786299 /* FileReaderTests.swift in Sources */, D2A1EE38287EEB7400D28DFB /* NetworkConnectionInfoPublisherTests.swift in Sources */, D24C9C7129A7D57A002057CF /* DirectoriesMock.swift in Sources */, + F603F1302CAEA7620088E6B7 /* DDInternalLogger+apiTests.m in Sources */, D22743E329DEB90B001A7EF9 /* RUMDebuggingTests.swift in Sources */, 614798992A459B2E0095CB02 /* DDTraceConfigurationTests.swift in Sources */, 61DCC84A2C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift in Sources */, @@ -8282,6 +8295,7 @@ D2553807288AA84F00727FAD /* UploadMock.swift in Sources */, D28F836C29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift in Sources */, D22743E629DEB953001A7EF9 /* UIApplicationSwizzlerTests.swift in Sources */, + F603F12B2CAEA4FA0088E6B7 /* DDInternalLoggerTests.swift in Sources */, D20FD9D62ACC0934004D3569 /* WebLogIntegrationTests.swift in Sources */, D21C26D128A64599005DD405 /* MessageBusTests.swift in Sources */, ); @@ -8298,6 +8312,7 @@ A728ADAB2934EA2100397996 /* W3CHTTPHeadersWriter+objc.swift in Sources */, A79B0F66292BD7CA008742B3 /* B3HTTPHeadersWriter+objc.swift in Sources */, 3CCCA5C42ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift in Sources */, + F603F1262CAE9F760088E6B7 /* DDInternalLogger+objc.swift in Sources */, 61133C0E2423983800786299 /* Datadog+objc.swift in Sources */, 3CA852642BF2148200B52CBA /* TraceContextInjection+objc.swift in Sources */, 61133C102423983800786299 /* Logs+objc.swift in Sources */, @@ -9492,6 +9507,8 @@ 6136CB4B2A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift in Sources */, D25085112976E30000E931C3 /* DatadogRemoteFeatureMock.swift in Sources */, A7CA21842BEBB2E200732571 /* ExtensionBackgroundTaskCoordinatorTests.swift in Sources */, + F603F1312CAEA7630088E6B7 /* DDInternalLogger+apiTests.m in Sources */, + F603F12C2CAEA7180088E6B7 /* DDInternalLoggerTests.swift in Sources */, D2CB6F3327C520D400A62B57 /* FilesOrchestratorTests.swift in Sources */, D2FB1258292E0F10005B13F8 /* TrackingConsentPublisherTests.swift in Sources */, D2CB6F3B27C520D400A62B57 /* NSURLSessionBridge.m in Sources */, @@ -9572,6 +9589,7 @@ files = ( F6E106552C75E0D000716DC6 /* LogsDataModels+objc.swift in Sources */, D2CB6F9927C5217A00A62B57 /* Casting.swift in Sources */, + F603F1272CAE9F760088E6B7 /* DDInternalLogger+objc.swift in Sources */, D2CB6F9A27C5217A00A62B57 /* RUMDataModels+objc.swift in Sources */, D2CB6F9B27C5217A00A62B57 /* DDSpanContext+objc.swift in Sources */, D2CB6F9C27C5217A00A62B57 /* OTTracer+objc.swift in Sources */, diff --git a/DatadogCore/Tests/DatadogObjc/DDInternalLoggerTests.swift b/DatadogCore/Tests/DatadogObjc/DDInternalLoggerTests.swift new file mode 100644 index 0000000000..ffb2074a5d --- /dev/null +++ b/DatadogCore/Tests/DatadogObjc/DDInternalLoggerTests.swift @@ -0,0 +1,89 @@ +/* +* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +* This product includes software developed at Datadog (https://www.datadoghq.com/). +* Copyright 2019-Present Datadog, Inc. +*/ + +import XCTest +import TestUtilities + +@testable import DatadogInternal +@testable import DatadogCore +@testable import DatadogObjc + +class DDInternalLoggerTests: XCTestCase { + let telemetry = TelemetryReceiverMock() + + private var core: PassthroughCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional + + override func setUp() { + super.setUp() + core = PassthroughCoreMock(messageReceiver: telemetry) + } + + override func tearDown() { + core = nil + super.tearDown() + } + + func testObjcTelemetryDebugCallsTelemetryDebug() throws { + CoreRegistry.register(default: core) + defer { CoreRegistry.unregisterDefault() } + + // Given + let id: String = .mockAny() + let message: String = .mockAny() + + // When + DDInternalLogger.telemetryDebug(id: id, message: message) + + // Then + XCTAssertEqual(telemetry.messages.count, 1) + let debug = try XCTUnwrap(telemetry.messages.first?.asDebug, "A debug should be send to `telemetry`.") + XCTAssertEqual(debug.id, id) + XCTAssertEqual(debug.message, message) + } + + func testObjcTelemetryErrorCallsTelemetryError() throws { + CoreRegistry.register(default: core) + defer { CoreRegistry.unregisterDefault() } + + // Given + let id: String = .mockAny() + let message: String = .mockAny() + let stack: String = .mockAny() + let kind: String = .mockAny() + + // When + DDInternalLogger.telemetryError(id: id, message: message, kind: kind, stack: stack) + + // Then + XCTAssertEqual(telemetry.messages.count, 1) + + let error = try XCTUnwrap(telemetry.messages.first?.asError, "An error should be send to `telemetry`.") + XCTAssertEqual(error.id, id) + XCTAssertEqual(error.message, message) + XCTAssertEqual(error.kind, kind) + XCTAssertEqual(error.stack, stack) + } + + func testWhenTelemetryIsSentThroughObjc_thenItForwardsToDDTelemetry() throws { + CoreRegistry.register(default: core) + defer { CoreRegistry.unregisterDefault() } + + // When + let randomDebugMessage: String = .mockRandom() + let randomErrorMessage: String = .mockRandom() + DDInternalLogger.telemetryDebug(id: .mockAny(), message: randomDebugMessage) + DDInternalLogger.telemetryError(id: .mockAny(), message: randomErrorMessage, kind: .mockAny(), stack: .mockAny()) + + // Then + XCTAssertEqual(telemetry.messages.count, 2) + + let debug = try XCTUnwrap(telemetry.messages.first?.asDebug, "A debug should be send to `telemetry`.") + XCTAssertEqual(debug.message, randomDebugMessage) + + let error = try XCTUnwrap(telemetry.messages.last?.asError, "An error should be send to `telemetry`.") + XCTAssertEqual(error.message, randomErrorMessage) + } +} diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDInternalLogger+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDInternalLogger+apiTests.m new file mode 100644 index 0000000000..dd19b37dcd --- /dev/null +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDInternalLogger+apiTests.m @@ -0,0 +1,25 @@ +/* +* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +* This product includes software developed at Datadog (https://www.datadoghq.com/). +* Copyright 2019-Present Datadog, Inc. +*/ + +#import +@import DatadogObjc; + +@interface DDInternalLogger_apiTests : XCTestCase +@end + +/* + * `DDInternalLogger` APIs smoke tests - only check if the interface is available to Objc. + */ +@implementation DDInternalLogger_apiTests + +- (void)testDDInternalLogger { + + [DDInternalLogger consolePrint:@"" :DDCoreLoggerLevelWarn]; + [DDInternalLogger telemetryDebugWithId:@"" message:@""]; + [DDInternalLogger telemetryErrorWithId:@"" message:@"" kind:@"" stack:@""]; +} + +@end diff --git a/DatadogObjc/Sources/DDInternalLogger+objc.swift b/DatadogObjc/Sources/DDInternalLogger+objc.swift new file mode 100644 index 0000000000..17125114c5 --- /dev/null +++ b/DatadogObjc/Sources/DDInternalLogger+objc.swift @@ -0,0 +1,42 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation +import DatadogInternal +import DatadogCore + +@objc +public class DDInternalLogger: NSObject { + /// Function printing `String` content to console. Intended to be used only by SDK components. + @objc + public static func consolePrint(_ message: String, _ level: DDCoreLoggerLevel) { + let coreLoggerLevel: CoreLoggerLevel = switch level { + case .debug: .debug + case .warn: .warn + case .error: .error + case .critical: .critical + } + DatadogInternal.consolePrint(message, coreLoggerLevel) + } + + @objc + public static func telemetryDebug(id: String, message: String) { + Datadog._internal.telemetry.debug(id: id, message: message) + } + + @objc + public static func telemetryError(id: String, message: String, kind: String?, stack: String?) { + Datadog._internal.telemetry.error(id: id, message: message, kind: kind, stack: stack) + } +} + +@objc +public enum DDCoreLoggerLevel: Int { + case debug + case warn + case error + case critical +} diff --git a/api-surface-objc b/api-surface-objc index fbeea6cb9c..540786f163 100644 --- a/api-surface-objc +++ b/api-surface-objc @@ -2,6 +2,15 @@ # API surface for DatadogObjc: # ---------------------------------- +public class DDInternalLogger: NSObject + public static func consolePrint(_ message: String, _ level: DDCoreLoggerLevel) + public static func telemetryDebug(id: String, message: String) + public static func telemetryError(id: String, message: String, kind: String?, stack: String?) +public enum DDCoreLoggerLevel: Int + case debug + case warn + case error + case critical open class DDNSURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate override public init() public init(additionalFirstPartyHostsWithHeaderTypes: [String: Set])