ddtrace 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|