datadog 2.22.0 → 2.24.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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -1
  3. data/ext/LIBDATADOG_DEVELOPMENT.md +1 -58
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +21 -5
  5. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
  7. data/ext/datadog_profiling_native_extension/extconf.rb +9 -4
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
  9. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
  10. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  11. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  12. data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
  13. data/ext/libdatadog_api/feature_flags.c +554 -0
  14. data/ext/libdatadog_api/feature_flags.h +5 -0
  15. data/ext/libdatadog_api/init.c +2 -0
  16. data/ext/libdatadog_api/library_config.c +12 -11
  17. data/ext/libdatadog_extconf_helpers.rb +1 -1
  18. data/lib/datadog/appsec/api_security/route_extractor.rb +23 -6
  19. data/lib/datadog/appsec/api_security/sampler.rb +7 -4
  20. data/lib/datadog/appsec/assets/blocked.html +8 -0
  21. data/lib/datadog/appsec/assets/blocked.json +1 -1
  22. data/lib/datadog/appsec/assets/blocked.text +3 -1
  23. data/lib/datadog/appsec/assets.rb +1 -1
  24. data/lib/datadog/appsec/context.rb +2 -1
  25. data/lib/datadog/appsec/remote.rb +5 -9
  26. data/lib/datadog/appsec/response.rb +18 -4
  27. data/lib/datadog/appsec/security_engine/result.rb +2 -1
  28. data/lib/datadog/core/configuration/components.rb +30 -3
  29. data/lib/datadog/core/configuration/config_helper.rb +2 -2
  30. data/lib/datadog/core/configuration/deprecations.rb +2 -2
  31. data/lib/datadog/core/configuration/option_definition.rb +4 -2
  32. data/lib/datadog/core/configuration/options.rb +8 -5
  33. data/lib/datadog/core/configuration/settings.rb +28 -3
  34. data/lib/datadog/core/configuration/supported_configurations.rb +332 -302
  35. data/lib/datadog/core/ddsketch.rb +0 -2
  36. data/lib/datadog/core/environment/cgroup.rb +52 -25
  37. data/lib/datadog/core/environment/container.rb +140 -46
  38. data/lib/datadog/core/environment/ext.rb +7 -0
  39. data/lib/datadog/core/environment/process.rb +87 -0
  40. data/lib/datadog/core/feature_flags.rb +61 -0
  41. data/lib/datadog/core/rate_limiter.rb +9 -1
  42. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  43. data/lib/datadog/core/remote/client.rb +14 -6
  44. data/lib/datadog/core/remote/component.rb +6 -4
  45. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  46. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  47. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  48. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  49. data/lib/datadog/core/remote/transport/config.rb +4 -25
  50. data/lib/datadog/core/remote/transport/http/config.rb +10 -50
  51. data/lib/datadog/core/remote/transport/http/negotiation.rb +14 -44
  52. data/lib/datadog/core/remote/transport/http.rb +15 -24
  53. data/lib/datadog/core/remote/transport/negotiation.rb +8 -33
  54. data/lib/datadog/core/remote/worker.rb +25 -37
  55. data/lib/datadog/core/tag_builder.rb +0 -4
  56. data/lib/datadog/core/tag_normalizer.rb +84 -0
  57. data/lib/datadog/core/telemetry/component.rb +59 -16
  58. data/lib/datadog/core/telemetry/event/app_started.rb +86 -49
  59. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  60. data/lib/datadog/core/telemetry/logger.rb +2 -2
  61. data/lib/datadog/core/telemetry/logging.rb +2 -8
  62. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  63. data/lib/datadog/core/telemetry/request.rb +17 -3
  64. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +3 -34
  65. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  66. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -11
  67. data/lib/datadog/core/telemetry/worker.rb +88 -32
  68. data/lib/datadog/core/transport/ext.rb +2 -0
  69. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  70. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  71. data/lib/datadog/core/transport/http/builder.rb +9 -5
  72. data/lib/datadog/core/transport/http/client.rb +80 -0
  73. data/lib/datadog/core/transport/http.rb +22 -19
  74. data/lib/datadog/core/transport/response.rb +9 -0
  75. data/lib/datadog/core/transport/transport.rb +90 -0
  76. data/lib/datadog/core/utils/array.rb +29 -0
  77. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
  78. data/lib/datadog/core/utils/network.rb +3 -1
  79. data/lib/datadog/core/utils/only_once_successful.rb +8 -2
  80. data/lib/datadog/core/utils/time.rb +1 -1
  81. data/lib/datadog/core/utils.rb +2 -0
  82. data/lib/datadog/core/workers/async.rb +10 -1
  83. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  84. data/lib/datadog/core/workers/polling.rb +2 -0
  85. data/lib/datadog/core/workers/queue.rb +100 -1
  86. data/lib/datadog/data_streams/configuration/settings.rb +49 -0
  87. data/lib/datadog/data_streams/configuration.rb +11 -0
  88. data/lib/datadog/data_streams/ext.rb +11 -0
  89. data/lib/datadog/data_streams/extensions.rb +16 -0
  90. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  91. data/lib/datadog/data_streams/processor.rb +509 -0
  92. data/lib/datadog/data_streams/transport/http/stats.rb +52 -0
  93. data/lib/datadog/data_streams/transport/http.rb +40 -0
  94. data/lib/datadog/data_streams/transport/stats.rb +46 -0
  95. data/lib/datadog/data_streams.rb +100 -0
  96. data/lib/datadog/di/component.rb +0 -16
  97. data/lib/datadog/di/contrib/active_record.rb +31 -5
  98. data/lib/datadog/di/el/compiler.rb +8 -4
  99. data/lib/datadog/di/el/evaluator.rb +1 -1
  100. data/lib/datadog/di/error.rb +9 -0
  101. data/lib/datadog/di/instrumenter.rb +93 -34
  102. data/lib/datadog/di/probe.rb +20 -0
  103. data/lib/datadog/di/probe_builder.rb +2 -1
  104. data/lib/datadog/di/probe_manager.rb +47 -33
  105. data/lib/datadog/di/probe_notification_builder.rb +77 -25
  106. data/lib/datadog/di/proc_responder.rb +32 -0
  107. data/lib/datadog/di/remote.rb +89 -84
  108. data/lib/datadog/di/transport/diagnostics.rb +8 -36
  109. data/lib/datadog/di/transport/http/diagnostics.rb +1 -33
  110. data/lib/datadog/di/transport/http/input.rb +1 -33
  111. data/lib/datadog/di/transport/http.rb +32 -17
  112. data/lib/datadog/di/transport/input.rb +67 -34
  113. data/lib/datadog/di.rb +61 -5
  114. data/lib/datadog/open_feature/component.rb +60 -0
  115. data/lib/datadog/open_feature/configuration.rb +27 -0
  116. data/lib/datadog/open_feature/evaluation_engine.rb +70 -0
  117. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  118. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  119. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  120. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  121. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  122. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  123. data/lib/datadog/open_feature/ext.rb +14 -0
  124. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  125. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  126. data/lib/datadog/open_feature/provider.rb +141 -0
  127. data/lib/datadog/open_feature/remote.rb +67 -0
  128. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  129. data/lib/datadog/open_feature/transport.rb +70 -0
  130. data/lib/datadog/open_feature.rb +19 -0
  131. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  132. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  133. data/lib/datadog/opentelemetry/metrics.rb +117 -0
  134. data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
  135. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +35 -0
  136. data/lib/datadog/opentelemetry.rb +3 -0
  137. data/lib/datadog/profiling/collectors/code_provenance.rb +41 -7
  138. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  139. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  140. data/lib/datadog/profiling/collectors/info.rb +2 -1
  141. data/lib/datadog/profiling/component.rb +12 -11
  142. data/lib/datadog/profiling/http_transport.rb +4 -1
  143. data/lib/datadog/profiling/profiler.rb +4 -0
  144. data/lib/datadog/profiling/tag_builder.rb +36 -3
  145. data/lib/datadog/profiling.rb +1 -2
  146. data/lib/datadog/single_step_instrument.rb +1 -1
  147. data/lib/datadog/tracing/configuration/ext.rb +9 -0
  148. data/lib/datadog/tracing/configuration/settings.rb +74 -0
  149. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
  150. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  151. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  152. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  153. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
  154. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
  155. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
  156. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  157. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
  158. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  159. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
  160. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
  161. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
  162. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
  163. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  164. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  165. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  166. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  167. data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
  168. data/lib/datadog/tracing/contrib/karafka/patcher.rb +35 -4
  169. data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
  170. data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
  171. data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
  172. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
  173. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
  174. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
  175. data/lib/datadog/tracing/contrib/status_range_matcher.rb +9 -1
  176. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  177. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  178. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  179. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  180. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  181. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  182. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
  183. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  184. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  185. data/lib/datadog/tracing/contrib.rb +1 -0
  186. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  187. data/lib/datadog/tracing/metadata/ext.rb +1 -1
  188. data/lib/datadog/tracing/remote.rb +1 -9
  189. data/lib/datadog/tracing/span_event.rb +2 -2
  190. data/lib/datadog/tracing/span_operation.rb +9 -4
  191. data/lib/datadog/tracing/trace_operation.rb +44 -6
  192. data/lib/datadog/tracing/tracer.rb +42 -16
  193. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  194. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  195. data/lib/datadog/tracing/transport/http.rb +15 -9
  196. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  197. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  198. data/lib/datadog/tracing/transport/traces.rb +9 -71
  199. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  200. data/lib/datadog/tracing/writer.rb +1 -0
  201. data/lib/datadog/version.rb +2 -2
  202. data/lib/datadog.rb +2 -0
  203. metadata +78 -21
  204. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  205. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  206. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  207. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  208. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  209. data/lib/datadog/di/transport/http/api.rb +0 -42
  210. data/lib/datadog/di/transport/http/client.rb +0 -47
  211. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  212. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -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
@@ -101,7 +114,7 @@ module Datadog
101
114
  end
102
115
 
103
116
  begin
104
- instrumenter.hook(probe, &method(:probe_executed_callback))
117
+ instrumenter.hook(probe, self)
105
118
 
106
119
  @installed_probes[probe.id] = probe
107
120
  payload = probe_notification_builder.build_installed(probe)
@@ -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
@@ -184,7 +189,8 @@ module Datadog
184
189
  begin
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
- instrumenter.hook(probe, &method(:probe_executed_callback))
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
@@ -242,6 +248,14 @@ module Datadog
242
248
  probe_notifier_worker.add_snapshot(payload)
243
249
  end
244
250
 
251
+ def probe_condition_evaluation_failed_callback(context, expr, exc)
252
+ probe = context.probe
253
+ if probe.condition_evaluation_failed_rate_limiter&.allow?
254
+ payload = probe_notification_builder.build_condition_evaluation_failed(context, expr, exc)
255
+ probe_notifier_worker.add_snapshot(payload)
256
+ end
257
+ end
258
+
245
259
  # Class/module definition trace point (:end type).
246
260
  # Used to install hooks when the target classes/modules aren't yet
247
261
  # defined when the hook request is received.
@@ -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)
@@ -87,10 +87,41 @@ module Datadog
87
87
  end
88
88
  end
89
89
 
90
+ message = nil
91
+ evaluation_errors = []
92
+ if segments = probe.template_segments
93
+ message, evaluation_errors = evaluate_template(segments, context)
94
+ end
95
+ build_snapshot_base(context,
96
+ evaluation_errors: evaluation_errors, message: message,
97
+ captures: captures)
98
+ end
99
+
100
+ def build_condition_evaluation_failed(context, expression, exception)
101
+ error = {
102
+ message: "#{exception.class}: #{exception}",
103
+ expr: expression.dsl_expr,
104
+ }
105
+ build_snapshot_base(context, evaluation_errors: [error])
106
+ end
107
+
108
+ private
109
+
110
+ def build_snapshot_base(context, evaluation_errors: [], captures: nil, message: nil)
111
+ probe = context.probe
112
+
113
+ timestamp = timestamp_now
114
+ duration = context.duration
115
+
90
116
  location = if probe.line?
91
117
  {
92
118
  file: context.path,
93
- lines: [probe.line_no],
119
+ # Line numbers are required to be strings by the
120
+ # system tests schema.
121
+ # Backend I think accepts them also as integers, but some
122
+ # other languages send strings and we decided to require
123
+ # strings for everyone.
124
+ lines: [probe.line_no.to_s],
94
125
  }
95
126
  elsif probe.method?
96
127
  {
@@ -103,28 +134,38 @@ module Datadog
103
134
  format_caller_locations(caller_locations)
104
135
  end
105
136
 
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
113
- {
137
+ payload = {
114
138
  service: settings.service,
115
- "debugger.snapshot": {
116
- id: SecureRandom.uuid,
117
- timestamp: timestamp,
118
- evaluationErrors: evaluation_errors,
119
- probe: {
120
- id: probe.id,
121
- version: 0,
122
- location: location,
139
+ debugger: {
140
+ type: 'snapshot',
141
+ # Product can have three values: di, ld, er.
142
+ # We do not currently implement exception replay.
143
+ # There is currently no specification, and no consensus, for
144
+ # when product should be di (dynamic instrumentation) and when
145
+ # it should be ld (live debugger). I thought the backend was
146
+ # supposed to provide this in probe specification via remote
147
+ # config, but apparently this is not the case and the expectation
148
+ # is that the library figures out the product via heuristics,
149
+ # except there is currently no consensus on said heuristics.
150
+ # .NET always sends ld, other languages send nothing at the moment.
151
+ # Don't send anything for the time being.
152
+ #product: 'di/ld',
153
+ snapshot: {
154
+ id: SecureRandom.uuid,
155
+ timestamp: timestamp,
156
+ evaluationErrors: evaluation_errors,
157
+ probe: {
158
+ id: probe.id,
159
+ version: 0,
160
+ location: location,
161
+ },
162
+ language: 'ruby',
163
+ # TODO add test coverage for callers being nil
164
+ stack: stack,
165
+ # System tests schema validation requires captures to
166
+ # always be present
167
+ captures: captures || {},
123
168
  },
124
- language: 'ruby',
125
- # TODO add test coverage for callers being nil
126
- stack: stack,
127
- captures: captures,
128
169
  },
129
170
  # In python tracer duration is under debugger.snapshot,
130
171
  # but UI appears to expect it here at top level.
@@ -132,7 +173,7 @@ module Datadog
132
173
  host: nil,
133
174
  logger: {
134
175
  name: probe.file,
135
- method: probe.method_name || 'no_method',
176
+ method: probe.method_name,
136
177
  thread_name: Thread.current.name,
137
178
  # Dynamic instrumentation currently does not need thread_id for
138
179
  # anything. It can be sent if a customer requests it at which point
@@ -148,9 +189,11 @@ module Datadog
148
189
  message: message,
149
190
  timestamp: timestamp,
150
191
  }
151
- end
152
192
 
153
- private
193
+ tag_process_tags!(payload, settings)
194
+
195
+ payload
196
+ end
154
197
 
155
198
  def build_status(probe, message:, status:)
156
199
  {
@@ -197,6 +240,15 @@ module Datadog
197
240
  [message, evaluation_errors]
198
241
  end
199
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
+
200
252
  def timestamp_now
201
253
  (Core::Utils::Time.now.to_f * MILLISECONDS).to_i
202
254
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module DI
5
+ # An adapter to convert procs to responders.
6
+ #
7
+ # Used in test suite and benchmarks.
8
+ #
9
+ # @api private
10
+ class ProcResponder
11
+ def initialize(executed_proc, failed_proc = nil)
12
+ @executed_proc = executed_proc
13
+ @failed_proc = failed_proc
14
+ end
15
+
16
+ attr_reader :executed_proc
17
+ attr_reader :failed_proc
18
+
19
+ def probe_executed_callback(context)
20
+ executed_proc.call(context)
21
+ end
22
+
23
+ def probe_condition_evaluation_failed_callback(context, exc)
24
+ if failed_proc.nil?
25
+ raise NotImplementedError, "Failed proc not provided"
26
+ end
27
+
28
+ failed_proc.call(context, exc)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -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,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  require_relative '../../core/transport/parcel'
4
- require_relative 'http/client'
6
+ require_relative '../../core/transport/request'
7
+ require_relative '../../core/transport/transport'
8
+ require_relative 'http/diagnostics'
5
9
 
6
10
  module Datadog
7
11
  module DI
@@ -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 = HTTP::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
@@ -1,43 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../../core/transport/http/api/instance'
4
- require_relative '../../../core/transport/http/api/spec'
5
- require_relative 'client'
3
+ require_relative '../../../core/transport/http/api/endpoint'
6
4
 
7
5
  module Datadog
8
6
  module DI
9
7
  module Transport
10
8
  module HTTP
11
9
  module Diagnostics
12
- module Client
13
- def send_diagnostics_payload(request)
14
- send_request(request) do |api, env|
15
- api.send_diagnostics(env)
16
- end
17
- end
18
- end
19
-
20
10
  module API
21
- class Instance < Core::Transport::HTTP::API::Instance
22
- def send_diagnostics(env)
23
- raise Core::Transport::HTTP::API::Instance::EndpointNotSupportedError.new('diagnostics', self) unless spec.is_a?(Diagnostics::API::Spec)
24
-
25
- spec.send_diagnostics(env) do |request_env|
26
- call(request_env)
27
- end
28
- end
29
- end
30
-
31
- class Spec < Core::Transport::HTTP::API::Spec
32
- attr_accessor :diagnostics
33
-
34
- def send_diagnostics(env, &block)
35
- raise Core::Transport::HTTP::API::Spec::EndpointNotDefinedError.new('diagnostics', self) if diagnostics.nil?
36
-
37
- diagnostics.call(env, &block)
38
- end
39
- end
40
-
41
11
  class Endpoint < Datadog::Core::Transport::HTTP::API::Endpoint
42
12
  attr_reader :encoder
43
13
 
@@ -57,8 +27,6 @@ module Datadog
57
27
  end
58
28
  end
59
29
  end
60
-
61
- HTTP::Client.include(Diagnostics::Client)
62
30
  end
63
31
  end
64
32
  end
@@ -1,43 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../../core/transport/http/api/instance'
4
- require_relative '../../../core/transport/http/api/spec'
5
- require_relative 'client'
3
+ require_relative '../../../core/transport/http/api/endpoint'
6
4
 
7
5
  module Datadog
8
6
  module DI
9
7
  module Transport
10
8
  module HTTP
11
9
  module Input
12
- module Client
13
- def send_input_payload(request)
14
- send_request(request) do |api, env|
15
- api.send_input(env)
16
- end
17
- end
18
- end
19
-
20
10
  module API
21
- class Instance < Core::Transport::HTTP::API::Instance
22
- def send_input(env)
23
- raise Core::Transport::HTTP::API::Instance::EndpointNotSupportedError.new('input', self) unless spec.is_a?(Input::API::Spec)
24
-
25
- spec.send_input(env) do |request_env|
26
- call(request_env)
27
- end
28
- end
29
- end
30
-
31
- class Spec < Core::Transport::HTTP::API::Spec
32
- attr_accessor :input
33
-
34
- def send_input(env, &block)
35
- raise Core::Transport::HTTP::API::Spec::EndpointNotDefinedError.new('input', self) if input.nil?
36
-
37
- input.call(env, &block)
38
- end
39
- end
40
-
41
11
  class Endpoint < Datadog::Core::Transport::HTTP::API::Endpoint
42
12
  HEADER_CONTENT_TYPE = 'Content-Type'
43
13
 
@@ -69,8 +39,6 @@ module Datadog
69
39
  end
70
40
  end
71
41
  end
72
-
73
- HTTP::Client.include(Input::Client)
74
42
  end
75
43
  end
76
44
  end