lib/ldclient-rb/ldclient.rb in launchdarkly-server-sdk-5.5.12 vs lib/ldclient-rb/ldclient.rb in launchdarkly-server-sdk-5.6.0

- old
+ new

@@ -1,5 +1,6 @@ +require "ldclient-rb/impl/event_factory" require "ldclient-rb/impl/store_client_wrapper" require "concurrent/atomics" require "digest/sha1" require "logger" require "benchmark" @@ -11,10 +12,11 @@ # A client for LaunchDarkly. Client instances are thread-safe. Users # should create a single client instance for the lifetime of the application. # class LDClient include Evaluation + include Impl # # Creates a new client instance that connects to LaunchDarkly. A custom # configuration parameter can also supplied to specify advanced options, # but for most use cases, the default configuration is appropriate. # @@ -30,10 +32,13 @@ # @return [LDClient] The LaunchDarkly client instance # def initialize(sdk_key, config = Config.default, wait_for_sec = 5) @sdk_key = sdk_key + @event_factory_default = EventFactory.new(false) + @event_factory_with_reasons = EventFactory.new(true) + # We need to wrap the feature store object with a FeatureStoreClientWrapper in order to add # some necessary logic around updates. Unfortunately, we have code elsewhere that accesses # the feature store through the Config object, so we need to make a new Config that uses # the wrapped store. @store = Impl::FeatureStoreClientWrapper.new(config.feature_store) @@ -163,11 +168,11 @@ # condition making it impossible to find or evaluate the flag # # @return the variation to show the user, or the default value if there's an an error # def variation(key, user, default) - evaluate_internal(key, user, default, false).value + evaluate_internal(key, user, default, @event_factory_default).value end # # Determines the variation of a feature flag for a user, like {#variation}, but also # provides additional information about how this value was calculated. @@ -190,11 +195,11 @@ # condition making it impossible to find or evaluate the flag # # @return [EvaluationDetail] an object describing the result # def variation_detail(key, user, default) - evaluate_internal(key, user, default, true) + evaluate_internal(key, user, default, @event_factory_with_reasons) end # # Registers the user. This method simply creates an analytics event containing the user # properties, so that LaunchDarkly will know about that user if it does not already. @@ -213,32 +218,43 @@ def identify(user) if !user || user[:key].nil? @config.logger.warn("Identify called with nil user or nil user key!") return end - @event_processor.add_event(kind: "identify", key: user[:key], user: user) + sanitize_user(user) + @event_processor.add_event(@event_factory_default.new_identify_event(user)) end # # Tracks that a user performed an event. This method creates a "custom" analytics event # containing the specified event name (key), user properties, and optional data. # # Note that event delivery is asynchronous, so the event may not actually be sent # until later; see {#flush}. # + # As of this version’s release date, the LaunchDarkly service does not support the `metricValue` + # parameter. As a result, specifying `metricValue` will not yet produce any different behavior + # from omitting it. Refer to the [SDK reference guide](https://docs.launchdarkly.com/docs/ruby-sdk-reference#section-track) + # for the latest status. + # # @param event_name [String] The name of the event # @param user [Hash] The user to register; this can have all the same user properties # described in {#variation} - # @param data [Hash] A hash containing any additional data associated with the event + # @param data [Hash] An optional hash containing any additional data associated with the event + # @param metric_value [Number] A numeric value used by the LaunchDarkly experimentation + # feature in numeric custom metrics. Can be omitted if this event is used by only + # non-numeric metrics. This field will also be returned as part of the custom event + # for Data Export. # @return [void] # - def track(event_name, user, data) + def track(event_name, user, data = nil, metric_value = nil) if !user || user[:key].nil? @config.logger.warn("Track called with nil user or nil user key!") return end - @event_processor.add_event(kind: "custom", key: event_name, user: user, data: data) + sanitize_user(user) + @event_processor.add_event(@event_factory_default.new_custom_event(event_name, user, data, metric_value)) end # # Returns all feature flag values for the given user. # @@ -292,11 +308,11 @@ features.each do |k, f| if client_only && !f[:clientSide] next end begin - result = evaluate(f, user, @store, @config.logger) + result = evaluate(f, user, @store, @config.logger, @event_factory_default) state.add_flag(f, result.detail.value, result.detail.variation_index, with_reasons ? result.detail.reason : nil, details_only_if_tracked) rescue => exn Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn) state.add_flag(f, nil, nil, with_reasons ? { kind: 'ERROR', errorKind: 'EXCEPTION' } : nil, details_only_if_tracked) @@ -332,75 +348,66 @@ PollingProcessor.new(config, requestor) end end # @return [EvaluationDetail] - def evaluate_internal(key, user, default, include_reasons_in_events) + def evaluate_internal(key, user, default, event_factory) if @config.offline? return error_result('CLIENT_NOT_READY', default) end if !initialized? if @store.initialized? @config.logger.warn { "[LDClient] Client has not finished initializing; using last known values from feature store" } else @config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" } - @event_processor.add_event(kind: "feature", key: key, value: default, default: default, user: user) - return error_result('CLIENT_NOT_READY', default) + detail = error_result('CLIENT_NOT_READY', default) + @event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason)) + return detail end end feature = @store.get(FEATURES, key) if feature.nil? @config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" } detail = error_result('FLAG_NOT_FOUND', default) - @event_processor.add_event(kind: "feature", key: key, value: default, default: default, user: user, - reason: include_reasons_in_events ? detail.reason : nil) + @event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason)) return detail end unless user @config.logger.error { "[LDClient] Must specify user" } detail = error_result('USER_NOT_SPECIFIED', default) - @event_processor.add_event(make_feature_event(feature, nil, detail, default, include_reasons_in_events)) + @event_processor.add_event(event_factory.new_default_event(feature, user, default, detail.reason)) return detail end begin - res = evaluate(feature, user, @store, @config.logger) # note, evaluate will do its own sanitization + res = evaluate(feature, user, @store, @config.logger, event_factory) if !res.events.nil? res.events.each do |event| @event_processor.add_event(event) end end detail = res.detail if detail.default_value? detail = EvaluationDetail.new(default, nil, detail.reason) end - @event_processor.add_event(make_feature_event(feature, user, detail, default, include_reasons_in_events)) + @event_processor.add_event(event_factory.new_eval_event(feature, user, detail, default)) return detail rescue => exn Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn) detail = error_result('EXCEPTION', default) - @event_processor.add_event(make_feature_event(feature, user, detail, default, include_reasons_in_events)) + @event_processor.add_event(event_factory.new_default_event(feature, user, default, detail.reason)) return detail end end - def make_feature_event(flag, user, detail, default, with_reasons) - { - kind: "feature", - key: flag[:key], - user: user, - variation: detail.variation_index, - value: detail.value, - default: default, - version: flag[:version], - trackEvents: flag[:trackEvents], - debugEventsUntilDate: flag[:debugEventsUntilDate], - reason: with_reasons ? detail.reason : nil - } + def sanitize_user(user) + if user[:key] + user[:key] = user[:key].to_s + end end end # # Used internally when the client is offline.