dfg-airbrake 5.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/airbrake.rb +57 -0
- data/lib/airbrake/capistrano/tasks.rb +65 -0
- data/lib/airbrake/delayed_job/plugin1.rb +52 -0
- data/lib/airbrake/rack/middleware.rb +59 -0
- data/lib/airbrake/rack/notice_builder.rb +125 -0
- data/lib/airbrake/rack/user.rb +61 -0
- data/lib/airbrake/rails/action_controller.rb +37 -0
- data/lib/airbrake/rails/active_job.rb +35 -0
- data/lib/airbrake/rails/active_record.rb +36 -0
- data/lib/airbrake/rails/railtie.rb +79 -0
- data/lib/airbrake/rake/task_ext.rb +66 -0
- data/lib/airbrake/rake/tasks.rb +118 -0
- data/lib/airbrake/resque/failure.rb +19 -0
- data/lib/airbrake/sidekiq/error_handler.rb +35 -0
- data/lib/airbrake/version.rb +6 -0
- data/lib/generators/airbrake_generator.rb +25 -0
- data/lib/generators/airbrake_initializer.rb.erb +68 -0
- data/spec/airbrake_spec.rb +17 -0
- data/spec/apps/rack/dummy_app.rb +17 -0
- data/spec/apps/rails/dummy_app.rb +156 -0
- data/spec/apps/rails/dummy_task.rake +20 -0
- data/spec/apps/sinatra/composite_app/sinatra_app1.rb +11 -0
- data/spec/apps/sinatra/composite_app/sinatra_app2.rb +11 -0
- data/spec/apps/sinatra/dummy_app.rb +12 -0
- data/spec/integration/rack/rack_spec.rb +17 -0
- data/spec/integration/rails/rails_spec.rb +216 -0
- data/spec/integration/rails/rake_spec.rb +160 -0
- data/spec/integration/shared_examples/rack_examples.rb +126 -0
- data/spec/integration/sinatra/sinatra_spec.rb +77 -0
- data/spec/spec_helper.rb +116 -0
- data/spec/unit/rack/middleware_spec.rb +136 -0
- data/spec/unit/rack/notice_builder_spec.rb +157 -0
- data/spec/unit/rack/user_spec.rb +172 -0
- data/spec/unit/rake/tasks_spec.rb +67 -0
- data/spec/unit/sidekiq/error_handler_spec.rb +33 -0
- metadata +247 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Airbrake do
|
4
|
+
describe ".add_rack_builder" do
|
5
|
+
let :builder do
|
6
|
+
proc { |_, _| nil }
|
7
|
+
end
|
8
|
+
|
9
|
+
after { Airbrake::Rack::NoticeBuilder.builders.delete(builder) }
|
10
|
+
|
11
|
+
it "adds new builder to the chain" do
|
12
|
+
expect { Airbrake.add_rack_builder(&builder) }.to change {
|
13
|
+
Airbrake::Rack::NoticeBuilder.builders.count
|
14
|
+
}.by(1)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
DummyApp = Rack::Builder.new do
|
2
|
+
use Rack::ShowExceptions
|
3
|
+
use Airbrake::Rack::Middleware
|
4
|
+
use Warden::Manager
|
5
|
+
|
6
|
+
map '/' do
|
7
|
+
run(
|
8
|
+
proc do |_env|
|
9
|
+
[200, { 'Content-Type' => 'text/plain' }, ['Hello from index']]
|
10
|
+
end
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
map '/crash' do
|
15
|
+
run proc { |_env| raise AirbrakeTestError }
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
class DummyApp < Rails::Application
|
2
|
+
# Rails requires these two keys.
|
3
|
+
config.session_store :cookie_store, key: 'jiez4Mielu1AiHugog3shiiPhe3lai3faer'
|
4
|
+
config.secret_token = 'ni6aeph6aeriBiphesh8omahv6cohpue5Quah5ceiMohtuvei8'
|
5
|
+
|
6
|
+
if Gem::Version.new(Rails.version) > Gem::Version.new('3.2.0')
|
7
|
+
config.secret_key_base = '62773890cad9d9d584b57320f8612f8f7378a90aadcabc6ee'
|
8
|
+
end
|
9
|
+
|
10
|
+
# Configure a logger, without it the tests can't run.
|
11
|
+
vsn = Rails.version.split('').values_at(0, 2).join('')
|
12
|
+
log_path = File.join(File.dirname(__FILE__), 'logs', "#{vsn}.log")
|
13
|
+
config.logger = Logger.new(log_path)
|
14
|
+
Rails.logger = config.logger
|
15
|
+
|
16
|
+
config.active_support.deprecation = :stderr
|
17
|
+
|
18
|
+
config.middleware.use Warden::Manager
|
19
|
+
|
20
|
+
# In Rails 4.2.x Active Record suppresses errors raised within
|
21
|
+
# 'after_rollback' & 'after_commit' callbacks and only print them to the
|
22
|
+
# logs. In the next version, these errors will no longer be suppressed.
|
23
|
+
# Instead, the errors will propagate normally just like in other Active Record
|
24
|
+
# callbacks.
|
25
|
+
config.active_record.raise_in_transactional_callbacks = true if vsn == '42'
|
26
|
+
|
27
|
+
# Silences the warning, which says 'config.eager_load is set to nil'.
|
28
|
+
config.eager_load = false
|
29
|
+
|
30
|
+
routes.append do
|
31
|
+
get '/' => 'dummy#index'
|
32
|
+
get '/crash' => 'dummy#crash'
|
33
|
+
get '/notify_airbrake_helper' => 'dummy#notify_airbrake_helper'
|
34
|
+
get '/notify_airbrake_sync_helper' => 'dummy#notify_airbrake_sync_helper'
|
35
|
+
get '/active_record_after_commit' => 'dummy#active_record_after_commit'
|
36
|
+
get '/active_record_after_rollback' => 'dummy#active_record_after_rollback'
|
37
|
+
get '/active_job' => 'dummy#active_job'
|
38
|
+
get '/resque' => 'dummy#resque'
|
39
|
+
get '/delayed_job' => 'dummy#delayed_job'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Book < ActiveRecord::Base
|
44
|
+
after_commit :raise_error_after_commit
|
45
|
+
after_rollback :raise_error_after_rollback
|
46
|
+
|
47
|
+
def raise_error_after_commit
|
48
|
+
raise AirbrakeTestError, 'after_commit'
|
49
|
+
end
|
50
|
+
|
51
|
+
def raise_error_after_rollback
|
52
|
+
raise AirbrakeTestError, 'after_rollback'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# ActiveJob.
|
57
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('4.2')
|
58
|
+
class BingoJob < ActiveJob::Base
|
59
|
+
queue_as :bingo
|
60
|
+
|
61
|
+
class BingoWrapper
|
62
|
+
def initialize(bingo)
|
63
|
+
@bingo = bingo
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def perform(*_args)
|
68
|
+
@wrapper = BingoWrapper.new(self)
|
69
|
+
raise AirbrakeTestError, 'active_job error'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Resque.
|
75
|
+
class BingoWorker
|
76
|
+
@queue = :bingo_workers_queue
|
77
|
+
|
78
|
+
def self.perform(_bango, _bongo)
|
79
|
+
raise AirbrakeTestError, 'resque error'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# DelayedJob.
|
84
|
+
BangoJob = Struct.new(:bingo, :bongo) do
|
85
|
+
def perform
|
86
|
+
raise AirbrakeTestError, 'delayed_job error'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class DummyController < ActionController::Base
|
91
|
+
layout 'application'
|
92
|
+
|
93
|
+
self.view_paths = [
|
94
|
+
ActionView::FixtureResolver.new(
|
95
|
+
'layouts/application.html.erb' => '<%= yield %>',
|
96
|
+
'dummy/index.html.erb' => 'Hello from index',
|
97
|
+
'dummy/notify_airbrake_helper.html.erb' => 'notify_airbrake_helper',
|
98
|
+
'dummy/notify_airbrake_sync_helper.html.erb' => 'notify_airbrake_helper_sync',
|
99
|
+
'dummy/active_record_after_commit.html.erb' => 'active_record_after_commit',
|
100
|
+
'dummy/active_record_after_rollback.html.erb' => 'active_record_after_rollback',
|
101
|
+
'dummy/active_job.html.erb' => 'active_job',
|
102
|
+
'dummy/resque.html.erb' => 'resque',
|
103
|
+
'dummy/delayed_job.html.erb' => 'delayed_job'
|
104
|
+
)
|
105
|
+
]
|
106
|
+
|
107
|
+
def index; end
|
108
|
+
|
109
|
+
def crash
|
110
|
+
raise AirbrakeTestError
|
111
|
+
end
|
112
|
+
|
113
|
+
def notify_airbrake_helper
|
114
|
+
notify_airbrake(AirbrakeTestError.new)
|
115
|
+
end
|
116
|
+
|
117
|
+
def notify_airbrake_sync_helper
|
118
|
+
notify_airbrake_sync(AirbrakeTestError.new)
|
119
|
+
end
|
120
|
+
|
121
|
+
def active_record_after_commit
|
122
|
+
Book.create(title: 'Bingo')
|
123
|
+
end
|
124
|
+
|
125
|
+
def active_record_after_rollback
|
126
|
+
Book.transaction do
|
127
|
+
Book.create(title: 'Bango')
|
128
|
+
raise ActiveRecord::Rollback
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def active_job
|
133
|
+
BingoJob.perform_later('bango', 'bongo')
|
134
|
+
end
|
135
|
+
|
136
|
+
def resque
|
137
|
+
Resque.enqueue(BingoWorker, 'bango', 'bongo')
|
138
|
+
end
|
139
|
+
|
140
|
+
def delayed_job
|
141
|
+
Delayed::Job.enqueue(BangoJob.new('bingo', 'bongo'))
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Initializes middlewares and such.
|
146
|
+
DummyApp.initialize!
|
147
|
+
|
148
|
+
ActiveRecord::Base.connection.create_table(:books) do |t|
|
149
|
+
t.string(:title)
|
150
|
+
end
|
151
|
+
|
152
|
+
ActiveRecord::Migration.verbose = false
|
153
|
+
require 'generators/delayed_job/templates/migration'
|
154
|
+
ActiveRecord::Schema.define do
|
155
|
+
CreateDelayedJobs.up
|
156
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Keep this before any task definitions to collect extra info about tasks.
|
2
|
+
# Without this line the tests will fail.
|
3
|
+
Rake::TaskManager.record_task_metadata = true
|
4
|
+
|
5
|
+
namespace :bingo do
|
6
|
+
# This task contains *maximum* amount of information.
|
7
|
+
desc 'Dummy description'
|
8
|
+
task :bango, [:dummy_arg] => [:environment] do |_t, _args|
|
9
|
+
raise AirbrakeTestError
|
10
|
+
end
|
11
|
+
|
12
|
+
# This task contains *minimum* amount of information.
|
13
|
+
task :bongo do
|
14
|
+
raise AirbrakeTestError
|
15
|
+
end
|
16
|
+
|
17
|
+
task :environment do
|
18
|
+
# No-op.
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
class SinatraApp1 < Sinatra::Base
|
4
|
+
get('/') { raise AirbrakeTestError }
|
5
|
+
end
|
6
|
+
|
7
|
+
Airbrake.configure(SinatraApp1) do |c|
|
8
|
+
c.project_id = 113743
|
9
|
+
c.project_key = 'fd04e13d806a90f96614ad8e529b2822'
|
10
|
+
c.logger = Logger.new('/dev/null')
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
class SinatraApp2 < Sinatra::Base
|
4
|
+
get('/') { raise AirbrakeTestError }
|
5
|
+
end
|
6
|
+
|
7
|
+
Airbrake.configure(SinatraApp2) do |c|
|
8
|
+
c.project_id = 99123
|
9
|
+
c.project_key = 'ad04e13d806a90f96614ad8e529b2821'
|
10
|
+
c.logger = Logger.new('/dev/null')
|
11
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'integration/shared_examples/rack_examples'
|
3
|
+
|
4
|
+
RSpec.describe "Rack integration specs" do
|
5
|
+
let(:app) { DummyApp }
|
6
|
+
|
7
|
+
include_examples 'rack examples'
|
8
|
+
|
9
|
+
describe "context payload" do
|
10
|
+
it "includes version" do
|
11
|
+
get '/crash'
|
12
|
+
wait_for_a_request_with_body(
|
13
|
+
/"context":{.*"version":"1.2.3 Rack\.version.+Rack\.release/
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'integration/shared_examples/rack_examples'
|
3
|
+
|
4
|
+
RSpec.describe "Rails integration specs" do
|
5
|
+
include Warden::Test::Helpers
|
6
|
+
|
7
|
+
let(:app) { Rails.application }
|
8
|
+
|
9
|
+
include_examples 'rack examples'
|
10
|
+
|
11
|
+
if ::Rails.version.start_with?('5.')
|
12
|
+
it "inserts the Airbrake Rack middleware after DebugExceptions" do
|
13
|
+
middlewares = Rails.configuration.middleware.middlewares.map(&:inspect)
|
14
|
+
own_idx = middlewares.index('Airbrake::Rack::Middleware')
|
15
|
+
|
16
|
+
expect(middlewares[own_idx - 1]).to eq('ActionDispatch::DebugExceptions')
|
17
|
+
end
|
18
|
+
else
|
19
|
+
it "inserts the Airbrake Rack middleware after ConnectionManagement" do
|
20
|
+
middlewares = Rails.configuration.middleware.middlewares.map(&:inspect)
|
21
|
+
own_idx = middlewares.index('Airbrake::Rack::Middleware')
|
22
|
+
|
23
|
+
expect(middlewares[own_idx - 1]).
|
24
|
+
to eq('ActiveRecord::ConnectionAdapters::ConnectionManagement')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
shared_examples 'context payload content' do |route|
|
29
|
+
before do
|
30
|
+
login_as(OpenStruct.new(id: 1, email: 'qa@example.com', username: 'qa-dept'))
|
31
|
+
get(route, foo: :bar)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "includes component information" do
|
35
|
+
wait_for_a_request_with_body(/"context":{.*"component":"dummy".*}/)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "includes action information" do
|
39
|
+
case route
|
40
|
+
when '/crash'
|
41
|
+
wait_for_a_request_with_body(/"context":{.*"action":"crash".*}/)
|
42
|
+
when '/notify_airbrake_helper'
|
43
|
+
wait_for_a_request_with_body(
|
44
|
+
/"context":{.*"action":"notify_airbrake_helper".*}/
|
45
|
+
)
|
46
|
+
when '/notify_airbrake_sync_helper'
|
47
|
+
wait_for_a_request_with_body(
|
48
|
+
/"context":{.*"action":"notify_airbrake_sync_helper".*}/
|
49
|
+
)
|
50
|
+
else
|
51
|
+
raise 'Unknown route'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "includes version" do
|
56
|
+
wait_for_a_request_with_body(/"context":{.*"version":"1.2.3 Rails/)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "includes session" do
|
60
|
+
wait_for_a_request_with_body(
|
61
|
+
/"context":{.*"session":{.*"session_id":"\w+".*}/
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "includes params" do
|
66
|
+
action = route[1..-1]
|
67
|
+
wait_for_a_request_with_body(
|
68
|
+
/"context":{.*"params":{.*"controller":"dummy","action":"#{action}".*}/
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "context payload" do
|
74
|
+
context "when exception reported through middleware" do
|
75
|
+
include_examples('context payload content', '/crash')
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when exception reported through the notify_airbrake helper" do
|
79
|
+
include_examples('context payload content', '/notify_airbrake_helper')
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when exception reported through the notify_airbrake_sync helper" do
|
83
|
+
include_examples('context payload content', '/notify_airbrake_sync_helper')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "Active Record callbacks" do
|
88
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('5.1.0.alpha')
|
89
|
+
it "reports exceptions in after_commit callbacks" do
|
90
|
+
get '/active_record_after_commit'
|
91
|
+
wait_for(
|
92
|
+
a_request(:post, endpoint).
|
93
|
+
with(body: /"type":"AirbrakeTestError","message":"after_commit"/)
|
94
|
+
).to have_been_made.twice
|
95
|
+
end
|
96
|
+
|
97
|
+
it "reports exceptions in after_rollback callbacks" do
|
98
|
+
get '/active_record_after_rollback'
|
99
|
+
wait_for(
|
100
|
+
a_request(:post, endpoint).
|
101
|
+
with(body: /"type":"AirbrakeTestError","message":"after_rollback"/)
|
102
|
+
).to have_been_made.twice
|
103
|
+
end
|
104
|
+
else
|
105
|
+
it "reports exceptions in after_commit callbacks" do
|
106
|
+
get '/active_record_after_commit'
|
107
|
+
wait_for_a_request_with_body(
|
108
|
+
/"type":"AirbrakeTestError","message":"after_commit"/
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "reports exceptions in after_rollback callbacks" do
|
113
|
+
get '/active_record_after_rollback'
|
114
|
+
wait_for_a_request_with_body(
|
115
|
+
/"type":"AirbrakeTestError","message":"after_rollback"/
|
116
|
+
)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('4.2')
|
122
|
+
describe "ActiveJob jobs" do
|
123
|
+
it "reports exceptions occurring in ActiveJob workers" do
|
124
|
+
get '/active_job'
|
125
|
+
sleep 2
|
126
|
+
|
127
|
+
wait_for(
|
128
|
+
a_request(:post, endpoint).
|
129
|
+
with(body: /"message":"active_job error"/)
|
130
|
+
).to have_been_made.at_least_once
|
131
|
+
end
|
132
|
+
|
133
|
+
it "does not raise SystemStackError" do
|
134
|
+
get '/active_job'
|
135
|
+
sleep 2
|
136
|
+
|
137
|
+
wait_for(
|
138
|
+
a_request(:post, endpoint).
|
139
|
+
with(body: /"type":"SystemStackError"/)
|
140
|
+
).not_to have_been_made
|
141
|
+
end
|
142
|
+
|
143
|
+
context "when Airbrake is not configured" do
|
144
|
+
it "doesn't report errors" do
|
145
|
+
allow(Airbrake).to receive(:build_notice).and_return(nil)
|
146
|
+
allow(Airbrake).to receive(:notify)
|
147
|
+
|
148
|
+
# Make sure we don't call `build_notice` more than 1 time. Rack
|
149
|
+
# integration will try to handle error 500 and we want to prevent
|
150
|
+
# that: https://github.com/airbrake/airbrake/pull/583
|
151
|
+
allow_any_instance_of(Airbrake::Rack::Middleware).to(
|
152
|
+
receive(:notify_airbrake).
|
153
|
+
and_return(nil)
|
154
|
+
)
|
155
|
+
|
156
|
+
get '/active_job'
|
157
|
+
sleep 2
|
158
|
+
|
159
|
+
wait_for(
|
160
|
+
a_request(:post, endpoint).
|
161
|
+
with(body: /"message":"active_job error"/)
|
162
|
+
).not_to have_been_made
|
163
|
+
|
164
|
+
expect(Airbrake).to have_received(:build_notice)
|
165
|
+
expect(Airbrake).not_to have_received(:notify)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "Resque workers" do
|
172
|
+
it "reports exceptions occurring in Resque workers" do
|
173
|
+
with_resque { get '/resque' }
|
174
|
+
|
175
|
+
wait_for_a_request_with_body(
|
176
|
+
/"message":"resque\serror".*"params":{.*
|
177
|
+
"class":"BingoWorker","args":\["bango","bongo"\].*}/x
|
178
|
+
)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Delayed Job doesn't support Ruby 1.9.2
|
183
|
+
if Gem::Version.new(RUBY_VERSION) > Gem::Version.new('1.9.2')
|
184
|
+
describe "DelayedJob jobs" do
|
185
|
+
it "reports exceptions occurring in DelayedJob jobs" do
|
186
|
+
get '/delayed_job'
|
187
|
+
sleep 2
|
188
|
+
|
189
|
+
wait_for_a_request_with_body(
|
190
|
+
%r("message":"delayed_job\serror".*"params":{.*
|
191
|
+
"handler":"---\s!ruby/struct:BangoJob\\nbingo:\s
|
192
|
+
bingo\\nbongo:\sbongo\\n".*})x
|
193
|
+
)
|
194
|
+
|
195
|
+
# Two requests are performed during this example. We care only about one.
|
196
|
+
# Sleep guarantees that we let the unimportant request occur here and not
|
197
|
+
# elsewhere.
|
198
|
+
sleep 2
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe "notice payload when a user is authenticated without Warden" do
|
204
|
+
context "when the current_user method is defined" do
|
205
|
+
it "contains the user information" do
|
206
|
+
user = OpenStruct.new(id: 1, email: 'qa@example.com', username: 'qa-dept')
|
207
|
+
allow_any_instance_of(DummyController).to receive(:current_user) { user }
|
208
|
+
|
209
|
+
get '/crash'
|
210
|
+
wait_for_a_request_with_body(
|
211
|
+
/"user":{"id":"1","username":"qa-dept","email":"qa@example.com"}/
|
212
|
+
)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|