datadog 2.26.0 → 2.27.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -1
  3. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +2 -1
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +7 -6
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +2 -2
  6. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
  7. data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -5
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +16 -12
  9. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +2 -2
  10. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +48 -1
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +41 -0
  12. data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
  13. data/ext/datadog_profiling_native_extension/heap_recorder.c +24 -24
  14. data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
  15. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +3 -22
  16. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +0 -5
  17. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +9 -8
  18. data/ext/datadog_profiling_native_extension/profiling.c +20 -15
  19. data/ext/datadog_profiling_native_extension/ruby_helpers.c +55 -44
  20. data/ext/datadog_profiling_native_extension/ruby_helpers.h +17 -5
  21. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +8 -2
  22. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +3 -0
  23. data/ext/datadog_profiling_native_extension/stack_recorder.c +16 -16
  24. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
  25. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +5 -2
  26. data/ext/libdatadog_api/crashtracker.c +5 -8
  27. data/ext/libdatadog_api/datadog_ruby_common.c +48 -1
  28. data/ext/libdatadog_api/datadog_ruby_common.h +41 -0
  29. data/ext/libdatadog_api/ddsketch.c +4 -8
  30. data/ext/libdatadog_api/feature_flags.c +5 -5
  31. data/ext/libdatadog_api/helpers.h +27 -0
  32. data/ext/libdatadog_api/init.c +4 -0
  33. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +8 -1
  34. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +9 -2
  35. data/lib/datadog/appsec/component.rb +1 -1
  36. data/lib/datadog/appsec/context.rb +3 -3
  37. data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
  38. data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
  39. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +47 -12
  40. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +32 -15
  41. data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
  42. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
  43. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +50 -14
  44. data/lib/datadog/appsec/ext.rb +2 -0
  45. data/lib/datadog/appsec/metrics/collector.rb +8 -3
  46. data/lib/datadog/appsec/metrics/exporter.rb +7 -0
  47. data/lib/datadog/appsec/metrics/telemetry.rb +7 -2
  48. data/lib/datadog/appsec/metrics.rb +5 -5
  49. data/lib/datadog/appsec/remote.rb +4 -4
  50. data/lib/datadog/appsec.rb +7 -1
  51. data/lib/datadog/core/configuration/settings.rb +17 -0
  52. data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
  53. data/lib/datadog/core/telemetry/logger.rb +2 -0
  54. data/lib/datadog/core/telemetry/logging.rb +20 -2
  55. data/lib/datadog/profiling/component.rb +13 -0
  56. data/lib/datadog/profiling/exporter.rb +4 -0
  57. data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
  58. data/lib/datadog/profiling/flush.rb +3 -0
  59. data/lib/datadog/profiling/profiler.rb +3 -5
  60. data/lib/datadog/profiling/scheduler.rb +8 -7
  61. data/lib/datadog/profiling/tag_builder.rb +1 -0
  62. data/lib/datadog/version.rb +1 -1
  63. metadata +6 -4
@@ -71,7 +71,7 @@ static void install_sigprof_signal_handler_internal(
71
71
  );
72
72
  }
73
73
 
74
- rb_raise(
74
+ raise_error(
75
75
  rb_eRuntimeError,
76
76
  "Could not install profiling signal handler (%s): There's a pre-existing SIGPROF signal handler",
77
77
  handler_pretty_name
@@ -95,8 +95,14 @@ static inline void toggle_sigprof_signal_handler_for_current_thread(int action)
95
95
  sigset_t signals_to_toggle;
96
96
  sigemptyset(&signals_to_toggle);
97
97
  sigaddset(&signals_to_toggle, SIGPROF);
98
+
98
99
  int error = pthread_sigmask(action, &signals_to_toggle, NULL);
99
- if (error) rb_exc_raise(rb_syserr_new_str(error, rb_sprintf("Unexpected failure in pthread_sigmask, action=%d", action)));
100
+ if (error) {
101
+ const char *message = (action == SIG_BLOCK) ?
102
+ "Unexpected failure in pthread_sigmask: action SIG_BLOCK" :
103
+ "Unexpected failure in pthread_sigmask: action SIG_UNBLOCK";
104
+ private_raise_syserr(error, message, message);
105
+ }
100
106
  }
101
107
 
102
108
  void block_sigprof_signal_handler_from_running_in_current_thread(void) {
@@ -3,7 +3,10 @@
3
3
  #include <signal.h>
4
4
  #include "datadog_ruby_common.h"
5
5
 
6
+
7
+
6
8
  void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
9
+
7
10
  void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name);
8
11
  void replace_sigprof_signal_handler_with_empty_handler(void (*expected_existing_handler)(int, siginfo_t *, void *));
9
12
  void remove_sigprof_signal_handler(void);
@@ -349,7 +349,7 @@ static VALUE _native_new(VALUE klass) {
349
349
  ddog_prof_ManagedStringStorageNewResult string_storage = ddog_prof_ManagedStringStorage_new();
350
350
 
351
351
  if (string_storage.tag == DDOG_PROF_MANAGED_STRING_STORAGE_NEW_RESULT_ERR) {
352
- rb_raise(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err));
352
+ raise_error(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err));
353
353
  }
354
354
 
355
355
  state->string_storage = string_storage.ok;
@@ -383,7 +383,7 @@ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_Val
383
383
  ddog_prof_Profile_with_string_storage(sample_types, NULL /* period is optional */, state->string_storage);
384
384
 
385
385
  if (slot_one_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) {
386
- rb_raise(rb_eRuntimeError, "Failed to initialize slot one profile: %"PRIsVALUE, get_error_details_and_drop(&slot_one_profile_result.err));
386
+ raise_error(rb_eRuntimeError, "Failed to initialize slot one profile: %"PRIsVALUE, get_error_details_and_drop(&slot_one_profile_result.err));
387
387
  }
388
388
 
389
389
  state->profile_slot_one = (profile_slot) { .profile = slot_one_profile_result.ok, .start_timestamp = start_timestamp };
@@ -393,7 +393,7 @@ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_Val
393
393
 
394
394
  if (slot_two_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) {
395
395
  // Note: No need to take any special care of slot one, it'll get cleaned up by stack_recorder_typed_data_free
396
- rb_raise(rb_eRuntimeError, "Failed to initialize slot two profile: %"PRIsVALUE, get_error_details_and_drop(&slot_two_profile_result.err));
396
+ raise_error(rb_eRuntimeError, "Failed to initialize slot two profile: %"PRIsVALUE, get_error_details_and_drop(&slot_two_profile_result.err));
397
397
  }
398
398
 
399
399
  state->profile_slot_two = (profile_slot) { .profile = slot_two_profile_result.ok, .start_timestamp = start_timestamp };
@@ -591,7 +591,7 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
591
591
 
592
592
  ddog_prof_MaybeError result = args.advance_gen_result;
593
593
  if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
594
- rb_raise(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&result.some));
594
+ raise_error(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&result.some));
595
595
  }
596
596
 
597
597
  VALUE start = ruby_time_from(args.slot->start_timestamp);
@@ -658,7 +658,7 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
658
658
  sampler_unlock_active_profile(active_slot);
659
659
 
660
660
  if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
661
- rb_raise(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&result.err));
661
+ raise_error(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&result.err));
662
662
  }
663
663
  }
664
664
 
@@ -682,7 +682,7 @@ void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_
682
682
  sampler_unlock_active_profile(active_slot);
683
683
 
684
684
  if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
685
- rb_raise(rb_eArgError, "Failed to record endpoint: %"PRIsVALUE, get_error_details_and_drop(&result.err));
685
+ raise_error(rb_eArgError, "Failed to record endpoint: %"PRIsVALUE, get_error_details_and_drop(&result.err));
686
686
  }
687
687
  }
688
688
 
@@ -824,7 +824,7 @@ static locked_profile_slot sampler_lock_active_profile(stack_recorder_state *sta
824
824
  }
825
825
 
826
826
  // We already tried both multiple times, and we did not succeed. This is not expected to happen. Let's stop sampling.
827
- rb_raise(rb_eRuntimeError, "Failed to grab either mutex in sampler_lock_active_profile");
827
+ raise_error(rb_eRuntimeError, "Failed to grab either mutex in sampler_lock_active_profile");
828
828
  }
829
829
 
830
830
  static void sampler_unlock_active_profile(locked_profile_slot active_slot) {
@@ -889,7 +889,7 @@ static VALUE test_slot_mutex_state(VALUE recorder_instance, int slot) {
889
889
  return Qtrue;
890
890
  } else {
891
891
  ENFORCE_SUCCESS_GVL(error);
892
- rb_raise(rb_eRuntimeError, "Failed to raise exception in test_slot_mutex_state; this should never happen");
892
+ raise_error(rb_eRuntimeError, "Failed to raise exception in test_slot_mutex_state; this should never happen");
893
893
  }
894
894
  }
895
895
 
@@ -941,7 +941,7 @@ static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_ins
941
941
  static void reset_profile_slot(profile_slot *slot, ddog_Timespec start_timestamp) {
942
942
  ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(&slot->profile);
943
943
  if (reset_result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
944
- rb_raise(rb_eRuntimeError, "Failed to reset profile: %"PRIsVALUE, get_error_details_and_drop(&reset_result.err));
944
+ raise_error(rb_eRuntimeError, "Failed to reset profile: %"PRIsVALUE, get_error_details_and_drop(&reset_result.err));
945
945
  }
946
946
  slot->start_timestamp = start_timestamp;
947
947
  slot->stats = (stats_slot) {};
@@ -1056,14 +1056,14 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE
1056
1056
  ddog_prof_ManagedStringStorageNewResult string_storage = ddog_prof_ManagedStringStorage_new();
1057
1057
 
1058
1058
  if (string_storage.tag == DDOG_PROF_MANAGED_STRING_STORAGE_NEW_RESULT_ERR) {
1059
- rb_raise(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err));
1059
+ raise_error(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err));
1060
1060
  }
1061
1061
 
1062
1062
  ddog_prof_Slice_ValueType sample_types = {.ptr = all_value_types, .len = ALL_VALUE_TYPES_COUNT};
1063
1063
  ddog_prof_Profile_NewResult profile = ddog_prof_Profile_with_string_storage(sample_types, NULL, string_storage.ok);
1064
1064
 
1065
1065
  if (profile.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) {
1066
- rb_raise(rb_eRuntimeError, "Failed to initialize profile: %"PRIsVALUE, get_error_details_and_drop(&profile.err));
1066
+ raise_error(rb_eRuntimeError, "Failed to initialize profile: %"PRIsVALUE, get_error_details_and_drop(&profile.err));
1067
1067
  }
1068
1068
 
1069
1069
  ddog_prof_ManagedStringId hello = intern_or_raise(string_storage.ok, DDOG_CHARSLICE_C("hello"));
@@ -1097,7 +1097,7 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE
1097
1097
  );
1098
1098
 
1099
1099
  if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
1100
- rb_raise(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&result.err));
1100
+ raise_error(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&result.err));
1101
1101
  }
1102
1102
 
1103
1103
  ddog_Timespec finish_timestamp = system_epoch_now_timespec();
@@ -1105,13 +1105,13 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE
1105
1105
  ddog_prof_Profile_SerializeResult serialize_result = ddog_prof_Profile_serialize(&profile.ok, &start_timestamp, &finish_timestamp);
1106
1106
 
1107
1107
  if (serialize_result.tag == DDOG_PROF_PROFILE_SERIALIZE_RESULT_ERR) {
1108
- rb_raise(rb_eRuntimeError, "Failed to serialize: %"PRIsVALUE, get_error_details_and_drop(&serialize_result.err));
1108
+ raise_error(rb_eRuntimeError, "Failed to serialize: %"PRIsVALUE, get_error_details_and_drop(&serialize_result.err));
1109
1109
  }
1110
1110
 
1111
1111
  ddog_prof_MaybeError advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(string_storage.ok);
1112
1112
 
1113
1113
  if (advance_gen_result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
1114
- rb_raise(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&advance_gen_result.some));
1114
+ raise_error(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&advance_gen_result.some));
1115
1115
  }
1116
1116
 
1117
1117
  VALUE encoded_pprof_1 = from_ddog_prof_EncodedProfile(serialize_result.ok);
@@ -1127,13 +1127,13 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE
1127
1127
  );
1128
1128
 
1129
1129
  if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
1130
- rb_raise(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&result.err));
1130
+ raise_error(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&result.err));
1131
1131
  }
1132
1132
 
1133
1133
  serialize_result = ddog_prof_Profile_serialize(&profile.ok, &start_timestamp, &finish_timestamp);
1134
1134
 
1135
1135
  if (serialize_result.tag == DDOG_PROF_PROFILE_SERIALIZE_RESULT_ERR) {
1136
- rb_raise(rb_eArgError, "Failed to serialize: %"PRIsVALUE, get_error_details_and_drop(&serialize_result.err));
1136
+ raise_error(rb_eArgError, "Failed to serialize: %"PRIsVALUE, get_error_details_and_drop(&serialize_result.err));
1137
1137
  }
1138
1138
 
1139
1139
  VALUE encoded_pprof_2 = from_ddog_prof_EncodedProfile(serialize_result.ok);
@@ -3,6 +3,7 @@
3
3
  #include <stdbool.h>
4
4
 
5
5
  #include "datadog_ruby_common.h"
6
+ #include "ruby_helpers.h"
6
7
  #include "unsafe_api_calls_check.h"
7
8
  #include "extconf.h"
8
9
 
@@ -21,7 +22,7 @@ void unsafe_api_calls_check_init(void) {
21
22
  check_for_unsafe_api_calls_handle = rb_postponed_job_preregister(unused_flags, check_for_unsafe_api_calls, NULL);
22
23
 
23
24
  if (check_for_unsafe_api_calls_handle == POSTPONED_JOB_HANDLE_INVALID) {
24
- rb_raise(rb_eRuntimeError, "Failed to register check_for_unsafe_api_calls_handle postponed job (got POSTPONED_JOB_HANDLE_INVALID)");
25
+ raise_error(rb_eRuntimeError, "Failed to register check_for_unsafe_api_calls_handle postponed job (got POSTPONED_JOB_HANDLE_INVALID)");
25
26
  }
26
27
  #endif
27
28
  }
@@ -5,8 +5,11 @@
5
5
  // Specifically, when the profiler is sampling, we're never supposed to call into Ruby code (e.g. methods
6
6
  // implemented using Ruby code) or allocate Ruby objects.
7
7
  // That's because those events introduce thread switch points, and really we don't the VM switching between threads
8
- // in the middle of the profiler sampling.
9
- // This includes raising exceptions, unless we're trying to stop the profiler, and even then we must be careful.
8
+ // in the middle of the profiler sampling. This includes raising exceptions.
9
+ //
10
+ // Raising exceptions as the very last operation, to stop the profiler is ok, but comes a caveat: raising exceptions
11
+ // will fail the unsafe check. When testing exception paths, you must disable unsafe checking for that test execution.
12
+ // See `allow_exception` usage for how to we disable it for testing today.
10
13
  //
11
14
  // The above is especially true in situations such as GC profiling or allocation/heap profiling, as in those situations
12
15
  // we can even crash the Ruby VM if we switch away at the wrong time.
@@ -2,6 +2,7 @@
2
2
  #include <datadog/crashtracker.h>
3
3
 
4
4
  #include "datadog_ruby_common.h"
5
+ #include "helpers.h"
5
6
 
6
7
  static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
7
8
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self);
@@ -41,7 +42,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
41
42
  ENFORCE_TYPE(action, T_SYMBOL);
42
43
  ENFORCE_TYPE(upload_timeout_seconds, T_FIXNUM);
43
44
 
44
- if (action != start_action && action != update_on_fork_action) rb_raise(rb_eArgError, "Unexpected action: %+"PRIsVALUE, action);
45
+ if (action != start_action && action != update_on_fork_action) raise_error(rb_eArgError, "Unexpected action: %+"PRIsVALUE, action);
45
46
 
46
47
  VALUE version = datadog_gem_version();
47
48
 
@@ -49,7 +50,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
49
50
  // Start of exception-free zone to prevent leaks {{
50
51
  ddog_Endpoint *endpoint = ddog_endpoint_from_url(char_slice_from_ruby_string(agent_base_url));
51
52
  if (endpoint == NULL) {
52
- rb_raise(rb_eRuntimeError, "Failed to create endpoint from agent_base_url: %"PRIsVALUE, agent_base_url);
53
+ raise_error(rb_eRuntimeError, "Failed to create endpoint from agent_base_url: %"PRIsVALUE, agent_base_url);
53
54
  }
54
55
  ddog_Vec_Tag tags = convert_tags(tags_as_array);
55
56
 
@@ -107,9 +108,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
107
108
  ddog_endpoint_drop(endpoint);
108
109
  // }} End of exception-free zone to prevent leaks
109
110
 
110
- if (result.tag == DDOG_VOID_RESULT_ERR) {
111
- rb_raise(rb_eRuntimeError, "Failed to start/update the crash tracker: %"PRIsVALUE, get_error_details_and_drop(&result.err));
112
- }
111
+ CHECK_VOID_RESULT("Failed to start/update the crash tracker", result);
113
112
 
114
113
  return Qtrue;
115
114
  }
@@ -117,9 +116,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
117
116
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self) {
118
117
  ddog_VoidResult result = ddog_crasht_disable();
119
118
 
120
- if (result.tag == DDOG_VOID_RESULT_ERR) {
121
- rb_raise(rb_eRuntimeError, "Failed to stop the crash tracker: %"PRIsVALUE, get_error_details_and_drop(&result.err));
122
- }
119
+ CHECK_VOID_RESULT("Failed to stop the crash tracker", result);
123
120
 
124
121
  return Qtrue;
125
122
  }
@@ -1,8 +1,11 @@
1
1
  #include "datadog_ruby_common.h"
2
+ #include <stdarg.h>
2
3
 
3
4
  // IMPORTANT: Currently this file is copy-pasted between extensions. Make sure to update all versions when doing any change!
4
5
 
5
- void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name) {
6
+ static ID telemetry_message_id;
7
+
8
+ void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char *function_name) {
6
9
  rb_exc_raise(
7
10
  rb_exc_new_str(
8
11
  rb_eTypeError,
@@ -18,6 +21,26 @@ void raise_unexpected_type(VALUE value, const char *value_name, const char *type
18
21
  );
19
22
  }
20
23
 
24
+ // Raises an exception with separate telemetry-safe and detailed messages.
25
+ // NOTE: Raising an exception always invokes Ruby code so it requires the GVL and is not compatible with "debug_enter_unsafe_context".
26
+ // @see debug_enter_unsafe_context
27
+ void private_raise_exception(VALUE exception, const char *static_message) {
28
+ rb_ivar_set(exception, telemetry_message_id, rb_str_new_cstr(static_message));
29
+ rb_exc_raise(exception);
30
+ }
31
+
32
+ // Helper for raising pre-formatted exceptions
33
+ void private_raise_error_formatted(VALUE exception_class, const char *detailed_message, const char *static_message) {
34
+ VALUE exception = rb_exc_new_cstr(exception_class, detailed_message);
35
+ private_raise_exception(exception, static_message);
36
+ }
37
+
38
+ // Use `raise_error` the macro instead, as it provides additional argument checks.
39
+ void private_raise_error(VALUE exception_class, const char *fmt, ...) {
40
+ FORMAT_VA_ERROR_MESSAGE(detailed_message, fmt);
41
+ private_raise_error_formatted(exception_class, detailed_message, fmt);
42
+ }
43
+
21
44
  VALUE datadog_gem_version(void) {
22
45
  VALUE ddtrace_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
23
46
  ENFORCE_TYPE(ddtrace_module, T_MODULE);
@@ -78,3 +101,27 @@ ddog_Vec_Tag convert_tags(VALUE tags_as_array) {
78
101
 
79
102
  return tags;
80
103
  }
104
+
105
+ size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capacity) {
106
+ if (capacity == 0 || string == NULL) {
107
+ // short-circuit, we can't write anything
108
+ ddog_Error_drop(error);
109
+ return 0;
110
+ }
111
+
112
+ ddog_CharSlice error_msg_slice = ddog_Error_message(error);
113
+ size_t error_msg_size = error_msg_slice.len;
114
+ // Account for extra null char for proper cstring
115
+ if (error_msg_size >= capacity) {
116
+ // Error message too big, lets truncate it to capacity - 1 to allow for extra null at end
117
+ error_msg_size = capacity - 1;
118
+ }
119
+ strncpy(string, error_msg_slice.ptr, error_msg_size);
120
+ string[error_msg_size] = '\0';
121
+ ddog_Error_drop(error);
122
+ return error_msg_size;
123
+ }
124
+
125
+ void datadog_ruby_common_init(void) {
126
+ telemetry_message_id = rb_intern("@telemetry_message");
127
+ }
@@ -5,6 +5,9 @@
5
5
  #include <ruby.h>
6
6
  #include <datadog/common.h>
7
7
 
8
+ // Must be called once during initialization
9
+ void datadog_ruby_common_init(void);
10
+
8
11
  // Used to mark symbols to be exported to the outside of the extension.
9
12
  // Consider very carefully before tagging a function with this.
10
13
  #define DDTRACE_EXPORT __attribute__ ((visibility ("default")))
@@ -32,6 +35,39 @@
32
35
 
33
36
  NORETURN(void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name));
34
37
 
38
+ // Raises an exception of the specified class with the formatted string as its message.
39
+ // This macro ensures that the literal string is sent for telemetry, while the formatted
40
+ // message is the default `Exception#message`.
41
+ // *Ruby exceptions not raised through this function will not be reported via telemetry.*
42
+ #define raise_error(exception_class, fmt, ...) \
43
+ private_raise_error(exception_class, "" fmt, ##__VA_ARGS__)
44
+
45
+ NORETURN(
46
+ void private_raise_error(VALUE exception_class, const char *fmt, ...)
47
+ __attribute__ ((format (printf, 2, 3)));
48
+ );
49
+
50
+ // Internal helper for raising pre-formatted exceptions
51
+ NORETURN(
52
+ void private_raise_error_formatted(VALUE exception_class, const char *detailed_message, const char *static_message)
53
+ );
54
+
55
+ // Raises an exception with separate telemetry-safe and detailed messages.
56
+ // NOTE: Raising an exception always invokes Ruby code so it requires the GVL and is not compatible with "debug_enter_unsafe_context".
57
+ // @see debug_enter_unsafe_context
58
+ NORETURN(
59
+ void private_raise_exception(VALUE exception, const char *static_message)
60
+ );
61
+
62
+ #define MAX_RAISE_MESSAGE_SIZE 256
63
+
64
+ #define FORMAT_VA_ERROR_MESSAGE(buf, fmt) \
65
+ char buf[MAX_RAISE_MESSAGE_SIZE]; \
66
+ va_list buf##_args; \
67
+ va_start(buf##_args, fmt); \
68
+ vsnprintf(buf, MAX_RAISE_MESSAGE_SIZE, fmt, buf##_args); \
69
+ va_end(buf##_args);
70
+
35
71
  // Helper to retrieve Datadog::VERSION::STRING
36
72
  VALUE datadog_gem_version(void);
37
73
 
@@ -61,3 +97,8 @@ static inline VALUE get_error_details_and_drop(ddog_Error *error) {
61
97
  ddog_Error_drop(error);
62
98
  return result;
63
99
  }
100
+
101
+ // Utility function to be able to extract an error cstring from a ddog_Error.
102
+ // Returns the amount of characters written to string (which are necessarily
103
+ // bounded by capacity - 1 since the string will be null-terminated).
104
+ size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capacity);
@@ -2,6 +2,7 @@
2
2
  #include <datadog/ddsketch.h>
3
3
 
4
4
  #include "datadog_ruby_common.h"
5
+ #include "helpers.h"
5
6
 
6
7
  static VALUE _native_new(VALUE klass);
7
8
  static void ddsketch_free(void *ptr);
@@ -9,7 +10,6 @@ static VALUE native_add(VALUE self, VALUE point);
9
10
  static VALUE native_add_with_count(VALUE self, VALUE point, VALUE count);
10
11
  static VALUE native_count(VALUE self);
11
12
  static VALUE native_encode(VALUE self);
12
- NORETURN(static void raise_ddsketch_error(const char *message, ddog_VoidResult result));
13
13
 
14
14
  void ddsketch_init(VALUE core_module) {
15
15
  VALUE ddsketch_class = rb_define_class_under(core_module, "DDSketch", rb_cObject);
@@ -48,17 +48,13 @@ static void ddsketch_free(void *ptr) {
48
48
  ruby_xfree(ptr);
49
49
  }
50
50
 
51
- static void raise_ddsketch_error(const char *message, ddog_VoidResult result) {
52
- rb_raise(rb_eRuntimeError, "%s: %"PRIsVALUE, message, get_error_details_and_drop(&result.err));
53
- }
54
-
55
51
  static VALUE native_add(VALUE self, VALUE point) {
56
52
  ddsketch_Handle_DDSketch *state;
57
53
  TypedData_Get_Struct(self, ddsketch_Handle_DDSketch, &ddsketch_typed_data, state);
58
54
 
59
55
  ddog_VoidResult result = ddog_ddsketch_add(state, NUM2DBL(point));
60
56
 
61
- if (result.tag == DDOG_VOID_RESULT_ERR) raise_ddsketch_error("DDSketch add failed", result);
57
+ CHECK_VOID_RESULT("DDSketch add failed", result);
62
58
 
63
59
  return self;
64
60
  }
@@ -69,7 +65,7 @@ static VALUE native_add_with_count(VALUE self, VALUE point, VALUE count) {
69
65
 
70
66
  ddog_VoidResult result = ddog_ddsketch_add_with_count(state, NUM2DBL(point), NUM2DBL(count));
71
67
 
72
- if (result.tag == DDOG_VOID_RESULT_ERR) raise_ddsketch_error("DDSketch add_with_count failed", result);
68
+ CHECK_VOID_RESULT("DDSketch add_with_count failed", result);
73
69
 
74
70
  return self;
75
71
  }
@@ -81,7 +77,7 @@ static VALUE native_count(VALUE self) {
81
77
  double count_out;
82
78
  ddog_VoidResult result = ddog_ddsketch_count(state, &count_out);
83
79
 
84
- if (result.tag == DDOG_VOID_RESULT_ERR) raise_ddsketch_error("DDSketch count failed", result);
80
+ CHECK_VOID_RESULT("DDSketch count failed", result);
85
81
 
86
82
  return DBL2NUM(count_out);
87
83
  }
@@ -130,7 +130,7 @@ void feature_flags_init(VALUE core_module) {
130
130
  static VALUE configuration_new(VALUE klass, VALUE json_str) {
131
131
  struct ddog_ffe_Result_HandleConfiguration result = ddog_ffe_configuration_new(borrow_str(json_str));
132
132
  if (result.tag == DDOG_FFE_RESULT_HANDLE_CONFIGURATION_ERR_HANDLE_CONFIGURATION) {
133
- rb_raise(feature_flags_error_class, "Failed to create configuration from JSON: %"PRIsVALUE, get_error_details_and_drop(&result.err));
133
+ raise_error(feature_flags_error_class, "Failed to create configuration from JSON: %"PRIsVALUE, get_error_details_and_drop(&result.err));
134
134
  }
135
135
  return TypedData_Wrap_Struct(klass, &configuration_data_type, result.ok);
136
136
  }
@@ -159,7 +159,7 @@ static ddog_ffe_ExpectedFlagType expected_type_from_value(VALUE expected_type) {
159
159
  } else if (id == id_float) {
160
160
  return DDOG_FFE_EXPECTED_FLAG_TYPE_FLOAT;
161
161
  } else {
162
- rb_raise(feature_flags_error_class, "Internal: Unexpected flag type: %"PRIsVALUE, expected_type);
162
+ raise_error(feature_flags_error_class, "Internal: Unexpected flag type: %"PRIsVALUE, expected_type);
163
163
  }
164
164
  }
165
165
 
@@ -199,7 +199,7 @@ static int evaluation_context_foreach_callback(VALUE key, VALUE value, VALUE arg
199
199
  if (builder->attr_count >= builder->attr_capacity) {
200
200
  // This should never happen because evaluation_context_from_hash()
201
201
  // pre-allocates attr_capacity equal to iterated Hash size.
202
- rb_raise(feature_flags_error_class, "Internal: Attribute count exceeded capacity");
202
+ raise_error(feature_flags_error_class, "Internal: Attribute count exceeded capacity");
203
203
  }
204
204
 
205
205
  ddog_ffe_AttributePair *attr = &builder->attrs[builder->attr_count];
@@ -354,7 +354,7 @@ static VALUE resolution_details_get_raw_value(VALUE self) {
354
354
  return Qnil;
355
355
  default:
356
356
  // This should never happen as we checked for all possible tag values.
357
- rb_raise(feature_flags_error_class, "Internal: Unexpected ResolutionDetails value tag");
357
+ raise_error(feature_flags_error_class, "Internal: Unexpected ResolutionDetails value tag");
358
358
  }
359
359
  }
360
360
 
@@ -387,7 +387,7 @@ static VALUE resolution_details_get_flag_type(VALUE self) {
387
387
  return Qnil;
388
388
  default:
389
389
  // This should never happen as we checked for all possible tag values.
390
- rb_raise(feature_flags_error_class, "Internal: Unexpected ResolutionDetails value tag");
390
+ raise_error(feature_flags_error_class, "Internal: Unexpected ResolutionDetails value tag");
391
391
  }
392
392
  }
393
393
 
@@ -0,0 +1,27 @@
1
+ #pragma once
2
+
3
+ #include "datadog_ruby_common.h"
4
+
5
+ // Raises a Ruby error if the `ddog_VoidResult` indicates an error.
6
+ // The error message in `result.err` is appended to the provided `message`.
7
+ //
8
+ // @param[in] message (const char *) The error message
9
+ // @param[in] result (ddog_VoidResult) A result to check
10
+ #define CHECK_VOID_RESULT(message, result) \
11
+ do { \
12
+ if (result.tag == DDOG_VOID_RESULT_ERR) { \
13
+ raise_lib_error(message, result); \
14
+ } \
15
+ } while (0)
16
+
17
+ // Raises a Ruby error for the error result.
18
+ // The error message in `result.err` is appended to the provided `message`.
19
+ //
20
+ // @param[in] message (const char *) The error message
21
+ // @param[in] result (struct { ddog_Error res; ... }) Any type of result
22
+ #define raise_lib_error(message, result) \
23
+ do { \
24
+ char error_msg[MAX_RAISE_MESSAGE_SIZE]; \
25
+ read_ddogerr_string_and_drop(&result.err, error_msg, MAX_RAISE_MESSAGE_SIZE); \
26
+ raise_error(rb_eRuntimeError, message ": %s", error_msg); \
27
+ } while (0)
@@ -10,6 +10,10 @@ void ddsketch_init(VALUE core_module);
10
10
 
11
11
  void DDTRACE_EXPORT Init_libdatadog_api(void) {
12
12
  VALUE datadog_module = rb_define_module("Datadog");
13
+
14
+ // MUST be called before all other initialization
15
+ datadog_ruby_common_init();
16
+
13
17
  VALUE core_module = rb_define_module_under(datadog_module, "Core");
14
18
 
15
19
  crashtracker_init(core_module);
@@ -19,7 +19,14 @@ module Datadog
19
19
  Enumerator.new do |yielder|
20
20
  @routes.each do |route|
21
21
  if route.dispatcher?
22
- yielder.yield RailsRouteSerializer.serialize(route)
22
+ if route.verb.include?('|')
23
+ # report separate route for each method for multi-method routes
24
+ route.verb.split('|').each do |method|
25
+ yielder.yield RailsRouteSerializer.serialize(route, method_override: method)
26
+ end
27
+ else
28
+ yielder.yield RailsRouteSerializer.serialize(route)
29
+ end
23
30
  elsif mounted_grape_app?(route.app.rack_app)
24
31
  route.app.rack_app.routes.each do |grape_route|
25
32
  yielder.yield GrapeRouteSerializer.serialize(grape_route, path_prefix: route.path.spec.to_s)
@@ -10,8 +10,15 @@ module Datadog
10
10
 
11
11
  module_function
12
12
 
13
- def serialize(route)
14
- method = route.verb.empty? ? "*" : route.verb
13
+ def serialize(route, method_override: nil)
14
+ method = if method_override
15
+ method_override
16
+ elsif route.verb.empty?
17
+ "*"
18
+ else
19
+ route.verb
20
+ end
21
+
15
22
  path = route.path.spec.to_s.delete_suffix(FORMAT_SUFFIX)
16
23
 
17
24
  {
@@ -78,7 +78,7 @@ module Datadog
78
78
  end
79
79
 
80
80
  def reconfigure!
81
- security_engine.reconfigure!
81
+ security_engine&.reconfigure!
82
82
  end
83
83
 
84
84
  def shutdown!
@@ -48,11 +48,11 @@ module Datadog
48
48
  result
49
49
  end
50
50
 
51
- def run_rasp(type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
51
+ def run_rasp(type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT, phase: nil)
52
52
  result = @waf_runner.run(persistent_data, ephemeral_data, timeout)
53
53
 
54
- Metrics::Telemetry.report_rasp(type, result)
55
- @metrics.record_rasp(result)
54
+ Metrics::Telemetry.report_rasp(type, result, phase: phase)
55
+ @metrics.record_rasp(result, type: type, phase: phase)
56
56
 
57
57
  result
58
58
  end
@@ -24,7 +24,7 @@ module Datadog
24
24
  end
25
25
 
26
26
  def self.compatible?
27
- super && version >= MINIMUM_VERSION
27
+ super && !!(version&.>= MINIMUM_VERSION)
28
28
  end
29
29
 
30
30
  def self.auto_instrument?
@@ -9,7 +9,7 @@ module Datadog
9
9
  module_function
10
10
 
11
11
  def patched?
12
- Patcher.instance_variable_get(:@patched)
12
+ !!Patcher.instance_variable_get(:@patched)
13
13
  end
14
14
 
15
15
  def target_version