datadog 2.27.0 → 2.29.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 +64 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +64 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +23 -4
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +5 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +183 -51
- data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +34 -5
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -1
- data/ext/libdatadog_api/crashtracker.c +5 -0
- data/ext/libdatadog_api/crashtracker_report_exception.c +236 -0
- data/lib/datadog/ai_guard/configuration/settings.rb +13 -1
- data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +42 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
- data/lib/datadog/ai_guard.rb +2 -0
- data/lib/datadog/appsec/assets/blocked.html +2 -1
- data/lib/datadog/appsec/configuration/settings.rb +14 -0
- data/lib/datadog/appsec/context.rb +44 -9
- data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +54 -5
- data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +60 -7
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +11 -6
- data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +6 -10
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +1 -3
- data/lib/datadog/appsec/contrib/rails/patcher.rb +10 -2
- data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +2 -0
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +72 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +7 -4
- data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +4 -4
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +1 -1
- data/lib/datadog/appsec/counter_sampler.rb +25 -0
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +18 -0
- data/lib/datadog/appsec/security_engine/engine.rb +23 -2
- data/lib/datadog/appsec/utils/http/body.rb +38 -0
- data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
- data/lib/datadog/appsec/utils/http/media_type.rb +33 -26
- data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
- data/lib/datadog/core/configuration/components.rb +29 -4
- data/lib/datadog/core/configuration/supported_configurations.rb +4 -0
- data/lib/datadog/core/configuration.rb +2 -2
- data/lib/datadog/core/crashtracking/component.rb +79 -19
- data/lib/datadog/core/crashtracking/tag_builder.rb +6 -0
- data/lib/datadog/core/environment/agent_info.rb +65 -1
- data/lib/datadog/core/knuth_sampler.rb +57 -0
- data/lib/datadog/core/logger.rb +1 -1
- data/lib/datadog/core/metrics/logging.rb +1 -1
- data/lib/datadog/core/process_discovery.rb +15 -19
- data/lib/datadog/core/rate_limiter.rb +2 -0
- data/lib/datadog/core/remote/component.rb +16 -5
- data/lib/datadog/core/remote/transport/config.rb +5 -11
- data/lib/datadog/core/telemetry/component.rb +0 -13
- data/lib/datadog/core/telemetry/transport/telemetry.rb +5 -6
- data/lib/datadog/core/transport/ext.rb +1 -0
- data/lib/datadog/core/transport/http/response.rb +4 -0
- data/lib/datadog/core/transport/parcel.rb +61 -9
- data/lib/datadog/core/utils/fnv.rb +26 -0
- data/lib/datadog/core.rb +6 -1
- data/lib/datadog/data_streams/processor.rb +34 -33
- data/lib/datadog/data_streams/transport/http/stats.rb +6 -0
- data/lib/datadog/data_streams/transport/http.rb +0 -4
- data/lib/datadog/data_streams/transport/stats.rb +5 -12
- data/lib/datadog/di/component.rb +1 -1
- data/lib/datadog/di/configuration/settings.rb +31 -0
- data/lib/datadog/di/context.rb +6 -0
- data/lib/datadog/di/instrumenter.rb +178 -133
- data/lib/datadog/di/probe.rb +10 -1
- data/lib/datadog/di/probe_file_loader.rb +2 -2
- data/lib/datadog/di/probe_manager.rb +7 -2
- data/lib/datadog/di/probe_notification_builder.rb +29 -8
- data/lib/datadog/di/probe_notifier_worker.rb +13 -3
- data/lib/datadog/di/proc_responder.rb +4 -0
- data/lib/datadog/di/redactor.rb +8 -1
- data/lib/datadog/di/remote.rb +2 -2
- data/lib/datadog/di/transport/diagnostics.rb +5 -7
- data/lib/datadog/di/transport/http/diagnostics.rb +3 -1
- data/lib/datadog/di/transport/http/input.rb +1 -1
- data/lib/datadog/di/transport/input.rb +5 -6
- data/lib/datadog/kit/tracing/method_tracer.rb +132 -0
- data/lib/datadog/open_feature/transport.rb +8 -11
- data/lib/datadog/profiling/component.rb +0 -6
- data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +10 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +24 -0
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +18 -6
- data/lib/datadog/tracing/contrib/registerable.rb +11 -0
- data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +3 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
- data/lib/datadog/tracing/transport/io/client.rb +5 -8
- data/lib/datadog/tracing/transport/io/traces.rb +28 -34
- data/lib/datadog/tracing/transport/traces.rb +4 -10
- data/lib/datadog/version.rb +1 -1
- metadata +17 -7
- data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
|
@@ -112,51 +112,57 @@ module Datadog
|
|
|
112
112
|
end
|
|
113
113
|
rate_limiter = probe.rate_limiter
|
|
114
114
|
settings = self.settings
|
|
115
|
+
instrumenter = self
|
|
115
116
|
|
|
116
117
|
mod = Module.new do
|
|
117
118
|
define_method(method_name) do |*args, **kwargs, &target_block| # steep:ignore NoMethod
|
|
118
119
|
# Steep: Unsure why it cannot detect kwargs in this block. Workaround:
|
|
119
120
|
# @type var kwargs: ::Hash[::Symbol, untyped]
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
121
|
+
di_start_time = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
|
|
122
|
+
|
|
123
|
+
if continue = probe.enabled?
|
|
124
|
+
if condition = probe.condition
|
|
125
|
+
begin
|
|
126
|
+
# This context will be recreated later, unlike for line probes.
|
|
127
|
+
#
|
|
128
|
+
# We do not need the stack for condition evaluation, therefore
|
|
129
|
+
# stack is not passed to Context here.
|
|
130
|
+
context = Context.new(
|
|
131
|
+
locals: serializer.combine_args(args, kwargs, self),
|
|
132
|
+
target_self: self,
|
|
133
|
+
probe: probe, settings: settings, serializer: serializer,
|
|
134
|
+
)
|
|
135
|
+
continue = condition.satisfied?(context)
|
|
136
|
+
rescue => exc
|
|
137
|
+
# Evaluation error exception can be raised for "expected"
|
|
138
|
+
# errors, we probably need another setting to control whether
|
|
139
|
+
# these exceptions are propagated.
|
|
140
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
|
|
141
|
+
!exc.is_a?(DI::Error::ExpressionEvaluationError)
|
|
142
|
+
|
|
143
|
+
if context
|
|
144
|
+
# We want to report evaluation errors for conditions
|
|
145
|
+
# as probe snapshots. However, if we failed to create
|
|
146
|
+
# the context, we won't be able to report anything as
|
|
147
|
+
# the probe notifier builder requires a context.
|
|
148
|
+
begin
|
|
149
|
+
responder.probe_condition_evaluation_failed_callback(context, exc)
|
|
150
|
+
rescue
|
|
151
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
152
|
+
|
|
153
|
+
# TODO log / report via telemetry?
|
|
154
|
+
end
|
|
155
|
+
else
|
|
156
|
+
_ = 42 # stop standard from wrecking this code
|
|
157
|
+
|
|
146
158
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
147
159
|
|
|
148
160
|
# TODO log / report via telemetry?
|
|
161
|
+
# If execution gets here, there is probably a bug in the tracer.
|
|
149
162
|
end
|
|
150
|
-
else
|
|
151
|
-
_ = 42 # stop standard from wrecking this code
|
|
152
|
-
|
|
153
|
-
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
154
163
|
|
|
155
|
-
|
|
156
|
-
# If execution gets here, there is probably a bug in the tracer.
|
|
164
|
+
continue = false
|
|
157
165
|
end
|
|
158
|
-
|
|
159
|
-
continue = false
|
|
160
166
|
end
|
|
161
167
|
end
|
|
162
168
|
|
|
@@ -173,6 +179,8 @@ module Datadog
|
|
|
173
179
|
# customer, and DI is not allowed to invoke customer code.
|
|
174
180
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
175
181
|
|
|
182
|
+
di_duration = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID) - di_start_time
|
|
183
|
+
|
|
176
184
|
rv = nil
|
|
177
185
|
begin
|
|
178
186
|
# Under Ruby 2.6 we cannot just call super(*args, **kwargs)
|
|
@@ -195,7 +203,15 @@ module Datadog
|
|
|
195
203
|
# the instrumentation callback runs.
|
|
196
204
|
end
|
|
197
205
|
|
|
198
|
-
|
|
206
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
207
|
+
duration = end_time - start_time
|
|
208
|
+
|
|
209
|
+
# Restart DI timer.
|
|
210
|
+
# The DI execution duration covers time spent in DI code before
|
|
211
|
+
# the customer method is invoked and time spent in DI code
|
|
212
|
+
# after the customer method finishes.
|
|
213
|
+
di_start_time = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
|
|
214
|
+
|
|
199
215
|
# The method itself is not part of the stack trace because
|
|
200
216
|
# we are getting the stack trace from outside of the method.
|
|
201
217
|
# Add the method in manually as the top frame.
|
|
@@ -222,6 +238,9 @@ module Datadog
|
|
|
222
238
|
return_value: rv, duration: duration, exception: exc,)
|
|
223
239
|
|
|
224
240
|
responder.probe_executed_callback(context)
|
|
241
|
+
|
|
242
|
+
instrumenter.send(:check_and_disable_if_exceeded, probe, responder, di_start_time, di_duration)
|
|
243
|
+
|
|
225
244
|
if exc
|
|
226
245
|
raise exc
|
|
227
246
|
else
|
|
@@ -296,7 +315,6 @@ module Datadog
|
|
|
296
315
|
end
|
|
297
316
|
|
|
298
317
|
line_no = probe.line_no!
|
|
299
|
-
rate_limiter = probe.rate_limiter
|
|
300
318
|
|
|
301
319
|
# Memoize the value to ensure this method always uses the same
|
|
302
320
|
# value for the setting.
|
|
@@ -367,103 +385,7 @@ module Datadog
|
|
|
367
385
|
[:line]
|
|
368
386
|
end
|
|
369
387
|
tp = TracePoint.new(*types) do |tp|
|
|
370
|
-
|
|
371
|
-
# If trace point is not targeted, we must verify that the invocation
|
|
372
|
-
# is the file & line that we want, because untargeted trace points
|
|
373
|
-
# are invoked for *each* line of Ruby executed.
|
|
374
|
-
# TODO find out exactly when the path in trace point is relative.
|
|
375
|
-
# Looks like this is the case when line trace point is not targeted?
|
|
376
|
-
continue = iseq || tp.lineno == probe.line_no && (
|
|
377
|
-
probe.file == tp.path || probe.file_matches?(tp.path)
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
# We set the trace point on :return to be able to instrument
|
|
381
|
-
# 'end' lines. This also causes the trace point to be invoked on
|
|
382
|
-
# non-'end' lines when a line raises an exception, since the
|
|
383
|
-
# exception causes the method to stop executing and stack unwends.
|
|
384
|
-
# We do not want two invocations of the trace point.
|
|
385
|
-
# Therefore, if a trace point is invoked with a :line event,
|
|
386
|
-
# mark it as such and ignore subsequent :return events.
|
|
387
|
-
continue &&= if probe.executed_on_line?
|
|
388
|
-
tp.event == :line
|
|
389
|
-
else
|
|
390
|
-
if tp.event == :line
|
|
391
|
-
probe.executed_on_line!
|
|
392
|
-
end
|
|
393
|
-
true
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
if continue
|
|
397
|
-
if condition = probe.condition
|
|
398
|
-
begin
|
|
399
|
-
context = Context.new(
|
|
400
|
-
locals: Instrumenter.get_local_variables(tp),
|
|
401
|
-
target_self: tp.self,
|
|
402
|
-
probe: probe, settings: settings, serializer: serializer,
|
|
403
|
-
path: tp.path,
|
|
404
|
-
caller_locations: caller_locations,
|
|
405
|
-
)
|
|
406
|
-
continue = condition.satisfied?(context)
|
|
407
|
-
rescue => exc
|
|
408
|
-
# Evaluation error exception can be raised for "expected"
|
|
409
|
-
# errors, we probably need another setting to control whether
|
|
410
|
-
# these exceptions are propagated.
|
|
411
|
-
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
|
|
412
|
-
!exc.is_a?(DI::Error::ExpressionEvaluationError)
|
|
413
|
-
|
|
414
|
-
continue = false
|
|
415
|
-
if context
|
|
416
|
-
# We want to report evaluation errors for conditions
|
|
417
|
-
# as probe snapshots. However, if we failed to create
|
|
418
|
-
# the context, we won't be able to report anything as
|
|
419
|
-
# the probe notifier builder requires a context.
|
|
420
|
-
begin
|
|
421
|
-
responder.probe_condition_evaluation_failed_callback(context, condition, exc)
|
|
422
|
-
rescue
|
|
423
|
-
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
424
|
-
|
|
425
|
-
# TODO log / report via telemetry?
|
|
426
|
-
end
|
|
427
|
-
else
|
|
428
|
-
_ = 42 # stop standard from wrecking this code
|
|
429
|
-
|
|
430
|
-
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
431
|
-
|
|
432
|
-
# TODO log / report via telemetry?
|
|
433
|
-
# If execution gets here, there is probably a bug in the tracer.
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
end
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
continue &&= rate_limiter.nil? || rate_limiter.allow? # standard:disable Style/AndOr
|
|
440
|
-
|
|
441
|
-
if continue
|
|
442
|
-
# The context creation is relatively expensive and we don't
|
|
443
|
-
# want to run it if the callback won't be executed due to the
|
|
444
|
-
# rate limit.
|
|
445
|
-
# Thus the copy-paste of the creation call here.
|
|
446
|
-
context ||= Context.new(
|
|
447
|
-
locals: Instrumenter.get_local_variables(tp),
|
|
448
|
-
target_self: tp.self,
|
|
449
|
-
probe: probe, settings: settings, serializer: serializer,
|
|
450
|
-
path: tp.path,
|
|
451
|
-
caller_locations: caller_locations,
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
responder.probe_executed_callback(context)
|
|
455
|
-
end
|
|
456
|
-
rescue => exc
|
|
457
|
-
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
458
|
-
logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
|
|
459
|
-
telemetry&.report(exc, description: "Unhandled exception in line trace point")
|
|
460
|
-
# TODO test this path
|
|
461
|
-
end
|
|
462
|
-
rescue => exc
|
|
463
|
-
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
464
|
-
logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
|
|
465
|
-
telemetry&.report(exc, description: "Unhandled exception in line trace point")
|
|
466
|
-
# TODO test this path
|
|
388
|
+
line_trace_point_callback(probe, iseq, responder, tp)
|
|
467
389
|
end
|
|
468
390
|
|
|
469
391
|
# Internal sanity check - untargeted trace points create a huge
|
|
@@ -551,6 +473,129 @@ module Datadog
|
|
|
551
473
|
|
|
552
474
|
attr_reader :lock
|
|
553
475
|
|
|
476
|
+
def line_trace_point_callback(probe, iseq, responder, tp)
|
|
477
|
+
di_start_time = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
|
|
478
|
+
|
|
479
|
+
# Check if probe is enabled before doing any processing
|
|
480
|
+
return unless probe.enabled?
|
|
481
|
+
|
|
482
|
+
# If trace point is not targeted, we must verify that the invocation
|
|
483
|
+
# is the file & line that we want, because untargeted trace points
|
|
484
|
+
# are invoked for *each* line of Ruby executed.
|
|
485
|
+
# TODO find out exactly when the path in trace point is relative.
|
|
486
|
+
# Looks like this is the case when line trace point is not targeted?
|
|
487
|
+
unless iseq
|
|
488
|
+
return unless tp.lineno == probe.line_no && ( # standard:disable Style/UnlessLogicalOperators
|
|
489
|
+
probe.file == tp.path || probe.file_matches?(tp.path)
|
|
490
|
+
)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# We set the trace point on :return to be able to instrument
|
|
494
|
+
# 'end' lines. This also causes the trace point to be invoked on
|
|
495
|
+
# non-'end' lines when a line raises an exception, since the
|
|
496
|
+
# exception causes the method to stop executing and stack unwends.
|
|
497
|
+
# We do not want two invocations of the trace point.
|
|
498
|
+
# Therefore, if a trace point is invoked with a :line event,
|
|
499
|
+
# mark it as such and ignore subsequent :return events.
|
|
500
|
+
if probe.executed_on_line?
|
|
501
|
+
return unless tp.event == :line
|
|
502
|
+
else
|
|
503
|
+
_ = 42 # stop standard from changing this code
|
|
504
|
+
|
|
505
|
+
if tp.event == :line
|
|
506
|
+
probe.executed_on_line!
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
if condition = probe.condition
|
|
511
|
+
begin
|
|
512
|
+
context = build_trace_point_context(probe, tp)
|
|
513
|
+
return unless condition.satisfied?(context)
|
|
514
|
+
rescue => exc
|
|
515
|
+
# Evaluation error exception can be raised for "expected"
|
|
516
|
+
# errors, we probably need another setting to control whether
|
|
517
|
+
# these exceptions are propagated.
|
|
518
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
|
|
519
|
+
!exc.is_a?(DI::Error::ExpressionEvaluationError)
|
|
520
|
+
|
|
521
|
+
if context
|
|
522
|
+
# We want to report evaluation errors for conditions
|
|
523
|
+
# as probe snapshots. However, if we failed to create
|
|
524
|
+
# the context, we won't be able to report anything as
|
|
525
|
+
# the probe notifier builder requires a context.
|
|
526
|
+
begin
|
|
527
|
+
responder.probe_condition_evaluation_failed_callback(context, condition, exc)
|
|
528
|
+
rescue
|
|
529
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
530
|
+
|
|
531
|
+
# TODO log / report via telemetry?
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
return
|
|
535
|
+
else
|
|
536
|
+
_ = 42 # stop standard from wrecking this code
|
|
537
|
+
|
|
538
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
539
|
+
|
|
540
|
+
# TODO log / report via telemetry?
|
|
541
|
+
# If execution gets here, there is probably a bug in the tracer.
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# In practice we should always have a rate limiter, but be safe
|
|
547
|
+
# and check that it is in fact set.
|
|
548
|
+
return if probe.rate_limiter && !probe.rate_limiter.allow?
|
|
549
|
+
|
|
550
|
+
# The context creation is relatively expensive and we don't
|
|
551
|
+
# want to run it if the callback won't be executed due to the
|
|
552
|
+
# rate limit.
|
|
553
|
+
# Thus the copy-paste of the creation call here.
|
|
554
|
+
context ||= build_trace_point_context(probe, tp)
|
|
555
|
+
|
|
556
|
+
responder.probe_executed_callback(context)
|
|
557
|
+
|
|
558
|
+
check_and_disable_if_exceeded(probe, responder, di_start_time)
|
|
559
|
+
rescue => exc
|
|
560
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
561
|
+
logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
|
|
562
|
+
telemetry&.report(exc, description: "Unhandled exception in line trace point")
|
|
563
|
+
# TODO test this path
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def build_trace_point_context(probe, tp)
|
|
567
|
+
stack = caller_locations
|
|
568
|
+
# We have two helper methods being invoked from the trace point
|
|
569
|
+
# handler block, remove them from the stack.
|
|
570
|
+
#
|
|
571
|
+
# According to steep stack may be nil.
|
|
572
|
+
stack&.shift(2)
|
|
573
|
+
Context.new(
|
|
574
|
+
locals: Instrumenter.get_local_variables(tp),
|
|
575
|
+
target_self: tp.self,
|
|
576
|
+
probe: probe,
|
|
577
|
+
settings: settings,
|
|
578
|
+
serializer: serializer,
|
|
579
|
+
path: tp.path,
|
|
580
|
+
caller_locations: stack,
|
|
581
|
+
)
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# Circuit breaker: disables the probe if total CPU time consumed by
|
|
585
|
+
# DI processing exceeds the configured threshold.
|
|
586
|
+
def check_and_disable_if_exceeded(probe, responder, di_start_time, accumulated_duration = 0.0)
|
|
587
|
+
return unless max_processing_time = settings.dynamic_instrumentation.internal.max_processing_time
|
|
588
|
+
|
|
589
|
+
di_duration = accumulated_duration + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID) - di_start_time
|
|
590
|
+
if di_duration > max_processing_time
|
|
591
|
+
logger.debug { "di: disabling probe: consumed #{di_duration}: #{probe}" }
|
|
592
|
+
# We disable the probe here rather than remove it to
|
|
593
|
+
# avoid a dependency on ProbeManager from Instrumenter.
|
|
594
|
+
probe.disable!
|
|
595
|
+
responder.probe_disabled_callback(probe, di_duration)
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
554
599
|
def raise_if_probe_in_loaded_features(probe)
|
|
555
600
|
return unless probe.file
|
|
556
601
|
|
data/lib/datadog/di/probe.rb
CHANGED
|
@@ -97,6 +97,7 @@ module Datadog
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
@emitting_notified = false
|
|
100
|
+
@enabled = true
|
|
100
101
|
end
|
|
101
102
|
|
|
102
103
|
attr_reader :id
|
|
@@ -219,13 +220,21 @@ module Datadog
|
|
|
219
220
|
end
|
|
220
221
|
|
|
221
222
|
def executed_on_line?
|
|
222
|
-
|
|
223
|
+
!!(defined?(@executed_on_line) && @executed_on_line)
|
|
223
224
|
end
|
|
224
225
|
|
|
225
226
|
def executed_on_line!
|
|
226
227
|
# TODO lock?
|
|
227
228
|
@executed_on_line = true
|
|
228
229
|
end
|
|
230
|
+
|
|
231
|
+
def enabled?
|
|
232
|
+
@enabled
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def disable!
|
|
236
|
+
@enabled = false
|
|
237
|
+
end
|
|
229
238
|
end
|
|
230
239
|
end
|
|
231
240
|
end
|
|
@@ -46,7 +46,7 @@ module Datadog
|
|
|
46
46
|
component.telemetry&.report(exc, description: "Line probe is targeting a loaded file that is not in code tracker")
|
|
47
47
|
|
|
48
48
|
payload = component.probe_notification_builder.build_errored(probe, exc)
|
|
49
|
-
component.probe_notifier_worker.add_status(payload)
|
|
49
|
+
component.probe_notifier_worker.add_status(payload, probe: probe)
|
|
50
50
|
rescue => exc
|
|
51
51
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
52
52
|
|
|
@@ -55,7 +55,7 @@ module Datadog
|
|
|
55
55
|
|
|
56
56
|
# TODO test this path
|
|
57
57
|
payload = component.probe_notification_builder.build_errored(probe, exc)
|
|
58
|
-
component.probe_notifier_worker.add_status(payload)
|
|
58
|
+
component.probe_notifier_worker.add_status(payload, probe: probe)
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
rescue => exc
|
|
@@ -118,7 +118,7 @@ module Datadog
|
|
|
118
118
|
|
|
119
119
|
@installed_probes[probe.id] = probe
|
|
120
120
|
payload = probe_notification_builder.build_installed(probe)
|
|
121
|
-
probe_notifier_worker.add_status(payload)
|
|
121
|
+
probe_notifier_worker.add_status(payload, probe: probe)
|
|
122
122
|
# The probe would only be in the pending probes list if it was
|
|
123
123
|
# previously attempted to be installed and the target was not loaded.
|
|
124
124
|
# Always remove from pending list here because it makes the
|
|
@@ -240,7 +240,7 @@ module Datadog
|
|
|
240
240
|
logger.trace { "di: executed #{probe.type} probe at #{probe.location} (#{probe.id})" }
|
|
241
241
|
unless probe.emitting_notified?
|
|
242
242
|
payload = probe_notification_builder.build_emitting(probe)
|
|
243
|
-
probe_notifier_worker.add_status(payload)
|
|
243
|
+
probe_notifier_worker.add_status(payload, probe: probe)
|
|
244
244
|
probe.emitting_notified = true
|
|
245
245
|
end
|
|
246
246
|
|
|
@@ -256,6 +256,11 @@ module Datadog
|
|
|
256
256
|
end
|
|
257
257
|
end
|
|
258
258
|
|
|
259
|
+
def probe_disabled_callback(probe, duration)
|
|
260
|
+
payload = probe_notification_builder.build_disabled(probe, duration)
|
|
261
|
+
probe_notifier_worker.add_status(payload, probe: probe)
|
|
262
|
+
end
|
|
263
|
+
|
|
259
264
|
# Class/module definition trace point (:end type).
|
|
260
265
|
# Used to install hooks when the target classes/modules aren't yet
|
|
261
266
|
# defined when the hook request is received.
|
|
@@ -37,6 +37,13 @@ module Datadog
|
|
|
37
37
|
def build_errored(probe, exc)
|
|
38
38
|
build_status(probe,
|
|
39
39
|
message: "Instrumentation for probe #{probe.id} failed: #{exc}",
|
|
40
|
+
status: 'ERROR',
|
|
41
|
+
exception: exc)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_disabled(probe, duration)
|
|
45
|
+
build_status(probe,
|
|
46
|
+
message: "Probe #{probe.id} was disabled because it consumed #{duration} seconds of CPU time in DI processing",
|
|
40
47
|
status: 'ERROR',)
|
|
41
48
|
end
|
|
42
49
|
|
|
@@ -195,20 +202,34 @@ module Datadog
|
|
|
195
202
|
payload
|
|
196
203
|
end
|
|
197
204
|
|
|
198
|
-
def build_status(probe, message:, status:)
|
|
205
|
+
def build_status(probe, message:, status:, exception: nil)
|
|
206
|
+
diagnostics = {
|
|
207
|
+
probeId: probe.id,
|
|
208
|
+
probeVersion: 0,
|
|
209
|
+
runtimeId: Core::Environment::Identity.id,
|
|
210
|
+
parentId: nil,
|
|
211
|
+
status: status,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# Exception field is required by the backend for ERROR status.
|
|
215
|
+
# If the ERROR status is sent without the exception field, the status
|
|
216
|
+
# appears to be completely ignored by the backend.
|
|
217
|
+
# Note: The Go DI implementation does not send the top-level message
|
|
218
|
+
# field at all when sending error statuses.
|
|
219
|
+
if status == 'ERROR'
|
|
220
|
+
diagnostics[:exception] = { # steep:ignore
|
|
221
|
+
type: exception ? exception.class.name : 'Error',
|
|
222
|
+
message: exception ? exception.message : message
|
|
223
|
+
}
|
|
224
|
+
end
|
|
225
|
+
|
|
199
226
|
{
|
|
200
227
|
service: settings.service,
|
|
201
228
|
timestamp: timestamp_now,
|
|
202
229
|
message: message,
|
|
203
230
|
ddsource: 'dd_debugger',
|
|
204
231
|
debugger: {
|
|
205
|
-
diagnostics:
|
|
206
|
-
probeId: probe.id,
|
|
207
|
-
probeVersion: 0,
|
|
208
|
-
runtimeId: Core::Environment::Identity.id,
|
|
209
|
-
parentId: nil,
|
|
210
|
-
status: status,
|
|
211
|
-
},
|
|
232
|
+
diagnostics: diagnostics,
|
|
212
233
|
},
|
|
213
234
|
}
|
|
214
235
|
end
|
|
@@ -208,13 +208,23 @@ module Datadog
|
|
|
208
208
|
# Signals the background thread to wake up (and do the sending)
|
|
209
209
|
# if it has been more than 1 second since the last send of the same
|
|
210
210
|
# event type.
|
|
211
|
-
define_method("add_#{event_type}") do |event|
|
|
211
|
+
define_method("add_#{event_type}") do |event, probe: nil|
|
|
212
212
|
@lock.synchronize do
|
|
213
213
|
queue = send("#{event_type}_queue")
|
|
214
214
|
if queue.length > settings.dynamic_instrumentation.internal.snapshot_queue_capacity
|
|
215
|
-
|
|
215
|
+
if event_type == :status && probe
|
|
216
|
+
status = event.dig(:debugger, :diagnostics, :status)
|
|
217
|
+
logger.debug { "di: dropping status for #{probe.type} probe at #{probe.location} (#{probe.id}): #{status} because queue is full" }
|
|
218
|
+
else
|
|
219
|
+
logger.debug { "di: #{self.class.name}: dropping #{event_type} event because queue is full" }
|
|
220
|
+
end
|
|
216
221
|
else
|
|
217
|
-
|
|
222
|
+
if event_type == :status && probe
|
|
223
|
+
status = event.dig(:debugger, :diagnostics, :status)
|
|
224
|
+
logger.trace { "di: queueing status for #{probe.type} probe at #{probe.location} (#{probe.id}): #{status}" }
|
|
225
|
+
else
|
|
226
|
+
logger.trace { "di: #{self.class.name}: queueing #{event_type} event" }
|
|
227
|
+
end
|
|
218
228
|
queue << event
|
|
219
229
|
end
|
|
220
230
|
end
|
data/lib/datadog/di/redactor.rb
CHANGED
|
@@ -13,6 +13,10 @@ module Datadog
|
|
|
13
13
|
# redaction. Additional names can be provided by the user via the
|
|
14
14
|
# settings.dynamic_instrumentation.redacted_identifiers setting or
|
|
15
15
|
# the DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS environment
|
|
16
|
+
# variable. Users can also exclude specific identifiers from the default
|
|
17
|
+
# redaction list via the
|
|
18
|
+
# settings.dynamic_instrumentation.redaction_excluded_identifiers setting or
|
|
19
|
+
# the DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS environment
|
|
16
20
|
# variable. Currently no class names are subject to redaction by default;
|
|
17
21
|
# class names can be provided via the
|
|
18
22
|
# settings.dynamic_instrumentation.redacted_type_names setting or
|
|
@@ -61,7 +65,10 @@ module Datadog
|
|
|
61
65
|
names.map! do |name|
|
|
62
66
|
normalize(name)
|
|
63
67
|
end
|
|
64
|
-
|
|
68
|
+
excluded = settings.dynamic_instrumentation.redaction_excluded_identifiers.map do |name|
|
|
69
|
+
normalize(name)
|
|
70
|
+
end
|
|
71
|
+
Set.new(names) - Set.new(excluded)
|
|
65
72
|
end
|
|
66
73
|
end
|
|
67
74
|
|
data/lib/datadog/di/remote.rb
CHANGED
|
@@ -80,7 +80,7 @@ module Datadog
|
|
|
80
80
|
component.telemetry&.report(exc, description: "Line probe is targeting a loaded file that is not in code tracker")
|
|
81
81
|
|
|
82
82
|
payload = component.probe_notification_builder.build_errored(probe, exc)
|
|
83
|
-
component.probe_notifier_worker.add_status(payload)
|
|
83
|
+
component.probe_notifier_worker.add_status(payload, probe: probe)
|
|
84
84
|
|
|
85
85
|
# If a probe fails to install, we will mark the content
|
|
86
86
|
# as errored. On subsequent remote configuration application
|
|
@@ -98,7 +98,7 @@ module Datadog
|
|
|
98
98
|
|
|
99
99
|
# TODO test this path
|
|
100
100
|
payload = component.probe_notification_builder.build_errored(probe, exc)
|
|
101
|
-
component.probe_notifier_worker.add_status(payload)
|
|
101
|
+
component.probe_notifier_worker.add_status(payload, probe: probe)
|
|
102
102
|
|
|
103
103
|
# If a probe fails to install, we will mark the content
|
|
104
104
|
# as errored. On subsequent remote configuration application
|
|
@@ -11,18 +11,16 @@ module Datadog
|
|
|
11
11
|
module DI
|
|
12
12
|
module Transport
|
|
13
13
|
module Diagnostics
|
|
14
|
-
class EncodedParcel
|
|
15
|
-
include Datadog::Core::Transport::Parcel
|
|
16
|
-
end
|
|
17
|
-
|
|
18
14
|
class Request < Datadog::Core::Transport::Request
|
|
19
15
|
end
|
|
20
16
|
|
|
21
17
|
class Transport < Core::Transport::Transport
|
|
22
18
|
def send_diagnostics(payload)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
encoder = Core::Encoding::JSONEncoder
|
|
20
|
+
parcel = Core::Transport::Parcel.new(
|
|
21
|
+
encoder.encode(payload),
|
|
22
|
+
content_type: encoder.content_type,
|
|
23
|
+
)
|
|
26
24
|
request = Request.new(parcel)
|
|
27
25
|
|
|
28
26
|
client.send_request(:diagnostics, request)
|
|
@@ -18,7 +18,9 @@ module Datadog
|
|
|
18
18
|
|
|
19
19
|
def call(env, &block)
|
|
20
20
|
event_payload = Core::Vendor::Multipart::Post::UploadIO.new(
|
|
21
|
-
StringIO.new(env.request.parcel.data),
|
|
21
|
+
StringIO.new(env.request.parcel.data),
|
|
22
|
+
env.request.parcel.content_type,
|
|
23
|
+
'event.json',
|
|
22
24
|
)
|
|
23
25
|
env.form = {'event' => event_payload}
|
|
24
26
|
|
|
@@ -21,7 +21,7 @@ module Datadog
|
|
|
21
21
|
|
|
22
22
|
def call(env, &block)
|
|
23
23
|
# Encode body & type
|
|
24
|
-
env.headers[HEADER_CONTENT_TYPE] =
|
|
24
|
+
env.headers[HEADER_CONTENT_TYPE] = env.request.parcel.content_type
|
|
25
25
|
env.body = env.request.parcel.data
|
|
26
26
|
env.query = {
|
|
27
27
|
# DEV: In theory we could serialize the tags here
|
|
@@ -13,10 +13,6 @@ module Datadog
|
|
|
13
13
|
module DI
|
|
14
14
|
module Transport
|
|
15
15
|
module Input
|
|
16
|
-
class EncodedParcel
|
|
17
|
-
include Datadog::Core::Transport::Parcel
|
|
18
|
-
end
|
|
19
|
-
|
|
20
16
|
class Request < Datadog::Core::Transport::Request
|
|
21
17
|
attr_reader :serialized_tags
|
|
22
18
|
|
|
@@ -51,7 +47,6 @@ module Datadog
|
|
|
51
47
|
# Tags are the same for all chunks, serialize them one time.
|
|
52
48
|
serialized_tags = Core::TagBuilder.serialize_tags(tags)
|
|
53
49
|
|
|
54
|
-
encoder = Core::Encoding::JSONEncoder
|
|
55
50
|
encoded_snapshots = Core::Utils::Array.filter_map(payload) do |snapshot|
|
|
56
51
|
encoded = encoder.encode(snapshot)
|
|
57
52
|
if encoded.length > MAX_SERIALIZED_SNAPSHOT_SIZE
|
|
@@ -86,7 +81,7 @@ module Datadog
|
|
|
86
81
|
end
|
|
87
82
|
|
|
88
83
|
def send_input_chunk(chunked_payload, serialized_tags)
|
|
89
|
-
parcel =
|
|
84
|
+
parcel = Core::Transport::Parcel.new(chunked_payload, content_type: encoder.content_type)
|
|
90
85
|
request = Request.new(parcel, serialized_tags)
|
|
91
86
|
|
|
92
87
|
client.send_request(:input, request).tap do |response|
|
|
@@ -96,6 +91,10 @@ module Datadog
|
|
|
96
91
|
end
|
|
97
92
|
end
|
|
98
93
|
end
|
|
94
|
+
|
|
95
|
+
def encoder
|
|
96
|
+
Core::Encoding::JSONEncoder
|
|
97
|
+
end
|
|
99
98
|
end
|
|
100
99
|
end
|
|
101
100
|
end
|