ddtrace 0.7.2 → 0.8.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 (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