From c10ec2936e77959f828a041b71ea56e454e39ff2 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Thu, 30 May 2024 15:11:00 -0500 Subject: [PATCH 1/2] feat: adds ability to provide a OSLog instance via the Config.logger property --- Source/LDSwiftEventSource.swift | 29 ++++++++++++++--- Source/Logs.swift | 58 ++++++++++++++++++++------------- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/Source/LDSwiftEventSource.swift b/Source/LDSwiftEventSource.swift index 86952d2..1225e5b 100644 --- a/Source/LDSwiftEventSource.swift +++ b/Source/LDSwiftEventSource.swift @@ -4,6 +4,12 @@ import Foundation import FoundationNetworking #endif +#if canImport(os) +// os_log is not supported on some platforms, but we want to use it for most of our customer's +// use cases that use Apple OSs +import os.log +#endif + /** Provides an EventSource client for consuming Server-Sent Events. @@ -51,12 +57,18 @@ public class EventSource { public var method: String = "GET" /// Optional HTTP body to be included in the API request. public var body: Data? - /// An initial value for the last-event-id header to be sent on the initial request - public var lastEventId: String = "" /// Additional HTTP headers to be set on the request public var headers: [String: String] = [:] /// Transform function to allow dynamically configuring the headers on each API request. public var headerTransform: HeaderTransform = { $0 } + /// An initial value for the last-event-id header to be sent on the initial request + public var lastEventId: String = "" + +#if canImport(os) + /// Configure the logger that will be used. + public var logger: OSLog = OSLog(subsystem: "com.launchdarkly.swift-eventsource", category: "LDEventSource") +#endif + /// The minimum amount of time to wait before reconnecting after a failure public var reconnectTime: TimeInterval = 1.0 /// The maximum amount of time to wait before reconnecting after a failure @@ -152,8 +164,9 @@ class ReconnectionTimer { // MARK: EventSourceDelegate class EventSourceDelegate: NSObject, URLSessionDataDelegate { private let delegateQueue: DispatchQueue = DispatchQueue(label: "ESDelegateQueue") - private let logger = Logs() - + + public var logger: InternalLogging + private let config: EventSource.Config private var readyState: ReadyState = .raw { @@ -170,6 +183,14 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate { init(config: EventSource.Config) { self.config = config + +#if canImport(os) + self.logger = OSLogAdapter(osLog: config.logger) +#else + self.logger = NoOpLogging() +#endif + + self.eventParser = EventParser(handler: config.handler, initialEventId: config.lastEventId, initialRetry: config.reconnectTime) diff --git a/Source/Logs.swift b/Source/Logs.swift index ec0abd8..46d62bd 100644 --- a/Source/Logs.swift +++ b/Source/Logs.swift @@ -1,38 +1,52 @@ import Foundation -#if !os(Linux) && !os(Windows) +#if canImport(os) import os.log #endif -class Logs { - enum Level { - case debug, info, warn, error +protocol InternalLogging { + func log(_ level: Level, _ staticMsg: StaticString) + func log(_ level: Level, _ staticMsg: StaticString, _ arg: CVarArg) + func log(_ level: Level, _ staticMsg: StaticString, _ arg1: CVarArg, _ arg2: CVarArg) +} -#if !os(Linux) && !os(Windows) - private static let osLogTypes = [ Level.debug: OSLogType.debug, - Level.info: OSLogType.info, - Level.warn: OSLogType.default, - Level.error: OSLogType.error] - var osLogType: OSLogType { Level.osLogTypes[self]! } -#endif - } +enum Level { + case debug, info, warn, error -#if !os(Linux) && !os(Windows) - private let logger: OSLog = OSLog(subsystem: "com.launchdarkly.swift-eventsource", category: "LDEventSource") +#if canImport(os) + private static let osLogTypes = [ Level.debug: OSLogType.debug, + Level.info: OSLogType.info, + Level.warn: OSLogType.default, + Level.error: OSLogType.error] + var osLogType: OSLogType { Level.osLogTypes[self]! } +#endif +} +#if canImport(os) +class OSLogAdapter: InternalLogging { + + private let osLog: OSLog + + init(osLog: OSLog) { + self.osLog = osLog + } + func log(_ level: Level, _ staticMsg: StaticString) { - os_log(staticMsg, log: logger, type: level.osLogType) + os_log(staticMsg, log: self.osLog, type: level.osLogType) } - + func log(_ level: Level, _ staticMsg: StaticString, _ arg: CVarArg) { - os_log(staticMsg, log: logger, type: level.osLogType, arg) + os_log(staticMsg, log: self.osLog, type: level.osLogType, arg) } - + func log(_ level: Level, _ staticMsg: StaticString, _ arg1: CVarArg, _ arg2: CVarArg) { - os_log(staticMsg, log: logger, type: level.osLogType, arg1, arg2) + os_log(staticMsg, log: self.osLog, type: level.osLogType, arg1, arg2) } -#else - // We use Any over CVarArg here, because on Linux prior to Swift 5.4 String does not conform to CVarArg - func log(_ level: Level, _ staticMsg: StaticString, _ args: Any...) { } +} #endif + +class NoOpLogging: InternalLogging { + func log(_ level: Level, _ staticMsg: StaticString) {} + func log(_ level: Level, _ staticMsg: StaticString, _ arg: CVarArg) {} + func log(_ level: Level, _ staticMsg: StaticString, _ arg1: CVarArg, _ arg2: CVarArg) {} } From 10986092a8ee40d306cb5850b6601b7fbab5abc0 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Thu, 30 May 2024 15:49:51 -0500 Subject: [PATCH 2/2] refactoring to work around String CVarArg compatibility issue --- Source/LDSwiftEventSource.swift | 6 ++++-- Source/Logs.swift | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Source/LDSwiftEventSource.swift b/Source/LDSwiftEventSource.swift index 1225e5b..cd10eb7 100644 --- a/Source/LDSwiftEventSource.swift +++ b/Source/LDSwiftEventSource.swift @@ -298,7 +298,8 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate { readyState = .closed let sleep = reconnectionTimer.reconnectDelay(baseDelay: currentRetry) - logger.log(.info, "Waiting %.3f seconds before reconnecting...", sleep) + // this formatting shenanigans is to workaround String not implementing CVarArg on Swift<5.4 on Linux + logger.log(.info, "Waiting %@ seconds before reconnecting...", String(format: "%.3f", sleep)) delegateQueue.asyncAfter(deadline: .now() + sleep) { [weak self] in self?.connect() } @@ -326,7 +327,8 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate { config.handler.onOpened() completionHandler(.allow) } else { - logger.log(.info, "Unsuccessful response: %d", statusCode) + // this formatting shenanigans is to workaround String not implementing CVarArg on Swift<5.4 on Linux + logger.log(.info, "Unsuccessful response: %@", String(format: "%d", statusCode)) if dispatchError(error: UnsuccessfulResponseError(responseCode: statusCode)) == .shutdown { logger.log(.info, "Connection has been explicitly shut down by error handler") readyState = .shutdown diff --git a/Source/Logs.swift b/Source/Logs.swift index 46d62bd..9a384f0 100644 --- a/Source/Logs.swift +++ b/Source/Logs.swift @@ -6,8 +6,8 @@ import os.log protocol InternalLogging { func log(_ level: Level, _ staticMsg: StaticString) - func log(_ level: Level, _ staticMsg: StaticString, _ arg: CVarArg) - func log(_ level: Level, _ staticMsg: StaticString, _ arg1: CVarArg, _ arg2: CVarArg) + func log(_ level: Level, _ staticMsg: StaticString, _ arg: String) + func log(_ level: Level, _ staticMsg: StaticString, _ arg1: String, _ arg2: String) } enum Level { @@ -35,11 +35,11 @@ class OSLogAdapter: InternalLogging { os_log(staticMsg, log: self.osLog, type: level.osLogType) } - func log(_ level: Level, _ staticMsg: StaticString, _ arg: CVarArg) { + func log(_ level: Level, _ staticMsg: StaticString, _ arg: String) { os_log(staticMsg, log: self.osLog, type: level.osLogType, arg) } - func log(_ level: Level, _ staticMsg: StaticString, _ arg1: CVarArg, _ arg2: CVarArg) { + func log(_ level: Level, _ staticMsg: StaticString, _ arg1: String, _ arg2: String) { os_log(staticMsg, log: self.osLog, type: level.osLogType, arg1, arg2) } } @@ -47,6 +47,6 @@ class OSLogAdapter: InternalLogging { class NoOpLogging: InternalLogging { func log(_ level: Level, _ staticMsg: StaticString) {} - func log(_ level: Level, _ staticMsg: StaticString, _ arg: CVarArg) {} - func log(_ level: Level, _ staticMsg: StaticString, _ arg1: CVarArg, _ arg2: CVarArg) {} + func log(_ level: Level, _ staticMsg: StaticString, _ arg: String) {} + func log(_ level: Level, _ staticMsg: StaticString, _ arg1: String, _ arg2: String) {} }