Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds ability to provide a OSLog instance via the Config.logger property #78

Merged
merged 2 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions Source/LDSwiftEventSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 = ""
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reviewers: just reordering a bit for something closer to alphabetization.


#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
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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
Expand Down
62 changes: 38 additions & 24 deletions Source/Logs.swift
Original file line number Diff line number Diff line change
@@ -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) {}
}
Loading