datadog 2.36.0 → 2.37.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 +46 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +5 -12
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +6 -3
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +17 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +219 -98
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +2 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +11 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +7 -0
- data/ext/libdatadog_api/di.c +76 -0
- data/ext/libdatadog_api/extconf.rb +6 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/api_client.rb +6 -4
- data/lib/datadog/appsec/api_security/route_extractor.rb +7 -4
- data/lib/datadog/appsec/configuration.rb +6 -0
- data/lib/datadog/appsec/context.rb +4 -2
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +59 -4
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +59 -5
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -0
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +4 -17
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +21 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +2 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +60 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +4 -0
- data/lib/datadog/appsec/ext.rb +1 -0
- data/lib/datadog/appsec/metrics/collector.rb +18 -1
- data/lib/datadog/appsec/metrics/exporter.rb +9 -3
- data/lib/datadog/appsec/remote.rb +9 -3
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/core/configuration/components.rb +82 -2
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/settings.rb +0 -3
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/remote/client/capabilities.rb +34 -7
- data/lib/datadog/core/telemetry/event/app_started.rb +8 -1
- data/lib/datadog/di/base.rb +3 -1
- data/lib/datadog/di/code_tracker.rb +5 -2
- data/lib/datadog/di/component.rb +110 -36
- data/lib/datadog/di/error.rb +10 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +381 -165
- data/lib/datadog/di/probe.rb +14 -0
- data/lib/datadog/di/probe_file_loader.rb +8 -3
- data/lib/datadog/di/probe_manager.rb +67 -9
- data/lib/datadog/di/probe_notification_builder.rb +4 -1
- data/lib/datadog/di/probe_notifier_worker.rb +52 -32
- data/lib/datadog/di/redactor.rb +16 -1
- data/lib/datadog/di/remote.rb +175 -14
- data/lib/datadog/di/serializer.rb +12 -5
- data/lib/datadog/di/transport/input.rb +8 -4
- data/lib/datadog/di.rb +63 -0
- data/lib/datadog/kit/appsec/events/v2.rb +60 -2
- data/lib/datadog/open_feature/component.rb +29 -6
- data/lib/datadog/open_feature/configuration.rb +8 -0
- data/lib/datadog/open_feature/ext.rb +2 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
- data/lib/datadog/open_feature/hooks/{flag_eval_hook.rb → flag_eval_metrics_hook.rb} +1 -1
- data/lib/datadog/open_feature/native_evaluator.rb +22 -0
- data/lib/datadog/open_feature/provider.rb +59 -27
- data/lib/datadog/open_feature/transport.rb +55 -1
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +5 -12
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +4 -11
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
- data/lib/datadog/symbol_database/component.rb +164 -47
- data/lib/datadog/symbol_database/configuration.rb +12 -4
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +124 -87
- data/lib/datadog/symbol_database/file_hash.rb +3 -1
- data/lib/datadog/symbol_database/remote.rb +8 -3
- data/lib/datadog/symbol_database/scope_batcher.rb +7 -3
- data/lib/datadog/symbol_database/uploader.rb +9 -2
- data/lib/datadog/symbol_database.rb +22 -0
- data/lib/datadog/tracing/distributed/baggage.rb +2 -1
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/remote.rb +34 -3
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +1 -0
- metadata +19 -6
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
|
|
5
|
+
require_relative 'fatal_exceptions'
|
|
6
|
+
|
|
5
7
|
module Datadog
|
|
6
8
|
module DI
|
|
7
9
|
module ProbeFileLoader
|
|
@@ -47,7 +49,8 @@ module Datadog
|
|
|
47
49
|
|
|
48
50
|
payload = component.probe_notification_builder.build_errored(probe, exc)
|
|
49
51
|
component.probe_notifier_worker.add_status(payload, probe: probe)
|
|
50
|
-
rescue => exc
|
|
52
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
53
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
51
54
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
52
55
|
|
|
53
56
|
component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI probe file loader: #{exc.class}: #{exc.message}" }
|
|
@@ -58,7 +61,8 @@ module Datadog
|
|
|
58
61
|
component.probe_notifier_worker.add_status(payload, probe: probe)
|
|
59
62
|
end
|
|
60
63
|
end
|
|
61
|
-
rescue => exc
|
|
64
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
65
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
62
66
|
if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
63
67
|
should_propagate = true
|
|
64
68
|
raise
|
|
@@ -67,7 +71,8 @@ module Datadog
|
|
|
67
71
|
component.logger.debug { "di: unhandled exception handling a probe in DI probe file loader: #{exc.class}: #{exc.message}" }
|
|
68
72
|
component.telemetry&.report(exc, description: "Unhandled exception handling probe in DI probe file loader")
|
|
69
73
|
end
|
|
70
|
-
rescue
|
|
74
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
75
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
71
76
|
# We should generally never get here, but if component tree
|
|
72
77
|
# initialization fails for some unexpected reason, don't nuke
|
|
73
78
|
# the customer application.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# rubocop:disable Lint/AssignmentInCondition
|
|
4
4
|
|
|
5
|
+
require_relative 'fatal_exceptions'
|
|
6
|
+
|
|
5
7
|
module Datadog
|
|
6
8
|
module DI
|
|
7
9
|
# Orchestrates probe lifecycle: installation, removal, and execution callbacks.
|
|
@@ -26,13 +28,13 @@ module Datadog
|
|
|
26
28
|
@telemetry = telemetry
|
|
27
29
|
@probe_repository = probe_repository
|
|
28
30
|
|
|
29
|
-
@definition_trace_point = TracePoint.
|
|
31
|
+
@definition_trace_point = TracePoint.new(:end) do |tp|
|
|
30
32
|
install_pending_method_probes(tp.self)
|
|
31
|
-
rescue => exc
|
|
33
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
34
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
32
35
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
33
36
|
logger.debug { "di: unhandled exception in definition trace point: #{exc.class}: #{exc.message}" }
|
|
34
37
|
telemetry&.report(exc, description: "Unhandled exception in definition trace point")
|
|
35
|
-
# TODO test this path
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
|
|
@@ -40,15 +42,68 @@ module Datadog
|
|
|
40
42
|
attr_reader :telemetry
|
|
41
43
|
attr_reader :probe_repository
|
|
42
44
|
|
|
45
|
+
# Stops the probe manager without permanently releasing resources.
|
|
46
|
+
#
|
|
47
|
+
# Disables the class definition trace point and unhooks all installed
|
|
48
|
+
# probe instrumentation, but preserves the probe repository so that
|
|
49
|
+
# {#reopen} can re-install probes. Can be reversed by calling {#reopen}.
|
|
50
|
+
#
|
|
51
|
+
# Why preserve the repository: RC dispatch only delivers LIVE_DEBUGGING
|
|
52
|
+
# changes when the content hash changes. If a customer toggles DI off
|
|
53
|
+
# then back on in the UI with the same probe content, the receiver
|
|
54
|
+
# is not re-invoked, so probes wiped here would never be redelivered.
|
|
55
|
+
# Preserving the repo lets {#reopen} replay the probes locally.
|
|
56
|
+
#
|
|
57
|
+
# @return [void]
|
|
58
|
+
def stop
|
|
59
|
+
definition_trace_point.disable
|
|
60
|
+
probe_repository.synchronize do
|
|
61
|
+
probe_repository.installed_probes.each_value do |probe|
|
|
62
|
+
instrumenter.unhook(probe)
|
|
63
|
+
rescue => exc
|
|
64
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
65
|
+
logger.debug { "di: error unhooking #{probe.type} probe at #{probe.location} (#{probe.id}) on stop: #{exc.class}: #{exc.message}" }
|
|
66
|
+
telemetry&.report(exc, description: "Error unhooking probe on stop")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Re-enables the probe manager after a {#stop}.
|
|
72
|
+
#
|
|
73
|
+
# Re-enables the class definition trace point so that pending
|
|
74
|
+
# method probes can be installed when their target classes are defined,
|
|
75
|
+
# and re-hooks all previously-installed probes from the repository.
|
|
76
|
+
# If a probe's target is no longer present (e.g. code was unloaded
|
|
77
|
+
# between stop and reopen), it is demoted to pending so the trace
|
|
78
|
+
# point or code tracker can re-install it if the target reappears.
|
|
79
|
+
#
|
|
80
|
+
# @return [void]
|
|
81
|
+
def reopen
|
|
82
|
+
definition_trace_point.enable
|
|
83
|
+
probe_repository.synchronize do
|
|
84
|
+
# values.each so concurrent mutation of @installed_probes (via
|
|
85
|
+
# remove_installed below) does not iterate over a stale key set.
|
|
86
|
+
probe_repository.installed_probes.values.each do |probe|
|
|
87
|
+
instrumenter.hook(probe, self)
|
|
88
|
+
rescue Error::DITargetNotDefined
|
|
89
|
+
probe_repository.remove_installed(probe.id)
|
|
90
|
+
probe_repository.add_pending(probe)
|
|
91
|
+
logger.trace { "di: target for #{probe.type} probe (#{probe.id}) no longer defined on reopen, demoting to pending" }
|
|
92
|
+
rescue => exc
|
|
93
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
94
|
+
logger.debug { "di: error re-hooking #{probe.type} probe at #{probe.location} (#{probe.id}) on reopen: #{exc.class}: #{exc.message}" }
|
|
95
|
+
telemetry&.report(exc, description: "Error re-hooking probe on reopen")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
43
100
|
# Shuts down the probe manager and releases all resources.
|
|
44
101
|
#
|
|
45
102
|
# Disables the class definition trace point and removes all installed
|
|
46
103
|
# probe instrumentation. Called during component teardown.
|
|
104
|
+
# Unlike {#stop}, this is not reversible.
|
|
47
105
|
#
|
|
48
106
|
# @return [void]
|
|
49
|
-
#
|
|
50
|
-
# TODO test that close is called during component teardown and
|
|
51
|
-
# the trace point is cleared
|
|
52
107
|
def close
|
|
53
108
|
definition_trace_point.disable
|
|
54
109
|
clear_hooks
|
|
@@ -129,7 +184,8 @@ module Datadog
|
|
|
129
184
|
false
|
|
130
185
|
end
|
|
131
186
|
end
|
|
132
|
-
rescue => exc
|
|
187
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
188
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
133
189
|
# In "propagate all exceptions" mode we will try to instrument again.
|
|
134
190
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
135
191
|
|
|
@@ -160,7 +216,8 @@ module Datadog
|
|
|
160
216
|
begin
|
|
161
217
|
instrumenter.unhook(probe)
|
|
162
218
|
probe_repository.remove_installed(probe_id)
|
|
163
|
-
rescue => exc
|
|
219
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
220
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
164
221
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
165
222
|
# Silence all exceptions?
|
|
166
223
|
# TODO should we propagate here and rescue upstream?
|
|
@@ -190,7 +247,8 @@ module Datadog
|
|
|
190
247
|
break
|
|
191
248
|
rescue Error::DITargetNotDefined
|
|
192
249
|
# This should not happen... try installing again later?
|
|
193
|
-
rescue => exc
|
|
250
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
251
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
194
252
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
195
253
|
|
|
196
254
|
logger.debug { "di: error installing #{probe.type} probe at #{probe.location} (#{probe.id}) after class is defined: #{exc.class}: #{exc.message}" }
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# rubocop:disable Lint/AssignmentInCondition
|
|
4
4
|
|
|
5
|
+
require_relative 'fatal_exceptions'
|
|
6
|
+
|
|
5
7
|
module Datadog
|
|
6
8
|
module DI
|
|
7
9
|
# Builds probe status notification and snapshot payloads.
|
|
@@ -366,7 +368,8 @@ module Datadog
|
|
|
366
368
|
else
|
|
367
369
|
raise ArgumentError, "Invalid template segment type: #{segment}"
|
|
368
370
|
end
|
|
369
|
-
rescue => exc
|
|
371
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
372
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
370
373
|
evaluation_errors << {
|
|
371
374
|
message: "#{exc.class}: #{exc.message}",
|
|
372
375
|
expr: segment.dsl_expr, # steep:ignore NoMethod
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../core/semaphore'
|
|
4
|
+
require_relative 'fatal_exceptions'
|
|
4
5
|
|
|
5
6
|
module Datadog
|
|
6
7
|
module DI
|
|
@@ -62,6 +63,7 @@ module Datadog
|
|
|
62
63
|
# @return [void]
|
|
63
64
|
def start
|
|
64
65
|
return if @thread && @pid == Process.pid
|
|
66
|
+
@stop_requested = false
|
|
65
67
|
logger.trace { "di: starting probe notifier: pid #{$$}" }
|
|
66
68
|
@thread = Thread.new do
|
|
67
69
|
loop do
|
|
@@ -92,10 +94,11 @@ module Datadog
|
|
|
92
94
|
|
|
93
95
|
begin
|
|
94
96
|
more = maybe_send
|
|
95
|
-
rescue => exc
|
|
97
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
98
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
96
99
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
97
100
|
|
|
98
|
-
logger.debug { "di: error in probe notifier worker: #{exc.class}: #{exc.message} (at #{exc.backtrace
|
|
101
|
+
logger.debug { "di: error in probe notifier worker: #{exc.class}: #{exc.message} (at #{exc.backtrace&.first})" }
|
|
99
102
|
telemetry&.report(exc, description: "Error in probe notifier worker")
|
|
100
103
|
end
|
|
101
104
|
@lock.synchronize do
|
|
@@ -257,40 +260,56 @@ module Datadog
|
|
|
257
260
|
# if it has been more than 1 second since the last send of the same
|
|
258
261
|
# event type.
|
|
259
262
|
define_method("add_#{event_type}") do |event, probe: nil|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
263
|
+
# Enqueueing reads DI configuration and writes debug/trace logs, both
|
|
264
|
+
# of which can call stdlib methods (e.g. Set#include? via the
|
|
265
|
+
# configuration layer's supported-configuration lookup). If a customer
|
|
266
|
+
# has a method probe on such a method, firing it here would re-enter
|
|
267
|
+
# the worker through probe_executed_callback and deadlock on @lock
|
|
268
|
+
# (recursive locking). Hold the re-entrancy guard for the duration so
|
|
269
|
+
# those DI-internal calls reach the original method without firing the
|
|
270
|
+
# probe. Restore the prior state afterwards so a caller that already
|
|
271
|
+
# holds the guard (e.g. run_method_probe post-processing calling
|
|
272
|
+
# add_snapshot) is not cleared early.
|
|
273
|
+
was_in_probe = Datadog::DI.in_probe?
|
|
274
|
+
Datadog::DI.enter_probe unless was_in_probe
|
|
275
|
+
begin
|
|
276
|
+
@lock.synchronize do
|
|
277
|
+
queue = send("#{event_type}_queue")
|
|
278
|
+
if queue.length > settings.dynamic_instrumentation.internal.snapshot_queue_capacity
|
|
279
|
+
if event_type == :status && probe
|
|
280
|
+
status = event.dig(:debugger, :diagnostics, :status)
|
|
281
|
+
logger.debug { "di: dropping status for #{probe.type} probe at #{probe.location} (#{probe.id}): #{status} because queue is full" }
|
|
282
|
+
else
|
|
283
|
+
logger.debug { "di: #{self.class.name}: dropping #{event_type} event because queue is full" }
|
|
284
|
+
end
|
|
273
285
|
else
|
|
274
|
-
|
|
286
|
+
if event_type == :status && probe
|
|
287
|
+
status = event.dig(:debugger, :diagnostics, :status)
|
|
288
|
+
logger.trace { "di: queueing status for #{probe.type} probe at #{probe.location} (#{probe.id}): #{status}" }
|
|
289
|
+
else
|
|
290
|
+
logger.trace { "di: #{self.class.name}: queueing #{event_type} event" }
|
|
291
|
+
end
|
|
292
|
+
queue << event
|
|
275
293
|
end
|
|
276
|
-
queue << event
|
|
277
294
|
end
|
|
278
|
-
end
|
|
279
295
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
296
|
+
# Figure out whether to wake up the worker thread.
|
|
297
|
+
# If minimum send interval has elapsed since the last send,
|
|
298
|
+
# wake up immediately.
|
|
299
|
+
@lock.synchronize do
|
|
300
|
+
unless @wake_scheduled
|
|
301
|
+
@wake_scheduled = true
|
|
302
|
+
set_sleep_remaining
|
|
303
|
+
wake.signal
|
|
304
|
+
end
|
|
288
305
|
end
|
|
289
|
-
end
|
|
290
306
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
307
|
+
# Worker could be not running if the process forked - check and
|
|
308
|
+
# start it again in this case.
|
|
309
|
+
start
|
|
310
|
+
ensure
|
|
311
|
+
Datadog::DI.leave_probe unless was_in_probe
|
|
312
|
+
end
|
|
294
313
|
end
|
|
295
314
|
|
|
296
315
|
public "add_#{event_type}"
|
|
@@ -318,9 +337,10 @@ module Datadog
|
|
|
318
337
|
@lock.synchronize do
|
|
319
338
|
@last_sent = time
|
|
320
339
|
end
|
|
321
|
-
rescue => exc
|
|
340
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
341
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
322
342
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
323
|
-
logger.debug { "di: failed to send #{event_name}: #{exc.class}: #{exc.message} (at #{exc.backtrace
|
|
343
|
+
logger.debug { "di: failed to send #{event_name}: #{exc.class}: #{exc.message} (at #{exc.backtrace&.first})" }
|
|
324
344
|
telemetry&.report(exc, description: "Error sending #{event_type}")
|
|
325
345
|
end
|
|
326
346
|
end
|
data/lib/datadog/di/redactor.rb
CHANGED
|
@@ -186,8 +186,23 @@ module Datadog
|
|
|
186
186
|
]
|
|
187
187
|
|
|
188
188
|
# Input can be a string or a symbol.
|
|
189
|
+
#
|
|
190
|
+
# Hash keys captured in snapshots often originate from a Rack environment
|
|
191
|
+
# (Rack/Rails/Sinatra middleware and controllers all keep `env` in scope).
|
|
192
|
+
# Rack rewrites incoming HTTP header names to the CGI form
|
|
193
|
+
# `HTTP_<HEADER>` with dashes converted to underscores — for example,
|
|
194
|
+
# `Authorization` becomes `HTTP_AUTHORIZATION`, `Cookie` becomes
|
|
195
|
+
# `HTTP_COOKIE`, `X-API-Key` becomes `HTTP_X_API_KEY`. Without stripping
|
|
196
|
+
# the prefix here, those keys normalize to `httpauthorization`,
|
|
197
|
+
# `httpcookie`, `httpxapikey`, etc., none of which appear in the default
|
|
198
|
+
# identifier list — so the values would not be matched against entries
|
|
199
|
+
# like `authorization`, `cookie`, or `xapikey`. The `\Ahttp_` anchor
|
|
200
|
+
# limits the strip to the literal CGI form, so identifiers that merely
|
|
201
|
+
# begin with the letters `http` (e.g. `httpinfo`) are unaffected. The
|
|
202
|
+
# strip must run before the punctuation gsub: the gsub removes the
|
|
203
|
+
# underscore, after which `\Ahttp_` could never match.
|
|
189
204
|
def normalize(str)
|
|
190
|
-
str.to_s.strip.downcase.
|
|
205
|
+
str.to_s.strip.downcase.sub(/\Ahttp_/, "").tr("-_$@", "")
|
|
191
206
|
end
|
|
192
207
|
end
|
|
193
208
|
end
|
data/lib/datadog/di/remote.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'fatal_exceptions'
|
|
4
|
+
|
|
3
5
|
module Datadog
|
|
4
6
|
module DI
|
|
5
7
|
# Provides an interface expected by the core Remote subsystem to
|
|
@@ -15,14 +17,125 @@ module Datadog
|
|
|
15
17
|
class << self
|
|
16
18
|
PRODUCT = 'LIVE_DEBUGGING'
|
|
17
19
|
|
|
20
|
+
# Declared here (not in Tracing::Remote::CAPABILITIES) so it is
|
|
21
|
+
# registered only with the gated DI block in Capabilities#register:
|
|
22
|
+
# when DI is explicitly disabled, or the runtime cannot run DI
|
|
23
|
+
# (JRuby, Ruby 2.5), that block — including this bit — is skipped.
|
|
24
|
+
# The enable signal itself is delivered in APM_TRACING payloads and
|
|
25
|
+
# routed here by Tracing::Remote.process_config.
|
|
26
|
+
CAPABILITIES = [
|
|
27
|
+
1 << 38, # APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION: Implicit DI enablement
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
18
30
|
def products
|
|
19
|
-
# TODO: do not send our product on unsupported runtimes
|
|
20
|
-
# (Ruby 2.5 / JRuby)
|
|
21
31
|
[PRODUCT]
|
|
22
32
|
end
|
|
23
33
|
|
|
24
34
|
def capabilities
|
|
25
|
-
|
|
35
|
+
CAPABILITIES
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Entry point for the RC-driven DI enable/disable path.
|
|
39
|
+
#
|
|
40
|
+
# Invoked from {Datadog::Tracing::Remote.process_config} when an
|
|
41
|
+
# APM_TRACING payload carries `dynamic_instrumentation_enabled`. Runs
|
|
42
|
+
# on the remote-config thread; never raises.
|
|
43
|
+
#
|
|
44
|
+
# @param enabled [Boolean] desired state from RC: true to start DI,
|
|
45
|
+
# false to stop it. The `DD_DYNAMIC_INSTRUMENTATION_ENABLED=false`
|
|
46
|
+
# env var blocks an enable here (see {.explicitly_disabled?}).
|
|
47
|
+
# @param repository [Datadog::Core::Remote::Configuration::Repository, nil]
|
|
48
|
+
# the RC repository, passed by {Datadog::Tracing::Remote.process_config}
|
|
49
|
+
# so that a stopped->started transition can reconcile against probes that
|
|
50
|
+
# were delivered in an earlier poll. nil when called outside the RC
|
|
51
|
+
# dispatch path (e.g. unit tests), in which case no reconcile happens.
|
|
52
|
+
# @return [void]
|
|
53
|
+
def handle_rc_enablement(enabled, repository = nil)
|
|
54
|
+
# allow_initialization: false because this runs on the remote-config
|
|
55
|
+
# thread (a callback context). The default `true` would synchronously
|
|
56
|
+
# build the entire component tree from the wrong thread if the RC
|
|
57
|
+
# signal lands before Components#initialize completed.
|
|
58
|
+
components = Datadog.send(:components, allow_initialization: false)
|
|
59
|
+
component = components&.dynamic_instrumentation
|
|
60
|
+
unless component
|
|
61
|
+
# The component is nil because Component.build returned nil at
|
|
62
|
+
# startup — a runtime precondition is not met (RC disabled, MRI
|
|
63
|
+
# required, Ruby 2.6+ required, Rails dev env, C extension absent).
|
|
64
|
+
# On disable, silently no-op: RC asking us to turn off something we
|
|
65
|
+
# don't have is fine. On enable, warn with the reason: this is the
|
|
66
|
+
# implicit-enablement counterpart to the warn-on-explicit message
|
|
67
|
+
# at build time. The customer who clicked "create probe" in the UI
|
|
68
|
+
# gets the same visibility a customer who set
|
|
69
|
+
# DD_DYNAMIC_INSTRUMENTATION_ENABLED would have gotten at boot.
|
|
70
|
+
if enabled
|
|
71
|
+
if explicitly_disabled?
|
|
72
|
+
Datadog.logger.warn(
|
|
73
|
+
"di: cannot enable dynamic instrumentation via remote configuration " \
|
|
74
|
+
"because DD_DYNAMIC_INSTRUMENTATION_ENABLED is explicitly set to false",
|
|
75
|
+
)
|
|
76
|
+
else
|
|
77
|
+
reason = DI.unsupported_reason
|
|
78
|
+
Datadog.logger.warn(
|
|
79
|
+
"di: cannot enable dynamic instrumentation via remote configuration: " \
|
|
80
|
+
"#{reason || "dynamic instrumentation was not initialized at startup"}",
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
return
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if enabled
|
|
88
|
+
if explicitly_disabled?
|
|
89
|
+
Datadog.logger.warn(
|
|
90
|
+
"di: ignoring implicit enablement signal from remote configuration " \
|
|
91
|
+
"because DD_DYNAMIC_INSTRUMENTATION_ENABLED is explicitly set to false. " \
|
|
92
|
+
"To allow remote enablement, unset DD_DYNAMIC_INSTRUMENTATION_ENABLED.",
|
|
93
|
+
)
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
# component is non-nil here only because Component.build's preconditions
|
|
97
|
+
# passed, which is the same condition under which di/base.rb is loaded
|
|
98
|
+
# and DI.activate_tracking is defined.
|
|
99
|
+
DI.activate_tracking
|
|
100
|
+
was_started = component.started?
|
|
101
|
+
component.start!
|
|
102
|
+
# A probe delivered in an earlier poll while the component was stopped
|
|
103
|
+
# was dropped by #receivers (which ignores changes while !started?) and
|
|
104
|
+
# never entered the probe repository. RC dispatch only re-delivers a
|
|
105
|
+
# config when its content hash changes (Core::Remote::Client#apply_config
|
|
106
|
+
# skips unchanged content), so the probe would otherwise stay dropped
|
|
107
|
+
# until the customer edits it. On the stopped->started transition,
|
|
108
|
+
# reconcile against the current LIVE_DEBUGGING contents so it installs now.
|
|
109
|
+
replay_current_probes(repository, component) if repository && !was_started
|
|
110
|
+
else
|
|
111
|
+
component.stop!
|
|
112
|
+
end
|
|
113
|
+
rescue => e
|
|
114
|
+
Datadog.logger.debug { "di: error handling implicit enablement: #{e.class}: #{e.message}" }
|
|
115
|
+
Datadog.send(:components, allow_initialization: false)&.telemetry&.report(
|
|
116
|
+
e,
|
|
117
|
+
description: "Error handling DI implicit enablement",
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Symmetric to {DI::Component.explicitly_enabled?} (see there for why
|
|
122
|
+
# using_default? rather than options[:enabled].default_precedence?).
|
|
123
|
+
#
|
|
124
|
+
# Canonical home for the explicit-disable check: it gates
|
|
125
|
+
# {DI::Component.build} and the DI block in
|
|
126
|
+
# {Core::Remote::Client::Capabilities#register}. The latter runs before
|
|
127
|
+
# DI::Component is loaded, so the check lives here on DI::Remote (always
|
|
128
|
+
# required by capabilities.rb) rather than on DI::Component. The
|
|
129
|
+
# `settings` argument lets those startup callers pass the settings being
|
|
130
|
+
# configured; the RC handler omits it and reads the global config.
|
|
131
|
+
#
|
|
132
|
+
# @param settings [Datadog::Core::Configuration::Settings]
|
|
133
|
+
# @return [Boolean] true when the customer set
|
|
134
|
+
# `DD_DYNAMIC_INSTRUMENTATION_ENABLED=false` (or the programmatic
|
|
135
|
+
# equivalent), which blocks DI build and RC-driven enablement.
|
|
136
|
+
def explicitly_disabled?(settings = Datadog.configuration)
|
|
137
|
+
!settings.dynamic_instrumentation.using_default?(:enabled) &&
|
|
138
|
+
!settings.dynamic_instrumentation.enabled
|
|
26
139
|
end
|
|
27
140
|
|
|
28
141
|
def receivers(telemetry)
|
|
@@ -32,17 +145,18 @@ module Datadog
|
|
|
32
145
|
# DEV: Apply this refactor to AppSec as well if implemented.
|
|
33
146
|
|
|
34
147
|
component = DI.component
|
|
35
|
-
|
|
36
|
-
# only add DI product to remote config request if DI is enabled.
|
|
37
|
-
# Ideally, we should be injected with the DI component here
|
|
38
|
-
# rather than having to retrieve it from global state.
|
|
39
|
-
# If the component is nil for some reason, we also don't have a
|
|
40
|
-
# logger instance to report the issue.
|
|
41
|
-
if component
|
|
148
|
+
if component&.started?
|
|
42
149
|
changes.each do |change|
|
|
43
150
|
case change.type
|
|
44
151
|
when :insert
|
|
45
|
-
|
|
152
|
+
# A stopped->started reconcile (#replay_current_probes) may have
|
|
153
|
+
# installed this probe earlier in the same dispatch — the enable
|
|
154
|
+
# signal is carried by the Tracing receiver, which runs before the
|
|
155
|
+
# DI receiver. Skip the redundant install rather than letting
|
|
156
|
+
# probe_manager raise AlreadyInstrumented and report a false error.
|
|
157
|
+
unless probe_in_content_known?(change.content, component) # steep:ignore NoMethod
|
|
158
|
+
add_probe(change.content, component) # steep:ignore NoMethod
|
|
159
|
+
end
|
|
46
160
|
when :update
|
|
47
161
|
# We do not implement updates at the moment, remove the
|
|
48
162
|
# probe and reinstall.
|
|
@@ -89,7 +203,8 @@ module Datadog
|
|
|
89
203
|
# message.
|
|
90
204
|
# TODO assert content state (errored for this example)
|
|
91
205
|
content.errored("Error applying dynamic instrumentation configuration: #{exc.class}: #{exc.message}")
|
|
92
|
-
rescue => exc
|
|
206
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
207
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
93
208
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
94
209
|
|
|
95
210
|
component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI remote receiver: #{exc.class}: #{exc.message}" }
|
|
@@ -114,7 +229,8 @@ module Datadog
|
|
|
114
229
|
# try to remove instrumentation that is still supposed to be
|
|
115
230
|
# active.
|
|
116
231
|
#current_probe_ids[probe_spec.fetch('id')] = true
|
|
117
|
-
rescue => exc
|
|
232
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
233
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
118
234
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
119
235
|
|
|
120
236
|
component.logger.debug { "di: unhandled exception handling a probe in DI remote receiver: #{exc.class}: #{exc.message}" }
|
|
@@ -124,6 +240,50 @@ module Datadog
|
|
|
124
240
|
content.errored("Error applying dynamic instrumentation configuration: #{exc.class}: #{exc.message}")
|
|
125
241
|
end
|
|
126
242
|
|
|
243
|
+
# Reconciles the DI probe repository against the LIVE_DEBUGGING configs
|
|
244
|
+
# currently held by the RC repository. Called on the stopped->started
|
|
245
|
+
# transition to install probes that arrived while DI was stopped: those
|
|
246
|
+
# were dropped by {.receivers} and RC will not re-dispatch them because
|
|
247
|
+
# their content hash is unchanged.
|
|
248
|
+
#
|
|
249
|
+
# Probes already tracked by the component (installed, pending, or failed)
|
|
250
|
+
# are skipped, so a probe handled in the same dispatch by {.receivers} is
|
|
251
|
+
# not added twice.
|
|
252
|
+
def replay_current_probes(repository, component)
|
|
253
|
+
repository.contents.each do |content|
|
|
254
|
+
next unless content.path.product == PRODUCT
|
|
255
|
+
|
|
256
|
+
begin
|
|
257
|
+
probe_id = parse_content(content)["id"]
|
|
258
|
+
rescue => exc
|
|
259
|
+
component.logger.debug { "di: skipping unparseable LIVE_DEBUGGING content on enable reconcile: #{exc.class}: #{exc.message}" }
|
|
260
|
+
next
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
next if probe_id && probe_known?(probe_id, component)
|
|
264
|
+
|
|
265
|
+
add_probe(content, component)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def probe_known?(probe_id, component)
|
|
270
|
+
repo = component.probe_manager.probe_repository
|
|
271
|
+
!!(repo.find_installed(probe_id) ||
|
|
272
|
+
repo.find_pending(probe_id) ||
|
|
273
|
+
repo.find_failed(probe_id))
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# True when the probe carried by +content+ is already tracked by the
|
|
277
|
+
# component. Returns false when the id cannot be determined, so add_probe
|
|
278
|
+
# still runs and reports the parse error through its own handling.
|
|
279
|
+
def probe_in_content_known?(content, component)
|
|
280
|
+
probe_id = parse_content(content)["id"]
|
|
281
|
+
!!(probe_id && probe_known?(probe_id, component))
|
|
282
|
+
rescue => exc
|
|
283
|
+
component.logger.debug { "di: could not check probe id for insert dedup: #{exc.class}: #{exc.message}" }
|
|
284
|
+
false
|
|
285
|
+
end
|
|
286
|
+
|
|
127
287
|
# This method does not mark +previous_content+ as succeeded or errored,
|
|
128
288
|
# because that content is from a previous RC response and has already
|
|
129
289
|
# been marked. Removal of probes happens when an RC entry disappears,
|
|
@@ -133,7 +293,8 @@ module Datadog
|
|
|
133
293
|
probe_spec = parse_content(previous_content)
|
|
134
294
|
probe_id = probe_spec.fetch('id')
|
|
135
295
|
component.probe_manager.remove_probe(probe_id)
|
|
136
|
-
rescue => exc
|
|
296
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
297
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
137
298
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
138
299
|
|
|
139
300
|
component.logger.debug { "di: unhandled exception removing probes in DI remote receiver: #{exc.class}: #{exc.message}" }
|
|
@@ -44,8 +44,11 @@ module Datadog
|
|
|
44
44
|
class Serializer
|
|
45
45
|
# Exception classes that should never be caught during serialization.
|
|
46
46
|
# These represent fatal conditions (signals, interrupts, system exit)
|
|
47
|
-
# that must propagate to the caller.
|
|
48
|
-
FATAL_EXCEPTION_CLASSES
|
|
47
|
+
# that must propagate to the caller. NoMemoryError is deliberately
|
|
48
|
+
# excluded (unlike DI::FATAL_EXCEPTION_CLASSES): serialization of large
|
|
49
|
+
# or deeply recursive objects can exhaust memory, and the serializer must
|
|
50
|
+
# return a safe stub rather than tear the process down.
|
|
51
|
+
SERIALIZABLE_FATAL_EXCEPTION_CLASSES = [SystemExit, SignalException].freeze
|
|
49
52
|
|
|
50
53
|
# Third-party library integration / custom serializers.
|
|
51
54
|
#
|
|
@@ -177,7 +180,9 @@ module Datadog
|
|
|
177
180
|
if condition
|
|
178
181
|
begin
|
|
179
182
|
condition_result = condition.call(value)
|
|
180
|
-
rescue => e
|
|
183
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
184
|
+
raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| e.is_a?(klass) }
|
|
185
|
+
|
|
181
186
|
# If a custom serializer condition raises an exception (e.g., regex match
|
|
182
187
|
# against invalid UTF-8), skip it and continue with the next serializer.
|
|
183
188
|
# We don't want custom serializer conditions to break the entire serialization.
|
|
@@ -331,7 +336,7 @@ module Datadog
|
|
|
331
336
|
rescue Exception => exc # standard:disable Lint/RescueException
|
|
332
337
|
# Re-raise fatal exceptions that should not be caught
|
|
333
338
|
# (signals, interrupts, system exit)
|
|
334
|
-
raise if
|
|
339
|
+
raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) }
|
|
335
340
|
|
|
336
341
|
# Catch all other exceptions including SystemStackError and NoMemoryError.
|
|
337
342
|
# These inherit from Exception (not StandardError) but can occur during
|
|
@@ -429,7 +434,9 @@ module Datadog
|
|
|
429
434
|
end
|
|
430
435
|
"#<#{class_name(value.class)}#{serialized}>"
|
|
431
436
|
end
|
|
432
|
-
rescue => exc
|
|
437
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
438
|
+
raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) }
|
|
439
|
+
|
|
433
440
|
telemetry&.report(exc, description: "Error serializing for message")
|
|
434
441
|
# TODO class_name(foo) can also fail, which we don't handle here.
|
|
435
442
|
# Telemetry reporting could potentially also fail?
|
|
@@ -7,6 +7,7 @@ require_relative '../../core/transport/parcel'
|
|
|
7
7
|
require_relative '../../core/transport/request'
|
|
8
8
|
require_relative '../../core/transport/transport'
|
|
9
9
|
require_relative '../error'
|
|
10
|
+
require_relative '../fatal_exceptions'
|
|
10
11
|
require_relative 'http/input'
|
|
11
12
|
|
|
12
13
|
module Datadog
|
|
@@ -77,7 +78,8 @@ module Datadog
|
|
|
77
78
|
next
|
|
78
79
|
end
|
|
79
80
|
encoded_snapshots << encoded
|
|
80
|
-
rescue => exc
|
|
81
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
82
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
81
83
|
# Serialization failed for this snapshot - report via callback
|
|
82
84
|
# This catches JSON::GeneratorError, Encoding errors, TypeError, etc.
|
|
83
85
|
probe_id = snapshot.dig(:debugger, :snapshot, :probe, :id)
|
|
@@ -87,7 +89,8 @@ module Datadog
|
|
|
87
89
|
if probe_id
|
|
88
90
|
begin
|
|
89
91
|
on_serialization_error.call(probe_id, exc)
|
|
90
|
-
rescue => callback_exc
|
|
92
|
+
rescue Exception => callback_exc # standard:disable Lint/RescueException
|
|
93
|
+
Datadog::DI.reraise_if_fatal(callback_exc)
|
|
91
94
|
logger.debug { "di: error in serialization error callback for probe #{probe_id}: #{callback_exc.class}: #{callback_exc.message}" }
|
|
92
95
|
telemetry&.report(callback_exc, description: "Error in serialization error callback")
|
|
93
96
|
end
|
|
@@ -109,8 +112,9 @@ module Datadog
|
|
|
109
112
|
# subsequent chunks are attempted to be sent.
|
|
110
113
|
begin
|
|
111
114
|
send_input_chunk(chunked_payload, serialized_tags)
|
|
112
|
-
rescue => exc
|
|
113
|
-
|
|
115
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
116
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
117
|
+
logger.debug { "di: failed to send snapshot chunk: #{exc.class}: #{exc.message} (at #{exc.backtrace&.first})" }
|
|
114
118
|
telemetry&.report(exc, description: "Error sending snapshot chunk")
|
|
115
119
|
end
|
|
116
120
|
end
|