datadog 2.21.0 → 2.22.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 +48 -1
- data/ext/LIBDATADOG_DEVELOPMENT.md +60 -0
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/libdatadog_api/ddsketch.c +106 -0
- data/ext/libdatadog_api/init.c +3 -0
- data/ext/libdatadog_api/library_config.c +35 -27
- data/ext/libdatadog_api/process_discovery.c +19 -13
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
- data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +359 -4
- data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
- data/lib/datadog/appsec/compressed_json.rb +1 -1
- data/lib/datadog/appsec/configuration/settings.rb +9 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +3 -2
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +3 -1
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +3 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -4
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -2
- data/lib/datadog/appsec/contrib/rails/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +3 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -4
- data/lib/datadog/appsec/event.rb +12 -14
- data/lib/datadog/appsec/metrics/collector.rb +19 -3
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +2 -1
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -4
- data/lib/datadog/appsec/remote.rb +25 -13
- data/lib/datadog/appsec/security_engine/result.rb +28 -9
- data/lib/datadog/appsec/security_engine/runner.rb +17 -7
- data/lib/datadog/appsec/security_event.rb +5 -7
- data/lib/datadog/core/configuration/components.rb +14 -6
- data/lib/datadog/core/configuration/stable_config.rb +10 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/core/ddsketch.rb +21 -0
- data/lib/datadog/core/environment/yjit.rb +2 -1
- data/lib/datadog/core/pin.rb +4 -8
- data/lib/datadog/core/process_discovery.rb +4 -2
- data/lib/datadog/core/remote/component.rb +4 -6
- data/lib/datadog/core/telemetry/component.rb +11 -0
- data/lib/datadog/core/telemetry/emitter.rb +6 -6
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
- data/lib/datadog/core/telemetry/event.rb +1 -0
- data/lib/datadog/core/transport/response.rb +4 -1
- data/lib/datadog/core/utils/network.rb +19 -0
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/component.rb +14 -0
- data/lib/datadog/di/context.rb +70 -0
- data/lib/datadog/di/el/compiler.rb +164 -0
- data/lib/datadog/di/el/evaluator.rb +159 -0
- data/lib/datadog/di/el/expression.rb +42 -0
- data/lib/datadog/di/el.rb +5 -0
- data/lib/datadog/di/error.rb +25 -0
- data/lib/datadog/di/instrumenter.rb +101 -32
- data/lib/datadog/di/probe.rb +35 -15
- data/lib/datadog/di/probe_builder.rb +39 -1
- data/lib/datadog/di/probe_manager.rb +3 -2
- data/lib/datadog/di/probe_notification_builder.rb +50 -51
- data/lib/datadog/di/serializer.rb +151 -7
- data/lib/datadog/tracing/component.rb +6 -17
- data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
- data/lib/datadog/tracing/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/component.rb +2 -2
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
- data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +53 -28
- data/lib/datadog/tracing/metadata/ext.rb +8 -0
- data/lib/datadog/version.rb +1 -1
- metadata +22 -9
- data/ext/libdatadog_api/macos_development.md +0 -26
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rubocop:disable Lint/AssignmentInCondition
|
4
|
+
|
3
5
|
module Datadog
|
4
6
|
module DI
|
5
7
|
# Builds probe status notification and snapshot payloads.
|
@@ -40,32 +42,17 @@ module Datadog
|
|
40
42
|
|
41
43
|
# Duration is in seconds.
|
42
44
|
# path is the actual path of the instrumented file.
|
43
|
-
def build_executed(
|
44
|
-
|
45
|
-
serialized_locals: nil, args: nil, kwargs: nil, target_self: nil,
|
46
|
-
serialized_entry_args: nil)
|
47
|
-
build_snapshot(probe, rv: rv, serialized_locals: serialized_locals,
|
48
|
-
# Actual path of the instrumented file.
|
49
|
-
path: path,
|
50
|
-
duration: duration,
|
51
|
-
# TODO check how many stack frames we should be keeping/sending,
|
52
|
-
# this should be all frames for enriched probes and no frames for
|
53
|
-
# non-enriched probes?
|
54
|
-
caller_locations: caller_locations,
|
55
|
-
args: args, kwargs: kwargs,
|
56
|
-
target_self: target_self,
|
57
|
-
serialized_entry_args: serialized_entry_args)
|
45
|
+
def build_executed(context)
|
46
|
+
build_snapshot(context)
|
58
47
|
end
|
59
48
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
serialized_entry_args: nil)
|
68
|
-
if probe.capture_snapshot? && !target_self
|
49
|
+
NANOSECONDS = 10**9
|
50
|
+
MILLISECONDS = 1000
|
51
|
+
|
52
|
+
def build_snapshot(context)
|
53
|
+
probe = context.probe
|
54
|
+
|
55
|
+
if probe.capture_snapshot? && !context.target_self
|
69
56
|
raise ArgumentError, "Asked to build snapshot with snapshot capture but target_self is nil"
|
70
57
|
end
|
71
58
|
|
@@ -74,22 +61,14 @@ module Datadog
|
|
74
61
|
captures = if probe.capture_snapshot?
|
75
62
|
if probe.method?
|
76
63
|
return_arguments = {
|
77
|
-
"@return": serializer.serialize_value(
|
64
|
+
"@return": serializer.serialize_value(context.return_value,
|
78
65
|
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
79
66
|
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
|
80
|
-
self: serializer.serialize_value(target_self),
|
67
|
+
self: serializer.serialize_value(context.target_self),
|
81
68
|
}
|
82
69
|
{
|
83
70
|
entry: {
|
84
|
-
|
85
|
-
arguments: if serialized_entry_args
|
86
|
-
serialized_entry_args
|
87
|
-
else
|
88
|
-
(args || kwargs) && serializer.serialize_args(args, kwargs, target_self,
|
89
|
-
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
90
|
-
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
|
91
|
-
end,
|
92
|
-
# standard:enable all
|
71
|
+
arguments: context.serialized_entry_args,
|
93
72
|
},
|
94
73
|
return: {
|
95
74
|
arguments: return_arguments,
|
@@ -98,10 +77,10 @@ module Datadog
|
|
98
77
|
}
|
99
78
|
elsif probe.line?
|
100
79
|
{
|
101
|
-
lines: serialized_locals && {
|
80
|
+
lines: (locals = context.serialized_locals) && {
|
102
81
|
probe.line_no => {
|
103
|
-
locals:
|
104
|
-
arguments: {self: serializer.serialize_value(target_self)},
|
82
|
+
locals: locals,
|
83
|
+
arguments: {self: serializer.serialize_value(context.target_self)},
|
105
84
|
},
|
106
85
|
},
|
107
86
|
}
|
@@ -110,7 +89,7 @@ module Datadog
|
|
110
89
|
|
111
90
|
location = if probe.line?
|
112
91
|
{
|
113
|
-
file: path,
|
92
|
+
file: context.path,
|
114
93
|
lines: [probe.line_no],
|
115
94
|
}
|
116
95
|
elsif probe.method?
|
@@ -120,17 +99,23 @@ module Datadog
|
|
120
99
|
}
|
121
100
|
end
|
122
101
|
|
123
|
-
stack = if caller_locations
|
102
|
+
stack = if caller_locations = context.caller_locations
|
124
103
|
format_caller_locations(caller_locations)
|
125
104
|
end
|
126
105
|
|
127
106
|
timestamp = timestamp_now
|
107
|
+
message = nil
|
108
|
+
evaluation_errors = []
|
109
|
+
if segments = probe.template_segments
|
110
|
+
message, evaluation_errors = evaluate_template(segments, context)
|
111
|
+
end
|
112
|
+
duration = context.duration
|
128
113
|
{
|
129
114
|
service: settings.service,
|
130
115
|
"debugger.snapshot": {
|
131
116
|
id: SecureRandom.uuid,
|
132
117
|
timestamp: timestamp,
|
133
|
-
evaluationErrors:
|
118
|
+
evaluationErrors: evaluation_errors,
|
134
119
|
probe: {
|
135
120
|
id: probe.id,
|
136
121
|
version: 0,
|
@@ -143,7 +128,7 @@ module Datadog
|
|
143
128
|
},
|
144
129
|
# In python tracer duration is under debugger.snapshot,
|
145
130
|
# but UI appears to expect it here at top level.
|
146
|
-
duration: duration ? (duration *
|
131
|
+
duration: duration ? (duration * NANOSECONDS).to_i : 0,
|
147
132
|
host: nil,
|
148
133
|
logger: {
|
149
134
|
name: probe.file,
|
@@ -160,8 +145,7 @@ module Datadog
|
|
160
145
|
"dd.trace_id": active_trace&.id&.to_s,
|
161
146
|
"dd.span_id": active_span&.id&.to_s,
|
162
147
|
ddsource: 'dd_debugger',
|
163
|
-
message:
|
164
|
-
duration: duration ? duration * 1000 : 0),
|
148
|
+
message: message,
|
165
149
|
timestamp: timestamp,
|
166
150
|
}
|
167
151
|
end
|
@@ -192,16 +176,29 @@ module Datadog
|
|
192
176
|
end
|
193
177
|
end
|
194
178
|
|
195
|
-
def evaluate_template(
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
179
|
+
def evaluate_template(template_segments, context)
|
180
|
+
evaluation_errors = []
|
181
|
+
message = template_segments.map do |segment|
|
182
|
+
case segment
|
183
|
+
when String
|
184
|
+
segment
|
185
|
+
when EL::Expression
|
186
|
+
serializer.serialize_value_for_message(segment.evaluate(context))
|
187
|
+
else
|
188
|
+
raise ArgumentError, "Invalid template segment type: #{segment}"
|
189
|
+
end
|
190
|
+
rescue => exc
|
191
|
+
evaluation_errors << {
|
192
|
+
message: "#{exc.class}: #{exc}",
|
193
|
+
expr: segment.dsl_expr,
|
194
|
+
}
|
195
|
+
'[evaluation error]'
|
196
|
+
end.join
|
197
|
+
[message, evaluation_errors]
|
201
198
|
end
|
202
199
|
|
203
200
|
def timestamp_now
|
204
|
-
(Core::Utils::Time.now.to_f *
|
201
|
+
(Core::Utils::Time.now.to_f * MILLISECONDS).to_i
|
205
202
|
end
|
206
203
|
|
207
204
|
def active_trace
|
@@ -218,3 +215,5 @@ module Datadog
|
|
218
215
|
end
|
219
216
|
end
|
220
217
|
end
|
218
|
+
|
219
|
+
# rubocop:enable Lint/AssignmentInCondition
|
@@ -64,6 +64,9 @@ module Datadog
|
|
64
64
|
# a common base class but are all of different classes) or for Mongoid
|
65
65
|
# models (that do not have a common base class at all but include a
|
66
66
|
# standard Mongoid module).
|
67
|
+
#
|
68
|
+
# Important: these serializers are NOT used in log messages.
|
69
|
+
# They are only used for variables that are captured in the snapshots.
|
67
70
|
@@flat_registry = []
|
68
71
|
def self.register(condition: nil, &block)
|
69
72
|
@@flat_registry << {condition: condition, proc: block}
|
@@ -79,6 +82,18 @@ module Datadog
|
|
79
82
|
attr_reader :redactor
|
80
83
|
attr_reader :telemetry
|
81
84
|
|
85
|
+
def combine_args(args, kwargs, target_self)
|
86
|
+
counter = 0
|
87
|
+
combined = args.each_with_object({}) do |value, c|
|
88
|
+
counter += 1
|
89
|
+
# Conversion to symbol is needed here to put args ahead of
|
90
|
+
# kwargs when they are merged below.
|
91
|
+
c[:"arg#{counter}"] = value
|
92
|
+
end.update(kwargs)
|
93
|
+
combined[:self] = target_self
|
94
|
+
combined
|
95
|
+
end
|
96
|
+
|
82
97
|
# Serializes positional and keyword arguments to a method,
|
83
98
|
# as obtained by a method probe.
|
84
99
|
#
|
@@ -93,13 +108,7 @@ module Datadog
|
|
93
108
|
def serialize_args(args, kwargs, target_self,
|
94
109
|
depth: settings.dynamic_instrumentation.max_capture_depth,
|
95
110
|
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count)
|
96
|
-
|
97
|
-
combined = args.each_with_object({}) do |value, c|
|
98
|
-
counter += 1
|
99
|
-
# Conversion to symbol is needed here to put args ahead of
|
100
|
-
# kwargs when they are merged below.
|
101
|
-
c[:"arg#{counter}"] = value
|
102
|
-
end.update(kwargs).update(self: target_self)
|
111
|
+
combined = combine_args(args, kwargs, target_self)
|
103
112
|
serialize_vars(combined, depth: depth, attribute_count: attribute_count)
|
104
113
|
end
|
105
114
|
|
@@ -150,6 +159,8 @@ module Datadog
|
|
150
159
|
end
|
151
160
|
|
152
161
|
serialized = {type: class_name(cls)}
|
162
|
+
# https://github.com/soutaro/steep/issues/1860
|
163
|
+
# @type var serialized: untyped
|
153
164
|
case value
|
154
165
|
when NilClass
|
155
166
|
serialized.update(isNull: true)
|
@@ -261,8 +272,120 @@ module Datadog
|
|
261
272
|
end
|
262
273
|
end
|
263
274
|
|
275
|
+
# This method is used for serializing arbitrary values into log messages.
|
276
|
+
# Because the output is meant to be human-readable, we cannot use
|
277
|
+
# the "normal" serialization format which is meant to be machine-readable.
|
278
|
+
# Serialize objects with depth of 1 and include the class name.
|
279
|
+
#
|
280
|
+
# Note that this method does not (currently) utilize the custom
|
281
|
+
# serializers that the "normal" serialization logic uses.
|
282
|
+
#
|
283
|
+
# This serializer differs from the RFC in two ways:
|
284
|
+
# 1. We omit the middle of long strings rather than the end,
|
285
|
+
# and also the inner entries in arrays/hashes/objects.
|
286
|
+
# 2. We use Ruby-ish syntax for hashes and objects.
|
287
|
+
#
|
288
|
+
# We also use the Ruby-like syntax for symbols, which don't exist
|
289
|
+
# in other languages.
|
290
|
+
def serialize_value_for_message(value, depth = 1)
|
291
|
+
# This method is more verbose than "normal" Ruby code to avoid
|
292
|
+
# array allocations.
|
293
|
+
case value
|
294
|
+
when NilClass
|
295
|
+
'nil'
|
296
|
+
when Integer, Float, TrueClass, FalseClass, Time, Date
|
297
|
+
value.to_s
|
298
|
+
when String
|
299
|
+
serialize_string_or_symbol_for_message(value)
|
300
|
+
when Symbol
|
301
|
+
':' + serialize_string_or_symbol_for_message(value)
|
302
|
+
when Array
|
303
|
+
return '...' if depth <= 0
|
304
|
+
|
305
|
+
max = max_capture_collection_size_for_message
|
306
|
+
if value.length > max
|
307
|
+
value_ = value[0...max - 1] || []
|
308
|
+
value_ << '...'
|
309
|
+
value_ << value[-1]
|
310
|
+
value = value_
|
311
|
+
end
|
312
|
+
'[' + value.map do |item|
|
313
|
+
serialize_value_for_message(item, depth - 1)
|
314
|
+
end.join(', ') + ']'
|
315
|
+
when Hash
|
316
|
+
return '...' if depth <= 0
|
317
|
+
|
318
|
+
max = max_capture_collection_size_for_message
|
319
|
+
keys = value.keys
|
320
|
+
truncated = false
|
321
|
+
if value.length > max
|
322
|
+
keys_ = keys[0...max - 1] || []
|
323
|
+
keys_ << keys[-1]
|
324
|
+
keys = keys_
|
325
|
+
truncated = true
|
326
|
+
end
|
327
|
+
serialized = keys.map do |key|
|
328
|
+
"#{serialize_value_for_message(key, depth - 1)} => #{serialize_value_for_message(value[key], depth - 1)}"
|
329
|
+
end
|
330
|
+
if truncated
|
331
|
+
serialized[serialized.length] = serialized[serialized.length - 1]
|
332
|
+
serialized[serialized.length - 2] = '...'
|
333
|
+
end
|
334
|
+
"{#{serialized.join(", ")}}"
|
335
|
+
else
|
336
|
+
return '...' if depth <= 0
|
337
|
+
|
338
|
+
vars = value.instance_variables
|
339
|
+
truncated = false
|
340
|
+
max = max_capture_attribute_count_for_message
|
341
|
+
if vars.length > max
|
342
|
+
vars_ = vars[0...max - 1] || []
|
343
|
+
vars_ << vars[-1]
|
344
|
+
truncated = true
|
345
|
+
vars = vars_
|
346
|
+
end
|
347
|
+
serialized = vars.map do |var|
|
348
|
+
# +var+ here is always the instance variable name which is a
|
349
|
+
# symbol, we do not need to run it through our serializer.
|
350
|
+
"#{var}=#{serialize_value_for_message(value.send(:instance_variable_get, var), depth - 1)}"
|
351
|
+
end
|
352
|
+
if truncated
|
353
|
+
serialized << serialized.last
|
354
|
+
serialized[-2] = '...'
|
355
|
+
end
|
356
|
+
serialized = if serialized.any?
|
357
|
+
' ' + serialized.join(' ')
|
358
|
+
end
|
359
|
+
"#<#{class_name(value.class)}#{serialized}>"
|
360
|
+
end
|
361
|
+
rescue => exc
|
362
|
+
telemetry&.report(exc, description: "Error serializing for message")
|
363
|
+
# TODO class_name(foo) can also fail, which we don't handle here.
|
364
|
+
# Telemetry reporting could potentially also fail?
|
365
|
+
"#<#{class_name(value.class)}: serialization error>"
|
366
|
+
end
|
367
|
+
|
264
368
|
private
|
265
369
|
|
370
|
+
MAX_MESSAGE_COLLECTION_SIZE = 3
|
371
|
+
MAX_MESSAGE_ATTRIBUTE_COUNT = 5
|
372
|
+
|
373
|
+
def max_capture_collection_size_for_message
|
374
|
+
max = settings.dynamic_instrumentation.max_capture_collection_size
|
375
|
+
if max > MAX_MESSAGE_COLLECTION_SIZE
|
376
|
+
max = MAX_MESSAGE_COLLECTION_SIZE
|
377
|
+
end
|
378
|
+
max
|
379
|
+
end
|
380
|
+
|
381
|
+
def max_capture_attribute_count_for_message
|
382
|
+
max = settings.dynamic_instrumentation.max_capture_attribute_count
|
383
|
+
if max > MAX_MESSAGE_ATTRIBUTE_COUNT
|
384
|
+
max = MAX_MESSAGE_ATTRIBUTE_COUNT
|
385
|
+
end
|
386
|
+
max
|
387
|
+
end
|
388
|
+
|
266
389
|
# Returns the name for the specified class object.
|
267
390
|
#
|
268
391
|
# Ruby can have nameless classes, e.g. Class.new is a class object
|
@@ -273,6 +396,27 @@ module Datadog
|
|
273
396
|
# and we don't want to invoke user code.
|
274
397
|
cls.name || "[Unnamed class]"
|
275
398
|
end
|
399
|
+
|
400
|
+
def serialize_string_or_symbol_for_message(value)
|
401
|
+
max = settings.dynamic_instrumentation.max_capture_string_length
|
402
|
+
if max > 100
|
403
|
+
max = 100
|
404
|
+
end
|
405
|
+
value = value.to_s
|
406
|
+
if (length = value.length) > max
|
407
|
+
if max < 5
|
408
|
+
value[0...max]
|
409
|
+
else
|
410
|
+
upper = length - max / 2 + 1
|
411
|
+
if max % 2 == 0
|
412
|
+
upper += 1
|
413
|
+
end
|
414
|
+
value[0...max / 2 - 1] + '...' + value[upper...length]
|
415
|
+
end
|
416
|
+
else
|
417
|
+
value
|
418
|
+
end
|
419
|
+
end
|
276
420
|
end
|
277
421
|
end
|
278
422
|
end
|
@@ -12,16 +12,7 @@ module Datadog
|
|
12
12
|
module Tracing
|
13
13
|
# Tracing component
|
14
14
|
module Component
|
15
|
-
|
16
|
-
module InstanceMethods
|
17
|
-
# Hot-swaps with a new sampler.
|
18
|
-
# This operation acquires the Components lock to ensure
|
19
|
-
# there is no concurrent modification of the sampler.
|
20
|
-
def reconfigure_live_sampler
|
21
|
-
sampler = self.class.build_sampler(Datadog.configuration)
|
22
|
-
Datadog.send(:safely_synchronize) { tracer.sampler.sampler = sampler }
|
23
|
-
end
|
24
|
-
end
|
15
|
+
module_function
|
25
16
|
|
26
17
|
def build_tracer(settings, agent_settings, logger:)
|
27
18
|
# If a custom tracer has been provided, use it instead.
|
@@ -156,11 +147,6 @@ module Datadog
|
|
156
147
|
Tracing::Sampling::Span::Sampler.new(rules || [])
|
157
148
|
end
|
158
149
|
|
159
|
-
# Configure non-privileged components.
|
160
|
-
def configure_tracing(settings)
|
161
|
-
Datadog::Tracing::Contrib::Component.configure(settings)
|
162
|
-
end
|
163
|
-
|
164
150
|
# Sampler wrapper component, to allow for hot-swapping
|
165
151
|
# the sampler instance used by the tracer.
|
166
152
|
# Swapping samplers happens during Dynamic Configuration.
|
@@ -182,8 +168,7 @@ module Datadog
|
|
182
168
|
end
|
183
169
|
end
|
184
170
|
|
185
|
-
private
|
186
|
-
|
171
|
+
# @api private
|
187
172
|
def build_tracer_tags(settings)
|
188
173
|
settings.tags.dup.tap do |tags|
|
189
174
|
tags[Core::Environment::Ext::TAG_ENV] = settings.env unless settings.env.nil?
|
@@ -193,6 +178,7 @@ module Datadog
|
|
193
178
|
|
194
179
|
# Build a post-sampler that limits the rate of traces to one per `seconds`.
|
195
180
|
# E.g.: `build_rate_limit_post_sampler(seconds: 60)` will limit the rate to one trace per minute.
|
181
|
+
# @api private
|
196
182
|
def build_rate_limit_post_sampler(seconds:)
|
197
183
|
Tracing::Sampling::RuleSampler.new(
|
198
184
|
rate_limiter: Datadog::Core::TokenBucket.new(1.0 / seconds, 1.0),
|
@@ -200,11 +186,13 @@ module Datadog
|
|
200
186
|
)
|
201
187
|
end
|
202
188
|
|
189
|
+
# @api private
|
203
190
|
def build_test_mode_trace_flush(settings)
|
204
191
|
# If context flush behavior is provided, use it instead.
|
205
192
|
settings.tracing.test_mode.trace_flush || build_trace_flush(settings)
|
206
193
|
end
|
207
194
|
|
195
|
+
# @api private
|
208
196
|
def build_test_mode_sampler
|
209
197
|
# Do not sample any spans for tests; all must be preserved.
|
210
198
|
# Set priority sampler to ensure the agent doesn't drop any traces.
|
@@ -214,6 +202,7 @@ module Datadog
|
|
214
202
|
)
|
215
203
|
end
|
216
204
|
|
205
|
+
# @api private
|
217
206
|
def build_test_mode_writer(settings, agent_settings)
|
218
207
|
writer_options = settings.tracing.test_mode.writer_options || {}
|
219
208
|
|
@@ -41,7 +41,7 @@ module Datadog
|
|
41
41
|
# Ensures sampler is rebuilt and new configuration is applied
|
42
42
|
def call(tracing_sampling_rate)
|
43
43
|
super
|
44
|
-
Datadog.send(:components).
|
44
|
+
Datadog.send(:components).reconfigure_sampler
|
45
45
|
end
|
46
46
|
|
47
47
|
protected
|
@@ -79,7 +79,7 @@ module Datadog
|
|
79
79
|
end
|
80
80
|
|
81
81
|
super
|
82
|
-
Datadog.send(:components).
|
82
|
+
Datadog.send(:components).reconfigure_sampler
|
83
83
|
end
|
84
84
|
|
85
85
|
protected
|
@@ -96,7 +96,7 @@ module Datadog
|
|
96
96
|
# Note: Alias (DD_TRACE_PROPAGATION_STYLE) defined in supported-configurations.json
|
97
97
|
o.env Configuration::Ext::Distributed::ENV_PROPAGATION_STYLE
|
98
98
|
o.default []
|
99
|
-
o.after_set do |styles|
|
99
|
+
o.after_set do |styles, _, precedence|
|
100
100
|
next if styles.empty?
|
101
101
|
|
102
102
|
# Make values case-insensitive
|
@@ -110,8 +110,8 @@ module Datadog
|
|
110
110
|
false
|
111
111
|
end
|
112
112
|
end
|
113
|
-
set_option(:propagation_style_extract, styles)
|
114
|
-
set_option(:propagation_style_inject, styles)
|
113
|
+
set_option(:propagation_style_extract, styles, precedence: precedence)
|
114
|
+
set_option(:propagation_style_inject, styles, precedence: precedence)
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
@@ -9,13 +9,13 @@ module Datadog
|
|
9
9
|
# Register a callback to be invoked when components are reconfigured.
|
10
10
|
# @param name [String] the name of the integration
|
11
11
|
# @param callback [Proc] the callback to invoke
|
12
|
-
# @yieldparam config [Datadog::Configuration] the configuration to pass to callbacks
|
12
|
+
# @yieldparam config [Datadog::Core::Configuration::Settings] the configuration to pass to callbacks
|
13
13
|
def register(name, &callback)
|
14
14
|
@registry[name] = callback
|
15
15
|
end
|
16
16
|
|
17
17
|
# Invoke all registered callbacks with the given configuration.
|
18
|
-
# @param config [Datadog::Configuration] the configuration to pass to callbacks
|
18
|
+
# @param config [Datadog::Core::Configuration::Settings] the configuration to pass to callbacks
|
19
19
|
def configure(config)
|
20
20
|
@registry.each do |name, callback|
|
21
21
|
callback.call(config)
|
@@ -58,6 +58,13 @@ module Datadog
|
|
58
58
|
o.default []
|
59
59
|
o.env_parser { |v| ErrorExtensionEnvParser.call(v) }
|
60
60
|
end
|
61
|
+
|
62
|
+
# Surface GraphQL errors in Error Tracking.
|
63
|
+
option :error_tracking do |o|
|
64
|
+
o.env Ext::ENV_ERROR_TRACKING
|
65
|
+
o.type :bool
|
66
|
+
o.default false
|
67
|
+
end
|
61
68
|
end
|
62
69
|
end
|
63
70
|
end
|
@@ -13,6 +13,7 @@ module Datadog
|
|
13
13
|
ENV_ANALYTICS_SAMPLE_RATE = 'DD_TRACE_GRAPHQL_ANALYTICS_SAMPLE_RATE'
|
14
14
|
ENV_WITH_UNIFIED_TRACER = 'DD_TRACE_GRAPHQL_WITH_UNIFIED_TRACER'
|
15
15
|
ENV_ERROR_EXTENSIONS = 'DD_TRACE_GRAPHQL_ERROR_EXTENSIONS'
|
16
|
+
ENV_ERROR_TRACKING = 'DD_TRACE_GRAPHQL_ERROR_TRACKING'
|
16
17
|
SERVICE_NAME = 'graphql'
|
17
18
|
TAG_COMPONENT = 'graphql'
|
18
19
|
|
@@ -11,11 +11,45 @@ module Datadog
|
|
11
11
|
# which is required to use features such as API Catalog.
|
12
12
|
# DEV-3.0: This tracer should be the default one in the next major version.
|
13
13
|
module UnifiedTrace
|
14
|
+
include ::GraphQL::Tracing::PlatformTrace
|
15
|
+
|
14
16
|
def initialize(*args, **kwargs)
|
15
17
|
@has_prepare_span = respond_to?(:prepare_span)
|
18
|
+
|
19
|
+
# Cache configuration values to avoid repeated lookups
|
20
|
+
config = Datadog.configuration.tracing[:graphql]
|
21
|
+
@service_name = config[:service_name]
|
22
|
+
@analytics_enabled = config[:analytics_enabled]
|
23
|
+
@analytics_sample_rate = config[:analytics_sample_rate]
|
24
|
+
@error_extensions_config = config[:error_extensions]
|
25
|
+
|
26
|
+
load_error_event_attributes(config[:error_tracking])
|
27
|
+
|
16
28
|
super
|
17
29
|
end
|
18
30
|
|
31
|
+
def load_error_event_attributes(error_tracking)
|
32
|
+
if error_tracking
|
33
|
+
@event_name = Tracing::Metadata::Ext::Errors::EVENT_NAME
|
34
|
+
@message_key = Tracing::Metadata::Ext::Errors::ATTRIBUTE_MESSAGE
|
35
|
+
@type_key = Tracing::Metadata::Ext::Errors::ATTRIBUTE_TYPE
|
36
|
+
@stacktrace_key = Tracing::Metadata::Ext::Errors::ATTRIBUTE_STACKTRACE
|
37
|
+
@locations_key = 'graphql.error.locations'
|
38
|
+
@path_key = 'graphql.error.path'
|
39
|
+
@extensions_key = 'graphql.error.extensions.'
|
40
|
+
else
|
41
|
+
@event_name = Ext::EVENT_QUERY_ERROR
|
42
|
+
@message_key = 'message'
|
43
|
+
@type_key = 'type'
|
44
|
+
@stacktrace_key = 'stacktrace'
|
45
|
+
@locations_key = 'locations'
|
46
|
+
@path_key = 'path'
|
47
|
+
@extensions_key = 'extensions.'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private :load_error_event_attributes
|
52
|
+
|
19
53
|
def lex(*args, query_string:, **kwargs)
|
20
54
|
trace(proc { super }, 'lex', query_string, query_string: query_string)
|
21
55
|
end
|
@@ -56,7 +90,7 @@ module Datadog
|
|
56
90
|
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_SERVER)
|
57
91
|
|
58
92
|
span.set_tag('graphql.source', query.query_string)
|
59
|
-
span.set_tag('graphql.operation.type', query.selected_operation
|
93
|
+
span.set_tag('graphql.operation.type', query.selected_operation&.operation_type)
|
60
94
|
if query.selected_operation_name
|
61
95
|
span.set_tag(
|
62
96
|
'graphql.operation.name',
|
@@ -130,8 +164,6 @@ module Datadog
|
|
130
164
|
resolve_type_span(proc { super }, 'resolve_type_lazy', **kwargs)
|
131
165
|
end
|
132
166
|
|
133
|
-
include ::GraphQL::Tracing::PlatformTrace
|
134
|
-
|
135
167
|
def platform_field_key(field, *args, **kwargs)
|
136
168
|
field.path
|
137
169
|
end
|
@@ -156,16 +188,14 @@ module Datadog
|
|
156
188
|
# @param kwargs [Hash] the arguments to pass to `prepare_span`
|
157
189
|
# @yield [Span] the block to run before the trace, same as the `before` parameter
|
158
190
|
def trace(callable, trace_key, resource, before = nil, after = nil, **kwargs, &before_block)
|
159
|
-
config = Datadog.configuration.tracing[:graphql]
|
160
|
-
|
161
191
|
Tracing.trace(
|
162
192
|
"graphql.#{trace_key}",
|
163
193
|
type: 'graphql',
|
164
194
|
resource: resource,
|
165
|
-
service:
|
195
|
+
service: @service_name
|
166
196
|
) do |span|
|
167
|
-
if Contrib::Analytics.enabled?(
|
168
|
-
Contrib::Analytics.set_sample_rate(span,
|
197
|
+
if Contrib::Analytics.enabled?(@analytics_enabled)
|
198
|
+
Contrib::Analytics.set_sample_rate(span, @analytics_sample_rate)
|
169
199
|
end
|
170
200
|
|
171
201
|
# A sanity check for us.
|
@@ -198,34 +228,29 @@ module Datadog
|
|
198
228
|
end
|
199
229
|
|
200
230
|
def operation_resource(operation)
|
201
|
-
if operation
|
231
|
+
if operation&.name
|
202
232
|
"#{operation.operation_type} #{operation.name}"
|
203
233
|
else
|
204
|
-
|
234
|
+
'anonymous'
|
205
235
|
end
|
206
236
|
end
|
207
237
|
|
208
238
|
# Create a Span Event for each error that occurs at query level.
|
209
|
-
#
|
210
|
-
# These are represented in the Datadog App as special GraphQL errors,
|
211
|
-
# given their event name `dd.graphql.query.error`.
|
212
239
|
def add_query_error_events(span, errors)
|
213
|
-
capture_extensions = Datadog.configuration.tracing[:graphql][:error_extensions]
|
214
240
|
errors.each do |error|
|
215
|
-
|
241
|
+
attributes = if !@error_extensions_config.empty? && (extensions = error.extensions)
|
216
242
|
# Capture extensions, ensuring all values are primitives
|
217
243
|
extensions.each_with_object({}) do |(key, value), hash|
|
218
|
-
next unless
|
244
|
+
next unless @error_extensions_config.include?(key.to_s)
|
219
245
|
|
220
246
|
value = case value
|
221
247
|
when TrueClass, FalseClass, Integer, Float
|
222
248
|
value
|
223
249
|
else
|
224
|
-
# Stringify anything that is not a boolean or a number
|
225
250
|
value.to_s
|
226
251
|
end
|
227
252
|
|
228
|
-
hash[
|
253
|
+
hash[@extensions_key + key.to_s] = value
|
229
254
|
end
|
230
255
|
else
|
231
256
|
{}
|
@@ -235,16 +260,16 @@ module Datadog
|
|
235
260
|
# This is an unwritten contract in the `graphql` library.
|
236
261
|
# See for an example: https://github.com/rmosolgo/graphql-ruby/blob/0afa241775e5a113863766cce126214dee093464/lib/graphql/execution_error.rb#L32
|
237
262
|
graphql_error = error.to_h
|
238
|
-
|
239
|
-
|
240
|
-
span.span_events <<
|
241
|
-
|
242
|
-
attributes:
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
263
|
+
parsed_error = Core::Error.build_from(error)
|
264
|
+
|
265
|
+
span.span_events << SpanEvent.new(
|
266
|
+
@event_name,
|
267
|
+
attributes: attributes.merge!(
|
268
|
+
@type_key => parsed_error.type,
|
269
|
+
@stacktrace_key => parsed_error.backtrace,
|
270
|
+
@message_key => graphql_error['message'],
|
271
|
+
@locations_key => serialize_error_locations(graphql_error['locations']),
|
272
|
+
@path_key => graphql_error['path'],
|
248
273
|
)
|
249
274
|
)
|
250
275
|
end
|