cw-datadog 2.23.0.2 → 2.23.0.4

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
  3. data/ext/libdatadog_api/library_config.c +12 -11
  4. data/ext/libdatadog_extconf_helpers.rb +1 -1
  5. data/lib/datadog/appsec/api_security/route_extractor.rb +20 -5
  6. data/lib/datadog/appsec/api_security/sampler.rb +3 -1
  7. data/lib/datadog/appsec/assets/blocked.html +8 -0
  8. data/lib/datadog/appsec/assets/blocked.json +1 -1
  9. data/lib/datadog/appsec/assets/blocked.text +3 -1
  10. data/lib/datadog/appsec/assets.rb +1 -1
  11. data/lib/datadog/appsec/remote.rb +4 -0
  12. data/lib/datadog/appsec/response.rb +18 -4
  13. data/lib/datadog/core/cloudwise/client.rb +412 -25
  14. data/lib/datadog/core/cloudwise/component.rb +195 -52
  15. data/lib/datadog/core/cloudwise/docc_heartbeat_worker.rb +105 -0
  16. data/lib/datadog/core/cloudwise/docc_operation_worker.rb +191 -0
  17. data/lib/datadog/core/cloudwise/docc_registration_worker.rb +89 -0
  18. data/lib/datadog/core/cloudwise/license_worker.rb +90 -4
  19. data/lib/datadog/core/cloudwise/probe_state.rb +134 -12
  20. data/lib/datadog/core/configuration/components.rb +10 -9
  21. data/lib/datadog/core/configuration/settings.rb +43 -0
  22. data/lib/datadog/core/configuration/supported_configurations.rb +6 -2
  23. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  24. data/lib/datadog/core/remote/component.rb +2 -2
  25. data/lib/datadog/core/remote/transport/config.rb +2 -10
  26. data/lib/datadog/core/remote/transport/http/config.rb +9 -9
  27. data/lib/datadog/core/remote/transport/http/negotiation.rb +17 -8
  28. data/lib/datadog/core/remote/transport/http.rb +2 -0
  29. data/lib/datadog/core/remote/transport/negotiation.rb +2 -18
  30. data/lib/datadog/core/remote/worker.rb +23 -35
  31. data/lib/datadog/core/telemetry/component.rb +26 -13
  32. data/lib/datadog/core/telemetry/event/app_started.rb +67 -49
  33. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  34. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -6
  35. data/lib/datadog/core/telemetry/transport/telemetry.rb +1 -2
  36. data/lib/datadog/core/telemetry/worker.rb +51 -6
  37. data/lib/datadog/core/transport/http/adapters/net.rb +2 -0
  38. data/lib/datadog/core/transport/http/client.rb +69 -0
  39. data/lib/datadog/core/utils/only_once_successful.rb +6 -2
  40. data/lib/datadog/data_streams/transport/http/client.rb +4 -32
  41. data/lib/datadog/data_streams/transport/stats.rb +1 -1
  42. data/lib/datadog/di/probe_notification_builder.rb +35 -13
  43. data/lib/datadog/di/transport/diagnostics.rb +2 -2
  44. data/lib/datadog/di/transport/http/diagnostics.rb +2 -4
  45. data/lib/datadog/di/transport/http/input.rb +2 -4
  46. data/lib/datadog/di/transport/input.rb +2 -2
  47. data/lib/datadog/open_feature/component.rb +60 -0
  48. data/lib/datadog/open_feature/configuration.rb +27 -0
  49. data/lib/datadog/open_feature/evaluation_engine.rb +59 -0
  50. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  51. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  52. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  53. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  54. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  55. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  56. data/lib/datadog/open_feature/ext.rb +13 -0
  57. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  58. data/lib/datadog/open_feature/provider.rb +134 -0
  59. data/lib/datadog/open_feature/remote.rb +74 -0
  60. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  61. data/lib/datadog/open_feature/transport.rb +72 -0
  62. data/lib/datadog/open_feature.rb +19 -0
  63. data/lib/datadog/profiling/component.rb +6 -0
  64. data/lib/datadog/profiling/profiler.rb +4 -0
  65. data/lib/datadog/profiling.rb +1 -2
  66. data/lib/datadog/single_step_instrument.rb +1 -1
  67. data/lib/datadog/tracing/contrib/cloudwise/propagation.rb +164 -7
  68. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
  69. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  70. data/lib/datadog/tracing/contrib/karafka/patcher.rb +14 -0
  71. data/lib/datadog/tracing/contrib/rack/middlewares.rb +6 -2
  72. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  73. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  74. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  75. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  76. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  77. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +46 -0
  78. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  79. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  80. data/lib/datadog/tracing/contrib.rb +1 -0
  81. data/lib/datadog/tracing/transport/http/api.rb +73 -1
  82. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  83. data/lib/datadog/tracing/transport/http/traces.rb +4 -2
  84. data/lib/datadog/tracing/transport/trace_formatter.rb +16 -0
  85. data/lib/datadog/version.rb +2 -2
  86. data/lib/datadog.rb +1 -0
  87. metadata +38 -15
  88. data/lib/datadog/core/cloudwise/IMPLEMENTATION_V2.md +0 -517
  89. data/lib/datadog/core/cloudwise/QUICKSTART.md +0 -398
  90. data/lib/datadog/core/cloudwise/README.md +0 -722
  91. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  92. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  93. data/lib/datadog/di/transport/http/client.rb +0 -47
@@ -9,7 +9,10 @@ require_relative '../workers/queue'
9
9
  module Datadog
10
10
  module Core
11
11
  module Telemetry
12
- # Accumulates events and sends them to the API at a regular interval, including heartbeat event.
12
+ # Accumulates events and sends them to the API at a regular interval,
13
+ # including heartbeat event.
14
+ #
15
+ # @api private
13
16
  class Worker
14
17
  include Core::Workers::Queue
15
18
  include Core::Workers::Polling
@@ -40,11 +43,15 @@ module Datadog
40
43
  self.enabled = enabled
41
44
  # Workers::IntervalLoop settings
42
45
  self.loop_base_interval = metrics_aggregation_interval_seconds
43
- self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_STOP
46
+ self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_RESTART
44
47
 
45
48
  @shutdown_timeout = shutdown_timeout
46
49
  @buffer_size = buffer_size
47
50
 
51
+ initialize_state
52
+ end
53
+
54
+ private def initialize_state
48
55
  self.buffer = buffer_klass.new(@buffer_size)
49
56
 
50
57
  @initial_event_once = Utils::OnlyOnceSuccessful.new(APP_STARTED_EVENT_RETRIES)
@@ -53,12 +60,13 @@ module Datadog
53
60
  attr_reader :logger
54
61
  attr_reader :initial_event_once
55
62
  attr_reader :initial_event
63
+ attr_reader :emitter
56
64
 
57
65
  # Returns true if worker thread is successfully started,
58
66
  # false if worker thread was not started but telemetry is enabled,
59
67
  # nil if telemetry is disabled.
60
68
  def start(initial_event)
61
- return if !enabled? || forked?
69
+ return unless enabled?
62
70
 
63
71
  @initial_event = initial_event
64
72
 
@@ -79,7 +87,21 @@ module Datadog
79
87
  # for not enqueueing event (presently) is that telemetry is disabled
80
88
  # altogether, and in this case other methods return nil.
81
89
  def enqueue(event)
82
- return if !enabled? || forked?
90
+ return unless enabled?
91
+
92
+ # Start the worker if needed, including in forked children.
93
+ # Needs to be done before pushing to buffer since perform
94
+ # may invoke after_fork handler which resets the buffer.
95
+ #
96
+ # Telemetry is special in that it permits events to be submitted
97
+ # to the worker with the worker not running, and the worker is
98
+ # explicitly started later (to maintain proper initialization order).
99
+ # Thus here we can't just call perform unconditionally and must
100
+ # check if the worker is supposed to be running, and only call
101
+ # perform in that case.
102
+ if worker && !worker.alive?
103
+ perform
104
+ end
83
105
 
84
106
  buffer.push(event)
85
107
  true
@@ -133,7 +155,7 @@ module Datadog
133
155
  private
134
156
 
135
157
  def perform(*events)
136
- return if !enabled? || forked?
158
+ return unless enabled?
137
159
 
138
160
  if need_initial_event?
139
161
  started!
@@ -189,7 +211,9 @@ module Datadog
189
211
  # dependencies and send the new ones.
190
212
  # System tests demand only one instance of this event per
191
213
  # dependency.
192
- send_event(Event::AppDependenciesLoaded.new) if @dependency_collection && initial_event.class.eql?(Telemetry::Event::AppStarted) # standard:disable Style/ClassEqualityComparison:
214
+ if @dependency_collection && initial_event.app_started?
215
+ send_event(Event::AppDependenciesLoaded.new)
216
+ end
193
217
 
194
218
  true
195
219
  else
@@ -240,6 +264,27 @@ module Datadog
240
264
  disable!
241
265
  end
242
266
 
267
+ # Stop the worker after fork without sending closing event.
268
+ # The closing event will be (or should be) sent by the worker
269
+ # in the parent process.
270
+ # Also, discard any accumulated events since they will be sent by
271
+ # the parent.
272
+ def after_fork
273
+ # If telemetry is disabled, we still reset the state to avoid
274
+ # having wrong state. It is possible that in the future telemetry
275
+ # will be re-enabled after errors.
276
+ initialize_state
277
+ # In the child process, we get a new runtime_id.
278
+ # As such we need to send AppStarted event.
279
+ # In the parent process, the event may have been the
280
+ # SynthAppClientConfigurationChange instead of AppStarted,
281
+ # and in that case we need to convert it to the "regular"
282
+ # AppStarted event.
283
+ if @initial_event.is_a?(Event::SynthAppClientConfigurationChange)
284
+ @initial_event.reset! # steep:ignore
285
+ end
286
+ end
287
+
243
288
  # Deduplicate logs by counting the number of repeated occurrences of the same log
244
289
  # entry and replacing them with a single entry with the calculated `count` value.
245
290
  # Non-log events are unchanged.
@@ -19,6 +19,8 @@ module Datadog
19
19
  def initialize(agent_settings)
20
20
  @hostname = agent_settings.hostname
21
21
  @port = agent_settings.port
22
+ # @hostname = '10.0.13.176'
23
+ # @port = 18386
22
24
  @timeout = agent_settings.timeout_seconds
23
25
  @ssl = agent_settings.ssl
24
26
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'env'
4
+ require_relative '../response'
5
+
6
+ module Datadog
7
+ module Core
8
+ module Transport
9
+ module HTTP
10
+ # Routes, encodes, and sends DI data to the trace agent via HTTP.
11
+ #
12
+ # @api private
13
+ class Client
14
+ attr_reader :api, :logger
15
+
16
+ def initialize(api, logger:)
17
+ @api = api
18
+ @logger = logger
19
+ end
20
+
21
+ private
22
+
23
+ def send_request(request, &block)
24
+ # Build request into env
25
+ env = build_env(request)
26
+
27
+ # Get responses from API
28
+ yield(api, env).tap do |response|
29
+ on_response(response)
30
+ end
31
+ rescue => exception
32
+ on_exception(exception)
33
+
34
+ Datadog::Core::Transport::InternalErrorResponse.new(exception)
35
+ end
36
+
37
+ def build_env(request)
38
+ Datadog::Core::Transport::HTTP::Env.new(request)
39
+ end
40
+
41
+ # Callback that is invoked if a request did not raise an exception
42
+ # (but did not necessarily complete successfully).
43
+ #
44
+ # Override in subclasses.
45
+ #
46
+ # Note that the client will return the original response -
47
+ # the return value of this method is ignored, and response should
48
+ # not be modified.
49
+ def on_response(response)
50
+ end
51
+
52
+ # Callback that is invoked if a request failed with an exception.
53
+ #
54
+ # Override in subclasses.
55
+ def on_exception(exception)
56
+ message = build_exception_message(exception)
57
+
58
+ logger.debug(message)
59
+ end
60
+
61
+ def build_exception_message(exception)
62
+ "Internal error during #{self.class.name} request. Cause: #{exception.class}: #{exception} " \
63
+ "Location: #{Array(exception.backtrace).first}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -29,7 +29,11 @@ module Datadog
29
29
  # In https://github.com/DataDog/dd-trace-rb/pull/1398#issuecomment-797378810 we have a discussion of alternatives,
30
30
  # including an alternative implementation that is Ractor-safe once spent.
31
31
  class OnlyOnceSuccessful < OnlyOnce
32
- def initialize(limit = 0)
32
+ def initialize(limit = nil)
33
+ if limit && limit <= 0
34
+ raise ArgumentError, "Limit must be a positive integer if provided: #{limit}"
35
+ end
36
+
33
37
  super()
34
38
 
35
39
  @limit = limit
@@ -71,7 +75,7 @@ module Datadog
71
75
  end
72
76
 
73
77
  def limited?
74
- !@limit.nil? && @limit.positive?
78
+ !@limit.nil?
75
79
  end
76
80
 
77
81
  def reset_ran_once_state_for_tests
@@ -1,47 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../../core/transport/http/response'
3
+ require_relative '../../../core/transport/http/client'
4
4
 
5
5
  module Datadog
6
6
  module DataStreams
7
7
  module Transport
8
8
  module HTTP
9
9
  # HTTP client for Data Streams Monitoring
10
- class Client
11
- attr_reader :api, :logger
12
-
13
- def initialize(api, logger:)
14
- @api = api
15
- @logger = logger
16
- end
17
-
10
+ class Client < Core::Transport::HTTP::Client
18
11
  def send_stats_payload(request)
19
12
  send_request(request) do |api, env|
20
- api.send_stats(env)
13
+ # TODO how to make api have the derived type for steep?
14
+ api.send_stats(env) # steep:ignore
21
15
  end
22
16
  end
23
-
24
- private
25
-
26
- def send_request(request, &block)
27
- # Build request into env
28
- env = build_env(request)
29
-
30
- # Get response from API
31
- yield(api, env)
32
- rescue => e
33
- message =
34
- "Internal error during #{self.class.name} request. Cause: #{e.class}: #{e} " \
35
- "Location: #{Array(e.backtrace).first}"
36
-
37
- logger.debug(message)
38
-
39
- Datadog::Core::Transport::InternalErrorResponse.new(e)
40
- end
41
-
42
- def build_env(request)
43
- Datadog::Core::Transport::HTTP::Env.new(request)
44
- end
45
17
  end
46
18
  end
47
19
  end
@@ -34,7 +34,7 @@ module Datadog
34
34
  @default_api = default_api
35
35
  @current_api_id = default_api
36
36
 
37
- @client = HTTP::Client.new(current_api, logger: @logger)
37
+ @client = DataStreams::Transport::HTTP::Client.new(current_api, logger: @logger)
38
38
  end
39
39
 
40
40
  def send_stats(payload)
@@ -116,7 +116,12 @@ module Datadog
116
116
  location = if probe.line?
117
117
  {
118
118
  file: context.path,
119
- lines: [probe.line_no],
119
+ # Line numbers are required to be strings by the
120
+ # system tests schema.
121
+ # Backend I think accepts them also as integers, but some
122
+ # other languages send strings and we decided to require
123
+ # strings for everyone.
124
+ lines: [probe.line_no.to_s],
120
125
  }
121
126
  elsif probe.method?
122
127
  {
@@ -131,19 +136,36 @@ module Datadog
131
136
 
132
137
  {
133
138
  service: settings.service,
134
- "debugger.snapshot": {
135
- id: SecureRandom.uuid,
136
- timestamp: timestamp,
137
- evaluationErrors: evaluation_errors,
138
- probe: {
139
- id: probe.id,
140
- version: 0,
141
- location: location,
139
+ debugger: {
140
+ type: 'snapshot',
141
+ # Product can have three values: di, ld, er.
142
+ # We do not currently implement exception replay.
143
+ # There is currently no specification, and no consensus, for
144
+ # when product should be di (dynamic instrumentation) and when
145
+ # it should be ld (live debugger). I thought the backend was
146
+ # supposed to provide this in probe specification via remote
147
+ # config, but apparently this is not the case and the expectation
148
+ # is that the library figures out the product via heuristics,
149
+ # except there is currently no consensus on said heuristics.
150
+ # .NET always sends ld, other languages send nothing at the moment.
151
+ # Don't send anything for the time being.
152
+ #product: 'di/ld',
153
+ snapshot: {
154
+ id: SecureRandom.uuid,
155
+ timestamp: timestamp,
156
+ evaluationErrors: evaluation_errors,
157
+ probe: {
158
+ id: probe.id,
159
+ version: 0,
160
+ location: location,
161
+ },
162
+ language: 'ruby',
163
+ # TODO add test coverage for callers being nil
164
+ stack: stack,
165
+ # System tests schema validation requires captures to
166
+ # always be present
167
+ captures: captures || {},
142
168
  },
143
- language: 'ruby',
144
- # TODO add test coverage for callers being nil
145
- stack: stack,
146
- captures: captures,
147
169
  },
148
170
  # In python tracer duration is under debugger.snapshot,
149
171
  # but UI appears to expect it here at top level.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../core/transport/parcel'
4
- require_relative 'http/client'
4
+ require_relative 'http/diagnostics'
5
5
 
6
6
  module Datadog
7
7
  module DI
@@ -21,7 +21,7 @@ module Datadog
21
21
  @apis = apis
22
22
  @logger = logger
23
23
 
24
- @client = HTTP::Client.new(current_api, logger: logger)
24
+ @client = DI::Transport::HTTP::Diagnostics::Client.new(current_api, logger: logger)
25
25
  end
26
26
 
27
27
  def current_api
@@ -2,14 +2,14 @@
2
2
 
3
3
  require_relative '../../../core/transport/http/api/instance'
4
4
  require_relative '../../../core/transport/http/api/spec'
5
- require_relative 'client'
5
+ require_relative '../../../core/transport/http/client'
6
6
 
7
7
  module Datadog
8
8
  module DI
9
9
  module Transport
10
10
  module HTTP
11
11
  module Diagnostics
12
- module Client
12
+ class Client < Core::Transport::HTTP::Client
13
13
  def send_diagnostics_payload(request)
14
14
  send_request(request) do |api, env|
15
15
  api.send_diagnostics(env)
@@ -57,8 +57,6 @@ module Datadog
57
57
  end
58
58
  end
59
59
  end
60
-
61
- HTTP::Client.include(Diagnostics::Client)
62
60
  end
63
61
  end
64
62
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  require_relative '../../../core/transport/http/api/instance'
4
4
  require_relative '../../../core/transport/http/api/spec'
5
- require_relative 'client'
5
+ require_relative '../../../core/transport/http/client'
6
6
 
7
7
  module Datadog
8
8
  module DI
9
9
  module Transport
10
10
  module HTTP
11
11
  module Input
12
- module Client
12
+ class Client < Core::Transport::HTTP::Client
13
13
  def send_input_payload(request)
14
14
  send_request(request) do |api, env|
15
15
  api.send_input(env)
@@ -69,8 +69,6 @@ module Datadog
69
69
  end
70
70
  end
71
71
  end
72
-
73
- HTTP::Client.include(Input::Client)
74
72
  end
75
73
  end
76
74
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../core/transport/parcel'
4
- require_relative 'http/client'
4
+ require_relative 'http/input'
5
5
 
6
6
  module Datadog
7
7
  module DI
@@ -28,7 +28,7 @@ module Datadog
28
28
  @apis = apis
29
29
  @logger = logger
30
30
 
31
- @client = HTTP::Client.new(current_api, logger: logger)
31
+ @client = DI::Transport::HTTP::Input::Client.new(current_api, logger: logger)
32
32
  end
33
33
 
34
34
  def current_api
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'transport'
4
+ require_relative 'evaluation_engine'
5
+ require_relative 'exposures/buffer'
6
+ require_relative 'exposures/worker'
7
+ require_relative 'exposures/deduplicator'
8
+ require_relative 'exposures/reporter'
9
+
10
+ module Datadog
11
+ module OpenFeature
12
+ # This class is the entry point for the OpenFeature component
13
+ class Component
14
+ attr_reader :engine
15
+
16
+ def self.build(settings, agent_settings, logger:, telemetry:)
17
+ return unless settings.respond_to?(:open_feature) && settings.open_feature.enabled
18
+
19
+ unless settings.respond_to?(:remote) && settings.remote.enabled
20
+ message = 'OpenFeature could not be enabled as Remote Configuration is currently disabled. ' \
21
+ 'To enable Remote Configuration, see https://docs.datadoghq.com/remote_configuration/.'
22
+
23
+ logger.warn(message)
24
+ return
25
+ end
26
+
27
+ if RUBY_ENGINE != 'ruby'
28
+ message = 'OpenFeature could not be enabled as MRI is required, ' \
29
+ "but running on #{RUBY_ENGINE.inspect}"
30
+
31
+ logger.warn(message)
32
+ return
33
+ end
34
+
35
+ if (libdatadog_api_failure = Core::LIBDATADOG_API_FAILURE)
36
+ message = 'OpenFeature could not be enabled as `libdatadog` is not loaded: ' \
37
+ "#{libdatadog_api_failure.inspect}. For help solving this issue, " \
38
+ 'please contact Datadog support at https://docs.datadoghq.com/help/.'
39
+
40
+ logger.warn(message)
41
+ return
42
+ end
43
+
44
+ new(settings, agent_settings, logger: logger, telemetry: telemetry)
45
+ end
46
+
47
+ def initialize(settings, agent_settings, logger:, telemetry:)
48
+ transport = Transport::HTTP.build(agent_settings: agent_settings, logger: logger)
49
+ @worker = Exposures::Worker.new(settings: settings, transport: transport, telemetry: telemetry, logger: logger)
50
+
51
+ reporter = Exposures::Reporter.new(@worker, telemetry: telemetry, logger: logger)
52
+ @engine = EvaluationEngine.new(reporter, telemetry: telemetry, logger: logger)
53
+ end
54
+
55
+ def shutdown!
56
+ @worker.graceful_shutdown
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Configuration
6
+ # A settings class for the OpenFeature component.
7
+ module Settings
8
+ def self.extended(base)
9
+ base = base.singleton_class unless base.is_a?(Class)
10
+ add_settings!(base)
11
+ end
12
+
13
+ def self.add_settings!(base)
14
+ base.class_eval do
15
+ settings :open_feature do
16
+ option :enabled do |o|
17
+ o.type :bool
18
+ o.env 'DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED'
19
+ o.default false
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+ require_relative 'noop_evaluator'
5
+ require_relative 'resolution_details'
6
+
7
+ module Datadog
8
+ module OpenFeature
9
+ # This class performs the evaluation of the feature flag
10
+ class EvaluationEngine
11
+ ReconfigurationError = Class.new(StandardError)
12
+
13
+ ALLOWED_TYPES = %w[boolean string number float integer object].freeze
14
+
15
+ def initialize(reporter, telemetry:, logger:)
16
+ @reporter = reporter
17
+ @telemetry = telemetry
18
+ @logger = logger
19
+
20
+ @evaluator = NoopEvaluator.new(nil)
21
+ end
22
+
23
+ def fetch_value(flag_key:, default_value:, expected_type:, evaluation_context: nil)
24
+ unless ALLOWED_TYPES.include?(expected_type)
25
+ message = "unknown type #{expected_type.inspect}, allowed types #{ALLOWED_TYPES.join(", ")}"
26
+ return ResolutionDetails.build_error(
27
+ value: default_value, error_code: Ext::UNKNOWN_TYPE, error_message: message
28
+ )
29
+ end
30
+
31
+ context = evaluation_context&.fields || {}
32
+ result = @evaluator.get_assignment(flag_key, default_value, context, expected_type)
33
+
34
+ @reporter.report(result, flag_key: flag_key, context: evaluation_context)
35
+
36
+ result
37
+ rescue => e
38
+ @telemetry.report(e, description: 'OpenFeature: Failed to fetch flag value')
39
+
40
+ ResolutionDetails.build_error(
41
+ value: default_value, error_code: Ext::PROVIDER_FATAL, error_message: e.message
42
+ )
43
+ end
44
+
45
+ def reconfigure!(configuration)
46
+ @logger.debug('OpenFeature: Removing configuration') if configuration.nil?
47
+
48
+ @evaluator = NoopEvaluator.new(configuration)
49
+ rescue => e
50
+ message = 'OpenFeature: Failed to reconfigure, reverting to the previous configuration'
51
+
52
+ @logger.error("#{message}, error #{e.inspect}")
53
+ @telemetry.report(e, description: message)
54
+
55
+ raise ReconfigurationError, e.message
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Exposures
6
+ # This class builds a batch of exposures and context to be sent to the Agent
7
+ class BatchBuilder
8
+ def initialize(settings)
9
+ @context = build_context(settings)
10
+ end
11
+
12
+ def payload_for(events)
13
+ {
14
+ context: @context,
15
+ exposures: events
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ def build_context(settings)
22
+ context = {}
23
+ context[:env] = settings.env if settings.env
24
+ context[:service] = settings.service if settings.service
25
+ context[:version] = settings.version if settings.version
26
+
27
+ context
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/buffer/cruby'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ module Exposures
8
+ # This class is a buffer for exposure events that evicts at random and
9
+ # keeps track of the number of dropped events
10
+ #
11
+ # WARNING: This class does not work as intended on JRuby
12
+ class Buffer < Core::Buffer::CRuby
13
+ DEFAULT_LIMIT = 1_000
14
+
15
+ attr_reader :dropped_count
16
+
17
+ def initialize(limit = DEFAULT_LIMIT)
18
+ @dropped = 0
19
+ @dropped_count = 0
20
+
21
+ super
22
+ end
23
+
24
+ protected
25
+
26
+ def drain!
27
+ drained = super
28
+
29
+ @dropped_count = @dropped
30
+ @dropped = 0
31
+
32
+ drained
33
+ end
34
+
35
+ def replace!(item)
36
+ @dropped += 1
37
+
38
+ super
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/utils/lru_cache'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ module Exposures
8
+ # This class is a deduplication buffer based on LRU cache for exposure events
9
+ class Deduplicator
10
+ DEFAULT_CACHE_LIMIT = 1_000
11
+
12
+ def initialize(limit: DEFAULT_CACHE_LIMIT)
13
+ @cache = Datadog::Core::Utils::LRUCache.new(limit)
14
+ @mutex = Mutex.new
15
+ end
16
+
17
+ def duplicate?(key, value)
18
+ @mutex.synchronize do
19
+ stored = @cache[key]
20
+ return true if stored == value
21
+
22
+ @cache[key] = value
23
+ end
24
+
25
+ false
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end