ddtrace 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.0.0.1"
5
+ gem "rails", "5.0.1"
6
6
  gem "mysql2", :platform => :ruby
7
7
 
8
8
  gemspec :path => "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.0.0.1"
5
+ gem "rails", "5.0.1"
6
6
  gem "pg", :platform => :ruby
7
7
 
8
8
  gemspec :path => "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.0.0.1"
5
+ gem "rails", "5.0.1"
6
6
  gem "pg", :platform => :ruby
7
7
  gem "redis-rails"
8
8
 
@@ -28,25 +28,27 @@ end
28
28
  # Datadog auto instrumentation for frameworks
29
29
  if defined?(Rails::VERSION)
30
30
  if Rails::VERSION::MAJOR.to_i >= 3
31
- begin
32
- # We include 'redis-rails' here if it's available, doing it later
33
- # (typically in initialize callback) does not work, it does not
34
- # get loaded in the right context.
35
- require 'redis-rails'
36
- Datadog::Tracer.log.info("'redis-rails' module found, datadog redis integration is available")
37
- rescue LoadError
38
- Datadog::Tracer.log.info("no 'redis-rails' module found, datadog redis integration is not available")
39
- end
40
31
  require 'ddtrace/contrib/rails/framework'
41
32
 
42
- Datadog::Monkey.patch_module(:redis) # does nothing if redis is not loaded
43
- Datadog::RailsPatcher.patch_renderer()
44
- Datadog::RailsPatcher.patch_cache_store()
45
-
46
33
  module Datadog
47
34
  # Run the auto instrumentation directly after the initialization of the application and
48
35
  # after the application initializers in config/initializers are run
49
36
  class Railtie < Rails::Railtie
37
+ config.before_configuration do
38
+ begin
39
+ # We include 'redis-rails' here if it's available, doing it later
40
+ # (typically in initialize callback) does not work, it does not
41
+ # get loaded in the right context.
42
+ require 'redis-rails'
43
+ Datadog::Tracer.log.debug("'redis-rails' module found, Datadog 'redis-rails' integration is available")
44
+ rescue LoadError
45
+ Datadog::Tracer.log.debug("'redis-rails' module not found, Datadog 'redis-rails' integration is disabled")
46
+ end
47
+
48
+ Datadog::Monkey.patch_module(:redis)
49
+ end
50
+
51
+ # we do actions
50
52
  config.after_initialize do |app|
51
53
  Datadog::Contrib::Rails::Framework.configure(config: app.config)
52
54
  Datadog::Contrib::Rails::Framework.auto_instrument()
@@ -17,14 +17,16 @@ module Datadog
17
17
 
18
18
  # Datadog APM Elastic Search integration.
19
19
  module TracedClient
20
- def initialize(*args)
20
+ def initialize(*)
21
21
  pin = Datadog::Pin.new(SERVICE, app: 'elasticsearch', app_type: Datadog::Ext::AppTypes::DB)
22
22
  pin.onto(self)
23
- super(*args)
23
+ super
24
24
  end
25
25
 
26
26
  def perform_request(*args)
27
27
  pin = Datadog::Pin.get_from(self)
28
+ return super unless pin && pin.tracer
29
+
28
30
  method = args[0]
29
31
  path = args[1]
30
32
  params = args[2]
@@ -0,0 +1,131 @@
1
+ # requirements should be kept minimal as Patcher is a shared requirement.
2
+
3
+ module Datadog
4
+ module Contrib
5
+ # Datadog Net/HTTP integration.
6
+ module HTTP
7
+ URL = 'http.url'.freeze
8
+ METHOD = 'http.method'.freeze
9
+ BODY = 'http.body'.freeze
10
+
11
+ NAME = 'http.request'.freeze
12
+ APP = 'net/http'.freeze
13
+ SERVICE = 'net/http'.freeze
14
+
15
+ module_function
16
+
17
+ # rubocop:disable Metrics/CyclomaticComplexity
18
+ # rubocop:disable Metrics/PerceivedComplexity
19
+ def should_skip_tracing?(req, address, port, transport, pin)
20
+ # we don't want to trace our own call to the API (they use net/http)
21
+ # when we know the host & port (from the URI) we use it, else (most-likely
22
+ # called with a block) rely on the URL at the end.
23
+ if req.uri
24
+ if req.uri.host.to_s == transport.hostname.to_s &&
25
+ req.uri.port.to_i == transport.port.to_i
26
+ return true
27
+ end
28
+ elsif address && port &&
29
+ address.to_s == transport.hostname.to_s &&
30
+ port.to_i == transport.port.to_i
31
+ return true
32
+ end
33
+ # we don't want a "shotgun" effect with two nested traces for one
34
+ # logical get, and request is likely to call itself recursively
35
+ active = pin.tracer.active_span()
36
+ return true if active && (active.name == NAME)
37
+ false
38
+ end
39
+
40
+ # Patcher enables patching of 'net/http' module.
41
+ # This is used in monkey.rb to automatically apply patches
42
+ module Patcher
43
+ @patched = false
44
+
45
+ module_function
46
+
47
+ # patch applies our patch if needed
48
+ def patch
49
+ unless @patched
50
+ begin
51
+ require 'uri'
52
+ require 'ddtrace/pin'
53
+ require 'ddtrace/monkey'
54
+ require 'ddtrace/ext/app_types'
55
+ require 'ddtrace/ext/http'
56
+ require 'ddtrace/ext/net'
57
+
58
+ patch_http()
59
+
60
+ @patched = true
61
+ rescue StandardError => e
62
+ Datadog::Tracer.log.error("Unable to apply net/http integration: #{e}")
63
+ end
64
+ end
65
+ @patched
66
+ end
67
+
68
+ # patched? tells wether patch has been successfully applied
69
+ def patched?
70
+ @patched
71
+ end
72
+
73
+ # rubocop:disable Metrics/MethodLength
74
+ def patch_http
75
+ ::Net::HTTP.class_eval do
76
+ alias_method :initialize_without_datadog, :initialize
77
+ Datadog::Monkey.without_warnings do
78
+ remove_method :initialize
79
+ end
80
+
81
+ def initialize(*args)
82
+ pin = Datadog::Pin.new(SERVICE, app: APP, app_type: Datadog::Ext::AppTypes::WEB)
83
+ pin.onto(self)
84
+ initialize_without_datadog(*args)
85
+ end
86
+
87
+ alias_method :request_without_datadog, :request
88
+ remove_method :request
89
+ def request(req, body = nil, &block) # :yield: +response+
90
+ pin = Datadog::Pin.get_from(self)
91
+ return request_without_datadog(req, body, &block) unless pin && pin.tracer
92
+
93
+ transport = pin.tracer.writer.transport
94
+ return request_without_datadog(req, body, &block) if
95
+ Datadog::Contrib::HTTP.should_skip_tracing?(req, @address, @port, transport, pin)
96
+
97
+ pin.tracer.trace(NAME) do |span|
98
+ span.service = pin.service
99
+ span.span_type = Datadog::Ext::HTTP::TYPE
100
+
101
+ span.resource = req.path
102
+ # *NOT* filling Datadog::Ext::HTTP::URL as it's already in resource.
103
+ # The agent can then decide to quantize the URL and store the original,
104
+ # untouched data in http.url but the client should not send redundant fields.
105
+ span.set_tag(Datadog::Ext::HTTP::METHOD, req.method)
106
+ response = request_without_datadog(req, body, &block)
107
+ span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.code)
108
+ if req.uri
109
+ span.set_tag(Datadog::Ext::NET::TARGET_HOST, req.uri.host)
110
+ span.set_tag(Datadog::Ext::NET::TARGET_PORT, req.uri.port.to_s)
111
+ else
112
+ span.set_tag(Datadog::Ext::NET::TARGET_HOST, @address)
113
+ span.set_tag(Datadog::Ext::NET::TARGET_PORT, @port.to_s)
114
+ end
115
+
116
+ case response.code.to_i / 100
117
+ when 4
118
+ span.set_error(response)
119
+ when 5
120
+ span.set_error(response)
121
+ end
122
+
123
+ response
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -6,6 +6,9 @@ module Datadog
6
6
  # TODO[manu]: write docs
7
7
  module ActionView
8
8
  def self.instrument
9
+ # patch Rails core components
10
+ Datadog::RailsPatcher.patch_renderer()
11
+
9
12
  # subscribe when the template rendering starts
10
13
  ::ActiveSupport::Notifications.subscribe('start_render_template.action_view') do |*args|
11
14
  start_render_template(*args)
@@ -8,6 +8,9 @@ module Datadog
8
8
  # TODO[manu]: write docs
9
9
  module ActiveRecord
10
10
  def self.instrument
11
+ # ActiveRecord is instrumented only if it's available
12
+ return unless defined?(::ActiveRecord)
13
+
11
14
  # subscribe when the active record query has been processed
12
15
  ::ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
13
16
  sql(*args)
@@ -7,6 +7,9 @@ module Datadog
7
7
  # TODO[manu]: write docs
8
8
  module ActiveSupport
9
9
  def self.instrument
10
+ # patch Rails core components
11
+ Datadog::RailsPatcher.patch_cache_store()
12
+
10
13
  # subscribe when a cache read starts being processed
11
14
  ::ActiveSupport::Notifications.subscribe('start_cache_read.active_support') do |*args|
12
15
  start_trace_cache('GET', *args)
@@ -54,6 +54,15 @@ module Datadog
54
54
  end
55
55
  end
56
56
 
57
+ # CacheInstrumentExtension prepends the instrument function that Rails 3.x uses
58
+ # to know if the underlying cache should be instrumented or not. By default,
59
+ # we force that instrumentation if the Rails application is auto instrumented.
60
+ module CacheInstrumentExtension
61
+ def instrument
62
+ true
63
+ end
64
+ end
65
+
57
66
  # RailsPatcher contains function to patch the Rails libraries.
58
67
  module RailsPatcher
59
68
  module_function
@@ -84,6 +93,14 @@ module Datadog
84
93
  Datadog::Tracer.log.debug("monkey patching #{c}.#{k} with #{v}.#{k}")
85
94
  c.prepend v
86
95
  end
96
+
97
+ # by default, Rails 3 doesn't instrument the cache system so we should turn it on
98
+ # using the ActiveSupport::Cache::Store.instrument= function. Unfortunately, early
99
+ # versions of Rails use a Thread.current store that is not compatible with some
100
+ # application servers like Passenger.
101
+ # More details: https://github.com/rails/rails/blob/v3.2.22.5/activesupport/lib/active_support/cache.rb#L175-L177
102
+ return unless ::Rails::VERSION::MAJOR.to_i == 3
103
+ ::ActiveSupport::Cache::Store.singleton_class.prepend Datadog::CacheInstrumentExtension
87
104
  end
88
105
  end
89
106
  end
@@ -1,10 +1,9 @@
1
- require 'ddtrace'
2
1
  require 'ddtrace/ext/app_types'
3
2
 
4
3
  require 'ddtrace/contrib/rails/core_extensions'
5
4
  require 'ddtrace/contrib/rails/action_controller'
6
5
  require 'ddtrace/contrib/rails/action_view'
7
- require 'ddtrace/contrib/rails/active_record' if defined?(::ActiveRecord)
6
+ require 'ddtrace/contrib/rails/active_record'
8
7
  require 'ddtrace/contrib/rails/active_support'
9
8
  require 'ddtrace/contrib/rails/utils'
10
9
 
@@ -14,11 +13,13 @@ module Datadog
14
13
  module Rails
15
14
  # TODO[manu]: write docs
16
15
  module Framework
17
- # the default configuration
16
+ # default configurations for the Rails integration; by default
17
+ # the Datadog.tracer is enabled, while the Rails auto instrumentation
18
+ # is kept disabled.
18
19
  DEFAULT_CONFIG = {
19
20
  enabled: true,
20
- auto_instrument: true,
21
- auto_instrument_redis: true,
21
+ auto_instrument: false,
22
+ auto_instrument_redis: false,
22
23
  default_service: 'rails-app',
23
24
  default_cache_service: 'rails-cache',
24
25
  template_base_path: 'views/',
@@ -57,9 +58,9 @@ module Datadog
57
58
  Datadog::Ext::AppTypes::CACHE
58
59
  )
59
60
 
60
- # set default database service details and store it in the configuration
61
61
  if defined?(::ActiveRecord)
62
62
  begin
63
+ # set default database service details and store it in the configuration
63
64
  conn_cfg = ::ActiveRecord::Base.connection_config()
64
65
  adapter_name = Datadog::Contrib::Rails::Utils.normalize_vendor(conn_cfg[:adapter])
65
66
  database_service = datadog_config.fetch(:default_database_service, adapter_name)
@@ -70,7 +71,7 @@ module Datadog
70
71
  Datadog::Ext::AppTypes::DB
71
72
  )
72
73
  rescue StandardError => e
73
- Datadog::Tracer.log.info("cannot configuring database service (#{e}), skipping activerecord instrumentation")
74
+ Datadog::Tracer.log.warn("Unable to get database config (#{e}), skipping ActiveRecord instrumentation")
74
75
  end
75
76
  end
76
77
 
@@ -79,27 +80,27 @@ module Datadog
79
80
  end
80
81
 
81
82
  def self.auto_instrument_redis
82
- Datadog::Tracer.log.debug('instrumenting redis')
83
+ # configure Redis PIN
83
84
  return unless (defined? ::Rails.cache) && ::Rails.cache.respond_to?(:data)
84
- Datadog::Tracer.log.debug('redis cache exists')
85
85
  pin = Datadog::Pin.get_from(::Rails.cache.data)
86
86
  return unless pin
87
- Datadog::Tracer.log.debug('redis cache pin is set')
87
+
88
+ # enable Redis instrumentation if activated
88
89
  pin.tracer = nil unless ::Rails.configuration.datadog_trace[:auto_instrument_redis]
90
+ return unless pin.tracer
91
+ Datadog::Tracer.log.debug("'redis' module found, Datadog 'redis' integration is available")
89
92
  end
90
93
 
91
94
  # automatically instrument all Rails component
92
95
  def self.auto_instrument
93
96
  return unless ::Rails.configuration.datadog_trace[:auto_instrument]
94
- Datadog::Tracer.log.info('Detected Rails >= 3.x. Enabling auto-instrumentation for core components.')
97
+ Datadog::Tracer.log.info('Detected Rails >= 3.x. Enabling auto-instrumentation for core components')
98
+
99
+ # instrumenting Rails framework
95
100
  Datadog::Contrib::Rails::ActionController.instrument()
96
101
  Datadog::Contrib::Rails::ActionView.instrument()
97
- Datadog::Contrib::Rails::ActiveRecord.instrument() if defined?(::ActiveRecord)
102
+ Datadog::Contrib::Rails::ActiveRecord.instrument()
98
103
  Datadog::Contrib::Rails::ActiveSupport.instrument()
99
-
100
- # by default, Rails 3 doesn't instrument the cache system
101
- return unless ::Rails::VERSION::MAJOR.to_i == 3
102
- ::ActiveSupport::Cache::Store.instrument = true
103
104
  end
104
105
  end
105
106
  end
@@ -3,6 +3,9 @@
3
3
  module Datadog
4
4
  module Contrib
5
5
  module Redis
6
+ SERVICE = 'redis'.freeze
7
+ DRIVER = 'redis.driver'.freeze
8
+
6
9
  # Patcher enables patching of 'redis' module.
7
10
  # This is used in monkey.rb to automatically apply patches
8
11
  module Patcher
@@ -15,9 +18,15 @@ module Datadog
15
18
  if !@patched && (defined?(::Redis::VERSION) && \
16
19
  Gem::Version.new(::Redis::VERSION) >= Gem::Version.new('3.0.0'))
17
20
  begin
18
- require 'ddtrace/contrib/redis/core'
19
- ::Redis.prepend Datadog::Contrib::Redis::TracedRedis
20
- ::Redis::Client.prepend Datadog::Contrib::Redis::TracedRedisClient
21
+ # do not require these by default, but only when actually patching
22
+ require 'ddtrace/monkey'
23
+ require 'ddtrace/ext/app_types'
24
+ require 'ddtrace/contrib/redis/tags'
25
+ require 'ddtrace/contrib/redis/quantize'
26
+
27
+ patch_redis()
28
+ patch_redis_client()
29
+
21
30
  @patched = true
22
31
  rescue StandardError => e
23
32
  Datadog::Tracer.log.error("Unable to apply Redis integration: #{e}")
@@ -26,6 +35,77 @@ module Datadog
26
35
  @patched
27
36
  end
28
37
 
38
+ def patch_redis
39
+ ::Redis.module_eval do
40
+ def datadog_pin=(pin)
41
+ # Forward the pin to client, which actually traces calls.
42
+ Datadog::Pin.onto(client, pin)
43
+ end
44
+
45
+ def datadog_pin
46
+ # Get the pin from client, which actually traces calls.
47
+ Datadog::Pin.get_from(client)
48
+ end
49
+ end
50
+ end
51
+
52
+ # rubocop:disable Metrics/MethodLength
53
+ def patch_redis_client
54
+ ::Redis::Client.class_eval do
55
+ alias_method :initialize_without_datadog, :initialize
56
+ Datadog::Monkey.without_warnings do
57
+ remove_method :initialize
58
+ end
59
+
60
+ def initialize(*args)
61
+ pin = Datadog::Pin.new(SERVICE, app: 'redis', app_type: Datadog::Ext::AppTypes::DB)
62
+ pin.onto(self)
63
+ initialize_without_datadog(*args)
64
+ end
65
+
66
+ alias_method :call_without_datadog, :call
67
+ remove_method :call
68
+ def call(*args, &block)
69
+ pin = Datadog::Pin.get_from(self)
70
+ return call_without_datadog(*args, &block) unless pin && pin.tracer
71
+
72
+ response = nil
73
+ pin.tracer.trace('redis.command') do |span|
74
+ span.service = pin.service
75
+ span.span_type = Datadog::Ext::Redis::TYPE
76
+ span.resource = Datadog::Contrib::Redis::Quantize.format_command_args(*args)
77
+ span.set_tag(Datadog::Ext::Redis::RAWCMD, span.resource)
78
+ Datadog::Contrib::Redis::Tags.set_common_tags(self, span)
79
+
80
+ response = call_without_datadog(*args, &block)
81
+ end
82
+
83
+ response
84
+ end
85
+
86
+ alias_method :call_pipeline_without_datadog, :call_pipeline
87
+ remove_method :call_pipeline
88
+ def call_pipeline(*args, &block)
89
+ pin = Datadog::Pin.get_from(self)
90
+ return call_pipeline_without_datadog(*args, &block) unless pin && pin.tracer
91
+
92
+ response = nil
93
+ pin.tracer.trace('redis.command') do |span|
94
+ span.service = pin.service
95
+ span.span_type = Datadog::Ext::Redis::TYPE
96
+ commands = args[0].commands.map { |c| Datadog::Contrib::Redis::Quantize.format_command_args(c) }
97
+ span.resource = commands.join("\n")
98
+ span.set_tag(Datadog::Ext::Redis::RAWCMD, span.resource)
99
+ Datadog::Contrib::Redis::Tags.set_common_tags(self, span)
100
+
101
+ response = call_pipeline_without_datadog(*args, &block)
102
+ end
103
+
104
+ response
105
+ end
106
+ end
107
+ end
108
+
29
109
  # patched? tells wether patch has been successfully applied
30
110
  def patched?
31
111
  @patched