ddtrace 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +5 -13
  2. data/Appraisals +38 -19
  3. data/README.md +13 -0
  4. data/Rakefile +9 -9
  5. data/circle.yml +14 -11
  6. data/ddtrace.gemspec +1 -0
  7. data/docs/GettingStarted.md +96 -6
  8. data/gemfiles/rails30_postgres.gemfile +10 -0
  9. data/gemfiles/rails30_postgres_sidekiq.gemfile +11 -0
  10. data/gemfiles/{rails3_mysql2.gemfile → rails32_mysql2.gemfile} +0 -0
  11. data/gemfiles/{rails3_postgres.gemfile → rails32_postgres.gemfile} +0 -0
  12. data/gemfiles/{rails3_postgres_redis.gemfile → rails32_postgres_redis.gemfile} +0 -0
  13. data/gemfiles/{rails3_postgres_sidekiq.gemfile → rails32_postgres_sidekiq.gemfile} +1 -1
  14. data/lib/ddtrace/buffer.rb +1 -26
  15. data/lib/ddtrace/context.rb +145 -0
  16. data/lib/ddtrace/contrib/active_record/patcher.rb +2 -2
  17. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +1 -0
  18. data/lib/ddtrace/contrib/grape/endpoint.rb +4 -4
  19. data/lib/ddtrace/contrib/http/patcher.rb +36 -9
  20. data/lib/ddtrace/contrib/rack/middlewares.rb +53 -12
  21. data/lib/ddtrace/contrib/rails/action_controller.rb +14 -5
  22. data/lib/ddtrace/contrib/rails/action_view.rb +3 -3
  23. data/lib/ddtrace/contrib/rails/active_record.rb +1 -1
  24. data/lib/ddtrace/contrib/rails/active_support.rb +2 -2
  25. data/lib/ddtrace/contrib/rails/core_extensions.rb +45 -20
  26. data/lib/ddtrace/contrib/rails/framework.rb +11 -0
  27. data/lib/ddtrace/distributed.rb +38 -0
  28. data/lib/ddtrace/ext/distributed.rb +10 -0
  29. data/lib/ddtrace/pin.rb +6 -4
  30. data/lib/ddtrace/provider.rb +16 -0
  31. data/lib/ddtrace/span.rb +54 -22
  32. data/lib/ddtrace/tracer.rb +120 -64
  33. data/lib/ddtrace/utils.rb +9 -2
  34. data/lib/ddtrace/version.rb +2 -2
  35. data/lib/ddtrace/workers.rb +1 -2
  36. data/lib/ddtrace/writer.rb +0 -1
  37. metadata +43 -27
@@ -46,7 +46,7 @@ module Datadog
46
46
  span.set_tag('rails.db.vendor', adapter_name)
47
47
  span.set_tag('rails.db.cached', cached) if cached
48
48
  span.start_time = start
49
- span.finish_at(finish)
49
+ span.finish(finish)
50
50
  rescue StandardError => e
51
51
  Datadog::Tracer.log.error(e.message)
52
52
  end
@@ -8,7 +8,7 @@ module Datadog
8
8
  module ActiveSupport
9
9
  def self.instrument
10
10
  # patch Rails core components
11
- Datadog::RailsPatcher.patch_cache_store()
11
+ Datadog::RailsCachePatcher.patch_cache_store()
12
12
 
13
13
  # subscribe when a cache read starts being processed
14
14
  ::ActiveSupport::Notifications.subscribe('start_cache_read.active_support') do |*args|
@@ -106,7 +106,7 @@ module Datadog
106
106
  end
107
107
  ensure
108
108
  span.start_time = start
109
- span.finish_at(finish)
109
+ span.finish(finish)
110
110
  end
111
111
  rescue StandardError => e
112
112
  Datadog::Tracer.log.error(e.message)
@@ -1,6 +1,6 @@
1
1
  module Datadog
2
- # RailsPatcher contains function to patch the Rails libraries.
3
- module RailsPatcher
2
+ # RailsRendererPatcher contains function to patch Rails rendering libraries.
3
+ module RailsRendererPatcher
4
4
  module_function
5
5
 
6
6
  def patch_renderer
@@ -8,33 +8,58 @@ module Datadog
8
8
  patch_renderer_render_partial
9
9
  end
10
10
 
11
- def patch_cache_store
12
- patch_cache_store_read
13
- patch_cache_store_fetch
14
- patch_cache_store_write
15
- patch_cache_store_delete
16
- patch_cache_store_instrument
17
- end
18
-
19
11
  def patch_renderer_render_template
20
- ::ActionView::Renderer.class_eval do
21
- alias_method :render_template_without_datadog, :render_template
22
- def render_template(*args, &block)
23
- ActiveSupport::Notifications.instrument('start_render_template.action_view')
24
- render_template_without_datadog(*args, &block)
12
+ if defined?(::ActionView::Renderer)
13
+ ::ActionView::Renderer.class_eval do
14
+ alias_method :render_template_without_datadog, :render_template
15
+ def render_template(*args, &block)
16
+ ActiveSupport::Notifications.instrument('start_render_template.action_view')
17
+ render_template_without_datadog(*args, &block)
18
+ end
19
+ end
20
+ else # Rails < 3.1
21
+ ::ActionView::Template.class_eval do
22
+ alias_method :render_template_without_datadog, :render
23
+ def render(*args, &block)
24
+ ActiveSupport::Notifications.instrument('start_render_template.action_view')
25
+ render_template_without_datadog(*args, &block)
26
+ end
25
27
  end
26
28
  end
27
29
  end
28
30
 
29
31
  def patch_renderer_render_partial
30
- ::ActionView::PartialRenderer.class_eval do
31
- alias_method :render_partial_without_datadog, :render_partial
32
- def render_partial(*args, &block)
33
- ActiveSupport::Notifications.instrument('start_render_partial.action_view')
34
- render_partial_without_datadog(*args, &block)
32
+ if defined?(::ActionView::PartialRenderer)
33
+ ::ActionView::PartialRenderer.class_eval do
34
+ alias_method :render_partial_without_datadog, :render_partial
35
+ def render_partial(*args, &block)
36
+ ActiveSupport::Notifications.instrument('start_render_partial.action_view')
37
+ render_partial_without_datadog(*args, &block)
38
+ end
39
+ end
40
+ else # Rails < 3.1
41
+ ::ActionView::Partials::PartialRenderer.class_eval do
42
+ alias_method :render_partial_without_datadog, :render
43
+ def render(*args, &block)
44
+ ActiveSupport::Notifications.instrument('start_render_partial.action_view')
45
+ render_partial_without_datadog(*args, &block)
46
+ end
35
47
  end
36
48
  end
37
49
  end
50
+ end
51
+
52
+ # RailsCachePatcher contains function to patch Rails caching libraries.
53
+ module RailsCachePatcher
54
+ module_function
55
+
56
+ def patch_cache_store
57
+ patch_cache_store_read
58
+ patch_cache_store_fetch
59
+ patch_cache_store_write
60
+ patch_cache_store_delete
61
+ patch_cache_store_instrument
62
+ end
38
63
 
39
64
  def cache_store_class(k)
40
65
  # When Redis is used, we can't only patch Cache::Store as it is
@@ -11,6 +11,17 @@ require 'ddtrace/contrib/rails/active_record'
11
11
  require 'ddtrace/contrib/rails/active_support'
12
12
  require 'ddtrace/contrib/rails/utils'
13
13
 
14
+ # Rails < 3.1
15
+ unless defined?(ActiveRecord::Base.connection_config)
16
+ ActiveRecord::Base.class_eval do
17
+ class << self
18
+ def connection_config
19
+ connection_pool.spec.config
20
+ end
21
+ end
22
+ end
23
+ end
24
+
14
25
  module Datadog
15
26
  module Contrib
16
27
  # TODO[manu]: write docs
@@ -0,0 +1,38 @@
1
+ require 'ddtrace/span'
2
+
3
+ module Datadog
4
+ # Common code related to distributed tracing.
5
+ module Distributed
6
+ module_function
7
+
8
+ # Parses a trace_id and a parent_id, typically sent as headers in
9
+ # a distributed tracing context, and returns a couple of trace_id,parent_id
10
+ # which are garanteed to be both non-zero. This does not 100% ensure they
11
+ # are valid (after all, the caller could mess up data) but at least it
12
+ # sorts out most common errors, such as syntax, nil values, etc.
13
+ # Both headers must be set, else nil values are returned, for both.
14
+ # Reports problem on debug log.
15
+ def parse_trace_headers(trace_id_header, parent_id_header)
16
+ return nil, nil if trace_id_header.nil? || parent_id_header.nil?
17
+ trace_id = trace_id_header.to_i
18
+ parent_id = parent_id_header.to_i
19
+ if trace_id.zero?
20
+ Datadog::Tracer.log.debug("invalid trace_id header: #{trace_id_header}")
21
+ return nil, nil
22
+ end
23
+ if parent_id.zero?
24
+ Datadog::Tracer.log.debug("invalid parent_id header: #{parent_id_header}")
25
+ return nil, nil
26
+ end
27
+ if trace_id < 0 || trace_id >= Datadog::Span::MAX_ID
28
+ Datadog::Tracer.log.debug("trace_id out of range: #{trace_id_header}")
29
+ return nil, nil
30
+ end
31
+ if parent_id < 0 || parent_id >= Datadog::Span::MAX_ID
32
+ Datadog::Tracer.log.debug("parent_id out of range: #{parent_id_header}")
33
+ return nil, nil
34
+ end
35
+ [trace_id, parent_id]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,10 @@
1
+ module Datadog
2
+ module Ext
3
+ module DistributedTracing
4
+ # HTTP headers one should set for distributed tracing.
5
+ # These are cross-language (eg: Python, Go and other implementations should honor these)
6
+ HTTP_HEADER_TRACE_ID = 'x-datadog-trace-id'.freeze
7
+ HTTP_HEADER_PARENT_ID = 'x-datadog-parent-id'.freeze
8
+ end
9
+ end
10
+ end
@@ -16,15 +16,17 @@ module Datadog
16
16
  attr_accessor :app_type
17
17
  attr_accessor :name
18
18
  attr_accessor :tracer
19
+ attr_accessor :config
19
20
 
20
21
  # [ruby19] named parameters would be more idiomatic here, but would break backward compatibility
21
- def initialize(service, options = { app: nil, tags: nil, app_type: nil, tracer: nil })
22
+ def initialize(service, options = { app: nil, tags: nil, app_type: nil, tracer: nil, config: nil })
22
23
  @service = service
23
- @app = options[:app]
24
- @tags = options[:tags]
25
- @app_type = options[:app_type]
24
+ @app = options.fetch(:app, nil)
25
+ @tags = options.fetch(:tags, nil)
26
+ @app_type = options.fetch(:app_type, nil)
26
27
  @name = nil # this would rarely be overriden as it's really span-specific
27
28
  @tracer = options[:tracer] || Datadog.tracer
29
+ @config = options.fetch(:config, nil)
28
30
  end
29
31
 
30
32
  def enabled?
@@ -0,0 +1,16 @@
1
+ module Datadog
2
+ # DefaultContextProvider is a default context provider that retrieves
3
+ # all contexts from the current thread-local storage. It is suitable for
4
+ # synchronous programming.
5
+ class DefaultContextProvider
6
+ # Initializes the default context provider with a thread-bound context.
7
+ def initialize
8
+ @context = Datadog::ThreadLocalContext.new
9
+ end
10
+
11
+ # Return the current context.
12
+ def context
13
+ @context.local
14
+ end
15
+ end
16
+ end
@@ -1,4 +1,5 @@
1
1
  require 'time'
2
+ require 'thread'
2
3
 
3
4
  require 'ddtrace/utils'
4
5
  require 'ddtrace/ext/errors'
@@ -10,23 +11,30 @@ module Datadog
10
11
  # within a larger operation. Spans can be nested within each other, and in those instances
11
12
  # will have a parent-child relationship.
12
13
  class Span
13
- # The max value for a \Span identifier
14
- MAX_ID = 2**64 - 1
14
+ # The max value for a \Span identifier.
15
+ # Span and trace identifiers should be strictly positive and strictly inferior to this limit.
16
+ #
17
+ # Limited to 63-bit positive integers, as some other languages might be limited to this,
18
+ # and IDs need to be easy to port across various languages and platforms.
19
+ MAX_ID = 2**63
15
20
 
16
21
  attr_accessor :name, :service, :resource, :span_type,
17
22
  :start_time, :end_time,
18
23
  :span_id, :trace_id, :parent_id,
19
- :status, :parent, :sampled
24
+ :status, :sampled,
25
+ :tracer, :context
26
+
27
+ attr_reader :parent
20
28
 
21
- # Create a new span linked to the given tracer. Call the <tt>finish()</tt> method once the
22
- # tracer operation is over or use the <tt>finish_at(time)</tt> helper to close the span with the
23
- # given +time+. Available options are:
29
+ # Create a new span linked to the given tracer. Call the \Tracer method <tt>start_span()</tt>
30
+ # and then <tt>finish()</tt> once the tracer operation is over.
24
31
  #
25
32
  # * +service+: the service name for this span
26
33
  # * +resource+: the resource this span refers, or +name+ if it's missing
27
34
  # * +span_type+: the type of the span (such as +http+, +db+ and so on)
28
35
  # * +parent_id+: the identifier of the parent span
29
36
  # * +trace_id+: the identifier of the root span for this trace
37
+ # * +context+: the context of the span
30
38
  def initialize(tracer, name, options = {})
31
39
  @tracer = tracer
32
40
 
@@ -35,9 +43,11 @@ module Datadog
35
43
  @resource = options.fetch(:resource, name)
36
44
  @span_type = options.fetch(:span_type, nil)
37
45
 
38
- @span_id = Datadog::Utils.next_id()
46
+ @span_id = Datadog::Utils.next_id
39
47
  @parent_id = options.fetch(:parent_id, 0)
40
- @trace_id = options.fetch(:trace_id, @span_id)
48
+ @trace_id = options.fetch(:trace_id, Datadog::Utils.next_id)
49
+
50
+ @context = options.fetch(:context, nil)
41
51
 
42
52
  @meta = {}
43
53
  @metrics = {}
@@ -46,8 +56,8 @@ module Datadog
46
56
  @parent = nil
47
57
  @sampled = true
48
58
 
49
- @start_time = Time.now.utc
50
- @end_time = nil
59
+ @start_time = nil # set by Tracer.start_span
60
+ @end_time = nil # set by Span.finish
51
61
  end
52
62
 
53
63
  # Set the given key / value tag pair on the span. Keys and values
@@ -65,8 +75,8 @@ module Datadog
65
75
  @meta[key]
66
76
  end
67
77
 
68
- # Set the given key / value metric pair on the span. Keys must be string.
69
- # Values must be floating point numbers.
78
+ # This method sets a tag with a floating point value for the given key. It acts
79
+ # like `set_tag()` and it simply add a tag without further processing.
70
80
  def set_metric(key, value)
71
81
  # enforce that the value is a floating point number
72
82
  value = Float(value)
@@ -91,18 +101,34 @@ module Datadog
91
101
 
92
102
  # Mark the span finished at the current time and submit it.
93
103
  def finish(finish_time = nil)
104
+ # A span should not be finished twice. Note that this is not thread-safe,
105
+ # finish is called from multiple threads, a given span might be finished
106
+ # several times. Again, one should not do this, so this test is more a
107
+ # fallback to avoid very bad things and protect you in most common cases.
94
108
  return if finished?
95
109
 
96
- @end_time = finish_time.nil? ? Time.now.utc : finish_time
97
- @tracer.record(self) unless @tracer.nil?
98
- self
99
- end
110
+ # Provide a default start_time if unset, but this should have been set by start_span.
111
+ # Using now here causes 0-duration spans, still, this is expected, as we never
112
+ # explicitely say when it started.
113
+ @start_time ||= Time.now.utc
114
+
115
+ @end_time = finish_time.nil? ? Time.now.utc : finish_time # finish this
116
+
117
+ # Finish does not really do anything if the span is not bound to a tracer and a context.
118
+ return self if @tracer.nil? || @context.nil?
100
119
 
101
- # Proxy function that flag a span as finished with the given
102
- # timestamp. This function is used for retro-compatibility.
103
- # DEPRECATED: remove this function in the next release
104
- def finish_at(finish_time)
105
- finish(finish_time)
120
+ # spans without a service would be dropped, so here we provide a default.
121
+ # This should really never happen with integrations in contrib, as a default
122
+ # service is always set. It's only for custom instrumentation.
123
+ @service ||= @tracer.default_service unless @tracer.nil?
124
+
125
+ begin
126
+ @context.close_span(self)
127
+ @tracer.record(self)
128
+ rescue StandardError => e
129
+ Datadog::Tracer.log.debug("error recording finished trace: #{e}")
130
+ end
131
+ self
106
132
  end
107
133
 
108
134
  # Return whether the span is finished or not.
@@ -115,9 +141,14 @@ module Datadog
115
141
  "Span(name:#{@name},sid:#{@span_id},tid:#{@trace_id},pid:#{@parent_id})"
116
142
  end
117
143
 
144
+ # DEPRECATED: remove this function in the next release, replaced by ``parent=``
145
+ def set_parent(parent)
146
+ self.parent = parent
147
+ end
148
+
118
149
  # Set this span's parent, inheriting any properties not explicitly set.
119
150
  # If the parent is nil, set the span zero values.
120
- def set_parent(parent)
151
+ def parent=(parent)
121
152
  @parent = parent
122
153
 
123
154
  if parent.nil?
@@ -127,6 +158,7 @@ module Datadog
127
158
  @trace_id = parent.trace_id
128
159
  @parent_id = parent.span_id
129
160
  @service ||= parent.service
161
+ @sampled = parent.sampled
130
162
  end
131
163
  end
132
164
 
@@ -4,7 +4,8 @@ require 'logger'
4
4
  require 'pathname'
5
5
 
6
6
  require 'ddtrace/span'
7
- require 'ddtrace/buffer'
7
+ require 'ddtrace/context'
8
+ require 'ddtrace/provider'
8
9
  require 'ddtrace/logger'
9
10
  require 'ddtrace/writer'
10
11
  require 'ddtrace/sampler'
@@ -15,6 +16,7 @@ module Datadog
15
16
  # example, a trace can be used to track the entire time spent processing a complicated web request.
16
17
  # Even though the request may require multiple resources and machines to handle the request, all
17
18
  # of these function calls and sub-requests would be encapsulated within a single trace.
19
+ # rubocop:disable Metrics/ClassLength
18
20
  class Tracer
19
21
  attr_reader :writer, :sampler, :services, :tags
20
22
  attr_accessor :enabled
@@ -55,6 +57,16 @@ module Datadog
55
57
  log.level == Logger::DEBUG
56
58
  end
57
59
 
60
+ # Return the current active \Context for this traced execution. This method is
61
+ # automatically called when calling Tracer.trace or Tracer.start_span,
62
+ # but it can be used in the application code during manual instrumentation.
63
+ #
64
+ # This method makes use of a \ContextProvider that is automatically set during the tracer
65
+ # initialization, or while using a library instrumentation.
66
+ def call_context
67
+ @provider.context
68
+ end
69
+
58
70
  # Initialize a new \Tracer used to create, sample and submit spans that measure the
59
71
  # time of sections of code. Available +options+ are:
60
72
  #
@@ -65,10 +77,10 @@ module Datadog
65
77
  @writer = options.fetch(:writer, Datadog::Writer.new)
66
78
  @sampler = options.fetch(:sampler, Datadog::AllSampler.new)
67
79
 
68
- @buffer = Datadog::SpanBuffer.new()
80
+ @provider = options.fetch(:context_provider, Datadog::DefaultContextProvider.new)
81
+ @provider ||= Datadog::DefaultContextProvider.new # @provider should never be nil
69
82
 
70
83
  @mutex = Mutex.new
71
- @spans = []
72
84
  @services = {}
73
85
  @tags = {}
74
86
  end
@@ -113,10 +125,10 @@ module Datadog
113
125
  # for non-root spans which have a parent. However, root spans without
114
126
  # a service would be invalid and rejected.
115
127
  def default_service
116
- return @default_service if @default_service
128
+ return @default_service if instance_variable_defined?(:@default_service) && @default_service
117
129
  begin
118
130
  @default_service = File.basename($PROGRAM_NAME, '.*')
119
- rescue => e
131
+ rescue StandardError => e
120
132
  Datadog::Tracer.log.error("unable to guess default service: #{e}")
121
133
  @default_service = 'ruby'.freeze
122
134
  end
@@ -132,6 +144,69 @@ module Datadog
132
144
  @tags.update(tags)
133
145
  end
134
146
 
147
+ # Guess context and parent from child_of entry.
148
+ def guess_context_and_parent(options = {})
149
+ child_of = options.fetch(:child_of, nil) # can be context or span
150
+
151
+ ctx = nil
152
+ parent = nil
153
+ unless child_of.nil?
154
+ if child_of.respond_to?(:current_span)
155
+ ctx = child_of
156
+ parent = child_of.current_span
157
+ elsif child_of.is_a?(Datadog::Span)
158
+ parent = child_of
159
+ ctx = child_of.context
160
+ end
161
+ end
162
+
163
+ ctx ||= call_context
164
+
165
+ [ctx, parent]
166
+ end
167
+
168
+ # Return a span that will trace an operation called \name. This method allows
169
+ # parenting passing \child_of as an option. If it's missing, the newly created span is a
170
+ # root span. Available options are:
171
+ #
172
+ # * +service+: the service name for this span
173
+ # * +resource+: the resource this span refers, or \name if it's missing
174
+ # * +span_type+: the type of the span (such as \http, \db and so on)
175
+ # * +child_of+: a \Span or a \Context instance representing the parent for this span.
176
+ # * +start_time+: when the span actually starts (defaults to \now)
177
+ # * +tags+: extra tags which should be added to the span.
178
+ def start_span(name, options = {})
179
+ start_time = options.fetch(:start_time, Time.now.utc)
180
+ tags = options.fetch(:tags, {})
181
+
182
+ opts = options.select do |k, _v|
183
+ # Filter options, we want no side effects with unexpected args.
184
+ # Plus, this documents the code (Ruby 2 named args would be better but we're Ruby 1.9 compatible)
185
+ [:service, :resource, :span_type].include?(k)
186
+ end
187
+
188
+ ctx, parent = guess_context_and_parent(options)
189
+ opts[:context] = ctx unless ctx.nil?
190
+
191
+ span = Span.new(self, name, opts)
192
+ if parent.nil?
193
+ # root span
194
+ @sampler.sample(span)
195
+ span.set_tag('system.pid', Process.pid)
196
+ else
197
+ # child span
198
+ span.parent = parent # sets service, trace_id, parent_id, sampled
199
+ end
200
+ tags.each { |k, v| span.set_tag(k, v) } unless tags.empty?
201
+ @tags.each { |k, v| span.set_tag(k, v) } unless @tags.empty?
202
+ span.start_time = start_time
203
+
204
+ # this could at some point be optional (start_active_span vs start_manual_span)
205
+ ctx.add_span(span) unless ctx.nil?
206
+
207
+ span
208
+ end
209
+
135
210
  # Return a +span+ that will trace an operation called +name+. You could trace your code
136
211
  # using a <tt>do-block</tt> like:
137
212
  #
@@ -160,22 +235,20 @@ module Datadog
160
235
  # parent2 = tracer.trace('parent2') # has no parent span
161
236
  # parent2.finish()
162
237
  #
238
+ # Available options are:
239
+ #
240
+ # * +service+: the service name for this span
241
+ # * +resource+: the resource this span refers, or \name if it's missing
242
+ # * +span_type+: the type of the span (such as \http, \db and so on)
243
+ # * +tags+: extra tags which should be added to the span.
163
244
  def trace(name, options = {})
164
- span = Span.new(self, name, options)
165
-
166
- # set up inheritance
167
- parent = @buffer.get()
168
- span.set_parent(parent)
169
- @buffer.set(span)
170
-
171
- @tags.each { |k, v| span.set_tag(k, v) } unless @tags.empty?
172
-
173
- # sampling
174
- if parent.nil?
175
- @sampler.sample(span)
176
- else
177
- span.sampled = span.parent.sampled
245
+ opts = options.select do |k, _v|
246
+ # Filter options, we want no side effects with unexpected args.
247
+ # Plus, this documents the code (Ruby 2 named args would be better but we're Ruby 1.9 compatible)
248
+ [:service, :resource, :span_type, :tags].include?(k)
178
249
  end
250
+ opts[:child_of] = call_context
251
+ span = start_span(name, opts)
179
252
 
180
253
  # call the finish only if a block is given; this ensures
181
254
  # that a call to tracer.trace() without a block, returns
@@ -183,9 +256,15 @@ module Datadog
183
256
  if block_given?
184
257
  begin
185
258
  yield(span)
186
- rescue StandardError => e
259
+ # rubocop:disable Lint/RescueException
260
+ # Here we really want to catch *any* exception, not only StandardError,
261
+ # as we really have no clue of what is in the block,
262
+ # and it is user code which should be executed no matter what.
263
+ # It's not a problem since we re-raise it afterwards so for example a
264
+ # SignalException::Interrupt would still bubble up.
265
+ rescue Exception => e
187
266
  span.set_error(e)
188
- raise
267
+ raise e
189
268
  ensure
190
269
  span.finish()
191
270
  end
@@ -194,61 +273,38 @@ module Datadog
194
273
  end
195
274
  end
196
275
 
197
- # Record the given finished span in the +spans+ list. When a +span+ is recorded, it will be sent
198
- # to the Datadog trace agent as soon as the trace is finished.
199
- def record(span)
200
- span.service ||= default_service
201
-
202
- spans = []
203
- @mutex.synchronize do
204
- @spans << span
205
- parent = span.parent
206
- # Bubble up until we find a non-finished parent. This is necessary for
207
- # the case when the parent finished after its parent.
208
- parent = parent.parent while !parent.nil? && parent.finished?
209
- @buffer.set(parent)
210
-
211
- return unless parent.nil?
212
-
213
- # In general, all spans within the buffer belong to the same trace.
214
- # But in heavily multithreaded contexts and/or when using lots of callbacks
215
- # hooks and other non-linear programming style, one can technically
216
- # end up in different situations. So we only extract the spans which
217
- # are associated to the root span that just finished, and save the
218
- # others for later.
219
- trace_spans = []
220
- alien_spans = []
221
- @spans.each do |s|
222
- if s.trace_id == span.trace_id
223
- trace_spans << s
224
- else
225
- alien_spans << s
226
- end
227
- end
228
- spans = trace_spans
229
- @spans = alien_spans
230
- end
231
-
232
- return if spans.empty? || !span.sampled
233
- write(spans)
276
+ # Record the given +context+. For compatibility with previous versions,
277
+ # +context+ can also be a span. It is similar to the +child_of+ argument,
278
+ # method will figure out what to do, submitting a +span+ for recording
279
+ # is like trying to record its +context+.
280
+ def record(context)
281
+ context = context.context if context.is_a?(Datadog::Span)
282
+ return if context.nil?
283
+ trace, sampled = context.get
284
+ ready = !trace.nil? && !trace.empty? && sampled
285
+ write(trace) if ready
234
286
  end
235
287
 
236
288
  # Return the current active span or +nil+.
237
289
  def active_span
238
- @buffer.get()
290
+ call_context.current_span
239
291
  end
240
292
 
241
- def write(spans)
293
+ # Send the trace to the writer to enqueue the spans list in the agent
294
+ # sending queue.
295
+ def write(trace)
242
296
  return if @writer.nil? || !@enabled
243
297
 
244
298
  if Datadog::Tracer.debug_logging
245
- Datadog::Tracer.log.debug("Writing #{spans.length} spans (enabled: #{@enabled})")
246
- PP.pp(spans)
299
+ Datadog::Tracer.log.debug("Writing #{trace.length} spans (enabled: #{@enabled})")
300
+ str = String.new('')
301
+ PP.pp(trace, str)
302
+ Datadog::Tracer.log.debug(str)
247
303
  end
248
304
 
249
- @writer.write(spans, @services)
305
+ @writer.write(trace, @services)
250
306
  end
251
307
 
252
- private :write
308
+ private :write, :guess_context_and_parent
253
309
  end
254
310
  end