ddtrace 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|