ddtrace 0.12.0.beta2 → 0.12.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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