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
@@ -0,0 +1,83 @@
1
+ module Datadog
2
+ module Contrib
3
+ module ActiveRecord
4
+ # Common utilities for Rails
5
+ module Utils
6
+ # Return a canonical name for a type of database
7
+ def self.normalize_vendor(vendor)
8
+ case vendor
9
+ when nil
10
+ 'defaultdb'
11
+ when 'postgresql'
12
+ 'postgres'
13
+ when 'sqlite3'
14
+ 'sqlite'
15
+ else
16
+ vendor
17
+ end
18
+ end
19
+
20
+ def self.adapter_name
21
+ connection_config[:adapter_name]
22
+ end
23
+
24
+ def self.database_name
25
+ connection_config[:database_name]
26
+ end
27
+
28
+ def self.adapter_host
29
+ connection_config[:adapter_host]
30
+ end
31
+
32
+ def self.adapter_port
33
+ connection_config[:adapter_port]
34
+ end
35
+
36
+ def self.connection_config(object_id = nil)
37
+ config = object_id.nil? ? default_connection_config : connection_config_by_id(object_id)
38
+ {
39
+ adapter_name: normalize_vendor(config[:adapter]),
40
+ adapter_host: config[:host],
41
+ adapter_port: config[:port],
42
+ database_name: config[:database]
43
+ }
44
+ end
45
+
46
+ # Attempt to retrieve the connection from an object ID.
47
+ def self.connection_by_id(object_id)
48
+ return nil if object_id.nil?
49
+ ObjectSpace._id2ref(object_id)
50
+ rescue StandardError
51
+ nil
52
+ end
53
+
54
+ # Attempt to retrieve the connection config from an object ID.
55
+ # Typical of ActiveSupport::Notifications `sql.active_record`
56
+ def self.connection_config_by_id(object_id)
57
+ connection = connection_by_id(object_id)
58
+ return {} if connection.nil?
59
+
60
+ if connection.instance_variable_defined?(:@config)
61
+ connection.instance_variable_get(:@config)
62
+ else
63
+ {}
64
+ end
65
+ end
66
+
67
+ def self.default_connection_config
68
+ return @default_connection_config if instance_variable_defined?(:@default_connection_config)
69
+ current_connection_name = if ::ActiveRecord::Base.respond_to?(:connection_specification_name)
70
+ ::ActiveRecord::Base.connection_specification_name
71
+ else
72
+ ::ActiveRecord::Base
73
+ end
74
+
75
+ connection_pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(current_connection_name)
76
+ connection_pool.nil? ? {} : (@default_connection_config = connection_pool.spec.config)
77
+ rescue StandardError
78
+ {}
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,66 @@
1
+ require 'set'
2
+ require 'ddtrace/contrib/active_support/notifications/subscription'
3
+
4
+ module Datadog
5
+ module Contrib
6
+ module ActiveSupport
7
+ module Notifications
8
+ # For classes that listen to ActiveSupport::Notification events.
9
+ # Creates subscriptions that are wrapped with tracing.
10
+ module Subscriber
11
+ def self.included(base)
12
+ base.send(:extend, ClassMethods)
13
+ end
14
+
15
+ # Class methods that are implemented in the inheriting class.
16
+ module ClassMethods
17
+ # Returns a list of subscriptions created for this class.
18
+ def subscriptions
19
+ @subscriptions ||= Set.new
20
+ end
21
+
22
+ # Returns whether subscriptions have been activated, via #subscribe!
23
+ def subscribed?
24
+ subscribed == true
25
+ end
26
+
27
+ protected
28
+
29
+ # Defines a callback for when subscribe! is called.
30
+ # Should contain subscription setup, defined by the inheriting class.
31
+ def on_subscribe(&block)
32
+ @on_subscribe_block = block
33
+ end
34
+
35
+ # Runs the on_subscribe callback once, to activate subscriptions.
36
+ # Should be triggered by the inheriting class.
37
+ def subscribe!
38
+ return subscribed? if subscribed? || on_subscribe_block.nil?
39
+ on_subscribe_block.call
40
+ @subscribed = true
41
+ end
42
+
43
+ # Creates a subscription and immediately activates it.
44
+ def subscribe(pattern, span_name, options = {}, tracer = Datadog.tracer, &block)
45
+ subscription(span_name, options, tracer, &block).tap do |subscription|
46
+ subscription.subscribe(pattern)
47
+ end
48
+ end
49
+
50
+ # Creates a subscription without activating it.
51
+ # Subscription is added to the inheriting class' list of subscriptions.
52
+ def subscription(span_name, options = {}, tracer = Datadog.tracer, &block)
53
+ Subscription.new(tracer, span_name, options, &block).tap do |subscription|
54
+ subscriptions << subscription
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ attr_reader :subscribed, :on_subscribe_block
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,155 @@
1
+ module Datadog
2
+ module Contrib
3
+ module ActiveSupport
4
+ module Notifications
5
+ # An ActiveSupport::Notification subscription that wraps events with tracing.
6
+ class Subscription
7
+ attr_accessor \
8
+ :tracer,
9
+ :span_name,
10
+ :options
11
+
12
+ def initialize(tracer, span_name, options, &block)
13
+ raise ArgumentError, 'Must be given a block!' unless block_given?
14
+ @tracer = tracer
15
+ @span_name = span_name
16
+ @options = options
17
+ @handler = Handler.new(&block)
18
+ @callbacks = Callbacks.new
19
+ end
20
+
21
+ # ActiveSupport 3.x calls this
22
+ def call(name, start, finish, id, payload)
23
+ start_span(name, id, payload, start)
24
+ finish_span(name, id, payload, finish)
25
+ end
26
+
27
+ # ActiveSupport 4+ calls this on start
28
+ def start(name, id, payload)
29
+ start_span(name, id, payload)
30
+ end
31
+
32
+ # ActiveSupport 4+ calls this on finish
33
+ def finish(name, id, payload)
34
+ finish_span(name, id, payload)
35
+ end
36
+
37
+ def before_trace(&block)
38
+ callbacks.add(:before_trace, &block) if block_given?
39
+ end
40
+
41
+ def after_trace(&block)
42
+ callbacks.add(:after_trace, &block) if block_given?
43
+ end
44
+
45
+ def subscribe(pattern)
46
+ return false if subscribers.key?(pattern)
47
+ subscribers[pattern] = ::ActiveSupport::Notifications.subscribe(pattern, self)
48
+ true
49
+ end
50
+
51
+ def unsubscribe(pattern)
52
+ return false unless subscribers.key?(pattern)
53
+ ::ActiveSupport::Notifications.unsubscribe(subscribers[pattern])
54
+ subscribers.delete(pattern)
55
+ true
56
+ end
57
+
58
+ def unsubscribe_all
59
+ return false if subscribers.empty?
60
+ subscribers.keys.each { |pattern| unsubscribe(pattern) }
61
+ true
62
+ end
63
+
64
+ protected
65
+
66
+ attr_reader \
67
+ :handler,
68
+ :callbacks
69
+
70
+ def start_span(name, id, payload, start = nil)
71
+ # Run callbacks
72
+ callbacks.run(name, :before_trace, id, payload, start)
73
+
74
+ # Start a trace
75
+ tracer.trace(@span_name, @options).tap do |span|
76
+ # Assign start time if provided
77
+ span.start_time = start unless start.nil?
78
+ end
79
+ end
80
+
81
+ def finish_span(name, id, payload, finish = nil)
82
+ tracer.active_span.tap do |span|
83
+ # If no active span, return.
84
+ return nil if span.nil?
85
+
86
+ # Run handler for event
87
+ handler.run(span, name, id, payload)
88
+
89
+ # Finish the span
90
+ span.finish(finish)
91
+
92
+ # Run callbacks
93
+ callbacks.run(name, :after_trace, span, id, payload, finish)
94
+ end
95
+ end
96
+
97
+ # Pattern => ActiveSupport:Notifications::Subscribers
98
+ def subscribers
99
+ @subscribers ||= {}
100
+ end
101
+
102
+ # Wrapper for subscription handler
103
+ class Handler
104
+ attr_reader :block
105
+
106
+ def initialize(&block)
107
+ @block = block
108
+ end
109
+
110
+ def run(span, name, id, payload)
111
+ run!(span, name, id, payload)
112
+ rescue StandardError => e
113
+ Datadog::Tracer.log.debug("ActiveSupport::Notifications handler for '#{name}' failed: #{e.message}")
114
+ end
115
+
116
+ def run!(*args)
117
+ @block.call(*args)
118
+ end
119
+ end
120
+
121
+ # Wrapper for subscription callbacks
122
+ class Callbacks
123
+ attr_reader :blocks
124
+
125
+ def initialize
126
+ @blocks = {}
127
+ end
128
+
129
+ def add(key, &block)
130
+ blocks_for(key) << block if block_given?
131
+ end
132
+
133
+ def run(event, key, *args)
134
+ blocks_for(key).each do |callback|
135
+ begin
136
+ callback.call(event, key, *args)
137
+ rescue StandardError => e
138
+ Datadog::Tracer.log.debug(
139
+ "ActiveSupport::Notifications '#{key}' callback for '#{event}' failed: #{e.message}"
140
+ )
141
+ end
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ def blocks_for(key)
148
+ blocks[key] ||= []
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -15,6 +15,7 @@ module Datadog
15
15
  include Base
16
16
  register_as :elasticsearch, auto_patch: true
17
17
  option :service_name, default: SERVICE
18
+ option :quantize, default: {}
18
19
 
19
20
  @patched = false
20
21
 
@@ -89,7 +90,11 @@ module Datadog
89
90
  span.set_tag(METHOD, method)
90
91
  span.set_tag(URL, url)
91
92
  span.set_tag(PARAMS, params) if params
92
- span.set_tag(BODY, body) if body
93
+ if body
94
+ quantize_options = Datadog.configuration[:elasticsearch][:quantize]
95
+ quantized_body = Datadog::Contrib::Elasticsearch::Quantize.format_body(body, quantize_options)
96
+ span.set_tag(BODY, quantized_body)
97
+ end
93
98
  span.set_tag('out.host', host) if host
94
99
  span.set_tag('out.port', port) if port
95
100
 
@@ -3,6 +3,11 @@ module Datadog
3
3
  module Elasticsearch
4
4
  # Quantize contains ES-specific resource quantization tools.
5
5
  module Quantize
6
+ PLACEHOLDER = '?'.freeze
7
+ EXCLUDE_KEYS = [].freeze
8
+ SHOW_KEYS = [:_index, :_type, :_id].freeze
9
+ DEFAULT_OPTIONS = { exclude: EXCLUDE_KEYS, show: SHOW_KEYS }.freeze
10
+
6
11
  ID_REGEXP = %r{\/([0-9]+)([\/\?]|$)}
7
12
  ID_PLACEHOLDER = '/?\2'.freeze
8
13
 
@@ -16,6 +21,90 @@ module Datadog
16
21
  quantized_url = url.gsub(ID_REGEXP, ID_PLACEHOLDER)
17
22
  quantized_url.gsub(INDEX_REGEXP, INDEX_PLACEHOLDER)
18
23
  end
24
+
25
+ def format_body(body, options = {})
26
+ format_body!(body, options)
27
+ rescue StandardError
28
+ PLACEHOLDER
29
+ end
30
+
31
+ def format_body!(body, options = {})
32
+ options = merge_options(DEFAULT_OPTIONS, options)
33
+
34
+ # Determine if bulk query or not, based on content
35
+ statements = body.end_with?("\n") ? body.split("\n") : [body]
36
+
37
+ # Parse each statement and quantize them.
38
+ statements.collect do |string|
39
+ reserialize_json(string) do |obj|
40
+ format_statement(obj, options)
41
+ end
42
+ end.join("\n")
43
+ end
44
+
45
+ def format_statement(statement, options = {})
46
+ return statement if options[:show] == :all
47
+
48
+ case statement
49
+ when Hash
50
+ statement.each_with_object({}) do |(key, value), quantized|
51
+ if options[:show].include?(key.to_sym)
52
+ quantized[key] = value
53
+ elsif !options[:exclude].include?(key.to_sym)
54
+ quantized[key] = format_value(value, options)
55
+ end
56
+ end
57
+ else
58
+ format_value(statement, options)
59
+ end
60
+ end
61
+
62
+ def format_value(value, options = {})
63
+ return value if options[:show] == :all
64
+
65
+ case value
66
+ when Hash
67
+ format_statement(value, options)
68
+ when Array
69
+ # If any are objects, format them.
70
+ if value.any? { |v| v.class <= Hash || v.class <= Array }
71
+ value.collect { |i| format_value(i, options) }
72
+ # Otherwise short-circuit and return single placeholder
73
+ else
74
+ PLACEHOLDER
75
+ end
76
+ else
77
+ PLACEHOLDER
78
+ end
79
+ end
80
+
81
+ def merge_options(original, additional)
82
+ {}.tap do |options|
83
+ # Show
84
+ # If either is :all, value becomes :all
85
+ options[:show] = if original[:show] == :all || additional[:show] == :all
86
+ :all
87
+ else
88
+ (original[:show] || []).dup.concat(additional[:show] || []).uniq
89
+ end
90
+
91
+ # Exclude
92
+ options[:exclude] = (original[:exclude] || []).dup.concat(additional[:exclude] || []).uniq
93
+ end
94
+ end
95
+
96
+ # Parses a JSON object from a string, passes its value
97
+ # to the block provided, and dumps its result back to JSON.
98
+ # If JSON parsing fails, it prints fail_value.
99
+ def reserialize_json(string, fail_value = PLACEHOLDER)
100
+ return string unless block_given?
101
+ begin
102
+ JSON.dump(yield(JSON.parse(string)))
103
+ rescue JSON::ParserError
104
+ # If it can't parse/dump, don't raise an error.
105
+ fail_value
106
+ end
107
+ end
19
108
  end
20
109
  end
21
110
  end
@@ -71,7 +71,7 @@ module Datadog
71
71
  span.resource = resource
72
72
 
73
73
  # set the request span resource if it's a `rack.request` span
74
- request_span = payload[:env][:datadog_rack_request_span]
74
+ request_span = payload[:env][Datadog::Contrib::Rack::TraceMiddleware::RACK_REQUEST_SPAN]
75
75
  if !request_span.nil? && request_span.name == 'rack.request'
76
76
  request_span.resource = resource
77
77
  end
@@ -1,4 +1,5 @@
1
1
  require 'ddtrace/ext/app_types'
2
+ require 'ddtrace/contrib/active_support/notifications/subscriber'
2
3
 
3
4
  module Datadog
4
5
  module Contrib
@@ -6,19 +7,50 @@ module Datadog
6
7
  # Provides instrumentation for `racecar` through ActiveSupport instrumentation signals
7
8
  module Patcher
8
9
  include Base
10
+ include ActiveSupport::Notifications::Subscriber
11
+
9
12
  NAME_MESSAGE = 'racecar.message'.freeze
10
13
  NAME_BATCH = 'racecar.batch'.freeze
11
14
  register_as :racecar
12
- option :tracer, default: Datadog.tracer
13
15
  option :service_name, default: 'racecar'
16
+ option :tracer, default: Datadog.tracer do |value|
17
+ (value || Datadog.tracer).tap do |v|
18
+ # Make sure to update tracers of all subscriptions
19
+ subscriptions.each do |subscription|
20
+ subscription.tracer = v
21
+ end
22
+ end
23
+ end
24
+
25
+ on_subscribe do
26
+ # Subscribe to single messages
27
+ subscription(
28
+ self::NAME_MESSAGE,
29
+ { service: configuration[:service_name] },
30
+ configuration[:tracer],
31
+ &method(:process)
32
+ ).tap do |subscription|
33
+ subscription.before_trace { ensure_clean_context! }
34
+ subscription.subscribe('process_message.racecar')
35
+ end
36
+
37
+ # Subscribe to batch messages
38
+ subscription(
39
+ self::NAME_BATCH,
40
+ { service: configuration[:service_name] },
41
+ configuration[:tracer],
42
+ &method(:process)
43
+ ).tap do |subscription|
44
+ subscription.before_trace { ensure_clean_context! }
45
+ subscription.subscribe('process_batch.racecar')
46
+ end
47
+ end
14
48
 
15
49
  class << self
16
50
  def patch
17
51
  return patched? if patched? || !compatible?
18
52
 
19
- ::ActiveSupport::Notifications.subscribe('process_batch.racecar', self)
20
- ::ActiveSupport::Notifications.subscribe('process_message.racecar', self)
21
-
53
+ subscribe!
22
54
  configuration[:tracer].set_service_info(
23
55
  configuration[:service_name],
24
56
  'racecar',
@@ -33,28 +65,17 @@ module Datadog
33
65
  @patched = false
34
66
  end
35
67
 
36
- def start(event, _, payload)
37
- ensure_clean_context!
38
-
39
- name = event[/message/] ? NAME_MESSAGE : NAME_BATCH
40
- span = configuration[:tracer].trace(name)
68
+ def process(span, event, _, payload)
41
69
  span.service = configuration[:service_name]
42
70
  span.resource = payload[:consumer_class]
71
+
43
72
  span.set_tag('kafka.topic', payload[:topic])
44
73
  span.set_tag('kafka.consumer', payload[:consumer_class])
45
74
  span.set_tag('kafka.partition', payload[:partition])
46
75
  span.set_tag('kafka.offset', payload[:offset]) if payload.key?(:offset)
47
76
  span.set_tag('kafka.first_offset', payload[:first_offset]) if payload.key?(:first_offset)
48
77
  span.set_tag('kafka.message_count', payload[:message_count]) if payload.key?(:message_count)
49
- end
50
-
51
- def finish(_, _, payload)
52
- current_span = configuration[:tracer].call_context.current_span
53
-
54
- return unless current_span
55
-
56
- current_span.set_error(payload[:exception_object]) if payload[:exception_object]
57
- current_span.finish
78
+ span.set_error(payload[:exception_object]) if payload[:exception_object]
58
79
  end
59
80
 
60
81
  private
@@ -67,9 +88,12 @@ module Datadog
67
88
  defined?(::Racecar) && defined?(::ActiveSupport::Notifications)
68
89
  end
69
90
 
91
+ # Context objects are thread-bound.
92
+ # If Racecar re-uses threads, context from a previous trace
93
+ # could leak into the new trace. This "cleans" current context,
94
+ # preventing such a leak.
70
95
  def ensure_clean_context!
71
96
  return unless configuration[:tracer].call_context.current_span
72
-
73
97
  configuration[:tracer].provider.context = Context.new
74
98
  end
75
99
  end