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.