datadog 2.45.0 → 2.46.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -2
  3. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +4 -0
  4. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
  5. data/ext/datadog_profiling_native_extension/profiling.c +6 -0
  6. data/ext/libdatadog_api/di.c +30 -5
  7. data/ext/libdatadog_api/extconf.rb +2 -0
  8. data/ext/libdatadog_api/otel_thread_context.c +20 -14
  9. data/ext/libdatadog_api/process_discovery.c +15 -1
  10. data/ext/libdatadog_api/trace_exporter.c +105 -25
  11. data/ext/libdatadog_extconf_helpers.rb +1 -1
  12. data/lib/datadog/core/configuration/components.rb +29 -8
  13. data/lib/datadog/core/configuration/supported_configurations.rb +5 -2
  14. data/lib/datadog/core/process_discovery.rb +5 -1
  15. data/lib/datadog/core/remote/client/capabilities.rb +34 -29
  16. data/lib/datadog/core/remote/component.rb +28 -12
  17. data/lib/datadog/core/remote/dispatcher.rb +13 -3
  18. data/lib/datadog/di/boot.rb +1 -0
  19. data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
  20. data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
  21. data/lib/datadog/di/component.rb +38 -3
  22. data/lib/datadog/di/configuration.rb +53 -2
  23. data/lib/datadog/di/context.rb +7 -2
  24. data/lib/datadog/di/contrib/active_record.rb +8 -2
  25. data/lib/datadog/di/evaluation_failure.rb +28 -0
  26. data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
  27. data/lib/datadog/di/guardrails_telemetry.rb +203 -0
  28. data/lib/datadog/di/instrumenter.rb +65 -15
  29. data/lib/datadog/di/logger.rb +30 -8
  30. data/lib/datadog/di/probe_manager.rb +48 -5
  31. data/lib/datadog/di/probe_notification_builder.rb +28 -26
  32. data/lib/datadog/di/probe_notifier_worker.rb +56 -6
  33. data/lib/datadog/di/serializer.rb +130 -54
  34. data/lib/datadog/di/snapshot_event.rb +38 -0
  35. data/lib/datadog/di/transport/http.rb +13 -3
  36. data/lib/datadog/di/transport/input.rb +32 -7
  37. data/lib/datadog/open_feature/activation.rb +268 -0
  38. data/lib/datadog/open_feature/component.rb +31 -15
  39. data/lib/datadog/open_feature/provider.rb +282 -5
  40. data/lib/datadog/open_feature.rb +31 -1
  41. data/lib/datadog/tracing/component.rb +14 -0
  42. data/lib/datadog/tracing/configuration/settings.rb +3 -1
  43. data/lib/datadog/tracing/otel_thread_context.rb +3 -5
  44. data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
  45. data/lib/datadog/tracing/transport/http/traces.rb +2 -0
  46. data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
  47. data/lib/datadog/tracing/transport/native.rb +16 -1
  48. data/lib/datadog/tracing/transport/telemetry.rb +62 -0
  49. data/lib/datadog/tracing/transport/traces.rb +82 -15
  50. data/lib/datadog/version.rb +1 -1
  51. metadata +16 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ec6218e3a5ff1d1a8e18c9c12e665dd21d88d0e281eb1ff7690958433b7734a
4
- data.tar.gz: 6bc2e56d66439425d29b7a9f7c843da914d6d7e4fb7509b0d52cd52b5c3e7dba
3
+ metadata.gz: 90e55c02331cafb4d96cd0dedf6edb09fc5a6c7f993ffd611d8ca13f12d42cdb
4
+ data.tar.gz: a99ea41f5975708838075815674c78e47a46ef8368188186d2e20653a8372b19
5
5
  SHA512:
6
- metadata.gz: 323a79d901180e0174cc4052a39cfd52637e7cb496ae8aef6813dfcbca21e943757516a7663acab009e286f74ab8d0306a7fe833d562814f1a78cb47f6f4af6f
7
- data.tar.gz: fc1a980e41d7f6872acdd1addf5bbcaa91e2609fa69283ca1fd3bdc9cd91e6593c225946bc6e3007fd0069fe5c8e32a6b42311ba69912742f96f504b4b42c0b1
6
+ metadata.gz: 4061492c8bba7bf1238b6442d9275bbd7172978ecdbe4d4a92b53f0c67c2ca2d7b20947aaa42247fdc37ccbe81502710b4d69733e420e18fcd1e13d6e27b9864
7
+ data.tar.gz: 6fe366c5fcb305780a575c830bc77967be48605478071c1d16975242a95cb5c178b81dac776306f5b082b27770140174d29ad0c4dd7df3c9522b4a048883303f
data/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.46.0] - 2026-10-19
6
+
7
+ ### Added
8
+
9
+ * Tracing: Add OpenTelemetry Thread Context support on Linux so that compatible agents can correlate Workload Protection events with application traces. ([#6454][])
10
+ * Open Feature: Start Feature Flags delivery during provider initialization, including for applications that never receive traced requests. ([#6418][])
11
+
12
+ ### Changed
13
+
14
+ * SSI: Reduce the single-step injection init image by about 35 MB, lowering download size and disk use when installing the library. ([#6469][], [#6474][], [#6481][], [#6485][], [#6488][])
15
+ * Profiling: Upgrade `datadog-ruby_core_source` to support Ruby 4.1.0-preview1. ([#6470][])
16
+ * Dynamic Instrumentation: Bound snapshot captures by the `DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS` budget (default `150`). `DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE` is deprecated. ([#6205][])
17
+
5
18
  ## [2.45.0] - 2026-10-13
6
19
 
7
20
  ### Changed
@@ -3838,7 +3851,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
3838
3851
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3839
3852
 
3840
3853
 
3841
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.45.0...master
3854
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.46.0...master
3855
+ [2.46.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.45.0...v2.46.0
3842
3856
  [2.45.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.44.0...v2.45.0
3843
3857
  [2.44.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.43.0...v2.44.0
3844
3858
  [2.43.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.42.0...v2.43.0
@@ -5669,6 +5683,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
5669
5683
  [#6142]: https://github.com/DataDog/dd-trace-rb/issues/6142
5670
5684
  [#6173]: https://github.com/DataDog/dd-trace-rb/issues/6173
5671
5685
  [#6176]: https://github.com/DataDog/dd-trace-rb/issues/6176
5686
+ [#6205]: https://github.com/DataDog/dd-trace-rb/issues/6205
5672
5687
  [#6213]: https://github.com/DataDog/dd-trace-rb/issues/6213
5673
5688
  [#6219]: https://github.com/DataDog/dd-trace-rb/issues/6219
5674
5689
  [#6220]: https://github.com/DataDog/dd-trace-rb/issues/6220
@@ -5695,10 +5710,18 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
5695
5710
  [#6401]: https://github.com/DataDog/dd-trace-rb/issues/6401
5696
5711
  [#6407]: https://github.com/DataDog/dd-trace-rb/issues/6407
5697
5712
  [#6412]: https://github.com/DataDog/dd-trace-rb/issues/6412
5713
+ [#6418]: https://github.com/DataDog/dd-trace-rb/issues/6418
5698
5714
  [#6419]: https://github.com/DataDog/dd-trace-rb/issues/6419
5699
5715
  [#6427]: https://github.com/DataDog/dd-trace-rb/issues/6427
5700
5716
  [#6433]: https://github.com/DataDog/dd-trace-rb/issues/6433
5701
5717
  [#6441]: https://github.com/DataDog/dd-trace-rb/issues/6441
5718
+ [#6454]: https://github.com/DataDog/dd-trace-rb/issues/6454
5719
+ [#6469]: https://github.com/DataDog/dd-trace-rb/issues/6469
5720
+ [#6470]: https://github.com/DataDog/dd-trace-rb/issues/6470
5721
+ [#6474]: https://github.com/DataDog/dd-trace-rb/issues/6474
5722
+ [#6481]: https://github.com/DataDog/dd-trace-rb/issues/6481
5723
+ [#6485]: https://github.com/DataDog/dd-trace-rb/issues/6485
5724
+ [#6488]: https://github.com/DataDog/dd-trace-rb/issues/6488
5702
5725
  [@AdrianLC]: https://github.com/AdrianLC
5703
5726
  [@Azure7111]: https://github.com/Azure7111
5704
5727
  [@BabyGroot]: https://github.com/BabyGroot
@@ -5855,4 +5878,4 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
5855
5878
  [@y-yagi]: https://github.com/y-yagi
5856
5879
  [@yujideveloper]: https://github.com/yujideveloper
5857
5880
  [@yukimurasawa]: https://github.com/yukimurasawa
5858
- [@zachmccormick]: https://github.com/zachmccormick
5881
+ [@zachmccormick]: https://github.com/zachmccormick
@@ -893,6 +893,10 @@ const char *imemo_kind(__attribute__((unused)) VALUE imemo) {
893
893
  }
894
894
  #endif
895
895
 
896
+ int imemo_mask(void) {
897
+ return IMEMO_MASK;
898
+ }
899
+
896
900
  // This is used to workaround a VM bug. See "handle_sampling_signal" in "collectors_cpu_and_wall_time_worker" for details.
897
901
  #ifdef NO_POSTPONED_TRIGGER
898
902
  void *objspace_ptr_for_gc_finalize_deferred_workaround(void) {
@@ -75,6 +75,8 @@ void self_test_mn_enabled(void);
75
75
  // Provides more specific information on what kind an imemo is
76
76
  const char *imemo_kind(VALUE imemo);
77
77
 
78
+ int imemo_mask(void);
79
+
78
80
  #ifdef NO_POSTPONED_TRIGGER
79
81
  void *objspace_ptr_for_gc_finalize_deferred_workaround(void);
80
82
  #endif
@@ -47,6 +47,7 @@ static VALUE _native_enforce_success(DDTRACE_UNUSED VALUE _self, VALUE syserr_er
47
47
  static void *trigger_enforce_success(void *trigger_args);
48
48
  static VALUE _native_malloc_stats(DDTRACE_UNUSED VALUE _self);
49
49
  static VALUE _native_safe_object_info(DDTRACE_UNUSED VALUE _self, VALUE obj);
50
+ static VALUE _native_imemo_mask(DDTRACE_UNUSED VALUE _self);
50
51
 
51
52
  void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
52
53
  // The profiler still has a lot of limitations around being used in Ractors BUT for now we're choosing to take care of those
@@ -101,6 +102,7 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
101
102
  rb_define_singleton_method(testing_module, "_native_enforce_success", _native_enforce_success, 2);
102
103
  rb_define_singleton_method(testing_module, "_native_malloc_stats", _native_malloc_stats, 0);
103
104
  rb_define_singleton_method(testing_module, "_native_safe_object_info", _native_safe_object_info, 1);
105
+ rb_define_singleton_method(testing_module, "_native_imemo_mask", _native_imemo_mask, 0);
104
106
  }
105
107
 
106
108
  static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
@@ -329,3 +331,7 @@ static VALUE _native_malloc_stats(DDTRACE_UNUSED VALUE _self) {
329
331
  static VALUE _native_safe_object_info(DDTRACE_UNUSED VALUE _self, VALUE obj) {
330
332
  return rb_str_new_cstr(safe_object_info(obj));
331
333
  }
334
+
335
+ static VALUE _native_imemo_mask(DDTRACE_UNUSED VALUE _self) {
336
+ return INT2NUM(imemo_mask());
337
+ }
@@ -1,4 +1,5 @@
1
1
  #include <stdbool.h>
2
+ #include <string.h>
2
3
 
3
4
  #include "datadog_ruby_common.h"
4
5
 
@@ -12,15 +13,19 @@ VALUE rb_iseqw_new(const void *iseq);
12
13
  // rb_iseqw_to_iseq unwraps a RubyVM::InstructionSequence object back
13
14
  // to its internal iseq pointer.
14
15
  const void *rb_iseqw_to_iseq(VALUE iseqw);
16
+ #ifdef HAVE_RB_IMEMO_NAME
17
+ const char *rb_imemo_name(int imemo_type);
18
+ #endif
15
19
  int rb_objspace_internal_object_p(VALUE obj);
16
20
  void rb_objspace_each_objects(
17
21
  int (*callback)(void *start, void *end, size_t stride, void *data),
18
22
  void *data);
19
23
 
20
- // The mask is unchanged between Ruby 2.5 and 4.1-dev, but the `enum imemo_type` values do get
21
- // renumbered from time to time (e.g. Ruby 4.0 moved imemo_callinfo from 11 to 10), and 4.1-dev already uses all
22
- // 16 values the mask allows. Thus IMEMO_TYPE_ISEQ must be re-checked when adding support for a new Ruby.
23
- #define IMEMO_MASK 0x0f
24
+ #ifdef HAVE_SHIFTED_IMEMO_MASK // Ruby 4.1+
25
+ #define IMEMO_MASK (FL_USER0 | FL_USER1 | FL_USER2 | FL_USER3 | FL_USER4)
26
+ #else
27
+ #define IMEMO_MASK 0x0f
28
+ #endif
24
29
  #define IMEMO_TYPE_ISEQ 7
25
30
 
26
31
  // The ID value of the string "mesg" which is used in Ruby source as
@@ -37,7 +42,11 @@ static ID id_datadog_di_in_probe;
37
42
  // Returns the imemo type of an imemo object, that is `imemo_type()` from CRuby's internal/imemo.h.
38
43
  // The caller must have already checked the object is a T_IMEMO, otherwise the result is meaningless.
39
44
  static inline int ddtrace_imemo_type(VALUE imemo) {
40
- return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
45
+ #ifdef HAVE_SHIFTED_IMEMO_MASK // Ruby 4.1+
46
+ return (RBASIC(imemo)->flags & IMEMO_MASK) >> FL_USHIFT;
47
+ #else
48
+ return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
49
+ #endif
41
50
  }
42
51
 
43
52
  // Returns whether the argument is an IMEMO of type ISEQ.
@@ -199,7 +208,20 @@ static VALUE iseq_type(DDTRACE_UNUSED VALUE _self, VALUE iseq_val) {
199
208
  }
200
209
  #endif
201
210
 
211
+ static VALUE _native_imemo_mask(DDTRACE_UNUSED VALUE _self) {
212
+ return INT2NUM(IMEMO_MASK);
213
+ }
214
+
202
215
  void di_init(VALUE datadog_module) {
216
+ #ifdef HAVE_RB_IMEMO_NAME
217
+ const char *iseq_name = rb_imemo_name(IMEMO_TYPE_ISEQ);
218
+ if (strcmp(iseq_name, "iseq") != 0) {
219
+ rb_raise(rb_eRuntimeError,
220
+ "IMEMO_TYPE_ISEQ self-test failed: rb_imemo_name(%d) returned '%s' instead of 'iseq'",
221
+ IMEMO_TYPE_ISEQ, iseq_name);
222
+ }
223
+ #endif
224
+
203
225
  id_mesg = rb_intern("mesg");
204
226
  id_datadog_di_in_probe = rb_intern("datadog_di_in_probe");
205
227
 
@@ -213,6 +235,9 @@ void di_init(VALUE datadog_module) {
213
235
  #ifdef HAVE_RB_ISEQ_TYPE
214
236
  rb_define_singleton_method(di_module, "iseq_type", iseq_type, 1);
215
237
  #endif
238
+
239
+ VALUE testing_module = rb_define_module_under(di_module, "Testing");
240
+ rb_define_singleton_method(testing_module, "_native_imemo_mask", _native_imemo_mask, 0);
216
241
  }
217
242
 
218
243
  #endif // TRUFFLERUBY
@@ -98,6 +98,8 @@ EXTENSION_NAME = "libdatadog_api.#{RUBY_VERSION[/\d+.\d+/]}_#{RUBY_PLATFORM}".fr
98
98
 
99
99
  have_func("rb_iseq_type")
100
100
 
101
+ $defs << "-DHAVE_RB_IMEMO_NAME" if RUBY_VERSION >= "3.0"
102
+ $defs << "-DHAVE_SHIFTED_IMEMO_MASK" if RUBY_VERSION >= "4.1"
101
103
  $defs << "-DHAVE_RUBY_THREAD_STORAGE_API" if RUBY_VERSION >= "3.3"
102
104
  $defs << "-DHAVE_RUBY_THREAD_HAS_GVL_P" if have_func("ruby_thread_has_gvl_p")
103
105
 
@@ -30,7 +30,6 @@ DDTRACE_UNUSED static bool otel_context_enabled = false;
30
30
 
31
31
  static VALUE native_set(VALUE _self, VALUE trace_id, VALUE span_id, VALUE local_root_span_id);
32
32
  static VALUE native_clear(VALUE _self);
33
- static VALUE native_supported_p(VALUE _self);
34
33
  static VALUE native_enable(VALUE _self);
35
34
  static VALUE native_read(VALUE _self);
36
35
 
@@ -42,7 +41,6 @@ void otel_thread_context_init(VALUE tracing_module) {
42
41
  rb_define_method(otel_thread_context_class, "_native_enable", native_enable, 0);
43
42
  rb_define_method(otel_thread_context_class, "_native_set", native_set, 3);
44
43
  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);
46
44
 
47
45
  VALUE testing_module = rb_define_module_under(otel_thread_context_class, "Testing");
48
46
  rb_define_singleton_method(testing_module, "_native_read", native_read, 0);
@@ -100,6 +98,19 @@ void otel_thread_context_init(VALUE tracing_module) {
100
98
  ) {
101
99
  ddog_otel_thread_ctx_detach();
102
100
  }
101
+
102
+ // CRuby fires RUBY_EVENT_THREAD_END only after the thread body returns,
103
+ // and Thread#kill unwinds the body via EC_JUMP_TAG(TAG_FATAL):
104
+ // https://github.com/ruby/ruby/blob/v3_1_4/thread.c#L744-L775
105
+ static void on_thread_begin(
106
+ DDTRACE_UNUSED rb_event_flag_t evflag,
107
+ DDTRACE_UNUSED VALUE data,
108
+ DDTRACE_UNUSED VALUE self,
109
+ DDTRACE_UNUSED ID mid,
110
+ DDTRACE_UNUSED VALUE klass
111
+ ) {
112
+ ddog_otel_thread_ctx_detach();
113
+ }
103
114
  #endif
104
115
 
105
116
  #ifdef HAVE_RUBY_THREAD_STORAGE_API
@@ -166,6 +177,11 @@ static VALUE native_enable(DDTRACE_UNUSED VALUE _self) {
166
177
  rb_internal_thread_add_event_hook(on_thread_exited, RUBY_INTERNAL_THREAD_EVENT_EXITED, NULL);
167
178
  #else
168
179
  rb_add_event_hook(on_thread_end, RUBY_EVENT_THREAD_END, Qnil);
180
+ // RUBY_EVENT_THREAD_END does not fire for threads terminated via Thread#kill
181
+ // on Ruby < 3.2, so a killed thread's otel_thread_ctx_v1 TLS stays attached
182
+ // on a native thread CRuby may recycle for a later Ruby thread. Detach on
183
+ // thread begin to drop that stale record before the new thread runs.
184
+ rb_add_event_hook(on_thread_begin, RUBY_EVENT_THREAD_BEGIN, Qnil);
169
185
  #endif
170
186
 
171
187
  // These hooks only matter under the M:N thread scheduler (Ruby 3.3+),
@@ -179,15 +195,7 @@ static VALUE native_enable(DDTRACE_UNUSED VALUE _self) {
179
195
 
180
196
  return Qtrue;
181
197
  #else
182
- return Qfalse;
183
- #endif
184
- }
185
-
186
- static VALUE native_supported_p(DDTRACE_UNUSED VALUE _self) {
187
- #ifdef OTEL_THREAD_CONTEXT_SUPPORTED
188
- return Qtrue;
189
- #else
190
- return Qfalse;
198
+ raise_error(rb_eRuntimeError, "OTel thread context is not supported on this platform");
191
199
  #endif
192
200
  }
193
201
 
@@ -216,9 +224,7 @@ static VALUE native_set(
216
224
  store_current_fiber_handle(handle);
217
225
  ddog_otel_thread_ctx_attach(handle);
218
226
  } else {
219
- // FIXME: add a method to libdatadog to update the context record before attaching it
220
- ddog_otel_thread_ctx_attach(handle);
221
- ddog_otel_thread_ctx_update(&trace_id_bytes, &span_id_bytes, 0, &local_root_span_id_bytes);
227
+ ddog_otel_thread_ctx_update_and_attach(handle, &trace_id_bytes, &span_id_bytes, 0, &local_root_span_id_bytes);
222
228
  }
223
229
 
224
230
  return Qtrue;
@@ -4,6 +4,7 @@
4
4
  #include <datadog/library-config.h>
5
5
 
6
6
  #include "datadog_ruby_common.h"
7
+ #include "helpers.h"
7
8
 
8
9
  static VALUE _native_store_tracer_metadata(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
9
10
  static VALUE _native_to_rb_int(DDTRACE_UNUSED VALUE _self, VALUE tracer_memfd);
@@ -38,8 +39,9 @@ void process_discovery_init(VALUE core_module) {
38
39
 
39
40
  static VALUE _native_store_tracer_metadata(int argc, VALUE *argv, VALUE self) {
40
41
  VALUE logger;
42
+ VALUE otel_thread_context_enabled;
41
43
  VALUE options;
42
- rb_scan_args(argc, argv, "1:", &logger, &options);
44
+ rb_scan_args(argc, argv, "2:", &logger, &otel_thread_context_enabled, &options);
43
45
  if (options == Qnil) options = rb_hash_new();
44
46
 
45
47
  VALUE runtime_id = rb_hash_fetch(options, ID2SYM(rb_intern("runtime_id")));
@@ -61,6 +63,7 @@ static VALUE _native_store_tracer_metadata(int argc, VALUE *argv, VALUE self) {
61
63
  ENFORCE_TYPE(service_version, T_STRING);
62
64
  ENFORCE_TYPE(process_tags, T_STRING);
63
65
  ENFORCE_TYPE(container_id, T_STRING);
66
+ ENFORCE_BOOLEAN(otel_thread_context_enabled);
64
67
 
65
68
  void* builder = ddog_tracer_metadata_new();
66
69
 
@@ -74,6 +77,16 @@ static VALUE _native_store_tracer_metadata(int argc, VALUE *argv, VALUE self) {
74
77
  ddog_tracer_metadata_set(builder, DDOG_METADATA_KIND_PROCESS_TAGS, StringValueCStr(process_tags));
75
78
  ddog_tracer_metadata_set(builder, DDOG_METADATA_KIND_CONTAINER_ID, StringValueCStr(container_id));
76
79
 
80
+ if (RTEST(otel_thread_context_enabled)) {
81
+ ddog_VoidResult include_result = ddog_tracer_metadata_include_otel_thread_context(builder);
82
+
83
+ if (include_result.tag == DDOG_VOID_RESULT_ERR) {
84
+ ddog_tracer_metadata_free(builder);
85
+ }
86
+
87
+ CHECK_VOID_RESULT("Failed to include OTel thread context metadata", include_result);
88
+ }
89
+
77
90
  ddog_Result_TracerMemfdHandle result = ddog_tracer_metadata_store(builder);
78
91
  ddog_tracer_metadata_free(builder);
79
92
 
@@ -89,6 +102,7 @@ static VALUE _native_store_tracer_metadata(int argc, VALUE *argv, VALUE self) {
89
102
  *fd = result.ok.fd;
90
103
  VALUE tracer_memfd_class = rb_const_get(self, rb_intern("TracerMemfd"));
91
104
  VALUE tracer_memfd = TypedData_Wrap_Struct(tracer_memfd_class, &tracer_memfd_type, fd);
105
+
92
106
  return tracer_memfd;
93
107
  }
94
108
 
@@ -104,6 +104,8 @@ static VALUE serializable_backtrace_class = Qnil;
104
104
  /* Response class (loaded from Ruby) */
105
105
  static VALUE response_class = Qnil;
106
106
  static ID id_new;
107
+ static ID id_handle_interrupt;
108
+ static VALUE observation_interrupt_mask = Qnil;
107
109
 
108
110
  /* Response keyword-argument IDs, cached to avoid re-interning per send */
109
111
  static ID kw_ok;
@@ -1464,6 +1466,11 @@ static VALUE _native_exporter_new(
1464
1466
  /* Phase 2: configure before creating the separately-owned runtime. */
1465
1467
  ddog_TraceExporterConfig *config = NULL;
1466
1468
  ddog_trace_exporter_config_new(&config);
1469
+ ddog_TraceExporterError *observation_error = ddog_trace_exporter_config_enable_observations(config);
1470
+ if (observation_error != NULL) {
1471
+ ddog_trace_exporter_config_free(config);
1472
+ check_exporter_error("Failed to select exporter observations", observation_error);
1473
+ }
1467
1474
 
1468
1475
  set_config_field(config, ddog_trace_exporter_config_set_url, rb_url, "url");
1469
1476
  set_config_field(config, ddog_trace_exporter_config_set_tracer_version, rb_tracer_version, "tracer_version");
@@ -1621,6 +1628,7 @@ typedef struct {
1621
1628
  const ddog_TraceExporter *exporter;
1622
1629
  ddog_TracerTraceChunks *chunks;
1623
1630
  ddog_TraceExporterResponse *response;
1631
+ ddog_TraceExporterObservations observations;
1624
1632
  ddog_TraceExporterCancelToken *cancel_token; /* borrowed, not owned */
1625
1633
  ddog_TraceExporterErrorCode error_code;
1626
1634
  bool failed;
@@ -1629,8 +1637,8 @@ typedef struct {
1629
1637
 
1630
1638
  static void *send_chunks_without_gvl(void *data) {
1631
1639
  send_chunks_args_t *args = (send_chunks_args_t *)data;
1632
- ddog_TraceExporterError *err = ddog_trace_exporter_send_trace_chunks(
1633
- args->exporter, args->chunks, &args->response, args->cancel_token);
1640
+ ddog_TraceExporterError *err = ddog_trace_exporter_send_trace_chunks_observed(
1641
+ args->exporter, args->chunks, &args->response, &args->observations, args->cancel_token);
1634
1642
  if (err != NULL) {
1635
1643
  args->error_code = err->code;
1636
1644
  args->failed = true;
@@ -1698,8 +1706,73 @@ typedef struct {
1698
1706
  long trace_count;
1699
1707
  raw_span_owner span_owner;
1700
1708
  ddog_TracerTraceChunks *chunks; /* NULL after send consumes it */
1709
+ ddog_TraceExporterResponse *response;
1701
1710
  } send_traces_ctx;
1702
1711
 
1712
+ typedef struct {
1713
+ const ddog_TraceExporterObservations *report;
1714
+ VALUE block;
1715
+ } observations_callback_args;
1716
+
1717
+ static VALUE yield_observations(VALUE arg) {
1718
+ observations_callback_args *callback = (observations_callback_args *)arg;
1719
+ const ddog_TraceExporterObservations *report = callback->report;
1720
+ VALUE values = rb_hash_new();
1721
+ #define ADD_OBSERVATION(field) if (report->field > 0) rb_hash_aset(values, ID2SYM(rb_intern_const(#field)), ULL2NUM(report->field))
1722
+ ADD_OBSERVATION(requests_count);
1723
+ ADD_OBSERVATION(errors_network);
1724
+ ADD_OBSERVATION(errors_timeout);
1725
+ ADD_OBSERVATION(errors_status_code);
1726
+ ADD_OBSERVATION(bytes_sent);
1727
+ ADD_OBSERVATION(chunks_sent);
1728
+ ADD_OBSERVATION(chunks_dropped_serialization_error);
1729
+ ADD_OBSERVATION(chunks_dropped_send_failure);
1730
+ ADD_OBSERVATION(spans_enqueued_for_serialization);
1731
+ ADD_OBSERVATION(spans_dropped_serialization_error);
1732
+ ADD_OBSERVATION(spans_dropped_api_error);
1733
+ ADD_OBSERVATION(responses_count);
1734
+ ADD_OBSERVATION(status_code);
1735
+ #undef ADD_OBSERVATION
1736
+ return rb_proc_call_with_block(callback->block, 1, &values, Qnil);
1737
+ }
1738
+
1739
+ static VALUE ignore_observation_error(DDTRACE_UNUSED VALUE arg, DDTRACE_UNUSED VALUE error) {
1740
+ return Qnil;
1741
+ }
1742
+
1743
+ static VALUE protected_observations(RB_BLOCK_CALL_FUNC_ARGLIST(yielded, arg)) {
1744
+ (void)yielded;
1745
+ (void)argc;
1746
+ (void)argv;
1747
+ (void)blockarg;
1748
+ return rb_rescue2(yield_observations, arg, ignore_observation_error, Qnil,
1749
+ rb_eStandardError, (VALUE)0);
1750
+ }
1751
+
1752
+ static VALUE forward_observations(VALUE arg) {
1753
+ observations_callback_args callback = {
1754
+ .report = (const ddog_TraceExporterObservations *)arg,
1755
+ .block = rb_block_proc(),
1756
+ };
1757
+ /* Deliver fresh interrupts after the callback's StandardError rescue has ended. */
1758
+ VALUE result = rb_block_call(rb_cThread, id_handle_interrupt, 1, &observation_interrupt_mask,
1759
+ protected_observations, (VALUE)&callback);
1760
+ RB_GC_GUARD(callback.block);
1761
+ return result;
1762
+ }
1763
+
1764
+ static VALUE ensure_observations(VALUE arg) {
1765
+ int ignored_state = 0;
1766
+ /* The original interruption wins over any callback failure or non-local exit. */
1767
+ rb_protect(forward_observations, arg, &ignored_state);
1768
+ return Qnil;
1769
+ }
1770
+
1771
+ static VALUE rethrow_interrupt(VALUE tag) {
1772
+ rb_jump_tag(FIX2INT(tag));
1773
+ return Qnil;
1774
+ }
1775
+
1703
1776
  /*
1704
1777
  * Body: build trace chunks from Ruby spans, then send them.
1705
1778
  * Passed to rb_ensure as the "try" block.
@@ -1777,40 +1850,37 @@ static VALUE build_and_send_traces(VALUE arg) {
1777
1850
  if (args.send_ran) {
1778
1851
  ctx->chunks = NULL;
1779
1852
  }
1853
+ ctx->response = args.response;
1854
+
1855
+ /* A completed send can race with cancellation; retain its report while unwinding. */
1856
+ if (!pending_exception) {
1857
+ pending_exception = check_if_pending_exception();
1858
+ }
1859
+ if (args.send_ran && rb_block_given_p()) {
1860
+ if (pending_exception) {
1861
+ /* rb_ensure preserves Thread#kill's fatal sentinel, which rb_set_errinfo cannot restore. */
1862
+ rb_ensure(rethrow_interrupt, INT2FIX(pending_exception),
1863
+ ensure_observations, (VALUE)&args.observations);
1864
+ } else {
1865
+ forward_observations((VALUE)&args.observations);
1866
+ }
1867
+ }
1868
+ if (pending_exception) {
1869
+ rb_jump_tag(pending_exception);
1870
+ }
1780
1871
 
1781
1872
  /* Extract the response body as a Ruby string before freeing. */
1782
1873
  VALUE payload = Qnil;
1783
1874
  if (args.response != NULL) {
1784
- ddog_ByteSlice body =
1785
- ddog_trace_exporter_response_get_body(args.response);
1875
+ ddog_ByteSlice body = ddog_trace_exporter_response_get_body(args.response);
1786
1876
  if (body.len > 0) {
1787
1877
  payload = rb_str_new((const char *)body.ptr, (long)body.len);
1788
1878
  }
1789
1879
  ddog_trace_exporter_response_free(args.response);
1880
+ ctx->response = NULL;
1790
1881
  args.response = NULL;
1791
1882
  }
1792
1883
 
1793
- /*
1794
- * Re-check for a pending interrupt unconditionally before deciding the
1795
- * outcome. In a race, the unblock function (interrupt_exporter_call) can
1796
- * fire -- cancelling the token -- while the send still completes, so
1797
- * rb_thread_call_without_gvl2 returns with args.send_ran == true. In that
1798
- * case the loop above exits without ever calling check_if_pending_exception(),
1799
- * leaving the interrupt pending. If we did not check here, the cancelled
1800
- * send would fall through and be reported as an ordinary transport error
1801
- * response, swallowing the interrupt (e.g. Thread#kill / shutdown).
1802
- *
1803
- * This runs after the response has already been extracted and freed and
1804
- * after chunks have been handed off to the ensure handler, so re-raising
1805
- * here leaks nothing.
1806
- */
1807
- if (!pending_exception) {
1808
- pending_exception = check_if_pending_exception();
1809
- }
1810
- if (pending_exception) {
1811
- rb_jump_tag(pending_exception);
1812
- }
1813
-
1814
1884
  if (args.failed) {
1815
1885
  VALUE err_resp = create_error_response(args.error_code, ctx->trace_count);
1816
1886
  return rb_ary_new_from_args(1, err_resp);
@@ -1827,6 +1897,10 @@ static VALUE build_and_send_traces(VALUE arg) {
1827
1897
  static VALUE free_send_resources(VALUE arg) {
1828
1898
  send_traces_ctx *ctx = (send_traces_ctx *)arg;
1829
1899
  free_raw_span((VALUE)&ctx->span_owner);
1900
+ if (ctx->response != NULL) {
1901
+ ddog_trace_exporter_response_free(ctx->response);
1902
+ ctx->response = NULL;
1903
+ }
1830
1904
  if (ctx->chunks != NULL) {
1831
1905
  ddog_tracer_trace_chunks_free(ctx->chunks);
1832
1906
  ctx->chunks = NULL;
@@ -1883,6 +1957,12 @@ static VALUE _native_send_traces(VALUE self, VALUE traces, VALUE native_events_s
1883
1957
  * ======================================================================== */
1884
1958
 
1885
1959
  void trace_exporter_init(VALUE tracing_module) {
1960
+ id_handle_interrupt = rb_intern("handle_interrupt");
1961
+ observation_interrupt_mask = rb_hash_new();
1962
+ rb_hash_aset(observation_interrupt_mask, rb_cObject, ID2SYM(rb_intern("never")));
1963
+ rb_obj_freeze(observation_interrupt_mask);
1964
+ rb_global_variable(&observation_interrupt_mask);
1965
+
1886
1966
  /* -- Module hierarchy -- */
1887
1967
  VALUE transport_module = rb_define_module_under(tracing_module, "Transport");
1888
1968
  VALUE native_module =
@@ -11,7 +11,7 @@ module Datadog
11
11
  module LibdatadogExtconfHelpers
12
12
  # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
13
13
  # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
14
- LIBDATADOG_VERSION = "~> 45.0.0.1.0"
14
+ LIBDATADOG_VERSION = "~> 46.0.0.1.1"
15
15
 
16
16
  # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
17
17
  # libdatadog are moved after the extension gets compiled.
@@ -21,7 +21,7 @@ require_relative "../../ai_guard/component"
21
21
  require_relative "../../di/component"
22
22
  require_relative "../../symbol_database"
23
23
  require_relative "../../symbol_database/component"
24
- require_relative "../../open_feature/component"
24
+ require_relative "../../open_feature/activation"
25
25
  require_relative "../../error_tracking/component"
26
26
  require_relative "../crashtracking/component"
27
27
  require_relative "../environment/agent_info"
@@ -154,8 +154,7 @@ module Datadog
154
154
  :ai_guard,
155
155
  :agent_info,
156
156
  :data_streams,
157
- :symbol_database,
158
- :open_feature
157
+ :symbol_database
159
158
 
160
159
  def initialize(settings)
161
160
  @settings = settings
@@ -188,15 +187,14 @@ module Datadog
188
187
 
189
188
  @telemetry = self.class.build_telemetry(settings, agent_settings, @logger)
190
189
 
191
- # Bind Remote Configuration dispatch to this tree, which starts before it becomes the global Components instance.
192
190
  @remote = Remote::Component.build(
193
191
  settings,
194
192
  agent_settings,
195
193
  logger: @logger,
196
194
  telemetry: telemetry,
197
- open_feature_component_provider: -> { @open_feature },
198
195
  )
199
196
  @tracer = Datadog::Tracing::Component.build_tracer(settings, agent_settings, logger: @logger)
197
+ Datadog::Tracing::Component.bind_transport_telemetry(@tracer, telemetry)
200
198
  @crashtracker = self.class.build_crashtracker(settings, agent_settings, logger: @logger)
201
199
 
202
200
  @profiler, profiler_logger_extra = Datadog::Profiling::Component.build_profiler_component(
@@ -211,7 +209,13 @@ module Datadog
211
209
  @health_metrics = self.class.build_health_metrics(settings, @logger, telemetry)
212
210
  @appsec = Datadog::AppSec::Component.build_appsec_component(settings, telemetry: telemetry)
213
211
  @ai_guard = Datadog::AIGuard::Component.build(settings, logger: @logger, telemetry: telemetry)
214
- @open_feature = OpenFeature::Component.build(settings, agent_settings, logger: @logger, telemetry: telemetry)
212
+ @open_feature_activation = OpenFeature::Activation.new(
213
+ settings,
214
+ agent_settings,
215
+ remote,
216
+ logger: @logger,
217
+ telemetry: telemetry,
218
+ )
215
219
  @dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
216
220
  # Only build symbol database when enabled, so a disabled component is
217
221
  # never constructed.
@@ -257,6 +261,7 @@ module Datadog
257
261
  ProcessDiscovery.after_fork
258
262
  symbol_database&.after_fork!
259
263
  data_streams&.restart_flush_thread
264
+ @open_feature_activation.after_fork
260
265
  end
261
266
 
262
267
  # Hot-swaps with a new sampler.
@@ -271,6 +276,8 @@ module Datadog
271
276
  def startup!(settings, old_state: nil)
272
277
  telemetry.start(old_state&.telemetry_enabled?, components: self)
273
278
 
279
+ @open_feature_activation.startup!
280
+
274
281
  if settings.profiling.enabled
275
282
  if profiler
276
283
  profiler.start
@@ -329,8 +336,14 @@ module Datadog
329
336
  # Shutdown Symbol Database
330
337
  symbol_database&.shutdown!
331
338
 
332
- # Shutdown OpenFeature component
333
- open_feature&.shutdown!
339
+ begin
340
+ @open_feature_activation.shutdown!
341
+ rescue => e
342
+ # Feature Flags is optional and must never interrupt library shutdown.
343
+ description = "Feature Flags delivery failed to shut down"
344
+ logger.error("#{description}: #{e.class}: #{e.message}")
345
+ telemetry.report(e, description: description)
346
+ end
334
347
 
335
348
  # Decommission AppSec
336
349
  appsec&.shutdown!
@@ -407,6 +420,14 @@ module Datadog
407
420
  di_implicitly_enabled: di_implicit || false,
408
421
  )
409
422
  end
423
+
424
+ def open_feature
425
+ @open_feature_activation.component
426
+ end
427
+
428
+ private
429
+
430
+ attr_reader :open_feature_activation
410
431
  end
411
432
  end
412
433
  end
@@ -51,6 +51,7 @@ module Datadog
51
51
  "DD_DBM_INJECT_SQL_BASEHASH",
52
52
  "DD_DBM_PROPAGATION_MODE",
53
53
  "DD_DISABLE_DATADOG_RAILS",
54
+ "DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS",
54
55
  "DD_DYNAMIC_INSTRUMENTATION_ENABLED",
55
56
  "DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE",
56
57
  "DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE",
@@ -399,6 +400,7 @@ module Datadog
399
400
 
400
401
  ALIASES =
401
402
  {"DD_DISABLE_DATADOG_RAILS" => ["DISABLE_DATADOG_RAILS"],
403
+ "DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS" => ["DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT"],
402
404
  "DD_PROFILING_GVL_ENABLED" => ["DD_PROFILING_PREVIEW_GVL_ENABLED"],
403
405
  "DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED" => ["DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED"],
404
406
  "DD_SERVICE" => ["OTEL_SERVICE_NAME"],
@@ -409,10 +411,11 @@ module Datadog
409
411
  "DD_TRACE_SAMPLE_RATE" => ["OTEL_TRACES_SAMPLER"]}.freeze
410
412
 
411
413
  DEPRECATIONS =
412
- Set["DD_PROFILING_PREVIEW_GVL_ENABLED", "DISABLE_DATADOG_RAILS"]
414
+ Set["DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT", "DD_PROFILING_PREVIEW_GVL_ENABLED", "DISABLE_DATADOG_RAILS"]
413
415
 
414
416
  ALIAS_TO_CANONICAL =
415
- {"DD_PROFILING_PREVIEW_GVL_ENABLED" => "DD_PROFILING_GVL_ENABLED",
417
+ {"DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT" => "DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS",
418
+ "DD_PROFILING_PREVIEW_GVL_ENABLED" => "DD_PROFILING_GVL_ENABLED",
416
419
  "DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED" => "DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED",
417
420
  "DISABLE_DATADOG_RAILS" => "DD_DISABLE_DATADOG_RAILS",
418
421
  "OTEL_LOG_LEVEL" => "DD_TRACE_DEBUG",