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.
- checksums.yaml +5 -13
- data/Appraisals +38 -19
- data/README.md +13 -0
- data/Rakefile +9 -9
- data/circle.yml +14 -11
- data/ddtrace.gemspec +1 -0
- data/docs/GettingStarted.md +96 -6
- data/gemfiles/rails30_postgres.gemfile +10 -0
- data/gemfiles/rails30_postgres_sidekiq.gemfile +11 -0
- data/gemfiles/{rails3_mysql2.gemfile → rails32_mysql2.gemfile} +0 -0
- data/gemfiles/{rails3_postgres.gemfile → rails32_postgres.gemfile} +0 -0
- data/gemfiles/{rails3_postgres_redis.gemfile → rails32_postgres_redis.gemfile} +0 -0
- data/gemfiles/{rails3_postgres_sidekiq.gemfile → rails32_postgres_sidekiq.gemfile} +1 -1
- data/lib/ddtrace/buffer.rb +1 -26
- data/lib/ddtrace/context.rb +145 -0
- data/lib/ddtrace/contrib/active_record/patcher.rb +2 -2
- data/lib/ddtrace/contrib/elasticsearch/patcher.rb +1 -0
- data/lib/ddtrace/contrib/grape/endpoint.rb +4 -4
- data/lib/ddtrace/contrib/http/patcher.rb +36 -9
- data/lib/ddtrace/contrib/rack/middlewares.rb +53 -12
- data/lib/ddtrace/contrib/rails/action_controller.rb +14 -5
- data/lib/ddtrace/contrib/rails/action_view.rb +3 -3
- data/lib/ddtrace/contrib/rails/active_record.rb +1 -1
- data/lib/ddtrace/contrib/rails/active_support.rb +2 -2
- data/lib/ddtrace/contrib/rails/core_extensions.rb +45 -20
- data/lib/ddtrace/contrib/rails/framework.rb +11 -0
- data/lib/ddtrace/distributed.rb +38 -0
- data/lib/ddtrace/ext/distributed.rb +10 -0
- data/lib/ddtrace/pin.rb +6 -4
- data/lib/ddtrace/provider.rb +16 -0
- data/lib/ddtrace/span.rb +54 -22
- data/lib/ddtrace/tracer.rb +120 -64
- data/lib/ddtrace/utils.rb +9 -2
- data/lib/ddtrace/version.rb +2 -2
- data/lib/ddtrace/workers.rb +1 -2
- data/lib/ddtrace/writer.rb +0 -1
- metadata +43 -27
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
# \Context is used to keep track of a hierarchy of spans for the current
|
5
|
+
# execution flow. During each logical execution, the same \Context is
|
6
|
+
# used to represent a single logical trace, even if the trace is built
|
7
|
+
# asynchronously.
|
8
|
+
#
|
9
|
+
# A single code execution may use multiple \Context if part of the execution
|
10
|
+
# must not be related to the current tracing. As example, a delayed job may
|
11
|
+
# compose a standalone trace instead of being related to the same trace that
|
12
|
+
# generates the job itself. On the other hand, if it's part of the same
|
13
|
+
# \Context, it will be related to the original trace.
|
14
|
+
#
|
15
|
+
# This data structure is thread-safe.
|
16
|
+
class Context
|
17
|
+
# Initialize a new thread-safe \Context.
|
18
|
+
def initialize
|
19
|
+
@mutex = Mutex.new
|
20
|
+
reset
|
21
|
+
end
|
22
|
+
|
23
|
+
def reset
|
24
|
+
@trace = []
|
25
|
+
@sampled = false
|
26
|
+
@finished_spans = 0
|
27
|
+
@current_span = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return the last active span that corresponds to the last inserted
|
31
|
+
# item in the trace list. This cannot be considered as the current active
|
32
|
+
# span in asynchronous environments, because some spans can be closed
|
33
|
+
# earlier while child spans still need to finish their traced execution.
|
34
|
+
def current_span
|
35
|
+
@mutex.synchronize do
|
36
|
+
return @current_span
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add a span to the context trace list, keeping it as the last active span.
|
41
|
+
def add_span(span)
|
42
|
+
@mutex.synchronize do
|
43
|
+
@current_span = span
|
44
|
+
@sampled = span.sampled
|
45
|
+
@trace << span
|
46
|
+
span.context = self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Mark a span as a finished, increasing the internal counter to prevent
|
51
|
+
# cycles inside _trace list.
|
52
|
+
def close_span(span)
|
53
|
+
@mutex.synchronize do
|
54
|
+
@finished_spans += 1
|
55
|
+
# Current span is only meaningful for linear tree-like traces,
|
56
|
+
# in other cases, this is just broken and one should rely
|
57
|
+
# on per-instrumentation code to retrieve handle parent/child relations.
|
58
|
+
@current_span = span.parent
|
59
|
+
return if span.tracer.nil?
|
60
|
+
return unless Datadog::Tracer.debug_logging
|
61
|
+
if span.parent.nil? && !check_finished_spans
|
62
|
+
opened_spans = @trace.length - @finished_spans
|
63
|
+
Datadog::Tracer.log.debug("root span #{span.name} closed but has #{opened_spans} unfinished spans:")
|
64
|
+
@trace.each do |s|
|
65
|
+
Datadog::Tracer.log.debug("unfinished span: #{s}") unless s.finished?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns if the trace for the current Context is finished or not.
|
72
|
+
# Low-level internal function, not thread-safe.
|
73
|
+
def check_finished_spans
|
74
|
+
@finished_spans > 0 && @trace.length == @finished_spans
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns if the trace for the current Context is finished or not. A \Context
|
78
|
+
# is considered finished if all spans in this context are finished.
|
79
|
+
def finished?
|
80
|
+
@mutex.synchronize do
|
81
|
+
return check_finished_spans
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns true if the context is sampled, that is, if it should be kept
|
86
|
+
# and sent to the trace agent.
|
87
|
+
def sampled?
|
88
|
+
@mutex.synchronize do
|
89
|
+
return @sampled
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns both the trace list generated in the current context and
|
94
|
+
# if the context is sampled or not. It returns nil, nil if the ``Context`` is
|
95
|
+
# not finished. If a trace is returned, the \Context will be reset so that it
|
96
|
+
# can be re-used immediately.
|
97
|
+
#
|
98
|
+
# This operation is thread-safe.
|
99
|
+
def get
|
100
|
+
@mutex.synchronize do
|
101
|
+
return nil, nil unless check_finished_spans
|
102
|
+
|
103
|
+
trace = @trace
|
104
|
+
sampled = @sampled
|
105
|
+
reset
|
106
|
+
return trace, sampled
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Return a string representation of the context.
|
111
|
+
def to_s
|
112
|
+
@mutex.synchronize do
|
113
|
+
# rubocop:disable Metrics/LineLength
|
114
|
+
"Context(trace.length:#{@trace.length},sampled:#{@sampled},finished_spans:#{@finished_spans},current_span:#{@current_span})"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private :reset
|
119
|
+
private :check_finished_spans
|
120
|
+
end
|
121
|
+
|
122
|
+
# ThreadLocalContext can be used as a tracer global reference to create
|
123
|
+
# a different \Context for each thread. In synchronous tracer, this
|
124
|
+
# is required to prevent multiple threads sharing the same \Context
|
125
|
+
# in different executions.
|
126
|
+
class ThreadLocalContext
|
127
|
+
# ThreadLocalContext can be used as a tracer global reference to create
|
128
|
+
# a different \Context for each thread. In synchronous tracer, this
|
129
|
+
# is required to prevent multiple threads sharing the same \Context
|
130
|
+
# in different executions.
|
131
|
+
def initialize
|
132
|
+
self.local = Datadog::Context.new
|
133
|
+
end
|
134
|
+
|
135
|
+
# Override the thread-local context with a new context.
|
136
|
+
def local=(ctx)
|
137
|
+
Thread.current[:datadog_context] = ctx
|
138
|
+
end
|
139
|
+
|
140
|
+
# Return the thread-local context.
|
141
|
+
def local
|
142
|
+
Thread.current[:datadog_context] ||= Datadog::Context.new
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -23,7 +23,7 @@ module Datadog
|
|
23
23
|
patch_active_record()
|
24
24
|
|
25
25
|
@patched = true
|
26
|
-
rescue => e
|
26
|
+
rescue StandardError => e
|
27
27
|
Datadog::Tracer.log.error("Unable to apply Active Record integration: #{e}")
|
28
28
|
end
|
29
29
|
end
|
@@ -84,7 +84,7 @@ module Datadog
|
|
84
84
|
span.span_type = Datadog::Ext::SQL::TYPE
|
85
85
|
span.set_tag('active_record.db.vendor', adapter_name)
|
86
86
|
span.start_time = start
|
87
|
-
span.
|
87
|
+
span.finish(finish)
|
88
88
|
rescue StandardError => e
|
89
89
|
Datadog::Tracer.log.error(e.message)
|
90
90
|
end
|
@@ -79,12 +79,12 @@ module Datadog
|
|
79
79
|
# catch thrown exceptions
|
80
80
|
span.set_error(payload[:exception_object]) unless payload[:exception_object].nil?
|
81
81
|
|
82
|
-
#
|
82
|
+
# override the current span with this notification values
|
83
83
|
span.set_tag('grape.route.endpoint', api_view)
|
84
84
|
span.set_tag('grape.route.path', path)
|
85
85
|
ensure
|
86
86
|
span.start_time = start
|
87
|
-
span.
|
87
|
+
span.finish(finish)
|
88
88
|
end
|
89
89
|
rescue StandardError => e
|
90
90
|
Datadog::Tracer.log.error(e.message)
|
@@ -125,7 +125,7 @@ module Datadog
|
|
125
125
|
span.set_error(payload[:exception_object]) unless payload[:exception_object].nil?
|
126
126
|
ensure
|
127
127
|
span.start_time = start
|
128
|
-
span.
|
128
|
+
span.finish(finish)
|
129
129
|
end
|
130
130
|
rescue StandardError => e
|
131
131
|
Datadog::Tracer.log.error(e.message)
|
@@ -153,7 +153,7 @@ module Datadog
|
|
153
153
|
span.set_tag('grape.filter.type', type.to_s)
|
154
154
|
ensure
|
155
155
|
span.start_time = start
|
156
|
-
span.
|
156
|
+
span.finish(finish)
|
157
157
|
end
|
158
158
|
rescue StandardError => e
|
159
159
|
Datadog::Tracer.log.error(e.message)
|
@@ -12,6 +12,12 @@ module Datadog
|
|
12
12
|
APP = 'net/http'.freeze
|
13
13
|
SERVICE = 'net/http'.freeze
|
14
14
|
|
15
|
+
@distributed_tracing_enabled = false
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :distributed_tracing_enabled
|
19
|
+
end
|
20
|
+
|
15
21
|
module_function
|
16
22
|
|
17
23
|
def should_skip_tracing?(req, address, port, transport, pin)
|
@@ -35,6 +41,13 @@ module Datadog
|
|
35
41
|
false
|
36
42
|
end
|
37
43
|
|
44
|
+
def should_skip_distributed_tracing?(pin)
|
45
|
+
unless pin.config.nil?
|
46
|
+
return !pin.config.fetch(:distributed_tracing_enabled, @distributed_tracing_enabled)
|
47
|
+
end
|
48
|
+
!@distributed_tracing_enabled
|
49
|
+
end
|
50
|
+
|
38
51
|
# Patcher enables patching of 'net/http' module.
|
39
52
|
# This is used in monkey.rb to automatically apply patches
|
40
53
|
module Patcher
|
@@ -52,6 +65,7 @@ module Datadog
|
|
52
65
|
require 'ddtrace/ext/app_types'
|
53
66
|
require 'ddtrace/ext/http'
|
54
67
|
require 'ddtrace/ext/net'
|
68
|
+
require 'ddtrace/ext/distributed'
|
55
69
|
|
56
70
|
patch_http()
|
57
71
|
|
@@ -69,6 +83,8 @@ module Datadog
|
|
69
83
|
end
|
70
84
|
|
71
85
|
# rubocop:disable Metrics/MethodLength
|
86
|
+
# rubocop:disable Metrics/BlockLength
|
87
|
+
# rubocop:disable Metrics/AbcSize
|
72
88
|
def patch_http
|
73
89
|
::Net::HTTP.class_eval do
|
74
90
|
alias_method :initialize_without_datadog, :initialize
|
@@ -84,6 +100,7 @@ module Datadog
|
|
84
100
|
|
85
101
|
alias_method :request_without_datadog, :request
|
86
102
|
remove_method :request
|
103
|
+
|
87
104
|
def request(req, body = nil, &block) # :yield: +response+
|
88
105
|
pin = Datadog::Pin.get_from(self)
|
89
106
|
return request_without_datadog(req, body, &block) unless pin && pin.tracer
|
@@ -93,15 +110,25 @@ module Datadog
|
|
93
110
|
Datadog::Contrib::HTTP.should_skip_tracing?(req, @address, @port, transport, pin)
|
94
111
|
|
95
112
|
pin.tracer.trace(NAME) do |span|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
113
|
+
begin
|
114
|
+
span.service = pin.service
|
115
|
+
span.span_type = Datadog::Ext::HTTP::TYPE
|
116
|
+
|
117
|
+
span.resource = req.method
|
118
|
+
# Using the method as a resource, as URL/path can trigger
|
119
|
+
# a possibly infinite number of resources.
|
120
|
+
span.set_tag(Datadog::Ext::HTTP::URL, req.path)
|
121
|
+
span.set_tag(Datadog::Ext::HTTP::METHOD, req.method)
|
122
|
+
|
123
|
+
unless Datadog::Contrib::HTTP.should_skip_distributed_tracing?(pin)
|
124
|
+
req.add_field(Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID, span.trace_id)
|
125
|
+
req.add_field(Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID, span.span_id)
|
126
|
+
end
|
127
|
+
rescue StandardError => e
|
128
|
+
Datadog::Tracer.log.error("error preparing span for http request: #{e}")
|
129
|
+
ensure
|
130
|
+
response = request_without_datadog(req, body, &block)
|
131
|
+
end
|
105
132
|
span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.code)
|
106
133
|
if req.respond_to?(:uri) && req.uri
|
107
134
|
span.set_tag(Datadog::Ext::NET::TARGET_HOST, req.uri.host)
|
@@ -1,11 +1,21 @@
|
|
1
1
|
require 'ddtrace/ext/app_types'
|
2
2
|
require 'ddtrace/ext/http'
|
3
|
+
require 'ddtrace/distributed'
|
3
4
|
|
4
5
|
module Datadog
|
5
6
|
module Contrib
|
6
7
|
# Rack module includes middlewares that are required to trace any framework
|
7
8
|
# and application built on top of Rack.
|
8
9
|
module Rack
|
10
|
+
# RACK headers to test when doing distributed tracing.
|
11
|
+
# They are slightly different from real headers as Rack uppercases everything
|
12
|
+
|
13
|
+
# Header used to transmit the trace ID.
|
14
|
+
HTTP_HEADER_TRACE_ID = 'HTTP_X_DATADOG_TRACE_ID'.freeze
|
15
|
+
|
16
|
+
# Header used to transmit the parent ID.
|
17
|
+
HTTP_HEADER_PARENT_ID = 'HTTP_X_DATADOG_PARENT_ID'.freeze
|
18
|
+
|
9
19
|
# TraceMiddleware ensures that the Rack Request is properly traced
|
10
20
|
# from the beginning to the end. The middleware adds the request span
|
11
21
|
# in the Rack environment so that it can be retrieved by the underlying
|
@@ -14,13 +24,15 @@ module Datadog
|
|
14
24
|
class TraceMiddleware
|
15
25
|
DEFAULT_CONFIG = {
|
16
26
|
tracer: Datadog.tracer,
|
17
|
-
default_service: 'rack'
|
27
|
+
default_service: 'rack',
|
28
|
+
distributed_tracing_enabled: false
|
18
29
|
}.freeze
|
19
30
|
|
20
31
|
def initialize(app, options = {})
|
21
32
|
# update options with our configuration, unless it's already available
|
22
|
-
|
23
|
-
|
33
|
+
[:tracer, :default_service, :distributed_tracing_enabled].each do |k|
|
34
|
+
options[k] ||= DEFAULT_CONFIG[k]
|
35
|
+
end
|
24
36
|
|
25
37
|
@app = app
|
26
38
|
@options = options
|
@@ -33,6 +45,7 @@ module Datadog
|
|
33
45
|
# retrieve the current tracer and service
|
34
46
|
@tracer = @options.fetch(:tracer)
|
35
47
|
@service = @options.fetch(:default_service)
|
48
|
+
@distributed_tracing_enabled = @options.fetch(:distributed_tracing_enabled)
|
36
49
|
|
37
50
|
# configure the Rack service
|
38
51
|
@tracer.set_service_info(
|
@@ -42,23 +55,45 @@ module Datadog
|
|
42
55
|
)
|
43
56
|
end
|
44
57
|
|
58
|
+
# rubocop:disable Metrics/MethodLength
|
45
59
|
def call(env)
|
46
60
|
# configure the Rack middleware once
|
47
61
|
configure()
|
48
62
|
|
49
|
-
|
50
|
-
# we must ensure that the span `resource` is set later
|
51
|
-
request_span = @tracer.trace(
|
52
|
-
'rack.request',
|
63
|
+
trace_options = {
|
53
64
|
service: @service,
|
54
65
|
resource: nil,
|
55
66
|
span_type: Datadog::Ext::HTTP::TYPE
|
56
|
-
|
67
|
+
}
|
68
|
+
|
69
|
+
# start a new request span and attach it to the current Rack environment;
|
70
|
+
# we must ensure that the span `resource` is set later
|
71
|
+
request_span = @tracer.trace('rack.request', trace_options)
|
72
|
+
|
73
|
+
if @distributed_tracing_enabled
|
74
|
+
# Merge distributed trace ids if present
|
75
|
+
#
|
76
|
+
# Use integer values for tests, as it will catch both
|
77
|
+
# a non-existing header or a badly formed one.
|
78
|
+
trace_id, parent_id = Datadog::Distributed.parse_trace_headers(
|
79
|
+
env[Datadog::Contrib::Rack::HTTP_HEADER_TRACE_ID],
|
80
|
+
env[Datadog::Contrib::Rack::HTTP_HEADER_PARENT_ID]
|
81
|
+
)
|
82
|
+
request_span.trace_id = trace_id unless trace_id.nil?
|
83
|
+
request_span.parent_id = parent_id unless parent_id.nil?
|
84
|
+
end
|
85
|
+
|
57
86
|
env[:datadog_rack_request_span] = request_span
|
58
87
|
|
59
88
|
# call the rest of the stack
|
60
89
|
status, headers, response = @app.call(env)
|
61
|
-
|
90
|
+
# rubocop:disable Lint/RescueException
|
91
|
+
# Here we really want to catch *any* exception, not only StandardError,
|
92
|
+
# as we really have no clue of what is in the block,
|
93
|
+
# and it is user code which should be executed no matter what.
|
94
|
+
# It's not a problem since we re-raise it afterwards so for example a
|
95
|
+
# SignalException::Interrupt would still bubble up.
|
96
|
+
rescue Exception => e
|
62
97
|
# catch exceptions that may be raised in the middleware chain
|
63
98
|
# Note: if a middleware catches an Exception without re raising,
|
64
99
|
# the Exception cannot be recorded here
|
@@ -79,9 +114,15 @@ module Datadog
|
|
79
114
|
# be set in another level but if they're missing, reasonable defaults
|
80
115
|
# are used.
|
81
116
|
request_span.resource = "#{env['REQUEST_METHOD']} #{status}".strip unless request_span.resource
|
82
|
-
|
83
|
-
|
84
|
-
|
117
|
+
if request_span.get_tag(Datadog::Ext::HTTP::METHOD).nil?
|
118
|
+
request_span.set_tag(Datadog::Ext::HTTP::METHOD, env['REQUEST_METHOD'])
|
119
|
+
end
|
120
|
+
if request_span.get_tag(Datadog::Ext::HTTP::URL).nil?
|
121
|
+
request_span.set_tag(Datadog::Ext::HTTP::URL, url)
|
122
|
+
end
|
123
|
+
if request_span.get_tag(Datadog::Ext::HTTP::STATUS_CODE).nil? && status
|
124
|
+
request_span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, status)
|
125
|
+
end
|
85
126
|
|
86
127
|
# detect if the status code is a 5xx and flag the request span as an error
|
87
128
|
# unless it has been already set by the underlying framework
|
@@ -33,6 +33,7 @@ module Datadog
|
|
33
33
|
Datadog::Tracer.log.error(e.message)
|
34
34
|
end
|
35
35
|
|
36
|
+
# rubocop:disable Metrics/MethodLength
|
36
37
|
def self.process_action(_name, start, finish, _id, payload)
|
37
38
|
return unless Thread.current[KEY]
|
38
39
|
Thread.current[KEY] = false
|
@@ -63,14 +64,22 @@ module Datadog
|
|
63
64
|
end
|
64
65
|
else
|
65
66
|
error = payload[:exception]
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
if defined?(::ActionDispatch::ExceptionWrapper)
|
68
|
+
status = ::ActionDispatch::ExceptionWrapper.status_code_for_exception(error[0])
|
69
|
+
status = status ? status.to_s : '?'
|
70
|
+
else
|
71
|
+
status = '500'
|
72
|
+
end
|
73
|
+
if status.starts_with?('5')
|
74
|
+
span.status = 1
|
75
|
+
span.set_tag(Datadog::Ext::Errors::TYPE, error[0])
|
76
|
+
span.set_tag(Datadog::Ext::Errors::MSG, error[1])
|
77
|
+
span.set_tag(Datadog::Ext::Errors::STACK, caller().join("\n"))
|
78
|
+
end
|
70
79
|
end
|
71
80
|
ensure
|
72
81
|
span.start_time = start
|
73
|
-
span.
|
82
|
+
span.finish(finish)
|
74
83
|
end
|
75
84
|
rescue StandardError => e
|
76
85
|
Datadog::Tracer.log.error(e.message)
|
@@ -7,7 +7,7 @@ module Datadog
|
|
7
7
|
module ActionView
|
8
8
|
def self.instrument
|
9
9
|
# patch Rails core components
|
10
|
-
Datadog::
|
10
|
+
Datadog::RailsRendererPatcher.patch_renderer()
|
11
11
|
|
12
12
|
# subscribe when the template rendering starts
|
13
13
|
::ActiveSupport::Notifications.subscribe('start_render_template.action_view') do |*args|
|
@@ -83,7 +83,7 @@ module Datadog
|
|
83
83
|
end
|
84
84
|
ensure
|
85
85
|
span.start_time = start
|
86
|
-
span.
|
86
|
+
span.finish(finish)
|
87
87
|
end
|
88
88
|
rescue StandardError => e
|
89
89
|
Datadog::Tracer.log.error(e.message)
|
@@ -111,7 +111,7 @@ module Datadog
|
|
111
111
|
end
|
112
112
|
ensure
|
113
113
|
span.start_time = start
|
114
|
-
span.
|
114
|
+
span.finish(finish)
|
115
115
|
end
|
116
116
|
rescue StandardError => e
|
117
117
|
Datadog::Tracer.log.error(e.message)
|