diff --git a/LaunchDarkly/LaunchDarkly/LDClient.swift b/LaunchDarkly/LaunchDarkly/LDClient.swift index 5883af6a..acfff012 100644 --- a/LaunchDarkly/LaunchDarkly/LDClient.swift +++ b/LaunchDarkly/LaunchDarkly/LDClient.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog enum LDClientRunMode { case foreground, background @@ -163,7 +164,10 @@ public class LDClient { } } isOnline = goOnline - Log.debug(typeName(and: "setOnline", appending: ": ") + (reasonOnlineUnavailable.isEmpty ? "\(self.isOnline)." : "true aborted.") + reasonOnlineUnavailable) + os_log("%s %s. %s", log: config.logger, type: .debug, + typeName(and: "setOnline"), + reasonOnlineUnavailable.isEmpty ? self.isOnline.description : "true aborted", + reasonOnlineUnavailable) } private var canGoOnline: Bool { @@ -194,10 +198,9 @@ public class LDClient { didSet { guard runMode != oldValue else { - Log.debug(typeName(and: #function) + " aborted. Old runMode equals new runMode.") + os_log("%s runMode aborted. Old runMode equals new runMode", log: config.logger, type: .debug, typeName(and: #function)) return } - Log.debug(typeName(and: #function, appending: ": ") + "\(runMode)") let willSetSynchronizerOnline = isOnline && isInSupportedRunMode flagSynchronizer.isOnline = false @@ -216,7 +219,7 @@ public class LDClient { // Stores ConnectionInformation in UserDefaults on change var connectionInformation: ConnectionInformation { didSet { - Log.debug(connectionInformation.description) + os_log("%s", log: config.logger, type: .debug, connectionInformation.description) ConnectionInformationStore.storeConnectionInformation(connectionInformation: connectionInformation) if connectionInformation.currentConnectionMode != oldValue.currentConnectionMode { flagChangeNotifier.notifyConnectionModeChangedObservers(connectionMode: connectionInformation.currentConnectionMode) @@ -238,22 +241,22 @@ public class LDClient { } private func internalClose() { - Log.debug(typeName(and: #function, appending: "- ") + "stopping") + os_log("%s stopping", log: config.logger, type: .debug, typeName(and: #function)) internalFlush() internalSetOnline(false) hasStarted = false - Log.debug(typeName(and: #function, appending: "- ") + "stopped") + os_log("%s stopped", log: config.logger, type: .debug, typeName(and: #function)) } @objc private func didEnterBackground() { - Log.debug(typeName(and: #function)) + os_log("%s", log: config.logger, type: .debug, typeName(and: #function)) Thread.performOnMain { runMode = .background } } @objc private func willEnterForeground() { - Log.debug(typeName(and: #function)) + os_log("%s", log: config.logger, type: .debug, typeName(and: #function)) Thread.performOnMain { runMode = .foreground } @@ -289,7 +292,7 @@ public class LDClient { func internalIdentify(newContext: LDContext, completion: (() -> Void)? = nil) { var updatedContext = newContext if config.autoEnvAttributes { - updatedContext = AutoEnvContextModifier(environmentReporter: environmentReporter).modifyContext(updatedContext) + updatedContext = AutoEnvContextModifier(environmentReporter: environmentReporter, logger: config.logger).modifyContext(updatedContext) } internalIdentifyQueue.sync { @@ -300,7 +303,7 @@ public class LDClient { } self.context = updatedContext - Log.debug(self.typeName(and: #function) + "new context set with key: " + self.context.fullyQualifiedKey() ) + os_log("%s new context set with key: %s", log: config.logger, type: .debug, typeName(and: #function), self.context.fullyQualifiedKey()) let wasOnline = self.isOnline self.internalSetOnline(false) @@ -358,7 +361,7 @@ public class LDClient { - parameter handler: The closure the SDK will execute when the feature flag changes. */ public func observe(key: LDFlagKey, owner: LDObserverOwner, handler: @escaping LDFlagChangeHandler) { - Log.debug(typeName(and: #function) + "flagKey: \(key), owner: \(String(describing: owner))") + os_log("%s flagKey: %s owner: %s", log: config.logger, type: .debug, typeName(and: #function), key, String(describing: owner)) flagChangeNotifier.addFlagChangeObserver(FlagChangeObserver(key: key, owner: owner, flagChangeHandler: handler)) } @@ -386,7 +389,7 @@ public class LDClient { - parameter handler: The LDFlagCollectionChangeHandler the SDK will execute 1 time when any of the observed feature flags change. */ public func observe(keys: [LDFlagKey], owner: LDObserverOwner, handler: @escaping LDFlagCollectionChangeHandler) { - Log.debug(typeName(and: #function) + "flagKeys: \(keys), owner: \(String(describing: owner))") + os_log("%s flagKeys: %s owner: %s", log: config.logger, type: .debug, typeName(and: #function), String(describing: keys), String(describing: owner)) flagChangeNotifier.addFlagChangeObserver(FlagChangeObserver(keys: keys, owner: owner, flagCollectionChangeHandler: handler)) } @@ -413,7 +416,7 @@ public class LDClient { - parameter handler: The LDFlagCollectionChangeHandler the SDK will execute 1 time when any of the observed feature flags change. */ public func observeAll(owner: LDObserverOwner, handler: @escaping LDFlagCollectionChangeHandler) { - Log.debug(typeName(and: #function) + " owner: \(String(describing: owner))") + os_log("%s owner: %s", log: config.logger, type: .debug, typeName(and: #function), String(describing: owner)) flagChangeNotifier.addFlagChangeObserver(FlagChangeObserver(keys: LDFlagKey.anyKey, owner: owner, flagCollectionChangeHandler: handler)) } @@ -440,7 +443,7 @@ public class LDClient { - parameter handler: The LDFlagsUnchangedHandler the SDK will execute 1 time when a flag request completes with no flags changed. */ public func observeFlagsUnchanged(owner: LDObserverOwner, handler: @escaping LDFlagsUnchangedHandler) { - Log.debug(typeName(and: #function) + " owner: \(String(describing: owner))") + os_log("%s owner: %s", log: config.logger, type: .debug, typeName(and: #function), String(describing: owner)) flagChangeNotifier.addFlagsUnchangedObserver(FlagsUnchangedObserver(owner: owner, flagsUnchangedHandler: handler)) } @@ -464,7 +467,7 @@ public class LDClient { - parameter handler: The LDConnectionModeChangedHandler the SDK will execute 1 time when ConnectionInformation.currentConnectionMode is changed. */ public func observeCurrentConnectionMode(owner: LDObserverOwner, handler: @escaping LDConnectionModeChangedHandler) { - Log.debug(typeName(and: #function) + " owner: \(String(describing: owner))") + os_log("%s owner: %s", log: config.logger, type: .debug, typeName(and: #function), String(describing: owner)) flagChangeNotifier.addConnectionModeChangedObserver(ConnectionModeChangedObserver(owner: owner, connectionModeChangedHandler: handler)) } @@ -476,12 +479,12 @@ public class LDClient { - parameter owner: The LDFlagChangeOwner owning the handlers to remove, whether a flag change handler or flags unchanged handler. */ public func stopObserving(owner: LDObserverOwner) { - Log.debug(typeName(and: #function) + " owner: \(String(describing: owner))") + os_log("%s owner: %s", log: config.logger, type: .debug, typeName(and: #function), String(describing: owner)) flagChangeNotifier.removeObserver(owner: owner) } private func onFlagSyncComplete(result: FlagSyncResult) { - Log.debug(typeName(and: #function) + "result: \(result)") + os_log("%s result: %s", log: config.logger, type: .debug, typeName(and: #function), String(describing: result)) switch result { case let .flagCollection((flagCollection, etag)): let oldStoredItems = flagStore.storedItems @@ -502,13 +505,13 @@ public class LDClient { connectionInformation.lastKnownFlagValidity = Date() flagChangeNotifier.notifyUnchanged() case .error(let synchronizingError): - process(synchronizingError, logPrefix: typeName(and: #function, appending: ": ")) + process(synchronizingError, logPrefix: typeName(and: #function)) } } private func process(_ synchronizingError: SynchronizingError, logPrefix: String) { if synchronizingError.isClientUnauthorized { - Log.debug(logPrefix + "LDClient is unauthorized") + os_log("%s LDClient is unauthorized", log: config.logger, type: .debug, logPrefix) internalSetOnline(false) } connectionInformation = ConnectionInformation.synchronizingErrorCheck(synchronizingError: synchronizingError, connectionInformation: connectionInformation) @@ -542,11 +545,15 @@ public class LDClient { public func track(key: String, data: LDValue? = nil, metricValue: Double? = nil) { guard hasStarted else { - Log.debug(typeName(and: #function) + "aborted. LDClient not started") + os_log("%s aborted. LDClient not started", log: config.logger, type: .debug, typeName(and: #function)) return } let event = CustomEvent(key: key, context: context, data: data ?? .null, metricValue: metricValue) - Log.debug(typeName(and: #function) + "key: \(key), data: \(String(describing: data)), metricValue: \(String(describing: metricValue))") + os_log("%s key: %s data: %s, metricValue: %s", log: config.logger, type: .debug, + typeName(and: #function), + key, + String(describing: data), + String(describing: metricValue)) eventReporter.record(event) } @@ -567,15 +574,15 @@ public class LDClient { private func onEventSyncComplete(result: SynchronizingError?) { if let synchronizingError = result { - Log.debug(typeName(and: #function) + "result: \(synchronizingError)") - process(synchronizingError, logPrefix: typeName(and: #function, appending: ": ")) + os_log("%s result: %s", log: config.logger, type: .debug, typeName(and: #function), String(describing: synchronizingError)) + process(synchronizingError, logPrefix: typeName(and: #function)) } else { - Log.debug(typeName(and: #function) + "result: success") + os_log("%s result: success", log: config.logger, type: .debug, typeName(and: #function)) } } @objc private func didCloseEventSource() { - Log.debug(typeName(and: #function)) + os_log("%s", log: config.logger, type: .debug, typeName(and: #function)) self.connectionInformation = ConnectionInformation.lastSuccessfulConnectionCheck(connectionInformation: self.connectionInformation) } @@ -597,16 +604,16 @@ public class LDClient { } static func start(serviceFactory: ClientServiceCreating?, config: LDConfig, context: LDContext? = nil, completion: (() -> Void)? = nil) { - Log.debug("LDClient starting") + os_log("%s LDClient starting", log: config.logger, type: .debug, typeName(and: #function)) if serviceFactory != nil { get()?.close() } if instances != nil { - Log.debug("LDClient.start() was called more than once!") + os_log("%s LDClient.start() was called more than once!", log: config.logger, type: .debug, typeName(and: #function)) return } - let serviceFactory = serviceFactory ?? ClientServiceFactory() + let serviceFactory = serviceFactory ?? ClientServiceFactory(config: config) var keys = [config.mobileKey] keys.append(contentsOf: config.getSecondaryMobileKeys().values) serviceFactory.makeCacheConverter().convertCacheData(serviceFactory: serviceFactory, keysToConvert: keys, maxCachedContexts: config.maxCachedContexts) @@ -617,7 +624,7 @@ public class LDClient { let completionCheck = { internalCount += 1 if internalCount > mobileKeys.count { - Log.debug("All LDClients finished starting") + os_log("%s All LDClients finished starting", log: config.logger, type: .debug, typeName(and: #function)) completion?() } } @@ -675,7 +682,6 @@ public class LDClient { */ public static func get(environment: String = LDConfig.Constants.primaryEnvironmentName) -> LDClient? { guard let internalInstances = LDClient.instances else { - Log.debug("LDClient.get() was called before init()!") return nil } return internalInstances[environment] @@ -708,7 +714,7 @@ public class LDClient { private init(serviceFactory: ClientServiceCreating, configuration: LDConfig, startContext: LDContext?, completion: (() -> Void)? = nil) { self.serviceFactory = serviceFactory - environmentReporter = self.serviceFactory.makeEnvironmentReporter(config: configuration) + environmentReporter = self.serviceFactory.makeEnvironmentReporter() flagCache = self.serviceFactory.makeFeatureFlagCache(mobileKey: configuration.mobileKey, maxCachedContexts: configuration.maxCachedContexts) flagStore = self.serviceFactory.makeFlagStore() flagChangeNotifier = self.serviceFactory.makeFlagChangeNotifier() @@ -719,10 +725,10 @@ public class LDClient { context = startContext ?? anonymousContext if config.autoEnvAttributes { - context = AutoEnvContextModifier(environmentReporter: environmentReporter).modifyContext(context) + context = AutoEnvContextModifier(environmentReporter: environmentReporter, logger: config.logger).modifyContext(context) } - service = self.serviceFactory.makeDarklyServiceProvider(config: config, context: context, envReporter: environmentReporter) + service = self.serviceFactory.makeDarklyServiceProvider(context: context, envReporter: environmentReporter) diagnosticReporter = self.serviceFactory.makeDiagnosticReporter(service: service, environmentReporter: environmentReporter) eventReporter = self.serviceFactory.makeEventReporter(service: service) connectionInformation = self.serviceFactory.makeConnectionInformation() @@ -749,7 +755,6 @@ public class LDClient { service: service, onSyncComplete: onFlagSyncComplete) - Log.level = environmentReporter.isDebugBuild && config.isDebugMode ? .debug : .noLogging if let cachedFlags = cachedData.items, !cachedFlags.isEmpty { flagStore.replaceStore(newStoredItems: cachedFlags) } @@ -758,7 +763,7 @@ public class LDClient { self.connectionInformation = ConnectionInformation.uncacheConnectionInformation(config: config, ldClient: self, clientServiceFactory: self.serviceFactory) internalSetOnline(configuration.startOnline) { - Log.debug("LDClient started") + os_log("%s LDClient started", log: configuration.logger, type: .debug, self.typeName(and: #function)) completion?() } } diff --git a/LaunchDarkly/LaunchDarkly/LDClientVariation.swift b/LaunchDarkly/LaunchDarkly/LDClientVariation.swift index 95e9465b..2814bc15 100644 --- a/LaunchDarkly/LaunchDarkly/LDClientVariation.swift +++ b/LaunchDarkly/LaunchDarkly/LDClientVariation.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog extension LDClient { // MARK: Flag variation methods @@ -130,7 +131,7 @@ extension LDClient { result = LDEvaluationDetail(value: defaultValue, variationIndex: nil, reason: ["kind": "ERROR", "errorKind": "WRONG_TYPE"]) } } else { - Log.debug(typeName(and: #function) + " Unknown feature flag \(flagKey); returning default value") + os_log("%s Unknown feature flag %s; returning default value", log: config.logger, type: .debug, typeName(and: #function), flagKey.description) result = LDEvaluationDetail(value: defaultValue, variationIndex: nil, reason: ["kind": "ERROR", "errorKind": "FLAG_NOT_FOUND"]) } eventReporter.recordFlagEvaluationEvents(flagKey: flagKey, diff --git a/LaunchDarkly/LaunchDarkly/Models/ConnectionInformation.swift b/LaunchDarkly/LaunchDarkly/Models/ConnectionInformation.swift index 1aa72cf5..0627d806 100644 --- a/LaunchDarkly/LaunchDarkly/Models/ConnectionInformation.swift +++ b/LaunchDarkly/LaunchDarkly/Models/ConnectionInformation.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog public struct ConnectionInformation: Codable, CustomStringConvertible { public enum ConnectionMode: String, Codable { @@ -127,13 +128,13 @@ public struct ConnectionInformation: Codable, CustomStringConvertible { var reason = "" let streamingMode: LDStreamingMode = ldClient.isInSupportedRunMode && config.streamingMode == .streaming && config.allowStreamingMode ? .streaming : .polling if config.streamingMode == .streaming && !ldClient.isInSupportedRunMode { - reason = " LDClient is in background mode with background updates disabled." + reason = "LDClient is in background mode with background updates disabled." } if reason.isEmpty && config.streamingMode == .streaming && !config.allowStreamingMode { - reason = " LDConfig disallowed streaming mode. " + reason = "LDConfig disallowed streaming mode. " reason += !SystemCapabilities.operatingSystem.isStreamingEnabled ? "Streaming is not allowed on \(SystemCapabilities.operatingSystem)." : "Unknown reason." } - Log.debug(ldClient.typeName(and: #function, appending: ": ") + "\(streamingMode)\(reason)") + os_log("%s %s %s", log: config.logger, type: .debug, ldClient.typeName(and: #function), String(describing: streamingMode), reason) return streamingMode } diff --git a/LaunchDarkly/LaunchDarkly/Models/Context/LDContext.swift b/LaunchDarkly/LaunchDarkly/Models/Context/LDContext.swift index 28ea2666..72634ac8 100644 --- a/LaunchDarkly/LaunchDarkly/Models/Context/LDContext.swift +++ b/LaunchDarkly/LaunchDarkly/Models/Context/LDContext.swift @@ -394,7 +394,6 @@ public struct LDContext: Encodable, Equatable { return .string(String(kind)) } - Log.debug(typeName(and: #function) + ": Cannot get non-kind attribute from multi-context") return nil } @@ -703,7 +702,6 @@ public struct LDContextBuilder { public mutating func trySetValue(_ name: String, _ value: LDValue) -> Bool { switch (name, value) { case ("", _): - Log.debug(typeName(and: #function) + ": Provided attribute is empty. Ignoring.") return false case ("kind", .string(let val)): self.kind(val) diff --git a/LaunchDarkly/LaunchDarkly/Models/Context/Modifier.swift b/LaunchDarkly/LaunchDarkly/Models/Context/Modifier.swift index 2b9d6ff8..8809bc5a 100644 --- a/LaunchDarkly/LaunchDarkly/Models/Context/Modifier.swift +++ b/LaunchDarkly/LaunchDarkly/Models/Context/Modifier.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog protocol ContextModifier { func modifyContext(_ context: LDContext) -> LDContext @@ -7,9 +8,11 @@ protocol ContextModifier { class AutoEnvContextModifier { static let specVersion = "1.0" private let environmentReporter: EnvironmentReporting + private let logger: OSLog - init(environmentReporter: EnvironmentReporting) { + init(environmentReporter: EnvironmentReporting, logger: OSLog) { self.environmentReporter = environmentReporter + self.logger = logger } private func makeRecipeList() -> [ContextRecipe] { @@ -27,7 +30,7 @@ class AutoEnvContextModifier { recipe.attributeCallables.forEach { (key, callable) in let succeess = builder.trySetValue(key, callable()) if !succeess { - Log.debug(self.typeName(and: #function) + " Failed setting value for key \(key)") + os_log("%s Failed setting value for key %s", log: logger, type: .debug, typeName(and: #function), key) } } @@ -109,7 +112,7 @@ extension AutoEnvContextModifier: ContextModifier { let contextKeys = context.contextKeys() for recipe in makeRecipeList() { if contextKeys[recipe.kind.description] != nil { - Log.debug(self.typeName(and: #function) + " Unable to automatically add environment attributes for kind \(recipe.kind). It already exists.") + os_log("%s Unable to automatically add environment attributes for kind %s. It already exists.", log: logger, type: .debug, typeName(and: #function), recipe.kind.description) continue } @@ -117,7 +120,7 @@ extension AutoEnvContextModifier: ContextModifier { case .success(let ctx): builder.addContext(ctx) case .failure(let err): - Log.debug(self.typeName(and: #function) + " Failed adding context of kind \(recipe.kind) with error \(err)") + os_log("%s Failed adding context of kind %s with error %s", log: logger, type: .debug, typeName(and: #function), recipe.kind.description, String(describing: err)) } } @@ -125,7 +128,7 @@ extension AutoEnvContextModifier: ContextModifier { case .success(let newContext): return newContext case .failure(let err): - Log.debug(self.typeName(and: #function) + " Failed adding telemetry context information with error \(err). Using customer context instead.") + os_log("%s Failed adding telemetry context information with error %s. Using customer context instead.", log: logger, type: .debug, typeName(and: #function), String(describing: err)) return context } } diff --git a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagRequestTracker.swift b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagRequestTracker.swift index 8bd8465e..a0897dfc 100644 --- a/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagRequestTracker.swift +++ b/LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagRequestTracker.swift @@ -1,8 +1,14 @@ import Foundation +import OSLog struct FlagRequestTracker { let startDate = Date() var flagCounters: [LDFlagKey: FlagCounter] = [:] + let logger: OSLog + + init(logger: OSLog) { + self.logger = logger + } mutating func trackRequest(flagKey: LDFlagKey, reportedValue: LDValue, featureFlag: FeatureFlag?, defaultValue: LDValue, context: LDContext) { if flagCounters[flagKey] == nil { @@ -12,11 +18,13 @@ struct FlagRequestTracker { else { return } flagCounter.trackRequest(reportedValue: reportedValue, featureFlag: featureFlag, defaultValue: defaultValue, context: context) - Log.debug(typeName(and: #function) + "\n\tflagKey: \(flagKey)" - + "\n\treportedValue: \(reportedValue), " - + "\n\tvariation: \(String(describing: featureFlag?.variation)), " - + "\n\tversion: \(String(describing: featureFlag?.flagVersion ?? featureFlag?.version)), " - + "\n\tdefaultValue: \(defaultValue)\n") + os_log("%s \n\tflagKey: %s\n\treportedValue: %s\n\tvariation: %s\n\tversion: %s\n\tdefaultValue: %s", log: logger, type: .debug, + typeName(and: #function), + flagKey, + String(describing: reportedValue), + String(describing: featureFlag?.variation), + String(describing: featureFlag?.flagVersion ?? featureFlag?.version), + String(describing: defaultValue)) } var hasLoggedRequests: Bool { !flagCounters.isEmpty } diff --git a/LaunchDarkly/LaunchDarkly/Models/LDConfig.swift b/LaunchDarkly/LaunchDarkly/Models/LDConfig.swift index 4817e5a2..7beaa874 100644 --- a/LaunchDarkly/LaunchDarkly/Models/LDConfig.swift +++ b/LaunchDarkly/LaunchDarkly/Models/LDConfig.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog /// Defines the connection modes the SDK may be configured to use to retrieve feature flag data from LaunchDarkly. public enum LDStreamingMode { @@ -145,7 +146,6 @@ public struct ApplicationInfo: Equatable { let sanitized = unwrapped.replacingOccurrences(of: " ", with: "-") if let error = validate(sanitized) { - Log.debug("Issue validating \(inputName) value \(sanitized). \(error)") return } @@ -247,6 +247,9 @@ public struct LDConfig { /// The default behavior for environment attributes is to not modify any provided context UNLESS the developer specifically opts-in. static let autoEnvAttributes: Bool = false + + /// The default logger for the SDK. Can be overridden to provide customization. + static let logger: OSLog = OSLog(subsystem: "com.launchdarkly", category: "ios-client-sdk") } /// Constants relevant to setting up an `LDConfig` @@ -407,6 +410,9 @@ public struct LDConfig { /// based on application name or version, or on device characteristics including manufacturer, model, operating system, locale, and so on. public var autoEnvAttributes: Bool = Defaults.autoEnvAttributes + /// Configure the logger that will be used by the rest of the SDK. + public var logger: OSLog = Defaults.logger + /// LaunchDarkly defined minima for selected configurable items public let minima: Minima @@ -463,7 +469,7 @@ public struct LDConfig { allowBackgroundUpdates = SystemCapabilities.operatingSystem.isBackgroundEnabled _secondaryMobileKeys = Defaults.secondaryMobileKeys if mobileKey.isEmpty { - Log.debug(typeName(and: #function, appending: ": ") + "mobileKey is empty. The SDK will not operate correctly without a valid mobile key.") + os_log("%s mobileKey is empty. The SDK will not operate correctly without a valid mobile key.", log: logger, type: .debug, typeName(and: #function)) } } @@ -486,16 +492,12 @@ public struct LDConfig { // Determine the effective flag polling interval based on runMode, configured foreground & background polling interval, and minimum foreground & background polling interval. func flagPollingInterval(runMode: LDClientRunMode) -> TimeInterval { - let pollingInterval = runMode == .foreground ? max(flagPollingInterval, minima.flagPollingInterval) : max(backgroundFlagPollingInterval, minima.backgroundFlagPollingInterval) - Log.debug(typeName(and: #function, appending: ": ") + "\(pollingInterval)") - return pollingInterval + return runMode == .foreground ? max(flagPollingInterval, minima.flagPollingInterval) : max(backgroundFlagPollingInterval, minima.backgroundFlagPollingInterval) } // Determines if the status code is a code that should cause the SDK to retry a failed HTTP Request that used the REPORT method. Retried requests will use the GET method. static func isReportRetryStatusCode(_ statusCode: Int) -> Bool { - let isRetryStatusCode = LDConfig.flagRetryStatusCodes.contains(statusCode) - Log.debug(LDConfig.typeName(and: #function, appending: ": ") + "\(isRetryStatusCode)") - return isRetryStatusCode + return LDConfig.flagRetryStatusCodes.contains(statusCode) } } diff --git a/LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift b/LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift index 3ff246fe..fb879b63 100644 --- a/LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift +++ b/LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift @@ -1,5 +1,6 @@ import Foundation import LDSwiftEventSource +import OSLog typealias ServiceResponse = (data: Data?, urlResponse: URLResponse?, error: Error?, etag: String?) typealias ServiceCompletionHandler = (ServiceResponse) -> Void @@ -95,7 +96,7 @@ final class DarklyService: DarklyServiceProvider { guard let contextJsonData = try? encoder.encode(context) else { - Log.debug(typeName(and: #function, appending: ": ") + "Aborting. Unable to create flagRequest.") + os_log("%s Aborting. Unable to create flag request.", log: config.logger, type: .debug, typeName(and: #function)) return } @@ -214,7 +215,7 @@ final class DarklyService: DarklyServiceProvider { private func hasMobileKey(_ location: String) -> Bool { if config.mobileKey.isEmpty { - Log.debug(typeName(and: location, appending: ": ") + "Aborting. No mobile key.") + os_log("%s Aborting. No mobile key.", log: config.logger, type: .debug, typeName(and: #function)) } return !config.mobileKey.isEmpty } diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/ConnectionInformationStore.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/ConnectionInformationStore.swift index 6a3e6fe9..794bca86 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/ConnectionInformationStore.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/ConnectionInformationStore.swift @@ -23,10 +23,8 @@ private extension UserDefaults { func retrieve(object type: T.Type, fromKey key: String) -> T? { guard let data = self.data(forKey: key), let object = try? JSONDecoder().decode(type, from: data) - else { - Log.debug("Couldnt decode object: \(key)") - return nil - } + else { return nil } + return object } } diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/ClientServiceFactory.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/ClientServiceFactory.swift index 74f0817b..428a0df4 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/ClientServiceFactory.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/ClientServiceFactory.swift @@ -5,7 +5,7 @@ protocol ClientServiceCreating { func makeKeyedValueCache(cacheKey: String?) -> KeyedValueCaching func makeFeatureFlagCache(mobileKey: String, maxCachedContexts: Int) -> FeatureFlagCaching func makeCacheConverter() -> CacheConverting - func makeDarklyServiceProvider(config: LDConfig, context: LDContext, envReporter: EnvironmentReporting) -> DarklyServiceProvider + func makeDarklyServiceProvider(context: LDContext, envReporter: EnvironmentReporting) -> DarklyServiceProvider func makeFlagSynchronizer(streamingMode: LDStreamingMode, pollingInterval: TimeInterval, useReport: Bool, service: DarklyServiceProvider) -> LDFlagSynchronizing func makeFlagSynchronizer(streamingMode: LDStreamingMode, pollingInterval: TimeInterval, @@ -16,7 +16,7 @@ protocol ClientServiceCreating { func makeEventReporter(service: DarklyServiceProvider) -> EventReporting func makeEventReporter(service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure?) -> EventReporting func makeStreamingProvider(url: URL, httpHeaders: [String: String], connectMethod: String, connectBody: Data?, handler: EventHandler, delegate: RequestHeaderTransform?, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider - func makeEnvironmentReporter(config: LDConfig) -> EnvironmentReporting + func makeEnvironmentReporter() -> EnvironmentReporting func makeThrottler(environmentReporter: EnvironmentReporting) -> Throttling func makeConnectionInformation() -> ConnectionInformation func makeDiagnosticCache(sdkKey: String) -> DiagnosticCaching @@ -25,6 +25,12 @@ protocol ClientServiceCreating { } final class ClientServiceFactory: ClientServiceCreating { + private let config: LDConfig + + init(config: LDConfig) { + self.config = config + } + func makeKeyedValueCache(cacheKey: String?) -> KeyedValueCaching { UserDefaults(suiteName: cacheKey)! } @@ -37,7 +43,7 @@ final class ClientServiceFactory: ClientServiceCreating { CacheConverter() } - func makeDarklyServiceProvider(config: LDConfig, context: LDContext, envReporter: EnvironmentReporting) -> DarklyServiceProvider { + func makeDarklyServiceProvider(context: LDContext, envReporter: EnvironmentReporting) -> DarklyServiceProvider { DarklyService(config: config, context: context, envReporter: envReporter, serviceFactory: self) } @@ -54,7 +60,7 @@ final class ClientServiceFactory: ClientServiceCreating { } func makeFlagChangeNotifier() -> FlagChangeNotifying { - FlagChangeNotifier() + FlagChangeNotifier(logger: config.logger) } func makeEventReporter(service: DarklyServiceProvider) -> EventReporting { @@ -85,7 +91,7 @@ final class ClientServiceFactory: ClientServiceCreating { return EventSource(config: config) } - func makeEnvironmentReporter(config: LDConfig) -> EnvironmentReporting { + func makeEnvironmentReporter() -> EnvironmentReporting { let builder = EnvironmentReporterBuilder() if let info = config.applicationInfo { @@ -100,7 +106,7 @@ final class ClientServiceFactory: ClientServiceCreating { } func makeThrottler(environmentReporter: EnvironmentReporting) -> Throttling { - Throttler() + Throttler(logger: config.logger) } func makeConnectionInformation() -> ConnectionInformation { @@ -116,6 +122,6 @@ final class ClientServiceFactory: ClientServiceCreating { } func makeFlagStore() -> FlagMaintaining { - FlagStore() + FlagStore(logger: config.logger) } } diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/DiagnosticReporter.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/DiagnosticReporter.swift index 89396d1b..240013d4 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/DiagnosticReporter.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/DiagnosticReporter.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog // sourcery: autoMockable protocol DiagnosticReporting { @@ -64,7 +65,7 @@ class DiagnosticReporter: DiagnosticReporting { } private func sendDiagnosticEventSync(diagnosticEvent: T) { - Log.debug(typeName + ": Sending diagnostic event: \(String(describing: diagnosticEvent))") + os_log("%s Sending diagnostic event: %s", log: service.config.logger, type: .debug, typeName(and: #function), String(describing: diagnosticEvent)) self.service.publishDiagnostic(diagnosticEvent: diagnosticEvent) { response in let shouldRetry = self.processSendResponse(response: response.urlResponse as? HTTPURLResponse, error: response.error, isRetry: false) if shouldRetry { @@ -77,19 +78,22 @@ class DiagnosticReporter: DiagnosticReporting { private func processSendResponse(response: HTTPURLResponse?, error: Error?, isRetry: Bool) -> Bool { if error == nil && (200..<300).contains(response?.statusCode ?? 0) { - Log.debug(typeName + ": Completed sending diagnostic event.") + os_log("%s Completed sending diagnostic event.", log: service.config.logger, type: .debug, typeName) return false } if let statusCode = response?.statusCode, (400..<500).contains(statusCode) && ![400, 408, 429].contains(statusCode) { - Log.debug(typeName + ": Dropping diagnostic event due to non-retriable response: \(String(describing: response))") + os_log("%s Dropping diagnostic event due to non-retriable response: %s", log: service.config.logger, type: .debug, typeName, String(describing: response)) return false } - Log.debug(typeName + ": Sending diagnostic failed with error: \(String(describing: error)) response: \(String(describing: response))") + os_log("%s Sending diagnostic failed with error: %s response: %s", log: service.config.logger, type: .debug, + typeName, + String(describing: error), + String(describing: response)) if isRetry { - Log.debug(typeName + ": dropping diagnostic due to failed retry") + os_log("%s dropping diagnostic due to failed retry", log: service.config.logger, type: .debug, typeName) return false } diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/EventReporter.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/EventReporter.swift index e3529226..5fc3fb5d 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/EventReporter.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/EventReporter.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog typealias EventSyncCompleteClosure = ((SynchronizingError?) -> Void) // sourcery: autoMockable @@ -27,7 +28,7 @@ class EventReporter: EventReporting { private let eventQueue = DispatchQueue(label: "com.launchdarkly.eventSyncQueue", qos: .userInitiated) // These fields should only be used synchronized on the eventQueue private(set) var eventStore: [Event] = [] - private(set) var flagRequestTracker = FlagRequestTracker() + private(set) var flagRequestTracker: FlagRequestTracker private var timerQueue = DispatchQueue(label: "com.launchdarkly.EventReporter.timerQueue") private var eventReportTimer: TimeResponding? @@ -39,6 +40,7 @@ class EventReporter: EventReporting { self.service = service self.onSyncComplete = onSyncComplete self.lastEventResponseDate = Date() + self.flagRequestTracker = FlagRequestTracker(logger: service.config.logger) } func record(_ event: Event) { @@ -48,7 +50,7 @@ class EventReporter: EventReporting { func recordNoSync(_ event: Event) { if self.eventStore.count >= self.service.config.eventCapacity { - Log.debug(self.typeName(and: #function) + "aborted. Event store is full") + os_log("%s aborted. Event store is full", log: service.config.logger, type: .debug, typeName(and: #function)) self.service.diagnosticCache?.incrementDroppedEventCount() return } @@ -97,7 +99,7 @@ class EventReporter: EventReporting { private func reportEvents(completion: CompletionClosure?) { guard isOnline else { - Log.debug(typeName(and: #function) + "aborted. EventReporter is offline") + os_log("%s aborted. EventReporter is offline", log: service.config.logger, type: .debug, typeName(and: #function)) reportSyncComplete(.isOffline) completion?() return @@ -106,17 +108,18 @@ class EventReporter: EventReporting { if flagRequestTracker.hasLoggedRequests { let summaryEvent = SummaryEvent(flagRequestTracker: flagRequestTracker) self.eventStore.append(summaryEvent) - flagRequestTracker = FlagRequestTracker() + flagRequestTracker = FlagRequestTracker(logger: service.config.logger) } guard !eventStore.isEmpty else { - Log.debug(typeName(and: #function) + "aborted. Event store is empty") + os_log("%s aborted. Event store is empty", log: service.config.logger, type: .debug, typeName(and: #function)) reportSyncComplete(nil) completion?() return } - Log.debug(typeName(and: #function, appending: " - ") + "starting") + + os_log("%s starting", log: service.config.logger, type: .debug, typeName(and: #function)) let toPublish = self.eventStore self.eventStore = [] @@ -142,14 +145,14 @@ class EventReporter: EventReporting { } guard let eventData = try? encoder.encode(events) else { - Log.debug(self.typeName(and: #function) + "Failed to serialize event(s) for publication: \(events)") + os_log("%s Failed to serialize event(s) for publication: %s", log: service.config.logger, type: .debug, typeName(and: #function), String(describing: events)) completion?() return } self.service.publishEventData(eventData, payloadId) { response in let shouldRetry = self.processEventResponse(sentEvents: events.count, response: response.urlResponse as? HTTPURLResponse, error: response.error, isRetry: false) if shouldRetry { - Log.debug("Retrying event post after delay.") + os_log("%s Retrying event post after delay.", log: self.service.config.logger, type: .debug, self.typeName(and: #function)) DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 1.0) { self.service.publishEventData(eventData, payloadId) { response in _ = self.processEventResponse(sentEvents: events.count, response: response.urlResponse as? HTTPURLResponse, error: response.error, isRetry: true) @@ -169,21 +172,21 @@ class EventReporter: EventReporting { self.lastEventResponseDate = serverTime } - Log.debug(self.typeName(and: #function) + "Completed sending \(sentEvents) event(s)") + os_log("%s Completed sending %d event(s)", log: service.config.logger, type: .debug, typeName(and: #function), sentEvents) self.reportSyncComplete(nil) return false } if let statusCode = response?.statusCode, (400..<500).contains(statusCode) && ![400, 408, 429].contains(statusCode) { - Log.debug(typeName(and: #function) + "dropping events due to non-retriable response: \(String(describing: response))") + os_log("%s dropping events due to non-retriable response: %s", log: service.config.logger, type: .debug, typeName(and: #function), String(describing: response)) self.reportSyncComplete(.response(response)) return false } - Log.debug(typeName(and: #function) + "Sending events failed with error: \(String(describing: error)) response: \(String(describing: response))") + os_log("%s Sending events failed with error: %s response: %s", log: service.config.logger, type: .debug, typeName(and: #function), String(describing: error), String(describing: response)) if isRetry { - Log.debug(typeName(and: #function) + "dropping events due to failed retry") + os_log("%s dropping events due to failed retry", log: service.config.logger, type: .debug, typeName(and: #function)) if let error = error { reportSyncComplete(.request(error)) } else { diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagChangeNotifier.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagChangeNotifier.swift index 9938eb90..af22939a 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagChangeNotifier.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagChangeNotifier.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog // sourcery: autoMockable protocol FlagChangeNotifying { @@ -19,25 +20,30 @@ final class FlagChangeNotifier: FlagChangeNotifying { private var flagChangeQueue = DispatchQueue(label: "com.launchdarkly.FlagChangeNotifier.FlagChangeQueue") private var flagsUnchangedQueue = DispatchQueue(label: "com.launchdarkly.FlagChangeNotifier.FlagsUnchangedQueue") private var connectionModeChangedQueue = DispatchQueue(label: "com.launchdarkly.FlagChangeNotifier.ConnectionModeChangedQueue") + private let logger: OSLog + + init(logger: OSLog) { + self.logger = logger + } func addFlagChangeObserver(_ observer: FlagChangeObserver) { - Log.debug(typeName(and: #function) + "observer: \(observer)") + os_log("%s observer: %s", log: logger, type: .debug, typeName(and: #function), String(describing: observer)) flagChangeQueue.sync { flagChangeObservers.append(observer) } } func addFlagsUnchangedObserver(_ observer: FlagsUnchangedObserver) { - Log.debug(typeName(and: #function) + "observer: \(observer)") + os_log("%s observer: %s", log: logger, type: .debug, typeName(and: #function), String(describing: observer)) flagsUnchangedQueue.sync { flagsUnchangedObservers.append(observer) } } func addConnectionModeChangedObserver(_ observer: ConnectionModeChangedObserver) { - Log.debug(typeName(and: #function) + "observer: \(observer)") + os_log("%s observer: %s", log: logger, type: .debug, typeName(and: #function), String(describing: observer)) connectionModeChangedQueue.sync { connectionModeChangedObservers.append(observer) } } /// Removes all change handling closures from owner func removeObserver(owner: LDObserverOwner) { - Log.debug(typeName(and: #function) + "owner: \(owner)") + os_log("%s owner: %s", log: logger, type: .debug, typeName(and: #function), String(describing: owner)) flagChangeQueue.sync { flagChangeObservers.removeAll { $0.owner === owner } } flagsUnchangedQueue.sync { flagsUnchangedObservers.removeAll { $0.owner === owner } } connectionModeChangedQueue.sync { connectionModeChangedObservers.removeAll { $0.owner === owner } } @@ -58,9 +64,9 @@ final class FlagChangeNotifier: FlagChangeNotifying { removeOldObservers() if flagsUnchangedObservers.isEmpty { - Log.debug(typeName(and: #function) + "aborted. Flags unchanged and no flagsUnchanged observers set.") + os_log("%s aborted. Flags unchanged and no flagsUnchanged observers set.", log: logger, type: .debug, typeName(and: #function)) } else { - Log.debug(typeName(and: #function) + "notifying observers that flags are unchanged.") + os_log("%s notifying observers that flags are unchanged.", log: logger, type: .debug, typeName(and: #function)) } flagsUnchangedQueue.sync { flagsUnchangedObservers.forEach { flagsUnchangedObserver in @@ -85,14 +91,14 @@ final class FlagChangeNotifier: FlagChangeNotifying { } guard !selectedObservers.isEmpty else { - Log.debug(typeName(and: #function) + "aborted. No observers watching changed flags.") + os_log("%s aborted. No observers watching changed flags.", log: logger, type: .debug, typeName(and: #function)) return } let changedFlags = [LDFlagKey: LDChangedFlag](uniqueKeysWithValues: changedFlagKeys.map { ($0, LDChangedFlag(key: $0, oldValue: oldFlags[$0]?.value ?? .null, newValue: newFlags[$0]?.value ?? .null)) }) - Log.debug(typeName(and: #function) + "notifying observers for changes to flags: \(changedFlags.keys.joined(separator: ", ")).") + os_log("%s notifying observers for changes to flags: %s.", log: logger, type: .debug, typeName(and: #function), changedFlags.keys.joined(separator: ", ")) selectedObservers.forEach { observer in let filteredChangedFlags = changedFlags.filter { flagKey, _ -> Bool in observer.flagKeys == LDFlagKey.anyKey || observer.flagKeys.contains(flagKey) @@ -107,7 +113,7 @@ final class FlagChangeNotifier: FlagChangeNotifying { } private func removeOldObservers() { - Log.debug(typeName(and: #function)) + os_log("%s", log: logger, type: .debug, typeName(and: #function)) flagChangeQueue.sync { flagChangeObservers.removeAll { $0.owner == nil } } flagsUnchangedQueue.sync { flagsUnchangedObservers.removeAll { $0.owner == nil } } } diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagStore.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagStore.swift index 664d156e..98a5a48a 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagStore.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagStore.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog enum StorageItem: Codable { case item(FeatureFlag) @@ -79,15 +80,20 @@ final class FlagStore: FlagMaintaining { // Used with .barrier as reader writer lock on _featureFlags private var flagQueue = DispatchQueue(label: Constants.flagQueueLabel, attributes: .concurrent) - init() { } + private let logger: OSLog - init(storedItems: StoredItems) { - Log.debug(typeName(and: #function) + "storedItems: \(String(describing: storedItems))") + init(logger: OSLog) { + self.logger = logger + } + + init(logger: OSLog, storedItems: StoredItems) { + self.logger = logger self._storedItems = storedItems + os_log("%s storedItems:", log: logger, type: .debug, typeName(and: #function), String(describing: storedItems)) } func replaceStore(newStoredItems: StoredItems) { - Log.debug(typeName(and: #function) + "newFlags: \(String(describing: newStoredItems))") + os_log("%s newFlags: ", log: logger, type: .debug, typeName(and: #function), String(describing: newStoredItems)) flagQueue.sync(flags: .barrier) { self._storedItems = newStoredItems } @@ -97,13 +103,11 @@ final class FlagStore: FlagMaintaining { flagQueue.sync(flags: .barrier) { guard self.isValidVersion(for: updatedFlag.flagKey, newVersion: updatedFlag.version) else { - Log.debug(self.typeName(and: #function) + "aborted. Invalid version. updateDictionary: \(updatedFlag) " - + "existing flag: \(String(describing: self._storedItems[updatedFlag.flagKey]))") + os_log("%s aborted. Invalid version. updateDictionary: %s existing flag: %s", log: logger, type: .debug, typeName(and: #function), String(describing: updatedFlag), String(describing: self._storedItems[updatedFlag.flagKey])) return } - Log.debug(self.typeName(and: #function) + "succeeded. new flag: \(updatedFlag), " + - "prior flag: \(String(describing: self._storedItems[updatedFlag.flagKey]))") + os_log("%s succeeded. new flag: %s prior flag: %s", log: logger, type: .debug, typeName(and: #function), String(describing: updatedFlag), String(describing: self._storedItems[updatedFlag.flagKey])) self._storedItems.updateValue(StorageItem.item(updatedFlag), forKey: updatedFlag.flagKey) } } @@ -112,12 +116,11 @@ final class FlagStore: FlagMaintaining { flagQueue.sync(flags: .barrier) { guard self.isValidVersion(for: deleteResponse.key, newVersion: deleteResponse.version) else { - Log.debug(self.typeName(and: #function) + "aborted. Invalid version. deleteResponse: \(deleteResponse) " - + "existing flag: \(String(describing: self._storedItems[deleteResponse.key]))") + os_log("%s aborted. Invalid version. deleteResponse: %s existing flag: %s", log: logger, type: .debug, typeName(and: #function), String(describing: deleteResponse), String(describing: self._storedItems[deleteResponse.key])) return } - Log.debug(self.typeName(and: #function) + "deleted flag with key: " + deleteResponse.key) + os_log("%s deleted flag with key: %s", log: logger, type: .debug, typeName(and: #function), deleteResponse.key) self._storedItems.updateValue(StorageItem.tombstone(deleteResponse.version ?? 0), forKey: deleteResponse.key) } } diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagSynchronizer.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagSynchronizer.swift index 8bacae9e..3a979651 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagSynchronizer.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/FlagSynchronizer.swift @@ -1,6 +1,7 @@ import Foundation import Dispatch import LDSwiftEventSource +import OSLog // sourcery: autoMockable protocol LDFlagSynchronizing { @@ -68,7 +69,7 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { set { isOnlineQueue.sync { _isOnline = newValue - Log.debug(typeName(and: #function, appending: ": ") + "\(_isOnline)") + os_log("%s %{bool}d", log: service.config.logger, type: .debug, typeName(and: #function), _isOnline) configureCommunications(isOnline: _isOnline) } } @@ -87,12 +88,12 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { useReport: Bool, service: DarklyServiceProvider, onSyncComplete: FlagSyncCompleteClosure?) { - Log.debug(FlagSynchronizer.typeName(and: #function) + "streamingMode: \(streamingMode), " + "pollingInterval: \(pollingInterval), " + "useReport: \(useReport)") self.streamingMode = streamingMode self.pollingInterval = pollingInterval self.useReport = useReport self.service = service self.onSyncComplete = onSyncComplete + os_log("%s streamingMode: %s pollingInterval: %s useReport: %s", log: service.config.logger, type: .debug, typeName(and: #function), String(describing: streamingMode), String(describing: pollingInterval), useReport.description) } private func configureCommunications(isOnline: Bool) { @@ -115,9 +116,12 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { private func startEventSource() { guard eventSource == nil - else { return Log.debug(typeName(and: #function) + "aborted. Clientstream already connected.") } + else { + os_log("%s aborted. Clientstream already connected.", log: service.config.logger, type: .debug, typeName(and: #function)) + return + } - Log.debug(typeName(and: #function)) + os_log("%s", log: service.config.logger, type: .debug, typeName(and: #function)) eventSourceStarted = Date() // The LDConfig.connectionTimeout should NOT be set here. Heartbeat is sent every 3m. ES default timeout is 5m. This is an async operation. // LDEventSource reacts to connection errors by closing the connection and establishing a new one after an exponentially increasing wait. That makes it self healing. @@ -128,9 +132,12 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { private func stopEventSource() { guard eventSource != nil - else { return Log.debug(typeName(and: #function) + "aborted. Clientstream is not connected.") } + else { + os_log("%s aborted. Clientstream is not connected.", log: service.config.logger, type: .debug, typeName(and: #function)) + return + } - Log.debug(typeName(and: #function)) + os_log("%s", log: service.config.logger, type: .debug, typeName(and: #function)) eventSource?.stop() eventSource = nil } @@ -139,18 +146,24 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { private func startPolling() { guard flagRequestTimer == nil - else { return Log.debug(typeName(and: #function) + "aborted. Polling already active.") } + else { + os_log("%s aborted. Polling already active.", log: service.config.logger, type: .debug, typeName(and: #function)) + return + } - Log.debug(typeName(and: #function)) + os_log("%s", log: service.config.logger, type: .debug, typeName(and: #function)) flagRequestTimer = LDTimer(withTimeInterval: pollingInterval, fireQueue: syncQueue, execute: processTimer) makeFlagRequest(isOnline: true) } private func stopPolling() { guard flagRequestTimer != nil - else { return Log.debug(typeName(and: #function) + "aborted. Polling already inactive.") } + else { + os_log("%s aborted. Polling already inactive.", log: service.config.logger, type: .debug, typeName(and: #function)) + return + } - Log.debug(typeName(and: #function)) + os_log("%s", log: service.config.logger, type: .debug, typeName(and: #function)) flagRequestTimer?.cancel() flagRequestTimer = nil } @@ -164,18 +177,20 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { private func makeFlagRequest(isOnline: Bool) { guard isOnline else { - Log.debug(typeName(and: #function) + "aborted. Flag Synchronizer is offline.") + os_log("%s aborted. Flag Synchronizer is offline.", log: service.config.logger, type: .debug, typeName(and: #function)) reportSyncComplete(.error(.isOffline)) return } - Log.debug(typeName(and: #function, appending: " - ") + "starting") + os_log("%s starting", log: service.config.logger, type: .debug, typeName(and: #function)) let context = (useReport: useReport, - logPrefix: typeName(and: #function, appending: " - ")) + logPrefix: typeName(and: #function)) service.getFeatureFlags(useReport: useReport) { [weak self] serviceResponse in if FlagSynchronizer.shouldRetryFlagRequest(useReport: context.useReport, statusCode: (serviceResponse.urlResponse as? HTTPURLResponse)?.statusCode) { - Log.debug(context.logPrefix + "retrying via GET") - self?.service.getFeatureFlags(useReport: false) { retryServiceResponse in - self?.processFlagResponse(serviceResponse: retryServiceResponse) + if let myself = self { + os_log("%s retrying via GET", log: myself.service.config.logger, type: .debug, context.logPrefix) + myself.service.getFeatureFlags(useReport: false) { retryServiceResponse in + myself.processFlagResponse(serviceResponse: retryServiceResponse) + } } } else { self?.processFlagResponse(serviceResponse: serviceResponse) @@ -191,7 +206,7 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { private func processFlagResponse(serviceResponse: ServiceResponse) { if let serviceResponseError = serviceResponse.error { - Log.debug(typeName(and: #function) + "error: \(serviceResponseError)") + os_log("%s error: %s", log: service.config.logger, type: .debug, typeName(and: #function), String(describing: serviceResponseError)) reportSyncComplete(.error(.request(serviceResponseError))) return } @@ -201,7 +216,7 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { } guard serviceResponse.urlResponse?.httpStatusCode == HTTPURLResponse.StatusCodes.ok else { - Log.debug(typeName(and: #function) + "response: \(String(describing: serviceResponse.urlResponse))") + os_log("%s response: %s", log: service.config.logger, type: .debug, typeName(and: #function), String(describing: serviceResponse.urlResponse)) reportSyncComplete(.error(.response(serviceResponse.urlResponse))) return } @@ -215,7 +230,7 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { } private func reportDataError(_ data: Data?) { - Log.debug(typeName(and: #function) + "data: \(String(describing: data))") + os_log("%s data: %s", log: service.config.logger, type: .debug, typeName(and: #function), String(describing: data)) reportSyncComplete(.error(.data(data))) } @@ -259,18 +274,18 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { func shouldAbortStreamUpdate() -> Bool { // Because this method is called asynchronously by the LDEventSource, need to check these conditions prior to processing the event. if !isOnline { - Log.debug(typeName(and: #function) + "aborted. " + "Flag Synchronizer is offline.") + os_log("%s aborted. Flag Synchronizer is offline.", log: service.config.logger, type: .debug, typeName(and: #function)) reportSyncComplete(.error(.isOffline)) return true } if streamingMode == .polling { - Log.debug(typeName(and: #function) + "aborted. " + "Flag Synchronizer is in polling mode.") + os_log("%s aborted. Flag Synchronizer is in polling mode.", log: service.config.logger, type: .debug, typeName(and: #function)) reportSyncComplete(.error(.streamEventWhilePolling)) return true } if eventSource == nil { // Since eventSource.close() is async, this prevents responding to events after .close() is called, but before it's actually closed - Log.debug(typeName(and: #function) + "aborted. " + "Clientstream is not active.") + os_log("%s aborted. Clientstream is not active", log: service.config.logger, type: .debug, typeName(and: #function)) reportSyncComplete(.error(.isOffline)) return true } @@ -279,7 +294,7 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { // MARK: EventHandler methods public func onOpened() { - Log.debug(self.typeName(and: #function) + "EventSource opened") + os_log("%s EventSource opened", log: service.config.logger, type: .debug, typeName(and: #function)) if let startedAt = eventSourceStarted?.millisSince1970 { let now = Date().millisSince1970 let streamInit = DiagnosticStreamInit(timestamp: now, durationMillis: Int(now - startedAt), failed: false) @@ -288,7 +303,7 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { } public func onClosed() { - Log.debug(self.typeName(and: #function) + "EventSource closed") + os_log("%s EventSource closed", log: service.config.logger, type: .debug, typeName(and: #function)) NotificationCenter.default.post(name: Notification.Name(FlagSynchronizer.Constants.didCloseEventSourceName), object: nil) } @@ -326,7 +341,7 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { } reportSyncComplete(.delete(deleteResponse)) default: - Log.debug(typeName(and: #function) + "aborted. Unknown event type.") + os_log("%s aborted. Unknown event type", log: service.config.logger, type: .debug, typeName(and: #function)) reportSyncComplete(.error(.unknownEventType(eventType))) return } @@ -339,7 +354,7 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler { guard !shouldAbortStreamUpdate() else { return } - Log.debug(typeName(and: #function) + "aborted. Streaming event reported an error. error: \(error)") + os_log("%s aborted. Streaming event reported an error. error: %s", log: service.config.logger, type: .debug, typeName(and: #function), String(describing: error)) reportSyncComplete(.error(.streamError(error))) } } diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/Log.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/Log.swift index 4e8c9ed1..e679b655 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/Log.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/Log.swift @@ -1,40 +1,5 @@ import Foundation -protocol Logger { - func log(_ level: Log.Level, message: String) -} - -struct Log { - - enum Level: Int { - case debug - case noLogging - } - - struct BasicLogger: Logger { - func log(_ level: Level, message: String) { - var prefix = "" - - switch level { - case .debug: - prefix = "LaunchDarkly" - case .noLogging: - prefix = "" - } - NSLog("%@", "\(prefix): \(message)") - } - } - - static var level = Level.noLogging - static var logger: Logger = BasicLogger() - - static func debug(_ msg: @autoclosure () -> String) { - if level.rawValue <= Level.debug.rawValue { - logger.log(.debug, message: msg()) - } - } -} - protocol TypeIdentifying { } extension TypeIdentifying { @@ -42,15 +7,15 @@ extension TypeIdentifying { String(describing: type(of: self)) } - func typeName(and method: String, appending suffix: String = " ") -> String { - typeName + "." + method + suffix + func typeName(and method: String) -> String { + typeName + "." + method } static var typeName: String { String(describing: self) } - static func typeName(and method: String, appending suffix: String = " ") -> String { - typeName + "." + method + suffix + static func typeName(and method: String) -> String { + typeName + "." + method } } diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/Throttler.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/Throttler.swift index 5a0564e4..33c1a6cc 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/Throttler.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/Throttler.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog typealias RunClosure = () -> Void @@ -19,20 +20,26 @@ final class Throttler: Throttling { let dispatcher: ((@escaping RunClosure) -> Void) let throttlingEnabled: Bool let maxDelay: TimeInterval + private let logger: OSLog private (set) var runAttempts = -1 private (set) var workItem: DispatchWorkItem? - init(maxDelay: TimeInterval = Constants.defaultDelay, - isDebugBuild: Bool = false, - dispatcher: ((@escaping RunClosure) -> Void)? = nil) { + init( + logger: OSLog, + maxDelay: TimeInterval = Constants.defaultDelay, + isDebugBuild: Bool = false, + dispatcher: ((@escaping RunClosure) -> Void)? = nil) { + self.logger = logger self.throttlingEnabled = !isDebugBuild self.maxDelay = maxDelay self.dispatcher = dispatcher ?? { DispatchQueue.global(qos: .userInitiated).async(execute: $0) } } func runThrottled(_ runClosure: @escaping RunClosure) { - if let logMsg = runThrottledSync(runClosure) { Log.debug(logMsg) } + if let logMsg = runThrottledSync(runClosure) { + os_log("%s", log: self.logger, type: .debug, typeName(and: #function), logMsg) + } } func runThrottledSync(_ runClosure: @escaping RunClosure) -> String? { diff --git a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift index b8a70f56..b4bb38cc 100644 --- a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog import Quick import Nimble import LDSwiftEventSource @@ -25,7 +26,7 @@ final class LDClientSpec: QuickSpec { var config: LDConfig! var context: LDContext! var subject: LDClient! - let serviceFactoryMock = ClientServiceMockFactory() + let serviceFactoryMock: ClientServiceMockFactory // mock getters based on setting up the context & subject var serviceMock: DarklyServiceMock! { subject.service as? DarklyServiceMock @@ -73,7 +74,16 @@ final class LDClientSpec: QuickSpec { startOnline: Bool = false, streamingMode: LDStreamingMode = .streaming, enableBackgroundUpdates: Bool = true) { - serviceFactoryMock.makeFlagChangeNotifierReturnValue = FlagChangeNotifier() + config = newConfig ?? LDConfig.stub(mobileKey: LDConfig.Constants.mockMobileKey, autoEnvAttributes: .disabled, isDebugBuild: false) + config.startOnline = startOnline + config.streamingMode = streamingMode + config.enableBackgroundUpdates = enableBackgroundUpdates + config.eventFlushInterval = 300.0 // 5 min...don't want this to trigger + + context = LDContext.stub() + + serviceFactoryMock = ClientServiceMockFactory(config: config) + serviceFactoryMock.makeFlagChangeNotifierReturnValue = FlagChangeNotifier(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) serviceFactoryMock.makeFeatureFlagCacheCallback = { let mobileKey = self.serviceFactoryMock.makeFeatureFlagCacheReceivedParameters!.mobileKey @@ -83,14 +93,6 @@ final class LDClientSpec: QuickSpec { } self.serviceFactoryMock.makeFeatureFlagCacheReturnValue = mockCache } - - config = newConfig ?? LDConfig.stub(mobileKey: LDConfig.Constants.mockMobileKey, autoEnvAttributes: .disabled, isDebugBuild: false) - config.startOnline = startOnline - config.streamingMode = streamingMode - config.enableBackgroundUpdates = enableBackgroundUpdates - config.eventFlushInterval = 300.0 // 5 min...don't want this to trigger - - context = LDContext.stub() } func withContext(_ context: LDContext?) -> TestContext { @@ -923,7 +925,7 @@ final class LDClientSpec: QuickSpec { private func onSyncCompleteSuccessReplacingFlagsSpec() { let testContext = TestContext(startOnline: true) testContext.start() - testContext.subject.flagChangeNotifier = ClientServiceMockFactory().makeFlagChangeNotifier() + testContext.subject.flagChangeNotifier = ClientServiceMockFactory(config: testContext.config).makeFlagChangeNotifier() let newStoredItems = ["flag1": StorageItem.item(FeatureFlag(flagKey: "flag1"))] var updateDate: Date! @@ -950,7 +952,7 @@ final class LDClientSpec: QuickSpec { let stubFlags = FlagMaintainingMock.stubStoredItems() let testContext = TestContext(startOnline: true).withCached(flags: stubFlags.featureFlags) testContext.start() - testContext.subject.flagChangeNotifier = ClientServiceMockFactory().makeFlagChangeNotifier() + testContext.subject.flagChangeNotifier = ClientServiceMockFactory(config: testContext.config).makeFlagChangeNotifier() let updateFlag = FeatureFlag(flagKey: "abc") var updateDate: Date! @@ -977,7 +979,7 @@ final class LDClientSpec: QuickSpec { let stubFlags = FlagMaintainingMock.stubStoredItems() let testContext = TestContext(startOnline: true).withCached(flags: stubFlags.featureFlags) testContext.start() - testContext.subject.flagChangeNotifier = ClientServiceMockFactory().makeFlagChangeNotifier() + testContext.subject.flagChangeNotifier = ClientServiceMockFactory(config: testContext.config).makeFlagChangeNotifier() let deleteResponse = DeleteResponse(key: DarklyServiceMock.FlagKeys.int, version: DarklyServiceMock.Constants.version + 1) var updateDate: Date! @@ -1007,7 +1009,7 @@ final class LDClientSpec: QuickSpec { it(ctx) { let testContext = TestContext(startOnline: true) testContext.start() - testContext.subject.flagChangeNotifier = ClientServiceMockFactory().makeFlagChangeNotifier() + testContext.subject.flagChangeNotifier = ClientServiceMockFactory(config: testContext.config).makeFlagChangeNotifier() testContext.onSyncComplete?(.error(err)) expect(testContext.subject.isOnline) == !err.isClientUnauthorized diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift index c2813f73..c24f689f 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift @@ -3,6 +3,12 @@ import LDSwiftEventSource @testable import LaunchDarkly final class ClientServiceMockFactory: ClientServiceCreating { + private let config: LDConfig + + init(config: LDConfig) { + self.config = config + } + var makeKeyedValueCacheReturnValue = KeyedValueCachingMock() var makeKeyedValueCacheCallCount = 0 var makeKeyedValueCacheReceivedCacheKey: String? = nil @@ -28,7 +34,7 @@ final class ClientServiceMockFactory: ClientServiceCreating { return makeCacheConverterReturnValue } - func makeDarklyServiceProvider(config: LDConfig, context: LDContext, envReporter: EnvironmentReporting) -> DarklyServiceProvider { + func makeDarklyServiceProvider(context: LDContext, envReporter: EnvironmentReporting) -> DarklyServiceProvider { DarklyServiceMock(config: config, context: context) } @@ -105,7 +111,7 @@ final class ClientServiceMockFactory: ClientServiceCreating { } var makeEnvironmentReporterReturnValue: EnvironmentReportingMock = EnvironmentReportingMock() - func makeEnvironmentReporter(config: LDConfig) -> EnvironmentReporting { + func makeEnvironmentReporter() -> EnvironmentReporting { return makeEnvironmentReporterReturnValue } diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/FlagMaintainingMock.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/FlagMaintainingMock.swift index aaab2ad9..db2da8e7 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/FlagMaintainingMock.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/FlagMaintainingMock.swift @@ -1,15 +1,16 @@ import Foundation +import OSLog @testable import LaunchDarkly final class FlagMaintainingMock: FlagMaintaining { let innerStore: FlagStore init() { - innerStore = FlagStore() + innerStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) } init(storedItems: StoredItems) { - innerStore = FlagStore(storedItems: storedItems) + innerStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: storedItems) } var storedItems: StoredItems { diff --git a/LaunchDarkly/LaunchDarklyTests/Models/Context/ModifierSpec.swift b/LaunchDarkly/LaunchDarklyTests/Models/Context/ModifierSpec.swift index 924b9a52..a5c504b0 100644 --- a/LaunchDarkly/LaunchDarklyTests/Models/Context/ModifierSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Models/Context/ModifierSpec.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog import XCTest @testable import LaunchDarkly @@ -11,7 +12,7 @@ final class ModifierSpec: XCTestCase { * Requirement 1.2.2.7 - Adding all context kinds */ func testAdheresToSchema() throws { - let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock()) + let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock(), logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) var inputBuilder = LDContextBuilder(key: "aKey") inputBuilder.kind("aKind") @@ -62,7 +63,7 @@ final class ModifierSpec: XCTestCase { * Requirement 1.2.7.1 - Log warning when kind already exists */ func testDoesNotOverwriteCustomerData() throws { - let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock()) + let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock(), logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) var inputBuilder = LDContextBuilder(key: "aKey") inputBuilder.kind("ld_application") @@ -98,7 +99,7 @@ final class ModifierSpec: XCTestCase { * Requirement 1.2.5.1 - Doesn't change customer provided data */ func testDoesNotOverwriteCustomerDataMultiContext() throws { - let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock()) + let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock(), logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) var inputBuilder1 = LDContextBuilder(key: "aKey") inputBuilder1.kind("ld_application") @@ -127,7 +128,7 @@ final class ModifierSpec: XCTestCase { * Requirement 1.2.6.3 - Generated keys are consistent */ func testGeneratesConsistentContextAcrossMultipleCalls() throws { - let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock()) + let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock(), logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) var inputBuilder = LDContextBuilder(key: "aKey") inputBuilder.kind("aKind") @@ -141,7 +142,7 @@ final class ModifierSpec: XCTestCase { } func testGeneratedLdApplicationKey() throws { - let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock()) + let underTest = AutoEnvContextModifier(environmentReporter: EnvironmentReportingMock(), logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) var inputBuilder = LDContextBuilder(key: "aKey") inputBuilder.kind("aKind") let input = try inputBuilder.build().get() @@ -157,7 +158,7 @@ final class ModifierSpec: XCTestCase { info.applicationIdentifier("myID") // version is intentionally omitted let reporter = ApplicationInfoEnvironmentReporter(info) - let underTest = AutoEnvContextModifier(environmentReporter: reporter) + let underTest = AutoEnvContextModifier(environmentReporter: reporter, logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) var inputBuilder = LDContextBuilder(key: "aKey") inputBuilder.kind("aKind") let input = try inputBuilder.build().get() diff --git a/LaunchDarkly/LaunchDarklyTests/Models/EventSpec.swift b/LaunchDarkly/LaunchDarklyTests/Models/EventSpec.swift index 3f5db07e..373bfbdd 100644 --- a/LaunchDarkly/LaunchDarklyTests/Models/EventSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Models/EventSpec.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog import XCTest @testable import LaunchDarkly @@ -223,7 +224,7 @@ final class EventSpec: XCTestCase { func testSummaryEventEncoding() { let flag = FeatureFlag(flagKey: "bool-flag", variation: 1, version: 5, flagVersion: 2) - var flagRequestTracker = FlagRequestTracker() + var flagRequestTracker = FlagRequestTracker(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) flagRequestTracker.trackRequest(flagKey: "bool-flag", reportedValue: false, featureFlag: flag, defaultValue: true, context: LDContext.stub()) flagRequestTracker.trackRequest(flagKey: "bool-flag", reportedValue: false, featureFlag: flag, defaultValue: true, context: LDContext.stub()) let event = SummaryEvent(flagRequestTracker: flagRequestTracker, endDate: Date()) diff --git a/LaunchDarkly/LaunchDarklyTests/Models/FeatureFlag/FlagRequestTracking/FlagRequestTrackerSpec.swift b/LaunchDarkly/LaunchDarklyTests/Models/FeatureFlag/FlagRequestTracking/FlagRequestTrackerSpec.swift index 3907cf3e..7b036765 100644 --- a/LaunchDarkly/LaunchDarklyTests/Models/FeatureFlag/FlagRequestTracking/FlagRequestTrackerSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Models/FeatureFlag/FlagRequestTracking/FlagRequestTrackerSpec.swift @@ -1,11 +1,12 @@ import Foundation +import OSLog import XCTest @testable import LaunchDarkly final class FlagRequestTrackerSpec: XCTestCase { func testInit() { - let flagRequestTracker = FlagRequestTracker() + let flagRequestTracker = FlagRequestTracker(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) XCTAssertEqual(flagRequestTracker.flagCounters, [:]) XCTAssertFalse(flagRequestTracker.hasLoggedRequests) let now = Date() @@ -15,7 +16,7 @@ final class FlagRequestTrackerSpec: XCTestCase { func testTrackRequestInitial() { let flag = FeatureFlag(flagKey: "bool-flag", variation: 1, version: 5, flagVersion: 2) - var flagRequestTracker = FlagRequestTracker() + var flagRequestTracker = FlagRequestTracker(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) flagRequestTracker.trackRequest(flagKey: "bool-flag", reportedValue: false, featureFlag: flag, defaultValue: true, context: LDContext.stub()) XCTAssertEqual(flagRequestTracker.flagCounters.count, 1) let counter = FlagCounter() @@ -25,7 +26,7 @@ final class FlagRequestTrackerSpec: XCTestCase { func testTrackRequestSameFlagKey() { let flag = FeatureFlag(flagKey: "bool-flag", variation: 1, version: 5, flagVersion: 2) - var flagRequestTracker = FlagRequestTracker() + var flagRequestTracker = FlagRequestTracker(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) flagRequestTracker.trackRequest(flagKey: "bool-flag", reportedValue: false, featureFlag: flag, defaultValue: true, context: LDContext.stub()) flagRequestTracker.trackRequest(flagKey: "bool-flag", reportedValue: false, featureFlag: flag, defaultValue: true, context: LDContext.stub()) XCTAssertEqual(flagRequestTracker.flagCounters.count, 1) @@ -38,7 +39,7 @@ final class FlagRequestTrackerSpec: XCTestCase { func testTrackRequestDifferentFlagKey() { let flag = FeatureFlag(flagKey: "bool-flag", variation: 1, version: 5, flagVersion: 2) let secondFlag = FeatureFlag(flagKey: "alt-flag", variation: 2, version: 6, flagVersion: 3) - var flagRequestTracker = FlagRequestTracker() + var flagRequestTracker = FlagRequestTracker(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) flagRequestTracker.trackRequest(flagKey: "bool-flag", reportedValue: false, featureFlag: flag, defaultValue: true, context: LDContext.stub()) flagRequestTracker.trackRequest(flagKey: "alt-flag", reportedValue: true, featureFlag: secondFlag, defaultValue: false, context: LDContext.stub()) XCTAssertEqual(flagRequestTracker.flagCounters.count, 2) @@ -51,7 +52,7 @@ final class FlagRequestTrackerSpec: XCTestCase { } func testHasLoggedRequests() { - var flagRequestTracker = FlagRequestTracker() + var flagRequestTracker = FlagRequestTracker(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) flagRequestTracker.trackRequest(flagKey: "test-key", reportedValue: nil, featureFlag: FeatureFlag(flagKey: "test-key"), defaultValue: nil, context: LDContext.stub()) XCTAssert(flagRequestTracker.hasLoggedRequests) } @@ -59,7 +60,7 @@ final class FlagRequestTrackerSpec: XCTestCase { extension FlagRequestTracker { static func stub() -> FlagRequestTracker { - var tracker = FlagRequestTracker() + var tracker = FlagRequestTracker(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) DarklyServiceMock.FlagKeys.knownFlags.forEach { flagKey in tracker.flagCounters[flagKey] = FlagCounter.stub(flagKey: flagKey) } diff --git a/LaunchDarkly/LaunchDarklyTests/Networking/DarklyServiceSpec.swift b/LaunchDarkly/LaunchDarklyTests/Networking/DarklyServiceSpec.swift index 85794869..c0130b90 100644 --- a/LaunchDarkly/LaunchDarklyTests/Networking/DarklyServiceSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Networking/DarklyServiceSpec.swift @@ -20,7 +20,7 @@ final class DarklyServiceSpec: QuickSpec { var config: LDConfig! var envReporterMock = EnvironmentReportingMock() var serviceMock: DarklyServiceMock! - var serviceFactoryMock: ClientServiceMockFactory = ClientServiceMockFactory() + var serviceFactoryMock: ClientServiceMockFactory var service: DarklyService! var httpHeaders: HTTPHeaders let stubFlags = FlagMaintainingMock.stubStoredItems() @@ -32,6 +32,7 @@ final class DarklyServiceSpec: QuickSpec { config = LDConfig.stub(mobileKey: mobileKey, autoEnvAttributes: .disabled, isDebugBuild: true) config.useReport = useReport config.diagnosticOptOut = diagnosticOptOut + self.serviceFactoryMock = ClientServiceMockFactory(config: config) serviceMock = DarklyServiceMock(config: config) service = DarklyService(config: config, context: context, envReporter: envReporterMock, serviceFactory: serviceFactoryMock) httpHeaders = HTTPHeaders(config: config, environmentReporter: envReporterMock) diff --git a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/CacheConverterSpec.swift b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/CacheConverterSpec.swift index de705ad7..af72ee03 100644 --- a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/CacheConverterSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/CacheConverterSpec.swift @@ -14,7 +14,7 @@ final class CacheConverterSpec: XCTestCase { } override func setUp() { - serviceFactory = ClientServiceMockFactory() + serviceFactory = ClientServiceMockFactory(config: LDConfig(mobileKey: "sdk-key", autoEnvAttributes: .disabled)) } func testNoKeysGiven() { diff --git a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift index 0ee3b9b8..7dc12c9d 100644 --- a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift @@ -11,7 +11,7 @@ final class FeatureFlagCacheSpec: XCTestCase { private var mockValueCache: KeyedValueCachingMock { serviceFactory.makeKeyedValueCacheReturnValue } override func setUp() { - serviceFactory = ClientServiceMockFactory() + serviceFactory = ClientServiceMockFactory(config: LDConfig(mobileKey: "sdk-key", autoEnvAttributes: .disabled)) } func testInit() { diff --git a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagChangeNotifierSpec.swift b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagChangeNotifierSpec.swift index 33576b86..5c4bbe7b 100644 --- a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagChangeNotifierSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagChangeNotifierSpec.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog import Quick import Nimble @testable import LaunchDarkly @@ -88,7 +89,7 @@ private class MockConnectionModeChangedObserver { final class FlagChangeNotifierSpec: QuickSpec { struct TestContext { - let subject: FlagChangeNotifier = FlagChangeNotifier() + let subject: FlagChangeNotifier = FlagChangeNotifier(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) fileprivate var flagChangeObservers: [LDFlagKey: MockFlagChangeObserver] = [:] fileprivate var flagCollectionChangeObservers: [MockFlagCollectionChangeObserver] = [] fileprivate var flagsUnchangedObservers: [MockFlagsUnchangedObserver] = [] @@ -136,7 +137,7 @@ final class FlagChangeNotifierSpec: QuickSpec { override func spec() { describe("init") { it("no initial observers") { - let notifier = FlagChangeNotifier() + let notifier = FlagChangeNotifier(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) expect(notifier.flagChangeObservers).to(beEmpty()) expect(notifier.flagsUnchangedObservers).to(beEmpty()) expect(notifier.connectionModeChangedObservers).to(beEmpty()) diff --git a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagStoreSpec.swift b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagStoreSpec.swift index 39ff59dc..74df0f15 100644 --- a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagStoreSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagStoreSpec.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog import XCTest @testable import LaunchDarkly @@ -7,19 +8,19 @@ final class FlagStoreSpec: XCTestCase { let stubFlags = DarklyServiceMock.Constants.stubFeatureFlags() func testInit() { - XCTAssertEqual(FlagStore().storedItems, [:]) - XCTAssertEqual(FlagStore(storedItems: StoredItems(items: self.stubFlags)).storedItems.featureFlags, self.stubFlags) + XCTAssertEqual(FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")).storedItems, [:]) + XCTAssertEqual(FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: self.stubFlags)).storedItems.featureFlags, self.stubFlags) } func testReplaceStore() { let featureFlags = StoredItems(items: DarklyServiceMock.Constants.stubFeatureFlags()) - let flagStore = FlagStore() + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) flagStore.replaceStore(newStoredItems: featureFlags) XCTAssertEqual(flagStore.storedItems, featureFlags) } func testUpdateStoreNewFlag() { - let flagStore = FlagStore(storedItems: StoredItems(items: stubFlags)) + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: stubFlags)) let flagUpdate = FeatureFlag(flagKey: "new-int-flag", value: "abc", version: 0) flagStore.updateStore(updatedFlag: flagUpdate) XCTAssertEqual(flagStore.storedItems.count, stubFlags.count + 1) @@ -27,7 +28,7 @@ final class FlagStoreSpec: XCTestCase { } func testUpdateStoreNewerVersion() { - let flagStore = FlagStore(storedItems: StoredItems(items: stubFlags)) + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: stubFlags)) let flagUpdate = DarklyServiceMock.Constants.stubFeatureFlag(for: DarklyServiceMock.FlagKeys.int, useAlternateVersion: true) flagStore.updateStore(updatedFlag: flagUpdate) XCTAssertEqual(flagStore.storedItems.count, stubFlags.count) @@ -35,7 +36,7 @@ final class FlagStoreSpec: XCTestCase { } func testUpdateStoreNoVersion() { - let flagStore = FlagStore(storedItems: StoredItems(items: stubFlags)) + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: stubFlags)) let flagUpdate = FeatureFlag(flagKey: DarklyServiceMock.FlagKeys.int, value: "abc", version: nil) flagStore.updateStore(updatedFlag: flagUpdate) XCTAssertEqual(flagStore.storedItems.count, stubFlags.count) @@ -45,7 +46,7 @@ final class FlagStoreSpec: XCTestCase { func testUpdateStoreEarlierOrSameVersion() { let testFlag = DarklyServiceMock.Constants.stubFeatureFlag(for: DarklyServiceMock.FlagKeys.int) let initialVersion = testFlag.version! - let flagStore = FlagStore(storedItems: StoredItems(items: stubFlags)) + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: stubFlags)) let flagUpdateSameVersion = FeatureFlag(flagKey: DarklyServiceMock.FlagKeys.int, value: "abc", version: initialVersion) let flagUpdateOlderVersion = FeatureFlag(flagKey: DarklyServiceMock.FlagKeys.int, value: "abc", version: initialVersion - 1) flagStore.updateStore(updatedFlag: flagUpdateSameVersion) @@ -54,7 +55,7 @@ final class FlagStoreSpec: XCTestCase { } func testDeleteFlagNewerVersion() { - let flagStore = FlagStore(storedItems: StoredItems(items: stubFlags)) + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: stubFlags)) flagStore.deleteFlag(deleteResponse: DeleteResponse(key: DarklyServiceMock.FlagKeys.int, version: DarklyServiceMock.Constants.version + 1)) XCTAssertEqual(flagStore.storedItems.count, self.stubFlags.count) XCTAssertEqual(flagStore.storedItems.featureFlags.count, self.stubFlags.count - 1) @@ -62,7 +63,7 @@ final class FlagStoreSpec: XCTestCase { } func testDeleteFlagMissingVersion() { - let flagStore = FlagStore(storedItems: StoredItems(items: stubFlags)) + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: stubFlags)) flagStore.deleteFlag(deleteResponse: DeleteResponse(key: DarklyServiceMock.FlagKeys.int, version: nil)) XCTAssertEqual(flagStore.storedItems.count, self.stubFlags.count) XCTAssertEqual(flagStore.storedItems.featureFlags.count, self.stubFlags.count - 1) @@ -70,7 +71,7 @@ final class FlagStoreSpec: XCTestCase { } func testDeleteOlderOrNonExistent() { - let flagStore = FlagStore(storedItems: StoredItems(items: stubFlags)) + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: stubFlags)) flagStore.deleteFlag(deleteResponse: DeleteResponse(key: DarklyServiceMock.FlagKeys.int, version: DarklyServiceMock.Constants.version)) flagStore.deleteFlag(deleteResponse: DeleteResponse(key: DarklyServiceMock.FlagKeys.int, version: DarklyServiceMock.Constants.version - 1)) flagStore.deleteFlag(deleteResponse: DeleteResponse(key: "new-int-flag", version: DarklyServiceMock.Constants.version + 1)) @@ -78,7 +79,7 @@ final class FlagStoreSpec: XCTestCase { } func testCannotReplaceDeletedFlagWithOlderVersion() { - let flagStore = FlagStore(storedItems: StoredItems(items: stubFlags)) + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: stubFlags)) let flagUpdate = FeatureFlag(flagKey: "new-int-flag", value: "abc", version: 0) flagStore.updateStore(updatedFlag: flagUpdate) XCTAssertEqual(stubFlags.count + 1, flagStore.storedItems.count) @@ -94,7 +95,7 @@ final class FlagStoreSpec: XCTestCase { } func testFeatureFlag() { - let flagStore = FlagStore(storedItems: StoredItems(items: stubFlags)) + let flagStore = FlagStore(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), storedItems: StoredItems(items: stubFlags)) flagStore.storedItems.forEach { flagKey, featureFlag in guard case .item(let flag) = featureFlag else { diff --git a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/ThrottlerSpec.swift b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/ThrottlerSpec.swift index 62c79c46..c3fe020d 100644 --- a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/ThrottlerSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/ThrottlerSpec.swift @@ -1,4 +1,5 @@ import Foundation +import OSLog import Quick import Nimble @testable import LaunchDarkly @@ -12,7 +13,8 @@ final class ThrottlerSpec: QuickSpec { let dispatchQueue = DispatchQueue(label: "ThrottlerSpecQueue") func testThrottler(throttlingDisabled: Bool = false) -> Throttler { - return Throttler(maxDelay: Constants.maxDelay, + return Throttler(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), + maxDelay: Constants.maxDelay, isDebugBuild: throttlingDisabled, dispatcher: { self.dispatchQueue.sync(execute: $0) }) } @@ -26,13 +28,13 @@ final class ThrottlerSpec: QuickSpec { func initSpec() { describe("init") { it("with a maxDelay parameter") { - let throttler = Throttler(maxDelay: Constants.maxDelay) + let throttler = Throttler(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), maxDelay: Constants.maxDelay) expect(throttler.maxDelay) == Constants.maxDelay expect(throttler.runAttempts) == -1 expect(throttler.workItem).to(beNil()) } it("without a maxDelay parameter") { - let throttler = Throttler() + let throttler = Throttler(logger: OSLog(subsystem: "com.launchdarkly", category: "tests")) expect(throttler.maxDelay) == Throttler.Constants.defaultDelay expect(throttler.runAttempts) == -1 expect(throttler.workItem).to(beNil()) @@ -120,7 +122,7 @@ final class ThrottlerSpec: QuickSpec { func maxDelaySpec() { it("limits delay to maxDelay") { - let throttler = Throttler(maxDelay: 1.0, isDebugBuild: false) + let throttler = Throttler(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"), maxDelay: 1.0, isDebugBuild: false) (0..<10).forEach { _ in throttler.runThrottled { } } let callDate = Date() var runDate: Date?