datadog 2.20.0 → 2.22.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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -1
  3. data/README.md +0 -1
  4. data/ext/LIBDATADOG_DEVELOPMENT.md +60 -0
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  6. data/ext/libdatadog_api/ddsketch.c +106 -0
  7. data/ext/libdatadog_api/init.c +3 -0
  8. data/ext/libdatadog_api/library_config.c +35 -27
  9. data/ext/libdatadog_api/process_discovery.c +24 -18
  10. data/ext/libdatadog_extconf_helpers.rb +1 -1
  11. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  12. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
  13. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
  14. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  15. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  16. data/lib/datadog/appsec/api_security/route_extractor.rb +6 -2
  17. data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
  18. data/lib/datadog/appsec/assets/waf_rules/recommended.json +359 -4
  19. data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
  20. data/lib/datadog/appsec/autoload.rb +1 -1
  21. data/lib/datadog/appsec/compressed_json.rb +1 -1
  22. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  23. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
  24. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +3 -2
  25. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +3 -1
  26. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +3 -1
  27. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -4
  28. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -1
  29. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -2
  30. data/lib/datadog/appsec/contrib/rails/patcher.rb +30 -0
  31. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +3 -1
  32. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -4
  33. data/lib/datadog/appsec/event.rb +12 -14
  34. data/lib/datadog/appsec/metrics/collector.rb +19 -3
  35. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +2 -1
  36. data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -4
  37. data/lib/datadog/appsec/remote.rb +25 -13
  38. data/lib/datadog/appsec/security_engine/result.rb +28 -9
  39. data/lib/datadog/appsec/security_engine/runner.rb +17 -7
  40. data/lib/datadog/appsec/security_event.rb +5 -7
  41. data/lib/datadog/core/configuration/agent_settings_resolver.rb +4 -4
  42. data/lib/datadog/core/configuration/components.rb +22 -8
  43. data/lib/datadog/core/configuration/config_helper.rb +100 -0
  44. data/lib/datadog/core/configuration/deprecations.rb +36 -0
  45. data/lib/datadog/core/configuration/ext.rb +0 -1
  46. data/lib/datadog/core/configuration/option.rb +38 -43
  47. data/lib/datadog/core/configuration/option_definition.rb +0 -9
  48. data/lib/datadog/core/configuration/options.rb +1 -5
  49. data/lib/datadog/core/configuration/settings.rb +10 -6
  50. data/lib/datadog/core/configuration/stable_config.rb +10 -0
  51. data/lib/datadog/core/configuration/supported_configurations.rb +337 -0
  52. data/lib/datadog/core/configuration.rb +2 -2
  53. data/lib/datadog/core/ddsketch.rb +21 -0
  54. data/lib/datadog/core/deprecations.rb +2 -2
  55. data/lib/datadog/core/environment/ext.rb +0 -2
  56. data/lib/datadog/core/environment/git.rb +2 -2
  57. data/lib/datadog/core/environment/variable_helpers.rb +3 -3
  58. data/lib/datadog/core/environment/yjit.rb +2 -1
  59. data/lib/datadog/core/metrics/client.rb +2 -2
  60. data/lib/datadog/core/pin.rb +4 -8
  61. data/lib/datadog/core/process_discovery/tracer_memfd.rb +2 -4
  62. data/lib/datadog/core/process_discovery.rb +48 -23
  63. data/lib/datadog/core/remote/component.rb +4 -6
  64. data/lib/datadog/core/runtime/ext.rb +0 -1
  65. data/lib/datadog/core/telemetry/component.rb +11 -0
  66. data/lib/datadog/core/telemetry/emitter.rb +6 -6
  67. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  68. data/lib/datadog/core/telemetry/event/app_started.rb +2 -2
  69. data/lib/datadog/core/telemetry/event.rb +1 -0
  70. data/lib/datadog/core/transport/response.rb +4 -1
  71. data/lib/datadog/core/utils/network.rb +19 -0
  72. data/lib/datadog/core.rb +2 -0
  73. data/lib/datadog/di/boot.rb +5 -3
  74. data/lib/datadog/di/component.rb +14 -0
  75. data/lib/datadog/di/context.rb +70 -0
  76. data/lib/datadog/di/el/compiler.rb +164 -0
  77. data/lib/datadog/di/el/evaluator.rb +159 -0
  78. data/lib/datadog/di/el/expression.rb +42 -0
  79. data/lib/datadog/di/el.rb +5 -0
  80. data/lib/datadog/di/error.rb +25 -0
  81. data/lib/datadog/di/instrumenter.rb +101 -32
  82. data/lib/datadog/di/probe.rb +35 -15
  83. data/lib/datadog/di/probe_builder.rb +39 -1
  84. data/lib/datadog/di/probe_file_loader.rb +1 -1
  85. data/lib/datadog/di/probe_manager.rb +3 -2
  86. data/lib/datadog/di/probe_notification_builder.rb +50 -51
  87. data/lib/datadog/di/serializer.rb +151 -7
  88. data/lib/datadog/opentelemetry/sdk/configurator.rb +1 -1
  89. data/lib/datadog/profiling/collectors/info.rb +1 -1
  90. data/lib/datadog/profiling/ext.rb +2 -1
  91. data/lib/datadog/profiling/http_transport.rb +1 -1
  92. data/lib/datadog/profiling/tasks/exec.rb +2 -2
  93. data/lib/datadog/tracing/component.rb +6 -17
  94. data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
  95. data/lib/datadog/tracing/configuration/ext.rb +0 -3
  96. data/lib/datadog/tracing/configuration/settings.rb +15 -10
  97. data/lib/datadog/tracing/contrib/component.rb +2 -2
  98. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
  99. data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
  100. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +63 -27
  101. data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -0
  102. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +7 -1
  103. data/lib/datadog/tracing/contrib/rails/ext.rb +2 -1
  104. data/lib/datadog/tracing/contrib/rails/integration.rb +1 -1
  105. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +1 -1
  106. data/lib/datadog/tracing/metadata/ext.rb +8 -0
  107. data/lib/datadog/version.rb +1 -1
  108. metadata +25 -9
  109. data/ext/libdatadog_api/macos_development.md +0 -26
@@ -3,6 +3,7 @@
3
3
  require_relative '../core/utils/time'
4
4
 
5
5
  # rubocop:disable Lint/AssignmentInCondition
6
+ # rubocop:disable Style/AndOr
6
7
 
7
8
  module Datadog
8
9
  module DI
@@ -118,7 +119,26 @@ module Datadog
118
119
 
119
120
  mod = Module.new do
120
121
  define_method(method_name) do |*args, **kwargs, &target_block| # steep:ignore
121
- if rate_limiter.nil? || rate_limiter.allow?
122
+ continue = true
123
+ if condition = probe.condition
124
+ begin
125
+ # This context will be recreated later, unlike for line probes.
126
+ context = Context.new(
127
+ locals: serializer.combine_args(args, kwargs, self),
128
+ target_self: self,
129
+ probe: probe, settings: settings, serializer: serializer,
130
+ caller_locations: caller_locations,
131
+ )
132
+ continue = condition.satisfied?(context)
133
+ rescue
134
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
135
+
136
+ # TODO log / report via telemetry?
137
+ continue = false
138
+ end
139
+ end
140
+
141
+ if continue and rate_limiter.nil? || rate_limiter.allow?
122
142
  # Arguments may be mutated by the method, therefore
123
143
  # they need to be serialized prior to method invocation.
124
144
  serialized_entry_args = if probe.capture_snapshot?
@@ -127,19 +147,29 @@ module Datadog
127
147
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
128
148
  end
129
149
  start_time = Core::Utils::Time.get_time
130
- # Under Ruby 2.6 we cannot just call super(*args, **kwargs)
131
- # for methods defined via method_missing.
132
- rv = if args.any?
133
- if kwargs.any?
134
- super(*args, **kwargs, &target_block)
150
+
151
+ rv = nil
152
+ begin
153
+ # Under Ruby 2.6 we cannot just call super(*args, **kwargs)
154
+ # for methods defined via method_missing.
155
+ rv = if args.any?
156
+ if kwargs.any?
157
+ super(*args, **kwargs, &target_block)
158
+ else
159
+ super(*args, &target_block)
160
+ end
161
+ elsif kwargs.any?
162
+ super(**kwargs, &target_block)
135
163
  else
136
- super(*args, &target_block)
164
+ super(&target_block)
137
165
  end
138
- elsif kwargs.any?
139
- super(**kwargs, &target_block)
140
- else
141
- super(&target_block)
166
+ rescue NoMemoryError, Interrupt, SystemExit
167
+ raise
168
+ rescue Exception => exc # standard:disable Lint/RescueException
169
+ # We will raise the exception captured here later, after
170
+ # the instrumentation callback runs.
142
171
  end
172
+
143
173
  duration = Core::Utils::Time.get_time - start_time
144
174
  # The method itself is not part of the stack trace because
145
175
  # we are getting the stack trace from outside of the method.
@@ -158,12 +188,20 @@ module Datadog
158
188
  end
159
189
  caller_locs = method_frame + caller_locations # steep:ignore
160
190
  # TODO capture arguments at exit
191
+
192
+ context = Context.new(locals: nil, target_self: self,
193
+ probe: probe, settings: settings, serializer: serializer,
194
+ serialized_entry_args: serialized_entry_args,
195
+ caller_locations: caller_locs,
196
+ return_value: rv, duration: duration, exception: exc,)
197
+
161
198
  # & is to stop steep complaints, block is always present here.
162
- block&.call(probe: probe, rv: rv,
163
- duration: duration, caller_locations: caller_locs,
164
- target_self: self,
165
- serialized_entry_args: serialized_entry_args)
166
- rv
199
+ block&.call(context)
200
+ if exc
201
+ raise exc
202
+ else
203
+ rv
204
+ end
167
205
  else
168
206
  # stop standard from trying to mess up my code
169
207
  _ = 42
@@ -307,27 +345,57 @@ module Datadog
307
345
  # are invoked for *each* line of Ruby executed.
308
346
  # TODO find out exactly when the path in trace point is relative.
309
347
  # Looks like this is the case when line trace point is not targeted?
310
- if iseq || tp.lineno == probe.line_no && (
348
+ continue = iseq || tp.lineno == probe.line_no && (
311
349
  probe.file == tp.path || probe.file_matches?(tp.path)
312
350
  )
313
- if rate_limiter.nil? || rate_limiter.allow?
314
- serialized_locals = if probe.capture_snapshot?
315
- serializer.serialize_vars(Instrumenter.get_local_variables(tp),
316
- depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
317
- attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)
318
- end
319
- if probe.capture_snapshot?
320
- serializer.serialize_value(tp.self,
321
- depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
322
- attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)
323
- end
324
- # & is to stop steep complaints, block is always present here.
325
- block&.call(probe: probe,
326
- serialized_locals: serialized_locals,
351
+
352
+ # We set the trace point on :return to be able to instrument
353
+ # 'end' lines. This also causes the trace point to be invoked on
354
+ # non-'end' lines when a line raises an exception, since the
355
+ # exception causes the method to stop executing and stack unwends.
356
+ # We do not want two invocations of the trace point.
357
+ # Therefore, if a trace point is invoked with a :line event,
358
+ # mark it as such and ignore subsequent :return events.
359
+ continue &&= if probe.executed_on_line?
360
+ tp.event == :line
361
+ else
362
+ if tp.event == :line
363
+ probe.executed_on_line!
364
+ end
365
+ true
366
+ end
367
+
368
+ if continue
369
+ if condition = probe.condition
370
+ context = Context.new(
371
+ locals: Instrumenter.get_local_variables(tp),
327
372
  target_self: tp.self,
328
- path: tp.path, caller_locations: caller_locations)
373
+ probe: probe, settings: settings, serializer: serializer,
374
+ path: tp.path,
375
+ caller_locations: caller_locations,
376
+ )
377
+ continue = condition.satisfied?(context)
329
378
  end
330
379
  end
380
+
381
+ continue &&= rate_limiter.nil? || rate_limiter.allow? # standard:disable Style/AndOr
382
+
383
+ if continue
384
+ # The context creation is relatively expensive and we don't
385
+ # want to run it if the callback won't be executed due to the
386
+ # rate limit.
387
+ # Thus the copy-paste of the creation call here.
388
+ context ||= Context.new(
389
+ locals: Instrumenter.get_local_variables(tp),
390
+ target_self: tp.self,
391
+ probe: probe, settings: settings, serializer: serializer,
392
+ path: tp.path,
393
+ caller_locations: caller_locations,
394
+ )
395
+
396
+ # & is to stop steep complaints, block is always present here.
397
+ block&.call(context)
398
+ end
331
399
  rescue => exc
332
400
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
333
401
  logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
@@ -449,3 +517,4 @@ module Datadog
449
517
  end
450
518
 
451
519
  # rubocop:enable Lint/AssignmentInCondition
520
+ # rubocop:enable Style/AndOr
@@ -17,7 +17,7 @@ module Datadog
17
17
  # and remote config code must be prepared to deal with exceptions
18
18
  # raised by Probe constructor in particular. Therefore, Probe constructor
19
19
  # will raise an exception if it determines that there is not enough
20
- # information (or confilcting information) in the arguments to create a
20
+ # information (or conflicting information) in the arguments to create a
21
21
  # functional probe, and upstream code is tasked with not spamming logs
22
22
  # with notifications of such errors (and potentially limiting the
23
23
  # attempts to construct probe from a given payload).
@@ -36,8 +36,9 @@ module Datadog
36
36
 
37
37
  def initialize(id:, type:,
38
38
  file: nil, line_no: nil, type_name: nil, method_name: nil,
39
- template: nil, capture_snapshot: false, max_capture_depth: nil,
40
- max_capture_attribute_count: nil,
39
+ template: nil, template_segments: nil,
40
+ capture_snapshot: false, max_capture_depth: nil,
41
+ max_capture_attribute_count: nil, condition: nil,
41
42
  rate_limit: nil)
42
43
  # Perform some sanity checks here to detect unexpected attribute
43
44
  # combinations, in order to not do them in subsequent code.
@@ -45,9 +46,17 @@ module Datadog
45
46
  raise ArgumentError, "Unknown probe type: #{type}"
46
47
  end
47
48
 
48
- if line_no && method_name
49
- raise ArgumentError, "Probe contains both line number and method name: #{id}"
50
- end
49
+ # Probe should be inferred to be a line probe if the specification
50
+ # contains a line number. This how Java tracer works and Go tracer
51
+ # is implementing the same behavior, and Go will have all 3 fields
52
+ # (file path, line number and method name) for line probes.
53
+ # Do not raise if line number and method name both exist - instead
54
+ # treat the probe as a line probe.
55
+ #
56
+ # In the future we want to provide type name and method name to line
57
+ # probes, so that the library can verify that the instrumented line
58
+ # is in the method that the frontend showed to the user when the
59
+ # user created the probe.
51
60
 
52
61
  if line_no && !file
53
62
  raise ArgumentError, "Probe contains line number but not file: #{id}"
@@ -57,6 +66,10 @@ module Datadog
57
66
  raise ArgumentError, "Partial method probe definition: #{id}"
58
67
  end
59
68
 
69
+ if line_no.nil? && method_name.nil?
70
+ raise ArgumentError, "Unhandled probe type: neither method nor line probe: #{id}"
71
+ end
72
+
60
73
  @id = id
61
74
  @type = type
62
75
  @file = file
@@ -64,17 +77,11 @@ module Datadog
64
77
  @type_name = type_name
65
78
  @method_name = method_name
66
79
  @template = template
80
+ @template_segments = template_segments
67
81
  @capture_snapshot = !!capture_snapshot
68
82
  @max_capture_depth = max_capture_depth
69
83
  @max_capture_attribute_count = max_capture_attribute_count
70
-
71
- # These checks use instance methods that have more complex logic
72
- # than checking a single argument value. To avoid duplicating
73
- # the logic here, use the methods and perform these checks after
74
- # instance variable assignment.
75
- unless method? || line?
76
- raise ArgumentError, "Unhandled probe type: neither method nor line probe: #{id}"
77
- end
84
+ @condition = condition
78
85
 
79
86
  @rate_limit = rate_limit || (@capture_snapshot ? 1 : 5000)
80
87
  @rate_limiter = Datadog::Core::TokenBucket.new(@rate_limit)
@@ -89,6 +96,10 @@ module Datadog
89
96
  attr_reader :type_name
90
97
  attr_reader :method_name
91
98
  attr_reader :template
99
+ attr_reader :template_segments
100
+
101
+ # The compiled condition for the probe, as a String.
102
+ attr_reader :condition
92
103
 
93
104
  # Configured maximum capture depth. Can be nil in which case
94
105
  # the global default will be used.
@@ -122,7 +133,7 @@ module Datadog
122
133
 
123
134
  # Returns whether the probe is a method probe.
124
135
  def method?
125
- !!(type_name && method_name)
136
+ line_no.nil?
126
137
  end
127
138
 
128
139
  # Returns the line number associated with the probe, raising
@@ -186,6 +197,15 @@ module Datadog
186
197
  def emitting_notified?
187
198
  !!@emitting_notified
188
199
  end
200
+
201
+ def executed_on_line?
202
+ !!@executed_on_line
203
+ end
204
+
205
+ def executed_on_line!
206
+ # TODO lock?
207
+ @executed_on_line = true
208
+ end
189
209
  end
190
210
  end
191
211
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Lint/AssignmentInCondition
4
+
3
5
  require_relative "probe"
6
+ require_relative 'el'
4
7
 
5
8
  module Datadog
6
9
  module DI
@@ -21,10 +24,19 @@ module Datadog
21
24
  'LOG_PROBE' => :log,
22
25
  }.freeze
23
26
 
24
- module_function def build_from_remote_config(config)
27
+ module_function
28
+
29
+ def build_from_remote_config(config)
25
30
  # The validations here are not yet comprehensive.
26
31
  type = config.fetch('type')
27
32
  type_symbol = PROBE_TYPES[type] or raise ArgumentError, "Unrecognized probe type: #{type}"
33
+ cond = if cond_spec = config['when']
34
+ unless cond_spec['dsl'] && cond_spec['json']
35
+ raise ArgumentError, "Malformed condition specification for probe: #{config}"
36
+ end
37
+ compiled = EL::Compiler.new.compile(cond_spec['json'])
38
+ EL::Expression.new(cond_spec['dsl'], compiled)
39
+ end
28
40
  Probe.new(
29
41
  id: config.fetch("id"),
30
42
  type: type_symbol,
@@ -34,15 +46,41 @@ module Datadog
34
46
  line_no: config["where"]&.[]("lines")&.compact&.map(&:to_i)&.first,
35
47
  type_name: config["where"]&.[]("typeName"),
36
48
  method_name: config["where"]&.[]("methodName"),
49
+ # We should not be using the template for anything - we instead
50
+ # use +segments+ - but keep the template for debugging.
37
51
  template: config["template"],
52
+ template_segments: build_template_segments(config['segments']),
38
53
  capture_snapshot: !!config["captureSnapshot"],
39
54
  max_capture_depth: config["capture"]&.[]("maxReferenceDepth"),
40
55
  max_capture_attribute_count: config["capture"]&.[]("maxFieldCount"),
41
56
  rate_limit: config["sampling"]&.[]("snapshotsPerSecond"),
57
+ condition: cond,
42
58
  )
43
59
  rescue KeyError => exc
44
60
  raise ArgumentError, "Malformed remote configuration entry for probe: #{exc.class}: #{exc}: #{config}"
45
61
  end
62
+
63
+ def build_template_segments(segments)
64
+ segments&.map do |segment|
65
+ if Hash === segment
66
+ if str = segment['str']
67
+ str
68
+ elsif ast = segment['json']
69
+ unless dsl = segment['dsl']
70
+ raise ArgumentError, "Missing dsl for json in segment: #{segment}"
71
+ end
72
+ compiled = EL::Compiler.new.compile(ast)
73
+ EL::Expression.new(dsl, compiled)
74
+ else
75
+ # TODO report to telemetry?
76
+ end
77
+ else
78
+ # TODO report to telemetry?
79
+ end
80
+ end&.compact
81
+ end
46
82
  end
47
83
  end
48
84
  end
85
+
86
+ # rubocop:enable Lint/AssignmentInCondition
@@ -20,7 +20,7 @@ module Datadog
20
20
  module_function def load_now
21
21
  should_propagate = false
22
22
 
23
- probe_file_path = ENV['DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE']
23
+ probe_file_path = DATADOG_ENV['DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE']
24
24
  if probe_file_path.nil? || probe_file_path.empty?
25
25
  return
26
26
  end
@@ -229,7 +229,8 @@ module Datadog
229
229
  # This method is responsible for queueing probe status to be sent to the
230
230
  # backend (once per the probe's lifetime) and a snapshot corresponding
231
231
  # to the current invocation.
232
- def probe_executed_callback(probe:, **opts)
232
+ def probe_executed_callback(context)
233
+ probe = context.probe
233
234
  logger.trace { "di: executed #{probe.type} probe at #{probe.location} (#{probe.id})" }
234
235
  unless probe.emitting_notified?
235
236
  payload = probe_notification_builder.build_emitting(probe)
@@ -237,7 +238,7 @@ module Datadog
237
238
  probe.emitting_notified = true
238
239
  end
239
240
 
240
- payload = probe_notification_builder.build_executed(probe, **opts)
241
+ payload = probe_notification_builder.build_executed(context)
241
242
  probe_notifier_worker.add_snapshot(payload)
242
243
  end
243
244
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Lint/AssignmentInCondition
4
+
3
5
  module Datadog
4
6
  module DI
5
7
  # Builds probe status notification and snapshot payloads.
@@ -40,32 +42,17 @@ module Datadog
40
42
 
41
43
  # Duration is in seconds.
42
44
  # path is the actual path of the instrumented file.
43
- def build_executed(probe,
44
- path: nil, rv: nil, duration: nil, caller_locations: nil,
45
- serialized_locals: nil, args: nil, kwargs: nil, target_self: nil,
46
- serialized_entry_args: nil)
47
- build_snapshot(probe, rv: rv, serialized_locals: serialized_locals,
48
- # Actual path of the instrumented file.
49
- path: path,
50
- duration: duration,
51
- # TODO check how many stack frames we should be keeping/sending,
52
- # this should be all frames for enriched probes and no frames for
53
- # non-enriched probes?
54
- caller_locations: caller_locations,
55
- args: args, kwargs: kwargs,
56
- target_self: target_self,
57
- serialized_entry_args: serialized_entry_args)
45
+ def build_executed(context)
46
+ build_snapshot(context)
58
47
  end
59
48
 
60
- def build_snapshot(probe, rv: nil, serialized_locals: nil, path: nil,
61
- # In Ruby everything is a method, therefore we should always have
62
- # a target self. However, if we are not capturing a snapshot,
63
- # there is no need to pass in the target self.
64
- target_self: nil,
65
- duration: nil, caller_locations: nil,
66
- args: nil, kwargs: nil,
67
- serialized_entry_args: nil)
68
- if probe.capture_snapshot? && !target_self
49
+ NANOSECONDS = 10**9
50
+ MILLISECONDS = 1000
51
+
52
+ def build_snapshot(context)
53
+ probe = context.probe
54
+
55
+ if probe.capture_snapshot? && !context.target_self
69
56
  raise ArgumentError, "Asked to build snapshot with snapshot capture but target_self is nil"
70
57
  end
71
58
 
@@ -74,22 +61,14 @@ module Datadog
74
61
  captures = if probe.capture_snapshot?
75
62
  if probe.method?
76
63
  return_arguments = {
77
- "@return": serializer.serialize_value(rv,
64
+ "@return": serializer.serialize_value(context.return_value,
78
65
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
79
66
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
80
- self: serializer.serialize_value(target_self),
67
+ self: serializer.serialize_value(context.target_self),
81
68
  }
82
69
  {
83
70
  entry: {
84
- # standard:disable all
85
- arguments: if serialized_entry_args
86
- serialized_entry_args
87
- else
88
- (args || kwargs) && serializer.serialize_args(args, kwargs, target_self,
89
- depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
90
- attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
91
- end,
92
- # standard:enable all
71
+ arguments: context.serialized_entry_args,
93
72
  },
94
73
  return: {
95
74
  arguments: return_arguments,
@@ -98,10 +77,10 @@ module Datadog
98
77
  }
99
78
  elsif probe.line?
100
79
  {
101
- lines: serialized_locals && {
80
+ lines: (locals = context.serialized_locals) && {
102
81
  probe.line_no => {
103
- locals: serialized_locals,
104
- arguments: {self: serializer.serialize_value(target_self)},
82
+ locals: locals,
83
+ arguments: {self: serializer.serialize_value(context.target_self)},
105
84
  },
106
85
  },
107
86
  }
@@ -110,7 +89,7 @@ module Datadog
110
89
 
111
90
  location = if probe.line?
112
91
  {
113
- file: path,
92
+ file: context.path,
114
93
  lines: [probe.line_no],
115
94
  }
116
95
  elsif probe.method?
@@ -120,17 +99,23 @@ module Datadog
120
99
  }
121
100
  end
122
101
 
123
- stack = if caller_locations
102
+ stack = if caller_locations = context.caller_locations
124
103
  format_caller_locations(caller_locations)
125
104
  end
126
105
 
127
106
  timestamp = timestamp_now
107
+ message = nil
108
+ evaluation_errors = []
109
+ if segments = probe.template_segments
110
+ message, evaluation_errors = evaluate_template(segments, context)
111
+ end
112
+ duration = context.duration
128
113
  {
129
114
  service: settings.service,
130
115
  "debugger.snapshot": {
131
116
  id: SecureRandom.uuid,
132
117
  timestamp: timestamp,
133
- evaluationErrors: [],
118
+ evaluationErrors: evaluation_errors,
134
119
  probe: {
135
120
  id: probe.id,
136
121
  version: 0,
@@ -143,7 +128,7 @@ module Datadog
143
128
  },
144
129
  # In python tracer duration is under debugger.snapshot,
145
130
  # but UI appears to expect it here at top level.
146
- duration: duration ? (duration * 10**9).to_i : 0,
131
+ duration: duration ? (duration * NANOSECONDS).to_i : 0,
147
132
  host: nil,
148
133
  logger: {
149
134
  name: probe.file,
@@ -160,8 +145,7 @@ module Datadog
160
145
  "dd.trace_id": active_trace&.id&.to_s,
161
146
  "dd.span_id": active_span&.id&.to_s,
162
147
  ddsource: 'dd_debugger',
163
- message: probe.template && evaluate_template(probe.template,
164
- duration: duration ? duration * 1000 : 0),
148
+ message: message,
165
149
  timestamp: timestamp,
166
150
  }
167
151
  end
@@ -192,16 +176,29 @@ module Datadog
192
176
  end
193
177
  end
194
178
 
195
- def evaluate_template(template, **vars)
196
- message = template.dup
197
- vars.each do |key, value|
198
- message.gsub!("{@#{key}}", value.to_s)
199
- end
200
- message
179
+ def evaluate_template(template_segments, context)
180
+ evaluation_errors = []
181
+ message = template_segments.map do |segment|
182
+ case segment
183
+ when String
184
+ segment
185
+ when EL::Expression
186
+ serializer.serialize_value_for_message(segment.evaluate(context))
187
+ else
188
+ raise ArgumentError, "Invalid template segment type: #{segment}"
189
+ end
190
+ rescue => exc
191
+ evaluation_errors << {
192
+ message: "#{exc.class}: #{exc}",
193
+ expr: segment.dsl_expr,
194
+ }
195
+ '[evaluation error]'
196
+ end.join
197
+ [message, evaluation_errors]
201
198
  end
202
199
 
203
200
  def timestamp_now
204
- (Core::Utils::Time.now.to_f * 1000).to_i
201
+ (Core::Utils::Time.now.to_f * MILLISECONDS).to_i
205
202
  end
206
203
 
207
204
  def active_trace
@@ -218,3 +215,5 @@ module Datadog
218
215
  end
219
216
  end
220
217
  end
218
+
219
+ # rubocop:enable Lint/AssignmentInCondition