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.
- checksums.yaml +4 -4
- data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
- data/ext/libdatadog_api/library_config.c +12 -11
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/api_security/route_extractor.rb +20 -5
- data/lib/datadog/appsec/api_security/sampler.rb +3 -1
- data/lib/datadog/appsec/assets/blocked.html +8 -0
- data/lib/datadog/appsec/assets/blocked.json +1 -1
- data/lib/datadog/appsec/assets/blocked.text +3 -1
- data/lib/datadog/appsec/assets.rb +1 -1
- data/lib/datadog/appsec/remote.rb +4 -0
- data/lib/datadog/appsec/response.rb +18 -4
- data/lib/datadog/core/cloudwise/client.rb +412 -25
- data/lib/datadog/core/cloudwise/component.rb +195 -52
- data/lib/datadog/core/cloudwise/docc_heartbeat_worker.rb +105 -0
- data/lib/datadog/core/cloudwise/docc_operation_worker.rb +191 -0
- data/lib/datadog/core/cloudwise/docc_registration_worker.rb +89 -0
- data/lib/datadog/core/cloudwise/license_worker.rb +90 -4
- data/lib/datadog/core/cloudwise/probe_state.rb +134 -12
- data/lib/datadog/core/configuration/components.rb +10 -9
- data/lib/datadog/core/configuration/settings.rb +43 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +6 -2
- data/lib/datadog/core/remote/client/capabilities.rb +7 -0
- data/lib/datadog/core/remote/component.rb +2 -2
- data/lib/datadog/core/remote/transport/config.rb +2 -10
- data/lib/datadog/core/remote/transport/http/config.rb +9 -9
- data/lib/datadog/core/remote/transport/http/negotiation.rb +17 -8
- data/lib/datadog/core/remote/transport/http.rb +2 -0
- data/lib/datadog/core/remote/transport/negotiation.rb +2 -18
- data/lib/datadog/core/remote/worker.rb +23 -35
- data/lib/datadog/core/telemetry/component.rb +26 -13
- data/lib/datadog/core/telemetry/event/app_started.rb +67 -49
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -6
- data/lib/datadog/core/telemetry/transport/telemetry.rb +1 -2
- data/lib/datadog/core/telemetry/worker.rb +51 -6
- data/lib/datadog/core/transport/http/adapters/net.rb +2 -0
- data/lib/datadog/core/transport/http/client.rb +69 -0
- data/lib/datadog/core/utils/only_once_successful.rb +6 -2
- data/lib/datadog/data_streams/transport/http/client.rb +4 -32
- data/lib/datadog/data_streams/transport/stats.rb +1 -1
- data/lib/datadog/di/probe_notification_builder.rb +35 -13
- data/lib/datadog/di/transport/diagnostics.rb +2 -2
- data/lib/datadog/di/transport/http/diagnostics.rb +2 -4
- data/lib/datadog/di/transport/http/input.rb +2 -4
- data/lib/datadog/di/transport/input.rb +2 -2
- data/lib/datadog/open_feature/component.rb +60 -0
- data/lib/datadog/open_feature/configuration.rb +27 -0
- data/lib/datadog/open_feature/evaluation_engine.rb +59 -0
- data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
- data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
- data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
- data/lib/datadog/open_feature/exposures/event.rb +60 -0
- data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
- data/lib/datadog/open_feature/exposures/worker.rb +116 -0
- data/lib/datadog/open_feature/ext.rb +13 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
- data/lib/datadog/open_feature/provider.rb +134 -0
- data/lib/datadog/open_feature/remote.rb +74 -0
- data/lib/datadog/open_feature/resolution_details.rb +35 -0
- data/lib/datadog/open_feature/transport.rb +72 -0
- data/lib/datadog/open_feature.rb +19 -0
- data/lib/datadog/profiling/component.rb +6 -0
- data/lib/datadog/profiling/profiler.rb +4 -0
- data/lib/datadog/profiling.rb +1 -2
- data/lib/datadog/single_step_instrument.rb +1 -1
- data/lib/datadog/tracing/contrib/cloudwise/propagation.rb +164 -7
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
- data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +14 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +6 -2
- data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
- data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
- data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
- data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
- data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +46 -0
- data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
- data/lib/datadog/tracing/contrib.rb +1 -0
- data/lib/datadog/tracing/transport/http/api.rb +73 -1
- data/lib/datadog/tracing/transport/http/client.rb +12 -26
- data/lib/datadog/tracing/transport/http/traces.rb +4 -2
- data/lib/datadog/tracing/transport/trace_formatter.rb +16 -0
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +1 -0
- metadata +38 -15
- data/lib/datadog/core/cloudwise/IMPLEMENTATION_V2.md +0 -517
- data/lib/datadog/core/cloudwise/QUICKSTART.md +0 -398
- data/lib/datadog/core/cloudwise/README.md +0 -722
- data/lib/datadog/core/remote/transport/http/client.rb +0 -49
- data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
- 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,
|
|
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::
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
@@ -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 =
|
|
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?
|
|
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/
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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
|