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

Support cleaning up exception backtrace with customized backtrace_cleaner #1011

Merged
merged 2 commits into from
Sep 4, 2020
Merged
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
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ Style/CommentedKeyword:
Style/RescueModifier:
Enabled: false

Style/RegexpLiteral:
Enabled: false

Style/StringLiterals:
Enabled: false

Expand Down
2 changes: 2 additions & 0 deletions lib/raven/backtrace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ def self.in_app_pattern
def self.parse(backtrace, opts = {})
ruby_lines = backtrace.is_a?(Array) ? backtrace : backtrace.split(/\n\s*/)

ruby_lines = opts[:configuration].backtrace_cleanup_callback.call(ruby_lines) if opts[:configuration]&.backtrace_cleanup_callback

filters = opts[:filters] || []
filtered_lines = ruby_lines.to_a.map do |line|
filters.reduce(line) do |nested_line, proc|
Expand Down
13 changes: 13 additions & 0 deletions lib/raven/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ class Configuration
# Otherwise, can be one of "http", "https", or "dummy"
attr_accessor :scheme

# a proc/lambda that takes an array of stack traces
# it'll be used to silence (reduce) backtrace of the exception
#
# for example:
#
# ```ruby
# Raven.configuration.backtrace_cleanup_callback = lambda do |backtrace|
# Rails.backtrace_cleaner.clean(backtrace)
# end
# ```
#
attr_accessor :backtrace_cleanup_callback

# Secret key for authentication with the Sentry server
# If you provide a DSN, this will be set automatically.
#
Expand Down
2 changes: 1 addition & 1 deletion lib/raven/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def add_exception_interface(exc)
end

def stacktrace_interface_from(backtrace)
Backtrace.parse(backtrace).lines.reverse.each_with_object([]) do |line, memo|
Backtrace.parse(backtrace, { configuration: configuration }).lines.reverse.each_with_object([]) do |line, memo|
frame = StacktraceInterface::Frame.new
frame.abs_path = line.file if line.file
frame.function = line.method if line.method
Expand Down
7 changes: 7 additions & 0 deletions lib/raven/integrations/rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Rails < ::Rails::Railtie
require 'raven/integrations/rails/overrides/streaming_reporter'
require 'raven/integrations/rails/controller_methods'
require 'raven/integrations/rails/controller_transaction'
require 'raven/integrations/rails/backtrace_cleaner'
require 'raven/integrations/rack'

initializer "raven.use_rack_middleware" do |app|
Expand Down Expand Up @@ -37,6 +38,12 @@ class Rails < ::Rails::Railtie

config.before_initialize do
Raven.configuration.logger = ::Rails.logger

backtrace_cleaner = Raven::Rails::BacktraceCleaner.new

Raven.configuration.backtrace_cleanup_callback = lambda do |backtrace|
backtrace_cleaner.clean(backtrace)
end
end

config.after_initialize do
Expand Down
29 changes: 29 additions & 0 deletions lib/raven/integrations/rails/backtrace_cleaner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require "active_support/backtrace_cleaner"
require "active_support/core_ext/string/access"

module Raven
class Rails
class BacktraceCleaner < ActiveSupport::BacktraceCleaner
APP_DIRS_PATTERN = /\A(?:\.\/)?(?:app|config|lib|test|\(\w*\))/.freeze
RENDER_TEMPLATE_PATTERN = /:in `.*_\w+_{2,3}\d+_\d+'/.freeze

def initialize
super
# we don't want any default silencers because they're too aggressive
remove_silencers!

@root = "#{Raven.configuration.project_root}/"
add_filter do |line|
line.start_with?(@root) ? line.from(@root.size) : line
end
add_filter do |line|
if line =~ RENDER_TEMPLATE_PATTERN
line.sub(RENDER_TEMPLATE_PATTERN, "")
else
line
end
end
end
end
end
end
13 changes: 13 additions & 0 deletions spec/raven/backtrace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
@backtrace = Raven::Backtrace.parse(Thread.current.backtrace)
end

it "calls backtrace_cleanup_callback if it's present in the configuration" do
called = false
callback = proc do |backtrace|
called = true
backtrace
end
config = Raven.configuration
config.backtrace_cleanup_callback = callback
Raven::Backtrace.parse(Thread.current.backtrace, configuration: config)

expect(called).to eq(true)
end

it "#lines" do
expect(@backtrace.lines.first).to be_a(Raven::Backtrace::Line)
end
Expand Down
12 changes: 12 additions & 0 deletions spec/raven/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,18 @@
end
end

describe "config: backtrace_cleanup_callback" do
it "defaults to nil" do
expect(subject.backtrace_cleanup_callback).to eq(nil)
end

it "takes a proc and store it" do
subject.backtrace_cleanup_callback = proc {}

expect(subject.backtrace_cleanup_callback).to be_a(Proc)
end
end

context 'with a should_capture callback configured' do
before(:each) do
subject.should_capture = ->(exc_or_msg) { exc_or_msg != "dont send me" }
Expand Down
28 changes: 26 additions & 2 deletions spec/raven/integrations/rails_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,35 @@
expect(event["exception"]["values"][0]["value"]).to eq("An unhandled exception!")
end

it "should properly set the exception's URL" do
it "should capture exceptions in production" do
get "/exception"

expect(response.status).to eq(500)
event = JSON.parse!(Raven.client.transport.events.first[1])
expect(event["exception"]["values"][0]["type"]).to eq("RuntimeError")
expect(event["exception"]["values"][0]["value"]).to eq("An unhandled exception!")
end

it "filters exception backtrace with with custom BacktraceCleaner" do
get "/view_exception"

event = JSON.parse!(Raven.client.transport.events.first[1])
traces = event.dig("exception", "values", 0, "stacktrace", "frames")
expect(traces.dig(-1, "filename")).to eq("inline template")

# we want to avoid something like "_inline_template__3014794444104730113_10960"
expect(traces.dig(-1, "function")).to be_nil
end

it "doesn't filters exception backtrace if backtrace_cleanup_callback is overridden" do
Raven.configuration.backtrace_cleanup_callback = nil

get "/view_exception"

event = JSON.parse!(Raven.client.transport.events.first[1])
expect(event['request']['url']).to eq("http://www.example.com/exception")
traces = event.dig("exception", "values", 0, "stacktrace", "frames")
expect(traces.dig(-1, "filename")).to eq("inline template")
expect(traces.dig(-1, "function")).not_to be_nil
end

it "sets transaction to ControllerName#method" do
Expand Down
5 changes: 5 additions & 0 deletions spec/support/test_rails_app/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class TestApp < Rails::Application

routes.append do
get "/exception", :to => "hello#exception"
get "/view_exception", :to => "hello#view_exception"
root :to => "hello#world"
end

Expand All @@ -36,6 +37,10 @@ def exception
raise "An unhandled exception!"
end

def view_exception
render inline: "<%= foo %>"
end

def world
render :plain => "Hello World!"
end
Expand Down