datadog 2.42.0 → 2.44.0

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -2
  3. data/README.md +3 -0
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +46 -5
  5. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +6 -7
  6. data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -7
  7. data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +80 -36
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
  10. data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +13 -0
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +4 -0
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +34 -2
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +1 -0
  15. data/ext/datadog_profiling_native_extension/profiling.c +1 -0
  16. data/ext/datadog_profiling_native_extension/stack_recorder.c +17 -4
  17. data/ext/datadog_profiling_native_extension/time_helpers.h +5 -4
  18. data/ext/libdatadog_api/di.c +8 -3
  19. data/ext/libdatadog_api/extconf.rb +1 -1
  20. data/ext/libdatadog_api/feature_flags.c +8 -0
  21. data/ext/libdatadog_api/init.c +3 -1
  22. data/ext/libdatadog_api/otel_thread_context.c +51 -15
  23. data/ext/libdatadog_api/trace_exporter.c +642 -22
  24. data/ext/libdatadog_extconf_helpers.rb +14 -9
  25. data/lib/datadog/ai_guard/component.rb +17 -8
  26. data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
  27. data/lib/datadog/ai_guard/configuration.rb +13 -0
  28. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
  29. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
  30. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
  31. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
  32. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
  33. data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
  34. data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
  35. data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
  36. data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
  37. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
  38. data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
  39. data/lib/datadog/ai_guard/evaluation/request.rb +4 -60
  40. data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
  41. data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
  42. data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
  43. data/lib/datadog/ai_guard/evaluation.rb +25 -11
  44. data/lib/datadog/ai_guard/ext.rb +1 -0
  45. data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +6 -4
  46. data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
  47. data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
  48. data/lib/datadog/ai_guard/redaction/result.rb +29 -0
  49. data/lib/datadog/ai_guard/redaction.rb +80 -0
  50. data/lib/datadog/ai_guard.rb +58 -57
  51. data/lib/datadog/core/configuration/components.rb +9 -1
  52. data/lib/datadog/core/configuration/settings.rb +5 -2
  53. data/lib/datadog/core/configuration/supported_configurations.rb +8 -0
  54. data/lib/datadog/core/remote/client/capabilities.rb +10 -4
  55. data/lib/datadog/core/remote/component.rb +9 -2
  56. data/lib/datadog/core/remote/worker.rb +8 -0
  57. data/lib/datadog/di/instrumenter.rb +1 -1
  58. data/lib/datadog/di/probe_manager.rb +10 -5
  59. data/lib/datadog/di/proc_responder.rb +1 -1
  60. data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
  61. data/lib/datadog/open_feature/agentless/parser.rb +43 -0
  62. data/lib/datadog/open_feature/agentless/response.rb +19 -0
  63. data/lib/datadog/open_feature/agentless/transport.rb +103 -0
  64. data/lib/datadog/open_feature/component.rb +57 -1
  65. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
  66. data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
  67. data/lib/datadog/open_feature/configuration/source.rb +43 -0
  68. data/lib/datadog/open_feature/configuration.rb +118 -0
  69. data/lib/datadog/open_feature/native_evaluator.rb +1 -14
  70. data/lib/datadog/open_feature/remote.rb +5 -5
  71. data/lib/datadog/open_feature/transport.rb +5 -0
  72. data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
  73. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -2
  74. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
  75. data/lib/datadog/profiling/collectors/info.rb +2 -6
  76. data/lib/datadog/symbol_database/extractor.rb +84 -102
  77. data/lib/datadog/tracing/component.rb +2 -0
  78. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  79. data/lib/datadog/tracing/configuration/settings.rb +6 -0
  80. data/lib/datadog/tracing/context.rb +24 -2
  81. data/lib/datadog/tracing/context_provider.rb +9 -5
  82. data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
  83. data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
  84. data/lib/datadog/tracing/contrib/sequel/utils.rb +49 -62
  85. data/lib/datadog/tracing/metadata/ext.rb +3 -0
  86. data/lib/datadog/tracing/otel_thread_context.rb +78 -9
  87. data/lib/datadog/tracing/tracer.rb +15 -2
  88. data/lib/datadog/tracing/transport/native.rb +18 -26
  89. data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
  90. data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
  91. data/lib/datadog/tracing/transport/traces.rb +3 -23
  92. data/lib/datadog/version.rb +1 -1
  93. metadata +25 -7
  94. data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Agentless
6
+ # Result of one agentless configuration request.
7
+ class Response
8
+ attr_reader :status, :etag, :body, :error
9
+
10
+ def initialize(status: nil, etag: nil, body: nil, error: nil)
11
+ @status = status
12
+ @etag = etag
13
+ @body = body
14
+ @error = error
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "stringio"
5
+ require "timeout"
6
+ require "uri"
7
+ require "zlib"
8
+
9
+ require_relative "../../core/environment/identity"
10
+ require_relative "../../core/transport/ext"
11
+ require_relative "response"
12
+
13
+ module Datadog
14
+ module OpenFeature
15
+ module Agentless
16
+ # HTTP transport for agentless configuration delivery.
17
+ class Transport
18
+ def initialize(endpoint:, api_key:, timeout_seconds:)
19
+ @endpoint = endpoint
20
+ @api_key = endpoint.managed? ? api_key : nil
21
+ @timeout_seconds = timeout_seconds
22
+ end
23
+
24
+ def get(etag)
25
+ request = Net::HTTP::Get.new(@endpoint.uri.request_uri, headers(etag))
26
+ apply_custom_authentication(request)
27
+ response = request(request)
28
+ status = response.code.to_i
29
+
30
+ Response.new(
31
+ status: status,
32
+ etag: response["ETag"],
33
+ # Decoding another status can erase the status the poller needs to classify the response.
34
+ body: (status == 200) ? response_body(response) : nil,
35
+ )
36
+ # Net::HTTP uses several version-specific exception types. None may end
37
+ # the delivery worker; the poller classifies every transport failure.
38
+ rescue => error
39
+ Response.new(error: error)
40
+ end
41
+
42
+ private
43
+
44
+ def apply_custom_authentication(request)
45
+ return if @endpoint.managed?
46
+
47
+ uri = @endpoint.uri
48
+ username = uri.user
49
+ return unless username
50
+
51
+ request.basic_auth(
52
+ URI::RFC2396_PARSER.unescape(username),
53
+ URI::RFC2396_PARSER.unescape(uri.password.to_s),
54
+ )
55
+ end
56
+
57
+ def headers(etag)
58
+ headers = {
59
+ "Accept-Encoding" => "gzip",
60
+ "DD-Client-Library-Language" => Core::Environment::Identity.lang,
61
+ "DD-Client-Library-Version" => Core::Environment::Identity.gem_datadog_version_semver2,
62
+ Core::Transport::Ext::HTTP::HEADER_DD_INTERNAL_UNTRACED_REQUEST => "1",
63
+ }
64
+ api_key = @api_key
65
+ headers[Core::Transport::Ext::HTTP::HEADER_DD_API_KEY] = api_key if api_key
66
+ headers["If-None-Match"] = etag if etag
67
+ headers
68
+ end
69
+
70
+ def request(request)
71
+ uri = @endpoint.uri
72
+ hostname = uri.hostname
73
+ raise ArgumentError, "Feature Flags agentless endpoint must have a host" unless hostname
74
+
75
+ # Agentless delivery requires public egress, so use Ruby's standard proxy discovery.
76
+ http = Net::HTTP.new(hostname, uri.port)
77
+ http.max_retries = 0
78
+ http.use_ssl = uri.scheme == "https"
79
+ http.open_timeout = @timeout_seconds
80
+ http.read_timeout = @timeout_seconds
81
+ http.write_timeout = @timeout_seconds if http.respond_to?(:write_timeout=)
82
+
83
+ # Bounds total request time, including DNS resolution, which the per-phase timeouts do not cover.
84
+ Timeout.timeout(@timeout_seconds) do
85
+ http.start { |connection| connection.request(request) }
86
+ end
87
+ end
88
+
89
+ def response_body(response)
90
+ body = response.body.to_s
91
+ return body unless response["Content-Encoding"].to_s.strip.casecmp("gzip") == 0
92
+
93
+ reader = Zlib::GzipReader.new(StringIO.new(body))
94
+ begin
95
+ reader.read.to_s
96
+ ensure
97
+ reader.close
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "transport"
4
4
  require_relative "evaluation_engine"
5
+ require_relative "../core/utils/time"
5
6
  require_relative "exposures/buffer"
6
7
  require_relative "exposures/worker"
7
8
  require_relative "exposures/deduplicator"
@@ -16,6 +17,10 @@ module Datadog
16
17
  module OpenFeature
17
18
  # This class is the entry point for the OpenFeature component
18
19
  class Component
20
+ CONFIGURATION_READY = :ready
21
+ CONFIGURATION_TIMEOUT = :timeout
22
+ CONFIGURATION_SHUTDOWN = :shutdown
23
+
19
24
  attr_reader :engine, :flag_eval_metrics_hook, :flag_eval_evp_hook, :span_enrichment_hook
20
25
 
21
26
  def self.build(settings, agent_settings, logger:, telemetry:)
@@ -29,7 +34,7 @@ module Datadog
29
34
  return
30
35
  end
31
36
 
32
- if RUBY_ENGINE != "ruby"
37
+ unless ["ruby", "truffleruby"].include?(RUBY_ENGINE)
33
38
  message = "OpenFeature could not be enabled as MRI is required, " \
34
39
  "but running on #{RUBY_ENGINE.inspect}"
35
40
 
@@ -63,9 +68,56 @@ module Datadog
63
68
  @flag_eval_metrics_hook = create_flag_eval_metrics_hook
64
69
  @flag_eval_evp_hook = create_flag_eval_evp_hook
65
70
  @span_enrichment_hook = create_span_enrichment_hook
71
+
72
+ @configuration_mutex = Mutex.new
73
+ @reconfiguration_mutex = Mutex.new
74
+ @configuration_condition = ConditionVariable.new
75
+ @configuration_received = false
76
+ @configuration_shutdown = false
77
+ end
78
+
79
+ def reconfigure!(configuration)
80
+ @reconfiguration_mutex.synchronize do
81
+ return if configuration_shutdown?
82
+
83
+ @engine.reconfigure!(configuration)
84
+
85
+ @configuration_mutex.synchronize do
86
+ return if @configuration_shutdown
87
+
88
+ @configuration_received = !configuration.nil?
89
+ @configuration_condition.broadcast
90
+ end
91
+ end
92
+ end
93
+
94
+ def wait_for_configuration
95
+ timeout_seconds = @settings.feature_flags.initialization_timeout_ms / 1000.0
96
+ deadline = Core::Utils::Time.get_time + timeout_seconds
97
+
98
+ @configuration_mutex.synchronize do
99
+ loop do
100
+ return CONFIGURATION_SHUTDOWN if @configuration_shutdown
101
+ return CONFIGURATION_READY if @configuration_received
102
+
103
+ remaining = deadline - Core::Utils::Time.get_time
104
+ return CONFIGURATION_TIMEOUT unless remaining.positive?
105
+
106
+ @configuration_condition.wait(@configuration_mutex, remaining)
107
+ end
108
+ end
109
+ end
110
+
111
+ def configuration_received?
112
+ @configuration_mutex.synchronize { @configuration_received }
66
113
  end
67
114
 
68
115
  def shutdown!
116
+ @configuration_mutex.synchronize do
117
+ @configuration_shutdown = true
118
+ @configuration_condition.broadcast
119
+ end
120
+
69
121
  @worker.graceful_shutdown
70
122
  @flag_eval_evp_writer&.stop
71
123
  # Symmetric teardown: drop any accumulated span-enrichment state and
@@ -75,6 +127,10 @@ module Datadog
75
127
 
76
128
  private
77
129
 
130
+ def configuration_shutdown?
131
+ @configuration_mutex.synchronize { @configuration_shutdown }
132
+ end
133
+
78
134
  def create_flag_eval_metrics_hook
79
135
  return unless Hooks::FlagEvalMetricsHook.available?
80
136
 
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ module Configuration
8
+ # Validated endpoint for agentless Feature Flags configuration delivery.
9
+ class AgentlessEndpoint
10
+ DEFAULT_SITE = "datadoghq.com"
11
+ AGENTLESS_SUBDOMAIN = "ufc-server.ff-cdn"
12
+ CONFIGURATION_PATH = "/api/v2/feature-flagging/config/rules-based/server"
13
+ INVALID_SITE_CHARACTERS = /[\s\/?#@:]/
14
+ INTERNAL_WHITESPACE = /\s/
15
+
16
+ class << self
17
+ def build(site:, environment:, base_url: nil, logger: Datadog.logger)
18
+ if base_url
19
+ build_custom(base_url, logger)
20
+ else
21
+ build_managed(site, environment, logger)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def build_custom(base_url, logger)
28
+ unless base_url.ascii_only?
29
+ logger.warn("Feature Flags agentless base URL is invalid; agentless delivery is disabled")
30
+ return
31
+ end
32
+
33
+ if base_url.match?(INTERNAL_WHITESPACE)
34
+ logger.warn("Feature Flags agentless base URL contains whitespace; agentless delivery is disabled")
35
+ return
36
+ end
37
+
38
+ uri = URI.parse(base_url)
39
+ unless uri.is_a?(URI::HTTP) && uri.absolute? && uri.host
40
+ logger.warn("Feature Flags agentless base URL must be an absolute HTTP or HTTPS URL; agentless delivery is disabled")
41
+ return
42
+ end
43
+
44
+ uri.path = CONFIGURATION_PATH if uri.path.to_s.empty? || uri.path == "/"
45
+ new(uri, managed: false)
46
+ rescue URI::InvalidURIError
47
+ logger.warn("Feature Flags agentless base URL is invalid; agentless delivery is disabled")
48
+ nil
49
+ end
50
+
51
+ def build_managed(site, environment, logger)
52
+ string_site = site.to_s
53
+ unless string_site.ascii_only?
54
+ logger.warn("Feature Flags site is invalid; agentless delivery is disabled")
55
+ return
56
+ end
57
+
58
+ normalized_site = string_site.strip.downcase
59
+ normalized_site = DEFAULT_SITE if normalized_site.empty?
60
+ if normalized_site.match?(INVALID_SITE_CHARACTERS)
61
+ logger.warn("Feature Flags site is invalid; agentless delivery is disabled")
62
+ return
63
+ end
64
+
65
+ uri = URI::HTTPS.build(
66
+ host: "#{AGENTLESS_SUBDOMAIN}.#{normalized_site}",
67
+ path: CONFIGURATION_PATH,
68
+ query: environment.nil? ? nil : URI.encode_www_form(dd_env: environment),
69
+ )
70
+ new(uri, managed: true)
71
+ rescue URI::InvalidComponentError
72
+ logger.warn("Feature Flags site is invalid; agentless delivery is disabled")
73
+ nil
74
+ end
75
+ end
76
+
77
+ attr_reader :uri
78
+
79
+ def initialize(uri, managed:)
80
+ @uri = uri
81
+ @managed = managed
82
+ end
83
+
84
+ def managed?
85
+ @managed
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Configuration
6
+ module Source
7
+ class Resolution
8
+ attr_reader :source
9
+
10
+ def initialize(source, enabled:)
11
+ @source = source
12
+ @enabled = enabled
13
+ end
14
+
15
+ def enabled?
16
+ @enabled
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "source/resolution"
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ module Configuration
8
+ module Source
9
+ AGENTLESS = "agentless"
10
+ REMOTE_CONFIG = "remote_config"
11
+ OFFLINE = "offline"
12
+
13
+ class << self
14
+ def resolve(settings, logger: Datadog.logger)
15
+ feature_flags = settings.feature_flags
16
+ source = resolve_source(feature_flags, legacy: settings.open_feature, logger: logger)
17
+ enabled = feature_flags.enabled && source != OFFLINE
18
+
19
+ Resolution.new(source, enabled: enabled)
20
+ end
21
+
22
+ private
23
+
24
+ def resolve_source(settings, legacy:, logger:)
25
+ configured_source = settings.configuration_source
26
+ if !settings.using_default?(:configuration_source) && !configured_source.empty?
27
+ return configured_source if configured_source == AGENTLESS ||
28
+ configured_source == REMOTE_CONFIG ||
29
+ configured_source == OFFLINE
30
+
31
+ logger.warn("Unsupported Feature Flags configuration source; Feature Flags are disabled")
32
+ OFFLINE
33
+ elsif !legacy.using_default?(:enabled)
34
+ legacy.enabled ? REMOTE_CONFIG : OFFLINE
35
+ else
36
+ AGENTLESS
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -5,6 +5,25 @@ module Datadog
5
5
  module Configuration
6
6
  # A settings class for the OpenFeature component.
7
7
  module Settings
8
+ DEFAULT_POLL_INTERVAL_SECONDS = 30
9
+ DEFAULT_REQUEST_TIMEOUT_SECONDS = 5
10
+ DEFAULT_INITIALIZATION_TIMEOUT_MS = 30_000
11
+
12
+ MAX_REQUEST_TIMEOUT_SECONDS = 300
13
+ MAX_INITIALIZATION_TIMEOUT_MS = 2_147_483_647
14
+
15
+ def self.configure_integer_option(option, environment_variable:, default:, setting_name:)
16
+ option.type :int
17
+ option.env environment_variable
18
+ option.env_parser do |value|
19
+ Integer(value, 10)
20
+ rescue ArgumentError
21
+ Datadog.logger.warn("#{setting_name} must be an integer; using the default")
22
+ default
23
+ end
24
+ option.default default
25
+ end
26
+
8
27
  def self.extended(base)
9
28
  base = base.singleton_class unless base.is_a?(Class)
10
29
  add_settings!(base)
@@ -15,10 +34,19 @@ module Datadog
15
34
  # Steep does not update `self` for this `class_eval` block.
16
35
  # @type self: Datadog::Core::Configuration::Base::_DslContext
17
36
  settings :open_feature do
37
+ # Deprecated switch retained for existing Remote Configuration adopters.
18
38
  option :enabled do |o|
19
39
  o.type :bool
20
40
  o.env "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED"
21
41
  o.default false
42
+ o.after_set do |_, _, precedence|
43
+ next if precedence == Core::Configuration::Option::Precedence::DEFAULT
44
+
45
+ Datadog.logger.warn(
46
+ "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED is deprecated; use " \
47
+ "DD_FEATURE_FLAGS_ENABLED and DD_FEATURE_FLAGS_CONFIGURATION_SOURCE instead"
48
+ )
49
+ end
22
50
  end
23
51
 
24
52
  # Opt-in gate for APM feature-flag span enrichment. When enabled,
@@ -42,6 +70,96 @@ module Datadog
42
70
  o.default true
43
71
  end
44
72
  end
73
+
74
+ settings :feature_flags do
75
+ option :enabled do |o|
76
+ o.type :bool
77
+ o.env "DD_FEATURE_FLAGS_ENABLED"
78
+ o.default true
79
+ end
80
+
81
+ option :configuration_source do |o|
82
+ o.type :string
83
+ o.env "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE"
84
+ o.default "agentless"
85
+ o.setter do |value|
86
+ value.to_s.strip.downcase
87
+ end
88
+ end
89
+
90
+ settings :agentless do
91
+ option :base_url do |o|
92
+ o.type :string, nilable: true
93
+ o.env "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL"
94
+ o.skip_telemetry true
95
+ o.setter do |value|
96
+ next unless value
97
+
98
+ stripped_value = value.to_s.strip
99
+ stripped_value.empty? ? nil : stripped_value
100
+ end
101
+ end
102
+
103
+ option :poll_interval_seconds do |o|
104
+ Settings.configure_integer_option(
105
+ o,
106
+ environment_variable: "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS",
107
+ default: Settings::DEFAULT_POLL_INTERVAL_SECONDS,
108
+ setting_name: "Feature Flags agentless poll interval",
109
+ )
110
+ o.setter do |value|
111
+ if value.is_a?(Integer) && value > 0
112
+ value
113
+ else
114
+ Datadog.logger.warn(
115
+ "Feature Flags agentless poll interval must be positive; using the default"
116
+ )
117
+ Settings::DEFAULT_POLL_INTERVAL_SECONDS
118
+ end
119
+ end
120
+ end
121
+
122
+ option :request_timeout_seconds do |o|
123
+ Settings.configure_integer_option(
124
+ o,
125
+ environment_variable: "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS",
126
+ default: Settings::DEFAULT_REQUEST_TIMEOUT_SECONDS,
127
+ setting_name: "Feature Flags agentless request timeout",
128
+ )
129
+ o.setter do |value|
130
+ if value.is_a?(Integer) && value > 0 && value <= Settings::MAX_REQUEST_TIMEOUT_SECONDS
131
+ value
132
+ else
133
+ Datadog.logger.warn(
134
+ "Feature Flags agentless request timeout must be within (0, " \
135
+ "#{Settings::MAX_REQUEST_TIMEOUT_SECONDS}]; using the default"
136
+ )
137
+ Settings::DEFAULT_REQUEST_TIMEOUT_SECONDS
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ option :initialization_timeout_ms do |o|
144
+ Settings.configure_integer_option(
145
+ o,
146
+ environment_variable: "DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS",
147
+ default: Settings::DEFAULT_INITIALIZATION_TIMEOUT_MS,
148
+ setting_name: "Feature Flags provider initialization timeout",
149
+ )
150
+ o.setter do |value|
151
+ if value.is_a?(Integer) && value > 0 && value <= Settings::MAX_INITIALIZATION_TIMEOUT_MS
152
+ value
153
+ else
154
+ Datadog.logger.warn(
155
+ "Feature Flags provider initialization timeout must be within (0, " \
156
+ "#{Settings::MAX_INITIALIZATION_TIMEOUT_MS}]; using the default"
157
+ )
158
+ Settings::DEFAULT_INITIALIZATION_TIMEOUT_MS
159
+ end
160
+ end
161
+ end
162
+ end
45
163
  end
46
164
  end
47
165
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
3
  require_relative "../core/feature_flags"
5
4
  require_relative "ext"
6
5
  require_relative "resolution_details"
@@ -16,7 +15,7 @@ module Datadog
16
15
  # in the format expected by `libdatadog` without any modifications
17
16
  def initialize(configuration)
18
17
  @configuration = Core::FeatureFlags::Configuration.new(configuration)
19
- @observe_full_evaluation_data = parse_observe_full_evaluation_data(configuration)
18
+ @observe_full_evaluation_data = @configuration.observe_full_evaluation_data
20
19
  end
21
20
 
22
21
  attr_reader :observe_full_evaluation_data
@@ -42,18 +41,6 @@ module Datadog
42
41
 
43
42
  private
44
43
 
45
- # Parse observe_full_evaluation_data from the top level of the UFC JSON (a sibling
46
- # of `environment`). Absent, null, or wrong-typed values return false.
47
- def parse_observe_full_evaluation_data(configuration)
48
- return false unless configuration.is_a?(String) && !configuration.empty?
49
-
50
- parsed = JSON.parse(configuration)
51
- parsed.is_a?(Hash) && parsed["observeFullEvaluationData"] == true
52
- rescue
53
- # This secondary policy parse must not reject configuration accepted by the native evaluator.
54
- false
55
- end
56
-
57
44
  def build_resolution_details(result, default_value)
58
45
  ResolutionDetails.new(
59
46
  value: result.variant.nil? ? default_value : result.value,
@@ -19,11 +19,11 @@ module Datadog
19
19
  FFE_PRODUCTS
20
20
  end
21
21
 
22
- def receivers(telemetry)
22
+ def receivers(telemetry, component_provider:)
23
23
  matcher = Core::Remote::Dispatcher::Matcher::Product.new(FFE_PRODUCTS)
24
24
  receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
25
- engine = OpenFeature.engine
26
- next unless engine
25
+ component = component_provider.call
26
+ next unless component
27
27
 
28
28
  changes.each do |change|
29
29
  content = repository[change.path]
@@ -38,7 +38,7 @@ module Datadog
38
38
  when :insert, :update
39
39
  begin
40
40
  # @type var content: Core::Remote::Configuration::Content
41
- engine.reconfigure!(read_content(content))
41
+ component.reconfigure!(read_content(content))
42
42
  content.applied
43
43
  rescue EvaluationEngine::ReconfigurationError => e
44
44
  content.errored("Error applying OpenFeature configuration: #{e.class}: #{e.message}")
@@ -46,7 +46,7 @@ module Datadog
46
46
  when :delete
47
47
  # NOTE: For now, we treat deletion as clearing the configuration
48
48
  # In a multi-config scenario, we might track configs per path
49
- engine.reconfigure!(nil)
49
+ component.reconfigure!(nil)
50
50
  end
51
51
  end
52
52
  end
@@ -8,6 +8,7 @@ require_relative "../core/transport/http/api/endpoint"
8
8
  require_relative "../core/transport/http/api/instance"
9
9
  require_relative "../core/transport/parcel"
10
10
  require_relative "../core/transport/request"
11
+ require_relative "../version"
11
12
 
12
13
  module Datadog
13
14
  module OpenFeature
@@ -27,6 +28,8 @@ module Datadog
27
28
  request_env.headers["Content-Type"] = env.request.parcel.content_type
28
29
  request_env.headers[Core::EVP::SUBDOMAIN_HEADER_NAME] =
29
30
  Core::EVP::EVENT_PLATFORM_INTAKE_SUBDOMAIN
31
+ request_env.headers["DD-EVP-ORIGIN"] = "dd-trace-rb"
32
+ request_env.headers["DD-EVP-ORIGIN-VERSION"] = Datadog::VERSION::STRING
30
33
  request_env.body = env.request.parcel.data
31
34
 
32
35
  block.call(request_env)
@@ -49,6 +52,8 @@ module Datadog
49
52
  request_env.headers["Content-Type"] = env.request.parcel.content_type
50
53
  request_env.headers[Core::EVP::SUBDOMAIN_HEADER_NAME] =
51
54
  Core::EVP::EVENT_PLATFORM_INTAKE_SUBDOMAIN
55
+ request_env.headers["DD-EVP-ORIGIN"] = "dd-trace-rb"
56
+ request_env.headers["DD-EVP-ORIGIN-VERSION"] = Datadog::VERSION::STRING
52
57
  request_env.body = env.request.parcel.data
53
58
 
54
59
  block.call(request_env)
@@ -12,23 +12,24 @@ module Datadog
12
12
  def create_resource
13
13
  resource_attributes = {}
14
14
 
15
- @settings.tags&.each do |key, value| # steep:ignore
15
+ @settings.tags&.each do |key, value|
16
16
  otel_key = case key
17
17
  when "service" then "service.name"
18
- when "env" then "deployment.environment"
18
+ when "env", "deployment.environment.name", "deployment.environment" then next
19
19
  when "version" then "service.version"
20
20
  else key
21
21
  end
22
22
  resource_attributes[otel_key] = value
23
23
  end
24
24
 
25
- resource_attributes["service.name"] = @settings.service_without_fallback || resource_attributes["service.name"] || Datadog::Core::Environment::Ext::FALLBACK_SERVICE_NAME # steep:ignore
26
- resource_attributes["deployment.environment"] = @settings.env if @settings.env # steep:ignore
27
- resource_attributes["service.version"] = @settings.version if @settings.version # steep:ignore
25
+ resource_attributes["service.name"] = @settings.service_without_fallback || resource_attributes["service.name"] || Datadog::Core::Environment::Ext::FALLBACK_SERVICE_NAME
26
+ environment = @settings.env || @settings.tags["env"] || @settings.tags["deployment.environment.name"] || @settings.tags["deployment.environment"]
27
+ resource_attributes["deployment.environment.name"] = environment if environment
28
+ resource_attributes["service.version"] = @settings.version if @settings.version
28
29
 
29
- hostname = Datadog::Core::Environment::Socket.resolved_hostname(@settings) # steep:ignore
30
+ hostname = Datadog::Core::Environment::Socket.resolved_hostname(@settings)
30
31
  if hostname
31
- if hostname == @settings.hostname # steep:ignore
32
+ if hostname == @settings.hostname
32
33
  resource_attributes["host.name"] = hostname
33
34
  elsif !resource_attributes.key?("host.name")
34
35
  resource_attributes["host.name"] = hostname
@@ -41,9 +42,12 @@ module Datadog
41
42
  # Returns the signal-specific option value when explicitly set,
42
43
  # otherwise falls back to the general OTLP exporter config or computed_default.
43
44
  def config_or_exporter_fallback(signal:, option_name:, computed_default: nil)
44
- signal_settings = @settings.opentelemetry.public_send(signal) # steep:ignore
45
+ signal_settings = case signal
46
+ when :logs then @settings.opentelemetry.logs
47
+ when :metrics then @settings.opentelemetry.metrics
48
+ end
45
49
  if signal_settings.using_default?(option_name)
46
- @settings.opentelemetry.exporter.public_send(option_name) || computed_default # steep:ignore
50
+ @settings.opentelemetry.exporter.public_send(option_name) || computed_default
47
51
  else
48
52
  signal_settings.public_send(option_name)
49
53
  end
@@ -51,7 +51,7 @@ module Datadog
51
51
  # profiler overhead!
52
52
  dynamic_sampling_rate_enabled: true,
53
53
  skip_idle_samples_for_testing: false,
54
- idle_sampling_helper: IdleSamplingHelper.new(thread_context_collector: thread_context_collector)
54
+ idle_sampling_helper: IdleSamplingHelper.new
55
55
  )
56
56
  unless dynamic_sampling_rate_enabled
57
57
  Datadog.logger.warn(
@@ -98,7 +98,7 @@ module Datadog
98
98
 
99
99
  Datadog.logger.debug { "Starting thread for: #{self}" }
100
100
 
101
- @idle_sampling_helper.start
101
+ @idle_sampling_helper.start(self)
102
102
 
103
103
  @worker_thread = Thread.new do
104
104
  Thread.current.name = self.class.name