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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -2
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +64 -3
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +23 -4
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -1
  6. data/ext/datadog_profiling_native_extension/extconf.rb +5 -0
  7. data/ext/datadog_profiling_native_extension/heap_recorder.c +183 -51
  8. data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
  9. data/ext/datadog_profiling_native_extension/stack_recorder.c +34 -5
  10. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -1
  11. data/ext/libdatadog_api/crashtracker.c +5 -0
  12. data/ext/libdatadog_api/crashtracker_report_exception.c +236 -0
  13. data/lib/datadog/ai_guard/configuration/settings.rb +13 -1
  14. data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
  15. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +42 -0
  16. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
  17. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
  18. data/lib/datadog/ai_guard.rb +2 -0
  19. data/lib/datadog/appsec/assets/blocked.html +2 -1
  20. data/lib/datadog/appsec/configuration/settings.rb +14 -0
  21. data/lib/datadog/appsec/context.rb +44 -9
  22. data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
  23. data/lib/datadog/appsec/contrib/active_record/patcher.rb +1 -1
  24. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +54 -5
  25. data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
  26. data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
  27. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +60 -7
  28. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +11 -6
  29. data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -1
  30. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +6 -10
  31. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +1 -3
  32. data/lib/datadog/appsec/contrib/rails/patcher.rb +10 -2
  33. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +2 -0
  34. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +72 -7
  35. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +7 -4
  36. data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
  37. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +4 -4
  38. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +1 -1
  39. data/lib/datadog/appsec/counter_sampler.rb +25 -0
  40. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +18 -0
  41. data/lib/datadog/appsec/security_engine/engine.rb +23 -2
  42. data/lib/datadog/appsec/utils/http/body.rb +38 -0
  43. data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
  44. data/lib/datadog/appsec/utils/http/media_type.rb +33 -26
  45. data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
  46. data/lib/datadog/core/configuration/components.rb +29 -4
  47. data/lib/datadog/core/configuration/supported_configurations.rb +4 -0
  48. data/lib/datadog/core/configuration.rb +2 -2
  49. data/lib/datadog/core/crashtracking/component.rb +79 -19
  50. data/lib/datadog/core/crashtracking/tag_builder.rb +6 -0
  51. data/lib/datadog/core/environment/agent_info.rb +65 -1
  52. data/lib/datadog/core/knuth_sampler.rb +57 -0
  53. data/lib/datadog/core/logger.rb +1 -1
  54. data/lib/datadog/core/metrics/logging.rb +1 -1
  55. data/lib/datadog/core/process_discovery.rb +15 -19
  56. data/lib/datadog/core/rate_limiter.rb +2 -0
  57. data/lib/datadog/core/remote/component.rb +16 -5
  58. data/lib/datadog/core/remote/transport/config.rb +5 -11
  59. data/lib/datadog/core/telemetry/component.rb +0 -13
  60. data/lib/datadog/core/telemetry/transport/telemetry.rb +5 -6
  61. data/lib/datadog/core/transport/ext.rb +1 -0
  62. data/lib/datadog/core/transport/http/response.rb +4 -0
  63. data/lib/datadog/core/transport/parcel.rb +61 -9
  64. data/lib/datadog/core/utils/fnv.rb +26 -0
  65. data/lib/datadog/core.rb +6 -1
  66. data/lib/datadog/data_streams/processor.rb +34 -33
  67. data/lib/datadog/data_streams/transport/http/stats.rb +6 -0
  68. data/lib/datadog/data_streams/transport/http.rb +0 -4
  69. data/lib/datadog/data_streams/transport/stats.rb +5 -12
  70. data/lib/datadog/di/component.rb +1 -1
  71. data/lib/datadog/di/configuration/settings.rb +31 -0
  72. data/lib/datadog/di/context.rb +6 -0
  73. data/lib/datadog/di/instrumenter.rb +178 -133
  74. data/lib/datadog/di/probe.rb +10 -1
  75. data/lib/datadog/di/probe_file_loader.rb +2 -2
  76. data/lib/datadog/di/probe_manager.rb +7 -2
  77. data/lib/datadog/di/probe_notification_builder.rb +29 -8
  78. data/lib/datadog/di/probe_notifier_worker.rb +13 -3
  79. data/lib/datadog/di/proc_responder.rb +4 -0
  80. data/lib/datadog/di/redactor.rb +8 -1
  81. data/lib/datadog/di/remote.rb +2 -2
  82. data/lib/datadog/di/transport/diagnostics.rb +5 -7
  83. data/lib/datadog/di/transport/http/diagnostics.rb +3 -1
  84. data/lib/datadog/di/transport/http/input.rb +1 -1
  85. data/lib/datadog/di/transport/input.rb +5 -6
  86. data/lib/datadog/kit/tracing/method_tracer.rb +132 -0
  87. data/lib/datadog/open_feature/transport.rb +8 -11
  88. data/lib/datadog/profiling/component.rb +0 -6
  89. data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
  90. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
  91. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -1
  92. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
  93. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +2 -1
  94. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +10 -0
  95. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  96. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +24 -0
  97. data/lib/datadog/tracing/contrib/rack/route_inference.rb +18 -6
  98. data/lib/datadog/tracing/contrib/registerable.rb +11 -0
  99. data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
  100. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
  101. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +3 -1
  102. data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
  103. data/lib/datadog/tracing/transport/io/client.rb +5 -8
  104. data/lib/datadog/tracing/transport/io/traces.rb +28 -34
  105. data/lib/datadog/tracing/transport/traces.rb +4 -10
  106. data/lib/datadog/version.rb +1 -1
  107. metadata +17 -7
  108. 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
- continue = true
121
- if condition = probe.condition
122
- begin
123
- # This context will be recreated later, unlike for line probes.
124
- context = Context.new(
125
- locals: serializer.combine_args(args, kwargs, self),
126
- target_self: self,
127
- probe: probe, settings: settings, serializer: serializer,
128
- caller_locations: caller_locations,
129
- )
130
- continue = condition.satisfied?(context)
131
- rescue => exc
132
- # Evaluation error exception can be raised for "expected"
133
- # errors, we probably need another setting to control whether
134
- # these exceptions are propagated.
135
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
136
- !exc.is_a?(DI::Error::ExpressionEvaluationError)
137
-
138
- if context
139
- # We want to report evaluation errors for conditions
140
- # as probe snapshots. However, if we failed to create
141
- # the context, we won't be able to report anything as
142
- # the probe notifier builder requires a context.
143
- begin
144
- responder.probe_condition_evaluation_failed_callback(context, exc)
145
- rescue
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
- # TODO log / report via telemetry?
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
- duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
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
- begin
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
 
@@ -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
- !!@executed_on_line
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
- logger.debug { "di: #{self.class.name}: dropping #{event_type} event because queue is full" }
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
- logger.trace { "di: #{self.class.name}: queueing #{event_type} event" }
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
@@ -27,6 +27,10 @@ module Datadog
27
27
 
28
28
  failed_proc.call(context, exc)
29
29
  end
30
+
31
+ def probe_disabled_callback(probe, duration)
32
+ raise NotImplementedError
33
+ end
30
34
  end
31
35
  end
32
36
  end
@@ -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
- Set.new(names)
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
 
@@ -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
- # TODO use transport encoder functionality?
24
- json = JSON.dump(payload)
25
- parcel = EncodedParcel.new(json)
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), 'application/json', 'event.json'
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] = encoder.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 = EncodedParcel.new(chunked_payload)
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