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
@@ -4,8 +4,241 @@ module Datadog
4
4
  module DI
5
5
  # Configuration for DI
6
6
  module Configuration
7
+ # Settings
8
+ module Settings
9
+ def self.extended(base)
10
+ base = base.singleton_class unless base.is_a?(Class)
11
+ add_settings!(base)
12
+ end
13
+
14
+ def self.add_settings!(base)
15
+ base.class_eval do
16
+ settings :dynamic_instrumentation do
17
+ option :enabled do |o|
18
+ o.type :bool
19
+ # The environment variable has an "internal" prefix so that
20
+ # any customers that have the "proper" environment variable
21
+ # turned on (i.e. DD_DYNAMIC_INSTRUMENTATION_ENABLED)
22
+ # do not enable Ruby DI until the latter is ready for
23
+ # customer testing.
24
+ o.env "DD_DYNAMIC_INSTRUMENTATION_ENABLED"
25
+ o.default false
26
+ end
27
+
28
+ # An array of variable and key names to redact in addition to
29
+ # the built-in list of identifiers.
30
+ #
31
+ # The names will be normalized by removing the following
32
+ # symbols: _, -, @, $, and then matched to the complete
33
+ # variable or key name while ignoring the case.
34
+ # For example, specifying pass_word will match password and
35
+ # PASSWORD, and specifying PASSWORD will match pass_word.
36
+ # Note that, while the at sign (@) is used in Ruby to refer
37
+ # to instance variables, it does not have any significance
38
+ # for this setting (and is removed before matching identifiers).
39
+ option :redacted_identifiers do |o|
40
+ o.env "DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS"
41
+ o.env_parser do |value|
42
+ value&.split(",")&.map(&:strip)
43
+ end
44
+
45
+ o.type :array
46
+ o.default []
47
+ end
48
+
49
+ # An array of variable and key names to exclude from the
50
+ # built-in redaction list.
51
+ #
52
+ # This allows users to capture values of variables that would
53
+ # otherwise be redacted by the default identifier list.
54
+ # For example, if an application has a "session" variable
55
+ # that does not contain sensitive data, "session" can be added
56
+ # to this list to exclude it from redaction.
57
+ #
58
+ # The names will be normalized the same way as redacted_identifiers,
59
+ # by removing the following symbols: _, -, @, $, and then matched
60
+ # against the complete variable or key name while ignoring the case.
61
+ option :redaction_excluded_identifiers do |o|
62
+ o.env "DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS"
63
+ o.env_parser do |value|
64
+ value&.split(",")&.map(&:strip)
65
+ end
66
+
67
+ o.type :array
68
+ o.default []
69
+ end
70
+
71
+ # An array of class names, values of which will be redacted from
72
+ # dynamic instrumentation snapshots. Example: FooClass.
73
+ # If a name is suffixed by '*', it becomes a wildcard and
74
+ # instances of any class whose name begins with the specified
75
+ # prefix will be redacted (example: Foo*).
76
+ #
77
+ # The names must all be fully-qualified, if any prefix of a
78
+ # class name is configured to be redacted, the value will be
79
+ # subject to redaction. For example, if Foo* is in the
80
+ # redacted class name list, instances of Foo, FooBar,
81
+ # Foo::Bar are all subject to redaction, but Bar::Foo will
82
+ # not be subject to redaction.
83
+ #
84
+ # Leading double-colon is permitted but has no effect,
85
+ # because the names are always considered to be fully-qualified.
86
+ # For example, adding ::Foo to the list will redact instances
87
+ # of Foo.
88
+ #
89
+ # Trailing colons should not be used because they will trigger
90
+ # exact match behavior but Ruby class names do not have
91
+ # trailing colons. For example, Foo:: will not cause anything
92
+ # to be redacted. Use Foo::* to redact all classes under
93
+ # the Foo module.
94
+ option :redacted_type_names do |o|
95
+ o.env "DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES"
96
+ o.env_parser do |value|
97
+ value&.split(",")&.map(&:strip)
98
+ end
99
+
100
+ o.type :array
101
+ o.default []
102
+ end
103
+
104
+ # Maximum number of object or collection traversals that
105
+ # will be permitted when serializing captured values.
106
+ option :max_capture_depth do |o|
107
+ o.type :int
108
+ o.default 3
109
+ end
110
+
111
+ # Maximum number of collection (Array and Hash) elements
112
+ # that will be captured. Arrays and hashes that have more
113
+ # elements will be truncated to this many elements.
114
+ option :max_capture_collection_size do |o|
115
+ o.type :int
116
+ o.default 100
117
+ end
118
+
119
+ # Strings longer than this length will be truncated to this
120
+ # length in dynamic instrumentation snapshots.
121
+ #
122
+ # Note that while all values are stringified during
123
+ # serialization, only values which are originally instances
124
+ # of the String class are subject to this length limit.
125
+ option :max_capture_string_length do |o|
126
+ o.type :int
127
+ o.default 255
128
+ end
129
+
130
+ # Maximim number of attributes that will be captured for
131
+ # a single non-primitive value.
132
+ option :max_capture_attribute_count do |o|
133
+ o.type :int
134
+ o.default 20
135
+ end
136
+
137
+ # Settings in the 'internal' group are for internal Datadog
138
+ # use only, and are needed to test dynamic instrumentation or
139
+ # experiment with features not released to customers.
140
+ settings :internal do
141
+ # This option instructs dynamic instrumentation to use
142
+ # untargeted trace points when installing line probes and
143
+ # code tracking is not active.
144
+ # WARNING: untargeted trace points carry a massive performance
145
+ # penalty for the entire file in which a line probe is placed.
146
+ #
147
+ # If this option is set to false, which is the default,
148
+ # dynamic instrumentation will add probes that reference
149
+ # unknown files to the list of pending probes, and when
150
+ # the respective files are loaded, the line probes will be
151
+ # installed using targeted trace points. If the file in
152
+ # question is already loaded when the probe is received
153
+ # (for example, it is in a third-party library loaded during
154
+ # application boot), and code tracking was not active when
155
+ # the file was loaded, such files will not be instrumentable
156
+ # via line probes.
157
+ #
158
+ # If this option is set to true, dynamic instrumentation will
159
+ # install untargeted trace points for all line probes,
160
+ # regardless of whether the referenced file is loaded.
161
+ # This permits instrumenting code which was loaded prior to
162
+ # code tracking being activated and instrumenting lines when
163
+ # code tracking is not activated at all. However, untargeted
164
+ # trace points are extremely slow and will greatly degrade
165
+ # performance of *all* code executed while they are installed,
166
+ # not just the instrumentation target.
167
+ option :untargeted_trace_points do |o|
168
+ o.type :bool
169
+ o.default false
170
+ end
171
+
172
+ # If true, all of the catch-all rescue blocks in DI
173
+ # will propagate the exceptions onward.
174
+ # WARNING: for internal Datadog use only - this will break
175
+ # the DI product and potentially the library in general in
176
+ # a multitude of ways, cause resource leakage, permanent
177
+ # performance decreases, etc.
178
+ option :propagate_all_exceptions do |o|
179
+ o.type :bool
180
+ o.default false
181
+ end
182
+
183
+ # Minimum interval, in seconds, between probe status and
184
+ # snapshot submissions to the agent. Probe notifier worker will
185
+ # batch together payloads submitted during each interval.
186
+ # A longer interval reduces the overhead imposed by dynamic
187
+ # instrumentation on the application, but also increases the
188
+ # time when application code cannot run (when the batches are
189
+ # being sent out by the probe notifier worker) and creates a
190
+ # possibility of dropping payloads if the queue gets too long.
191
+ option :min_send_interval do |o|
192
+ o.type :float
193
+ o.default 3
194
+ end
195
+
196
+ # Number of snapshots that can be stored in the probe
197
+ # notifier worker queue. Larger capacity runs the risk of
198
+ # creating snapshots that exceed the agent's request size
199
+ # limit. Smaller capacity increases the risk of dropping
200
+ # snapshots.
201
+ option :snapshot_queue_capacity do |o|
202
+ o.type :int
203
+ o.default 100
204
+ end
205
+
206
+ # Enable dynamic instrumentation in development environments.
207
+ # Currently DI does not fully implement support for code
208
+ # unloading and reloading, and is not supported in
209
+ # non-production environments.
210
+ option :development do |o|
211
+ o.type :bool
212
+ o.default false
213
+ end
214
+
215
+ # Enable logging of dynamic instrumentation activity.
216
+ # This is quite verbose.
217
+ option :trace_logging do |o|
218
+ o.type :bool
219
+ o.default false
220
+
221
+ # Use the same environment variable as the rest of
222
+ # dd-trace-rb logging for now. Could change to a
223
+ # dedicated environment variable in the future but
224
+ # will likely need a way to turn on remote config
225
+ # debugging (since DI uses RC for configuration).
226
+ o.env 'DD_TRACE_DEBUG'
227
+ end
228
+
229
+ # If the CPU time consumed by the thread performing instrumentation
230
+ # exceeds this amount, the offending probe will be automatically disabled.
231
+ # Set to nil to disable the circuit breaker.
232
+ # Set to zero to disable every probe after it executes once.
233
+ option :max_processing_time do |o|
234
+ o.type :float
235
+ o.default 0.5
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
7
242
  end
8
243
  end
9
244
  end
10
-
11
- require_relative "configuration/settings"
@@ -89,6 +89,11 @@ module Datadog
89
89
  # from the method but from outside of the method).
90
90
  Location = Struct.new(:path, :lineno, :label)
91
91
 
92
+ # Method probes can only target instance methods. The implementation uses
93
+ # Module#prepend with a module that defines an instance method matching the
94
+ # probe's target — class/singleton methods (def self.foo, module_function)
95
+ # are not reachable via prepend on the class itself. Line probes are
96
+ # unaffected since they install via TracePoint, not method dispatch.
92
97
  def hook_method(probe, responder)
93
98
  lock.synchronize do
94
99
  if probe.instrumentation_module
@@ -150,7 +155,7 @@ module Datadog
150
155
  rescue => nested_exc
151
156
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
152
157
 
153
- instrumenter.logger.debug { "di: error in probe condition evaluation failed callback: #{nested_exc.class}: #{nested_exc}" }
158
+ instrumenter.logger.debug { "di: error in probe condition evaluation failed callback: #{nested_exc.class}: #{nested_exc.message}" }
154
159
  instrumenter.telemetry&.report(nested_exc, description: "Error in probe condition evaluation failed callback")
155
160
  end
156
161
  else
@@ -158,7 +163,7 @@ module Datadog
158
163
 
159
164
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
160
165
 
161
- instrumenter.logger.debug { "di: error evaluating condition without context (tracer bug?): #{exc.class}: #{exc}" }
166
+ instrumenter.logger.debug { "di: error evaluating condition without context (tracer bug?): #{exc.class}: #{exc.message}" }
162
167
  instrumenter.telemetry&.report(exc, description: "Error evaluating condition without context")
163
168
  # If execution gets here, there is probably a bug in the tracer.
164
169
  end
@@ -229,8 +234,7 @@ module Datadog
229
234
  # that location here.
230
235
  []
231
236
  end
232
- # Steep: https://github.com/ruby/rbs/pull/2745
233
- caller_locs = method_frame + caller_locations # steep:ignore ArgumentTypeMismatch
237
+ caller_locs = method_frame + caller_locations
234
238
  # TODO capture arguments at exit
235
239
 
236
240
  context = Context.new(locals: nil, target_self: self,
@@ -239,9 +243,16 @@ module Datadog
239
243
  caller_locations: caller_locs,
240
244
  return_value: rv, duration: duration, exception: exc,)
241
245
 
242
- responder.probe_executed_callback(context)
246
+ begin
247
+ responder.probe_executed_callback(context)
248
+
249
+ instrumenter.send(:check_and_disable_if_exceeded, probe, responder, di_start_time, di_duration)
250
+ rescue => di_exc
251
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
243
252
 
244
- instrumenter.send(:check_and_disable_if_exceeded, probe, responder, di_start_time, di_duration)
253
+ instrumenter.logger.debug { "di: unhandled exception in method probe: #{di_exc.class}: #{di_exc.message}" }
254
+ instrumenter.telemetry&.report(di_exc, description: "Unhandled exception in method probe")
255
+ end
245
256
 
246
257
  if exc
247
258
  raise exc
@@ -331,7 +342,11 @@ module Datadog
331
342
  # Steep: Complex type narrowing (before calling hook_line,
332
343
  # we check that probe.line? is true which itself checks that probe.file is not nil)
333
344
  # Annotation do not work here as `file` is a method on probe, not a local variable.
334
- ret = code_tracker.iseqs_for_path_suffix(probe.file) # steep:ignore ArgumentTypeMismatch
345
+ ret = if code_tracker.respond_to?(:iseq_for_line)
346
+ code_tracker.iseq_for_line(probe.file, line_no) # steep:ignore ArgumentTypeMismatch
347
+ else
348
+ code_tracker.iseqs_for_path_suffix(probe.file) # steep:ignore ArgumentTypeMismatch
349
+ end
335
350
  unless ret
336
351
  if permit_untargeted_trace_points
337
352
  # Continue withoout targeting the trace point.
@@ -349,16 +364,16 @@ module Datadog
349
364
  # to instrument and install the hook when the file in
350
365
  # question is loaded (and hopefully, by then code tracking
351
366
  # is active, otherwise the line will never be instrumented.)
352
- raise_if_probe_in_loaded_features(probe)
353
- raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
367
+ raise_if_probe_in_loaded_features(probe, line_no, code_tracker)
368
+ raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}:#{line_no}"
354
369
  end
355
370
  end
356
371
  elsif !permit_untargeted_trace_points
357
372
  # Same as previous comment, if untargeted trace points are not
358
373
  # explicitly defined, and we do not have code tracking, do not
359
374
  # instrument the method.
360
- raise_if_probe_in_loaded_features(probe)
361
- raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
375
+ raise_if_probe_in_loaded_features(probe, line_no, nil)
376
+ raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}:#{line_no}"
362
377
  end
363
378
 
364
379
  if ret
@@ -530,7 +545,7 @@ module Datadog
530
545
  rescue => nested_exc
531
546
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
532
547
 
533
- logger.debug { "di: error in probe condition evaluation failed callback: #{nested_exc.class}: #{nested_exc}" }
548
+ logger.debug { "di: error in probe condition evaluation failed callback: #{nested_exc.class}: #{nested_exc.message}" }
534
549
  telemetry&.report(nested_exc, description: "Error in probe condition evaluation failed callback")
535
550
  end
536
551
 
@@ -540,7 +555,7 @@ module Datadog
540
555
 
541
556
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
542
557
 
543
- logger.debug { "di: error evaluating condition without context (tracer bug?): #{exc.class}: #{exc}" }
558
+ logger.debug { "di: error evaluating condition without context (tracer bug?): #{exc.class}: #{exc.message}" }
544
559
  telemetry&.report(exc, description: "Error evaluating condition without context")
545
560
  # If execution gets here, there is probably a bug in the tracer.
546
561
  end
@@ -562,7 +577,7 @@ module Datadog
562
577
  check_and_disable_if_exceeded(probe, responder, di_start_time)
563
578
  rescue => exc
564
579
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
565
- logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
580
+ logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc.message}" }
566
581
  telemetry&.report(exc, description: "Unhandled exception in line trace point")
567
582
  # TODO test this path
568
583
  end
@@ -600,23 +615,34 @@ module Datadog
600
615
  end
601
616
  end
602
617
 
603
- def raise_if_probe_in_loaded_features(probe)
618
+ def raise_if_probe_in_loaded_features(probe, line_no, code_tracker)
604
619
  return unless probe.file
605
620
 
606
- # If the probe file is in the list of loaded files
607
- # (as per $LOADED_FEATURES, using either exact or suffix match),
608
- # raise an error indicating that
609
- # code tracker is missing the loaded file because the file
610
- # won't be loaded again (DI only works in production environments
611
- # that do not normally reload code).
612
- if $LOADED_FEATURES.include?(probe.file)
613
- raise Error::DITargetNotInRegistry, "File loaded but is not in code tracker registry: #{probe.file}"
621
+ # Find the loaded path matching the probe file.
622
+ loaded_path = if $LOADED_FEATURES.include?(probe.file)
623
+ probe.file
624
+ else
625
+ # Expensive suffix check.
626
+ $LOADED_FEATURES.find { |path| Utils.path_matches_suffix?(path, probe.file) }
614
627
  end
615
- # Ths is an expensive check
616
- $LOADED_FEATURES.each do |path|
617
- if Utils.path_matches_suffix?(path, probe.file)
618
- raise Error::DITargetNotInRegistry, "File matching probe path (#{probe.file}) was loaded and is not in code tracker registry: #{path}"
619
- end
628
+
629
+ return unless loaded_path
630
+
631
+ # Distinguish between "no iseqs at all" and "has per-method iseqs
632
+ # but none cover this line".
633
+ has_per_method = code_tracker&.send(:instance_variable_defined?, :@per_method_registry) &&
634
+ code_tracker.send(:per_method_registry).key?(loaded_path)
635
+
636
+ if has_per_method
637
+ raise Error::DITargetNotInRegistry,
638
+ "File #{loaded_path} is loaded and has per-method iseqs, " \
639
+ "but none cover line #{line_no}. " \
640
+ "The line may be in file-level setup code outside any method."
641
+ else
642
+ raise Error::DITargetNotInRegistry,
643
+ "File #{loaded_path} is loaded but has no surviving iseqs " \
644
+ "(whole-file iseq was garbage collected and no per-method iseqs remain). " \
645
+ "Line probes cannot target this file."
620
646
  end
621
647
  end
622
648
 
@@ -624,7 +650,7 @@ module Datadog
624
650
  def symbolize_class_name(cls_name)
625
651
  Object.const_get(cls_name)
626
652
  rescue NameError => exc
627
- raise Error::DITargetNotDefined, "Class not defined: #{cls_name}: #{exc.class}: #{exc}"
653
+ raise Error::DITargetNotDefined, "Class not defined: #{cls_name}: #{exc.class}: #{exc.message}"
628
654
  end
629
655
  end
630
656
  end
@@ -58,7 +58,7 @@ module Datadog
58
58
  condition: cond,
59
59
  )
60
60
  rescue KeyError => exc
61
- raise ArgumentError, "Malformed remote configuration entry for probe: #{exc.class}: #{exc}: #{config}"
61
+ raise ArgumentError, "Malformed remote configuration entry for probe: #{exc.class}: #{exc.message}: #{config}"
62
62
  end
63
63
 
64
64
  def build_template_segments(segments)
@@ -50,7 +50,7 @@ module Datadog
50
50
  rescue => exc
51
51
  raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
52
52
 
53
- component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI probe file loader: #{exc.class}: #{exc}" }
53
+ component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI probe file loader: #{exc.class}: #{exc.message}" }
54
54
  component.telemetry&.report(exc, description: "Unhandled exception adding probe in DI probe file loader")
55
55
 
56
56
  # TODO test this path
@@ -64,7 +64,7 @@ module Datadog
64
64
  raise
65
65
  end
66
66
 
67
- component.logger.debug { "di: unhandled exception handling a probe in DI probe file loader: #{exc.class}: #{exc}" }
67
+ component.logger.debug { "di: unhandled exception handling a probe in DI probe file loader: #{exc.class}: #{exc.message}" }
68
68
  component.telemetry&.report(exc, description: "Unhandled exception handling probe in DI probe file loader")
69
69
  end
70
70
  rescue
@@ -30,7 +30,7 @@ module Datadog
30
30
  install_pending_method_probes(tp.self)
31
31
  rescue => exc
32
32
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
33
- logger.debug { "di: unhandled exception in definition trace point: #{exc.class}: #{exc}" }
33
+ logger.debug { "di: unhandled exception in definition trace point: #{exc.class}: #{exc.message}" }
34
34
  telemetry&.report(exc, description: "Unhandled exception in definition trace point")
35
35
  # TODO test this path
36
36
  end
@@ -133,13 +133,13 @@ module Datadog
133
133
  # In "propagate all exceptions" mode we will try to instrument again.
134
134
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
135
135
 
136
- logger.debug { "di: error processing probe configuration: #{exc.class}: #{exc}" }
136
+ logger.debug { "di: error processing probe configuration: #{exc.class}: #{exc.message}" }
137
137
  telemetry&.report(exc, description: "Error processing probe configuration")
138
138
 
139
139
  payload = probe_notification_builder.build_errored(probe, exc)
140
140
  probe_notifier_worker.add_status(payload, probe: probe)
141
141
 
142
- probe_repository.add_failed(probe.id, "#{exc.class}: #{exc}")
142
+ probe_repository.add_failed(probe.id, "#{exc.class}: #{exc.message}")
143
143
 
144
144
  raise
145
145
  end
@@ -164,7 +164,7 @@ module Datadog
164
164
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
165
165
  # Silence all exceptions?
166
166
  # TODO should we propagate here and rescue upstream?
167
- logger.debug { "di: error removing #{probe.type} probe at #{probe.location} (#{probe.id}): #{exc.class}: #{exc}" }
167
+ logger.debug { "di: error removing #{probe.type} probe at #{probe.location} (#{probe.id}): #{exc.class}: #{exc.message}" }
168
168
  telemetry&.report(exc, description: "Error removing probe")
169
169
  end
170
170
  end
@@ -193,13 +193,13 @@ module Datadog
193
193
  rescue => exc
194
194
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
195
195
 
196
- logger.debug { "di: error installing #{probe.type} probe at #{probe.location} (#{probe.id}) after class is defined: #{exc.class}: #{exc}" }
196
+ logger.debug { "di: error installing #{probe.type} probe at #{probe.location} (#{probe.id}) after class is defined: #{exc.class}: #{exc.message}" }
197
197
  telemetry&.report(exc, description: "Error installing probe after class is defined")
198
198
 
199
199
  payload = probe_notification_builder.build_errored(probe, exc)
200
200
  probe_notifier_worker.add_status(payload, probe: probe)
201
201
 
202
- probe_repository.add_failed(probe.id, "#{exc.class}: #{exc}")
202
+ probe_repository.add_failed(probe.id, "#{exc.class}: #{exc.message}")
203
203
  end
204
204
  end
205
205
  end
@@ -79,7 +79,7 @@ module Datadog
79
79
  },
80
80
  return: {
81
81
  arguments: return_arguments,
82
- throwable: nil,
82
+ throwable: context.exception ? serialize_throwable(context.exception) : nil,
83
83
  },
84
84
  }
85
85
  elsif probe.line?
@@ -153,6 +153,114 @@ module Datadog
153
153
 
154
154
  private
155
155
 
156
+ # Serializes an exception for the throwable field in snapshot captures.
157
+ #
158
+ # Uses the C extension's exception_message to get the original message
159
+ # without invoking any Ruby-level message method override, which
160
+ # could be customer code.
161
+ #
162
+ # Caveats:
163
+ #
164
+ # 1. The value returned by exception_message is not guaranteed to be
165
+ # a string — it is whatever was passed to the Exception constructor.
166
+ # Calling .to_s on an arbitrary object would invoke customer code,
167
+ # violating DI's constraint of never executing customer methods
168
+ # during instrumentation. We only use the value directly when it
169
+ # is a String; for non-string values we return a redacted
170
+ # placeholder (reporting the class name would duplicate the
171
+ # exception type already present in the :type field).
172
+ #
173
+ # 2. Custom exception classes may not store a meaningful message via
174
+ # the constructor (e.g. they may compute it in an overridden
175
+ # +message+ method). In such cases exception_message may return
176
+ # nil or an unrelated constructor argument. This is acceptable:
177
+ # we still report the exception type, and a missing/wrong message
178
+ # is better than invoking customer code or reporting nothing.
179
+ #
180
+ # @param exception [Exception] the exception to serialize
181
+ # @return [Hash{Symbol => String?}] hash with :type and :message keys
182
+ def serialize_throwable(exception)
183
+ msg = DI.exception_message(exception)
184
+ message = if msg.nil? || String === msg
185
+ msg
186
+ else
187
+ # Non-string constructor argument — return a redacted placeholder
188
+ # rather than calling .to_s which could be customer code.
189
+ # The exception class is already reported via the :type field.
190
+ '<REDACTED: not a string value>'
191
+ end
192
+ # Prefer backtrace_locations (structured Location objects) over
193
+ # backtrace (formatted strings that need regex parsing).
194
+ #
195
+ # However, backtrace_locations returns nil when someone has called
196
+ # Exception#set_backtrace with Array<String> — the VM cannot
197
+ # reconstruct Location objects from formatted strings. This happens
198
+ # in exception wrapping patterns (catch, create new exception, copy
199
+ # original's string backtrace via set_backtrace, re-raise).
200
+ # In that case, fall back to backtrace strings.
201
+ #
202
+ # Both accessors use the UnboundMethod trick to bypass subclass
203
+ # overrides, consistent with the rest of this method.
204
+ #
205
+ # If a subclass overrides #backtrace, MRI's raise never stores
206
+ # the real backtrace — both backtrace_locations and backtrace
207
+ # return nil, and stacktrace is [].
208
+ # This is unrecoverable without calling customer code.
209
+ # See DI::EXCEPTION_BACKTRACE comment for details.
210
+ locations = DI::EXCEPTION_BACKTRACE_LOCATIONS.bind(exception).call
211
+ stacktrace = if locations
212
+ format_backtrace_locations(locations)
213
+ else
214
+ format_backtrace_strings(DI::EXCEPTION_BACKTRACE.bind(exception).call)
215
+ end
216
+ {
217
+ type: exception.class.name,
218
+ message: message,
219
+ stacktrace: stacktrace,
220
+ }
221
+ end
222
+
223
+ # Matches Ruby backtrace frame format: "/path/file.rb:42:in `method_name'"
224
+ # Captures: $1 = file path, $2 = line number, $3 = method name
225
+ BACKTRACE_FRAME_PATTERN = /\A(.+):(\d+):in\s+[`'](.+)'\z/
226
+
227
+ # Converts backtrace locations into the stack frame format
228
+ # expected by the Datadog UI.
229
+ #
230
+ # Uses Thread::Backtrace::Location objects which provide structured
231
+ # path/lineno/label directly, avoiding the round-trip of formatting
232
+ # to strings and regex-parsing back.
233
+ #
234
+ # @param locations [Array<Thread::Backtrace::Location>]
235
+ # @return [Array<Hash>]
236
+ def format_backtrace_locations(locations)
237
+ locations.map do |loc|
238
+ {fileName: loc.path, function: loc.label, lineNumber: loc.lineno}
239
+ end
240
+ end
241
+
242
+ # Parses Ruby backtrace strings into the stack frame format
243
+ # expected by the Datadog UI.
244
+ #
245
+ # Fallback for when backtrace_locations returns nil (see
246
+ # serialize_throwable for details on when this happens).
247
+ #
248
+ # Ruby backtrace format: "/path/file.rb:42:in `method_name'"
249
+ #
250
+ # @param backtrace [Array<String>, nil] from Exception#backtrace
251
+ # @return [Array<Hash>]
252
+ def format_backtrace_strings(backtrace)
253
+ return [] if backtrace.nil?
254
+
255
+ backtrace.map do |frame|
256
+ if frame =~ BACKTRACE_FRAME_PATTERN
257
+ {fileName: $1, function: $3, lineNumber: $2.to_i}
258
+ else
259
+ {fileName: frame, function: '', lineNumber: 0}
260
+ end
261
+ end
262
+ end
263
+
156
264
  def build_snapshot_base(context, evaluation_errors: [], captures: nil, message: nil)
157
265
  probe = context.probe
158
266
 
@@ -260,7 +368,7 @@ module Datadog
260
368
  end
261
369
  rescue => exc
262
370
  evaluation_errors << {
263
- message: "#{exc.class}: #{exc}",
371
+ message: "#{exc.class}: #{exc.message}",
264
372
  expr: segment.dsl_expr,
265
373
  }
266
374
  '[evaluation error]'
@@ -95,7 +95,7 @@ module Datadog
95
95
  rescue => exc
96
96
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
97
97
 
98
- logger.debug { "di: error in probe notifier worker: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
98
+ logger.debug { "di: error in probe notifier worker: #{exc.class}: #{exc.message} (at #{exc.backtrace.first})" }
99
99
  telemetry&.report(exc, description: "Error in probe notifier worker")
100
100
  end
101
101
  @lock.synchronize do
@@ -320,7 +320,7 @@ module Datadog
320
320
  end
321
321
  rescue => exc
322
322
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
323
- logger.debug { "di: failed to send #{event_name}: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
323
+ logger.debug { "di: failed to send #{event_name}: #{exc.class}: #{exc.message} (at #{exc.backtrace.first})" }
324
324
  telemetry&.report(exc, description: "Error sending #{event_type}")
325
325
  end
326
326
  end