ddtrace 0.12.0.beta2 → 0.12.0.rc1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +8 -8
  3. data/CHANGELOG.md +293 -0
  4. data/README.md +11 -114
  5. data/Rakefile +26 -18
  6. data/docs/GettingStarted.md +704 -453
  7. data/gemfiles/contrib.gemfile +2 -2
  8. data/gemfiles/rails4_mysql2.gemfile +1 -1
  9. data/gemfiles/rails5_mysql2.gemfile +2 -2
  10. data/gemfiles/rails5_postgres.gemfile +1 -1
  11. data/gemfiles/rails5_postgres_redis.gemfile +1 -1
  12. data/gemfiles/rails5_postgres_sidekiq.gemfile +1 -1
  13. data/lib/ddtrace.rb +1 -0
  14. data/lib/ddtrace/context.rb +96 -34
  15. data/lib/ddtrace/context_flush.rb +132 -0
  16. data/lib/ddtrace/contrib/active_record/patcher.rb +55 -70
  17. data/lib/ddtrace/contrib/active_record/utils.rb +83 -0
  18. data/lib/ddtrace/contrib/active_support/notifications/subscriber.rb +66 -0
  19. data/lib/ddtrace/contrib/active_support/notifications/subscription.rb +155 -0
  20. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +6 -1
  21. data/lib/ddtrace/contrib/elasticsearch/quantize.rb +89 -0
  22. data/lib/ddtrace/contrib/grape/endpoint.rb +1 -1
  23. data/lib/ddtrace/contrib/racecar/patcher.rb +43 -19
  24. data/lib/ddtrace/contrib/rack/middlewares.rb +58 -11
  25. data/lib/ddtrace/contrib/rack/patcher.rb +18 -11
  26. data/lib/ddtrace/contrib/rails/action_controller.rb +9 -11
  27. data/lib/ddtrace/contrib/rails/action_view.rb +5 -1
  28. data/lib/ddtrace/contrib/rails/active_support.rb +6 -2
  29. data/lib/ddtrace/contrib/rails/core_extensions.rb +280 -215
  30. data/lib/ddtrace/contrib/rails/framework.rb +38 -23
  31. data/lib/ddtrace/contrib/rails/middlewares.rb +7 -2
  32. data/lib/ddtrace/contrib/rails/patcher.rb +9 -6
  33. data/lib/ddtrace/contrib/rails/railtie.rb +4 -2
  34. data/lib/ddtrace/contrib/rails/utils.rb +9 -40
  35. data/lib/ddtrace/patcher.rb +32 -10
  36. data/lib/ddtrace/quantization/http.rb +86 -0
  37. data/lib/ddtrace/tracer.rb +29 -2
  38. data/lib/ddtrace/transport.rb +33 -20
  39. data/lib/ddtrace/version.rb +1 -1
  40. data/lib/ddtrace/writer.rb +11 -5
  41. metadata +8 -3
  42. data/lib/ddtrace/contrib/rails/active_record.rb +0 -80
@@ -1,13 +1,13 @@
1
1
  require 'ddtrace/pin'
2
2
  require 'ddtrace/ext/app_types'
3
3
 
4
+ require 'ddtrace/contrib/active_record/patcher'
4
5
  require 'ddtrace/contrib/grape/endpoint'
5
6
  require 'ddtrace/contrib/rack/middlewares'
6
7
 
7
8
  require 'ddtrace/contrib/rails/core_extensions'
8
9
  require 'ddtrace/contrib/rails/action_controller'
9
10
  require 'ddtrace/contrib/rails/action_view'
10
- require 'ddtrace/contrib/rails/active_record'
11
11
  require 'ddtrace/contrib/rails/active_support'
12
12
  require 'ddtrace/contrib/rails/utils'
13
13
 
@@ -21,38 +21,53 @@ module Datadog
21
21
  module Framework
22
22
  # configure Datadog settings
23
23
  def self.setup
24
- config = Datadog.configuration[:rails]
25
- config[:service_name] ||= Utils.app_name
26
- tracer = config[:tracer]
24
+ config = config_with_defaults
25
+
26
+ activate_rack!(config)
27
+ activate_active_record!(config)
28
+ set_service_info!(config)
29
+
30
+ # By default, default service would be guessed from the script
31
+ # being executed, but here we know better, get it from Rails config.
32
+ config[:tracer].default_service = config[:service_name]
33
+ end
27
34
 
35
+ def self.config_with_defaults
36
+ # We set defaults here instead of in the patcher because we need to wait
37
+ # for the Rails application to be fully initialized.
38
+ Datadog.configuration[:rails].tap do |config|
39
+ config[:service_name] ||= Utils.app_name
40
+ config[:database_service] ||= "#{config[:service_name]}-#{Contrib::ActiveRecord::Utils.adapter_name}"
41
+ config[:controller_service] ||= config[:service_name]
42
+ config[:cache_service] ||= "#{config[:service_name]}-cache"
43
+ end
44
+ end
45
+
46
+ def self.activate_rack!(config)
28
47
  Datadog.configuration.use(
29
48
  :rack,
30
- tracer: tracer,
49
+ tracer: config[:tracer],
50
+ application: ::Rails.application,
31
51
  service_name: config[:service_name],
52
+ middleware_names: config[:middleware_names],
32
53
  distributed_tracing: config[:distributed_tracing]
33
54
  )
34
-
35
- config[:controller_service] ||= config[:service_name]
36
- config[:cache_service] ||= "#{config[:service_name]}-cache"
37
-
38
- tracer.set_service_info(config[:controller_service], 'rails', Ext::AppTypes::WEB)
39
- tracer.set_service_info(config[:cache_service], 'rails', Ext::AppTypes::CACHE)
40
- set_database_service
41
-
42
- # By default, default service would be guessed from the script
43
- # being executed, but here we know better, get it from Rails config.
44
- tracer.default_service = config[:service_name]
45
55
  end
46
56
 
47
- def self.set_database_service
57
+ def self.activate_active_record!(config)
48
58
  return unless defined?(::ActiveRecord)
49
59
 
50
- config = Datadog.configuration[:rails]
51
- adapter_name = Utils.adapter_name
52
- config[:database_service] ||= "#{config[:service_name]}-#{adapter_name}"
53
- config[:tracer].set_service_info(config[:database_service], adapter_name, Ext::AppTypes::DB)
54
- rescue => e
55
- Tracer.log.warn("Unable to get database config (#{e}), skipping ActiveRecord instrumentation")
60
+ Datadog.configuration.use(
61
+ :active_record,
62
+ service_name: config[:database_service],
63
+ tracer: config[:tracer]
64
+ )
65
+ end
66
+
67
+ def self.set_service_info!(config)
68
+ tracer = config[:tracer]
69
+ tracer.set_service_info(config[:controller_service], 'rails', Ext::AppTypes::WEB)
70
+ tracer.set_service_info(config[:cache_service], 'rails', Ext::AppTypes::CACHE)
56
71
  end
57
72
  end
58
73
  end
@@ -22,8 +22,13 @@ module Datadog
22
22
  # SignalException::Interrupt would still bubble up.
23
23
  rescue Exception => e
24
24
  tracer = Datadog.configuration[:rails][:tracer]
25
- span = tracer.active_span()
26
- span.set_error(e) unless span.nil?
25
+ span = tracer.active_span
26
+ unless span.nil?
27
+ # Only set error if it's supposed to be flagged as such
28
+ # e.g. we don't want to flag 404s.
29
+ # You can add custom errors via `config.action_dispatch.rescue_responses`
30
+ span.set_error(e) if Utils.exception_is_error?(e)
31
+ end
27
32
  raise e
28
33
  end
29
34
  end
@@ -1,3 +1,5 @@
1
+ require 'ddtrace/contrib/rails/utils'
2
+
1
3
  module Datadog
2
4
  module Contrib
3
5
  module Rails
@@ -9,7 +11,13 @@ module Datadog
9
11
  option :service_name
10
12
  option :controller_service
11
13
  option :cache_service
12
- option :database_service
14
+ option :database_service, depends_on: [:service_name] do |value|
15
+ value.tap do
16
+ # Update ActiveRecord service name too
17
+ Datadog.configuration[:active_record][:service_name] = value
18
+ end
19
+ end
20
+ option :middleware_names, default: false
13
21
  option :distributed_tracing, default: false
14
22
  option :template_base_path, default: 'views/'
15
23
  option :exception_controller, default: nil
@@ -36,11 +44,6 @@ module Datadog
36
44
 
37
45
  defined?(::Rails::VERSION) && ::Rails::VERSION::MAJOR.to_i >= 3
38
46
  end
39
-
40
- def active_record_instantiation_tracing_supported?
41
- Gem.loaded_specs['activerecord'] \
42
- && Gem.loaded_specs['activerecord'].version >= Gem::Version.new('4.2')
43
- end
44
47
  end
45
48
  end
46
49
  end
@@ -6,13 +6,15 @@ module Datadog
6
6
  # Railtie class initializes
7
7
  class Railtie < Rails::Railtie
8
8
  config.app_middleware.insert_before(0, Datadog::Contrib::Rack::TraceMiddleware)
9
- config.app_middleware.use(Datadog::Contrib::Rails::ExceptionMiddleware)
9
+ # Insert right after Rails exception handling middleware, because if it's before,
10
+ # it catches and swallows the error. If it's too far after, custom middleware can find itself
11
+ # between, and raise exceptions that don't end up getting tagged on the request properly (e.g lost stack trace.)
12
+ config.app_middleware.insert_after(ActionDispatch::ShowExceptions, Datadog::Contrib::Rails::ExceptionMiddleware)
10
13
 
11
14
  config.after_initialize do
12
15
  Datadog::Contrib::Rails::Framework.setup
13
16
  Datadog::Contrib::Rails::ActionController.instrument
14
17
  Datadog::Contrib::Rails::ActionView.instrument
15
- Datadog::Contrib::Rails::ActiveRecord.instrument
16
18
  Datadog::Contrib::Rails::ActiveSupport.instrument
17
19
  end
18
20
  end
@@ -23,21 +23,6 @@ module Datadog
23
23
  return name.to_s
24
24
  end
25
25
 
26
- # TODO: Consider moving this out of Rails.
27
- # Return a canonical name for a type of database
28
- def self.normalize_vendor(vendor)
29
- case vendor
30
- when nil
31
- 'defaultdb'
32
- when 'sqlite3'
33
- 'sqlite'
34
- when 'postgresql'
35
- 'postgres'
36
- else
37
- vendor
38
- end
39
- end
40
-
41
26
  def self.app_name
42
27
  if ::Rails::VERSION::MAJOR >= 4
43
28
  ::Rails.application.class.parent_name.underscore
@@ -46,33 +31,17 @@ module Datadog
46
31
  end
47
32
  end
48
33
 
49
- def self.adapter_name
50
- normalize_vendor(connection_config[:adapter])
51
- end
52
-
53
- def self.database_name
54
- connection_config[:database]
55
- end
56
-
57
- def self.adapter_host
58
- connection_config[:host]
59
- end
60
-
61
- def self.adapter_port
62
- connection_config[:port]
63
- end
64
-
65
- def self.connection_config
66
- @connection_config ||= begin
67
- if defined?(::ActiveRecord::Base.connection_config)
68
- ::ActiveRecord::Base.connection_config
69
- else
70
- ::ActiveRecord::Base.connection_pool.spec.config
71
- end
34
+ def self.exception_is_error?(exception)
35
+ if defined?(::ActionDispatch::ExceptionWrapper)
36
+ # Gets the equivalent status code for the exception (not all are 5XX)
37
+ # You can add custom errors via `config.action_dispatch.rescue_responses`
38
+ status = ::ActionDispatch::ExceptionWrapper.status_code_for_exception(exception.class.name)
39
+ # Only 5XX exceptions are actually errors (e.g. don't flag 404s)
40
+ status.to_s.starts_with?('5')
41
+ else
42
+ true
72
43
  end
73
44
  end
74
-
75
- private_class_method :connection_config
76
45
  end
77
46
  end
78
47
  end
@@ -1,18 +1,40 @@
1
1
  module Datadog
2
2
  # Defines some useful patching methods for integrations
3
3
  module Patcher
4
- module_function
4
+ def self.included(base)
5
+ base.send(:extend, CommonMethods)
6
+ base.send(:include, CommonMethods)
7
+ end
5
8
 
6
- def without_warnings
7
- # This is typically used when monkey patching functions such as
8
- # intialize, which Ruby advices you not to. Use cautiously.
9
- v = $VERBOSE
10
- $VERBOSE = nil
11
- begin
12
- yield
13
- ensure
14
- $VERBOSE = v
9
+ # Defines some common methods for patching, that can be used
10
+ # at the instance, class, or module level.
11
+ module CommonMethods
12
+ def without_warnings
13
+ # This is typically used when monkey patching functions such as
14
+ # intialize, which Ruby advices you not to. Use cautiously.
15
+ v = $VERBOSE
16
+ $VERBOSE = nil
17
+ begin
18
+ yield
19
+ ensure
20
+ $VERBOSE = v
21
+ end
22
+ end
23
+
24
+ def do_once(key = nil)
25
+ # If already done, don't do again
26
+ @done_once ||= {}
27
+ return @done_once[key] if @done_once.key?(key)
28
+
29
+ # Otherwise 'do'
30
+ yield.tap do
31
+ # Then add the key so we don't do again.
32
+ @done_once[key] = true
33
+ end
15
34
  end
16
35
  end
36
+
37
+ # Extend the common methods so they're available as a module function.
38
+ extend(CommonMethods)
17
39
  end
18
40
  end
@@ -0,0 +1,86 @@
1
+ require 'uri'
2
+ require 'set'
3
+
4
+ module Datadog
5
+ module Quantization
6
+ # Quantization for HTTP resources
7
+ module HTTP
8
+ PLACEHOLDER = '?'.freeze
9
+
10
+ module_function
11
+
12
+ def url(url, options = {})
13
+ url!(url, options)
14
+ rescue StandardError
15
+ options[:placeholder] || PLACEHOLDER
16
+ end
17
+
18
+ def url!(url, options = {})
19
+ options ||= {}
20
+
21
+ URI.parse(url).tap do |uri|
22
+ # Format the query string
23
+ if uri.query
24
+ query = query(uri.query, options[:query])
25
+ uri.query = (!query.nil? && query.empty? ? nil : query)
26
+ end
27
+
28
+ # Remove any URI framents
29
+ uri.fragment = nil unless options[:fragment] == :show
30
+ end.to_s
31
+ end
32
+
33
+ def query(query, options = {})
34
+ query!(query, options)
35
+ rescue StandardError
36
+ options[:placeholder] || PLACEHOLDER
37
+ end
38
+
39
+ def query!(query, options = {})
40
+ options ||= {}
41
+ options[:show] = options[:show] || []
42
+ options[:exclude] = options[:exclude] || []
43
+
44
+ # Short circuit if query string is meant to exclude everything
45
+ # or if the query string is meant to include everything
46
+ return '' if options[:exclude] == :all
47
+ return query if options[:show] == :all
48
+
49
+ collect_query(query, uniq: true) do |key, value|
50
+ if options[:exclude].include?(key)
51
+ [nil, nil]
52
+ else
53
+ value = options[:show].include?(key) ? value : nil
54
+ [key, value]
55
+ end
56
+ end
57
+ end
58
+
59
+ # Iterate over each key value pair, yielding to the block given.
60
+ # Accepts :uniq option, which keeps uniq copies of keys without values.
61
+ # e.g. Reduces "foo&bar=bar&bar=bar&foo" to "foo&bar=bar&bar=bar"
62
+ def collect_query(query, options = {})
63
+ return query unless block_given?
64
+ uniq = options[:uniq].nil? ? false : options[:uniq]
65
+ keys = Set.new
66
+
67
+ delims = query.scan(/(^|&|;)/).flatten
68
+ query.split(/[&;]/).collect.with_index do |pairs, i|
69
+ key, value = pairs.split('=', 2)
70
+ key, value = yield(key, value, delims[i])
71
+ if uniq && keys.include?(key)
72
+ ''
73
+ elsif key && value
74
+ "#{delims[i]}#{key}=#{value}"
75
+ elsif key
76
+ "#{delims[i]}#{key}".tap { keys << key }
77
+ else
78
+ ''
79
+ end
80
+ end.join.sub(/^[&;]/, '')
81
+ end
82
+
83
+ private_class_method :collect_query
84
+ end
85
+ end
86
+ end
@@ -5,6 +5,7 @@ require 'pathname'
5
5
 
6
6
  require 'ddtrace/span'
7
7
  require 'ddtrace/context'
8
+ require 'ddtrace/context_flush'
8
9
  require 'ddtrace/provider'
9
10
  require 'ddtrace/logger'
10
11
  require 'ddtrace/writer'
@@ -97,6 +98,8 @@ module Datadog
97
98
  @provider = options.fetch(:context_provider, Datadog::DefaultContextProvider.new)
98
99
  @provider ||= Datadog::DefaultContextProvider.new # @provider should never be nil
99
100
 
101
+ @context_flush = options[:partial_flush] ? Datadog::ContextFlush.new(options) : nil
102
+
100
103
  @mutex = Mutex.new
101
104
  @services = {}
102
105
  @tags = {}
@@ -117,8 +120,13 @@ module Datadog
117
120
  enabled = options.fetch(:enabled, nil)
118
121
  hostname = options.fetch(:hostname, nil)
119
122
  port = options.fetch(:port, nil)
123
+
124
+ # Those are rare "power-user" options.
120
125
  sampler = options.fetch(:sampler, nil)
121
126
  priority_sampling = options[:priority_sampling]
127
+ max_spans_before_partial_flush = options.fetch(:max_spans_before_partial_flush, nil)
128
+ min_spans_before_partial_flush = options.fetch(:min_spans_before_partial_flush, nil)
129
+ partial_flush_timeout = options.fetch(:partial_flush_timeout, nil)
122
130
 
123
131
  @enabled = enabled unless enabled.nil?
124
132
  @sampler = sampler unless sampler.nil?
@@ -130,6 +138,10 @@ module Datadog
130
138
 
131
139
  @writer.transport.hostname = hostname unless hostname.nil?
132
140
  @writer.transport.port = port unless port.nil?
141
+
142
+ @context_flush = Datadog::ContextFlush.new(options) unless min_spans_before_partial_flush.nil? &&
143
+ max_spans_before_partial_flush.nil? &&
144
+ partial_flush_timeout.nil?
133
145
  end
134
146
 
135
147
  # Set the information about the given service. A valid example is:
@@ -295,8 +307,23 @@ module Datadog
295
307
  context = context.context if context.is_a?(Datadog::Span)
296
308
  return if context.nil?
297
309
  trace, sampled = context.get
298
- ready = !trace.nil? && !trace.empty? && sampled
299
- write(trace) if ready
310
+
311
+ # If context flushing is configured...
312
+ if @context_flush
313
+ if sampled
314
+ if trace.nil? || trace.empty?
315
+ @context_flush.each_partial_trace(context) do |t|
316
+ write(t)
317
+ end
318
+ else
319
+ write(trace)
320
+ end
321
+ end
322
+ # Default behavior
323
+ else
324
+ ready = !trace.nil? && !trace.empty? && sampled
325
+ write(trace) if ready
326
+ end
300
327
  end
301
328
 
302
329
  # Return the current active span or +nil+.
@@ -8,6 +8,7 @@ module Datadog
8
8
  # Transport class that handles the spans delivery to the
9
9
  # local trace-agent. The class wraps a Net:HTTP instance
10
10
  # so that the Transport is thread-safe.
11
+ # rubocop:disable Metrics/ClassLength
11
12
  class HTTPTransport
12
13
  attr_accessor :hostname, :port
13
14
  attr_reader :traces_endpoint, :services_endpoint
@@ -21,18 +22,21 @@ module Datadog
21
22
 
22
23
  API = {
23
24
  V4 = 'v0.4'.freeze => {
25
+ version: V4,
24
26
  traces_endpoint: '/v0.4/traces'.freeze,
25
27
  services_endpoint: '/v0.4/services'.freeze,
26
28
  encoder: Encoding::MsgpackEncoder,
27
29
  fallback: 'v0.3'.freeze
28
30
  }.freeze,
29
31
  V3 = 'v0.3'.freeze => {
32
+ version: V3,
30
33
  traces_endpoint: '/v0.3/traces'.freeze,
31
34
  services_endpoint: '/v0.3/services'.freeze,
32
35
  encoder: Encoding::MsgpackEncoder,
33
36
  fallback: 'v0.2'.freeze
34
37
  }.freeze,
35
38
  V2 = 'v0.2'.freeze => {
39
+ version: V2,
36
40
  traces_endpoint: '/v0.2/traces'.freeze,
37
41
  services_endpoint: '/v0.2/services'.freeze,
38
42
  encoder: Encoding::JSONEncoder
@@ -71,34 +75,45 @@ module Datadog
71
75
  case endpoint
72
76
  when :services
73
77
  payload = @encoder.encode_services(data)
74
- status_code = post(@api[:services_endpoint], payload)
78
+ status_code = post(@api[:services_endpoint], payload) do |response|
79
+ process_callback(:services, response)
80
+ end
75
81
  when :traces
76
82
  count = data.length
77
83
  payload = @encoder.encode_traces(data)
78
- status_code = post(@api[:traces_endpoint], payload, count)
84
+ status_code = post(@api[:traces_endpoint], payload, count) do |response|
85
+ process_callback(:traces, response)
86
+ end
79
87
  else
80
88
  Datadog::Tracer.log.error("Unsupported endpoint: #{endpoint}")
81
89
  return nil
82
90
  end
83
91
 
84
- downgrade! && send(endpoint, data) if downgrade?(status_code)
85
-
86
- status_code
92
+ if downgrade?(status_code)
93
+ downgrade!
94
+ send(endpoint, data)
95
+ else
96
+ status_code
97
+ end
87
98
  end
88
99
 
89
100
  # send data to the trace-agent; the method is thread-safe
90
101
  def post(url, data, count = nil)
91
- Datadog::Tracer.log.debug("Sending data from process: #{Process.pid}")
92
- headers = count.nil? ? {} : { TRACE_COUNT_HEADER => count.to_s }
93
- headers = headers.merge(@headers)
94
- request = Net::HTTP::Post.new(url, headers)
95
- request.body = data
96
-
97
- response = Net::HTTP.start(@hostname, @port, read_timeout: TIMEOUT) { |http| http.request(request) }
98
- handle_response(response)
99
- rescue StandardError => e
100
- Datadog::Tracer.log.error(e.message)
101
- 500
102
+ begin
103
+ Datadog::Tracer.log.debug("Sending data from process: #{Process.pid}")
104
+ headers = count.nil? ? {} : { TRACE_COUNT_HEADER => count.to_s }
105
+ headers = headers.merge(@headers)
106
+ request = Net::HTTP::Post.new(url, headers)
107
+ request.body = data
108
+
109
+ response = Net::HTTP.start(@hostname, @port, read_timeout: TIMEOUT) { |http| http.request(request) }
110
+ handle_response(response)
111
+ rescue StandardError => e
112
+ Datadog::Tracer.log.error(e.message)
113
+ 500
114
+ end.tap do
115
+ yield(response) if block_given?
116
+ end
102
117
  end
103
118
 
104
119
  # Downgrade the connection to a compatibility version of the HTTPTransport;
@@ -166,8 +181,6 @@ module Datadog
166
181
  @mutex.synchronize { @count_server_error += 1 }
167
182
  end
168
183
 
169
- process_callback(response)
170
-
171
184
  status_code
172
185
  rescue StandardError => e
173
186
  Datadog::Tracer.log.error(e.message)
@@ -188,10 +201,10 @@ module Datadog
188
201
 
189
202
  private
190
203
 
191
- def process_callback(response)
204
+ def process_callback(action, response)
192
205
  return unless @response_callback && @response_callback.respond_to?(:call)
193
206
 
194
- @response_callback.call(response)
207
+ @response_callback.call(action, response, @api)
195
208
  rescue => e
196
209
  Tracer.log.debug("Error processing callback: #{e}")
197
210
  end