datadog 2.10.0 → 2.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +3 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +44 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +4 -0
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +2 -0
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +0 -8
- data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +56 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +7 -0
- data/ext/datadog_profiling_native_extension/profiling.c +7 -0
- data/ext/libdatadog_api/crashtracker.c +4 -4
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/configuration/settings.rb +64 -11
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +0 -3
- data/lib/datadog/appsec/contrib/devise/configuration.rb +76 -0
- data/lib/datadog/appsec/contrib/devise/event.rb +4 -7
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +16 -21
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +8 -15
- data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +1 -1
- data/lib/datadog/appsec/contrib/devise/patcher.rb +0 -3
- data/lib/datadog/appsec/contrib/devise/tracking.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/excon/patcher.rb +28 -0
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +43 -0
- data/lib/datadog/appsec/contrib/faraday/connection_patch.rb +22 -0
- data/lib/datadog/appsec/contrib/faraday/integration.rb +42 -0
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +53 -0
- data/lib/datadog/appsec/contrib/faraday/rack_builder_patch.rb +22 -0
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +42 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +10 -12
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +0 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +65 -73
- data/lib/datadog/appsec/contrib/rack/patcher.rb +0 -3
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +20 -25
- data/lib/datadog/appsec/contrib/rails/patcher.rb +0 -3
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +38 -49
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +0 -3
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +19 -25
- data/lib/datadog/appsec/remote.rb +4 -0
- data/lib/datadog/appsec.rb +2 -0
- data/lib/datadog/core/configuration/components.rb +7 -1
- data/lib/datadog/core/configuration/ext.rb +1 -1
- data/lib/datadog/core/configuration/option_definition.rb +2 -0
- data/lib/datadog/core/configuration/settings.rb +22 -6
- data/lib/datadog/core/encoding.rb +16 -0
- data/lib/datadog/core/environment/agent_info.rb +77 -0
- data/lib/datadog/core/remote/transport/http/api.rb +13 -18
- data/lib/datadog/core/remote/transport/http/config.rb +0 -18
- data/lib/datadog/core/remote/transport/http/negotiation.rb +1 -18
- data/lib/datadog/core/remote/transport/http.rb +7 -12
- data/lib/datadog/core/remote/transport/negotiation.rb +13 -1
- data/lib/datadog/core/telemetry/event.rb +5 -0
- data/lib/datadog/core/transport/http/adapters/unix_socket.rb +1 -1
- data/lib/datadog/{tracing → core}/transport/http/api/instance.rb +1 -1
- data/lib/datadog/{tracing → core}/transport/http/api/spec.rb +1 -1
- data/lib/datadog/{tracing → core}/transport/http/builder.rb +37 -17
- data/lib/datadog/core/transport/response.rb +4 -0
- data/lib/datadog/di/code_tracker.rb +15 -8
- data/lib/datadog/di/component.rb +1 -0
- data/lib/datadog/di/configuration/settings.rb +14 -0
- data/lib/datadog/di/contrib.rb +2 -0
- data/lib/datadog/di/logger.rb +30 -0
- data/lib/datadog/di/probe.rb +3 -6
- data/lib/datadog/di/probe_manager.rb +5 -2
- data/lib/datadog/di/probe_notifier_worker.rb +15 -4
- data/lib/datadog/di/remote.rb +3 -3
- data/lib/datadog/di/utils.rb +91 -0
- data/lib/datadog/di.rb +3 -0
- data/lib/datadog/profiling/component.rb +2 -8
- data/lib/datadog/profiling/load_native_extension.rb +1 -33
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/extensions.rb +14 -0
- data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +21 -0
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +11 -0
- data/lib/datadog/tracing/contrib/graphql/ext.rb +5 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +102 -11
- data/lib/datadog/tracing/contrib/rack/header_collection.rb +11 -1
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +1 -1
- data/lib/datadog/tracing/contrib/span_attribute_schema.rb +6 -1
- data/lib/datadog/tracing/transport/http/api.rb +11 -2
- data/lib/datadog/tracing/transport/http/traces.rb +0 -3
- data/lib/datadog/tracing/transport/http.rb +12 -7
- data/lib/datadog/tracing/transport/serializable_trace.rb +8 -4
- data/lib/datadog/tracing/transport/traces.rb +25 -8
- data/lib/datadog/version.rb +1 -1
- metadata +23 -28
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +0 -142
- data/ext/datadog_profiling_loader/extconf.rb +0 -60
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +0 -46
- data/lib/datadog/appsec/contrib/patcher.rb +0 -12
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +0 -69
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +0 -47
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +0 -53
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +0 -53
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +0 -48
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +0 -45
- data/lib/datadog/appsec/reactive/address_hash.rb +0 -22
- data/lib/datadog/appsec/reactive/engine.rb +0 -47
- data/lib/datadog/appsec/reactive/subscriber.rb +0 -19
- data/lib/datadog/core/remote/transport/http/api/instance.rb +0 -39
- data/lib/datadog/core/remote/transport/http/api/spec.rb +0 -21
- data/lib/datadog/core/remote/transport/http/builder.rb +0 -219
@@ -10,7 +10,7 @@ module Datadog
|
|
10
10
|
# The loop inside the worker rescues all exceptions to prevent termination
|
11
11
|
# due to unhandled exceptions raised by any downstream code.
|
12
12
|
# This includes communication and protocol errors when sending the
|
13
|
-
#
|
13
|
+
# events to the agent.
|
14
14
|
#
|
15
15
|
# The worker groups the data to send into batches. The goal is to perform
|
16
16
|
# no more than one network operation per event type per second.
|
@@ -36,6 +36,7 @@ module Datadog
|
|
36
36
|
@sleep_remaining = nil
|
37
37
|
@wake_scheduled = false
|
38
38
|
@thread = nil
|
39
|
+
@pid = nil
|
39
40
|
@flush = 0
|
40
41
|
end
|
41
42
|
|
@@ -44,7 +45,8 @@ module Datadog
|
|
44
45
|
attr_reader :telemetry
|
45
46
|
|
46
47
|
def start
|
47
|
-
return if @thread
|
48
|
+
return if @thread && @pid == Process.pid
|
49
|
+
logger.trace { "di: starting probe notifier: pid #{$$}" }
|
48
50
|
@thread = Thread.new do
|
49
51
|
loop do
|
50
52
|
# TODO If stop is requested, we stop immediately without
|
@@ -86,6 +88,7 @@ module Datadog
|
|
86
88
|
wake.wait(more ? min_send_interval : nil)
|
87
89
|
end
|
88
90
|
end
|
91
|
+
@pid = Process.pid
|
89
92
|
end
|
90
93
|
|
91
94
|
# Stops the background thread.
|
@@ -94,6 +97,7 @@ module Datadog
|
|
94
97
|
# to killing the thread using Thread#kill.
|
95
98
|
def stop(timeout = 1)
|
96
99
|
@stop_requested = true
|
100
|
+
logger.trace { "di: stopping probe notifier: pid #{$$}" }
|
97
101
|
wake.signal
|
98
102
|
if thread
|
99
103
|
unless thread.join(timeout)
|
@@ -184,8 +188,9 @@ module Datadog
|
|
184
188
|
@lock.synchronize do
|
185
189
|
queue = send("#{event_type}_queue")
|
186
190
|
if queue.length > settings.dynamic_instrumentation.internal.snapshot_queue_capacity
|
187
|
-
logger.debug { "di: #{self.class.name}: dropping #{event_type} because queue is full" }
|
191
|
+
logger.debug { "di: #{self.class.name}: dropping #{event_type} event because queue is full" }
|
188
192
|
else
|
193
|
+
logger.trace { "di: #{self.class.name}: queueing #{event_type} event" }
|
189
194
|
queue << event
|
190
195
|
end
|
191
196
|
end
|
@@ -200,6 +205,10 @@ module Datadog
|
|
200
205
|
wake.signal
|
201
206
|
end
|
202
207
|
end
|
208
|
+
|
209
|
+
# Worker could be not running if the process forked - check and
|
210
|
+
# start it again in this case.
|
211
|
+
start
|
203
212
|
end
|
204
213
|
|
205
214
|
# Determine how much longer the worker thread should sleep
|
@@ -232,8 +241,10 @@ module Datadog
|
|
232
241
|
instance_variable_set("@#{event_type}_queue", [])
|
233
242
|
@io_in_progress = batch.any? # steep:ignore
|
234
243
|
end
|
244
|
+
logger.trace { "di: #{self.class.name}: checking #{event_type} queue - #{batch.length} entries" } # steep:ignore
|
235
245
|
if batch.any? # steep:ignore
|
236
246
|
begin
|
247
|
+
logger.trace { "di: sending #{batch.length} #{event_type} event(s) to agent" } # steep:ignore
|
237
248
|
transport.public_send("send_#{event_type}", batch)
|
238
249
|
time = Core::Utils::Time.get_time
|
239
250
|
@lock.synchronize do
|
@@ -263,7 +274,7 @@ module Datadog
|
|
263
274
|
|
264
275
|
def maybe_send
|
265
276
|
rv = maybe_send_status
|
266
|
-
|
277
|
+
maybe_send_snapshot || rv
|
267
278
|
end
|
268
279
|
end
|
269
280
|
end
|
data/lib/datadog/di/remote.rb
CHANGED
@@ -53,7 +53,7 @@ module Datadog
|
|
53
53
|
payload = probe_notification_builder.build_received(probe)
|
54
54
|
probe_notifier_worker = component.probe_notifier_worker
|
55
55
|
probe_notifier_worker.add_status(payload)
|
56
|
-
component.logger.debug { "di: received probe
|
56
|
+
component.logger.debug { "di: received #{probe.type} probe at #{probe.location} (#{probe.id}) via RC" }
|
57
57
|
|
58
58
|
begin
|
59
59
|
# TODO test exception capture
|
@@ -76,7 +76,7 @@ module Datadog
|
|
76
76
|
rescue => exc
|
77
77
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
78
78
|
|
79
|
-
component.logger.debug { "di: unhandled exception adding probe in DI remote receiver: #{exc.class}: #{exc}" }
|
79
|
+
component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI remote receiver: #{exc.class}: #{exc}" }
|
80
80
|
component.telemetry&.report(exc, description: "Unhandled exception adding probe in DI remote receiver")
|
81
81
|
|
82
82
|
# TODO test this path
|
@@ -101,7 +101,7 @@ module Datadog
|
|
101
101
|
rescue => exc
|
102
102
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
103
103
|
|
104
|
-
component.logger.debug { "di: unhandled exception handling probe in DI remote receiver: #{exc.class}: #{exc}" }
|
104
|
+
component.logger.debug { "di: unhandled exception handling a probe in DI remote receiver: #{exc.class}: #{exc}" }
|
105
105
|
component.telemetry&.report(exc, description: "Unhandled exception handling probe in DI remote receiver")
|
106
106
|
|
107
107
|
# TODO assert content state (errored for this example)
|
data/lib/datadog/di/utils.rb
CHANGED
@@ -3,6 +3,82 @@
|
|
3
3
|
module Datadog
|
4
4
|
module DI
|
5
5
|
module Utils
|
6
|
+
# General path matching considerations
|
7
|
+
# ------------------------------------
|
8
|
+
#
|
9
|
+
# The following use cases must be supported:
|
10
|
+
# 1. The "probe path" is relative path to the file from source code
|
11
|
+
# repository root. The project is deployed from the repository root,
|
12
|
+
# such that that same relative path exists at runtime from the
|
13
|
+
# root of the application.
|
14
|
+
# 2. The "probe path" is a relative path to the file in a monorepo
|
15
|
+
# where the project being deployed is in a subdirectory.
|
16
|
+
# This the "probe path" contains additional directory components
|
17
|
+
# in the beginning that do not exist in the runtime environment.
|
18
|
+
# 3. The "probe path" is an absolute path to the file on the customer's
|
19
|
+
# development system. As specified this path definitely does not
|
20
|
+
# exist at runtime, and can start with a prefix that is unknown
|
21
|
+
# to any both UI and tracer code.
|
22
|
+
# 4. Same as (3), but the customer is using a Windows computer for
|
23
|
+
# development and has the path specified in the wrong case
|
24
|
+
# (which works fine on their development machine).
|
25
|
+
# 5. The "probe path" is the basename or any suffix of the path to
|
26
|
+
# the desired file, typed manually by the customer into the UI.
|
27
|
+
#
|
28
|
+
# A related concern is that if multiple runtime paths match the path
|
29
|
+
# specification in the probe, the tracer must return an error to the
|
30
|
+
# backend/UI rather than instrumenting any of the matching paths.
|
31
|
+
#
|
32
|
+
# The logic for path matching should therefore, generally, be as follows:
|
33
|
+
# 1. If the "probe path" is absolute, see if it exists at runtime.
|
34
|
+
# If so, take it as the desired path and finish.
|
35
|
+
# 2. Attempt to identify the application root, by checking if the current
|
36
|
+
# working directory contains a file called Gemfile. If yes, assume
|
37
|
+
# the current working directory is the application root, otherwise
|
38
|
+
# consider the application root to be unknown.
|
39
|
+
# 3. If the application root is known and the "probe path" is relative,
|
40
|
+
# concatenate the "probe path" to the application root and check
|
41
|
+
# if the resulting path exists at runtime. If so, take it as the
|
42
|
+
# desired path and finish.
|
43
|
+
# 4. If the "probe path" is relative, go through the known file paths,
|
44
|
+
# filter these paths down to those whose suffix is the "probe path",
|
45
|
+
# and check how many we are left with. If exactly one, assume this
|
46
|
+
# is the desired path and finish. If more than one, return an error
|
47
|
+
# "multiple matching paths".
|
48
|
+
# 5. If the application root is known, for each suffix of the "probe path",
|
49
|
+
# see if that relative paths concatenated to the application root
|
50
|
+
# results in a known file. If a known file is found, assume this
|
51
|
+
# is the wanted file and finish.
|
52
|
+
# 6. For each suffix of the "probe path", filter the set of known paths
|
53
|
+
# down to those that end in the suffix. If exactly one path remains
|
54
|
+
# for a given suffix, assume this is the wanted path and finish.
|
55
|
+
# If more than one path remains for a given suffix, return the error
|
56
|
+
# "multiple matching paths".
|
57
|
+
# 7. Repeat step 5 but perform case-insensitive comparison.
|
58
|
+
# 8. Repeat step 6 but perform case-insensitive comparison.
|
59
|
+
#
|
60
|
+
# Note that we do not look for "probe paths" under the current working
|
61
|
+
# directory at runtime if the current working directory does not contain
|
62
|
+
# a Gemfile, to avoid finding files from potentially undesired bases.
|
63
|
+
#
|
64
|
+
# What "known file"/"known path" means also differs based on the
|
65
|
+
# operation being performed:
|
66
|
+
# - If the operation is "install a probe", "known file/path" can
|
67
|
+
# include files on the filesystem that have not been loaded as
|
68
|
+
# well as paths from the code tracker.
|
69
|
+
# - If the operation is "check if any pending probes match the file
|
70
|
+
# that was just loaded", we would only consider the path that was
|
71
|
+
# just loaded and not check the filesystem.
|
72
|
+
#
|
73
|
+
# Filesystem inquiries are obviously quite expensive and should be
|
74
|
+
# cached. For the vast majority of applications it should be safe to
|
75
|
+
# indefinitely cache whether a particular filesystem paths exists
|
76
|
+
# in both positive and negative.
|
77
|
+
#
|
78
|
+
# As a "quick fix", currently after performing the suffix matching
|
79
|
+
# we just strip leading directory components from the "probe path"
|
80
|
+
# until we get a match via a "suffix of the suffix".
|
81
|
+
|
6
82
|
# Returns whether the provided +path+ matches the user-designated
|
7
83
|
# file suffix (of a line probe).
|
8
84
|
#
|
@@ -41,6 +117,21 @@ module Datadog
|
|
41
117
|
# !!(path =~ %r,(/|\A)#{Regexp.quote(suffix)}\z,)
|
42
118
|
end
|
43
119
|
end
|
120
|
+
|
121
|
+
# Returns whether the provided +path+ matches the "probe path" in
|
122
|
+
# +spec+. Attempts all of the fuzzy matches by stripping directories
|
123
|
+
# from the front of +spec+. Does not consider othr known paths to
|
124
|
+
# identify the case of (potentially) multiple matching paths for +spec+.
|
125
|
+
module_function def path_can_match_spec?(path, spec)
|
126
|
+
return true if path_matches_suffix?(path, spec)
|
127
|
+
|
128
|
+
spec = spec.dup
|
129
|
+
loop do
|
130
|
+
return false unless spec.include?('/')
|
131
|
+
spec.sub!(%r{.*/+}, '')
|
132
|
+
return true if path_matches_suffix?(path, spec)
|
133
|
+
end
|
134
|
+
end
|
44
135
|
end
|
45
136
|
end
|
46
137
|
end
|
data/lib/datadog/di.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'di/logger'
|
3
4
|
require_relative 'di/base'
|
4
5
|
require_relative 'di/error'
|
5
6
|
require_relative 'di/code_tracker'
|
@@ -58,6 +59,8 @@ if %w(1 true).include?(ENV['DD_DYNAMIC_INSTRUMENTATION_ENABLED']) # steep:ignore
|
|
58
59
|
#
|
59
60
|
# If DI is enabled programmatically, the application can (and must,
|
60
61
|
# for line probes to work) activate tracking in an initializer.
|
62
|
+
# We seem to have Datadog.logger here already
|
63
|
+
Datadog.logger.debug("di: activating code tracking")
|
61
64
|
Datadog::DI.activate_tracking
|
62
65
|
end
|
63
66
|
|
@@ -435,17 +435,11 @@ module Datadog
|
|
435
435
|
end
|
436
436
|
|
437
437
|
private_class_method def self.enable_gvl_profiling?(settings, logger)
|
438
|
-
if RUBY_VERSION < "3.2"
|
439
|
-
if settings.profiling.advanced.preview_gvl_enabled
|
440
|
-
logger.warn("GVL profiling is currently not supported in Ruby < 3.2 and will not be enabled.")
|
441
|
-
end
|
442
|
-
|
443
|
-
return false
|
444
|
-
end
|
438
|
+
return false if RUBY_VERSION < "3.2"
|
445
439
|
|
446
440
|
# GVL profiling only makes sense in the context of timeline. We could emit a warning here, but not sure how
|
447
441
|
# useful it is -- if a customer disables timeline, there's nowhere to look for GVL profiling anyway!
|
448
|
-
settings.profiling.advanced.timeline_enabled && settings.profiling.advanced.
|
442
|
+
settings.profiling.advanced.timeline_enabled && settings.profiling.advanced.gvl_enabled
|
449
443
|
end
|
450
444
|
end
|
451
445
|
end
|
@@ -1,41 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# This file is used to load the profiling native extension. It works in two steps:
|
4
|
-
#
|
5
|
-
# 1. Load the datadog_profiling_loader extension. This extension will be used to load the actual extension, but in
|
6
|
-
# a special way that avoids exposing native-level code symbols. See `datadog_profiling_loader.c` for more details.
|
7
|
-
#
|
8
|
-
# 2. Use the Datadog::Profiling::Loader exposed by the datadog_profiling_loader extension to load the actual
|
9
|
-
# profiling native extension.
|
10
|
-
#
|
11
|
-
# All code on this file is on-purpose at the top-level; this makes it so this file is executed only once,
|
12
|
-
# the first time it gets required, to avoid any issues with the native extension being initialized more than once.
|
13
|
-
|
14
3
|
begin
|
15
|
-
require "
|
4
|
+
require "datadog_profiling_native_extension.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
|
16
5
|
rescue LoadError => e
|
17
6
|
raise LoadError,
|
18
7
|
"Failed to load the profiling loader extension. To fix this, please remove and then reinstall datadog " \
|
19
8
|
"(Details: #{e.message})"
|
20
9
|
end
|
21
|
-
|
22
|
-
extension_name = "datadog_profiling_native_extension.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
|
23
|
-
file_name = "#{extension_name}.#{RbConfig::CONFIG["DLEXT"]}"
|
24
|
-
full_file_path = "#{__dir__}/../../#{file_name}"
|
25
|
-
|
26
|
-
unless File.exist?(full_file_path)
|
27
|
-
extension_dir = Gem.loaded_specs["datadog"].extension_dir
|
28
|
-
candidate_path = "#{extension_dir}/#{file_name}"
|
29
|
-
if File.exist?(candidate_path)
|
30
|
-
full_file_path = candidate_path
|
31
|
-
else
|
32
|
-
# We found none of the files. This is unexpected. Let's go ahead anyway, the error is going to be reported further
|
33
|
-
# down anyway.
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
init_function_name = "Init_#{extension_name.split(".").first}"
|
38
|
-
|
39
|
-
status, result = Datadog::Profiling::Loader._native_load(full_file_path, init_function_name)
|
40
|
-
|
41
|
-
raise "Failure to load #{extension_name} due to #{result}" if status == :error
|
@@ -16,6 +16,7 @@ module Datadog
|
|
16
16
|
# @public_api
|
17
17
|
module SpanAttributeSchema
|
18
18
|
ENV_GLOBAL_DEFAULT_SERVICE_NAME_ENABLED = 'DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED'
|
19
|
+
ENV_PEER_SERVICE_DEFAULTS_ENABLED = 'DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED'
|
19
20
|
ENV_PEER_SERVICE_MAPPING = 'DD_TRACE_PEER_SERVICE_MAPPING'
|
20
21
|
end
|
21
22
|
|
@@ -130,6 +130,20 @@ module Datadog
|
|
130
130
|
o.default({})
|
131
131
|
end
|
132
132
|
|
133
|
+
# Enables population of default in the `peer.service` span tag.
|
134
|
+
# Explicitly setting the `peer.service` for an integration will
|
135
|
+
# still be honored with this option disabled.
|
136
|
+
#
|
137
|
+
# Also when disabled, other peer service related configurations have no effect.
|
138
|
+
#
|
139
|
+
# @default `DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED` environment variable, otherwise `false`
|
140
|
+
# @return [Boolean]
|
141
|
+
option :peer_service_defaults do |o|
|
142
|
+
o.env Tracing::Configuration::Ext::SpanAttributeSchema::ENV_PEER_SERVICE_DEFAULTS_ENABLED
|
143
|
+
o.type :bool
|
144
|
+
o.default false
|
145
|
+
end
|
146
|
+
|
133
147
|
# Global service name behavior
|
134
148
|
settings :global_default_service_name do
|
135
149
|
# Overrides default service name to global service name
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module Tracing
|
5
|
+
module Contrib
|
6
|
+
module GraphQL
|
7
|
+
module Configuration
|
8
|
+
# Parses the environment variable `DD_TRACE_GRAPHQL_ERROR_EXTENSIONS` for error extension names declaration.
|
9
|
+
class ErrorExtensionEnvParser
|
10
|
+
# Parses the environment variable `DD_TRACE_GRAPHQL_ERROR_EXTENSIONS` into an array of error extension names.
|
11
|
+
def self.call(values)
|
12
|
+
# Split by comma, remove leading and trailing whitespaces,
|
13
|
+
# remove empty values, and remove repeated values.
|
14
|
+
values.split(',').each(&:strip!).reject(&:empty?).uniq
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative '../../configuration/settings'
|
4
4
|
require_relative '../ext'
|
5
|
+
require_relative 'error_extension_env_parser'
|
5
6
|
|
6
7
|
module Datadog
|
7
8
|
module Tracing
|
@@ -44,9 +45,19 @@ module Datadog
|
|
44
45
|
end
|
45
46
|
|
46
47
|
option :with_unified_tracer do |o|
|
48
|
+
o.env Ext::ENV_WITH_UNIFIED_TRACER
|
47
49
|
o.type :bool
|
48
50
|
o.default false
|
49
51
|
end
|
52
|
+
|
53
|
+
# Capture error extensions provided by the user in their GraphQL error responses.
|
54
|
+
# The extensions can be anything, so the user is responsible for ensuring they are safe to capture.
|
55
|
+
option :error_extensions do |o|
|
56
|
+
o.env Ext::ENV_ERROR_EXTENSIONS
|
57
|
+
o.type :array, nilable: false
|
58
|
+
o.default []
|
59
|
+
o.env_parser { |v| ErrorExtensionEnvParser.call(v) }
|
60
|
+
end
|
50
61
|
end
|
51
62
|
end
|
52
63
|
end
|
@@ -11,8 +11,13 @@ module Datadog
|
|
11
11
|
# @!visibility private
|
12
12
|
ENV_ANALYTICS_ENABLED = 'DD_TRACE_GRAPHQL_ANALYTICS_ENABLED'
|
13
13
|
ENV_ANALYTICS_SAMPLE_RATE = 'DD_TRACE_GRAPHQL_ANALYTICS_SAMPLE_RATE'
|
14
|
+
ENV_WITH_UNIFIED_TRACER = 'DD_TRACE_GRAPHQL_WITH_UNIFIED_TRACER'
|
15
|
+
ENV_ERROR_EXTENSIONS = 'DD_TRACE_GRAPHQL_ERROR_EXTENSIONS'
|
14
16
|
SERVICE_NAME = 'graphql'
|
15
17
|
TAG_COMPONENT = 'graphql'
|
18
|
+
|
19
|
+
# Span event name for query-level errors
|
20
|
+
EVENT_QUERY_ERROR = 'dd.graphql.query.error'
|
16
21
|
end
|
17
22
|
end
|
18
23
|
end
|
@@ -47,14 +47,26 @@ module Datadog
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def execute_query(*args, query:, **kwargs)
|
50
|
-
trace(
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
span.set_tag(
|
56
|
-
|
57
|
-
|
50
|
+
trace(
|
51
|
+
proc { super },
|
52
|
+
'execute',
|
53
|
+
query.selected_operation_name,
|
54
|
+
lambda { |span|
|
55
|
+
span.set_tag('graphql.source', query.query_string)
|
56
|
+
span.set_tag('graphql.operation.type', query.selected_operation.operation_type)
|
57
|
+
if query.selected_operation_name
|
58
|
+
span.set_tag(
|
59
|
+
'graphql.operation.name',
|
60
|
+
query.selected_operation_name
|
61
|
+
)
|
62
|
+
end
|
63
|
+
query.variables.instance_variable_get(:@storage).each do |key, value|
|
64
|
+
span.set_tag("graphql.variables.#{key}", value)
|
65
|
+
end
|
66
|
+
},
|
67
|
+
->(span) { add_query_error_events(span, query.context.errors) },
|
68
|
+
query: query,
|
69
|
+
)
|
58
70
|
end
|
59
71
|
|
60
72
|
def execute_query_lazy(*args, query:, multiplex:, **kwargs)
|
@@ -131,7 +143,16 @@ module Datadog
|
|
131
143
|
|
132
144
|
private
|
133
145
|
|
134
|
-
|
146
|
+
# Traces the given callable with the given trace key, resource, and kwargs.
|
147
|
+
#
|
148
|
+
# @param callable [Proc] the original method call
|
149
|
+
# @param trace_key [String] the sub-operation name (`"graphql.#{trace_key}"`)
|
150
|
+
# @param resource [String] the resource name for the trace
|
151
|
+
# @param before [Proc, nil] a callable to run before the trace, same as the block parameter
|
152
|
+
# @param after [Proc, nil] a callable to run after the trace, which has access to query values after execution
|
153
|
+
# @param kwargs [Hash] the arguments to pass to `prepare_span`
|
154
|
+
# @yield [Span] the block to run before the trace, same as the `before` parameter
|
155
|
+
def trace(callable, trace_key, resource, before = nil, after = nil, **kwargs, &before_block)
|
135
156
|
config = Datadog.configuration.tracing[:graphql]
|
136
157
|
|
137
158
|
Tracing.trace(
|
@@ -144,11 +165,20 @@ module Datadog
|
|
144
165
|
Contrib::Analytics.set_sample_rate(span, config[:analytics_sample_rate])
|
145
166
|
end
|
146
167
|
|
147
|
-
|
168
|
+
# A sanity check for us.
|
169
|
+
raise 'Please provide either `before` or a block, but not both' if before && before_block
|
170
|
+
|
171
|
+
if (before_callable = before || before_block)
|
172
|
+
before_callable.call(span)
|
173
|
+
end
|
148
174
|
|
149
175
|
prepare_span(trace_key, kwargs, span) if @has_prepare_span
|
150
176
|
|
151
|
-
callable.call
|
177
|
+
ret = callable.call
|
178
|
+
|
179
|
+
after.call(span) if after
|
180
|
+
|
181
|
+
ret
|
152
182
|
end
|
153
183
|
end
|
154
184
|
|
@@ -163,6 +193,67 @@ module Datadog
|
|
163
193
|
operations
|
164
194
|
end
|
165
195
|
end
|
196
|
+
|
197
|
+
# Create a Span Event for each error that occurs at query level.
|
198
|
+
#
|
199
|
+
# These are represented in the Datadog App as special GraphQL errors,
|
200
|
+
# given their event name `dd.graphql.query.error`.
|
201
|
+
def add_query_error_events(span, errors)
|
202
|
+
capture_extensions = Datadog.configuration.tracing[:graphql][:error_extensions]
|
203
|
+
errors.each do |error|
|
204
|
+
extensions = if !capture_extensions.empty? && (extensions = error.extensions)
|
205
|
+
# Capture extensions, ensuring all values are primitives
|
206
|
+
extensions.each_with_object({}) do |(key, value), hash|
|
207
|
+
next unless capture_extensions.include?(key.to_s)
|
208
|
+
|
209
|
+
value = case value
|
210
|
+
when TrueClass, FalseClass, Integer, Float
|
211
|
+
value
|
212
|
+
else
|
213
|
+
# Stringify anything that is not a boolean or a number
|
214
|
+
value.to_s
|
215
|
+
end
|
216
|
+
|
217
|
+
hash["extensions.#{key}"] = value
|
218
|
+
end
|
219
|
+
else
|
220
|
+
{}
|
221
|
+
end
|
222
|
+
|
223
|
+
# {::GraphQL::Error#to_h} returns the error formatted in compliance with the GraphQL spec.
|
224
|
+
# This is an unwritten contract in the `graphql` library.
|
225
|
+
# See for an example: https://github.com/rmosolgo/graphql-ruby/blob/0afa241775e5a113863766cce126214dee093464/lib/graphql/execution_error.rb#L32
|
226
|
+
graphql_error = error.to_h
|
227
|
+
error = Core::Error.build_from(error)
|
228
|
+
|
229
|
+
span.span_events << Datadog::Tracing::SpanEvent.new(
|
230
|
+
Ext::EVENT_QUERY_ERROR,
|
231
|
+
attributes: extensions.merge!(
|
232
|
+
message: graphql_error['message'],
|
233
|
+
type: error.type,
|
234
|
+
stacktrace: error.backtrace,
|
235
|
+
locations: serialize_error_locations(graphql_error['locations']),
|
236
|
+
path: graphql_error['path'],
|
237
|
+
)
|
238
|
+
)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Serialize error's `locations` array as an array of Strings, given
|
243
|
+
# Span Events do not support hashes nested inside arrays.
|
244
|
+
#
|
245
|
+
# Here's an example in which `locations`:
|
246
|
+
# [
|
247
|
+
# {"line" => 3, "column" => 10},
|
248
|
+
# {"line" => 7, "column" => 8},
|
249
|
+
# ]
|
250
|
+
# is serialized as:
|
251
|
+
# ["3:10", "7:8"]
|
252
|
+
def serialize_error_locations(locations)
|
253
|
+
locations.map do |location|
|
254
|
+
"#{location['line']}:#{location['column']}"
|
255
|
+
end
|
256
|
+
end
|
166
257
|
end
|
167
258
|
end
|
168
259
|
end
|
@@ -31,7 +31,17 @@ module Datadog
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.to_rack_header(name)
|
34
|
-
|
34
|
+
key = name.to_s.upcase.gsub(/[-\s]/, '_')
|
35
|
+
case key
|
36
|
+
when 'CONTENT_TYPE', 'CONTENT_LENGTH'
|
37
|
+
# NOTE: The Rack spec says:
|
38
|
+
# > The environment must not contain the keys HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH
|
39
|
+
# > (use the versions without HTTP_).
|
40
|
+
# See https://github.com/rack/rack/blob/e217a399eb116362710aac7c5b8dc691ea2189b3/SPEC.rdoc?plain=1#L119-L121
|
41
|
+
key
|
42
|
+
else
|
43
|
+
"HTTP_#{key}"
|
44
|
+
end
|
35
45
|
end
|
36
46
|
end
|
37
47
|
end
|
@@ -43,7 +43,7 @@ module Datadog
|
|
43
43
|
# so that all spans will be added to the distributed trace.
|
44
44
|
if configuration[:distributed_tracing]
|
45
45
|
trace_digest = Contrib::HTTP.extract(env)
|
46
|
-
Tracing.continue_trace!(trace_digest)
|
46
|
+
Tracing.continue_trace!(trace_digest) if trace_digest
|
47
47
|
end
|
48
48
|
|
49
49
|
TraceProxyMiddleware.call(env, configuration) do
|
@@ -21,9 +21,14 @@ module Datadog
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def self.set_peer_service!(span, sources)
|
24
|
+
config = Datadog.configuration.tracing.contrib
|
25
|
+
|
26
|
+
# If `peer_service_defaults` is disabled, we only read peer service from an explicitly set `peer.service` tag
|
27
|
+
sources = Datadog::Tracing::Contrib::SpanAttributeSchema::REFLEXIVE_SOURCES unless config.peer_service_defaults
|
28
|
+
|
24
29
|
# Acquire all peer.service values as well as any potential remapping
|
25
30
|
peer_service_val, peer_service_source = set_peer_service_from_source(span, sources)
|
26
|
-
remap_val =
|
31
|
+
remap_val = config.peer_service_mapping[peer_service_val]
|
27
32
|
|
28
33
|
# Only continue to setting peer.service if actual source is found
|
29
34
|
return false unless peer_service_source
|
@@ -3,7 +3,8 @@
|
|
3
3
|
require_relative '../../../core/encoding'
|
4
4
|
|
5
5
|
require_relative '../../../core/transport/http/api/map'
|
6
|
-
require_relative 'api/
|
6
|
+
require_relative '../../../core/transport/http/api/instance'
|
7
|
+
require_relative '../../../core/transport/http/api/spec'
|
7
8
|
|
8
9
|
require_relative 'traces'
|
9
10
|
|
@@ -20,7 +21,7 @@ module Datadog
|
|
20
21
|
module_function
|
21
22
|
|
22
23
|
def defaults
|
23
|
-
|
24
|
+
Core::Transport::HTTP::API::Map[
|
24
25
|
V4 => Spec.new do |s|
|
25
26
|
s.traces = Traces::API::Endpoint.new(
|
26
27
|
'/v0.4/traces',
|
@@ -36,6 +37,14 @@ module Datadog
|
|
36
37
|
end,
|
37
38
|
].with_fallbacks(V4 => V3)
|
38
39
|
end
|
40
|
+
|
41
|
+
class Instance < Core::Transport::HTTP::API::Instance
|
42
|
+
include Traces::API::Instance
|
43
|
+
end
|
44
|
+
|
45
|
+
class Spec < Core::Transport::HTTP::API::Spec
|
46
|
+
include Traces::API::Spec
|
47
|
+
end
|
39
48
|
end
|
40
49
|
end
|
41
50
|
end
|
@@ -6,7 +6,6 @@ require_relative '../traces'
|
|
6
6
|
require_relative 'client'
|
7
7
|
require_relative '../../../core/transport/http/response'
|
8
8
|
require_relative '../../../core/transport/http/api/endpoint'
|
9
|
-
require_relative 'api/instance'
|
10
9
|
|
11
10
|
module Datadog
|
12
11
|
module Tracing
|
@@ -143,8 +142,6 @@ module Datadog
|
|
143
142
|
|
144
143
|
# Add traces behavior to transport components
|
145
144
|
HTTP::Client.include(Traces::Client)
|
146
|
-
HTTP::API::Spec.include(Traces::API::Spec)
|
147
|
-
HTTP::API::Instance.include(Traces::API::Instance)
|
148
145
|
end
|
149
146
|
end
|
150
147
|
end
|