spec/rollbar_spec.rb in rollbar-1.1.0 vs spec/rollbar_spec.rb in rollbar-1.2.0

- old
+ new

@@ -10,55 +10,801 @@ require 'sucker_punch/testing/inline' rescue LoadError end describe Rollbar do + let(:notifier) { Rollbar.notifier } + context 'Notifier' do + context 'log' do + let(:exception) do + begin + foo = bar + rescue => e + e + end + end + let(:configuration) { Rollbar.configuration } - describe '.report_exception' do - before(:each) do - configure - Rollbar.configure do |config| - config.logger = logger_mock + it 'should report a simple message' do + expect(notifier).to receive(:report).with('error', 'test message', nil, nil) + notifier.log('error', 'test message') end + it 'should report a simple message with extra data' do + extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}} + + expect(notifier).to receive(:report).with('error', 'test message', nil, extra_data) + notifier.log('error', 'test message', extra_data) + end + + it 'should report an exception' do + expect(notifier).to receive(:report).with('error', nil, exception, nil) + notifier.log('error', exception) + end + + it 'should report an exception with extra data' do + extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}} + + expect(notifier).to receive(:report).with('error', nil, exception, extra_data) + notifier.log('error', exception, extra_data) + end + + it 'should report an exception with a description' do + expect(notifier).to receive(:report).with('error', 'exception description', exception, nil) + notifier.log('error', exception, 'exception description') + end + + it 'should report an exception with a description and extra data' do + extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}} + + expect(notifier).to receive(:report).with('error', 'exception description', exception, extra_data) + notifier.log('error', exception, extra_data, 'exception description') + end + end + + context 'debug/info/warning/error/critical' do + let(:exception) do + begin + foo = bar + rescue => e + e + end + end + + let(:extra_data) { {:key => 'value', :hash => {:inner_key => 'inner_value'}} } + + it 'should report with a debug level' do + expect(notifier).to receive(:report).with('debug', nil, exception, nil) + notifier.debug(exception) + + expect(notifier).to receive(:report).with('debug', 'description', exception, nil) + notifier.debug(exception, 'description') + + expect(notifier).to receive(:report).with('debug', 'description', exception, extra_data) + notifier.debug(exception, 'description', extra_data) + end + + it 'should report with an info level' do + expect(notifier).to receive(:report).with('info', nil, exception, nil) + notifier.info(exception) + + expect(notifier).to receive(:report).with('info', 'description', exception, nil) + notifier.info(exception, 'description') + + expect(notifier).to receive(:report).with('info', 'description', exception, extra_data) + notifier.info(exception, 'description', extra_data) + end + + it 'should report with a warning level' do + expect(notifier).to receive(:report).with('warning', nil, exception, nil) + notifier.warning(exception) + + expect(notifier).to receive(:report).with('warning', 'description', exception, nil) + notifier.warning(exception, 'description') + + expect(notifier).to receive(:report).with('warning', 'description', exception, extra_data) + notifier.warning(exception, 'description', extra_data) + end + + it 'should report with an error level' do + expect(notifier).to receive(:report).with('error', nil, exception, nil) + notifier.error(exception) + + expect(notifier).to receive(:report).with('error', 'description', exception, nil) + notifier.error(exception, 'description') + + expect(notifier).to receive(:report).with('error', 'description', exception, extra_data) + notifier.error(exception, 'description', extra_data) + end + + it 'should report with a critical level' do + expect(notifier).to receive(:report).with('critical', nil, exception, nil) + notifier.critical(exception) + + expect(notifier).to receive(:report).with('critical', 'description', exception, nil) + notifier.critical(exception, 'description') + + expect(notifier).to receive(:report).with('critical', 'description', exception, extra_data) + notifier.critical(exception, 'description', extra_data) + end + end + + context 'scope' do + it 'should create a new notifier object' do + notifier2 = notifier.scope + + notifier2.should_not eq(notifier) + notifier2.should be_instance_of(Rollbar::Notifier) + end + + it 'should create a copy of the parent notifier\'s configuration' do + notifier.configure do |config| + config.code_version = '123' + config.payload_options = { + :a => 'a', + :b => {:c => 'c'} + } + end + + notifier2 = notifier.scope + + notifier2.configuration.code_version.should == '123' + notifier2.configuration.should_not equal(notifier.configuration) + notifier2.configuration.payload_options.should_not equal(notifier.configuration.payload_options) + notifier2.configuration.payload_options.should == notifier.configuration.payload_options + notifier2.configuration.payload_options.should == { + :a => 'a', + :b => {:c => 'c'} + } + end + + it 'should not modify any parent notifier configuration' do + configure + Rollbar.configuration.code_version.should be_nil + Rollbar.configuration.payload_options.should be_empty + + notifier.configure do |config| + config.code_version = '123' + config.payload_options = { + :a => 'a', + :b => {:c => 'c'} + } + end + + notifier2 = notifier.scope + + notifier2.configure do |config| + config.payload_options[:c] = 'c' + end + + notifier.configuration.payload_options[:c].should be_nil + + notifier3 = notifier2.scope({ + :b => {:c => 3, :d => 'd'} + }) + + notifier3.configure do |config| + config.code_version = '456' + end + + notifier.configuration.code_version.should == '123' + notifier.configuration.payload_options.should == { + :a => 'a', + :b => {:c => 'c'} + } + notifier2.configuration.code_version.should == '123' + notifier2.configuration.payload_options.should == { + :a => 'a', + :b => {:c => 'c'}, + :c => 'c' + } + notifier3.configuration.code_version.should == '456' + notifier3.configuration.payload_options.should == { + :a => 'a', + :b => {:c => 3, :d => 'd'}, + :c => 'c' + } + + Rollbar.configuration.code_version.should be_nil + Rollbar.configuration.payload_options.should be_empty + end + end + + context 'report' do + let(:logger_mock) { double("Rails.logger").as_null_object } + + before(:each) do + configure + Rollbar.configure do |config| + config.logger = logger_mock + end + end + + after(:each) do + Rollbar.unconfigure + configure + end + + it 'should reject input that doesn\'t contain an exception, message or extra data' do + expect(logger_mock).to receive(:error).with('[Rollbar] Tried to send a report with no message, exception or extra data.') + expect(notifier).not_to receive(:schedule_payload) + + result = notifier.send(:report, 'info', nil, nil, nil) + result.should == 'error' + end + + it 'should be ignored if the person is ignored' do + person_data = { + :id => 1, + :username => "test", + :email => "test@example.com" + } + + notifier.configure do |config| + config.ignored_person_ids += [1] + config.payload_options = { :person => person_data } + end + + expect(notifier).not_to receive(:schedule_payload) + + result = notifier.send(:report, 'info', 'message', nil, nil) + result.should == 'ignored' + end + + it 'should evaluate callables in the payload' do + notifier.should receive(:schedule_payload) do |payload| + data = payload['data'] + data[:body][:message][:extra][:callable].should == 2 + end + + callable = proc { 1 + 1 } + notifier.send(:report, 'warning', 'message', nil, { :callable => callable }) + end + end + + context 'build_payload' do + after(:each) do + Rollbar.unconfigure + configure + end + + context 'a basic payload' do + let(:extra_data) { {:key => 'value', :hash => {:inner_key => 'inner_value'}} } + let(:payload) { notifier.send(:build_payload, 'info', 'message', nil, extra_data) } + + it 'should have the correct root-level keys' do + payload.keys.should match_array(['access_token', 'data']) + end + + it 'should have the correct data keys' do + payload['data'].keys.should include(:timestamp, :environment, :level, :language, :framework, :server, + :notifier, :body) + end + + it 'should have the correct notifier name and version' do + payload['data'][:notifier][:name].should == 'rollbar-gem' + payload['data'][:notifier][:version].should == Rollbar::VERSION + end + + it 'should have the correct language and framework' do + payload['data'][:language].should == 'ruby' + payload['data'][:framework].should == Rollbar.configuration.framework + payload['data'][:framework].should match(/^Rails/) + end + + it 'should have the correct server keys' do + payload['data'][:server].keys.should match_array([:host, :root]) + end + + it 'should have the correct level and message body' do + payload['data'][:level].should == 'info' + payload['data'][:body][:message][:body].should == 'message' + end + end + + it 'should merge in a new key from payload_options' do + notifier.configure do |config| + config.payload_options = { :some_new_key => 'some new value' } + end + + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + + payload['data'][:some_new_key].should == 'some new value' + end + + it 'should overwrite existing keys from payload_options' do + payload_options = { + :notifier => 'bad notifier', + :server => { :host => 'new host', :new_server_key => 'value' } + } + + notifier.configure do |config| + config.payload_options = payload_options + end + + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + + payload['data'][:notifier].should == 'bad notifier' + payload['data'][:server][:host].should == 'new host' + payload['data'][:server][:root].should_not be_nil + payload['data'][:server][:new_server_key].should == 'value' + end + + it 'should have default environment "unspecified"' do + Rollbar.unconfigure + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + payload['data'][:environment].should == 'unspecified' + end + + it 'should have an overridden environment' do + Rollbar.configure do |config| + config.environment = 'overridden' + end + + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + payload['data'][:environment].should == 'overridden' + end + + it 'should not have custom data under default configuration' do + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + payload['data'][:body][:message][:extra].should be_nil + end + + it 'should have custom message data when custom_data_method is configured' do + Rollbar.configure do |config| + config.custom_data_method = lambda { {:a => 1, :b => [2, 3, 4]} } + end + + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + payload['data'][:body][:message][:extra].should_not be_nil + payload['data'][:body][:message][:extra][:a].should == 1 + payload['data'][:body][:message][:extra][:b][2].should == 4 + end + + it 'should merge extra data into custom message data' do + custom_method = lambda do + { :a => 1, + :b => [2, 3, 4], + :c => { :d => 'd', :e => 'e' }, + :f => ['1', '2'] + } + end + + Rollbar.configure do |config| + config.custom_data_method = custom_method + end + + payload = notifier.send(:build_payload, 'info', 'message', nil, {:c => {:e => 'g'}, :f => 'f'}) + payload['data'][:body][:message][:extra].should_not be_nil + payload['data'][:body][:message][:extra][:a].should == 1 + payload['data'][:body][:message][:extra][:b][2].should == 4 + payload['data'][:body][:message][:extra][:c][:d].should == 'd' + payload['data'][:body][:message][:extra][:c][:e].should == 'g' + payload['data'][:body][:message][:extra][:f].should == 'f' + end + + it 'should include project_gem_paths' do + notifier.configure do |config| + config.project_gems = ['rails', 'rspec'] + end + + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + + payload['data'][:project_package_paths].should have(2).items + end + + it 'should include a code_version' do + notifier.configure do |config| + config.code_version = 'abcdef' + end + + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + + payload['data'][:code_version].should == 'abcdef' + end + + it 'should have the right hostname' do + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + + payload['data'][:server][:host].should == Socket.gethostname + end + + it 'should have root and branch set when configured' do + configure + Rollbar.configure do |config| + config.root = '/path/to/root' + config.branch = 'master' + end + + payload = notifier.send(:build_payload, 'info', 'message', nil, nil) + + payload['data'][:server][:root].should == '/path/to/root' + payload['data'][:server][:branch].should == 'master' + end + end + + context 'build_payload_body' do + let(:exception) do + begin + foo = bar + rescue => e + e + end + end + + it 'should build a message body when no exception is passed in' do + body = notifier.send(:build_payload_body, 'message', nil, nil) + body[:message][:body].should == 'message' + body[:message][:extra].should be_nil + body[:trace].should be_nil + end + + it 'should build a message body when no exception and extra data is passed in' do + body = notifier.send(:build_payload_body, 'message', nil, {:a => 'b'}) + body[:message][:body].should == 'message' + body[:message][:extra].should == {:a => 'b'} + body[:trace].should be_nil + end + + it 'should build an exception body when one is passed in' do + body = notifier.send(:build_payload_body, 'message', exception, nil) + body[:message].should be_nil + + trace = body[:trace] + trace.should_not be_nil + trace[:extra].should be_nil + + trace[:exception][:class].should_not be_nil + trace[:exception][:message].should_not be_nil + end + + it 'should build an exception body when one is passed in along with extra data' do + body = notifier.send(:build_payload_body, 'message', exception, {:a => 'b'}) + body[:message].should be_nil + + trace = body[:trace] + trace.should_not be_nil + + trace[:exception][:class].should_not be_nil + trace[:exception][:message].should_not be_nil + trace[:extra].should == {:a => 'b'} + end + end + + context 'build_payload_body_exception' do + let(:exception) do + begin + foo = bar + rescue => e + e + end + end + + after(:each) do + Rollbar.unconfigure + configure + end + + it 'should build valid exception data' do + body = notifier.send(:build_payload_body_exception, nil, exception, nil) + body[:message].should be_nil + + trace = body[:trace] + + frames = trace[:frames] + frames.should be_a_kind_of(Array) + frames.each do |frame| + frame[:filename].should be_a_kind_of(String) + frame[:lineno].should be_a_kind_of(Fixnum) + if frame[:method] + frame[:method].should be_a_kind_of(String) + end + end + + # should be NameError, but can be NoMethodError sometimes on rubinius 1.8 + # http://yehudakatz.com/2010/01/02/the-craziest-fing-bug-ive-ever-seen/ + trace[:exception][:class].should match(/^(NameError|NoMethodError)$/) + trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/) + end + + it 'should build exception data with a description' do + body = notifier.send(:build_payload_body_exception, 'exception description', exception, nil) + + trace = body[:trace] + + trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/) + trace[:exception][:description].should == 'exception description' + end + + it 'should build exception data with a description and extra data' do + extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}} + body = notifier.send(:build_payload_body_exception, 'exception description', exception, extra_data) + + trace = body[:trace] + + trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/) + trace[:exception][:description].should == 'exception description' + trace[:extra][:key].should == 'value' + trace[:extra][:hash].should == {:inner_key => 'inner_value'} + end + + it 'should build exception data with a extra data' do + extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}} + body = notifier.send(:build_payload_body_exception, nil, exception, extra_data) + + trace = body[:trace] + + trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/) + trace[:extra][:key].should == 'value' + trace[:extra][:hash].should == {:inner_key => 'inner_value'} + end + + context 'with nested exceptions' do + let(:crashing_code) do + proc do + begin + begin + fail CauseException.new('the cause') + rescue + fail StandardError.new('the error') + end + rescue => e + e + end + end + end + + let(:rescued_exception) { crashing_code.call } + let(:message) { 'message' } + let(:extra) { {} } + + context 'using ruby >= 2.1' do + next unless Exception.instance_methods.include?(:cause) + + it 'sends the two exceptions in the trace_chain attribute' do + body = notifier.send(:build_payload_body_exception, message, rescued_exception, extra) + + body[:trace].should be_nil + body[:trace_chain].should be_kind_of(Array) + + chain = body[:trace_chain] + chain[0][:exception][:class].should match(/StandardError/) + chain[0][:exception][:message].should match(/the error/) + + chain[1][:exception][:class].should match(/CauseException/) + chain[1][:exception][:message].should match(/the cause/) + end + + context 'using ruby <= 2.1' do + next if Exception.instance_methods.include?(:cause) + + it 'sends only the last exception in the trace attribute' do + body = notifier.send(:build_payload_body_exception, message, rescued_exception, extra) + + body[:trace].should be_kind_of(Hash) + body[:trace_chain].should be_nil + + body[:trace][:exception][:class].should match(/StandardError/) + body[:trace][:exception][:message].should match(/the error/) + end + end + end + end + end + + context 'build_payload_body_message' do + it 'should build a message' do + body = notifier.send(:build_payload_body_message, 'message', nil) + body[:message][:body].should == 'message' + body[:trace].should be_nil + end + + it 'should build a message with extra data' do + extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}} + body = notifier.send(:build_payload_body_message, 'message', extra_data) + body[:message][:body].should == 'message' + body[:message][:extra][:key].should == 'value' + body[:message][:extra][:hash].should == {:inner_key => 'inner_value'} + end + + it 'should build an empty message with extra data' do + extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}} + body = notifier.send(:build_payload_body_message, nil, extra_data) + body[:message][:body].should == 'Empty message' + body[:message][:extra][:key].should == 'value' + body[:message][:extra][:hash].should == {:inner_key => 'inner_value'} + end + end + + context 'evaluate_payload' do + let(:notifier) do + notifier = Rollbar.notifier + + notifier.configure do |config| + config.logger = logger_mock + end + + notifier + end + + let(:logger_mock) { double("Rails.logger").as_null_object } + + before(:each) do + configure + notifier.configure do |config| + config.logger = logger_mock + end + end + + it 'should evaluate callables and store the result' do + a = 'some string' + + payload = { + :evaluate1 => lambda { 1 + 1 }, + :evaluate2 => Proc.new { 2 + 2 }, + :hash => { + :inner_evaluate1 => a.method(:to_s), + :inner_evaluate2 => lambda { {:key => 'value'} } + } + } + + payload_copy = payload.clone + notifier.send(:evaluate_payload, payload_copy) + + payload_copy[:evaluate1].should == 2 + payload_copy[:evaluate2].should == 4 + payload_copy[:hash][:inner_evaluate1].should == 'some string' + payload_copy[:hash][:inner_evaluate2][:key].should == 'value' + end + + it 'should not crash when the callable raises an exception' do + logger_mock.should_receive(:error).with("[Rollbar] Error while evaluating callable in payload for key evaluate1") + + payload = { + :evaluate1 => lambda { a = b }, + :evaluate2 => Proc.new { 2 + 2 }, + :key => 'value' + } + + payload_copy = payload.clone + notifier.send(:evaluate_payload, payload_copy) + + payload_copy[:evaluate1].should be_nil + payload_copy[:evaluate2].should == 4 + payload_copy[:key].should == 'value' + end + end + + context 'enforce_valid_utf8' do + it 'should replace invalid utf8 values' do + payload = { + :bad_value => "bad value 1\255", + :bad_value_2 => "bad\255 value 2", + "bad\255 key" => "good value", + :hash => { + :inner_bad_value => "\255\255bad value 3", + "inner \255bad key" => 'inner good value', + "bad array key\255" => [ + 'good array value 1', + "bad\255 array value 1\255", + { + :inner_inner_bad => "bad inner \255inner value" + } + ] + } + } + + payload_copy = payload.clone + notifier.send(:enforce_valid_utf8, payload_copy) + + payload_copy[:bad_value].should == "bad value 1" + payload_copy[:bad_value_2].should == "bad value 2" + payload_copy["bad key"].should == "good value" + payload_copy.keys.should_not include("bad\456 key") + payload_copy[:hash][:inner_bad_value].should == "bad value 3" + payload_copy[:hash]["inner bad key"].should == 'inner good value' + payload_copy[:hash]["bad array key"].should == [ + 'good array value 1', + 'bad array value 1', + { + :inner_inner_bad => 'bad inner inner value' + } + ] + end + end + + context 'truncate_payload' do + it 'should truncate all nested strings in the payload' do + payload = { + :truncated => '1234567', + :not_truncated => '123456', + :hash => { + :inner_truncated => '123456789', + :inner_not_truncated => '567', + :array => ['12345678', '12', {:inner_inner => '123456789'}] + } + } + + payload_copy = payload.clone + notifier.send(:truncate_payload, payload_copy, 6) + + payload_copy[:truncated].should == '123...' + payload_copy[:not_truncated].should == '123456' + payload_copy[:hash][:inner_truncated].should == '123...' + payload_copy[:hash][:inner_not_truncated].should == '567' + payload_copy[:hash][:array].should == ['123...', '12', {:inner_inner => '123...'}] + end + + it 'should truncate utf8 strings properly' do + payload = { + :truncated => 'Ŝǻмρļẻ śţяịņģ', + :not_truncated => '123456', + } + + payload_copy = payload.clone + notifier.send(:truncate_payload, payload_copy, 6) + + payload_copy[:truncated].should == "Ŝǻм..." + payload_copy[:not_truncated].should == '123456' + end + end + end + + context 'reporting' do + let(:exception) do begin foo = bar rescue => e - @exception = e + e end end let(:logger_mock) { double("Rails.logger").as_null_object } + let(:user) { User.create(:email => 'email@example.com', :encrypted_password => '', :created_at => Time.now, :updated_at => Time.now) } + before(:each) do + configure + Rollbar.configure do |config| + config.logger = logger_mock + end + end + + after(:each) do + Rollbar.unconfigure + configure + end + it 'should report exceptions without person or request data' do logger_mock.should_receive(:info).with('[Rollbar] Success') - Rollbar.report_exception(@exception) + Rollbar.error(exception) end it 'should not report anything when disabled' do logger_mock.should_not_receive(:info).with('[Rollbar] Success') + Rollbar.configure do |config| config.enabled = false end - Rollbar.report_exception(@exception) + Rollbar.error(exception).should == 'disabled' + end - Rollbar.configure do |config| - config.enabled = true - end + it 'should report exceptions without person or request data' do + logger_mock.should_receive(:info).with('[Rollbar] Success') + Rollbar.error(exception) end it 'should be enabled when freshly configured' do Rollbar.configuration.enabled.should == true end it 'should not be enabled when not configured' do Rollbar.unconfigure Rollbar.configuration.enabled.should be_nil - Rollbar.report_exception(@exception).should == 'disabled' + Rollbar.error(exception).should == 'disabled' end it 'should stay disabled if configure is called again' do Rollbar.unconfigure @@ -69,167 +815,139 @@ # now configure again (perhaps to change some other values) Rollbar.configure do |config| end Rollbar.configuration.enabled.should == false - Rollbar.report_exception(@exception).should == 'disabled' + Rollbar.error(exception).should == 'disabled' end - it 'should report exceptions with request and person data' do - logger_mock.should_receive(:info).with('[Rollbar] Success') - request_data = { - :params => { :foo => "bar" }, - :url => 'http://localhost/', - :user_ip => '127.0.0.1', - :headers => {}, - :GET => { "baz" => "boz" }, - :session => { :user_id => 123 }, - :method => "GET", - } - person_data = { - :id => 1, - :username => "test", - :email => "test@example.com" - } - Rollbar.report_exception(@exception, request_data, person_data) - end - - it "should work with an IO object as rack.errors" do - logger_mock.should_receive(:info).with('[Rollbar] Success') - request_data = { - :params => { :foo => "bar" }, - :url => 'http://localhost/', - :user_ip => '127.0.0.1', - :headers => {}, - :GET => { "baz" => "boz" }, - :session => { :user_id => 123 }, - :method => "GET", - :env => { :"rack.errors" => IO.new(2, File::WRONLY) }, - } - person_data = { - :id => 1, - :username => "test", - :email => "test@example.com" - } - Rollbar.report_exception(@exception, request_data, person_data) - end - it 'should ignore ignored exception classes' do - saved_filters = Rollbar.configuration.exception_level_filters Rollbar.configure do |config| config.exception_level_filters = { 'NameError' => 'ignore' } end logger_mock.should_not_receive(:info) logger_mock.should_not_receive(:warn) logger_mock.should_not_receive(:error) - Rollbar.report_exception(@exception) + Rollbar.error(exception) + end - Rollbar.configure do |config| - config.exception_level_filters = saved_filters - end + it "should work with an IO object as rack.errors" do + logger_mock.should_receive(:info).with('[Rollbar] Success') + + Rollbar.error(exception, :env => { :"rack.errors" => IO.new(2, File::WRONLY) }) end it 'should ignore ignored persons' do + person_data = { + :id => 1, + :username => "test", + :email => "test@example.com" + } + Rollbar.configure do |config| + config.payload_options = {:person => person_data} config.ignored_person_ids += [1] end logger_mock.should_not_receive(:info) logger_mock.should_not_receive(:warn) logger_mock.should_not_receive(:error) + Rollbar.error(exception) + end + + it 'should not ignore non-ignored persons' do person_data = { :id => 1, :username => "test", :email => "test@example.com" } - Rollbar.report_exception(@exception, {}, person_data) - end - - it 'should not ignore non-ignored persons' do Rollbar.configure do |config| + config.payload_options = { :person => person_data } config.ignored_person_ids += [1] end Rollbar.last_report = nil - person_data = { - :id => 1, - :username => "test", - :email => "test@example.com" - } - Rollbar.report_exception(@exception, {}, person_data) + Rollbar.error(exception) Rollbar.last_report.should be_nil person_data = { :id => 2, :username => "test2", :email => "test2@example.com" } - Rollbar.report_exception(@exception, {}, person_data) + + new_options = { + :person => person_data + } + + Rollbar.scoped(new_options) do + Rollbar.error(exception) + end + Rollbar.last_report.should_not be_nil end it 'should allow callables to set exception filtered level' do callable_mock = double saved_filters = Rollbar.configuration.exception_level_filters Rollbar.configure do |config| config.exception_level_filters = { 'NameError' => callable_mock } end - callable_mock.should_receive(:call).with(@exception).at_least(:once).and_return("info") + callable_mock.should_receive(:call).with(exception).at_least(:once).and_return("info") logger_mock.should_receive(:info) logger_mock.should_not_receive(:warn) logger_mock.should_not_receive(:error) - Rollbar.report_exception(@exception) - - Rollbar.configure do |config| - config.exception_level_filters = saved_filters - end + Rollbar.error(exception) end it 'should not report exceptions when silenced' do - Rollbar.should_not_receive :schedule_payload + expect_any_instance_of(Rollbar::Notifier).to_not receive(:schedule_payload) begin test_var = 1 Rollbar.silenced do test_var = 2 raise end rescue => e - Rollbar.report_exception(e) + Rollbar.error(e) end test_var.should == 2 end it 'should report exception objects with no backtrace' do payload = nil - Rollbar.stub(:schedule_payload) do |*args| + + notifier.stub(:schedule_payload) do |*args| payload = args[0] end - Rollbar.report_exception(StandardError.new("oops")) + + Rollbar.error(StandardError.new("oops")) + payload["data"][:body][:trace][:frames].should == [] payload["data"][:body][:trace][:exception][:class].should == "StandardError" payload["data"][:body][:trace][:exception][:message].should == "oops" end it 'should return the exception data with a uuid, on platforms with SecureRandom' do if defined?(SecureRandom) and SecureRandom.respond_to?(:uuid) - Rollbar.stub(:schedule_payload) do |*args| end - exception_data = Rollbar.report_exception(StandardError.new("oops")) + exception_data = Rollbar.error(StandardError.new("oops")) exception_data[:uuid].should_not be_nil end end it 'should report exception objects with nonstandard backtraces' do payload = nil - Rollbar.stub(:schedule_payload) do |*args| + + notifier.stub(:schedule_payload) do |*args| payload = args[0] end class CustomException < StandardError def backtrace @@ -237,31 +955,33 @@ end end exception = CustomException.new("oops") - Rollbar.report_exception(exception) + notifier.error(exception) payload["data"][:body][:trace][:frames][0][:method].should == "custom backtrace line" end it 'should report exceptions with a custom level' do payload = nil - Rollbar.stub(:schedule_payload) do |*args| + + notifier.stub(:schedule_payload) do |*args| payload = args[0] end - Rollbar.report_exception(@exception) + Rollbar.error(exception) - payload["data"][:level].should == 'error' + payload['data'][:level].should == 'error' - Rollbar.report_exception(@exception, nil, nil, 'debug') + Rollbar.log('debug', exception) - payload["data"][:level].should == 'debug' + payload['data'][:level].should == 'debug' end end + # Backwards context 'report_message' do before(:each) do configure Rollbar.configure do |config| config.logger = logger_mock @@ -294,19 +1014,21 @@ logger_mock.should_receive(:info).with('[Rollbar] Success') Rollbar.report_message("Test message with extra data", 'debug', :foo => "bar", :hash => { :a => 123, :b => "xyz" }) end + # END Backwards + it 'should not crash with circular extra_data' do a = { :foo => "bar" } b = { :a => a } c = { :b => b } a[:c] = c logger_mock.should_receive(:error).with(/\[Rollbar\] Reporting internal error encountered while sending data to Rollbar./) - Rollbar.report_message("Test message with circular extra data", 'debug', a) + Rollbar.error("Test message with circular extra data", a) end it 'should be able to report form validation errors when they are present' do logger_mock.should_receive(:info).with('[Rollbar] Success') user.errors.add(:example, "error") @@ -317,40 +1039,17 @@ logger_mock.should_not_receive(:info).with('[Rollbar] Success') user.errors.clear user.report_validation_errors_to_rollbar end - after(:each) do - Rollbar.configure do |config| - config.logger = ::Rails.logger - end - end - end - - context 'report_message_with_request' do - before(:each) do - configure - Rollbar.configure do |config| - config.logger = logger_mock - end - end - - let(:logger_mock) { double("Rails.logger").as_null_object } - let(:user) { User.create(:email => 'email@example.com', :encrypted_password => '', :created_at => Time.now, :updated_at => Time.now) } - - it 'should report simple messages' do - logger_mock.should_receive(:info).with('[Rollbar] Scheduling payload') + it 'should report messages with extra data' do logger_mock.should_receive(:info).with('[Rollbar] Success') - Rollbar.report_message_with_request("Test message") - - Rollbar.last_report[:request].should be_nil - Rollbar.last_report[:person].should be_nil + Rollbar.info("Test message with extra data", :foo => "bar", + :hash => { :a => 123, :b => "xyz" }) end it 'should report messages with request, person data and extra data' do - Rollbar.last_report = nil - logger_mock.should_receive(:info).with('[Rollbar] Scheduling payload') logger_mock.should_receive(:info).with('[Rollbar] Success') request_data = { :params => {:foo => 'bar'} @@ -363,40 +1062,54 @@ extra_data = { :extra_foo => 'extra_bar' } - Rollbar.report_message_with_request("Test message", 'info', request_data, person_data, extra_data) + Rollbar.configure do |config| + config.payload_options = { + :request => request_data, + :person => person_data + } + end + Rollbar.info("Test message", extra_data) + Rollbar.last_report[:request].should == request_data Rollbar.last_report[:person].should == person_data - Rollbar.last_report[:body][:message][:extra_foo].should == 'extra_bar' + Rollbar.last_report[:body][:message][:extra][:extra_foo].should == 'extra_bar' end end context 'payload_destination' do before(:each) do configure Rollbar.configure do |config| config.logger = logger_mock config.filepath = 'test.rollbar' end + end + after(:each) do + Rollbar.unconfigure + configure + end + + let(:exception) do begin foo = bar rescue => e - @exception = e + e end end let(:logger_mock) { double("Rails.logger").as_null_object } it 'should send the payload over the network by default' do logger_mock.should_not_receive(:info).with('[Rollbar] Writing payload to file') logger_mock.should_receive(:info).with('[Rollbar] Sending payload').once logger_mock.should_receive(:info).with('[Rollbar] Success').once - Rollbar.report_exception(@exception) + Rollbar.error(exception) end it 'should save the payload to a file if set' do logger_mock.should_not_receive(:info).with('[Rollbar] Sending payload') logger_mock.should_receive(:info).with('[Rollbar] Writing payload to file').once @@ -407,11 +1120,11 @@ Rollbar.configure do |config| config.write_to_file = true filepath = config.filepath end - Rollbar.report_exception(@exception) + Rollbar.error(exception) File.exist?(filepath).should == true File.read(filepath).should include test_access_token File.delete(filepath) @@ -425,15 +1138,22 @@ before(:each) do configure Rollbar.configure do |config| config.logger = logger_mock end + end + after(:each) do + Rollbar.unconfigure + configure + end + + let(:exception) do begin foo = bar rescue => e - @exception = e + e end end let(:logger_mock) { double("Rails.logger").as_null_object } @@ -445,11 +1165,11 @@ Rollbar.configure do |config| config.use_async = true GirlFriday::WorkQueue.immediate! end - Rollbar.report_exception(@exception) + Rollbar.error(exception) Rollbar.configure do |config| config.use_async = false GirlFriday::WorkQueue.queue! end @@ -466,16 +1186,11 @@ logger_mock.info 'Custom async handler called' Rollbar.process_payload(payload) } end - Rollbar.report_exception(@exception) - - Rollbar.configure do |config| - config.use_async = false - config.async_handler = Rollbar.default_async_handler - end + Rollbar.error(exception) end # We should be able to send String payloads, generated # by a previous version of the gem. This can happend just # after a deploy with an gem upgrade. @@ -495,11 +1210,11 @@ end it 'sends a payload generated as String, not as a Hash' do logger_mock.should_receive(:info).with('[Rollbar] Success') - Rollbar.report_exception(@exception) + Rollbar.report_exception(exception) end context 'with async failover handlers' do before do Rollbar.reconfigure do |config| @@ -534,11 +1249,11 @@ end it 'logs the error but doesnt try to report an internal error' do expect(logger_mock).to receive(:error).with(log_message) - Rollbar.report_exception(exception) + Rollbar.error(exception) end end context 'if the first failover handler success' do let(:handler) { proc { |_| 'success' } } @@ -546,11 +1261,11 @@ it 'calls the failover handler and doesnt report internal error' do expect(Rollbar).not_to receive(:report_internal_error) expect(handler).to receive(:call) - Rollbar.report_exception(exception) + Rollbar.error(exception) end end context 'with two handlers, the first failing' do let(:handler1) { proc { |_| fail 'this handler fails' } } @@ -558,11 +1273,11 @@ let(:handlers) { [handler1, handler2] } it 'calls the second handler and doesnt report internal error' do expect(handler2).to receive(:call) - Rollbar.report_exception(exception) + Rollbar.error(exception) end end context 'with two handlers, both failing' do let(:handler1) { proc { |_| fail 'this handler fails' } } @@ -570,11 +1285,11 @@ let(:handlers) { [handler1, handler2] } it 'reports internal error' do expect(logger_mock).to receive(:error) - Rollbar.report_exception(exception) + Rollbar.error(exception) end end end end end @@ -587,16 +1302,11 @@ Rollbar.configure do |config| config.use_sucker_punch end - Rollbar.report_exception(@exception) - - Rollbar.configure do |config| - config.use_async = false - config.async_handler = Rollbar.default_async_handler - end + Rollbar.error(exception) end end describe "#use_sidekiq", :if => defined?(Sidekiq) do it "should instanciate sidekiq delayer with custom values" do @@ -612,185 +1322,34 @@ Rollbar.configure do |config| config.use_sidekiq config.async_handler = handler end - Rollbar.report_exception(@exception) - - Rollbar.configure do |config| - config.use_async = false - config.async_handler = Rollbar.default_async_handler - end + Rollbar.error(exception) end end end - context 'message_data' do - before(:each) do - configure - @message_body = "This is a test" - @level = 'debug' - end - - it 'should build a message' do - data = Rollbar.send(:message_data, @message_body, @level, {}) - data[:body][:message][:body].should == @message_body - data[:level].should == @level - data[:custom].should be_nil - end - - it 'should accept extra_data' do - user_id = 123 - name = "Tester" - - data = Rollbar.send(:message_data, @message_body, 'info', - :user_id => user_id, :name => name) - - data[:level].should == 'info' - message = data[:body][:message] - message[:body].should == @message_body - message[:user_id].should == user_id - message[:name].should == name - end - - it 'should build a message with custom data when configured' do - Rollbar.configure do |config| - config.custom_data_method = lambda { {:foo => "bar", :hello => [1, 2, 3]} } - end - - data = Rollbar.send(:message_data, @message_body, @level, {}) - - data[:level].should == @level - data[:body][:message][:body].should == @message_body - data[:custom].should_not be_nil - data[:custom][:foo].should == "bar" - data[:custom][:hello][2].should == 3 - - Rollbar.configure do |config| - config.custom_data_method = nil - end - end - end - - describe '.exception_data' do - before(:each) do - configure - begin - foo = bar - rescue => e - @exception = e - end - end - - it 'should accept force_level' do - level = 'critical' - data = Rollbar.send(:exception_data, @exception, level) - data[:level].should == level - end - - it 'should build valid exception data' do - data = Rollbar.send(:exception_data, @exception) - - data[:level].should_not be_nil - data[:custom].should be_nil - - trace = data[:body][:trace] - - frames = trace[:frames] - frames.should be_a_kind_of(Array) - frames.each do |frame| - frame[:filename].should be_a_kind_of(String) - frame[:lineno].should be_a_kind_of(Fixnum) - if frame[:method] - frame[:method].should be_a_kind_of(String) - end - end - - # should be NameError, but can be NoMethodError sometimes on rubinius 1.8 - # http://yehudakatz.com/2010/01/02/the-craziest-fing-bug-ive-ever-seen/ - trace[:exception][:class].should match(/^(NameError|NoMethodError)$/) - trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/) - end - - it 'should include custom data when configured' do - Rollbar.configure do |config| - config.custom_data_method = lambda { {:foo => "baz", :hello => [4, 5, 6]} } - end - - data = Rollbar.send(:exception_data, @exception) - data[:body][:trace].should_not be_nil - data[:custom][:foo].should == "baz" - data[:custom][:hello][2].should == 6 - - Rollbar.configure do |config| - config.custom_data_method = nil - end - end - - context 'with nested exceptions' do - let(:crashing_code) do - proc do - begin - begin - fail CauseException.new('the cause') - rescue - fail StandardError.new('the error') - end - rescue => e - e - end - end - end - let(:rescued_exception) { crashing_code.call } - - if Exception.instance_methods.include?(:cause) - it 'sends the two exceptions in the trace_chain attribute' do - data = Rollbar.send(:exception_data, rescued_exception) - body = data[:body] - - body[:trace].should be_nil - body[:trace_chain].should be_kind_of(Array) - - chain = body[:trace_chain] - chain[0][:exception][:class].should match(/StandardError/) - chain[0][:exception][:message].should match(/the error/) - - chain[1][:exception][:class].should match(/CauseException/) - chain[1][:exception][:message].should match(/the cause/) - end - - else - it 'sends only the last exception in the trace attribute' do - data = Rollbar.send(:exception_data, rescued_exception) - body = data[:body] - - body[:trace].should be_kind_of(Hash) - body[:trace_chain].should be_nil - - body[:trace][:exception][:class].should match(/StandardError/) - body[:trace][:exception][:message].should match(/the error/) - end - end - end - end - context 'logger' do before(:each) do reset_configuration end it 'should have use the Rails logger when configured to do so' do configure - Rollbar.send(:logger).should == ::Rails.logger + expect(Rollbar.send(:logger)).to be_kind_of(Rollbar::LoggerProxy) + expect(Rollbar.send(:logger).object).should == ::Rails.logger end it 'should use the default_logger when no logger is set' do logger = Logger.new(STDERR) + Rollbar.configure do |config| config.default_logger = lambda { logger } end - Rollbar.send(:logger).should == logger + + Rollbar.send(:logger).object.should == logger end it 'should have a default default_logger' do Rollbar.send(:logger).should_not be_nil end @@ -798,76 +1357,15 @@ after(:each) do reset_configuration end end - context 'build_payload' do - before(:each) do - configure - Rollbar.configure do |config| - config.logger = logger_mock - end - end - - let(:logger_mock) { double("Rails.logger").as_null_object } - - it 'should build valid json' do - data = { :foo => { :bar => 'baz'}} - payload = Rollbar.send(:build_payload, data) - payload["data"][:foo][:bar].should == "baz" - end - - it 'should strip out invalid utf-8' do - payload = Rollbar.send(:build_payload, { - :good_key => "\255bad value", - "bad\255 key" => "good value", - "bad key 2\255" => "bad \255value", - :hash => { - "bad array \255key" => ["bad\255 array element", "good array element"] - } - }) - - payload["data"][:good_key].should == 'bad value' - payload["data"]["bad key"].should == 'good value' - payload["data"]["bad key 2"].should == 'bad value' - payload["data"][:hash].should == { - "bad array key" => ["bad array element", "good array element"] - } - end - - it 'should truncate large strings if the payload is too big' do - data = {:foo => {:bar => "baz"}, :large => 'a' * (128 * 1024), :small => 'b' * 1024} - payload = Rollbar.send(:build_payload, data) - json = Rollbar.send(:dump_payload, payload) - - hash = MultiJson.load(json) - hash["data"]["large"].should == '%s...' % ('a' * 1021) - hash["data"]["small"].should == 'b' * 1024 - end - - it 'should send a failsafe message if the payload cannot be reduced enough' do - logger_mock.should_receive(:error).with(/Sending failsafe response due to Could not send payload due to it being too large after truncating attempts/) - logger_mock.should_receive(:info).with('[Rollbar] Success') - - orig_max = Rollbar::MAX_PAYLOAD_SIZE - Rollbar::MAX_PAYLOAD_SIZE = 1 - orig_send_failsafe = Rollbar.method(:send_failsafe) - - Rollbar.stub(:send_failsafe) do |message, exception| - Rollbar::MAX_PAYLOAD_SIZE = orig_max - orig_send_failsafe.call(message, exception) - end - - Rollbar.report_exception(@exception) - end - end - context 'enforce_valid_utf8' do it 'should replace invalid utf8 values' do bad_key = "inner \x92bad key" bad_key.force_encoding('ASCII-8BIT') if bad_key.respond_to?('force_encoding') - + payload = { :bad_value => "bad value 1\255", :bad_value_2 => "bad\255 value 2", "bad\255 key" => "good value", :hash => { @@ -882,11 +1380,11 @@ ] } } payload_copy = payload.clone - Rollbar.send(:enforce_valid_utf8, payload_copy) + notifier.send(:enforce_valid_utf8, payload_copy) payload_copy[:bad_value].should == "bad value 1" payload_copy[:bad_value_2].should == "bad value 2" payload_copy["bad key"].should == "good value" payload_copy.keys.should_not include("bad\456 key") @@ -913,11 +1411,11 @@ :array => ['12345678', '12', {:inner_inner => '123456789'}] } } payload_copy = payload.clone - Rollbar.send(:truncate_payload, payload_copy, 6) + notifier.send(:truncate_payload, payload_copy, 6) payload_copy[:truncated].should == '123...' payload_copy[:not_truncated].should == '123456' payload_copy[:hash][:inner_truncated].should == '123...' payload_copy[:hash][:inner_not_truncated].should == '567' @@ -929,81 +1427,30 @@ :truncated => 'Ŝǻмρļẻ śţяịņģ', :not_truncated => '123456', } payload_copy = payload.clone - Rollbar.send(:truncate_payload, payload_copy, 6) + notifier.send(:truncate_payload, payload_copy, 6) payload_copy[:truncated].should == "Ŝǻм..." payload_copy[:not_truncated].should == '123456' end end - context 'base_data' do - before(:each) { configure } - - it 'should have the correct notifier name' do - Rollbar.send(:base_data)[:notifier][:name].should == 'rollbar-gem' - end - - it 'should have the correct notifier version' do - Rollbar.send(:base_data)[:notifier][:version].should == Rollbar::VERSION - end - - it 'should have all the required keys' do - data = Rollbar.send(:base_data) - data[:timestamp].should_not be_nil - data[:environment].should_not be_nil - data[:level].should_not be_nil - data[:language].should == 'ruby' - data[:framework].should match(/^Rails/) - end - - it 'should have default environment "unspecified"' do - data = Rollbar.send(:base_data) - data[:environment].should == 'unspecified' - end - - it 'should have an overridden environment' do - Rollbar.configure do |config| - config.environment = 'overridden' - end - - data = Rollbar.send(:base_data) - data[:environment].should == 'overridden' - end - - it 'should not have custom data under default configuration' do - data = Rollbar.send(:base_data) - data[:custom].should be_nil - end - - it 'should have custom data when custom_data_method is configured' do - Rollbar.configure do |config| - config.custom_data_method = lambda { {:a => 1, :b => [2, 3, 4]} } - end - - data = Rollbar.send(:base_data) - data[:custom].should_not be_nil - data[:custom][:a].should == 1 - data[:custom][:b][2].should == 4 - end - end - context 'server_data' do it 'should have the right hostname' do - Rollbar.send(:server_data)[:host] == Socket.gethostname + notifier.send(:server_data)[:host] == Socket.gethostname end it 'should have root and branch set when configured' do configure Rollbar.configure do |config| config.root = '/path/to/root' config.branch = 'master' end - data = Rollbar.send(:server_data) + data = notifier.send(:server_data) data[:root].should == '/path/to/root' data[:branch].should == 'master' end end @@ -1018,11 +1465,11 @@ gems.each {|gem| gem_paths.push(Gem::Specification.find_by_name(gem).gem_dir) } - data = Rollbar.send(:message_data, 'test', 'info', {}) + data = notifier.send(:build_payload, 'info', 'test', nil, {})['data'] data[:project_package_paths].kind_of?(Array).should == true data[:project_package_paths].length.should == gem_paths.length data[:project_package_paths].each_with_index{|path, index| path.should == gem_paths[index] @@ -1041,11 +1488,11 @@ gem_paths.length.should > 1 gem_paths.any?{|path| path.include? 'rollbar-gem'}.should == true gem_paths.any?{|path| path.include? 'rspec-rails'}.should == true - data = Rollbar.send(:message_data, 'test', 'info', {}) + data = notifier.send(:build_payload, 'info', 'test', nil, {})['data'] data[:project_package_paths].kind_of?(Array).should == true data[:project_package_paths].length.should == gem_paths.length (data[:project_package_paths] - gem_paths).length.should == 0 end @@ -1054,37 +1501,37 @@ Rollbar.configure do |config| config.project_gems = gems end - data = Rollbar.send(:message_data, 'test', 'info', {}) + data = notifier.send(:build_payload, 'info', 'test', nil, {})['data'] data[:project_package_paths].kind_of?(Array).should == true data[:project_package_paths].length.should == 1 end end context "report_internal_error" do it "should not crash when given an exception object" do begin 1 / 0 rescue => e - Rollbar.send(:report_internal_error, e) + notifier.send(:report_internal_error, e) end end end context "send_failsafe" do it "should not crash when given a message and exception" do begin 1 / 0 rescue => e - Rollbar.send(:send_failsafe, "test failsafe", e) + notifier.send(:send_failsafe, "test failsafe", e) end end it "should not crash when given all nils" do - Rollbar.send(:send_failsafe, nil, nil) + notifier.send(:send_failsafe, nil, nil) end end context "request_data_extractor" do before(:each) do @@ -1099,9 +1546,72 @@ env = {"HTTP_USER_AGENT" => "test", "HTTP_COOKIE" => "cookie"} headers = @dummy_class.send(:rollbar_headers, env) headers.should have_key "User-Agent" headers.should_not have_key "Cookie" end + end + end + + describe '.scoped' do + let(:scope_options) do + { :foo => 'bar' } + end + + it 'changes payload options inside the block' do + Rollbar.reset_notifier! + configure + + current_notifier_id = Rollbar.notifier.object_id + + Rollbar.scoped(scope_options) do + configuration = Rollbar.notifier.configuration + + expect(Rollbar.notifier.object_id).not_to be_eql(current_notifier_id) + expect(configuration.payload_options).to be_eql(scope_options) + end + + expect(Rollbar.notifier.object_id).to be_eql(current_notifier_id) + end + + context 'if the block fails' do + let(:crashing_block) { proc { fail } } + + it 'restores the old notifier' do + notifier = Rollbar.notifier + + expect { Rollbar.scoped(&crashing_block) }.to raise_error + expect(notifier).to be_eql(Rollbar.notifier) + end + end + + context 'if the block creates a new thread' do + let(:block) do + proc do + Thread.new do + scope = Rollbar.notifier.configuration.payload_options + Thread.main[:inner_scope] = scope + end.join + end + end + + let(:scope) do + { :foo => 'bar' } + end + + it 'maintains the parent thread notifier scope' do + Rollbar.scoped(scope, &block) + + expect(Thread.main[:inner_scope]).to be_eql(scope) + end + end + end + + describe '.reset_notifier' do + it 'resets the notifier' do + notifier1_id = Rollbar.notifier.object_id + + Rollbar.reset_notifier! + expect(Rollbar.notifier.object_id).not_to be_eql(notifier1_id) end end # configure with some basic params def configure