datadog 2.31.0 → 2.32.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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +17 -7
  3. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +11 -4
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +6 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +5 -4
  6. data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
  7. data/ext/libdatadog_api/di.c +48 -0
  8. data/ext/libdatadog_api/extconf.rb +7 -4
  9. data/ext/libdatadog_extconf_helpers.rb +37 -0
  10. data/lib/datadog/ai_guard/configuration.rb +105 -2
  11. data/lib/datadog/ai_guard/evaluation.rb +1 -0
  12. data/lib/datadog/ai_guard/ext.rb +1 -0
  13. data/lib/datadog/appsec/autoload.rb +1 -1
  14. data/lib/datadog/appsec/component.rb +1 -1
  15. data/lib/datadog/appsec/configuration.rb +414 -1
  16. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +2 -1
  17. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  18. data/lib/datadog/appsec/contrib/rails/patcher.rb +2 -2
  19. data/lib/datadog/appsec/metrics/telemetry.rb +13 -1
  20. data/lib/datadog/appsec/security_engine/runner.rb +1 -1
  21. data/lib/datadog/appsec/trace_keeper.rb +18 -6
  22. data/lib/datadog/appsec/utils/http/url_encoded.rb +2 -2
  23. data/lib/datadog/core/configuration/components.rb +1 -1
  24. data/lib/datadog/core/configuration/settings.rb +3 -0
  25. data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
  26. data/lib/datadog/core/configuration.rb +1 -1
  27. data/lib/datadog/core/contrib/rails/utils.rb +1 -1
  28. data/lib/datadog/core/crashtracking/component.rb +3 -3
  29. data/lib/datadog/core/diagnostics/environment_logger.rb +3 -1
  30. data/lib/datadog/core/environment/container.rb +2 -2
  31. data/lib/datadog/core/feature_flags.rb +1 -1
  32. data/lib/datadog/core/metrics/client.rb +5 -5
  33. data/lib/datadog/core/remote/client.rb +1 -1
  34. data/lib/datadog/core/remote/component.rb +2 -2
  35. data/lib/datadog/core/runtime/metrics.rb +1 -1
  36. data/lib/datadog/core/telemetry/emitter.rb +1 -1
  37. data/lib/datadog/core/telemetry/event/app_started.rb +2 -2
  38. data/lib/datadog/core/transport/http.rb +2 -0
  39. data/lib/datadog/core/utils.rb +1 -1
  40. data/lib/datadog/core/workers/async.rb +1 -1
  41. data/lib/datadog/core.rb +1 -1
  42. data/lib/datadog/data_streams/configuration.rb +40 -1
  43. data/lib/datadog/data_streams/pathway_context.rb +1 -1
  44. data/lib/datadog/data_streams/processor.rb +1 -1
  45. data/lib/datadog/data_streams.rb +1 -1
  46. data/lib/datadog/di/base.rb +8 -5
  47. data/lib/datadog/di/code_tracker.rb +179 -1
  48. data/lib/datadog/di/component.rb +1 -1
  49. data/lib/datadog/di/configuration.rb +235 -2
  50. data/lib/datadog/di/instrumenter.rb +46 -26
  51. data/lib/datadog/di/probe_builder.rb +1 -1
  52. data/lib/datadog/di/probe_file_loader.rb +2 -2
  53. data/lib/datadog/di/probe_manager.rb +6 -6
  54. data/lib/datadog/di/probe_notification_builder.rb +1 -1
  55. data/lib/datadog/di/probe_notifier_worker.rb +2 -2
  56. data/lib/datadog/di/remote.rb +6 -6
  57. data/lib/datadog/di/serializer.rb +1 -1
  58. data/lib/datadog/di/transport/input.rb +3 -3
  59. data/lib/datadog/error_tracking/configuration.rb +55 -2
  60. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  61. data/lib/datadog/open_feature/component.rb +18 -1
  62. data/lib/datadog/open_feature/evaluation_engine.rb +3 -3
  63. data/lib/datadog/open_feature/exposures/reporter.rb +1 -1
  64. data/lib/datadog/open_feature/exposures/worker.rb +1 -1
  65. data/lib/datadog/open_feature/hooks/flag_eval_hook.rb +49 -0
  66. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
  67. data/lib/datadog/open_feature/provider.rb +19 -1
  68. data/lib/datadog/open_feature/remote.rb +1 -1
  69. data/lib/datadog/open_feature/transport.rb +1 -1
  70. data/lib/datadog/opentelemetry/metrics.rb +3 -3
  71. data/lib/datadog/opentelemetry/sdk/configurator.rb +1 -1
  72. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +1 -1
  73. data/lib/datadog/profiling/collectors/code_provenance.rb +35 -9
  74. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +31 -2
  75. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +8 -2
  76. data/lib/datadog/profiling/collectors/info.rb +16 -3
  77. data/lib/datadog/profiling/component.rb +3 -5
  78. data/lib/datadog/profiling/exporter.rb +37 -12
  79. data/lib/datadog/profiling/ext.rb +0 -2
  80. data/lib/datadog/profiling/flush.rb +21 -12
  81. data/lib/datadog/profiling/http_transport.rb +12 -1
  82. data/lib/datadog/profiling/load_native_extension.rb +1 -1
  83. data/lib/datadog/profiling/profiler.rb +13 -1
  84. data/lib/datadog/profiling/scheduler.rb +2 -2
  85. data/lib/datadog/profiling/tasks/exec.rb +8 -3
  86. data/lib/datadog/profiling/tasks/help.rb +1 -0
  87. data/lib/datadog/profiling/tasks/setup.rb +2 -2
  88. data/lib/datadog/single_step_instrument.rb +1 -1
  89. data/lib/datadog/symbol_database/configuration.rb +65 -0
  90. data/lib/datadog/symbol_database/extractor.rb +915 -0
  91. data/lib/datadog/symbol_database/file_hash.rb +46 -0
  92. data/lib/datadog/symbol_database/logger.rb +43 -0
  93. data/lib/datadog/symbol_database/scope.rb +98 -0
  94. data/lib/datadog/symbol_database/service_version.rb +57 -0
  95. data/lib/datadog/symbol_database/symbol.rb +66 -0
  96. data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
  97. data/lib/datadog/symbol_database/transport/http.rb +45 -0
  98. data/lib/datadog/symbol_database/transport.rb +54 -0
  99. data/lib/datadog/symbol_database/uploader.rb +166 -0
  100. data/lib/datadog/symbol_database.rb +49 -0
  101. data/lib/datadog/tracing/buffer.rb +3 -3
  102. data/lib/datadog/tracing/configuration/settings.rb +1 -1
  103. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -3
  104. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  105. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  106. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  107. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  108. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  109. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  110. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  111. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  112. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  113. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
  114. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  115. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  116. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  117. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +1 -1
  118. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
  119. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
  120. data/lib/datadog/tracing/contrib/component.rb +1 -1
  121. data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -4
  122. data/lib/datadog/tracing/contrib/dalli/quantize.rb +1 -1
  123. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +1 -1
  124. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
  125. data/lib/datadog/tracing/contrib/extensions.rb +9 -0
  126. data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -2
  127. data/lib/datadog/tracing/contrib/grape/endpoint.rb +5 -5
  128. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +2 -2
  129. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +2 -2
  130. data/lib/datadog/tracing/contrib/http/instrumentation.rb +2 -2
  131. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -2
  132. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +2 -2
  133. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +2 -2
  134. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +2 -2
  135. data/lib/datadog/tracing/contrib/karafka/patcher.rb +1 -1
  136. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +3 -3
  137. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +1 -1
  138. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +3 -3
  139. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  140. data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -1
  141. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  142. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  143. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
  144. data/lib/datadog/tracing/contrib/redis/quantize.rb +1 -1
  145. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  146. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
  147. data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
  148. data/lib/datadog/tracing/contrib.rb +8 -0
  149. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
  150. data/lib/datadog/tracing/distributed/baggage.rb +59 -5
  151. data/lib/datadog/tracing/distributed/datadog.rb +11 -11
  152. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +1 -1
  153. data/lib/datadog/tracing/distributed/propagation.rb +2 -2
  154. data/lib/datadog/tracing/distributed/trace_context.rb +74 -32
  155. data/lib/datadog/tracing/event.rb +1 -1
  156. data/lib/datadog/tracing/metadata/tagging.rb +2 -2
  157. data/lib/datadog/tracing/pipeline.rb +1 -1
  158. data/lib/datadog/tracing/remote.rb +1 -1
  159. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  160. data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -2
  161. data/lib/datadog/tracing/sampling/span/rule_parser.rb +2 -2
  162. data/lib/datadog/tracing/span_operation.rb +3 -3
  163. data/lib/datadog/tracing/trace_operation.rb +4 -4
  164. data/lib/datadog/tracing/tracer.rb +5 -5
  165. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  166. data/lib/datadog/tracing/workers.rb +2 -1
  167. data/lib/datadog/version.rb +1 -1
  168. metadata +18 -9
  169. data/lib/datadog/ai_guard/configuration/settings.rb +0 -113
  170. data/lib/datadog/appsec/configuration/settings.rb +0 -423
  171. data/lib/datadog/data_streams/configuration/settings.rb +0 -49
  172. data/lib/datadog/di/configuration/settings.rb +0 -243
  173. data/lib/datadog/error_tracking/configuration/settings.rb +0 -63
@@ -37,7 +37,7 @@ module Datadog
37
37
  # moved into C extension
38
38
  @value = json?(value) ? JSON.parse(value) : value
39
39
  rescue JSON::ParserError => e
40
- raise Error, "Failed to parse JSON value: #{e.class}: #{e}"
40
+ raise Error, "Failed to parse JSON value: #{e.class}: #{e.message}"
41
41
  end
42
42
 
43
43
  # Check if the resolution resulted in an error
@@ -101,7 +101,7 @@ module Datadog
101
101
  statsd.count(stat, value, metric_options(options))
102
102
  rescue => e
103
103
  logger.error(
104
- "Failed to send count stat. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
104
+ "Failed to send count stat. Cause: #{e.class}: #{e.message} Source: #{Array(e.backtrace).first}"
105
105
  )
106
106
  telemetry.report(e, description: 'Failed to send count stat')
107
107
  end
@@ -115,7 +115,7 @@ module Datadog
115
115
  statsd.distribution(stat, value, metric_options(options))
116
116
  rescue => e
117
117
  logger.error(
118
- "Failed to send distribution stat. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
118
+ "Failed to send distribution stat. Cause: #{e.class}: #{e.message} Source: #{Array(e.backtrace).first}"
119
119
  )
120
120
  telemetry.report(e, description: 'Failed to send distribution stat')
121
121
  end
@@ -128,7 +128,7 @@ module Datadog
128
128
  statsd.increment(stat, metric_options(options))
129
129
  rescue => e
130
130
  logger.error(
131
- "Failed to send increment stat. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
131
+ "Failed to send increment stat. Cause: #{e.class}: #{e.message} Source: #{Array(e.backtrace).first}"
132
132
  )
133
133
  telemetry.report(e, description: 'Failed to send increment stat')
134
134
  end
@@ -142,7 +142,7 @@ module Datadog
142
142
  statsd.gauge(stat, value, metric_options(options))
143
143
  rescue => e
144
144
  logger.error(
145
- "Failed to send gauge stat. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
145
+ "Failed to send gauge stat. Cause: #{e.class}: #{e.message} Source: #{Array(e.backtrace).first}"
146
146
  )
147
147
  telemetry.report(e, description: 'Failed to send gauge stat')
148
148
  end
@@ -162,7 +162,7 @@ module Datadog
162
162
  rescue => e
163
163
  # TODO: Likely to be redundant, since `distribution` handles its own errors.
164
164
  logger.error(
165
- "Failed to send time stat. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
165
+ "Failed to send time stat. Cause: #{e.class}: #{e.message} Source: #{Array(e.backtrace).first}"
166
166
  )
167
167
  telemetry.report(e, description: 'Failed to send time stat')
168
168
  end
@@ -57,7 +57,7 @@ module Datadog
57
57
 
58
58
  contents = Configuration::ContentList.parse(response.target_files)
59
59
  rescue Remote::Configuration::Path::ParseError => e
60
- raise SyncError, e.message
60
+ raise SyncError, "#{e.class}: #{e.message}"
61
61
  end
62
62
 
63
63
  # To make sure steep does not complain
@@ -45,7 +45,7 @@ module Datadog
45
45
  rescue Client::SyncError => e
46
46
  # Transient errors due to network or agent. Logged the error but not via telemetry
47
47
  logger.error do
48
- "remote worker client sync error: #{e.class}: #{e} location: #{Array(e.backtrace).first}. skipping sync"
48
+ "remote worker client sync error: #{e.class}: #{e.message} location: #{Array(e.backtrace).first}. skipping sync"
49
49
  end
50
50
  rescue => e
51
51
  # In case of unexpected errors, reset the negotiation object
@@ -55,7 +55,7 @@ module Datadog
55
55
 
56
56
  # Transient errors due to network or agent. Logged the error but not via telemetry
57
57
  logger.error do
58
- "remote worker error: #{e.class}: #{e} location: #{Array(e.backtrace).first}. " \
58
+ "remote worker error: #{e.class}: #{e.message} location: #{Array(e.backtrace).first}. " \
59
59
  'resetting client state'
60
60
  end
61
61
 
@@ -100,7 +100,7 @@ module Datadog
100
100
  def try_flush
101
101
  yield
102
102
  rescue => e
103
- Datadog.logger.warn("Error while sending runtime metric. Cause: #{e.class}: #{e}")
103
+ Datadog.logger.warn("Error while sending runtime metric. Cause: #{e.class}: #{e.message}")
104
104
  end
105
105
 
106
106
  def default_metric_options
@@ -39,7 +39,7 @@ module Datadog
39
39
  res
40
40
  rescue => e
41
41
  logger.debug {
42
- "Unable to send telemetry request for event `#{event.respond_to?(:type) ? event.type : event.to_s}`: #{e.class}: #{e}"
42
+ "Unable to send telemetry request for event `#{event.respond_to?(:type) ? event.type : event.to_s}`: #{e.class}: #{e.message}"
43
43
  }
44
44
  Core::Transport::InternalErrorResponse.new(e)
45
45
  end
@@ -260,9 +260,9 @@ module Datadog
260
260
  end
261
261
 
262
262
  def collect_integration_configuration_options(tracing_settings)
263
- return [] unless tracing_settings.respond_to?(:instrumented_integrations)
263
+ return [] unless tracing_settings.respond_to?(:instrumented_built_in_integrations)
264
264
 
265
- tracing_settings.instrumented_integrations.each_value.with_object([]) do |integration, entries|
265
+ tracing_settings.instrumented_built_in_integrations.each_with_object([]) do |integration, entries|
266
266
  integration.configurations.each_value do |configuration|
267
267
  entries.concat(collect_configuration_options_from(configuration))
268
268
  end
@@ -4,6 +4,8 @@ require_relative 'http/builder'
4
4
  require_relative 'http/adapters/net'
5
5
  require_relative 'http/adapters/unix_socket'
6
6
  require_relative 'http/adapters/test'
7
+ require_relative '../environment/container'
8
+ require_relative '../environment/ext'
7
9
 
8
10
  module Datadog
9
11
  module Core
@@ -63,7 +63,7 @@ module Datadog
63
63
  str.encode(::Encoding::UTF_8)
64
64
  end
65
65
  rescue => e
66
- Datadog.logger.debug("Error encoding string in UTF-8: #{e}")
66
+ Datadog.logger.debug("Error encoding string in UTF-8: #{e.class}: #{e.message}")
67
67
 
68
68
  placeholder
69
69
  end
@@ -168,7 +168,7 @@ module Datadog
168
168
  rescue Exception => e
169
169
  @error = e
170
170
  Datadog.logger.debug(
171
- "Worker thread error. Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
171
+ "Worker thread error. Cause: #{e.class}: #{e.message} Location: #{Array(e.backtrace).first}"
172
172
  )
173
173
  raise
174
174
 
data/lib/datadog/core.rb CHANGED
@@ -18,7 +18,7 @@ module Datadog
18
18
  require "libdatadog_api.#{RUBY_VERSION[/\d+.\d+/]}_#{RUBY_PLATFORM}"
19
19
  nil
20
20
  rescue LoadError => e
21
- e.message
21
+ "#{e.class}: #{e.message}"
22
22
  end
23
23
  end
24
24
 
@@ -1,11 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'configuration/settings'
3
+ require_relative '../core/environment/variable_helpers'
4
+ require_relative 'ext'
4
5
 
5
6
  module Datadog
6
7
  module DataStreams
7
8
  # Configuration for Data Streams Monitoring
8
9
  module Configuration
10
+ # Configuration settings for Data Streams Monitoring.
11
+ module Settings
12
+ def self.extended(base)
13
+ base = base.singleton_class unless base.is_a?(Class)
14
+ add_settings!(base)
15
+ end
16
+
17
+ def self.add_settings!(base)
18
+ base.class_eval do
19
+ # Data Streams Monitoring configuration
20
+ # @public_api
21
+ settings :data_streams do
22
+ # Whether Data Streams Monitoring is enabled. When enabled, the library will
23
+ # collect and report data lineage information for messaging systems.
24
+ #
25
+ # @default `DD_DATA_STREAMS_ENABLED` environment variable, otherwise `false`.
26
+ # @return [Boolean]
27
+ option :enabled do |o|
28
+ o.type :bool
29
+ o.env Ext::ENV_ENABLED
30
+ o.default false
31
+ end
32
+
33
+ # The interval (in seconds) at which Data Streams Monitoring stats are flushed.
34
+ #
35
+ # @default 10.0
36
+ # @env '_DD_TRACE_STATS_WRITER_INTERVAL'
37
+ # @return [Float]
38
+ # @!visibility private
39
+ option :interval do |o|
40
+ o.type :float
41
+ o.env '_DD_TRACE_STATS_WRITER_INTERVAL'
42
+ o.default 10.0
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
9
48
  end
10
49
  end
11
50
  end
@@ -46,7 +46,7 @@ module Datadog
46
46
  decode(binary_data)
47
47
  rescue ArgumentError => e
48
48
  # Invalid base64 encoding - may indicate version mismatch or corruption
49
- Datadog.logger.debug { "Failed to decode DSM pathway context: #{e.class}: #{e}" }
49
+ Datadog.logger.debug { "Failed to decode DSM pathway context: #{e.class}: #{e.message}" }
50
50
  nil
51
51
  end
52
52
  end
@@ -329,7 +329,7 @@ module Datadog
329
329
  # Send to agent outside mutex to avoid blocking customer code if agent is slow/hung.
330
330
  send_stats_to_agent(payload)
331
331
  rescue => e
332
- @logger.debug("Failed to flush DSM stats to agent: #{e.class}: #{e}")
332
+ @logger.debug("Failed to flush DSM stats to agent: #{e.class}: #{e.message}")
333
333
  end
334
334
 
335
335
  def get_current_pathway
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative 'data_streams/processor'
4
4
  require_relative 'data_streams/pathway_context'
5
- require_relative 'data_streams/configuration/settings'
5
+ require_relative 'data_streams/configuration'
6
6
  require_relative 'data_streams/extensions'
7
7
  require_relative 'core/utils/time'
8
8
 
@@ -17,6 +17,10 @@ module Datadog
17
17
  module DI
18
18
  LOCK = Mutex.new
19
19
 
20
+ # Initialized eagerly to avoid "instance variable not initialized"
21
+ # warning on Ruby 2.6/2.7 and to simplify the type to non-nullable.
22
+ @current_components = []
23
+
20
24
  class << self
21
25
  attr_reader :code_tracker
22
26
 
@@ -48,12 +52,12 @@ module Datadog
48
52
  Datadog::DI.activate_tracking!
49
53
  rescue => exc
50
54
  if defined?(Datadog.logger)
51
- Datadog.logger.warn { "di: Failed to activate code tracking for DI: #{exc.class}: #{exc}" }
55
+ Datadog.logger.warn { "di: Failed to activate code tracking for DI: #{exc.class}: #{exc.message}" }
52
56
  else
53
57
  # We do not have Datadog logger potentially because DI code tracker is
54
58
  # being loaded early in application boot process and the rest of datadog
55
59
  # wasn't loaded yet. Output to standard error.
56
- warn("datadog: di: Failed to activate code tracking for DI: #{exc.class}: #{exc}")
60
+ warn("datadog: di: Failed to activate code tracking for DI: #{exc.class}: #{exc.message}")
57
61
  end
58
62
  end
59
63
  end
@@ -88,7 +92,7 @@ module Datadog
88
92
  # Datadog.components from the code tracker.
89
93
  def current_component
90
94
  LOCK.synchronize do
91
- @current_components&.last
95
+ @current_components.last
92
96
  end
93
97
  end
94
98
 
@@ -100,14 +104,13 @@ module Datadog
100
104
  # guaranteed to not end up with no component when one is running.
101
105
  def add_current_component(component)
102
106
  LOCK.synchronize do
103
- @current_components ||= []
104
107
  @current_components << component
105
108
  end
106
109
  end
107
110
 
108
111
  def remove_current_component(component)
109
112
  LOCK.synchronize do
110
- @current_components&.delete(component)
113
+ @current_components.delete(component)
111
114
  end
112
115
  end
113
116
  end
@@ -22,11 +22,114 @@ module Datadog
22
22
  class CodeTracker
23
23
  def initialize
24
24
  @registry = {}
25
+ @per_method_registry = {}
25
26
  @trace_point_lock = Mutex.new
26
27
  @registry_lock = Mutex.new
27
28
  @compiled_trace_point = nil
28
29
  end
29
30
 
31
+ # Populates the registry with iseqs for files that were loaded
32
+ # before code tracking started.
33
+ #
34
+ # Uses the all_iseqs C extension to walk the Ruby object space and
35
+ # find instruction sequences for already-loaded code. Whole-file
36
+ # iseqs are stored in the main registry; per-method/block/class
37
+ # iseqs are stored in per_method_registry as fallback for files
38
+ # whose whole-file iseq was GC'd.
39
+ #
40
+ # See docs/DynamicInstrumentationDevelopment.md "Iseq Lifecycle and GC"
41
+ # for which iseq types survive GC and implications for backfill.
42
+ #
43
+ # Whole-file detection uses two strategies:
44
+ # - Ruby 3.1+: DI.iseq_type (wraps rb_iseq_type) returns :top for
45
+ # require/load and :main for the entry script. This is precise.
46
+ # - Ruby < 3.1: falls back to first_lineno == 0, which is true for
47
+ # whole-file iseqs from require/load (INT2FIX(0) in Ruby's
48
+ # rb_iseq_new_top and rb_iseq_new_main) and false for
49
+ # method/block/class definitions (first_lineno >= 1).
50
+ # InstructionSequence.compile passes first_lineno = 1 by default,
51
+ # so eval'd code is not matched. Both strategies produce the same
52
+ # result in practice.
53
+ #
54
+ # Does not overwrite iseqs already in the registry (from
55
+ # :script_compiled), since those are guaranteed to be whole-file
56
+ # iseqs and are authoritative.
57
+ #
58
+ # @return [void]
59
+ def backfill_registry
60
+ iseqs = DI.file_iseqs
61
+ have_iseq_type = DI.respond_to?(:iseq_type)
62
+ registry_lock.synchronize do
63
+ iseqs.each do |iseq|
64
+ path = iseq.absolute_path
65
+ next unless path
66
+
67
+ whole_file = if have_iseq_type
68
+ type = DI.iseq_type(iseq)
69
+ # Require first_lineno == 0 to exclude compile_file/compile
70
+ # iseqs. These are :top type but have first_lineno == 1 and
71
+ # produce iseq objects distinct from require-produced iseqs.
72
+ # Targeted TracePoints are bound to the specific iseq object
73
+ # — a probe on a compile_file iseq silently never fires when
74
+ # the require-produced code runs.
75
+ (type == :top || type == :main) && iseq.first_lineno == 0
76
+ else
77
+ iseq.first_lineno == 0
78
+ end
79
+
80
+ if whole_file
81
+ # Ruby 3.2.9+ creates dummy profiler iseqs during require/load
82
+ # (rb_iseq_alloc_with_dummy_path in iseq.c). These have type
83
+ # :top, first_lineno == 0, and the same absolute_path as the
84
+ # real iseq — but iseq_size == 0 (no bytecode). A targeted
85
+ # TracePoint on a dummy iseq can't find child iseqs and raises
86
+ # ArgumentError "can not enable any hooks". Filter them out:
87
+ # a real top-level iseq always has at least one trace event.
88
+ next if iseq.trace_points.empty?
89
+
90
+ # Do not overwrite entries from :script_compiled — those are
91
+ # captured at load time and are authoritative.
92
+ next if registry.key?(path)
93
+
94
+ registry[path] = iseq
95
+ else
96
+ # Skip top-level script iseqs (:top/:main) produced by
97
+ # RubyVM::InstructionSequence.compile_file and .compile
98
+ # (compile source to bytecode without executing it).
99
+ # These represent the file body,
100
+ # not a method or block. They pass the first_lineno check
101
+ # (lineno != 0) but a targeted TracePoint bound to one
102
+ # of these never fires for method-level code — the
103
+ # user's probe silently produces no snapshots.
104
+ #
105
+ # On Ruby < 3.1 (no iseq_type), we cannot distinguish
106
+ # these from method iseqs, so they leak into
107
+ # per_method_registry. If iseq_for_line selects a leaked
108
+ # top-level iseq instead of the real method iseq, the
109
+ # probe installs but silently never fires — same failure
110
+ # as above. This requires the application to call
111
+ # compile_file and hold the result, which is rare outside
112
+ # tooling like bootsnap (which discards it).
113
+ next if have_iseq_type && (type == :top || type == :main)
114
+
115
+ # Store per-method/block/class iseqs as fallback for files
116
+ # whose whole-file iseq was GC'd. These can be used to
117
+ # target line probes on lines within their range.
118
+ (per_method_registry[path] ||= []) << iseq
119
+ end
120
+ end
121
+ end
122
+ nil
123
+ rescue => exc
124
+ # Backfill is best-effort — if it fails, line probes on
125
+ # pre-loaded code won't work but everything else is unaffected.
126
+ if component = DI.current_component
127
+ component.logger.debug { "di: backfill_registry failed: #{exc.class}: #{exc.message}" }
128
+ component.telemetry&.report(exc, description: "backfill_registry failed")
129
+ end
130
+ nil
131
+ end
132
+
30
133
  # Starts tracking loaded code.
31
134
  #
32
135
  # This method should generally be called early in application boot
@@ -94,7 +197,7 @@ module Datadog
94
197
  # but we will have DI.current_component (set to nil).
95
198
  if component = DI.current_component
96
199
  raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
97
- component.logger.debug { "di: unhandled exception in script_compiled trace point: #{exc.class}: #{exc}" }
200
+ component.logger.debug { "di: unhandled exception in script_compiled trace point: #{exc.class}: #{exc.message}" }
98
201
  component.telemetry&.report(exc, description: "Unhandled exception in script_compiled trace point")
99
202
  # TODO test this path
100
203
  else
@@ -104,6 +207,12 @@ module Datadog
104
207
  # TODO test this path
105
208
  end
106
209
  end
210
+
211
+ # Backfill the registry with iseqs for files that were loaded
212
+ # before tracking started. This must happen after the trace
213
+ # point is enabled so that any files loaded concurrently are
214
+ # captured by the trace point (backfill won't overwrite them).
215
+ backfill_registry
107
216
  end
108
217
  end
109
218
 
@@ -160,6 +269,51 @@ module Datadog
160
269
  end
161
270
  end
162
271
 
272
+ # Returns a [path, iseq] pair for a line probe target, or nil.
273
+ #
274
+ # First checks the whole-file iseq registry (via iseqs_for_path_suffix).
275
+ # If no whole-file iseq exists, searches the per-method iseq registry
276
+ # for an iseq whose trace_points include the target line.
277
+ #
278
+ # @param suffix [String] file path or suffix to match
279
+ # @param line [Integer] target line number
280
+ # @return [Array(String, RubyVM::InstructionSequence), nil]
281
+ def iseq_for_line(suffix, line)
282
+ # Try whole-file iseq first — it always covers all lines.
283
+ result = iseqs_for_path_suffix(suffix)
284
+ return result if result
285
+
286
+ # Fall back to per-method iseqs.
287
+ registry_lock.synchronize do
288
+ # Resolve the path using the per-method registry keys.
289
+ path = resolve_path_suffix(suffix, per_method_registry.keys)
290
+ return nil unless path
291
+
292
+ iseqs = per_method_registry[path]
293
+ return nil unless iseqs
294
+
295
+ # Only match event types the instrumenter subscribes to
296
+ # (:line, :return, :b_return — see hook_line). Lines that
297
+ # only carry :call (e.g. a `def` line within the defined
298
+ # method's own iseq, not the enclosing scope) have no
299
+ # subscribed event at that position; TracePoint#enable
300
+ # raises because it cannot bind an enabled event there.
301
+ matches = iseqs.select do |iseq|
302
+ iseq.trace_points.any? do |tp_line, event|
303
+ tp_line == line && (event == :line || event == :return || event == :b_return)
304
+ end
305
+ end
306
+ # When multiple iseqs contain the target line (e.g. a method
307
+ # and an inline block sharing the same line), picking one
308
+ # would silently miss executions in the other context.
309
+ # Raise so the probe is recorded as failed with a clear error.
310
+ if matches.length > 1
311
+ raise Error::MultiplePathsMatch, "Multiple code locations match line #{line}"
312
+ end
313
+ matches.first ? [path, matches.first] : nil
314
+ end
315
+ end
316
+
163
317
  # Stops tracking code that is being loaded.
164
318
  #
165
319
  # This method should ordinarily never be called - if a file is loaded
@@ -186,6 +340,7 @@ module Datadog
186
340
  def clear
187
341
  registry_lock.synchronize do
188
342
  registry.clear
343
+ per_method_registry.clear
189
344
  end
190
345
  end
191
346
 
@@ -195,8 +350,31 @@ module Datadog
195
350
  # objects representing compiled code of those files.
196
351
  attr_reader :registry
197
352
 
353
+ # Mapping from paths to arrays of per-method/block/class iseqs.
354
+ # Used as fallback when the whole-file iseq has been GC'd.
355
+ attr_reader :per_method_registry
356
+
198
357
  attr_reader :trace_point_lock
199
358
  attr_reader :registry_lock
359
+
360
+ # Resolves a path suffix against a set of known paths.
361
+ # Returns the matching path or nil.
362
+ #
363
+ # Must be called within registry_lock.
364
+ def resolve_path_suffix(suffix, paths)
365
+ # Exact match.
366
+ return suffix if paths.include?(suffix)
367
+
368
+ # Suffix match.
369
+ suffix = suffix.dup
370
+ loop do
371
+ matches = paths.select { |p| Utils.path_matches_suffix?(p, suffix) }
372
+ raise Error::MultiplePathsMatch, "Multiple paths matched requested suffix" if matches.length > 1
373
+ return matches.first if matches.any?
374
+ return nil unless suffix.include?('/')
375
+ suffix.sub!(%r{.*/+}, '')
376
+ end
377
+ end
200
378
  end
201
379
  end
202
380
  end
@@ -122,7 +122,7 @@ module Datadog
122
122
  payload = probe_notification_builder.build_errored(probe, exc)
123
123
  probe_notifier_worker.add_status(payload)
124
124
  rescue => nested_exc
125
- logger.debug { "di: failed to build error notification: #{nested_exc.class}: #{nested_exc}" }
125
+ logger.debug { "di: failed to build error notification: #{nested_exc.class}: #{nested_exc.message}" }
126
126
  telemetry&.report(nested_exc, description: 'Error building probe error notification')
127
127
  raise
128
128
  end