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.
- checksums.yaml +4 -4
- data/Appraisals +52 -48
- data/README.md +6 -6
- data/Rakefile +29 -26
- data/circle.yml +28 -25
- data/ddtrace.gemspec +3 -4
- data/docs/GettingStarted +94 -49
- data/gemfiles/rails5_mysql2.gemfile +1 -1
- data/gemfiles/rails5_postgres.gemfile +1 -1
- data/gemfiles/rails5_postgres_redis.gemfile +1 -1
- data/lib/ddtrace.rb +15 -13
- data/lib/ddtrace/contrib/elasticsearch/core.rb +4 -2
- data/lib/ddtrace/contrib/http/patcher.rb +131 -0
- data/lib/ddtrace/contrib/rails/action_view.rb +3 -0
- data/lib/ddtrace/contrib/rails/active_record.rb +3 -0
- data/lib/ddtrace/contrib/rails/active_support.rb +3 -0
- data/lib/ddtrace/contrib/rails/core_extensions.rb +17 -0
- data/lib/ddtrace/contrib/rails/framework.rb +17 -16
- data/lib/ddtrace/contrib/redis/patcher.rb +83 -3
- data/lib/ddtrace/contrib/sinatra/tracer.rb +181 -0
- data/lib/ddtrace/monkey.rb +15 -1
- data/lib/ddtrace/pin.rb +2 -2
- data/lib/ddtrace/span.rb +2 -2
- data/lib/ddtrace/transport.rb +1 -0
- data/lib/ddtrace/version.rb +2 -2
- metadata +11 -24
- data/lib/ddtrace/contrib/redis/core.rb +0 -72
data/lib/ddtrace.rb
CHANGED
@@ -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(*
|
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
|
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'
|
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
|
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:
|
21
|
-
auto_instrument_redis:
|
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.
|
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
|
-
|
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
|
-
|
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()
|
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
|
19
|
-
|
20
|
-
|
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
|