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
@@ -88,11 +88,11 @@ module Datadog
88
88
  # content as errored but with a somewhat different exception
89
89
  # message.
90
90
  # TODO assert content state (errored for this example)
91
- content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
91
+ content.errored("Error applying dynamic instrumentation configuration: #{exc.class}: #{exc.message}")
92
92
  rescue => exc
93
93
  raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
94
94
 
95
- component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI remote receiver: #{exc.class}: #{exc}" }
95
+ component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI remote receiver: #{exc.class}: #{exc.message}" }
96
96
  component.telemetry&.report(exc, description: "Unhandled exception adding probe in DI remote receiver")
97
97
 
98
98
  # TODO test this path
@@ -106,7 +106,7 @@ module Datadog
106
106
  # content as errored but with a somewhat different exception
107
107
  # message.
108
108
  # TODO assert content state (errored for this example)
109
- content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
109
+ content.errored("Error applying dynamic instrumentation configuration: #{exc.class}: #{exc.message}")
110
110
  end
111
111
 
112
112
  # Important: even if processing fails for this probe config,
@@ -117,11 +117,11 @@ module Datadog
117
117
  rescue => exc
118
118
  raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
119
119
 
120
- component.logger.debug { "di: unhandled exception handling a probe in DI remote receiver: #{exc.class}: #{exc}" }
120
+ component.logger.debug { "di: unhandled exception handling a probe in DI remote receiver: #{exc.class}: #{exc.message}" }
121
121
  component.telemetry&.report(exc, description: "Unhandled exception handling probe in DI remote receiver")
122
122
 
123
123
  # TODO assert content state (errored for this example)
124
- content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
124
+ content.errored("Error applying dynamic instrumentation configuration: #{exc.class}: #{exc.message}")
125
125
  end
126
126
 
127
127
  # This method does not mark +previous_content+ as succeeded or errored,
@@ -136,7 +136,7 @@ module Datadog
136
136
  rescue => exc
137
137
  raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
138
138
 
139
- component.logger.debug { "di: unhandled exception removing probes in DI remote receiver: #{exc.class}: #{exc}" }
139
+ component.logger.debug { "di: unhandled exception removing probes in DI remote receiver: #{exc.class}: #{exc.message}" }
140
140
  component.telemetry&.report(exc, description: "Unhandled exception removing probes in DI remote receiver")
141
141
  end
142
142
 
@@ -81,14 +81,14 @@ module Datadog
81
81
  # Serialization failed for this snapshot - report via callback
82
82
  # This catches JSON::GeneratorError, Encoding errors, TypeError, etc.
83
83
  probe_id = snapshot.dig(:debugger, :snapshot, :probe, :id)
84
- logger.debug { "di: JSON encoding failed for snapshot (probe #{probe_id}): #{exc.class}: #{exc}" }
84
+ logger.debug { "di: JSON encoding failed for snapshot (probe #{probe_id}): #{exc.class}: #{exc.message}" }
85
85
  telemetry&.report(exc, description: "JSON encoding failed for snapshot")
86
86
 
87
87
  if probe_id
88
88
  begin
89
89
  on_serialization_error.call(probe_id, exc)
90
90
  rescue => callback_exc
91
- logger.debug { "di: error in serialization error callback for probe #{probe_id}: #{callback_exc.class}: #{callback_exc}" }
91
+ logger.debug { "di: error in serialization error callback for probe #{probe_id}: #{callback_exc.class}: #{callback_exc.message}" }
92
92
  telemetry&.report(callback_exc, description: "Error in serialization error callback")
93
93
  end
94
94
  end
@@ -110,7 +110,7 @@ module Datadog
110
110
  begin
111
111
  send_input_chunk(chunked_payload, serialized_tags)
112
112
  rescue => exc
113
- logger.debug { "di: failed to send snapshot chunk: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
113
+ logger.debug { "di: failed to send snapshot chunk: #{exc.class}: #{exc.message} (at #{exc.backtrace.first})" }
114
114
  telemetry&.report(exc, description: "Error sending snapshot chunk")
115
115
  end
116
116
  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
  #
@@ -1,11 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'ext'
4
+
3
5
  module Datadog
4
6
  module ErrorTracking
5
7
  # Configuration for ErrorTracking
6
8
  module Configuration
9
+ # Settings
10
+ module Settings
11
+ def self.extended(base)
12
+ base = base.singleton_class unless base.is_a?(Class)
13
+ add_settings!(base)
14
+ end
15
+
16
+ def self.add_settings!(base)
17
+ base.class_eval do
18
+ # Error Tracking specific configurations.
19
+ # @public_api
20
+ settings :error_tracking do
21
+ # Enable automatic reporting of handled errors and defines the scope
22
+ # for which to report errors: user code, gems, or both. Possible
23
+ # values are: all | user | third_party.
24
+ #
25
+ # @default 'DD_ERROR_TRACKING_HANDLED_ERRORS' environment variable, otherwise `nil`
26
+ # @return [String, nil]
27
+ option :handled_errors do |o|
28
+ o.type :string, nilable: true
29
+ o.default Ext::DEFAULT_HANDLED_ERRORS
30
+ o.env Ext::ENV_HANDLED_ERRORS
31
+ o.setter do |value|
32
+ next value if Ext::VALID_HANDLED_ERRORS.include?(value)
33
+
34
+ unless value.nil?
35
+ Datadog.logger.warn(
36
+ "Invalid handled errors scope: #{value}. " \
37
+ "Supported values are: #{Ext::VALID_HANDLED_ERRORS.join(" | ")}. " \
38
+ 'Deactivating the feature.'
39
+ )
40
+ end
41
+
42
+ Ext::DEFAULT_HANDLED_ERRORS
43
+ end
44
+ end
45
+
46
+ # Enable automatic reporting of handled errors and specify what files should be
47
+ # instrumented. The value is a list of comma separated paths, filenames or gem names.
48
+ # The paths can be absolute, starting with '/' or relative to directory in which the program
49
+ # is launched, starting with './'.
50
+ #
51
+ # @default 'DD_ERROR_TRACKING_HANDLED_ERRORS_MODULES' environment variable, otherwise `nil`
52
+ # @return [String, nil]
53
+ option :handled_errors_include do |o|
54
+ o.type :array
55
+ o.default []
56
+ o.env Ext::ENV_HANDLED_ERRORS_INCLUDE
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
7
62
  end
8
63
  end
9
64
  end
10
-
11
- require_relative 'configuration/settings'
@@ -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.message} " \
34
34
  "Location: #{Array(e.backtrace).first}"
35
35
  )
36
36
  return
@@ -6,12 +6,13 @@ require_relative 'exposures/buffer'
6
6
  require_relative 'exposures/worker'
7
7
  require_relative 'exposures/deduplicator'
8
8
  require_relative 'exposures/reporter'
9
+ require_relative 'metrics/flag_eval_metrics'
9
10
 
10
11
  module Datadog
11
12
  module OpenFeature
12
13
  # This class is the entry point for the OpenFeature component
13
14
  class Component
14
- attr_reader :engine
15
+ attr_reader :engine, :flag_eval_hook
15
16
 
16
17
  def self.build(settings, agent_settings, logger:, telemetry:)
17
18
  return unless settings.respond_to?(:open_feature) && settings.open_feature.enabled
@@ -50,11 +51,27 @@ module Datadog
50
51
 
51
52
  reporter = Exposures::Reporter.new(@worker, telemetry: telemetry, logger: logger)
52
53
  @engine = EvaluationEngine.new(reporter, telemetry: telemetry, logger: logger)
54
+
55
+ @telemetry = telemetry
56
+ @logger = logger
57
+ @flag_eval_hook = create_flag_eval_hook
53
58
  end
54
59
 
55
60
  def shutdown!
56
61
  @worker.graceful_shutdown
57
62
  end
63
+
64
+ private
65
+
66
+ def create_flag_eval_hook
67
+ require_relative 'hooks/flag_eval_hook'
68
+ return unless Hooks::FlagEvalHook.available?
69
+
70
+ metrics = Metrics::FlagEvalMetrics.new(telemetry: @telemetry, logger: @logger)
71
+ Hooks::FlagEvalHook.new(metrics)
72
+ rescue LoadError
73
+ nil
74
+ end
58
75
  end
59
76
  end
60
77
  end
@@ -42,7 +42,7 @@ module Datadog
42
42
  @telemetry.report(e, description: 'OpenFeature: Failed to fetch flag value')
43
43
 
44
44
  ResolutionDetails.build_error(
45
- value: default_value, error_code: Ext::GENERAL, error_message: e.message
45
+ value: default_value, error_code: Ext::GENERAL, error_message: "#{e.class}: #{e.message}"
46
46
  )
47
47
  end
48
48
 
@@ -63,7 +63,7 @@ module Datadog
63
63
  @logger.error("#{message}, #{e.class}: #{e.message}")
64
64
  @telemetry.report(e, description: "#{message} (#{e.class})")
65
65
 
66
- raise ReconfigurationError, e.message
66
+ raise ReconfigurationError, "#{e.class}: #{e.message}"
67
67
  end
68
68
  end
69
69
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Hooks
6
+ # Records flag evaluation metrics via OpenTelemetry hook
7
+ #
8
+ # Compatible with OpenFeature SDK >= 0.5.0 which provides the Hooks::Hook module,
9
+ # but also works with older versions since the SDK uses respond_to?(:finally)
10
+ # to detect hook capabilities.
11
+ class FlagEvalHook
12
+ # Include the Hook module if available (SDK >= 0.5.0) for interface documentation
13
+ # and default implementations of other hook methods (before, after, error)
14
+ include ::OpenFeature::SDK::Hooks::Hook if defined?(::OpenFeature::SDK::Hooks::Hook)
15
+
16
+ # Returns true if the OpenFeature SDK supports hooks (>= 0.5.0)
17
+ def self.available?
18
+ !!defined?(::OpenFeature::SDK::Hooks::Hook)
19
+ end
20
+
21
+ def initialize(metrics)
22
+ @metrics = metrics
23
+ end
24
+
25
+ def finally(hook_context:, evaluation_details:, **_opts)
26
+ metrics = @metrics
27
+ return unless metrics
28
+
29
+ metrics.record(
30
+ hook_context.flag_key,
31
+ variant: evaluation_details.variant,
32
+ reason: evaluation_details.reason,
33
+ error_code: evaluation_details.error_code,
34
+ allocation_key: extract_allocation_key(evaluation_details),
35
+ )
36
+ end
37
+
38
+ private
39
+
40
+ def extract_allocation_key(evaluation_details)
41
+ metadata = evaluation_details.flag_metadata
42
+ return unless metadata.is_a?(Hash)
43
+
44
+ metadata['__dd_allocation_key']
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Metrics
6
+ # Records flag evaluation metrics via OpenTelemetry
7
+ class FlagEvalMetrics
8
+ METER_NAME = 'ddtrace.openfeature'
9
+ METRIC_NAME = 'feature_flag.evaluations'
10
+ METRIC_UNIT = '{evaluation}'
11
+ METRIC_DESCRIPTION = 'Number of feature flag evaluations'
12
+
13
+ ATTR_FLAG_KEY = 'feature_flag.key'
14
+ ATTR_VARIANT = 'feature_flag.result.variant'
15
+ ATTR_REASON = 'feature_flag.result.reason'
16
+ ATTR_ALLOCATION_KEY = 'feature_flag.result.allocation_key'
17
+ ATTR_ERROR_TYPE = 'error.type'
18
+
19
+ DEFAULT_ERROR_TYPE = 'general'
20
+
21
+ ERROR_TYPE_MAP = {
22
+ 'FLAG_NOT_FOUND' => 'flag_not_found',
23
+ 'TYPE_MISMATCH' => 'type_mismatch',
24
+ 'PARSE_ERROR' => 'parse_error',
25
+ 'PROVIDER_NOT_READY' => 'provider_not_ready',
26
+ 'TARGETING_KEY_MISSING' => 'targeting_key_missing',
27
+ 'INVALID_CONTEXT' => 'invalid_context',
28
+ 'PROVIDER_FATAL' => 'provider_fatal',
29
+ 'GENERAL' => DEFAULT_ERROR_TYPE,
30
+ }.freeze
31
+
32
+ # Reasons that should not include allocation_key in metrics
33
+ EXCLUDE_ALLOCATION_KEY_REASONS = %w[error default disabled].freeze
34
+
35
+ def initialize(telemetry:, logger:)
36
+ @telemetry = telemetry
37
+ @logger = logger
38
+ @enabled = Datadog.configuration.opentelemetry.metrics.enabled
39
+ @counter = nil
40
+ @mutex = Mutex.new
41
+
42
+ unless @enabled
43
+ @logger.debug { 'OpenFeature: OTel metrics not enabled (DD_METRICS_OTEL_ENABLED=false), flag evaluation metrics disabled' }
44
+ end
45
+ end
46
+
47
+ def record(flag_key, variant:, reason:, error_code: nil, allocation_key: nil)
48
+ return unless @enabled
49
+
50
+ counter = get_or_create_counter
51
+ return unless counter
52
+
53
+ attributes = build_attributes(
54
+ flag_key,
55
+ variant: variant,
56
+ reason: reason,
57
+ error_code: error_code,
58
+ allocation_key: allocation_key,
59
+ )
60
+ counter.add(1, attributes: attributes)
61
+ rescue => e
62
+ @logger.debug { "OpenFeature: Failed to record evaluation metric: #{e.class}: #{e.message}" }
63
+ @telemetry.report(e, description: 'OpenFeature: Failed to record evaluation metric')
64
+ end
65
+
66
+ private
67
+
68
+ # Counter is created lazily because OTel SDK may not be initialized
69
+ # when the OpenFeature component is created.
70
+ def get_or_create_counter
71
+ @mutex.synchronize do
72
+ return @counter if @counter
73
+
74
+ meter_provider = get_or_initialize_meter_provider
75
+ return unless meter_provider
76
+
77
+ meter = meter_provider.meter(METER_NAME)
78
+ @counter = meter.create_counter(
79
+ METRIC_NAME,
80
+ unit: METRIC_UNIT,
81
+ description: METRIC_DESCRIPTION
82
+ )
83
+ end
84
+ rescue => e
85
+ @logger.debug { "OpenFeature: Failed to create metrics counter: #{e.class}: #{e.message}" }
86
+ nil
87
+ end
88
+
89
+ # Fetch an available OTel meter provider, initializing if needed.
90
+ # Returns the meter provider if available, nil otherwise.
91
+ def get_or_initialize_meter_provider
92
+ meter_provider = defined?(::OpenTelemetry) ? ::OpenTelemetry.meter_provider : nil
93
+ return meter_provider if sdk_meter_provider?(meter_provider)
94
+
95
+ @logger.debug { 'OpenFeature: Initializing OTel meter provider directly' }
96
+ require 'opentelemetry-metrics-sdk'
97
+ require 'datadog/opentelemetry/metrics'
98
+ Datadog::OpenTelemetry::Metrics.initialize!(Datadog.send(:components))
99
+
100
+ meter_provider = ::OpenTelemetry.meter_provider
101
+ sdk_meter_provider?(meter_provider) ? meter_provider : nil
102
+ rescue LoadError => e
103
+ @logger.debug { "OpenFeature: Failed to initialize OTel metrics: #{e.class}: #{e.message}" }
104
+ nil
105
+ end
106
+
107
+ def sdk_meter_provider?(meter_provider)
108
+ return false if meter_provider.nil?
109
+ return false unless defined?(::OpenTelemetry::SDK::Metrics::MeterProvider)
110
+
111
+ meter_provider.is_a?(::OpenTelemetry::SDK::Metrics::MeterProvider)
112
+ end
113
+
114
+ def build_attributes(flag_key, variant:, reason:, error_code:, allocation_key:)
115
+ reason_downcase = normalize_reason(reason)
116
+
117
+ attrs = {
118
+ ATTR_FLAG_KEY => flag_key,
119
+ ATTR_VARIANT => variant.to_s,
120
+ ATTR_REASON => reason_downcase,
121
+ }
122
+
123
+ if allocation_key && !allocation_key.empty? && !exclude_allocation_key?(reason_downcase)
124
+ attrs[ATTR_ALLOCATION_KEY] = allocation_key
125
+ end
126
+
127
+ if error_code
128
+ attrs[ATTR_ERROR_TYPE] = normalize_error_type(error_code)
129
+ end
130
+
131
+ attrs
132
+ end
133
+
134
+ def normalize_reason(reason)
135
+ reason = reason.to_s
136
+ reason.empty? ? 'unknown' : reason.downcase
137
+ end
138
+
139
+ def normalize_error_type(error_code)
140
+ ERROR_TYPE_MAP.fetch(error_code.to_s, DEFAULT_ERROR_TYPE)
141
+ end
142
+
143
+ def exclude_allocation_key?(reason_downcase)
144
+ EXCLUDE_ALLOCATION_KEY_REASONS.include?(reason_downcase)
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -7,6 +7,8 @@ module Datadog
7
7
  module OpenFeature
8
8
  # OpenFeature feature flagging provider backed by Datadog Remote Configuration.
9
9
  #
10
+ # Requires openfeature-sdk >= 0.5.1 for flag evaluation metrics support.
11
+ #
10
12
  # Implementation follows the OpenFeature contract of Provider SDK.
11
13
  # For details see:
12
14
  # - https://github.com/open-feature/ruby-sdk/blob/v0.4.1/README.md#develop-a-provider
@@ -67,6 +69,11 @@ module Datadog
67
69
  # no-op
68
70
  end
69
71
 
72
+ def hooks
73
+ hook = Datadog.send(:components).open_feature&.flag_eval_hook
74
+ [hook].compact
75
+ end
76
+
70
77
  def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
71
78
  evaluate(flag_key, default_value: default_value, expected_type: :boolean, evaluation_context: evaluation_context)
72
79
  end
@@ -117,7 +124,7 @@ module Datadog
117
124
  value: result.value,
118
125
  variant: result.variant,
119
126
  reason: result.reason,
120
- flag_metadata: result.flag_metadata
127
+ flag_metadata: build_flag_metadata(result),
121
128
  )
122
129
  rescue => e
123
130
  ::OpenFeature::SDK::Provider::ResolutionDetails.new(
@@ -128,6 +135,17 @@ module Datadog
128
135
  )
129
136
  end
130
137
 
138
+ def build_flag_metadata(result)
139
+ metadata = result.flag_metadata || {}
140
+ allocation_key = result.allocation_key
141
+ if allocation_key && !allocation_key.empty?
142
+ metadata = metadata.dup
143
+ metadata['__dd_allocation_key'] = allocation_key
144
+ end
145
+
146
+ metadata
147
+ end
148
+
131
149
  def component_not_configured_default(value)
132
150
  ::OpenFeature::SDK::Provider::ResolutionDetails.new(
133
151
  value: value,
@@ -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.message}")
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.message} " \
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
@@ -11,7 +11,7 @@ module Datadog
11
11
  new(components).configure_metrics_sdk
12
12
  true
13
13
  rescue => exc
14
- components.logger.error("Failed to initialize OpenTelemetry metrics: #{exc.class}: #{exc}: #{exc.backtrace.join("\n")}")
14
+ components.logger.error("Failed to initialize OpenTelemetry metrics: #{exc.class}: #{exc.message}: #{exc.backtrace.join("\n")}")
15
15
  false
16
16
  end
17
17
 
@@ -67,7 +67,7 @@ module Datadog
67
67
 
68
68
  configure_otlp_exporter(provider)
69
69
  rescue => e
70
- @logger.warn("Failed to configure OTLP metrics exporter: #{e.class}: #{e}")
70
+ @logger.warn("Failed to configure OTLP metrics exporter: #{e.class}: #{e.message}")
71
71
  end
72
72
 
73
73
  def default_metrics_endpoint
@@ -101,7 +101,7 @@ module Datadog
101
101
  )
102
102
  provider.add_metric_reader(reader)
103
103
  rescue LoadError => e
104
- @logger.warn("Could not load OTLP metrics exporter: #{e.class}: #{e}")
104
+ @logger.warn("Could not load OTLP metrics exporter: #{e.class}: #{e.message}")
105
105
  end
106
106
 
107
107
  # Returns metrics config value if explicitly set, otherwise falls back to exporter config or computed default value.
@@ -37,7 +37,7 @@ module Datadog
37
37
  begin
38
38
  require 'opentelemetry-metrics-sdk'
39
39
  rescue LoadError => exc
40
- components.logger.warn("Failed to load OpenTelemetry metrics gems: #{exc.class}: #{exc}")
40
+ components.logger.warn("Failed to load OpenTelemetry metrics gems: #{exc.class}: #{exc.message}")
41
41
  return super
42
42
  end
43
43
 
@@ -19,7 +19,7 @@ module Datadog
19
19
  telemetry&.inc(TELEMETRY_NAMESPACE, metric_name, 1, tags: TELEMETRY_TAGS)
20
20
  result
21
21
  rescue => e
22
- Datadog.logger.error("Failed to export OpenTelemetry Metrics: #{e.class}: #{e}")
22
+ Datadog.logger.error("Failed to export OpenTelemetry Metrics: #{e.class}: #{e.message}")
23
23
  telemetry&.inc(TELEMETRY_NAMESPACE, METRIC_EXPORT_FAILURES, 1, tags: TELEMETRY_TAGS)
24
24
  raise
25
25
  end