Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow to set custom status code for different exception classes #2334

Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@
The feature is only activated in apps that use Ruby 3.2+ and Rails 7.1+. By default only queries that take longer than 100ms will have source recorded, which can be adjusted by updating the value of `config.rails.db_query_source_threshold_ms`.
- Log envelope delivery message with debug instead of info ([#2320](https://github.com/getsentry/sentry-ruby/pull/2320))

- Add `exception_status_code` configuration option that allows to specify which transaction status code should be used for exceptions ([#2333](https://github.com/getsentry/sentry-ruby/issues/2333))

### Bug Fixes

- Don't throw error on arbitrary arguments being passed to `capture_event` options [#2301](https://github.com/getsentry/sentry-ruby/pull/2301)
Expand Down
7 changes: 7 additions & 0 deletions sentry-ruby/lib/sentry-ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,13 @@ def start_transaction(**options)
get_current_hub.start_transaction(**options)
end

# Takes or initializes a new Sentry::Transaction and makes a sampling decision for it.
#
# @return [Transaction, nil]
def status_code_for_exception(exception)
get_current_hub.status_code_for_exception(exception)
end

# Records the block's execution as a child of the current span.
# If the current scope doesn't have a span, the block would still be executed but the yield param will be nil.
# @param attributes [Hash] attributes for the child span.
Expand Down
12 changes: 12 additions & 0 deletions sentry-ruby/lib/sentry/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,17 @@ def capture_exception_frame_locals=(value)
# @return [Boolean]
attr_accessor :auto_session_tracking

# Take a Proc that controls the status code for various exceptions, e.g.
# @example
# config.exception_status_code = lambda do |exception|
# # exception is the captured exception instance
# # return 404 if exception.is_a?(ActiveRecord::RecordNotFound)
#
# 500 # default behavior
# end
# @return [Proc]
attr_accessor :exception_status_code

# Whether to downsample transactions automatically because of backpressure.
# Starts a new monitor thread to check health of the SDK every 10 seconds.
# Default is false
Expand Down Expand Up @@ -405,6 +416,7 @@ def initialize
self.before_send_transaction = nil
self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
self.traces_sampler = nil
self.exception_status_code = nil
self.enable_tracing = nil

self.profiler_class = Sentry::Profiler
Expand Down
11 changes: 11 additions & 0 deletions sentry-ruby/lib/sentry/hub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ def start_transaction(transaction: nil, custom_sampling_context: {}, instrumente
transaction
end

def status_code_for_exception(exception)
case configuration.exception_status_code
when Numeric
configuration.exception_status_code
when Proc
configuration.exception_status_code.call(exception)
else
500
end
end

def with_child_span(instrumenter: :sentry, **attributes, &block)
return yield(nil) unless instrumenter == configuration.instrumenter

Expand Down
7 changes: 6 additions & 1 deletion sentry-ruby/lib/sentry/rack/capture_exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def call(env)
raise # Don't capture Sentry errors
rescue Exception => e
capture_exception(e, env)
finish_transaction(transaction, 500)

finish_transaction(transaction, exception_status_code(e))
raise
end

Expand All @@ -49,6 +50,10 @@ def call(env)

private

def exception_status_code(exception)
Sentry.status_code_for_exception(exception)
end

def collect_exception(env)
env["rack.exception"] || env["sinatra.error"]
end
Expand Down
26 changes: 26 additions & 0 deletions sentry-ruby/spec/sentry/hub_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,32 @@
end
end

describe '#status_code_for_exception' do
subject(:status_code_for_exception) { described_class.new(client, scope).status_code_for_exception(exception) }

let(:exception) { Exception.new }

context 'when configuration exception_status_code is a number' do
before do
configuration.exception_status_code = 400
end

it { is_expected.to eq(400) }
end

context 'when configuration exception_status_code is a proc' do
before do
configuration.exception_status_code = ->(exception) { 355 }
end

it { is_expected.to eq(355) }
end

context 'when configuration exception_status_code is blank' do
it { is_expected.to eq(500) }
end
end

describe "#with_scope" do
it "builds a temporary scope" do
inner_event = nil
Expand Down
17 changes: 17 additions & 0 deletions sentry-ruby/spec/sentry/rack/capture_exceptions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,23 @@
expect(env.key?("sentry.error_event_id")).to eq(false)
end

context 'with a custom exception status code config' do
before do
Sentry.configuration.enable_tracing = true
allow(Sentry).to receive(:status_code_for_exception).and_return(404)
end

it 'uses the custom status code' do
app = ->(_e) { raise exception }
stack = described_class.new(app)

expect { stack.call(env) }.to raise_error(ZeroDivisionError)

event = last_sentry_event.contexts
expect(event.dig(:trace, :status)).to eq('not_found')
end
end

context "with config.include_local_variables = true" do
before do
perform_basic_setup do |config|
Expand Down