cw-datadog 2.23.0.4 → 2.23.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/ext/libdatadog_api/feature_flags.c +554 -0
  3. data/ext/libdatadog_api/feature_flags.h +5 -0
  4. data/ext/libdatadog_api/init.c +2 -0
  5. data/lib/datadog/core/cloudwise/client.rb +99 -4
  6. data/lib/datadog/core/cloudwise/component.rb +55 -13
  7. data/lib/datadog/core/cloudwise/docc_operation_worker.rb +11 -1
  8. data/lib/datadog/core/cloudwise/license_worker.rb +7 -0
  9. data/lib/datadog/core/cloudwise/time_sync_worker.rb +200 -0
  10. data/lib/datadog/core/configuration/settings.rb +12 -0
  11. data/lib/datadog/core/configuration/supported_configurations.rb +14 -0
  12. data/lib/datadog/core/environment/ext.rb +6 -0
  13. data/lib/datadog/core/environment/process.rb +79 -0
  14. data/lib/datadog/core/feature_flags.rb +61 -0
  15. data/lib/datadog/core/tag_normalizer.rb +84 -0
  16. data/lib/datadog/core/transport/http/adapters/net.rb +8 -0
  17. data/lib/datadog/core/utils/array.rb +29 -0
  18. data/lib/datadog/core/utils.rb +2 -0
  19. data/lib/datadog/data_streams/processor.rb +1 -1
  20. data/lib/datadog/di/transport/http.rb +6 -2
  21. data/lib/datadog/di/transport/input.rb +62 -2
  22. data/lib/datadog/open_feature/evaluation_engine.rb +19 -9
  23. data/lib/datadog/open_feature/ext.rb +1 -0
  24. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  25. data/lib/datadog/open_feature/noop_evaluator.rb +3 -3
  26. data/lib/datadog/open_feature/provider.rb +15 -8
  27. data/lib/datadog/open_feature/remote.rb +1 -1
  28. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  29. data/lib/datadog/opentelemetry/metrics.rb +110 -0
  30. data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
  31. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +38 -0
  32. data/lib/datadog/opentelemetry.rb +3 -0
  33. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  34. data/lib/datadog/tracing/contrib/cloudwise/propagation.rb +143 -17
  35. data/lib/datadog/tracing/contrib/grape/endpoint.rb +141 -0
  36. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +26 -0
  37. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +26 -0
  38. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +79 -9
  39. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +29 -6
  40. data/lib/datadog/tracing/contrib/rack/middlewares.rb +6 -54
  41. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  42. data/lib/datadog/tracing/transport/serializable_trace.rb +8 -1
  43. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  44. data/lib/datadog/tracing/transport/traces.rb +3 -5
  45. data/lib/datadog/version.rb +1 -1
  46. metadata +29 -4
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+ require_relative '../tag_normalizer'
5
+
6
+ module Datadog
7
+ module Core
8
+ module Environment
9
+ # Retrieves process level information such that it can be attached to various payloads
10
+ #
11
+ # @api private
12
+ module Process
13
+ # This method returns a key/value part of serialized tags in the format of k1:v1,k2:v2,k3:v3
14
+ # @return [String] comma-separated normalized key:value pairs
15
+ def self.serialized
16
+ return @serialized if defined?(@serialized)
17
+ tags = []
18
+
19
+ workdir = TagNormalizer.normalize_process_value(entrypoint_workdir.to_s)
20
+ tags << "#{Environment::Ext::TAG_ENTRYPOINT_WORKDIR}:#{workdir}" unless workdir.empty?
21
+
22
+ entry_name = TagNormalizer.normalize_process_value(entrypoint_name.to_s)
23
+ tags << "#{Environment::Ext::TAG_ENTRYPOINT_NAME}:#{entry_name}" unless entry_name.empty?
24
+
25
+ basedir = TagNormalizer.normalize_process_value(entrypoint_basedir.to_s)
26
+ tags << "#{Environment::Ext::TAG_ENTRYPOINT_BASEDIR}:#{basedir}" unless basedir.empty?
27
+
28
+ tags << "#{Environment::Ext::TAG_ENTRYPOINT_TYPE}:#{TagNormalizer.normalize(entrypoint_type, remove_digit_start_char: false)}"
29
+
30
+ @serialized = tags.join(',').freeze
31
+ end
32
+
33
+ # Returns the last segment of the working directory of the process
34
+ # Example: /app/myapp -> myapp
35
+ # @return [String] the last segment of the working directory
36
+ def self.entrypoint_workdir
37
+ File.basename(Dir.pwd)
38
+ end
39
+
40
+ # Returns the entrypoint type of the process
41
+ # In Ruby, the entrypoint type is always 'script'
42
+ # @return [String] the type of the process, which is fixed in Ruby
43
+ def self.entrypoint_type
44
+ Environment::Ext::PROCESS_TYPE
45
+ end
46
+
47
+ # Returns the last segment of the base directory of the process
48
+ # Example 1: /bin/mybin -> mybin
49
+ # Example 2: ruby /test/myapp.rb -> myapp
50
+ # @return [String] the last segment of base directory of the script
51
+ #
52
+ # @note Determining true entrypoint name is rather complicated. This method
53
+ # is the initial implementation but it does not produce optimal output in all cases.
54
+ # For example, all Rails applications launched via `rails server` get `rails`
55
+ # as their entrypoint name.
56
+ # We might improve the behavior in the future if there is customer demand for it.
57
+ def self.entrypoint_name
58
+ File.basename($0)
59
+ end
60
+
61
+ # Returns the last segment of the base directory of the process
62
+ # Example 1: /bin/mybin -> bin
63
+ # Example 2: ruby /test/myapp.js -> test
64
+ # @return [String] the last segment of the base directory of the script
65
+ #
66
+ # @note As with entrypoint name, determining true entrypoint directory is complicated.
67
+ # This method has an initial implementation that does not necessarily return good
68
+ # results in all cases. For example, for Rails applications launched via `rails server`
69
+ # the entrypoint basedir is `bin` which is not very helpful.
70
+ # We might improve this in the future if there is customer demand.
71
+ def self.entrypoint_basedir
72
+ File.basename(File.expand_path(File.dirname($0)))
73
+ end
74
+
75
+ private_class_method :entrypoint_workdir, :entrypoint_type, :entrypoint_name, :entrypoint_basedir
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Datadog
6
+ module Core
7
+ # Feature flags evaluation using libdatadog
8
+ # The classes in this module are defined as C extensions in ext/libdatadog_api/feature_flags.c
9
+ #
10
+ # @api private
11
+ module FeatureFlags
12
+ # A top-level error raised by the extension
13
+ class Error < StandardError # rubocop:disable Lint/EmptyClass
14
+ end
15
+
16
+ # Configuration for feature flags evaluation
17
+ # This class is defined in the C extension
18
+ class Configuration # rubocop:disable Lint/EmptyClass
19
+ end
20
+
21
+ # Resolution details for a feature flag evaluation
22
+ # Base class is defined in the C extension, with Ruby methods added here
23
+ class ResolutionDetails
24
+ attr_writer :value
25
+
26
+ # Get the resolved value, with JSON parsing for object types
27
+ #
28
+ # @return [Object] The resolved value (parsed from JSON if object type)
29
+ # @raise [Datadog::Core::FeatureFlags::Error] If JSON parsing fails
30
+ def value
31
+ return @value if defined?(@value)
32
+
33
+ # NOTE: Raw value method call doesn't support memoization right now
34
+ value = raw_value
35
+
36
+ # NOTE: Lazy parsing of the JSON is a temporary solution and will be
37
+ # moved into C extension
38
+ @value = json?(value) ? JSON.parse(value) : value
39
+ rescue JSON::ParserError => e
40
+ raise Error, "Failed to parse JSON value: #{e.class}: #{e}"
41
+ end
42
+
43
+ # Check if the resolution resulted in an error
44
+ #
45
+ # @return [Boolean] True if there was an error
46
+ def error?
47
+ reason == 'ERROR'
48
+ end
49
+
50
+ private
51
+
52
+ # NOTE: A JSON raw string will be returned by the `libdatadog` as
53
+ # a Ruby String class with a flag type `:object`, otherwise it's
54
+ # just a string.
55
+ def json?(value)
56
+ flag_type == :object && value.is_a?(String)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'utils'
4
+
5
+ module Datadog
6
+ module Core
7
+ # @api private
8
+ module TagNormalizer
9
+ # Normalization logic used for tag keys and values that the Trace Agent has for traces
10
+ # Useful for ensuring that tag keys and values are normalized consistently
11
+ # An use case for now is Process Tags which need to be sent across various intakes (profiling, tracing, etc.) consistently
12
+
13
+ module_function
14
+
15
+ INVALID_TAG_CHARACTERS = %r{[^\p{L}0-9_\-:./]}
16
+ LEADING_INVALID_CHARS_NO_DIGITS = %r{\A[^\p{L}:]++}
17
+ LEADING_INVALID_CHARS_WITH_DIGITS = %r{\A[^\p{L}0-9:./]++}
18
+ MAX_BYTE_SIZE = 200 # Represents the general max tag length
19
+ MAX_PROCESS_VALUE_BYTE_SIZE = 100 # Represents the max tag length for process tags
20
+ VALID_ASCII_TAG = %r{\A[a-z:][a-z0-9:./-]*\z}
21
+
22
+ # Based on https://github.com/DataDog/datadog-agent/blob/45799c842bbd216bcda208737f9f11cade6fdd95/pkg/trace/traceutil/normalize.go#L131
23
+ # Specifically for general normalization:
24
+ # - Must be valid UTF-8
25
+ # - Invalid characters are replaced with an underscore
26
+ # - Leading non-letter characters are removed but colons are kept
27
+ # - Trailing non-letter characters are removed
28
+ # - Trailing underscores are removed
29
+ # - Consecutive underscores are merged into a single underscore
30
+ # - Maximum length is 200 characters
31
+ # If it's a tag value, allow it to start with a digit
32
+ # @param original_value [String] The original string
33
+ # @param remove_digit_start_char [Boolean] - whether to remove the leading digit (currently only used for tag values)
34
+ # @return [String] The normalized string
35
+ def self.normalize(original_value, remove_digit_start_char: false)
36
+ # DEV-3.0: Ideally this encode call should be replaced with Datadog::Core::Utils.utf8_encode once it
37
+ # is safe to modify the default behavior.
38
+ value = original_value.to_s.encode('UTF-8', invalid: :replace, undef: :replace)
39
+ value.strip!
40
+ return "" if value.empty?
41
+
42
+ return value if value.bytesize <= MAX_BYTE_SIZE &&
43
+ value.match?(VALID_ASCII_TAG)
44
+
45
+ if value.bytesize > MAX_BYTE_SIZE
46
+ value = value.byteslice(0, MAX_BYTE_SIZE)
47
+ value.scrub!("")
48
+ end
49
+
50
+ value.downcase!
51
+ value.gsub!(INVALID_TAG_CHARACTERS, '_')
52
+
53
+ # The Trace Agent allows tag values to start with a number so this logic is here too
54
+ leading_invalid_regex = remove_digit_start_char ? LEADING_INVALID_CHARS_NO_DIGITS : LEADING_INVALID_CHARS_WITH_DIGITS
55
+ value.sub!(leading_invalid_regex, "")
56
+
57
+ value.squeeze!('_') if value.include?('__')
58
+ value.delete_suffix!('_')
59
+
60
+ value
61
+ end
62
+
63
+ # Process tags values follow an additional piece of normalization:
64
+ # - must not be more than 100 bytes
65
+ # - and must not contain colons
66
+ # @param value [String] The original string
67
+ # @return [String] The normalized string
68
+ def self.normalize_process_value(value)
69
+ value = normalize(value)
70
+ return value if value.empty?
71
+
72
+ value.tr!(':', '_')
73
+ value.squeeze!('_') if value.include?('__')
74
+
75
+ if value.bytesize > MAX_PROCESS_VALUE_BYTE_SIZE
76
+ value = value.byteslice(0, MAX_PROCESS_VALUE_BYTE_SIZE) || value
77
+ value.scrub!("")
78
+ end
79
+
80
+ value
81
+ end
82
+ end
83
+ end
84
+ end
@@ -81,6 +81,14 @@ module Datadog
81
81
  http.request(post)
82
82
  end
83
83
 
84
+ # Debug log: response result
85
+ Datadog.logger.debug do
86
+ response_body = http_response.body.to_s
87
+ # 截断过长的响应体
88
+ truncated_body = response_body.length > 500 ? "#{response_body[0..500]}..." : response_body
89
+ "[Trace Transport] Flushing trace response: POST.uri =#{net_http_path_from_env(env)}, body=#{truncated_body}"
90
+ end if defined?(Datadog.logger)
91
+
84
92
  # Build and return response
85
93
  Response.new(http_response)
86
94
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ module Utils
6
+ # Common array-related utility functions.
7
+ module Array
8
+ def self.filter_map(array, &block)
9
+ if array.respond_to?(:filter_map)
10
+ # DEV Supported since Ruby 2.7, saves an intermediate object creation
11
+ array.filter_map(&block)
12
+ elsif array.is_a?(Enumerator::Lazy)
13
+ # You would think that .compact would work here, but it does not:
14
+ # the result of .map could be an Enumerator::Lazy instance which
15
+ # does not implement #compact on Ruby 2.5/2.6.
16
+ array.map(&block).reject do |item|
17
+ item.nil?
18
+ end
19
+ else
20
+ array.each_with_object([]) do |item, memo|
21
+ new_item = block.call(item)
22
+ memo.push(new_item) unless new_item.nil?
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -38,6 +38,8 @@ module Datadog
38
38
 
39
39
  # Ensure `str` is a valid UTF-8, ready to be
40
40
  # sent through the tracer transport.
41
+ # DEV-3.0: This method should unconditionally handle invalid byte sequences
42
+ # DEV-3.0: and return a safe string to display.
41
43
  #
42
44
  # @param [String,#to_s] str object to be converted to a UTF-8 string
43
45
  # @param [Boolean] binary whether to expect binary data in the `str` parameter
@@ -475,7 +475,7 @@ module Datadog
475
475
  @consumer_stats.map do |stat|
476
476
  {
477
477
  'Tags' => [
478
- 'type:kafka_consume',
478
+ 'type:kafka_commit',
479
479
  "topic:#{stat[:topic]}",
480
480
  "partition:#{stat[:partition]}"
481
481
  ],
@@ -40,9 +40,13 @@ module Datadog
40
40
  api_version: nil,
41
41
  headers: nil
42
42
  )
43
- Core::Transport::HTTP.build(api_instance_class: Input::API::Instance,
43
+ Core::Transport::HTTP.build(
44
+ api_instance_class: Input::API::Instance,
44
45
  logger: logger,
45
- agent_settings: agent_settings, api_version: api_version, headers: headers) do |transport|
46
+ agent_settings: agent_settings,
47
+ api_version: api_version,
48
+ headers: headers,
49
+ ) do |transport|
46
50
  apis = API.defaults
47
51
 
48
52
  transport.api API::INPUT, apis[API::INPUT]
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../core/chunker'
4
+ require_relative '../../core/encoding'
5
+ require_relative '../../core/tag_builder'
3
6
  require_relative '../../core/transport/parcel'
7
+ require_relative '../../core/transport/request'
8
+ require_relative '../error'
4
9
  require_relative 'http/input'
5
10
 
6
11
  module Datadog
@@ -24,6 +29,25 @@ module Datadog
24
29
  class Transport
25
30
  attr_reader :client, :apis, :default_api, :current_api_id, :logger
26
31
 
32
+ # The limit on an individual snapshot payload, aka "log line",
33
+ # is 1 MB.
34
+ #
35
+ # TODO There is an RFC for snapshot pruning that should be
36
+ # implemented to reduce the size of snapshots to be below this
37
+ # limit, so that we can send a portion of the captured data
38
+ # rather than dropping the snapshot entirely.
39
+ MAX_SERIALIZED_SNAPSHOT_SIZE = 1024 * 1024
40
+
41
+ # The maximum chunk (batch) size that intake permits is 5 MB.
42
+ #
43
+ # Two bytes are for the [ and ] of JSON array syntax.
44
+ MAX_CHUNK_SIZE = 5 * 1024 * 1024 - 2
45
+
46
+ # Try to send smaller payloads to avoid large network requests.
47
+ # If a payload is larger than default chunk size but is under the
48
+ # max chunk size, it will still get sent out.
49
+ DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024
50
+
27
51
  def initialize(apis, default_api, logger:)
28
52
  @apis = apis
29
53
  @logger = logger
@@ -36,9 +60,45 @@ module Datadog
36
60
  end
37
61
 
38
62
  def send_input(payload, tags)
39
- json = JSON.dump(payload)
40
- parcel = EncodedParcel.new(json)
63
+ # Tags are the same for all chunks, serialize them one time.
41
64
  serialized_tags = Core::TagBuilder.serialize_tags(tags)
65
+
66
+ encoder = Core::Encoding::JSONEncoder
67
+ encoded_snapshots = Core::Utils::Array.filter_map(payload) do |snapshot|
68
+ encoded = encoder.encode(snapshot)
69
+ if encoded.length > MAX_SERIALIZED_SNAPSHOT_SIZE
70
+ # Drop the snapshot.
71
+ # TODO report via telemetry metric?
72
+ logger.debug { "di: dropping too big snapshot" }
73
+ nil
74
+ else
75
+ encoded
76
+ end
77
+ end
78
+
79
+ Datadog::Core::Chunker.chunk_by_size(
80
+ encoded_snapshots, DEFAULT_CHUNK_SIZE,
81
+ ).each do |chunk|
82
+ # We drop snapshots that are too big earlier.
83
+ # The limit on chunked payload length here is greater
84
+ # than the limit on snapshot size, therefore no chunks
85
+ # can exceed limits here.
86
+ chunked_payload = encoder.join(chunk)
87
+
88
+ # We need to rescue exceptions for each chunk so that
89
+ # subsequent chunks are attempted to be sent.
90
+ begin
91
+ send_input_chunk(chunked_payload, serialized_tags)
92
+ rescue => exc
93
+ logger.debug { "di: failed to send snapshot chunk: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
94
+ end
95
+ end
96
+
97
+ payload
98
+ end
99
+
100
+ def send_input_chunk(chunked_payload, serialized_tags)
101
+ parcel = EncodedParcel.new(chunked_payload)
42
102
  request = Request.new(parcel, serialized_tags)
43
103
 
44
104
  response = @client.send_input_payload(request)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'ext'
4
4
  require_relative 'noop_evaluator'
5
+ require_relative 'native_evaluator'
5
6
  require_relative 'resolution_details'
6
7
 
7
8
  module Datadog
@@ -10,7 +11,7 @@ module Datadog
10
11
  class EvaluationEngine
11
12
  ReconfigurationError = Class.new(StandardError)
12
13
 
13
- ALLOWED_TYPES = %w[boolean string number float integer object].freeze
14
+ ALLOWED_TYPES = %i[boolean string number float integer object].freeze
14
15
 
15
16
  def initialize(reporter, telemetry:, logger:)
16
17
  @reporter = reporter
@@ -20,7 +21,7 @@ module Datadog
20
21
  @evaluator = NoopEvaluator.new(nil)
21
22
  end
22
23
 
23
- def fetch_value(flag_key:, default_value:, expected_type:, evaluation_context: nil)
24
+ def fetch_value(flag_key, default_value:, expected_type:, evaluation_context: nil)
24
25
  unless ALLOWED_TYPES.include?(expected_type)
25
26
  message = "unknown type #{expected_type.inspect}, allowed types #{ALLOWED_TYPES.join(", ")}"
26
27
  return ResolutionDetails.build_error(
@@ -28,8 +29,10 @@ module Datadog
28
29
  )
29
30
  end
30
31
 
31
- context = evaluation_context&.fields || {}
32
- result = @evaluator.get_assignment(flag_key, default_value, context, expected_type)
32
+ context = evaluation_context&.fields.to_h
33
+ result = @evaluator.get_assignment(
34
+ flag_key, default_value: default_value, context: context, expected_type: expected_type
35
+ )
33
36
 
34
37
  @reporter.report(result, flag_key: flag_key, context: evaluation_context)
35
38
 
@@ -38,19 +41,26 @@ module Datadog
38
41
  @telemetry.report(e, description: 'OpenFeature: Failed to fetch flag value')
39
42
 
40
43
  ResolutionDetails.build_error(
41
- value: default_value, error_code: Ext::PROVIDER_FATAL, error_message: e.message
44
+ value: default_value, error_code: Ext::GENERAL, error_message: e.message
42
45
  )
43
46
  end
44
47
 
48
+ # NOTE: In a currect implementation configuration is expected to be a raw
49
+ # JSON string containing feature flags (straight from the remote config)
50
+ # in the format expected by `libdatadog` without any modifications
45
51
  def reconfigure!(configuration)
46
- @logger.debug('OpenFeature: Removing configuration') if configuration.nil?
52
+ if configuration.nil?
53
+ @logger.debug('OpenFeature: Removing configuration')
54
+
55
+ return @evaluator = NoopEvaluator.new(configuration)
56
+ end
47
57
 
48
- @evaluator = NoopEvaluator.new(configuration)
58
+ @evaluator = NativeEvaluator.new(configuration)
49
59
  rescue => e
50
60
  message = 'OpenFeature: Failed to reconfigure, reverting to the previous configuration'
51
61
 
52
- @logger.error("#{message}, error #{e.inspect}")
53
- @telemetry.report(e, description: message)
62
+ @logger.error("#{message}, #{e.class}: #{e.message}")
63
+ @telemetry.report(e, description: "#{message} (#{e.class})")
54
64
 
55
65
  raise ReconfigurationError, e.message
56
66
  end
@@ -6,6 +6,7 @@ module Datadog
6
6
  ERROR = 'ERROR'
7
7
  INITIALIZING = 'INITIALIZING'
8
8
  UNKNOWN_TYPE = 'UNKNOWN_TYPE'
9
+ GENERAL = 'GENERAL'
9
10
  PROVIDER_FATAL = 'PROVIDER_FATAL'
10
11
  PROVIDER_NOT_READY = 'PROVIDER_NOT_READY'
11
12
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/feature_flags'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ # This class is an interface of evaluation logic using native extension
8
+ class NativeEvaluator
9
+ # NOTE: In a currect implementation configuration is expected to be a raw
10
+ # JSON string containing feature flags (straight from the remote config)
11
+ # in the format expected by `libdatadog` without any modifications
12
+ def initialize(configuration)
13
+ @configuration = Core::FeatureFlags::Configuration.new(configuration)
14
+ end
15
+
16
+ # Returns the assignment for a given flag key based on the feature flags
17
+ # configuration
18
+ #
19
+ # @param flag_key [String] The key of the feature flag
20
+ # @param default_value [Object] The default value to return if the flag is
21
+ # not found or evaluation itself fails
22
+ # @param expected_type [Symbol] The expected type of the flag
23
+ # @param context [Hash] The context of the evaluation, containing targeting key
24
+ # and other attributes
25
+ #
26
+ # @return [Core::FeatureFlags::ResolutionDetails] The assignment for the flag
27
+ def get_assignment(flag_key, default_value:, expected_type:, context:)
28
+ result = @configuration.get_assignment(flag_key, expected_type, context)
29
+
30
+ # NOTE: This is a special case when we need to fallback to the default
31
+ # value, even tho the evaluation itself doesn't produce an error
32
+ # resolution details
33
+ result.value = default_value if result.variant.nil?
34
+ result
35
+ end
36
+ end
37
+ end
38
+ end
@@ -11,14 +11,14 @@ module Datadog
11
11
  # no-op
12
12
  end
13
13
 
14
- def get_assignment(_flag_key, default_value, _context, _expected_type)
14
+ def get_assignment(_flag_key, default_value:, context:, expected_type:)
15
15
  ResolutionDetails.new(
16
16
  value: default_value,
17
17
  log?: false,
18
18
  error?: true,
19
19
  error_code: Ext::PROVIDER_NOT_READY,
20
- error_message: 'Waiting for universal flag configuration',
21
- reason: Ext::INITIALIZING
20
+ error_message: 'Waiting for flags configuration',
21
+ reason: Ext::ERROR
22
22
  )
23
23
  end
24
24
  end
@@ -68,27 +68,27 @@ module Datadog
68
68
  end
69
69
 
70
70
  def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
71
- evaluate(flag_key, default_value: default_value, expected_type: 'boolean', evaluation_context: evaluation_context)
71
+ evaluate(flag_key, default_value: default_value, expected_type: :boolean, evaluation_context: evaluation_context)
72
72
  end
73
73
 
74
74
  def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
75
- evaluate(flag_key, default_value: default_value, expected_type: 'string', evaluation_context: evaluation_context)
75
+ evaluate(flag_key, default_value: default_value, expected_type: :string, evaluation_context: evaluation_context)
76
76
  end
77
77
 
78
78
  def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
79
- evaluate(flag_key, default_value: default_value, expected_type: 'number', evaluation_context: evaluation_context)
79
+ evaluate(flag_key, default_value: default_value, expected_type: :number, evaluation_context: evaluation_context)
80
80
  end
81
81
 
82
82
  def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
83
- evaluate(flag_key, default_value: default_value, expected_type: 'integer', evaluation_context: evaluation_context)
83
+ evaluate(flag_key, default_value: default_value, expected_type: :integer, evaluation_context: evaluation_context)
84
84
  end
85
85
 
86
86
  def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
87
- evaluate(flag_key, default_value: default_value, expected_type: 'float', evaluation_context: evaluation_context)
87
+ evaluate(flag_key, default_value: default_value, expected_type: :float, evaluation_context: evaluation_context)
88
88
  end
89
89
 
90
90
  def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
91
- evaluate(flag_key, default_value: default_value, expected_type: 'object', evaluation_context: evaluation_context)
91
+ evaluate(flag_key, default_value: default_value, expected_type: :object, evaluation_context: evaluation_context)
92
92
  end
93
93
 
94
94
  private
@@ -98,7 +98,7 @@ module Datadog
98
98
  return component_not_configured_default(default_value) if engine.nil?
99
99
 
100
100
  result = engine.fetch_value(
101
- flag_key: flag_key,
101
+ flag_key,
102
102
  default_value: default_value,
103
103
  expected_type: expected_type,
104
104
  evaluation_context: evaluation_context
@@ -106,7 +106,7 @@ module Datadog
106
106
 
107
107
  if result.error?
108
108
  return ::OpenFeature::SDK::Provider::ResolutionDetails.new(
109
- value: result.value,
109
+ value: default_value,
110
110
  error_code: result.error_code,
111
111
  error_message: result.error_message,
112
112
  reason: result.reason
@@ -119,6 +119,13 @@ module Datadog
119
119
  reason: result.reason,
120
120
  flag_metadata: result.flag_metadata
121
121
  )
122
+ rescue => e
123
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
124
+ value: default_value,
125
+ error_code: Ext::GENERAL,
126
+ error_message: "#{e.class}: #{e.message}",
127
+ reason: Ext::ERROR
128
+ )
122
129
  end
123
130
 
124
131
  def component_not_configured_default(value)
@@ -25,7 +25,7 @@ module Datadog
25
25
  matcher = Core::Remote::Dispatcher::Matcher::Product.new(FFE_PRODUCTS)
26
26
  receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
27
27
  engine = OpenFeature.engine
28
- break unless engine
28
+ next unless engine
29
29
 
30
30
  changes.each do |change|
31
31
  content = repository[change.path]