rollbar 2.10.0 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +7 -2
- data/CHANGELOG.md +20 -0
- data/README.md +73 -16
- data/docs/configuration.md +10 -0
- data/gemfiles/rails30.gemfile +2 -0
- data/gemfiles/rails31.gemfile +2 -0
- data/gemfiles/rails32.gemfile +2 -0
- data/gemfiles/rails40.gemfile +2 -0
- data/gemfiles/rails41.gemfile +2 -0
- data/gemfiles/rails42.gemfile +2 -0
- data/gemfiles/rails50.gemfile +2 -0
- data/gemfiles/ruby_1_8_and_1_9_2.gemfile +43 -0
- data/lib/rollbar.rb +139 -353
- data/lib/rollbar/configuration.rb +4 -0
- data/lib/rollbar/item.rb +225 -0
- data/lib/rollbar/item/backtrace.rb +97 -0
- data/lib/rollbar/js.rb +0 -28
- data/lib/rollbar/language_support.rb +10 -0
- data/lib/rollbar/{js/middleware.rb → middleware/js.rb} +3 -4
- data/lib/rollbar/plugin.rb +63 -0
- data/lib/rollbar/plugins.rb +41 -0
- data/lib/rollbar/{active_job.rb → plugins/active_job.rb} +0 -0
- data/lib/rollbar/plugins/basic_socket.rb +16 -0
- data/lib/rollbar/plugins/delayed_job.rb +12 -0
- data/lib/rollbar/plugins/delayed_job/job_data.rb +16 -0
- data/lib/rollbar/{delayed_job.rb → plugins/delayed_job/plugin.rb} +1 -17
- data/lib/rollbar/plugins/goalie.rb +46 -0
- data/lib/rollbar/plugins/rack.rb +16 -0
- data/lib/rollbar/plugins/rails.rb +77 -0
- data/lib/rollbar/{rails → plugins/rails}/controller_methods.rb +0 -0
- data/lib/rollbar/plugins/rails/railtie30.rb +17 -0
- data/lib/rollbar/plugins/rails/railtie32.rb +18 -0
- data/lib/rollbar/plugins/rails/railtie_mixin.rb +33 -0
- data/lib/rollbar/plugins/rake.rb +45 -0
- data/lib/rollbar/plugins/sidekiq.rb +35 -0
- data/lib/rollbar/{sidekiq.rb → plugins/sidekiq/plugin.rb} +0 -18
- data/lib/rollbar/plugins/thread.rb +13 -0
- data/lib/rollbar/plugins/validations.rb +33 -0
- data/lib/rollbar/request_data_extractor.rb +30 -18
- data/lib/rollbar/scrubbers/params.rb +4 -2
- data/lib/rollbar/scrubbers/url.rb +30 -28
- data/lib/rollbar/util.rb +10 -0
- data/lib/rollbar/version.rb +1 -1
- data/spec/controllers/home_controller_spec.rb +4 -3
- data/spec/dummyapp/app/models/post.rb +9 -0
- data/spec/dummyapp/app/models/user.rb +2 -0
- data/spec/dummyapp/config/initializers/rollbar.rb +1 -0
- data/spec/fixtures/plugins/dummy1.rb +5 -0
- data/spec/fixtures/plugins/dummy2.rb +5 -0
- data/spec/rollbar/item_spec.rb +635 -0
- data/spec/rollbar/logger_proxy_spec.rb +4 -0
- data/spec/rollbar/{js/middleware_spec.rb → middleware/js_spec.rb} +32 -3
- data/spec/rollbar/plugin_spec.rb +147 -0
- data/spec/rollbar/{active_job_spec.rb → plugins/active_job_spec.rb} +0 -1
- data/spec/rollbar/{delayed_job → plugins/delayed_job}/job_data.rb +0 -0
- data/spec/rollbar/{delayed_job_spec.rb → plugins/delayed_job_spec.rb} +3 -6
- data/spec/rollbar/{middleware/rack/builder_spec.rb → plugins/rack_spec.rb} +2 -1
- data/spec/rollbar/{js/frameworks/rails_spec.rb → plugins/rails_js_spec.rb} +1 -1
- data/spec/rollbar/{rake_spec.rb → plugins/rake_spec.rb} +2 -1
- data/spec/rollbar/{sidekiq_spec.rb → plugins/sidekiq_spec.rb} +2 -1
- data/spec/rollbar/plugins/validations_spec.rb +43 -0
- data/spec/rollbar/plugins_spec.rb +68 -0
- data/spec/rollbar/request_data_extractor_spec.rb +56 -10
- data/spec/rollbar/scrubbers/params_spec.rb +13 -10
- data/spec/rollbar/scrubbers/url_spec.rb +17 -12
- data/spec/rollbar/sidekig/clear_scope_spec.rb +2 -1
- data/spec/rollbar/util_spec.rb +61 -0
- data/spec/rollbar_bc_spec.rb +10 -10
- data/spec/rollbar_spec.rb +57 -706
- data/spec/spec_helper.rb +8 -0
- data/spec/support/notifier_helpers.rb +1 -0
- data/spec/support/rollbar_api.rb +57 -0
- metadata +57 -33
- data/lib/rollbar/active_record_extension.rb +0 -14
- data/lib/rollbar/core_ext/basic_socket.rb +0 -7
- data/lib/rollbar/core_ext/thread.rb +0 -9
- data/lib/rollbar/goalie.rb +0 -33
- data/lib/rollbar/js/frameworks.rb +0 -6
- data/lib/rollbar/js/frameworks/rails.rb +0 -49
- data/lib/rollbar/js/version.rb +0 -5
- data/lib/rollbar/rack.rb +0 -9
- data/lib/rollbar/railtie.rb +0 -46
- data/lib/rollbar/rake.rb +0 -40
@@ -0,0 +1,13 @@
|
|
1
|
+
Rollbar.plugins.define('thread') do
|
2
|
+
execute do
|
3
|
+
Thread.class_eval do
|
4
|
+
def initialize_with_rollbar(*args, &block)
|
5
|
+
self[:_rollbar_notifier] ||= Rollbar.notifier.scope
|
6
|
+
initialize_without_rollbar(*args, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
alias_method :initialize_without_rollbar, :initialize
|
10
|
+
alias_method :initialize, :initialize_with_rollbar
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Rollbar.plugins.define('active_model') do
|
2
|
+
dependency { !configuration.disable_monkey_patch }
|
3
|
+
dependency { defined?(ActiveModel::Validations) }
|
4
|
+
dependency do
|
5
|
+
require 'active_record/version'
|
6
|
+
|
7
|
+
ActiveModel::VERSION::MAJOR >= 3
|
8
|
+
end
|
9
|
+
|
10
|
+
execute do
|
11
|
+
module Rollbar
|
12
|
+
# Module that defines methods to be used by instances using
|
13
|
+
# ActiveModel::Validations
|
14
|
+
# The name is ActiveRecordExtension in order to not break backwards
|
15
|
+
# compatibility, although probably it should be named
|
16
|
+
# Rollbar::ValidationsExtension or similar
|
17
|
+
module ActiveRecordExtension
|
18
|
+
def report_validation_errors_to_rollbar
|
19
|
+
errors.full_messages.each do |error|
|
20
|
+
Rollbar.log_info "[Rollbar] Reporting form validation error: #{error} for #{self}"
|
21
|
+
Rollbar.warning("Form Validation Error: #{error} for #{self}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
execute do
|
29
|
+
ActiveModel::Validations.module_eval do
|
30
|
+
include Rollbar::ActiveRecordExtension
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -23,20 +23,15 @@ module Rollbar
|
|
23
23
|
rack_req = ::Rack::Request.new(env)
|
24
24
|
|
25
25
|
sensitive_params = sensitive_params_list(env)
|
26
|
-
request_params =
|
27
|
-
get_params =
|
28
|
-
post_params =
|
29
|
-
raw_body_params =
|
30
|
-
cookies =
|
31
|
-
session =
|
32
|
-
route_params =
|
33
|
-
|
34
|
-
|
35
|
-
:scrub_user => Rollbar.configuration.scrub_user,
|
36
|
-
:scrub_password => Rollbar.configuration.scrub_password,
|
37
|
-
:randomize_scrub_length => Rollbar.configuration.randomize_scrub_length)
|
38
|
-
url = url_scrubber.call(rollbar_url(env))
|
39
|
-
|
26
|
+
request_params = scrub_params(rollbar_request_params(env), sensitive_params)
|
27
|
+
get_params = scrub_params(rollbar_get_params(rack_req), sensitive_params)
|
28
|
+
post_params = scrub_params(rollbar_post_params(rack_req), sensitive_params)
|
29
|
+
raw_body_params = scrub_params(mergeable_raw_body_params(rack_req), sensitive_params)
|
30
|
+
cookies = scrub_params(rollbar_request_cookies(rack_req), sensitive_params)
|
31
|
+
session = scrub_params(rollbar_request_session(rack_req), sensitive_params)
|
32
|
+
route_params = scrub_params(rollbar_route_params(env), sensitive_params)
|
33
|
+
|
34
|
+
url = scrub_url(rollbar_url(env), sensitive_params)
|
40
35
|
params = request_params.merge(get_params).merge(post_params).merge(raw_body_params)
|
41
36
|
|
42
37
|
data = {
|
@@ -57,6 +52,27 @@ module Rollbar
|
|
57
52
|
data
|
58
53
|
end
|
59
54
|
|
55
|
+
def scrub_url(url, sensitive_params)
|
56
|
+
options = {
|
57
|
+
:url => url,
|
58
|
+
:scrub_fields => Array(Rollbar.configuration.scrub_fields) + sensitive_params,
|
59
|
+
:scrub_user => Rollbar.configuration.scrub_user,
|
60
|
+
:scrub_password => Rollbar.configuration.scrub_password,
|
61
|
+
:randomize_scrub_length => Rollbar.configuration.randomize_scrub_length
|
62
|
+
}
|
63
|
+
|
64
|
+
Rollbar::Scrubbers::URL.call(options)
|
65
|
+
end
|
66
|
+
|
67
|
+
def scrub_params(params, sensitive_params)
|
68
|
+
options = {
|
69
|
+
:params => params,
|
70
|
+
:config => Rollbar.configuration.scrub_fields,
|
71
|
+
:extra_fields => sensitive_params
|
72
|
+
}
|
73
|
+
Rollbar::Scrubbers::Params.call(options)
|
74
|
+
end
|
75
|
+
|
60
76
|
private
|
61
77
|
|
62
78
|
def mergeable_raw_body_params(rack_req)
|
@@ -180,10 +196,6 @@ module Rollbar
|
|
180
196
|
{}
|
181
197
|
end
|
182
198
|
|
183
|
-
def rollbar_filtered_params(sensitive_params, params)
|
184
|
-
Rollbar::Scrubbers::Params.call(params, sensitive_params)
|
185
|
-
end
|
186
|
-
|
187
199
|
def sensitive_params_list(env)
|
188
200
|
Array(env['action_dispatch.parameter_filter'])
|
189
201
|
end
|
@@ -15,10 +15,12 @@ module Rollbar
|
|
15
15
|
new.call(*args)
|
16
16
|
end
|
17
17
|
|
18
|
-
def call(
|
18
|
+
def call(options = {})
|
19
|
+
params = options[:params]
|
19
20
|
return {} unless params
|
20
21
|
|
21
|
-
config =
|
22
|
+
config = options[:config]
|
23
|
+
extra_fields = options[:extra_fields]
|
22
24
|
|
23
25
|
scrub(params, build_scrub_options(config, extra_fields))
|
24
26
|
end
|
@@ -6,34 +6,36 @@ require 'rollbar/language_support'
|
|
6
6
|
module Rollbar
|
7
7
|
module Scrubbers
|
8
8
|
class URL
|
9
|
-
|
10
|
-
|
11
|
-
attr_reader :scrub_password
|
12
|
-
attr_reader :randomize_scrub_length
|
13
|
-
|
14
|
-
def initialize(options = {})
|
15
|
-
@regex = build_regex(options[:scrub_fields])
|
16
|
-
@scrub_user = options[:scrub_user]
|
17
|
-
@scrub_password = options[:scrub_password]
|
18
|
-
@randomize_scrub_length = options.fetch(:randomize_scrub_length, true)
|
9
|
+
def self.call(*args)
|
10
|
+
new.call(*args)
|
19
11
|
end
|
20
12
|
|
21
|
-
def call(
|
13
|
+
def call(options = {})
|
14
|
+
url = options[:url]
|
22
15
|
return url unless Rollbar::LanguageSupport.can_scrub_url?
|
23
16
|
|
17
|
+
filter(url,
|
18
|
+
build_regex(options[:scrub_fields]),
|
19
|
+
options[:scrub_user],
|
20
|
+
options[:scrub_password],
|
21
|
+
options.fetch(:randomize_scrub_length, true))
|
22
|
+
rescue => e
|
23
|
+
Rollbar.logger.error("[Rollbar] There was an error scrubbing the url: #{e}, options: #{options.inspect}")
|
24
|
+
url
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def filter(url, regex, scrub_user, scrub_password, randomize_scrub_length)
|
24
30
|
uri = URI.parse(url)
|
25
31
|
|
26
|
-
uri.user = filter_user(uri.user)
|
27
|
-
uri.password = filter_password(uri.password)
|
28
|
-
uri.query = filter_query(uri.query)
|
32
|
+
uri.user = filter_user(uri.user, scrub_user, randomize_scrub_length)
|
33
|
+
uri.password = filter_password(uri.password, scrub_password, randomize_scrub_length)
|
34
|
+
uri.query = filter_query(uri.query, regex, randomize_scrub_length)
|
29
35
|
|
30
36
|
uri.to_s
|
31
|
-
rescue
|
32
|
-
url
|
33
37
|
end
|
34
38
|
|
35
|
-
private
|
36
|
-
|
37
39
|
# Builds a regex to match with any of the received fields.
|
38
40
|
# The built regex will also match array params like 'user_ids[]'.
|
39
41
|
def build_regex(fields)
|
@@ -42,20 +44,20 @@ module Rollbar
|
|
42
44
|
Regexp.new("^#{fields_or}$")
|
43
45
|
end
|
44
46
|
|
45
|
-
def filter_user(user)
|
46
|
-
scrub_user && user ? filtered_value(user) : user
|
47
|
+
def filter_user(user, scrub_user, randomize_scrub_length)
|
48
|
+
scrub_user && user ? filtered_value(user, randomize_scrub_length) : user
|
47
49
|
end
|
48
50
|
|
49
|
-
def filter_password(password)
|
50
|
-
scrub_password && password ? filtered_value(password) : password
|
51
|
+
def filter_password(password, scrub_password, randomize_scrub_length)
|
52
|
+
scrub_password && password ? filtered_value(password, randomize_scrub_length) : password
|
51
53
|
end
|
52
54
|
|
53
|
-
def filter_query(query)
|
55
|
+
def filter_query(query, regex, randomize_scrub_length)
|
54
56
|
return query unless query
|
55
57
|
|
56
58
|
params = decode_www_form(query)
|
57
59
|
|
58
|
-
encoded_query = encode_www_form(filter_query_params(params))
|
60
|
+
encoded_query = encode_www_form(filter_query_params(params, regex, randomize_scrub_length))
|
59
61
|
|
60
62
|
# We want this to rebuild array params like foo[]=1&foo[]=2
|
61
63
|
CGI.unescape(encoded_query)
|
@@ -69,17 +71,17 @@ module Rollbar
|
|
69
71
|
URI.encode_www_form(params)
|
70
72
|
end
|
71
73
|
|
72
|
-
def filter_query_params(params)
|
74
|
+
def filter_query_params(params, regex, randomize_scrub_length)
|
73
75
|
params.map do |key, value|
|
74
|
-
[key, filter_key?(key) ? filtered_value(value) : value]
|
76
|
+
[key, filter_key?(key, regex) ? filtered_value(value, randomize_scrub_length) : value]
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
78
|
-
def filter_key?(key)
|
80
|
+
def filter_key?(key, regex)
|
79
81
|
!!(key =~ regex)
|
80
82
|
end
|
81
83
|
|
82
|
-
def filtered_value(value)
|
84
|
+
def filtered_value(value, randomize_scrub_length)
|
83
85
|
if randomize_scrub_length
|
84
86
|
random_filtered_value
|
85
87
|
else
|
data/lib/rollbar/util.rb
CHANGED
@@ -91,5 +91,15 @@ module Rollbar
|
|
91
91
|
|
92
92
|
str.unpack("U*").slice(0, length - ellipsis.length).pack("U*") + ellipsis
|
93
93
|
end
|
94
|
+
|
95
|
+
def self.uuid_rollbar_url(data, configuration)
|
96
|
+
"#{configuration.web_base}/instance/uuid?uuid=#{data[:uuid]}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.enforce_valid_utf8(payload)
|
100
|
+
normalizer = lambda { |object| Encoding.encode(object) }
|
101
|
+
|
102
|
+
Util.iterate_and_update(payload, normalizer)
|
103
|
+
end
|
94
104
|
end
|
95
105
|
end
|
data/lib/rollbar/version.rb
CHANGED
@@ -12,13 +12,14 @@ describe HomeController do
|
|
12
12
|
Rollbar.configure do |config|
|
13
13
|
config.access_token = test_access_token
|
14
14
|
config.logger = logger_mock
|
15
|
+
config.open_timeout = 60
|
15
16
|
config.request_timeout = 60
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
20
|
context "rollbar base_data" do
|
20
21
|
it 'should have the Rails environment' do
|
21
|
-
data = Rollbar.notifier.send(:
|
22
|
+
data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil)
|
22
23
|
data['data'][:environment].should == ::Rails.env
|
23
24
|
end
|
24
25
|
|
@@ -27,7 +28,7 @@ describe HomeController do
|
|
27
28
|
config.environment = 'dev'
|
28
29
|
end
|
29
30
|
|
30
|
-
data = Rollbar.notifier.send(:
|
31
|
+
data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil)
|
31
32
|
data['data'][:environment].should == 'dev'
|
32
33
|
end
|
33
34
|
|
@@ -35,7 +36,7 @@ describe HomeController do
|
|
35
36
|
old_env, ::Rails.env = ::Rails.env, ''
|
36
37
|
preconfigure_rails_notifier
|
37
38
|
|
38
|
-
data = Rollbar.notifier.send(:
|
39
|
+
data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil)
|
39
40
|
data['data'][:environment].should == 'unspecified'
|
40
41
|
|
41
42
|
::Rails.env = old_env
|
@@ -0,0 +1,635 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rollbar/configuration'
|
3
|
+
require 'rollbar/item'
|
4
|
+
require 'rollbar/lazy_store'
|
5
|
+
|
6
|
+
describe Rollbar::Item do
|
7
|
+
let(:notifier) { double('notifier', :safely => safely_notifier) }
|
8
|
+
let(:safely_notifier) { double('safely_notifier') }
|
9
|
+
let(:logger) { double }
|
10
|
+
let(:configuration) do
|
11
|
+
c = Rollbar::Configuration.new
|
12
|
+
c.enabled = true
|
13
|
+
c.access_token = 'footoken'
|
14
|
+
c.root = '/foo/'
|
15
|
+
c.framework = 'Rails'
|
16
|
+
c
|
17
|
+
end
|
18
|
+
let(:level) { 'info' }
|
19
|
+
let(:message) { 'message' }
|
20
|
+
let(:exception) {}
|
21
|
+
let(:extra) {}
|
22
|
+
let(:scope) {}
|
23
|
+
|
24
|
+
let(:options) do
|
25
|
+
{
|
26
|
+
:level => level,
|
27
|
+
:message => message,
|
28
|
+
:exception => exception,
|
29
|
+
:extra => extra,
|
30
|
+
:configuration => configuration,
|
31
|
+
:logger => logger,
|
32
|
+
:scope => scope,
|
33
|
+
:notifier => notifier
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
subject { described_class.new(options) }
|
38
|
+
|
39
|
+
describe '#build' do
|
40
|
+
let(:payload) { subject.build }
|
41
|
+
|
42
|
+
context 'a basic payload' do
|
43
|
+
let(:extra) { {:key => 'value', :hash => {:inner_key => 'inner_value'}} }
|
44
|
+
|
45
|
+
it 'calls Rollbar::Util.enforce_valid_utf8' do
|
46
|
+
expect(Rollbar::Util).to receive(:enforce_valid_utf8).with(kind_of(Hash))
|
47
|
+
|
48
|
+
subject.build
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should have the correct root-level keys' do
|
52
|
+
payload.keys.should match_array(['access_token', 'data'])
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should have the correct data keys' do
|
56
|
+
payload['data'].keys.should include(:timestamp, :environment, :level, :language, :framework, :server,
|
57
|
+
:notifier, :body)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should have the correct notifier name and version' do
|
61
|
+
payload['data'][:notifier][:name].should == 'rollbar-gem'
|
62
|
+
payload['data'][:notifier][:version].should == Rollbar::VERSION
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should have the correct language and framework' do
|
66
|
+
payload['data'][:language].should == 'ruby'
|
67
|
+
payload['data'][:framework].should == configuration.framework
|
68
|
+
payload['data'][:framework].should match(/^Rails/)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should have the correct server keys' do
|
72
|
+
payload['data'][:server].keys.should match_array([:host, :root, :pid])
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should have the correct level and message body' do
|
76
|
+
payload['data'][:level].should == 'info'
|
77
|
+
payload['data'][:body][:message][:body].should == 'message'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should merge in a new key from payload_options' do
|
82
|
+
configuration.payload_options = { :some_new_key => 'some new value' }
|
83
|
+
|
84
|
+
payload['data'][:some_new_key].should == 'some new value'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should overwrite existing keys from payload_options' do
|
88
|
+
payload_options = {
|
89
|
+
:notifier => 'bad notifier',
|
90
|
+
:server => { :host => 'new host', :new_server_key => 'value' }
|
91
|
+
}
|
92
|
+
configuration.payload_options = payload_options
|
93
|
+
|
94
|
+
payload['data'][:notifier].should == 'bad notifier'
|
95
|
+
payload['data'][:server][:host].should == 'new host'
|
96
|
+
payload['data'][:server][:root].should_not be_nil
|
97
|
+
payload['data'][:server][:new_server_key].should == 'value'
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should have default environment "unspecified"' do
|
101
|
+
configuration.environment = nil
|
102
|
+
|
103
|
+
payload['data'][:environment].should == 'unspecified'
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should have an overridden environment' do
|
107
|
+
configuration.environment = 'overridden'
|
108
|
+
|
109
|
+
payload['data'][:environment].should == 'overridden'
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should not have custom data under default configuration' do
|
113
|
+
payload['data'][:body][:message][:extra].should be_nil
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should have custom message data when custom_data_method is configured' do
|
117
|
+
configuration.custom_data_method = lambda { {:a => 1, :b => [2, 3, 4]} }
|
118
|
+
|
119
|
+
payload['data'][:body][:message][:extra].should_not be_nil
|
120
|
+
payload['data'][:body][:message][:extra][:a].should == 1
|
121
|
+
payload['data'][:body][:message][:extra][:b][2].should == 4
|
122
|
+
end
|
123
|
+
|
124
|
+
context do
|
125
|
+
let(:extra) do
|
126
|
+
{ :c => {:e => 'g' }, :f => 'f' }
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should merge extra data into custom message data' do
|
130
|
+
custom_method = lambda do
|
131
|
+
{ :a => 1,
|
132
|
+
:b => [2, 3, 4],
|
133
|
+
:c => { :d => 'd', :e => 'e' },
|
134
|
+
:f => ['1', '2']
|
135
|
+
}
|
136
|
+
end
|
137
|
+
configuration.custom_data_method = custom_method
|
138
|
+
|
139
|
+
payload['data'][:body][:message][:extra].should_not be_nil
|
140
|
+
payload['data'][:body][:message][:extra][:a].should == 1
|
141
|
+
payload['data'][:body][:message][:extra][:b][2].should == 4
|
142
|
+
payload['data'][:body][:message][:extra][:c][:d].should == 'd'
|
143
|
+
payload['data'][:body][:message][:extra][:c][:e].should == 'g'
|
144
|
+
payload['data'][:body][:message][:extra][:f].should == 'f'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'with custom_data_method crashing' do
|
149
|
+
next unless defined?(SecureRandom) && SecureRandom.respond_to?(:uuid)
|
150
|
+
|
151
|
+
let(:crashing_exception) { StandardError.new }
|
152
|
+
let(:custom_method) { proc { raise crashing_exception } }
|
153
|
+
let(:extra) { { :foo => :bar } }
|
154
|
+
let(:custom_data_report) do
|
155
|
+
{ :_error_in_custom_data_method => SecureRandom.uuid }
|
156
|
+
end
|
157
|
+
let(:expected_extra) { extra.merge(custom_data_report) }
|
158
|
+
|
159
|
+
before do
|
160
|
+
configuration.custom_data_method = custom_method
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'doesnt crash the report' do
|
164
|
+
expect(subject).to receive(:report_custom_data_error).once.and_return(custom_data_report)
|
165
|
+
|
166
|
+
expect(payload['data'][:body][:message][:extra]).to be_eql(expected_extra)
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'and for some reason the safely.error returns a String' do
|
170
|
+
it 'returns an empty Hash' do
|
171
|
+
allow(safely_notifier).to receive(:error).and_return('ignored')
|
172
|
+
|
173
|
+
expect(payload['data'][:body][:message][:extra]).to be_eql(extra)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should include project_gem_paths' do
|
179
|
+
gems = Gem::Specification.map(&:name)
|
180
|
+
project_gems = ['rails']
|
181
|
+
project_gems << 'rspec' if gems.include?('rspec')
|
182
|
+
project_gems << 'rspec-core' if gems.include?('rspec-core')
|
183
|
+
|
184
|
+
configuration.project_gems = project_gems
|
185
|
+
|
186
|
+
expect(payload['data'][:project_package_paths].count).to eq(project_gems.size)
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should include a code_version' do
|
190
|
+
configuration.code_version = 'abcdef'
|
191
|
+
|
192
|
+
payload['data'][:code_version].should == 'abcdef'
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'should have the right hostname' do
|
196
|
+
payload['data'][:server][:host].should == Socket.gethostname
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should have root and branch set when configured' do
|
200
|
+
configuration.root = '/path/to/root'
|
201
|
+
configuration.branch = 'master'
|
202
|
+
|
203
|
+
payload['data'][:server][:root].should == '/path/to/root'
|
204
|
+
payload['data'][:server][:branch].should == 'master'
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'build_payload_body' do
|
208
|
+
let(:exception) do
|
209
|
+
begin
|
210
|
+
foo = bar
|
211
|
+
rescue => e
|
212
|
+
e
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'with no exception' do
|
217
|
+
let(:exception) { nil }
|
218
|
+
|
219
|
+
it 'should build a message body when no exception is passed in' do
|
220
|
+
payload['data'][:body][:message][:body].should == 'message'
|
221
|
+
payload['data'][:body][:message][:extra].should be_nil
|
222
|
+
payload['data'][:body][:trace].should be_nil
|
223
|
+
end
|
224
|
+
|
225
|
+
context 'and extra data' do
|
226
|
+
let(:extra) do
|
227
|
+
{:a => 'b'}
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should build a message body when no exception and extra data is passed in' do
|
231
|
+
payload['data'][:body][:message][:body].should == 'message'
|
232
|
+
payload['data'][:body][:message][:extra].should == {:a => 'b'}
|
233
|
+
payload['data'][:body][:trace].should be_nil
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'should build an exception body when one is passed in' do
|
239
|
+
body = payload['data'][:body]
|
240
|
+
body[:message].should be_nil
|
241
|
+
|
242
|
+
trace = body[:trace]
|
243
|
+
trace.should_not be_nil
|
244
|
+
trace[:extra].should be_nil
|
245
|
+
|
246
|
+
trace[:exception][:class].should_not be_nil
|
247
|
+
trace[:exception][:message].should_not be_nil
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'with extra data' do
|
251
|
+
let(:extra) do
|
252
|
+
{:a => 'b'}
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'should build an exception body when one is passed in along with extra data' do
|
256
|
+
body = payload['data'][:body]
|
257
|
+
body[:message].should be_nil
|
258
|
+
|
259
|
+
trace = body[:trace]
|
260
|
+
trace.should_not be_nil
|
261
|
+
|
262
|
+
trace[:exception][:class].should_not be_nil
|
263
|
+
trace[:exception][:message].should_not be_nil
|
264
|
+
trace[:extra].should == {:a => 'b'}
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context 'build_payload_body_exception' do
|
270
|
+
let(:exception) do
|
271
|
+
begin
|
272
|
+
foo = bar
|
273
|
+
rescue => e
|
274
|
+
e
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'should build valid exception data' do
|
279
|
+
body = payload['data'][:body]
|
280
|
+
body[:message].should be_nil
|
281
|
+
|
282
|
+
trace = body[:trace]
|
283
|
+
|
284
|
+
frames = trace[:frames]
|
285
|
+
frames.should be_a_kind_of(Array)
|
286
|
+
frames.each do |frame|
|
287
|
+
frame[:filename].should be_a_kind_of(String)
|
288
|
+
frame[:lineno].should be_a_kind_of(Fixnum)
|
289
|
+
if frame[:method]
|
290
|
+
frame[:method].should be_a_kind_of(String)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# should be NameError, but can be NoMethodError sometimes on rubinius 1.8
|
295
|
+
# http://yehudakatz.com/2010/01/02/the-craziest-fing-bug-ive-ever-seen/
|
296
|
+
trace[:exception][:class].should match(/^(NameError|NoMethodError)$/)
|
297
|
+
trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/)
|
298
|
+
end
|
299
|
+
|
300
|
+
context 'with description message' do
|
301
|
+
let(:message) { 'exception description' }
|
302
|
+
|
303
|
+
it 'should build exception data with a description' do
|
304
|
+
body = payload['data'][:body]
|
305
|
+
|
306
|
+
trace = body[:trace]
|
307
|
+
|
308
|
+
trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/)
|
309
|
+
trace[:exception][:description].should == 'exception description'
|
310
|
+
end
|
311
|
+
|
312
|
+
context 'and extra data' do
|
313
|
+
let(:extra) do
|
314
|
+
{:key => 'value', :hash => {:inner_key => 'inner_value'}}
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'should build exception data with a description and extra data' do
|
318
|
+
body = payload['data'][:body]
|
319
|
+
trace = body[:trace]
|
320
|
+
|
321
|
+
trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/)
|
322
|
+
trace[:exception][:description].should == 'exception description'
|
323
|
+
trace[:extra][:key].should == 'value'
|
324
|
+
trace[:extra][:hash].should == {:inner_key => 'inner_value'}
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
context 'with extra data' do
|
330
|
+
let(:extra) do
|
331
|
+
{:key => 'value', :hash => {:inner_key => 'inner_value'}}
|
332
|
+
end
|
333
|
+
it 'should build exception data with a extra data' do
|
334
|
+
body = payload['data'][:body]
|
335
|
+
trace = body[:trace]
|
336
|
+
|
337
|
+
trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/)
|
338
|
+
trace[:extra][:key].should == 'value'
|
339
|
+
trace[:extra][:hash].should == {:inner_key => 'inner_value'}
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
context 'with nested exceptions' do
|
344
|
+
let(:crashing_code) do
|
345
|
+
proc do
|
346
|
+
begin
|
347
|
+
begin
|
348
|
+
fail CauseException.new('the cause')
|
349
|
+
rescue
|
350
|
+
fail StandardError.new('the error')
|
351
|
+
end
|
352
|
+
rescue => e
|
353
|
+
e
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
let(:exception) { crashing_code.call }
|
359
|
+
let(:message) { 'message' }
|
360
|
+
let(:extra) { {} }
|
361
|
+
|
362
|
+
context 'using ruby >= 2.1' do
|
363
|
+
next unless Exception.instance_methods.include?(:cause)
|
364
|
+
|
365
|
+
it 'sends the two exceptions in the trace_chain attribute' do
|
366
|
+
body = payload['data'][:body]
|
367
|
+
|
368
|
+
body[:trace].should be_nil
|
369
|
+
body[:trace_chain].should be_kind_of(Array)
|
370
|
+
|
371
|
+
chain = body[:trace_chain]
|
372
|
+
chain[0][:exception][:class].should match(/StandardError/)
|
373
|
+
chain[0][:exception][:message].should match(/the error/)
|
374
|
+
|
375
|
+
chain[1][:exception][:class].should match(/CauseException/)
|
376
|
+
chain[1][:exception][:message].should match(/the cause/)
|
377
|
+
end
|
378
|
+
|
379
|
+
context 'when cause is not an Exception' do
|
380
|
+
let(:exception) { Exception.new('custom cause') }
|
381
|
+
|
382
|
+
it 'ignores the cause when it is not an Exception' do
|
383
|
+
allow(exception).to receive(:cause) { "Foo" }
|
384
|
+
|
385
|
+
payload['data'][:body][:trace].should_not be_nil
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
context 'with cyclic nested exceptions' do
|
390
|
+
let(:exception1) { Exception.new('exception1') }
|
391
|
+
let(:exception2) { Exception.new('exception2') }
|
392
|
+
let(:exception) { exception1 }
|
393
|
+
|
394
|
+
before do
|
395
|
+
allow(exception1).to receive(:cause).and_return(exception2)
|
396
|
+
allow(exception2).to receive(:cause).and_return(exception1)
|
397
|
+
end
|
398
|
+
|
399
|
+
it 'doesnt loop for ever' do
|
400
|
+
chain = payload['data'][:body][:trace_chain]
|
401
|
+
|
402
|
+
expect(chain[0][:exception][:message]).to be_eql('exception1')
|
403
|
+
expect(chain[1][:exception][:message]).to be_eql('exception2')
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
context 'using ruby <= 2.1' do
|
409
|
+
next if Exception.instance_methods.include?(:cause)
|
410
|
+
|
411
|
+
it 'sends only the last exception in the trace attribute' do
|
412
|
+
body = payload['data'][:body]
|
413
|
+
|
414
|
+
body[:trace].should be_kind_of(Hash)
|
415
|
+
body[:trace_chain].should be_nil
|
416
|
+
|
417
|
+
body[:trace][:exception][:class].should match(/StandardError/)
|
418
|
+
body[:trace][:exception][:message].should match(/the error/)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
context 'build_payload_body_message' do
|
425
|
+
it 'should build a message' do
|
426
|
+
payload['data'][:body][:message][:body].should == 'message'
|
427
|
+
payload['data'][:body][:trace].should be_nil
|
428
|
+
end
|
429
|
+
|
430
|
+
context 'with extra data' do
|
431
|
+
let(:extra) do
|
432
|
+
{:key => 'value', :hash => {:inner_key => 'inner_value'}}
|
433
|
+
end
|
434
|
+
|
435
|
+
it 'should build a message with extra data' do
|
436
|
+
payload['data'][:body][:message][:body].should == 'message'
|
437
|
+
payload['data'][:body][:message][:extra][:key].should == 'value'
|
438
|
+
payload['data'][:body][:message][:extra][:hash].should == {:inner_key => 'inner_value'}
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
context 'with empty message and extra data' do
|
443
|
+
let(:message) { nil }
|
444
|
+
let(:extra) do
|
445
|
+
{:key => 'value', :hash => {:inner_key => 'inner_value'}}
|
446
|
+
end
|
447
|
+
|
448
|
+
it 'should build an empty message with extra data' do
|
449
|
+
payload['data'][:body][:message][:body].should == 'Empty message'
|
450
|
+
payload['data'][:body][:message][:extra][:key].should == 'value'
|
451
|
+
payload['data'][:body][:message][:extra][:hash].should == {:inner_key => 'inner_value'}
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
context 'with transform handlers in configuration' do
|
457
|
+
let(:scope) { Rollbar::LazyStore.new({ :bar => :foo }) }
|
458
|
+
let(:message) { 'message' }
|
459
|
+
let(:exception) { Exception.new }
|
460
|
+
let(:extra) { { :foo => :bar } }
|
461
|
+
let(:level) { 'error' }
|
462
|
+
|
463
|
+
context 'without mutation in payload' do
|
464
|
+
let(:handler) do
|
465
|
+
proc do |options|
|
466
|
+
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
before do
|
471
|
+
configuration.transform = handler
|
472
|
+
end
|
473
|
+
|
474
|
+
it 'calls the handler with the correct options' do
|
475
|
+
options = {
|
476
|
+
:level => subject.level,
|
477
|
+
:scope => subject.scope,
|
478
|
+
:exception => subject.exception,
|
479
|
+
:message => subject.message,
|
480
|
+
:extra => subject.extra,
|
481
|
+
:payload => kind_of(Hash)
|
482
|
+
}
|
483
|
+
|
484
|
+
expect(handler).to receive(:call).with(options).and_call_original
|
485
|
+
|
486
|
+
subject.build
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
context 'with mutation in payload' do
|
491
|
+
let(:new_payload) do
|
492
|
+
{
|
493
|
+
'access_token' => configuration.access_token,
|
494
|
+
'data' => {
|
495
|
+
}
|
496
|
+
}
|
497
|
+
end
|
498
|
+
let(:handler) do
|
499
|
+
proc do |options|
|
500
|
+
payload = options[:payload]
|
501
|
+
|
502
|
+
payload.replace(new_payload)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
before do
|
507
|
+
configuration.transform = handler
|
508
|
+
end
|
509
|
+
|
510
|
+
it 'calls the handler with the correct options' do
|
511
|
+
options = {
|
512
|
+
:level => level,
|
513
|
+
:scope => Rollbar::LazyStore.new(scope),
|
514
|
+
:exception => exception,
|
515
|
+
:message => message,
|
516
|
+
:extra => extra,
|
517
|
+
:payload => kind_of(Hash)
|
518
|
+
}
|
519
|
+
expect(handler).to receive(:call).with(options).and_call_original
|
520
|
+
expect(payload).to be_eql(new_payload)
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
context 'with two handlers' do
|
525
|
+
let(:handler1) { proc { |options|} }
|
526
|
+
let(:handler2) { proc { |options|} }
|
527
|
+
|
528
|
+
before do
|
529
|
+
configuration.transform << handler1
|
530
|
+
configuration.transform << handler2
|
531
|
+
end
|
532
|
+
|
533
|
+
context 'and the first one fails' do
|
534
|
+
let(:exception) { StandardError.new('foo') }
|
535
|
+
let(:handler1) do
|
536
|
+
proc { |options| raise exception }
|
537
|
+
end
|
538
|
+
|
539
|
+
it 'doesnt call the second handler and logs the error' do
|
540
|
+
expect(handler2).not_to receive(:call)
|
541
|
+
expect(logger).to receive(:error).with("[Rollbar] Error calling the `transform` hook: #{exception}")
|
542
|
+
|
543
|
+
subject.build
|
544
|
+
end
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
describe '#custom_data' do
|
550
|
+
before do
|
551
|
+
configuration.custom_data_method = proc { raise 'this-will-raise' }
|
552
|
+
|
553
|
+
expect(safely_notifier).to receive(:error).and_return(report_data)
|
554
|
+
end
|
555
|
+
|
556
|
+
context 'with uuid in reported data' do
|
557
|
+
next unless defined?(SecureRandom) and SecureRandom.respond_to?(:uuid)
|
558
|
+
|
559
|
+
let(:report_data) { { :uuid => SecureRandom.uuid } }
|
560
|
+
let(:expected_url) { "https://rollbar.com/instance/uuid?uuid=#{report_data[:uuid]}" }
|
561
|
+
|
562
|
+
it 'returns the uuid in :_error_in_custom_data_method' do
|
563
|
+
expect(payload['data'][:body][:message][:extra]).to be_eql(:_error_in_custom_data_method => expected_url)
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
context 'without uuid in reported data' do
|
568
|
+
let(:report_data) { { :some => 'other-data' } }
|
569
|
+
|
570
|
+
it 'returns empty data' do
|
571
|
+
expect(payload['data'][:body][:message][:extra]).to be_eql({})
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
context 'server_data' do
|
577
|
+
it 'should have the right hostname' do
|
578
|
+
payload['data'][:server][:host] == Socket.gethostname
|
579
|
+
end
|
580
|
+
|
581
|
+
it 'should have root and branch set when configured' do
|
582
|
+
configuration.root = '/path/to/root'
|
583
|
+
configuration.branch = 'master'
|
584
|
+
|
585
|
+
payload['data'][:server][:root].should == '/path/to/root'
|
586
|
+
payload['data'][:server][:branch].should == 'master'
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
context 'with ignored person ids' do
|
591
|
+
let(:ignored_ids) { [1,2,4] }
|
592
|
+
let(:person_data) do
|
593
|
+
{ :person => {
|
594
|
+
:id => 2,
|
595
|
+
:username => 'foo'
|
596
|
+
}
|
597
|
+
}
|
598
|
+
end
|
599
|
+
let(:scope) { Rollbar::LazyStore.new(person_data) }
|
600
|
+
|
601
|
+
before do
|
602
|
+
configuration.person_id_method = :id
|
603
|
+
configuration.ignored_person_ids = ignored_ids
|
604
|
+
end
|
605
|
+
|
606
|
+
it 'sets ignored property to true' do
|
607
|
+
subject.build
|
608
|
+
|
609
|
+
expect(subject).to be_ignored
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
end # end #build
|
614
|
+
|
615
|
+
describe '#dump' do
|
616
|
+
context 'with Redis instance in payload and ActiveSupport is enabled' do
|
617
|
+
let(:redis) { ::Redis.new }
|
618
|
+
let(:payload) do
|
619
|
+
{
|
620
|
+
:key => {
|
621
|
+
:value => redis
|
622
|
+
}
|
623
|
+
}
|
624
|
+
end
|
625
|
+
let(:item) { Rollbar::Item.build_with(payload) }
|
626
|
+
|
627
|
+
it 'dumps to JSON correctly' do
|
628
|
+
redis.set('foo', 'bar')
|
629
|
+
json = item.dump
|
630
|
+
|
631
|
+
expect(json).to be_kind_of(String)
|
632
|
+
end
|
633
|
+
end
|
634
|
+
end
|
635
|
+
end
|