ddtrace 0.8.2 → 0.9.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 (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