datadog 2.43.0 → 2.45.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +136 -132
  4. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
  5. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
  6. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
  7. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
  9. data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +146 -109
  11. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
  12. data/ext/datadog_profiling_native_extension/extconf.rb +6 -2
  13. data/ext/datadog_profiling_native_extension/heap_recorder.c +2 -2
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +122 -53
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -2
  16. data/ext/datadog_profiling_native_extension/profiling.c +22 -23
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +3 -1
  18. data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -0
  19. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +0 -2
  20. data/ext/datadog_profiling_native_extension/stack_recorder.c +10 -28
  21. data/ext/datadog_profiling_native_extension/stack_recorder.h +0 -1
  22. data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
  23. data/ext/datadog_profiling_native_extension/time_helpers.h +13 -14
  24. data/ext/libdatadog_api/di.c +8 -3
  25. data/ext/libdatadog_api/extconf.rb +1 -1
  26. data/ext/libdatadog_api/feature_flags.c +8 -0
  27. data/ext/libdatadog_api/init.c +3 -1
  28. data/ext/libdatadog_api/otel_thread_context.c +38 -17
  29. data/ext/libdatadog_api/trace_exporter.c +77 -11
  30. data/ext/libdatadog_extconf_helpers.rb +14 -9
  31. data/lib/datadog/ai_guard/component.rb +17 -8
  32. data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
  33. data/lib/datadog/ai_guard/configuration.rb +13 -0
  34. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
  35. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
  36. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
  37. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
  38. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
  39. data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
  40. data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
  41. data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
  42. data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
  43. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
  44. data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
  45. data/lib/datadog/ai_guard/evaluation/request.rb +4 -60
  46. data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
  47. data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
  48. data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
  49. data/lib/datadog/ai_guard/evaluation.rb +25 -11
  50. data/lib/datadog/ai_guard/ext.rb +1 -0
  51. data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +6 -4
  52. data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
  53. data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
  54. data/lib/datadog/ai_guard/redaction/result.rb +29 -0
  55. data/lib/datadog/ai_guard/redaction.rb +80 -0
  56. data/lib/datadog/ai_guard.rb +58 -57
  57. data/lib/datadog/core/configuration/components.rb +9 -1
  58. data/lib/datadog/core/configuration/settings.rb +5 -2
  59. data/lib/datadog/core/configuration/supported_configurations.rb +8 -0
  60. data/lib/datadog/core/remote/client/capabilities.rb +10 -4
  61. data/lib/datadog/core/remote/component.rb +9 -2
  62. data/lib/datadog/core/remote/worker.rb +8 -0
  63. data/lib/datadog/di/instrumenter.rb +1 -1
  64. data/lib/datadog/di/probe_manager.rb +10 -5
  65. data/lib/datadog/di/proc_responder.rb +1 -1
  66. data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
  67. data/lib/datadog/open_feature/agentless/parser.rb +43 -0
  68. data/lib/datadog/open_feature/agentless/response.rb +19 -0
  69. data/lib/datadog/open_feature/agentless/transport.rb +103 -0
  70. data/lib/datadog/open_feature/component.rb +57 -1
  71. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
  72. data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
  73. data/lib/datadog/open_feature/configuration/source.rb +43 -0
  74. data/lib/datadog/open_feature/configuration.rb +118 -0
  75. data/lib/datadog/open_feature/native_evaluator.rb +1 -14
  76. data/lib/datadog/open_feature/remote.rb +5 -5
  77. data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
  78. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +7 -2
  79. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
  80. data/lib/datadog/profiling/component.rb +2 -3
  81. data/lib/datadog/profiling/exporter.rb +7 -5
  82. data/lib/datadog/symbol_database/extractor.rb +84 -102
  83. data/lib/datadog/tracing/component.rb +3 -1
  84. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  85. data/lib/datadog/tracing/configuration/settings.rb +9 -2
  86. data/lib/datadog/tracing/context.rb +24 -2
  87. data/lib/datadog/tracing/context_provider.rb +9 -5
  88. data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
  89. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -3
  90. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -0
  91. data/lib/datadog/tracing/metadata/ext.rb +3 -0
  92. data/lib/datadog/tracing/otel_thread_context.rb +76 -11
  93. data/lib/datadog/tracing/tracer.rb +15 -2
  94. data/lib/datadog/tracing/transport/native.rb +23 -22
  95. data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
  96. data/lib/datadog/tracing/transport/traces.rb +1 -1
  97. data/lib/datadog/version.rb +1 -1
  98. metadata +23 -7
  99. data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
@@ -28,10 +28,8 @@ long monotonic_to_system_epoch_ns(monotonic_to_system_epoch_state *state, long m
28
28
  (state->delta_to_epoch_ns + monotonic_wall_time_ns > state->system_epoch_ns_reference + SECONDS_AS_NS(60));
29
29
 
30
30
  if (reference_needs_update) {
31
- state->system_epoch_ns_reference = system_epoch_time_now_ns(RAISE_ON_FAILURE);
32
- long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
33
-
34
- state->delta_to_epoch_ns = state->system_epoch_ns_reference - current_monotonic_wall_time_ns;
31
+ state->system_epoch_ns_reference = system_epoch_time_now_ns();
32
+ state->delta_to_epoch_ns = state->system_epoch_ns_reference - monotonic_wall_time_now_ns();
35
33
  }
36
34
 
37
35
  return state->delta_to_epoch_ns + monotonic_wall_time_ns;
@@ -1,17 +1,15 @@
1
1
  #pragma once
2
2
 
3
- #include <stdbool.h>
3
+ #include <ruby.h>
4
4
  #include <errno.h>
5
+ #include <stdbool.h>
5
6
  #include <time.h>
6
7
 
7
8
  #include "extconf.h"
8
- #include "ruby_helpers.h"
9
-
10
- #define SECONDS_AS_NS(value) (value * 1000 * 1000 * 1000L)
11
- #define MILLIS_AS_NS(value) (value * 1000 * 1000L)
12
- #define MICROS_AS_NS(value) (value * 1000L)
13
9
 
14
- typedef enum { RAISE_ON_FAILURE, DO_NOT_RAISE_ON_FAILURE } raise_on_failure_setting;
10
+ #define SECONDS_AS_NS(value) ((value) * 1000L * 1000L * 1000L)
11
+ #define MILLIS_AS_NS(value) ((value) * 1000L * 1000L)
12
+ #define MICROS_AS_NS(value) ((value) * 1000L)
15
13
 
16
14
  #define INVALID_TIME -1
17
15
 
@@ -23,12 +21,13 @@ typedef struct {
23
21
 
24
22
  #define MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER {.system_epoch_ns_reference = INVALID_TIME}
25
23
 
26
- static inline long retrieve_clock_as_ns(clockid_t clock_id, raise_on_failure_setting raise_on_failure) {
24
+ // clock_id MUST be one of CLOCK_REALTIME/CLOCK_MONOTONIC_FOR_PROFILING/CLOCK_MONOTONIC_COARSE_FOR_PROFILING
25
+ // as for those we're guaranteed to never see failures here unless something is really off
26
+ static inline long retrieve_clock_as_ns(clockid_t clock_id) {
27
27
  struct timespec clock_value;
28
28
 
29
- if (clock_gettime(clock_id, &clock_value) != 0) {
30
- if (raise_on_failure == RAISE_ON_FAILURE) ENFORCE_SUCCESS_GVL(errno);
31
- return 0;
29
+ if (RB_UNLIKELY(clock_gettime(clock_id, &clock_value) != 0)) {
30
+ rb_bug("[ddtrace] clock_gettime(%d) unexpectedly failed (errno=%d) but it should always succeed, libc/kernel bug?", (int) clock_id, errno);
32
31
  }
33
32
 
34
33
  return clock_value.tv_nsec + SECONDS_AS_NS(clock_value.tv_sec);
@@ -43,8 +42,8 @@ static inline long retrieve_clock_as_ns(clockid_t clock_id, raise_on_failure_set
43
42
  #define CLOCK_MONOTONIC_COARSE_FOR_PROFILING CLOCK_MONOTONIC_COARSE
44
43
  #endif
45
44
 
46
- static inline long monotonic_wall_time_now_ns(raise_on_failure_setting raise_on_failure) { return retrieve_clock_as_ns(CLOCK_MONOTONIC_FOR_PROFILING, raise_on_failure); }
47
- static inline long system_epoch_time_now_ns(raise_on_failure_setting raise_on_failure) { return retrieve_clock_as_ns(CLOCK_REALTIME, raise_on_failure); }
45
+ static inline long monotonic_wall_time_now_ns(void) { return retrieve_clock_as_ns(CLOCK_MONOTONIC_FOR_PROFILING); }
46
+ static inline long system_epoch_time_now_ns(void) { return retrieve_clock_as_ns(CLOCK_REALTIME); }
48
47
 
49
48
  // Coarse instants use CLOCK_MONOTONIC_COARSE on Linux which is expected to provide resolution in the millisecond range:
50
49
  // https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/sect-posix_clocks#Using_clock_getres_to_compare_clock_resolution
@@ -57,7 +56,7 @@ typedef struct {
57
56
  static inline coarse_instant to_coarse_instant(long timestamp_ns) { return (coarse_instant) {.timestamp_ns = timestamp_ns}; }
58
57
 
59
58
  static inline coarse_instant monotonic_coarse_wall_time_now_ns(void) {
60
- return to_coarse_instant(retrieve_clock_as_ns(CLOCK_MONOTONIC_COARSE_FOR_PROFILING, DO_NOT_RAISE_ON_FAILURE));
59
+ return to_coarse_instant(retrieve_clock_as_ns(CLOCK_MONOTONIC_COARSE_FOR_PROFILING));
61
60
  }
62
61
 
63
62
  long monotonic_to_system_epoch_ns(monotonic_to_system_epoch_state *state, long monotonic_wall_time_ns);
@@ -2,6 +2,9 @@
2
2
 
3
3
  #include "datadog_ruby_common.h"
4
4
 
5
+ // rb_iseq is CRuby-specific
6
+ #ifndef TRUFFLERUBY
7
+
5
8
  // Prototypes for Ruby functions declared in internal Ruby headers.
6
9
  // rb_iseqw_new wraps an internal iseq pointer into a Ruby-visible
7
10
  // RubyVM::InstructionSequence object.
@@ -207,7 +210,9 @@ void di_init(VALUE datadog_module) {
207
210
  rb_define_singleton_method(di_module, "enter_probe", enter_probe, 0);
208
211
  rb_define_singleton_method(di_module, "leave_probe", leave_probe, 0);
209
212
  rb_define_singleton_method(di_module, "hash?", is_hash, 1);
210
- #ifdef HAVE_RB_ISEQ_TYPE
211
- rb_define_singleton_method(di_module, "iseq_type", iseq_type, 1);
212
- #endif
213
+ #ifdef HAVE_RB_ISEQ_TYPE
214
+ rb_define_singleton_method(di_module, "iseq_type", iseq_type, 1);
215
+ #endif
213
216
  }
217
+
218
+ #endif // TRUFFLERUBY
@@ -24,7 +24,7 @@ end
24
24
  if ENV["DD_NO_EXTENSION"].to_s.strip.downcase == "true"
25
25
  skip_building_extension!("the `DD_NO_EXTENSION` environment variable is/was set to `true` during installation")
26
26
  end
27
- skip_building_extension!("current Ruby VM is not supported") if RUBY_ENGINE != "ruby"
27
+ skip_building_extension!("current Ruby VM is not supported") unless %w[ruby truffleruby].include?(RUBY_ENGINE)
28
28
  skip_building_extension!("Microsoft Windows is not supported") if Gem.win_platform?
29
29
 
30
30
  libdatadog_issue = Datadog::LibdatadogExtconfHelpers.load_libdatadog_or_get_issue
@@ -13,6 +13,7 @@
13
13
  // Forward declarations
14
14
  static VALUE configuration_new(VALUE klass, VALUE json_str);
15
15
  static void configuration_free(void *ptr);
16
+ static VALUE configuration_get_observe_full_evaluation_data(VALUE self);
16
17
  static VALUE configuration_get_assignment(
17
18
  VALUE self, VALUE flag_key, VALUE expected_type, VALUE context);
18
19
 
@@ -94,6 +95,7 @@ void feature_flags_init(VALUE core_module) {
94
95
  rb_undef_alloc_func(configuration_class);
95
96
  rb_define_singleton_method(configuration_class, "new", configuration_new, 1);
96
97
  rb_define_method(configuration_class, "get_assignment", configuration_get_assignment, 3);
98
+ rb_define_method(configuration_class, "observe_full_evaluation_data", configuration_get_observe_full_evaluation_data, 0);
97
99
 
98
100
  rb_gc_register_address(&resolution_details_class);
99
101
  resolution_details_class = rb_define_class_under(feature_flags_module, "ResolutionDetails", rb_cObject);
@@ -137,6 +139,12 @@ static VALUE configuration_new(VALUE klass, VALUE json_str) {
137
139
  return TypedData_Wrap_Struct(klass, &configuration_data_type, result.ok);
138
140
  }
139
141
 
142
+ static VALUE configuration_get_observe_full_evaluation_data(VALUE self) {
143
+ ddog_ffe_Handle_Configuration configuration =
144
+ (ddog_ffe_Handle_Configuration)rb_check_typeddata(self, &configuration_data_type);
145
+ return ddog_ffe_configuration_get_observe_full_evaluation_data(configuration) ? Qtrue : Qfalse;
146
+ }
147
+
140
148
  static void configuration_free(void *ptr) {
141
149
  ddog_ffe_Handle_Configuration config = (ddog_ffe_Handle_Configuration)ptr;
142
150
  ddog_ffe_configuration_drop(&config);
@@ -25,7 +25,9 @@ void DDTRACE_EXPORT Init_libdatadog_api(void) {
25
25
  library_config_init(core_module);
26
26
  ddsketch_init(core_module);
27
27
  feature_flags_init(core_module);
28
- di_init(datadog_module);
28
+ #ifndef TRUFFLERUBY
29
+ di_init(datadog_module);
30
+ #endif
29
31
 
30
32
  VALUE tracing_module = rb_define_module_under(datadog_module, "Tracing");
31
33
  trace_exporter_init(tracing_module);
@@ -4,7 +4,12 @@
4
4
  #include "datadog_ruby_common.h"
5
5
  #include "otel_thread_context.h"
6
6
 
7
- #ifdef __linux__
7
+ // Relies on a :fiber_switch TracePoint that TruffleRuby does not support yet
8
+ #if defined(__linux__) && !defined(TRUFFLERUBY)
9
+ #define OTEL_THREAD_CONTEXT_SUPPORTED
10
+ #endif
11
+
12
+ #ifdef OTEL_THREAD_CONTEXT_SUPPORTED
8
13
  #include <datadog/otel-thread-ctx.h>
9
14
  extern __thread const uint8_t *otel_thread_ctx_v1;
10
15
 
@@ -32,18 +37,18 @@ static VALUE native_read(VALUE _self);
32
37
  void otel_thread_context_init(VALUE tracing_module) {
33
38
  fiber_context_slot = rb_intern("__dd_otel_fiber_context");
34
39
 
35
- VALUE otel_thread_context_module = rb_define_module_under(tracing_module, "OTelThreadContext");
40
+ VALUE otel_thread_context_class = rb_define_class_under(tracing_module, "OTelThreadContext", rb_cObject);
36
41
 
37
- rb_define_singleton_method(otel_thread_context_module, "_native_enable", native_enable, 0);
38
- rb_define_singleton_method(otel_thread_context_module, "_native_set", native_set, 3);
39
- rb_define_singleton_method(otel_thread_context_module, "_native_clear", native_clear, 0);
40
- rb_define_singleton_method(otel_thread_context_module, "_native_supported?", native_supported_p, 0);
42
+ rb_define_method(otel_thread_context_class, "_native_enable", native_enable, 0);
43
+ rb_define_method(otel_thread_context_class, "_native_set", native_set, 3);
44
+ rb_define_method(otel_thread_context_class, "_native_clear", native_clear, 0);
45
+ rb_define_method(otel_thread_context_class, "_native_supported?", native_supported_p, 0);
41
46
 
42
- VALUE testing_module = rb_define_module_under(otel_thread_context_module, "Testing");
47
+ VALUE testing_module = rb_define_module_under(otel_thread_context_class, "Testing");
43
48
  rb_define_singleton_method(testing_module, "_native_read", native_read, 0);
44
49
  }
45
50
 
46
- #ifdef __linux__
51
+ #ifdef OTEL_THREAD_CONTEXT_SUPPORTED
47
52
  static ddog_ThreadContextHandle *get_fiber_handle_for(VALUE thread) {
48
53
  VALUE obj = rb_thread_local_aref(thread, fiber_context_slot);
49
54
 
@@ -56,6 +61,22 @@ void otel_thread_context_init(VALUE tracing_module) {
56
61
  return get_fiber_handle_for(rb_thread_current());
57
62
  }
58
63
 
64
+ static void pack_span_id(VALUE id, uint8_t bytes[8]) {
65
+ // Tracing::Utils.next_id caps generated span IDs at (1 << 62) - 1, which fits
66
+ // in a Fixnum on 64-bit Ruby. Packing them directly avoids rb_integer_pack's
67
+ // generic conversion and format checks on each context update.
68
+ // https://github.com/DataDog/dd-trace-rb/blob/0b10368e0a2940c1f4d50c9739b9973b4f8bc32a/lib/datadog/tracing/utils.rb#L20-L37
69
+ if (FIXNUM_P(id)) {
70
+ uint64_t number = (uint64_t) FIX2LONG(id);
71
+ for (int i = 7; i >= 0; i--) {
72
+ bytes[i] = (uint8_t) number;
73
+ number >>= 8;
74
+ }
75
+ } else {
76
+ rb_integer_pack(id, bytes, 1, sizeof(uint64_t), 0, INTEGER_PACK_BIG_ENDIAN);
77
+ }
78
+ }
79
+
59
80
  static void store_current_fiber_handle(ddog_ThreadContextHandle *handle) {
60
81
  VALUE obj = TypedData_Wrap_Struct(rb_cObject, &otel_ctx_handle_t, handle);
61
82
  rb_thread_local_aset(rb_thread_current(), fiber_context_slot, obj);
@@ -137,7 +158,7 @@ void otel_thread_context_init(VALUE tracing_module) {
137
158
  #endif
138
159
 
139
160
  static VALUE native_enable(DDTRACE_UNUSED VALUE _self) {
140
- #ifdef __linux__
161
+ #ifdef OTEL_THREAD_CONTEXT_SUPPORTED
141
162
  if (otel_context_enabled) return Qtrue;
142
163
  otel_context_enabled = true;
143
164
 
@@ -163,7 +184,7 @@ static VALUE native_enable(DDTRACE_UNUSED VALUE _self) {
163
184
  }
164
185
 
165
186
  static VALUE native_supported_p(DDTRACE_UNUSED VALUE _self) {
166
- #ifdef __linux__
187
+ #ifdef OTEL_THREAD_CONTEXT_SUPPORTED
167
188
  return Qtrue;
168
189
  #else
169
190
  return Qfalse;
@@ -176,17 +197,17 @@ static VALUE native_set(
176
197
  DDTRACE_UNUSED VALUE span_id,
177
198
  DDTRACE_UNUSED VALUE local_root_span_id
178
199
  ) {
179
- #ifdef __linux__
200
+ #ifdef OTEL_THREAD_CONTEXT_SUPPORTED
180
201
  if (!otel_context_enabled) return Qfalse;
181
202
 
182
203
  uint8_t trace_id_bytes[16];
183
204
  uint8_t span_id_bytes[8];
184
205
  uint8_t local_root_span_id_bytes[8];
185
206
 
186
- const int BIG_ENDIAN_PACK_FLAGS = INTEGER_PACK_MSWORD_FIRST | INTEGER_PACK_BIG_ENDIAN;
187
- rb_integer_pack(trace_id, trace_id_bytes, sizeof(trace_id_bytes), 1, 0, BIG_ENDIAN_PACK_FLAGS);
188
- rb_integer_pack(span_id, span_id_bytes, sizeof(span_id_bytes), 1, 0, BIG_ENDIAN_PACK_FLAGS);
189
- rb_integer_pack(local_root_span_id, local_root_span_id_bytes, sizeof(local_root_span_id_bytes), 1, 0, BIG_ENDIAN_PACK_FLAGS);
207
+ // Note: We use 2 words of size 8, as `rb_integer_pack` has a fast path for it (vs 16 bytes of size 1)
208
+ rb_integer_pack(trace_id, trace_id_bytes, 2, sizeof(uint64_t), 0, INTEGER_PACK_BIG_ENDIAN);
209
+ pack_span_id(span_id, span_id_bytes);
210
+ pack_span_id(local_root_span_id, local_root_span_id_bytes);
190
211
 
191
212
  ddog_ThreadContextHandle *handle = get_current_fiber_handle();
192
213
 
@@ -207,7 +228,7 @@ static VALUE native_set(
207
228
  }
208
229
 
209
230
  static VALUE native_clear(DDTRACE_UNUSED VALUE _self) {
210
- #ifdef __linux__
231
+ #ifdef OTEL_THREAD_CONTEXT_SUPPORTED
211
232
  if (!otel_context_enabled) return Qfalse;
212
233
 
213
234
  ddog_ThreadContextHandle *detached = ddog_otel_thread_ctx_detach();
@@ -220,7 +241,7 @@ static VALUE native_clear(DDTRACE_UNUSED VALUE _self) {
220
241
  }
221
242
 
222
243
  static VALUE native_read(DDTRACE_UNUSED VALUE _self) {
223
- #ifdef __linux__
244
+ #ifdef OTEL_THREAD_CONTEXT_SUPPORTED
224
245
  const uint8_t *raw = otel_thread_ctx_v1;
225
246
  if (!raw) return Qnil;
226
247
 
@@ -34,6 +34,7 @@ static VALUE _native_from_span(VALUE klass, VALUE span);
34
34
 
35
35
  /* TraceExporter methods */
36
36
  static VALUE _native_exporter_new(int argc, VALUE *argv, VALUE klass);
37
+ static VALUE _native_close(VALUE self);
37
38
  static VALUE _native_send_traces(VALUE self, VALUE traces, VALUE native_events_supported);
38
39
  static VALUE _native_before_fork(VALUE self);
39
40
  static VALUE _native_after_fork_in_parent(VALUE self);
@@ -1410,9 +1411,10 @@ static VALUE create_ok_response(long trace_count, VALUE payload) {
1410
1411
  * TraceExporter._native_new(
1411
1412
  * url:, tracer_version: nil, language: nil, language_version: nil,
1412
1413
  * language_interpreter: nil, hostname: nil, env: nil,
1413
- * service: nil, version: nil) -> TraceExporter
1414
+ * service: nil, version: nil, client_computed_stats: false,
1415
+ * timeout_milliseconds: nil) -> TraceExporter
1414
1416
  *
1415
- * +url+ is required (String). All other arguments may be nil.
1417
+ * +url+ is required (String). Metadata arguments may be nil.
1416
1418
  * ======================================================================== */
1417
1419
 
1418
1420
  static VALUE _native_exporter_new(
@@ -1431,6 +1433,8 @@ static VALUE _native_exporter_new(
1431
1433
  VALUE rb_env = rb_hash_fetch(options, ID2SYM(rb_intern("env")));
1432
1434
  VALUE rb_service = rb_hash_fetch(options, ID2SYM(rb_intern("service")));
1433
1435
  VALUE rb_version = rb_hash_fetch(options, ID2SYM(rb_intern("version")));
1436
+ VALUE rb_client_computed_stats = rb_hash_lookup2(options, ID2SYM(rb_intern("client_computed_stats")), Qfalse);
1437
+ VALUE rb_timeout_milliseconds = rb_hash_lookup2(options, ID2SYM(rb_intern("timeout_milliseconds")), Qnil);
1434
1438
 
1435
1439
  /* Phase 1: validate types (may raise, no Rust resources yet) */
1436
1440
  ENFORCE_TYPE(rb_url, T_STRING);
@@ -1442,6 +1446,20 @@ static VALUE _native_exporter_new(
1442
1446
  if (rb_env != Qnil) ENFORCE_TYPE(rb_env, T_STRING);
1443
1447
  if (rb_service != Qnil) ENFORCE_TYPE(rb_service, T_STRING);
1444
1448
  if (rb_version != Qnil) ENFORCE_TYPE(rb_version, T_STRING);
1449
+ if (rb_client_computed_stats != Qtrue && rb_client_computed_stats != Qfalse) {
1450
+ rb_raise(rb_eTypeError, "client_computed_stats must be true or false");
1451
+ }
1452
+
1453
+ long long timeout_milliseconds = 0;
1454
+ if (rb_timeout_milliseconds != Qnil) {
1455
+ if (!RB_TYPE_P(rb_timeout_milliseconds, T_FIXNUM) && !RB_TYPE_P(rb_timeout_milliseconds, T_BIGNUM)) {
1456
+ rb_raise(rb_eTypeError, "timeout_milliseconds must be an Integer or nil");
1457
+ }
1458
+ timeout_milliseconds = NUM2LL(rb_timeout_milliseconds);
1459
+ if (timeout_milliseconds < 0) {
1460
+ rb_raise(rb_eRangeError, "timeout_milliseconds must be non-negative");
1461
+ }
1462
+ }
1445
1463
 
1446
1464
  /* Phase 2: configure before creating the separately-owned runtime. */
1447
1465
  ddog_TraceExporterConfig *config = NULL;
@@ -1457,6 +1475,22 @@ static VALUE _native_exporter_new(
1457
1475
  set_config_field(config, ddog_trace_exporter_config_set_service, rb_service, "service");
1458
1476
  set_config_field(config, ddog_trace_exporter_config_set_version, rb_version, "version");
1459
1477
 
1478
+ if (rb_timeout_milliseconds != Qnil) {
1479
+ ddog_TraceExporterError *timeout_err = ddog_trace_exporter_config_set_connection_timeout(
1480
+ config, (uint64_t)timeout_milliseconds);
1481
+ if (timeout_err != NULL) {
1482
+ ddog_trace_exporter_config_free(config);
1483
+ check_exporter_error("Failed to configure native transport timeout", timeout_err);
1484
+ }
1485
+ }
1486
+
1487
+ ddog_TraceExporterError *stats_err = ddog_trace_exporter_config_set_client_computed_stats(
1488
+ config, rb_client_computed_stats == Qtrue);
1489
+ if (stats_err != NULL) {
1490
+ ddog_trace_exporter_config_free(config);
1491
+ check_exporter_error("Failed to configure client-computed stats", stats_err);
1492
+ }
1493
+
1460
1494
  /*
1461
1495
  * Create a SharedRuntime and attach it to the config before building the
1462
1496
  * exporter. The exporter holds a clone of the runtime's Arc; we keep our
@@ -1511,12 +1545,47 @@ static VALUE _native_exporter_new(
1511
1545
  * (Puma, Unicorn, Passenger).
1512
1546
  * ======================================================================== */
1513
1547
 
1548
+ static void *close_exporter_without_gvl(void *ptr) {
1549
+ trace_exporter_t *resources = ptr;
1550
+ if (resources->exporter != NULL) {
1551
+ ddog_trace_exporter_free(resources->exporter);
1552
+ resources->exporter = NULL;
1553
+ }
1554
+ if (resources->runtime != NULL) {
1555
+ ddog_shared_runtime_free(resources->runtime);
1556
+ resources->runtime = NULL;
1557
+ }
1558
+ return NULL;
1559
+ }
1560
+
1561
+ static VALUE close_exporter_body(VALUE ptr) {
1562
+ rb_thread_call_without_gvl(close_exporter_without_gvl, (void *)ptr, NULL, NULL);
1563
+ return Qnil;
1564
+ }
1565
+
1566
+ static VALUE close_exporter_ensure(VALUE ptr) {
1567
+ /* An interrupt may arrive before the no-GVL call starts. */
1568
+ close_exporter_without_gvl((void *)ptr);
1569
+ return Qnil;
1570
+ }
1571
+
1572
+ static VALUE _native_close(VALUE self) {
1573
+ trace_exporter_t *wrapper;
1574
+ TypedData_Get_Struct(self, trace_exporter_t, &trace_exporter_typed_data, wrapper);
1575
+ if (wrapper == NULL) return Qnil;
1576
+
1577
+ trace_exporter_t resources = *wrapper;
1578
+ wrapper->exporter = NULL;
1579
+ wrapper->runtime = NULL;
1580
+ rb_ensure(close_exporter_body, (VALUE)&resources, close_exporter_ensure, (VALUE)&resources);
1581
+ RB_GC_GUARD(self);
1582
+ return Qnil;
1583
+ }
1584
+
1514
1585
  static VALUE _native_before_fork(VALUE self) {
1515
1586
  trace_exporter_t *wrapper;
1516
1587
  TypedData_Get_Struct(self, trace_exporter_t, &trace_exporter_typed_data, wrapper);
1517
- if (wrapper == NULL || wrapper->runtime == NULL) {
1518
- raise_error(rb_eRuntimeError, "TraceExporter has not been initialized or was already freed");
1519
- }
1588
+ if (wrapper == NULL || wrapper->runtime == NULL) return Qnil;
1520
1589
  ddog_SharedRuntimeFFIError *err = ddog_shared_runtime_before_fork(wrapper->runtime);
1521
1590
  check_shared_runtime_error("Failed to prepare for fork", err);
1522
1591
  return Qnil;
@@ -1525,9 +1594,7 @@ static VALUE _native_before_fork(VALUE self) {
1525
1594
  static VALUE _native_after_fork_in_parent(VALUE self) {
1526
1595
  trace_exporter_t *wrapper;
1527
1596
  TypedData_Get_Struct(self, trace_exporter_t, &trace_exporter_typed_data, wrapper);
1528
- if (wrapper == NULL || wrapper->runtime == NULL) {
1529
- raise_error(rb_eRuntimeError, "TraceExporter has not been initialized or was already freed");
1530
- }
1597
+ if (wrapper == NULL || wrapper->runtime == NULL) return Qnil;
1531
1598
  ddog_SharedRuntimeFFIError *err = ddog_shared_runtime_after_fork_parent(wrapper->runtime);
1532
1599
  check_shared_runtime_error("Failed to restore after fork in parent", err);
1533
1600
  return Qnil;
@@ -1536,9 +1603,7 @@ static VALUE _native_after_fork_in_parent(VALUE self) {
1536
1603
  static VALUE _native_after_fork_in_child(VALUE self) {
1537
1604
  trace_exporter_t *wrapper;
1538
1605
  TypedData_Get_Struct(self, trace_exporter_t, &trace_exporter_typed_data, wrapper);
1539
- if (wrapper == NULL || wrapper->runtime == NULL) {
1540
- raise_error(rb_eRuntimeError, "TraceExporter has not been initialized or was already freed");
1541
- }
1606
+ if (wrapper == NULL || wrapper->runtime == NULL) return Qnil;
1542
1607
  ddog_SharedRuntimeFFIError *err = ddog_shared_runtime_after_fork_child(wrapper->runtime);
1543
1608
  check_shared_runtime_error("Failed to restore after fork in child", err);
1544
1609
  return Qnil;
@@ -1848,6 +1913,7 @@ void trace_exporter_init(VALUE tracing_module) {
1848
1913
  /* Instance: _native_send_traces(traces, native_events_supported) -> Array[Response] */
1849
1914
  rb_define_method(trace_exporter_class, "_native_send_traces",
1850
1915
  _native_send_traces, 2);
1916
+ rb_define_method(trace_exporter_class, "_native_close", _native_close, 0);
1851
1917
 
1852
1918
  /* Instance: fork safety hooks */
1853
1919
  rb_define_method(trace_exporter_class, "_native_before_fork",
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "rubygems"
4
4
  require "pathname"
5
+ require "shellwords"
5
6
 
6
7
  module Datadog
7
8
  # Contains a bunch of shared helpers that get used during building of extensions that link to libdatadog
@@ -10,7 +11,7 @@ module Datadog
10
11
  module LibdatadogExtconfHelpers
11
12
  # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
12
13
  # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
13
- LIBDATADOG_VERSION = "~> 40.0.0.2.0"
14
+ LIBDATADOG_VERSION = "~> 45.0.0.1.0"
14
15
 
15
16
  # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
16
17
  # libdatadog are moved after the extension gets compiled.
@@ -116,7 +117,10 @@ module Datadog
116
117
  extconf_folder:,
117
118
  libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder,
118
119
  gem_dir: Gem.dir,
119
- logger: Logging
120
+ logger: Logging,
121
+ target_incflags: $INCFLAGS,
122
+ target_ldflags: $LDFLAGS,
123
+ target_libs: $libs
120
124
  )
121
125
  return unless libdatadog_pkgconfig_folder
122
126
 
@@ -124,10 +128,9 @@ module Datadog
124
128
  libdir = "#{libdatadog_pkgconfig_folder}/../../lib"
125
129
  includedir = "#{libdatadog_pkgconfig_folder}/../../include"
126
130
 
127
- # Set mkmf global variables
128
- $INCFLAGS << " -I#{includedir}"
129
- $LDFLAGS << " -L#{libdir} -Wl,-rpath,#{libdir}"
130
- $libs << " -ldatadog_profiling"
131
+ target_incflags << " -I#{Shellwords.escape(includedir)}"
132
+ target_ldflags << " -L#{Shellwords.escape(libdir)} -Wl,-rpath,#{Shellwords.escape(libdir)}"
133
+ target_libs << " -ldatadog_profiling"
131
134
 
132
135
  # Add extra relative rpaths using $ORIGIN to handle environments where gems are moved after installation.
133
136
  # The excessive escaping is needed to get these special characters through Make and the shell untouched.
@@ -141,10 +144,12 @@ module Datadog
141
144
  libdatadog_pkgconfig_folder: libdatadog_pkgconfig_folder,
142
145
  ),
143
146
  ]
144
- extra_relative_rpaths.each { |folder| $LDFLAGS << " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder}" }
147
+ extra_relative_rpaths.each { |folder| target_ldflags << " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{Shellwords.escape(folder)}" }
145
148
 
146
- logger.message("linking with libdatadog (include=#{includedir}, lib=#{libdir})\n")
147
- logger.message("[datadog] $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
149
+ # includedir/libdir/ldflags might have a `%` in there and `logger.message` bottoms out on `printf` so we need to
150
+ # use the `message("%s", ...)` format otherwise the logger will complain
151
+ logger.message("%s", "linking with libdatadog (include=#{includedir}, lib=#{libdir})\n")
152
+ logger.message("%s", "[datadog] $LDFLAGS were set to: #{target_ldflags.inspect}\n")
148
153
 
149
154
  true
150
155
  end
@@ -1,37 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "api_client"
3
+ require "json"
4
+
5
+ require_relative "ext"
6
+ require_relative "http_client"
4
7
  require_relative "evaluation"
5
8
  require_relative "evaluation/request"
9
+ require_relative "evaluation/response"
10
+ require_relative "evaluation/client"
6
11
  require_relative "evaluation/result"
12
+ require_relative "evaluation/outcome"
7
13
  require_relative "evaluation/no_op_result"
8
14
  require_relative "evaluation/message"
9
15
  require_relative "evaluation/tool_call"
10
16
  require_relative "evaluation/content_part"
11
- require_relative "evaluation/content_builder"
12
- require_relative "ext"
17
+ require_relative "evaluation/message_builder"
18
+ require_relative "redaction"
19
+ require_relative "redaction/result"
20
+ require_relative "redaction/replacements"
21
+ require_relative "metrics/telemetry"
13
22
 
14
23
  module Datadog
15
24
  module AIGuard
16
25
  # Component for API Guard product
17
26
  class Component
18
- attr_reader :api_client, :logger, :telemetry
27
+ attr_reader :http_client, :logger, :telemetry
19
28
 
20
29
  def self.build(settings, logger:, telemetry:)
21
30
  return unless settings.respond_to?(:ai_guard) && settings.ai_guard.enabled
22
31
 
23
- api_client = APIClient.new(
32
+ http_client = HTTPClient.new(
24
33
  endpoint: settings.ai_guard.endpoint,
25
34
  api_key: settings.api_key,
26
35
  application_key: settings.ai_guard.app_key,
27
36
  timeout: settings.ai_guard.timeout_ms / 1_000
28
37
  )
29
38
 
30
- new(api_client, logger: logger, telemetry: telemetry)
39
+ new(http_client, logger: logger, telemetry: telemetry)
31
40
  end
32
41
 
33
- def initialize(api_client, logger:, telemetry:)
34
- @api_client = api_client
42
+ def initialize(http_client, logger:, telemetry:)
43
+ @http_client = http_client
35
44
  @logger = logger
36
45
  @telemetry = telemetry
37
46
  end
@@ -6,6 +6,8 @@ module Datadog
6
6
  # This module contains constants for AI Guard component
7
7
  module Ext
8
8
  ENV_AI_GUARD_ENABLED = "DD_AI_GUARD_ENABLED"
9
+ ENV_AI_GUARD_REDACTION_ENABLED = "DD_AI_GUARD_REDACTION_ENABLED"
10
+
9
11
  ENV_AI_GUARD_ENDPOINT = "DD_AI_GUARD_ENDPOINT"
10
12
  ENV_AI_GUARD_TIMEOUT = "DD_AI_GUARD_TIMEOUT"
11
13
  ENV_AI_GUARD_MAX_CONTENT_SIZE = "DD_AI_GUARD_MAX_CONTENT_SIZE"
@@ -34,6 +34,19 @@ module Datadog
34
34
  o.default false
35
35
  end
36
36
 
37
+ # Redact sensitive data from evaluated messages.
38
+ #
39
+ # When enabled, values flagged by AI Guard are replaced before the messages are
40
+ # stored in the span or returned to the caller. Disable to skip redaction entirely.
41
+ #
42
+ # @default `DD_AI_GUARD_REDACTION_ENABLED`, otherwise `true`
43
+ # @return [Boolean]
44
+ option :redaction_enabled do |o|
45
+ o.type :bool
46
+ o.env Ext::ENV_AI_GUARD_REDACTION_ENABLED
47
+ o.default true
48
+ end
49
+
37
50
  define_method(:instrument) do |integration_name|
38
51
  return unless enabled # steep:ignore
39
52