diff --git a/lib/jsonapi/configuration.rb b/lib/jsonapi/configuration.rb index 63ad5fab1..729bc09f1 100644 --- a/lib/jsonapi/configuration.rb +++ b/lib/jsonapi/configuration.rb @@ -26,6 +26,7 @@ class Configuration :top_level_meta_page_count_key, :allow_transactions, :include_backtraces_in_errors, + :include_application_backtraces_in_errors, :exception_class_whitelist, :whitelist_all_exceptions, :always_include_to_one_linkage_data, @@ -80,6 +81,10 @@ def initialize # responses. Defaults to `false` in production, and `true` otherwise. self.include_backtraces_in_errors = !Rails.env.production? + # Whether or not to include exception application backtraces in JSONAPI error + # responses. Defaults to `false` in production, and `true` otherwise. + self.include_application_backtraces_in_errors = !Rails.env.production? + # List of classes that should not be rescued by the operations processor. # For example, if you use Pundit for authorization, you might # raise a Pundit::NotAuthorizedError at some point during operations @@ -246,6 +251,8 @@ def resource_finder=(resource_finder) attr_writer :include_backtraces_in_errors + attr_writer :include_application_backtraces_in_errors + attr_writer :exception_class_whitelist attr_writer :whitelist_all_exceptions diff --git a/lib/jsonapi/exceptions.rb b/lib/jsonapi/exceptions.rb index bb967d4fd..e589f08e0 100644 --- a/lib/jsonapi/exceptions.rb +++ b/lib/jsonapi/exceptions.rb @@ -49,6 +49,12 @@ def errors meta[:backtrace] = exception.backtrace end + if JSONAPI.configuration.include_application_backtraces_in_errors + meta ||= Hash.new + meta[:exception] ||= exception.message + meta[:application_backtrace] = exception.backtrace.select{|line| line =~ /#{Rails.root}/} + end + [create_error_object(code: JSONAPI::INTERNAL_SERVER_ERROR, status: :internal_server_error, title: I18n.t('jsonapi-resources.exceptions.internal_server_error.title', diff --git a/test/controllers/controller_test.rb b/test/controllers/controller_test.rb index 950430493..2bc5454a6 100644 --- a/test/controllers/controller_test.rb +++ b/test/controllers/controller_test.rb @@ -126,18 +126,37 @@ def test_exception_includes_backtrace_when_enabled JSONAPI.configuration.include_backtraces_in_errors = true assert_cacheable_get :index assert_response 500 - assert_includes @response.body, "backtrace", "expected backtrace in error body" + assert_includes @response.body, '"backtrace"', "expected backtrace in error body" JSONAPI.configuration.include_backtraces_in_errors = false assert_cacheable_get :index assert_response 500 - refute_includes @response.body, "backtrace", "expected backtrace in error body" + refute_includes @response.body, '"backtrace"', "expected backtrace in error body" ensure $PostProcessorRaisesErrors = false JSONAPI.configuration.include_backtraces_in_errors = original_config end + def test_exception_includes_application_backtrace_when_enabled + original_config = JSONAPI.configuration.include_application_backtraces_in_errors + $PostProcessorRaisesErrors = true + + JSONAPI.configuration.include_application_backtraces_in_errors = true + assert_cacheable_get :index + assert_response 500 + assert_includes @response.body, '"application_backtrace"', "expected application backtrace in error body" + + JSONAPI.configuration.include_application_backtraces_in_errors = false + assert_cacheable_get :index + assert_response 500 + refute_includes @response.body, '"application_backtrace"', "expected application backtrace in error body" + + ensure + $PostProcessorRaisesErrors = false + JSONAPI.configuration.include_application_backtraces_in_errors = original_config + end + def test_on_server_error_block_callback_with_exception original_config = JSONAPI.configuration.dup JSONAPI.configuration.exception_class_whitelist = [] diff --git a/test/test_helper.rb b/test/test_helper.rb index 5d79ef2e6..5e536e594 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -513,7 +513,7 @@ def assert_cacheable_get(action, *args) ActiveSupport::Notifications.subscribed(normal_query_callback, 'sql.active_record') do get action, *args end - non_caching_response = json_response_sans_backtraces + non_caching_response = json_response_sans_all_backtraces non_caching_status = response.status # Don't let all the cache-testing requests mess with assert_query_count @@ -565,7 +565,7 @@ def assert_cacheable_get(action, *args) ) assert_equal( non_caching_response.pretty_inspect, - json_response_sans_backtraces.pretty_inspect, + json_response_sans_all_backtraces.pretty_inspect, "Cache (mode: #{mode}) #{phase} response body must match normal response" ) assert_operator( @@ -605,12 +605,13 @@ def assert_cacheable_get(action, *args) private - def json_response_sans_backtraces + def json_response_sans_all_backtraces return nil if response.body.to_s.strip.empty? r = json_response.dup (r["errors"] || []).each do |err| err["meta"].delete("backtrace") if err.has_key?("meta") + err["meta"].delete("application_backtrace") if err.has_key?("meta") end return r end