datadog 2.30.0 → 2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -1
  3. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +18 -0
  4. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +10 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +2 -0
  6. data/ext/libdatadog_api/crashtracker.c +5 -8
  7. data/ext/libdatadog_api/datadog_ruby_common.c +18 -0
  8. data/ext/libdatadog_api/datadog_ruby_common.h +10 -0
  9. data/ext/libdatadog_api/di.c +79 -0
  10. data/ext/libdatadog_api/extconf.rb +2 -0
  11. data/ext/libdatadog_api/init.c +5 -2
  12. data/ext/libdatadog_extconf_helpers.rb +9 -1
  13. data/lib/datadog/ai_guard/component.rb +2 -0
  14. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +41 -3
  15. data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
  16. data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
  17. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +3 -1
  18. data/lib/datadog/ai_guard/evaluation/request.rb +14 -9
  19. data/lib/datadog/ai_guard/evaluation/result.rb +3 -1
  20. data/lib/datadog/ai_guard/evaluation.rb +36 -7
  21. data/lib/datadog/ai_guard.rb +26 -8
  22. data/lib/datadog/appsec/autoload.rb +1 -1
  23. data/lib/datadog/appsec/component.rb +11 -7
  24. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  25. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +6 -7
  26. data/lib/datadog/appsec/contrib/rails/patcher.rb +2 -2
  27. data/lib/datadog/appsec/instrumentation/gateway.rb +0 -13
  28. data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -0
  29. data/lib/datadog/appsec/utils/http/media_type.rb +1 -2
  30. data/lib/datadog/appsec/utils/http/url_encoded.rb +2 -2
  31. data/lib/datadog/appsec.rb +5 -9
  32. data/lib/datadog/core/configuration/base.rb +17 -5
  33. data/lib/datadog/core/configuration/components.rb +21 -8
  34. data/lib/datadog/core/configuration/config_helper.rb +9 -0
  35. data/lib/datadog/core/configuration/option.rb +30 -5
  36. data/lib/datadog/core/configuration/option_definition.rb +38 -12
  37. data/lib/datadog/core/configuration/options.rb +40 -6
  38. data/lib/datadog/core/configuration/settings.rb +15 -0
  39. data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
  40. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  41. data/lib/datadog/core/contrib/rails/utils.rb +7 -3
  42. data/lib/datadog/core/crashtracking/component.rb +3 -3
  43. data/lib/datadog/core/environment/container.rb +2 -2
  44. data/lib/datadog/core/environment/ext.rb +1 -0
  45. data/lib/datadog/core/environment/identity.rb +25 -3
  46. data/lib/datadog/core/environment/process.rb +12 -0
  47. data/lib/datadog/core/metrics/client.rb +5 -5
  48. data/lib/datadog/core/remote/component.rb +38 -21
  49. data/lib/datadog/core/runtime/metrics.rb +1 -1
  50. data/lib/datadog/core/telemetry/component.rb +3 -0
  51. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +2 -3
  52. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  53. data/lib/datadog/core/telemetry/event/app_started.rb +151 -169
  54. data/lib/datadog/core/telemetry/event.rb +1 -7
  55. data/lib/datadog/core/telemetry/ext.rb +1 -0
  56. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -0
  57. data/lib/datadog/core/telemetry/worker.rb +20 -0
  58. data/lib/datadog/core/utils/only_once.rb +1 -1
  59. data/lib/datadog/core/utils/spawn_monkey_patch.rb +36 -0
  60. data/lib/datadog/core/workers/async.rb +1 -1
  61. data/lib/datadog/core.rb +0 -1
  62. data/lib/datadog/data_streams/pathway_context.rb +1 -1
  63. data/lib/datadog/di/boot.rb +2 -4
  64. data/lib/datadog/di/component.rb +4 -0
  65. data/lib/datadog/di/instrumenter.rb +10 -4
  66. data/lib/datadog/di/probe_notification_builder.rb +109 -1
  67. data/lib/datadog/di/serializer.rb +1 -1
  68. data/lib/datadog/di.rb +81 -0
  69. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  70. data/lib/datadog/open_feature/evaluation_engine.rb +1 -1
  71. data/lib/datadog/open_feature/exposures/reporter.rb +1 -1
  72. data/lib/datadog/open_feature/exposures/worker.rb +1 -1
  73. data/lib/datadog/open_feature/remote.rb +1 -1
  74. data/lib/datadog/open_feature/transport.rb +1 -1
  75. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -0
  76. data/lib/datadog/profiling/collectors/code_provenance.rb +2 -3
  77. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  78. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  79. data/lib/datadog/profiling/component.rb +11 -1
  80. data/lib/datadog/profiling/load_native_extension.rb +1 -1
  81. data/lib/datadog/profiling/profiler.rb +0 -4
  82. data/lib/datadog/profiling/scheduler.rb +2 -2
  83. data/lib/datadog/profiling/tasks/exec.rb +2 -2
  84. data/lib/datadog/profiling/tasks/setup.rb +2 -2
  85. data/lib/datadog/profiling.rb +1 -2
  86. data/lib/datadog/single_step_instrument.rb +1 -1
  87. data/lib/datadog/tracing/buffer.rb +3 -3
  88. data/lib/datadog/tracing/component.rb +11 -0
  89. data/lib/datadog/tracing/configuration/settings.rb +2 -1
  90. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  91. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +20 -0
  92. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
  93. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  94. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  95. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  96. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  97. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  98. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  99. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  100. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  101. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  102. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  103. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  104. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  105. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +1 -1
  106. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
  107. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
  108. data/lib/datadog/tracing/contrib/configurable.rb +18 -3
  109. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +1 -1
  110. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
  111. data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -2
  112. data/lib/datadog/tracing/contrib/grape/endpoint.rb +5 -5
  113. data/lib/datadog/tracing/contrib/http/instrumentation.rb +1 -1
  114. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +1 -1
  115. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +1 -1
  116. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  117. data/lib/datadog/tracing/contrib/rails/patcher.rb +0 -1
  118. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  119. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
  120. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  121. data/lib/datadog/tracing/contrib/status_range_matcher.rb +4 -0
  122. data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
  123. data/lib/datadog/tracing/distributed/datadog.rb +4 -2
  124. data/lib/datadog/tracing/event.rb +1 -1
  125. data/lib/datadog/tracing/remote.rb +1 -1
  126. data/lib/datadog/tracing/sampling/ext.rb +2 -0
  127. data/lib/datadog/tracing/sampling/priority_sampler.rb +13 -0
  128. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  129. data/lib/datadog/tracing/sampling/rule_sampler.rb +54 -25
  130. data/lib/datadog/tracing/sampling/span/rule_parser.rb +1 -1
  131. data/lib/datadog/tracing/span_operation.rb +1 -1
  132. data/lib/datadog/tracing/trace_operation.rb +50 -6
  133. data/lib/datadog/tracing/tracer.rb +25 -0
  134. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  135. data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
  136. data/lib/datadog/version.rb +1 -1
  137. metadata +13 -7
@@ -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
 
@@ -186,7 +186,7 @@ module Datadog
186
186
  # surface errors so they can fix their serializers) or they may be defined
187
187
  # internally by dd-trace-rb (in which case we need to fix them). We use
188
188
  # WARN level to surface these errors in either case.
189
- Datadog.logger.warn("DI: Custom serializer condition failed: #{e.class}: #{e.message}")
189
+ Datadog.logger.warn("DI: Custom serializer condition failed: #{e.class}: #{e}")
190
190
  telemetry&.report(e, description: "Custom serializer condition failed")
191
191
  next
192
192
  end
data/lib/datadog/di.rb CHANGED
@@ -11,11 +11,92 @@ module Datadog
11
11
  module DI
12
12
  INSTRUMENTED_COUNTERS_LOCK = Mutex.new
13
13
 
14
+ # Captured at load time from Exception itself (not a subclass).
15
+ # Used to bypass subclass overrides of backtrace_locations.
16
+ #
17
+ # This does NOT protect against monkeypatching Exception#backtrace_locations
18
+ # before dd-trace-rb loads — in that case we'd capture the monkeypatch.
19
+ # The practical threat model is customer subclasses overriding the method:
20
+ #
21
+ # class MyError < StandardError
22
+ # def backtrace_locations; []; end
23
+ # end
24
+ #
25
+ # The UnboundMethod bypasses subclass overrides: bind(exception).call
26
+ # always dispatches to the original Exception implementation.
27
+ #
28
+ # Note: if the subclass overrides #backtrace (not #backtrace_locations),
29
+ # MRI's setup_exception skips storing the VM backtrace entirely — both
30
+ # @bt and @bt_locations stay nil. In that case this UnboundMethod also
31
+ # returns nil. See EXCEPTION_BACKTRACE comment for details.
32
+ EXCEPTION_BACKTRACE_LOCATIONS = Exception.instance_method(:backtrace_locations)
33
+
34
+ # Same UnboundMethod trick for Exception#backtrace (Array<String>).
35
+ # Used as a fallback when backtrace_locations returns nil — which happens
36
+ # when someone calls Exception#set_backtrace with an Array<String>.
37
+ #
38
+ # set_backtrace accepts Array<String> or nil. When called with strings,
39
+ # it replaces the VM-level backtrace: backtrace returns the new strings,
40
+ # but backtrace_locations returns nil because the VM cannot reconstruct
41
+ # Location objects from formatted strings. This occurs in exception
42
+ # wrapping patterns where a library catches an exception, creates a new
43
+ # one, and copies the original's string backtrace onto it via
44
+ # set_backtrace before re-raising.
45
+ #
46
+ # Ruby 3.4+ also allows set_backtrace(Array<Location>), which preserves
47
+ # backtrace_locations — but older Rubies and most existing code use
48
+ # the string form.
49
+ #
50
+ # Like EXCEPTION_BACKTRACE_LOCATIONS, this UnboundMethod bypasses
51
+ # subclass overrides of #backtrace: bind(exception).call dispatches
52
+ # to Exception#backtrace regardless of what the subclass defines.
53
+ #
54
+ # However, when a subclass overrides #backtrace, MRI's setup_exception
55
+ # (eval.c) calls the override via rb_get_backtrace during raise. If it
56
+ # gets a non-nil result, it skips storing the VM backtrace in @bt and
57
+ # @bt_locations entirely. So the UnboundMethod bypasses the override
58
+ # at dispatch but reads nil from @bt because the data was never stored.
59
+ #
60
+ # This constant is used as a fallback when backtrace_locations returns
61
+ # nil. In the common set_backtrace-with-strings case, no subclass
62
+ # override is involved and the fallback works. The only unrecoverable
63
+ # case: a subclass overrides #backtrace, the exception is raised
64
+ # normally, and set_backtrace is never called. Both @bt and
65
+ # @bt_locations are nil — DI reports an empty stacktrace (type and
66
+ # message are still reported).
67
+ EXCEPTION_BACKTRACE = Exception.instance_method(:backtrace)
68
+
14
69
  class << self
15
70
  def enabled?
16
71
  Datadog.configuration.dynamic_instrumentation.enabled
17
72
  end
18
73
 
74
+ # Returns iseqs that correspond to loaded files (filtering out eval'd code).
75
+ #
76
+ # There are several types of iseqs returned by +all_iseqs+:
77
+ #
78
+ # 1. Eval'd code — these have a nil +absolute_path+ and are filtered out here.
79
+ # 2. Whole-file iseqs — have +absolute_path+ set and +first_lineno+ of 0.
80
+ # Only available for a subset of loaded files (the full-file iseq may be
81
+ # garbage collected after loading completes). Easiest to work with since
82
+ # we just match the file path to the probe specification.
83
+ # 3. Per-method iseqs — have +absolute_path+ set and +first_lineno+ > 0.
84
+ # Often the only iseqs available for third-party code. Require identifying
85
+ # the correct iseq containing the target line, which may involve examining
86
+ # the iseq's +trace_points+ since +define_method+ can create nested,
87
+ # non-contiguous line ranges.
88
+ #
89
+ # Note: the same line of code can appear in multiple iseqs (e.g. when
90
+ # +define_method+ is used inside a method). DI treats this as an error
91
+ # since a probe must resolve to exactly one code location.
92
+ #
93
+ # @return [Array<RubyVM::InstructionSequence>] iseqs with non-nil +absolute_path+
94
+ def file_iseqs
95
+ all_iseqs.select do |iseq|
96
+ iseq.absolute_path
97
+ end
98
+ end
99
+
19
100
  # This method is called from DI Remote handler to issue DI operations
20
101
  # to the probe manager (add or remove probes).
21
102
  #
@@ -30,7 +30,7 @@ module Datadog
30
30
  Process.setrlimit(:CORE, maximum_size)
31
31
  rescue => e
32
32
  Kernel.warn(
33
- "[datadog] Failed to enable core dumps. Cause: #{e.class.name} #{e.message} " \
33
+ "[datadog] Failed to enable core dumps. Cause: #{e.class}: #{e} " \
34
34
  "Location: #{Array(e.backtrace).first}"
35
35
  )
36
36
  return
@@ -60,7 +60,7 @@ module Datadog
60
60
  rescue => e
61
61
  message = 'OpenFeature: Failed to reconfigure, reverting to the previous configuration'
62
62
 
63
- @logger.error("#{message}, #{e.class}: #{e.message}")
63
+ @logger.error("#{message}, #{e.class}: #{e}")
64
64
  @telemetry.report(e, description: "#{message} (#{e.class})")
65
65
 
66
66
  raise ReconfigurationError, e.message
@@ -29,7 +29,7 @@ module Datadog
29
29
  event = Event.build(result, flag_key: flag_key, context: context)
30
30
  @worker.enqueue(event)
31
31
  rescue => e
32
- @logger.debug { "OpenFeature: Failed to report resolution details: #{e.class}: #{e.message}" }
32
+ @logger.debug { "OpenFeature: Failed to report resolution details: #{e.class}: #{e}" }
33
33
  @telemetry.report(e, description: 'OpenFeature: Failed to report resolution details')
34
34
 
35
35
  false
@@ -105,7 +105,7 @@ module Datadog
105
105
 
106
106
  response
107
107
  rescue => e
108
- @logger.debug { "OpenFeature: Failed to flush resolution details events: #{e.class}: #{e.message}" }
108
+ @logger.debug { "OpenFeature: Failed to flush resolution details events: #{e.class}: #{e}" }
109
109
  @telemetry.report(e, description: 'OpenFeature: Failed to flush resolution details events')
110
110
 
111
111
  nil
@@ -41,7 +41,7 @@ module Datadog
41
41
  engine.reconfigure!(read_content(content))
42
42
  content.applied
43
43
  rescue EvaluationEngine::ReconfigurationError => e
44
- content.errored("Error applying OpenFeature configuration: #{e.message}")
44
+ content.errored("Error applying OpenFeature configuration: #{e.class}: #{e}")
45
45
  end
46
46
  when :delete
47
47
  # NOTE: For now, we treat deletion as clearing the configuration
@@ -55,7 +55,7 @@ module Datadog
55
55
  @api.call(env)
56
56
  end
57
57
  rescue => e
58
- message = "Internal error during request. Cause: #{e.class.name} #{e.message} " \
58
+ message = "Internal error during request. Cause: #{e.class}: #{e} " \
59
59
  "Location: #{Array(e.backtrace).first}"
60
60
  @logger.debug(message)
61
61
 
@@ -76,6 +76,7 @@ module Datadog
76
76
  end
77
77
 
78
78
  option :headers do |o|
79
+ o.skip_telemetry true
79
80
  o.type :hash
80
81
  o.env 'OTEL_EXPORTER_OTLP_HEADERS'
81
82
  o.default { {} }
@@ -131,6 +132,7 @@ module Datadog
131
132
  end
132
133
 
133
134
  option :headers do |o|
135
+ o.skip_telemetry true
134
136
  o.type :hash, nilable: true
135
137
  o.env 'OTEL_EXPORTER_OTLP_METRICS_HEADERS'
136
138
  o.default nil
@@ -135,7 +135,7 @@ module Datadog
135
135
  rescue Exception => e # rubocop:disable Lint/RescueException
136
136
  Datadog.logger.debug(
137
137
  "CodeProvenance#bundler_bin_path failed. " \
138
- "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
138
+ "Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
139
139
  )
140
140
  nil
141
141
  end
@@ -161,8 +161,7 @@ module Datadog
161
161
  end
162
162
 
163
163
  def to_json(arg = nil)
164
- # Steep: https://github.com/ruby/rbs/pull/2691 (remove after RBS 4.0 release)
165
- {kind: @kind, name: @name, version: @version, paths: @paths}.to_json(arg) # steep:ignore ArgumentTypeMismatch
164
+ {kind: @kind, name: @name, version: @version, paths: @paths}.to_json(arg)
166
165
  end
167
166
 
168
167
  def path
@@ -93,7 +93,7 @@ module Datadog
93
93
  operation_name = self.class._native_failure_exception_during_operation(self).inspect
94
94
  Datadog.logger.warn(
95
95
  "CpuAndWallTimeWorker thread error. " \
96
- "Operation: #{operation_name} Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
96
+ "Operation: #{operation_name} Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
97
97
  )
98
98
  on_failure_proc&.call
99
99
  Datadog::Core::Telemetry::Logger.report(e, description: "CpuAndWallTimeWorker thread error: #{operation_name}")
@@ -39,7 +39,7 @@ module Datadog
39
39
  @failure_exception = e
40
40
  Datadog.logger.warn(
41
41
  "IdleSamplingHelper thread error. " \
42
- "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
42
+ "Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
43
43
  )
44
44
  Datadog::Core::Telemetry::Logger.report(e, description: "IdleSamplingHelper thread error")
45
45
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../core/telemetry/logger"
4
+
3
5
  module Datadog
4
6
  module Profiling
5
7
  # Responsible for wiring up the Profiler for execution
@@ -90,6 +92,14 @@ module Datadog
90
92
  end
91
93
 
92
94
  [profiler, {profiling_enabled: true}]
95
+ rescue Exception => e # rubocop:disable Lint/RescueException
96
+ logger.warn do
97
+ "Failed to initialize profiling: #{e.class}: #{e} " \
98
+ "Location: #{Array(e.backtrace).first}"
99
+ end
100
+ Datadog::Core::Telemetry::Logger.report(e, description: "Failed to initialize profiling")
101
+
102
+ [nil, {profiling_enabled: false}]
93
103
  end
94
104
 
95
105
  private_class_method def self.build_thread_context_collector(settings, recorder, optional_tracer, timeline_enabled)
@@ -368,7 +378,7 @@ module Datadog
368
378
  rescue StandardError, LoadError => e
369
379
  logger.warn(
370
380
  "Failed to probe `mysql2` gem information. " \
371
- "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
381
+ "Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
372
382
  )
373
383
 
374
384
  true
@@ -4,6 +4,6 @@ begin
4
4
  require "datadog_profiling_native_extension.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
5
5
  rescue LoadError => e
6
6
  raise LoadError,
7
- "Failed to load the profiling loader extension. To fix this, please remove and then reinstall datadog " \
7
+ "Failed to load the profiling native extension. To fix this, please remove and then reinstall datadog " \
8
8
  "(Details: #{e.message})"
9
9
  end
@@ -17,10 +17,6 @@ module Datadog
17
17
  @scheduler = scheduler
18
18
  end
19
19
 
20
- def enabled?
21
- scheduler.running?
22
- end
23
-
24
20
  def start
25
21
  after_fork! do
26
22
  worker.reset_after_fork
@@ -65,7 +65,7 @@ module Datadog
65
65
  rescue Exception => e # rubocop:disable Lint/RescueException
66
66
  Datadog.logger.warn(
67
67
  "Profiling::Scheduler thread error. " \
68
- "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
68
+ "Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
69
69
  )
70
70
  on_failure_proc&.call
71
71
  Datadog::Core::Telemetry::Logger.report(e, description: "Profiling::Scheduler thread error")
@@ -136,7 +136,7 @@ module Datadog
136
136
  transport.export(flush)
137
137
  rescue => e
138
138
  Datadog.logger.warn(
139
- "Unable to report profile. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
139
+ "Unable to report profile. Cause: #{e.class}: #{e} Location: #{Array(e.backtrace).first}"
140
140
  )
141
141
  Datadog::Core::Telemetry::Logger.report(e, description: "Unable to report profile")
142
142
  end
@@ -38,10 +38,10 @@ module Datadog
38
38
  def exec_with_error_handling(args)
39
39
  Kernel.exec(*args)
40
40
  rescue Errno::ENOENT => e
41
- Kernel.warn "ddprofrb exec failed: #{e.class.name} #{e.message} (command was '#{args.join(" ")}')"
41
+ Kernel.warn "ddprofrb exec failed: #{e.class}: #{e} (command was '#{args.join(" ")}')"
42
42
  Kernel.exit 127
43
43
  rescue Errno::EACCES, Errno::ENOEXEC => e
44
- Kernel.warn "ddprofrb exec failed: #{e.class.name} #{e.message} (command was '#{args.join(" ")}')"
44
+ Kernel.warn "ddprofrb exec failed: #{e.class}: #{e} (command was '#{args.join(" ")}')"
45
45
  Kernel.exit 126
46
46
  end
47
47
  end
@@ -16,7 +16,7 @@ module Datadog
16
16
  setup_at_fork_hooks
17
17
  rescue StandardError, ScriptError => e
18
18
  Datadog.logger.warn do
19
- "Profiler extensions unavailable. Cause: #{e.class.name} #{e.message} " \
19
+ "Profiler extensions unavailable. Cause: #{e.class}: #{e} " \
20
20
  "Location: #{Array(e.backtrace).first}"
21
21
  end
22
22
  Datadog::Core::Telemetry::Logger.report(e, description: "Profiler extensions unavailable")
@@ -31,7 +31,7 @@ module Datadog
31
31
  Profiling.start_if_enabled
32
32
  rescue => e
33
33
  Datadog.logger.warn do
34
- "Error during post-fork hooks. Cause: #{e.class.name} #{e.message} " \
34
+ "Error during post-fork hooks. Cause: #{e.class}: #{e} " \
35
35
  "Location: #{Array(e.backtrace).first}"
36
36
  end
37
37
  Datadog::Core::Telemetry::Logger.report(e, description: "Error during post-fork hooks")
@@ -61,8 +61,7 @@ module Datadog
61
61
  end
62
62
 
63
63
  def self.enabled?
64
- profiler = Datadog.send(:components).profiler
65
- !!profiler&.enabled?
64
+ !!Datadog.send(:components).profiler
66
65
  end
67
66
 
68
67
  def self.wait_until_running(timeout_seconds: 5)
@@ -17,5 +17,5 @@ begin
17
17
  require_relative 'auto_instrument'
18
18
  Datadog::SingleStepInstrument.const_set(:LOADED, true)
19
19
  rescue StandardError, LoadError => e
20
- warn "Single step instrumentation failed: #{e.class}:#{e.message}\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
20
+ warn "Single step instrumentation failed: #{e.class}: #{e}\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
21
21
  end
@@ -56,7 +56,7 @@ module Datadog
56
56
  @buffer_spans += trace.length
57
57
  rescue => e
58
58
  Datadog.logger.debug(
59
- "Failed to measure queue accept. Cause: #{e.class.name} #{e.message} Source: #{Array(e.backtrace).first}"
59
+ "Failed to measure queue accept. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
60
60
  )
61
61
  end
62
62
 
@@ -66,7 +66,7 @@ module Datadog
66
66
  @buffer_spans -= trace.length
67
67
  rescue => e
68
68
  Datadog.logger.debug(
69
- "Failed to measure queue drop. Cause: #{e.class.name} #{e.message} Source: #{Array(e.backtrace).first}"
69
+ "Failed to measure queue drop. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
70
70
  )
71
71
  end
72
72
 
@@ -91,7 +91,7 @@ module Datadog
91
91
  @buffer_spans = 0
92
92
  rescue => e
93
93
  Datadog.logger.debug(
94
- "Failed to measure queue. Cause: #{e.class.name} #{e.message} Source: #{Array(e.backtrace).first}"
94
+ "Failed to measure queue. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
95
95
  )
96
96
  end
97
97
  end
@@ -161,6 +161,17 @@ module Datadog
161
161
  @sampler.sample!(trace)
162
162
  end
163
163
 
164
+ def reconsider_sample_resource!(trace)
165
+ return unless @sampler.respond_to?(:reconsider_sample_resource!)
166
+
167
+ @sampler.reconsider_sample_resource!(trace)
168
+ end
169
+
170
+ def resource_sampling?
171
+ @sampler.respond_to?(:resource_sampling?) &&
172
+ @sampler.resource_sampling?
173
+ end
174
+
164
175
  def update(*args, **kwargs)
165
176
  return unless @sampler.respond_to?(:update)
166
177
 
@@ -430,7 +430,7 @@ module Datadog
430
430
  rescue => e
431
431
  # `File#read` errors have clear and actionable messages, no need to add extra exception info.
432
432
  Datadog.logger.warn(
433
- "Cannot read span sampling rules file `#{rules_file}`: #{e.message}." \
433
+ "Cannot read span sampling rules file `#{rules_file}`: #{e.class}: #{e}." \
434
434
  'No span sampling rules will be applied.'
435
435
  )
436
436
  nil
@@ -481,6 +481,7 @@ module Datadog
481
481
  # @default `{}`
482
482
  # @return [Hash]
483
483
  option :writer_options do |o|
484
+ o.skip_telemetry true
484
485
  o.type :hash
485
486
  o.default({})
486
487
  end
@@ -43,7 +43,7 @@ module Datadog
43
43
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
44
44
  span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_CONTROLLER)
45
45
  rescue => e
46
- Datadog.logger.error(e.message)
46
+ Datadog.logger.error("#{e.class}: #{e}")
47
47
  Datadog::Core::Telemetry::Logger.report(e)
48
48
  end
49
49
 
@@ -88,7 +88,7 @@ module Datadog
88
88
  span.finish
89
89
  end
90
90
  rescue => e
91
- Datadog.logger.error(e.message)
91
+ Datadog.logger.error("#{e.class}: #{e}")
92
92
  Datadog::Core::Telemetry::Logger.report(e)
93
93
  end
94
94
 
@@ -76,6 +76,26 @@ module Datadog
76
76
  end
77
77
  end
78
78
  end
79
+
80
+ # Since Rails 8.1, `Router#find_routes` was removed by inlining its body into `recognize`.
81
+ # https://github.com/rails/rails/commit/e533a32ddf06668dfa3dfbe9b665607e235b06ac
82
+ module RecognizeRouter
83
+ def recognize(req)
84
+ # recognize modifies SCRIPT_NAME before yielding; capture it before super.
85
+ original_script_name = req.env[SCRIPT_NAME_KEY]
86
+
87
+ super do |route, parameters|
88
+ if Instrumentation.dispatcher_route?(route)
89
+ http_route = route.path.spec.to_s
90
+ http_route = http_route.delete_suffix(FORMAT_SUFFIX)
91
+
92
+ Instrumentation.set_http_route_tags(http_route, original_script_name)
93
+ end
94
+
95
+ yield route, parameters
96
+ end
97
+ end
98
+ end
79
99
  end
80
100
  end
81
101
  end
@@ -19,7 +19,9 @@ module Datadog
19
19
  end
20
20
 
21
21
  def patch
22
- if ::ActionPack.gem_version >= Gem::Version.new('7.1')
22
+ if ::ActionPack.gem_version >= Gem::Version.new('8.1')
23
+ ::ActionDispatch::Journey::Router.prepend(ActionDispatch::Instrumentation::Journey::RecognizeRouter)
24
+ elsif ::ActionPack.gem_version >= Gem::Version.new('7.1')
23
25
  ::ActionDispatch::Journey::Router.prepend(ActionDispatch::Instrumentation::Journey::LazyRouter)
24
26
  else
25
27
  ::ActionDispatch::Journey::Router.prepend(ActionDispatch::Instrumentation::Journey::Router)
@@ -47,7 +47,7 @@ module Datadog
47
47
 
48
48
  record_exception(span, payload)
49
49
  rescue => e
50
- Datadog.logger.debug(e.message)
50
+ Datadog.logger.debug { "#{e.class}: #{e}" }
51
51
  end
52
52
  end
53
53
  end
@@ -40,7 +40,7 @@ module Datadog
40
40
  set_common_tags(span, payload)
41
41
  span.set_tag(Ext::TAG_JOB_ERROR, payload[:error])
42
42
  rescue => e
43
- Datadog.logger.debug(e.message)
43
+ Datadog.logger.debug { "#{e.class}: #{e}" }
44
44
  end
45
45
  end
46
46
  end
@@ -39,7 +39,7 @@ module Datadog
39
39
 
40
40
  set_common_tags(span, payload)
41
41
  rescue => e
42
- Datadog.logger.debug(e.message)
42
+ Datadog.logger.debug { "#{e.class}: #{e}" }
43
43
  end
44
44
  end
45
45
  end
@@ -39,7 +39,7 @@ module Datadog
39
39
 
40
40
  set_common_tags(span, payload)
41
41
  rescue => e
42
- Datadog.logger.debug(e.message)
42
+ Datadog.logger.debug { "#{e.class}: #{e}" }
43
43
  end
44
44
  end
45
45
  end
@@ -41,7 +41,7 @@ module Datadog
41
41
  span.set_tag(Ext::TAG_JOB_ERROR, payload[:error])
42
42
  span.set_tag(Ext::TAG_JOB_RETRY_WAIT, payload[:wait])
43
43
  rescue => e
44
- Datadog.logger.debug(e.message)
44
+ Datadog.logger.debug { "#{e.class}: #{e}" }
45
45
  end
46
46
  end
47
47
  end
@@ -39,7 +39,7 @@ module Datadog
39
39
 
40
40
  set_common_tags(span, payload)
41
41
  rescue => e
42
- Datadog.logger.debug(e.message)
42
+ Datadog.logger.debug { "#{e.class}: #{e}" }
43
43
  end
44
44
  end
45
45
  end