ddtrace 1.16.2 → 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -1
  3. data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +3 -0
  4. data/ext/ddtrace_profiling_native_extension/collectors_thread_context.c +8 -1
  5. data/ext/ddtrace_profiling_native_extension/extconf.rb +28 -10
  6. data/ext/ddtrace_profiling_native_extension/http_transport.c +5 -2
  7. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +78 -18
  8. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +6 -0
  9. data/ext/ddtrace_profiling_native_extension/profiling.c +1 -0
  10. data/ext/ddtrace_profiling_native_extension/stack_recorder.c +1 -3
  11. data/lib/datadog/appsec/component.rb +4 -1
  12. data/lib/datadog/appsec/configuration/settings.rb +4 -0
  13. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +2 -0
  14. data/lib/datadog/appsec/processor/rule_loader.rb +60 -0
  15. data/lib/datadog/appsec/remote.rb +12 -9
  16. data/lib/datadog/core/configuration.rb +4 -0
  17. data/lib/datadog/core/remote/worker.rb +1 -0
  18. data/lib/datadog/core/workers/async.rb +1 -0
  19. data/lib/datadog/kit/enable_core_dumps.rb +5 -6
  20. data/lib/datadog/opentelemetry/sdk/span_processor.rb +39 -8
  21. data/lib/datadog/opentelemetry/sdk/trace/span.rb +102 -3
  22. data/lib/datadog/opentracer/text_map_propagator.rb +2 -1
  23. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -0
  24. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -0
  25. data/lib/datadog/profiling/component.rb +13 -5
  26. data/lib/datadog/tracing/configuration/ext.rb +3 -0
  27. data/lib/datadog/tracing/configuration/settings.rb +18 -3
  28. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
  29. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +24 -0
  30. data/lib/datadog/tracing/distributed/datadog.rb +0 -1
  31. data/lib/datadog/tracing/distributed/propagation.rb +25 -4
  32. data/lib/datadog/tracing/trace_digest.rb +31 -0
  33. data/lib/datadog/tracing/workers.rb +1 -0
  34. data/lib/ddtrace/version.rb +2 -2
  35. metadata +7 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31833e75aea54c0952a8c9bce646f0e75e304a3d9124f372c022a6bb5c700ea6
4
- data.tar.gz: 6c487891f51fb0ef0c196fab1135f20d99417db62d82ee985cac621fb584f00a
3
+ metadata.gz: 69e775ab06a83ce14114a7287056e3d3fb575191b7ff6ccdc5c7b33f7fd58172
4
+ data.tar.gz: 13b607a4e29e516be4988dca7827eca09b79e968cf98e0641a155039d2ec3273
5
5
  SHA512:
6
- metadata.gz: d410408162b5ac03f8f80b8a18f5bd1adbcdd70625a45d288bd2f83a38b3b4668514d70ffd57c829d2ecfc9d7c2ac147ed831ccc7f45843ab51676fe501af2be
7
- data.tar.gz: a76c4aef579a961e0d588577d42ea23ee2ab28f08fa75d29de2fd422e3e8a5c3543e3ea63ae260ddee054c0b4ffa38c469ece4307abac92d36c08d32a971b3f2
6
+ metadata.gz: d345e07c8b0a654974c51a7457b3fc6d3d7eb99226cfc5555d6bc8ee3e65b17b3782b1e582591be925297c09dd104108007b2081e28ee43c103f8f2fec3ffe5b
7
+ data.tar.gz: '085ea801f5fae16ed58cd79bab86839cd1aa23fa09261b39219264f603455633e19dbb8f60d647e407c7218d7d00052ef7b593405ec5af828af56ffecd58227d'
data/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.18.0] - 2023-12-07
6
+
7
+ ### Added
8
+
9
+ * Tracing: Support lib injection for ARM64 architecture ([#3307][])
10
+ * Tracing: Add `error_handler` for `pg` instrumentation ([#3303][])
11
+ * Appsec: Enable "Trusted IPs", a.k.a passlist with optional monitoring ([#3229][])
12
+
13
+ ### Changed
14
+
15
+ * Mark ddtrace threads as fork-safe ([#3279][])
16
+ * Bump `datadog-ci` dependency to 0.5.0 ([#3308][])
17
+ * Bump `debase-ruby_core_source` dependency to 3.2.3 ([#3284][])
18
+ * Profiling: Disable profiler on Ruby 3.3 when running with RUBY_MN_THREADS=1 ([#3259][])
19
+ * Profiling: Run without "no signals" workaround on passenger 6.0.19+ ([#3280][])
20
+
21
+ ### Fixed
22
+
23
+ * Tracing: Fix `pg` instrumentation `enabled` settings ([#3271][])
24
+ * Profiling: Fix potential crash by importing upstream `rb_profile_frames` fix ([#3289][])
25
+ * Appsec: Call `devise` RegistrationsController block ([#3286][])
26
+
27
+ ## [1.17.0] - 2023-11-22
28
+
29
+ For W3C Trace Context, this release adds [`tracecontext`](https://www.w3.org/TR/trace-context/) to the default trace propagation extraction and injection styles. The new defaults are:
30
+ * Extraction: `Datadog,b3multi,b3,tracecontext`
31
+ * Injection: `Datadog,tracecontext`
32
+
33
+ And to increase interoperability with `tracecontext`, 128-bit Trace ID generation is now the default.
34
+
35
+ For OpenTelemetry, this release adds support for converting [OpenTelemetry Trace Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/general/trace/) into equivalent Datadog trace semantics. Also, it's now possible to configure top-level Datadog span fields using OpenTelemetry span attributes (https://github.com/DataDog/dd-trace-rb/pull/3262).
36
+
37
+ For CI Visibility, you can now manually create CI traces and spans with the [newly released API](https://github.com/DataDog/datadog-ci-rb/releases/tag/v0.4.0).
38
+
39
+ ### Added
40
+
41
+ * OpenTelemetry: Parse OpenTelemetry semantic conventions to Datadog's ([#3273][])
42
+ * OpenTelemetry: Support span reserved attribute overrides ([#3262][])
43
+ * Tracing: Ensure W3C `tracestate` is always propagated ([#3255][])
44
+
45
+ ### Changed
46
+
47
+ * Tracing: Set 128-bit trace_id to true by default ([#3266][])
48
+ * Tracing: Default trace propagation styles to `Datadog,b3multi,b3,tracecontext` ([#3248][],[#3267][])
49
+ * Ci-App: Upgraded `datadog-ci` dependency to 0.4 ([#3270][])
50
+
5
51
  ## [1.16.2] - 2023-11-10
6
52
 
7
53
  This release reverts a change to appsec response body parsing that was introduced in [1.16.0 ](https://github.com/DataDog/dd-trace-rb/releases/tag/v1.16.0) that may cause memory leaks.
@@ -2634,7 +2680,9 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
2634
2680
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
2635
2681
 
2636
2682
 
2637
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.16.2...master
2683
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.18.0...master
2684
+ [1.18.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.17.0...v1.18.0
2685
+ [1.17.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.16.2...v1.17.0
2638
2686
  [1.16.2]: https://github.com/DataDog/dd-trace-rb/compare/v1.16.1...v1.16.2
2639
2687
  [1.16.1]: https://github.com/DataDog/dd-trace-rb/compare/v1.16.0...v1.16.1
2640
2688
  [1.16.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.15.0...v1.16.0
@@ -3845,11 +3893,29 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3845
3893
  [#3206]: https://github.com/DataDog/dd-trace-rb/issues/3206
3846
3894
  [#3207]: https://github.com/DataDog/dd-trace-rb/issues/3207
3847
3895
  [#3223]: https://github.com/DataDog/dd-trace-rb/issues/3223
3896
+ [#3229]: https://github.com/DataDog/dd-trace-rb/issues/3229
3848
3897
  [#3234]: https://github.com/DataDog/dd-trace-rb/issues/3234
3849
3898
  [#3235]: https://github.com/DataDog/dd-trace-rb/issues/3235
3850
3899
  [#3240]: https://github.com/DataDog/dd-trace-rb/issues/3240
3851
3900
  [#3242]: https://github.com/DataDog/dd-trace-rb/issues/3242
3901
+ [#3248]: https://github.com/DataDog/dd-trace-rb/issues/3248
3852
3902
  [#3252]: https://github.com/DataDog/dd-trace-rb/issues/3252
3903
+ [#3255]: https://github.com/DataDog/dd-trace-rb/issues/3255
3904
+ [#3259]: https://github.com/DataDog/dd-trace-rb/issues/3259
3905
+ [#3262]: https://github.com/DataDog/dd-trace-rb/issues/3262
3906
+ [#3266]: https://github.com/DataDog/dd-trace-rb/issues/3266
3907
+ [#3267]: https://github.com/DataDog/dd-trace-rb/issues/3267
3908
+ [#3270]: https://github.com/DataDog/dd-trace-rb/issues/3270
3909
+ [#3271]: https://github.com/DataDog/dd-trace-rb/issues/3271
3910
+ [#3273]: https://github.com/DataDog/dd-trace-rb/issues/3273
3911
+ [#3279]: https://github.com/DataDog/dd-trace-rb/issues/3279
3912
+ [#3280]: https://github.com/DataDog/dd-trace-rb/issues/3280
3913
+ [#3284]: https://github.com/DataDog/dd-trace-rb/issues/3284
3914
+ [#3286]: https://github.com/DataDog/dd-trace-rb/issues/3286
3915
+ [#3289]: https://github.com/DataDog/dd-trace-rb/issues/3289
3916
+ [#3303]: https://github.com/DataDog/dd-trace-rb/issues/3303
3917
+ [#3307]: https://github.com/DataDog/dd-trace-rb/issues/3307
3918
+ [#3308]: https://github.com/DataDog/dd-trace-rb/issues/3308
3853
3919
  [@AdrianLC]: https://github.com/AdrianLC
3854
3920
  [@Azure7111]: https://github.com/Azure7111
3855
3921
  [@BabyGroot]: https://github.com/BabyGroot
@@ -25,6 +25,9 @@ void self_test_clock_id(void) {
25
25
  // Safety: This function is assumed never to raise exceptions by callers
26
26
  thread_cpu_time_id thread_cpu_time_id_for(VALUE thread) {
27
27
  rb_nativethread_id_t thread_id = pthread_id_for(thread);
28
+
29
+ if (thread_id == 0) return (thread_cpu_time_id) {.valid = false};
30
+
28
31
  clockid_t clock_id;
29
32
 
30
33
  int error = pthread_getcpuclockid(thread_id, &clock_id);
@@ -1150,6 +1150,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1150
1150
  // Since this is stack allocated, be careful about moving it
1151
1151
  ddog_CharSlice class_name;
1152
1152
  ddog_CharSlice *optional_class_name = NULL;
1153
+ char imemo_type[100];
1153
1154
 
1154
1155
  if (state->allocation_type_enabled) {
1155
1156
  optional_class_name = &class_name;
@@ -1197,7 +1198,13 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1197
1198
  class_name = ruby_value_type_to_class_name(type);
1198
1199
  }
1199
1200
  } else if (type == RUBY_T_IMEMO) {
1200
- class_name = DDOG_CHARSLICE_C("(VM Internal, T_IMEMO)");
1201
+ const char *imemo_string = imemo_kind(new_object);
1202
+ if (imemo_string != NULL) {
1203
+ snprintf(imemo_type, 100, "(VM Internal, T_IMEMO, %s)", imemo_string);
1204
+ class_name = (ddog_CharSlice) {.ptr = imemo_type, .len = strlen(imemo_type)};
1205
+ } else { // Ruby < 3
1206
+ class_name = DDOG_CHARSLICE_C("(VM Internal, T_IMEMO)");
1207
+ }
1201
1208
  } else {
1202
1209
  class_name = ruby_vm_type; // For other weird internal things we just use the VM type
1203
1210
  }
@@ -81,10 +81,7 @@ end
81
81
 
82
82
  # Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go.
83
83
  # But we can enable it in CI, so that we quickly spot any new warnings that just got introduced.
84
- #
85
- # @ivoanjo TODO: 3.3.0-preview releases are causing issues in CI because `have_header('vm_core.h')` below triggers warnings;
86
- # I've chosen to disable `-Werror` for this Ruby version for now, and we can revisit this on a later 3.3 release.
87
- add_compiler_flag '-Werror' if ENV['DDTRACE_CI'] == 'true' && !RUBY_DESCRIPTION.include?('3.3.0preview')
84
+ add_compiler_flag '-Werror' if ENV['DDTRACE_CI'] == 'true'
88
85
 
89
86
  # Older gcc releases may not default to C99 and we need to ask for this. This is also used:
90
87
  # * by upstream Ruby -- search for gnu99 in the codebase
@@ -102,9 +99,6 @@ add_compiler_flag '-Wno-declaration-after-statement'
102
99
  # cause a segfault later. Let's ensure that never happens.
103
100
  add_compiler_flag '-Werror-implicit-function-declaration'
104
101
 
105
- # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
106
- add_compiler_flag '-Wunused-parameter'
107
-
108
102
  # The native extension is not intended to expose any symbols/functions for other native libraries to use;
109
103
  # the sole exception being `Init_ddtrace_profiling_native_extension` which needs to be visible for Ruby to call it when
110
104
  # it `dlopen`s the library.
@@ -131,6 +125,9 @@ if RUBY_PLATFORM.include?('linux')
131
125
  $defs << '-DHAVE_PTHREAD_GETCPUCLOCKID'
132
126
  end
133
127
 
128
+ # On older Rubies, M:N threads were not available
129
+ $defs << '-DNO_MN_THREADS_AVAILABLE' if RUBY_VERSION < '3.3'
130
+
134
131
  # On older Rubies, we did not need to include the ractor header (this was built into the MJIT header)
135
132
  $defs << '-DNO_RACTOR_HEADER_INCLUDE' if RUBY_VERSION < '3.3'
136
133
 
@@ -152,12 +149,18 @@ $defs << '-DNO_PRIMITIVE_POP' if RUBY_VERSION < '3.2'
152
149
  # On older Rubies, there was no tid member in the internal thread structure
153
150
  $defs << '-DNO_THREAD_TID' if RUBY_VERSION < '3.1'
154
151
 
152
+ # On older Rubies, there was no jit_return member on the rb_control_frame_t struct
153
+ $defs << '-DNO_JIT_RETURN' if RUBY_VERSION < '3.1'
154
+
155
155
  # On older Rubies, we need to use a backported version of this function. See private_vm_api_access.h for details.
156
156
  $defs << '-DUSE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME' if RUBY_VERSION < '3'
157
157
 
158
158
  # On older Rubies, there are no Ractors
159
159
  $defs << '-DNO_RACTORS' if RUBY_VERSION < '3'
160
160
 
161
+ # On older Rubies, rb_imemo_name did not exist
162
+ $defs << '-DNO_IMEMO_NAME' if RUBY_VERSION < '3'
163
+
161
164
  # On older Rubies, objects would not move
162
165
  $defs << '-DNO_T_MOVED' if RUBY_VERSION < '2.7'
163
166
 
@@ -238,6 +241,10 @@ if Datadog::Profiling::NativeExtensionHelpers::CAN_USE_MJIT_HEADER
238
241
  # NOTE: This needs to come after all changes to $defs
239
242
  create_header
240
243
 
244
+ # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
245
+ # See the comment on the same flag below for why this is done last.
246
+ add_compiler_flag '-Wunused-parameter'
247
+
241
248
  create_makefile EXTENSION_NAME
242
249
  else
243
250
  # The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on
@@ -253,9 +260,20 @@ else
253
260
  Debase::RubyCoreSource
254
261
  .create_makefile_with_core(
255
262
  proc do
256
- have_header('vm_core.h') &&
257
- have_header('iseq.h') &&
258
- (RUBY_VERSION < '3.3' || have_header('ractor_core.h'))
263
+ headers_available =
264
+ have_header('vm_core.h') &&
265
+ have_header('iseq.h') &&
266
+ (RUBY_VERSION < '3.3' || have_header('ractor_core.h'))
267
+
268
+ if headers_available
269
+ # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
270
+ # This is added as late as possible because in some Rubies we support (e.g. 3.3), adding this flag before
271
+ # checking if internal VM headers are available causes those checks to fail because of this warning (and not
272
+ # because the headers are not available.)
273
+ add_compiler_flag '-Wunused-parameter'
274
+ end
275
+
276
+ headers_available
259
277
  end,
260
278
  EXTENSION_NAME,
261
279
  )
@@ -16,7 +16,6 @@ static ID agent_id; // id of :agent in Ruby
16
16
 
17
17
  static ID log_failure_to_process_tag_id; // id of :log_failure_to_process_tag in Ruby
18
18
 
19
- static VALUE http_transport_class = Qnil;
20
19
  static VALUE library_version_string = Qnil;
21
20
 
22
21
  struct call_exporter_without_gvl_arguments {
@@ -54,7 +53,7 @@ static void interrupt_exporter_call(void *cancel_token);
54
53
  static VALUE ddtrace_version(void);
55
54
 
56
55
  void http_transport_init(VALUE profiling_module) {
57
- http_transport_class = rb_define_class_under(profiling_module, "HttpTransport", rb_cObject);
56
+ VALUE http_transport_class = rb_define_class_under(profiling_module, "HttpTransport", rb_cObject);
58
57
 
59
58
  rb_define_singleton_method(http_transport_class, "_native_validate_exporter", _native_validate_exporter, 1);
60
59
  rb_define_singleton_method(http_transport_class, "_native_do_export", _native_do_export, 12);
@@ -180,6 +179,10 @@ static ddog_Vec_Tag convert_tags(VALUE tags_as_array) {
180
179
  }
181
180
 
182
181
  static VALUE log_failure_to_process_tag(VALUE err_details) {
182
+ VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
183
+ VALUE profiling_module = rb_const_get(datadog_module, rb_intern("Profiling"));
184
+ VALUE http_transport_class = rb_const_get(profiling_module, rb_intern("HttpTransport"));
185
+
183
186
  return rb_funcall(http_transport_class, log_failure_to_process_tag_id, 1, err_details);
184
187
  }
185
188
 
@@ -58,9 +58,12 @@ static inline rb_thread_t *thread_struct_from_object(VALUE thread) {
58
58
  }
59
59
 
60
60
  rb_nativethread_id_t pthread_id_for(VALUE thread) {
61
- // struct rb_native_thread was introduced in Ruby 3.2 (preview2): https://github.com/ruby/ruby/pull/5836
61
+ // struct rb_native_thread was introduced in Ruby 3.2: https://github.com/ruby/ruby/pull/5836
62
62
  #ifndef NO_RB_NATIVE_THREAD
63
- return thread_struct_from_object(thread)->nt->thread_id;
63
+ struct rb_native_thread* native_thread = thread_struct_from_object(thread)->nt;
64
+ // This can be NULL on Ruby 3.3 with MN threads (RUBY_MN_THREADS=1)
65
+ if (native_thread == NULL) return 0;
66
+ return native_thread->thread_id;
64
67
  #else
65
68
  return thread_struct_from_object(thread)->thread_id;
66
69
  #endif
@@ -113,15 +116,16 @@ bool is_current_thread_holding_the_gvl(void) {
113
116
 
114
117
  if (current_owner == NULL) return (current_gvl_owner) {.valid = false};
115
118
 
116
- return (current_gvl_owner) {
117
- .valid = true,
118
- .owner =
119
- #ifndef NO_RB_NATIVE_THREAD
120
- current_owner->nt->thread_id
121
- #else
122
- current_owner->thread_id
123
- #endif
124
- };
119
+ #ifndef NO_RB_NATIVE_THREAD
120
+ struct rb_native_thread* current_owner_native_thread = current_owner->nt;
121
+
122
+ // This can be NULL on Ruby 3.3 with MN threads (RUBY_MN_THREADS=1)
123
+ if (current_owner_native_thread == NULL) return (current_gvl_owner) {.valid = false};
124
+
125
+ return (current_gvl_owner) {.valid = true, .owner = current_owner_native_thread->thread_id};
126
+ #else
127
+ return (current_gvl_owner) {.valid = true, .owner = current_owner->thread_id};
128
+ #endif
125
129
  }
126
130
  #else
127
131
  current_gvl_owner gvl_owner(void) {
@@ -182,7 +186,9 @@ uint64_t native_thread_id_for(VALUE thread) {
182
186
  // The tid is only available on Ruby >= 3.1 + Linux (and FreeBSD). It's the same as `gettid()` aka the task id as seen in /proc
183
187
  #if !defined(NO_THREAD_TID) && defined(RB_THREAD_T_HAS_NATIVE_ID)
184
188
  #ifndef NO_RB_NATIVE_THREAD
185
- return thread_struct_from_object(thread)->nt->tid;
189
+ struct rb_native_thread* native_thread = thread_struct_from_object(thread)->nt;
190
+ if (native_thread == NULL) rb_raise(rb_eRuntimeError, "BUG: rb_native_thread* is null. Is this Ruby running with RUBY_MN_THREADS=1?");
191
+ return native_thread->tid;
186
192
  #else
187
193
  return thread_struct_from_object(thread)->tid;
188
194
  #endif
@@ -407,6 +413,7 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
407
413
  // the `VALUE` returned by rb_profile_frames returns `(eval)` instead of the path of the file where the `eval`
408
414
  // was called from.
409
415
  // * Imported fix from https://github.com/ruby/ruby/pull/7116 to avoid sampling threads that are still being created
416
+ // * Imported fix from https://github.com/ruby/ruby/pull/8415 to avoid potential crash when using YJIT.
410
417
  //
411
418
  // What is rb_profile_frames?
412
419
  // `rb_profile_frames` is a Ruby VM debug API added for use by profilers for sampling the stack trace of a Ruby thread.
@@ -442,12 +449,15 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, i
442
449
  // Modified from upstream: Instead of using `GET_EC` to collect info from the current thread,
443
450
  // support sampling any thread (including the current) passed as an argument
444
451
  rb_thread_t *th = thread_struct_from_object(thread);
445
- #ifndef USE_THREAD_INSTEAD_OF_EXECUTION_CONTEXT // Modern Rubies
446
- const rb_execution_context_t *ec = th->ec;
447
- #else // Ruby < 2.5
448
- const rb_thread_t *ec = th;
449
- #endif
452
+ #ifndef USE_THREAD_INSTEAD_OF_EXECUTION_CONTEXT // Modern Rubies
453
+ const rb_execution_context_t *ec = th->ec;
454
+ #else // Ruby < 2.5
455
+ const rb_thread_t *ec = th;
456
+ #endif
450
457
  const rb_control_frame_t *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
458
+ #ifndef NO_JIT_RETURN
459
+ const rb_control_frame_t *top = cfp;
460
+ #endif
451
461
  const rb_callable_method_entry_t *cme;
452
462
 
453
463
  // Avoid sampling dead threads
@@ -522,7 +532,20 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, i
522
532
  buff[i] = (VALUE)cfp->iseq;
523
533
  }
524
534
 
525
- lines[i] = calc_lineno(cfp->iseq, cfp->pc);
535
+ // The topmost frame may not have an updated PC because the JIT
536
+ // may not have set one. The JIT compiler will update the PC
537
+ // before entering a new function (so that `caller` will work),
538
+ // so only the topmost frame could possibly have an out of date PC
539
+ #ifndef NO_JIT_RETURN
540
+ if (cfp == top && cfp->jit_return) {
541
+ lines[i] = 0;
542
+ } else {
543
+ lines[i] = calc_lineno(cfp->iseq, cfp->pc);
544
+ }
545
+ #else // Ruby < 3.1
546
+ lines[i] = calc_lineno(cfp->iseq, cfp->pc);
547
+ #endif
548
+
526
549
  is_ruby_frame[i] = true;
527
550
  i++;
528
551
  }
@@ -811,3 +834,40 @@ VALUE invoke_location_for(VALUE thread, int *line_location) {
811
834
  *line_location = NUM2INT(rb_iseq_first_lineno(iseq));
812
835
  return rb_iseq_path(iseq);
813
836
  }
837
+
838
+ void self_test_mn_enabled(void) {
839
+ #ifdef NO_MN_THREADS_AVAILABLE
840
+ return;
841
+ #else
842
+ if (ddtrace_get_ractor()->threads.sched.enable_mn_threads == true) {
843
+ rb_raise(rb_eRuntimeError, "Ruby VM is running with RUBY_MN_THREADS=1. This is not yet supported");
844
+ }
845
+ #endif
846
+ }
847
+
848
+ // Taken from upstream imemo.h at commit 6ebcf25de2859b5b6402b7e8b181066c32d0e0bf (November 2023, master branch)
849
+ // (See the Ruby project copyright and license above)
850
+ // to enable calling rb_imemo_name
851
+ //
852
+ // Modifications:
853
+ // * Added IMEMO_MASK define
854
+ // * Changed return type to int to avoid having to define `enum imemo_type`
855
+ static inline int ddtrace_imemo_type(VALUE imemo) {
856
+ // This mask is the same between Ruby 2.5 and 3.3-preview3. Furthermore, the intention of this method is to be used
857
+ // to call `rb_imemo_name` which correctly handles invalid numbers so even if the mask changes in the future, at most
858
+ // we'll get incorrect results (and never a VM crash)
859
+ #define IMEMO_MASK 0x0f
860
+ return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
861
+ }
862
+
863
+ // Safety: This function assumes the object passed in is of the imemo type. But in the worst case, you'll just get
864
+ // a string that doesn't make any sense.
865
+ #ifndef NO_IMEMO_NAME
866
+ const char *imemo_kind(VALUE imemo) {
867
+ return rb_imemo_name(ddtrace_imemo_type(imemo));
868
+ }
869
+ #else
870
+ const char *imemo_kind(__attribute__((unused)) VALUE imemo) {
871
+ return NULL;
872
+ }
873
+ #endif
@@ -49,3 +49,9 @@ bool ddtrace_rb_ractor_main_p(void);
49
49
  // This is what Ruby shows in `Thread#to_s`.
50
50
  // The file is returned directly, and the line is recorded onto *line_location.
51
51
  VALUE invoke_location_for(VALUE thread, int *line_location);
52
+
53
+ // Check if RUBY_MN_THREADS is enabled (aka main Ractor is not doing 1:1 threads)
54
+ void self_test_mn_enabled(void);
55
+
56
+ // Provides more specific information on what kind an imemo is
57
+ const char *imemo_kind(VALUE imemo);
@@ -68,6 +68,7 @@ void DDTRACE_EXPORT Init_ddtrace_profiling_native_extension(void) {
68
68
 
69
69
  static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
70
70
  self_test_clock_id();
71
+ self_test_mn_enabled();
71
72
 
72
73
  return Qtrue;
73
74
  }
@@ -129,8 +129,6 @@
129
129
  static VALUE ok_symbol = Qnil; // :ok in Ruby
130
130
  static VALUE error_symbol = Qnil; // :error in Ruby
131
131
 
132
- static VALUE stack_recorder_class = Qnil;
133
-
134
132
  // Note: Please DO NOT use `VALUE_STRING` anywhere else, instead use `DDOG_CHARSLICE_C`.
135
133
  // `VALUE_STRING` is only needed because older versions of gcc (4.9.2, used in our Ruby 2.2 CI test images)
136
134
  // tripped when compiling `enabled_value_types` using `-std=gnu99` due to the extra cast that is included in
@@ -217,7 +215,7 @@ static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_
217
215
  static void reset_profile(ddog_prof_Profile *profile, ddog_Timespec *start_time /* Can be null */);
218
216
 
219
217
  void stack_recorder_init(VALUE profiling_module) {
220
- stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject);
218
+ VALUE stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject);
221
219
  // Hosts methods used for testing the native code using RSpec
222
220
  VALUE testing_module = rb_define_module_under(stack_recorder_class, "Testing");
223
221
 
@@ -38,12 +38,15 @@ module Datadog
38
38
 
39
39
  data = AppSec::Processor::RuleLoader.load_data(
40
40
  ip_denylist: settings.appsec.ip_denylist,
41
- user_id_denylist: settings.appsec.user_id_denylist
41
+ user_id_denylist: settings.appsec.user_id_denylist,
42
42
  )
43
43
 
44
+ exclusions = AppSec::Processor::RuleLoader.load_exclusions(ip_passlist: settings.appsec.ip_passlist)
45
+
44
46
  ruleset = AppSec::Processor::RuleMerger.merge(
45
47
  rules: [rules],
46
48
  data: data,
49
+ exclusions: exclusions,
47
50
  )
48
51
 
49
52
  processor = Processor.new(ruleset: ruleset)
@@ -54,6 +54,10 @@ module Datadog
54
54
  o.default :recommended
55
55
  end
56
56
 
57
+ option :ip_passlist do |o|
58
+ o.default []
59
+ end
60
+
57
61
  option :ip_denylist do |o|
58
62
  o.type :array
59
63
  o.default []
@@ -42,6 +42,8 @@ module Datadog
42
42
  **event_information.to_h
43
43
  )
44
44
  end
45
+
46
+ yield resource if block_given?
45
47
  end
46
48
  end
47
49
  end
@@ -47,6 +47,13 @@ module Datadog
47
47
  data
48
48
  end
49
49
 
50
+ def load_exclusions(ip_passlist: [])
51
+ exclusions = []
52
+ exclusions << [passlist_exclusions(ip_passlist)] if ip_passlist.any?
53
+
54
+ exclusions
55
+ end
56
+
50
57
  private
51
58
 
52
59
  def denylist_data(id, denylist)
@@ -56,6 +63,59 @@ module Datadog
56
63
  'data' => denylist.map { |v| { 'value' => v.to_s, 'expiration' => 2**63 } }
57
64
  }
58
65
  end
66
+
67
+ def passlist_exclusions(ip_passlist) # rubocop:disable Metrics/MethodLength
68
+ case ip_passlist
69
+ when Array
70
+ pass = ip_passlist
71
+ monitor = []
72
+ when Hash
73
+ pass = ip_passlist[:pass]
74
+ monitor = ip_passlist[:monitor]
75
+ else
76
+ pass = []
77
+ monitor = []
78
+ end
79
+
80
+ exclusions = []
81
+
82
+ exclusions << {
83
+ 'conditions' => [
84
+ {
85
+ 'operator' => 'ip_match',
86
+ 'parameters' => {
87
+ 'inputs' => [
88
+ {
89
+ 'address' => 'http.client_ip'
90
+ }
91
+ ],
92
+ 'list' => pass
93
+ }
94
+ }
95
+ ],
96
+ 'id' => SecureRandom.uuid,
97
+ }
98
+
99
+ exclusions << {
100
+ 'conditions' => [
101
+ {
102
+ 'operator' => 'ip_match',
103
+ 'parameters' => {
104
+ 'inputs' => [
105
+ {
106
+ 'address' => 'http.client_ip'
107
+ }
108
+ ],
109
+ 'list' => monitor
110
+ }
111
+ }
112
+ ],
113
+ 'id' => SecureRandom.uuid,
114
+ 'on_match' => 'monitor'
115
+ }
116
+
117
+ exclusions
118
+ end
59
119
  end
60
120
  end
61
121
  end
@@ -12,15 +12,17 @@ module Datadog
12
12
  class NoRulesError < StandardError; end
13
13
 
14
14
  class << self
15
- CAP_ASM_ACTIVATION = 1 << 1 # Remote activation via ASM_FEATURES product
16
- CAP_ASM_IP_BLOCKING = 1 << 2 # accept IP blocking data from ASM_DATA product
17
- CAP_ASM_DD_RULES = 1 << 3 # read ASM rules from ASM_DD product
18
- CAP_ASM_EXCLUSIONS = 1 << 4 # exclusion filters (passlist) via ASM product
19
- CAP_ASM_REQUEST_BLOCKING = 1 << 5 # can block on request info
20
- CAP_ASM_RESPONSE_BLOCKING = 1 << 6 # can block on response info
21
- CAP_ASM_USER_BLOCKING = 1 << 7 # accept user blocking data from ASM_DATA product
22
- CAP_ASM_CUSTOM_RULES = 1 << 8 # accept custom rules
23
- CAP_ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9 # supports custom http code or redirect sa blocking response
15
+ CAP_ASM_RESERVED_1 = 1 << 0 # RESERVED
16
+ CAP_ASM_ACTIVATION = 1 << 1 # Remote activation via ASM_FEATURES product
17
+ CAP_ASM_IP_BLOCKING = 1 << 2 # accept IP blocking data from ASM_DATA product
18
+ CAP_ASM_DD_RULES = 1 << 3 # read ASM rules from ASM_DD product
19
+ CAP_ASM_EXCLUSIONS = 1 << 4 # exclusion filters (passlist) via ASM product
20
+ CAP_ASM_REQUEST_BLOCKING = 1 << 5 # can block on request info
21
+ CAP_ASM_RESPONSE_BLOCKING = 1 << 6 # can block on response info
22
+ CAP_ASM_USER_BLOCKING = 1 << 7 # accept user blocking data from ASM_DATA product
23
+ CAP_ASM_CUSTOM_RULES = 1 << 8 # accept custom rules
24
+ CAP_ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9 # supports custom http code or redirect sa blocking response
25
+ CAP_ASM_TRUSTED_IPS = 1 << 10 # supports trusted ip
24
26
 
25
27
  # TODO: we need to dynamically add CAP_ASM_ACTIVATION once we support it
26
28
  ASM_CAPABILITIES = [
@@ -32,6 +34,7 @@ module Datadog
32
34
  CAP_ASM_DD_RULES,
33
35
  CAP_ASM_CUSTOM_RULES,
34
36
  CAP_ASM_CUSTOM_BLOCKING_RESPONSE,
37
+ CAP_ASM_TRUSTED_IPS,
35
38
  ].freeze
36
39
 
37
40
  ASM_PRODUCTS = [
@@ -273,6 +273,10 @@ module Datadog
273
273
  def handle_interrupt_shutdown!
274
274
  logger = Datadog.logger
275
275
  shutdown_thread = Thread.new { shutdown! }
276
+ unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
277
+ shutdown_thread.name = Datadog::Core::Configuration.name
278
+ end
279
+
276
280
  print_message_treshold_seconds = 0.2
277
281
 
278
282
  slow_shutdown = shutdown_thread.join(print_message_treshold_seconds).nil?
@@ -30,6 +30,7 @@ module Datadog
30
30
 
31
31
  thread = Thread.new { poll(@interval) }
32
32
  thread.name = self.class.name unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
33
+ thread.thread_variable_set(:fork_safe, true)
33
34
  @thr = thread
34
35
 
35
36
  @started = true
@@ -147,6 +147,7 @@ module Datadog
147
147
  # rubocop:enable Lint/RescueException
148
148
  end
149
149
  @worker.name = self.class.name unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
150
+ @worker.thread_variable_set(:fork_safe, true)
150
151
 
151
152
  nil
152
153
  end
@@ -16,11 +16,13 @@ module Datadog
16
16
  '(Could not open /proc/sys/kernel/core_pattern)'
17
17
  end
18
18
 
19
+ enabled_status = "Maximum size: #{maximum_size} Output pattern: '#{core_pattern}'"
20
+
19
21
  if maximum_size <= 0
20
22
  Kernel.warn("[ddtrace] Could not enable core dumps on crash, maximum size is #{maximum_size} (disabled).")
21
23
  return
22
24
  elsif maximum_size == current_size
23
- Kernel.warn('[ddtrace] Core dumps already enabled, nothing to do!')
25
+ Kernel.warn("[ddtrace] Core dumps already enabled, nothing to do. #{enabled_status}")
24
26
  return
25
27
  end
26
28
 
@@ -35,12 +37,9 @@ module Datadog
35
37
  end
36
38
 
37
39
  if current_size == 0
38
- Kernel.warn("[ddtrace] Enabled core dumps. Maximum size: #{maximum_size} Output pattern: '#{core_pattern}'")
40
+ Kernel.warn("[ddtrace] Enabled core dumps. #{enabled_status}")
39
41
  else
40
- Kernel.warn(
41
- "[ddtrace] Raised core dump limit. Old size: #{current_size} " \
42
- "Maximum size: #{maximum_size} Output pattern: '#{core_pattern}'"
43
- )
42
+ Kernel.warn("[ddtrace] Raised core dump limit. Old size: #{current_size} #{enabled_status}")
44
43
  end
45
44
  end
46
45
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'trace/span'
4
+
3
5
  module Datadog
4
6
  module OpenTelemetry
5
7
  module SDK
@@ -76,16 +78,11 @@ module Datadog
76
78
  end
77
79
 
78
80
  def start_datadog_span(span)
79
- tags = span.resource.attribute_enumerator.to_h
81
+ attributes = span.attributes.dup # Dup to allow modification of frozen Hash
80
82
 
81
- kind = span.kind || 'internal'
82
- tags[Tracing::Metadata::Ext::TAG_KIND] = kind
83
+ name, kwargs = span_arguments(span, attributes)
83
84
 
84
- datadog_span = Tracing.trace(
85
- span.name,
86
- tags: tags,
87
- start_time: ns_to_time(span.start_timestamp)
88
- )
85
+ datadog_span = Tracing.trace(name, **kwargs)
89
86
 
90
87
  datadog_span.set_error([nil, span.status.description]) unless span.status.ok?
91
88
  datadog_span.set_tags(span.attributes)
@@ -93,6 +90,40 @@ module Datadog
93
90
  datadog_span
94
91
  end
95
92
 
93
+ # Some special attributes can override Datadog Span fields
94
+ def span_arguments(span, attributes)
95
+ if attributes.key?('analytics.event') && (analytics_event = attributes['analytics.event']).respond_to?(:casecmp)
96
+ attributes[Tracing::Metadata::Ext::Analytics::TAG_SAMPLE_RATE] = analytics_event.casecmp('true') == 0 ? 1 : 0
97
+ end
98
+ attributes[Tracing::Metadata::Ext::TAG_KIND] = span.kind || 'internal'
99
+
100
+ kwargs = { start_time: ns_to_time(span.start_timestamp) }
101
+
102
+ name = if attributes.key?('operation.name')
103
+ attributes['operation.name']
104
+ elsif (rich_name = Datadog::OpenTelemetry::Trace::Span.enrich_name(span.kind, attributes))
105
+ rich_name.downcase
106
+ else
107
+ span.kind
108
+ end
109
+
110
+ kwargs[:resource] = attributes.key?('resource.name') ? attributes['resource.name'] : span.name
111
+ kwargs[:service] = attributes['service.name'] if attributes.key?('service.name')
112
+ kwargs[:type] = attributes['span.type'] if attributes.key?('span.type')
113
+
114
+ attributes.reject! { |key, _| OpenTelemetry::Trace::Span::DATADOG_SPAN_ATTRIBUTE_OVERRIDES.include?(key) }
115
+
116
+ # DEV: There's no `flat_map!`; we have to split it into two operations
117
+ attributes = attributes.map do |key, value|
118
+ Datadog::OpenTelemetry::Trace::Span.serialize_attribute(key, value)
119
+ end
120
+ attributes.flatten!(1)
121
+
122
+ kwargs[:tags] = attributes
123
+
124
+ [name, kwargs]
125
+ end
126
+
96
127
  # From nanoseconds, used by OpenTelemetry, to a {Time} object, used by the Datadog Tracer.
97
128
  def ns_to_time(timestamp_ns)
98
129
  Time.at(timestamp_ns / 1000000000.0)
@@ -36,6 +36,78 @@ module Datadog
36
36
  span.set_error(status.description) if status && status.code == ::OpenTelemetry::Trace::Status::ERROR
37
37
  end
38
38
 
39
+ # Serialize values into Datadog span tags and metrics.
40
+ # Notably, arrays are exploded into many keys, each with
41
+ # a numeric suffix representing the array index, for example:
42
+ # `'foo' => ['a','b']` becomes `'foo.0' => 'a', 'foo.1' => 'b'`
43
+ def self.serialize_attribute(key, value)
44
+ if value.is_a?(Array)
45
+ value.flat_map.with_index do |v, idx|
46
+ serialize_attribute("#{key}.#{idx}", v)
47
+ end
48
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
49
+ [[key, value.to_s]]
50
+ else
51
+ [[key, value]]
52
+ end
53
+ end
54
+
55
+ # Create a meaningful Datadog operation name from the OpenTelemetry
56
+ # semantic convention for span kind and span attributes.
57
+ # @see https://opentelemetry.io/docs/specs/semconv/general/trace/
58
+
59
+ # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
60
+ def self.enrich_name(kind, attrs)
61
+ if attrs.key?('http.request.method')
62
+ return 'http.server.request' if kind == :server
63
+ return 'http.client.request' if kind == :client
64
+ end
65
+
66
+ return "#{attrs['db.system']}.query" if attrs.key?('db.system') && kind == :client
67
+
68
+ if (attrs.key?('messaging.system') || attrs.key?('messaging.operation')) &&
69
+ [:consumer, :producer, :server, :client].include?(kind)
70
+
71
+ return "#{attrs['messaging.system']}.#{attrs['messaging.operation']}"
72
+ end
73
+
74
+ if attrs.key?('rpc.system')
75
+ if attrs['rpc.system'] == 'aws-api' && kind == :client
76
+ service = attrs['rpc.service']
77
+ return "aws.#{service || 'client'}.request"
78
+ end
79
+
80
+ if kind == :client
81
+ return "#{attrs['rpc.system']}.client.request"
82
+ elsif kind == :server
83
+ return "#{attrs['rpc.system']}.server.request"
84
+ end
85
+ end
86
+
87
+ if attrs.key?('faas.invoked_provider') && attrs.key?('faas.invoked_name') && kind == :client
88
+ provider = attrs['faas.invoked_provider']
89
+ name = attrs['faas.invoked_name']
90
+ return "#{provider}.#{name}.invoke"
91
+ end
92
+
93
+ return "#{attrs['faas.trigger']}.invoke" if attrs.key?('faas.trigger') && kind == :server
94
+
95
+ return 'graphql.server.request' if attrs.key?('graphql.operation.type')
96
+
97
+ if kind == :server
98
+ protocol = attrs['network.protocol.name']
99
+ return protocol ? "#{protocol}.server.request" : 'server.request'
100
+ end
101
+
102
+ if kind == :client
103
+ protocol = attrs['network.protocol.name']
104
+ return protocol ? "#{protocol}.client.request" : 'client.request'
105
+ end
106
+
107
+ kind.to_s
108
+ end
109
+ # rubocop:enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
110
+
39
111
  private
40
112
 
41
113
  def datadog_set_attribute(key)
@@ -46,10 +118,18 @@ module Datadog
46
118
  # DEV: Accesses `@attributes` directly, since using `#attributes`
47
119
  # DEV: clones the hash, causing unnecessary overhead.
48
120
  if @attributes.key?(key)
49
- value = @attributes[key]
50
- span.set_tag(key, value)
121
+ # Try to find a richer operation name, unless an explicit override was provided.
122
+ if !@attributes.key?('operation.name') && (rich_name = Span.enrich_name(kind, @attributes))
123
+ span.name = rich_name.downcase
124
+ end
125
+
126
+ Span.serialize_attribute(key, @attributes[key]).each do |new_key, value|
127
+ override_datadog_values(span, new_key, value)
51
128
 
52
- span.service = value if key == 'service.name'
129
+ # When an attribute is used to override a Datadog Span property,
130
+ # it should NOT be set as a Datadog Span tag.
131
+ span.set_tag(new_key, value) unless DATADOG_SPAN_ATTRIBUTE_OVERRIDES.include?(new_key)
132
+ end
53
133
  else
54
134
  span.clear_tag(key)
55
135
 
@@ -61,6 +141,25 @@ module Datadog
61
141
  end
62
142
  end
63
143
 
144
+ # Some special attributes can override Datadog Span fields beyond tags and metrics.
145
+ # @return [Boolean] true if the key is a Datadog Span override attribute, false otherwise
146
+ def override_datadog_values(span, key, value)
147
+ span.name = value if key == 'operation.name'
148
+ span.resource = value if key == 'resource.name'
149
+ span.service = value if key == 'service.name'
150
+ span.type = value if key == 'span.type'
151
+
152
+ if key == 'analytics.event' && value.respond_to?(:casecmp)
153
+ Datadog::Tracing::Analytics.set_sample_rate(
154
+ span,
155
+ value.casecmp('true') == 0 ? 1 : 0
156
+ )
157
+ end
158
+ end
159
+
160
+ DATADOG_SPAN_ATTRIBUTE_OVERRIDES = ['analytics.event', 'operation.name', 'resource.name', 'service.name',
161
+ 'span.type'].freeze
162
+
64
163
  ::OpenTelemetry::SDK::Trace::Span.prepend(self)
65
164
  end
66
165
  end
@@ -33,7 +33,8 @@ module Datadog
33
33
  carrier[Tracing::Distributed::Datadog::ORIGIN_KEY] = digest.trace_origin
34
34
  carrier[Tracing::Distributed::Datadog::PARENT_ID_KEY] = digest.span_id
35
35
  carrier[Tracing::Distributed::Datadog::SAMPLING_PRIORITY_KEY] = digest.trace_sampling_priority
36
- carrier[Tracing::Distributed::Datadog::TRACE_ID_KEY] = digest.trace_id
36
+ carrier[Tracing::Distributed::Datadog::TRACE_ID_KEY] =
37
+ Datadog::Tracing::Utils::TraceId.to_low_order(digest.trace_id)
37
38
 
38
39
  nil
39
40
  end
@@ -78,6 +78,7 @@ module Datadog
78
78
  end
79
79
  end
80
80
  @worker_thread.name = self.class.name # Repeated from above to make sure thread gets named asap
81
+ @worker_thread.thread_variable_set(:fork_safe, true)
81
82
  end
82
83
 
83
84
  true
@@ -43,6 +43,7 @@ module Datadog
43
43
  end
44
44
  end
45
45
  @worker_thread.name = self.class.name # Repeated from above to make sure thread gets named asap
46
+ @worker_thread.thread_variable_set(:fork_safe, true)
46
47
  end
47
48
 
48
49
  true
@@ -171,12 +171,11 @@ module Datadog
171
171
  return true
172
172
  end
173
173
 
174
- if defined?(::PhusionPassenger)
174
+ if (defined?(::PhusionPassenger) || Gem.loaded_specs['passenger']) && incompatible_passenger_version?
175
175
  Datadog.logger.warn(
176
- 'Enabling the profiling "no signals" workaround because the passenger web server is in use. ' \
177
- 'This is needed because passenger is currently incompatible with the normal working mode ' \
178
- 'of the profiler, as detailed in <https://github.com/DataDog/dd-trace-rb/issues/2976>. ' \
179
- 'Profiling data will have lower quality.'
176
+ 'Enabling the profiling "no signals" workaround because an incompatible version of the passenger gem is ' \
177
+ 'installed. Profiling data will have lower quality.' \
178
+ 'To fix this, upgrade the passenger gem to version 6.0.19 or above.'
180
179
  )
181
180
  return true
182
181
  end
@@ -237,6 +236,15 @@ module Datadog
237
236
  true
238
237
  end
239
238
  end
239
+
240
+ # See https://github.com/datadog/dd-trace-rb/issues/2976 for details.
241
+ private_class_method def self.incompatible_passenger_version?
242
+ if Gem.loaded_specs['passenger']
243
+ Gem.loaded_specs['passenger'].version < Gem::Version.new('6.0.19')
244
+ else
245
+ true
246
+ end
247
+ end
240
248
  end
241
249
  end
242
250
  end
@@ -62,6 +62,9 @@ module Datadog
62
62
  # @see https://opentelemetry.io/docs/concepts/sdk-configuration/general-sdk-configuration/#get_otel__propagators
63
63
  PROPAGATION_STYLE_NONE = 'none'
64
64
 
65
+ # Strictly stop at the first successfully serialized style.
66
+ EXTRACT_FIRST = 'DD_TRACE_PROPAGATION_EXTRACT_FIRST'
67
+
65
68
  ENV_X_DATADOG_TAGS_MAX_LENGTH = 'DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH'
66
69
  end
67
70
 
@@ -63,6 +63,7 @@ module Datadog
63
63
  Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_DATADOG,
64
64
  Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_B3_MULTI_HEADER,
65
65
  Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_B3_SINGLE_HEADER,
66
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_TRACE_CONTEXT,
66
67
  ]
67
68
  )
68
69
  o.after_set do |styles|
@@ -93,7 +94,10 @@ module Datadog
93
94
  o.deprecated_env Tracing::Configuration::Ext::Distributed::ENV_PROPAGATION_STYLE_INJECT_OLD
94
95
  o.env Tracing::Configuration::Ext::Distributed::ENV_PROPAGATION_STYLE_INJECT
95
96
  # DEV-2.0: Change default value to `tracecontext, Datadog`.
96
- o.default [Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_DATADOG]
97
+ o.default [
98
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_DATADOG,
99
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_TRACE_CONTEXT,
100
+ ]
97
101
  o.after_set do |styles|
98
102
  # Modernize B3 options
99
103
  # DEV-2.0: Can be removed with the removal of deprecated B3 constants.
@@ -142,6 +146,17 @@ module Datadog
142
146
  set_option(:propagation_inject_style, styles)
143
147
  end
144
148
  end
149
+
150
+ # Strictly stop at the first successfully serialized style.
151
+ # This prevents the tracer from enriching the extracted context with information from
152
+ # other valid propagations styles present in the request.
153
+ # @default `DD_TRACE_PROPAGATION_EXTRACT_FIRST` environment variable, otherwise `false`.
154
+ # @return [Boolean]
155
+ option :propagation_extract_first do |o|
156
+ o.env Tracing::Configuration::Ext::Distributed::EXTRACT_FIRST
157
+ o.default false
158
+ o.type :bool
159
+ end
145
160
  end
146
161
 
147
162
  # Enable trace collection and span generation.
@@ -177,11 +192,11 @@ module Datadog
177
192
 
178
193
  # Enable 128 bit trace id generation.
179
194
  #
180
- # @default `DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED` environment variable, otherwise `false`
195
+ # @default `DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED` environment variable, otherwise `true`
181
196
  # @return [Boolean]
182
197
  option :trace_id_128_bit_generation_enabled do |o|
183
198
  o.env Tracing::Configuration::Ext::ENV_TRACE_ID_128_BIT_GENERATION_ENABLED
184
- o.default false
199
+ o.default true
185
200
  o.type :bool
186
201
  end
187
202
 
@@ -25,6 +25,11 @@ module Datadog
25
25
  o.default false
26
26
  end
27
27
 
28
+ option :error_handler do |o|
29
+ o.type :proc
30
+ o.default_proc(&Tracing::SpanOperation::Events::DEFAULT_ON_ERROR)
31
+ end
32
+
28
33
  option :analytics_sample_rate do |o|
29
34
  o.type :float
30
35
  o.env Ext::ENV_ANALYTICS_SAMPLE_RATE
@@ -21,18 +21,24 @@ module Datadog
21
21
  # PG::Connection patch methods
22
22
  module InstanceMethods
23
23
  def exec(sql, *args, &block)
24
+ return super unless enabled?
25
+
24
26
  trace(Ext::SPAN_EXEC, sql: sql, block: block) do |sql_statement, wrapped_block|
25
27
  super(sql_statement, *args, &wrapped_block)
26
28
  end
27
29
  end
28
30
 
29
31
  def exec_params(sql, params, *args, &block)
32
+ return super unless enabled?
33
+
30
34
  trace(Ext::SPAN_EXEC_PARAMS, sql: sql, block: block) do |sql_statement, wrapped_block|
31
35
  super(sql_statement, params, *args, &wrapped_block)
32
36
  end
33
37
  end
34
38
 
35
39
  def exec_prepared(statement_name, params, *args, &block)
40
+ return super unless enabled?
41
+
36
42
  trace(Ext::SPAN_EXEC_PREPARED, statement_name: statement_name, block: block) do |_, wrapped_block|
37
43
  super(statement_name, params, *args, &wrapped_block)
38
44
  end
@@ -40,6 +46,8 @@ module Datadog
40
46
 
41
47
  # async_exec is an alias to exec
42
48
  def async_exec(sql, *args, &block)
49
+ return super unless enabled?
50
+
43
51
  trace(Ext::SPAN_ASYNC_EXEC, sql: sql, block: block) do |sql_statement, wrapped_block|
44
52
  super(sql_statement, *args, &wrapped_block)
45
53
  end
@@ -47,6 +55,8 @@ module Datadog
47
55
 
48
56
  # async_exec_params is an alias to exec_params
49
57
  def async_exec_params(sql, params, *args, &block)
58
+ return super unless enabled?
59
+
50
60
  trace(Ext::SPAN_ASYNC_EXEC_PARAMS, sql: sql, block: block) do |sql_statement, wrapped_block|
51
61
  super(sql_statement, params, *args, &wrapped_block)
52
62
  end
@@ -54,24 +64,32 @@ module Datadog
54
64
 
55
65
  # async_exec_prepared is an alias to exec_prepared
56
66
  def async_exec_prepared(statement_name, params, *args, &block)
67
+ return super unless enabled?
68
+
57
69
  trace(Ext::SPAN_ASYNC_EXEC_PREPARED, statement_name: statement_name, block: block) do |_, wrapped_block|
58
70
  super(statement_name, params, *args, &wrapped_block)
59
71
  end
60
72
  end
61
73
 
62
74
  def sync_exec(sql, *args, &block)
75
+ return super unless enabled?
76
+
63
77
  trace(Ext::SPAN_SYNC_EXEC, sql: sql, block: block) do |sql_statement, wrapped_block|
64
78
  super(sql_statement, *args, &wrapped_block)
65
79
  end
66
80
  end
67
81
 
68
82
  def sync_exec_params(sql, params, *args, &block)
83
+ return super unless enabled?
84
+
69
85
  trace(Ext::SPAN_SYNC_EXEC_PARAMS, sql: sql, block: block) do |sql_statement, wrapped_block|
70
86
  super(sql_statement, params, *args, &wrapped_block)
71
87
  end
72
88
  end
73
89
 
74
90
  def sync_exec_prepared(statement_name, params, *args, &block)
91
+ return super unless enabled?
92
+
75
93
  trace(Ext::SPAN_SYNC_EXEC_PREPARED, statement_name: statement_name, block: block) do |_, wrapped_block|
76
94
  super(statement_name, params, *args, &wrapped_block)
77
95
  end
@@ -81,10 +99,12 @@ module Datadog
81
99
 
82
100
  def trace(name, sql: nil, statement_name: nil, block: nil)
83
101
  service = Datadog.configuration_for(self, :service_name) || datadog_configuration[:service_name]
102
+ error_handler = datadog_configuration[:error_handler]
84
103
  resource = statement_name || sql
85
104
 
86
105
  Tracing.trace(
87
106
  name,
107
+ on_error: error_handler,
88
108
  service: service,
89
109
  resource: resource,
90
110
  type: Tracing::Metadata::Ext::SQL::TYPE
@@ -172,6 +192,10 @@ module Datadog
172
192
  def comment_propagation
173
193
  datadog_configuration[:comment_propagation]
174
194
  end
195
+
196
+ def enabled?
197
+ datadog_configuration[:enabled]
198
+ end
175
199
  end
176
200
  end
177
201
  end
@@ -49,7 +49,6 @@ module Datadog
49
49
  build_tags(digest).tap do |tags|
50
50
  inject_tags!(tags, data) unless tags.empty?
51
51
  end
52
-
53
52
  data
54
53
  end
55
54
 
@@ -83,19 +83,40 @@ module Datadog
83
83
 
84
84
  extracted_trace_digest = nil
85
85
 
86
- ::Datadog.configuration.tracing.distributed_tracing.propagation_extract_style.each do |style|
86
+ config = ::Datadog.configuration.tracing.distributed_tracing
87
+
88
+ config.propagation_extract_style.each do |style|
87
89
  propagator = @propagation_styles[style]
88
90
  next if propagator.nil?
89
91
 
90
92
  begin
91
- extracted_trace_digest = propagator.extract(data)
93
+ if extracted_trace_digest
94
+ # Return if we are only inspecting the first valid style.
95
+ next if config.propagation_extract_first
96
+
97
+ # Continue parsing styles to find the W3C `tracestate` header, if present.
98
+ # `tracestate` must always be propagated, as it might contain pass-through data that we don't control.
99
+ # @see https://www.w3.org/TR/2021/REC-trace-context-1-20211123/#mutating-the-tracestate-field
100
+ next if style != Configuration::Ext::Distributed::PROPAGATION_STYLE_TRACE_CONTEXT
101
+
102
+ if (tracecontext_digest = propagator.extract(data))
103
+ # Only parse if it represent the same trace as the successfully extracted one
104
+ next unless tracecontext_digest.trace_id == extracted_trace_digest.trace_id
105
+
106
+ # Preserve the `tracestate`
107
+ extracted_trace_digest = extracted_trace_digest.merge(
108
+ trace_state: tracecontext_digest.trace_state,
109
+ trace_state_unknown_fields: tracecontext_digest.trace_state_unknown_fields
110
+ )
111
+ end
112
+ else
113
+ extracted_trace_digest = propagator.extract(data)
114
+ end
92
115
  rescue => e
93
116
  ::Datadog.logger.error(
94
117
  "Error extracting distributed trace data. Cause: #{e} Location: #{Array(e.backtrace).first}"
95
118
  )
96
119
  end
97
-
98
- break if extracted_trace_digest
99
120
  end
100
121
 
101
122
  extracted_trace_digest
@@ -141,6 +141,37 @@ module Datadog
141
141
 
142
142
  freeze
143
143
  end
144
+
145
+ # Creates a copy of this object, modifying the provided fields.
146
+ # @param field_value_pairs [Hash<String>] the fields to be overwritten
147
+ # @return [TraceDigest] returns a copy of this object with the `field_value_pairs` modified
148
+ def merge(field_value_pairs)
149
+ # DEV: Because we want to sometimes freeze the values provided to `TraceDigest`, it's best
150
+ # DEV: to let `#initialize` decide how to handle each field, instead of duplicating that logic here.
151
+ TraceDigest.new(
152
+ **{
153
+ span_id: span_id,
154
+ span_name: span_name,
155
+ span_resource: span_resource,
156
+ span_service: span_service,
157
+ span_type: span_type,
158
+ trace_distributed_tags: trace_distributed_tags,
159
+ trace_hostname: trace_hostname,
160
+ trace_id: trace_id,
161
+ trace_name: trace_name,
162
+ trace_origin: trace_origin,
163
+ trace_process_id: trace_process_id,
164
+ trace_resource: trace_resource,
165
+ trace_runtime_id: trace_runtime_id,
166
+ trace_sampling_priority: trace_sampling_priority,
167
+ trace_service: trace_service,
168
+ trace_distributed_id: trace_distributed_id,
169
+ trace_flags: trace_flags,
170
+ trace_state: trace_state,
171
+ trace_state_unknown_fields: trace_state_unknown_fields,
172
+ }.merge!(field_value_pairs)
173
+ )
174
+ end
144
175
  end
145
176
  end
146
177
  end
@@ -69,6 +69,7 @@ module Datadog
69
69
  Datadog.logger.debug { "Starting thread for: #{self}" }
70
70
  @worker = Thread.new { perform }
71
71
  @worker.name = self.class.name unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
72
+ @worker.thread_variable_set(:fork_safe, true)
72
73
 
73
74
  nil
74
75
  end
@@ -3,8 +3,8 @@
3
3
  module DDTrace
4
4
  module VERSION
5
5
  MAJOR = 1
6
- MINOR = 16
7
- PATCH = 2
6
+ MINOR = 18
7
+ PATCH = 0
8
8
  PRE = nil
9
9
  BUILD = nil
10
10
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ddtrace
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.16.2
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-10 00:00:00.000000000 Z
11
+ date: 2023-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 3.2.2
33
+ version: 3.2.3
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 3.2.2
40
+ version: 3.2.3
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: libddwaf
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.3.0
75
+ version: 0.5.0
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 0.3.0
82
+ version: 0.5.0
83
83
  description: |
84
84
  ddtrace is Datadog's tracing client for Ruby. It is used to trace requests
85
85
  as they flow across web servers, databases and microservices so that developers
@@ -890,7 +890,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
890
890
  - !ruby/object:Gem::Version
891
891
  version: 2.0.0
892
892
  requirements: []
893
- rubygems_version: 3.4.1
893
+ rubygems_version: 3.4.21
894
894
  signing_key:
895
895
  specification_version: 4
896
896
  summary: Datadog tracing code for your Ruby applications