datadog 2.29.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 (214) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -2
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +21 -12
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +9 -7
  5. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +18 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +10 -0
  7. data/ext/datadog_profiling_native_extension/extconf.rb +6 -24
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +5 -6
  9. data/ext/datadog_profiling_native_extension/http_transport.c +51 -64
  10. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
  11. data/ext/datadog_profiling_native_extension/profiling.c +3 -1
  12. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +24 -8
  13. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -3
  14. data/ext/datadog_profiling_native_extension/stack_recorder.c +29 -43
  15. data/ext/libdatadog_api/crashtracker.c +5 -8
  16. data/ext/libdatadog_api/crashtracker_report_exception.c +34 -144
  17. data/ext/libdatadog_api/datadog_ruby_common.c +18 -0
  18. data/ext/libdatadog_api/datadog_ruby_common.h +10 -0
  19. data/ext/libdatadog_api/di.c +79 -0
  20. data/ext/libdatadog_api/extconf.rb +5 -20
  21. data/ext/libdatadog_api/init.c +5 -2
  22. data/ext/libdatadog_extconf_helpers.rb +57 -11
  23. data/lib/datadog/ai_guard/component.rb +2 -0
  24. data/lib/datadog/ai_guard/configuration/settings.rb +3 -0
  25. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +41 -3
  26. data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
  27. data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
  28. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +3 -1
  29. data/lib/datadog/ai_guard/evaluation/request.rb +14 -9
  30. data/lib/datadog/ai_guard/evaluation/result.rb +3 -1
  31. data/lib/datadog/ai_guard/evaluation.rb +36 -7
  32. data/lib/datadog/ai_guard.rb +26 -8
  33. data/lib/datadog/appsec/autoload.rb +1 -1
  34. data/lib/datadog/appsec/component.rb +11 -7
  35. data/lib/datadog/appsec/contrib/active_record/patcher.rb +3 -0
  36. data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
  37. data/lib/datadog/appsec/contrib/excon/patcher.rb +2 -0
  38. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +1 -1
  39. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  40. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  41. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +10 -11
  42. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
  43. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +25 -2
  44. data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
  45. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
  46. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  47. data/lib/datadog/appsec/contrib/rails/patcher.rb +2 -2
  48. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +2 -0
  49. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
  50. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +3 -3
  51. data/lib/datadog/appsec/event.rb +1 -17
  52. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
  53. data/lib/datadog/appsec/instrumentation/gateway.rb +2 -15
  54. data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -2
  55. data/lib/datadog/appsec/utils/http/media_type.rb +1 -2
  56. data/lib/datadog/appsec/utils/http/url_encoded.rb +2 -2
  57. data/lib/datadog/appsec.rb +5 -9
  58. data/lib/datadog/core/configuration/base.rb +17 -5
  59. data/lib/datadog/core/configuration/components.rb +21 -8
  60. data/lib/datadog/core/configuration/config_helper.rb +9 -0
  61. data/lib/datadog/core/configuration/option.rb +32 -6
  62. data/lib/datadog/core/configuration/option_definition.rb +38 -12
  63. data/lib/datadog/core/configuration/options.rb +41 -7
  64. data/lib/datadog/core/configuration/settings.rb +42 -3
  65. data/lib/datadog/core/configuration/supported_configurations.rb +17 -0
  66. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  67. data/lib/datadog/core/contrib/rails/utils.rb +7 -3
  68. data/lib/datadog/core/crashtracking/component.rb +7 -15
  69. data/lib/datadog/core/environment/container.rb +2 -2
  70. data/lib/datadog/core/environment/ext.rb +1 -0
  71. data/lib/datadog/core/environment/identity.rb +25 -3
  72. data/lib/datadog/core/environment/process.rb +12 -0
  73. data/lib/datadog/core/metrics/client.rb +5 -5
  74. data/lib/datadog/core/process_discovery.rb +5 -0
  75. data/lib/datadog/core/remote/component.rb +38 -21
  76. data/lib/datadog/core/runtime/metrics.rb +2 -3
  77. data/lib/datadog/core/telemetry/component.rb +3 -0
  78. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +2 -3
  79. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  80. data/lib/datadog/core/telemetry/event/app_started.rb +151 -169
  81. data/lib/datadog/core/telemetry/event.rb +1 -7
  82. data/lib/datadog/core/telemetry/ext.rb +1 -0
  83. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -0
  84. data/lib/datadog/core/telemetry/worker.rb +20 -0
  85. data/lib/datadog/core/utils/base64.rb +1 -1
  86. data/lib/datadog/core/utils/only_once.rb +1 -1
  87. data/lib/datadog/core/utils/spawn_monkey_patch.rb +36 -0
  88. data/lib/datadog/core/workers/async.rb +1 -1
  89. data/lib/datadog/core/workers/interval_loop.rb +13 -6
  90. data/lib/datadog/core/workers/queue.rb +0 -4
  91. data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
  92. data/lib/datadog/core.rb +0 -1
  93. data/lib/datadog/data_streams/pathway_context.rb +1 -1
  94. data/lib/datadog/data_streams/processor.rb +1 -0
  95. data/lib/datadog/di/boot.rb +3 -4
  96. data/lib/datadog/di/component.rb +20 -4
  97. data/lib/datadog/di/instrumenter.rb +20 -10
  98. data/lib/datadog/di/probe_manager.rb +79 -62
  99. data/lib/datadog/di/probe_notification_builder.rb +148 -33
  100. data/lib/datadog/di/probe_notifier_worker.rb +52 -6
  101. data/lib/datadog/di/probe_repository.rb +198 -0
  102. data/lib/datadog/di/remote.rb +5 -6
  103. data/lib/datadog/di/serializer.rb +127 -9
  104. data/lib/datadog/di/transport/http.rb +12 -3
  105. data/lib/datadog/di/transport/input.rb +46 -8
  106. data/lib/datadog/di.rb +81 -0
  107. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  108. data/lib/datadog/open_feature/configuration.rb +2 -0
  109. data/lib/datadog/open_feature/evaluation_engine.rb +1 -1
  110. data/lib/datadog/open_feature/exposures/reporter.rb +1 -1
  111. data/lib/datadog/open_feature/exposures/worker.rb +1 -1
  112. data/lib/datadog/open_feature/remote.rb +1 -1
  113. data/lib/datadog/open_feature/transport.rb +1 -1
  114. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -0
  115. data/lib/datadog/profiling/collectors/code_provenance.rb +2 -3
  116. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +14 -1
  117. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  118. data/lib/datadog/profiling/component.rb +31 -1
  119. data/lib/datadog/profiling/http_transport.rb +5 -6
  120. data/lib/datadog/profiling/load_native_extension.rb +1 -1
  121. data/lib/datadog/profiling/profiler.rb +15 -12
  122. data/lib/datadog/profiling/scheduler.rb +2 -2
  123. data/lib/datadog/profiling/tasks/exec.rb +2 -2
  124. data/lib/datadog/profiling/tasks/setup.rb +2 -2
  125. data/lib/datadog/profiling.rb +1 -2
  126. data/lib/datadog/single_step_instrument.rb +1 -1
  127. data/lib/datadog/tracing/buffer.rb +3 -3
  128. data/lib/datadog/tracing/component.rb +11 -0
  129. data/lib/datadog/tracing/configuration/settings.rb +2 -1
  130. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  131. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +20 -0
  132. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
  133. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  134. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  135. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  136. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  137. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  138. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  139. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  140. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  141. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  142. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  143. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  144. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  145. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +1 -1
  146. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
  147. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
  148. data/lib/datadog/tracing/contrib/configurable.rb +18 -3
  149. data/lib/datadog/tracing/contrib/dalli/integration.rb +4 -1
  150. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +1 -1
  151. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
  152. data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
  153. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -2
  154. data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
  155. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
  156. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +5 -2
  157. data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
  158. data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -2
  159. data/lib/datadog/tracing/contrib/grape/endpoint.rb +7 -7
  160. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
  161. data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
  162. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
  163. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  164. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +5 -2
  165. data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
  166. data/lib/datadog/tracing/contrib/http/instrumentation.rb +1 -1
  167. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +5 -2
  168. data/lib/datadog/tracing/contrib/httpclient/ext.rb +1 -0
  169. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +5 -2
  170. data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
  171. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +1 -1
  172. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
  173. data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
  174. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +1 -1
  175. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
  176. data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
  177. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +5 -1
  178. data/lib/datadog/tracing/contrib/rack/ext.rb +1 -0
  179. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +5 -2
  180. data/lib/datadog/tracing/contrib/rails/ext.rb +1 -0
  181. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  182. data/lib/datadog/tracing/contrib/rails/patcher.rb +0 -1
  183. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  184. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
  185. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  186. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
  187. data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
  188. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
  189. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  190. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
  191. data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
  192. data/lib/datadog/tracing/contrib/status_range_matcher.rb +4 -0
  193. data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
  194. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +5 -1
  195. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +1 -0
  196. data/lib/datadog/tracing/distributed/datadog.rb +4 -2
  197. data/lib/datadog/tracing/event.rb +1 -1
  198. data/lib/datadog/tracing/metadata/ext.rb +4 -0
  199. data/lib/datadog/tracing/remote.rb +1 -1
  200. data/lib/datadog/tracing/sampling/ext.rb +2 -0
  201. data/lib/datadog/tracing/sampling/priority_sampler.rb +13 -0
  202. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  203. data/lib/datadog/tracing/sampling/rule_sampler.rb +54 -25
  204. data/lib/datadog/tracing/sampling/span/rule_parser.rb +1 -1
  205. data/lib/datadog/tracing/span_operation.rb +1 -1
  206. data/lib/datadog/tracing/sync_writer.rb +0 -1
  207. data/lib/datadog/tracing/trace_operation.rb +50 -6
  208. data/lib/datadog/tracing/tracer.rb +25 -0
  209. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  210. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  211. data/lib/datadog/tracing/writer.rb +0 -1
  212. data/lib/datadog/version.rb +1 -1
  213. metadata +15 -8
  214. data/lib/datadog/tracing/workers/trace_writer.rb +0 -204
@@ -40,7 +40,8 @@ append_cflags '-Werror' if ENV['DATADOG_GEM_CI'] == 'true'
40
40
  # * by upstream Ruby -- search for gnu99 in the codebase
41
41
  # * by msgpack, another datadog gem dependency
42
42
  # (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
43
- append_cflags '-std=gnu99'
43
+ # @ivoanjo: We could probably start using C11/gnu11 for non macOS-too but it's somewhat hard to validate so I chickened out for now
44
+ append_cflags RUBY_PLATFORM.include?('darwin') ? '-std=gnu11' : '-std=gnu99'
44
45
 
45
46
  # Allow defining variables at any point in a function
46
47
  append_cflags '-Wno-declaration-after-statement'
@@ -74,29 +75,13 @@ if ENV['DDTRACE_DEBUG'] == 'true'
74
75
  end
75
76
 
76
77
  # If we got here, libdatadog is available and loaded
77
- ENV['PKG_CONFIG_PATH'] = "#{ENV["PKG_CONFIG_PATH"]}:#{Libdatadog.pkgconfig_folder}"
78
- Logging.message("[datadog] PKG_CONFIG_PATH set to #{ENV["PKG_CONFIG_PATH"].inspect}\n")
79
78
  $stderr.puts("Using libdatadog #{Libdatadog::VERSION} from #{Libdatadog.pkgconfig_folder}")
80
79
 
81
- unless pkg_config('datadog_profiling_with_rpath')
82
- Logging.message("[datadog] Ruby detected the pkg-config command is #{$PKGCONFIG.inspect}\n")
83
-
84
- if Datadog::LibdatadogExtconfHelpers.pkg_config_missing?
85
- skip_building_extension!('the `pkg-config` system tool is missing')
86
- else
87
- skip_building_extension!('there was a problem in setting up the `libdatadog` dependency')
88
- end
80
+ unless Datadog::LibdatadogExtconfHelpers.configure_libdatadog(extconf_folder: __dir__)
81
+ skip_building_extension!('there was a problem in setting up the `libdatadog` dependency')
89
82
  end
90
83
 
91
- # See comments on the helper methods being used for why we need to additionally set this.
92
- # The extremely excessive escaping around ORIGIN below seems to be correct and was determined after a lot of
93
- # experimentation. We need to get these special characters across a lot of tools untouched...
94
- extra_relative_rpaths = [
95
- Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_native_lib_folder(current_folder: __dir__),
96
- *Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_ruby_extensions_folders,
97
- ]
98
- extra_relative_rpaths.each { |folder| $LDFLAGS += " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder.to_str}" }
99
- Logging.message("[datadog] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
84
+ Datadog::LibdatadogExtconfHelpers.add_libdatadog_version_define
100
85
 
101
86
  # Tag the native extension library with the Ruby version and Ruby platform.
102
87
  # This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
@@ -1,12 +1,14 @@
1
1
  #include <ruby.h>
2
2
 
3
3
  #include "datadog_ruby_common.h"
4
+
4
5
  #include "crashtracker.h"
5
- #include "process_discovery.h"
6
- #include "library_config.h"
7
6
  #include "feature_flags.h"
7
+ #include "library_config.h"
8
+ #include "process_discovery.h"
8
9
 
9
10
  void ddsketch_init(VALUE core_module);
11
+ void di_init(VALUE datadog_module);
10
12
 
11
13
  void DDTRACE_EXPORT Init_libdatadog_api(void) {
12
14
  VALUE datadog_module = rb_define_module("Datadog");
@@ -21,4 +23,5 @@ void DDTRACE_EXPORT Init_libdatadog_api(void) {
21
23
  library_config_init(core_module);
22
24
  ddsketch_init(core_module);
23
25
  feature_flags_init(core_module);
26
+ di_init(datadog_module);
24
27
  }
@@ -10,7 +10,7 @@ module Datadog
10
10
  module LibdatadogExtconfHelpers
11
11
  # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
12
12
  # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
13
- LIBDATADOG_VERSION = '~> 25.0.0.1.0'
13
+ LIBDATADOG_VERSION = '~> 30.0.0.1.0'
14
14
 
15
15
  # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
16
16
  # libdatadog are moved after the extension gets compiled.
@@ -23,9 +23,9 @@ module Datadog
23
23
  # Linux: e.g. `readelf -d datadog_profiling_native_extension.2.7.3_x86_64-linux.so`.
24
24
  #
25
25
  # In older versions of the datadog gem, we only set as runpath an absolute path to libdatadog.
26
- # (This gets set automatically by the call
27
- # to `pkg_config('datadog_profiling_with_rpath')` in `extconf.rb`). This worked fine as long as libdatadog was **NOT**
28
- # moved from the folder it was present at datadog gem installation/linking time.
26
+ # (This gets set automatically by the call to `configure_libdatadog` in `extconf.rb`).
27
+ # This worked fine as long as libdatadog was **NOT** moved from the folder it was present at
28
+ # datadog gem installation/linking time.
29
29
  #
30
30
  # Unfortunately, environments such as Heroku and AWS Elastic Beanstalk move gems around in the filesystem after
31
31
  # installation. Thus, the profiling native extension could not be loaded in these environments
@@ -47,12 +47,12 @@ module Datadog
47
47
  # we could setup when doing a `require`.
48
48
  #
49
49
  def self.libdatadog_folder_relative_to_native_lib_folder(
50
- current_folder:,
50
+ extconf_folder:,
51
51
  libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
52
52
  )
53
53
  return unless libdatadog_pkgconfig_folder
54
54
 
55
- native_lib_folder = "#{current_folder}/../../lib/"
55
+ native_lib_folder = "#{extconf_folder}/../../lib/"
56
56
  libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
57
57
 
58
58
  Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(native_lib_folder)).to_s
@@ -104,13 +104,51 @@ module Datadog
104
104
  end
105
105
  end
106
106
 
107
- # mkmf sets $PKGCONFIG after the `pkg_config` gets used in extconf.rb. When `pkg_config` is unsuccessful, we use
108
- # this helper to decide if we can show more specific error message vs a generic "something went wrong".
109
- def self.pkg_config_missing?(command: $PKGCONFIG) # standard:disable Style/GlobalVars
110
- pkg_config_available = command && xsystem("#{command} --version")
107
+ # Directly configures mkmf to link against libdatadog by setting $INCFLAGS, $LDFLAGS, and $libs.
108
+ #
109
+ # This replaces the previous use of mkmf's `pkg_config("datadog_profiling_with_rpath")`, removing the need for
110
+ # the pkg-config system tool to be installed.
111
+ #
112
+ # The extconf_folder argument should be the __dir__ of the calling extconf.rb, used to compute relative rpaths.
113
+ # The logger argument is the mkmf Logging module, dependency-injected to make testing easier.
114
+ # rubocop:disable Style/GlobalVars
115
+ def self.configure_libdatadog(
116
+ extconf_folder:,
117
+ libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder,
118
+ gem_dir: Gem.dir,
119
+ logger: Logging
120
+ )
121
+ return unless libdatadog_pkgconfig_folder
122
+
123
+ # The lib and include folders are at a fixed relative path from pkgconfig_folder
124
+ libdir = "#{libdatadog_pkgconfig_folder}/../../lib"
125
+ includedir = "#{libdatadog_pkgconfig_folder}/../../include"
111
126
 
112
- pkg_config_available != true
127
+ # Set mkmf global variables
128
+ $INCFLAGS << " -I#{includedir}"
129
+ $LDFLAGS << " -L#{libdir} -Wl,-rpath,#{libdir}"
130
+ $libs << " -ldatadog_profiling"
131
+
132
+ # Add extra relative rpaths using $ORIGIN to handle environments where gems are moved after installation.
133
+ # The excessive escaping is needed to get these special characters through Make and the shell untouched.
134
+ extra_relative_rpaths = [
135
+ libdatadog_folder_relative_to_native_lib_folder(
136
+ extconf_folder: extconf_folder,
137
+ libdatadog_pkgconfig_folder: libdatadog_pkgconfig_folder,
138
+ ),
139
+ *libdatadog_folder_relative_to_ruby_extensions_folders(
140
+ gem_dir: gem_dir,
141
+ libdatadog_pkgconfig_folder: libdatadog_pkgconfig_folder,
142
+ ),
143
+ ]
144
+ extra_relative_rpaths.each { |folder| $LDFLAGS << " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder}" }
145
+
146
+ logger.message("linking with libdatadog (include=#{includedir}, lib=#{libdir})\n")
147
+ logger.message("[datadog] $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
148
+
149
+ true
113
150
  end
151
+ # rubocop:enable Style/GlobalVars
114
152
 
115
153
  def self.try_loading_libdatadog
116
154
  gem 'libdatadog', LIBDATADOG_VERSION
@@ -124,6 +162,14 @@ module Datadog
124
162
  end
125
163
  end
126
164
 
165
+ # Adds a C preprocessor define with the libdatadog version used at compile time.
166
+ # This allows runtime verification that the loaded libdatadog matches what was compiled against.
167
+ # rubocop:disable Style/GlobalVars
168
+ def self.add_libdatadog_version_define
169
+ $defs << %(-DEXPECTED_LIBDATADOG_VERSION=\\"#{Libdatadog::VERSION}\\")
170
+ end
171
+ # rubocop:enable Style/GlobalVars
172
+
127
173
  # Note: This helper is currently only used in the `libdatadog_api/extconf.rb` BUT still lives here to enable testing.
128
174
  def self.load_libdatadog_or_get_issue
129
175
  try_loading_libdatadog do |exception|
@@ -7,6 +7,8 @@ require_relative 'evaluation/result'
7
7
  require_relative 'evaluation/no_op_result'
8
8
  require_relative 'evaluation/message'
9
9
  require_relative 'evaluation/tool_call'
10
+ require_relative 'evaluation/content_part'
11
+ require_relative 'evaluation/content_builder'
10
12
  require_relative 'ext'
11
13
 
12
14
  module Datadog
@@ -17,6 +17,9 @@ module Datadog
17
17
  base.class_eval do
18
18
  # AI Guard specific configurations.
19
19
  # @public_api
20
+ #
21
+ # Steep does not update `self` for this `class_eval` block.
22
+ # @type self: Datadog::Core::Configuration::Base::_DslContext
20
23
  settings :ai_guard do
21
24
  # Enable AI Guard.
22
25
  #
@@ -14,13 +14,51 @@ module Datadog
14
14
  AIGuard.assistant(id: tool_call_id, tool_name: tool_call.name, arguments: tool_call.arguments.to_s)
15
15
  end
16
16
  elsif message.tool_result?
17
- AIGuard.tool(tool_call_id: message.tool_call_id, content: message.content)
17
+ build_ai_guard_tool(message)
18
18
  else
19
- AIGuard.message(role: message.role, content: message.content)
19
+ build_ai_guard_message(message)
20
20
  end
21
21
  end
22
22
 
23
- AIGuard.evaluate(*ai_guard_messages, allow_raise: true)
23
+ AIGuard.evaluate(*ai_guard_messages)
24
+ end
25
+
26
+ private
27
+
28
+ def build_ai_guard_message(message)
29
+ content = message.content
30
+
31
+ case content
32
+ when ::RubyLLM::Content
33
+ AIGuard.message(role: message.role) do |m|
34
+ m.text(content.text.to_s) if content.text
35
+
36
+ # Calling attachment.for_llm triggers lazy loading of file contents.
37
+ # The result is memoized, so providers won't re-read.
38
+ content.attachments.each do |attachment|
39
+ case attachment.type
40
+ when :image
41
+ m.image_url(attachment.for_llm)
42
+ when :text
43
+ m.text(attachment.for_llm)
44
+ end
45
+ # Skip :pdf, :audio, :video, :unknown — not supported by AIGuard
46
+ end
47
+ end
48
+ else
49
+ AIGuard.message(role: message.role, content: content)
50
+ end
51
+ end
52
+
53
+ def build_ai_guard_tool(message)
54
+ content = message.content
55
+ # Tools can return Content or Content::Raw objects (e.g. with attachments),
56
+ # but AIGuard.tool expects a String. Extract text when content is a Content object.
57
+ case content
58
+ when ::RubyLLM::Content
59
+ content = content.text.to_s
60
+ end
61
+ AIGuard.tool(tool_call_id: message.tool_call_id, content: content)
24
62
  end
25
63
  end
26
64
 
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Evaluation
6
+ # Builder for collecting content parts inside a message block.
7
+ #
8
+ # Used via the block form of {Datadog::AIGuard.message}:
9
+ #
10
+ # Datadog::AIGuard.message(role: :user) do |m|
11
+ # m.text("What's in this image?")
12
+ # m.image_url("https://example.com/img.png")
13
+ # end
14
+ class ContentBuilder
15
+ attr_reader :parts
16
+
17
+ def initialize
18
+ @parts = []
19
+ end
20
+
21
+ def text(text)
22
+ @parts << ContentPart::Text.new(text)
23
+ end
24
+
25
+ def image_url(url)
26
+ @parts << ContentPart::ImageURL.new(url)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Evaluation
6
+ # Namespace for content part types used in multi-modal messages.
7
+ module ContentPart
8
+ # A text content part.
9
+ class Text
10
+ attr_reader :text
11
+
12
+ def initialize(text)
13
+ @text = text
14
+ end
15
+
16
+ def type
17
+ :text
18
+ end
19
+ end
20
+
21
+ # An image URL content part. Accepts an absolute URL or a base64 data URI.
22
+ class ImageURL
23
+ attr_reader :url
24
+
25
+ def initialize(url)
26
+ @url = url
27
+ end
28
+
29
+ def type
30
+ :image_url
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -5,12 +5,14 @@ module Datadog
5
5
  module Evaluation
6
6
  # Class for emulating AI Guard evaluation result when AI Guard is disabled.
7
7
  class NoOpResult
8
- attr_reader :action, :reason, :tags
8
+ attr_reader :action, :reason, :tags, :sds_findings, :tag_probabilities
9
9
 
10
10
  def initialize
11
11
  @action = Result::ALLOW_ACTION
12
12
  @reason = "AI Guard is disabled"
13
13
  @tags = []
14
+ @sds_findings = []
15
+ @tag_probabilities = {}
14
16
  end
15
17
 
16
18
  def allow?
@@ -44,15 +44,7 @@ module Datadog
44
44
  end
45
45
 
46
46
  def serialize_messages(messages)
47
- serialized_messages = []
48
-
49
- messages.each do |message|
50
- serialized_messages << serialize_message(message)
51
-
52
- break if serialized_messages.count == Datadog.configuration.ai_guard.max_messages_length
53
- end
54
-
55
- serialized_messages
47
+ messages.map { |message| serialize_message(message) }
56
48
  end
57
49
 
58
50
  def serialize_message(message)
@@ -69,12 +61,25 @@ module Datadog
69
61
  }
70
62
  ]
71
63
  }
64
+ elsif message.content.is_a?(::Array)
65
+ {role: message.role, content: serialize_content_parts(message.content)}
72
66
  elsif message.tool_call_id
73
67
  {role: message.role, tool_call_id: message.tool_call_id, content: message.content}
74
68
  else
75
69
  {role: message.role, content: message.content}
76
70
  end
77
71
  end
72
+
73
+ def serialize_content_parts(parts)
74
+ parts.map do |part|
75
+ case part
76
+ when ContentPart::Text
77
+ {type: "text", text: part.text}
78
+ when ContentPart::ImageURL
79
+ {type: "image_url", image_url: {url: part.url}}
80
+ end
81
+ end
82
+ end
78
83
  end
79
84
  end
80
85
  end
@@ -9,7 +9,7 @@ module Datadog
9
9
  DENY_ACTION = "DENY"
10
10
  ABORT_ACTION = "ABORT"
11
11
 
12
- attr_reader :action, :reason, :tags
12
+ attr_reader :action, :reason, :tags, :sds_findings, :tag_probabilities
13
13
 
14
14
  def initialize(raw_response)
15
15
  attributes = raw_response.fetch("data").fetch("attributes")
@@ -17,7 +17,9 @@ module Datadog
17
17
  @action = attributes.fetch("action")
18
18
  @reason = attributes.fetch("reason")
19
19
  @tags = attributes.fetch("tags")
20
+ @tag_probabilities = attributes.fetch("tag_probs")
20
21
  @is_blocking_enabled = attributes.fetch("is_blocking_enabled")
22
+ @sds_findings = attributes.fetch("sds_findings", [])
21
23
  rescue KeyError => e
22
24
  raise AIGuardClientError, "Missing key: \"#{e.key}\""
23
25
  end
@@ -6,10 +6,16 @@ module Datadog
6
6
  # and creating `ai_guard` span with required tags
7
7
  module Evaluation
8
8
  class << self
9
- def perform(messages, allow_raise: false)
9
+ def perform(messages, allow_raise: true)
10
10
  raise ArgumentError, "Messages must not be empty" if messages&.empty?
11
11
 
12
12
  Tracing.trace(Ext::SPAN_NAME) do |span, trace|
13
+ trace.keep!
14
+ trace.set_tag(
15
+ Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER,
16
+ Tracing::Sampling::Ext::Decision::AI_GUARD
17
+ )
18
+
13
19
  if (last_message = messages.last)
14
20
  if last_message.tool_call
15
21
  span.set_tag(Ext::TARGET_TAG, "tool")
@@ -34,8 +40,10 @@ module Datadog
34
40
  span.set_metastruct_tag(
35
41
  Ext::METASTRUCT_TAG,
36
42
  {
37
- messages: truncate_content(request.serialized_messages),
38
- attack_categories: result.tags
43
+ messages: truncate_content(truncate_messages(request.serialized_messages)),
44
+ attack_categories: result.tags,
45
+ sds: result.sds_findings,
46
+ tag_probs: result.tag_probabilities
39
47
  }
40
48
  )
41
49
 
@@ -56,14 +64,35 @@ module Datadog
56
64
 
57
65
  private
58
66
 
67
+ def truncate_messages(serialized_messages)
68
+ max_length = Datadog.configuration.ai_guard.max_messages_length
69
+ serialized_messages.first(max_length)
70
+ end
71
+
72
+ # Truncates content in serialized messages to stay within the configured byte limit.
73
+ # For multi-modal messages, only text parts are truncated; image URLs are left intact.
59
74
  def truncate_content(serialized_messages)
75
+ max_bytes = Datadog.configuration.ai_guard.max_content_size_bytes
76
+
60
77
  serialized_messages.map do |message| # steep:ignore
61
78
  next message unless message[:content]
62
79
 
63
- {
64
- **message,
65
- content: message[:content].byteslice(0, Datadog.configuration.ai_guard.max_content_size_bytes)
66
- }
80
+ if message[:content].is_a?(::Array)
81
+ serialized_content = message[:content].map do |part|
82
+ if part[:text]
83
+ {**part, text: part[:text].to_s.byteslice(0, max_bytes)}
84
+ else
85
+ part
86
+ end
87
+ end
88
+
89
+ {**message, content: serialized_content}
90
+ else
91
+ {
92
+ **message,
93
+ content: message[:content].byteslice(0, max_bytes)
94
+ }
95
+ end
67
96
  end
68
97
  end
69
98
  end
@@ -10,7 +10,7 @@ module Datadog
10
10
  module AIGuard
11
11
  Core::Configuration::Settings.extend(Configuration::Settings)
12
12
 
13
- # This error is raised when user passes `allow_raise: true` to Evaluation.perform
13
+ # This error is raised when `allow_raise` is set to true (the default) in Evaluation.perform
14
14
  # and AI Guard considers the messages not safe. Intended to be rescued by the user.
15
15
  #
16
16
  # WARNING: This name must not change, since front-end is using it.
@@ -68,13 +68,14 @@ module Datadog
68
68
  # One or more message objects to be evaluated.
69
69
  # @param allow_raise [Boolean]
70
70
  # Whether this method may raise an exception when evaluation result is not ALLOW.
71
+ # Defaults to true.
71
72
  #
72
73
  # @return [Datadog::AIGuard::Evaluation::Result]
73
74
  # The result of AI Guard evaluation.
74
75
  # @raise [Datadog::AIGuard::AIGuardAbortError]
75
76
  # If the evaluation results in DENY or ABORT action and `allow_raise` is set to true
76
77
  # @public_api
77
- def evaluate(*messages, allow_raise: false)
78
+ def evaluate(*messages, allow_raise: true)
78
79
  if enabled?
79
80
  Evaluation.perform(messages, allow_raise: allow_raise)
80
81
  else
@@ -84,25 +85,42 @@ module Datadog
84
85
 
85
86
  # Builds a generic evaluation message.
86
87
  #
87
- # Example:
88
+ # Accepts either a string content or a block for multi-modal content parts:
88
89
  #
89
90
  # ```
91
+ # # String content:
90
92
  # Datadog::AIGuard.message(role: :user, content: "Hello, assistant")
93
+ #
94
+ # # Multi-modal content with block:
95
+ # Datadog::AIGuard.message(role: :user) do |m|
96
+ # m.text("What's in this image?")
97
+ # m.image_url("https://example.com/img.png")
98
+ # end
91
99
  # ```
92
100
  #
93
101
  # @param role [Symbol]
94
102
  # The role associated with the message.
95
103
  # Must be one of `:assistant`, `:tool`, `:system`, `:developer`, or `:user`.
96
- # @param content [String]
97
- # The textual content of the message.
104
+ # @param content [String, nil]
105
+ # The textual content of the message. Cannot be combined with a block.
106
+ # @yield [builder] A block for building multi-modal content parts.
107
+ # @yieldparam builder [Datadog::AIGuard::Evaluation::ContentBuilder]
98
108
  #
99
109
  # @return [Datadog::AIGuard::Evaluation::Message]
100
110
  # A new message instance with the given role and content.
101
111
  # @raise [ArgumentError]
102
- # If an invalid role is provided.
112
+ # If both content and a block are provided, or if an invalid role is provided.
103
113
  # @public_api
104
- def message(role:, content:)
105
- Evaluation::Message.new(role: role, content: content)
114
+ def message(role:, content: nil)
115
+ if block_given?
116
+ raise ArgumentError, "Cannot pass both content and a block" if content
117
+
118
+ builder = Evaluation::ContentBuilder.new
119
+ yield builder
120
+ Evaluation::Message.new(role: role, content: builder.parts)
121
+ else
122
+ Evaluation::Message.new(role: role, content: content)
123
+ end
106
124
  end
107
125
 
108
126
  # Builds an assistant message representing a tool call initiated by the model.
@@ -7,7 +7,7 @@ if %w[1 true].include?((Datadog::DATADOG_ENV['DD_APPSEC_ENABLED'] || '').downcas
7
7
  rescue => e
8
8
  Kernel.warn(
9
9
  '[datadog] AppSec failed to instrument. No security check will be performed. error: ' \
10
- " #{e.class.name} #{e.message}"
10
+ " #{e.class}: #{e}"
11
11
  )
12
12
  end
13
13
  end
@@ -45,10 +45,15 @@ module Datadog
45
45
  settings.appsec.instrument(:devise) unless devise_integration.patcher.patched?
46
46
 
47
47
  security_engine = SecurityEngine::Engine.new(appsec_settings: settings.appsec, telemetry: telemetry)
48
- new(security_engine: security_engine, telemetry: telemetry)
49
- rescue
50
- Datadog.logger.warn('AppSec is disabled, see logged errors above')
51
-
48
+ new(security_engine: security_engine)
49
+ rescue => e
50
+ Datadog.logger.warn("AppSec is disabled: #{e.class}: #{e}; there may be additional logged errors above")
51
+
52
+ # Not reporting to telemetry here because some of the rescued exceptions
53
+ # have already been reported by the code that raised them
54
+ # (e.g. SecurityEngine::Engine.new reports WAF init failures).
55
+ # TODO: reconsider whether telemetry reporting belongs here
56
+ # (single catch-all) or in the downstream code (as it is now).
52
57
  nil
53
58
  end
54
59
 
@@ -70,11 +75,10 @@ module Datadog
70
75
  end
71
76
  end
72
77
 
73
- attr_reader :security_engine, :telemetry
78
+ attr_reader :security_engine
74
79
 
75
- def initialize(security_engine:, telemetry:)
80
+ def initialize(security_engine:)
76
81
  @security_engine = security_engine
77
- @telemetry = telemetry
78
82
  end
79
83
 
80
84
  def reconfigure!
@@ -61,6 +61,7 @@ module Datadog
61
61
  end
62
62
 
63
63
  ::ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(instrumentation_module)
64
+ Patcher.instance_variable_set(:@patched, true)
64
65
  end
65
66
 
66
67
  def patch_mysql2_adapter
@@ -73,6 +74,7 @@ module Datadog
73
74
  end
74
75
 
75
76
  ::ActiveRecord::ConnectionAdapters::Mysql2Adapter.prepend(instrumentation_module)
77
+ Patcher.instance_variable_set(:@patched, true)
76
78
  end
77
79
 
78
80
  def patch_postgresql_adapter
@@ -93,6 +95,7 @@ module Datadog
93
95
  end
94
96
 
95
97
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(instrumentation_module)
98
+ Patcher.instance_variable_set(:@patched, true)
96
99
  end
97
100
  end
98
101
  end
@@ -24,7 +24,7 @@ module Datadog
24
24
  end
25
25
 
26
26
  def self.compatible?
27
- super && version >= MINIMUM_VERSION
27
+ !!(super && (version&.>= MINIMUM_VERSION))
28
28
  end
29
29
 
30
30
  def self.auto_instrument?
@@ -20,6 +20,8 @@ module Datadog
20
20
  require_relative 'ssrf_detection_middleware'
21
21
 
22
22
  ::Excon.defaults[:middlewares].insert(0, SSRFDetectionMiddleware)
23
+
24
+ Patcher.instance_variable_set(:@patched, true)
23
25
  end
24
26
  end
25
27
  end
@@ -98,7 +98,7 @@ module Datadog
98
98
 
99
99
  def request_url(data)
100
100
  klass = (data[:scheme] == 'https') ? URI::HTTPS : URI::HTTP
101
- klass.build(host: data[:host], path: data[:path], query: data[:query]).to_s
101
+ klass.build(host: data[:host], port: data[:port], path: data[:path], query: data[:query]).to_s
102
102
  end
103
103
 
104
104
  def normalize_headers(headers)
@@ -22,7 +22,7 @@ module Datadog
22
22
  end
23
23
 
24
24
  def watch_multiplex(gateway = Instrumentation.gateway)
25
- gateway.watch('graphql.multiplex', :appsec) do |stack, gateway_multiplex|
25
+ gateway.watch('graphql.multiplex') do |stack, gateway_multiplex|
26
26
  context = AppSec::Context.active
27
27
 
28
28
  if context
@@ -25,7 +25,7 @@ module Datadog
25
25
  def query
26
26
  ::Rack::Utils.parse_query(request.query_string)
27
27
  rescue => e
28
- Datadog.logger.debug { "AppSec: Failed to parse request query string: #{e.class}: #{e.message}" }
28
+ Datadog.logger.debug { "AppSec: Failed to parse request query string: #{e.class}: #{e}" }
29
29
  AppSec.telemetry.report(e, description: 'AppSec: Failed to parse request query string')
30
30
 
31
31
  {}