datadog 2.20.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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -1
  3. data/README.md +0 -1
  4. data/ext/LIBDATADOG_DEVELOPMENT.md +60 -0
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  6. data/ext/libdatadog_api/ddsketch.c +106 -0
  7. data/ext/libdatadog_api/init.c +3 -0
  8. data/ext/libdatadog_api/library_config.c +35 -27
  9. data/ext/libdatadog_api/process_discovery.c +24 -18
  10. data/ext/libdatadog_extconf_helpers.rb +1 -1
  11. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  12. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
  13. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
  14. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  15. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  16. data/lib/datadog/appsec/api_security/route_extractor.rb +6 -2
  17. data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
  18. data/lib/datadog/appsec/assets/waf_rules/recommended.json +359 -4
  19. data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
  20. data/lib/datadog/appsec/autoload.rb +1 -1
  21. data/lib/datadog/appsec/compressed_json.rb +1 -1
  22. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  23. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
  24. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +3 -2
  25. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +3 -1
  26. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +3 -1
  27. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -4
  28. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -1
  29. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -2
  30. data/lib/datadog/appsec/contrib/rails/patcher.rb +30 -0
  31. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +3 -1
  32. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -4
  33. data/lib/datadog/appsec/event.rb +12 -14
  34. data/lib/datadog/appsec/metrics/collector.rb +19 -3
  35. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +2 -1
  36. data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -4
  37. data/lib/datadog/appsec/remote.rb +25 -13
  38. data/lib/datadog/appsec/security_engine/result.rb +28 -9
  39. data/lib/datadog/appsec/security_engine/runner.rb +17 -7
  40. data/lib/datadog/appsec/security_event.rb +5 -7
  41. data/lib/datadog/core/configuration/agent_settings_resolver.rb +4 -4
  42. data/lib/datadog/core/configuration/components.rb +22 -8
  43. data/lib/datadog/core/configuration/config_helper.rb +100 -0
  44. data/lib/datadog/core/configuration/deprecations.rb +36 -0
  45. data/lib/datadog/core/configuration/ext.rb +0 -1
  46. data/lib/datadog/core/configuration/option.rb +38 -43
  47. data/lib/datadog/core/configuration/option_definition.rb +0 -9
  48. data/lib/datadog/core/configuration/options.rb +1 -5
  49. data/lib/datadog/core/configuration/settings.rb +10 -6
  50. data/lib/datadog/core/configuration/stable_config.rb +10 -0
  51. data/lib/datadog/core/configuration/supported_configurations.rb +337 -0
  52. data/lib/datadog/core/configuration.rb +2 -2
  53. data/lib/datadog/core/ddsketch.rb +21 -0
  54. data/lib/datadog/core/deprecations.rb +2 -2
  55. data/lib/datadog/core/environment/ext.rb +0 -2
  56. data/lib/datadog/core/environment/git.rb +2 -2
  57. data/lib/datadog/core/environment/variable_helpers.rb +3 -3
  58. data/lib/datadog/core/environment/yjit.rb +2 -1
  59. data/lib/datadog/core/metrics/client.rb +2 -2
  60. data/lib/datadog/core/pin.rb +4 -8
  61. data/lib/datadog/core/process_discovery/tracer_memfd.rb +2 -4
  62. data/lib/datadog/core/process_discovery.rb +48 -23
  63. data/lib/datadog/core/remote/component.rb +4 -6
  64. data/lib/datadog/core/runtime/ext.rb +0 -1
  65. data/lib/datadog/core/telemetry/component.rb +11 -0
  66. data/lib/datadog/core/telemetry/emitter.rb +6 -6
  67. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  68. data/lib/datadog/core/telemetry/event/app_started.rb +2 -2
  69. data/lib/datadog/core/telemetry/event.rb +1 -0
  70. data/lib/datadog/core/transport/response.rb +4 -1
  71. data/lib/datadog/core/utils/network.rb +19 -0
  72. data/lib/datadog/core.rb +2 -0
  73. data/lib/datadog/di/boot.rb +5 -3
  74. data/lib/datadog/di/component.rb +14 -0
  75. data/lib/datadog/di/context.rb +70 -0
  76. data/lib/datadog/di/el/compiler.rb +164 -0
  77. data/lib/datadog/di/el/evaluator.rb +159 -0
  78. data/lib/datadog/di/el/expression.rb +42 -0
  79. data/lib/datadog/di/el.rb +5 -0
  80. data/lib/datadog/di/error.rb +25 -0
  81. data/lib/datadog/di/instrumenter.rb +101 -32
  82. data/lib/datadog/di/probe.rb +35 -15
  83. data/lib/datadog/di/probe_builder.rb +39 -1
  84. data/lib/datadog/di/probe_file_loader.rb +1 -1
  85. data/lib/datadog/di/probe_manager.rb +3 -2
  86. data/lib/datadog/di/probe_notification_builder.rb +50 -51
  87. data/lib/datadog/di/serializer.rb +151 -7
  88. data/lib/datadog/opentelemetry/sdk/configurator.rb +1 -1
  89. data/lib/datadog/profiling/collectors/info.rb +1 -1
  90. data/lib/datadog/profiling/ext.rb +2 -1
  91. data/lib/datadog/profiling/http_transport.rb +1 -1
  92. data/lib/datadog/profiling/tasks/exec.rb +2 -2
  93. data/lib/datadog/tracing/component.rb +6 -17
  94. data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
  95. data/lib/datadog/tracing/configuration/ext.rb +0 -3
  96. data/lib/datadog/tracing/configuration/settings.rb +15 -10
  97. data/lib/datadog/tracing/contrib/component.rb +2 -2
  98. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
  99. data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
  100. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +63 -27
  101. data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -0
  102. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +7 -1
  103. data/lib/datadog/tracing/contrib/rails/ext.rb +2 -1
  104. data/lib/datadog/tracing/contrib/rails/integration.rb +1 -1
  105. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +1 -1
  106. data/lib/datadog/tracing/metadata/ext.rb +8 -0
  107. data/lib/datadog/version.rb +1 -1
  108. metadata +25 -9
  109. data/ext/libdatadog_api/macos_development.md +0 -26
@@ -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
- counter = 0
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
@@ -19,7 +19,7 @@ module Datadog
19
19
  # Ensure Datadog-configure propagation styles have are applied when configured.
20
20
  #
21
21
  # DEV: Support configuring propagation through the environment variable
22
- # DEV: `OTEL_PROPAGATORS`, similar to `DD_TRACE_PROPAGATION*`?
22
+ # DEV: `OTEL_PROPAGATORS`, alias to `DD_TRACE_PROPAGATION_STYLE`
23
23
  def configure_propagation
24
24
  @propagators = [Propagator.new(Tracing::Contrib::HTTP)]
25
25
  super
@@ -146,7 +146,7 @@ module Datadog
146
146
  return @gc_tuning_info if defined?(@gc_tuning_info)
147
147
 
148
148
  @gc_tuning_info = RUBY_GC_TUNING_ENV_VARS.each_with_object({}) do |var, hash|
149
- current_value = ENV[var]
149
+ current_value = DATADOG_ENV[var]
150
150
  hash[var.to_sym] = current_value if current_value
151
151
  end.freeze
152
152
  end
@@ -6,9 +6,10 @@ module Datadog
6
6
  ENV_ENABLED = "DD_PROFILING_ENABLED"
7
7
  ENV_UPLOAD_TIMEOUT = "DD_PROFILING_UPLOAD_TIMEOUT"
8
8
  ENV_MAX_FRAMES = "DD_PROFILING_MAX_FRAMES"
9
- ENV_AGENTLESS = "DD_PROFILING_AGENTLESS"
10
9
  ENV_ENDPOINT_COLLECTION_ENABLED = "DD_PROFILING_ENDPOINT_COLLECTION_ENABLED"
11
10
 
11
+ # WARNING: This should not be used, only for internal testing
12
+ ENV_AGENTLESS = "DD_PROFILING_AGENTLESS" # rubocop:disable CustomCops/EnvStringValidationCop
12
13
  module Transport
13
14
  module HTTP
14
15
  FORM_FIELD_TAG_PROFILER_VERSION = "profiler_version"
@@ -56,7 +56,7 @@ module Datadog
56
56
  private
57
57
 
58
58
  def agentless?(site, api_key)
59
- site && api_key && Core::Environment::VariableHelpers.env_to_bool(Profiling::Ext::ENV_AGENTLESS, false)
59
+ site && api_key && %w[1 true].include?(ENV[Profiling::Ext::ENV_AGENTLESS] || '') # rubocop:disable CustomCops/EnvUsageCop
60
60
  end
61
61
 
62
62
  def config_without_api_key
@@ -25,9 +25,9 @@ module Datadog
25
25
  private
26
26
 
27
27
  def set_rubyopt!
28
- existing_rubyopt = ENV["RUBYOPT"]
28
+ existing_rubyopt = ENV["RUBYOPT"] # rubocop:disable CustomCops/EnvUsageCop
29
29
 
30
- ENV["RUBYOPT"] = existing_rubyopt ? "#{existing_rubyopt} #{rubyopts.join(" ")}" : rubyopts.join(" ")
30
+ ENV["RUBYOPT"] = existing_rubyopt ? "#{existing_rubyopt} #{rubyopts.join(" ")}" : rubyopts.join(" ") # rubocop:disable CustomCops/EnvUsageCop
31
31
  end
32
32
 
33
33
  # If there's an error here, rather than throwing a cryptic stack trace, let's instead have clearer messages, and
@@ -12,16 +12,7 @@ module Datadog
12
12
  module Tracing
13
13
  # Tracing component
14
14
  module Component
15
- # Methods that interact with component instance fields.
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).reconfigure_live_sampler
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).reconfigure_live_sampler
82
+ Datadog.send(:components).reconfigure_sampler
83
83
  end
84
84
 
85
85
  protected
@@ -9,7 +9,6 @@ module Datadog
9
9
  # e.g. Env vars, default values, enums, etc...
10
10
  module Ext
11
11
  ENV_ENABLED = 'DD_TRACE_ENABLED'
12
- ENV_OTEL_TRACES_EXPORTER = 'OTEL_TRACES_EXPORTER'
13
12
  ENV_HEADER_TAGS = 'DD_TRACE_HEADER_TAGS'
14
13
  ENV_BAGGAGE_TAG_KEYS = 'DD_TRACE_BAGGAGE_TAG_KEYS'
15
14
  ENV_TRACE_ID_128_BIT_GENERATION_ENABLED = 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED'
@@ -55,7 +54,6 @@ module Datadog
55
54
  # Has lower precedence than `DD_TRACE_PROPAGATION_STYLE_INJECT` or
56
55
  # `DD_TRACE_PROPAGATION_STYLE_EXTRACT`.
57
56
  ENV_PROPAGATION_STYLE = 'DD_TRACE_PROPAGATION_STYLE'
58
- ENV_OTEL_PROPAGATION_STYLE = 'OTEL_PROPAGATORS'
59
57
 
60
58
  ENV_PROPAGATION_STYLE_INJECT = 'DD_TRACE_PROPAGATION_STYLE_INJECT'
61
59
 
@@ -81,7 +79,6 @@ module Datadog
81
79
  ENV_SAMPLE_RATE = 'DD_TRACE_SAMPLE_RATE'
82
80
  ENV_RATE_LIMIT = 'DD_TRACE_RATE_LIMIT'
83
81
  ENV_RULES = 'DD_TRACE_SAMPLING_RULES'
84
- ENV_OTEL_TRACES_SAMPLER = 'OTEL_TRACES_SAMPLER'
85
82
  OTEL_TRACES_SAMPLER_ARG = 'OTEL_TRACES_SAMPLER_ARG'
86
83
 
87
84
  # @public_api
@@ -93,9 +93,10 @@ module Datadog
93
93
  # @return [Array<String>]
94
94
  option :propagation_style do |o|
95
95
  o.type :array
96
- o.env [Configuration::Ext::Distributed::ENV_PROPAGATION_STYLE, Configuration::Ext::Distributed::ENV_OTEL_PROPAGATION_STYLE]
96
+ # Note: Alias (DD_TRACE_PROPAGATION_STYLE) defined in supported-configurations.json
97
+ o.env Configuration::Ext::Distributed::ENV_PROPAGATION_STYLE
97
98
  o.default []
98
- o.after_set do |styles|
99
+ o.after_set do |styles, _, precedence|
99
100
  next if styles.empty?
100
101
 
101
102
  # Make values case-insensitive
@@ -109,8 +110,8 @@ module Datadog
109
110
  false
110
111
  end
111
112
  end
112
- set_option(:propagation_style_extract, styles)
113
- set_option(:propagation_style_inject, styles)
113
+ set_option(:propagation_style_extract, styles, precedence: precedence)
114
+ set_option(:propagation_style_inject, styles, precedence: precedence)
114
115
  end
115
116
  end
116
117
 
@@ -133,13 +134,16 @@ module Datadog
133
134
  # @default `DD_TRACE_ENABLED` environment variable, otherwise `true`
134
135
  # @return [Boolean]
135
136
  option :enabled do |o|
136
- o.env [Tracing::Configuration::Ext::ENV_ENABLED, Tracing::Configuration::Ext::ENV_OTEL_TRACES_EXPORTER]
137
+ # Note: Alias (OTEL_TRACES_EXPORTER) defined in supported-configurations.json
138
+ o.env Tracing::Configuration::Ext::ENV_ENABLED
137
139
  o.default true
138
140
  o.type :bool
139
141
  o.env_parser do |value|
140
142
  value = value&.downcase
141
143
  # Tracing is disabled when OTEL_TRACES_EXPORTER is none or
142
144
  # DD_TRACE_ENABLED is 0 or false.
145
+ # DEV: The current implementation accepts all of the mentioned values
146
+ # for both environment variables, which is incorrect.
143
147
  if ['none', 'false', '0'].include?(value)
144
148
  false
145
149
  # Tracing is enabled when DD_TRACE_ENABLED is true or 1
@@ -302,7 +306,8 @@ module Datadog
302
306
  # @return [Float, nil]
303
307
  option :default_rate do |o|
304
308
  o.type :float, nilable: true
305
- o.env [Tracing::Configuration::Ext::Sampling::ENV_SAMPLE_RATE, Tracing::Configuration::Ext::Sampling::ENV_OTEL_TRACES_SAMPLER]
309
+ # Note: Alias (OTEL_TRACES_SAMPLER) defined in supported-configurations.json
310
+ o.env Tracing::Configuration::Ext::Sampling::ENV_SAMPLE_RATE
306
311
  o.env_parser do |value|
307
312
  # Parse the value as a float
308
313
  next if value.nil?
@@ -321,7 +326,7 @@ module Datadog
321
326
  when 'parentbased_always_off'
322
327
  0.0
323
328
  when 'parentbased_traceidratio'
324
- ENV.fetch(Tracing::Configuration::Ext::Sampling::OTEL_TRACES_SAMPLER_ARG, 1.0).to_f
329
+ DATADOG_ENV.fetch(Configuration::Ext::Sampling::OTEL_TRACES_SAMPLER_ARG, 1.0).to_f
325
330
  else
326
331
  value.to_f
327
332
  end
@@ -355,7 +360,7 @@ module Datadog
355
360
  # @public_api
356
361
  option :rules do |o|
357
362
  o.type :string, nilable: true
358
- o.default { ENV.fetch(Configuration::Ext::Sampling::ENV_RULES, nil) }
363
+ o.default { DATADOG_ENV.fetch(Configuration::Ext::Sampling::ENV_RULES, nil) }
359
364
  end
360
365
 
361
366
  # Single span sampling rules.
@@ -372,8 +377,8 @@ module Datadog
372
377
  option :span_rules do |o|
373
378
  o.type :string, nilable: true
374
379
  o.default do
375
- rules = ENV[Tracing::Configuration::Ext::Sampling::Span::ENV_SPAN_SAMPLING_RULES]
376
- rules_file = ENV[Tracing::Configuration::Ext::Sampling::Span::ENV_SPAN_SAMPLING_RULES_FILE]
380
+ rules = DATADOG_ENV[Tracing::Configuration::Ext::Sampling::Span::ENV_SPAN_SAMPLING_RULES]
381
+ rules_file = DATADOG_ENV[Tracing::Configuration::Ext::Sampling::Span::ENV_SPAN_SAMPLING_RULES_FILE]
377
382
 
378
383
  if rules
379
384
  if rules_file
@@ -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
@@ -50,10 +84,13 @@ module Datadog
50
84
  trace(
51
85
  proc { super },
52
86
  'execute',
53
- query.selected_operation_name,
87
+ operation_resource(query.selected_operation),
54
88
  lambda { |span|
89
+ # Ensure this span can be aggregated by in the Datadog App, and generates RED metrics.
90
+ span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_SERVER)
91
+
55
92
  span.set_tag('graphql.source', query.query_string)
56
- span.set_tag('graphql.operation.type', query.selected_operation.operation_type)
93
+ span.set_tag('graphql.operation.type', query.selected_operation&.operation_type)
57
94
  if query.selected_operation_name
58
95
  span.set_tag(
59
96
  'graphql.operation.name',
@@ -127,8 +164,6 @@ module Datadog
127
164
  resolve_type_span(proc { super }, 'resolve_type_lazy', **kwargs)
128
165
  end
129
166
 
130
- include ::GraphQL::Tracing::PlatformTrace
131
-
132
167
  def platform_field_key(field, *args, **kwargs)
133
168
  field.path
134
169
  end
@@ -153,16 +188,14 @@ module Datadog
153
188
  # @param kwargs [Hash] the arguments to pass to `prepare_span`
154
189
  # @yield [Span] the block to run before the trace, same as the `before` parameter
155
190
  def trace(callable, trace_key, resource, before = nil, after = nil, **kwargs, &before_block)
156
- config = Datadog.configuration.tracing[:graphql]
157
-
158
191
  Tracing.trace(
159
192
  "graphql.#{trace_key}",
160
193
  type: 'graphql',
161
194
  resource: resource,
162
- service: config[:service_name]
195
+ service: @service_name
163
196
  ) do |span|
164
- if Contrib::Analytics.enabled?(config[:analytics_enabled])
165
- Contrib::Analytics.set_sample_rate(span, config[:analytics_sample_rate])
197
+ if Contrib::Analytics.enabled?(@analytics_enabled)
198
+ Contrib::Analytics.set_sample_rate(span, @analytics_sample_rate)
166
199
  end
167
200
 
168
201
  # A sanity check for us.
@@ -194,27 +227,30 @@ module Datadog
194
227
  end
195
228
  end
196
229
 
230
+ def operation_resource(operation)
231
+ if operation&.name
232
+ "#{operation.operation_type} #{operation.name}"
233
+ else
234
+ 'anonymous'
235
+ end
236
+ end
237
+
197
238
  # 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
239
  def add_query_error_events(span, errors)
202
- capture_extensions = Datadog.configuration.tracing[:graphql][:error_extensions]
203
240
  errors.each do |error|
204
- extensions = if !capture_extensions.empty? && (extensions = error.extensions)
241
+ attributes = if !@error_extensions_config.empty? && (extensions = error.extensions)
205
242
  # Capture extensions, ensuring all values are primitives
206
243
  extensions.each_with_object({}) do |(key, value), hash|
207
- next unless capture_extensions.include?(key.to_s)
244
+ next unless @error_extensions_config.include?(key.to_s)
208
245
 
209
246
  value = case value
210
247
  when TrueClass, FalseClass, Integer, Float
211
248
  value
212
249
  else
213
- # Stringify anything that is not a boolean or a number
214
250
  value.to_s
215
251
  end
216
252
 
217
- hash["extensions.#{key}"] = value
253
+ hash[@extensions_key + key.to_s] = value
218
254
  end
219
255
  else
220
256
  {}
@@ -224,16 +260,16 @@ module Datadog
224
260
  # This is an unwritten contract in the `graphql` library.
225
261
  # See for an example: https://github.com/rmosolgo/graphql-ruby/blob/0afa241775e5a113863766cce126214dee093464/lib/graphql/execution_error.rb#L32
226
262
  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'],
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'],
237
273
  )
238
274
  )
239
275
  end
@@ -24,6 +24,7 @@ module Datadog
24
24
  # nginx header is seconds in the format "t=1512379167.574"
25
25
  # apache header is microseconds in the format "t=1570633834463123"
26
26
  # heroku header is milliseconds in the format "1570634024294"
27
+ # @see https://github.com/heroku/vegur/blob/65d168f757e0ddb448f41cfb9e4b0281c747378d/README.md?plain=1#L383-L384
27
28
  time_string = header.to_s.delete('^0-9')
28
29
  return if time_string.nil?
29
30
 
@@ -43,7 +43,13 @@ module Datadog
43
43
  # excluding the time spent processing the request itself
44
44
  queue_span.finish
45
45
 
46
- yield.tap { request_span.finish }
46
+ yield
47
+ ensure
48
+ # Ensure that the spans are finished even if an exception is raised.
49
+ # **This is very important** to prevent the trace from leaking between requests,
50
+ # especially because `queue_span` is normally a root span.
51
+ queue_span&.finish
52
+ request_span&.finish
47
53
  end
48
54
  end
49
55
  end