datadog 2.45.0 → 2.47.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 +49 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +31 -14
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +5 -0
- data/ext/datadog_profiling_native_extension/profiling.c +6 -0
- data/ext/libdatadog_api/di.c +30 -5
- data/ext/libdatadog_api/extconf.rb +2 -0
- data/ext/libdatadog_api/otel_thread_context.c +20 -14
- data/ext/libdatadog_api/process_discovery.c +15 -1
- data/ext/libdatadog_api/trace_exporter.c +105 -25
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +29 -2
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +14 -2
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
- data/lib/datadog/appsec/security_engine/engine.rb +2 -1
- data/lib/datadog/core/configuration/components.rb +40 -18
- data/lib/datadog/core/configuration/components_state.rb +8 -1
- data/lib/datadog/core/configuration/supported_configurations.rb +7 -2
- data/lib/datadog/core/process_discovery.rb +5 -1
- data/lib/datadog/core/remote/client/capabilities.rb +34 -29
- data/lib/datadog/core/remote/client.rb +44 -3
- data/lib/datadog/core/remote/component.rb +28 -12
- data/lib/datadog/core/remote/dispatcher.rb +47 -6
- data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
- data/lib/datadog/di/component.rb +108 -3
- data/lib/datadog/di/configuration.rb +53 -2
- data/lib/datadog/di/context.rb +7 -2
- data/lib/datadog/di/contrib/active_record.rb +8 -2
- data/lib/datadog/di/evaluation_failure.rb +28 -0
- data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
- data/lib/datadog/di/guardrails_telemetry.rb +203 -0
- data/lib/datadog/di/instrumenter.rb +65 -15
- data/lib/datadog/di/logger.rb +30 -8
- data/lib/datadog/di/probe_manager.rb +48 -5
- data/lib/datadog/di/probe_notification_builder.rb +28 -26
- data/lib/datadog/di/probe_notifier_worker.rb +56 -6
- data/lib/datadog/di/remote.rb +46 -2
- data/lib/datadog/di/serializer.rb +147 -55
- data/lib/datadog/di/snapshot_event.rb +38 -0
- data/lib/datadog/di/transport/http.rb +13 -3
- data/lib/datadog/di/transport/input.rb +32 -7
- data/lib/datadog/open_feature/activation.rb +268 -0
- data/lib/datadog/open_feature/component.rb +31 -15
- data/lib/datadog/open_feature/configuration.rb +6 -0
- data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
- data/lib/datadog/open_feature/provider.rb +304 -8
- data/lib/datadog/open_feature.rb +31 -1
- data/lib/datadog/profiling/collectors/info.rb +37 -6
- data/lib/datadog/profiling/component.rb +22 -20
- data/lib/datadog/profiling/exporter.rb +3 -5
- data/lib/datadog/tracing/component.rb +21 -2
- data/lib/datadog/tracing/configuration/ext.rb +2 -0
- data/lib/datadog/tracing/configuration/settings.rb +14 -1
- data/lib/datadog/tracing/context_provider.rb +8 -0
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
- data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +102 -0
- data/lib/datadog/tracing/otel_thread_context.rb +3 -5
- data/lib/datadog/tracing/remote.rb +50 -19
- data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
- data/lib/datadog/tracing/transport/http/traces.rb +2 -0
- data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
- data/lib/datadog/tracing/transport/native.rb +16 -1
- data/lib/datadog/tracing/transport/telemetry.rb +62 -0
- data/lib/datadog/tracing/transport/traces.rb +82 -15
- data/lib/datadog/version.rb +1 -1
- metadata +19 -10
- data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
|
@@ -66,10 +66,23 @@ module Datadog
|
|
|
66
66
|
gateway.watch("rails.response.body.json") do |stack, container|
|
|
67
67
|
context = container.context
|
|
68
68
|
|
|
69
|
+
body_bytesize = container.data.bytesize
|
|
70
|
+
limit = Datadog.configuration.appsec.body_parsing_size_limit
|
|
71
|
+
|
|
69
72
|
persistent_data = {
|
|
70
|
-
"server.response.body" =>
|
|
73
|
+
"server.response.body.byte_length" => body_bytesize,
|
|
71
74
|
}
|
|
72
|
-
|
|
75
|
+
|
|
76
|
+
if limit > 0 && body_bytesize <= limit
|
|
77
|
+
json = extract_json(container.data)
|
|
78
|
+
persistent_data["server.response.body"] = WAF::JSONString.new(json) if json
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
result = begin
|
|
82
|
+
context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
|
|
83
|
+
rescue WAF::ConversionError
|
|
84
|
+
next stack.call(container)
|
|
85
|
+
end
|
|
73
86
|
|
|
74
87
|
if result.match?
|
|
75
88
|
context.events.push(
|
|
@@ -85,6 +98,20 @@ module Datadog
|
|
|
85
98
|
stack.call(container)
|
|
86
99
|
end
|
|
87
100
|
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def extract_json(body)
|
|
105
|
+
return body unless body.start_with?("/**/")
|
|
106
|
+
return unless body.end_with?(")")
|
|
107
|
+
|
|
108
|
+
# NOTE: Best-effort support for standard Rails JSONP callbacks.
|
|
109
|
+
# Callback expressions with parentheses may prevent extraction
|
|
110
|
+
opening_parenthesis_index = body.b.index("(")
|
|
111
|
+
return unless opening_parenthesis_index
|
|
112
|
+
|
|
113
|
+
body.byteslice(opening_parenthesis_index + 1, body.bytesize - opening_parenthesis_index - 2) #: String
|
|
114
|
+
end
|
|
88
115
|
end
|
|
89
116
|
end
|
|
90
117
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../../utils/hash_coercion"
|
|
4
3
|
require_relative "../../../instrumentation/gateway/argument"
|
|
5
4
|
|
|
6
5
|
module Datadog
|
|
@@ -8,8 +7,8 @@ module Datadog
|
|
|
8
7
|
module Contrib
|
|
9
8
|
module Rails
|
|
10
9
|
module Patches
|
|
11
|
-
# A patch
|
|
12
|
-
#
|
|
10
|
+
# A patch for `AbstractController::Rendering#render_to_body` that captures
|
|
11
|
+
# the serialized JSON response body for AppSec inspection
|
|
13
12
|
module RenderToBodyPatch
|
|
14
13
|
def render_to_body(options = {})
|
|
15
14
|
return super unless options.key?(:json)
|
|
@@ -17,13 +16,15 @@ module Datadog
|
|
|
17
16
|
context = request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
|
|
18
17
|
return super unless context
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
# NOTE: Custom renderers can return streaming bodies, which we
|
|
20
|
+
# currently do not inspect
|
|
21
|
+
body = super
|
|
22
|
+
return body unless body.is_a?(String)
|
|
22
23
|
|
|
23
|
-
container = Instrumentation::Gateway::DataContainer.new(
|
|
24
|
+
container = Instrumentation::Gateway::DataContainer.new(body, context: context)
|
|
24
25
|
Instrumentation.gateway.push("rails.response.body.json", container)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
body
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
end
|
|
@@ -101,10 +101,22 @@ module Datadog
|
|
|
101
101
|
gateway.watch("sinatra.response.body.json") do |stack, container|
|
|
102
102
|
context = container.context # : Context
|
|
103
103
|
|
|
104
|
+
body_bytesize = container.data.bytesize
|
|
105
|
+
limit = Datadog.configuration.appsec.body_parsing_size_limit
|
|
106
|
+
|
|
104
107
|
persistent_data = {
|
|
105
|
-
"server.response.body" =>
|
|
108
|
+
"server.response.body.byte_length" => body_bytesize,
|
|
106
109
|
}
|
|
107
|
-
|
|
110
|
+
|
|
111
|
+
if limit > 0 && body_bytesize <= limit
|
|
112
|
+
persistent_data["server.response.body"] = WAF::JSONString.new(container.data)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
result = begin
|
|
116
|
+
context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
|
|
117
|
+
rescue WAF::ConversionError
|
|
118
|
+
next stack.call(container)
|
|
119
|
+
end
|
|
108
120
|
|
|
109
121
|
if result.match?
|
|
110
122
|
context.events.push(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../../utils/hash_coercion"
|
|
4
3
|
require_relative "../../../instrumentation/gateway/argument"
|
|
5
4
|
|
|
6
5
|
module Datadog
|
|
@@ -8,20 +7,22 @@ module Datadog
|
|
|
8
7
|
module Contrib
|
|
9
8
|
module Sinatra
|
|
10
9
|
module Patches
|
|
11
|
-
# A patch
|
|
12
|
-
# body
|
|
10
|
+
# A patch for `Sinatra::JSON#json` that captures the serialized
|
|
11
|
+
# response body for AppSec inspection
|
|
13
12
|
module JsonPatch
|
|
14
13
|
def json(object, options = {})
|
|
15
14
|
context = @request.env[Datadog::AppSec::Ext::CONTEXT_KEY] # : Context?
|
|
16
15
|
return super unless context
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
# NOTE: Custom renderers can return streaming bodies, which we
|
|
18
|
+
# currently do not inspect
|
|
19
|
+
body = super
|
|
20
|
+
return body unless body.is_a?(String)
|
|
20
21
|
|
|
21
|
-
container = Instrumentation::Gateway::DataContainer.new(
|
|
22
|
+
container = Instrumentation::Gateway::DataContainer.new(body, context: context)
|
|
22
23
|
Instrumentation.gateway.push("sinatra.response.body.json", container)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
body
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -71,7 +71,8 @@ module Datadog
|
|
|
71
71
|
if is_ruleset_update &&
|
|
72
72
|
(diagnostics.key?("error") ||
|
|
73
73
|
diagnostics.dig("rules", "error") ||
|
|
74
|
-
diagnostics.dig("processors", "
|
|
74
|
+
diagnostics.dig("processors", "error"))
|
|
75
|
+
@waf_builder.remove_config_at_path(path)
|
|
75
76
|
diagnostics = load_default_config(telemetry: AppSec.telemetry)
|
|
76
77
|
@reconfigured_ruleset_version = diagnostics["ruleset_version"]
|
|
77
78
|
report_configuration_diagnostics(diagnostics, action: "update", telemetry: AppSec.telemetry)
|
|
@@ -21,7 +21,7 @@ require_relative "../../ai_guard/component"
|
|
|
21
21
|
require_relative "../../di/component"
|
|
22
22
|
require_relative "../../symbol_database"
|
|
23
23
|
require_relative "../../symbol_database/component"
|
|
24
|
-
require_relative "../../open_feature/
|
|
24
|
+
require_relative "../../open_feature/activation"
|
|
25
25
|
require_relative "../../error_tracking/component"
|
|
26
26
|
require_relative "../crashtracking/component"
|
|
27
27
|
require_relative "../environment/agent_info"
|
|
@@ -55,7 +55,7 @@ module Datadog
|
|
|
55
55
|
# extension, remote config off, non-MRI). Under the always-build model
|
|
56
56
|
# it is non-nil even when the DI setting defaults to off, so a built
|
|
57
57
|
# SymbolDatabase component is inert until an upload is actually
|
|
58
|
-
# permitted: the component's own gate
|
|
58
|
+
# permitted: the component's own gate, SymbolDatabase::Component#upload_allowed?,
|
|
59
59
|
# only extracts and uploads when DI is truly active or the customer
|
|
60
60
|
# opted in explicitly. This mirrors DI advertising/building a component
|
|
61
61
|
# by default while installing no probes until it is enabled.
|
|
@@ -154,8 +154,7 @@ module Datadog
|
|
|
154
154
|
:ai_guard,
|
|
155
155
|
:agent_info,
|
|
156
156
|
:data_streams,
|
|
157
|
-
:symbol_database
|
|
158
|
-
:open_feature
|
|
157
|
+
:symbol_database
|
|
159
158
|
|
|
160
159
|
def initialize(settings)
|
|
161
160
|
@settings = settings
|
|
@@ -188,15 +187,14 @@ module Datadog
|
|
|
188
187
|
|
|
189
188
|
@telemetry = self.class.build_telemetry(settings, agent_settings, @logger)
|
|
190
189
|
|
|
191
|
-
# Bind Remote Configuration dispatch to this tree, which starts before it becomes the global Components instance.
|
|
192
190
|
@remote = Remote::Component.build(
|
|
193
191
|
settings,
|
|
194
192
|
agent_settings,
|
|
195
193
|
logger: @logger,
|
|
196
194
|
telemetry: telemetry,
|
|
197
|
-
open_feature_component_provider: -> { @open_feature },
|
|
198
195
|
)
|
|
199
196
|
@tracer = Datadog::Tracing::Component.build_tracer(settings, agent_settings, logger: @logger)
|
|
197
|
+
Datadog::Tracing::Component.bind_transport_telemetry(@tracer, telemetry)
|
|
200
198
|
@crashtracker = self.class.build_crashtracker(settings, agent_settings, logger: @logger)
|
|
201
199
|
|
|
202
200
|
@profiler, profiler_logger_extra = Datadog::Profiling::Component.build_profiler_component(
|
|
@@ -211,7 +209,13 @@ module Datadog
|
|
|
211
209
|
@health_metrics = self.class.build_health_metrics(settings, @logger, telemetry)
|
|
212
210
|
@appsec = Datadog::AppSec::Component.build_appsec_component(settings, telemetry: telemetry)
|
|
213
211
|
@ai_guard = Datadog::AIGuard::Component.build(settings, logger: @logger, telemetry: telemetry)
|
|
214
|
-
@
|
|
212
|
+
@open_feature_activation = OpenFeature::Activation.new(
|
|
213
|
+
settings,
|
|
214
|
+
agent_settings,
|
|
215
|
+
remote,
|
|
216
|
+
logger: @logger,
|
|
217
|
+
telemetry: telemetry,
|
|
218
|
+
)
|
|
215
219
|
@dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
|
|
216
220
|
# Only build symbol database when enabled, so a disabled component is
|
|
217
221
|
# never constructed.
|
|
@@ -257,6 +261,7 @@ module Datadog
|
|
|
257
261
|
ProcessDiscovery.after_fork
|
|
258
262
|
symbol_database&.after_fork!
|
|
259
263
|
data_streams&.restart_flush_thread
|
|
264
|
+
@open_feature_activation.after_fork
|
|
260
265
|
end
|
|
261
266
|
|
|
262
267
|
# Hot-swaps with a new sampler.
|
|
@@ -271,6 +276,8 @@ module Datadog
|
|
|
271
276
|
def startup!(settings, old_state: nil)
|
|
272
277
|
telemetry.start(old_state&.telemetry_enabled?, components: self)
|
|
273
278
|
|
|
279
|
+
@open_feature_activation.startup!
|
|
280
|
+
|
|
274
281
|
if settings.profiling.enabled
|
|
275
282
|
if profiler
|
|
276
283
|
profiler.start
|
|
@@ -281,12 +288,20 @@ module Datadog
|
|
|
281
288
|
end
|
|
282
289
|
end
|
|
283
290
|
|
|
291
|
+
if dynamic_instrumentation && old_state
|
|
292
|
+
dynamic_instrumentation.record_rc_enablement_signal_paths(old_state.rc_enablement_signal_paths)
|
|
293
|
+
end
|
|
294
|
+
|
|
284
295
|
if remote && old_state&.remote_started?
|
|
285
296
|
# The library was reconfigured and previously it already started
|
|
286
297
|
# the remote component (i.e., it received at least one request
|
|
287
298
|
# through the installed Rack middleware which started the remote).
|
|
288
299
|
# If the new configuration also has remote enabled, start the
|
|
289
300
|
# new remote right away.
|
|
301
|
+
#
|
|
302
|
+
# The DI signal-path restore above must precede this: the
|
|
303
|
+
# replacement client's first sync can dispatch the APM_TRACING
|
|
304
|
+
# merge, whose withdrawal branch reads the recorded paths.
|
|
290
305
|
remote.start
|
|
291
306
|
end
|
|
292
307
|
|
|
@@ -329,8 +344,14 @@ module Datadog
|
|
|
329
344
|
# Shutdown Symbol Database
|
|
330
345
|
symbol_database&.shutdown!
|
|
331
346
|
|
|
332
|
-
|
|
333
|
-
|
|
347
|
+
begin
|
|
348
|
+
@open_feature_activation.shutdown!
|
|
349
|
+
rescue => e
|
|
350
|
+
# Feature Flags is optional and must never interrupt library shutdown.
|
|
351
|
+
description = "Feature Flags delivery failed to shut down"
|
|
352
|
+
logger.error("#{description}: #{e.class}: #{e.message}")
|
|
353
|
+
telemetry.report(e, description: description)
|
|
354
|
+
end
|
|
334
355
|
|
|
335
356
|
# Decommission AppSec
|
|
336
357
|
appsec&.shutdown!
|
|
@@ -392,21 +413,22 @@ module Datadog
|
|
|
392
413
|
# di_implicitly_enabled distinguishes RC-driven start from explicit
|
|
393
414
|
# start (env var or programmatic) so that an explicit
|
|
394
415
|
# `enabled = false` on reconfiguration can take effect.
|
|
395
|
-
|
|
396
|
-
# using_default? — not `!enabled` — because Datadog.configure has
|
|
397
|
-
# already mutated the singleton settings by the time #state runs,
|
|
398
|
-
# so `!enabled` would treat a customer's explicit
|
|
399
|
-
# `enabled = false` as "implicitly enabled" and restart DI.
|
|
400
|
-
# using_default? asks "did the customer ever touch this setting",
|
|
401
|
-
# which is the right question for RC carry-over.
|
|
402
|
-
di_implicit = dynamic_instrumentation&.started? &&
|
|
403
|
-
@settings.dynamic_instrumentation.using_default?(:enabled)
|
|
416
|
+
di_implicit = dynamic_instrumentation&.implicitly_enabled?
|
|
404
417
|
ComponentsState.new(
|
|
405
418
|
telemetry_enabled: telemetry.enabled,
|
|
406
419
|
remote_started: remote&.started?,
|
|
407
420
|
di_implicitly_enabled: di_implicit || false,
|
|
421
|
+
rc_enablement_signal_paths: dynamic_instrumentation&.rc_enablement_signal_paths || [],
|
|
408
422
|
)
|
|
409
423
|
end
|
|
424
|
+
|
|
425
|
+
def open_feature
|
|
426
|
+
@open_feature_activation.component
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
private
|
|
430
|
+
|
|
431
|
+
attr_reader :open_feature_activation
|
|
410
432
|
end
|
|
411
433
|
end
|
|
412
434
|
end
|
|
@@ -5,10 +5,12 @@ module Datadog
|
|
|
5
5
|
module Configuration
|
|
6
6
|
# Stores the state of component tree when replacing the tree.
|
|
7
7
|
class ComponentsState
|
|
8
|
-
def initialize(telemetry_enabled:, remote_started:, di_implicitly_enabled: false
|
|
8
|
+
def initialize(telemetry_enabled:, remote_started:, di_implicitly_enabled: false,
|
|
9
|
+
rc_enablement_signal_paths: [])
|
|
9
10
|
@telemetry_enabled = !!telemetry_enabled
|
|
10
11
|
@remote_started = !!remote_started
|
|
11
12
|
@di_implicitly_enabled = !!di_implicitly_enabled
|
|
13
|
+
@rc_enablement_signal_paths = rc_enablement_signal_paths || []
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def telemetry_enabled?
|
|
@@ -22,6 +24,11 @@ module Datadog
|
|
|
22
24
|
def di_implicitly_enabled?
|
|
23
25
|
@di_implicitly_enabled
|
|
24
26
|
end
|
|
27
|
+
|
|
28
|
+
# The RC enable-signal carrier paths recorded by the replaced tree's DI component.
|
|
29
|
+
#
|
|
30
|
+
# @return [Array<Core::Remote::Configuration::Path>]
|
|
31
|
+
attr_reader :rc_enablement_signal_paths
|
|
25
32
|
end
|
|
26
33
|
end
|
|
27
34
|
end
|
|
@@ -51,6 +51,7 @@ module Datadog
|
|
|
51
51
|
"DD_DBM_INJECT_SQL_BASEHASH",
|
|
52
52
|
"DD_DBM_PROPAGATION_MODE",
|
|
53
53
|
"DD_DISABLE_DATADOG_RAILS",
|
|
54
|
+
"DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS",
|
|
54
55
|
"DD_DYNAMIC_INSTRUMENTATION_ENABLED",
|
|
55
56
|
"DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE",
|
|
56
57
|
"DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE",
|
|
@@ -71,6 +72,7 @@ module Datadog
|
|
|
71
72
|
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS",
|
|
72
73
|
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS",
|
|
73
74
|
"DD_FEATURE_FLAGS_ENABLED",
|
|
75
|
+
"DD_FEATURE_FLAGS_EVALUATION_TELEMETRY_PER_SUBJECT_OPT_OUT_ENABLED",
|
|
74
76
|
"DD_FLAGGING_EVALUATION_COUNTS_ENABLED",
|
|
75
77
|
"DD_GIT_COMMIT_SHA",
|
|
76
78
|
"DD_GIT_REPOSITORY_URL",
|
|
@@ -241,6 +243,7 @@ module Datadog
|
|
|
241
243
|
"DD_TRACE_HTTP_ANALYTICS_ENABLED",
|
|
242
244
|
"DD_TRACE_HTTP_ANALYTICS_SAMPLE_RATE",
|
|
243
245
|
"DD_TRACE_HTTP_CLIENT_ERROR_STATUSES",
|
|
246
|
+
"DD_TRACE_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE",
|
|
244
247
|
"DD_TRACE_HTTP_DISTRIBUTED_TRACING",
|
|
245
248
|
"DD_TRACE_HTTP_ENABLED",
|
|
246
249
|
"DD_TRACE_HTTP_ERROR_STATUS_CODES",
|
|
@@ -399,6 +402,7 @@ module Datadog
|
|
|
399
402
|
|
|
400
403
|
ALIASES =
|
|
401
404
|
{"DD_DISABLE_DATADOG_RAILS" => ["DISABLE_DATADOG_RAILS"],
|
|
405
|
+
"DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS" => ["DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT"],
|
|
402
406
|
"DD_PROFILING_GVL_ENABLED" => ["DD_PROFILING_PREVIEW_GVL_ENABLED"],
|
|
403
407
|
"DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED" => ["DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED"],
|
|
404
408
|
"DD_SERVICE" => ["OTEL_SERVICE_NAME"],
|
|
@@ -409,10 +413,11 @@ module Datadog
|
|
|
409
413
|
"DD_TRACE_SAMPLE_RATE" => ["OTEL_TRACES_SAMPLER"]}.freeze
|
|
410
414
|
|
|
411
415
|
DEPRECATIONS =
|
|
412
|
-
Set["DD_PROFILING_PREVIEW_GVL_ENABLED", "DISABLE_DATADOG_RAILS"]
|
|
416
|
+
Set["DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT", "DD_PROFILING_PREVIEW_GVL_ENABLED", "DISABLE_DATADOG_RAILS"]
|
|
413
417
|
|
|
414
418
|
ALIAS_TO_CANONICAL =
|
|
415
|
-
{"
|
|
419
|
+
{"DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT" => "DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS",
|
|
420
|
+
"DD_PROFILING_PREVIEW_GVL_ENABLED" => "DD_PROFILING_GVL_ENABLED",
|
|
416
421
|
"DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED" => "DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED",
|
|
417
422
|
"DISABLE_DATADOG_RAILS" => "DD_DISABLE_DATADOG_RAILS",
|
|
418
423
|
"OTEL_LOG_LEVEL" => "DD_TRACE_DEBUG",
|
|
@@ -23,7 +23,11 @@ module Datadog
|
|
|
23
23
|
metadata = get_metadata(settings)
|
|
24
24
|
|
|
25
25
|
shutdown!
|
|
26
|
-
@file_descriptor = _native_store_tracer_metadata(
|
|
26
|
+
@file_descriptor = _native_store_tracer_metadata(
|
|
27
|
+
Datadog.logger,
|
|
28
|
+
settings.tracing.otel_thread_context_enabled,
|
|
29
|
+
**metadata
|
|
30
|
+
)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def shutdown!
|
|
@@ -6,7 +6,6 @@ require_relative "../../../tracing/remote"
|
|
|
6
6
|
require_relative "../../../di/remote"
|
|
7
7
|
require_relative "../../../symbol_database"
|
|
8
8
|
require_relative "../../../symbol_database/remote"
|
|
9
|
-
require_relative "../../../open_feature/remote"
|
|
10
9
|
|
|
11
10
|
module Datadog
|
|
12
11
|
module Core
|
|
@@ -14,40 +13,57 @@ module Datadog
|
|
|
14
13
|
class Client
|
|
15
14
|
# Capabilities
|
|
16
15
|
class Capabilities
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def initialize(settings, telemetry, open_feature_component_provider: nil)
|
|
20
|
-
open_feature_component_provider ||= -> {}
|
|
16
|
+
def initialize(settings, telemetry:)
|
|
21
17
|
@capabilities = []
|
|
22
18
|
@products = []
|
|
23
|
-
@products_mutex = Mutex.new
|
|
24
19
|
@receivers = []
|
|
25
20
|
@telemetry = telemetry
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
|
|
23
|
+
register(settings)
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
@base64_capabilities = capabilities_to_base64(@capabilities)
|
|
26
|
+
end
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
def capabilities
|
|
29
|
+
@mutex.synchronize { @capabilities.dup }
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def products
|
|
33
|
-
@
|
|
33
|
+
@mutex.synchronize { @products.dup }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
@
|
|
38
|
-
|
|
36
|
+
def receivers
|
|
37
|
+
@mutex.synchronize { @receivers.dup }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def base64_capabilities
|
|
41
|
+
@mutex.synchronize { @base64_capabilities }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def register_runtime(capabilities:, products:, receivers:)
|
|
45
|
+
@mutex.synchronize do
|
|
46
|
+
register_capabilities(capabilities)
|
|
47
|
+
register_products(products)
|
|
48
|
+
register_receivers(receivers)
|
|
49
|
+
@base64_capabilities = capabilities_to_base64(@capabilities)
|
|
39
50
|
end
|
|
40
51
|
nil
|
|
41
52
|
end
|
|
42
53
|
|
|
54
|
+
def add_products(*products)
|
|
55
|
+
@mutex.synchronize { register_products(products) }
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
|
|
43
59
|
def remove_products(*products)
|
|
44
|
-
@
|
|
60
|
+
@mutex.synchronize { products.each { |product| @products.delete(product) } }
|
|
45
61
|
nil
|
|
46
62
|
end
|
|
47
63
|
|
|
48
64
|
private
|
|
49
65
|
|
|
50
|
-
def register(settings
|
|
66
|
+
def register(settings)
|
|
51
67
|
if settings.respond_to?(:appsec) && settings.appsec.enabled
|
|
52
68
|
register_capabilities(Datadog::AppSec::Remote.capabilities)
|
|
53
69
|
register_products(Datadog::AppSec::Remote.products)
|
|
@@ -95,32 +111,21 @@ module Datadog
|
|
|
95
111
|
end
|
|
96
112
|
end
|
|
97
113
|
end
|
|
98
|
-
|
|
99
|
-
if settings.respond_to?(:open_feature) && settings.open_feature.enabled
|
|
100
|
-
register_capabilities(Datadog::OpenFeature::Remote.capabilities)
|
|
101
|
-
register_products(Datadog::OpenFeature::Remote.products)
|
|
102
|
-
register_receivers(
|
|
103
|
-
Datadog::OpenFeature::Remote.receivers(
|
|
104
|
-
@telemetry,
|
|
105
|
-
component_provider: open_feature_component_provider,
|
|
106
|
-
),
|
|
107
|
-
)
|
|
108
|
-
end
|
|
109
114
|
end
|
|
110
115
|
|
|
111
116
|
def register_capabilities(capabilities)
|
|
112
|
-
@capabilities.
|
|
117
|
+
capabilities.each { |capability| @capabilities << capability unless @capabilities.include?(capability) }
|
|
113
118
|
end
|
|
114
119
|
|
|
115
120
|
def register_receivers(receivers)
|
|
116
|
-
@receivers.
|
|
121
|
+
receivers.each { |receiver| @receivers << receiver unless @receivers.include?(receiver) }
|
|
117
122
|
end
|
|
118
123
|
|
|
119
124
|
def register_products(products)
|
|
120
|
-
@products.
|
|
125
|
+
products.each { |product| @products << product unless @products.include?(product) }
|
|
121
126
|
end
|
|
122
127
|
|
|
123
|
-
def capabilities_to_base64
|
|
128
|
+
def capabilities_to_base64(capabilities)
|
|
124
129
|
return "" if capabilities.empty?
|
|
125
130
|
|
|
126
131
|
cap_to_hexs = capabilities.reduce(:|).to_s(16).tap { |s| s.size.odd? && s.prepend("0") }.scan(/\h\h/)
|
|
@@ -16,6 +16,12 @@ module Datadog
|
|
|
16
16
|
|
|
17
17
|
attr_reader :transport, :repository, :id, :dispatcher, :settings, :logger
|
|
18
18
|
|
|
19
|
+
# Whether the initial reconcile is still pending, to run on the first
|
|
20
|
+
# sync whose response contains targets.
|
|
21
|
+
#
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
attr_reader :pending_initial_reconcile
|
|
24
|
+
|
|
19
25
|
def initialize(transport, capabilities, settings:, logger:, repository: Configuration::Repository.new)
|
|
20
26
|
@transport = transport
|
|
21
27
|
@settings = settings
|
|
@@ -25,27 +31,52 @@ module Datadog
|
|
|
25
31
|
@id = SecureRandom.uuid
|
|
26
32
|
@capabilities = capabilities
|
|
27
33
|
@dispatcher = Dispatcher.new(@capabilities.receivers)
|
|
34
|
+
@pending_initial_reconcile = true
|
|
28
35
|
end
|
|
29
36
|
|
|
37
|
+
# Polls the agent for remote configuration, applies the response to
|
|
38
|
+
# the repository and dispatches the resulting changes to the receivers.
|
|
39
|
+
#
|
|
40
|
+
# The agent sends an empty response when it holds no targets newer
|
|
41
|
+
# than the targets version the client reported. Until the client
|
|
42
|
+
# applies a response that carries targets, it reports the initial
|
|
43
|
+
# targets version, so an empty response then means the agent has not
|
|
44
|
+
# received targets from the backend. The initial reconcile therefore
|
|
45
|
+
# waits for a response that carries targets.
|
|
46
|
+
#
|
|
47
|
+
# @return [void]
|
|
48
|
+
# @raise [TransportError] when the agent reports an internal error
|
|
49
|
+
# @raise [SyncError] when the response cannot be applied to the repository
|
|
30
50
|
def sync
|
|
31
51
|
# TODO: Skip sync if no capabilities are registered
|
|
32
52
|
response = transport.send_config(payload)
|
|
33
53
|
|
|
34
54
|
if response.ok?
|
|
35
|
-
process_response(response)
|
|
55
|
+
changes = process_response(response)
|
|
56
|
+
if pending_initial_reconcile && !response.empty?
|
|
57
|
+
dispatcher.reconcile(changes, repository)
|
|
58
|
+
@pending_initial_reconcile = false
|
|
59
|
+
end
|
|
36
60
|
elsif response.internal_error?
|
|
37
61
|
raise TransportError, response.to_s
|
|
38
62
|
end
|
|
63
|
+
nil
|
|
39
64
|
end
|
|
40
65
|
|
|
41
66
|
private
|
|
42
67
|
|
|
68
|
+
# Applies an OK response to the repository and returns the resulting
|
|
69
|
+
# change set, which is empty when the response is empty.
|
|
70
|
+
#
|
|
71
|
+
# @param response [Transport::Config::Response] the response returned by the agent
|
|
72
|
+
# @return [Configuration::Repository::ChangeSet] the changes applied by this response
|
|
73
|
+
# @raise [SyncError] when the response cannot be applied to the repository
|
|
43
74
|
def process_response(response)
|
|
44
75
|
# when response is completely empty, do nothing as in: leave as is
|
|
45
76
|
if response.empty?
|
|
46
77
|
logger.debug { "remote: empty response => NOOP" }
|
|
47
78
|
|
|
48
|
-
return
|
|
79
|
+
return Configuration::Repository::ChangeSet.new
|
|
49
80
|
end
|
|
50
81
|
|
|
51
82
|
begin
|
|
@@ -61,7 +92,7 @@ module Datadog
|
|
|
61
92
|
end
|
|
62
93
|
|
|
63
94
|
# To make sure steep does not complain
|
|
64
|
-
return unless paths && targets && contents
|
|
95
|
+
return Configuration::Repository::ChangeSet.new unless paths && targets && contents
|
|
65
96
|
|
|
66
97
|
# TODO: sometimes it can strangely be so that paths.empty?
|
|
67
98
|
# TODO: sometimes it can strangely be so that targets.empty?
|
|
@@ -69,6 +100,14 @@ module Datadog
|
|
|
69
100
|
apply_config(paths, targets, contents)
|
|
70
101
|
end
|
|
71
102
|
|
|
103
|
+
# Applies the response to the repository in a transaction, dispatches the
|
|
104
|
+
# resulting changes to the receivers and returns them.
|
|
105
|
+
#
|
|
106
|
+
# @param paths [Array<Configuration::Path>] ingress paths from the response
|
|
107
|
+
# @param targets [Configuration::TargetMap] targets keyed by path
|
|
108
|
+
# @param contents [Configuration::ContentList] contents from the response
|
|
109
|
+
# @return [Configuration::Repository::ChangeSet] the changes applied by the transaction
|
|
110
|
+
# @raise [SyncError] when a path has no matching target or content
|
|
72
111
|
def apply_config(paths, targets, contents)
|
|
73
112
|
changes = repository.transaction do |current, transaction|
|
|
74
113
|
# paths to be removed: previously applied paths minus ingress paths
|
|
@@ -121,6 +160,8 @@ module Datadog
|
|
|
121
160
|
else
|
|
122
161
|
dispatcher.dispatch(changes, repository)
|
|
123
162
|
end
|
|
163
|
+
|
|
164
|
+
changes
|
|
124
165
|
end
|
|
125
166
|
|
|
126
167
|
def payload # standard:disable Metrics/MethodLength
|