datadog 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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