Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/lib/opentelemetry/trace/span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ def add_event(name: nil, attributes: nil, timestamp: nil)
self
end

# Record an error during the execution of this span. Multiple errors
# can be recorded on a span.
#
# @param [Exception] error The error to recorded
#
# @return [void]
def record_error(error); end

# Sets the Status to the Span
#
# If used, this will override the default Span status. Default is OK.
Expand Down
9 changes: 8 additions & 1 deletion api/lib/opentelemetry/trace/tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@ def active_span_context(context = nil)
#
# OpenTelemetry.tracer.with_span(OpenTelemetry.tracer.start_span('do-the-thing')) do ... end
#
# On exit, the Span that was active before calling this method will be reactivated.
# On exit, the Span that was active before calling this method will be reactivated. If an
# exception occurs during the execution of the provided block, it will be recorded on the
# span and reraised.
def in_span(name, attributes: nil, links: nil, start_timestamp: nil, kind: nil, sampling_hint: nil, with_parent: nil, with_parent_context: nil)
span = start_span(name, attributes: attributes, links: links, start_timestamp: start_timestamp, kind: kind, sampling_hint: sampling_hint, with_parent: with_parent, with_parent_context: with_parent_context)
with_span(span) { |s| yield s }
rescue Exception => e # rubocop:disable Lint/RescueException
span.record_error(e)
span.status = Status.new(Status::UNKNOWN_ERROR,
description: "Unhandled exception of type: #{e.class}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

raise e
ensure
span.finish
end
Expand Down
6 changes: 6 additions & 0 deletions api/test/opentelemetry/trace/span_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
end
end

describe '#record_error' do
it 'returns nil' do
_(span.record_error(StandardError.new('oops'))).must_be_nil
end
end

describe '#finish' do
it 'returns self' do
_(span.finish).must_equal(span)
Expand Down
16 changes: 16 additions & 0 deletions sdk/lib/opentelemetry/sdk/trace/span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def set_attribute(key, value)
def add_event(name: nil, attributes: nil, timestamp: nil)
super
event = block_given? ? yield : OpenTelemetry::Trace::Event.new(name: name, attributes: attributes, timestamp: timestamp || Time.now)

@mutex.synchronize do
if @ended
OpenTelemetry.logger.warn('Calling add_event on an ended Span.')
Expand All @@ -119,6 +120,21 @@ def add_event(name: nil, attributes: nil, timestamp: nil)
self
end

# Record an error during the execution of this span. Multiple errors
# can be recorded on a span.
#
# @param [Exception] error The error to be recorded
#
# @return [void]
def record_error(error)
add_event(name: 'error',
attributes: {
'error.type' => error.class.to_s,
'error.message' => error.message,
'error.stack' => error.backtrace.join("\n")
})
end

# Sets the Status to the Span
#
# If used, this will override the default Span status. Default is OK.
Expand Down
43 changes: 43 additions & 0 deletions sdk/test/opentelemetry/sdk/trace/span_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,49 @@
end
end

describe '#record_error' do
let(:trace_config) do
TraceConfig.new(
max_attributes_count: 10,
max_events_count: 5,
max_attributes_per_event: 10
)
end

let(:error) do
begin
raise 'oops'
rescue StandardError => e
e
end
end

it 'records error as an event' do
span.record_error(error)
events = span.events
_(events.size).must_equal(1)

ev = events[0]

_(ev.name).must_equal('error')
_(ev.attributes['error.type']).must_equal(error.class.to_s)
_(ev.attributes['error.message']).must_equal(error.message)
_(ev.attributes['error.stack']).must_equal(error.backtrace.join("\n"))
end

it 'records multiple errors' do
3.times { span.record_error(error) }
events = span.events
_(events.size).must_equal(3)

events.each do |ev|
_(ev.attributes['error.type']).must_equal(error.class.to_s)
_(ev.attributes['error.message']).must_equal(error.message)
_(ev.attributes['error.stack']).must_equal(error.backtrace.join("\n"))
end
end
end

describe '#status=' do
it 'sets the status' do
span.status = Status.new(1, description: 'cancelled')
Expand Down
19 changes: 19 additions & 0 deletions sdk/test/opentelemetry/sdk/trace/tracer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,25 @@
end
end

describe '#in_span' do
it 'records and reraises exceptions' do
span = nil
_(proc do
tracer.in_span('op') do |s|
span = s
raise 'this is fine'
end
end).must_raise(RuntimeError)

_(span.events.size).must_equal(1)

_(span.events[0].name).must_equal('error')
_(span.events[0].attributes['error.message']).must_equal('this is fine')
_(span.status.canonical_code).must_equal(OpenTelemetry::Trace::Status::UNKNOWN_ERROR)
_(span.status.description).must_equal('Unhandled exception of type: RuntimeError')
end
end

def activate_trace_config(trace_config)
tracer_factory.active_trace_config = trace_config
end
Expand Down