diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/subscriber.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/subscriber.rb index fb5cf4824..36c3caed0 100644 --- a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/subscriber.rb +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/subscriber.rb @@ -55,10 +55,12 @@ def finish(_name, _id, payload) span = otel&.fetch(:span) tokens = otel&.fetch(:ctx_tokens) - # Unhandled exceptions are reported in `exception_object` - # while handled exceptions are reported in `error` exception = payload[:error] || payload[:exception_object] - on_exception(exception, span) if exception + if exception + on_exception(exception, span) + else + span&.status = OpenTelemetry::Trace::Status.ok + end rescue StandardError => e OpenTelemetry.handle_error(exception: e) ensure @@ -68,6 +70,7 @@ def finish(_name, _id, payload) def finish_span(span, tokens) # closes the span after all attributes have been finalized begin + # Relies on span status being immutable span&.finish rescue StandardError => e OpenTelemetry.handle_error(exception: e) diff --git a/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/subscriber_test.rb b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/subscriber_test.rb index 5ef2023f9..9e76386ea 100644 --- a/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/subscriber_test.rb +++ b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/subscriber_test.rb @@ -92,10 +92,33 @@ _(process_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR _(process_span.status.description).must_equal 'discard me' + _(discard_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR + _(discard_span.status.description).must_equal 'discard me' _(discard_span.events.first.name).must_equal 'exception' _(discard_span.events.first.attributes['exception.type']).must_equal 'DiscardJob::DiscardError' _(discard_span.events.first.attributes['exception.message']).must_equal 'discard me' end + + it 'captures errors that were handled by rescue_from in versions earlier than Rails 7' do + skip 'rescue_from jobs behave differently in Rails 7 and newer' if ActiveJob.version >= Gem::Version.new('7') + RescueFromJob.perform_later + + _(process_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR + _(process_span.status.description).must_equal 'I was handled by rescue_from' + + _(process_span.events.first.name).must_equal 'exception' + _(process_span.events.first.attributes['exception.type']).must_equal 'RescueFromJob::RescueFromError' + _(process_span.events.first.attributes['exception.message']).must_equal 'I was handled by rescue_from' + end + + it 'ignores errors that were handled by rescue_from in versions of Rails 7 or newer' do + skip 'rescue_from jobs behave differently in Rails 7 and newer' if ActiveJob.version < Gem::Version.new('7') + RescueFromJob.perform_later + + _(process_span.status.code).must_equal OpenTelemetry::Trace::Status::OK + + _(process_span.events).must_be_nil + end end describe 'span kind' do diff --git a/instrumentation/active_job/test/test_helper.rb b/instrumentation/active_job/test/test_helper.rb index a9fc64a4d..21b022b46 100644 --- a/instrumentation/active_job/test/test_helper.rb +++ b/instrumentation/active_job/test/test_helper.rb @@ -76,6 +76,18 @@ def initialize(*) end end +class RescueFromJob < ActiveJob::Base + class RescueFromError < StandardError; end + + rescue_from RescueFromError do + # do nothing + end + + def perform + raise RescueFromError, 'I was handled by rescue_from' + end +end + ActiveJob::Base.queue_adapter = :inline ActiveJob::Base.logger = Logger.new($stderr, level: ENV.fetch('OTEL_LOG_LEVEL', 'fatal').to_sym)