datadog 2.1.0 → 2.3.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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +101 -1
  3. data/ext/datadog_profiling_loader/extconf.rb +15 -15
  4. data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
  5. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
  6. data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
  7. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +132 -44
  8. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +49 -26
  9. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
  10. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
  11. data/ext/datadog_profiling_native_extension/collectors_stack.c +90 -37
  12. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  13. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +81 -19
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
  15. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +110 -0
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +57 -0
  17. data/ext/datadog_profiling_native_extension/extconf.rb +69 -62
  18. data/ext/datadog_profiling_native_extension/heap_recorder.c +34 -6
  19. data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
  20. data/ext/datadog_profiling_native_extension/helpers.h +6 -17
  21. data/ext/datadog_profiling_native_extension/http_transport.c +3 -3
  22. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
  23. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
  24. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -126
  25. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +64 -138
  26. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +17 -11
  27. data/ext/datadog_profiling_native_extension/profiling.c +0 -2
  28. data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
  29. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
  30. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +1 -1
  31. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
  32. data/ext/datadog_profiling_native_extension/stack_recorder.c +27 -8
  33. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
  34. data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
  35. data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
  36. data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +20 -7
  37. data/ext/libdatadog_api/datadog_ruby_common.c +110 -0
  38. data/ext/libdatadog_api/datadog_ruby_common.h +57 -0
  39. data/ext/libdatadog_api/extconf.rb +108 -0
  40. data/ext/libdatadog_api/macos_development.md +26 -0
  41. data/ext/libdatadog_extconf_helpers.rb +130 -0
  42. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +49 -0
  43. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +73 -0
  44. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +68 -0
  45. data/lib/datadog/appsec/contrib/graphql/integration.rb +41 -0
  46. data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
  47. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
  48. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  49. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +1 -1
  50. data/lib/datadog/appsec/extensions.rb +1 -0
  51. data/lib/datadog/appsec/processor/actions.rb +1 -1
  52. data/lib/datadog/appsec/response.rb +15 -1
  53. data/lib/datadog/appsec.rb +1 -0
  54. data/lib/datadog/core/configuration/components.rb +17 -12
  55. data/lib/datadog/core/configuration/settings.rb +93 -7
  56. data/lib/datadog/core/configuration.rb +3 -17
  57. data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
  58. data/lib/datadog/core/crashtracking/component.rb +111 -0
  59. data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
  60. data/lib/datadog/core/deprecations.rb +58 -0
  61. data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
  62. data/lib/datadog/core/environment/yjit.rb +5 -0
  63. data/lib/datadog/core/runtime/ext.rb +1 -0
  64. data/lib/datadog/core/runtime/metrics.rb +6 -0
  65. data/lib/datadog/core/telemetry/component.rb +154 -0
  66. data/lib/datadog/core/telemetry/emitter.rb +9 -11
  67. data/lib/datadog/core/telemetry/event.rb +132 -26
  68. data/lib/datadog/core/telemetry/ext.rb +3 -0
  69. data/lib/datadog/core/telemetry/http/adapters/net.rb +11 -13
  70. data/lib/datadog/core/telemetry/http/ext.rb +3 -0
  71. data/lib/datadog/core/telemetry/http/transport.rb +38 -9
  72. data/lib/datadog/core/telemetry/logging.rb +35 -0
  73. data/lib/datadog/core/telemetry/metric.rb +167 -0
  74. data/lib/datadog/core/telemetry/metrics_collection.rb +81 -0
  75. data/lib/datadog/core/telemetry/metrics_manager.rb +81 -0
  76. data/lib/datadog/core/telemetry/request.rb +1 -1
  77. data/lib/datadog/core/telemetry/worker.rb +173 -0
  78. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
  79. data/lib/datadog/core/utils/only_once_successful.rb +76 -0
  80. data/lib/datadog/core.rb +2 -19
  81. data/lib/datadog/kit/appsec/events.rb +2 -4
  82. data/lib/datadog/opentelemetry/sdk/propagator.rb +5 -10
  83. data/lib/datadog/opentelemetry/sdk/span_processor.rb +15 -2
  84. data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
  85. data/lib/datadog/profiling/collectors/code_provenance.rb +24 -11
  86. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +17 -17
  87. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
  88. data/lib/datadog/profiling/collectors/info.rb +3 -3
  89. data/lib/datadog/profiling/collectors/thread_context.rb +4 -2
  90. data/lib/datadog/profiling/component.rb +85 -90
  91. data/lib/datadog/profiling/exporter.rb +3 -3
  92. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +410 -0
  93. data/lib/datadog/profiling/ext.rb +21 -21
  94. data/lib/datadog/profiling/flush.rb +1 -1
  95. data/lib/datadog/profiling/http_transport.rb +8 -6
  96. data/lib/datadog/profiling/load_native_extension.rb +5 -5
  97. data/lib/datadog/profiling/preload.rb +1 -1
  98. data/lib/datadog/profiling/profiler.rb +5 -8
  99. data/lib/datadog/profiling/scheduler.rb +31 -25
  100. data/lib/datadog/profiling/tag_builder.rb +2 -2
  101. data/lib/datadog/profiling/tasks/exec.rb +5 -5
  102. data/lib/datadog/profiling/tasks/setup.rb +16 -35
  103. data/lib/datadog/profiling.rb +5 -5
  104. data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
  105. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +1 -1
  106. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +1 -1
  107. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +1 -1
  108. data/lib/datadog/tracing/contrib/action_mailer/event.rb +4 -6
  109. data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +9 -4
  110. data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +3 -2
  111. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +1 -5
  112. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  113. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  114. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  115. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  116. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  117. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  118. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  119. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  120. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  121. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  122. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +2 -1
  123. data/lib/datadog/tracing/contrib/active_support/cache/event.rb +32 -0
  124. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +156 -0
  125. data/lib/datadog/tracing/contrib/active_support/cache/events.rb +34 -0
  126. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +45 -41
  127. data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +17 -40
  128. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +4 -1
  129. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +29 -6
  130. data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +16 -4
  131. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +33 -29
  132. data/lib/datadog/tracing/contrib/analytics.rb +5 -0
  133. data/lib/datadog/tracing/contrib/ext.rb +14 -0
  134. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +5 -0
  135. data/lib/datadog/tracing/contrib/graphql/patcher.rb +8 -2
  136. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +166 -0
  137. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +28 -0
  138. data/lib/datadog/tracing/contrib/kafka/consumer_event.rb +1 -1
  139. data/lib/datadog/tracing/contrib/kafka/consumer_group_event.rb +1 -1
  140. data/lib/datadog/tracing/contrib/kafka/event.rb +1 -1
  141. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +3 -3
  142. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +3 -3
  143. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +3 -3
  144. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +3 -3
  145. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +3 -3
  146. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +3 -3
  147. data/lib/datadog/tracing/contrib/lograge/patcher.rb +16 -0
  148. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
  149. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
  150. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
  151. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
  152. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
  153. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  154. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
  155. data/lib/datadog/tracing/contrib/racecar/event.rb +2 -2
  156. data/lib/datadog/tracing/contrib/rails/ext.rb +9 -0
  157. data/lib/datadog/tracing/contrib/rails/patcher.rb +7 -0
  158. data/lib/datadog/tracing/contrib/rails/runner.rb +95 -0
  159. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
  160. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
  161. data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
  162. data/lib/datadog/tracing/distributed/b3_multi.rb +1 -1
  163. data/lib/datadog/tracing/distributed/b3_single.rb +3 -1
  164. data/lib/datadog/tracing/distributed/datadog.rb +2 -2
  165. data/lib/datadog/tracing/distributed/propagation.rb +9 -2
  166. data/lib/datadog/tracing/distributed/trace_context.rb +3 -2
  167. data/lib/datadog/tracing/metadata/errors.rb +9 -1
  168. data/lib/datadog/tracing/metadata/ext.rb +4 -0
  169. data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
  170. data/lib/datadog/tracing/span.rb +9 -2
  171. data/lib/datadog/tracing/span_event.rb +41 -0
  172. data/lib/datadog/tracing/span_operation.rb +9 -4
  173. data/lib/datadog/tracing/trace_operation.rb +7 -3
  174. data/lib/datadog/tracing/trace_segment.rb +4 -1
  175. data/lib/datadog/tracing/tracer.rb +9 -2
  176. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
  177. data/lib/datadog/tracing.rb +5 -1
  178. data/lib/datadog/version.rb +2 -2
  179. metadata +43 -12
  180. data/lib/datadog/core/telemetry/client.rb +0 -95
  181. data/lib/datadog/core/telemetry/heartbeat.rb +0 -33
  182. data/lib/datadog/profiling/crashtracker.rb +0 -91
  183. data/lib/datadog/profiling/ext/forking.rb +0 -98
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'pathname'
5
+
6
+ module Datadog
7
+ # Contains a bunch of shared helpers that get used during building of extensions that link to libdatadog
8
+ module LibdatadogExtconfHelpers
9
+ # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
10
+ # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
11
+ LIBDATADOG_VERSION = '~> 11.0.0.1.0'
12
+
13
+ # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
14
+ # libdatadog are moved after the extension gets compiled.
15
+ #
16
+ # Because the libdatadog native library is installed on a non-standard system path, in order for it to be
17
+ # found by the system dynamic linker (e.g. what takes care of dlopen(), which is used to load
18
+ # native extensions), we need to add a "runpath" -- a list of folders to search for libdatadog.
19
+ #
20
+ # This runpath gets hardcoded at native library linking time. You can look at it using the `readelf` tool in
21
+ # Linux: e.g. `readelf -d datadog_profiling_native_extension.2.7.3_x86_64-linux.so`.
22
+ #
23
+ # In older versions of the datadog gem, we only set as runpath an absolute path to libdatadog.
24
+ # (This gets set automatically by the call
25
+ # to `pkg_config('datadog_profiling_with_rpath')` in `extconf.rb`). This worked fine as long as libdatadog was **NOT**
26
+ # moved from the folder it was present at datadog gem installation/linking time.
27
+ #
28
+ # Unfortunately, environments such as Heroku and AWS Elastic Beanstalk move gems around in the filesystem after
29
+ # installation. Thus, the profiling native extension could not be loaded in these environments
30
+ # (see https://github.com/DataDog/dd-trace-rb/issues/2067) because libdatadog could not be found.
31
+ #
32
+ # To workaround this issue, this method computes the **relative** path between the folder where
33
+ # native extensions are going to be installed and the folder where libdatadog is installed, and returns it
34
+ # to be set as an additional runpath. (Yes, you can set multiple runpath folders to be searched).
35
+ #
36
+ # This way, if both gems are moved together (and it turns out that they are in these environments),
37
+ # the relative path can still be traversed to find libdatadog.
38
+ #
39
+ # This is incredibly awful, and it's kinda bizarre how it's not possible to just find these paths at runtime
40
+ # and set them correctly; rather than needing to set stuff at linking-time and then praying to $deity that
41
+ # weird moves don't happen.
42
+ #
43
+ # As a curiosity, `LD_LIBRARY_PATH` can be used to influence the folders that get searched but **CANNOT BE
44
+ # SET DYNAMICALLY**, e.g. it needs to be set at the start of the process (Ruby VM) and thus it's not something
45
+ # we could setup when doing a `require`.
46
+ #
47
+ def self.libdatadog_folder_relative_to_native_lib_folder(
48
+ current_folder:,
49
+ libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
50
+ )
51
+ return unless libdatadog_pkgconfig_folder
52
+
53
+ native_lib_folder = "#{current_folder}/../../lib/"
54
+ libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
55
+
56
+ Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(native_lib_folder)).to_s
57
+ end
58
+
59
+ # In https://github.com/DataDog/dd-trace-rb/pull/3582 we got a report of a customer for which the native extension
60
+ # only got installed into the extensions folder.
61
+ #
62
+ # But then this fix was not enough to fully get them moving because then they started to see the issue from
63
+ # https://github.com/DataDog/dd-trace-rb/issues/2067 / https://github.com/DataDog/dd-trace-rb/pull/2125 :
64
+ #
65
+ # > Profiling was requested but is not supported, profiling disabled: There was an error loading the profiling
66
+ # > native extension due to 'RuntimeError Failure to load datadog_profiling_native_extension.3.2.2_x86_64-linux
67
+ # > due to libdatadog_profiling.so: cannot open shared object file: No such file or directory
68
+ #
69
+ # The problem is that when loading the native extension from the extensions directory, the relative rpath we add
70
+ # with the #libdatadog_folder_relative_to_native_lib_folder helper above is not correct, we need to add a relative
71
+ # rpath to the extensions directory.
72
+ #
73
+ # So how do we find the full path where the native extension is placed?
74
+ # * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/bundler/runtime.rb#L166
75
+ # `extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"]`
76
+ # we get that's in one of two fixed subdirectories of `Gem.dir`
77
+ # * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/rubygems/basic_specification.rb#L111-L115
78
+ # we get the structure of the subdirectory (platform/extension_api_version/gem_and_version)
79
+ #
80
+ # Thus, `Gem.dir` of `/var/app/current/vendor/bundle/ruby/3.2.0` becomes (for instance)
81
+ # `/var/app/current/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/datadog-2.0.0/` or
82
+ # `/var/app/current/vendor/bundle/ruby/3.2.0/bundler/gems/extensions/x86_64-linux/3.2.0/datadog-2.0.0/`
83
+ #
84
+ # We then compute the relative path between these folders and the libdatadog folder, and use that as a relative path.
85
+ def self.libdatadog_folder_relative_to_ruby_extensions_folders(
86
+ gem_dir: Gem.dir,
87
+ libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
88
+ )
89
+ return unless libdatadog_pkgconfig_folder
90
+
91
+ # For the purposes of calculating a folder relative to the other, we don't actually NEED to fill in the
92
+ # platform, extension_api_version and gem version. We're basically just after how many folders it is deep from
93
+ # the Gem.dir.
94
+ expected_ruby_extensions_folders = [
95
+ "#{gem_dir}/extensions/platform/extension_api_version/datadog_version/",
96
+ "#{gem_dir}/bundler/gems/extensions/platform/extension_api_version/datadog_version/",
97
+ ]
98
+ libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
99
+
100
+ expected_ruby_extensions_folders.map do |folder|
101
+ Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(folder)).to_s
102
+ end
103
+ end
104
+
105
+ # mkmf sets $PKGCONFIG after the `pkg_config` gets used in extconf.rb. When `pkg_config` is unsuccessful, we use
106
+ # this helper to decide if we can show more specific error message vs a generic "something went wrong".
107
+ def self.pkg_config_missing?(command: $PKGCONFIG) # rubocop:disable Style/GlobalVars
108
+ pkg_config_available = command && xsystem("#{command} --version")
109
+
110
+ pkg_config_available != true
111
+ end
112
+
113
+ def self.try_loading_libdatadog
114
+ gem 'libdatadog', LIBDATADOG_VERSION
115
+ require 'libdatadog'
116
+ nil
117
+ rescue Exception => e # rubocop:disable Lint/RescueException
118
+ if block_given?
119
+ yield e
120
+ else
121
+ e
122
+ end
123
+ end
124
+
125
+ def self.libdatadog_issue?
126
+ try_loading_libdatadog { |_exception| return true }
127
+ Libdatadog.pkgconfig_folder.nil?
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'gateway/multiplex'
5
+ require_relative '../../instrumentation/gateway'
6
+
7
+ module Datadog
8
+ module AppSec
9
+ module Contrib
10
+ module GraphQL
11
+ # These methods will be called by the GraphQL runtime to send the variables to the WAF.
12
+ # We actually don't need to create any span/trace.
13
+ module AppSecTrace
14
+ def execute_multiplex(multiplex:)
15
+ return super unless Datadog::AppSec.enabled?
16
+
17
+ gateway_multiplex = Gateway::Multiplex.new(multiplex)
18
+
19
+ multiplex_return, multiplex_response = Instrumentation.gateway.push('graphql.multiplex', gateway_multiplex) do
20
+ super
21
+ end
22
+
23
+ # Returns an error * the number of queries so that the entire multiplex is blocked
24
+ if multiplex_response
25
+ blocked_event = multiplex_response.find { |action, _options| action == :block }
26
+ multiplex_return = AppSec::Response.graphql_response(gateway_multiplex) if blocked_event
27
+ end
28
+
29
+ multiplex_return
30
+ end
31
+
32
+ private
33
+
34
+ def active_trace
35
+ return unless defined?(Datadog::Tracing)
36
+
37
+ Datadog::Tracing.active_trace
38
+ end
39
+
40
+ def active_span
41
+ return unless defined?(Datadog::Tracing)
42
+
43
+ Datadog::Tracing.active_span
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ require_relative '../../../instrumentation/gateway/argument'
6
+
7
+ module Datadog
8
+ module AppSec
9
+ module Contrib
10
+ module GraphQL
11
+ module Gateway
12
+ # Gateway Request argument. Normalized extration of data from Rack::Request
13
+ class Multiplex < Instrumentation::Gateway::Argument
14
+ attr_reader :multiplex
15
+
16
+ def initialize(multiplex)
17
+ super()
18
+ @multiplex = multiplex
19
+ end
20
+
21
+ def arguments
22
+ @arguments ||= create_arguments_hash
23
+ end
24
+
25
+ def queries
26
+ @multiplex.queries
27
+ end
28
+
29
+ private
30
+
31
+ def create_arguments_hash
32
+ args = {}
33
+ @multiplex.queries.each_with_index do |query, index|
34
+ resolver_args = {}
35
+ resolver_dirs = {}
36
+ selections = (query.selected_operation.selections.dup if query.selected_operation) || []
37
+ # Iterative tree traversal
38
+ while selections.any?
39
+ selection = selections.shift
40
+ set_hash_with_variables(resolver_args, selection.arguments, query.provided_variables)
41
+ selection.directives.each do |dir|
42
+ resolver_dirs[dir.name] ||= {}
43
+ set_hash_with_variables(resolver_dirs[dir.name], dir.arguments, query.provided_variables)
44
+ end
45
+ selections.concat(selection.selections)
46
+ end
47
+ next if resolver_args.empty? && resolver_dirs.empty?
48
+
49
+ args_resolver = (args[query.operation_name || "query#{index + 1}"] ||= [])
50
+ # We don't want to add empty hashes so we check again if the arguments and directives are empty
51
+ args_resolver << resolver_args unless resolver_args.empty?
52
+ args_resolver << resolver_dirs unless resolver_dirs.empty?
53
+ end
54
+ args
55
+ end
56
+
57
+ # Set the resolver hash (resolver_args and resolver_dirs) with the arguments and provided variables
58
+ def set_hash_with_variables(resolver_hash, arguments, provided_variables)
59
+ arguments.each do |arg|
60
+ resolver_hash[arg.name] =
61
+ if arg.value.is_a?(::GraphQL::Language::Nodes::VariableIdentifier)
62
+ provided_variables[arg.value.name]
63
+ else
64
+ arg.value
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative '../../../instrumentation/gateway'
5
+ require_relative '../reactive/multiplex'
6
+ require_relative '../../../reactive/operation'
7
+
8
+ module Datadog
9
+ module AppSec
10
+ module Contrib
11
+ module GraphQL
12
+ module Gateway
13
+ # Watcher for Rack gateway events
14
+ module Watcher
15
+ class << self
16
+ def watch
17
+ gateway = Instrumentation.gateway
18
+
19
+ watch_multiplex(gateway)
20
+ end
21
+
22
+ # This time we don't throw but use next
23
+ def watch_multiplex(gateway = Instrumentation.gateway)
24
+ gateway.watch('graphql.multiplex', :appsec) do |stack, gateway_multiplex|
25
+ block = false
26
+ event = nil
27
+ scope = AppSec::Scope.active_scope
28
+
29
+ AppSec::Reactive::Operation.new('graphql.multiplex') do |op|
30
+ GraphQL::Reactive::Multiplex.subscribe(op, scope.processor_context) do |result|
31
+ event = {
32
+ waf_result: result,
33
+ trace: scope.trace,
34
+ span: scope.service_entry_span,
35
+ multiplex: gateway_multiplex,
36
+ actions: result.actions
37
+ }
38
+
39
+ if scope.service_entry_span
40
+ scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
41
+ scope.service_entry_span.set_tag('appsec.event', 'true')
42
+ end
43
+
44
+ scope.processor_context.events << event
45
+ end
46
+
47
+ block = GraphQL::Reactive::Multiplex.publish(op, gateway_multiplex)
48
+ end
49
+
50
+ next [nil, [[:block, event]]] if block
51
+
52
+ ret, res = stack.call(gateway_multiplex.arguments)
53
+
54
+ if event
55
+ res ||= []
56
+ res << [:monitor, event]
57
+ end
58
+
59
+ [ret, res]
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../integration'
4
+ require_relative 'patcher'
5
+
6
+ module Datadog
7
+ module AppSec
8
+ module Contrib
9
+ module GraphQL
10
+ # Description of GraphQL integration
11
+ class Integration
12
+ include Datadog::AppSec::Contrib::Integration
13
+
14
+ MINIMUM_VERSION = Gem::Version.new('2.0.19')
15
+
16
+ register_as :graphql, auto_patch: false
17
+
18
+ def self.version
19
+ Gem.loaded_specs['graphql'] && Gem.loaded_specs['graphql'].version
20
+ end
21
+
22
+ def self.loaded?
23
+ !defined?(::GraphQL).nil?
24
+ end
25
+
26
+ def self.compatible?
27
+ super && version >= MINIMUM_VERSION
28
+ end
29
+
30
+ def self.auto_instrument?
31
+ true
32
+ end
33
+
34
+ def patcher
35
+ Patcher
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../patcher'
4
+ require_relative 'gateway/watcher'
5
+
6
+ if Gem.loaded_specs['graphql'] && Gem.loaded_specs['graphql'].version >= Gem::Version.new('2.0.19')
7
+ require_relative 'appsec_trace'
8
+ end
9
+
10
+ module Datadog
11
+ module AppSec
12
+ module Contrib
13
+ module GraphQL
14
+ # Patcher for AppSec on GraphQL
15
+ module Patcher
16
+ include Datadog::AppSec::Contrib::Patcher
17
+
18
+ module_function
19
+
20
+ def patched?
21
+ Patcher.instance_variable_get(:@patched)
22
+ end
23
+
24
+ def target_version
25
+ Integration.version
26
+ end
27
+
28
+ def patch
29
+ Gateway::Watcher.watch
30
+ ::GraphQL::Schema.trace_with(AppSecTrace)
31
+ Patcher.instance_variable_set(:@patched, true)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module Contrib
6
+ module GraphQL
7
+ module Reactive
8
+ # Dispatch data from a GraphQL resolve query to the WAF context
9
+ module Multiplex
10
+ ADDRESSES = [
11
+ 'graphql.server.all_resolvers'
12
+ ].freeze
13
+ private_constant :ADDRESSES
14
+
15
+ def self.publish(op, gateway_multiplex)
16
+ catch(:block) do
17
+ op.publish('graphql.server.all_resolvers', gateway_multiplex.arguments)
18
+
19
+ nil
20
+ end
21
+ end
22
+
23
+ def self.subscribe(op, waf_context)
24
+ op.subscribe(*ADDRESSES) do |*values|
25
+ Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
26
+ arguments = values[0]
27
+
28
+ waf_args = {
29
+ 'graphql.server.all_resolvers' => arguments
30
+ }
31
+
32
+ waf_timeout = Datadog.configuration.appsec.waf_timeout
33
+ result = waf_context.run(waf_args, waf_timeout)
34
+
35
+ Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
36
+
37
+ case result.status
38
+ when :match
39
+ Datadog.logger.debug { "WAF: #{result.inspect}" }
40
+
41
+ yield result
42
+ throw(:block, true) unless result.actions.empty?
43
+ when :ok
44
+ Datadog.logger.debug { "WAF OK: #{result.inspect}" }
45
+ when :invalid_call
46
+ Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
47
+ when :invalid_rule, :invalid_flow, :no_rule
48
+ Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
49
+ else
50
+ Datadog.logger.debug { "WAF UNKNOWN: #{result.status.inspect} #{result.inspect}" }
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -41,7 +41,7 @@ module Datadog
41
41
 
42
42
  def headers
43
43
  result = request.env.each_with_object({}) do |(k, v), h|
44
- h[k.gsub(/^HTTP_/, '').downcase!.tr('_', '-')] = v if k =~ /^HTTP_/
44
+ h[k.delete_prefix('HTTP_').tap(&:downcase!).tap { |s| s.tr!('_', '-') }] = v if k.start_with?('HTTP_')
45
45
  end
46
46
 
47
47
  result['content-type'] = request.content_type if request.content_type
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../../tracing/contrib/rack/middlewares'
3
+ require_relative '../../../tracing/contrib'
4
4
 
5
5
  require_relative '../patcher'
6
6
  require_relative '../../response'
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'configuration'
4
+ require_relative '../core/configuration'
4
5
 
5
6
  module Datadog
6
7
  module AppSec
@@ -11,7 +11,7 @@ module Datadog
11
11
  @actions ||= []
12
12
  end
13
13
 
14
- def fecth_configuration(action)
14
+ def fetch_configuration(action)
15
15
  actions.find { |action_configuration| action_configuration['id'] == action }
16
16
  end
17
17
 
@@ -36,7 +36,7 @@ module Datadog
36
36
  # I rather use break to stop the execution
37
37
  next if configured_response
38
38
 
39
- action_configuration = AppSec::Processor::Actions.fecth_configuration(action)
39
+ action_configuration = AppSec::Processor::Actions.fetch_configuration(action)
40
40
  next unless action_configuration
41
41
 
42
42
  configured_response = case action_configuration['type']
@@ -50,6 +50,20 @@ module Datadog
50
50
  configured_response || default_response(env)
51
51
  end
52
52
 
53
+ def graphql_response(gateway_multiplex)
54
+ multiplex_return = []
55
+ gateway_multiplex.queries.each do |query|
56
+ # This method is only called in places where GraphQL-Ruby is already required
57
+ query_result = ::GraphQL::Query::Result.new(
58
+ query: query,
59
+ values: JSON.parse(content('application/json'))
60
+ )
61
+ multiplex_return << query_result
62
+ end
63
+
64
+ multiplex_return
65
+ end
66
+
53
67
  private
54
68
 
55
69
  def default_response(env)
@@ -56,5 +56,6 @@ require_relative 'appsec/contrib/rack/integration'
56
56
  require_relative 'appsec/contrib/sinatra/integration'
57
57
  require_relative 'appsec/contrib/rails/integration'
58
58
  require_relative 'appsec/contrib/devise/integration'
59
+ require_relative 'appsec/contrib/graphql/integration'
59
60
 
60
61
  require_relative 'appsec/autoload'
@@ -6,13 +6,14 @@ require_relative '../diagnostics/environment_logger'
6
6
  require_relative '../diagnostics/health'
7
7
  require_relative '../logger'
8
8
  require_relative '../runtime/metrics'
9
- require_relative '../telemetry/client'
9
+ require_relative '../telemetry/component'
10
10
  require_relative '../workers/runtime_metrics'
11
11
 
12
12
  require_relative '../remote/component'
13
13
  require_relative '../../tracing/component'
14
14
  require_relative '../../profiling/component'
15
15
  require_relative '../../appsec/component'
16
+ require_relative '../crashtracking/component'
16
17
 
17
18
  module Datadog
18
19
  module Core
@@ -56,17 +57,18 @@ module Datadog
56
57
  end
57
58
 
58
59
  def build_telemetry(settings, agent_settings, logger)
59
- enabled = settings.telemetry.enabled
60
- if agent_settings.adapter != Datadog::Core::Configuration::Ext::Agent::HTTP::ADAPTER
61
- enabled = false
62
- logger.debug { "Telemetry disabled. Agent network adapter not supported: #{agent_settings.adapter}" }
60
+ Telemetry::Component.build(settings, agent_settings, logger)
61
+ end
62
+
63
+ def build_crashtracker(settings, agent_settings, logger:)
64
+ return unless settings.crashtracking.enabled
65
+
66
+ if (libdatadog_api_failure = Datadog::Core::Crashtracking::Component::LIBDATADOG_API_FAILURE)
67
+ logger.debug("Cannot enable crashtracking: #{libdatadog_api_failure}")
68
+ return
63
69
  end
64
70
 
65
- Telemetry::Client.new(
66
- enabled: enabled,
67
- heartbeat_interval_seconds: settings.telemetry.heartbeat_interval_seconds,
68
- dependency_collection: settings.telemetry.dependency_collection
69
- )
71
+ Datadog::Core::Crashtracking::Component.build(settings, agent_settings, logger: logger)
70
72
  end
71
73
  end
72
74
 
@@ -80,6 +82,7 @@ module Datadog
80
82
  :runtime_metrics,
81
83
  :telemetry,
82
84
  :tracer,
85
+ :crashtracker,
83
86
  :appsec
84
87
 
85
88
  def initialize(settings)
@@ -93,11 +96,12 @@ module Datadog
93
96
 
94
97
  @remote = Remote::Component.build(settings, agent_settings)
95
98
  @tracer = self.class.build_tracer(settings, agent_settings, logger: @logger)
99
+ @crashtracker = self.class.build_crashtracker(settings, agent_settings, logger: @logger)
96
100
 
97
101
  @profiler, profiler_logger_extra = Datadog::Profiling::Component.build_profiler_component(
98
102
  settings: settings,
99
103
  agent_settings: agent_settings,
100
- optional_tracer: @tracer,
104
+ optional_tracer: @tracer
101
105
  )
102
106
  @environment_logger_extra.merge!(profiler_logger_extra) if profiler_logger_extra
103
107
 
@@ -169,8 +173,9 @@ module Datadog
169
173
  unused_statsd = (old_statsd - (old_statsd & new_statsd))
170
174
  unused_statsd.each(&:close)
171
175
 
172
- telemetry.stop!
176
+ # enqueue closing event before stopping telemetry so it will be send out on shutdown
173
177
  telemetry.emit_closing! unless replacement
178
+ telemetry.stop!
174
179
  end
175
180
  end
176
181
  end