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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../core/utils/time'
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module OpenFeature
|
|
7
|
+
module Exposures
|
|
8
|
+
# A data model for an exposure event
|
|
9
|
+
module Event
|
|
10
|
+
TARGETING_KEY_FIELD = 'targeting_key'
|
|
11
|
+
ALLOWED_FIELD_TYPES = [String, Integer, Float, TrueClass, FalseClass].freeze
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def cache_key(result, flag_key:, context:)
|
|
15
|
+
"#{flag_key}:#{context.targeting_key}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def cache_value(result, flag_key:, context:)
|
|
19
|
+
"#{result.allocation_key}:#{result.variant}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build(result, flag_key:, context:)
|
|
23
|
+
{
|
|
24
|
+
timestamp: current_timestamp_ms,
|
|
25
|
+
allocation: {
|
|
26
|
+
key: result.allocation_key
|
|
27
|
+
},
|
|
28
|
+
flag: {
|
|
29
|
+
key: flag_key
|
|
30
|
+
},
|
|
31
|
+
variant: {
|
|
32
|
+
key: result.variant
|
|
33
|
+
},
|
|
34
|
+
subject: {
|
|
35
|
+
id: context.targeting_key,
|
|
36
|
+
attributes: extract_attributes(context)
|
|
37
|
+
}
|
|
38
|
+
}.freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# NOTE: We take all filds of the context that does not support nesting
|
|
44
|
+
# and will ignore targeting key as it will be set as `subject.id`
|
|
45
|
+
def extract_attributes(context)
|
|
46
|
+
context.fields.select do |key, value|
|
|
47
|
+
next false if key == TARGETING_KEY_FIELD
|
|
48
|
+
|
|
49
|
+
ALLOWED_FIELD_TYPES.include?(value.class)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def current_timestamp_ms
|
|
54
|
+
(Datadog::Core::Utils::Time.now.to_f * 1000).to_i
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'event'
|
|
4
|
+
require_relative 'deduplicator'
|
|
5
|
+
|
|
6
|
+
module Datadog
|
|
7
|
+
module OpenFeature
|
|
8
|
+
module Exposures
|
|
9
|
+
# This class is responsible for reporting exposures to the Agent
|
|
10
|
+
class Reporter
|
|
11
|
+
def initialize(worker, telemetry:, logger:)
|
|
12
|
+
@worker = worker
|
|
13
|
+
@logger = logger
|
|
14
|
+
@telemetry = telemetry
|
|
15
|
+
@deduplicator = Deduplicator.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# NOTE: Reporting expects evaluation context to be always present, but it
|
|
19
|
+
# might be missing depending on the customer way of using flags evaluation API.
|
|
20
|
+
# In addition to that the evaluation result must be marked for reporting.
|
|
21
|
+
def report(result, flag_key:, context:)
|
|
22
|
+
return false if context.nil?
|
|
23
|
+
return false unless result.log?
|
|
24
|
+
|
|
25
|
+
key = Event.cache_key(result, flag_key: flag_key, context: context)
|
|
26
|
+
value = Event.cache_value(result, flag_key: flag_key, context: context)
|
|
27
|
+
return false if @deduplicator.duplicate?(key, value)
|
|
28
|
+
|
|
29
|
+
event = Event.build(result, flag_key: flag_key, context: context)
|
|
30
|
+
@worker.enqueue(event)
|
|
31
|
+
rescue => e
|
|
32
|
+
@logger.debug { "OpenFeature: Failed to report resolution details: #{e.class}: #{e.message}" }
|
|
33
|
+
@telemetry.report(e, description: 'OpenFeature: Failed to report resolution details')
|
|
34
|
+
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../core/utils/time'
|
|
4
|
+
require_relative '../../core/workers/queue'
|
|
5
|
+
require_relative '../../core/workers/polling'
|
|
6
|
+
|
|
7
|
+
require_relative 'buffer'
|
|
8
|
+
require_relative 'batch_builder'
|
|
9
|
+
|
|
10
|
+
module Datadog
|
|
11
|
+
module OpenFeature
|
|
12
|
+
module Exposures
|
|
13
|
+
# This class is responsible for sending exposures to the Agent
|
|
14
|
+
class Worker
|
|
15
|
+
include Core::Workers::Queue
|
|
16
|
+
include Core::Workers::Polling
|
|
17
|
+
|
|
18
|
+
GRACEFUL_SHUTDOWN_EXTRA_SECONDS = 5
|
|
19
|
+
GRACEFUL_SHUTDOWN_WAIT_INTERVAL_SECONDS = 0.5
|
|
20
|
+
|
|
21
|
+
DEFAULT_FLUSH_INTERVAL_SECONDS = 30
|
|
22
|
+
DEFAULT_BUFFER_LIMIT = Buffer::DEFAULT_LIMIT
|
|
23
|
+
|
|
24
|
+
def initialize(
|
|
25
|
+
settings:,
|
|
26
|
+
transport:,
|
|
27
|
+
telemetry:,
|
|
28
|
+
logger:,
|
|
29
|
+
flush_interval_seconds: DEFAULT_FLUSH_INTERVAL_SECONDS,
|
|
30
|
+
buffer_limit: DEFAULT_BUFFER_LIMIT
|
|
31
|
+
)
|
|
32
|
+
@logger = logger
|
|
33
|
+
@transport = transport
|
|
34
|
+
@telemetry = telemetry
|
|
35
|
+
@batch_builder = BatchBuilder.new(settings)
|
|
36
|
+
@buffer_limit = buffer_limit
|
|
37
|
+
|
|
38
|
+
self.buffer = Buffer.new(buffer_limit)
|
|
39
|
+
self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_RESTART
|
|
40
|
+
self.loop_base_interval = flush_interval_seconds
|
|
41
|
+
self.enabled = true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def start
|
|
45
|
+
return if !enabled? || running?
|
|
46
|
+
|
|
47
|
+
perform
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def stop(force_stop = false, timeout = Core::Workers::Polling::DEFAULT_SHUTDOWN_TIMEOUT)
|
|
51
|
+
buffer.close if running?
|
|
52
|
+
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def enqueue(event)
|
|
57
|
+
buffer.push(event)
|
|
58
|
+
start unless running?
|
|
59
|
+
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def dequeue
|
|
64
|
+
[buffer.pop, buffer.dropped_count]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def perform(*args)
|
|
68
|
+
events, dropped = args
|
|
69
|
+
send_events(Array(events), dropped.to_i)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def graceful_shutdown
|
|
73
|
+
return false unless enabled? || !run_loop?
|
|
74
|
+
|
|
75
|
+
self.enabled = false
|
|
76
|
+
|
|
77
|
+
started = Core::Utils::Time.get_time
|
|
78
|
+
wait_time = loop_base_interval + GRACEFUL_SHUTDOWN_EXTRA_SECONDS
|
|
79
|
+
|
|
80
|
+
loop do
|
|
81
|
+
break if buffer.empty? && !in_iteration?
|
|
82
|
+
|
|
83
|
+
sleep(GRACEFUL_SHUTDOWN_WAIT_INTERVAL_SECONDS)
|
|
84
|
+
break if Core::Utils::Time.get_time - started > wait_time
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
stop(true)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def send_events(events, dropped)
|
|
93
|
+
return if events.empty?
|
|
94
|
+
|
|
95
|
+
if dropped.positive?
|
|
96
|
+
@logger.debug { "OpenFeature: Resolution details worker dropped #{dropped} event(s) due to full buffer" }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
payload = @batch_builder.payload_for(events)
|
|
100
|
+
response = @transport.send_exposures(payload)
|
|
101
|
+
|
|
102
|
+
unless response&.ok?
|
|
103
|
+
@logger.debug { "OpenFeature: Resolution details upload response was not OK: #{response.inspect}" }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
response
|
|
107
|
+
rescue => e
|
|
108
|
+
@logger.debug { "OpenFeature: Failed to flush resolution details events: #{e.class}: #{e.message}" }
|
|
109
|
+
@telemetry.report(e, description: 'OpenFeature: Failed to flush resolution details events')
|
|
110
|
+
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module OpenFeature
|
|
5
|
+
module Ext
|
|
6
|
+
ERROR = 'ERROR'
|
|
7
|
+
INITIALIZING = 'INITIALIZING'
|
|
8
|
+
UNKNOWN_TYPE = 'UNKNOWN_TYPE'
|
|
9
|
+
PROVIDER_FATAL = 'PROVIDER_FATAL'
|
|
10
|
+
PROVIDER_NOT_READY = 'PROVIDER_NOT_READY'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ext'
|
|
4
|
+
require_relative 'resolution_details'
|
|
5
|
+
|
|
6
|
+
module Datadog
|
|
7
|
+
module OpenFeature
|
|
8
|
+
# This class is a noop interface of evaluation logic
|
|
9
|
+
class NoopEvaluator
|
|
10
|
+
def initialize(_configuration)
|
|
11
|
+
# no-op
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get_assignment(_flag_key, default_value, _context, _expected_type)
|
|
15
|
+
ResolutionDetails.new(
|
|
16
|
+
value: default_value,
|
|
17
|
+
log?: false,
|
|
18
|
+
error?: true,
|
|
19
|
+
error_code: Ext::PROVIDER_NOT_READY,
|
|
20
|
+
error_message: 'Waiting for universal flag configuration',
|
|
21
|
+
reason: Ext::INITIALIZING
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
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: 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: result.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
|
+
end
|
|
123
|
+
|
|
124
|
+
def component_not_configured_default(value)
|
|
125
|
+
::OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
126
|
+
value: value,
|
|
127
|
+
error_code: Ext::PROVIDER_FATAL,
|
|
128
|
+
error_message: "Datadog's OpenFeature component must be configured",
|
|
129
|
+
reason: Ext::ERROR
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
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
|
+
break 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
|
|
@@ -220,6 +220,12 @@ module Datadog
|
|
|
220
220
|
"Please upgrade to Ruby >= 3.1 in order to use this feature. Heap profiling has been disabled."
|
|
221
221
|
)
|
|
222
222
|
return false
|
|
223
|
+
elsif RUBY_VERSION.start_with?("4.")
|
|
224
|
+
logger.warn(
|
|
225
|
+
"Heap profiling is not supported in current Ruby version (#{RUBY_VERSION}) due to https://bugs.ruby-lang.org/issues/21710. " \
|
|
226
|
+
"Heap profiling has been disabled."
|
|
227
|
+
)
|
|
228
|
+
return false
|
|
223
229
|
end
|
|
224
230
|
|
|
225
231
|
unless allocation_profiling_enabled
|
data/lib/datadog/profiling.rb
CHANGED
|
@@ -62,8 +62,7 @@ module Datadog
|
|
|
62
62
|
|
|
63
63
|
def self.enabled?
|
|
64
64
|
profiler = Datadog.send(:components).profiler
|
|
65
|
-
|
|
66
|
-
!!profiler&.send(:scheduler)&.running?
|
|
65
|
+
!!profiler&.enabled?
|
|
67
66
|
end
|
|
68
67
|
|
|
69
68
|
def self.wait_until_running(timeout_seconds: 5)
|
|
@@ -15,7 +15,7 @@ end
|
|
|
15
15
|
|
|
16
16
|
begin
|
|
17
17
|
require_relative 'auto_instrument'
|
|
18
|
-
Datadog::SingleStepInstrument
|
|
18
|
+
Datadog::SingleStepInstrument.const_set(:LOADED, true)
|
|
19
19
|
rescue StandardError, LoadError => e
|
|
20
20
|
warn "Single step instrumentation failed: #{e.class}:#{e.message}\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
|
|
21
21
|
end
|