diff --git a/Source/LDSwiftEventSource.swift b/Source/LDSwiftEventSource.swift index 86952d2..cd10eb7 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) @@ -277,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() } @@ -305,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 ec0abd8..9a384f0 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: String) + func log(_ level: Level, _ staticMsg: StaticString, _ arg1: String, _ arg2: String) +} -#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) + + 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) { - os_log(staticMsg, log: logger, type: level.osLogType, arg1, arg2) + + func log(_ level: Level, _ staticMsg: StaticString, _ arg1: String, _ arg2: String) { + 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: String) {} + func log(_ level: Level, _ staticMsg: StaticString, _ arg1: String, _ arg2: String) {} }