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
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
3
4
  require_relative "redactor"
5
+ require_relative "guardrails_telemetry"
6
+ require_relative "../core/utils/time"
4
7
 
5
8
  module Datadog
6
9
  module DI
7
- # Serializes captured snapshot to primitive types, which are subsequently
10
+ # Serializes captured snapshots to primitive types, which are subsequently
8
11
  # serialized to JSON and sent to the backend.
9
12
  #
10
- # This class performs actual removal of sensitive values from the
11
- # snapshots. It uses Redactor to determine which values are sensitive
12
- # and need to be removed.
13
+ # Sensitive values are removed from the snapshots.
13
14
  #
14
15
  # Serializer normally ought not to invoke user (application) code,
15
16
  # to guarantee predictable performance. However, objects like ActiveRecord
@@ -19,7 +20,7 @@ module Datadog
19
20
  # thus they won't be captured at all). To accommodate complex objects,
20
21
  # there is an extension mechanism implemented permitting registration
21
22
  # of serializer callbacks for arbitrary types. Applications and libraries
22
- # definining such serializer callbacks should be very careful to
23
+ # defining such serializer callbacks should be very careful to
23
24
  # have predictable performance and avoid exceptions and infinite loops
24
25
  # and other such issues.
25
26
  #
@@ -36,20 +37,23 @@ module Datadog
36
37
  # efficient but there would be additional overhead from passing this
37
38
  # parameter all the time and the API would get more complex.
38
39
  #
39
- # Note: "self" cannot be used as a parameter name in Ruby, therefore
40
- # there should never be a conflict between instance variable
41
- # serialization and method parameters.
42
- #
43
40
  # @api private
44
41
  class Serializer
45
- # Exception classes that should never be caught during serialization.
46
- # These represent fatal conditions (signals, interrupts, system exit)
47
- # that must propagate to the caller. NoMemoryError is deliberately
48
- # excluded (unlike DI::FATAL_EXCEPTION_CLASSES): serialization of large
49
- # or deeply recursive objects can exhaust memory, and the serializer must
50
- # return a safe stub rather than tear the process down.
42
+ # Exception classes that must propagate to the caller during
43
+ # serialization: fatal conditions (signals, interrupts, system exit).
44
+ # NoMemoryError is deliberately excluded (unlike
45
+ # DI::FATAL_EXCEPTION_CLASSES): serialization of large or deeply
46
+ # recursive objects can exhaust memory, and the serializer must return
47
+ # a safe stub rather than terminate the process.
51
48
  SERIALIZABLE_FATAL_EXCEPTION_CLASSES = [SystemExit, SignalException].freeze
52
49
 
50
+ # Placeholder emitted by #serialize_value_for_message in place of a value
51
+ # whose identifier or type matches the redaction configuration. The
52
+ # message-path counterpart of the +notCapturedReason+
53
+ # "redactedIdent"/"redactedType" markers that #serialize_value produces
54
+ # on the snapshot path.
55
+ REDACTED_VALUE_FOR_MESSAGE = "[redacted]"
56
+
53
57
  # Third-party library integration / custom serializers.
54
58
  #
55
59
  # Dynamic instrumentation has limited payload sizes, and for efficiency
@@ -92,20 +96,63 @@ module Datadog
92
96
  # hex string, or UTF-8 with replacement characters) before returning
93
97
  # it from the custom serializer.
94
98
  @@flat_registry = []
95
- def self.register(condition: nil, &block)
99
+ # Registers a custom serializer. The block serializes each value
100
+ # for which +condition+ returns a truthy value.
101
+ #
102
+ # @param condition [Proc] receives the value being serialized and
103
+ # returns whether this serializer applies to it
104
+ # @param block [Proc] serializer for the values +condition+ accepts
105
+ # @raise [ArgumentError] if +condition+ is not a Proc or +block+ is absent
106
+ # @return [void]
107
+ def self.register(condition:, &block)
108
+ unless condition.is_a?(Proc)
109
+ raise ArgumentError, "Serializer.register condition must be a Proc, got #{condition.class}"
110
+ end
111
+
112
+ unless block
113
+ raise ArgumentError, "Serializer.register requires a serializer block"
114
+ end
115
+
96
116
  @@flat_registry << {condition: condition, proc: block}
117
+ nil
97
118
  end
98
119
 
99
- def initialize(settings, redactor, telemetry: nil)
120
+ # Initializes the serializer.
121
+ #
122
+ # @param settings [Datadog::Core::Configuration::Settings] tracer settings
123
+ # @param redactor [Redactor] redactor gating captured values
124
+ # @param logger [DI::Logger] logger for contained serialization failures
125
+ # @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical capture-incomplete metric
126
+ # @param telemetry [Datadog::Core::Telemetry::Component, nil] component serializer errors are reported through
127
+ # @return [void]
128
+ def initialize(settings, redactor, logger:, guardrails_telemetry:, telemetry: nil)
100
129
  @settings = settings
101
130
  @redactor = redactor
131
+ @logger = logger
132
+ @guardrails_telemetry = guardrails_telemetry
102
133
  @telemetry = telemetry
103
134
  end
104
135
 
105
136
  attr_reader :settings
106
137
  attr_reader :redactor
138
+ # The logger.
139
+ # @return [DI::Logger]
140
+ attr_reader :logger
141
+ # The guardrails metric emitter.
142
+ # @return [GuardrailsTelemetry]
143
+ attr_reader :guardrails_telemetry
107
144
  attr_reader :telemetry
108
145
 
146
+ # Emits the canonical capture-incomplete metric for a capture limit
147
+ # enforced while serializing a snapshot, with the snapshot event
148
+ # type.
149
+ #
150
+ # @param reason [String] a GuardrailsTelemetry::Reason constant naming the enforced limit
151
+ # @return [void]
152
+ def record_capture_incomplete(reason)
153
+ guardrails_telemetry.capture_incomplete(reason: reason, event_type: GuardrailsTelemetry::EVENT_TYPE_SNAPSHOT)
154
+ end
155
+
109
156
  def combine_args(args, kwargs, target_self)
110
157
  counter = 0
111
158
  combined = args.each_with_object({}) do |value, c|
@@ -126,31 +173,39 @@ module Datadog
126
173
  # arguments to keyword arguments ("arg1", "arg2", ...) and ensure
127
174
  # the positional arguments are listed first.
128
175
  #
129
- # Instance variables are technically a hash just like kwargs,
130
- # we take them as a separate parameter to avoid a hash merge
131
- # in upstream code.
176
+ # @param args [Array] positional arguments passed to the probed method
177
+ # @param kwargs [Hash{Symbol => Object}] keyword arguments passed to the probed method
178
+ # @param target_self [any] receiver of the probed method invocation
179
+ # @param deadline [Float] absolute capture deadline in thread CPU clock seconds
180
+ # @return [Hash{Symbol => Hash}] serialized arguments keyed by argument name
132
181
  def serialize_args(args, kwargs, target_self,
182
+ deadline:,
133
183
  depth: settings.dynamic_instrumentation.max_capture_depth,
134
184
  attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count,
135
185
  length: nil,
136
186
  collection_size: nil)
137
187
  combined = combine_args(args, kwargs, target_self)
138
188
  serialize_vars(combined, depth: depth, attribute_count: attribute_count,
139
- length: length, collection_size: collection_size)
189
+ length: length, collection_size: collection_size, deadline: deadline,)
140
190
  end
141
191
 
142
192
  # Serializes variables captured by a line probe.
143
193
  #
144
194
  # These are normally local variables that exist on a particular line
145
195
  # of executed code.
196
+ #
197
+ # @param vars [Hash{Symbol => Object}] variables to serialize, keyed by variable name
198
+ # @param deadline [Float] absolute capture deadline in thread CPU clock seconds
199
+ # @return [Hash{Symbol => Hash}] serialized variables keyed by variable name
146
200
  def serialize_vars(vars,
201
+ deadline:,
147
202
  depth: settings.dynamic_instrumentation.max_capture_depth,
148
203
  attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count,
149
204
  length: nil,
150
205
  collection_size: nil)
151
206
  vars.each_with_object({}) do |(k, v), agg|
152
207
  agg[k] = serialize_value(v, name: k, depth: depth, attribute_count: attribute_count,
153
- length: length, collection_size: collection_size)
208
+ length: length, collection_size: collection_size, deadline: deadline,)
154
209
  end
155
210
  end
156
211
 
@@ -166,7 +221,13 @@ module Datadog
166
221
  # (integers, strings, arrays, hashes).
167
222
  #
168
223
  # Respects string length, collection size and traversal depth limits.
169
- def serialize_value(value, name: nil,
224
+ #
225
+ # @param value [any] value to serialize
226
+ # @param name [Symbol, String, nil] name the value is bound to, used for redaction
227
+ # @param deadline [Float] absolute capture deadline in thread CPU clock seconds
228
+ # @return [Hash{Symbol => Object}] serialized value; the {type:, notCapturedReason: "timeout"} stub when the deadline is exceeded
229
+ def serialize_value(value, deadline:,
230
+ name: nil,
170
231
  depth: settings.dynamic_instrumentation.max_capture_depth,
171
232
  attribute_count: nil,
172
233
  length: nil,
@@ -175,6 +236,12 @@ module Datadog
175
236
  attribute_count ||= settings.dynamic_instrumentation.max_capture_attribute_count
176
237
  cls = type || value.class
177
238
  begin
239
+ if deadline_exceeded?(deadline)
240
+ telemetry&.inc(DI::TELEMETRY_NAMESPACE, "serialized_values_skipped_by_timeout", 1)
241
+ record_capture_incomplete(GuardrailsTelemetry::Reason::CAPTURE_TIMEOUT)
242
+ return {type: class_name(cls), notCapturedReason: "timeout"}
243
+ end
244
+
178
245
  if redactor.redact_type?(value)
179
246
  return {type: class_name(cls), notCapturedReason: "redactedType"}
180
247
  end
@@ -206,7 +273,7 @@ module Datadog
206
273
 
207
274
  if condition_result
208
275
  serializer_proc = entry.fetch(:proc)
209
- return serializer_proc.call(self, value, name: nil, depth: depth)
276
+ return serializer_proc.call(self, value, name: nil, depth: depth, deadline: deadline)
210
277
  end
211
278
  end
212
279
  end
@@ -220,8 +287,6 @@ module Datadog
220
287
  when Integer, Float, TrueClass, FalseClass
221
288
  serialized.update(value: value.to_s)
222
289
  when Time
223
- # This path also handles DateTime values although they do not need
224
- # to be explicitly added to the case statement.
225
290
  serialized.update(value: value.iso8601)
226
291
  when Date
227
292
  serialized.update(value: value.to_s)
@@ -239,27 +304,24 @@ module Datadog
239
304
  end
240
305
 
241
306
  # Handle binary strings and invalid UTF-8 by escaping to JSON-safe format.
242
- # See escape_binary_string for details on the escaping format.
243
- #
244
- # Truncate binary data BEFORE escaping to avoid cutting mid-escape-sequence.
245
307
  # For regular strings, the limit is applied to string length in characters.
246
308
  length ||= settings.dynamic_instrumentation.max_capture_string_length
247
309
  max = length
248
310
 
249
311
  if value.encoding == Encoding::BINARY || !value.valid_encoding?
250
- # Truncate binary data BEFORE escaping to avoid cutting mid-escape-sequence
312
+ # Truncate binary data before escaping to avoid cutting mid-escape-sequence
251
313
  # For invalid encodings, use bytesize instead of length to avoid encoding errors
252
314
  original_size = value.bytesize
253
315
  if original_size > max
254
316
  serialized.update(truncated: true, size: original_size)
317
+ record_capture_incomplete(GuardrailsTelemetry::Reason::STRING_LENGTH)
255
318
  value = value.byteslice(0...max)
256
319
  end
257
320
  value = escape_binary_string(value) # steep:ignore ArgumentTypeMismatch
258
- false # Already converted to a new string
259
321
  else
260
- # Truncate non-binary strings
261
322
  if value.length > max
262
323
  serialized.update(truncated: true, size: value.length)
324
+ record_capture_incomplete(GuardrailsTelemetry::Reason::STRING_LENGTH)
263
325
  value = value[0...max]
264
326
  need_dup = false
265
327
  end
@@ -271,23 +333,32 @@ module Datadog
271
333
  when Array
272
334
  if depth <= 0
273
335
  serialized.update(notCapturedReason: "depth")
336
+ record_capture_incomplete(GuardrailsTelemetry::Reason::DEPTH)
274
337
  else
275
338
  collection_size ||= settings.dynamic_instrumentation.max_capture_collection_size
276
339
  max = collection_size
277
340
  if max != 0 && value.length > max
278
341
  serialized.update(notCapturedReason: "collectionSize", size: value.length)
342
+ record_capture_incomplete(GuardrailsTelemetry::Reason::COLLECTION_SIZE)
279
343
  # same steep failure with array slices.
280
344
  # https://github.com/soutaro/steep/issues/1219
281
345
  value = value[0...max] || []
282
346
  end
283
- entries = value.map do |elt|
284
- serialize_value(elt, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count)
347
+ entries = []
348
+ value.each do |elt|
349
+ entry = serialize_value(elt, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count, deadline: deadline)
350
+ entries << entry
351
+ if entry[:notCapturedReason] == "timeout"
352
+ serialized.update(notCapturedReason: "timeout")
353
+ break
354
+ end
285
355
  end
286
356
  serialized.update(elements: entries)
287
357
  end
288
358
  when Hash
289
359
  if depth <= 0
290
360
  serialized.update(notCapturedReason: "depth")
361
+ record_capture_incomplete(GuardrailsTelemetry::Reason::DEPTH)
291
362
  else
292
363
  collection_size ||= settings.dynamic_instrumentation.max_capture_collection_size
293
364
  max = collection_size
@@ -296,17 +367,28 @@ module Datadog
296
367
  value.each do |k, v|
297
368
  if max != 0 && cur >= max
298
369
  serialized.update(notCapturedReason: "collectionSize", size: value.length)
370
+ record_capture_incomplete(GuardrailsTelemetry::Reason::COLLECTION_SIZE)
299
371
  break
300
372
  end
301
373
  cur += 1
302
- entries << [serialize_value(k, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count),
303
- serialize_value(v, name: k, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count)]
374
+ key_entry = serialize_value(k, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count, deadline: deadline)
375
+ if key_entry[:notCapturedReason] == "timeout"
376
+ serialized.update(notCapturedReason: "timeout")
377
+ break
378
+ end
379
+ value_entry = serialize_value(v, name: k, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count, deadline: deadline)
380
+ entries << [key_entry, value_entry]
381
+ if value_entry[:notCapturedReason] == "timeout"
382
+ serialized.update(notCapturedReason: "timeout")
383
+ break
384
+ end
304
385
  end
305
386
  serialized.update(entries: entries)
306
387
  end
307
388
  else
308
389
  if depth <= 0
309
390
  serialized.update(notCapturedReason: "depth")
391
+ record_capture_incomplete(GuardrailsTelemetry::Reason::DEPTH)
310
392
  else
311
393
  fields = {}
312
394
  cur = 0
@@ -327,40 +409,42 @@ module Datadog
327
409
  #
328
410
  # For consistency, we should have some kind of stable order of
329
411
  # instance variables on all supported Ruby runtimes, so that the UI
330
- # stays consistent. Given that initial implementation of Ruby DI
331
- # does not support JRuby, we don't handle JRuby's lack of ordering
332
- # of #instance_variables here, but if JRuby is supported in the
333
- # future this may need to be addressed.
412
+ # stays consistent. Ruby DI does not support JRuby, so JRuby's lack
413
+ # of ordering of #instance_variables is not handled here.
334
414
  ivars = value.instance_variables
335
415
 
336
416
  ivars.each do |ivar|
337
417
  if cur >= attribute_count
338
418
  serialized.update(notCapturedReason: "fieldCount", fields: fields)
419
+ record_capture_incomplete(GuardrailsTelemetry::Reason::FIELD_COUNT)
339
420
  break
340
421
  end
341
422
  cur += 1
342
- fields[ivar] = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count)
423
+ entry = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count, deadline: deadline)
424
+ fields[ivar] = entry
425
+ if entry[:notCapturedReason] == "timeout"
426
+ serialized.update(notCapturedReason: "timeout")
427
+ break
428
+ end
343
429
  end
344
430
  serialized.update(fields: fields)
345
431
  end
346
432
  end
347
433
  serialized
348
434
  rescue Exception => exc # standard:disable Lint/RescueException
349
- # Re-raise fatal exceptions that should not be caught
350
- # (signals, interrupts, system exit)
351
435
  raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) }
352
436
 
353
- # Catch all other exceptions including SystemStackError and NoMemoryError.
354
- # These inherit from Exception (not StandardError) but can occur during
355
- # serialization (e.g., infinite recursion in custom serializers, memory
356
- # exhaustion from large objects) and should return a safe structure
357
- # rather than propagating to the transport layer.
437
+ # Catch all remaining exceptions: SystemStackError and NoMemoryError can
438
+ # occur during serialization (e.g. a custom serializer recursing
439
+ # infinitely) and produce the safe stub.
440
+ record_capture_incomplete(GuardrailsTelemetry::Reason::RUNTIME_ERROR)
441
+ logger.debug { "di: error serializing value of #{class_name(cls)}: #{exc.class}: #{exc.message}" }
358
442
  telemetry&.report(exc, description: "Error serializing")
359
443
  {type: class_name(cls), notSerializedReason: exc.to_s}
360
444
  end
361
445
  end
362
446
 
363
- # This method is used for serializing arbitrary values into log messages.
447
+ # Serializes a value for interpolation into a log-probe message.
364
448
  # Because the output is meant to be human-readable, we cannot use
365
449
  # the "normal" serialization format which is meant to be machine-readable.
366
450
  # Serialize objects with depth of 1 and include the class name.
@@ -375,9 +459,19 @@ module Datadog
375
459
  #
376
460
  # We also use the Ruby-like syntax for symbols, which don't exist
377
461
  # in other languages.
378
- def serialize_value_for_message(value, depth = 1)
462
+ #
463
+ # +name+, when given, is the identifier the template expression
464
+ # references at its top level; a redacted identifier yields the
465
+ # redaction placeholder, mirroring #serialize_value on the snapshot path.
466
+ # Message rendering is capped by depth (1) and
467
+ # MAX_MESSAGE_COLLECTION_SIZE / MAX_MESSAGE_ATTRIBUTE_COUNT.
468
+ def serialize_value_for_message(value, depth: 1, name: nil)
379
469
  # This method is more verbose than "normal" Ruby code to avoid
380
470
  # array allocations.
471
+
472
+ return REDACTED_VALUE_FOR_MESSAGE if redactor.redact_type?(value)
473
+ return REDACTED_VALUE_FOR_MESSAGE if name && redactor.redact_identifier?(name)
474
+
381
475
  case value
382
476
  when NilClass
383
477
  "nil"
@@ -398,7 +492,7 @@ module Datadog
398
492
  value = value_
399
493
  end
400
494
  "[" + value.map do |item|
401
- serialize_value_for_message(item, depth - 1)
495
+ serialize_value_for_message(item, depth: depth - 1)
402
496
  end.join(", ") + "]"
403
497
  when Hash
404
498
  return "..." if depth <= 0
@@ -413,7 +507,12 @@ module Datadog
413
507
  truncated = true
414
508
  end
415
509
  serialized = keys.map do |key|
416
- "#{serialize_value_for_message(key, depth - 1)} => #{serialize_value_for_message(value[key], depth - 1)}"
510
+ serialized_value = if (String === key || Symbol === key) && redactor.redact_identifier?(key)
511
+ REDACTED_VALUE_FOR_MESSAGE
512
+ else
513
+ serialize_value_for_message(value[key], depth: depth - 1)
514
+ end
515
+ "#{serialize_value_for_message(key, depth: depth - 1)} => #{serialized_value}"
417
516
  end
418
517
  if truncated
419
518
  serialized[serialized.length] = serialized[serialized.length - 1]
@@ -435,7 +534,12 @@ module Datadog
435
534
  serialized = vars.map do |var|
436
535
  # +var+ here is always the instance variable name which is a
437
536
  # symbol, we do not need to run it through our serializer.
438
- "#{var}=#{serialize_value_for_message(value.send(:instance_variable_get, var), depth - 1)}"
537
+ serialized_value = if redactor.redact_identifier?(var)
538
+ REDACTED_VALUE_FOR_MESSAGE
539
+ else
540
+ serialize_value_for_message(value.send(:instance_variable_get, var), depth: depth - 1)
541
+ end
542
+ "#{var}=#{serialized_value}"
439
543
  end
440
544
  if truncated
441
545
  serialized << serialized.last
@@ -450,13 +554,26 @@ module Datadog
450
554
  raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) }
451
555
 
452
556
  telemetry&.report(exc, description: "Error serializing for message")
453
- # TODO class_name(foo) can also fail, which we don't handle here.
454
- # Telemetry reporting could potentially also fail?
557
+ # TODO handle class_name raising here as well.
455
558
  "#<#{class_name(value.class)}: serialization error>"
456
559
  end
457
560
 
561
+ # Computes the absolute thread CPU time deadline (in seconds) for a
562
+ # capture point from max_time_to_serialize_ms.
563
+ #
564
+ # @return [Float] absolute thread CPU clock reading, in seconds, at
565
+ # which the capture budget is exhausted
566
+ def serialization_deadline
567
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) +
568
+ settings.dynamic_instrumentation.max_time_to_serialize_ms / 1000.0
569
+ end
570
+
458
571
  private
459
572
 
573
+ def deadline_exceeded?(deadline)
574
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) >= deadline
575
+ end
576
+
460
577
  MAX_MESSAGE_COLLECTION_SIZE = 3
461
578
  MAX_MESSAGE_ATTRIBUTE_COUNT = 5
462
579
 
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module DI
5
+ # A snapshot payload queued for sending to the agent, paired with
6
+ # the guardrails event type of the probe that produced it, so drop
7
+ # metrics can be tagged with the probe's classification.
8
+ # @api private
9
+ class SnapshotEvent
10
+ # Initializes the event.
11
+ #
12
+ # @param payload [Hash] the snapshot payload to send to the agent
13
+ # @param event_type [String] one of GuardrailsTelemetry::EVENT_TYPE_SNAPSHOT and EVENT_TYPE_LOG
14
+ # @return [void]
15
+ def initialize(payload:, event_type:)
16
+ @payload = payload
17
+ @event_type = event_type
18
+ end
19
+
20
+ # The snapshot payload to send to the agent.
21
+ # @return [Hash]
22
+ attr_reader :payload
23
+
24
+ # The guardrails event type of the probe that produced the payload.
25
+ # @return [String] one of GuardrailsTelemetry::EVENT_TYPE_SNAPSHOT and EVENT_TYPE_LOG
26
+ attr_reader :event_type
27
+
28
+ # Compares two events by payload and event type.
29
+ #
30
+ # @param other [Object] the object to compare with
31
+ # @return [Boolean] whether the other object is an event with the
32
+ # same payload and event type
33
+ def ==(other)
34
+ other.is_a?(SnapshotEvent) && other.payload == payload && other.event_type == event_type
35
+ end
36
+ end
37
+ end
38
+ end
@@ -47,11 +47,20 @@ module Datadog
47
47
  end.to_transport(DI::Transport::Diagnostics::Transport)
48
48
  end
49
49
 
50
- # Builds a new Transport::HTTP::Client with default settings
51
- # Pass a block to override any settings.
50
+ # Builds the snapshot input transport. Pass a block to override
51
+ # any transport settings.
52
+ #
53
+ # @param agent_settings [Datadog::Core::Configuration::AgentSettings] agent connection settings
54
+ # @param logger [DI::Logger] logger for transport diagnostics
55
+ # @param headers [Hash{String => String}, nil] headers to send with every request
56
+ # @param telemetry [Datadog::Core::Telemetry::Component, nil] component transport errors are reported through
57
+ # @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical guardrails drop metric
58
+ # @return [DI::Transport::Input::Transport]
59
+ # @yield [builder] block overriding the default transport builder settings
52
60
  def self.input(
53
61
  agent_settings:,
54
62
  logger:,
63
+ guardrails_telemetry:,
55
64
  headers: nil,
56
65
  telemetry: nil
57
66
  )
@@ -72,7 +81,8 @@ module Datadog
72
81
  builder.to_api_instances,
73
82
  builder.default_api,
74
83
  logger: logger,
75
- telemetry: telemetry
84
+ telemetry: telemetry,
85
+ guardrails_telemetry: guardrails_telemetry,
76
86
  )
77
87
  end
78
88
  end
@@ -8,6 +8,7 @@ require_relative "../../core/transport/request"
8
8
  require_relative "../../core/transport/transport"
9
9
  require_relative "../error"
10
10
  require_relative "../fatal_exceptions"
11
+ require_relative "../guardrails_telemetry"
11
12
  require_relative "http/input"
12
13
 
13
14
  module Datadog
@@ -26,10 +27,22 @@ module Datadog
26
27
 
27
28
  class Transport < Core::Transport::Transport
28
29
  attr_reader :telemetry
30
+ # The guardrails metric emitter.
31
+ # @return [GuardrailsTelemetry]
32
+ attr_reader :guardrails_telemetry
29
33
 
30
- def initialize(apis, default_api, logger:, telemetry: nil)
34
+ # Initializes the transport.
35
+ #
36
+ # @param apis [Datadog::Core::Transport::HTTP::API::Map] API instances by name
37
+ # @param default_api [String] name of the API requests are sent through when unspecified
38
+ # @param logger [Datadog::Core::Logger] logger for transport diagnostics
39
+ # @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical payload-drop metric
40
+ # @param telemetry [Datadog::Core::Telemetry::Component, nil] component transport errors are reported through
41
+ # @return [void]
42
+ def initialize(apis, default_api, logger:, guardrails_telemetry:, telemetry: nil)
31
43
  super(apis, default_api, logger: logger)
32
44
  @telemetry = telemetry
45
+ @guardrails_telemetry = guardrails_telemetry
33
46
  end
34
47
 
35
48
  # The limit on an individual snapshot payload, aka "log line",
@@ -62,19 +75,24 @@ module Datadog
62
75
  # Large snapshots (> 1MB) are dropped. Batches are split into chunks
63
76
  # of ~2MB each to avoid large network requests.
64
77
  #
65
- # @param payload [Array<Hash>] Array of snapshot payloads
78
+ # @param payload [Array<SnapshotEvent>] the snapshot events to send, each paired with its probe's guardrails event type
66
79
  # @param tags [Hash] Tags to send with the snapshots
67
80
  # @param on_serialization_error [Proc] Called with (probe_id, exception)
68
81
  # when a snapshot fails to serialize.
82
+ # @return [Array<SnapshotEvent>] the events that were sent
69
83
  def send_input(payload, tags, on_serialization_error:)
70
84
  serialized_tags = Core::TagBuilder.serialize_tags(tags)
71
85
 
72
86
  # Serialize each snapshot individually to isolate failures
73
87
  encoded_snapshots = []
74
- payload.each do |snapshot|
75
- encoded = encoder.encode(snapshot)
76
- if encoded.length > MAX_SERIALIZED_SNAPSHOT_SIZE
77
- logger.debug { "di: dropping too big snapshot" }
88
+ dropped_events = []
89
+ payload.each do |event|
90
+ encoded = encoder.encode(event.payload)
91
+ if encoded.bytesize > MAX_SERIALIZED_SNAPSHOT_SIZE
92
+ logger.debug do
93
+ "di: dropping too big snapshot (#{GuardrailsTelemetry::Reason::PAYLOAD_TOO_LARGE})"
94
+ end
95
+ dropped_events << [event, encoded.bytesize]
78
96
  next
79
97
  end
80
98
  encoded_snapshots << encoded
@@ -82,7 +100,7 @@ module Datadog
82
100
  Datadog::DI.reraise_if_fatal(exc)
83
101
  # Serialization failed for this snapshot - report via callback
84
102
  # This catches JSON::GeneratorError, Encoding errors, TypeError, etc.
85
- probe_id = snapshot.dig(:debugger, :snapshot, :probe, :id)
103
+ probe_id = event.payload.dig(:debugger, :snapshot, :probe, :id)
86
104
  logger.debug { "di: JSON encoding failed for snapshot (probe #{probe_id}): #{exc.class}: #{exc.message}" }
87
105
  telemetry&.report(exc, description: "JSON encoding failed for snapshot")
88
106
 
@@ -97,6 +115,13 @@ module Datadog
97
115
  end
98
116
  end
99
117
 
118
+ dropped_events.each do |event, bytes|
119
+ guardrails_telemetry.dropped(
120
+ reason: GuardrailsTelemetry::Reason::PAYLOAD_TOO_LARGE,
121
+ event_type: event.event_type, bytes: bytes,
122
+ )
123
+ end
124
+
100
125
  return payload if encoded_snapshots.empty?
101
126
 
102
127
  Datadog::Core::Chunker.chunk_by_size(
@@ -154,7 +154,7 @@ module Datadog
154
154
  attributes = {
155
155
  "exception.type" => formatted_exception.type,
156
156
  "exception.message" => formatted_exception.message,
157
- "exception.stacktrace" => formatted_exception.backtrace
157
+ "exception.stacktrace" => formatted_exception.backtrace,
158
158
  }
159
159
  Datadog::Tracing::SpanEvent.new("exception", attributes: attributes)
160
160
  end
@@ -19,7 +19,7 @@ module Datadog
19
19
  TELEMETRY_METRICS_EVENTS_INTO_TYPES = {
20
20
  LOGIN_SUCCESS_EVENT => "login_success",
21
21
  LOGIN_FAILURE_EVENT => "login_failure",
22
- SIGNUP_EVENT => "signup"
22
+ SIGNUP_EVENT => "signup",
23
23
  }.freeze
24
24
 
25
25
  class << self
@@ -241,7 +241,7 @@ module Datadog
241
241
 
242
242
  tags = {
243
243
  event_type: TELEMETRY_METRICS_EVENTS_INTO_TYPES[event],
244
- sdk_version: TELEMETRY_METRICS_SDK_VERSION
244
+ sdk_version: TELEMETRY_METRICS_SDK_VERSION,
245
245
  }
246
246
  telemetry.inc(TELEMETRY_METRICS_NAMESPACE, TELEMETRY_METRICS_SDK_EVENT, 1, tags: tags)
247
247
  end