diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index f5b7c052a..ed13b8327 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -149,7 +149,7 @@ jobs: -Dsonar.objc.file.suffixes=- -Dsonar.coverageReportPaths=sonarqube-generic-coverage.xml -Dsonar.verbose=true - -Dsonar.sources=Split + -Dsonar.sources=Split,Sources -Dsonar.exclusions=**/Tests/**,**/*.generated.swift -Dsonar.coverage.exclusions=**/Tests/**,**/*.generated.swift -Dsonar.qualitygate.wait=true @@ -179,7 +179,7 @@ jobs: -Dsonar.objc.file.suffixes=- -Dsonar.coverageReportPaths=sonarqube-generic-coverage.xml -Dsonar.verbose=true - -Dsonar.sources=Split + -Dsonar.sources=Split,Sources -Dsonar.exclusions=**/Tests/**,**/*.generated.swift -Dsonar.coverage.exclusions=**/Tests/**,**/*.generated.swift -Dsonar.qualitygate.wait=true diff --git a/Package.swift b/Package.swift index f175b80ea..610db0f67 100644 --- a/Package.swift +++ b/Package.swift @@ -6,17 +6,32 @@ let package = Package( name: "Split", platforms: [.iOS(.v9), .macOS(.v10_11), .watchOS(.v7), .tvOS(.v9)], products: [ - .library(name: "Split", targets: ["Split"]) - ], + .library(name: "Split", targets: ["Split"]), + + .library(name: "SplitCommons", targets: ["Logging"]),], targets: [ .target( name: "Split", + dependencies: ["Logging"], path: "Split", exclude: [ "Common/Yaml/LICENSE", "Info.plist", "Split.h" ] - ) + ), + + .target( + name: "Logging", + dependencies: [], + path: "Sources/Logging", + exclude: ["Tests", "README.md"] + ), + .testTarget( + name: "LoggingTests", + dependencies: ["Logging"], + path: "Sources/Logging/Tests" + ), + // #INJECT_TARGET ] ) diff --git a/Sources/Logging/DateProvider.swift b/Sources/Logging/DateProvider.swift new file mode 100644 index 000000000..f52d466d3 --- /dev/null +++ b/Sources/Logging/DateProvider.swift @@ -0,0 +1,36 @@ +// +// DateProvider.swift +// Logging +// +// Created by Split SDK Team +// + +import Foundation + +/// Protocol for providing date/time functionality to the logging system +public protocol DateProvider { + /// Returns current time in milliseconds since epoch + func nowMillis() -> Int64 + + /// Returns a formatted timestamp string for logging + func nowLabel() -> String +} + +/// Default implementation that uses Foundation's Date +public struct DefaultDateProvider: DateProvider { + public init() {} + + private static let formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "dd-MM-yyyy HH:mm:ss.SSS" + return formatter + }() + + public func nowMillis() -> Int64 { + return Int64(Date().timeIntervalSince1970 * 1000) + } + + public func nowLabel() -> String { + return Self.formatter.string(from: Date()) + } +} diff --git a/Sources/Logging/LogLevel.swift b/Sources/Logging/LogLevel.swift new file mode 100644 index 000000000..9c93c2143 --- /dev/null +++ b/Sources/Logging/LogLevel.swift @@ -0,0 +1,34 @@ +// +// LogLevel.swift +// Logging +// + +import Foundation + +/// Log level enumeration for the Logging module +public enum LogLevel: String { + case verbose = "VERBOSE" + case debug = "DEBUG" + case info = "INFO" + case warning = "WARNING" + case error = "ERROR" + case none = "NONE" + + /// Returns the numeric order of the log level (lower = more verbose) + public func order() -> Int { + switch self { + case .verbose: + return 0 + case .debug: + return 1 + case .info: + return 2 + case .warning: + return 3 + case .error: + return 4 + case .none: + return 5 + } + } +} diff --git a/Sources/Logging/LogPrinter.swift b/Sources/Logging/LogPrinter.swift new file mode 100644 index 000000000..daf4db2d3 --- /dev/null +++ b/Sources/Logging/LogPrinter.swift @@ -0,0 +1,20 @@ +// +// LogPrinter.swift +// Logging +// + +import Foundation + +/// Protocol to enable testing for Logger class +public protocol LogPrinter { + func stdout(_ items: Any...) +} + +/// Default implementation that prints to stdout +public class DefaultLogPrinter: LogPrinter { + public init() {} + + public func stdout(_ items: Any...) { + print(items) + } +} diff --git a/Sources/Logging/Logger.swift b/Sources/Logging/Logger.swift new file mode 100644 index 000000000..4f9ad7790 --- /dev/null +++ b/Sources/Logging/Logger.swift @@ -0,0 +1,54 @@ +// +// Logger.swift +// Logging +// + +import Foundation + +/// Main logger class for the Logging module +public class Logger: @unchecked Sendable { + public var printer: LogPrinter = DefaultLogPrinter() + public var dateProvider: DateProvider = DefaultDateProvider() + private let tag: String = "SplitSDK" + + public var level: LogLevel = .none + + public static let shared: Logger = { + return Logger() + }() + + private init() {} + + private func log(level: LogLevel, msg: String, _ ctx: Any ...) { + if level.order() < self.level.order() { + return + } + + let timeLabel = dateProvider.nowLabel() + if ctx.count == 0 { + printer.stdout(timeLabel, level.rawValue, tag, msg) + } else { + printer.stdout(timeLabel, level.rawValue, tag, msg, ctx[0]) + } + } + + public static func v(_ message: String, _ context: Any ...) { + shared.log(level: .verbose, msg: message, context) + } + + public static func d(_ message: String, _ context: Any ...) { + shared.log(level: .debug, msg: message, context) + } + + public static func i(_ message: String, _ context: Any ...) { + shared.log(level: .info, msg: message, context) + } + + public static func w(_ message: String, _ context: Any ...) { + shared.log(level: .warning, msg: message, context) + } + + public static func e(_ message: String, _ context: Any ...) { + shared.log(level: .error, msg: message, context) + } +} diff --git a/Sources/Logging/Logging.swift b/Sources/Logging/Logging.swift new file mode 100644 index 000000000..cf17f85db --- /dev/null +++ b/Sources/Logging/Logging.swift @@ -0,0 +1,8 @@ +import Foundation + +public struct LoggingInternal { + public init() {} + public func action() { + print("Logging ready.") + } +} diff --git a/Sources/Logging/README.md b/Sources/Logging/README.md new file mode 100644 index 000000000..2a7d698e6 --- /dev/null +++ b/Sources/Logging/README.md @@ -0,0 +1,73 @@ +# Logging + +## What this module provides + +- `Logger`: shared logger with convenience methods `Logger.v/d/i/w/e` +- `LogLevel`: logging level enum +- `LogPrinter`: output interface (default prints to stdout) +- `TimeChecker`: helper to measure and log time intervals +- `DateProvider`: timestamp + label formatting (ships with a `DefaultDateProvider` backed by `Foundation.Date`) + +## Usage + +### 1) Configure (optional) + +The module ships with a `DefaultDateProvider` that uses `Foundation.Date`, so it works out of the box. You can still supply a custom `DateProvider` if you need different formatting or a custom clock: + +```swift +import Logging + +// Optional: override the default provider +Logger.shared.dateProvider = MyCustomDateProvider() +Logger.shared.level = .info +``` + +### 2) Log + +```swift +import Logging + +Logger.i("SDK initialized") +Logger.w("Something looks off", ["context": "value"]) +Logger.e("Something failed") +``` + +### Optional: customize output + +```swift +import Logging + +final class MyPrinter: LogPrinter { + func stdout(_ items: Any...) { + // route to OSLog, a file, your analytics, etc. + } +} + +Logger.shared.printer = MyPrinter() +``` + +### Optional: TimeChecker + +`TimeChecker` uses `Logger.shared.dateProvider` by default. + +```swift +import Logging + +TimeChecker.start() +// ... do work ... +TimeChecker.logInterval("Finished work") +``` + +## Notes + +- If the consumer has its own log levels, do the mapping in the consumer module (e.g. map `YourLogLevel` → `LogLevel`). + +## Running tests + +### Swift Package Manager + +From the repository root: + +```bash +swift test --filter LoggingTests +``` diff --git a/Sources/Logging/Tests/LogPrinterStub.swift b/Sources/Logging/Tests/LogPrinterStub.swift new file mode 100644 index 000000000..0dbf58bc0 --- /dev/null +++ b/Sources/Logging/Tests/LogPrinterStub.swift @@ -0,0 +1,30 @@ +// +// LogPrinterStub.swift +// LoggingTests +// +// Created by Javier Avrudsky on 08-Jul-2022. +// Copyright © 2022 Split. All rights reserved. +// + +import Foundation +@testable import Logging + +class LogPrinterStub: LogPrinter, @unchecked Sendable { + + private(set) var logs = [String]() + + private let queue = DispatchQueue(label: "Logging.LogPrinterStub", + target: .global()) + + func stdout(_ items: Any...) { + queue.sync { + self.logs.append(items.map { "\($0)" }.joined(separator: ",")) + } + } + + func clear() { + queue.sync { + self.logs.removeAll() + } + } +} diff --git a/Sources/Logging/Tests/LoggingTests.swift b/Sources/Logging/Tests/LoggingTests.swift new file mode 100644 index 000000000..b41c23a4c --- /dev/null +++ b/Sources/Logging/Tests/LoggingTests.swift @@ -0,0 +1,113 @@ +// +// LoggingTests.swift +// LoggingTests +// +// Created by Javier Avrudsky on 08-Jul-2022. +// Copyright © 2022 Split. All rights reserved. +// + +import Foundation +@testable import Logging + +import XCTest + +class LoggingTests : XCTestCase { + + let printer = LogPrinterStub() + + override func setUp() { + printer.clear() + Logger.shared.printer = printer + Logger.shared.dateProvider = DefaultDateProvider() + } + + func testNone() { + Logger.shared.level = .none + + logAll() + + XCTAssertFalse(isLogged(level: .verbose)) + XCTAssertFalse(isLogged(level: .debug)) + XCTAssertFalse(isLogged(level: .info)) + XCTAssertFalse(isLogged(level: .warning)) + XCTAssertFalse(isLogged(level: .error)) + } + + func testVerbose() { + Logger.shared.level = .verbose + + logAll() + + XCTAssertTrue(isLogged(level: .verbose)) + XCTAssertTrue(isLogged(level: .debug)) + XCTAssertTrue(isLogged(level: .info)) + XCTAssertTrue(isLogged(level: .warning)) + XCTAssertTrue(isLogged(level: .error)) + } + + func testDebug() { + Logger.shared.level = .debug + + logAll() + + XCTAssertFalse(isLogged(level: .verbose)) + XCTAssertTrue(isLogged(level: .debug)) + XCTAssertTrue(isLogged(level: .info)) + XCTAssertTrue(isLogged(level: .warning)) + XCTAssertTrue(isLogged(level: .error)) + } + + func testInfo() { + Logger.shared.level = .info + + logAll() + + XCTAssertFalse(isLogged(level: .verbose)) + XCTAssertFalse(isLogged(level: .debug)) + XCTAssertTrue(isLogged(level: .info)) + XCTAssertTrue(isLogged(level: .warning)) + XCTAssertTrue(isLogged(level: .error)) + } + + func testWarning() { + Logger.shared.level = .warning + + logAll() + + XCTAssertFalse(isLogged(level: .verbose)) + XCTAssertFalse(isLogged(level: .debug)) + XCTAssertFalse(isLogged(level: .info)) + XCTAssertTrue(isLogged(level: .warning)) + XCTAssertTrue(isLogged(level: .error)) + } + + func testError() { + Logger.shared.level = .error + + logAll() + + XCTAssertFalse(isLogged(level: .verbose)) + XCTAssertFalse(isLogged(level: .debug)) + XCTAssertFalse(isLogged(level: .info)) + XCTAssertFalse(isLogged(level: .warning)) + XCTAssertTrue(isLogged(level: .error)) + } + + private func isLogged(level: LogLevel) -> Bool { + return printer.logs.filter { $0.contains("\(level.rawValue)") }.count > 0 + } + + private func logAll() { + Logger.v("log") + Logger.d("log") + Logger.i("log") + Logger.w("log") + Logger.e("log") + } + + override func tearDown() { + Logger.shared.printer = DefaultLogPrinter() + Logger.shared.dateProvider = DefaultDateProvider() + printer.clear() + } +} diff --git a/Sources/Logging/TimeChecker.swift b/Sources/Logging/TimeChecker.swift new file mode 100644 index 000000000..3778f5ab2 --- /dev/null +++ b/Sources/Logging/TimeChecker.swift @@ -0,0 +1,66 @@ +// +// TimeChecker.swift +// Logging +// +// Created by Split SDK Team +// + +import Foundation + +/// Utility for checking and logging time intervals +public struct TimeChecker { + + #if swift(>=6.0) + nonisolated(unsafe) private static var startTime: Int64 = 0 + #else + private static var startTime: Int64 = 0 + #endif + + private static let tag = "[SPTPRF] " + private static let showTimestamp = true + private static let showSinceMsg = true + + public static func start(dateProvider: DateProvider? = nil) { + let provider = dateProvider ?? Logger.shared.dateProvider + startTime = provider.nowMillis() + Logger.v("\(tag) TimeChecker started at: \(startTime)") + } + + public static func logInterval(_ msg: String, dateProvider: DateProvider? = nil) { + let provider = dateProvider ?? Logger.shared.dateProvider + let now = provider.nowMillis() + let interval = now - startTime + Logger.v("\(tag) \(msg) \(formatTimestamp(now)) \(formatIntervalSinceStart(interval))") + } + + public static func logTime(_ msg: String, dateProvider: DateProvider? = nil) { + let provider = dateProvider ?? Logger.shared.dateProvider + Logger.v("\(tag) \(msg) \(formatIntervalSinceStart(provider.nowMillis()))") + } + + public static func logInterval(_ msg: String, startTime: Int64, dateProvider: DateProvider? = nil) { + let provider = dateProvider ?? Logger.shared.dateProvider + Logger.v("\(tag) \(msg) \(provider.nowMillis() - startTime) ms \(formatTimestamp(provider.nowMillis()))") + } + + public static func formatInterval(_ interval: Int64) -> String { + if !showSinceMsg { + return "\(interval)" + } + return "Time since instanciation start \(interval) ms" + } + + public static func formatIntervalSinceStart(_ interval: Int64) -> String { + if !showSinceMsg { + return "\(interval)" + } + return "\(interval) ms since instanciation start" + } + + public static func formatTimestamp(_ now: Int64) -> String { + if !showTimestamp { + return "" + } + return "at \(now)" + } +} diff --git a/Split.podspec b/Split.podspec index f1cd0923f..d72c93881 100644 --- a/Split.podspec +++ b/Split.podspec @@ -16,7 +16,8 @@ This SDK is designed to work with Split, the platform for controlled rollouts, s s.swift_versions = ['4.0', '4.2', '5.0', '5.1', '5.2', '5.3'] s.resources = "Split/Storage/split_cache.xcdatamodeld" s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '$(PROJECT_DIR)/Split/Common/Utils/JFBCrypt', 'SWIFT_INCLUDE_PATHS' => '${PROJECT_DIR}/Split/Common/Utils/JFBCrypt' } - s.source_files = 'Split/**/*.{swift}','Split/Common/Utils/JFBCrypt/*.{h,m}' - s.exclude_files = 'Split/Common/Utils/Lib/*' + s.source_files = 'Sources/**/*' + s.exclude_files = 'Sources/**/Tests/**' + # #INJECT_SUBSPEC end diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj index 5ea92b038..b4fdd9685 100644 --- a/Split.xcodeproj/project.pbxproj +++ b/Split.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2302B134A0BFE6B5626F07AD /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC9438DCA8FC60FEC194548 /* LoggingTests.swift */; }; 31AA2036872EEC62D9EDF4AB /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA22D3DA2B022F2E7F3231 /* Endpoint.swift */; }; 31AA21DE01A000A7C30630F2 /* SseClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA280A65C72BC92858422C /* SseClientTest.swift */; }; 31AA228978A886748090A790 /* HttpStreamResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA2744A2006D558FCBE8CE /* HttpStreamResponse.swift */; }; @@ -104,6 +105,7 @@ 3B6DEF8920EA6AE50067435E /* ConcurrentArrayQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B6DEF0620EA6AE40067435E /* ConcurrentArrayQueue.swift */; }; 3B6DEF8B20EA6AE50067435E /* DefaultFileStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B6DEF0920EA6AE40067435E /* DefaultFileStorage.swift */; }; 3BBFF93320EFA2A0005F8C26 /* SplitEventActionTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBFF93220EFA2A0005F8C26 /* SplitEventActionTask.swift */; }; + 55F2612BD679415B346A4584 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDAE97952FA0ADED787D7ACC /* Logging.swift */; }; 5903C497210A5E0900A754B0 /* SynchronizedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5903C496210A5E0900A754B0 /* SynchronizedList.swift */; }; 5905D4CA2555F99D006DA3B1 /* GeneralInfoEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5905D4C02555F99D006DA3B1 /* GeneralInfoEntity.swift */; }; 5905D4CC2555F99D006DA3B1 /* ImpressionEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5905D4C22555F99D006DA3B1 /* ImpressionEntity.swift */; }; @@ -833,6 +835,8 @@ 5BF52DF72DE0B60700FEDAFE /* PrerequisitesMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */; }; 5BF52DF92DE4B8D400FEDAFE /* PrerequisitesMatcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF82DE4B8CA00FEDAFE /* PrerequisitesMatcherTest.swift */; }; 5BF52E032DE62F0500FEDAFE /* PrerequisitesMatcherMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52E022DE62EFE00FEDAFE /* PrerequisitesMatcherMock.swift */; }; + 88E54A26B0FA48B97D7B6AE3 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FD80A6B13740C2114DFA2F /* LoggingTests.swift */; }; + 945D15B2B918E1875EF7470E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB05351F643A2046D7E1EC50 /* Foundation.framework */; }; 9500D9922C56F9BA00383593 /* HostDomainFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9500D9912C56F9BA00383593 /* HostDomainFilterTests.swift */; }; 9500D9A92C59297400383593 /* HostDomainFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9500D9A82C59297400383593 /* HostDomainFilter.swift */; }; 9500D9AA2C59382000383593 /* HostDomainFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9500D9A82C59297400383593 /* HostDomainFilter.swift */; }; @@ -1549,6 +1553,7 @@ 95F7BC272C45C12700C5F2E4 /* TlsPinCheckerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95F7BC262C45C12700C5F2E4 /* TlsPinCheckerMock.swift */; }; 95F7BC292C46A4C800C5F2E4 /* SecurityHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95F7BC282C46A4C800C5F2E4 /* SecurityHelper.swift */; }; 95F8F06828170473009E09B1 /* multi_client_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 95F8F06728170473009E09B1 /* multi_client_test.json */; }; + B3A862C479A657597413B150 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB05351F643A2046D7E1EC50 /* Foundation.framework */; }; C514F6B62BEA6F1F00C8DF78 /* HttpParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = C514F6B52BEA6F1F00C8DF78 /* HttpParameters.swift */; }; C526DE4C2D9B09EB0051DAB8 /* ImpressionsPropertiesE2ETest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C526DE4B2D9B09EB0051DAB8 /* ImpressionsPropertiesE2ETest.swift */; }; C52C57292EEB41350064D049 /* EncryptionKeyValidationIntegrationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C52C57282EEB41350064D049 /* EncryptionKeyValidationIntegrationTest.swift */; }; @@ -1647,6 +1652,12 @@ C5A7D5572DDBD4380081D190 /* split_changes_rbs.json in Resources */ = {isa = PBXBuildFile; fileRef = C5A7D5562DDBD4280081D190 /* split_changes_rbs.json */; }; C5A7D5592DDBD7A30081D190 /* OutdatedProxyIntegrationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5A7D5582DDBD7A30081D190 /* OutdatedProxyIntegrationTest.swift */; }; C5A7D5612DDF88BA0081D190 /* RuleBasedSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C539CAB52D88C1F10050C732 /* RuleBasedSegment.swift */; }; + C5B9D4B82F259BD9000D1C11 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B9D4B42F259BD9000D1C11 /* Logger.swift */; }; + C5B9D4B92F259BD9000D1C11 /* LogPrinter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B9D4B62F259BD9000D1C11 /* LogPrinter.swift */; }; + C5B9D4BA2F259BD9000D1C11 /* DateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B9D4B32F259BD9000D1C11 /* DateProvider.swift */; }; + C5B9D4BB2F259BD9000D1C11 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B9D4B52F259BD9000D1C11 /* LogLevel.swift */; }; + C5B9D4BC2F259BD9000D1C11 /* TimeChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B9D4B72F259BD9000D1C11 /* TimeChecker.swift */; }; + C5B9D4BE2F259BEA000D1C11 /* LogPrinterStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B9D4BD2F259BEA000D1C11 /* LogPrinterStub.swift */; }; C5BD1E4C2D11A993008EF198 /* DecoratedImpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */; }; C5BD1E4D2D11A993008EF198 /* DecoratedImpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */; }; C5BD1E4F2D130EAF008EF198 /* ImpressionsToggleTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BD1E4E2D130EA7008EF198 /* ImpressionsToggleTest.swift */; }; @@ -1668,6 +1679,20 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 35353B9E8A9A3BA51D122AFE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3B6DEE5120EA6A4D0067435E /* Project object */; + proxyType = 1; + remoteGlobalIDString = CE3D219AC42E3E9CBC6E8D2B; + remoteInfo = Logging; + }; + 3B51FC238425C7FF12F8FF97 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3B6DEE5120EA6A4D0067435E /* Project object */; + proxyType = 1; + remoteGlobalIDString = CE3D219AC42E3E9CBC6E8D2B; + remoteInfo = Logging; + }; 592C6AAB211B6C99002D120C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3B6DEE5120EA6A4D0067435E /* Project object */; @@ -1682,6 +1707,27 @@ remoteGlobalIDString = 3B6DEE5920EA6A4E0067435E; remoteInfo = Split; }; + 6E4424077045251A1E0C26AF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3B6DEE5120EA6A4D0067435E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 557D062442413680641861B3; + remoteInfo = Logging; + }; + 95B02CAB28D0BD7A0030EC8C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3B6DEE5120EA6A4D0067435E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 557D062442413680641861B3; + remoteInfo = Logging; + }; + ED0C4156420B8E843DF9A23B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3B6DEE5120EA6A4D0067435E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 557D062442413680641861B3; + remoteInfo = Logging; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -1706,6 +1752,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 06B656827046A8E6AFD642E8 /* LoggingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoggingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 23FD80A6B13740C2114DFA2F /* LoggingTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = ""; }; 31AA215F3C0E2305966CD52F /* HttpStreamRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpStreamRequest.swift; sourceTree = ""; }; 31AA229CA74193186137A991 /* RestClient+Impressions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RestClient+Impressions.swift"; sourceTree = ""; }; 31AA22D3DA2B022F2E7F3231 /* Endpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = ""; }; @@ -1718,6 +1766,7 @@ 31AA2B308C842042CD5D4ED3 /* RestClient+SplitChanges.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RestClient+SplitChanges.swift"; sourceTree = ""; }; 31AA2B33B325F1FD8A8C1776 /* HttpDataRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpDataRequest.swift; sourceTree = ""; }; 31AA2EAA5F782C4F108D7777 /* ServiceEndpoints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceEndpoints.swift; sourceTree = ""; }; + 3310AD6DAA71B09C53FD06F4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 3B6DEE5A20EA6A4E0067435E /* Split.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Split.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B6DEE5E20EA6A4E0067435E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3B6DEE7820EA6ADF0067435E /* EventDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventDTO.swift; sourceTree = ""; }; @@ -1806,6 +1855,7 @@ 3B6DEF0620EA6AE40067435E /* ConcurrentArrayQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConcurrentArrayQueue.swift; sourceTree = ""; }; 3B6DEF0920EA6AE40067435E /* DefaultFileStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultFileStorage.swift; sourceTree = ""; }; 3BBFF93220EFA2A0005F8C26 /* SplitEventActionTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplitEventActionTask.swift; sourceTree = ""; }; + 47D728CC6DD6BE8801F7CA8D /* Logging.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; 5903C496210A5E0900A754B0 /* SynchronizedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizedList.swift; sourceTree = ""; }; 5905D4C02555F99D006DA3B1 /* GeneralInfoEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralInfoEntity.swift; sourceTree = ""; }; 5905D4C22555F99D006DA3B1 /* ImpressionEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImpressionEntity.swift; sourceTree = ""; }; @@ -2077,6 +2127,7 @@ 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrerequisitesMatcher.swift; sourceTree = ""; }; 5BF52DF82DE4B8CA00FEDAFE /* PrerequisitesMatcherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrerequisitesMatcherTest.swift; sourceTree = ""; }; 5BF52E022DE62EFE00FEDAFE /* PrerequisitesMatcherMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrerequisitesMatcherMock.swift; sourceTree = ""; }; + 78AFF5AE2FCD12D9D4917100 /* LoggingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoggingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9500D9912C56F9BA00383593 /* HostDomainFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostDomainFilterTests.swift; sourceTree = ""; }; 9500D9A82C59297400383593 /* HostDomainFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostDomainFilter.swift; sourceTree = ""; }; 9500D9AC2C5A918300383593 /* split_cache_v5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = split_cache_v5.xcdatamodel; sourceTree = ""; }; @@ -2486,6 +2537,8 @@ 95F7BC262C45C12700C5F2E4 /* TlsPinCheckerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TlsPinCheckerMock.swift; sourceTree = ""; }; 95F7BC282C46A4C800C5F2E4 /* SecurityHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityHelper.swift; sourceTree = ""; }; 95F8F06728170473009E09B1 /* multi_client_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = multi_client_test.json; sourceTree = ""; }; + A84C2F1D387F870A3749BCE7 /* Logging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Logging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BDAE97952FA0ADED787D7ACC /* Logging.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; C514F6B52BEA6F1F00C8DF78 /* HttpParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpParameters.swift; sourceTree = ""; }; C526DE4B2D9B09EB0051DAB8 /* ImpressionsPropertiesE2ETest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImpressionsPropertiesE2ETest.swift; sourceTree = ""; }; C52C57282EEB41350064D049 /* EncryptionKeyValidationIntegrationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyValidationIntegrationTest.swift; sourceTree = ""; }; @@ -2558,6 +2611,12 @@ C5A7D5522DD672CF0081D190 /* RuleBasedSegmentsIntegrationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleBasedSegmentsIntegrationTest.swift; sourceTree = ""; }; C5A7D5562DDBD4280081D190 /* split_changes_rbs.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = split_changes_rbs.json; sourceTree = ""; }; C5A7D5582DDBD7A30081D190 /* OutdatedProxyIntegrationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutdatedProxyIntegrationTest.swift; sourceTree = ""; }; + C5B9D4B32F259BD9000D1C11 /* DateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateProvider.swift; sourceTree = ""; }; + C5B9D4B42F259BD9000D1C11 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + C5B9D4B52F259BD9000D1C11 /* LogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogLevel.swift; sourceTree = ""; }; + C5B9D4B62F259BD9000D1C11 /* LogPrinter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogPrinter.swift; sourceTree = ""; }; + C5B9D4B72F259BD9000D1C11 /* TimeChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeChecker.swift; sourceTree = ""; }; + C5B9D4BD2F259BEA000D1C11 /* LogPrinterStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogPrinterStub.swift; sourceTree = ""; }; C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecoratedImpression.swift; sourceTree = ""; }; C5BD1E4E2D130EA7008EF198 /* ImpressionsToggleTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImpressionsToggleTest.swift; sourceTree = ""; }; C5BD1E512D130FB6008EF198 /* splitchanges_toggle.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = splitchanges_toggle.json; sourceTree = ""; }; @@ -2570,9 +2629,18 @@ C5E967562D38013000112DAC /* GeneralInfoStorageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralInfoStorageMock.swift; sourceTree = ""; }; C5E9675E2D3849BE00112DAC /* RolloutDefinitionsCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RolloutDefinitionsCache.swift; sourceTree = ""; }; C5E967612D395DAA00112DAC /* RolloutCacheManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RolloutCacheManagerTest.swift; sourceTree = ""; }; + DB05351F643A2046D7E1EC50 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 020B54F29090B10B36E722EE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 945D15B2B918E1875EF7470E /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3B6DEE5620EA6A4E0067435E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2596,6 +2664,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 632973C2BFAC5B8884612D82 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B3A862C479A657597413B150 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 95825B9D271F004700A0CDAD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2611,9 +2687,48 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A48D2E8293F47EF36C399431 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8265D23DD90A3945041F7846 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 09594BE53BC4B1E633601B3A /* Logging */ = { + isa = PBXGroup; + children = ( + C5B9D4B32F259BD9000D1C11 /* DateProvider.swift */, + C5B9D4B42F259BD9000D1C11 /* Logger.swift */, + C5B9D4B52F259BD9000D1C11 /* LogLevel.swift */, + C5B9D4B62F259BD9000D1C11 /* LogPrinter.swift */, + C5B9D4B72F259BD9000D1C11 /* TimeChecker.swift */, + 11D5B257E8900DEAB2408E42 /* Tests */, + BDAE97952FA0ADED787D7ACC /* Logging.swift */, + ); + path = Logging; + sourceTree = ""; + }; + 11D5B257E8900DEAB2408E42 /* Tests */ = { + isa = PBXGroup; + children = ( + C5B9D4BD2F259BEA000D1C11 /* LogPrinterStub.swift */, + 23FD80A6B13740C2114DFA2F /* LoggingTests.swift */, + ); + path = Tests; + sourceTree = ""; + }; + 237E67ECC8C144123A55D057 /* Sources */ = { + isa = PBXGroup; + children = ( + 09594BE53BC4B1E633601B3A /* Logging */, + ); + path = Sources; + sourceTree = ""; + }; 31AA215C368D12E69FE80703 /* Streaming */ = { isa = PBXGroup; children = ( @@ -2648,6 +2763,14 @@ path = Streaming; sourceTree = ""; }; + 38B4ACD0F9962990A6B7B40C /* iOS */ = { + isa = PBXGroup; + children = ( + DB05351F643A2046D7E1EC50 /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; 3B6DEE5020EA6A4D0067435E = { isa = PBXGroup; children = ( @@ -2680,6 +2803,7 @@ 3B6DEE5B20EA6A4E0067435E /* Products */, 590DF9962125F6900082B94F /* Frameworks */, 5B3C18052ED76BAE0068D623 /* SplitTests copy-Info.plist */, + 237E67ECC8C144123A55D057 /* Sources */, ); sourceTree = ""; }; @@ -2691,6 +2815,8 @@ 95825BA0271F004700A0CDAD /* Split_Split.bundle */, 95B02CA428D0BD790030EC8B /* SplitWatchOS.framework */, 5B3C18042ED76BAD0068D623 /* SplitTestsSwift6.xctest */, + A84C2F1D387F870A3749BCE7 /* Logging.framework */, + 06B656827046A8E6AFD642E8 /* LoggingTests.xctest */, ); name = Products; sourceTree = ""; @@ -3307,6 +3433,7 @@ 95B02E0928D130670030EC8B /* WatchKit.framework */, 954F9BBF2575A1AB00140B81 /* UIKit.framework */, 5946783722E1144400F94E58 /* Swifter.framework */, + 38B4ACD0F9962990A6B7B40C /* iOS */, ); name = Frameworks; sourceTree = ""; @@ -3758,6 +3885,15 @@ path = FallbackTreatmentsTests; sourceTree = ""; }; + 8B111CC6F6949E959F8BAE82 /* Sources */ = { + isa = PBXGroup; + children = ( + FC5ED3CB5B734CFCE8D24EB9 /* Logging */, + ); + name = Sources; + path = Sources; + sourceTree = ""; + }; 95118EE9281AC80400782F33 /* UserConsent */ = { isa = PBXGroup; children = ( @@ -4268,6 +4404,14 @@ path = CertPinning; sourceTree = ""; }; + B2F2ED01E7AEF469AA85A910 /* iOS */ = { + isa = PBXGroup; + children = ( + 3310AD6DAA71B09C53FD06F4 /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; C539CAE42D947D2A0050C732 /* Validators */ = { isa = PBXGroup; children = ( @@ -4335,9 +4479,35 @@ path = RuleBasedSegments; sourceTree = ""; }; + C7B5F1ABBC6EB7AD87BB39EC /* Tests */ = { + isa = PBXGroup; + children = ( + DDC9438DCA8FC60FEC194548 /* LoggingTests.swift */, + ); + name = Tests; + path = Tests; + sourceTree = ""; + }; + FC5ED3CB5B734CFCE8D24EB9 /* Logging */ = { + isa = PBXGroup; + children = ( + C7B5F1ABBC6EB7AD87BB39EC /* Tests */, + 47D728CC6DD6BE8801F7CA8D /* Logging.swift */, + ); + name = Logging; + path = Logging; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 0F5883123EAFFF015B41AEF3 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3B6DEE5720EA6A4E0067435E /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -4346,6 +4516,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 89981F1DF50512CA261EB5F0 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 954DAFA928DBCF74009E108F /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -4369,14 +4546,31 @@ buildRules = ( ); dependencies = ( + 626C2B9BBF3ADCABE4527521 /* PBXTargetDependency */, ); name = Split; - packageProductDependencies = ( - ); productName = Split; productReference = 3B6DEE5A20EA6A4E0067435E /* Split.framework */; productType = "com.apple.product-type.framework"; }; + 557D062442413680641861B3 /* Logging */ = { + isa = PBXNativeTarget; + buildConfigurationList = FF245C94AAE01CB254D3F6B4 /* Build configuration list for PBXNativeTarget "Logging" */; + buildPhases = ( + 0F5883123EAFFF015B41AEF3 /* Headers */, + 2723760E87F4BCE53CCD6973 /* Sources */, + 020B54F29090B10B36E722EE /* Frameworks */, + BDE7E7FFA4358D0E69C01D32 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Logging; + productName = Logging; + productReference = A84C2F1D387F870A3749BCE7 /* Logging.framework */; + productType = "com.apple.product-type.framework"; + }; 592C6AA4211B6C99002D120C /* SplitTestsSwift5 */ = { isa = PBXNativeTarget; buildConfigurationList = 592C6AAF211B6C99002D120C /* Build configuration list for PBXNativeTarget "SplitTestsSwift5" */; @@ -4392,8 +4586,6 @@ 592C6AAC211B6C99002D120C /* PBXTargetDependency */, ); name = SplitTestsSwift5; - packageProductDependencies = ( - ); productName = SplitTests; productReference = 592C6AA5211B6C99002D120C /* SplitTestsSwift5.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -4413,12 +4605,28 @@ 5B3C16352ED76BAD0068D623 /* PBXTargetDependency */, ); name = SplitTestsSwift6; - packageProductDependencies = ( - ); productName = SplitTests; productReference = 5B3C18042ED76BAD0068D623 /* SplitTestsSwift6.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 8000B2F11F535D99847B28BE /* LoggingTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2CC43A0BA4C3F04300DABA16 /* Build configuration list for PBXNativeTarget "LoggingTests" */; + buildPhases = ( + 8D531141D7BF6F4CE4BB1F42 /* Sources */, + 632973C2BFAC5B8884612D82 /* Frameworks */, + FD5894504B3665533EF83AF7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3B70B5C59A2844E0D2897B9C /* PBXTargetDependency */, + ); + name = LoggingTests; + productName = LoggingTests; + productReference = 06B656827046A8E6AFD642E8 /* LoggingTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 95825B9F271F004700A0CDAD /* Split_Split */ = { isa = PBXNativeTarget; buildConfigurationList = 95825BA5271F004800A0CDAD /* Build configuration list for PBXNativeTarget "Split_Split" */; @@ -4448,12 +4656,49 @@ buildRules = ( ); dependencies = ( + 95B02CAC28D0BD7A0030EC8D /* PBXTargetDependency */, ); name = SplitWatchOS; productName = SplitTvOS; productReference = 95B02CA428D0BD790030EC8B /* SplitWatchOS.framework */; productType = "com.apple.product-type.framework"; }; + CE3D219AC42E3E9CBC6E8D2B /* Logging */ = { + isa = PBXNativeTarget; + buildConfigurationList = ED69C1CC20B893177298AF56 /* Build configuration list for PBXNativeTarget "Logging" */; + buildPhases = ( + 89981F1DF50512CA261EB5F0 /* Headers */, + 1F4ABFFF91376225DFBAFA5A /* Sources */, + A48D2E8293F47EF36C399431 /* Frameworks */, + 8224E32C5E4322A5C6156BC4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Logging; + productName = Logging; + productReference = A1EC0DDCC4C5B8B13AB584FB /* Logging.framework */; + productType = "com.apple.product-type.framework"; + }; + FDC0744272FCA047AB3E733A /* LoggingTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 57BB370AA2DDCDB349EB11A8 /* Build configuration list for PBXNativeTarget "LoggingTests" */; + buildPhases = ( + 155AAD5319839498831ED6CB /* Sources */, + 166F98756B419A185AFCBBA3 /* Frameworks */, + 1F86C4B0E41E1F7244BF3AE4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 0AC860035C528C4D12E3623D /* PBXTargetDependency */, + ); + name = LoggingTests; + productName = LoggingTests; + productReference = 78AFF5AE2FCD12D9D4917100 /* LoggingTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -4491,8 +4736,6 @@ Base, ); mainGroup = 3B6DEE5020EA6A4D0067435E; - packageReferences = ( - ); productRefGroup = 3B6DEE5B20EA6A4E0067435E /* Products */; projectDirPath = ""; projectRoot = ""; @@ -4502,11 +4745,20 @@ 5B3C16342ED76BAD0068D623 /* SplitTestsSwift6 */, 95825B9F271F004700A0CDAD /* Split_Split */, 95B02CA328D0BD790030EC8B /* SplitWatchOS */, + 557D062442413680641861B3 /* Logging */, + 8000B2F11F535D99847B28BE /* LoggingTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 1F86C4B0E41E1F7244BF3AE4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3B6DEE5820EA6A4E0067435E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -4701,6 +4953,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8224E32C5E4322A5C6156BC4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 95825B9E271F004700A0CDAD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -4718,12 +4977,39 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + BDE7E7FFA4358D0E69C01D32 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FD5894504B3665533EF83AF7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 2723760E87F4BCE53CCD6973 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 55F2612BD679415B346A4584 /* Logging.swift in Sources */, + C5B9D4B82F259BD9000D1C11 /* Logger.swift in Sources */, + C5B9D4B92F259BD9000D1C11 /* LogPrinter.swift in Sources */, + C5B9D4BA2F259BD9000D1C11 /* DateProvider.swift in Sources */, + C5B9D4BB2F259BD9000D1C11 /* LogLevel.swift in Sources */, + C5B9D4BC2F259BD9000D1C11 /* TimeChecker.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3B6DEE5520EA6A4E0067435E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -5856,6 +6142,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8D531141D7BF6F4CE4BB1F42 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88E54A26B0FA48B97D7B6AE3 /* LoggingTests.swift in Sources */, + C5B9D4BE2F259BEA000D1C11 /* LogPrinterStub.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 95825B9C271F004700A0CDAD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -6234,6 +6529,12 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 3B70B5C59A2844E0D2897B9C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Logging; + target = 557D062442413680641861B3 /* Logging */; + targetProxy = 6E4424077045251A1E0C26AF /* PBXContainerItemProxy */; + }; 592C6AAC211B6C99002D120C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3B6DEE5920EA6A4E0067435E /* Split */; @@ -6244,9 +6545,112 @@ target = 3B6DEE5920EA6A4E0067435E /* Split */; targetProxy = 5B3C16362ED76BAD0068D623 /* PBXContainerItemProxy */; }; + 626C2B9BBF3ADCABE4527521 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Logging; + target = 557D062442413680641861B3 /* Logging */; + targetProxy = ED0C4156420B8E843DF9A23B /* PBXContainerItemProxy */; + }; + 95B02CAC28D0BD7A0030EC8D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Logging; + target = 557D062442413680641861B3 /* Logging */; + targetProxy = 95B02CAB28D0BD7A0030EC8C /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 1E6BFABC4C3C513B159FB9B9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).Logging"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos appletvsimulator appletvos"; + TARGETED_DEVICE_FAMILY = "1,2,4"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 7.0; + }; + name = Release; + }; + 3809C6CC6901B64FF7C40F6F /* Debug-Swift6 */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).Logging"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos appletvsimulator appletvos"; + TARGETED_DEVICE_FAMILY = "1,2,4"; + WATCHOS_DEPLOYMENT_TARGET = 7.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Debug-Swift6"; + }; + 3961E0B6BAE2C41629EBA80C /* Debug-Swift5 */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).Logging"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos appletvsimulator appletvos"; + TARGETED_DEVICE_FAMILY = "1,2,4"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 7.0; + }; + name = "Debug-Swift5"; + }; 3B6DEE6020EA6A4E0067435E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6473,6 +6877,36 @@ }; name = Release; }; + 3FAFFF1BCBD47F87A6574701 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).Logging"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos appletvsimulator appletvos"; + TARGETED_DEVICE_FAMILY = "1,2,4"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 7.0; + }; + name = Debug; + }; 592C6AAD211B6C99002D120C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6583,6 +7017,25 @@ }; name = Release; }; + 8BC296B950AC84FEFBE7C76F /* Debug-Swift5 */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).LoggingTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TEST_HOST = ""; + }; + name = "Debug-Swift5"; + }; 95825BA3271F004800A0CDAD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6713,6 +7166,25 @@ }; name = Release; }; + A1EFF1E396DE776C46CDFF70 /* Debug-Swift6 */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).LoggingTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TEST_HOST = ""; + }; + name = "Debug-Swift6"; + }; C59D18C82EDA131D002C86FB /* Debug-Swift5 */ = { isa = XCBuildConfiguration; buildSettings = { @@ -7194,9 +7666,78 @@ }; name = "Debug-Swift6"; }; + DFA8FF5A377EC1B5BDA8B991 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).LoggingTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TEST_HOST = ""; + }; + name = Debug; + }; + E19FEC1F2F10CDEDA6DA853D /* Debug-Swift6 */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).LoggingTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TEST_HOST = ""; + }; + name = "Debug-Swift6"; + }; + FA374EC131AEDC301A0339CA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).LoggingTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TEST_HOST = ""; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 2CC43A0BA4C3F04300DABA16 /* Build configuration list for PBXNativeTarget "LoggingTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FA374EC131AEDC301A0339CA /* Release */, + DFA8FF5A377EC1B5BDA8B991 /* Debug */, + 8BC296B950AC84FEFBE7C76F /* Debug-Swift5 */, + E19FEC1F2F10CDEDA6DA853D /* Debug-Swift6 */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 3B6DEE5420EA6A4D0067435E /* Build configuration list for PBXProject "Split" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -7219,6 +7760,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 57BB370AA2DDCDB349EB11A8 /* Build configuration list for PBXNativeTarget "LoggingTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0268BCB9F51644101827EFB5 /* Release */, + F600AD2D0D1396247B6BC357 /* Debug */, + 0B2CA9D58777E78CFBC8AA0B /* Debug-Swift5 */, + A1EFF1E396DE776C46CDFF70 /* Debug-Swift6 */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 592C6AAF211B6C99002D120C /* Build configuration list for PBXNativeTarget "SplitTestsSwift5" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -7263,6 +7815,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + FF245C94AAE01CB254D3F6B4 /* Build configuration list for PBXNativeTarget "Logging" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1E6BFABC4C3C513B159FB9B9 /* Release */, + 3FAFFF1BCBD47F87A6574701 /* Debug */, + 3961E0B6BAE2C41629EBA80C /* Debug-Swift5 */, + 3809C6CC6901B64FF7C40F6F /* Debug-Swift6 */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCVersionGroup section */ diff --git a/Split/Api/SplitClientConfig.swift b/Split/Api/SplitClientConfig.swift index 131ef7a64..d6dc349a4 100644 --- a/Split/Api/SplitClientConfig.swift +++ b/Split/Api/SplitClientConfig.swift @@ -126,11 +126,11 @@ public class SplitClientConfig: NSObject { @available(*, deprecated, message: "Use logLevel instead") @objc public var isDebugModeEnabled: Bool { get { - return Logger.shared.level == .debug + return SplitLogLevel.from(Logger.shared.level) == .debug } set { if Logger.shared.level == .none { - Logger.shared.level = newValue ? .debug : .none + Logger.shared.level = newValue ? SplitLogLevel.debug.toLogLevel() : .none } } } @@ -142,11 +142,11 @@ public class SplitClientConfig: NSObject { @available(*, deprecated, message: "Use logLevel instead") @objc public var isVerboseModeEnabled: Bool { get { - return Logger.shared.level == .verbose + return SplitLogLevel.from(Logger.shared.level) == .verbose } set { if Logger.shared.level == .none { - Logger.shared.level = newValue ? .verbose : .none + Logger.shared.level = newValue ? SplitLogLevel.verbose.toLogLevel() : .none } } } @@ -155,15 +155,15 @@ public class SplitClientConfig: NSObject { /// Swift only method public var logLevel: SplitLogLevel { get { - return Logger.shared.level + return SplitLogLevel.from(Logger.shared.level) } set { - Logger.shared.level = newValue + Logger.shared.level = newValue.toLogLevel() } } @objc public func set(logLevel: String) { - Logger.shared.level = SplitLogLevel(rawValue: logLevel) ?? .none + Logger.shared.level = (SplitLogLevel(rawValue: logLevel) ?? .none).toLogLevel() } /// diff --git a/Split/Api/SplitLogLevel.swift b/Split/Api/SplitLogLevel.swift index 566786f87..e0d2a20d3 100644 --- a/Split/Api/SplitLogLevel.swift +++ b/Split/Api/SplitLogLevel.swift @@ -7,6 +7,7 @@ // import Foundation +import Logging public enum SplitLogLevel: String { @@ -33,4 +34,42 @@ public enum SplitLogLevel: String { return 5 } } + + /// Maps SplitLogLevel to Logging's LogLevel + func toLogLevel() -> LogLevel { + switch self { + case .verbose: + return .verbose + case .debug: + return .debug + case .info: + return .info + case .warning: + return .warning + case .error: + return .error + case .none: + return .none + } + } + + /// Creates SplitLogLevel from Logging's LogLevel + static func from(_ logLevel: LogLevel) -> SplitLogLevel { + switch logLevel { + case .verbose: + return .verbose + case .debug: + return .debug + case .info: + return .info + case .warning: + return .warning + case .error: + return .error + case .none: + return .none + @unknown default: + return .none + } + } } diff --git a/Split/Common/Utils/Logger.swift b/Split/Common/Utils/Logger.swift index 0bf30d2c1..a964763ca 100644 --- a/Split/Common/Utils/Logger.swift +++ b/Split/Common/Utils/Logger.swift @@ -6,113 +6,16 @@ // import Foundation +import Logging -struct TimeChecker { +// Re-export Logger from Logging module for backward compatibility +// This allows existing Split code to continue using Logger.* without changes +typealias Logger = Logging.Logger - #if swift(>=6.0) - nonisolated(unsafe) private static var startTime: Int64 = 0 - #else - private static var startTime: Int64 = 0 - #endif - - private static let tag = "[SPTPRF] " - private static let showTimestamp = true - private static let showSinceMsg = true - static func start() { - startTime = Date.nowMillis() - Logger.v("\(tag) TimeChecker started at: \(startTime)") - } +// Use Logging's TimeChecker implementation. +typealias TimeChecker = Logging.TimeChecker - static func logInterval(_ msg: String) { - let now = Date.nowMillis() - let interval = now - startTime - Logger.v("\(tag) \(msg) \(formatTimestamp(now)) \(formatIntervalSinceStart(interval))") - } - - static func logTime(_ msg: String) { - Logger.v("\(tag) \(msg) \(formatIntervalSinceStart(Date.nowMillis()))") - } - - static func logInterval(_ msg: String, startTime: Int64) { - Logger.v("\(tag) \(msg) \(Date.nowMillis() - startTime) ms \(formatTimestamp(Date.nowMillis()))") - } - - static func formatInterval(_ interval: Int64) -> String { - if !showSinceMsg { - return "\(interval)" - } - return "Time since instanciation start \(interval) ms" - } - - static func formatIntervalSinceStart(_ interval: Int64) -> String { - if !showSinceMsg { - return "\(interval)" - } - return "\(interval) ms since instanciation start" - } - - static func formatTimestamp(_ now: Int64) -> String { - if !showTimestamp { - return "" - } - return "at \(now)" - } -} - -// Protocol to enable testing for Logger class -protocol LogPrinter { - func stdout(_ items: Any...) -} - -class DefaultLogPrinter: LogPrinter { - func stdout(_ items: Any...) { - print(items) - } -} - -class Logger: @unchecked Sendable { - var printer: LogPrinter = DefaultLogPrinter() - private let tag: String = "SplitSDK" - - var level: SplitLogLevel = .none - - static let shared: Logger = { - return Logger() - }() - - private init() {} - - private func log(level: SplitLogLevel, msg: String, _ ctx: Any ...) { - - if level.order() < self.level.order() { - return - } - - let timeLabel = Date.nowLabel() - if ctx.count == 0 { - printer.stdout(timeLabel, level.rawValue, tag, msg) - } else { - printer.stdout(timeLabel, level.rawValue, tag, msg, ctx[0]) - } - } - - static func v(_ message: String, _ context: Any ...) { - shared.log(level: .verbose, msg: message, context) - } - - static func d(_ message: String, _ context: Any ...) { - shared.log(level: .debug, msg: message, context) - } - - static func i(_ message: String, _ context: Any ...) { - shared.log(level: .info, msg: message, context) - } - - static func w(_ message: String, _ context: Any ...) { - shared.log(level: .warning, msg: message, context) - } - - static func e(_ message: String, _ context: Any ...) { - shared.log(level: .error, msg: message, context) - } -} +// LogPrinter and DefaultLogPrinter are now in the Logging module +// Re-export for backward compatibility +typealias LogPrinter = Logging.LogPrinter +typealias DefaultLogPrinter = Logging.DefaultLogPrinter diff --git a/SplitTests/Fake/LogPrinterStub.swift b/SplitTests/Fake/LogPrinterStub.swift index f3b3d285e..3a170d7ef 100644 --- a/SplitTests/Fake/LogPrinterStub.swift +++ b/SplitTests/Fake/LogPrinterStub.swift @@ -7,6 +7,7 @@ // import Foundation +import Logging @testable import Split class LogPrinterStub: LogPrinter, @unchecked Sendable { diff --git a/SplitTests/Init/ConfigTest.swift b/SplitTests/Init/ConfigTest.swift index 813835ed1..82fb74611 100644 --- a/SplitTests/Init/ConfigTest.swift +++ b/SplitTests/Init/ConfigTest.swift @@ -14,7 +14,138 @@ import XCTest class ConfigTest: XCTestCase { var config = SplitClientConfig() - // MARK: ImpressionsMode + override func setUp() { + super.setUp() + config = SplitClientConfig() + // Reset logger level to default (.none) before each test + Logger.shared.level = SplitLogLevel.none.toLogLevel() + } + + func testIsDebugModeEnabledGetterReturnsTrueWhenDebug() { + Logger.shared.level = SplitLogLevel.debug.toLogLevel() + + XCTAssertTrue(config.isDebugModeEnabled) + } + + func testIsDebugModeEnabledGetterReturnsFalseWhenNotDebug() { + Logger.shared.level = SplitLogLevel.verbose.toLogLevel() + + XCTAssertFalse(config.isDebugModeEnabled) + } + + func testIsDebugModeEnabledGetterReturnsFalseWhenNone() { + Logger.shared.level = SplitLogLevel.none.toLogLevel() + + XCTAssertFalse(config.isDebugModeEnabled) + } + + func testIsDebugModeEnabledSetterSetsDebugWhenLevelIsNone() { + // Level starts at .none + config.isDebugModeEnabled = true + + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .debug) + } + + func testIsDebugModeEnabledSetterKeepsNoneWhenSetToFalse() { + config.isDebugModeEnabled = false + + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .none) + } + + func testIsDebugModeEnabledSetterDoesNotChangeWhenLevelIsNotNone() { + Logger.shared.level = SplitLogLevel.warning.toLogLevel() + + config.isDebugModeEnabled = true + + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .warning) + } + + func testIsVerboseModeEnabledGetterReturnsTrueWhenVerbose() { + Logger.shared.level = SplitLogLevel.verbose.toLogLevel() + + XCTAssertTrue(config.isVerboseModeEnabled) + } + + func testIsVerboseModeEnabledGetterReturnsFalseWhenNotVerbose() { + Logger.shared.level = SplitLogLevel.debug.toLogLevel() + + XCTAssertFalse(config.isVerboseModeEnabled) + } + + func testIsVerboseModeEnabledGetterReturnsFalseWhenNone() { + Logger.shared.level = SplitLogLevel.none.toLogLevel() + + XCTAssertFalse(config.isVerboseModeEnabled) + } + + func testIsVerboseModeEnabledSetterSetsVerboseWhenLevelIsNone() { + config.isVerboseModeEnabled = true + + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .verbose) + } + + func testIsVerboseModeEnabledSetterKeepsNoneWhenSetToFalse() { + config.isVerboseModeEnabled = false + + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .none) + } + + func testIsVerboseModeEnabledSetterDoesNotChangeWhenLevelIsNotNone() { + Logger.shared.level = SplitLogLevel.error.toLogLevel() + + config.isVerboseModeEnabled = true + + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .error) + } + + func testLogLevelGetterReturnsCurrentLevel() { + let levels: [SplitLogLevel] = [.verbose, .debug, .info, .warning, .error, .none] + + for level in levels { + Logger.shared.level = level.toLogLevel() + + XCTAssertEqual(config.logLevel, level, + "Expected logLevel to return \(level)") + } + } + + func testSetLogLevelWithValidString() { + config.set(logLevel: "VERBOSE") + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .verbose) + + config.set(logLevel: "DEBUG") + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .debug) + + config.set(logLevel: "INFO") + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .info) + + config.set(logLevel: "WARNING") + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .warning) + + config.set(logLevel: "ERROR") + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .error) + + config.set(logLevel: "NONE") + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .none) + } + + func testSetLogLevelWithInvalidStringDefaultsToNone() { + Logger.shared.level = SplitLogLevel.debug.toLogLevel() + + config.set(logLevel: "INVALID") + + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .none) + } + + func testSetLogLevelWithEmptyStringDefaultsToNone() { + Logger.shared.level = SplitLogLevel.verbose.toLogLevel() + + config.set(logLevel: "") + + XCTAssertEqual(SplitLogLevel.from(Logger.shared.level), .none) + } + + // MARK: - ImpressionsMode func testImpressionsModeEmpty() { config.impressionsMode = "" diff --git a/SplitTests/Integration/Api/LoggerTest.swift b/SplitTests/Integration/Api/LoggerTest.swift index fdb440cb5..53e7ae682 100644 --- a/SplitTests/Integration/Api/LoggerTest.swift +++ b/SplitTests/Integration/Api/LoggerTest.swift @@ -7,11 +7,12 @@ // import Foundation +import Logging @testable import Split import XCTest -@testable import Split +// This test verifies that SplitLogLevel mapping to LogLevel works correctly class LoggerTest : XCTestCase { let printer = LogPrinterStub() @@ -19,10 +20,11 @@ class LoggerTest : XCTestCase { override func setUp() { printer.clear() Logger.shared.printer = printer + Logger.shared.dateProvider = DefaultDateProvider() } func testNone() { - Logger.shared.level = .none + Logger.shared.level = SplitLogLevel.none.toLogLevel() logAll() @@ -34,7 +36,7 @@ class LoggerTest : XCTestCase { } func testVerbose() { - Logger.shared.level = .verbose + Logger.shared.level = SplitLogLevel.verbose.toLogLevel() logAll() @@ -46,7 +48,7 @@ class LoggerTest : XCTestCase { } func testDebug() { - Logger.shared.level = .debug + Logger.shared.level = SplitLogLevel.debug.toLogLevel() logAll() @@ -58,7 +60,7 @@ class LoggerTest : XCTestCase { } func testInfo() { - Logger.shared.level = .info + Logger.shared.level = SplitLogLevel.info.toLogLevel() logAll() @@ -70,7 +72,7 @@ class LoggerTest : XCTestCase { } func testWarning() { - Logger.shared.level = .warning + Logger.shared.level = SplitLogLevel.warning.toLogLevel() logAll() @@ -82,7 +84,7 @@ class LoggerTest : XCTestCase { } func testError() { - Logger.shared.level = .error + Logger.shared.level = SplitLogLevel.error.toLogLevel() logAll() @@ -93,7 +95,31 @@ class LoggerTest : XCTestCase { XCTAssertTrue(isLogged(level: .error)) } - private func isLogged(level: SplitLogLevel) -> Bool { + func testToLogLevelVerbose() { + XCTAssertEqual(SplitLogLevel.verbose.toLogLevel(), LogLevel.verbose) + } + + func testToLogLevelDebug() { + XCTAssertEqual(SplitLogLevel.debug.toLogLevel(), LogLevel.debug) + } + + func testToLogLevelInfo() { + XCTAssertEqual(SplitLogLevel.info.toLogLevel(), LogLevel.info) + } + + func testToLogLevelWarning() { + XCTAssertEqual(SplitLogLevel.warning.toLogLevel(), LogLevel.warning) + } + + func testToLogLevelError() { + XCTAssertEqual(SplitLogLevel.error.toLogLevel(), LogLevel.error) + } + + func testToLogLevelNone() { + XCTAssertEqual(SplitLogLevel.none.toLogLevel(), LogLevel.none) + } + + private func isLogged(level: LogLevel) -> Bool { return printer.logs.filter { $0.contains("\(level.rawValue)") }.count > 0 } @@ -107,6 +133,7 @@ class LoggerTest : XCTestCase { override func tearDown() { Logger.shared.printer = DefaultLogPrinter() + Logger.shared.dateProvider = DefaultDateProvider() printer.clear() } } diff --git a/SplitiOSFull.xctestplan b/SplitiOSFull.xctestplan index ddb408e9d..03ba6994a 100644 --- a/SplitiOSFull.xctestplan +++ b/SplitiOSFull.xctestplan @@ -48,6 +48,13 @@ "identifier" : "592C6AA4211B6C99002D120C", "name" : "SplitTestsSwift5" } + }, + { + "target" : { + "containerPath" : "container:Split.xcodeproj", + "identifier" : "8000B2F11F535D99847B28BE", + "name" : "LoggingTests" + } } ], "version" : 1