datadog 2.39.0 → 2.47.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 (296) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +269 -44
  3. data/README.md +3 -0
  4. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  5. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +256 -193
  6. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
  7. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
  8. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
  9. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
  10. data/ext/datadog_profiling_native_extension/collectors_stack.c +267 -171
  11. data/ext/datadog_profiling_native_extension/collectors_stack.h +17 -3
  12. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +307 -217
  13. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -5
  14. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
  15. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +19 -11
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -10
  17. data/ext/datadog_profiling_native_extension/extconf.rb +67 -99
  18. data/ext/datadog_profiling_native_extension/heap_recorder.c +353 -257
  19. data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
  20. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -27
  21. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +853 -496
  22. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +42 -15
  23. data/ext/datadog_profiling_native_extension/profiling.c +30 -23
  24. data/ext/datadog_profiling_native_extension/ruby_helpers.c +4 -80
  25. data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -7
  26. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +0 -2
  27. data/ext/datadog_profiling_native_extension/stack_recorder.c +116 -89
  28. data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -5
  29. data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
  30. data/ext/datadog_profiling_native_extension/time_helpers.h +15 -15
  31. data/ext/libdatadog_api/datadog_ruby_common.c +19 -11
  32. data/ext/libdatadog_api/datadog_ruby_common.h +7 -10
  33. data/ext/libdatadog_api/di.c +43 -3
  34. data/ext/libdatadog_api/extconf.rb +6 -1
  35. data/ext/libdatadog_api/feature_flags.c +8 -0
  36. data/ext/libdatadog_api/init.c +9 -1
  37. data/ext/libdatadog_api/otel_thread_context.c +274 -0
  38. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  39. data/ext/libdatadog_api/process_discovery.c +15 -1
  40. data/ext/libdatadog_api/trace_exporter.c +2078 -0
  41. data/ext/libdatadog_api/trace_exporter.h +5 -0
  42. data/ext/libdatadog_extconf_helpers.rb +14 -9
  43. data/lib/datadog/ai_guard/component.rb +17 -8
  44. data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
  45. data/lib/datadog/ai_guard/configuration.rb +13 -0
  46. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
  47. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
  48. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
  49. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
  50. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
  51. data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
  52. data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
  53. data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
  54. data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
  55. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
  56. data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
  57. data/lib/datadog/ai_guard/evaluation/request.rb +8 -64
  58. data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
  59. data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
  60. data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
  61. data/lib/datadog/ai_guard/evaluation.rb +36 -14
  62. data/lib/datadog/ai_guard/ext.rb +4 -1
  63. data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +7 -5
  64. data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
  65. data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
  66. data/lib/datadog/ai_guard/redaction/result.rb +29 -0
  67. data/lib/datadog/ai_guard/redaction.rb +80 -0
  68. data/lib/datadog/ai_guard.rb +58 -57
  69. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +21 -42
  70. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +1 -1
  71. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
  72. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +1 -1
  73. data/lib/datadog/appsec/assets/blocked.html +1 -108
  74. data/lib/datadog/appsec/configuration.rb +11 -2
  75. data/lib/datadog/appsec/context.rb +1 -1
  76. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +1 -1
  77. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +2 -2
  78. data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
  79. data/lib/datadog/appsec/contrib/devise/patcher.rb +1 -1
  80. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +2 -2
  81. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +2 -2
  82. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  83. data/lib/datadog/appsec/contrib/rack/ext.rb +2 -2
  84. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +2 -2
  85. data/lib/datadog/appsec/contrib/rack/patcher.rb +1 -1
  86. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +6 -3
  87. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +30 -3
  88. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
  89. data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
  90. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
  91. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +15 -3
  92. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
  93. data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
  94. data/lib/datadog/appsec/processor/rule_loader.rb +12 -12
  95. data/lib/datadog/appsec/response.rb +1 -1
  96. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +3 -3
  97. data/lib/datadog/appsec/security_engine/engine.rb +5 -4
  98. data/lib/datadog/core/configuration/components.rb +50 -19
  99. data/lib/datadog/core/configuration/components_state.rb +8 -1
  100. data/lib/datadog/core/configuration/option_definition.rb +1 -1
  101. data/lib/datadog/core/configuration/options.rb +1 -1
  102. data/lib/datadog/core/configuration/settings.rb +38 -43
  103. data/lib/datadog/core/configuration/supported_configurations.rb +18 -2
  104. data/lib/datadog/core/crashtracking/component.rb +5 -1
  105. data/lib/datadog/core/diagnostics/environment_logger.rb +36 -1
  106. data/lib/datadog/core/environment/gc.rb +1 -1
  107. data/lib/datadog/core/environment/vm_cache.rb +1 -1
  108. data/lib/datadog/core/environment/yjit.rb +2 -2
  109. data/lib/datadog/core/metrics/helpers.rb +1 -1
  110. data/lib/datadog/core/metrics/options.rb +2 -2
  111. data/lib/datadog/core/process_discovery.rb +6 -2
  112. data/lib/datadog/core/remote/client/capabilities.rb +32 -21
  113. data/lib/datadog/core/remote/client.rb +45 -4
  114. data/lib/datadog/core/remote/component.rb +30 -7
  115. data/lib/datadog/core/remote/configuration/repository.rb +2 -2
  116. data/lib/datadog/core/remote/dispatcher.rb +47 -6
  117. data/lib/datadog/core/remote/worker.rb +8 -0
  118. data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
  119. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -1
  120. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +1 -1
  121. data/lib/datadog/core/telemetry/event/app_started.rb +5 -24
  122. data/lib/datadog/core/telemetry/event/generate_metrics.rb +1 -1
  123. data/lib/datadog/core/telemetry/event/log.rb +2 -2
  124. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
  125. data/lib/datadog/core/telemetry/metric.rb +2 -2
  126. data/lib/datadog/core/telemetry/request.rb +2 -2
  127. data/lib/datadog/core/transport/http.rb +1 -1
  128. data/lib/datadog/core/transport/transport.rb +1 -1
  129. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +170 -18
  130. data/lib/datadog/core/utils/sequence.rb +3 -6
  131. data/lib/datadog/core/utils/time.rb +16 -54
  132. data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
  133. data/lib/datadog/data_streams/pathway_context.rb +20 -22
  134. data/lib/datadog/data_streams/processor.rb +45 -14
  135. data/lib/datadog/di/boot.rb +1 -0
  136. data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
  137. data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
  138. data/lib/datadog/di/component.rb +108 -3
  139. data/lib/datadog/di/configuration.rb +53 -2
  140. data/lib/datadog/di/context.rb +7 -2
  141. data/lib/datadog/di/contrib/active_record.rb +8 -2
  142. data/lib/datadog/di/el/compiler.rb +88 -12
  143. data/lib/datadog/di/el/evaluator.rb +82 -2
  144. data/lib/datadog/di/el/expression.rb +13 -2
  145. data/lib/datadog/di/evaluation_failure.rb +28 -0
  146. data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
  147. data/lib/datadog/di/guardrails_telemetry.rb +203 -0
  148. data/lib/datadog/di/instrumenter.rb +106 -15
  149. data/lib/datadog/di/logger.rb +32 -10
  150. data/lib/datadog/di/probe.rb +9 -1
  151. data/lib/datadog/di/probe_builder.rb +7 -6
  152. data/lib/datadog/di/probe_manager.rb +56 -8
  153. data/lib/datadog/di/probe_notification_builder.rb +31 -28
  154. data/lib/datadog/di/probe_notifier_worker.rb +56 -6
  155. data/lib/datadog/di/proc_responder.rb +1 -1
  156. data/lib/datadog/di/remote.rb +49 -5
  157. data/lib/datadog/di/serializer.rb +172 -55
  158. data/lib/datadog/di/snapshot_event.rb +38 -0
  159. data/lib/datadog/di/transport/http.rb +13 -3
  160. data/lib/datadog/di/transport/input.rb +32 -7
  161. data/lib/datadog/error_tracking/component.rb +1 -1
  162. data/lib/datadog/kit/appsec/events/v2.rb +2 -2
  163. data/lib/datadog/open_feature/activation.rb +268 -0
  164. data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
  165. data/lib/datadog/open_feature/agentless/parser.rb +43 -0
  166. data/lib/datadog/open_feature/agentless/response.rb +19 -0
  167. data/lib/datadog/open_feature/agentless/transport.rb +103 -0
  168. data/lib/datadog/open_feature/component.rb +85 -13
  169. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
  170. data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
  171. data/lib/datadog/open_feature/configuration/source.rb +43 -0
  172. data/lib/datadog/open_feature/configuration.rb +124 -0
  173. data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
  174. data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
  175. data/lib/datadog/open_feature/exposures/event.rb +15 -8
  176. data/lib/datadog/open_feature/ext.rb +19 -0
  177. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
  178. data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
  179. data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
  180. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
  181. data/lib/datadog/open_feature/native_evaluator.rb +20 -6
  182. data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
  183. data/lib/datadog/open_feature/provider.rb +315 -10
  184. data/lib/datadog/open_feature/remote.rb +5 -5
  185. data/lib/datadog/open_feature/transport.rb +5 -0
  186. data/lib/datadog/open_feature.rb +31 -1
  187. data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
  188. data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
  189. data/lib/datadog/opentelemetry/trace.rb +3 -0
  190. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +10 -2
  191. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
  192. data/lib/datadog/profiling/collectors/info.rb +40 -13
  193. data/lib/datadog/profiling/collectors/thread_context.rb +6 -17
  194. data/lib/datadog/profiling/component.rb +33 -39
  195. data/lib/datadog/profiling/exporter.rb +10 -10
  196. data/lib/datadog/profiling/tasks/exec.rb +1 -1
  197. data/lib/datadog/profiling.rb +4 -0
  198. data/lib/datadog/symbol_database/extractor.rb +89 -107
  199. data/lib/datadog/tracing/component.rb +44 -2
  200. data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
  201. data/lib/datadog/tracing/configuration/dynamic.rb +5 -3
  202. data/lib/datadog/tracing/configuration/ext.rb +4 -0
  203. data/lib/datadog/tracing/configuration/settings.rb +36 -0
  204. data/lib/datadog/tracing/context.rb +24 -2
  205. data/lib/datadog/tracing/context_provider.rb +17 -5
  206. data/lib/datadog/tracing/contrib/action_cable/events.rb +1 -1
  207. data/lib/datadog/tracing/contrib/action_mailer/events.rb +1 -1
  208. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  209. data/lib/datadog/tracing/contrib/action_view/events.rb +1 -1
  210. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +1 -1
  211. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
  212. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  213. data/lib/datadog/tracing/contrib/active_record/events.rb +1 -1
  214. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -2
  215. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
  216. data/lib/datadog/tracing/contrib/aws/services.rb +1 -1
  217. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +1 -1
  218. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
  219. data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
  220. data/lib/datadog/tracing/contrib/ext.rb +3 -3
  221. data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
  222. data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
  223. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +1 -1
  224. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
  225. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +1 -1
  226. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +2 -1
  227. data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
  228. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
  229. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
  230. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +4 -0
  231. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +7 -2
  232. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +7 -2
  233. data/lib/datadog/tracing/contrib/kafka/events.rb +1 -1
  234. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +1 -1
  235. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
  236. data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -1
  237. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +1 -1
  238. data/lib/datadog/tracing/contrib/patchable.rb +1 -1
  239. data/lib/datadog/tracing/contrib/patcher.rb +1 -1
  240. data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
  241. data/lib/datadog/tracing/contrib/racecar/events.rb +1 -1
  242. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -1
  243. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +1 -1
  244. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  245. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +1 -1
  246. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +1 -1
  247. data/lib/datadog/tracing/contrib/redis/quantize.rb +36 -2
  248. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +9 -3
  249. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
  250. data/lib/datadog/tracing/contrib/sequel/database.rb +1 -5
  251. data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
  252. data/lib/datadog/tracing/contrib/sequel/utils.rb +50 -63
  253. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +1 -1
  254. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -1
  255. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -1
  256. data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +1 -1
  257. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +108 -3
  258. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +1 -1
  259. data/lib/datadog/tracing/correlation.rb +2 -2
  260. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -2
  261. data/lib/datadog/tracing/distributed/baggage.rb +1 -3
  262. data/lib/datadog/tracing/distributed/datadog.rb +3 -3
  263. data/lib/datadog/tracing/distributed/propagation.rb +3 -0
  264. data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
  265. data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
  266. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  267. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  268. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  269. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  270. data/lib/datadog/tracing/metadata/ext.rb +3 -0
  271. data/lib/datadog/tracing/metadata/tagging.rb +1 -1
  272. data/lib/datadog/tracing/otel_thread_context.rb +97 -0
  273. data/lib/datadog/tracing/remote.rb +236 -37
  274. data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
  275. data/lib/datadog/tracing/span.rb +1 -1
  276. data/lib/datadog/tracing/span_operation.rb +1 -1
  277. data/lib/datadog/tracing/sync_writer.rb +8 -3
  278. data/lib/datadog/tracing/trace_digest.rb +22 -4
  279. data/lib/datadog/tracing/trace_operation.rb +22 -10
  280. data/lib/datadog/tracing/tracer.rb +28 -7
  281. data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
  282. data/lib/datadog/tracing/transport/http/traces.rb +2 -0
  283. data/lib/datadog/tracing/transport/io/traces.rb +1 -1
  284. data/lib/datadog/tracing/transport/native/response.rb +71 -0
  285. data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
  286. data/lib/datadog/tracing/transport/native.rb +395 -0
  287. data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
  288. data/lib/datadog/tracing/transport/telemetry.rb +62 -0
  289. data/lib/datadog/tracing/transport/trace_formatter.rb +14 -4
  290. data/lib/datadog/tracing/transport/traces.rb +85 -38
  291. data/lib/datadog/tracing/writer.rb +17 -4
  292. data/lib/datadog/version.rb +1 -1
  293. metadata +51 -13
  294. data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
  295. data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
  296. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require_relative "../telemetry"
5
+
6
+ module Datadog
7
+ module Tracing
8
+ module Transport
9
+ module HTTP
10
+ class Telemetry < Datadog::Tracing::Transport::Telemetry
11
+ def initialize(client)
12
+ super(client, source: "ruby")
13
+ end
14
+
15
+ def batch(traces)
16
+ return unless @client.enabled? && @client.metrics_manager.enabled
17
+
18
+ Batch.new(self, traces)
19
+ rescue
20
+ nil
21
+ end
22
+
23
+ class Batch
24
+ def initialize(reporter, traces)
25
+ @reporter = reporter
26
+ @remaining_chunks = traces.length
27
+ @remaining_spans = traces.sum(&:length)
28
+ @reporter.record(spans_enqueued_for_serialization: @remaining_spans)
29
+ end
30
+
31
+ def record_oversized_drop(span_count)
32
+ @remaining_chunks -= 1
33
+ @remaining_spans -= span_count
34
+ @reporter.record(chunks_dropped_payload_too_large: 1, spans_dropped_payload_too_large: span_count)
35
+ end
36
+
37
+ def serialization_failed
38
+ @reporter.record(chunks_dropped_serialization_error: @remaining_chunks,
39
+ spans_dropped_serialization_error: @remaining_spans,)
40
+ end
41
+
42
+ def sent(request, response, chunks, spans, bytes, fallback:)
43
+ @reporter.record(requests_count: 1)
44
+ return if fallback
45
+
46
+ @remaining_chunks -= chunks
47
+ @remaining_spans -= spans
48
+ observations = {} #: Hash[Symbol, Integer]
49
+ if (status = request.http_status)
50
+ observations[:responses_count] = 1
51
+ observations[:status_code] = status
52
+ end
53
+ success = status ? status.between?(200, 299) : request.http_success
54
+ if success
55
+ observations[:chunks_sent] = chunks
56
+ observations[:bytes_sent] = bytes
57
+ elsif status || success == false
58
+ observations[:errors_status_code] = 1 if status
59
+ observations[:chunks_dropped_send_failure] = chunks
60
+ observations[:spans_dropped_api_error] = spans
61
+ elsif response.is_a?(Core::Transport::InternalErrorResponse)
62
+ error = response.error
63
+ if error.is_a?(ArgumentError) || error.is_a?(TypeError)
64
+ observations[:chunks_dropped_serialization_error] = chunks
65
+ observations[:spans_dropped_serialization_error] = spans
66
+ else
67
+ observations[:chunks_dropped_send_failure] = chunks
68
+ observations[:spans_dropped_api_error] = spans
69
+ if error.is_a?(::Timeout::Error) || error.is_a?(Errno::ETIMEDOUT)
70
+ observations[:errors_timeout] = 1
71
+ elsif network_error?(error)
72
+ observations[:errors_network] = 1
73
+ end
74
+ end
75
+ end
76
+ @reporter.record(observations)
77
+ rescue
78
+ nil
79
+ end
80
+
81
+ private
82
+
83
+ def network_error?(error)
84
+ error.is_a?(SystemCallError) || error.is_a?(IOError) || error.is_a?(SocketError) ||
85
+ error.is_a?(::Net::ProtocolError) || error.is_a?(::Net::HTTPBadResponse) ||
86
+ error.is_a?(::Net::HTTPHeaderSyntaxError) ||
87
+ (defined?(::OpenSSL::SSL::SSLError) && error.is_a?(::OpenSSL::SSL::SSLError))
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -51,6 +51,8 @@ module Datadog
51
51
 
52
52
  # Query for response
53
53
  http_response = super
54
+ # Delivery succeeded or failed before service-rate parsing can raise.
55
+ env.request.capture_status(http_response)
54
56
 
55
57
  # Process the response
56
58
  response_options = {trace_count: env.request.parcel.trace_count}.tap do |options|
@@ -27,7 +27,7 @@ module Datadog
27
27
  ENCODED_IDS = [
28
28
  :trace_id,
29
29
  :span_id,
30
- :parent_id
30
+ :parent_id,
31
31
  ].freeze
32
32
 
33
33
  # Encodes a list of traces
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Datadog
6
+ module Tracing
7
+ module Transport
8
+ module Native
9
+ # Response from the native trace exporter.
10
+ #
11
+ # Constructed by the C extension after a send completes (or fails).
12
+ # Implements the same predicate interface as the HTTP transport's
13
+ # response so callers can treat both uniformly.
14
+ class Response
15
+ SERVICE_RATE_KEY = "rate_by_service"
16
+
17
+ attr_reader :trace_count, :payload
18
+
19
+ def initialize(ok:, internal_error: false, server_error: false, client_error: false,
20
+ not_found: false, unsupported: false, trace_count: 0, payload: nil)
21
+ @ok = ok
22
+ @internal_error = internal_error
23
+ @server_error = server_error
24
+ @client_error = client_error
25
+ @not_found = not_found
26
+ @unsupported = unsupported
27
+ @trace_count = trace_count
28
+ @payload = payload
29
+ end
30
+
31
+ def ok?
32
+ @ok
33
+ end
34
+
35
+ def internal_error?
36
+ @internal_error
37
+ end
38
+
39
+ def server_error?
40
+ @server_error
41
+ end
42
+
43
+ def client_error?
44
+ @client_error
45
+ end
46
+
47
+ def not_found?
48
+ @not_found
49
+ end
50
+
51
+ def unsupported?
52
+ @unsupported
53
+ end
54
+
55
+ # Parse the agent's JSON response body and extract the
56
+ # +rate_by_service+ map. Returns +nil+ when the payload
57
+ # is absent or does not contain sampling rates.
58
+ def service_rates
59
+ payload = @payload
60
+ return nil if payload.nil? || payload.empty?
61
+
62
+ parsed = JSON.parse(payload)
63
+ parsed[SERVICE_RATE_KEY] if parsed.is_a?(Hash)
64
+ rescue JSON::ParserError
65
+ nil
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../telemetry"
4
+
5
+ module Datadog
6
+ module Tracing
7
+ module Transport
8
+ module Native
9
+ class Telemetry < Datadog::Tracing::Transport::Telemetry
10
+ def initialize(client)
11
+ super(client, source: "libdatadog")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,395 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "trace_formatter"
5
+ require_relative "statistics"
6
+ require_relative "span_events_negotiation"
7
+ require_relative "native/telemetry"
8
+
9
+ module Datadog
10
+ module Tracing
11
+ module Transport
12
+ # Transport backed by the native trace exporter (Rust via C FFI).
13
+ #
14
+ # Converts Ruby Span objects directly to Rust spans and delegates
15
+ # serialization and sending to the Rust data pipeline, which handles
16
+ # stats computation, msgpack encoding, and HTTP transport with retry
17
+ # logic.
18
+ #
19
+ # Implements the same +send_traces+ / +stats+ interface as
20
+ # {Datadog::Tracing::Transport::Traces::Transport} so it can be used
21
+ # as a drop-in replacement via the +Writer+'s +:transport+ option.
22
+ module Native
23
+ # Returns +nil+ when the native extension is available, or a +String+
24
+ # describing why it is not.
25
+ UNSUPPORTED_REASON = begin
26
+ require "datadog/core"
27
+ Datadog::Core::LIBDATADOG_API_FAILURE
28
+ rescue => e
29
+ e.message
30
+ end
31
+
32
+ def self.supported?
33
+ UNSUPPORTED_REASON.nil?
34
+ end
35
+
36
+ # +TraceExporter+ and +TracerSpan+ are defined by the C extension in
37
+ # +ext/libdatadog_api/trace_exporter.c+ and become available after the
38
+ # native extension is loaded. +Response+ is a plain Ruby class (see
39
+ # +native/response.rb+); the C side only resolves and instantiates it.
40
+ #
41
+ # The hierarchy is:
42
+ # Datadog::Tracing::Transport::Native::TraceExporter (C)
43
+ # Datadog::Tracing::Transport::Native::TracerSpan (C)
44
+ # Datadog::Tracing::Transport::Native::Response (Ruby)
45
+
46
+ # Drop-in transport that delegates to the native trace exporter.
47
+ class Transport
48
+ include Statistics
49
+ include SpanEventsNegotiation
50
+
51
+ attr_reader :logger, :url
52
+
53
+ def telemetry=(client)
54
+ @send_mutex.synchronize do
55
+ if (reporter = @telemetry)
56
+ reporter.client = client
57
+ elsif @exporter
58
+ @telemetry = Telemetry.new(client)
59
+ end
60
+ end
61
+ end
62
+
63
+ # @param agent_settings [Datadog::Core::Configuration::AgentSettingsResolver::AgentSettings]
64
+ # Agent connection settings, including the request timeout.
65
+ # @param logger [Logger]
66
+ def initialize(agent_settings:, logger:)
67
+ unless Native.supported?
68
+ raise "Native transport is not supported: #{UNSUPPORTED_REASON}"
69
+ end
70
+
71
+ @logger = logger
72
+ @telemetry = nil
73
+
74
+ # Serializes native sends and is held across a fork. See the
75
+ # fork-safety note below.
76
+ @send_mutex = Mutex.new
77
+
78
+ # Serializes concurrent fork-hook lifecycles. Unlike @send_mutex,
79
+ # this is held across the fork through the matching completion hook.
80
+ @fork_mutex = Mutex.new
81
+
82
+ @url = agent_settings.url
83
+ tracer_version = tracer_version_string
84
+ language = Core::Environment::Ext::LANG
85
+ language_version = Core::Environment::Ext::LANG_VERSION
86
+ language_interpreter = Core::Environment::Ext::LANG_INTERPRETER
87
+ hostname = begin
88
+ Core::Environment::Socket.hostname
89
+ rescue
90
+ nil
91
+ end
92
+ env = Datadog.configuration.env
93
+ service = Datadog.configuration.service
94
+ version = Datadog.configuration.version
95
+
96
+ exporter = Native::TraceExporter._native_new(
97
+ url: url,
98
+ timeout_milliseconds: agent_settings.timeout_seconds * 1000,
99
+ tracer_version: tracer_version,
100
+ language: language,
101
+ language_version: language_version,
102
+ language_interpreter: language_interpreter,
103
+ hostname: hostname,
104
+ env: env,
105
+ service: service,
106
+ version: version,
107
+ client_computed_stats: !Datadog.configuration.apm.tracing.enabled
108
+ )
109
+ @exporter = exporter
110
+
111
+ # Fork safety: the native exporter owns a long-lived tokio runtime
112
+ # with background worker threads. Around a fork we must quiesce the
113
+ # runtime before forking and restore it afterwards in both the parent
114
+ # and the child (where the inherited runtime is dead).
115
+ #
116
+ # A libdatadog Rust call must NOT be interrupted by `fork()`. A
117
+ # native send releases the GVL during the Rust
118
+ # `ddog_trace_exporter_send_trace_chunks` call, so if another thread
119
+ # forks while a (typically the writer/`AsyncTransport`) thread is
120
+ # mid-send, the child would inherit a half-completed send and
121
+ # Rust-internal locks, deadlocking or crashing. `_native_before_fork`
122
+ # also tears down and replaces the runtime, so it must not run while
123
+ # a send is still using that runtime.
124
+ #
125
+ # `@fork_mutex` serializes concurrent forks for this exporter. The
126
+ # `:before` hook locks it first, then calls `_native_before_fork`,
127
+ # and finally locks `@send_mutex`, which serializes sends and is
128
+ # held across the fork. Sends and #close only acquire @send_mutex,
129
+ # so there is no lock-order inversion.
130
+ #
131
+ # `_native_before_fork` must run before locking `@send_mutex` to
132
+ # pause the runtime before waiting for an in-flight send to drain.
133
+ # The hook first calls
134
+ # `_native_before_fork` to pause the runtime, THEN locks the mutex.
135
+ # This ordering minimizes the serialized section and is safe to run
136
+ # concurrently with an in-flight send: `block_on` (used by
137
+ # `send_trace_chunks`) only briefly locks the runtime mutex to clone
138
+ # the `Arc<Runtime>` (or build a throwaway `current_thread` runtime
139
+ # if it has been taken) and then drives the send on its own clone
140
+ # holding no mutex, while `_native_before_fork` just `take()`s the
141
+ # shared `Arc` and pauses workers -- so neither deadlocks the other.
142
+ # Locking the mutex AFTER `_native_before_fork` still guarantees the
143
+ # drain: it BLOCKS until any in-flight send finishes (bounded by the
144
+ # exporter's request timeout), which drops the send's last `Arc` and
145
+ # fully shuts the runtime down, and prevents new sends from starting.
146
+ # The lock is released in the `:parent` and `:child` hooks. In
147
+ # practice only one writer thread sends, so serializing adds no real
148
+ # contention.
149
+ #
150
+ # Note: the `:before` block runs before genuine forks (web-server
151
+ # workers, `Process.daemon`) that go through `Process._fork`. It
152
+ # does NOT run for `system`/backtick/`IO.popen`/`Process.spawn`,
153
+ # which spawn via `posix_spawn`/`vfork`+`exec` and replace the
154
+ # process image rather than carrying over the parent's work. Keeping
155
+ # it cheap is still worthwhile since real forks can be frequent.
156
+ #
157
+ # The hooks below are process-global and are never auto-removed by
158
+ # the fork machinery, so each transport must deregister its own
159
+ # hooks when it goes away (see #close and the finalizer): otherwise
160
+ # the closures keep the exporter -- and its runtime threads -- alive
161
+ # forever, and every later fork runs them against every
162
+ # historically-created exporter. The closures intentionally close
163
+ # over only locals (NOT `self`), so the Transport stays GC-eligible and
164
+ # its finalizer can fire even when #close was not called explicitly.
165
+ send_mutex = @send_mutex
166
+ fork_mutex = @fork_mutex
167
+ before_hook = proc do
168
+ fork_mutex.lock
169
+
170
+ begin
171
+ # Pause the runtime first (safe to run concurrently with an
172
+ # in-flight send), then wait for it to drain by acquiring the
173
+ # mutex. The lock must happen even if `_native_before_fork` raises, so the
174
+ # in-flight send has returned before the fork. Held across the
175
+ # fork; released in :parent/:child.
176
+ exporter._native_before_fork
177
+ rescue => e
178
+ Datadog.logger.warn { "Native transport before-fork preparation failed; traces may not be sent to Datadog: #{e.class}: #{e.message}" }
179
+ ensure
180
+ send_mutex.lock
181
+ end
182
+ end
183
+ parent_hook = proc do
184
+ exporter._native_after_fork_in_parent if fork_mutex.owned?
185
+ rescue => e
186
+ Datadog.logger.warn { "Native transport after-fork reset failed; traces may not be sent to Datadog: #{e.class}: #{e.message}" }
187
+ ensure
188
+ # `:before`'s ensure always locks the mutex on the forking thread
189
+ # before any fork outcome, so it is normally owned here; the guard
190
+ # only covers `:before` being interrupted mid-lock (e.g. a kill
191
+ # between `_native_before_fork` and the lock). Released even if the
192
+ # native call raised, so a failure can't leave the mutex locked.
193
+ send_mutex.unlock if send_mutex.owned?
194
+ fork_mutex.unlock if fork_mutex.owned?
195
+ end
196
+ child_hook = proc do
197
+ exporter._native_after_fork_in_child if fork_mutex.owned?
198
+ rescue => e
199
+ Datadog.logger.warn { "Native transport after-fork reset failed; traces may not be sent to Datadog: #{e.class}: #{e.message}" }
200
+ ensure
201
+ # See the :parent hook: normally owned (the forking thread is the
202
+ # lone survivor in the child); the guard only covers `:before`
203
+ # being interrupted mid-lock.
204
+ send_mutex.unlock if send_mutex.owned?
205
+ fork_mutex.unlock if fork_mutex.owned?
206
+ end
207
+
208
+ @fork_hooks = Core::Utils::AtForkMonkeyPatch.at_fork_blocks(
209
+ before: before_hook,
210
+ parent: parent_hook,
211
+ child: child_hook
212
+ )
213
+ remove_fork_hooks = self.class.send(:fork_hooks_remover, @fork_hooks)
214
+
215
+ # Fallback so a dropped (un-#close'd) transport doesn't leak its
216
+ # global fork hooks (and, through them, the exporter/runtime). The
217
+ # removal proc is built by a class method capturing ONLY the
218
+ # stage->block handles, never `self` -- a finalizer that closed over
219
+ # the object it is attached to would keep that object reachable and
220
+ # never run. A fork that already copied these callbacks still owns
221
+ # them through its matching completion stage.
222
+ ObjectSpace.define_finalizer(
223
+ self,
224
+ remove_fork_hooks
225
+ )
226
+ end
227
+
228
+ # Deregister this transport's process-global fork hooks and release the
229
+ # native exporter so its runtime can shut down. Idempotent: safe to
230
+ # call multiple times and safe to call after the finalizer has run.
231
+ def close
232
+ @fork_mutex.synchronize do
233
+ @send_mutex.synchronize do
234
+ hooks = @fork_hooks
235
+ return if hooks.nil?
236
+
237
+ begin
238
+ @exporter&._native_close
239
+ ensure
240
+ @fork_hooks = nil
241
+ @telemetry = nil
242
+ @exporter = nil
243
+ hooks.each do |stage, block|
244
+ Core::Utils::AtForkMonkeyPatch.remove_at_fork(stage, block)
245
+ end
246
+ ObjectSpace.undefine_finalizer(self)
247
+ end
248
+ end
249
+ end
250
+
251
+ nil
252
+ end
253
+
254
+ # Builds the proc that deregisters a transport's fork hooks.
255
+ #
256
+ # Defined as a class method so the proc closes over ONLY the
257
+ # stage->block handles passed in and never over a Transport instance
258
+ # (which would keep the instance reachable and prevent the finalizer
259
+ # from ever running). Removing the hooks unpins the exporter captured
260
+ # by those closures, allowing it (and its runtime) to be freed.
261
+ def self.fork_hooks_remover(fork_hooks)
262
+ proc do
263
+ fork_hooks.each do |stage, block|
264
+ Core::Utils::AtForkMonkeyPatch.remove_at_fork(stage, block)
265
+ end
266
+ end
267
+ end
268
+ private_class_method :fork_hooks_remover
269
+
270
+ # Send a list of traces to the agent.
271
+ #
272
+ # Each trace is a {Datadog::Tracing::TraceSegment} whose +#spans+
273
+ # returns an +Array+ of {Datadog::Tracing::Span}.
274
+ #
275
+ # @param traces [Array<Datadog::Tracing::TraceSegment>]
276
+ # @return [Array<Response>] one response per batch sent
277
+ def send_traces(traces)
278
+ return [] if traces.empty?
279
+
280
+ # Apply trace-level tags to root spans (same as the HTTP transport)
281
+ traces.each { |trace| TraceFormatter.format!(trace, transport: "native") }
282
+
283
+ # Build the Array<Array<Span>> structure expected by the C extension.
284
+ # Each trace segment becomes one inner array (one trace chunk).
285
+ chunks = traces.map(&:spans)
286
+
287
+ native_events_supported = native_events_supported?
288
+ apply_legacy_span_events!(chunks, native_events_supported)
289
+
290
+ # Serialize the native send and hold the mutex across it so a
291
+ # concurrent fork's :before hook blocks until this send drains
292
+ # (and `_native_before_fork` cannot tear down the runtime mid-send).
293
+ # `Mutex#synchronize` releases on exception / `rb_jump_tag` via its
294
+ # ensure, so interrupt propagation stays correct.
295
+ responses = @send_mutex.synchronize do
296
+ # Resolve the exporter under the same lock used by #close, so
297
+ # close either drains this send or prevents it from starting.
298
+ exporter = @exporter
299
+ raise "Native transport has been closed" if exporter.nil?
300
+
301
+ exporter._native_send_traces(chunks, native_events_supported) do |observations|
302
+ @telemetry&.record(observations)
303
+ end
304
+ end
305
+
306
+ # Update statistics from the response
307
+ responses.each { |response| update_stats_from_response!(response) }
308
+
309
+ responses
310
+ rescue => e
311
+ logger.debug { "Native transport error: #{e.class} #{e.message}" }
312
+ update_stats_from_exception!(e)
313
+ [InternalErrorResponse.new(e)]
314
+ end
315
+
316
+ private
317
+
318
+ # Writes each span's events into the legacy JSON +events+ meta tag
319
+ # when the agent lacks typed-event support, mutating spans in place.
320
+ #
321
+ # @param chunks [Array<Array<Datadog::Tracing::Span>>] trace chunks to serialize
322
+ # @param native_events_supported [Boolean] whether the agent accepts typed events
323
+ # @return [void]
324
+ def apply_legacy_span_events!(chunks, native_events_supported)
325
+ return if native_events_supported
326
+
327
+ chunks.each do |spans|
328
+ spans.each do |span|
329
+ next if span.events.empty?
330
+
331
+ span.set_tag("events", span.events.map(&:to_hash).to_json)
332
+ end
333
+ end
334
+
335
+ nil
336
+ end
337
+
338
+ def tracer_version_string
339
+ defined?(Datadog::VERSION::STRING) ? Datadog::VERSION::STRING : "unknown"
340
+ end
341
+ end
342
+
343
+ # Response for internal errors (exceptions raised before reaching
344
+ # the native transport).
345
+ class InternalErrorResponse
346
+ attr_reader :error
347
+
348
+ def initialize(error)
349
+ @error = error
350
+ end
351
+
352
+ def ok?
353
+ false
354
+ end
355
+
356
+ def internal_error?
357
+ true
358
+ end
359
+
360
+ def server_error?
361
+ false
362
+ end
363
+
364
+ def client_error?
365
+ false
366
+ end
367
+
368
+ def not_found?
369
+ false
370
+ end
371
+
372
+ def unsupported?
373
+ false
374
+ end
375
+
376
+ def payload
377
+ nil
378
+ end
379
+
380
+ def trace_count
381
+ 0
382
+ end
383
+
384
+ def service_rates
385
+ nil
386
+ end
387
+
388
+ def inspect
389
+ "#<#{self.class} error=#{error.inspect}>"
390
+ end
391
+ end
392
+ end
393
+ end
394
+ end
395
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Tracing
5
+ module Transport
6
+ # Shared agent capability negotiation for span event wire formats.
7
+ module SpanEventsNegotiation
8
+ private
9
+
10
+ # Queries whether the agent accepts typed span events, which selects
11
+ # between the typed field and legacy JSON metadata. Only successful
12
+ # capability responses are cached so a later flush can recover.
13
+ #
14
+ # The memo is read and written without synchronization, independent of
15
+ # any lock the including transport holds for its own sends (the native
16
+ # transport's +@send_mutex+, for example). Two concurrent sends can both
17
+ # observe it unset and each issue an +agent_info.fetch+; the duplicate
18
+ # fetch is self-correcting because the last writer wins.
19
+ #
20
+ # @return [Boolean] true if typed span events are supported
21
+ def native_events_supported?
22
+ return @native_events_supported if defined?(@native_events_supported)
23
+
24
+ option = Datadog.configuration.tracing.native_span_events
25
+ unless option.nil?
26
+ @native_events_supported = option
27
+ return option
28
+ end
29
+
30
+ components = Datadog.send(:components, allow_initialization: false)
31
+ return false unless components
32
+
33
+ response = components.agent_info.fetch
34
+ return false unless response
35
+
36
+ @native_events_supported = response.span_events == true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end