Skip to content

Commit 9a51844

Browse files
authored
feat: Add LDConfig.sendEvents option to disable all events (#414)
1 parent 94b9c2e commit 9a51844

File tree

14 files changed

+81
-24
lines changed

14 files changed

+81
-24
lines changed

ContractTests/Source/Controllers/SdkController.swift

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ final class SdkController: RouteCollection {
9292
if let enableCompression = events.enableGzip {
9393
config.enableCompression = enableCompression
9494
}
95+
} else {
96+
config.sendEvents = false
9597
}
9698

9799
if let tags = createInstance.configuration.tags {

LaunchDarkly.xcodeproj/project.pbxproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@
240240
A3599E892A4B4AD400DB5C67 /* Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3599E872A4B4AD400DB5C67 /* Modifier.swift */; };
241241
A3599E8A2A4B4AD400DB5C67 /* Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3599E872A4B4AD400DB5C67 /* Modifier.swift */; };
242242
A3599E8B2A4B4AD400DB5C67 /* Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3599E872A4B4AD400DB5C67 /* Modifier.swift */; };
243+
A35AC5572CD559DE00875751 /* CwlPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; productRef = A3F4A4802CC2F640006EF480 /* CwlPreconditionTesting */; };
243244
A35AD4602A619E45005A8DCB /* SystemCapabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35AD45F2A619E45005A8DCB /* SystemCapabilities.swift */; };
244245
A35AD4612A619E45005A8DCB /* SystemCapabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35AD45F2A619E45005A8DCB /* SystemCapabilities.swift */; };
245246
A35AD4622A619E45005A8DCB /* SystemCapabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35AD45F2A619E45005A8DCB /* SystemCapabilities.swift */; };
@@ -560,10 +561,10 @@
560561
isa = PBXFrameworksBuildPhase;
561562
buildActionMask = 2147483647;
562563
files = (
564+
A35AC5572CD559DE00875751 /* CwlPreconditionTesting in Frameworks */,
563565
B4903D9E24BD61EF00F087C4 /* Quick in Frameworks */,
564566
B4903D9B24BD61D000F087C4 /* Nimble in Frameworks */,
565567
B4903D9824BD61B200F087C4 /* OHHTTPStubsSwift in Frameworks */,
566-
A3F4A4812CC2F640006EF480 /* CwlPreconditionTesting in Frameworks */,
567568
8354EFCC1F22491C00C05156 /* LaunchDarkly.framework in Frameworks */,
568569
);
569570
runOnlyForDeploymentPostprocessing = 0;

LaunchDarkly/LaunchDarkly/LDClient.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -878,8 +878,8 @@ public class LDClient {
878878
}
879879

880880
service = self.serviceFactory.makeDarklyServiceProvider(config: config, context: context, envReporter: environmentReporter)
881-
diagnosticReporter = self.serviceFactory.makeDiagnosticReporter(service: service, environmentReporter: environmentReporter)
882-
eventReporter = self.serviceFactory.makeEventReporter(service: service)
881+
diagnosticReporter = self.serviceFactory.makeDiagnosticReporter(config: config, service: service, environmentReporter: environmentReporter)
882+
eventReporter = self.serviceFactory.makeEventReporter(config: config, service: service)
883883
connectionInformation = self.serviceFactory.makeConnectionInformation()
884884
let cachedData = flagCache.getCachedData(cacheKey: context.fullyQualifiedHashedKey(), contextHash: context.contextHash())
885885
flagSynchronizer = self.serviceFactory.makeFlagSynchronizer(streamingMode: config.allowStreamingMode ? config.streamingMode : .polling,
@@ -897,7 +897,7 @@ public class LDClient {
897897

898898
NotificationCenter.default.addObserver(self, selector: #selector(didCloseEventSource), name: Notification.Name(FlagSynchronizer.Constants.didCloseEventSourceName), object: nil)
899899

900-
eventReporter = self.serviceFactory.makeEventReporter(service: service, onSyncComplete: onEventSyncComplete)
900+
eventReporter = self.serviceFactory.makeEventReporter(config: configuration, service: service, onSyncComplete: onEventSyncComplete)
901901
service.resetFlagResponseCache(etag: cachedData.etag)
902902
flagSynchronizer = self.serviceFactory.makeFlagSynchronizer(streamingMode: config.allowStreamingMode ? config.streamingMode : .polling,
903903
pollingInterval: config.flagPollingInterval(runMode: runMode),

LaunchDarkly/LaunchDarkly/LDClientVariation.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,11 @@ extension LDClient {
173173
var result: LDEvaluationDetail<T>
174174
let featureFlag = flagStore.featureFlag(for: flagKey)
175175
if let featureFlag = featureFlag {
176-
featureFlag.prerequisites?.forEach{ prereqFlagKey in
176+
featureFlag.prerequisites?.forEach { prereqFlagKey in
177177
// recurse on prerequisites to emulate prereq evaluations occurring with desirable side effects such as events for prereqs
178178
_ = variationDetailInternal(prereqFlagKey, LDValue.null, needsReason: needsReason, methodName: methodName)
179179
}
180-
180+
181181
if featureFlag.value == .null {
182182
result = LDEvaluationDetail(value: defaultValue, variationIndex: featureFlag.variation, reason: featureFlag.reason)
183183
} else {
@@ -193,7 +193,7 @@ extension LDClient {
193193
os_log("%s Unknown feature flag %s; returning default value", log: config.logger, type: .debug, typeName(and: #function), flagKey.description)
194194
result = LDEvaluationDetail(value: defaultValue, variationIndex: nil, reason: ["kind": "ERROR", "errorKind": "FLAG_NOT_FOUND"])
195195
}
196-
196+
197197
eventReporter.recordFlagEvaluationEvents(flagKey: flagKey,
198198
value: result.value.toLDValue(),
199199
defaultValue: defaultValue.toLDValue(),

LaunchDarkly/LaunchDarkly/Models/FeatureFlag/FlagRequestTracker.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ final class FlagCounter: Encodable {
4444
private(set) var defaultValue: LDValue
4545
private(set) var flagValueCounters: [CounterKey: CounterValue] = [:]
4646
private(set) var contextKinds: Set<String> = Set()
47-
47+
4848
init(defaultValue: LDValue) {
4949
// default value follows a "first one wins" approach where the first evaluation for a flag key sets the default value for the summary events
5050
self.defaultValue = defaultValue

LaunchDarkly/LaunchDarkly/Models/LDConfig.swift

+8
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ public struct LDConfig {
188188
/// The default base url for connecting to streaming service
189189
static let streamUrl = URL(string: "https://clientstream.launchdarkly.com")!
190190

191+
/// The default behavior for the SDK is to send events.
192+
static let sendEvents = true
193+
191194
/// The default maximum number of events the LDClient can store
192195
static let eventCapacity = 100
193196

@@ -305,6 +308,10 @@ public struct LDConfig {
305308
/// The base url for connecting to the streaming service. Do not change unless instructed by LaunchDarkly.
306309
public var streamUrl: URL = Defaults.streamUrl
307310

311+
/// Whether to send events back to LaunchDarkly. This differs from {#offline?} in that it affects
312+
/// only the sending of client-side events, not streaming or polling for events from the server.
313+
public var sendEvents: Bool = Defaults.sendEvents
314+
308315
/// The maximum number of analytics events the LDClient can store. When the LDClient event store reaches the eventCapacity, the SDK discards events until it successfully reports them to LaunchDarkly. (Default: 100)
309316
public var eventCapacity: Int = Defaults.eventCapacity
310317

@@ -532,6 +539,7 @@ extension LDConfig: Equatable {
532539
&& lhs.eventsUrl == rhs.eventsUrl
533540
&& lhs.streamUrl == rhs.streamUrl
534541
&& lhs.eventCapacity == rhs.eventCapacity
542+
&& lhs.sendEvents == rhs.sendEvents
535543
&& lhs.connectionTimeout == rhs.connectionTimeout
536544
&& lhs.eventFlushInterval == rhs.eventFlushInterval
537545
&& lhs.flagPollingInterval == rhs.flagPollingInterval

LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ final class DarklyService: DarklyServiceProvider {
5151
var context: LDContext
5252
let httpHeaders: HTTPHeaders
5353
let diagnosticCache: DiagnosticCaching?
54-
private (set) var serviceFactory: ClientServiceCreating
54+
private(set) var serviceFactory: ClientServiceCreating
5555
private var session: URLSession
5656
var flagRequestEtag: String?
5757

@@ -60,7 +60,7 @@ final class DarklyService: DarklyServiceProvider {
6060
self.context = context
6161
self.serviceFactory = serviceFactory
6262

63-
if !config.mobileKey.isEmpty && !config.diagnosticOptOut {
63+
if !config.mobileKey.isEmpty && !config.diagnosticOptOut && config.sendEvents {
6464
self.diagnosticCache = serviceFactory.makeDiagnosticCache(sdkKey: config.mobileKey)
6565
} else {
6666
self.diagnosticCache = nil

LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDConfig.swift

+5
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public final class ObjcLDConfig: NSObject {
5151
get { config.eventFlushInterval }
5252
set { config.eventFlushInterval = newValue }
5353
}
54+
// Whether or not the SDK should send events
55+
@objc public var sendEvents: Bool {
56+
get { config.sendEvents }
57+
set { config.sendEvents = newValue }
58+
}
5459
/// The interval between feature flag requests. Used only for polling mode. (Default: 5 minutes)
5560
@objc public var flagPollingInterval: TimeInterval {
5661
get { config.flagPollingInterval }

LaunchDarkly/LaunchDarkly/ServiceObjects/ClientServiceFactory.swift

+17-9
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ protocol ClientServiceCreating {
1515
service: DarklyServiceProvider,
1616
onSyncComplete: FlagSyncCompleteClosure?) -> LDFlagSynchronizing
1717
func makeFlagChangeNotifier() -> FlagChangeNotifying
18-
func makeEventReporter(service: DarklyServiceProvider) -> EventReporting
19-
func makeEventReporter(service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure?) -> EventReporting
18+
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider) -> EventReporting
19+
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure?) -> EventReporting
2020
func makeStreamingProvider(url: URL, httpHeaders: [String: String], connectMethod: String, connectBody: Data?, handler: EventHandler, delegate: RequestHeaderTransform?, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider
2121
func makeEnvironmentReporter(config: LDConfig) -> EnvironmentReporting
2222
func makeThrottler(environmentReporter: EnvironmentReporting) -> Throttling
2323
func makeConnectionInformation() -> ConnectionInformation
2424
func makeDiagnosticCache(sdkKey: String) -> DiagnosticCaching
25-
func makeDiagnosticReporter(service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting
25+
func makeDiagnosticReporter(config: LDConfig, service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting
2626
func makeFlagStore() -> FlagMaintaining
2727
}
2828

@@ -66,12 +66,16 @@ final class ClientServiceFactory: ClientServiceCreating {
6666
FlagChangeNotifier(logger: logger)
6767
}
6868

69-
func makeEventReporter(service: DarklyServiceProvider) -> EventReporting {
70-
makeEventReporter(service: service, onSyncComplete: nil)
69+
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider) -> EventReporting {
70+
makeEventReporter(config: config, service: service, onSyncComplete: nil)
7171
}
7272

73-
func makeEventReporter(service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure? = nil) -> EventReporting {
74-
EventReporter(service: service, onSyncComplete: onSyncComplete)
73+
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure? = nil) -> EventReporting {
74+
if config.sendEvents {
75+
return EventReporter(service: service, onSyncComplete: onSyncComplete)
76+
} else {
77+
return NullEventReporter()
78+
}
7579
}
7680

7781
func makeStreamingProvider(url: URL,
@@ -121,8 +125,12 @@ final class ClientServiceFactory: ClientServiceCreating {
121125
DiagnosticCache(sdkKey: sdkKey)
122126
}
123127

124-
func makeDiagnosticReporter(service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting {
125-
DiagnosticReporter(service: service, environmentReporting: environmentReporter)
128+
func makeDiagnosticReporter(config: LDConfig, service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting {
129+
if config.sendEvents && !config.diagnosticOptOut {
130+
return DiagnosticReporter(service: service, environmentReporting: environmentReporter)
131+
} else {
132+
return NullDiagnosticReporter()
133+
}
126134
}
127135

128136
func makeFlagStore() -> FlagMaintaining {

LaunchDarkly/LaunchDarkly/ServiceObjects/DiagnosticReporter.swift

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ protocol DiagnosticReporting {
66
func setMode(_ runMode: LDClientRunMode, online: Bool)
77
}
88

9+
class NullDiagnosticReporter: DiagnosticReporting {
10+
func setMode(_ runMode: LDClientRunMode, online: Bool) {
11+
}
12+
}
13+
914
class DiagnosticReporter: DiagnosticReporting {
1015
private let service: DarklyServiceProvider
1116
private let environmentReporting: EnvironmentReporting

LaunchDarkly/LaunchDarkly/ServiceObjects/EventReporter.swift

+15
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ protocol EventReporting {
1515
func flush(completion: CompletionClosure?)
1616
}
1717

18+
class NullEventReporter: EventReporting {
19+
var isOnline: Bool = true
20+
var lastEventResponseDate: Date = Date()
21+
22+
func record(_ event: Event) {
23+
}
24+
25+
func recordFlagEvaluationEvents(flagKey: LDFlagKey, value: LDValue, defaultValue: LDValue, featureFlag: FeatureFlag?, context: LDContext, includeReason: Bool) {
26+
}
27+
28+
func flush(completion: CompletionClosure?) {
29+
completion?()
30+
}
31+
}
32+
1833
class EventReporter: EventReporting {
1934
var isOnline: Bool {
2035
get { timerQueue.sync { eventReportTimer != nil } }

LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift

+9
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,15 @@ final class LDClientSpec: QuickSpec {
649649
expect(receivedEvent?.data) == "abc"
650650
expect(receivedEvent?.metricValue) == 5.0
651651
}
652+
context("does not record when events are turned off") {
653+
var config = LDConfig.stub(mobileKey: LDConfig.Constants.mockMobileKey, autoEnvAttributes: .disabled, isDebugBuild: false)
654+
config.sendEvents = false
655+
let testContext = TestContext(newConfig: config)
656+
testContext.start()
657+
let priorRecordedEvents = testContext.eventReporterMock.recordCallCount
658+
testContext.subject.track(key: "customEvent", data: "abc", metricValue: 5.0)
659+
expect(testContext.eventReporterMock.recordCallCount) == priorRecordedEvents
660+
}
652661
context("does not record when client was stopped") {
653662
let testContext = TestContext()
654663
testContext.start()

LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift

+8-4
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,20 @@ final class ClientServiceMockFactory: ClientServiceCreating {
6767
}
6868

6969
var makeEventReporterCallCount = 0
70+
var makeEventReporterReceivedConfig: LDConfig? = nil
7071
var makeEventReporterReceivedService: DarklyServiceProvider? = nil
7172
var onEventSyncComplete: EventSyncCompleteClosure? = nil
72-
func makeEventReporter(service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure?) -> EventReporting {
73+
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure?) -> EventReporting {
7374
makeEventReporterCallCount += 1
75+
makeEventReporterReceivedConfig = config
7476
makeEventReporterReceivedService = service
7577
onEventSyncComplete = onSyncComplete
7678

7779
return EventReportingMock()
7880
}
7981

80-
func makeEventReporter(service: DarklyServiceProvider) -> EventReporting {
81-
return makeEventReporter(service: service, onSyncComplete: nil)
82+
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider) -> EventReporting {
83+
return makeEventReporter(config: config, service: service, onSyncComplete: nil)
8284
}
8385

8486
var makeStreamingProviderCallCount = 0
@@ -105,9 +107,11 @@ final class ClientServiceMockFactory: ClientServiceCreating {
105107

106108
var makeDiagnosticReporterCallCount = 0
107109
var makeDiagnosticReporterReceivedService: DarklyServiceProvider? = nil
108-
func makeDiagnosticReporter(service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting {
110+
var makeDiagnosticReporterReceivedConfig: LDConfig? = nil
111+
func makeDiagnosticReporter(config: LDConfig, service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting {
109112
makeDiagnosticReporterCallCount += 1
110113
makeDiagnosticReporterReceivedService = service
114+
makeDiagnosticReporterReceivedConfig = config
111115
return DiagnosticReportingMock()
112116
}
113117

LaunchDarkly/LaunchDarklyTests/Models/FeatureFlag/FlagRequestTracking/FlagRequestTrackerSpec.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class FlagRequestTrackerSpec: XCTestCase {
3535
counter.trackRequest(reportedValue: false, featureFlag: flag, context: LDContext.stub())
3636
XCTAssertEqual(flagRequestTracker.flagCounters["bool-flag"], counter)
3737
}
38-
38+
3939
func testTrackRequestSameFlagKeyDifferentDefault() {
4040
let flag = FeatureFlag(flagKey: "bool-flag", variation: 1, version: 5, flagVersion: 2)
4141
var flagRequestTracker = FlagRequestTracker(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"))

0 commit comments

Comments
 (0)