rollbar 2.10.0 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -2
  3. data/CHANGELOG.md +20 -0
  4. data/README.md +73 -16
  5. data/docs/configuration.md +10 -0
  6. data/gemfiles/rails30.gemfile +2 -0
  7. data/gemfiles/rails31.gemfile +2 -0
  8. data/gemfiles/rails32.gemfile +2 -0
  9. data/gemfiles/rails40.gemfile +2 -0
  10. data/gemfiles/rails41.gemfile +2 -0
  11. data/gemfiles/rails42.gemfile +2 -0
  12. data/gemfiles/rails50.gemfile +2 -0
  13. data/gemfiles/ruby_1_8_and_1_9_2.gemfile +43 -0
  14. data/lib/rollbar.rb +139 -353
  15. data/lib/rollbar/configuration.rb +4 -0
  16. data/lib/rollbar/item.rb +225 -0
  17. data/lib/rollbar/item/backtrace.rb +97 -0
  18. data/lib/rollbar/js.rb +0 -28
  19. data/lib/rollbar/language_support.rb +10 -0
  20. data/lib/rollbar/{js/middleware.rb → middleware/js.rb} +3 -4
  21. data/lib/rollbar/plugin.rb +63 -0
  22. data/lib/rollbar/plugins.rb +41 -0
  23. data/lib/rollbar/{active_job.rb → plugins/active_job.rb} +0 -0
  24. data/lib/rollbar/plugins/basic_socket.rb +16 -0
  25. data/lib/rollbar/plugins/delayed_job.rb +12 -0
  26. data/lib/rollbar/plugins/delayed_job/job_data.rb +16 -0
  27. data/lib/rollbar/{delayed_job.rb → plugins/delayed_job/plugin.rb} +1 -17
  28. data/lib/rollbar/plugins/goalie.rb +46 -0
  29. data/lib/rollbar/plugins/rack.rb +16 -0
  30. data/lib/rollbar/plugins/rails.rb +77 -0
  31. data/lib/rollbar/{rails → plugins/rails}/controller_methods.rb +0 -0
  32. data/lib/rollbar/plugins/rails/railtie30.rb +17 -0
  33. data/lib/rollbar/plugins/rails/railtie32.rb +18 -0
  34. data/lib/rollbar/plugins/rails/railtie_mixin.rb +33 -0
  35. data/lib/rollbar/plugins/rake.rb +45 -0
  36. data/lib/rollbar/plugins/sidekiq.rb +35 -0
  37. data/lib/rollbar/{sidekiq.rb → plugins/sidekiq/plugin.rb} +0 -18
  38. data/lib/rollbar/plugins/thread.rb +13 -0
  39. data/lib/rollbar/plugins/validations.rb +33 -0
  40. data/lib/rollbar/request_data_extractor.rb +30 -18
  41. data/lib/rollbar/scrubbers/params.rb +4 -2
  42. data/lib/rollbar/scrubbers/url.rb +30 -28
  43. data/lib/rollbar/util.rb +10 -0
  44. data/lib/rollbar/version.rb +1 -1
  45. data/spec/controllers/home_controller_spec.rb +4 -3
  46. data/spec/dummyapp/app/models/post.rb +9 -0
  47. data/spec/dummyapp/app/models/user.rb +2 -0
  48. data/spec/dummyapp/config/initializers/rollbar.rb +1 -0
  49. data/spec/fixtures/plugins/dummy1.rb +5 -0
  50. data/spec/fixtures/plugins/dummy2.rb +5 -0
  51. data/spec/rollbar/item_spec.rb +635 -0
  52. data/spec/rollbar/logger_proxy_spec.rb +4 -0
  53. data/spec/rollbar/{js/middleware_spec.rb → middleware/js_spec.rb} +32 -3
  54. data/spec/rollbar/plugin_spec.rb +147 -0
  55. data/spec/rollbar/{active_job_spec.rb → plugins/active_job_spec.rb} +0 -1
  56. data/spec/rollbar/{delayed_job → plugins/delayed_job}/job_data.rb +0 -0
  57. data/spec/rollbar/{delayed_job_spec.rb → plugins/delayed_job_spec.rb} +3 -6
  58. data/spec/rollbar/{middleware/rack/builder_spec.rb → plugins/rack_spec.rb} +2 -1
  59. data/spec/rollbar/{js/frameworks/rails_spec.rb → plugins/rails_js_spec.rb} +1 -1
  60. data/spec/rollbar/{rake_spec.rb → plugins/rake_spec.rb} +2 -1
  61. data/spec/rollbar/{sidekiq_spec.rb → plugins/sidekiq_spec.rb} +2 -1
  62. data/spec/rollbar/plugins/validations_spec.rb +43 -0
  63. data/spec/rollbar/plugins_spec.rb +68 -0
  64. data/spec/rollbar/request_data_extractor_spec.rb +56 -10
  65. data/spec/rollbar/scrubbers/params_spec.rb +13 -10
  66. data/spec/rollbar/scrubbers/url_spec.rb +17 -12
  67. data/spec/rollbar/sidekig/clear_scope_spec.rb +2 -1
  68. data/spec/rollbar/util_spec.rb +61 -0
  69. data/spec/rollbar_bc_spec.rb +10 -10
  70. data/spec/rollbar_spec.rb +57 -706
  71. data/spec/spec_helper.rb +8 -0
  72. data/spec/support/notifier_helpers.rb +1 -0
  73. data/spec/support/rollbar_api.rb +57 -0
  74. metadata +57 -33
  75. data/lib/rollbar/active_record_extension.rb +0 -14
  76. data/lib/rollbar/core_ext/basic_socket.rb +0 -7
  77. data/lib/rollbar/core_ext/thread.rb +0 -9
  78. data/lib/rollbar/goalie.rb +0 -33
  79. data/lib/rollbar/js/frameworks.rb +0 -6
  80. data/lib/rollbar/js/frameworks/rails.rb +0 -49
  81. data/lib/rollbar/js/version.rb +0 -5
  82. data/lib/rollbar/rack.rb +0 -9
  83. data/lib/rollbar/railtie.rb +0 -46
  84. 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 = rollbar_filtered_params(sensitive_params, rollbar_request_params(env))
27
- get_params = rollbar_filtered_params(sensitive_params, rollbar_get_params(rack_req))
28
- post_params = rollbar_filtered_params(sensitive_params, rollbar_post_params(rack_req))
29
- raw_body_params = rollbar_filtered_params(sensitive_params, mergeable_raw_body_params(rack_req))
30
- cookies = rollbar_filtered_params(sensitive_params, rollbar_request_cookies(rack_req))
31
- session = rollbar_filtered_params(sensitive_params, rollbar_request_session(rack_req))
32
- route_params = rollbar_filtered_params(sensitive_params, rollbar_route_params(env))
33
-
34
- url_scrubber = Rollbar::Scrubbers::URL.new(:scrub_fields => sensitive_params,
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(params, extra_fields = [])
18
+ def call(options = {})
19
+ params = options[:params]
19
20
  return {} unless params
20
21
 
21
- config = Rollbar.configuration.scrub_fields
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
- attr_reader :regex
10
- attr_reader :scrub_user
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(url)
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
@@ -1,3 +1,3 @@
1
1
  module Rollbar
2
- VERSION = '2.10.0'
2
+ VERSION = '2.11.0'
3
3
  end
@@ -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(:build_payload, 'error', 'message', nil, nil)
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(:build_payload, 'error', 'message', nil, nil)
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(:build_payload, 'error', 'message', nil, nil)
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,9 @@
1
+ class Post
2
+ include ActiveModel::Validations
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ attr_accessor :title
6
+
7
+ validates_presence_of :title
8
+ after_validation :report_validation_errors_to_rollbar
9
+ end
@@ -4,4 +4,6 @@ class User < ActiveRecord::Base
4
4
  attr_accessible :username, :email, :password, :password_confirmation, :remember_me
5
5
  end
6
6
 
7
+ validates_presence_of :email
8
+ after_validation :report_validation_errors_to_rollbar
7
9
  end
@@ -1,5 +1,6 @@
1
1
  Rollbar.configure do |config|
2
2
  config.access_token = 'aaaabbbbccccddddeeeeffff00001111'
3
+ config.open_timeout = 60
3
4
  config.request_timeout = 60
4
5
  config.js_enabled = true
5
6
  config.js_options = {
@@ -0,0 +1,5 @@
1
+ require 'rollbar/plugins'
2
+
3
+ Rollbar.plugins.define(:dummy1) do
4
+ dependency { true }
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'rollbar/plugins'
2
+
3
+ Rollbar.plugins.define(:dummy2) do
4
+ dependency { true }
5
+ end
@@ -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