datadog 2.23.0 → 2.25.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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -1
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +17 -5
  4. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +4 -1
  6. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
  7. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  8. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  9. data/lib/datadog/ai_guard/api_client.rb +82 -0
  10. data/lib/datadog/ai_guard/component.rb +42 -0
  11. data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
  12. data/lib/datadog/ai_guard/configuration/settings.rb +98 -0
  13. data/lib/datadog/ai_guard/configuration.rb +11 -0
  14. data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
  15. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +34 -0
  16. data/lib/datadog/ai_guard/evaluation/request.rb +81 -0
  17. data/lib/datadog/ai_guard/evaluation/result.rb +43 -0
  18. data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
  19. data/lib/datadog/ai_guard/evaluation.rb +72 -0
  20. data/lib/datadog/ai_guard/ext.rb +16 -0
  21. data/lib/datadog/ai_guard.rb +153 -0
  22. data/lib/datadog/appsec/context.rb +2 -1
  23. data/lib/datadog/appsec/remote.rb +5 -12
  24. data/lib/datadog/appsec/security_engine/engine.rb +3 -3
  25. data/lib/datadog/appsec/security_engine/result.rb +2 -1
  26. data/lib/datadog/appsec/security_engine/runner.rb +2 -2
  27. data/lib/datadog/core/configuration/components.rb +6 -0
  28. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  29. data/lib/datadog/core/configuration/deprecations.rb +2 -2
  30. data/lib/datadog/core/configuration/option_definition.rb +4 -2
  31. data/lib/datadog/core/configuration/options.rb +8 -5
  32. data/lib/datadog/core/configuration/settings.rb +14 -3
  33. data/lib/datadog/core/configuration/supported_configurations.rb +8 -1
  34. data/lib/datadog/core/environment/cgroup.rb +52 -25
  35. data/lib/datadog/core/environment/container.rb +140 -46
  36. data/lib/datadog/core/environment/ext.rb +1 -0
  37. data/lib/datadog/core/environment/process.rb +9 -1
  38. data/lib/datadog/core/error.rb +6 -6
  39. data/lib/datadog/core/pin.rb +4 -0
  40. data/lib/datadog/core/rate_limiter.rb +9 -1
  41. data/lib/datadog/core/remote/client.rb +14 -6
  42. data/lib/datadog/core/remote/component.rb +6 -4
  43. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  44. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  45. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  46. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  47. data/lib/datadog/core/remote/transport/config.rb +3 -16
  48. data/lib/datadog/core/remote/transport/http/config.rb +4 -44
  49. data/lib/datadog/core/remote/transport/http/negotiation.rb +0 -39
  50. data/lib/datadog/core/remote/transport/http.rb +13 -24
  51. data/lib/datadog/core/remote/transport/negotiation.rb +7 -16
  52. data/lib/datadog/core/semaphore.rb +1 -4
  53. data/lib/datadog/core/telemetry/component.rb +52 -13
  54. data/lib/datadog/core/telemetry/event/app_started.rb +36 -1
  55. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  56. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  57. data/lib/datadog/core/telemetry/request.rb +17 -3
  58. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +2 -32
  59. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  60. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -10
  61. data/lib/datadog/core/telemetry/worker.rb +88 -32
  62. data/lib/datadog/core/transport/ext.rb +2 -0
  63. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  64. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  65. data/lib/datadog/core/transport/http/builder.rb +9 -5
  66. data/lib/datadog/core/transport/http/client.rb +19 -8
  67. data/lib/datadog/core/transport/http.rb +22 -19
  68. data/lib/datadog/core/transport/response.rb +12 -1
  69. data/lib/datadog/core/transport/transport.rb +90 -0
  70. data/lib/datadog/core/utils/only_once_successful.rb +2 -0
  71. data/lib/datadog/core/utils/safe_dup.rb +2 -2
  72. data/lib/datadog/core/utils/sequence.rb +2 -0
  73. data/lib/datadog/core/utils/time.rb +1 -1
  74. data/lib/datadog/core/workers/async.rb +10 -1
  75. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  76. data/lib/datadog/core/workers/polling.rb +2 -0
  77. data/lib/datadog/core/workers/queue.rb +100 -1
  78. data/lib/datadog/data_streams/processor.rb +1 -1
  79. data/lib/datadog/data_streams/transport/http/stats.rb +1 -36
  80. data/lib/datadog/data_streams/transport/http.rb +5 -6
  81. data/lib/datadog/data_streams/transport/stats.rb +3 -17
  82. data/lib/datadog/di/boot.rb +4 -2
  83. data/lib/datadog/di/contrib/active_record.rb +30 -5
  84. data/lib/datadog/di/el/compiler.rb +8 -4
  85. data/lib/datadog/di/error.rb +5 -0
  86. data/lib/datadog/di/instrumenter.rb +26 -7
  87. data/lib/datadog/di/logger.rb +2 -2
  88. data/lib/datadog/di/probe_builder.rb +2 -1
  89. data/lib/datadog/di/probe_file_loader/railtie.rb +1 -1
  90. data/lib/datadog/di/probe_manager.rb +37 -31
  91. data/lib/datadog/di/probe_notification_builder.rb +15 -2
  92. data/lib/datadog/di/probe_notifier_worker.rb +5 -5
  93. data/lib/datadog/di/remote.rb +89 -84
  94. data/lib/datadog/di/transport/diagnostics.rb +7 -35
  95. data/lib/datadog/di/transport/http/diagnostics.rb +1 -31
  96. data/lib/datadog/di/transport/http/input.rb +1 -31
  97. data/lib/datadog/di/transport/http.rb +28 -17
  98. data/lib/datadog/di/transport/input.rb +7 -34
  99. data/lib/datadog/di.rb +61 -5
  100. data/lib/datadog/error_tracking/filters.rb +2 -2
  101. data/lib/datadog/kit/appsec/events/v2.rb +2 -3
  102. data/lib/datadog/open_feature/evaluation_engine.rb +2 -1
  103. data/lib/datadog/open_feature/remote.rb +3 -10
  104. data/lib/datadog/open_feature/transport.rb +9 -11
  105. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  106. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -2
  107. data/lib/datadog/opentelemetry/metrics.rb +21 -14
  108. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +5 -8
  109. data/lib/datadog/profiling/collectors/code_provenance.rb +27 -2
  110. data/lib/datadog/profiling/collectors/info.rb +5 -4
  111. data/lib/datadog/profiling/component.rb +12 -11
  112. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +18 -0
  113. data/lib/datadog/profiling/http_transport.rb +4 -1
  114. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  115. data/lib/datadog/tracing/contrib/karafka/patcher.rb +31 -32
  116. data/lib/datadog/tracing/contrib/status_range_matcher.rb +2 -1
  117. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  118. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +6 -3
  119. data/lib/datadog/tracing/contrib/waterdrop.rb +4 -0
  120. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  121. data/lib/datadog/tracing/distributed/baggage.rb +3 -2
  122. data/lib/datadog/tracing/remote.rb +1 -9
  123. data/lib/datadog/tracing/sampling/priority_sampler.rb +3 -1
  124. data/lib/datadog/tracing/span.rb +1 -1
  125. data/lib/datadog/tracing/span_event.rb +2 -2
  126. data/lib/datadog/tracing/span_operation.rb +20 -9
  127. data/lib/datadog/tracing/trace_operation.rb +44 -6
  128. data/lib/datadog/tracing/tracer.rb +42 -16
  129. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  130. data/lib/datadog/tracing/transport/http.rb +15 -9
  131. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  132. data/lib/datadog/tracing/transport/traces.rb +6 -66
  133. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  134. data/lib/datadog/tracing/writer.rb +1 -0
  135. data/lib/datadog/version.rb +2 -2
  136. data/lib/datadog.rb +1 -0
  137. metadata +24 -17
  138. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  139. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  140. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  141. data/lib/datadog/data_streams/transport/http/api.rb +0 -33
  142. data/lib/datadog/data_streams/transport/http/client.rb +0 -21
  143. data/lib/datadog/di/transport/http/api.rb +0 -42
  144. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  145. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -1,12 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Datadog::DI::Serializer.register(condition: lambda { |value| ActiveRecord::Base === value }) do |serializer, value, name:, depth:| # steep:ignore
4
- # steep thinks all of the arguments are nil here
5
- # steep:ignore:start
3
+ Datadog::DI::Serializer.register(
4
+ # This serializer uses a dynamic condition to determine its applicability
5
+ # to a particular value. A simpler case could have been a serializer for
6
+ # a particular class, but in this case any ActiveRecord model is covered
7
+ # and they all have different classes.
8
+ #
9
+ # An alternative could have been to make DI specifically provide lookup
10
+ # logic for "instances of classes derived from X", but a condition Proc
11
+ # is more universal.
12
+ condition: lambda { |value| ActiveRecord::Base === value }
13
+ ) do |serializer, value, name:, depth:|
14
+ # +serializer+ is an instance of DI::Serializer.
15
+ # Use it to perform the serialization to primitive values.
16
+ #
17
+ # +value+ is the value to serialize. It should match the condition
18
+ # provided above, meaning it would be an ActiveRecord::Base instance.
19
+ #
20
+ # +name+ is the name of the (local/instance) variable being serialized.
21
+ # The name is used by DI for redaction (upstream of serialization logic),
22
+ # and could potentially be used for redaction here also.
23
+ #
24
+ # +depth+ is the remaining depth for serializing collections and objects.
25
+ # It should always be an integer.
26
+ # Reduce it by 1 when invoking +serialize_value+ on the contents of +value+.
27
+ # This serializer could also potentially do its own depth limiting.
28
+ #
29
+ # Steep: steep thinks all of the arguments are nil here
30
+ # Looks like it cannot handle kwargs in lambdas
31
+ # @type var depth: Integer
6
32
  value_to_serialize = {
7
33
  attributes: value.attributes,
8
34
  new_record: value.new_record?,
9
35
  }
10
- serializer.serialize_value(value_to_serialize, depth: depth ? depth - 1 : nil, type: value.class)
11
- # steep:ignore:end
36
+ serializer.serialize_value(value_to_serialize, depth: depth - 1, type: value.class)
12
37
  end
@@ -22,7 +22,8 @@ module Datadog
22
22
 
23
23
  private
24
24
 
25
- OPERATORS = {
25
+ # Steep: https://github.com/soutaro/steep/issues/363
26
+ OPERATORS = { # steep:ignore IncompatibleAssignment
26
27
  'eq' => '==',
27
28
  'ne' => '!=',
28
29
  'ge' => '>=',
@@ -31,16 +32,19 @@ module Datadog
31
32
  'lt' => '<',
32
33
  }.freeze
33
34
 
35
+ # Steep: https://github.com/soutaro/steep/issues/363
34
36
  SINGLE_ARG_METHODS = %w[
35
37
  len isEmpty isUndefined
36
- ].freeze
38
+ ].freeze # steep:ignore IncompatibleAssignment
37
39
 
40
+ # Steep: https://github.com/soutaro/steep/issues/363
38
41
  TWO_ARG_METHODS = %w[
39
42
  startsWith endsWith contains matches
40
43
  getmember index instanceof
41
- ].freeze
44
+ ].freeze # steep:ignore IncompatibleAssignment
42
45
 
43
- MULTI_ARG_METHODS = {
46
+ # Steep: https://github.com/soutaro/steep/issues/363
47
+ MULTI_ARG_METHODS = { # steep:ignore IncompatibleAssignment
44
48
  'and' => '&&',
45
49
  'or' => '||',
46
50
  }.freeze
@@ -42,6 +42,11 @@ module Datadog
42
42
  class ProbePreviouslyFailed < Error
43
43
  end
44
44
 
45
+ # Raised when trying to instrument a probe when there is existing
46
+ # instrumentation for the same probe id.
47
+ class AlreadyInstrumented < Error
48
+ end
49
+
45
50
  # Raised when installing a line probe and multiple files match the
46
51
  # specified path suffix.
47
52
  # A probe must be installed into one file only, since UI only
@@ -114,7 +114,9 @@ module Datadog
114
114
  settings = self.settings
115
115
 
116
116
  mod = Module.new do
117
- define_method(method_name) do |*args, **kwargs, &target_block| # steep:ignore
117
+ define_method(method_name) do |*args, **kwargs, &target_block| # steep:ignore NoMethod
118
+ # Steep: Unsure why it cannot detect kwargs in this block. Workaround:
119
+ # @type var kwargs: ::Hash[::Symbol, untyped]
118
120
  continue = true
119
121
  if condition = probe.condition
120
122
  begin
@@ -166,7 +168,10 @@ module Datadog
166
168
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
167
169
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
168
170
  end
169
- start_time = Core::Utils::Time.get_time
171
+ # We intentionally do not use Core::Utils::Time.get_time
172
+ # here because the time provider may be overridden by the
173
+ # customer, and DI is not allowed to invoke customer code.
174
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
170
175
 
171
176
  rv = nil
172
177
  begin
@@ -190,7 +195,7 @@ module Datadog
190
195
  # the instrumentation callback runs.
191
196
  end
192
197
 
193
- duration = Core::Utils::Time.get_time - start_time
198
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
194
199
  # The method itself is not part of the stack trace because
195
200
  # we are getting the stack trace from outside of the method.
196
201
  # Add the method in manually as the top frame.
@@ -206,7 +211,8 @@ module Datadog
206
211
  # that location here.
207
212
  []
208
213
  end
209
- caller_locs = method_frame + caller_locations # steep:ignore
214
+ # Steep: https://github.com/ruby/rbs/pull/2745
215
+ caller_locs = method_frame + caller_locations # steep:ignore ArgumentTypeMismatch
210
216
  # TODO capture arguments at exit
211
217
 
212
218
  context = Context.new(locals: nil, target_self: self,
@@ -256,6 +262,8 @@ module Datadog
256
262
 
257
263
  probe.instrumentation_module = mod
258
264
  cls.send(:prepend, mod)
265
+
266
+ DI.instrumented_count_inc(:method)
259
267
  end
260
268
  end
261
269
 
@@ -268,6 +276,8 @@ module Datadog
268
276
  if mod = probe.instrumentation_module
269
277
  mod.send(:remove_method, probe.method_name)
270
278
  probe.instrumentation_module = nil
279
+
280
+ DI.instrumented_count_dec(:method)
271
281
  end
272
282
  end
273
283
  end
@@ -298,7 +308,10 @@ module Datadog
298
308
 
299
309
  iseq = nil
300
310
  if code_tracker
301
- ret = code_tracker.iseqs_for_path_suffix(probe.file) # steep:ignore
311
+ # Steep: Complex type narrowing (before calling hook_line,
312
+ # we check that probe.line? is true which itself checks that probe.file is not nil)
313
+ # Annotation do not work here as `file` is a method on probe, not a local variable.
314
+ ret = code_tracker.iseqs_for_path_suffix(probe.file) # steep:ignore ArgumentTypeMismatch
302
315
  unless ret
303
316
  if permit_untargeted_trace_points
304
317
  # Continue withoout targeting the trace point.
@@ -470,12 +483,16 @@ module Datadog
470
483
  # actual_path could be nil if we don't use targeted trace points.
471
484
  probe.instrumented_path = actual_path
472
485
 
473
- if iseq
486
+ # TracePoint#enable returns false when it succeeds.
487
+ rv = if iseq
474
488
  tp.enable(target: iseq, target_line: line_no)
475
489
  else
476
490
  tp.enable
477
491
  end
478
- # TracePoint#enable returns false when it succeeds.
492
+
493
+ DI.instrumented_count_inc(:line)
494
+
495
+ rv
479
496
  end
480
497
  true
481
498
  end
@@ -485,6 +502,8 @@ module Datadog
485
502
  if tp = probe.instrumentation_trace_point
486
503
  tp.disable
487
504
  probe.instrumentation_trace_point = nil
505
+
506
+ DI.instrumented_count_dec(:line)
488
507
  end
489
508
  end
490
509
  end
@@ -8,7 +8,7 @@ module Datadog
8
8
  #
9
9
  # @api private
10
10
  class Logger
11
- extend Forwardable # steep:ignore
11
+ extend Forwardable
12
12
 
13
13
  def initialize(settings, target)
14
14
  @settings = settings
@@ -18,7 +18,7 @@ module Datadog
18
18
  attr_reader :settings
19
19
  attr_reader :target
20
20
 
21
- def_delegators :target, :debug # steep:ignore
21
+ def_delegators :target, :debug
22
22
 
23
23
  def trace(&block)
24
24
  if settings.dynamic_instrumentation.internal.trace_logging
@@ -20,7 +20,8 @@ module Datadog
20
20
  #
21
21
  # @api private
22
22
  module ProbeBuilder
23
- PROBE_TYPES = {
23
+ # Steep: https://github.com/soutaro/steep/issues/363
24
+ PROBE_TYPES = { # steep:ignore IncompatibleAssignment
24
25
  'LOG_PROBE' => :log,
25
26
  }.freeze
26
27
 
@@ -6,7 +6,7 @@ module Datadog
6
6
  # Railtie class initializes dynamic instrumentation contrib code
7
7
  # in Rails environments.
8
8
  class Railtie < Rails::Railtie
9
- initializer 'datadog.dynamic_instrumentation.load_probe_file' do |app| # steep:ignore
9
+ initializer 'datadog.dynamic_instrumentation.load_probe_file' do |app|
10
10
  ProbeFileLoader.load_now
11
11
  end
12
12
  end
@@ -94,6 +94,19 @@ module Datadog
94
94
  # matches.
95
95
  def add_probe(probe)
96
96
  @lock.synchronize do
97
+ if @installed_probes[probe.id]
98
+ # Either this probe was already installed, or another probe was
99
+ # installed with the same id (previous version perhaps?).
100
+ # Since our state tracking is keyed by probe id, we cannot
101
+ # install this probe since we won't have a way of removing the
102
+ # instrumentation for the probe with the same id which is already
103
+ # installed.
104
+ #
105
+ # The exception raised here will be caught below and logged and
106
+ # reported to telemetry.
107
+ raise Error::AlreadyInstrumented, "Probe with id #{probe.id} is already in installed probes"
108
+ end
109
+
97
110
  # Probe failed to install previously, do not try to install it again.
98
111
  if msg = @failed_probes[probe.id]
99
112
  # TODO test this path
@@ -134,38 +147,30 @@ module Datadog
134
147
  end
135
148
  end
136
149
 
137
- # Removes probes with ids other than in the specified list.
138
- #
139
- # This method is meant to be invoked from remote config processor.
140
- # Remote config contains the list of currently defined probes; any
141
- # probes not in that list have been removed by user and should be
142
- # de-instrumented from the application.
143
- def remove_other_probes(probe_ids)
150
+ # Removes probe with specified id. The probe could be pending or
151
+ # installed. Does nothing if there is no probe with the specified id.
152
+ def remove_probe(probe_id)
144
153
  @lock.synchronize do
145
- @pending_probes.values.each do |probe|
146
- unless probe_ids.include?(probe.id)
147
- @pending_probes.delete(probe.id)
148
- end
149
- end
150
- @installed_probes.values.each do |probe|
151
- unless probe_ids.include?(probe.id)
152
- begin
153
- instrumenter.unhook(probe)
154
- # Only remove the probe from installed list if it was
155
- # successfully de-instrumented. Active probes do incur overhead
156
- # for the running application, and if the error is ephemeral
157
- # we want to try removing the probe again at the next opportunity.
158
- #
159
- # TODO give up after some time?
160
- @installed_probes.delete(probe.id)
161
- rescue => exc
162
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
163
- # Silence all exceptions?
164
- # TODO should we propagate here and rescue upstream?
165
- logger.debug { "di: error removing #{probe.type} probe at #{probe.location} (#{probe.id}): #{exc.class}: #{exc}" }
166
- telemetry&.report(exc, description: "Error removing probe")
167
- end
168
- end
154
+ @pending_probes.delete(probe_id)
155
+ end
156
+
157
+ # Do not delete the probe from the registry here in case
158
+ # deinstrumentation fails - though I don't know why deinstrumentation
159
+ # would fail and how we could recover if it does.
160
+ # I plan on tracking the number of outstanding (instrumented) probes
161
+ # in the future, and if deinstrumentation fails I would want to
162
+ # keep that probe as "installed" for the count, so that we can
163
+ # investigate the situation.
164
+ if probe = @installed_probes[probe_id]
165
+ begin
166
+ instrumenter.unhook(probe)
167
+ @installed_probes.delete(probe_id)
168
+ rescue => exc
169
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
170
+ # Silence all exceptions?
171
+ # TODO should we propagate here and rescue upstream?
172
+ logger.debug { "di: error removing #{probe.type} probe at #{probe.location} (#{probe.id}): #{exc.class}: #{exc}" }
173
+ telemetry&.report(exc, description: "Error removing probe")
169
174
  end
170
175
  end
171
176
  end
@@ -185,6 +190,7 @@ module Datadog
185
190
  # TODO is it OK to hook from trace point handler?
186
191
  # TODO the class is now defined, but can hooking still fail?
187
192
  instrumenter.hook(probe, self)
193
+ @installed_probes[probe.id] = probe
188
194
  @pending_probes.delete(probe.id)
189
195
  break
190
196
  rescue Error::DITargetNotDefined
@@ -46,7 +46,7 @@ module Datadog
46
46
  build_snapshot(context)
47
47
  end
48
48
 
49
- NANOSECONDS = 10**9
49
+ NANOSECONDS = 1_000_000_000
50
50
  MILLISECONDS = 1000
51
51
 
52
52
  def build_snapshot(context)
@@ -134,7 +134,7 @@ module Datadog
134
134
  format_caller_locations(caller_locations)
135
135
  end
136
136
 
137
- {
137
+ payload = {
138
138
  service: settings.service,
139
139
  debugger: {
140
140
  type: 'snapshot',
@@ -189,6 +189,10 @@ module Datadog
189
189
  message: message,
190
190
  timestamp: timestamp,
191
191
  }
192
+
193
+ tag_process_tags!(payload, settings)
194
+
195
+ payload
192
196
  end
193
197
 
194
198
  def build_status(probe, message:, status:)
@@ -236,6 +240,15 @@ module Datadog
236
240
  [message, evaluation_errors]
237
241
  end
238
242
 
243
+ def tag_process_tags!(payload, settings)
244
+ return unless settings.experimental_propagate_process_tags_enabled
245
+
246
+ process_tags = Core::Environment::Process.serialized
247
+ return if process_tags.empty?
248
+
249
+ payload[:process_tags] = process_tags
250
+ end
251
+
239
252
  def timestamp_now
240
253
  (Core::Utils::Time.now.to_f * MILLISECONDS).to_i
241
254
  end
@@ -249,12 +249,12 @@ module Datadog
249
249
  @lock.synchronize do
250
250
  batch = instance_variable_get("@#{event_type}_queue")
251
251
  instance_variable_set("@#{event_type}_queue", [])
252
- @io_in_progress = batch.any? # steep:ignore
252
+ @io_in_progress = batch.any?
253
253
  end
254
- logger.trace { "di: #{self.class.name}: checking #{event_type} queue - #{batch.length} entries" } # steep:ignore
255
- if batch.any? # steep:ignore
254
+ logger.trace { "di: #{self.class.name}: checking #{event_type} queue - #{batch.length} entries" }
255
+ if batch.any?
256
256
  begin
257
- logger.trace { "di: sending #{batch.length} #{event_type} event(s) to agent" } # steep:ignore
257
+ logger.trace { "di: sending #{batch.length} #{event_type} event(s) to agent" }
258
258
  send("do_send_#{event_type}", batch)
259
259
  time = Core::Utils::Time.get_time
260
260
  @lock.synchronize do
@@ -268,7 +268,7 @@ module Datadog
268
268
  # telemetry message would also fail.
269
269
  end
270
270
  end
271
- batch.any? # steep:ignore
271
+ batch.any?
272
272
  rescue ThreadError => exc
273
273
  # Normally the queue should only be consumed in this method,
274
274
  # however if anyone consumes it elsewhere we don't want to block
@@ -12,8 +12,6 @@ module Datadog
12
12
  #
13
13
  # @api private
14
14
  module Remote
15
- class ReadError < StandardError; end
16
-
17
15
  class << self
18
16
  PRODUCT = 'LIVE_DEBUGGING'
19
17
 
@@ -28,7 +26,7 @@ module Datadog
28
26
  end
29
27
 
30
28
  def receivers(telemetry)
31
- receiver do |repository, _changes|
29
+ receiver do |repository, changes|
32
30
  # DEV: Filter our by product. Given it will be very common
33
31
  # DEV: we can filter this out before we receive the data in this method.
34
32
  # DEV: Apply this refactor to AppSec as well if implemented.
@@ -41,84 +39,23 @@ module Datadog
41
39
  # If the component is nil for some reason, we also don't have a
42
40
  # logger instance to report the issue.
43
41
  if component
44
-
45
- probe_manager = component.probe_manager
46
- probe_notification_builder = component.probe_notification_builder
47
- probe_notifier_worker = component.probe_notifier_worker
48
-
49
- current_probe_ids = {}
50
- repository.contents.each do |content|
51
- case content.path.product
52
- when PRODUCT
53
- begin
54
- probe_spec = parse_content(content)
55
- probe = component.parse_probe_spec_and_notify(probe_spec)
56
- component.logger.debug { "di: received #{probe.type} probe at #{probe.location} (#{probe.id}) via RC" }
57
-
58
- begin
59
- # TODO test exception capture
60
- probe_manager.add_probe(probe)
61
- content.applied
62
- rescue DI::Error::DITargetNotInRegistry => exc
63
- component.telemetry&.report(exc, description: "Line probe is targeting a loaded file that is not in code tracker")
64
-
65
- payload = probe_notification_builder.build_errored(probe, exc)
66
- probe_notifier_worker.add_status(payload)
67
-
68
- # If a probe fails to install, we will mark the content
69
- # as errored. On subsequent remote configuration application
70
- # attemps, probe manager will raise the "previously errored"
71
- # exception and we'll rescue it here, again marking the
72
- # content as errored but with a somewhat different exception
73
- # message.
74
- # TODO assert content state (errored for this example)
75
- content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
76
- rescue => exc
77
- raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
78
-
79
- component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI remote receiver: #{exc.class}: #{exc}" }
80
- component.telemetry&.report(exc, description: "Unhandled exception adding probe in DI remote receiver")
81
-
82
- # TODO test this path
83
- payload = probe_notification_builder.build_errored(probe, exc)
84
- probe_notifier_worker.add_status(payload)
85
-
86
- # If a probe fails to install, we will mark the content
87
- # as errored. On subsequent remote configuration application
88
- # attemps, probe manager will raise the "previously errored"
89
- # exception and we'll rescue it here, again marking the
90
- # content as errored but with a somewhat different exception
91
- # message.
92
- # TODO assert content state (errored for this example)
93
- content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
94
- end
95
-
96
- # Important: even if processing fails for this probe config,
97
- # we need to note it as being current so that we do not
98
- # try to remove instrumentation that is still supposed to be
99
- # active.
100
- current_probe_ids[probe_spec.fetch('id')] = true
101
- rescue => exc
102
- raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
103
-
104
- component.logger.debug { "di: unhandled exception handling a probe in DI remote receiver: #{exc.class}: #{exc}" }
105
- component.telemetry&.report(exc, description: "Unhandled exception handling probe in DI remote receiver")
106
-
107
- # TODO assert content state (errored for this example)
108
- content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
109
- end
42
+ changes.each do |change|
43
+ case change.type
44
+ when :insert
45
+ add_probe(change.content, component)
46
+ when :update
47
+ # We do not implement updates at the moment, remove the
48
+ # probe and reinstall.
49
+ remove_probe(change.content, component)
50
+ add_probe(change.content, component)
51
+ when :delete
52
+ remove_probe(change.previous, component)
53
+ else
54
+ # This really should never happen since we generate the
55
+ # change types in the library.
56
+ component.logger.debug { "di: unrecognized change type: #{change.type}" }
110
57
  end
111
58
  end
112
-
113
- begin
114
- # TODO test exception capture
115
- probe_manager.remove_other_probes(current_probe_ids.keys)
116
- rescue => exc
117
- raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
118
-
119
- component.logger.debug { "di: unhandled exception removing probes in DI remote receiver: #{exc.class}: #{exc}" }
120
- component.telemetry&.report(exc, description: "Unhandled exception removing probes in DI remote receiver")
121
- end
122
59
  end
123
60
  end
124
61
  end
@@ -130,14 +67,82 @@ module Datadog
130
67
 
131
68
  private
132
69
 
133
- def parse_content(content)
134
- data = content.data.read
70
+ def add_probe(content, component)
71
+ probe_spec = parse_content(content)
72
+ probe = component.parse_probe_spec_and_notify(probe_spec)
73
+ component.logger.debug { "di: received #{probe.type} probe at #{probe.location} (#{probe.id}) via RC" }
74
+
75
+ begin
76
+ # TODO test exception capture
77
+ component.probe_manager.add_probe(probe)
78
+ content.applied
79
+ rescue DI::Error::DITargetNotInRegistry => exc
80
+ component.telemetry&.report(exc, description: "Line probe is targeting a loaded file that is not in code tracker")
81
+
82
+ payload = component.probe_notification_builder.build_errored(probe, exc)
83
+ component.probe_notifier_worker.add_status(payload)
84
+
85
+ # If a probe fails to install, we will mark the content
86
+ # as errored. On subsequent remote configuration application
87
+ # attemps, probe manager will raise the "previously errored"
88
+ # exception and we'll rescue it here, again marking the
89
+ # content as errored but with a somewhat different exception
90
+ # message.
91
+ # TODO assert content state (errored for this example)
92
+ content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
93
+ rescue => exc
94
+ raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
95
+
96
+ component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI remote receiver: #{exc.class}: #{exc}" }
97
+ component.telemetry&.report(exc, description: "Unhandled exception adding probe in DI remote receiver")
98
+
99
+ # TODO test this path
100
+ payload = component.probe_notification_builder.build_errored(probe, exc)
101
+ component.probe_notifier_worker.add_status(payload)
102
+
103
+ # If a probe fails to install, we will mark the content
104
+ # as errored. On subsequent remote configuration application
105
+ # attemps, probe manager will raise the "previously errored"
106
+ # exception and we'll rescue it here, again marking the
107
+ # content as errored but with a somewhat different exception
108
+ # message.
109
+ # TODO assert content state (errored for this example)
110
+ content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
111
+ end
112
+
113
+ # Important: even if processing fails for this probe config,
114
+ # we need to note it as being current so that we do not
115
+ # try to remove instrumentation that is still supposed to be
116
+ # active.
117
+ #current_probe_ids[probe_spec.fetch('id')] = true
118
+ rescue => exc
119
+ raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
120
+
121
+ component.logger.debug { "di: unhandled exception handling a probe in DI remote receiver: #{exc.class}: #{exc}" }
122
+ component.telemetry&.report(exc, description: "Unhandled exception handling probe in DI remote receiver")
135
123
 
136
- content.data.rewind
124
+ # TODO assert content state (errored for this example)
125
+ content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
126
+ end
137
127
 
138
- raise ReadError, 'EOF reached' if data.nil?
128
+ # This method does not mark +previous_content+ as succeeded or errored,
129
+ # because that content is from a previous RC response and has already
130
+ # been marked. Removal of probes happens when an RC entry disappears,
131
+ # as such there is nothing to mark.
132
+ def remove_probe(previous_content, component)
133
+ # TODO test exception capture
134
+ probe_spec = parse_content(previous_content)
135
+ probe_id = probe_spec.fetch('id')
136
+ component.probe_manager.remove_probe(probe_id)
137
+ rescue => exc
138
+ raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
139
+
140
+ component.logger.debug { "di: unhandled exception removing probes in DI remote receiver: #{exc.class}: #{exc}" }
141
+ component.telemetry&.report(exc, description: "Unhandled exception removing probes in DI remote receiver")
142
+ end
139
143
 
140
- JSON.parse(data)
144
+ def parse_content(content)
145
+ JSON.parse(content.data)
141
146
  end
142
147
  end
143
148
  end
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  require_relative '../../core/transport/parcel'
6
+ require_relative '../../core/transport/request'
7
+ require_relative '../../core/transport/transport'
4
8
  require_relative 'http/diagnostics'
5
9
 
6
10
  module Datadog
@@ -14,46 +18,14 @@ module Datadog
14
18
  class Request < Datadog::Core::Transport::Request
15
19
  end
16
20
 
17
- class Transport
18
- attr_reader :client, :apis, :default_api, :current_api_id, :logger
19
-
20
- def initialize(apis, default_api, logger:)
21
- @apis = apis
22
- @logger = logger
23
-
24
- @client = DI::Transport::HTTP::Diagnostics::Client.new(current_api, logger: logger)
25
- end
26
-
27
- def current_api
28
- @apis[HTTP::API::DIAGNOSTICS]
29
- end
30
-
21
+ class Transport < Core::Transport::Transport
31
22
  def send_diagnostics(payload)
23
+ # TODO use transport encoder functionality?
32
24
  json = JSON.dump(payload)
33
25
  parcel = EncodedParcel.new(json)
34
26
  request = Request.new(parcel)
35
27
 
36
- response = @client.send_diagnostics_payload(request)
37
- unless response.ok?
38
- # TODO Datadog::Core::Transport::InternalErrorResponse
39
- # does not have +code+ method, what is the actual API of
40
- # these response objects?
41
- raise Error::AgentCommunicationError, "send_diagnostics failed: #{begin
42
- response.code
43
- rescue
44
- "???"
45
- end}: #{response.payload}"
46
- end
47
- rescue Error::AgentCommunicationError
48
- raise
49
- # Datadog::Core::Transport does not perform any exception mapping,
50
- # therefore we could have any exception here from failure to parse
51
- # agent URI for example.
52
- # If we ever implement retries for network errors, we should distinguish
53
- # actual network errors from non-network errors that are raised by
54
- # transport code.
55
- rescue => exc
56
- raise Error::AgentCommunicationError, "send_diagnostics failed: #{exc.class}: #{exc}"
28
+ client.send_request(:diagnostics, request)
57
29
  end
58
30
  end
59
31
  end