ddtrace 0.43.0 → 0.45.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 (67) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +148 -130
  3. data/.circleci/images/primary/Dockerfile-3.0.0 +73 -0
  4. data/.github/workflows/add-milestone-to-pull-requests.yml +1 -1
  5. data/.simplecov +4 -1
  6. data/Appraisals +215 -14
  7. data/CHANGELOG.md +1048 -377
  8. data/Gemfile +4 -2
  9. data/README.md +1 -0
  10. data/Rakefile +172 -6
  11. data/ddtrace.gemspec +6 -8
  12. data/docker-compose.yml +30 -0
  13. data/docs/GettingStarted.md +112 -14
  14. data/lib/ddtrace.rb +8 -0
  15. data/lib/ddtrace/auto_instrument.rb +3 -0
  16. data/lib/ddtrace/auto_instrument_base.rb +6 -0
  17. data/lib/ddtrace/contrib/action_cable/integration.rb +7 -0
  18. data/lib/ddtrace/contrib/action_pack/integration.rb +7 -0
  19. data/lib/ddtrace/contrib/action_view/event.rb +0 -4
  20. data/lib/ddtrace/contrib/action_view/events/render_partial.rb +1 -0
  21. data/lib/ddtrace/contrib/action_view/events/render_template.rb +1 -0
  22. data/lib/ddtrace/contrib/action_view/integration.rb +7 -0
  23. data/lib/ddtrace/contrib/active_record/integration.rb +7 -0
  24. data/lib/ddtrace/contrib/active_record/utils.rb +67 -21
  25. data/lib/ddtrace/contrib/active_support/integration.rb +7 -1
  26. data/lib/ddtrace/contrib/auto_instrument.rb +48 -0
  27. data/lib/ddtrace/contrib/aws/services.rb +1 -0
  28. data/lib/ddtrace/contrib/configuration/resolvers/pattern_resolver.rb +2 -0
  29. data/lib/ddtrace/contrib/cucumber/integration.rb +5 -0
  30. data/lib/ddtrace/contrib/ethon/easy_patch.rb +6 -5
  31. data/lib/ddtrace/contrib/ethon/ext.rb +1 -0
  32. data/lib/ddtrace/contrib/extensions.rb +27 -1
  33. data/lib/ddtrace/contrib/grape/endpoint.rb +29 -11
  34. data/lib/ddtrace/contrib/grape/ext.rb +1 -0
  35. data/lib/ddtrace/contrib/httpclient/configuration/settings.rb +32 -0
  36. data/lib/ddtrace/contrib/httpclient/ext.rb +17 -0
  37. data/lib/ddtrace/contrib/httpclient/instrumentation.rb +152 -0
  38. data/lib/ddtrace/contrib/httpclient/integration.rb +43 -0
  39. data/lib/ddtrace/contrib/httpclient/patcher.rb +35 -0
  40. data/lib/ddtrace/contrib/httprb/instrumentation.rb +1 -1
  41. data/lib/ddtrace/contrib/patchable.rb +18 -7
  42. data/lib/ddtrace/contrib/qless/configuration/settings.rb +35 -0
  43. data/lib/ddtrace/contrib/qless/ext.rb +20 -0
  44. data/lib/ddtrace/contrib/qless/integration.rb +38 -0
  45. data/lib/ddtrace/contrib/qless/patcher.rb +35 -0
  46. data/lib/ddtrace/contrib/qless/qless_job.rb +72 -0
  47. data/lib/ddtrace/contrib/qless/tracer_cleaner.rb +32 -0
  48. data/lib/ddtrace/contrib/rack/integration.rb +7 -0
  49. data/lib/ddtrace/contrib/rack/middlewares.rb +1 -1
  50. data/lib/ddtrace/contrib/rack/request_queue.rb +6 -1
  51. data/lib/ddtrace/contrib/rails/auto_instrument_railtie.rb +10 -0
  52. data/lib/ddtrace/contrib/rails/utils.rb +4 -0
  53. data/lib/ddtrace/contrib/rake/integration.rb +1 -1
  54. data/lib/ddtrace/contrib/redis/configuration/resolver.rb +3 -1
  55. data/lib/ddtrace/contrib/redis/configuration/settings.rb +5 -0
  56. data/lib/ddtrace/contrib/redis/ext.rb +1 -0
  57. data/lib/ddtrace/contrib/redis/patcher.rb +20 -3
  58. data/lib/ddtrace/contrib/redis/quantize.rb +27 -0
  59. data/lib/ddtrace/contrib/redis/tags.rb +5 -1
  60. data/lib/ddtrace/contrib/rspec/integration.rb +5 -0
  61. data/lib/ddtrace/contrib/sinatra/tracer_middleware.rb +2 -2
  62. data/lib/ddtrace/ext/ci.rb +42 -10
  63. data/lib/ddtrace/ext/git.rb +0 -1
  64. data/lib/ddtrace/propagation/http_propagator.rb +17 -2
  65. data/lib/ddtrace/version.rb +1 -1
  66. data/lib/ddtrace/workers/runtime_metrics.rb +7 -3
  67. metadata +91 -20
@@ -24,6 +24,7 @@ module Datadog
24
24
  end
25
25
 
26
26
  def process(span, _event, _id, payload)
27
+ span.service = configuration[:service_name]
27
28
  span.span_type = Datadog::Ext::HTTP::TEMPLATE
28
29
 
29
30
  if (template_name = Utils.normalize_template_name(payload[:identifier]))
@@ -1,6 +1,7 @@
1
1
  require 'ddtrace/contrib/integration'
2
2
  require 'ddtrace/contrib/action_view/configuration/settings'
3
3
  require 'ddtrace/contrib/action_view/patcher'
4
+ require 'ddtrace/contrib/rails/utils'
4
5
 
5
6
  module Datadog
6
7
  module Contrib
@@ -32,6 +33,12 @@ module Datadog
32
33
  super && version >= MINIMUM_VERSION
33
34
  end
34
35
 
36
+ # enabled by rails integration so should only auto instrument
37
+ # if detected that it is being used without rails
38
+ def auto_instrument?
39
+ !Datadog::Contrib::Rails::Utils.railtie_supported?
40
+ end
41
+
35
42
  def default_configuration
36
43
  Configuration::Settings.new
37
44
  end
@@ -5,6 +5,7 @@ require 'ddtrace/contrib/active_record/events'
5
5
  require 'ddtrace/contrib/active_record/configuration/resolver'
6
6
  require 'ddtrace/contrib/active_record/configuration/settings'
7
7
  require 'ddtrace/contrib/active_record/patcher'
8
+ require 'ddtrace/contrib/rails/utils'
8
9
 
9
10
  module Datadog
10
11
  module Contrib
@@ -29,6 +30,12 @@ module Datadog
29
30
  super && version >= MINIMUM_VERSION
30
31
  end
31
32
 
33
+ # enabled by rails integration so should only auto instrument
34
+ # if detected that it is being used without rails
35
+ def auto_instrument?
36
+ !Datadog::Contrib::Rails::Utils.railtie_supported?
37
+ end
38
+
32
39
  def default_configuration
33
40
  Configuration::Settings.new
34
41
  end
@@ -1,3 +1,5 @@
1
+ require 'ddtrace/ext/runtime'
2
+
1
3
  module Datadog
2
4
  module Contrib
3
5
  module ActiveRecord
@@ -21,42 +23,77 @@ module Datadog
21
23
  connection_config[:port]
22
24
  end
23
25
 
24
- # In newer Rails versions, the `payload` contains both the `connection` and its `object_id` named `connection_id`.
25
- #
26
- # So, if rails is recent we'll have a direct access to the connection.
27
- # Else, we'll find it thanks to the passed `connection_id`.
26
+ # Returns the connection configuration hash from the
27
+ # current connection
28
28
  #
29
- # See this PR for more details: https://github.com/rails/rails/pull/34602
29
+ # Since Rails 6.0, we have direct access to the object,
30
+ # while older versions of Rails only provide us the
31
+ # connection id.
30
32
  #
33
+ # @see https://github.com/rails/rails/pull/34602
31
34
  def self.connection_config(connection = nil, connection_id = nil)
32
35
  return default_connection_config if connection.nil? && connection_id.nil?
33
36
 
34
37
  conn = if !connection.nil?
38
+ # Since Rails 6.0, the connection object
39
+ # is directly available.
35
40
  connection
36
- # Rails 3.0 - 3.2
37
- elsif Gem.loaded_specs['activerecord'].version < Gem::Version.new('4.0')
38
- ::ActiveRecord::Base
39
- .connection_handler
40
- .connection_pools
41
- .values
42
- .flat_map(&:connections)
43
- .find { |c| c.object_id == connection_id }
44
- # Rails 4.2+
45
41
  else
46
- ::ActiveRecord::Base
47
- .connection_handler
48
- .connection_pool_list
49
- .flat_map(&:connections)
50
- .find { |c| c.object_id == connection_id }
42
+ # For Rails < 6.0, only the `connection_id`
43
+ # is available. We have to find the connection
44
+ # object from it.
45
+ connection_from_id(connection_id)
51
46
  end
52
47
 
53
- if conn.instance_variable_defined?(:@config)
48
+ if conn && conn.instance_variable_defined?(:@config)
54
49
  conn.instance_variable_get(:@config)
55
50
  else
56
51
  EMPTY_CONFIG
57
52
  end
58
53
  end
59
54
 
55
+ # DEV: JRuby responds to {ObjectSpace._id2ref}, despite raising an error
56
+ # DEV: when invoked. Thus, we have to explicitly check for Ruby runtime.
57
+ if Datadog::Ext::Runtime::RUBY_ENGINE != 'jruby'
58
+ # CRuby has access to {ObjectSpace._id2ref}, which allows for
59
+ # direct look up of the connection object.
60
+ def self.connection_from_id(connection_id)
61
+ # `connection_id` is the `#object_id` of the
62
+ # connection. We can perform an ObjectSpace
63
+ # lookup to find it.
64
+ #
65
+ # This works not only for ActiveRecord, but for
66
+ # extensions that might have their own connection
67
+ # pool (e.g. https://rubygems.org/gems/makara).
68
+ ObjectSpace._id2ref(connection_id)
69
+ rescue => e
70
+ # Because `connection_id` references a live connection
71
+ # present in the current stack, it is very unlikely that
72
+ # `_id2ref` will fail, but we add this safeguard just
73
+ # in case.
74
+ Datadog.logger.debug(
75
+ "connection_id #{connection_id} does not represent a valid object. " \
76
+ "Cause: #{e.message} Source: #{e.backtrace.first}"
77
+ )
78
+ end
79
+ else
80
+ # JRuby does not enable {ObjectSpace._id2ref} by default,
81
+ # as it has large performance impact:
82
+ # https://github.com/jruby/jruby/wiki/PerformanceTuning/cf155dd9#dont-enable-objectspace
83
+ #
84
+ # This fallback code does not support the makara gem,
85
+ # as its connections don't live in the ActiveRecord
86
+ # connection pool.
87
+ def self.connection_from_id(connection_id)
88
+ ::ActiveRecord::Base
89
+ .connection_handler
90
+ .connection_pool_list
91
+ .flat_map(&:connections)
92
+ .find { |c| c.object_id == connection_id }
93
+ end
94
+ end
95
+
96
+ # @return [Hash]
60
97
  def self.default_connection_config
61
98
  return @default_connection_config if instance_variable_defined?(:@default_connection_config)
62
99
  current_connection_name = if ::ActiveRecord::Base.respond_to?(:connection_specification_name)
@@ -66,10 +103,19 @@ module Datadog
66
103
  end
67
104
 
68
105
  connection_pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(current_connection_name)
69
- connection_pool.nil? ? EMPTY_CONFIG : (@default_connection_config = connection_pool.spec.config)
106
+ connection_pool.nil? ? EMPTY_CONFIG : (@default_connection_config = db_config(connection_pool))
70
107
  rescue StandardError
71
108
  EMPTY_CONFIG
72
109
  end
110
+
111
+ # @return [Hash]
112
+ def self.db_config(connection_pool)
113
+ if ::Rails::VERSION::MAJOR >= 6 && ::Rails::VERSION::MINOR >= 1
114
+ connection_pool.db_config.configuration_hash
115
+ else
116
+ connection_pool.spec.config
117
+ end
118
+ end
73
119
  end
74
120
  end
75
121
  end
@@ -1,8 +1,8 @@
1
1
  require 'ddtrace/contrib/integration'
2
2
  require 'ddtrace/contrib/active_support/configuration/settings'
3
3
  require 'ddtrace/contrib/active_support/patcher'
4
-
5
4
  require 'ddtrace/contrib/active_support/cache/redis'
5
+ require 'ddtrace/contrib/rails/utils'
6
6
 
7
7
  module Datadog
8
8
  module Contrib
@@ -27,6 +27,12 @@ module Datadog
27
27
  super && version >= MINIMUM_VERSION
28
28
  end
29
29
 
30
+ # enabled by rails integration so should only auto instrument
31
+ # if detected that it is being used without rails
32
+ def auto_instrument?
33
+ !Datadog::Contrib::Rails::Utils.railtie_supported?
34
+ end
35
+
30
36
  def default_configuration
31
37
  Configuration::Settings.new
32
38
  end
@@ -0,0 +1,48 @@
1
+ require 'ddtrace'
2
+
3
+ module Datadog
4
+ module Contrib
5
+ # Extensions for auto instrumentation added to the base library
6
+ # AutoInstrumentation enables all integration
7
+ module AutoInstrument
8
+ def self.extended(base)
9
+ base.send(:extend, Patch)
10
+ end
11
+
12
+ # Patch adds method for invoking auto_instrumentation
13
+ module Patch
14
+ def add_auto_instrument
15
+ super
16
+
17
+ if Datadog::Contrib::Rails::Utils.railtie_supported?
18
+ require 'ddtrace/contrib/rails/auto_instrument_railtie'
19
+ else
20
+ AutoInstrument.patch_all
21
+ end
22
+ AutoInstrument.patch_all
23
+ end
24
+ end
25
+
26
+ def self.patch_all
27
+ integrations = []
28
+
29
+ Datadog.registry.each do |integration|
30
+ # some instrumentations are automatically enabled when the `rails` instrumentation is enabled,
31
+ # patching them on their own automatically outside of the rails integration context would
32
+ # cause undesirable service naming, so we exclude them based their auto_instrument? setting.
33
+ # we also don't want to mix rspec/cucumber integration in as rspec is env we run tests in.
34
+ next unless integration.klass.auto_instrument?
35
+ integrations << integration.name
36
+ end
37
+
38
+ Datadog.configure do |c|
39
+ c.reduce_log_verbosity
40
+ # This will activate auto-instrumentation for Rails
41
+ integrations.each do |integration_name|
42
+ c.use integration_name
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -104,6 +104,7 @@ module Datadog
104
104
  States
105
105
  StorageGateway
106
106
  Support
107
+ Textract
107
108
  WAF
108
109
  WAFRegional
109
110
  WorkDocs
@@ -8,6 +8,8 @@ module Datadog
8
8
  # Matches strings against Regexps.
9
9
  class PatternResolver < Datadog::Contrib::Configuration::Resolver
10
10
  def resolve(name)
11
+ return if patterns.empty?
12
+
11
13
  # Try to find a matching pattern
12
14
  matching_pattern = patterns.find do |pattern|
13
15
  if pattern.is_a?(Proc)
@@ -27,6 +27,11 @@ module Datadog
27
27
  super && version >= MINIMUM_VERSION
28
28
  end
29
29
 
30
+ # test environments should not auto instrument test libraries
31
+ def auto_instrument?
32
+ false
33
+ end
34
+
30
35
  def default_configuration
31
36
  Configuration::Settings.new
32
37
  end
@@ -99,7 +99,7 @@ module Datadog
99
99
 
100
100
  def datadog_tag_request
101
101
  span = @datadog_span
102
- method = 'N/A'
102
+ method = Ext::NOT_APPLICABLE_METHOD
103
103
  if instance_variable_defined?(:@datadog_method) && !@datadog_method.nil?
104
104
  method = @datadog_method.to_s
105
105
  end
@@ -109,11 +109,12 @@ module Datadog
109
109
  # Set analytics sample rate
110
110
  Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
111
111
 
112
- return unless uri
113
- span.set_tag(Datadog::Ext::HTTP::URL, uri.path)
112
+ this_uri = uri
113
+ return unless this_uri
114
+ span.set_tag(Datadog::Ext::HTTP::URL, this_uri.path)
114
115
  span.set_tag(Datadog::Ext::HTTP::METHOD, method)
115
- span.set_tag(Datadog::Ext::NET::TARGET_HOST, uri.host)
116
- span.set_tag(Datadog::Ext::NET::TARGET_PORT, uri.port)
116
+ span.set_tag(Datadog::Ext::NET::TARGET_HOST, this_uri.host)
117
+ span.set_tag(Datadog::Ext::NET::TARGET_PORT, this_uri.port)
117
118
  end
118
119
 
119
120
  def set_span_error_message(message)
@@ -12,6 +12,7 @@ module Datadog
12
12
  SERVICE_NAME = 'ethon'.freeze
13
13
  SPAN_REQUEST = 'ethon.request'.freeze
14
14
  SPAN_MULTI_REQUEST = 'ethon.multi.request'.freeze
15
+ NOT_APPLICABLE_METHOD = 'N/A'.freeze
15
16
  end
16
17
  end
17
18
  end
@@ -27,8 +27,26 @@ module Datadog
27
27
 
28
28
  # Activate integrations
29
29
  if target.respond_to?(:integrations_pending_activation)
30
+ reduce_verbosity = target.respond_to?(:reduce_verbosity?) ? target.reduce_verbosity? : false
30
31
  target.integrations_pending_activation.each do |integration|
31
- integration.patch if integration.respond_to?(:patch)
32
+ next unless integration.respond_to?(:patch)
33
+ # integration.patch returns either true or a hash of details on why patching failed
34
+ patch_results = integration.patch
35
+
36
+ next if patch_results == true
37
+
38
+ # if patching failed, only log output if verbosity is unset
39
+ # or if patching failure is due to compatibility or integration specific reasons
40
+ next unless !reduce_verbosity ||
41
+ ((patch_results[:available] && patch_results[:loaded]) &&
42
+ (!patch_results[:compatible] || !patch_results[:patchable]))
43
+
44
+ desc = "Available?: #{patch_results[:available]}"
45
+ desc += ", Loaded? #{patch_results[:loaded]}"
46
+ desc += ", Compatible? #{patch_results[:compatible]}"
47
+ desc += ", Patchable? #{patch_results[:patchable]}"
48
+
49
+ Datadog.logger.warn("Unable to patch #{patch_results['name']} (#{desc})")
32
50
  end
33
51
 
34
52
  target.integrations_pending_activation.clear
@@ -86,6 +104,14 @@ module Datadog
86
104
  registry[name] ||
87
105
  raise(InvalidIntegrationError, "'#{name}' is not a valid integration.")
88
106
  end
107
+
108
+ def reduce_verbosity?
109
+ defined?(@reduce_verbosity) ? @reduce_verbosity : false
110
+ end
111
+
112
+ def reduce_log_verbosity
113
+ @reduce_verbosity ||= true
114
+ end
89
115
  end
90
116
  end
91
117
  end
@@ -61,17 +61,12 @@ module Datadog
61
61
  begin
62
62
  # collect endpoint details
63
63
  api = payload[:endpoint].options[:for]
64
- # If the API inherits from Grape::API in version >= 1.2.0
65
- # then the API will be an instance and the name must be derived from the base.
66
- # See https://github.com/ruby-grape/grape/issues/1825
67
- api_view = if defined?(::Grape::API::Instance) && api <= ::Grape::API::Instance
68
- api.base.to_s
69
- else
70
- api.to_s
71
- end
72
-
73
- path = payload[:endpoint].options[:path].join('/')
74
- resource = "#{api_view}##{path}"
64
+
65
+ api_view = api_view(api)
66
+
67
+ request_method = payload[:endpoint].options[:method].first
68
+ path = endpoint_expand_path(payload[:endpoint])
69
+ resource = "#{api_view} #{request_method} #{path}"
75
70
  span.resource = resource
76
71
 
77
72
  # set the request span resource if it's a `rack.request` span
@@ -97,6 +92,10 @@ module Datadog
97
92
  # override the current span with this notification values
98
93
  span.set_tag(Ext::TAG_ROUTE_ENDPOINT, api_view) unless api_view.nil?
99
94
  span.set_tag(Ext::TAG_ROUTE_PATH, path)
95
+ span.set_tag(Ext::TAG_ROUTE_METHOD, request_method)
96
+
97
+ span.set_tag(Datadog::Ext::HTTP::METHOD, request_method)
98
+ span.set_tag(Datadog::Ext::HTTP::URL, path)
100
99
  ensure
101
100
  span.start(start)
102
101
  span.finish(finish)
@@ -187,6 +186,25 @@ module Datadog
187
186
 
188
187
  private
189
188
 
189
+ def api_view(api)
190
+ # If the API inherits from Grape::API in version >= 1.2.0
191
+ # then the API will be an instance and the name must be derived from the base.
192
+ # See https://github.com/ruby-grape/grape/issues/1825
193
+ if defined?(::Grape::API::Instance) && api <= ::Grape::API::Instance
194
+ api.base.to_s
195
+ else
196
+ api.to_s
197
+ end
198
+ end
199
+
200
+ def endpoint_expand_path(endpoint)
201
+ route_path = endpoint.options[:path]
202
+ namespace = endpoint.routes.first && endpoint.routes.first.namespace || ''
203
+
204
+ parts = (namespace.split('/') + route_path).reject { |p| p.blank? || p.eql?('/') }
205
+ parts.join('/').prepend('/')
206
+ end
207
+
190
208
  def tracer
191
209
  datadog_configuration[:tracer]
192
210
  end
@@ -16,6 +16,7 @@ module Datadog
16
16
  TAG_FILTER_TYPE = 'grape.filter.type'.freeze
17
17
  TAG_ROUTE_ENDPOINT = 'grape.route.endpoint'.freeze
18
18
  TAG_ROUTE_PATH = 'grape.route.path'.freeze
19
+ TAG_ROUTE_METHOD = 'grape.route.method'.freeze
19
20
  end
20
21
  end
21
22
  end
@@ -0,0 +1,32 @@
1
+ require 'ddtrace/contrib/configuration/settings'
2
+ require 'ddtrace/contrib/httpclient/ext'
3
+
4
+ module Datadog
5
+ module Contrib
6
+ module Httpclient
7
+ module Configuration
8
+ # Custom settings for the Httpclient integration
9
+ class Settings < Contrib::Configuration::Settings
10
+ option :enabled do |o|
11
+ o.default { env_to_bool(Ext::ENV_ENABLED, true) }
12
+ o.lazy
13
+ end
14
+
15
+ option :analytics_enabled do |o|
16
+ o.default { env_to_bool([Ext::ENV_ANALYTICS_ENABLED, Ext::ENV_ANALYTICS_ENABLED_OLD], false) }
17
+ o.lazy
18
+ end
19
+
20
+ option :analytics_sample_rate do |o|
21
+ o.default { env_to_float([Ext::ENV_ANALYTICS_SAMPLE_RATE, Ext::ENV_ANALYTICS_SAMPLE_RATE_OLD], 1.0) }
22
+ o.lazy
23
+ end
24
+
25
+ option :distributed_tracing, default: true
26
+ option :service_name, default: Ext::SERVICE_NAME
27
+ option :split_by_domain, default: false
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end