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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2983471f43d0c9b3473b3c65d0ca656514c6cf8cd7a32e35ec26826193fe533
4
- data.tar.gz: 687d088d90ce4ee0ae5af66d9e604135f058bd7609641f4c7963f1ba32a2bb37
3
+ metadata.gz: 1328115b16393f9d96152ad1d94cb6190c307d8dd5df341087ffe21e2212ec9b
4
+ data.tar.gz: f408b1437ae55de02488433b637c868bce087289ea5fc8af4aa808cfc4ee3b73
5
5
  SHA512:
6
- metadata.gz: 96f3744eadab0292666e18beac7deab2a6b7ef61b0da216ab4490e7621b0cec7839d18a519079b5b0563e2a260388b63ef59b5243a65c3d6ba997f3c7981986c
7
- data.tar.gz: a82f01f50d499f1a08309f23144e5453cb9cf79bdc5b0c7daa9b3c9c373e3a41983babe3f86dc027f2dd2e9f056bc4b7645a49f799531e40eae9455ab7e00a6d
6
+ metadata.gz: faf8598916d674b5da3b3ab57c013060f9ecf92b939d69a51528a81e495d07340e9a8a0ba70bf9498f6d8c303a32c08d3fbf971759199ff35b47f34a25d88b0a
7
+ data.tar.gz: f7629d50a2537e08c9ceb5da1e81c25b788401c2992a3a70a2f2b29e2a44a3cbda7d2e621a316953202f5a068fa3ec201a4810d01b1b27ee4bab5bd1592a9bbd
data/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.31.0] - 2026-04-20
6
+
7
+ ### Added
8
+
9
+ * Core: Add `app-extended-heartbeat` telemetry event to resend full configuration every 24h for long-running services. ([#5531][])
10
+ * AI Guard: Add multi-model messages support to AI Guard, enabling text and image URL content parts in evaluation SDK ([#5564][])
11
+ * AI Guard: Add evaluation of images in user prompt and tool output for RubyLLM contrib. ([#5573][])
12
+ * AI Guard: Expose Sensitive Data Scanner findings in AI Guard results via `AIGuard::Evaluation::Result#sds_findings`. ([#5579][])
13
+ * AI Guard: Expose tag probabilities in AI Guard results via `AIGuard::Evaluation::Result#tag_probabilities`. ([#5580][])
14
+ * Tracing: Integrations: Add `rails.application` to the process tags. ([#5468][])
15
+ * Tracing: Report and display all configurations used by a service in the 'SDK & Agent Configurations' tab of a Ruby service page in Datadog UI ([#5483][])
16
+
17
+ ### Changed
18
+
19
+ * Core: Upgrade `libdatadog` dependency to version 30.0.0 ([#5574][])
20
+ * AI Guard: **Breaking change:** AIGuard.evaluate now defaults to `allow_raise: true`. Pass `allow_raise: false` to keep the previous non-raising behavior. ([#5587][])
21
+ * Dynamic Instrumentation: Report exception messages in method probe snapshots. Require the libdatadog_api C extension for Dynamic Instrumentation. ([#5111][])
22
+
23
+ ### Fixed
24
+
25
+ * Profiling: Detect when `libdatadog` version does not match expected version. ([#5484][])
26
+ * Profiling: Log profiler initialization errors as warnings instead of raising them. ([#5509][])
27
+ * Tracing: Fix `NoMethodError` in `extract_trace_id!` caused by `extract_tags` returning `true`. ([#5499][])
28
+
29
+ ### Removed
30
+
31
+ * Core: Removed JRuby from the CI test matrix ([#5594][])
32
+
5
33
  ## [2.30.0] - 2026-03-19
6
34
 
7
35
  ### Added
@@ -3539,7 +3567,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
3539
3567
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3540
3568
 
3541
3569
 
3542
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.30.0...master
3570
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.31.0...master
3571
+ [2.31.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.30.0...v2.31.0
3543
3572
  [2.30.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.29.0...v2.30.0
3544
3573
  [2.29.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.28.0...v2.29.0
3545
3574
  [2.28.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.27.0...v2.28.0
@@ -5193,6 +5222,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
5193
5222
  [#5076]: https://github.com/DataDog/dd-trace-rb/issues/5076
5194
5223
  [#5086]: https://github.com/DataDog/dd-trace-rb/issues/5086
5195
5224
  [#5091]: https://github.com/DataDog/dd-trace-rb/issues/5091
5225
+ [#5111]: https://github.com/DataDog/dd-trace-rb/issues/5111
5196
5226
  [#5122]: https://github.com/DataDog/dd-trace-rb/issues/5122
5197
5227
  [#5144]: https://github.com/DataDog/dd-trace-rb/issues/5144
5198
5228
  [#5145]: https://github.com/DataDog/dd-trace-rb/issues/5145
@@ -5251,7 +5281,20 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
5251
5281
  [#5448]: https://github.com/DataDog/dd-trace-rb/issues/5448
5252
5282
  [#5449]: https://github.com/DataDog/dd-trace-rb/issues/5449
5253
5283
  [#5461]: https://github.com/DataDog/dd-trace-rb/issues/5461
5284
+ [#5468]: https://github.com/DataDog/dd-trace-rb/issues/5468
5254
5285
  [#5469]: https://github.com/DataDog/dd-trace-rb/issues/5469
5286
+ [#5483]: https://github.com/DataDog/dd-trace-rb/issues/5483
5287
+ [#5484]: https://github.com/DataDog/dd-trace-rb/issues/5484
5288
+ [#5499]: https://github.com/DataDog/dd-trace-rb/issues/5499
5289
+ [#5509]: https://github.com/DataDog/dd-trace-rb/issues/5509
5290
+ [#5531]: https://github.com/DataDog/dd-trace-rb/issues/5531
5291
+ [#5564]: https://github.com/DataDog/dd-trace-rb/issues/5564
5292
+ [#5573]: https://github.com/DataDog/dd-trace-rb/issues/5573
5293
+ [#5574]: https://github.com/DataDog/dd-trace-rb/issues/5574
5294
+ [#5579]: https://github.com/DataDog/dd-trace-rb/issues/5579
5295
+ [#5580]: https://github.com/DataDog/dd-trace-rb/issues/5580
5296
+ [#5587]: https://github.com/DataDog/dd-trace-rb/issues/5587
5297
+ [#5594]: https://github.com/DataDog/dd-trace-rb/issues/5594
5255
5298
  [@AdrianLC]: https://github.com/AdrianLC
5256
5299
  [@Azure7111]: https://github.com/Azure7111
5257
5300
  [@BabyGroot]: https://github.com/BabyGroot
@@ -122,6 +122,24 @@ size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capa
122
122
  return error_msg_size;
123
123
  }
124
124
 
125
+ static void verify_libdatadog_version(void) {
126
+ rb_eval_string(
127
+ "require 'libdatadog';"
128
+ "expected = '" EXPECTED_LIBDATADOG_VERSION "';"
129
+ "if expected != Libdatadog::VERSION;"
130
+ "raise(LoadError, <<MSG\n"
131
+ "The `datadog` gem needs to be reinstalled whenever the `libdatadog` gem version is changed. "
132
+ "The currently-installed version of `datadog` was built to work with `libdatadog` gem version #{expected} "
133
+ "but the currently-loaded version of `libdatadog` is #{Libdatadog::VERSION}. "
134
+ "To fix this, reinstall the `datadog` gem (e.g. `bundle exec gem pristine datadog`) "
135
+ "or contact Datadog support for help at <https://docs.datadoghq.com/help/>.\n"
136
+ "MSG\n"
137
+ ");"
138
+ "end"
139
+ );
140
+ }
141
+
125
142
  void datadog_ruby_common_init(void) {
126
143
  telemetry_message_id = rb_intern("@telemetry_message");
144
+ verify_libdatadog_version();
127
145
  }
@@ -102,3 +102,13 @@ static inline VALUE get_error_details_and_drop(ddog_Error *error) {
102
102
  // Returns the amount of characters written to string (which are necessarily
103
103
  // bounded by capacity - 1 since the string will be null-terminated).
104
104
  size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capacity);
105
+
106
+ #define IMEMO_MASK 0x0f
107
+
108
+ // Returns the imemo type of an imemo object.
109
+ // This mask is the same between Ruby 2.5 and 3.3-preview3. Furthermore, the intention of this method is to be used
110
+ // to call `rb_imemo_name` which correctly handles invalid numbers so even if the mask changes in the future, at most
111
+ // we'll get incorrect results (and never a VM crash)
112
+ static inline int ddtrace_imemo_type(VALUE imemo) {
113
+ return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
114
+ }
@@ -217,6 +217,8 @@ unless Datadog::LibdatadogExtconfHelpers.configure_libdatadog(extconf_folder: __
217
217
  skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::FAILED_TO_CONFIGURE_LIBDATADOG)
218
218
  end
219
219
 
220
+ Datadog::LibdatadogExtconfHelpers.add_libdatadog_version_define
221
+
220
222
  unless have_type("atomic_int", ["stdatomic.h"])
221
223
  skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::COMPILER_ATOMIC_MISSING)
222
224
  end
@@ -51,12 +51,8 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
51
51
 
52
52
  VALUE version = datadog_gem_version();
53
53
 
54
- // Tags and endpoint are heap-allocated, so after here we can't raise exceptions otherwise we'll leak this memory
54
+ // Tags are heap-allocated, so after here we can't raise exceptions otherwise we'll leak this memory
55
55
  // Start of exception-free zone to prevent leaks {{
56
- ddog_Endpoint *endpoint = ddog_endpoint_from_url(char_slice_from_ruby_string(agent_base_url));
57
- if (endpoint == NULL) {
58
- raise_error(rb_eRuntimeError, "Failed to create endpoint from agent_base_url: %"PRIsVALUE, agent_base_url);
59
- }
60
56
  ddog_Vec_Tag tags = convert_tags(tags_as_array);
61
57
 
62
58
  ddog_crasht_Config config = {
@@ -73,7 +69,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
73
69
  // overriding what Ruby set up seems a saner default to keep anyway.
74
70
  .create_alt_stack = false,
75
71
  .use_alt_stack = true,
76
- .endpoint = endpoint,
72
+ .endpoint = {.url = char_slice_from_ruby_string(agent_base_url)},
77
73
  .resolve_frames = DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER,
78
74
  .timeout_ms = FIX2INT(upload_timeout_seconds) * 1000,
79
75
  };
@@ -106,11 +102,12 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
106
102
  ) :
107
103
  ddog_crasht_update_on_fork(config, receiver_config, metadata);
108
104
 
109
- first_init = false;
105
+ // We use first_init to know which of [init, reconfigure] needs to be called. BUT if init failed we actually need
106
+ // to call init next time again, not reconfigure.
107
+ if (result.tag == DDOG_VOID_RESULT_OK) first_init = false;
110
108
 
111
109
  // Clean up before potentially raising any exceptions
112
110
  ddog_Vec_Tag_drop(tags);
113
- ddog_endpoint_drop(endpoint);
114
111
  // }} End of exception-free zone to prevent leaks
115
112
 
116
113
  CHECK_VOID_RESULT("Failed to start/update the crash tracker", result);
@@ -122,6 +122,24 @@ size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capa
122
122
  return error_msg_size;
123
123
  }
124
124
 
125
+ static void verify_libdatadog_version(void) {
126
+ rb_eval_string(
127
+ "require 'libdatadog';"
128
+ "expected = '" EXPECTED_LIBDATADOG_VERSION "';"
129
+ "if expected != Libdatadog::VERSION;"
130
+ "raise(LoadError, <<MSG\n"
131
+ "The `datadog` gem needs to be reinstalled whenever the `libdatadog` gem version is changed. "
132
+ "The currently-installed version of `datadog` was built to work with `libdatadog` gem version #{expected} "
133
+ "but the currently-loaded version of `libdatadog` is #{Libdatadog::VERSION}. "
134
+ "To fix this, reinstall the `datadog` gem (e.g. `bundle exec gem pristine datadog`) "
135
+ "or contact Datadog support for help at <https://docs.datadoghq.com/help/>.\n"
136
+ "MSG\n"
137
+ ");"
138
+ "end"
139
+ );
140
+ }
141
+
125
142
  void datadog_ruby_common_init(void) {
126
143
  telemetry_message_id = rb_intern("@telemetry_message");
144
+ verify_libdatadog_version();
127
145
  }
@@ -102,3 +102,13 @@ static inline VALUE get_error_details_and_drop(ddog_Error *error) {
102
102
  // Returns the amount of characters written to string (which are necessarily
103
103
  // bounded by capacity - 1 since the string will be null-terminated).
104
104
  size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capacity);
105
+
106
+ #define IMEMO_MASK 0x0f
107
+
108
+ // Returns the imemo type of an imemo object.
109
+ // This mask is the same between Ruby 2.5 and 3.3-preview3. Furthermore, the intention of this method is to be used
110
+ // to call `rb_imemo_name` which correctly handles invalid numbers so even if the mask changes in the future, at most
111
+ // we'll get incorrect results (and never a VM crash)
112
+ static inline int ddtrace_imemo_type(VALUE imemo) {
113
+ return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
114
+ }
@@ -0,0 +1,79 @@
1
+ #include <stdbool.h>
2
+
3
+ #include "datadog_ruby_common.h"
4
+
5
+ // Prototypes for Ruby functions declared in internal Ruby headers.
6
+ VALUE rb_iseqw_new(const void *iseq);
7
+ int rb_objspace_internal_object_p(VALUE obj);
8
+ void rb_objspace_each_objects(
9
+ int (*callback)(void *start, void *end, size_t stride, void *data),
10
+ void *data);
11
+
12
+ #define IMEMO_TYPE_ISEQ 7
13
+
14
+ // The ID value of the string "mesg" which is used in Ruby source as
15
+ // id_mesg or idMesg, and is used to set and retrieve the exception message
16
+ // from standard library exception classes like NameError.
17
+ static ID id_mesg;
18
+
19
+ // Returns whether the argument is an IMEMO of type ISEQ.
20
+ static bool ddtrace_imemo_iseq_p(VALUE v) {
21
+ return rb_objspace_internal_object_p(v) && RB_TYPE_P(v, T_IMEMO) && ddtrace_imemo_type(v) == IMEMO_TYPE_ISEQ;
22
+ }
23
+
24
+ static int ddtrace_di_os_obj_of_i(void *vstart, void *vend, size_t stride, void *data)
25
+ {
26
+ VALUE *array = (VALUE *)data;
27
+
28
+ VALUE v = (VALUE)vstart;
29
+ for (; v != (VALUE)vend; v += stride) {
30
+ if (ddtrace_imemo_iseq_p(v)) {
31
+ VALUE iseq = rb_iseqw_new((void *) v);
32
+ rb_ary_push(*array, iseq);
33
+ }
34
+ }
35
+
36
+ return 0;
37
+ }
38
+
39
+ /*
40
+ Returns all RubyVM::InstructionSequence objects existing in the current process.
41
+
42
+ This uses the same approach as ruby/debug's iseq_collector.c:
43
+ https://github.com/ruby/debug/blob/master/ext/debug/iseq_collector.c
44
+ */
45
+ static VALUE all_iseqs(DDTRACE_UNUSED VALUE _self) {
46
+ VALUE array = rb_ary_new();
47
+ rb_objspace_each_objects(ddtrace_di_os_obj_of_i, &array);
48
+ return array;
49
+ }
50
+
51
+ /*
52
+ * call-seq:
53
+ * DI.exception_message(exception) -> String | Object
54
+ *
55
+ * Returns the exception message associated with the exception via the
56
+ * exception's constructor.
57
+ *
58
+ * This method does not invoke Ruby code and as such will not call
59
+ * the +message+ method, if one is defined on the exception object.
60
+ *
61
+ * Normally, the exception message is a string, however there is no
62
+ * type enforcement done by Ruby for the messages and objects of arbitrary
63
+ * classes can be passed to exception constructors and will, subsequently,
64
+ * be returned by this method.
65
+ *
66
+ * @param exception [Exception] The exception object
67
+ * @return [String | Object] The exception message
68
+ */
69
+ static VALUE exception_message(DDTRACE_UNUSED VALUE _self, VALUE exception) {
70
+ return rb_ivar_get(exception, id_mesg);
71
+ }
72
+
73
+ void di_init(VALUE datadog_module) {
74
+ id_mesg = rb_intern("mesg");
75
+
76
+ VALUE di_module = rb_define_module_under(datadog_module, "DI");
77
+ rb_define_singleton_method(di_module, "all_iseqs", all_iseqs, 0);
78
+ rb_define_singleton_method(di_module, "exception_message", exception_message, 1);
79
+ }
@@ -81,6 +81,8 @@ unless Datadog::LibdatadogExtconfHelpers.configure_libdatadog(extconf_folder: __
81
81
  skip_building_extension!('there was a problem in setting up the `libdatadog` dependency')
82
82
  end
83
83
 
84
+ Datadog::LibdatadogExtconfHelpers.add_libdatadog_version_define
85
+
84
86
  # Tag the native extension library with the Ruby version and Ruby platform.
85
87
  # This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
86
88
  # the wrong library is never loaded.
@@ -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 = '~> 29.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.
@@ -162,6 +162,14 @@ module Datadog
162
162
  end
163
163
  end
164
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
+
165
173
  # Note: This helper is currently only used in the `libdatadog_api/extconf.rb` BUT still lives here to enable testing.
166
174
  def self.load_libdatadog_or_get_issue
167
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
@@ -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