ddtrace 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -3
- data/Appraisals +1 -0
- data/ddtrace.gemspec +3 -0
- data/docs/GettingStarted.md +31 -8
- data/gemfiles/rails32_postgres_redis.gemfile +1 -0
- data/lib/ddtrace.rb +20 -34
- data/lib/ddtrace/buffer.rb +1 -7
- data/lib/ddtrace/configurable.rb +77 -0
- data/lib/ddtrace/configuration.rb +35 -0
- data/lib/ddtrace/configuration/proxy.rb +29 -0
- data/lib/ddtrace/configuration/resolver.rb +24 -0
- data/lib/ddtrace/context.rb +55 -7
- data/lib/ddtrace/contrib/active_record/patcher.rb +4 -1
- data/lib/ddtrace/contrib/aws/patcher.rb +3 -0
- data/lib/ddtrace/contrib/base.rb +14 -0
- data/lib/ddtrace/contrib/dalli/patcher.rb +3 -0
- data/lib/ddtrace/contrib/elasticsearch/patcher.rb +3 -0
- data/lib/ddtrace/contrib/faraday/middleware.rb +5 -6
- data/lib/ddtrace/contrib/faraday/patcher.rb +3 -0
- data/lib/ddtrace/contrib/grape/patcher.rb +3 -0
- data/lib/ddtrace/contrib/http/patcher.rb +22 -7
- data/lib/ddtrace/contrib/mongodb/patcher.rb +3 -0
- data/lib/ddtrace/contrib/rack/middlewares.rb +21 -35
- data/lib/ddtrace/contrib/rails/action_controller.rb +2 -2
- data/lib/ddtrace/contrib/rails/action_view.rb +2 -2
- data/lib/ddtrace/contrib/rails/active_record.rb +2 -2
- data/lib/ddtrace/contrib/rails/active_support.rb +2 -2
- data/lib/ddtrace/contrib/rails/framework.rb +36 -58
- data/lib/ddtrace/contrib/rails/middlewares.rb +1 -1
- data/lib/ddtrace/contrib/rails/patcher.rb +56 -0
- data/lib/ddtrace/contrib/rails/railtie.rb +18 -0
- data/lib/ddtrace/contrib/rails/utils.rb +1 -1
- data/lib/ddtrace/contrib/redis/patcher.rb +4 -0
- data/lib/ddtrace/contrib/redis/quantize.rb +1 -1
- data/lib/ddtrace/contrib/redis/tags.rb +1 -0
- data/lib/ddtrace/contrib/resque/patcher.rb +9 -0
- data/lib/ddtrace/contrib/resque/resque_job.rb +6 -6
- data/lib/ddtrace/contrib/sidekiq/tracer.rb +11 -11
- data/lib/ddtrace/contrib/sinatra/tracer.rb +23 -63
- data/lib/ddtrace/contrib/sucker_punch/patcher.rb +3 -0
- data/lib/ddtrace/ext/distributed.rb +2 -0
- data/lib/ddtrace/ext/redis.rb +6 -0
- data/lib/ddtrace/monkey.rb +20 -37
- data/lib/ddtrace/propagation/distributed_headers.rb +48 -0
- data/lib/ddtrace/propagation/http_propagator.rb +28 -0
- data/lib/ddtrace/registry.rb +42 -0
- data/lib/ddtrace/registry/registerable.rb +20 -0
- data/lib/ddtrace/sampler.rb +61 -1
- data/lib/ddtrace/sync_writer.rb +36 -0
- data/lib/ddtrace/tracer.rb +23 -21
- data/lib/ddtrace/transport.rb +52 -15
- data/lib/ddtrace/version.rb +2 -2
- data/lib/ddtrace/workers.rb +33 -31
- data/lib/ddtrace/writer.rb +20 -1
- metadata +42 -3
- data/lib/ddtrace/distributed.rb +0 -38
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ddtrace/span'
|
2
|
+
require 'ddtrace/ext/distributed'
|
3
|
+
|
4
|
+
module Datadog
|
5
|
+
# DistributedHeaders provides easy access and validation to headers
|
6
|
+
class DistributedHeaders
|
7
|
+
include Ext::DistributedTracing
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@env = env
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
# Sampling priority is optional.
|
15
|
+
trace_id && parent_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def trace_id
|
19
|
+
value = header(HTTP_HEADER_TRACE_ID).to_i
|
20
|
+
return if value <= 0 || value >= Span::MAX_ID
|
21
|
+
value
|
22
|
+
end
|
23
|
+
|
24
|
+
def parent_id
|
25
|
+
value = header(HTTP_HEADER_PARENT_ID).to_i
|
26
|
+
return if value <= 0 || value >= Span::MAX_ID
|
27
|
+
value
|
28
|
+
end
|
29
|
+
|
30
|
+
def sampling_priority
|
31
|
+
hdr = header(HTTP_HEADER_SAMPLING_PRIORITY)
|
32
|
+
# It's important to make a difference between no header,
|
33
|
+
# and a header defined to zero.
|
34
|
+
return unless hdr
|
35
|
+
value = hdr.to_i
|
36
|
+
return if value < 0
|
37
|
+
value
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def header(name)
|
43
|
+
rack_header = "http-#{name}".upcase!.tr('-', '_')
|
44
|
+
|
45
|
+
@env[rack_header]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'ddtrace/context'
|
2
|
+
require 'ddtrace/ext/distributed'
|
3
|
+
require 'ddtrace/propagation/distributed_headers'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
# HTTPPropagator helps extracting and injecting HTTP headers.
|
7
|
+
module HTTPPropagator
|
8
|
+
include Ext::DistributedTracing
|
9
|
+
|
10
|
+
# inject! popolates the env with span ID, trace ID and sampling priority
|
11
|
+
def self.inject!(context, env)
|
12
|
+
env[HTTP_HEADER_TRACE_ID] = context.trace_id.to_s
|
13
|
+
env[HTTP_HEADER_PARENT_ID] = context.span_id.to_s
|
14
|
+
env[HTTP_HEADER_SAMPLING_PRIORITY] = context.sampling_priority.to_s
|
15
|
+
env.delete(HTTP_HEADER_SAMPLING_PRIORITY) unless context.sampling_priority
|
16
|
+
end
|
17
|
+
|
18
|
+
# extract returns a context containing the span ID, trace ID and
|
19
|
+
# sampling priority defined in env.
|
20
|
+
def self.extract(env)
|
21
|
+
headers = DistributedHeaders.new(env)
|
22
|
+
return Datadog::Context.new unless headers.valid?
|
23
|
+
Datadog::Context.new(trace_id: headers.trace_id,
|
24
|
+
span_id: headers.parent_id,
|
25
|
+
sampling_priority: headers.sampling_priority)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'registry/registerable'
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
# Registry provides insertion/retrieval capabilities for integrations
|
5
|
+
class Registry
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
Entry = Struct.new(:name, :klass, :auto_patch)
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@data = {}
|
12
|
+
@mutex = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(name, klass, auto_patch = false)
|
16
|
+
@mutex.synchronize do
|
17
|
+
@data[name] = Entry.new(name, klass, auto_patch).freeze
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def each
|
22
|
+
@mutex.synchronize do
|
23
|
+
@data.each { |_, entry| yield(entry) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](name)
|
28
|
+
@mutex.synchronize do
|
29
|
+
entry = @data[name]
|
30
|
+
entry.klass if entry
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_h
|
35
|
+
@mutex.synchronize do
|
36
|
+
@data.each_with_object({}) do |(_, entry), hash|
|
37
|
+
hash[entry.name] = entry.auto_patch
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Datadog
|
2
|
+
class Registry
|
3
|
+
# Registerable provides a convenience method for self-registering
|
4
|
+
module Registerable
|
5
|
+
def self.included(base)
|
6
|
+
base.singleton_class.send(:include, ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
# ClassMethods
|
10
|
+
module ClassMethods
|
11
|
+
def register_as(name, options = {})
|
12
|
+
registry = options.fetch(:registry, Datadog.registry)
|
13
|
+
auto_patch = options.fetch(:auto_patch, false)
|
14
|
+
|
15
|
+
registry.add(name, self, auto_patch)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/ddtrace/sampler.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module Datadog
|
2
4
|
# \Sampler performs client-side trace sampling.
|
3
5
|
class Sampler
|
@@ -42,8 +44,66 @@ module Datadog
|
|
42
44
|
end
|
43
45
|
|
44
46
|
def sample(span)
|
45
|
-
span.sampled = ((span.trace_id * KNUTH_FACTOR) % Datadog::Span::MAX_ID) <= @sampling_id_threshold
|
46
47
|
span.set_metric(SAMPLE_RATE_METRIC_KEY, @sample_rate)
|
48
|
+
span.sampled = ((span.trace_id * KNUTH_FACTOR) % Datadog::Span::MAX_ID) <= @sampling_id_threshold
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# \RateByServiceSampler samples different services at different rates
|
53
|
+
class RateByServiceSampler < Sampler
|
54
|
+
DEFAULT_KEY = 'service:,env:'.freeze
|
55
|
+
|
56
|
+
def initialize(rate = 1.0, opts = {})
|
57
|
+
@env = opts.fetch(:env, Datadog.tracer.tags[:env])
|
58
|
+
@mutex = Mutex.new
|
59
|
+
@fallback = RateSampler.new(rate)
|
60
|
+
@sampler = { DEFAULT_KEY => @fallback }
|
61
|
+
end
|
62
|
+
|
63
|
+
def sample(span)
|
64
|
+
key = key_for(span)
|
65
|
+
|
66
|
+
@mutex.synchronize do
|
67
|
+
@sampler.fetch(key, @fallback).sample(span)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def update(rate_by_service)
|
72
|
+
@mutex.synchronize do
|
73
|
+
@sampler.delete_if { |key, _| key != DEFAULT_KEY && !rate_by_service.key?(key) }
|
74
|
+
|
75
|
+
rate_by_service.each do |key, rate|
|
76
|
+
@sampler[key] ||= RateSampler.new(rate)
|
77
|
+
@sampler[key].sample_rate = rate
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def key_for(span)
|
85
|
+
"service:#{span.service},env:#{@env}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# \PrioritySampler
|
90
|
+
class PrioritySampler
|
91
|
+
extend Forwardable
|
92
|
+
|
93
|
+
def initialize(opts = {})
|
94
|
+
@base_sampler = opts[:base_sampler] || RateSampler.new
|
95
|
+
@post_sampler = opts[:post_sampler] || RateByServiceSampler.new
|
47
96
|
end
|
97
|
+
|
98
|
+
def sample(span)
|
99
|
+
span.context.sampling_priority = 0 if span.context
|
100
|
+
return unless @base_sampler.sample(span)
|
101
|
+
return unless @post_sampler.sample(span)
|
102
|
+
span.context.sampling_priority = 1 if span.context
|
103
|
+
|
104
|
+
true
|
105
|
+
end
|
106
|
+
|
107
|
+
def_delegators :@post_sampler, :update
|
48
108
|
end
|
49
109
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Datadog
|
2
|
+
# SyncWriter flushes both services and traces synchronously
|
3
|
+
class SyncWriter
|
4
|
+
attr_reader :transport
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@transport = options.fetch(:transport) do
|
8
|
+
HTTPTransport.new(Writer::HOSTNAME, Writer::PORT)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(trace, services)
|
13
|
+
perform_concurrently(
|
14
|
+
proc { flush_services(services) },
|
15
|
+
proc { flush_trace(trace) }
|
16
|
+
)
|
17
|
+
rescue => e
|
18
|
+
Tracer.log.debug(e)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def perform_concurrently(*tasks)
|
24
|
+
tasks.map { |task| Thread.new(&task) }.each(&:join)
|
25
|
+
end
|
26
|
+
|
27
|
+
def flush_services(services)
|
28
|
+
transport.send(:services, services)
|
29
|
+
end
|
30
|
+
|
31
|
+
def flush_trace(trace)
|
32
|
+
processed_traces = Pipeline.process!([trace])
|
33
|
+
transport.send(:traces, processed_traces)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/ddtrace/tracer.rb
CHANGED
@@ -18,8 +18,8 @@ module Datadog
|
|
18
18
|
# of these function calls and sub-requests would be encapsulated within a single trace.
|
19
19
|
# rubocop:disable Metrics/ClassLength
|
20
20
|
class Tracer
|
21
|
-
attr_reader :
|
22
|
-
attr_accessor :enabled
|
21
|
+
attr_reader :sampler, :services, :tags, :provider
|
22
|
+
attr_accessor :enabled, :writer
|
23
23
|
attr_writer :default_service
|
24
24
|
|
25
25
|
# Global, memoized, lazy initialized instance of a logger that is used within the the Datadog
|
@@ -71,7 +71,7 @@ module Datadog
|
|
71
71
|
#
|
72
72
|
def shutdown!
|
73
73
|
return if !@enabled || @writer.worker.nil?
|
74
|
-
@writer.worker.
|
74
|
+
@writer.worker.stop
|
75
75
|
end
|
76
76
|
|
77
77
|
# Return the current active \Context for this traced execution. This method is
|
@@ -118,11 +118,18 @@ module Datadog
|
|
118
118
|
hostname = options.fetch(:hostname, nil)
|
119
119
|
port = options.fetch(:port, nil)
|
120
120
|
sampler = options.fetch(:sampler, nil)
|
121
|
+
priority_sampling = options[:priority_sampling]
|
121
122
|
|
122
123
|
@enabled = enabled unless enabled.nil?
|
124
|
+
@sampler = sampler unless sampler.nil?
|
125
|
+
|
126
|
+
if priority_sampling
|
127
|
+
@sampler = PrioritySampler.new(base_sampler: @sampler)
|
128
|
+
@writer = Writer.new(priority_sampler: @sampler)
|
129
|
+
end
|
130
|
+
|
123
131
|
@writer.transport.hostname = hostname unless hostname.nil?
|
124
132
|
@writer.transport.port = port unless port.nil?
|
125
|
-
@sampler = sampler unless sampler.nil?
|
126
133
|
end
|
127
134
|
|
128
135
|
# Set the information about the given service. A valid example is:
|
@@ -162,24 +169,15 @@ module Datadog
|
|
162
169
|
end
|
163
170
|
|
164
171
|
# Guess context and parent from child_of entry.
|
165
|
-
def guess_context_and_parent(
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
unless child_of.nil?
|
171
|
-
if child_of.respond_to?(:current_span)
|
172
|
-
ctx = child_of
|
173
|
-
parent = child_of.current_span
|
174
|
-
elsif child_of.is_a?(Datadog::Span)
|
175
|
-
parent = child_of
|
176
|
-
ctx = child_of.context
|
177
|
-
end
|
178
|
-
end
|
172
|
+
def guess_context_and_parent(child_of)
|
173
|
+
# call_context should not be in this code path, as start_span
|
174
|
+
# should never try and pick an existing context, but only get
|
175
|
+
# it from the parameters passed to it (child_of)
|
176
|
+
return [Datadog::Context.new, nil] unless child_of
|
179
177
|
|
180
|
-
|
178
|
+
return [child_of, child_of.current_span] if child_of.is_a?(Context)
|
181
179
|
|
182
|
-
[
|
180
|
+
[child_of.context, child_of]
|
183
181
|
end
|
184
182
|
|
185
183
|
# Return a span that will trace an operation called \name. This method allows
|
@@ -202,7 +200,7 @@ module Datadog
|
|
202
200
|
[:service, :resource, :span_type].include?(k)
|
203
201
|
end
|
204
202
|
|
205
|
-
ctx, parent = guess_context_and_parent(options)
|
203
|
+
ctx, parent = guess_context_and_parent(options[:child_of])
|
206
204
|
opts[:context] = ctx unless ctx.nil?
|
207
205
|
|
208
206
|
span = Span.new(self, name, opts)
|
@@ -210,6 +208,10 @@ module Datadog
|
|
210
208
|
# root span
|
211
209
|
@sampler.sample(span)
|
212
210
|
span.set_tag('system.pid', Process.pid)
|
211
|
+
if ctx && ctx.trace_id && ctx.span_id
|
212
|
+
span.trace_id = ctx.trace_id
|
213
|
+
span.parent_id = ctx.span_id
|
214
|
+
end
|
213
215
|
else
|
214
216
|
# child span
|
215
217
|
span.parent = parent # sets service, trace_id, parent_id, sampled
|
data/lib/ddtrace/transport.rb
CHANGED
@@ -19,13 +19,36 @@ module Datadog
|
|
19
19
|
TRACE_COUNT_HEADER = 'X-Datadog-Trace-Count'.freeze
|
20
20
|
RUBY_INTERPRETER = RUBY_VERSION > '1.9' ? RUBY_ENGINE + '-' + RUBY_PLATFORM : 'ruby-' + RUBY_PLATFORM
|
21
21
|
|
22
|
+
API = {
|
23
|
+
V4 = 'v0.4'.freeze => {
|
24
|
+
traces_endpoint: '/v0.4/traces'.freeze,
|
25
|
+
services_endpoint: '/v0.4/services'.freeze,
|
26
|
+
encoder: Encoding::MsgpackEncoder,
|
27
|
+
fallback: 'v0.3'.freeze
|
28
|
+
}.freeze,
|
29
|
+
V3 = 'v0.3'.freeze => {
|
30
|
+
traces_endpoint: '/v0.3/traces'.freeze,
|
31
|
+
services_endpoint: '/v0.3/services'.freeze,
|
32
|
+
encoder: Encoding::MsgpackEncoder,
|
33
|
+
fallback: 'v0.2'.freeze
|
34
|
+
}.freeze,
|
35
|
+
V2 = 'v0.2'.freeze => {
|
36
|
+
traces_endpoint: '/v0.2/traces'.freeze,
|
37
|
+
services_endpoint: '/v0.2/services'.freeze,
|
38
|
+
encoder: Encoding::JSONEncoder
|
39
|
+
}.freeze
|
40
|
+
}.freeze
|
41
|
+
|
42
|
+
private_constant :API
|
43
|
+
|
22
44
|
def initialize(hostname, port, options = {})
|
45
|
+
api_version = options.fetch(:api_version, V3)
|
46
|
+
|
23
47
|
@hostname = hostname
|
24
48
|
@port = port
|
25
|
-
@
|
26
|
-
@
|
27
|
-
@
|
28
|
-
@encoder = options.fetch(:encoder, Datadog::Encoding::MsgpackEncoder.new())
|
49
|
+
@api = API.fetch(api_version)
|
50
|
+
@encoder = options[:encoder] || @api[:encoder].new
|
51
|
+
@response_callback = options[:response_callback]
|
29
52
|
|
30
53
|
# overwrite the Content-type with the one chosen in the Encoder
|
31
54
|
@headers = options.fetch(:headers, {})
|
@@ -48,21 +71,19 @@ module Datadog
|
|
48
71
|
case endpoint
|
49
72
|
when :services
|
50
73
|
payload = @encoder.encode_services(data)
|
51
|
-
status_code = post(@services_endpoint, payload)
|
74
|
+
status_code = post(@api[:services_endpoint], payload)
|
52
75
|
when :traces
|
53
76
|
count = data.length
|
54
77
|
payload = @encoder.encode_traces(data)
|
55
|
-
status_code = post(@traces_endpoint, payload, count)
|
78
|
+
status_code = post(@api[:traces_endpoint], payload, count)
|
56
79
|
else
|
57
80
|
Datadog::Tracer.log.error("Unsupported endpoint: #{endpoint}")
|
58
81
|
return nil
|
59
82
|
end
|
60
83
|
|
61
|
-
|
84
|
+
downgrade! && send(endpoint, data) if downgrade?(status_code)
|
62
85
|
|
63
|
-
|
64
|
-
downgrade!
|
65
|
-
send(endpoint, data)
|
86
|
+
status_code
|
66
87
|
end
|
67
88
|
|
68
89
|
# send data to the trace-agent; the method is thread-safe
|
@@ -84,11 +105,13 @@ module Datadog
|
|
84
105
|
# this method should target a stable API that works whatever is the agent
|
85
106
|
# or the tracing client versions.
|
86
107
|
def downgrade!
|
87
|
-
@
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
108
|
+
@mutex.synchronize do
|
109
|
+
fallback_version = @api.fetch(:fallback)
|
110
|
+
|
111
|
+
@api = API.fetch(fallback_version)
|
112
|
+
@encoder = @api[:encoder].new
|
113
|
+
@headers['Content-Type'] = @encoder.content_type
|
114
|
+
end
|
92
115
|
end
|
93
116
|
|
94
117
|
def informational?(code)
|
@@ -119,6 +142,8 @@ module Datadog
|
|
119
142
|
# endpoint. In both cases, we're going to downgrade the transporter encoder so that
|
120
143
|
# it will target a stable API.
|
121
144
|
def downgrade?(code)
|
145
|
+
return unless @api[:fallback]
|
146
|
+
|
122
147
|
code == 404 || code == 415
|
123
148
|
end
|
124
149
|
|
@@ -141,6 +166,8 @@ module Datadog
|
|
141
166
|
@mutex.synchronize { @count_server_error += 1 }
|
142
167
|
end
|
143
168
|
|
169
|
+
process_callback(response)
|
170
|
+
|
144
171
|
status_code
|
145
172
|
rescue StandardError => e
|
146
173
|
Datadog::Tracer.log.error(e.message)
|
@@ -158,5 +185,15 @@ module Datadog
|
|
158
185
|
}
|
159
186
|
end
|
160
187
|
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def process_callback(response)
|
192
|
+
return unless @response_callback && @response_callback.respond_to?(:call)
|
193
|
+
|
194
|
+
@response_callback.call(response)
|
195
|
+
rescue => e
|
196
|
+
Tracer.log.debug("Error processing callback: #{e}")
|
197
|
+
end
|
161
198
|
end
|
162
199
|
end
|