From 7c8db7f064b3e54228ccbd511e6776650dc287e7 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Wed, 13 May 2026 11:28:22 +0100 Subject: [PATCH] Make logger shared state concurrency-safe --- .../ShopifyAcceleratedCheckouts.swift | 11 ++++-- .../Sources/ShopifyCheckoutKit/Logger.swift | 36 +++++++++++++---- .../ShopifyCheckoutKitTests/LoggerTests.swift | 18 ++++++++- .../api/ShopifyAcceleratedCheckouts.json | 7 ---- platforms/swift/api/ShopifyCheckoutKit.json | 39 ++++++++----------- 5 files changed, 68 insertions(+), 43 deletions(-) diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts.swift index 15ccd698..da76fea0 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts.swift @@ -8,13 +8,16 @@ public enum ShopifyAcceleratedCheckouts { /// The logging level for Accelerated Checkouts operations /// Default: .error - which will emit "error" and "fault" logs - public static var logLevel: LogLevel = .error { - didSet { - logger.logLevel = logLevel + public static var logLevel: LogLevel { + get { + logger.logLevel + } + set { + logger.logLevel = newValue } } /// Shared logger for ShopifyAcceleratedCheckouts /// To modify the logLevel - internal static var logger = OSLogger(prefix: name, logLevel: logLevel) + internal static let logger = OSLogger(prefix: name, logLevel: .error) } diff --git a/platforms/swift/Sources/ShopifyCheckoutKit/Logger.swift b/platforms/swift/Sources/ShopifyCheckoutKit/Logger.swift index 0ce01e2d..9307d736 100644 --- a/platforms/swift/Sources/ShopifyCheckoutKit/Logger.swift +++ b/platforms/swift/Sources/ShopifyCheckoutKit/Logger.swift @@ -10,21 +10,39 @@ public enum LogLevel: String, CaseIterable { case none } -public class OSLogger { +public class OSLogger: @unchecked Sendable { private let logger = OSLog(subsystem: subsystem, category: OSLog.Category.pointsOfInterest) - private var prefix: String - package var logLevel: LogLevel + private let prefix: String + private let logLevelStorage: LockedValue - public static var shared = OSLogger() + package var logLevel: LogLevel { + get { + logLevelStorage.get() + } + set { + logLevelStorage.set(newValue) + } + } + + private static let sharedLogger = LockedValue(OSLogger()) + + public static var shared: OSLogger { + get { + sharedLogger.get() + } + set { + sharedLogger.set(newValue) + } + } public init() { prefix = "ShopifyCheckoutKit" - logLevel = ShopifyCheckoutKit.configuration.logLevel + logLevelStorage = LockedValue(ShopifyCheckoutKit.configuration.logLevel) } public init(prefix: String, logLevel: LogLevel) { self.prefix = prefix - self.logLevel = logLevel + logLevelStorage = LockedValue(logLevel) } public func debug(_ message: String) { @@ -58,11 +76,13 @@ public class OSLogger { } private func shouldEmit(_ choice: LogLevel) -> Bool { - if logLevel == .none { + let currentLogLevel = logLevel + + if currentLogLevel == .none { return false } - return logLevel == .all || logLevel == choice + return currentLogLevel == .all || currentLogLevel == choice } } diff --git a/platforms/swift/Tests/ShopifyCheckoutKitTests/LoggerTests.swift b/platforms/swift/Tests/ShopifyCheckoutKitTests/LoggerTests.swift index 076d6884..91192d95 100644 --- a/platforms/swift/Tests/ShopifyCheckoutKitTests/LoggerTests.swift +++ b/platforms/swift/Tests/ShopifyCheckoutKitTests/LoggerTests.swift @@ -37,9 +37,23 @@ final class OSLoggerTests: XCTestCase { XCTAssertNotNil(OSLogger.shared) } - func test_defaultInitializer_withNoParameters_shouldMaintainBackwardsCompatibility() { + func test_defaultInitializer_withNoParameters_shouldUseConfigurationLogLevel() { + ShopifyCheckoutKit.configure { $0.logLevel = .debug } + let logger = OSLogger() - XCTAssertNotNil(logger) + + XCTAssertEqual(logger.logLevel, .debug) + } + + func test_sharedLogger_canBeReplaced() { + let originalLogger = OSLogger.shared + defer { OSLogger.shared = originalLogger } + + let replacementLogger = OSLogger(prefix: "Replacement", logLevel: .debug) + + OSLogger.shared = replacementLogger + + XCTAssertTrue(OSLogger.shared === replacementLogger) } func test_logLevelNone_withAllLogCalls_shouldBlockAllLogging() { diff --git a/platforms/swift/api/ShopifyAcceleratedCheckouts.json b/platforms/swift/api/ShopifyAcceleratedCheckouts.json index 9bee51c1..d5daeb64 100644 --- a/platforms/swift/api/ShopifyAcceleratedCheckouts.json +++ b/platforms/swift/api/ShopifyAcceleratedCheckouts.json @@ -131,11 +131,6 @@ "mangledName": "$s27ShopifyAcceleratedCheckoutsAAO8logLevel0A11CheckoutKit03LogE0OvpZ", "moduleName": "ShopifyAcceleratedCheckouts", "static": true, - "declAttributes": [ - "HasInitialValue", - "HasStorage" - ], - "hasStorage": true, "accessors": [ { "kind": "Accessor", @@ -154,7 +149,6 @@ "mangledName": "$s27ShopifyAcceleratedCheckoutsAAO8logLevel0A11CheckoutKit03LogE0OvgZ", "moduleName": "ShopifyAcceleratedCheckouts", "static": true, - "implicit": true, "accessorKind": "get" }, { @@ -179,7 +173,6 @@ "mangledName": "$s27ShopifyAcceleratedCheckoutsAAO8logLevel0A11CheckoutKit03LogE0OvsZ", "moduleName": "ShopifyAcceleratedCheckouts", "static": true, - "implicit": true, "accessorKind": "set" } ] diff --git a/platforms/swift/api/ShopifyCheckoutKit.json b/platforms/swift/api/ShopifyCheckoutKit.json index f14e3415..202b919e 100644 --- a/platforms/swift/api/ShopifyCheckoutKit.json +++ b/platforms/swift/api/ShopifyCheckoutKit.json @@ -4521,10 +4521,6 @@ "mangledName": "$s18ShopifyCheckoutKit8OSLoggerC8logLevelAA03LogF0Ovp", "moduleName": "ShopifyCheckoutKit", "isInternal": true, - "declAttributes": [ - "HasStorage" - ], - "hasStorage": true, "accessors": [ { "kind": "Accessor", @@ -4542,11 +4538,7 @@ "usr": "s:18ShopifyCheckoutKit8OSLoggerC8logLevelAA03LogF0Ovg", "mangledName": "$s18ShopifyCheckoutKit8OSLoggerC8logLevelAA03LogF0Ovg", "moduleName": "ShopifyCheckoutKit", - "implicit": true, "isInternal": true, - "declAttributes": [ - "Transparent" - ], "accessorKind": "get" }, { @@ -4570,11 +4562,7 @@ "usr": "s:18ShopifyCheckoutKit8OSLoggerC8logLevelAA03LogF0Ovs", "mangledName": "$s18ShopifyCheckoutKit8OSLoggerC8logLevelAA03LogF0Ovs", "moduleName": "ShopifyCheckoutKit", - "implicit": true, "isInternal": true, - "declAttributes": [ - "Transparent" - ], "accessorKind": "set" } ] @@ -4597,11 +4585,8 @@ "moduleName": "ShopifyCheckoutKit", "static": true, "declAttributes": [ - "HasInitialValue", - "Final", - "HasStorage" + "Final" ], - "hasStorage": true, "accessors": [ { "kind": "Accessor", @@ -4620,10 +4605,8 @@ "mangledName": "$s18ShopifyCheckoutKit8OSLoggerC6sharedACvgZ", "moduleName": "ShopifyCheckoutKit", "static": true, - "implicit": true, "declAttributes": [ - "Final", - "Transparent" + "Final" ], "accessorKind": "get" }, @@ -4649,10 +4632,8 @@ "mangledName": "$s18ShopifyCheckoutKit8OSLoggerC6sharedACvsZ", "moduleName": "ShopifyCheckoutKit", "static": true, - "implicit": true, "declAttributes": [ - "Final", - "Transparent" + "Final" ], "accessorKind": "set" } @@ -4804,6 +4785,20 @@ "mangledName": "$s18ShopifyCheckoutKit8OSLoggerC", "moduleName": "ShopifyCheckoutKit", "conformances": [ + { + "kind": "Conformance", + "name": "Sendable", + "printedName": "Sendable", + "usr": "s:s8SendableP", + "mangledName": "$ss8SendableP" + }, + { + "kind": "Conformance", + "name": "SendableMetatype", + "printedName": "SendableMetatype", + "usr": "s:s16SendableMetatypeP", + "mangledName": "$ss16SendableMetatypeP" + }, { "kind": "Conformance", "name": "Copyable",