ddtrace 0.3.1 → 0.4.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.
@@ -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