datadog 2.21.0 → 2.23.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 (205) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -2
  3. data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
  4. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  5. data/ext/datadog_profiling_native_extension/collectors_stack.c +4 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
  7. data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
  9. data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
  10. data/ext/libdatadog_api/ddsketch.c +106 -0
  11. data/ext/libdatadog_api/feature_flags.c +554 -0
  12. data/ext/libdatadog_api/feature_flags.h +5 -0
  13. data/ext/libdatadog_api/init.c +5 -0
  14. data/ext/libdatadog_api/library_config.c +34 -25
  15. data/ext/libdatadog_api/process_discovery.c +19 -13
  16. data/ext/libdatadog_extconf_helpers.rb +1 -1
  17. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  18. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
  19. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
  20. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  21. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  22. data/lib/datadog/appsec/api_security/route_extractor.rb +23 -6
  23. data/lib/datadog/appsec/api_security/sampler.rb +7 -4
  24. data/lib/datadog/appsec/assets/blocked.html +8 -0
  25. data/lib/datadog/appsec/assets/blocked.json +1 -1
  26. data/lib/datadog/appsec/assets/blocked.text +3 -1
  27. data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
  28. data/lib/datadog/appsec/assets/waf_rules/recommended.json +359 -4
  29. data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
  30. data/lib/datadog/appsec/assets.rb +1 -1
  31. data/lib/datadog/appsec/compressed_json.rb +1 -1
  32. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  33. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
  34. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +3 -2
  35. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +3 -1
  36. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +3 -1
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -4
  38. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -1
  39. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -2
  40. data/lib/datadog/appsec/contrib/rails/patcher.rb +30 -0
  41. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +3 -1
  42. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -4
  43. data/lib/datadog/appsec/event.rb +12 -14
  44. data/lib/datadog/appsec/metrics/collector.rb +19 -3
  45. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +2 -1
  46. data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -4
  47. data/lib/datadog/appsec/remote.rb +29 -13
  48. data/lib/datadog/appsec/response.rb +18 -4
  49. data/lib/datadog/appsec/security_engine/result.rb +28 -9
  50. data/lib/datadog/appsec/security_engine/runner.rb +17 -7
  51. data/lib/datadog/appsec/security_event.rb +5 -7
  52. data/lib/datadog/core/configuration/components.rb +44 -9
  53. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  54. data/lib/datadog/core/configuration/settings.rb +14 -0
  55. data/lib/datadog/core/configuration/stable_config.rb +10 -0
  56. data/lib/datadog/core/configuration/supported_configurations.rb +330 -299
  57. data/lib/datadog/core/configuration.rb +1 -1
  58. data/lib/datadog/core/ddsketch.rb +19 -0
  59. data/lib/datadog/core/environment/ext.rb +6 -0
  60. data/lib/datadog/core/environment/process.rb +79 -0
  61. data/lib/datadog/core/environment/yjit.rb +2 -1
  62. data/lib/datadog/core/feature_flags.rb +61 -0
  63. data/lib/datadog/core/pin.rb +4 -8
  64. data/lib/datadog/core/process_discovery.rb +4 -2
  65. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  66. data/lib/datadog/core/remote/component.rb +4 -6
  67. data/lib/datadog/core/remote/transport/config.rb +2 -10
  68. data/lib/datadog/core/remote/transport/http/config.rb +9 -9
  69. data/lib/datadog/core/remote/transport/http/negotiation.rb +17 -8
  70. data/lib/datadog/core/remote/transport/http.rb +2 -0
  71. data/lib/datadog/core/remote/transport/negotiation.rb +2 -18
  72. data/lib/datadog/core/remote/worker.rb +25 -37
  73. data/lib/datadog/core/tag_builder.rb +0 -4
  74. data/lib/datadog/core/tag_normalizer.rb +84 -0
  75. data/lib/datadog/core/telemetry/component.rb +18 -3
  76. data/lib/datadog/core/telemetry/emitter.rb +6 -6
  77. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  78. data/lib/datadog/core/telemetry/event/app_started.rb +52 -49
  79. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
  80. data/lib/datadog/core/telemetry/event.rb +1 -0
  81. data/lib/datadog/core/telemetry/logger.rb +2 -2
  82. data/lib/datadog/core/telemetry/logging.rb +2 -8
  83. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -6
  84. data/lib/datadog/core/telemetry/transport/telemetry.rb +1 -2
  85. data/lib/datadog/core/transport/http/client.rb +69 -0
  86. data/lib/datadog/core/transport/response.rb +4 -1
  87. data/lib/datadog/core/utils/array.rb +29 -0
  88. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
  89. data/lib/datadog/core/utils/network.rb +22 -1
  90. data/lib/datadog/core/utils/only_once_successful.rb +6 -2
  91. data/lib/datadog/core/utils.rb +2 -0
  92. data/lib/datadog/data_streams/configuration/settings.rb +49 -0
  93. data/lib/datadog/data_streams/configuration.rb +11 -0
  94. data/lib/datadog/data_streams/ext.rb +11 -0
  95. data/lib/datadog/data_streams/extensions.rb +16 -0
  96. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  97. data/lib/datadog/data_streams/processor.rb +509 -0
  98. data/lib/datadog/data_streams/transport/http/api.rb +33 -0
  99. data/lib/datadog/data_streams/transport/http/client.rb +21 -0
  100. data/lib/datadog/data_streams/transport/http/stats.rb +87 -0
  101. data/lib/datadog/data_streams/transport/http.rb +41 -0
  102. data/lib/datadog/data_streams/transport/stats.rb +60 -0
  103. data/lib/datadog/data_streams.rb +100 -0
  104. data/lib/datadog/di/boot.rb +1 -0
  105. data/lib/datadog/di/component.rb +14 -16
  106. data/lib/datadog/di/context.rb +70 -0
  107. data/lib/datadog/di/el/compiler.rb +164 -0
  108. data/lib/datadog/di/el/evaluator.rb +159 -0
  109. data/lib/datadog/di/el/expression.rb +42 -0
  110. data/lib/datadog/di/el.rb +5 -0
  111. data/lib/datadog/di/error.rb +29 -0
  112. data/lib/datadog/di/instrumenter.rb +163 -48
  113. data/lib/datadog/di/probe.rb +55 -15
  114. data/lib/datadog/di/probe_builder.rb +39 -1
  115. data/lib/datadog/di/probe_manager.rb +13 -4
  116. data/lib/datadog/di/probe_notification_builder.rb +105 -67
  117. data/lib/datadog/di/proc_responder.rb +32 -0
  118. data/lib/datadog/di/serializer.rb +151 -7
  119. data/lib/datadog/di/transport/diagnostics.rb +2 -2
  120. data/lib/datadog/di/transport/http/diagnostics.rb +2 -4
  121. data/lib/datadog/di/transport/http/input.rb +2 -4
  122. data/lib/datadog/di/transport/http.rb +6 -2
  123. data/lib/datadog/di/transport/input.rb +64 -4
  124. data/lib/datadog/open_feature/component.rb +60 -0
  125. data/lib/datadog/open_feature/configuration.rb +27 -0
  126. data/lib/datadog/open_feature/evaluation_engine.rb +69 -0
  127. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  128. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  129. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  130. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  131. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  132. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  133. data/lib/datadog/open_feature/ext.rb +14 -0
  134. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  135. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  136. data/lib/datadog/open_feature/provider.rb +141 -0
  137. data/lib/datadog/open_feature/remote.rb +74 -0
  138. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  139. data/lib/datadog/open_feature/transport.rb +72 -0
  140. data/lib/datadog/open_feature.rb +19 -0
  141. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  142. data/lib/datadog/opentelemetry/metrics.rb +110 -0
  143. data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
  144. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +38 -0
  145. data/lib/datadog/opentelemetry.rb +3 -0
  146. data/lib/datadog/profiling/collectors/code_provenance.rb +15 -6
  147. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  148. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  149. data/lib/datadog/profiling/profiler.rb +4 -0
  150. data/lib/datadog/profiling/tag_builder.rb +36 -3
  151. data/lib/datadog/profiling.rb +1 -2
  152. data/lib/datadog/single_step_instrument.rb +1 -1
  153. data/lib/datadog/tracing/component.rb +6 -17
  154. data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
  155. data/lib/datadog/tracing/configuration/ext.rb +9 -0
  156. data/lib/datadog/tracing/configuration/settings.rb +77 -3
  157. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
  158. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  159. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  160. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  161. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
  162. data/lib/datadog/tracing/contrib/component.rb +2 -2
  163. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
  164. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
  165. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
  166. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  167. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
  168. data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
  169. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +74 -44
  170. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
  171. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
  172. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
  173. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  174. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  175. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  176. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  177. data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
  178. data/lib/datadog/tracing/contrib/karafka/patcher.rb +32 -0
  179. data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
  180. data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
  181. data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
  182. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
  183. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
  184. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
  185. data/lib/datadog/tracing/contrib/status_range_matcher.rb +7 -0
  186. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  187. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  188. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  189. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  190. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  191. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +46 -0
  192. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  193. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  194. data/lib/datadog/tracing/contrib.rb +1 -0
  195. data/lib/datadog/tracing/metadata/ext.rb +9 -1
  196. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  197. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  198. data/lib/datadog/tracing/transport/traces.rb +3 -5
  199. data/lib/datadog/version.rb +2 -2
  200. data/lib/datadog.rb +2 -0
  201. metadata +92 -16
  202. data/ext/libdatadog_api/macos_development.md +0 -26
  203. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  204. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  205. data/lib/datadog/di/transport/http/client.rb +0 -47
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+ require 'open_feature/sdk'
5
+
6
+ module Datadog
7
+ module OpenFeature
8
+ # OpenFeature feature flagging provider backed by Datadog Remote Configuration.
9
+ #
10
+ # Implementation follows the OpenFeature contract of Provider SDK.
11
+ # For details see:
12
+ # - https://github.com/open-feature/ruby-sdk/blob/v0.4.1/README.md#develop-a-provider
13
+ # - https://github.com/open-feature/ruby-sdk/blob/v0.4.1/lib/open_feature/sdk/provider/no_op_provider.rb
14
+ #
15
+ # In the example below you can see how to configure the OpenFeature SDK
16
+ # https://github.com/open-feature/ruby-sdk to use the Datadog feature flags provider.
17
+ #
18
+ # Example:
19
+ #
20
+ # Make sure to enable Remote Configuration and OpenFeature in the Datadog configuration.
21
+ #
22
+ # ```ruby
23
+ # # FILE: initializers/datadog.rb
24
+ # Datadog.configure do |config|
25
+ # config.remote.enabled = true
26
+ # config.open_feature.enabled = true
27
+ # end
28
+ # ```
29
+ #
30
+ # And configure the OpenFeature SDK to use the Datadog feature flagging provider.
31
+ #
32
+ # ```ruby
33
+ # # FILE: initializers/open_feature.rb
34
+ # require 'open_feature/sdk'
35
+ # require 'datadog/open_feature/provider'
36
+ #
37
+ # OpenFeature::SDK.configure do |config|
38
+ # config.set_provider(Datadog::OpenFeature::Provider.new)
39
+ # end
40
+ # ```
41
+ #
42
+ # Now you can create OpenFeature SDK client and use it to fetch feature flag values.
43
+ #
44
+ # ```ruby
45
+ # client = OpenFeature::SDK.build_client
46
+ # context = OpenFeature::SDK::EvaluationContext.new('email' => 'john.doe@gmail.com')
47
+ #
48
+ # client.fetch_string_value(
49
+ # flag_key: 'banner', default_value: 'Greetings!', evaluation_context: context
50
+ # )
51
+ # # => 'Welcome back!'
52
+ # ```
53
+ class Provider
54
+ NAME = 'Datadog Feature Flagging Provider'
55
+
56
+ attr_reader :metadata
57
+
58
+ def initialize
59
+ @metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: NAME).freeze
60
+ end
61
+
62
+ def init
63
+ # no-op
64
+ end
65
+
66
+ def shutdown
67
+ # no-op
68
+ end
69
+
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)
72
+ end
73
+
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)
76
+ end
77
+
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)
80
+ end
81
+
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)
84
+ end
85
+
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)
88
+ end
89
+
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)
92
+ end
93
+
94
+ private
95
+
96
+ def evaluate(flag_key, default_value:, expected_type:, evaluation_context:)
97
+ engine = OpenFeature.engine
98
+ return component_not_configured_default(default_value) if engine.nil?
99
+
100
+ result = engine.fetch_value(
101
+ flag_key,
102
+ default_value: default_value,
103
+ expected_type: expected_type,
104
+ evaluation_context: evaluation_context
105
+ )
106
+
107
+ if result.error?
108
+ return ::OpenFeature::SDK::Provider::ResolutionDetails.new(
109
+ value: default_value,
110
+ error_code: result.error_code,
111
+ error_message: result.error_message,
112
+ reason: result.reason
113
+ )
114
+ end
115
+
116
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
117
+ value: result.value,
118
+ variant: result.variant,
119
+ reason: result.reason,
120
+ flag_metadata: result.flag_metadata
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
+ )
129
+ end
130
+
131
+ def component_not_configured_default(value)
132
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
133
+ value: value,
134
+ error_code: Ext::PROVIDER_FATAL,
135
+ error_message: "Datadog's OpenFeature component must be configured",
136
+ reason: Ext::ERROR
137
+ )
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/remote/dispatcher'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ # This module contains the remote configuration functionality for OpenFeature
8
+ module Remote
9
+ ReadError = Class.new(StandardError)
10
+
11
+ class << self
12
+ FFE_FLAG_CONFIGURATION_RULES = 1 << 46
13
+ FFE_PRODUCTS = ['FFE_FLAGS'].freeze
14
+ FFE_CAPABILITIES = [FFE_FLAG_CONFIGURATION_RULES].freeze
15
+
16
+ def capabilities
17
+ FFE_CAPABILITIES
18
+ end
19
+
20
+ def products
21
+ FFE_PRODUCTS
22
+ end
23
+
24
+ def receivers(telemetry)
25
+ matcher = Core::Remote::Dispatcher::Matcher::Product.new(FFE_PRODUCTS)
26
+ receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
27
+ engine = OpenFeature.engine
28
+ next unless engine
29
+
30
+ changes.each do |change|
31
+ content = repository[change.path]
32
+
33
+ unless content || change.type == :delete
34
+ next telemetry.error("OpenFeature: Remote Configuration change is not present on #{change.type}")
35
+ end
36
+
37
+ # NOTE: In the current RC implementation we immediately apply the configuration,
38
+ # but that might change if we need to apply patches instead.
39
+ case change.type
40
+ when :insert, :update
41
+ begin
42
+ # @type var content: Core::Remote::Configuration::Content
43
+ engine.reconfigure!(read_content(content))
44
+ content.applied
45
+ rescue ReadError => e
46
+ content.errored("Error reading Remote Configuration content: #{e.message}")
47
+ rescue EvaluationEngine::ReconfigurationError => e
48
+ content.errored("Error applying OpenFeature configuration: #{e.message}")
49
+ end
50
+ when :delete
51
+ # NOTE: For now, we treat deletion as clearing the configuration
52
+ # In a multi-config scenario, we might track configs per path
53
+ engine.reconfigure!(nil)
54
+ end
55
+ end
56
+ end
57
+
58
+ [receiver]
59
+ end
60
+
61
+ private
62
+
63
+ def read_content(content)
64
+ data = content.data.read
65
+ content.data.rewind
66
+
67
+ raise ReadError, 'EOF reached' if data.nil?
68
+
69
+ data
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ # This class is based on the `OpenFeature::SDK::Provider::ResolutionDetails` class
8
+ #
9
+ # See: https://github.com/open-feature/ruby-sdk/blob/v0.4.1/lib/open_feature/sdk/provider/resolution_details.rb
10
+ class ResolutionDetails < Struct.new(
11
+ :value,
12
+ :reason,
13
+ :variant,
14
+ :error_code,
15
+ :error_message,
16
+ :flag_metadata,
17
+ :allocation_key,
18
+ :extra_logging,
19
+ :log?,
20
+ :error?,
21
+ keyword_init: true
22
+ )
23
+ def self.build_error(value:, error_code:, error_message:, reason: Ext::ERROR)
24
+ new(
25
+ value: value,
26
+ error_code: error_code,
27
+ error_message: error_message,
28
+ reason: reason,
29
+ error?: true,
30
+ log?: false
31
+ ).freeze
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/transport/http'
4
+ require_relative '../core/transport/http/env'
5
+ require_relative '../core/transport/parcel'
6
+ require_relative '../core/transport/request'
7
+
8
+ module Datadog
9
+ module OpenFeature
10
+ module Transport
11
+ class EncodedParcel
12
+ include Core::Transport::Parcel
13
+
14
+ def encode_with(encoder)
15
+ encoder.encode(data)
16
+ end
17
+ end
18
+
19
+ class HTTP
20
+ class Spec < Core::Transport::HTTP::API::Spec
21
+ def initialize
22
+ @endpoint = Core::Transport::HTTP::API::Endpoint.new(
23
+ :post, '/evp_proxy/v2/api/v2/exposures'
24
+ )
25
+
26
+ super
27
+ end
28
+
29
+ def call(env, &block)
30
+ @endpoint.call(env) do |request_env|
31
+ request_env.headers['Content-Type'] = Core::Encoding::JSONEncoder.content_type
32
+ request_env.headers['X-Datadog-EVP-Subdomain'] = 'event-platform-intake'
33
+ request_env.body = env.request.parcel.encode_with(Core::Encoding::JSONEncoder)
34
+
35
+ block.call(request_env)
36
+ end
37
+ end
38
+ end
39
+
40
+ class Instance < Core::Transport::HTTP::API::Instance
41
+ def send_exposures(env)
42
+ @spec.call(env) { |request_env| call(request_env) }
43
+ end
44
+ end
45
+
46
+ def self.build(agent_settings:, logger:)
47
+ Core::Transport::HTTP.build(
48
+ api_instance_class: HTTP::Instance,
49
+ agent_settings: agent_settings,
50
+ logger: logger
51
+ ) { |t| t.api('exposures', HTTP::Spec.new) }.to_transport(self)
52
+ end
53
+
54
+ def initialize(apis, default_api, logger:)
55
+ @api = apis[default_api]
56
+ @logger = logger
57
+ end
58
+
59
+ def send_exposures(payload)
60
+ request = Core::Transport::Request.new(EncodedParcel.new(payload))
61
+ @api.send_exposures(Core::Transport::HTTP::Env.new(request))
62
+ rescue => e
63
+ message = "Internal error during request. Cause: #{e.class.name} #{e.message} " \
64
+ "Location: #{Array(e.backtrace).first}"
65
+ @logger.debug(message)
66
+
67
+ Core::Transport::InternalErrorResponse.new(e)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'core/configuration'
4
+ require_relative 'open_feature/configuration'
5
+
6
+ module Datadog
7
+ # A namespace for the OpenFeature component.
8
+ module OpenFeature
9
+ Core::Configuration::Settings.extend(Configuration::Settings)
10
+
11
+ def self.enabled?
12
+ Datadog.configuration.open_feature.enabled
13
+ end
14
+
15
+ def self.engine
16
+ Datadog.send(:components).open_feature&.engine
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/configuration/ext'
4
+
5
+ module Datadog
6
+ module OpenTelemetry
7
+ module Configuration
8
+ module Settings
9
+ def self.extended(base)
10
+ base = base.singleton_class unless base.is_a?(Class)
11
+ add_settings!(base)
12
+ end
13
+
14
+ def self.normalize_temporality_preference(env_var_name)
15
+ proc do |value|
16
+ if value && value.to_s.downcase != 'delta' && value.to_s.downcase != 'cumulative'
17
+ Datadog.logger.warn("#{env_var_name}=#{value} is not supported. Using delta instead.")
18
+ 'delta'
19
+ else
20
+ value
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.normalize_protocol(env_var_name)
26
+ proc do |value|
27
+ if value && value.to_s.downcase != 'http/protobuf'
28
+ Datadog.logger.warn("#{env_var_name}=#{value} is not supported. Using http/protobuf instead.")
29
+ end
30
+ 'http/protobuf'
31
+ end
32
+ end
33
+
34
+ def self.headers_parser(env_var_name)
35
+ lambda do |value|
36
+ return {} if value.nil? || value.empty?
37
+
38
+ headers = {}
39
+ header_items = value.split(',')
40
+ header_items.each do |key_value|
41
+ key, header_value = key_value.split('=', 2)
42
+ # If header is malformed, return an empty hash
43
+ if key.nil? || header_value.nil?
44
+ Datadog.logger.warn("#{env_var_name} has malformed header: #{key_value.inspect}")
45
+ return {}
46
+ end
47
+
48
+ key.strip!
49
+ header_value.strip!
50
+ if key.empty? || header_value.empty?
51
+ Datadog.logger.warn("#{env_var_name} has empty key or value in: #{key_value.inspect}")
52
+ return {}
53
+ end
54
+
55
+ headers[key] = header_value
56
+ end
57
+ headers
58
+ end
59
+ end
60
+
61
+ def self.add_settings!(base)
62
+ base.class_eval do
63
+ settings :opentelemetry do
64
+ settings :exporter do
65
+ option :protocol do |o|
66
+ o.type :string
67
+ o.setter(&Settings.normalize_protocol('OTEL_EXPORTER_OTLP_PROTOCOL'))
68
+ o.env 'OTEL_EXPORTER_OTLP_PROTOCOL'
69
+ o.default 'http/protobuf'
70
+ end
71
+
72
+ option :timeout_millis do |o|
73
+ o.type :int
74
+ o.env 'OTEL_EXPORTER_OTLP_TIMEOUT'
75
+ o.default 10_000
76
+ end
77
+
78
+ option :headers do |o|
79
+ o.type :hash
80
+ o.env 'OTEL_EXPORTER_OTLP_HEADERS'
81
+ o.default { {} }
82
+ o.env_parser(&Settings.headers_parser('OTEL_EXPORTER_OTLP_HEADERS'))
83
+ end
84
+
85
+ option :endpoint do |o|
86
+ o.type :string, nilable: true
87
+ o.env 'OTEL_EXPORTER_OTLP_ENDPOINT'
88
+ o.default nil
89
+ end
90
+ end
91
+
92
+ settings :metrics do
93
+ # Metrics-specific options default to nil to detect unset state.
94
+ # If a metrics-specific env var (e.g., OTEL_EXPORTER_OTLP_METRICS_TIMEOUT) is not set,
95
+ # we fall back to the general OTLP env var (e.g., OTEL_EXPORTER_OTLP_TIMEOUT) per OpenTelemetry spec.
96
+ option :enabled do |o|
97
+ o.type :bool
98
+ o.env 'DD_METRICS_OTEL_ENABLED'
99
+ o.default false
100
+ end
101
+
102
+ option :exporter do |o|
103
+ o.type :string
104
+ o.env 'OTEL_METRICS_EXPORTER'
105
+ o.default 'otlp'
106
+ end
107
+
108
+ option :export_interval_millis do |o|
109
+ o.type :int
110
+ o.env 'OTEL_METRIC_EXPORT_INTERVAL'
111
+ o.default 10_000
112
+ end
113
+
114
+ option :export_timeout_millis do |o|
115
+ o.type :int
116
+ o.env 'OTEL_METRIC_EXPORT_TIMEOUT'
117
+ o.default 7_500
118
+ end
119
+
120
+ option :temporality_preference do |o|
121
+ o.type :string
122
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'
123
+ o.default 'delta'
124
+ o.setter(&Settings.normalize_temporality_preference('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'))
125
+ end
126
+
127
+ option :endpoint do |o|
128
+ o.type :string, nilable: true
129
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'
130
+ o.default nil
131
+ end
132
+
133
+ option :headers do |o|
134
+ o.type :hash, nilable: true
135
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_HEADERS'
136
+ o.default nil
137
+ o.env_parser(&Settings.headers_parser('OTEL_EXPORTER_OTLP_METRICS_HEADERS'))
138
+ end
139
+
140
+ option :timeout_millis do |o|
141
+ o.type :int, nilable: true
142
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_TIMEOUT'
143
+ o.default nil
144
+ end
145
+
146
+ option :protocol do |o|
147
+ o.type :string, nilable: true
148
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_PROTOCOL'
149
+ o.default nil
150
+ o.setter(&Settings.normalize_protocol('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL'))
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/configuration/ext'
4
+
5
+ module Datadog
6
+ module OpenTelemetry
7
+ class Metrics
8
+ EXPORTER_NONE = 'none'
9
+
10
+ def self.initialize!(components)
11
+ new(components).configure_metrics_sdk
12
+ true
13
+ rescue => exc
14
+ components.logger.error("Failed to initialize OpenTelemetry metrics: #{exc.class}: #{exc}: #{exc.backtrace.join("\n")}")
15
+ false
16
+ end
17
+
18
+ def initialize(components)
19
+ @logger = components.logger
20
+ @settings = components.settings
21
+ @agent_host = components.agent_settings.hostname
22
+ @agent_ssl = components.agent_settings.ssl
23
+ end
24
+
25
+ def configure_metrics_sdk
26
+ provider = ::OpenTelemetry.meter_provider
27
+ provider.shutdown if provider.is_a?(::OpenTelemetry::SDK::Metrics::MeterProvider)
28
+
29
+ # The OpenTelemetry SDK defaults to cumulative temporality, but Datadog prefers delta temporality.
30
+ # Here is an example of how this config is applied: https://github.com/open-telemetry/opentelemetry-ruby/blob/1933d4c18e5f5e45c53fa9e902e58aa91e85cc38/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/sum.rb#L14
31
+ if DATADOG_ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'].nil?
32
+ ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'] = 'delta' # rubocop:disable CustomCops/EnvUsageCop
33
+ end
34
+
35
+ resource = create_resource
36
+ provider = ::OpenTelemetry::SDK::Metrics::MeterProvider.new(resource: resource)
37
+ configure_metric_reader(provider)
38
+ ::OpenTelemetry.meter_provider = provider
39
+ end
40
+
41
+ private
42
+
43
+ def create_resource
44
+ resource_attributes = {}
45
+ resource_attributes['host.name'] = Datadog::Core::Environment::Socket.hostname if @settings.tracing.report_hostname
46
+
47
+ @settings.tags&.each do |key, value|
48
+ otel_key = case key
49
+ when 'service' then 'service.name'
50
+ when 'env' then 'deployment.environment'
51
+ when 'version' then 'service.version'
52
+ else key
53
+ end
54
+ resource_attributes[otel_key] = value
55
+ end
56
+
57
+ resource_attributes['service.name'] = @settings.service_without_fallback || resource_attributes['service.name'] || Datadog::Core::Environment::Ext::FALLBACK_SERVICE_NAME
58
+ resource_attributes['deployment.environment'] = @settings.env if @settings.env
59
+ resource_attributes['service.version'] = @settings.version if @settings.version
60
+
61
+ ::OpenTelemetry::SDK::Resources::Resource.create(resource_attributes)
62
+ end
63
+
64
+ def configure_metric_reader(provider)
65
+ exporter_name = @settings.opentelemetry.metrics.exporter
66
+ return if exporter_name == EXPORTER_NONE
67
+
68
+ configure_otlp_exporter(provider)
69
+ rescue => e
70
+ @logger.warn("Failed to configure OTLP metrics exporter: #{e.class}: #{e}")
71
+ end
72
+
73
+ def resolve_metrics_endpoint
74
+ metrics_config = @settings.opentelemetry.metrics
75
+ exporter_config = @settings.opentelemetry.exporter
76
+
77
+ return metrics_config.endpoint if metrics_config.endpoint
78
+ return exporter_config.endpoint if exporter_config.endpoint
79
+ "#{@agent_ssl ? "https" : "http"}://#{@agent_host}:4318/v1/metrics"
80
+ end
81
+
82
+ def configure_otlp_exporter(provider)
83
+ require 'opentelemetry/exporter/otlp_metrics'
84
+ require_relative 'sdk/metrics_exporter'
85
+
86
+ metrics_config = @settings.opentelemetry.metrics
87
+ exporter_config = @settings.opentelemetry.exporter
88
+ timeout = metrics_config.timeout_millis || exporter_config.timeout_millis
89
+ headers = metrics_config.headers || exporter_config.headers || {}
90
+
91
+ protocol = metrics_config.protocol || exporter_config.protocol
92
+ exporter = Datadog::OpenTelemetry::SDK::MetricsExporter.new(
93
+ endpoint: resolve_metrics_endpoint,
94
+ timeout: timeout / 1000.0,
95
+ headers: headers,
96
+ protocol: protocol
97
+ )
98
+
99
+ reader = ::OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(
100
+ exporter: exporter,
101
+ export_interval_millis: metrics_config.export_interval_millis,
102
+ export_timeout_millis: metrics_config.export_timeout_millis
103
+ )
104
+ provider.add_metric_reader(reader)
105
+ rescue LoadError => e
106
+ @logger.warn("Could not load OTLP metrics exporter: #{e.class}: #{e}")
107
+ end
108
+ end
109
+ end
110
+ end
@@ -30,7 +30,31 @@ module Datadog
30
30
  [SpanProcessor.new]
31
31
  end
32
32
 
33
- ::OpenTelemetry::SDK::Configurator.prepend(self)
33
+ def metrics_configuration_hook
34
+ components = Datadog.send(:components)
35
+ return super unless components.settings.opentelemetry.metrics.enabled
36
+
37
+ begin
38
+ require 'opentelemetry-metrics-sdk'
39
+ rescue LoadError => exc
40
+ components.logger.warn("Failed to load OpenTelemetry metrics gems: #{exc.class}: #{exc}")
41
+ return super
42
+ end
43
+
44
+ success = Datadog::OpenTelemetry::Metrics.initialize!(components)
45
+ super unless success
46
+ end
47
+
48
+ # Prepend to ConfiguratorPatch (not Configurator) so our hook runs first.
49
+ begin
50
+ require 'opentelemetry-metrics-sdk' if defined?(OpenTelemetry::SDK) && !defined?(OpenTelemetry::SDK::Metrics::ConfiguratorPatch)
51
+ rescue LoadError
52
+ end
53
+
54
+ if defined?(::OpenTelemetry::SDK::Metrics::ConfiguratorPatch)
55
+ ::OpenTelemetry::SDK::Metrics::ConfiguratorPatch.prepend(self) unless ::OpenTelemetry::SDK::Metrics::ConfiguratorPatch.ancestors.include?(self)
56
+ end
57
+ ::OpenTelemetry::SDK::Configurator.prepend(self) unless ::OpenTelemetry::SDK::Configurator.ancestors.include?(self)
34
58
  end
35
59
  end
36
60
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'opentelemetry/exporter/otlp_metrics'
4
+
5
+ module Datadog
6
+ module OpenTelemetry
7
+ module SDK
8
+ class MetricsExporter < ::OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter
9
+ METRIC_EXPORT_ATTEMPTS = 'otel.metrics_export_attempts'
10
+ METRIC_EXPORT_SUCCESSES = 'otel.metrics_export_successes'
11
+ METRIC_EXPORT_FAILURES = 'otel.metrics_export_failures'
12
+
13
+ def initialize(endpoint:, timeout:, headers:, protocol:)
14
+ super(endpoint: endpoint, timeout: timeout, headers: headers)
15
+ @telemetry_tags = {'protocol' => protocol, 'encoding' => 'protobuf'}
16
+ end
17
+
18
+ def export(metrics, timeout: nil)
19
+ telemetry&.inc('tracers', METRIC_EXPORT_ATTEMPTS, 1, tags: @telemetry_tags)
20
+ result = super
21
+ metric_name = (result == 0) ? METRIC_EXPORT_SUCCESSES : METRIC_EXPORT_FAILURES
22
+ telemetry&.inc('tracers', metric_name, 1, tags: @telemetry_tags)
23
+ result
24
+ rescue => e
25
+ Datadog.logger.error("Failed to export OpenTelemetry Metrics: #{e.class}: #{e}")
26
+ telemetry&.inc('tracers', METRIC_EXPORT_FAILURES, 1, tags: @telemetry_tags)
27
+ raise
28
+ end
29
+
30
+ private
31
+
32
+ def telemetry
33
+ Datadog.send(:components).telemetry
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end