datadog 2.22.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -2
- data/ext/LIBDATADOG_DEVELOPMENT.md +1 -58
- data/ext/datadog_profiling_native_extension/collectors_stack.c +4 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
- data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
- data/ext/libdatadog_api/feature_flags.c +554 -0
- data/ext/libdatadog_api/feature_flags.h +5 -0
- data/ext/libdatadog_api/init.c +2 -0
- 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 +23 -6
- data/lib/datadog/appsec/api_security/sampler.rb +7 -4
- 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/configuration/components.rb +30 -3
- data/lib/datadog/core/configuration/config_helper.rb +1 -1
- data/lib/datadog/core/configuration/settings.rb +14 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +330 -301
- data/lib/datadog/core/ddsketch.rb +0 -2
- data/lib/datadog/core/environment/ext.rb +6 -0
- data/lib/datadog/core/environment/process.rb +79 -0
- data/lib/datadog/core/feature_flags.rb +61 -0
- data/lib/datadog/core/remote/client/capabilities.rb +7 -0
- 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 +25 -37
- data/lib/datadog/core/tag_builder.rb +0 -4
- data/lib/datadog/core/tag_normalizer.rb +84 -0
- data/lib/datadog/core/telemetry/component.rb +7 -3
- data/lib/datadog/core/telemetry/event/app_started.rb +52 -49
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
- data/lib/datadog/core/telemetry/logger.rb +2 -2
- data/lib/datadog/core/telemetry/logging.rb +2 -8
- 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/transport/http/client.rb +69 -0
- data/lib/datadog/core/utils/array.rb +29 -0
- data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
- data/lib/datadog/core/utils/network.rb +3 -1
- data/lib/datadog/core/utils/only_once_successful.rb +6 -2
- data/lib/datadog/core/utils.rb +2 -0
- data/lib/datadog/data_streams/configuration/settings.rb +49 -0
- data/lib/datadog/data_streams/configuration.rb +11 -0
- data/lib/datadog/data_streams/ext.rb +11 -0
- data/lib/datadog/data_streams/extensions.rb +16 -0
- data/lib/datadog/data_streams/pathway_context.rb +169 -0
- data/lib/datadog/data_streams/processor.rb +509 -0
- data/lib/datadog/data_streams/transport/http/api.rb +33 -0
- data/lib/datadog/data_streams/transport/http/client.rb +21 -0
- data/lib/datadog/data_streams/transport/http/stats.rb +87 -0
- data/lib/datadog/data_streams/transport/http.rb +41 -0
- data/lib/datadog/data_streams/transport/stats.rb +60 -0
- data/lib/datadog/data_streams.rb +100 -0
- data/lib/datadog/di/component.rb +0 -16
- data/lib/datadog/di/el/evaluator.rb +1 -1
- data/lib/datadog/di/error.rb +4 -0
- data/lib/datadog/di/instrumenter.rb +76 -30
- data/lib/datadog/di/probe.rb +20 -0
- data/lib/datadog/di/probe_manager.rb +10 -2
- data/lib/datadog/di/probe_notification_builder.rb +62 -23
- data/lib/datadog/di/proc_responder.rb +32 -0
- 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/http.rb +6 -2
- data/lib/datadog/di/transport/input.rb +64 -4
- 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 +69 -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 +14 -0
- data/lib/datadog/open_feature/native_evaluator.rb +38 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
- data/lib/datadog/open_feature/provider.rb +141 -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/opentelemetry/configuration/settings.rb +159 -0
- data/lib/datadog/opentelemetry/metrics.rb +110 -0
- data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +38 -0
- data/lib/datadog/opentelemetry.rb +3 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +15 -6
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
- data/lib/datadog/profiling/profiler.rb +4 -0
- data/lib/datadog/profiling/tag_builder.rb +36 -3
- data/lib/datadog/profiling.rb +1 -2
- data/lib/datadog/single_step_instrument.rb +1 -1
- data/lib/datadog/tracing/configuration/ext.rb +9 -0
- data/lib/datadog/tracing/configuration/settings.rb +74 -0
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
- data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
- data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
- data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
- data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
- data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
- data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +32 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
- data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
- data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
- data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
- data/lib/datadog/tracing/contrib/status_range_matcher.rb +7 -0
- 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/metadata/ext.rb +1 -1
- data/lib/datadog/tracing/transport/http/client.rb +12 -26
- data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
- data/lib/datadog/tracing/transport/traces.rb +3 -5
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +2 -0
- metadata +78 -15
- 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
|
@@ -87,10 +87,41 @@ module Datadog
|
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
message = nil
|
|
91
|
+
evaluation_errors = []
|
|
92
|
+
if segments = probe.template_segments
|
|
93
|
+
message, evaluation_errors = evaluate_template(segments, context)
|
|
94
|
+
end
|
|
95
|
+
build_snapshot_base(context,
|
|
96
|
+
evaluation_errors: evaluation_errors, message: message,
|
|
97
|
+
captures: captures)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def build_condition_evaluation_failed(context, expression, exception)
|
|
101
|
+
error = {
|
|
102
|
+
message: "#{exception.class}: #{exception}",
|
|
103
|
+
expr: expression.dsl_expr,
|
|
104
|
+
}
|
|
105
|
+
build_snapshot_base(context, evaluation_errors: [error])
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def build_snapshot_base(context, evaluation_errors: [], captures: nil, message: nil)
|
|
111
|
+
probe = context.probe
|
|
112
|
+
|
|
113
|
+
timestamp = timestamp_now
|
|
114
|
+
duration = context.duration
|
|
115
|
+
|
|
90
116
|
location = if probe.line?
|
|
91
117
|
{
|
|
92
118
|
file: context.path,
|
|
93
|
-
|
|
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],
|
|
94
125
|
}
|
|
95
126
|
elsif probe.method?
|
|
96
127
|
{
|
|
@@ -103,28 +134,38 @@ module Datadog
|
|
|
103
134
|
format_caller_locations(caller_locations)
|
|
104
135
|
end
|
|
105
136
|
|
|
106
|
-
timestamp = timestamp_now
|
|
107
|
-
message = nil
|
|
108
|
-
evaluation_errors = []
|
|
109
|
-
if segments = probe.template_segments
|
|
110
|
-
message, evaluation_errors = evaluate_template(segments, context)
|
|
111
|
-
end
|
|
112
|
-
duration = context.duration
|
|
113
137
|
{
|
|
114
138
|
service: settings.service,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 || {},
|
|
123
168
|
},
|
|
124
|
-
language: 'ruby',
|
|
125
|
-
# TODO add test coverage for callers being nil
|
|
126
|
-
stack: stack,
|
|
127
|
-
captures: captures,
|
|
128
169
|
},
|
|
129
170
|
# In python tracer duration is under debugger.snapshot,
|
|
130
171
|
# but UI appears to expect it here at top level.
|
|
@@ -132,7 +173,7 @@ module Datadog
|
|
|
132
173
|
host: nil,
|
|
133
174
|
logger: {
|
|
134
175
|
name: probe.file,
|
|
135
|
-
method: probe.method_name
|
|
176
|
+
method: probe.method_name,
|
|
136
177
|
thread_name: Thread.current.name,
|
|
137
178
|
# Dynamic instrumentation currently does not need thread_id for
|
|
138
179
|
# anything. It can be sent if a customer requests it at which point
|
|
@@ -150,8 +191,6 @@ module Datadog
|
|
|
150
191
|
}
|
|
151
192
|
end
|
|
152
193
|
|
|
153
|
-
private
|
|
154
|
-
|
|
155
194
|
def build_status(probe, message:, status:)
|
|
156
195
|
{
|
|
157
196
|
service: settings.service,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
# An adapter to convert procs to responders.
|
|
6
|
+
#
|
|
7
|
+
# Used in test suite and benchmarks.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
class ProcResponder
|
|
11
|
+
def initialize(executed_proc, failed_proc = nil)
|
|
12
|
+
@executed_proc = executed_proc
|
|
13
|
+
@failed_proc = failed_proc
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :executed_proc
|
|
17
|
+
attr_reader :failed_proc
|
|
18
|
+
|
|
19
|
+
def probe_executed_callback(context)
|
|
20
|
+
executed_proc.call(context)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def probe_condition_evaluation_failed_callback(context, exc)
|
|
24
|
+
if failed_proc.nil?
|
|
25
|
+
raise NotImplementedError, "Failed proc not provided"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
failed_proc.call(context, exc)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
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/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
|
|
@@ -40,9 +40,13 @@ module Datadog
|
|
|
40
40
|
api_version: nil,
|
|
41
41
|
headers: nil
|
|
42
42
|
)
|
|
43
|
-
Core::Transport::HTTP.build(
|
|
43
|
+
Core::Transport::HTTP.build(
|
|
44
|
+
api_instance_class: Input::API::Instance,
|
|
44
45
|
logger: logger,
|
|
45
|
-
agent_settings: agent_settings,
|
|
46
|
+
agent_settings: agent_settings,
|
|
47
|
+
api_version: api_version,
|
|
48
|
+
headers: headers,
|
|
49
|
+
) do |transport|
|
|
46
50
|
apis = API.defaults
|
|
47
51
|
|
|
48
52
|
transport.api API::INPUT, apis[API::INPUT]
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../../core/chunker'
|
|
4
|
+
require_relative '../../core/encoding'
|
|
5
|
+
require_relative '../../core/tag_builder'
|
|
3
6
|
require_relative '../../core/transport/parcel'
|
|
4
|
-
require_relative '
|
|
7
|
+
require_relative '../../core/transport/request'
|
|
8
|
+
require_relative '../error'
|
|
9
|
+
require_relative 'http/input'
|
|
5
10
|
|
|
6
11
|
module Datadog
|
|
7
12
|
module DI
|
|
@@ -24,11 +29,30 @@ module Datadog
|
|
|
24
29
|
class Transport
|
|
25
30
|
attr_reader :client, :apis, :default_api, :current_api_id, :logger
|
|
26
31
|
|
|
32
|
+
# The limit on an individual snapshot payload, aka "log line",
|
|
33
|
+
# is 1 MB.
|
|
34
|
+
#
|
|
35
|
+
# TODO There is an RFC for snapshot pruning that should be
|
|
36
|
+
# implemented to reduce the size of snapshots to be below this
|
|
37
|
+
# limit, so that we can send a portion of the captured data
|
|
38
|
+
# rather than dropping the snapshot entirely.
|
|
39
|
+
MAX_SERIALIZED_SNAPSHOT_SIZE = 1024 * 1024
|
|
40
|
+
|
|
41
|
+
# The maximum chunk (batch) size that intake permits is 5 MB.
|
|
42
|
+
#
|
|
43
|
+
# Two bytes are for the [ and ] of JSON array syntax.
|
|
44
|
+
MAX_CHUNK_SIZE = 5 * 1024 * 1024 - 2
|
|
45
|
+
|
|
46
|
+
# Try to send smaller payloads to avoid large network requests.
|
|
47
|
+
# If a payload is larger than default chunk size but is under the
|
|
48
|
+
# max chunk size, it will still get sent out.
|
|
49
|
+
DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024
|
|
50
|
+
|
|
27
51
|
def initialize(apis, default_api, logger:)
|
|
28
52
|
@apis = apis
|
|
29
53
|
@logger = logger
|
|
30
54
|
|
|
31
|
-
@client = HTTP::Client.new(current_api, logger: logger)
|
|
55
|
+
@client = DI::Transport::HTTP::Input::Client.new(current_api, logger: logger)
|
|
32
56
|
end
|
|
33
57
|
|
|
34
58
|
def current_api
|
|
@@ -36,9 +60,45 @@ module Datadog
|
|
|
36
60
|
end
|
|
37
61
|
|
|
38
62
|
def send_input(payload, tags)
|
|
39
|
-
|
|
40
|
-
parcel = EncodedParcel.new(json)
|
|
63
|
+
# Tags are the same for all chunks, serialize them one time.
|
|
41
64
|
serialized_tags = Core::TagBuilder.serialize_tags(tags)
|
|
65
|
+
|
|
66
|
+
encoder = Core::Encoding::JSONEncoder
|
|
67
|
+
encoded_snapshots = Core::Utils::Array.filter_map(payload) do |snapshot|
|
|
68
|
+
encoded = encoder.encode(snapshot)
|
|
69
|
+
if encoded.length > MAX_SERIALIZED_SNAPSHOT_SIZE
|
|
70
|
+
# Drop the snapshot.
|
|
71
|
+
# TODO report via telemetry metric?
|
|
72
|
+
logger.debug { "di: dropping too big snapshot" }
|
|
73
|
+
nil
|
|
74
|
+
else
|
|
75
|
+
encoded
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
Datadog::Core::Chunker.chunk_by_size(
|
|
80
|
+
encoded_snapshots, DEFAULT_CHUNK_SIZE,
|
|
81
|
+
).each do |chunk|
|
|
82
|
+
# We drop snapshots that are too big earlier.
|
|
83
|
+
# The limit on chunked payload length here is greater
|
|
84
|
+
# than the limit on snapshot size, therefore no chunks
|
|
85
|
+
# can exceed limits here.
|
|
86
|
+
chunked_payload = encoder.join(chunk)
|
|
87
|
+
|
|
88
|
+
# We need to rescue exceptions for each chunk so that
|
|
89
|
+
# subsequent chunks are attempted to be sent.
|
|
90
|
+
begin
|
|
91
|
+
send_input_chunk(chunked_payload, serialized_tags)
|
|
92
|
+
rescue => exc
|
|
93
|
+
logger.debug { "di: failed to send snapshot chunk: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
payload
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def send_input_chunk(chunked_payload, serialized_tags)
|
|
101
|
+
parcel = EncodedParcel.new(chunked_payload)
|
|
42
102
|
request = Request.new(parcel, serialized_tags)
|
|
43
103
|
|
|
44
104
|
response = @client.send_input_payload(request)
|
|
@@ -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,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ext'
|
|
4
|
+
require_relative 'noop_evaluator'
|
|
5
|
+
require_relative 'native_evaluator'
|
|
6
|
+
require_relative 'resolution_details'
|
|
7
|
+
|
|
8
|
+
module Datadog
|
|
9
|
+
module OpenFeature
|
|
10
|
+
# This class performs the evaluation of the feature flag
|
|
11
|
+
class EvaluationEngine
|
|
12
|
+
ReconfigurationError = Class.new(StandardError)
|
|
13
|
+
|
|
14
|
+
ALLOWED_TYPES = %i[boolean string number float integer object].freeze
|
|
15
|
+
|
|
16
|
+
def initialize(reporter, telemetry:, logger:)
|
|
17
|
+
@reporter = reporter
|
|
18
|
+
@telemetry = telemetry
|
|
19
|
+
@logger = logger
|
|
20
|
+
|
|
21
|
+
@evaluator = NoopEvaluator.new(nil)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def fetch_value(flag_key, default_value:, expected_type:, evaluation_context: nil)
|
|
25
|
+
unless ALLOWED_TYPES.include?(expected_type)
|
|
26
|
+
message = "unknown type #{expected_type.inspect}, allowed types #{ALLOWED_TYPES.join(", ")}"
|
|
27
|
+
return ResolutionDetails.build_error(
|
|
28
|
+
value: default_value, error_code: Ext::UNKNOWN_TYPE, error_message: message
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context = evaluation_context&.fields.to_h
|
|
33
|
+
result = @evaluator.get_assignment(
|
|
34
|
+
flag_key, default_value: default_value, context: context, expected_type: expected_type
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@reporter.report(result, flag_key: flag_key, context: evaluation_context)
|
|
38
|
+
|
|
39
|
+
result
|
|
40
|
+
rescue => e
|
|
41
|
+
@telemetry.report(e, description: 'OpenFeature: Failed to fetch flag value')
|
|
42
|
+
|
|
43
|
+
ResolutionDetails.build_error(
|
|
44
|
+
value: default_value, error_code: Ext::GENERAL, error_message: e.message
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# NOTE: In a currect implementation configuration is expected to be a raw
|
|
49
|
+
# JSON string containing feature flags (straight from the remote config)
|
|
50
|
+
# in the format expected by `libdatadog` without any modifications
|
|
51
|
+
def reconfigure!(configuration)
|
|
52
|
+
if configuration.nil?
|
|
53
|
+
@logger.debug('OpenFeature: Removing configuration')
|
|
54
|
+
|
|
55
|
+
return @evaluator = NoopEvaluator.new(configuration)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
@evaluator = NativeEvaluator.new(configuration)
|
|
59
|
+
rescue => e
|
|
60
|
+
message = 'OpenFeature: Failed to reconfigure, reverting to the previous configuration'
|
|
61
|
+
|
|
62
|
+
@logger.error("#{message}, #{e.class}: #{e.message}")
|
|
63
|
+
@telemetry.report(e, description: "#{message} (#{e.class})")
|
|
64
|
+
|
|
65
|
+
raise ReconfigurationError, e.message
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
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
|
|
@@ -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
|