datadog 2.21.0 → 2.23.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 (205) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -2
  3. data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
  4. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  5. data/ext/datadog_profiling_native_extension/collectors_stack.c +4 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
  7. data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
  9. data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
  10. data/ext/libdatadog_api/ddsketch.c +106 -0
  11. data/ext/libdatadog_api/feature_flags.c +554 -0
  12. data/ext/libdatadog_api/feature_flags.h +5 -0
  13. data/ext/libdatadog_api/init.c +5 -0
  14. data/ext/libdatadog_api/library_config.c +34 -25
  15. data/ext/libdatadog_api/process_discovery.c +19 -13
  16. data/ext/libdatadog_extconf_helpers.rb +1 -1
  17. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  18. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
  19. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
  20. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  21. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  22. data/lib/datadog/appsec/api_security/route_extractor.rb +23 -6
  23. data/lib/datadog/appsec/api_security/sampler.rb +7 -4
  24. data/lib/datadog/appsec/assets/blocked.html +8 -0
  25. data/lib/datadog/appsec/assets/blocked.json +1 -1
  26. data/lib/datadog/appsec/assets/blocked.text +3 -1
  27. data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
  28. data/lib/datadog/appsec/assets/waf_rules/recommended.json +359 -4
  29. data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
  30. data/lib/datadog/appsec/assets.rb +1 -1
  31. data/lib/datadog/appsec/compressed_json.rb +1 -1
  32. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  33. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
  34. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +3 -2
  35. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +3 -1
  36. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +3 -1
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -4
  38. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -1
  39. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -2
  40. data/lib/datadog/appsec/contrib/rails/patcher.rb +30 -0
  41. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +3 -1
  42. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -4
  43. data/lib/datadog/appsec/event.rb +12 -14
  44. data/lib/datadog/appsec/metrics/collector.rb +19 -3
  45. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +2 -1
  46. data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -4
  47. data/lib/datadog/appsec/remote.rb +29 -13
  48. data/lib/datadog/appsec/response.rb +18 -4
  49. data/lib/datadog/appsec/security_engine/result.rb +28 -9
  50. data/lib/datadog/appsec/security_engine/runner.rb +17 -7
  51. data/lib/datadog/appsec/security_event.rb +5 -7
  52. data/lib/datadog/core/configuration/components.rb +44 -9
  53. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  54. data/lib/datadog/core/configuration/settings.rb +14 -0
  55. data/lib/datadog/core/configuration/stable_config.rb +10 -0
  56. data/lib/datadog/core/configuration/supported_configurations.rb +330 -299
  57. data/lib/datadog/core/configuration.rb +1 -1
  58. data/lib/datadog/core/ddsketch.rb +19 -0
  59. data/lib/datadog/core/environment/ext.rb +6 -0
  60. data/lib/datadog/core/environment/process.rb +79 -0
  61. data/lib/datadog/core/environment/yjit.rb +2 -1
  62. data/lib/datadog/core/feature_flags.rb +61 -0
  63. data/lib/datadog/core/pin.rb +4 -8
  64. data/lib/datadog/core/process_discovery.rb +4 -2
  65. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  66. data/lib/datadog/core/remote/component.rb +4 -6
  67. data/lib/datadog/core/remote/transport/config.rb +2 -10
  68. data/lib/datadog/core/remote/transport/http/config.rb +9 -9
  69. data/lib/datadog/core/remote/transport/http/negotiation.rb +17 -8
  70. data/lib/datadog/core/remote/transport/http.rb +2 -0
  71. data/lib/datadog/core/remote/transport/negotiation.rb +2 -18
  72. data/lib/datadog/core/remote/worker.rb +25 -37
  73. data/lib/datadog/core/tag_builder.rb +0 -4
  74. data/lib/datadog/core/tag_normalizer.rb +84 -0
  75. data/lib/datadog/core/telemetry/component.rb +18 -3
  76. data/lib/datadog/core/telemetry/emitter.rb +6 -6
  77. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  78. data/lib/datadog/core/telemetry/event/app_started.rb +52 -49
  79. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
  80. data/lib/datadog/core/telemetry/event.rb +1 -0
  81. data/lib/datadog/core/telemetry/logger.rb +2 -2
  82. data/lib/datadog/core/telemetry/logging.rb +2 -8
  83. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -6
  84. data/lib/datadog/core/telemetry/transport/telemetry.rb +1 -2
  85. data/lib/datadog/core/transport/http/client.rb +69 -0
  86. data/lib/datadog/core/transport/response.rb +4 -1
  87. data/lib/datadog/core/utils/array.rb +29 -0
  88. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
  89. data/lib/datadog/core/utils/network.rb +22 -1
  90. data/lib/datadog/core/utils/only_once_successful.rb +6 -2
  91. data/lib/datadog/core/utils.rb +2 -0
  92. data/lib/datadog/data_streams/configuration/settings.rb +49 -0
  93. data/lib/datadog/data_streams/configuration.rb +11 -0
  94. data/lib/datadog/data_streams/ext.rb +11 -0
  95. data/lib/datadog/data_streams/extensions.rb +16 -0
  96. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  97. data/lib/datadog/data_streams/processor.rb +509 -0
  98. data/lib/datadog/data_streams/transport/http/api.rb +33 -0
  99. data/lib/datadog/data_streams/transport/http/client.rb +21 -0
  100. data/lib/datadog/data_streams/transport/http/stats.rb +87 -0
  101. data/lib/datadog/data_streams/transport/http.rb +41 -0
  102. data/lib/datadog/data_streams/transport/stats.rb +60 -0
  103. data/lib/datadog/data_streams.rb +100 -0
  104. data/lib/datadog/di/boot.rb +1 -0
  105. data/lib/datadog/di/component.rb +14 -16
  106. data/lib/datadog/di/context.rb +70 -0
  107. data/lib/datadog/di/el/compiler.rb +164 -0
  108. data/lib/datadog/di/el/evaluator.rb +159 -0
  109. data/lib/datadog/di/el/expression.rb +42 -0
  110. data/lib/datadog/di/el.rb +5 -0
  111. data/lib/datadog/di/error.rb +29 -0
  112. data/lib/datadog/di/instrumenter.rb +163 -48
  113. data/lib/datadog/di/probe.rb +55 -15
  114. data/lib/datadog/di/probe_builder.rb +39 -1
  115. data/lib/datadog/di/probe_manager.rb +13 -4
  116. data/lib/datadog/di/probe_notification_builder.rb +105 -67
  117. data/lib/datadog/di/proc_responder.rb +32 -0
  118. data/lib/datadog/di/serializer.rb +151 -7
  119. data/lib/datadog/di/transport/diagnostics.rb +2 -2
  120. data/lib/datadog/di/transport/http/diagnostics.rb +2 -4
  121. data/lib/datadog/di/transport/http/input.rb +2 -4
  122. data/lib/datadog/di/transport/http.rb +6 -2
  123. data/lib/datadog/di/transport/input.rb +64 -4
  124. data/lib/datadog/open_feature/component.rb +60 -0
  125. data/lib/datadog/open_feature/configuration.rb +27 -0
  126. data/lib/datadog/open_feature/evaluation_engine.rb +69 -0
  127. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  128. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  129. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  130. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  131. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  132. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  133. data/lib/datadog/open_feature/ext.rb +14 -0
  134. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  135. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  136. data/lib/datadog/open_feature/provider.rb +141 -0
  137. data/lib/datadog/open_feature/remote.rb +74 -0
  138. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  139. data/lib/datadog/open_feature/transport.rb +72 -0
  140. data/lib/datadog/open_feature.rb +19 -0
  141. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  142. data/lib/datadog/opentelemetry/metrics.rb +110 -0
  143. data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
  144. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +38 -0
  145. data/lib/datadog/opentelemetry.rb +3 -0
  146. data/lib/datadog/profiling/collectors/code_provenance.rb +15 -6
  147. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  148. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  149. data/lib/datadog/profiling/profiler.rb +4 -0
  150. data/lib/datadog/profiling/tag_builder.rb +36 -3
  151. data/lib/datadog/profiling.rb +1 -2
  152. data/lib/datadog/single_step_instrument.rb +1 -1
  153. data/lib/datadog/tracing/component.rb +6 -17
  154. data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
  155. data/lib/datadog/tracing/configuration/ext.rb +9 -0
  156. data/lib/datadog/tracing/configuration/settings.rb +77 -3
  157. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
  158. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  159. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  160. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  161. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
  162. data/lib/datadog/tracing/contrib/component.rb +2 -2
  163. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
  164. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
  165. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
  166. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  167. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
  168. data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
  169. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +74 -44
  170. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
  171. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
  172. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
  173. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  174. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  175. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  176. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  177. data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
  178. data/lib/datadog/tracing/contrib/karafka/patcher.rb +32 -0
  179. data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
  180. data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
  181. data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
  182. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
  183. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
  184. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
  185. data/lib/datadog/tracing/contrib/status_range_matcher.rb +7 -0
  186. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  187. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  188. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  189. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  190. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  191. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +46 -0
  192. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  193. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  194. data/lib/datadog/tracing/contrib.rb +1 -0
  195. data/lib/datadog/tracing/metadata/ext.rb +9 -1
  196. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  197. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  198. data/lib/datadog/tracing/transport/traces.rb +3 -5
  199. data/lib/datadog/version.rb +2 -2
  200. data/lib/datadog.rb +2 -0
  201. metadata +92 -16
  202. data/ext/libdatadog_api/macos_development.md +0 -26
  203. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  204. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  205. data/lib/datadog/di/transport/http/client.rb +0 -47
@@ -10,6 +10,10 @@ module Datadog
10
10
  #
11
11
  # @api private
12
12
  class Error < StandardError
13
+ # Internal Dynamic Instrumentation error ("should never happen").
14
+ class InternalError < Error
15
+ end
16
+
13
17
  # Probe does not contain a line number (i.e., is not a line probe).
14
18
  class MissingLineNumber < Error
15
19
  end
@@ -48,6 +52,31 @@ module Datadog
48
52
  # and the user will need to make their suffix more precise.
49
53
  class MultiplePathsMatch < Error
50
54
  end
55
+
56
+ # Base class for exceptions arising during expression language AST
57
+ # compilation into Ruby code.
58
+ #
59
+ # Expression language does not specify behavior in all cases,
60
+ # leaving some choices to the language implementation in the tracers.
61
+ # It is therefore possible that some technically valid expressions are
62
+ # prohibited by our implementation.
63
+ #
64
+ # It is also possible that the sanitizers/validators prohibit some
65
+ # esoteric constructs that are technically valid in Ruby,
66
+ # for example if instance variable name rules are relaxed to allow
67
+ # arbitrary characters in them as permitted in method names.
68
+ class InvalidExpression < Error
69
+ end
70
+
71
+ # Variable name with invalid characters in an expression language
72
+ # expression.
73
+ class BadVariableName < InvalidExpression
74
+ end
75
+
76
+ # Base class for exceptions arising when evaluating expression language
77
+ # expressions.
78
+ class ExpressionEvaluationError < Error
79
+ end
51
80
  end
52
81
  end
53
82
  end
@@ -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
@@ -88,11 +89,7 @@ module Datadog
88
89
  # from the method but from outside of the method).
89
90
  Location = Struct.new(:path, :lineno, :label)
90
91
 
91
- def hook_method(probe, &block)
92
- unless block
93
- raise ArgumentError, 'block is required'
94
- end
95
-
92
+ def hook_method(probe, responder)
96
93
  lock.synchronize do
97
94
  if probe.instrumentation_module
98
95
  # Already instrumented, warn?
@@ -118,7 +115,50 @@ module Datadog
118
115
 
119
116
  mod = Module.new do
120
117
  define_method(method_name) do |*args, **kwargs, &target_block| # steep:ignore
121
- if rate_limiter.nil? || rate_limiter.allow?
118
+ continue = true
119
+ if condition = probe.condition
120
+ begin
121
+ # This context will be recreated later, unlike for line probes.
122
+ context = Context.new(
123
+ locals: serializer.combine_args(args, kwargs, self),
124
+ target_self: self,
125
+ probe: probe, settings: settings, serializer: serializer,
126
+ caller_locations: caller_locations,
127
+ )
128
+ continue = condition.satisfied?(context)
129
+ rescue => exc
130
+ # Evaluation error exception can be raised for "expected"
131
+ # errors, we probably need another setting to control whether
132
+ # these exceptions are propagated.
133
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
134
+ !exc.is_a?(DI::Error::ExpressionEvaluationError)
135
+
136
+ if context
137
+ # We want to report evaluation errors for conditions
138
+ # as probe snapshots. However, if we failed to create
139
+ # the context, we won't be able to report anything as
140
+ # the probe notifier builder requires a context.
141
+ begin
142
+ responder.probe_condition_evaluation_failed_callback(context, exc)
143
+ rescue
144
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
145
+
146
+ # TODO log / report via telemetry?
147
+ end
148
+ else
149
+ _ = 42 # stop standard from wrecking this code
150
+
151
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
152
+
153
+ # TODO log / report via telemetry?
154
+ # If execution gets here, there is probably a bug in the tracer.
155
+ end
156
+
157
+ continue = false
158
+ end
159
+ end
160
+
161
+ if continue and rate_limiter.nil? || rate_limiter.allow?
122
162
  # Arguments may be mutated by the method, therefore
123
163
  # they need to be serialized prior to method invocation.
124
164
  serialized_entry_args = if probe.capture_snapshot?
@@ -127,19 +167,29 @@ module Datadog
127
167
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
128
168
  end
129
169
  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)
170
+
171
+ rv = nil
172
+ begin
173
+ # Under Ruby 2.6 we cannot just call super(*args, **kwargs)
174
+ # for methods defined via method_missing.
175
+ rv = if args.any?
176
+ if kwargs.any?
177
+ super(*args, **kwargs, &target_block)
178
+ else
179
+ super(*args, &target_block)
180
+ end
181
+ elsif kwargs.any?
182
+ super(**kwargs, &target_block)
135
183
  else
136
- super(*args, &target_block)
184
+ super(&target_block)
137
185
  end
138
- elsif kwargs.any?
139
- super(**kwargs, &target_block)
140
- else
141
- super(&target_block)
186
+ rescue NoMemoryError, Interrupt, SystemExit
187
+ raise
188
+ rescue Exception => exc # standard:disable Lint/RescueException
189
+ # We will raise the exception captured here later, after
190
+ # the instrumentation callback runs.
142
191
  end
192
+
143
193
  duration = Core::Utils::Time.get_time - start_time
144
194
  # The method itself is not part of the stack trace because
145
195
  # we are getting the stack trace from outside of the method.
@@ -158,12 +208,19 @@ module Datadog
158
208
  end
159
209
  caller_locs = method_frame + caller_locations # steep:ignore
160
210
  # TODO capture arguments at exit
161
- # & 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
211
+
212
+ context = Context.new(locals: nil, target_self: self,
213
+ probe: probe, settings: settings, serializer: serializer,
214
+ serialized_entry_args: serialized_entry_args,
215
+ caller_locations: caller_locs,
216
+ return_value: rv, duration: duration, exception: exc,)
217
+
218
+ responder.probe_executed_callback(context)
219
+ if exc
220
+ raise exc
221
+ else
222
+ rv
223
+ end
167
224
  else
168
225
  # stop standard from trying to mess up my code
169
226
  _ = 42
@@ -220,11 +277,7 @@ module Datadog
220
277
  # not for eval'd code, unless the eval'd code is associated with
221
278
  # a file name and client invokes this method with the correct
222
279
  # file name for the eval'd code.
223
- def hook_line(probe, &block)
224
- unless block
225
- raise ArgumentError, 'No block given to hook_line'
226
- end
227
-
280
+ def hook_line(probe, responder)
228
281
  lock.synchronize do
229
282
  if probe.instrumentation_trace_point
230
283
  # Already instrumented, warn?
@@ -307,27 +360,86 @@ module Datadog
307
360
  # are invoked for *each* line of Ruby executed.
308
361
  # TODO find out exactly when the path in trace point is relative.
309
362
  # Looks like this is the case when line trace point is not targeted?
310
- if iseq || tp.lineno == probe.line_no && (
363
+ continue = iseq || tp.lineno == probe.line_no && (
311
364
  probe.file == tp.path || probe.file_matches?(tp.path)
312
365
  )
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,)
366
+
367
+ # We set the trace point on :return to be able to instrument
368
+ # 'end' lines. This also causes the trace point to be invoked on
369
+ # non-'end' lines when a line raises an exception, since the
370
+ # exception causes the method to stop executing and stack unwends.
371
+ # We do not want two invocations of the trace point.
372
+ # Therefore, if a trace point is invoked with a :line event,
373
+ # mark it as such and ignore subsequent :return events.
374
+ continue &&= if probe.executed_on_line?
375
+ tp.event == :line
376
+ else
377
+ if tp.event == :line
378
+ probe.executed_on_line!
379
+ end
380
+ true
381
+ end
382
+
383
+ if continue
384
+ if condition = probe.condition
385
+ begin
386
+ context = Context.new(
387
+ locals: Instrumenter.get_local_variables(tp),
388
+ target_self: tp.self,
389
+ probe: probe, settings: settings, serializer: serializer,
390
+ path: tp.path,
391
+ caller_locations: caller_locations,
392
+ )
393
+ continue = condition.satisfied?(context)
394
+ rescue => exc
395
+ # Evaluation error exception can be raised for "expected"
396
+ # errors, we probably need another setting to control whether
397
+ # these exceptions are propagated.
398
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
399
+ !exc.is_a?(DI::Error::ExpressionEvaluationError)
400
+
401
+ continue = false
402
+ if context
403
+ # We want to report evaluation errors for conditions
404
+ # as probe snapshots. However, if we failed to create
405
+ # the context, we won't be able to report anything as
406
+ # the probe notifier builder requires a context.
407
+ begin
408
+ responder.probe_condition_evaluation_failed_callback(context, condition, exc)
409
+ rescue
410
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
411
+
412
+ # TODO log / report via telemetry?
413
+ end
414
+ else
415
+ _ = 42 # stop standard from wrecking this code
416
+
417
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
418
+
419
+ # TODO log / report via telemetry?
420
+ # If execution gets here, there is probably a bug in the tracer.
421
+ end
323
422
  end
324
- # & is to stop steep complaints, block is always present here.
325
- block&.call(probe: probe,
326
- serialized_locals: serialized_locals,
327
- target_self: tp.self,
328
- path: tp.path, caller_locations: caller_locations)
329
423
  end
330
424
  end
425
+
426
+ continue &&= rate_limiter.nil? || rate_limiter.allow? # standard:disable Style/AndOr
427
+
428
+ if continue
429
+ # The context creation is relatively expensive and we don't
430
+ # want to run it if the callback won't be executed due to the
431
+ # rate limit.
432
+ # Thus the copy-paste of the creation call here.
433
+ context ||= Context.new(
434
+ locals: Instrumenter.get_local_variables(tp),
435
+ target_self: tp.self,
436
+ probe: probe, settings: settings, serializer: serializer,
437
+ path: tp.path,
438
+ caller_locations: caller_locations,
439
+ )
440
+
441
+ responder.probe_executed_callback(context)
442
+ end
331
443
  rescue => exc
332
444
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
333
445
  logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
@@ -341,9 +453,11 @@ module Datadog
341
453
  # TODO test this path
342
454
  end
343
455
 
344
- # TODO internal check - remove or use a proper exception
456
+ # Internal sanity check - untargeted trace points create a huge
457
+ # performance impact, and we absolutely do not want to set them
458
+ # accidentally.
345
459
  if !iseq && !permit_untargeted_trace_points
346
- raise "Trying to use an untargeted trace point when user did not permit it"
460
+ raise Error::InternalError, "Trying to use an untargeted trace point when user did not permit it"
347
461
  end
348
462
 
349
463
  lock.synchronize do
@@ -375,11 +489,11 @@ module Datadog
375
489
  end
376
490
  end
377
491
 
378
- def hook(probe, &block)
492
+ def hook(probe, responder)
379
493
  if probe.method?
380
- hook_method(probe, &block)
494
+ hook_method(probe, responder)
381
495
  elsif probe.line?
382
- hook_line(probe, &block)
496
+ hook_line(probe, responder)
383
497
  else
384
498
  # TODO add test coverage for this path
385
499
  logger.debug { "di: unknown probe type to hook: #{probe}" }
@@ -449,3 +563,4 @@ module Datadog
449
563
  end
450
564
 
451
565
  # rubocop:enable Lint/AssignmentInCondition
566
+ # 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,21 +77,25 @@ 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)
81
88
 
89
+ # At most one report per second.
90
+ # We create the rate limiter here even though it may never be used,
91
+ # to avoid having to synchronize the creation since method probes
92
+ # can be executed on multiple threads concurrently (even if line
93
+ # probes are never executed concurrently since those are done in a
94
+ # trace point).
95
+ if condition
96
+ @condition_evaluation_failed_rate_limiter = Datadog::Core::TokenBucket.new(1)
97
+ end
98
+
82
99
  @emitting_notified = false
83
100
  end
84
101
 
@@ -89,6 +106,10 @@ module Datadog
89
106
  attr_reader :type_name
90
107
  attr_reader :method_name
91
108
  attr_reader :template
109
+ attr_reader :template_segments
110
+
111
+ # The compiled condition for the probe, as a String.
112
+ attr_reader :condition
92
113
 
93
114
  # Configured maximum capture depth. Can be nil in which case
94
115
  # the global default will be used.
@@ -104,6 +125,16 @@ module Datadog
104
125
  # Rate limiter object. For internal DI use only.
105
126
  attr_reader :rate_limiter
106
127
 
128
+ # Rate limiter object for sending snapshots with evaluation errors
129
+ # for when probe condition evaluation fails.
130
+ # This rate limit is separate from the "base" rate limit for the probe
131
+ # because when the condition evaluation succeeds we want the "base"
132
+ # rate limit applied, not tainted by any evaluation errors
133
+ # (for example, the condition can be highly selective, and when it
134
+ # does not hold the evaluation may fail - we don't want to use up the
135
+ # probe rate limit for the errors).
136
+ attr_reader :condition_evaluation_failed_rate_limiter
137
+
107
138
  def capture_snapshot?
108
139
  @capture_snapshot
109
140
  end
@@ -122,7 +153,7 @@ module Datadog
122
153
 
123
154
  # Returns whether the probe is a method probe.
124
155
  def method?
125
- !!(type_name && method_name)
156
+ line_no.nil?
126
157
  end
127
158
 
128
159
  # Returns the line number associated with the probe, raising
@@ -186,6 +217,15 @@ module Datadog
186
217
  def emitting_notified?
187
218
  !!@emitting_notified
188
219
  end
220
+
221
+ def executed_on_line?
222
+ !!@executed_on_line
223
+ end
224
+
225
+ def executed_on_line!
226
+ # TODO lock?
227
+ @executed_on_line = true
228
+ end
189
229
  end
190
230
  end
191
231
  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
@@ -101,7 +101,7 @@ module Datadog
101
101
  end
102
102
 
103
103
  begin
104
- instrumenter.hook(probe, &method(:probe_executed_callback))
104
+ instrumenter.hook(probe, self)
105
105
 
106
106
  @installed_probes[probe.id] = probe
107
107
  payload = probe_notification_builder.build_installed(probe)
@@ -184,7 +184,7 @@ module Datadog
184
184
  begin
185
185
  # TODO is it OK to hook from trace point handler?
186
186
  # TODO the class is now defined, but can hooking still fail?
187
- instrumenter.hook(probe, &method(:probe_executed_callback))
187
+ instrumenter.hook(probe, self)
188
188
  @pending_probes.delete(probe.id)
189
189
  break
190
190
  rescue Error::DITargetNotDefined
@@ -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,10 +238,18 @@ 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
 
245
+ def probe_condition_evaluation_failed_callback(context, expr, exc)
246
+ probe = context.probe
247
+ if probe.condition_evaluation_failed_rate_limiter&.allow?
248
+ payload = probe_notification_builder.build_condition_evaluation_failed(context, expr, exc)
249
+ probe_notifier_worker.add_snapshot(payload)
250
+ end
251
+ end
252
+
244
253
  # Class/module definition trace point (:end type).
245
254
  # Used to install hooks when the target classes/modules aren't yet
246
255
  # defined when the hook request is received.