ddtrace 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.env +3 -1
  3. data/.gitignore +1 -0
  4. data/Appraisals +10 -0
  5. data/Rakefile +27 -1
  6. data/ddtrace.gemspec +2 -2
  7. data/docker-compose.yml +10 -0
  8. data/docs/GettingStarted.md +119 -0
  9. data/gemfiles/contrib.gemfile +5 -0
  10. data/gemfiles/contrib_old.gemfile +4 -0
  11. data/lib/ddtrace.rb +4 -11
  12. data/lib/ddtrace/buffer.rb +14 -0
  13. data/lib/ddtrace/contrib/aws/instrumentation.rb +43 -0
  14. data/lib/ddtrace/contrib/aws/parsed_context.rb +56 -0
  15. data/lib/ddtrace/contrib/aws/patcher.rb +56 -0
  16. data/lib/ddtrace/contrib/aws/services.rb +115 -0
  17. data/lib/ddtrace/contrib/dalli/instrumentation.rb +35 -0
  18. data/lib/ddtrace/contrib/dalli/patcher.rb +50 -0
  19. data/lib/ddtrace/contrib/dalli/quantize.rb +17 -0
  20. data/lib/ddtrace/contrib/faraday/middleware.rb +75 -0
  21. data/lib/ddtrace/contrib/faraday/patcher.rb +52 -0
  22. data/lib/ddtrace/contrib/mongodb/parsers.rb +57 -0
  23. data/lib/ddtrace/contrib/mongodb/patcher.rb +93 -0
  24. data/lib/ddtrace/contrib/mongodb/subscribers.rb +71 -0
  25. data/lib/ddtrace/contrib/rails/action_controller.rb +18 -19
  26. data/lib/ddtrace/contrib/rails/action_view.rb +51 -61
  27. data/lib/ddtrace/contrib/rails/active_support.rb +29 -73
  28. data/lib/ddtrace/contrib/rails/core_extensions.rb +191 -53
  29. data/lib/ddtrace/contrib/redis/quantize.rb +4 -6
  30. data/lib/ddtrace/contrib/resque/patcher.rb +38 -0
  31. data/lib/ddtrace/contrib/resque/resque_job.rb +31 -0
  32. data/lib/ddtrace/contrib/sucker_punch/exception_handler.rb +26 -0
  33. data/lib/ddtrace/contrib/sucker_punch/instrumentation.rb +60 -0
  34. data/lib/ddtrace/contrib/sucker_punch/patcher.rb +50 -0
  35. data/lib/ddtrace/ext/http.rb +1 -0
  36. data/lib/ddtrace/ext/mongo.rb +12 -0
  37. data/lib/ddtrace/monkey.rb +18 -0
  38. data/lib/ddtrace/pipeline.rb +46 -0
  39. data/lib/ddtrace/pipeline/span_filter.rb +38 -0
  40. data/lib/ddtrace/pipeline/span_processor.rb +20 -0
  41. data/lib/ddtrace/tracer.rb +18 -0
  42. data/lib/ddtrace/utils.rb +23 -3
  43. data/lib/ddtrace/version.rb +2 -2
  44. data/lib/ddtrace/workers.rb +30 -22
  45. data/lib/ddtrace/writer.rb +5 -7
  46. metadata +30 -9
@@ -1,50 +1,170 @@
1
1
  module Datadog
2
2
  # RailsRendererPatcher contains function to patch Rails rendering libraries.
3
+ # rubocop:disable Lint/RescueException
4
+ # rubocop:disable Metrics/MethodLength
5
+ # rubocop:disable Metrics/BlockLength
3
6
  module RailsRendererPatcher
4
7
  module_function
5
8
 
6
9
  def patch_renderer
7
- patch_renderer_render_template
8
- patch_renderer_render_partial
10
+ if defined?(::ActionView::TemplateRenderer) && defined?(::ActionView::PartialRenderer)
11
+ patch_template_renderer(::ActionView::TemplateRenderer)
12
+ patch_partial_renderer(::ActionView::PartialRenderer)
13
+ elsif defined?(::ActionView::Rendering) && defined?(::ActionView::Partials::PartialRenderer)
14
+ # NOTE: Rails < 3.1 compatibility: different classes are used
15
+ patch_template_renderer(::ActionView::Rendering)
16
+ patch_partial_renderer(::ActionView::Partials::PartialRenderer)
17
+ else
18
+ Datadog::Tracer.log.debug('Expected Template/Partial classes not found; template rendering disabled')
19
+ end
9
20
  end
10
21
 
11
- def patch_renderer_render_template
12
- if defined?(::ActionView::Renderer)
13
- ::ActionView::Renderer.class_eval do
14
- alias_method :render_template_without_datadog, :render_template
15
- def render_template(*args, &block)
16
- ActiveSupport::Notifications.instrument('start_render_template.action_view')
17
- render_template_without_datadog(*args, &block)
22
+ def patch_template_renderer(klass)
23
+ klass.class_eval do
24
+ def render_with_datadog(*args, &block)
25
+ # create a tracing context and start the rendering span
26
+ # NOTE: Rails < 3.1 compatibility: preserve the tracing
27
+ # context when a partial is rendered
28
+ @tracing_context ||= {}
29
+ if @tracing_context.empty?
30
+ ::ActiveSupport::Notifications.instrument(
31
+ '!datadog.start_render_template.action_view',
32
+ tracing_context: @tracing_context
33
+ )
18
34
  end
35
+ render_without_datadog(*args)
36
+ rescue Exception => e
37
+ # attach the exception to the tracing context if any
38
+ @tracing_context[:exception] = e
39
+ raise e
40
+ ensure
41
+ # ensure that the template `Span` is finished even during exceptions
42
+ ::ActiveSupport::Notifications.instrument(
43
+ '!datadog.finish_render_template.action_view',
44
+ tracing_context: @tracing_context
45
+ )
19
46
  end
20
- else # Rails < 3.1
21
- ::ActionView::Template.class_eval do
22
- alias_method :render_template_without_datadog, :render
23
- def render(*args, &block)
24
- ActiveSupport::Notifications.instrument('start_render_template.action_view')
25
- render_template_without_datadog(*args, &block)
47
+
48
+ def render_template_with_datadog(*args)
49
+ begin
50
+ # arguments based on render_template signature (stable since Rails 3.2)
51
+ template = args[0]
52
+ layout_name = args[1]
53
+
54
+ # update the tracing context with computed values before the rendering
55
+ template_name = template.try('identifier')
56
+ template_name = Datadog::Contrib::Rails::Utils.normalize_template_name(template_name)
57
+ layout = if layout_name.is_a?(String)
58
+ # NOTE: Rails < 3.1 compatibility: the second argument is the layout name
59
+ layout_name
60
+ else
61
+ layout_name.try(:[], 'virtual_path')
62
+ end
63
+ @tracing_context[:template_name] = template_name
64
+ @tracing_context[:layout] = layout
65
+ rescue StandardError => e
66
+ Datadog::Tracer.log.debug(e.message)
26
67
  end
68
+
69
+ # execute the original function anyway
70
+ render_template_without_datadog(*args)
71
+ end
72
+
73
+ # method aliasing to patch the class
74
+ alias_method :render_without_datadog, :render
75
+ alias_method :render, :render_with_datadog
76
+
77
+ if klass.private_method_defined?(:render_template) || klass.method_defined?(:render_template)
78
+ alias_method :render_template_without_datadog, :render_template
79
+ alias_method :render_template, :render_template_with_datadog
80
+ else
81
+ # NOTE: Rails < 3.1 compatibility: the method name is different
82
+ alias_method :render_template_without_datadog, :_render_template
83
+ alias_method :_render_template, :render_template_with_datadog
27
84
  end
28
85
  end
29
86
  end
30
87
 
31
- def patch_renderer_render_partial
32
- if defined?(::ActionView::PartialRenderer)
33
- ::ActionView::PartialRenderer.class_eval do
34
- alias_method :render_partial_without_datadog, :render_partial
35
- def render_partial(*args, &block)
36
- ActiveSupport::Notifications.instrument('start_render_partial.action_view')
37
- render_partial_without_datadog(*args, &block)
88
+ def patch_partial_renderer(klass)
89
+ klass.class_eval do
90
+ def render_with_datadog(*args, &block)
91
+ # create a tracing context and start the rendering span
92
+ @tracing_context = {}
93
+ ::ActiveSupport::Notifications.instrument(
94
+ '!datadog.start_render_partial.action_view',
95
+ tracing_context: @tracing_context
96
+ )
97
+ render_without_datadog(*args)
98
+ rescue Exception => e
99
+ # attach the exception to the tracing context if any
100
+ @tracing_context[:exception] = e
101
+ raise e
102
+ ensure
103
+ # ensure that the template `Span` is finished even during exceptions
104
+ ::ActiveSupport::Notifications.instrument(
105
+ '!datadog.finish_render_partial.action_view',
106
+ tracing_context: @tracing_context
107
+ )
108
+ end
109
+
110
+ def render_partial_with_datadog(*args)
111
+ begin
112
+ # update the tracing context with computed values before the rendering
113
+ template_name = Datadog::Contrib::Rails::Utils.normalize_template_name(@template.try('identifier'))
114
+ @tracing_context[:template_name] = template_name
115
+ rescue StandardError => e
116
+ Datadog::Tracer.log.debug(e.message)
38
117
  end
118
+
119
+ # execute the original function anyway
120
+ render_partial_without_datadog(*args)
39
121
  end
40
- else # Rails < 3.1
41
- ::ActionView::Partials::PartialRenderer.class_eval do
42
- alias_method :render_partial_without_datadog, :render
43
- def render(*args, &block)
44
- ActiveSupport::Notifications.instrument('start_render_partial.action_view')
45
- render_partial_without_datadog(*args, &block)
122
+
123
+ # method aliasing to patch the class
124
+ alias_method :render_without_datadog, :render
125
+ alias_method :render, :render_with_datadog
126
+ alias_method :render_partial_without_datadog, :render_partial
127
+ alias_method :render_partial, :render_partial_with_datadog
128
+ end
129
+ end
130
+ end
131
+
132
+ # RailsActionPatcher contains functions to patch Rails action controller instrumentation
133
+ module RailsActionPatcher
134
+ module_function
135
+
136
+ def patch_action_controller
137
+ patch_process_action
138
+ end
139
+
140
+ def patch_process_action
141
+ ::ActionController::Instrumentation.class_eval do
142
+ def process_action_with_datadog(*args)
143
+ # mutable payload with a tracing context that is used in two different
144
+ # signals; it propagates the request span so that it can be finished
145
+ # no matter what
146
+ raw_payload = {
147
+ controller: self.class.name,
148
+ action: action_name,
149
+ tracing_context: {}
150
+ }
151
+
152
+ # emits two different signals that start and finish the trace; this approach
153
+ # mimics the original behavior that is available since Rails 3.0:
154
+ # - https://github.com/rails/rails/blob/3-0-stable/actionpack/lib/action_controller/metal/instrumentation.rb#L17-L35
155
+ # - https://github.com/rails/rails/blob/5-1-stable/actionpack/lib/action_controller/metal/instrumentation.rb#L17-L39
156
+ ActiveSupport::Notifications.instrument('!datadog.start_processing.action_controller', raw_payload)
157
+
158
+ # process the request and finish the trace
159
+ ActiveSupport::Notifications.instrument('!datadog.finish_processing.action_controller', raw_payload) do |payload|
160
+ result = process_action_without_datadog(*args)
161
+ payload[:status] = response.status
162
+ result
46
163
  end
47
164
  end
165
+
166
+ alias_method :process_action_without_datadog, :process_action
167
+ alias_method :process_action, :process_action_with_datadog
48
168
  end
49
169
  end
50
170
  end
@@ -58,7 +178,6 @@ module Datadog
58
178
  patch_cache_store_fetch
59
179
  patch_cache_store_write
60
180
  patch_cache_store_delete
61
- patch_cache_store_instrument
62
181
  end
63
182
 
64
183
  def cache_store_class(k)
@@ -81,8 +200,17 @@ module Datadog
81
200
  cache_store_class(:read).class_eval do
82
201
  alias_method :read_without_datadog, :read
83
202
  def read(*args, &block)
84
- ActiveSupport::Notifications.instrument('start_cache_read.active_support')
85
- read_without_datadog(*args, &block)
203
+ raw_payload = {
204
+ action: 'GET',
205
+ key: args[0],
206
+ tracing_context: {}
207
+ }
208
+
209
+ ActiveSupport::Notifications.instrument('!datadog.start_cache_tracing.active_support', raw_payload)
210
+
211
+ ActiveSupport::Notifications.instrument('!datadog.finish_cache_tracing.active_support', raw_payload) do
212
+ read_without_datadog(*args, &block)
213
+ end
86
214
  end
87
215
  end
88
216
  end
@@ -91,8 +219,17 @@ module Datadog
91
219
  cache_store_class(:fetch).class_eval do
92
220
  alias_method :fetch_without_datadog, :fetch
93
221
  def fetch(*args, &block)
94
- ActiveSupport::Notifications.instrument('start_cache_fetch.active_support')
95
- fetch_without_datadog(*args, &block)
222
+ raw_payload = {
223
+ action: 'GET',
224
+ key: args[0],
225
+ tracing_context: {}
226
+ }
227
+
228
+ ActiveSupport::Notifications.instrument('!datadog.start_cache_tracing.active_support', raw_payload)
229
+
230
+ ActiveSupport::Notifications.instrument('!datadog.finish_cache_tracing.active_support', raw_payload) do
231
+ fetch_without_datadog(*args, &block)
232
+ end
96
233
  end
97
234
  end
98
235
  end
@@ -101,8 +238,17 @@ module Datadog
101
238
  cache_store_class(:write).class_eval do
102
239
  alias_method :write_without_datadog, :write
103
240
  def write(*args, &block)
104
- ActiveSupport::Notifications.instrument('start_cache_write.active_support')
105
- write_without_datadog(*args, &block)
241
+ raw_payload = {
242
+ action: 'SET',
243
+ key: args[0],
244
+ tracing_context: {}
245
+ }
246
+
247
+ ActiveSupport::Notifications.instrument('!datadog.start_cache_tracing.active_support', raw_payload)
248
+
249
+ ActiveSupport::Notifications.instrument('!datadog.finish_cache_tracing.active_support', raw_payload) do
250
+ write_without_datadog(*args, &block)
251
+ end
106
252
  end
107
253
  end
108
254
  end
@@ -111,25 +257,17 @@ module Datadog
111
257
  cache_store_class(:delete).class_eval do
112
258
  alias_method :delete_without_datadog, :delete
113
259
  def delete(*args, &block)
114
- ActiveSupport::Notifications.instrument('start_cache_delete.active_support')
115
- delete_without_datadog(*args, &block)
116
- end
117
- end
118
- end
260
+ raw_payload = {
261
+ action: 'DELETE',
262
+ key: args[0],
263
+ tracing_context: {}
264
+ }
119
265
 
120
- def patch_cache_store_instrument
121
- # by default, Rails 3 doesn't instrument the cache system so we should turn it on
122
- # using the ActiveSupport::Cache::Store.instrument= function. Unfortunately, early
123
- # versions of Rails use a Thread.current store that is not compatible with some
124
- # application servers like Passenger.
125
- # More details: https://github.com/rails/rails/blob/v3.2.22.5/activesupport/lib/active_support/cache.rb#L175-L177
126
- return unless ::Rails::VERSION::MAJOR.to_i == 3
127
- ::ActiveSupport::Cache::Store.singleton_class.class_eval do
128
- # Add the instrument function that Rails 3.x uses
129
- # to know if the underlying cache should be instrumented or not. By default,
130
- # we force that instrumentation if the Rails application is auto instrumented.
131
- def instrument
132
- true
266
+ ActiveSupport::Notifications.instrument('!datadog.start_cache_tracing.active_support', raw_payload)
267
+
268
+ ActiveSupport::Notifications.instrument('!datadog.finish_cache_tracing.active_support', raw_payload) do
269
+ delete_without_datadog(*args, &block)
270
+ end
133
271
  end
134
272
  end
135
273
  end
@@ -11,18 +11,16 @@ module Datadog
11
11
  module_function
12
12
 
13
13
  def format_arg(arg)
14
- a = arg.to_s
15
- a = a[0..(VALUE_MAX_LEN - TOO_LONG_MARK.length - 1)] + TOO_LONG_MARK if a.length > VALUE_MAX_LEN
16
- a
14
+ str = arg.to_s
15
+ Utils.truncate(str, VALUE_MAX_LEN, TOO_LONG_MARK)
17
16
  rescue StandardError => e
18
- Datadog::Tracer.log.debug("non formattable Redis arg #{a}: #{e}")
17
+ Datadog::Tracer.log.debug("non formattable Redis arg #{str}: #{e}")
19
18
  PLACEHOLDER
20
19
  end
21
20
 
22
21
  def format_command_args(command_args)
23
22
  cmd = command_args.map { |x| format_arg(x) }.join(' ')
24
- cmd = cmd[0..(CMD_MAX_LEN - TOO_LONG_MARK.length - 1)] + TOO_LONG_MARK if cmd.length > CMD_MAX_LEN
25
- cmd
23
+ Utils.truncate(cmd, CMD_MAX_LEN, TOO_LONG_MARK)
26
24
  end
27
25
  end
28
26
  end
@@ -0,0 +1,38 @@
1
+ module Datadog
2
+ module Contrib
3
+ module Resque
4
+ SERVICE = 'resque'.freeze
5
+
6
+ # Patcher for Resque integration - sets up the pin for the integration
7
+ module Patcher
8
+ @patched = false
9
+
10
+ class << self
11
+ def patch
12
+ return @patched if patched? || !defined?(::Resque)
13
+
14
+ require 'ddtrace/ext/app_types'
15
+
16
+ add_pin
17
+ @patched = true
18
+ rescue => e
19
+ Tracer.log.error("Unable to apply Resque integration: #{e}")
20
+ @patched
21
+ end
22
+
23
+ def patched?
24
+ @patched
25
+ end
26
+
27
+ private
28
+
29
+ def add_pin
30
+ Pin.new(SERVICE, app_type: Ext::AppTypes::WORKER).tap do |pin|
31
+ pin.onto(::Resque)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ require 'ddtrace/ext/app_types'
2
+ require 'resque'
3
+
4
+ module Datadog
5
+ module Contrib
6
+ module Resque
7
+ # Uses Resque job hooks to create traces
8
+ module ResqueJob
9
+ def around_perform(*args)
10
+ pin = Pin.get_from(::Resque)
11
+ pin.tracer.trace('resque.job', service: pin.service) do |span|
12
+ span.resource = name
13
+ span.span_type = pin.app_type
14
+ yield
15
+ span.service = pin.service
16
+ end
17
+ end
18
+
19
+ def after_perform(*args)
20
+ pin = Pin.get_from(::Resque)
21
+ pin.tracer.shutdown!
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ Resque.before_first_fork do
29
+ pin = Datadog::Pin.get_from(Resque)
30
+ pin.tracer.set_service_info(pin.service, 'resque', Datadog::Ext::AppTypes::WORKER)
31
+ end
@@ -0,0 +1,26 @@
1
+ require 'sucker_punch'
2
+
3
+ module Datadog
4
+ module Contrib
5
+ module SuckerPunch
6
+ # Patches `sucker_punch` exception handling
7
+ module ExceptionHandler
8
+ METHOD = ->(e, *) { raise(e) }
9
+
10
+ module_function
11
+
12
+ def patch!
13
+ ::SuckerPunch.class_eval do
14
+ class << self
15
+ alias_method :__exception_handler, :exception_handler
16
+
17
+ def exception_handler
18
+ ::Datadog::Contrib::SuckerPunch::ExceptionHandler::METHOD
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,60 @@
1
+ require 'sucker_punch'
2
+
3
+ module Datadog
4
+ module Contrib
5
+ module SuckerPunch
6
+ # Defines instrumentation patches for the `sucker_punch` gem
7
+ module Instrumentation
8
+ module_function
9
+
10
+ # rubocop:disable Metrics/MethodLength
11
+ def patch!
12
+ ::SuckerPunch::Job::ClassMethods.class_eval do
13
+ alias_method :__run_perform_without_datadog, :__run_perform
14
+ def __run_perform(*args)
15
+ pin = Datadog::Pin.get_from(::SuckerPunch)
16
+ pin.tracer.provider.context = Datadog::Context.new
17
+
18
+ __with_instrumentation('sucker_punch.perform') do |span|
19
+ span.resource = "PROCESS #{self}"
20
+ __run_perform_without_datadog(*args)
21
+ end
22
+ rescue => e
23
+ ::SuckerPunch.__exception_handler.call(e, self, args)
24
+ end
25
+
26
+ alias_method :__perform_async, :perform_async
27
+ def perform_async(*args)
28
+ __with_instrumentation('sucker_punch.perform_async') do |span|
29
+ span.resource = "ENQUEUE #{self}"
30
+ __perform_async(*args)
31
+ end
32
+ end
33
+
34
+ alias_method :__perform_in, :perform_in
35
+ def perform_in(interval, *args)
36
+ __with_instrumentation('sucker_punch.perform_in') do |span|
37
+ span.resource = "ENQUEUE #{self}"
38
+ span.set_tag('sucker_punch.perform_in', interval)
39
+ __perform_in(interval, *args)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def __with_instrumentation(name)
46
+ pin = Datadog::Pin.get_from(::SuckerPunch)
47
+
48
+ pin.tracer.trace(name) do |span|
49
+ span.service = pin.service
50
+ span.span_type = pin.app_type
51
+ span.set_tag('sucker_punch.queue', to_s)
52
+ yield span
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end