ddtrace 0.9.2 → 0.10.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -3
  3. data/Appraisals +1 -0
  4. data/ddtrace.gemspec +3 -0
  5. data/docs/GettingStarted.md +31 -8
  6. data/gemfiles/rails32_postgres_redis.gemfile +1 -0
  7. data/lib/ddtrace.rb +20 -34
  8. data/lib/ddtrace/buffer.rb +1 -7
  9. data/lib/ddtrace/configurable.rb +77 -0
  10. data/lib/ddtrace/configuration.rb +35 -0
  11. data/lib/ddtrace/configuration/proxy.rb +29 -0
  12. data/lib/ddtrace/configuration/resolver.rb +24 -0
  13. data/lib/ddtrace/context.rb +55 -7
  14. data/lib/ddtrace/contrib/active_record/patcher.rb +4 -1
  15. data/lib/ddtrace/contrib/aws/patcher.rb +3 -0
  16. data/lib/ddtrace/contrib/base.rb +14 -0
  17. data/lib/ddtrace/contrib/dalli/patcher.rb +3 -0
  18. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +3 -0
  19. data/lib/ddtrace/contrib/faraday/middleware.rb +5 -6
  20. data/lib/ddtrace/contrib/faraday/patcher.rb +3 -0
  21. data/lib/ddtrace/contrib/grape/patcher.rb +3 -0
  22. data/lib/ddtrace/contrib/http/patcher.rb +22 -7
  23. data/lib/ddtrace/contrib/mongodb/patcher.rb +3 -0
  24. data/lib/ddtrace/contrib/rack/middlewares.rb +21 -35
  25. data/lib/ddtrace/contrib/rails/action_controller.rb +2 -2
  26. data/lib/ddtrace/contrib/rails/action_view.rb +2 -2
  27. data/lib/ddtrace/contrib/rails/active_record.rb +2 -2
  28. data/lib/ddtrace/contrib/rails/active_support.rb +2 -2
  29. data/lib/ddtrace/contrib/rails/framework.rb +36 -58
  30. data/lib/ddtrace/contrib/rails/middlewares.rb +1 -1
  31. data/lib/ddtrace/contrib/rails/patcher.rb +56 -0
  32. data/lib/ddtrace/contrib/rails/railtie.rb +18 -0
  33. data/lib/ddtrace/contrib/rails/utils.rb +1 -1
  34. data/lib/ddtrace/contrib/redis/patcher.rb +4 -0
  35. data/lib/ddtrace/contrib/redis/quantize.rb +1 -1
  36. data/lib/ddtrace/contrib/redis/tags.rb +1 -0
  37. data/lib/ddtrace/contrib/resque/patcher.rb +9 -0
  38. data/lib/ddtrace/contrib/resque/resque_job.rb +6 -6
  39. data/lib/ddtrace/contrib/sidekiq/tracer.rb +11 -11
  40. data/lib/ddtrace/contrib/sinatra/tracer.rb +23 -63
  41. data/lib/ddtrace/contrib/sucker_punch/patcher.rb +3 -0
  42. data/lib/ddtrace/ext/distributed.rb +2 -0
  43. data/lib/ddtrace/ext/redis.rb +6 -0
  44. data/lib/ddtrace/monkey.rb +20 -37
  45. data/lib/ddtrace/propagation/distributed_headers.rb +48 -0
  46. data/lib/ddtrace/propagation/http_propagator.rb +28 -0
  47. data/lib/ddtrace/registry.rb +42 -0
  48. data/lib/ddtrace/registry/registerable.rb +20 -0
  49. data/lib/ddtrace/sampler.rb +61 -1
  50. data/lib/ddtrace/sync_writer.rb +36 -0
  51. data/lib/ddtrace/tracer.rb +23 -21
  52. data/lib/ddtrace/transport.rb +52 -15
  53. data/lib/ddtrace/version.rb +2 -2
  54. data/lib/ddtrace/workers.rb +33 -31
  55. data/lib/ddtrace/writer.rb +20 -1
  56. metadata +42 -3
  57. 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
@@ -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
@@ -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 :writer, :sampler, :services, :tags, :provider
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.shutdown!
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(options = {})
166
- child_of = options.fetch(:child_of, nil) # can be context or span
167
-
168
- ctx = nil
169
- parent = nil
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
- ctx ||= call_context
178
+ return [child_of, child_of.current_span] if child_of.is_a?(Context)
181
179
 
182
- [ctx, parent]
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
@@ -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
- @traces_endpoint = '/v0.3/traces'.freeze
26
- @services_endpoint = '/v0.3/services'.freeze
27
- @compatibility_mode = false
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
- return status_code unless downgrade?(status_code) && !@compatibility_mode
84
+ downgrade! && send(endpoint, data) if downgrade?(status_code)
62
85
 
63
- # the API endpoint is not available so we should downgrade the connection and re-try the call
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
- @compatibility_mode = true
88
- @traces_endpoint = '/v0.2/traces'.freeze
89
- @services_endpoint = '/v0.2/services'.freeze
90
- @encoder = Datadog::Encoding::JSONEncoder.new()
91
- @headers['Content-Type'] = @encoder.content_type
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