Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add LDConfig.sendEvents option to disable all events #414

Merged
merged 3 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ContractTests/Source/Controllers/SdkController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ final class SdkController: RouteCollection {
if let enableCompression = events.enableGzip {
config.enableCompression = enableCompression
}
} else {
config.sendEvents = false
}

if let tags = createInstance.configuration.tags {
Expand Down
3 changes: 2 additions & 1 deletion LaunchDarkly.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@
A3599E892A4B4AD400DB5C67 /* Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3599E872A4B4AD400DB5C67 /* Modifier.swift */; };
A3599E8A2A4B4AD400DB5C67 /* Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3599E872A4B4AD400DB5C67 /* Modifier.swift */; };
A3599E8B2A4B4AD400DB5C67 /* Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3599E872A4B4AD400DB5C67 /* Modifier.swift */; };
A35AC5572CD559DE00875751 /* CwlPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; productRef = A3F4A4802CC2F640006EF480 /* CwlPreconditionTesting */; };
A35AD4602A619E45005A8DCB /* SystemCapabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35AD45F2A619E45005A8DCB /* SystemCapabilities.swift */; };
A35AD4612A619E45005A8DCB /* SystemCapabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35AD45F2A619E45005A8DCB /* SystemCapabilities.swift */; };
A35AD4622A619E45005A8DCB /* SystemCapabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35AD45F2A619E45005A8DCB /* SystemCapabilities.swift */; };
Expand Down Expand Up @@ -560,10 +561,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A35AC5572CD559DE00875751 /* CwlPreconditionTesting in Frameworks */,
B4903D9E24BD61EF00F087C4 /* Quick in Frameworks */,
B4903D9B24BD61D000F087C4 /* Nimble in Frameworks */,
B4903D9824BD61B200F087C4 /* OHHTTPStubsSwift in Frameworks */,
A3F4A4812CC2F640006EF480 /* CwlPreconditionTesting in Frameworks */,
8354EFCC1F22491C00C05156 /* LaunchDarkly.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
4 changes: 2 additions & 2 deletions LaunchDarkly/GeneratedCode/mocks.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,12 @@ final class EventReportingMock: EventReporting {
}

var recordFlagEvaluationEventsCallCount = 0
var recordFlagEvaluationEventsCallback: ((_ event: FeatureEvent) throws -> Void)?
var recordFlagEvaluationEventsCallback: (() throws -> Void)?
var recordFlagEvaluationEventsReceivedArguments: (flagKey: LDFlagKey, value: LDValue, defaultValue: LDValue, featureFlag: FeatureFlag?, context: LDContext, includeReason: Bool)?
func recordFlagEvaluationEvents(flagKey: LDFlagKey, value: LDValue, defaultValue: LDValue, featureFlag: FeatureFlag?, context: LDContext, includeReason: Bool) {
recordFlagEvaluationEventsCallCount += 1
recordFlagEvaluationEventsReceivedArguments = (flagKey: flagKey, value: value, defaultValue: defaultValue, featureFlag: featureFlag, context: context, includeReason: includeReason)
try! recordFlagEvaluationEventsCallback?(FeatureEvent(key: flagKey, context: context, value: value, defaultValue: defaultValue, featureFlag: featureFlag, includeReason: includeReason, isDebug: false))
try! recordFlagEvaluationEventsCallback?()
}

var flushCallCount = 0
Expand Down
6 changes: 3 additions & 3 deletions LaunchDarkly/LaunchDarkly/LDClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -878,8 +878,8 @@ public class LDClient {
}

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

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

eventReporter = self.serviceFactory.makeEventReporter(service: service, onSyncComplete: onEventSyncComplete)
eventReporter = self.serviceFactory.makeEventReporter(config: configuration, service: service, onSyncComplete: onEventSyncComplete)
service.resetFlagResponseCache(etag: cachedData.etag)
flagSynchronizer = self.serviceFactory.makeFlagSynchronizer(streamingMode: config.allowStreamingMode ? config.streamingMode : .polling,
pollingInterval: config.flagPollingInterval(runMode: runMode),
Expand Down
6 changes: 3 additions & 3 deletions LaunchDarkly/LaunchDarkly/LDClientVariation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,11 @@ extension LDClient {
var result: LDEvaluationDetail<T>
let featureFlag = flagStore.featureFlag(for: flagKey)
if let featureFlag = featureFlag {
featureFlag.prerequisites?.forEach{ prereqFlagKey in
featureFlag.prerequisites?.forEach { prereqFlagKey in
// recurse on prerequisites to emulate prereq evaluations occurring with desirable side effects such as events for prereqs
_ = variationDetailInternal(prereqFlagKey, LDValue.null, needsReason: needsReason, methodName: methodName)
}

if featureFlag.value == .null {
result = LDEvaluationDetail(value: defaultValue, variationIndex: featureFlag.variation, reason: featureFlag.reason)
} else {
Expand All @@ -193,7 +193,7 @@ extension LDClient {
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,
value: result.value.toLDValue(),
defaultValue: defaultValue.toLDValue(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ final class FlagCounter: Encodable {
private(set) var defaultValue: LDValue
private(set) var flagValueCounters: [CounterKey: CounterValue] = [:]
private(set) var contextKinds: Set<String> = Set()

init(defaultValue: LDValue) {
// default value follows a "first one wins" approach where the first evaluation for a flag key sets the default value for the summary events
self.defaultValue = defaultValue
Expand Down
8 changes: 8 additions & 0 deletions LaunchDarkly/LaunchDarkly/Models/LDConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ public struct LDConfig {
/// The default base url for connecting to streaming service
static let streamUrl = URL(string: "https://clientstream.launchdarkly.com")!

/// The default behavior for the SDK is to send events.
static let sendEvents = true

/// The default maximum number of events the LDClient can store
static let eventCapacity = 100

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

/// Whether to send events back to LaunchDarkly. This differs from {#offline?} in that it affects
/// only the sending of client-side events, not streaming or polling for events from the server.
public var sendEvents: Bool = Defaults.sendEvents

/// 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)
public var eventCapacity: Int = Defaults.eventCapacity

Expand Down Expand Up @@ -532,6 +539,7 @@ extension LDConfig: Equatable {
&& lhs.eventsUrl == rhs.eventsUrl
&& lhs.streamUrl == rhs.streamUrl
&& lhs.eventCapacity == rhs.eventCapacity
&& lhs.sendEvents == rhs.sendEvents
&& lhs.connectionTimeout == rhs.connectionTimeout
&& lhs.eventFlushInterval == rhs.eventFlushInterval
&& lhs.flagPollingInterval == rhs.flagPollingInterval
Expand Down
2 changes: 1 addition & 1 deletion LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ final class DarklyService: DarklyServiceProvider {
self.context = context
self.serviceFactory = serviceFactory

if !config.mobileKey.isEmpty && !config.diagnosticOptOut {
if !config.mobileKey.isEmpty && !config.diagnosticOptOut && config.sendEvents {
Copy link
Member Author

Choose a reason for hiding this comment

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

This change alone is sufficient to disable diagnostic events. If the cache doesn't exist, we won't collect diagnostic events.

self.diagnosticCache = serviceFactory.makeDiagnosticCache(sdkKey: config.mobileKey)
} else {
self.diagnosticCache = nil
Expand Down
5 changes: 5 additions & 0 deletions LaunchDarkly/LaunchDarkly/ObjectiveC/ObjcLDConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public final class ObjcLDConfig: NSObject {
get { config.eventFlushInterval }
set { config.eventFlushInterval = newValue }
}
// Whether or not the SDK should send events
@objc public var sendEvents: Bool {
get { config.sendEvents }
set { config.sendEvents = newValue }
}
/// The interval between feature flag requests. Used only for polling mode. (Default: 5 minutes)
@objc public var flagPollingInterval: TimeInterval {
get { config.flagPollingInterval }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ protocol ClientServiceCreating {
service: DarklyServiceProvider,
onSyncComplete: FlagSyncCompleteClosure?) -> LDFlagSynchronizing
func makeFlagChangeNotifier() -> FlagChangeNotifying
func makeEventReporter(service: DarklyServiceProvider) -> EventReporting
func makeEventReporter(service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure?) -> EventReporting
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider) -> EventReporting
func makeEventReporter(config: LDConfig, 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 makeThrottler(environmentReporter: EnvironmentReporting) -> Throttling
func makeConnectionInformation() -> ConnectionInformation
func makeDiagnosticCache(sdkKey: String) -> DiagnosticCaching
func makeDiagnosticReporter(service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting
func makeDiagnosticReporter(config: LDConfig, service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting
func makeFlagStore() -> FlagMaintaining
}

Expand Down Expand Up @@ -66,12 +66,16 @@ final class ClientServiceFactory: ClientServiceCreating {
FlagChangeNotifier(logger: logger)
}

func makeEventReporter(service: DarklyServiceProvider) -> EventReporting {
makeEventReporter(service: service, onSyncComplete: nil)
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider) -> EventReporting {
makeEventReporter(config: config, service: service, onSyncComplete: nil)
}

func makeEventReporter(service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure? = nil) -> EventReporting {
EventReporter(service: service, onSyncComplete: onSyncComplete)
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure? = nil) -> EventReporting {
if config.sendEvents {
EventReporter(service: service, onSyncComplete: onSyncComplete)
} else {
NullEventReporter()
}
}

func makeStreamingProvider(url: URL,
Expand Down Expand Up @@ -121,8 +125,12 @@ final class ClientServiceFactory: ClientServiceCreating {
DiagnosticCache(sdkKey: sdkKey)
}

func makeDiagnosticReporter(service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting {
DiagnosticReporter(service: service, environmentReporting: environmentReporter)
func makeDiagnosticReporter(config: LDConfig, service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting {
if config.sendEvents && !config.diagnosticOptOut {
Copy link
Member Author

Choose a reason for hiding this comment

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

But we also add this bit to make it a little more efficient by not making a reporter at all.

Copy link
Member

Choose a reason for hiding this comment

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

The suspense between the two comments. I read the first one and I was like, "yes, but that indirectness scares me".

Copy link
Member Author

Choose a reason for hiding this comment

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

😆 yes, I hate that there are even two paths like this. But there are protocols that are depending on others in a cycle and so really teasing that out as much as I would prefer is probably more effort than it is worth. Especially since we're pushing for a larger re-work.

DiagnosticReporter(service: service, environmentReporting: environmentReporter)
} else {
NullDiagnosticReporter()
}
}

func makeFlagStore() -> FlagMaintaining {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ protocol DiagnosticReporting {
func setMode(_ runMode: LDClientRunMode, online: Bool)
}

class NullDiagnosticReporter: DiagnosticReporting {
func setMode(_ runMode: LDClientRunMode, online: Bool) {
}
}

class DiagnosticReporter: DiagnosticReporting {
private let service: DarklyServiceProvider
private let environmentReporting: EnvironmentReporting
Expand Down
15 changes: 15 additions & 0 deletions LaunchDarkly/LaunchDarkly/ServiceObjects/EventReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ protocol EventReporting {
func flush(completion: CompletionClosure?)
}

class NullEventReporter: EventReporting {
var isOnline: Bool = true
var lastEventResponseDate: Date = Date()

func record(_ event: Event) {
}

func recordFlagEvaluationEvents(flagKey: LDFlagKey, value: LDValue, defaultValue: LDValue, featureFlag: FeatureFlag?, context: LDContext, includeReason: Bool) {
}

func flush(completion: CompletionClosure?) {
completion?()
}
}

class EventReporter: EventReporting {
var isOnline: Bool {
get { timerQueue.sync { eventReportTimer != nil } }
Expand Down
9 changes: 9 additions & 0 deletions LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,15 @@ final class LDClientSpec: QuickSpec {
expect(receivedEvent?.data) == "abc"
expect(receivedEvent?.metricValue) == 5.0
}
context("does not record when events are turned off") {
var config = LDConfig.stub(mobileKey: LDConfig.Constants.mockMobileKey, autoEnvAttributes: .disabled, isDebugBuild: false)
config.sendEvents = false
let testContext = TestContext(newConfig: config)
testContext.start()
let priorRecordedEvents = testContext.eventReporterMock.recordCallCount
testContext.subject.track(key: "customEvent", data: "abc", metricValue: 5.0)
expect(testContext.eventReporterMock.recordCallCount) == priorRecordedEvents
}
context("does not record when client was stopped") {
let testContext = TestContext()
testContext.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,20 @@ final class ClientServiceMockFactory: ClientServiceCreating {
}

var makeEventReporterCallCount = 0
var makeEventReporterReceivedConfig: LDConfig? = nil
var makeEventReporterReceivedService: DarklyServiceProvider? = nil
var onEventSyncComplete: EventSyncCompleteClosure? = nil
func makeEventReporter(service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure?) -> EventReporting {
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure?) -> EventReporting {
makeEventReporterCallCount += 1
makeEventReporterReceivedConfig = config
makeEventReporterReceivedService = service
onEventSyncComplete = onSyncComplete

return EventReportingMock()
}

func makeEventReporter(service: DarklyServiceProvider) -> EventReporting {
return makeEventReporter(service: service, onSyncComplete: nil)
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider) -> EventReporting {
return makeEventReporter(config: config, service: service, onSyncComplete: nil)
}

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

var makeDiagnosticReporterCallCount = 0
var makeDiagnosticReporterReceivedService: DarklyServiceProvider? = nil
func makeDiagnosticReporter(service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting {
var makeDiagnosticReporterReceivedConfig: LDConfig? = nil
func makeDiagnosticReporter(config: LDConfig, service: DarklyServiceProvider, environmentReporter: EnvironmentReporting) -> DiagnosticReporting {
makeDiagnosticReporterCallCount += 1
makeDiagnosticReporterReceivedService = service
makeDiagnosticReporterReceivedConfig = config
return DiagnosticReportingMock()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class FlagRequestTrackerSpec: XCTestCase {
counter.trackRequest(reportedValue: false, featureFlag: flag, context: LDContext.stub())
XCTAssertEqual(flagRequestTracker.flagCounters["bool-flag"], counter)
}

func testTrackRequestSameFlagKeyDifferentDefault() {
let flag = FeatureFlag(flagKey: "bool-flag", variation: 1, version: 5, flagVersion: 2)
var flagRequestTracker = FlagRequestTracker(logger: OSLog(subsystem: "com.launchdarkly", category: "tests"))
Expand Down
Loading