datadog 2.29.0 → 2.31.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 (214) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -2
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +21 -12
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +9 -7
  5. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +18 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +10 -0
  7. data/ext/datadog_profiling_native_extension/extconf.rb +6 -24
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +5 -6
  9. data/ext/datadog_profiling_native_extension/http_transport.c +51 -64
  10. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
  11. data/ext/datadog_profiling_native_extension/profiling.c +3 -1
  12. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +24 -8
  13. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -3
  14. data/ext/datadog_profiling_native_extension/stack_recorder.c +29 -43
  15. data/ext/libdatadog_api/crashtracker.c +5 -8
  16. data/ext/libdatadog_api/crashtracker_report_exception.c +34 -144
  17. data/ext/libdatadog_api/datadog_ruby_common.c +18 -0
  18. data/ext/libdatadog_api/datadog_ruby_common.h +10 -0
  19. data/ext/libdatadog_api/di.c +79 -0
  20. data/ext/libdatadog_api/extconf.rb +5 -20
  21. data/ext/libdatadog_api/init.c +5 -2
  22. data/ext/libdatadog_extconf_helpers.rb +57 -11
  23. data/lib/datadog/ai_guard/component.rb +2 -0
  24. data/lib/datadog/ai_guard/configuration/settings.rb +3 -0
  25. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +41 -3
  26. data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
  27. data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
  28. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +3 -1
  29. data/lib/datadog/ai_guard/evaluation/request.rb +14 -9
  30. data/lib/datadog/ai_guard/evaluation/result.rb +3 -1
  31. data/lib/datadog/ai_guard/evaluation.rb +36 -7
  32. data/lib/datadog/ai_guard.rb +26 -8
  33. data/lib/datadog/appsec/autoload.rb +1 -1
  34. data/lib/datadog/appsec/component.rb +11 -7
  35. data/lib/datadog/appsec/contrib/active_record/patcher.rb +3 -0
  36. data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
  37. data/lib/datadog/appsec/contrib/excon/patcher.rb +2 -0
  38. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +1 -1
  39. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  40. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  41. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +10 -11
  42. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
  43. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +25 -2
  44. data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
  45. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
  46. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  47. data/lib/datadog/appsec/contrib/rails/patcher.rb +2 -2
  48. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +2 -0
  49. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
  50. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +3 -3
  51. data/lib/datadog/appsec/event.rb +1 -17
  52. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
  53. data/lib/datadog/appsec/instrumentation/gateway.rb +2 -15
  54. data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -2
  55. data/lib/datadog/appsec/utils/http/media_type.rb +1 -2
  56. data/lib/datadog/appsec/utils/http/url_encoded.rb +2 -2
  57. data/lib/datadog/appsec.rb +5 -9
  58. data/lib/datadog/core/configuration/base.rb +17 -5
  59. data/lib/datadog/core/configuration/components.rb +21 -8
  60. data/lib/datadog/core/configuration/config_helper.rb +9 -0
  61. data/lib/datadog/core/configuration/option.rb +32 -6
  62. data/lib/datadog/core/configuration/option_definition.rb +38 -12
  63. data/lib/datadog/core/configuration/options.rb +41 -7
  64. data/lib/datadog/core/configuration/settings.rb +42 -3
  65. data/lib/datadog/core/configuration/supported_configurations.rb +17 -0
  66. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  67. data/lib/datadog/core/contrib/rails/utils.rb +7 -3
  68. data/lib/datadog/core/crashtracking/component.rb +7 -15
  69. data/lib/datadog/core/environment/container.rb +2 -2
  70. data/lib/datadog/core/environment/ext.rb +1 -0
  71. data/lib/datadog/core/environment/identity.rb +25 -3
  72. data/lib/datadog/core/environment/process.rb +12 -0
  73. data/lib/datadog/core/metrics/client.rb +5 -5
  74. data/lib/datadog/core/process_discovery.rb +5 -0
  75. data/lib/datadog/core/remote/component.rb +38 -21
  76. data/lib/datadog/core/runtime/metrics.rb +2 -3
  77. data/lib/datadog/core/telemetry/component.rb +3 -0
  78. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +2 -3
  79. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  80. data/lib/datadog/core/telemetry/event/app_started.rb +151 -169
  81. data/lib/datadog/core/telemetry/event.rb +1 -7
  82. data/lib/datadog/core/telemetry/ext.rb +1 -0
  83. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -0
  84. data/lib/datadog/core/telemetry/worker.rb +20 -0
  85. data/lib/datadog/core/utils/base64.rb +1 -1
  86. data/lib/datadog/core/utils/only_once.rb +1 -1
  87. data/lib/datadog/core/utils/spawn_monkey_patch.rb +36 -0
  88. data/lib/datadog/core/workers/async.rb +1 -1
  89. data/lib/datadog/core/workers/interval_loop.rb +13 -6
  90. data/lib/datadog/core/workers/queue.rb +0 -4
  91. data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
  92. data/lib/datadog/core.rb +0 -1
  93. data/lib/datadog/data_streams/pathway_context.rb +1 -1
  94. data/lib/datadog/data_streams/processor.rb +1 -0
  95. data/lib/datadog/di/boot.rb +3 -4
  96. data/lib/datadog/di/component.rb +20 -4
  97. data/lib/datadog/di/instrumenter.rb +20 -10
  98. data/lib/datadog/di/probe_manager.rb +79 -62
  99. data/lib/datadog/di/probe_notification_builder.rb +148 -33
  100. data/lib/datadog/di/probe_notifier_worker.rb +52 -6
  101. data/lib/datadog/di/probe_repository.rb +198 -0
  102. data/lib/datadog/di/remote.rb +5 -6
  103. data/lib/datadog/di/serializer.rb +127 -9
  104. data/lib/datadog/di/transport/http.rb +12 -3
  105. data/lib/datadog/di/transport/input.rb +46 -8
  106. data/lib/datadog/di.rb +81 -0
  107. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  108. data/lib/datadog/open_feature/configuration.rb +2 -0
  109. data/lib/datadog/open_feature/evaluation_engine.rb +1 -1
  110. data/lib/datadog/open_feature/exposures/reporter.rb +1 -1
  111. data/lib/datadog/open_feature/exposures/worker.rb +1 -1
  112. data/lib/datadog/open_feature/remote.rb +1 -1
  113. data/lib/datadog/open_feature/transport.rb +1 -1
  114. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -0
  115. data/lib/datadog/profiling/collectors/code_provenance.rb +2 -3
  116. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +14 -1
  117. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  118. data/lib/datadog/profiling/component.rb +31 -1
  119. data/lib/datadog/profiling/http_transport.rb +5 -6
  120. data/lib/datadog/profiling/load_native_extension.rb +1 -1
  121. data/lib/datadog/profiling/profiler.rb +15 -12
  122. data/lib/datadog/profiling/scheduler.rb +2 -2
  123. data/lib/datadog/profiling/tasks/exec.rb +2 -2
  124. data/lib/datadog/profiling/tasks/setup.rb +2 -2
  125. data/lib/datadog/profiling.rb +1 -2
  126. data/lib/datadog/single_step_instrument.rb +1 -1
  127. data/lib/datadog/tracing/buffer.rb +3 -3
  128. data/lib/datadog/tracing/component.rb +11 -0
  129. data/lib/datadog/tracing/configuration/settings.rb +2 -1
  130. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  131. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +20 -0
  132. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
  133. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  134. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  135. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  136. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  137. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  138. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  139. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  140. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  141. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  142. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  143. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  144. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  145. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +1 -1
  146. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
  147. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
  148. data/lib/datadog/tracing/contrib/configurable.rb +18 -3
  149. data/lib/datadog/tracing/contrib/dalli/integration.rb +4 -1
  150. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +1 -1
  151. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
  152. data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
  153. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -2
  154. data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
  155. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
  156. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +5 -2
  157. data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
  158. data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -2
  159. data/lib/datadog/tracing/contrib/grape/endpoint.rb +7 -7
  160. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
  161. data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
  162. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
  163. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  164. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +5 -2
  165. data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
  166. data/lib/datadog/tracing/contrib/http/instrumentation.rb +1 -1
  167. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +5 -2
  168. data/lib/datadog/tracing/contrib/httpclient/ext.rb +1 -0
  169. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +5 -2
  170. data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
  171. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +1 -1
  172. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
  173. data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
  174. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +1 -1
  175. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
  176. data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
  177. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +5 -1
  178. data/lib/datadog/tracing/contrib/rack/ext.rb +1 -0
  179. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +5 -2
  180. data/lib/datadog/tracing/contrib/rails/ext.rb +1 -0
  181. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  182. data/lib/datadog/tracing/contrib/rails/patcher.rb +0 -1
  183. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  184. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
  185. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  186. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
  187. data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
  188. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
  189. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  190. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
  191. data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
  192. data/lib/datadog/tracing/contrib/status_range_matcher.rb +4 -0
  193. data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
  194. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +5 -1
  195. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +1 -0
  196. data/lib/datadog/tracing/distributed/datadog.rb +4 -2
  197. data/lib/datadog/tracing/event.rb +1 -1
  198. data/lib/datadog/tracing/metadata/ext.rb +4 -0
  199. data/lib/datadog/tracing/remote.rb +1 -1
  200. data/lib/datadog/tracing/sampling/ext.rb +2 -0
  201. data/lib/datadog/tracing/sampling/priority_sampler.rb +13 -0
  202. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  203. data/lib/datadog/tracing/sampling/rule_sampler.rb +54 -25
  204. data/lib/datadog/tracing/sampling/span/rule_parser.rb +1 -1
  205. data/lib/datadog/tracing/span_operation.rb +1 -1
  206. data/lib/datadog/tracing/sync_writer.rb +0 -1
  207. data/lib/datadog/tracing/trace_operation.rb +50 -6
  208. data/lib/datadog/tracing/tracer.rb +25 -0
  209. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  210. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  211. data/lib/datadog/tracing/writer.rb +0 -1
  212. data/lib/datadog/version.rb +1 -1
  213. metadata +15 -8
  214. data/lib/datadog/tracing/workers/trace_writer.rb +0 -204
@@ -52,9 +52,10 @@ module Datadog
52
52
  def self.input(
53
53
  agent_settings:,
54
54
  logger:,
55
- headers: nil
55
+ headers: nil,
56
+ telemetry: nil
56
57
  )
57
- Core::Transport::HTTP.build(
58
+ builder = Core::Transport::HTTP.build(
58
59
  logger: logger,
59
60
  agent_settings: agent_settings,
60
61
  headers: headers,
@@ -64,7 +65,15 @@ module Datadog
64
65
 
65
66
  # Call block to apply any customization, if provided
66
67
  yield(transport) if block_given?
67
- end.to_transport(DI::Transport::Input::Transport)
68
+ end
69
+
70
+ # Create transport with telemetry
71
+ DI::Transport::Input::Transport.new(
72
+ builder.to_api_instances,
73
+ builder.default_api,
74
+ logger: logger,
75
+ telemetry: telemetry
76
+ )
68
77
  end
69
78
  end
70
79
  end
@@ -24,6 +24,13 @@ module Datadog
24
24
  end
25
25
 
26
26
  class Transport < Core::Transport::Transport
27
+ attr_reader :telemetry
28
+
29
+ def initialize(apis, default_api, logger:, telemetry: nil)
30
+ super(apis, default_api, logger: logger)
31
+ @telemetry = telemetry
32
+ end
33
+
27
34
  # The limit on an individual snapshot payload, aka "log line",
28
35
  # is 1 MB.
29
36
  #
@@ -43,22 +50,52 @@ module Datadog
43
50
  # max chunk size, it will still get sent out.
44
51
  DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024
45
52
 
46
- def send_input(payload, tags)
47
- # Tags are the same for all chunks, serialize them one time.
53
+ # Sends snapshot payloads to the agent.
54
+ #
55
+ # Each snapshot is serialized individually. If serialization fails
56
+ # for a snapshot (e.g., due to binary data from custom serializers),
57
+ # the on_serialization_error callback is invoked with the probe ID
58
+ # and exception, allowing the caller to disable the affected probe.
59
+ # Successfully serialized snapshots are still sent.
60
+ #
61
+ # Large snapshots (> 1MB) are dropped. Batches are split into chunks
62
+ # of ~2MB each to avoid large network requests.
63
+ #
64
+ # @param payload [Array<Hash>] Array of snapshot payloads
65
+ # @param tags [Hash] Tags to send with the snapshots
66
+ # @param on_serialization_error [Proc] Called with (probe_id, exception)
67
+ # when a snapshot fails to serialize.
68
+ def send_input(payload, tags, on_serialization_error:)
48
69
  serialized_tags = Core::TagBuilder.serialize_tags(tags)
49
70
 
50
- encoded_snapshots = Core::Utils::Array.filter_map(payload) do |snapshot|
71
+ # Serialize each snapshot individually to isolate failures
72
+ encoded_snapshots = []
73
+ payload.each do |snapshot|
51
74
  encoded = encoder.encode(snapshot)
52
75
  if encoded.length > MAX_SERIALIZED_SNAPSHOT_SIZE
53
- # Drop the snapshot.
54
- # TODO report via telemetry metric?
55
76
  logger.debug { "di: dropping too big snapshot" }
56
- nil
57
- else
58
- encoded
77
+ next
78
+ end
79
+ encoded_snapshots << encoded
80
+ rescue => exc
81
+ # Serialization failed for this snapshot - report via callback
82
+ # This catches JSON::GeneratorError, Encoding errors, TypeError, etc.
83
+ probe_id = snapshot.dig(:debugger, :snapshot, :probe, :id)
84
+ logger.debug { "di: JSON encoding failed for snapshot (probe #{probe_id}): #{exc.class}: #{exc}" }
85
+ telemetry&.report(exc, description: "JSON encoding failed for snapshot")
86
+
87
+ if probe_id
88
+ begin
89
+ on_serialization_error.call(probe_id, exc)
90
+ rescue => callback_exc
91
+ logger.debug { "di: error in serialization error callback for probe #{probe_id}: #{callback_exc.class}: #{callback_exc}" }
92
+ telemetry&.report(callback_exc, description: "Error in serialization error callback")
93
+ end
59
94
  end
60
95
  end
61
96
 
97
+ return payload if encoded_snapshots.empty?
98
+
62
99
  Datadog::Core::Chunker.chunk_by_size(
63
100
  encoded_snapshots, DEFAULT_CHUNK_SIZE,
64
101
  ).each do |chunk|
@@ -74,6 +111,7 @@ module Datadog
74
111
  send_input_chunk(chunked_payload, serialized_tags)
75
112
  rescue => exc
76
113
  logger.debug { "di: failed to send snapshot chunk: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
114
+ telemetry&.report(exc, description: "Error sending snapshot chunk")
77
115
  end
78
116
  end
79
117
 
data/lib/datadog/di.rb CHANGED
@@ -11,11 +11,92 @@ module Datadog
11
11
  module DI
12
12
  INSTRUMENTED_COUNTERS_LOCK = Mutex.new
13
13
 
14
+ # Captured at load time from Exception itself (not a subclass).
15
+ # Used to bypass subclass overrides of backtrace_locations.
16
+ #
17
+ # This does NOT protect against monkeypatching Exception#backtrace_locations
18
+ # before dd-trace-rb loads — in that case we'd capture the monkeypatch.
19
+ # The practical threat model is customer subclasses overriding the method:
20
+ #
21
+ # class MyError < StandardError
22
+ # def backtrace_locations; []; end
23
+ # end
24
+ #
25
+ # The UnboundMethod bypasses subclass overrides: bind(exception).call
26
+ # always dispatches to the original Exception implementation.
27
+ #
28
+ # Note: if the subclass overrides #backtrace (not #backtrace_locations),
29
+ # MRI's setup_exception skips storing the VM backtrace entirely — both
30
+ # @bt and @bt_locations stay nil. In that case this UnboundMethod also
31
+ # returns nil. See EXCEPTION_BACKTRACE comment for details.
32
+ EXCEPTION_BACKTRACE_LOCATIONS = Exception.instance_method(:backtrace_locations)
33
+
34
+ # Same UnboundMethod trick for Exception#backtrace (Array<String>).
35
+ # Used as a fallback when backtrace_locations returns nil — which happens
36
+ # when someone calls Exception#set_backtrace with an Array<String>.
37
+ #
38
+ # set_backtrace accepts Array<String> or nil. When called with strings,
39
+ # it replaces the VM-level backtrace: backtrace returns the new strings,
40
+ # but backtrace_locations returns nil because the VM cannot reconstruct
41
+ # Location objects from formatted strings. This occurs in exception
42
+ # wrapping patterns where a library catches an exception, creates a new
43
+ # one, and copies the original's string backtrace onto it via
44
+ # set_backtrace before re-raising.
45
+ #
46
+ # Ruby 3.4+ also allows set_backtrace(Array<Location>), which preserves
47
+ # backtrace_locations — but older Rubies and most existing code use
48
+ # the string form.
49
+ #
50
+ # Like EXCEPTION_BACKTRACE_LOCATIONS, this UnboundMethod bypasses
51
+ # subclass overrides of #backtrace: bind(exception).call dispatches
52
+ # to Exception#backtrace regardless of what the subclass defines.
53
+ #
54
+ # However, when a subclass overrides #backtrace, MRI's setup_exception
55
+ # (eval.c) calls the override via rb_get_backtrace during raise. If it
56
+ # gets a non-nil result, it skips storing the VM backtrace in @bt and
57
+ # @bt_locations entirely. So the UnboundMethod bypasses the override
58
+ # at dispatch but reads nil from @bt because the data was never stored.
59
+ #
60
+ # This constant is used as a fallback when backtrace_locations returns
61
+ # nil. In the common set_backtrace-with-strings case, no subclass
62
+ # override is involved and the fallback works. The only unrecoverable
63
+ # case: a subclass overrides #backtrace, the exception is raised
64
+ # normally, and set_backtrace is never called. Both @bt and
65
+ # @bt_locations are nil — DI reports an empty stacktrace (type and
66
+ # message are still reported).
67
+ EXCEPTION_BACKTRACE = Exception.instance_method(:backtrace)
68
+
14
69
  class << self
15
70
  def enabled?
16
71
  Datadog.configuration.dynamic_instrumentation.enabled
17
72
  end
18
73
 
74
+ # Returns iseqs that correspond to loaded files (filtering out eval'd code).
75
+ #
76
+ # There are several types of iseqs returned by +all_iseqs+:
77
+ #
78
+ # 1. Eval'd code — these have a nil +absolute_path+ and are filtered out here.
79
+ # 2. Whole-file iseqs — have +absolute_path+ set and +first_lineno+ of 0.
80
+ # Only available for a subset of loaded files (the full-file iseq may be
81
+ # garbage collected after loading completes). Easiest to work with since
82
+ # we just match the file path to the probe specification.
83
+ # 3. Per-method iseqs — have +absolute_path+ set and +first_lineno+ > 0.
84
+ # Often the only iseqs available for third-party code. Require identifying
85
+ # the correct iseq containing the target line, which may involve examining
86
+ # the iseq's +trace_points+ since +define_method+ can create nested,
87
+ # non-contiguous line ranges.
88
+ #
89
+ # Note: the same line of code can appear in multiple iseqs (e.g. when
90
+ # +define_method+ is used inside a method). DI treats this as an error
91
+ # since a probe must resolve to exactly one code location.
92
+ #
93
+ # @return [Array<RubyVM::InstructionSequence>] iseqs with non-nil +absolute_path+
94
+ def file_iseqs
95
+ all_iseqs.select do |iseq|
96
+ iseq.absolute_path
97
+ end
98
+ end
99
+
19
100
  # This method is called from DI Remote handler to issue DI operations
20
101
  # to the probe manager (add or remove probes).
21
102
  #
@@ -30,7 +30,7 @@ module Datadog
30
30
  Process.setrlimit(:CORE, maximum_size)
31
31
  rescue => e
32
32
  Kernel.warn(
33
- "[datadog] Failed to enable core dumps. Cause: #{e.class.name} #{e.message} " \
33
+ "[datadog] Failed to enable core dumps. Cause: #{e.class}: #{e} " \
34
34
  "Location: #{Array(e.backtrace).first}"
35
35
  )
36
36
  return
@@ -12,6 +12,8 @@ module Datadog
12
12
 
13
13
  def self.add_settings!(base)
14
14
  base.class_eval do
15
+ # Steep does not update `self` for this `class_eval` block.
16
+ # @type self: Datadog::Core::Configuration::Base::_DslContext
15
17
  settings :open_feature do
16
18
  option :enabled do |o|
17
19
  o.type :bool
@@ -60,7 +60,7 @@ module Datadog
60
60
  rescue => e
61
61
  message = 'OpenFeature: Failed to reconfigure, reverting to the previous configuration'
62
62
 
63
- @logger.error("#{message}, #{e.class}: #{e.message}")
63
+ @logger.error("#{message}, #{e.class}: #{e}")
64
64
  @telemetry.report(e, description: "#{message} (#{e.class})")
65
65
 
66
66
  raise ReconfigurationError, e.message
@@ -29,7 +29,7 @@ module Datadog
29
29
  event = Event.build(result, flag_key: flag_key, context: context)
30
30
  @worker.enqueue(event)
31
31
  rescue => e
32
- @logger.debug { "OpenFeature: Failed to report resolution details: #{e.class}: #{e.message}" }
32
+ @logger.debug { "OpenFeature: Failed to report resolution details: #{e.class}: #{e}" }
33
33
  @telemetry.report(e, description: 'OpenFeature: Failed to report resolution details')
34
34
 
35
35
  false
@@ -105,7 +105,7 @@ module Datadog
105
105
 
106
106
  response
107
107
  rescue => e
108
- @logger.debug { "OpenFeature: Failed to flush resolution details events: #{e.class}: #{e.message}" }
108
+ @logger.debug { "OpenFeature: Failed to flush resolution details events: #{e.class}: #{e}" }
109
109
  @telemetry.report(e, description: 'OpenFeature: Failed to flush resolution details events')
110
110
 
111
111
  nil
@@ -41,7 +41,7 @@ module Datadog
41
41
  engine.reconfigure!(read_content(content))
42
42
  content.applied
43
43
  rescue EvaluationEngine::ReconfigurationError => e
44
- content.errored("Error applying OpenFeature configuration: #{e.message}")
44
+ content.errored("Error applying OpenFeature configuration: #{e.class}: #{e}")
45
45
  end
46
46
  when :delete
47
47
  # NOTE: For now, we treat deletion as clearing the configuration
@@ -55,7 +55,7 @@ module Datadog
55
55
  @api.call(env)
56
56
  end
57
57
  rescue => e
58
- message = "Internal error during request. Cause: #{e.class.name} #{e.message} " \
58
+ message = "Internal error during request. Cause: #{e.class}: #{e} " \
59
59
  "Location: #{Array(e.backtrace).first}"
60
60
  @logger.debug(message)
61
61
 
@@ -76,6 +76,7 @@ module Datadog
76
76
  end
77
77
 
78
78
  option :headers do |o|
79
+ o.skip_telemetry true
79
80
  o.type :hash
80
81
  o.env 'OTEL_EXPORTER_OTLP_HEADERS'
81
82
  o.default { {} }
@@ -131,6 +132,7 @@ module Datadog
131
132
  end
132
133
 
133
134
  option :headers do |o|
135
+ o.skip_telemetry true
134
136
  o.type :hash, nilable: true
135
137
  o.env 'OTEL_EXPORTER_OTLP_METRICS_HEADERS'
136
138
  o.default nil
@@ -135,7 +135,7 @@ module Datadog
135
135
  rescue Exception => e # rubocop:disable Lint/RescueException
136
136
  Datadog.logger.debug(
137
137
  "CodeProvenance#bundler_bin_path failed. " \
138
- "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
138
+ "Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
139
139
  )
140
140
  nil
141
141
  end
@@ -161,8 +161,7 @@ module Datadog
161
161
  end
162
162
 
163
163
  def to_json(arg = nil)
164
- # Steep: https://github.com/ruby/rbs/pull/2691 (remove after RBS 4.0 release)
165
- {kind: @kind, name: @name, version: @version, paths: @paths}.to_json(arg) # steep:ignore ArgumentTypeMismatch
164
+ {kind: @kind, name: @name, version: @version, paths: @paths}.to_json(arg)
166
165
  end
167
166
 
168
167
  def path
@@ -24,6 +24,7 @@ module Datadog
24
24
  allocation_counting_enabled:,
25
25
  gvl_profiling_enabled:,
26
26
  sighandler_sampling_enabled:,
27
+ cpu_sampling_interval_ms:,
27
28
  # **NOTE**: This should only be used for testing; disabling the dynamic sampling rate will increase the
28
29
  # profiler overhead!
29
30
  dynamic_sampling_rate_enabled: true,
@@ -39,6 +40,10 @@ module Datadog
39
40
  )
40
41
  end
41
42
 
43
+ if cpu_sampling_interval_ms < 1
44
+ raise ArgumentError, "cpu_sampling_interval_ms must be a positive integer, got #{cpu_sampling_interval_ms}"
45
+ end
46
+
42
47
  self.class._native_initialize(
43
48
  self_instance: self,
44
49
  thread_context_collector: thread_context_collector,
@@ -52,6 +57,7 @@ module Datadog
52
57
  gvl_profiling_enabled: gvl_profiling_enabled,
53
58
  sighandler_sampling_enabled: sighandler_sampling_enabled,
54
59
  skip_idle_samples_for_testing: skip_idle_samples_for_testing,
60
+ cpu_sampling_interval_ms: cpu_sampling_interval_ms,
55
61
  )
56
62
  @worker_thread = nil
57
63
  @failure_exception = nil
@@ -75,12 +81,19 @@ module Datadog
75
81
  self.class._native_sampling_loop(self)
76
82
 
77
83
  Datadog.logger.debug("CpuAndWallTimeWorker thread stopping cleanly")
84
+ rescue Profiling::ExistingSignalHandler => e
85
+ @failure_exception = e
86
+ Datadog.logger.warn(
87
+ "Profiling was not started as another profiler or gem is already using the SIGPROF signal. " \
88
+ "Please disable the other profiler to use Datadog profiling."
89
+ )
90
+ on_failure_proc&.call(log_failure: false)
78
91
  rescue Exception => e # rubocop:disable Lint/RescueException
79
92
  @failure_exception = e
80
93
  operation_name = self.class._native_failure_exception_during_operation(self).inspect
81
94
  Datadog.logger.warn(
82
95
  "CpuAndWallTimeWorker thread error. " \
83
- "Operation: #{operation_name} Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
96
+ "Operation: #{operation_name} Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
84
97
  )
85
98
  on_failure_proc&.call
86
99
  Datadog::Core::Telemetry::Logger.report(e, description: "CpuAndWallTimeWorker thread error: #{operation_name}")
@@ -39,7 +39,7 @@ module Datadog
39
39
  @failure_exception = e
40
40
  Datadog.logger.warn(
41
41
  "IdleSamplingHelper thread error. " \
42
- "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
42
+ "Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
43
43
  )
44
44
  Datadog::Core::Telemetry::Logger.report(e, description: "IdleSamplingHelper thread error")
45
45
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../core/telemetry/logger"
4
+
3
5
  module Datadog
4
6
  module Profiling
5
7
  # Responsible for wiring up the Profiler for execution
@@ -45,6 +47,8 @@ module Datadog
45
47
 
46
48
  overhead_target_percentage = valid_overhead_target(settings.profiling.advanced.overhead_target_percentage, logger)
47
49
  upload_period_seconds = [60, settings.profiling.advanced.upload_period_seconds].max
50
+ cpu_sampling_interval_ms =
51
+ valid_cpu_sampling_interval(settings.profiling.advanced.experimental_cpu_sampling_interval_ms, logger)
48
52
 
49
53
  recorder = Datadog::Profiling::StackRecorder.new(
50
54
  cpu_time_enabled: RUBY_PLATFORM.include?("linux"), # Only supported on Linux currently
@@ -65,6 +69,7 @@ module Datadog
65
69
  allocation_counting_enabled: settings.profiling.advanced.allocation_counting_enabled,
66
70
  gvl_profiling_enabled: enable_gvl_profiling?(settings, logger),
67
71
  sighandler_sampling_enabled: settings.profiling.advanced.sighandler_sampling_enabled,
72
+ cpu_sampling_interval_ms: cpu_sampling_interval_ms,
68
73
  )
69
74
 
70
75
  internal_metadata = {
@@ -87,6 +92,14 @@ module Datadog
87
92
  end
88
93
 
89
94
  [profiler, {profiling_enabled: true}]
95
+ rescue Exception => e # rubocop:disable Lint/RescueException
96
+ logger.warn do
97
+ "Failed to initialize profiling: #{e.class}: #{e} " \
98
+ "Location: #{Array(e.backtrace).first}"
99
+ end
100
+ Datadog::Core::Telemetry::Logger.report(e, description: "Failed to initialize profiling")
101
+
102
+ [nil, {profiling_enabled: false}]
90
103
  end
91
104
 
92
105
  private_class_method def self.build_thread_context_collector(settings, recorder, optional_tracer, timeline_enabled)
@@ -123,6 +136,7 @@ module Datadog
123
136
  site: settings.site,
124
137
  api_key: settings.api_key,
125
138
  upload_timeout_seconds: settings.profiling.upload.timeout_seconds,
139
+ use_system_dns: settings.profiling.advanced.experimental_use_system_dns,
126
140
  )
127
141
  end
128
142
 
@@ -364,7 +378,7 @@ module Datadog
364
378
  rescue StandardError, LoadError => e
365
379
  logger.warn(
366
380
  "Failed to probe `mysql2` gem information. " \
367
- "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
381
+ "Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
368
382
  )
369
383
 
370
384
  true
@@ -397,6 +411,22 @@ module Datadog
397
411
  end
398
412
  end
399
413
 
414
+ private_class_method def self.valid_cpu_sampling_interval(cpu_sampling_interval_ms, logger)
415
+ if cpu_sampling_interval_ms > 10
416
+ logger.warn(
417
+ "Profiling cpu_sampling_interval_ms is set to #{cpu_sampling_interval_ms}ms, but values above 10ms are " \
418
+ "not supported. Using 10ms instead. To reduce profiler overhead, consider adjusting the " \
419
+ "overhead_target_percentage setting."
420
+ )
421
+ 10
422
+ elsif cpu_sampling_interval_ms < 10
423
+ logger.debug { "Profiling cpu_sampling_interval_ms set to #{cpu_sampling_interval_ms}ms" }
424
+ cpu_sampling_interval_ms
425
+ else
426
+ cpu_sampling_interval_ms
427
+ end
428
+ end
429
+
400
430
  # To add just a bit more complexity to our detection code, in https://github.com/DataDog/dd-trace-rb/issues/3334
401
431
  # a user reported that our code was incorrectly flagging the mariadb variant of libmysqlclient as being
402
432
  # incompatible. In fact we have no reports of the mariadb variant needing the "no signals" workaround,
@@ -10,17 +10,17 @@ module Datadog
10
10
  class HttpTransport
11
11
  attr_reader :exporter_configuration
12
12
 
13
- def initialize(agent_settings:, site:, api_key:, upload_timeout_seconds:)
14
- @upload_timeout_milliseconds = (upload_timeout_seconds * 1_000).to_i
13
+ def initialize(agent_settings:, site:, api_key:, upload_timeout_seconds:, use_system_dns:)
14
+ timeout_milliseconds = (upload_timeout_seconds * 1_000).to_i
15
15
 
16
16
  # Steep: multiple issues here
17
17
  # first https://github.com/soutaro/steep/issues/363
18
18
  # then https://github.com/soutaro/steep/issues/1603 (remove the .freeze to see it)
19
19
  @exporter_configuration = # steep:ignore IncompatibleAssignment
20
20
  if agentless?(site, api_key)
21
- [:agentless, site, api_key].freeze
21
+ [:agentless, timeout_milliseconds, use_system_dns, site, api_key].freeze
22
22
  else
23
- [:agent, agent_settings.url].freeze
23
+ [:agent, timeout_milliseconds, use_system_dns, agent_settings.url].freeze
24
24
  end
25
25
 
26
26
  status, result = self.class._native_validate_exporter(exporter_configuration)
@@ -31,7 +31,6 @@ module Datadog
31
31
  def export(flush)
32
32
  status, result = self.class._native_do_export(
33
33
  exporter_configuration,
34
- @upload_timeout_milliseconds,
35
34
  flush
36
35
  )
37
36
 
@@ -63,7 +62,7 @@ module Datadog
63
62
  end
64
63
 
65
64
  def config_without_api_key
66
- "#{exporter_configuration[0]}: #{exporter_configuration[1]}"
65
+ "#{exporter_configuration[0]}: #{exporter_configuration[3]}"
67
66
  end
68
67
  end
69
68
  end
@@ -4,6 +4,6 @@ begin
4
4
  require "datadog_profiling_native_extension.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
5
5
  rescue LoadError => e
6
6
  raise LoadError,
7
- "Failed to load the profiling loader extension. To fix this, please remove and then reinstall datadog " \
7
+ "Failed to load the profiling native extension. To fix this, please remove and then reinstall datadog " \
8
8
  "(Details: #{e.message})"
9
9
  end
@@ -17,17 +17,18 @@ module Datadog
17
17
  @scheduler = scheduler
18
18
  end
19
19
 
20
- def enabled?
21
- scheduler.running?
22
- end
23
-
24
20
  def start
25
21
  after_fork! do
26
22
  worker.reset_after_fork
27
23
  scheduler.reset_after_fork
28
24
  end
29
25
 
30
- worker.start(on_failure_proc: proc { component_failed(:worker) })
26
+ worker.start(
27
+ on_failure_proc: ->(log_failure: true) do
28
+ # @type var log_failure: bool
29
+ component_failed(:worker, log_failure: log_failure)
30
+ end
31
+ )
31
32
  scheduler.start(on_failure_proc: proc { component_failed(:scheduler) })
32
33
  end
33
34
 
@@ -50,13 +51,15 @@ module Datadog
50
51
  scheduler.stop(true)
51
52
  end
52
53
 
53
- def component_failed(failed_component)
54
- Datadog.logger.warn(
55
- "Detected issue with profiler (#{failed_component} component), stopping profiling. " \
56
- "See previous log messages for details."
57
- )
58
- Datadog::Core::Telemetry::Logger
59
- .error("Detected issue with profiler (#{failed_component} component), stopping profiling")
54
+ def component_failed(failed_component, log_failure: true)
55
+ if log_failure
56
+ Datadog.logger.warn(
57
+ "Detected issue with profiler (#{failed_component} component), stopping profiling. " \
58
+ "See previous log messages for details."
59
+ )
60
+ Datadog::Core::Telemetry::Logger
61
+ .error("Detected issue with profiler (#{failed_component} component), stopping profiling")
62
+ end
60
63
 
61
64
  if failed_component == :worker
62
65
  scheduler.disable_reporting
@@ -65,7 +65,7 @@ module Datadog
65
65
  rescue Exception => e # rubocop:disable Lint/RescueException
66
66
  Datadog.logger.warn(
67
67
  "Profiling::Scheduler thread error. " \
68
- "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
68
+ "Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
69
69
  )
70
70
  on_failure_proc&.call
71
71
  Datadog::Core::Telemetry::Logger.report(e, description: "Profiling::Scheduler thread error")
@@ -136,7 +136,7 @@ module Datadog
136
136
  transport.export(flush)
137
137
  rescue => e
138
138
  Datadog.logger.warn(
139
- "Unable to report profile. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
139
+ "Unable to report profile. Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
140
140
  )
141
141
  Datadog::Core::Telemetry::Logger.report(e, description: "Unable to report profile")
142
142
  end
@@ -38,10 +38,10 @@ module Datadog
38
38
  def exec_with_error_handling(args)
39
39
  Kernel.exec(*args)
40
40
  rescue Errno::ENOENT => e
41
- Kernel.warn "ddprofrb exec failed: #{e.class.name} #{e.message} (command was '#{args.join(" ")}')"
41
+ Kernel.warn "ddprofrb exec failed: #{e.class}: #{e} (command was '#{args.join(" ")}')"
42
42
  Kernel.exit 127
43
43
  rescue Errno::EACCES, Errno::ENOEXEC => e
44
- Kernel.warn "ddprofrb exec failed: #{e.class.name} #{e.message} (command was '#{args.join(" ")}')"
44
+ Kernel.warn "ddprofrb exec failed: #{e.class}: #{e} (command was '#{args.join(" ")}')"
45
45
  Kernel.exit 126
46
46
  end
47
47
  end
@@ -16,7 +16,7 @@ module Datadog
16
16
  setup_at_fork_hooks
17
17
  rescue StandardError, ScriptError => e
18
18
  Datadog.logger.warn do
19
- "Profiler extensions unavailable. Cause: #{e.class.name} #{e.message} " \
19
+ "Profiler extensions unavailable. Cause: #{e.class}: #{e} " \
20
20
  "Location: #{Array(e.backtrace).first}"
21
21
  end
22
22
  Datadog::Core::Telemetry::Logger.report(e, description: "Profiler extensions unavailable")
@@ -31,7 +31,7 @@ module Datadog
31
31
  Profiling.start_if_enabled
32
32
  rescue => e
33
33
  Datadog.logger.warn do
34
- "Error during post-fork hooks. Cause: #{e.class.name} #{e.message} " \
34
+ "Error during post-fork hooks. Cause: #{e.class}: #{e} " \
35
35
  "Location: #{Array(e.backtrace).first}"
36
36
  end
37
37
  Datadog::Core::Telemetry::Logger.report(e, description: "Error during post-fork hooks")
@@ -61,8 +61,7 @@ module Datadog
61
61
  end
62
62
 
63
63
  def self.enabled?
64
- profiler = Datadog.send(:components).profiler
65
- !!profiler&.enabled?
64
+ !!Datadog.send(:components).profiler
66
65
  end
67
66
 
68
67
  def self.wait_until_running(timeout_seconds: 5)
@@ -17,5 +17,5 @@ begin
17
17
  require_relative 'auto_instrument'
18
18
  Datadog::SingleStepInstrument.const_set(:LOADED, true)
19
19
  rescue StandardError, LoadError => e
20
- warn "Single step instrumentation failed: #{e.class}:#{e.message}\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
20
+ warn "Single step instrumentation failed: #{e.class}: #{e}\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
21
21
  end
@@ -56,7 +56,7 @@ module Datadog
56
56
  @buffer_spans += trace.length
57
57
  rescue => e
58
58
  Datadog.logger.debug(
59
- "Failed to measure queue accept. Cause: #{e.class.name} #{e.message} Source: #{Array(e.backtrace).first}"
59
+ "Failed to measure queue accept. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
60
60
  )
61
61
  end
62
62
 
@@ -66,7 +66,7 @@ module Datadog
66
66
  @buffer_spans -= trace.length
67
67
  rescue => e
68
68
  Datadog.logger.debug(
69
- "Failed to measure queue drop. Cause: #{e.class.name} #{e.message} Source: #{Array(e.backtrace).first}"
69
+ "Failed to measure queue drop. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
70
70
  )
71
71
  end
72
72
 
@@ -91,7 +91,7 @@ module Datadog
91
91
  @buffer_spans = 0
92
92
  rescue => e
93
93
  Datadog.logger.debug(
94
- "Failed to measure queue. Cause: #{e.class.name} #{e.message} Source: #{Array(e.backtrace).first}"
94
+ "Failed to measure queue. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
95
95
  )
96
96
  end
97
97
  end