rollbar 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|