datadog 2.30.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 (219) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +17 -7
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +11 -4
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +6 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +18 -0
  7. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +10 -0
  8. data/ext/datadog_profiling_native_extension/extconf.rb +7 -4
  9. data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
  10. data/ext/libdatadog_api/crashtracker.c +5 -8
  11. data/ext/libdatadog_api/datadog_ruby_common.c +18 -0
  12. data/ext/libdatadog_api/datadog_ruby_common.h +10 -0
  13. data/ext/libdatadog_api/di.c +127 -0
  14. data/ext/libdatadog_api/extconf.rb +9 -4
  15. data/ext/libdatadog_api/init.c +5 -2
  16. data/ext/libdatadog_extconf_helpers.rb +46 -1
  17. data/lib/datadog/ai_guard/component.rb +2 -0
  18. data/lib/datadog/ai_guard/configuration.rb +105 -2
  19. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +41 -3
  20. data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
  21. data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
  22. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +3 -1
  23. data/lib/datadog/ai_guard/evaluation/request.rb +14 -9
  24. data/lib/datadog/ai_guard/evaluation/result.rb +3 -1
  25. data/lib/datadog/ai_guard/evaluation.rb +37 -7
  26. data/lib/datadog/ai_guard/ext.rb +1 -0
  27. data/lib/datadog/ai_guard.rb +26 -8
  28. data/lib/datadog/appsec/autoload.rb +1 -1
  29. data/lib/datadog/appsec/component.rb +11 -7
  30. data/lib/datadog/appsec/configuration.rb +414 -1
  31. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +2 -1
  32. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +6 -7
  33. data/lib/datadog/appsec/instrumentation/gateway.rb +0 -13
  34. data/lib/datadog/appsec/metrics/telemetry.rb +13 -1
  35. data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -0
  36. data/lib/datadog/appsec/security_engine/runner.rb +1 -1
  37. data/lib/datadog/appsec/trace_keeper.rb +18 -6
  38. data/lib/datadog/appsec/utils/http/media_type.rb +1 -2
  39. data/lib/datadog/appsec/utils/http/url_encoded.rb +3 -3
  40. data/lib/datadog/appsec.rb +5 -9
  41. data/lib/datadog/core/configuration/base.rb +17 -5
  42. data/lib/datadog/core/configuration/components.rb +22 -9
  43. data/lib/datadog/core/configuration/config_helper.rb +9 -0
  44. data/lib/datadog/core/configuration/option.rb +30 -5
  45. data/lib/datadog/core/configuration/option_definition.rb +38 -12
  46. data/lib/datadog/core/configuration/options.rb +40 -6
  47. data/lib/datadog/core/configuration/settings.rb +18 -0
  48. data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
  49. data/lib/datadog/core/configuration.rb +1 -1
  50. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  51. data/lib/datadog/core/contrib/rails/utils.rb +7 -3
  52. data/lib/datadog/core/crashtracking/component.rb +3 -3
  53. data/lib/datadog/core/diagnostics/environment_logger.rb +3 -1
  54. data/lib/datadog/core/environment/container.rb +2 -2
  55. data/lib/datadog/core/environment/ext.rb +1 -0
  56. data/lib/datadog/core/environment/identity.rb +25 -3
  57. data/lib/datadog/core/environment/process.rb +12 -0
  58. data/lib/datadog/core/feature_flags.rb +1 -1
  59. data/lib/datadog/core/metrics/client.rb +5 -5
  60. data/lib/datadog/core/remote/client.rb +1 -1
  61. data/lib/datadog/core/remote/component.rb +38 -21
  62. data/lib/datadog/core/runtime/metrics.rb +1 -1
  63. data/lib/datadog/core/telemetry/component.rb +3 -0
  64. data/lib/datadog/core/telemetry/emitter.rb +1 -1
  65. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +2 -3
  66. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  67. data/lib/datadog/core/telemetry/event/app_started.rb +151 -169
  68. data/lib/datadog/core/telemetry/event.rb +1 -7
  69. data/lib/datadog/core/telemetry/ext.rb +1 -0
  70. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -0
  71. data/lib/datadog/core/telemetry/worker.rb +20 -0
  72. data/lib/datadog/core/transport/http.rb +2 -0
  73. data/lib/datadog/core/utils/only_once.rb +1 -1
  74. data/lib/datadog/core/utils/spawn_monkey_patch.rb +36 -0
  75. data/lib/datadog/core/utils.rb +1 -1
  76. data/lib/datadog/core/workers/async.rb +1 -1
  77. data/lib/datadog/core.rb +1 -2
  78. data/lib/datadog/data_streams/configuration.rb +40 -1
  79. data/lib/datadog/data_streams/pathway_context.rb +1 -1
  80. data/lib/datadog/data_streams/processor.rb +1 -1
  81. data/lib/datadog/data_streams.rb +1 -1
  82. data/lib/datadog/di/base.rb +8 -5
  83. data/lib/datadog/di/boot.rb +2 -4
  84. data/lib/datadog/di/code_tracker.rb +179 -1
  85. data/lib/datadog/di/component.rb +5 -1
  86. data/lib/datadog/di/configuration.rb +235 -2
  87. data/lib/datadog/di/instrumenter.rb +55 -29
  88. data/lib/datadog/di/probe_builder.rb +1 -1
  89. data/lib/datadog/di/probe_file_loader.rb +2 -2
  90. data/lib/datadog/di/probe_manager.rb +6 -6
  91. data/lib/datadog/di/probe_notification_builder.rb +110 -2
  92. data/lib/datadog/di/probe_notifier_worker.rb +2 -2
  93. data/lib/datadog/di/remote.rb +6 -6
  94. data/lib/datadog/di/transport/input.rb +3 -3
  95. data/lib/datadog/di.rb +81 -0
  96. data/lib/datadog/error_tracking/configuration.rb +55 -2
  97. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  98. data/lib/datadog/open_feature/component.rb +18 -1
  99. data/lib/datadog/open_feature/evaluation_engine.rb +2 -2
  100. data/lib/datadog/open_feature/hooks/flag_eval_hook.rb +49 -0
  101. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
  102. data/lib/datadog/open_feature/provider.rb +19 -1
  103. data/lib/datadog/open_feature/remote.rb +1 -1
  104. data/lib/datadog/open_feature/transport.rb +1 -1
  105. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -0
  106. data/lib/datadog/opentelemetry/metrics.rb +3 -3
  107. data/lib/datadog/opentelemetry/sdk/configurator.rb +1 -1
  108. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +1 -1
  109. data/lib/datadog/profiling/collectors/code_provenance.rb +36 -11
  110. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +31 -2
  111. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +8 -2
  112. data/lib/datadog/profiling/collectors/info.rb +16 -3
  113. data/lib/datadog/profiling/component.rb +12 -4
  114. data/lib/datadog/profiling/exporter.rb +37 -12
  115. data/lib/datadog/profiling/ext.rb +0 -2
  116. data/lib/datadog/profiling/flush.rb +21 -12
  117. data/lib/datadog/profiling/http_transport.rb +12 -1
  118. data/lib/datadog/profiling/load_native_extension.rb +2 -2
  119. data/lib/datadog/profiling/profiler.rb +13 -5
  120. data/lib/datadog/profiling/scheduler.rb +2 -2
  121. data/lib/datadog/profiling/tasks/exec.rb +8 -3
  122. data/lib/datadog/profiling/tasks/help.rb +1 -0
  123. data/lib/datadog/profiling/tasks/setup.rb +2 -2
  124. data/lib/datadog/profiling.rb +1 -2
  125. data/lib/datadog/single_step_instrument.rb +1 -1
  126. data/lib/datadog/symbol_database/configuration.rb +65 -0
  127. data/lib/datadog/symbol_database/extractor.rb +915 -0
  128. data/lib/datadog/symbol_database/file_hash.rb +46 -0
  129. data/lib/datadog/symbol_database/logger.rb +43 -0
  130. data/lib/datadog/symbol_database/scope.rb +98 -0
  131. data/lib/datadog/symbol_database/service_version.rb +57 -0
  132. data/lib/datadog/symbol_database/symbol.rb +66 -0
  133. data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
  134. data/lib/datadog/symbol_database/transport/http.rb +45 -0
  135. data/lib/datadog/symbol_database/transport.rb +54 -0
  136. data/lib/datadog/symbol_database/uploader.rb +166 -0
  137. data/lib/datadog/symbol_database.rb +49 -0
  138. data/lib/datadog/tracing/buffer.rb +3 -3
  139. data/lib/datadog/tracing/component.rb +11 -0
  140. data/lib/datadog/tracing/configuration/settings.rb +2 -1
  141. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -3
  142. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +20 -0
  143. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
  144. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  145. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  146. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  147. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  148. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  149. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  150. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  151. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  152. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  153. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
  154. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  155. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  156. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  157. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +1 -1
  158. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
  159. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
  160. data/lib/datadog/tracing/contrib/component.rb +1 -1
  161. data/lib/datadog/tracing/contrib/configurable.rb +18 -3
  162. data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -4
  163. data/lib/datadog/tracing/contrib/dalli/quantize.rb +1 -1
  164. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +1 -1
  165. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
  166. data/lib/datadog/tracing/contrib/extensions.rb +9 -0
  167. data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -2
  168. data/lib/datadog/tracing/contrib/grape/endpoint.rb +5 -5
  169. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +2 -2
  170. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +2 -2
  171. data/lib/datadog/tracing/contrib/http/instrumentation.rb +3 -3
  172. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -2
  173. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +3 -3
  174. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +2 -2
  175. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +2 -2
  176. data/lib/datadog/tracing/contrib/karafka/patcher.rb +1 -1
  177. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +3 -3
  178. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +1 -1
  179. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +3 -3
  180. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  181. data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -1
  182. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  183. data/lib/datadog/tracing/contrib/rails/patcher.rb +0 -1
  184. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  185. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
  186. data/lib/datadog/tracing/contrib/redis/quantize.rb +1 -1
  187. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  188. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
  189. data/lib/datadog/tracing/contrib/status_range_matcher.rb +4 -0
  190. data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
  191. data/lib/datadog/tracing/contrib.rb +8 -0
  192. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
  193. data/lib/datadog/tracing/distributed/baggage.rb +59 -5
  194. data/lib/datadog/tracing/distributed/datadog.rb +13 -11
  195. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +1 -1
  196. data/lib/datadog/tracing/distributed/propagation.rb +2 -2
  197. data/lib/datadog/tracing/distributed/trace_context.rb +74 -32
  198. data/lib/datadog/tracing/event.rb +1 -1
  199. data/lib/datadog/tracing/metadata/tagging.rb +2 -2
  200. data/lib/datadog/tracing/pipeline.rb +1 -1
  201. data/lib/datadog/tracing/remote.rb +1 -1
  202. data/lib/datadog/tracing/sampling/ext.rb +2 -0
  203. data/lib/datadog/tracing/sampling/priority_sampler.rb +13 -0
  204. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  205. data/lib/datadog/tracing/sampling/rule_sampler.rb +54 -25
  206. data/lib/datadog/tracing/sampling/span/rule_parser.rb +2 -2
  207. data/lib/datadog/tracing/span_operation.rb +4 -4
  208. data/lib/datadog/tracing/trace_operation.rb +53 -9
  209. data/lib/datadog/tracing/tracer.rb +29 -4
  210. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  211. data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
  212. data/lib/datadog/tracing/workers.rb +2 -1
  213. data/lib/datadog/version.rb +1 -1
  214. metadata +27 -12
  215. data/lib/datadog/ai_guard/configuration/settings.rb +0 -113
  216. data/lib/datadog/appsec/configuration/settings.rb +0 -423
  217. data/lib/datadog/data_streams/configuration/settings.rb +0 -49
  218. data/lib/datadog/di/configuration/settings.rb +0 -243
  219. data/lib/datadog/error_tracking/configuration/settings.rb +0 -63
@@ -10,13 +10,6 @@ module Datadog
10
10
  #
11
11
  # @api private
12
12
  module Event
13
- extend Core::Utils::Forking
14
-
15
- # returns sequence that increments every time the configuration changes
16
- def self.configuration_sequence
17
- after_fork! { @sequence = Datadog::Core::Utils::Sequence.new(1) }
18
- @sequence ||= Datadog::Core::Utils::Sequence.new(1)
19
- end
20
13
  end
21
14
  end
22
15
  end
@@ -25,6 +18,7 @@ end
25
18
  require_relative 'event/base'
26
19
  require_relative 'event/app_client_configuration_change'
27
20
  require_relative 'event/app_closing'
21
+ require_relative 'event/app_extended_heartbeat'
28
22
  require_relative 'event/app_dependencies_loaded'
29
23
  require_relative 'event/app_endpoints_loaded'
30
24
  require_relative 'event/app_heartbeat'
@@ -7,6 +7,7 @@ module Datadog
7
7
  ENV_ENABLED = 'DD_INSTRUMENTATION_TELEMETRY_ENABLED'
8
8
  ENV_METRICS_ENABLED = 'DD_TELEMETRY_METRICS_ENABLED'
9
9
  ENV_HEARTBEAT_INTERVAL = 'DD_TELEMETRY_HEARTBEAT_INTERVAL'
10
+ ENV_EXTENDED_HEARTBEAT_INTERVAL = 'DD_TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL'
10
11
  ENV_METRICS_AGGREGATION_INTERVAL = 'DD_TELEMETRY_METRICS_AGGREGATION_INTERVAL'
11
12
  ENV_DEPENDENCY_COLLECTION = 'DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED'
12
13
  ENV_INSTALL_ID = 'DD_INSTRUMENTATION_INSTALL_ID'
@@ -44,11 +44,16 @@ module Datadog
44
44
  'DD-Telemetry-Request-Type' => request_type,
45
45
  'DD-Client-Library-Language' => Core::Environment::Ext::LANG,
46
46
  'DD-Client-Library-Version' => Core::Environment::Identity.gem_datadog_version_semver2,
47
+ 'DD-Session-ID' => Core::Environment::Identity.id,
47
48
 
48
49
  # Enable debug mode for telemetry
49
50
  # 'DD-Telemetry-Debug-Enabled' => 'true',
50
51
  }.tap do |result|
51
52
  result['DD-API-KEY'] = api_key unless api_key.nil?
53
+ root = Core::Environment::Identity.root_runtime_id
54
+ result['DD-Root-Session-ID'] = root if root
55
+ parent = Core::Environment::Identity.parent_runtime_id
56
+ result['DD-Parent-Session-ID'] = parent if parent
52
57
  end
53
58
  end
54
59
  end
@@ -27,6 +27,9 @@ module Datadog
27
27
  metrics_manager:,
28
28
  dependency_collection:,
29
29
  logger:,
30
+ settings:,
31
+ agent_settings:,
32
+ extended_heartbeat_interval_seconds:,
30
33
  enabled: true,
31
34
  shutdown_timeout: Workers::Polling::DEFAULT_SHUTDOWN_TIMEOUT,
32
35
  buffer_size: DEFAULT_BUFFER_MAX_SIZE
@@ -35,8 +38,11 @@ module Datadog
35
38
  @metrics_manager = metrics_manager
36
39
  @dependency_collection = dependency_collection
37
40
  @logger = logger
41
+ @settings = settings
42
+ @agent_settings = agent_settings
38
43
 
39
44
  @ticks_per_heartbeat = (heartbeat_interval_seconds / metrics_aggregation_interval_seconds).to_i
45
+ @ticks_per_extended_heartbeat = (extended_heartbeat_interval_seconds / metrics_aggregation_interval_seconds).to_i
40
46
  @current_ticks = 0
41
47
 
42
48
  # Workers::Polling settings
@@ -63,6 +69,7 @@ module Datadog
63
69
  self.buffer = buffer_klass.new(@buffer_size)
64
70
 
65
71
  @initial_event_once = Utils::OnlyOnceSuccessful.new(APP_STARTED_EVENT_RETRIES)
72
+ @extended_heartbeat_ticks = 0
66
73
  end
67
74
 
68
75
  attr_reader :logger
@@ -151,6 +158,13 @@ module Datadog
151
158
  end
152
159
 
153
160
  @current_ticks += 1
161
+ @extended_heartbeat_ticks += 1
162
+
163
+ if @extended_heartbeat_ticks >= @ticks_per_extended_heartbeat
164
+ @extended_heartbeat_ticks = 0
165
+ extended_heartbeat!
166
+ end
167
+
154
168
  return if @current_ticks < @ticks_per_heartbeat
155
169
 
156
170
  @current_ticks = 0
@@ -170,6 +184,12 @@ module Datadog
170
184
  send_event(Event::AppHeartbeat.new)
171
185
  end
172
186
 
187
+ def extended_heartbeat!
188
+ return if !enabled? || !sent_initial_event?
189
+
190
+ send_event(Event::AppExtendedHeartbeat.new(settings: @settings, agent_settings: @agent_settings))
191
+ end
192
+
173
193
  def started!
174
194
  return unless enabled?
175
195
 
@@ -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
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module Core
5
5
  module Utils
6
- # Helper class to execute something only once such as not repeating warning logs, and instrumenting classes
6
+ # Helper class to execute something only once, such as not repeating warning logs and instrumenting classes
7
7
  # only once.
8
8
  #
9
9
  # Thread-safe when used correctly (e.g. be careful of races when lazily initializing instances of this class).
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ module Utils
6
+ module SpawnMonkeyPatch
7
+ # @param lineage_envs_provider [#call] returns a Hash of env vars to merge into the child process
8
+ def self.apply!(lineage_envs_provider:)
9
+ @lineage_envs_provider = lineage_envs_provider
10
+ ::Process.singleton_class.prepend(ProcessSpawnPatch)
11
+ true
12
+ end
13
+
14
+ module ProcessSpawnPatch
15
+ def spawn(*args, **opts)
16
+ args.replace(SpawnMonkeyPatch.inject_lineage_envs(args))
17
+ super
18
+ end
19
+ end
20
+
21
+ # Process.spawn(env?, cmd, ...): env is optional first arg (Hash). When present, merge
22
+ # runtime_ids into it; when absent, prepend full ENV + runtime_ids so the child inherits both.
23
+ def self.inject_lineage_envs(args)
24
+ runtime_ids = @lineage_envs_provider.call
25
+ env_provided = Hash === args.first
26
+
27
+ base_env = env_provided ? args.first : DATADOG_ENV.to_h
28
+ env = base_env.merge(runtime_ids)
29
+ rest = env_provided ? args.drop(1) : args
30
+
31
+ [env, *rest]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -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.name} #{e.message} 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,11 +18,10 @@ 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
 
25
- DATADOG_ENV = Core::Configuration::ConfigHelper.new
26
25
  extend Core::Extensions
27
26
 
28
27
  # Add shutdown hook:
@@ -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.message}")
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
@@ -18,8 +18,7 @@ require_relative 'serializer'
18
18
  require_relative 'transport/http'
19
19
  require_relative 'utils'
20
20
 
21
- # Steep: https://github.com/ruby/rbs/pull/2715
22
- if %w[1 true yes].include?(Datadog::DATADOG_ENV['DD_DYNAMIC_INSTRUMENTATION_ENABLED']) # steep:ignore ArgumentTypeMismatch
21
+ if %w[1 true yes].include?(Datadog::DATADOG_ENV['DD_DYNAMIC_INSTRUMENTATION_ENABLED'])
23
22
 
24
23
  # For initial release of Dynamic Instrumentation, activate code tracking
25
24
  # only if DI is explicitly requested in the environment.
@@ -37,8 +36,7 @@ require_relative 'contrib'
37
36
 
38
37
  Datadog::DI::Contrib.load_now_or_later
39
38
 
40
- # Steep: https://github.com/ruby/rbs/pull/2715
41
- if %w[1 true yes].include?(Datadog::DATADOG_ENV['DD_DYNAMIC_INSTRUMENTATION_ENABLED']) # steep:ignore ArgumentTypeMismatch
39
+ if %w[1 true yes].include?(Datadog::DATADOG_ENV['DD_DYNAMIC_INSTRUMENTATION_ENABLED'])
42
40
  if Datadog::DATADOG_ENV['DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE']
43
41
  require_relative 'probe_file_loader'
44
42
  Datadog::DI::ProbeFileLoader.load_now_or_later
@@ -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
@@ -49,6 +49,10 @@ module Datadog
49
49
  logger.warn("di: cannot enable dynamic instrumentation: Ruby 2.6+ is required, but running on #{RUBY_VERSION}")
50
50
  return false
51
51
  end
52
+ unless DI.respond_to?(:exception_message)
53
+ logger.warn("di: cannot enable dynamic instrumentation: C extension is not available")
54
+ return false
55
+ end
52
56
  true
53
57
  end
54
58
  end
@@ -118,7 +122,7 @@ module Datadog
118
122
  payload = probe_notification_builder.build_errored(probe, exc)
119
123
  probe_notifier_worker.add_status(payload)
120
124
  rescue => nested_exc
121
- 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}" }
122
126
  telemetry&.report(nested_exc, description: 'Error building probe error notification')
123
127
  raise
124
128
  end