sentry-ruby/lib/sentry/rack/capture_exceptions.rb
# frozen_string_literal: true
module Sentry
module Rack
class CaptureExceptions
ERROR_EVENT_ID_KEY = "sentry.error_event_id"
MECHANISM_TYPE = "rack"
SPAN_ORIGIN = "auto.http.rack"
def initialize(app)
@app = app
end
def call(env)
return @app.call(env) unless Sentry.initialized?
# make sure the current thread has a clean hub
Sentry.clone_hub_to_current_thread
Sentry.with_scope do |scope|
Sentry.with_session_tracking do
scope.clear_breadcrumbs
scope.set_transaction_name(env["PATH_INFO"], source: :url) if env["PATH_INFO"]
scope.set_rack_env(env)
transaction = start_transaction(env, scope)
scope.set_span(transaction) if transaction
begin
response = @app.call(env)
rescue Sentry::Error
finish_transaction(transaction, 500)
raise # Don't capture Sentry errors
rescue Exception => e
capture_exception(e, env)
finish_transaction(transaction, 500)
raise
end
exception = collect_exception(env)
capture_exception(exception, env) if exception
finish_transaction(transaction, response[0])
response
end
end
end
private
def collect_exception(env)
env["rack.exception"] || env["sinatra.error"]
end
def transaction_op
"http.server"
end
def capture_exception(exception, env)
Sentry.capture_exception(exception, hint: { mechanism: mechanism }).tap do |event|
env[ERROR_EVENT_ID_KEY] = event.event_id if event
end
end
def start_transaction(env, scope)
options = {
name: scope.transaction_name,
source: scope.transaction_source,
op: transaction_op,
origin: SPAN_ORIGIN
}
transaction = Sentry.continue_trace(env, **options)
Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
end
def finish_transaction(transaction, status_code)
return unless transaction
transaction.set_http_status(status_code)
transaction.finish
end
def mechanism
Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
end
end
end
end