Skip to content

Commit 165af46

Browse files
authored
Merge pull request #181 from DataDog/pedro/fix-error-handling
Add `Datadog::Error` value-object
2 parents 4a273e0 + b153e71 commit 165af46

File tree

9 files changed

+127
-42
lines changed

9 files changed

+127
-42
lines changed

lib/ddtrace.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'ddtrace/monkey'
22
require 'ddtrace/pin'
33
require 'ddtrace/tracer'
4+
require 'ddtrace/error'
45

56
# \Datadog global namespace that includes all tracing functionality for Tracer and Span classes.
67
module Datadog

lib/ddtrace/contrib/rails/action_controller.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,7 @@ def self.process_action(_name, start, finish, _id, payload)
6666
else
6767
status = '500'
6868
end
69-
if status.starts_with?('5')
70-
span.status = 1
71-
span.set_tag(Datadog::Ext::Errors::TYPE, error[0])
72-
span.set_tag(Datadog::Ext::Errors::MSG, error[1])
73-
end
69+
span.set_error(error) if status.starts_with?('5')
7470
end
7571
ensure
7672
span.start_time = start

lib/ddtrace/contrib/rails/action_view.rb

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,7 @@ def self.render_template(_name, start, finish, _id, payload)
7474
template_name = Datadog::Contrib::Rails::Utils.normalize_template_name(payload.fetch(:identifier))
7575
span.set_tag('rails.template_name', template_name)
7676
span.set_tag('rails.layout', payload.fetch(:layout))
77-
78-
if payload[:exception]
79-
error = payload[:exception]
80-
span.status = 1
81-
span.set_tag(Datadog::Ext::Errors::TYPE, error[0])
82-
span.set_tag(Datadog::Ext::Errors::MSG, error[1])
83-
end
77+
span.set_error(payload[:exception]) if payload[:exception]
8478
ensure
8579
span.start_time = start
8680
span.finish(finish)
@@ -102,13 +96,7 @@ def self.render_partial(_name, start, finish, _id, payload)
10296
begin
10397
template_name = Datadog::Contrib::Rails::Utils.normalize_template_name(payload.fetch(:identifier))
10498
span.set_tag('rails.template_name', template_name)
105-
106-
if payload[:exception]
107-
error = payload[:exception]
108-
span.status = 1
109-
span.set_tag(Datadog::Ext::Errors::TYPE, error[0])
110-
span.set_tag(Datadog::Ext::Errors::MSG, error[1])
111-
end
99+
span.set_error(payload[:exception]) if payload[:exception]
112100
ensure
113101
span.start_time = start
114102
span.finish(finish)

lib/ddtrace/contrib/rails/active_support.rb

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,7 @@ def self.trace_cache(resource, _name, start, finish, _id, payload)
9797
store, = *Array.wrap(::Rails.configuration.cache_store).flatten
9898
span.set_tag('rails.cache.backend', store)
9999
span.set_tag('rails.cache.key', payload.fetch(:key))
100-
101-
if payload[:exception]
102-
error = payload[:exception]
103-
span.status = 1
104-
span.set_tag(Datadog::Ext::Errors::TYPE, error[0])
105-
span.set_tag(Datadog::Ext::Errors::MSG, error[1])
106-
end
100+
span.set_error(payload[:exception]) if payload[:exception]
107101
ensure
108102
span.start_time = start
109103
span.finish(finish)

lib/ddtrace/contrib/sinatra/tracer.rb

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -151,17 +151,7 @@ def render(engine, data, *)
151151
span.resource = "#{request.request_method} #{@datadog_route}"
152152
span.set_tag('sinatra.route.path', @datadog_route)
153153
span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.status)
154-
155-
if response.server_error?
156-
span.status = 1
157-
158-
err = env['sinatra.error']
159-
if err
160-
span.set_tag(Datadog::Ext::Errors::TYPE, err.class)
161-
span.set_tag(Datadog::Ext::Errors::MSG, err.message)
162-
end
163-
end
164-
154+
span.set_error(env['sinatra.error']) if response.server_error?
165155
span.finish()
166156
ensure
167157
@datadog_request_span = nil

lib/ddtrace/error.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Datadog global namespace
2+
module Datadog
3+
# Error is a value-object responsible for sanitizing/encapsulating error data
4+
class Error
5+
attr_reader :type, :message, :backtrace
6+
7+
def self.build_from(value)
8+
case value
9+
when Error then value
10+
when Array then new(*value)
11+
when Exception then new(value.class, value.message, value.backtrace)
12+
when ContainsMessage then new(value.class, value.message)
13+
else BlankError
14+
end
15+
end
16+
17+
def initialize(type = nil, message = nil, backtrace = nil)
18+
backtrace = Array(backtrace).join("\n")
19+
@type = sanitize(type)
20+
@message = sanitize(message)
21+
@backtrace = sanitize(backtrace)
22+
end
23+
24+
private
25+
26+
def sanitize(value)
27+
value = value.to_s
28+
29+
return value if value.encoding == ::Encoding::UTF_8
30+
31+
value.encode(::Encoding::UTF_8)
32+
end
33+
34+
BlankError = Error.new
35+
ContainsMessage = ->(v) { v.respond_to?(:message) }
36+
end
37+
end

lib/ddtrace/ext/errors.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Datadog
22
module Ext
33
module Errors
4+
STATUS = 1
45
MSG = 'error.msg'.freeze
56
TYPE = 'error.type'.freeze
67
STACK = 'error.stack'.freeze

lib/ddtrace/span.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,12 @@ def get_metric(key)
9292

9393
# Mark the span with the given error.
9494
def set_error(e)
95-
return if e.nil?
96-
@status = 1
97-
@meta[Datadog::Ext::Errors::MSG] = e.message if e.respond_to?(:message) && e.message
98-
@meta[Datadog::Ext::Errors::TYPE] = e.class.to_s
99-
@meta[Datadog::Ext::Errors::STACK] = e.backtrace.join("\n") if e.respond_to?(:backtrace) && e.backtrace
95+
e = Error.build_from(e)
96+
97+
@status = Ext::Errors::STATUS
98+
set_tag(Ext::Errors::TYPE, e.type) unless e.type.empty?
99+
set_tag(Ext::Errors::MSG, e.message) unless e.message.empty?
100+
set_tag(Ext::Errors::STACK, e.backtrace) unless e.backtrace.empty?
100101
end
101102

102103
# Mark the span finished at the current time and submit it.

test/error_test.rb

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
require 'helper'
2+
require 'ddtrace/error'
3+
4+
module Datadog
5+
class ErrorTest < Minitest::Test
6+
CustomMessage = Struct.new(:message)
7+
8+
def setup
9+
@error = Error.new('StandardError', 'message', %w[x y z])
10+
end
11+
12+
def test_type
13+
assert_equal('StandardError', @error.type)
14+
end
15+
16+
def test_message
17+
assert_equal('message', @error.message)
18+
end
19+
20+
def test_backtrace
21+
assert_equal("x\ny\nz", @error.backtrace)
22+
end
23+
24+
def test_default_values
25+
error = Error.new
26+
27+
assert_empty(error.type)
28+
assert_empty(error.message)
29+
assert_empty(error.backtrace)
30+
end
31+
32+
# Empty strings were being interpreted as ASCII strings breaking `msgpack`
33+
# decoding on the agent-side.
34+
def test_enconding
35+
error = Datadog::Error.new
36+
37+
assert_equal(::Encoding::UTF_8, error.type.encoding)
38+
assert_equal(::Encoding::UTF_8, error.message.encoding)
39+
assert_equal(::Encoding::UTF_8, error.backtrace.encoding)
40+
end
41+
42+
def test_array_coercion
43+
error_payload = ['ZeroDivisionError', 'divided by 0']
44+
error = Error.build_from(error_payload)
45+
46+
assert_equal('ZeroDivisionError', error.type)
47+
assert_equal('divided by 0', error.message)
48+
assert_empty(error.backtrace)
49+
end
50+
51+
def test_exception_coercion
52+
exception = ZeroDivisionError.new('divided by 0')
53+
error = Error.build_from(exception)
54+
55+
assert_equal('ZeroDivisionError', error.type)
56+
assert_equal('divided by 0', error.message)
57+
assert_empty(error.backtrace)
58+
end
59+
60+
def test_message_coercion
61+
message = CustomMessage.new('custom-message')
62+
error = Error.build_from(message)
63+
64+
assert_equal('Datadog::ErrorTest::CustomMessage', error.type)
65+
assert_equal('custom-message', error.message)
66+
assert_empty(error.backtrace)
67+
end
68+
69+
def test_nil_coercion
70+
error = Error.build_from(nil)
71+
72+
assert_empty(error.type)
73+
assert_empty(error.message)
74+
assert_empty(error.backtrace)
75+
end
76+
end
77+
end

0 commit comments

Comments
 (0)