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
|
@@ -15,7 +15,7 @@ module Datadog
|
|
|
15
15
|
#
|
|
16
16
|
# @api private
|
|
17
17
|
class Component
|
|
18
|
-
attr_reader :logger, :
|
|
18
|
+
attr_reader :logger, :healthy, :worker
|
|
19
19
|
|
|
20
20
|
def initialize(settings, capabilities, agent_settings, logger:)
|
|
21
21
|
@logger = logger
|
|
@@ -28,6 +28,7 @@ module Datadog
|
|
|
28
28
|
|
|
29
29
|
@barrier = Barrier.new(settings.remote.boot_timeout_seconds)
|
|
30
30
|
|
|
31
|
+
@client_mutex = Mutex.new
|
|
31
32
|
@client = Client.new(@transport, @capabilities, settings: settings, logger: logger)
|
|
32
33
|
@healthy = false
|
|
33
34
|
logger.debug { "new remote configuration client: #{@client.id} products: #{@capabilities.products.sort.join(", ")}" }
|
|
@@ -40,7 +41,7 @@ module Datadog
|
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
begin
|
|
43
|
-
|
|
44
|
+
client.sync
|
|
44
45
|
@healthy ||= true
|
|
45
46
|
rescue Client::SyncError => e
|
|
46
47
|
# Transient errors due to network or agent. Logged the error but not via telemetry
|
|
@@ -60,9 +61,11 @@ module Datadog
|
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
# client state is unknown, state might be corrupted
|
|
63
|
-
|
|
64
|
+
new_client = @client_mutex.synchronize do
|
|
65
|
+
@client = Client.new(@transport, @capabilities, settings: settings, logger: logger)
|
|
66
|
+
end
|
|
64
67
|
@healthy = false
|
|
65
|
-
logger.debug { "new remote configuration client: #{
|
|
68
|
+
logger.debug { "new remote configuration client: #{new_client.id} products: #{@capabilities.products.sort.join(", ")}" }
|
|
66
69
|
|
|
67
70
|
# TODO: bail out if too many errors?
|
|
68
71
|
end
|
|
@@ -81,6 +84,10 @@ module Datadog
|
|
|
81
84
|
@worker.started?
|
|
82
85
|
end
|
|
83
86
|
|
|
87
|
+
def client
|
|
88
|
+
@client_mutex.synchronize { @client }
|
|
89
|
+
end
|
|
90
|
+
|
|
84
91
|
# If the worker is not initialized, initialize it.
|
|
85
92
|
#
|
|
86
93
|
# Then, waits for one client sync to be executed if `kind` is `:once`.
|
|
@@ -98,9 +105,11 @@ module Datadog
|
|
|
98
105
|
def after_fork
|
|
99
106
|
@worker.after_fork
|
|
100
107
|
@barrier = Barrier.new(@settings.remote.boot_timeout_seconds)
|
|
101
|
-
|
|
108
|
+
new_client = @client_mutex.synchronize do
|
|
109
|
+
@client = Client.new(@transport, @capabilities, settings: @settings, logger: @logger)
|
|
110
|
+
end
|
|
102
111
|
@healthy = false
|
|
103
|
-
logger.debug { "remote configuration client recreated after fork: #{
|
|
112
|
+
logger.debug { "remote configuration client recreated after fork: #{new_client.id} products: #{@capabilities.products.sort.join(", ")}" }
|
|
104
113
|
end
|
|
105
114
|
|
|
106
115
|
def add_products(*products)
|
|
@@ -111,6 +120,17 @@ module Datadog
|
|
|
111
120
|
@capabilities.remove_products(*products)
|
|
112
121
|
end
|
|
113
122
|
|
|
123
|
+
def register(capabilities:, products:, receivers:)
|
|
124
|
+
@client_mutex.synchronize do
|
|
125
|
+
@capabilities.register_runtime(
|
|
126
|
+
capabilities: capabilities,
|
|
127
|
+
products: products,
|
|
128
|
+
receivers: receivers,
|
|
129
|
+
)
|
|
130
|
+
@client.dispatcher.add_receivers(*receivers)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
114
134
|
# Barrier provides a mechanism to fence execution until a condition happens
|
|
115
135
|
class Barrier
|
|
116
136
|
def initialize(timeout = nil)
|
|
@@ -189,14 +209,10 @@ module Datadog
|
|
|
189
209
|
#
|
|
190
210
|
# Those checks are instead performed inside the worker loop.
|
|
191
211
|
# This allows users to upgrade their agent while keeping their application running.
|
|
192
|
-
def build(settings, agent_settings, logger:, telemetry
|
|
212
|
+
def build(settings, agent_settings, logger:, telemetry:)
|
|
193
213
|
return unless settings.remote.enabled
|
|
194
214
|
|
|
195
|
-
capabilities = Client::Capabilities.new(
|
|
196
|
-
settings,
|
|
197
|
-
telemetry,
|
|
198
|
-
open_feature_component_provider: open_feature_component_provider,
|
|
199
|
-
)
|
|
215
|
+
capabilities = Client::Capabilities.new(settings, telemetry: telemetry)
|
|
200
216
|
new(settings, capabilities, agent_settings, logger: logger)
|
|
201
217
|
end
|
|
202
218
|
end
|
|
@@ -5,24 +5,57 @@ module Datadog
|
|
|
5
5
|
module Remote
|
|
6
6
|
# Repository update dispatcher
|
|
7
7
|
class Dispatcher
|
|
8
|
-
attr_reader :receivers
|
|
9
|
-
|
|
10
8
|
def initialize(receivers)
|
|
11
|
-
@receivers = receivers
|
|
9
|
+
@receivers = receivers.dup
|
|
10
|
+
@mutex = Mutex.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def receivers
|
|
14
|
+
@mutex.synchronize { @receivers.dup }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_receivers(*receivers)
|
|
18
|
+
@mutex.synchronize do
|
|
19
|
+
receivers.each { |receiver| @receivers << receiver unless @receivers.include?(receiver) }
|
|
20
|
+
end
|
|
21
|
+
nil
|
|
12
22
|
end
|
|
13
23
|
|
|
14
24
|
def dispatch(changes, repository)
|
|
15
25
|
receivers.each do |receiver|
|
|
16
|
-
|
|
26
|
+
matching = matching_changes(changes, receiver)
|
|
27
|
+
|
|
28
|
+
receiver.call(repository, matching) if matching.any?
|
|
29
|
+
end
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Calls each receiver that declares initial-reconcile participation and has no
|
|
34
|
+
# matching change in `changes`, passing an empty change list.
|
|
35
|
+
def reconcile(changes, repository)
|
|
36
|
+
receivers.each do |receiver|
|
|
37
|
+
next unless receiver.initial_reconcile?
|
|
17
38
|
|
|
18
|
-
receiver.call(repository,
|
|
39
|
+
receiver.call(repository, []) unless any_matching?(changes, receiver)
|
|
19
40
|
end
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def matching_changes(changes, receiver)
|
|
47
|
+
changes.select { |change| receiver.match?(change.path) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def any_matching?(changes, receiver)
|
|
51
|
+
changes.any? { |change| receiver.match?(change.path) }
|
|
20
52
|
end
|
|
21
53
|
|
|
22
54
|
# Store Matcher and block to be executed on a match
|
|
23
55
|
class Receiver
|
|
24
|
-
def initialize(matcher, &block)
|
|
56
|
+
def initialize(matcher, initial_reconcile: false, &block)
|
|
25
57
|
@block = block
|
|
58
|
+
@initial_reconcile = initial_reconcile
|
|
26
59
|
@matcher = matcher
|
|
27
60
|
end
|
|
28
61
|
|
|
@@ -30,6 +63,14 @@ module Datadog
|
|
|
30
63
|
@matcher.match?(path)
|
|
31
64
|
end
|
|
32
65
|
|
|
66
|
+
# Whether #reconcile may call this receiver with an empty change
|
|
67
|
+
# list on the client's first sync. A participating receiver must
|
|
68
|
+
# act on the repository's current contents when the change list is
|
|
69
|
+
# empty.
|
|
70
|
+
def initial_reconcile?
|
|
71
|
+
@initial_reconcile
|
|
72
|
+
end
|
|
73
|
+
|
|
33
74
|
def call(repository, changes)
|
|
34
75
|
@block.call(repository, changes)
|
|
35
76
|
end
|
|
@@ -32,7 +32,7 @@ module Datadog
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Workers::Async::Thread settings
|
|
35
|
-
self.fork_policy = options.fetch(:fork_policy, Workers::Async::Thread::
|
|
35
|
+
self.fork_policy = options.fetch(:fork_policy, Workers::Async::Thread::FORK_POLICY_RESTART)
|
|
36
36
|
|
|
37
37
|
# Workers::IntervalLoop settings
|
|
38
38
|
self.loop_base_interval = options.fetch(:interval, DEFAULT_FLUSH_INTERVAL)
|
data/lib/datadog/di/boot.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "code_tracker"
|
|
|
7
7
|
require_relative "component"
|
|
8
8
|
require_relative "context"
|
|
9
9
|
require_relative "capture_limits"
|
|
10
|
+
require_relative "guardrails_telemetry"
|
|
10
11
|
require_relative "capture_expression"
|
|
11
12
|
require_relative "capture_expression_evaluator"
|
|
12
13
|
require_relative "instrumenter"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
class CaptureExpressionEvaluator
|
|
6
|
+
# The result of evaluating a probe's capture expressions: the
|
|
7
|
+
# serialized values and the evaluation errors.
|
|
8
|
+
class Result
|
|
9
|
+
# Initializes the result.
|
|
10
|
+
#
|
|
11
|
+
# @param serialized_values [Hash{String => untyped}] serialized values keyed by capture expression name
|
|
12
|
+
# @param errors [Array<EvaluationFailure>] evaluation errors of the expressions that failed to evaluate or serialize
|
|
13
|
+
# @return [void]
|
|
14
|
+
def initialize(serialized_values:, errors:)
|
|
15
|
+
@serialized_values = serialized_values
|
|
16
|
+
@errors = errors
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The serialized values keyed by capture expression name.
|
|
20
|
+
# @return [Hash{String => untyped}]
|
|
21
|
+
attr_reader :serialized_values
|
|
22
|
+
|
|
23
|
+
# The evaluation errors; one entry per expression that failed to
|
|
24
|
+
# evaluate or serialize.
|
|
25
|
+
# @return [Array<EvaluationFailure>]
|
|
26
|
+
attr_reader :errors
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -2,17 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "capture_expression"
|
|
4
4
|
require_relative "capture_limits"
|
|
5
|
+
require_relative "capture_expression_evaluator/result"
|
|
6
|
+
require_relative "evaluation_failure"
|
|
5
7
|
require_relative "fatal_exceptions"
|
|
8
|
+
require_relative "guardrails_telemetry"
|
|
9
|
+
require_relative "../core/utils/time"
|
|
6
10
|
|
|
7
11
|
module Datadog
|
|
8
12
|
module DI
|
|
9
13
|
class CaptureExpressionEvaluator
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
# Initializes the evaluator.
|
|
15
|
+
#
|
|
16
|
+
# @param settings [Datadog::Core::Configuration::Settings] tracer settings
|
|
17
|
+
# @param serializer [Serializer] serializer for captured expression values
|
|
18
|
+
# @param logger [DI::Logger] logger for evaluation failure diagnostics
|
|
19
|
+
# @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical capture-timeout skip metric
|
|
20
|
+
# @param telemetry [Datadog::Core::Telemetry::Component, nil] component evaluation failures are reported through
|
|
21
|
+
# @return [void]
|
|
22
|
+
def initialize(settings:, serializer:, logger:, guardrails_telemetry:, telemetry: nil)
|
|
13
23
|
@settings = settings
|
|
14
24
|
@serializer = serializer
|
|
15
25
|
@logger = logger
|
|
26
|
+
@guardrails_telemetry = guardrails_telemetry
|
|
16
27
|
@telemetry = telemetry
|
|
17
28
|
end
|
|
18
29
|
|
|
@@ -22,11 +33,25 @@ module Datadog
|
|
|
22
33
|
|
|
23
34
|
attr_reader :logger
|
|
24
35
|
|
|
36
|
+
# The guardrails metric emitter.
|
|
37
|
+
# @return [GuardrailsTelemetry]
|
|
38
|
+
attr_reader :guardrails_telemetry
|
|
39
|
+
|
|
25
40
|
attr_reader :telemetry
|
|
26
41
|
|
|
42
|
+
# Evaluates each of the probe's capture expressions against the given
|
|
43
|
+
# context, serializing the captured values under one capture deadline
|
|
44
|
+
# resolved through the serializer and shared across all expressions.
|
|
45
|
+
# An expression that fails to evaluate or serialize contributes an entry
|
|
46
|
+
# to the returned errors.
|
|
47
|
+
#
|
|
48
|
+
# @param probe [Probe] the probe whose capture expressions are evaluated
|
|
49
|
+
# @param context [Context] evaluation context the expressions are evaluated against
|
|
50
|
+
# @return [Result] the evaluation result: the serialized values keyed by
|
|
51
|
+
# capture expression name and the errors of the expressions that failed
|
|
52
|
+
# to evaluate or serialize
|
|
27
53
|
def evaluate(probe, context)
|
|
28
|
-
|
|
29
|
-
deadline_ns = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :nanosecond) + budget_ns
|
|
54
|
+
deadline = serializer.serialization_deadline
|
|
30
55
|
|
|
31
56
|
output = {}
|
|
32
57
|
evaluation_errors = []
|
|
@@ -34,9 +59,13 @@ module Datadog
|
|
|
34
59
|
probe.capture_expressions.each do |capture_expression|
|
|
35
60
|
name = capture_expression.name
|
|
36
61
|
|
|
37
|
-
if
|
|
62
|
+
if Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) >= deadline
|
|
38
63
|
output[name] = {notCapturedReason: "timeout"}
|
|
39
|
-
telemetry&.inc(TELEMETRY_NAMESPACE, "capture_expressions_skipped_by_timeout", 1)
|
|
64
|
+
telemetry&.inc(DI::TELEMETRY_NAMESPACE, "capture_expressions_skipped_by_timeout", 1)
|
|
65
|
+
guardrails_telemetry.skipped(
|
|
66
|
+
reason: GuardrailsTelemetry::Reason::EVALUATION_TIMEOUT,
|
|
67
|
+
probe: probe,
|
|
68
|
+
)
|
|
40
69
|
next
|
|
41
70
|
end
|
|
42
71
|
|
|
@@ -53,10 +82,11 @@ module Datadog
|
|
|
53
82
|
attribute_count: limits[:attribute_count],
|
|
54
83
|
length: limits[:length],
|
|
55
84
|
collection_size: limits[:collection_size],
|
|
85
|
+
deadline: deadline,
|
|
56
86
|
)
|
|
57
87
|
rescue Exception => exc # standard:disable Lint/RescueException
|
|
58
88
|
Datadog::DI.reraise_if_fatal(exc)
|
|
59
|
-
evaluation_errors <<
|
|
89
|
+
evaluation_errors << EvaluationFailure.new(expr: name, message: "#{exc.class}: #{exc.message}")
|
|
60
90
|
logger.debug do
|
|
61
91
|
"di: probe #{probe.id}: capture expression #{name}: evaluation failed: #{exc.class}: #{exc.message}"
|
|
62
92
|
end
|
|
@@ -64,7 +94,7 @@ module Datadog
|
|
|
64
94
|
end
|
|
65
95
|
end
|
|
66
96
|
|
|
67
|
-
|
|
97
|
+
Result.new(serialized_values: output, errors: evaluation_errors)
|
|
68
98
|
end
|
|
69
99
|
end
|
|
70
100
|
end
|
data/lib/datadog/di/component.rb
CHANGED
|
@@ -16,6 +16,16 @@ module Datadog
|
|
|
16
16
|
# resources and installed tracepoints upon shutdown.
|
|
17
17
|
class Component
|
|
18
18
|
class << self
|
|
19
|
+
# Builds and registers the DI component, or returns nil without
|
|
20
|
+
# building one when the settings have no dynamic_instrumentation
|
|
21
|
+
# section, DI is explicitly disabled, the runtime is unsupported,
|
|
22
|
+
# or Remote Configuration is disabled.
|
|
23
|
+
#
|
|
24
|
+
# @param settings [Datadog::Core::Configuration::Settings]
|
|
25
|
+
# @param agent_settings [Datadog::Core::Configuration::AgentSettings]
|
|
26
|
+
# @param logger [Core::Logger]
|
|
27
|
+
# @param telemetry [Core::Telemetry::Component, nil]
|
|
28
|
+
# @return [Component, nil] the built component, or nil when no component was built
|
|
19
29
|
def build(settings, agent_settings, logger, telemetry: nil)
|
|
20
30
|
return unless settings.respond_to?(:dynamic_instrumentation)
|
|
21
31
|
|
|
@@ -65,40 +75,66 @@ module Datadog
|
|
|
65
75
|
end
|
|
66
76
|
end
|
|
67
77
|
|
|
78
|
+
# Initializes the DI component.
|
|
79
|
+
#
|
|
80
|
+
# @param settings [Datadog::Core::Configuration::Settings] tracer settings
|
|
81
|
+
# @param agent_settings [Datadog::Core::Configuration::AgentSettings] agent connection settings for the transports
|
|
82
|
+
# @param logger [Datadog::Core::Logger] diagnostics logger, wrapped in the DI logger facade
|
|
83
|
+
# @param code_tracker [CodeTracker, nil] code tracker for line probe installation
|
|
84
|
+
# @param telemetry [Datadog::Core::Telemetry::Component, nil] component DI errors and guardrails metrics are emitted through
|
|
85
|
+
# @return [void]
|
|
68
86
|
def initialize(settings, agent_settings, logger, code_tracker: nil, telemetry: nil)
|
|
69
87
|
@settings = settings
|
|
70
88
|
@agent_settings = agent_settings
|
|
71
89
|
logger = DI::Logger.new(settings, logger)
|
|
72
90
|
@logger = logger
|
|
73
91
|
@telemetry = telemetry
|
|
92
|
+
@guardrails_telemetry = GuardrailsTelemetry.new(settings: settings, logger: logger, telemetry: telemetry)
|
|
74
93
|
@code_tracker = code_tracker
|
|
75
94
|
@redactor = Redactor.new(settings)
|
|
76
|
-
@serializer = Serializer.new(settings, redactor,
|
|
77
|
-
|
|
95
|
+
@serializer = Serializer.new(settings, redactor, logger: logger,
|
|
96
|
+
guardrails_telemetry: guardrails_telemetry, telemetry: telemetry)
|
|
97
|
+
@capture_expression_evaluator = CaptureExpressionEvaluator.new(
|
|
98
|
+
settings: settings, serializer: serializer, logger: logger,
|
|
99
|
+
guardrails_telemetry: guardrails_telemetry, telemetry: telemetry,
|
|
100
|
+
)
|
|
101
|
+
@instrumenter = Instrumenter.new(settings, serializer, logger, code_tracker: code_tracker, telemetry: telemetry,
|
|
102
|
+
guardrails_telemetry: guardrails_telemetry,
|
|
103
|
+
capture_expression_evaluator: capture_expression_evaluator,)
|
|
78
104
|
@probe_repository = ProbeRepository.new
|
|
79
|
-
@probe_notification_builder = ProbeNotificationBuilder.new(settings, serializer, logger,
|
|
105
|
+
@probe_notification_builder = ProbeNotificationBuilder.new(settings, serializer, logger,
|
|
106
|
+
capture_expression_evaluator: capture_expression_evaluator,)
|
|
80
107
|
@probe_notifier_worker = ProbeNotifierWorker.new(
|
|
81
108
|
settings, logger,
|
|
82
109
|
agent_settings: agent_settings,
|
|
83
110
|
probe_repository: probe_repository,
|
|
84
111
|
probe_notification_builder: probe_notification_builder,
|
|
85
112
|
telemetry: telemetry,
|
|
113
|
+
guardrails_telemetry: guardrails_telemetry,
|
|
86
114
|
)
|
|
87
115
|
@probe_manager = ProbeManager.new(
|
|
88
116
|
settings, instrumenter, probe_notification_builder, probe_notifier_worker, logger, probe_repository,
|
|
89
117
|
telemetry: telemetry,
|
|
118
|
+
guardrails_telemetry: guardrails_telemetry,
|
|
90
119
|
)
|
|
91
120
|
# @started transitions are serialized by @lifecycle_mutex so that
|
|
92
121
|
# concurrent RC callbacks (which run on the remote-config thread)
|
|
93
122
|
# cannot race a foreground start! with a background stop!.
|
|
94
123
|
@lifecycle_mutex = Mutex.new
|
|
95
124
|
@started = false
|
|
125
|
+
@rc_enablement_signal_paths = []
|
|
96
126
|
end
|
|
97
127
|
|
|
98
128
|
attr_reader :settings
|
|
99
129
|
attr_reader :agent_settings
|
|
100
130
|
attr_reader :logger
|
|
101
131
|
attr_reader :telemetry
|
|
132
|
+
# The guardrails metric emitter.
|
|
133
|
+
# @return [GuardrailsTelemetry]
|
|
134
|
+
attr_reader :guardrails_telemetry
|
|
135
|
+
# The capture expression evaluator.
|
|
136
|
+
# @return [CaptureExpressionEvaluator]
|
|
137
|
+
attr_reader :capture_expression_evaluator
|
|
102
138
|
attr_reader :code_tracker
|
|
103
139
|
attr_reader :instrumenter
|
|
104
140
|
attr_reader :probe_repository
|
|
@@ -166,6 +202,75 @@ module Datadog
|
|
|
166
202
|
@started
|
|
167
203
|
end
|
|
168
204
|
|
|
205
|
+
# Whether the component is started while the customer left
|
|
206
|
+
# DD_DYNAMIC_INSTRUMENTATION_ENABLED at its default, the state produced
|
|
207
|
+
# by remote-configuration enablement.
|
|
208
|
+
#
|
|
209
|
+
# An explicit `enabled = false` takes precedence over a prior RC
|
|
210
|
+
# enablement: once the customer sets the option, this returns false
|
|
211
|
+
# even while the component is still started.
|
|
212
|
+
#
|
|
213
|
+
# @return [Boolean]
|
|
214
|
+
def implicitly_enabled?
|
|
215
|
+
started? && settings.dynamic_instrumentation.using_default?(:enabled)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Whether an APM_TRACING merge that omitted dynamic_instrumentation_enabled
|
|
219
|
+
# withdraws this component's RC enable signal, so the component must stop.
|
|
220
|
+
#
|
|
221
|
+
# Returns true when the component is implicitly enabled and no unparseable
|
|
222
|
+
# content can be the signal carrier. A content at a recorded signal path can be
|
|
223
|
+
# the carrier. When no paths are recorded, every unparseable content can be the
|
|
224
|
+
# carrier.
|
|
225
|
+
#
|
|
226
|
+
# @param unparseable_contents [Array<Core::Remote::Configuration::Content>] the APM_TRACING contents that failed to parse at this merge
|
|
227
|
+
# @return [Boolean]
|
|
228
|
+
def rc_enablement_signal_withdrawn?(unparseable_contents)
|
|
229
|
+
return false unless implicitly_enabled?
|
|
230
|
+
|
|
231
|
+
recorded_signal_paths = rc_enablement_signal_paths
|
|
232
|
+
signal_source_unparseable =
|
|
233
|
+
if recorded_signal_paths.empty?
|
|
234
|
+
unparseable_contents.any?
|
|
235
|
+
else
|
|
236
|
+
unparseable_contents.any? { |content| recorded_signal_paths.include?(content.path) }
|
|
237
|
+
end
|
|
238
|
+
if signal_source_unparseable
|
|
239
|
+
if recorded_signal_paths.empty?
|
|
240
|
+
logger.trace do
|
|
241
|
+
"APM_TRACING RC: dynamic_instrumentation_enabled absent while an APM_TRACING config " \
|
|
242
|
+
"failed to parse; the enable signal carrier is unknown (no signal paths recorded), " \
|
|
243
|
+
"keeping the DI state"
|
|
244
|
+
end
|
|
245
|
+
else
|
|
246
|
+
logger.trace do
|
|
247
|
+
"APM_TRACING RC: dynamic_instrumentation_enabled absent while the config that carried " \
|
|
248
|
+
"the enable signal failed to parse; keeping the DI state"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
false
|
|
252
|
+
else
|
|
253
|
+
logger.trace { "APM_TRACING RC: dynamic_instrumentation_enabled withdrawn; treating as false" }
|
|
254
|
+
true
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The repository paths of the APM_TRACING contents that supplied the
|
|
259
|
+
# dynamic_instrumentation_enabled value at the last merge where the field was
|
|
260
|
+
# present. The component holds the paths so that they outlive the RC client.
|
|
261
|
+
#
|
|
262
|
+
# @return [Array<Core::Remote::Configuration::Path>]
|
|
263
|
+
attr_reader :rc_enablement_signal_paths
|
|
264
|
+
|
|
265
|
+
# Replaces the recorded enable-signal carrier paths.
|
|
266
|
+
#
|
|
267
|
+
# @param paths [Array<Core::Remote::Configuration::Path>] the carrier paths, empty when the merged field was absent
|
|
268
|
+
# @return [void]
|
|
269
|
+
def record_rc_enablement_signal_paths(paths)
|
|
270
|
+
@rc_enablement_signal_paths = paths.dup
|
|
271
|
+
nil
|
|
272
|
+
end
|
|
273
|
+
|
|
169
274
|
# Shuts down dynamic instrumentation permanently.
|
|
170
275
|
#
|
|
171
276
|
# Removes all code hooks and stops background threads.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../core/configuration/stable_config"
|
|
4
|
+
|
|
3
5
|
module Datadog
|
|
4
6
|
module DI
|
|
5
7
|
# Configuration for DI
|
|
@@ -136,8 +138,35 @@ module Datadog
|
|
|
136
138
|
|
|
137
139
|
option :max_time_to_serialize_ms do |o|
|
|
138
140
|
o.type :int
|
|
139
|
-
o.
|
|
140
|
-
o.
|
|
141
|
+
o.env "DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS"
|
|
142
|
+
o.default do
|
|
143
|
+
legacy = Settings.legacy_capture_timeout_value
|
|
144
|
+
if legacy
|
|
145
|
+
Datadog::Core.log_deprecation(key: :di_legacy_max_time_to_serialize) do
|
|
146
|
+
"The DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE environment variable is deprecated " \
|
|
147
|
+
"and will be removed in a future release. " \
|
|
148
|
+
"Please use DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS instead."
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
begin
|
|
152
|
+
Integer(legacy, 10)
|
|
153
|
+
rescue ArgumentError
|
|
154
|
+
raise ArgumentError,
|
|
155
|
+
"Expected environment variable DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE " \
|
|
156
|
+
"to be an int, but '#{legacy}' was provided."
|
|
157
|
+
end
|
|
158
|
+
else
|
|
159
|
+
150
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
o.setter do |value, _old|
|
|
163
|
+
if value < 0
|
|
164
|
+
raise ArgumentError,
|
|
165
|
+
"The setting `dynamic_instrumentation.max_time_to_serialize_ms` must not be negative, " \
|
|
166
|
+
"but `#{value.inspect}` was provided."
|
|
167
|
+
end
|
|
168
|
+
value
|
|
169
|
+
end
|
|
141
170
|
end
|
|
142
171
|
|
|
143
172
|
# Settings in the 'internal' group are for internal Datadog
|
|
@@ -244,6 +273,28 @@ module Datadog
|
|
|
244
273
|
end
|
|
245
274
|
end
|
|
246
275
|
end
|
|
276
|
+
|
|
277
|
+
# Resolves the deprecated capture-timeout name for the setting's
|
|
278
|
+
# default. The legacy value applies only when the canonical variable
|
|
279
|
+
# is configured nowhere (environment with its registry alias, local or
|
|
280
|
+
# fleet stable configuration), and the legacy itself resolves in the
|
|
281
|
+
# option machinery's precedence order: fleet stable configuration,
|
|
282
|
+
# environment, local stable configuration.
|
|
283
|
+
def self.legacy_capture_timeout_value
|
|
284
|
+
canonical_name = "DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS"
|
|
285
|
+
legacy_name = "DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE"
|
|
286
|
+
|
|
287
|
+
stable = Datadog::Core::Configuration::StableConfig.configuration
|
|
288
|
+
local = stable&.dig(:local, :config)
|
|
289
|
+
fleet = stable&.dig(:fleet, :config)
|
|
290
|
+
|
|
291
|
+
canonical = (fleet && DATADOG_ENV.get_environment_variable(canonical_name, source_env: fleet)) ||
|
|
292
|
+
DATADOG_ENV[canonical_name] ||
|
|
293
|
+
(local && DATADOG_ENV.get_environment_variable(canonical_name, source_env: local))
|
|
294
|
+
return nil if canonical
|
|
295
|
+
|
|
296
|
+
(fleet && fleet[legacy_name]) || DATADOG_ENV[legacy_name] || (local && local[legacy_name])
|
|
297
|
+
end
|
|
247
298
|
end
|
|
248
299
|
end
|
|
249
300
|
end
|
data/lib/datadog/di/context.rb
CHANGED
|
@@ -61,9 +61,14 @@ module Datadog
|
|
|
61
61
|
# Exception raised by the method, if any, for a method probe
|
|
62
62
|
attr_reader :exception
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
# Returns the locals captured at the probe hit in serialized form,
|
|
65
|
+
# or nil when no locals were captured.
|
|
66
|
+
#
|
|
67
|
+
# @param serialization_deadline [Float] absolute capture deadline in monotonic clock seconds
|
|
68
|
+
# @return [Hash{Symbol => Hash}, nil] serialized locals keyed by variable name
|
|
69
|
+
def serialized_locals(serialization_deadline:)
|
|
65
70
|
# TODO cache?
|
|
66
|
-
locals && serializer.serialize_vars(locals, **probe.snapshot_serializer_limits(settings))
|
|
71
|
+
locals && serializer.serialize_vars(locals, deadline: serialization_deadline, **probe.snapshot_serializer_limits(settings))
|
|
67
72
|
end
|
|
68
73
|
|
|
69
74
|
def fetch(var_name)
|
|
@@ -10,7 +10,7 @@ Datadog::DI::Serializer.register(
|
|
|
10
10
|
# logic for "instances of classes derived from X", but a condition Proc
|
|
11
11
|
# is more universal.
|
|
12
12
|
condition: lambda { |value| ActiveRecord::Base === value }
|
|
13
|
-
) do |serializer, value, name:, depth:|
|
|
13
|
+
) do |serializer, value, name:, depth:, deadline:|
|
|
14
14
|
# +serializer+ is an instance of DI::Serializer.
|
|
15
15
|
# Use it to perform the serialization to primitive values.
|
|
16
16
|
#
|
|
@@ -30,12 +30,18 @@ Datadog::DI::Serializer.register(
|
|
|
30
30
|
# nesting levels in the output.
|
|
31
31
|
# This serializer could also potentially do its own depth limiting.
|
|
32
32
|
#
|
|
33
|
+
# +deadline+ is the absolute monotonic-clock deadline (in seconds) of the
|
|
34
|
+
# capture point that is being serialized. Pass it through to
|
|
35
|
+
# +serialize_value+ so the custom-serialized subtree draws down the same
|
|
36
|
+
# capture budget as the rest of the snapshot.
|
|
37
|
+
#
|
|
33
38
|
# Steep: steep thinks all of the arguments are nil here
|
|
34
39
|
# Looks like it cannot handle kwargs in lambdas
|
|
35
40
|
# @type var depth: Integer
|
|
41
|
+
# @type var deadline: Float
|
|
36
42
|
value_to_serialize = {
|
|
37
43
|
attributes: value.attributes,
|
|
38
44
|
new_record: value.new_record?,
|
|
39
45
|
}
|
|
40
|
-
serializer.serialize_value(value_to_serialize, depth: depth, type: value.class)
|
|
46
|
+
serializer.serialize_value(value_to_serialize, depth: depth, type: value.class, deadline: deadline)
|
|
41
47
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
# An expression evaluation failure reported in a snapshot's evaluation
|
|
6
|
+
# errors: the expression that failed and the error description.
|
|
7
|
+
# @api private
|
|
8
|
+
class EvaluationFailure
|
|
9
|
+
# Initializes the failure.
|
|
10
|
+
#
|
|
11
|
+
# @param expr [String] name of the capture expression, or source of the condition or template expression, that failed to evaluate or serialize
|
|
12
|
+
# @param message [String] error description of the failed evaluation
|
|
13
|
+
# @return [void]
|
|
14
|
+
def initialize(expr:, message:)
|
|
15
|
+
@expr = expr
|
|
16
|
+
@message = message
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The name of the capture expression, or the source of the condition or
|
|
20
|
+
# template expression, that failed to evaluate or serialize.
|
|
21
|
+
# @return [String]
|
|
22
|
+
attr_reader :expr
|
|
23
|
+
|
|
24
|
+
# @return [String] the error description of the failed evaluation
|
|
25
|
+
attr_reader :message
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|