diff --git a/Gemfile b/Gemfile index c6774981f..2e1ef8175 100644 --- a/Gemfile +++ b/Gemfile @@ -3,8 +3,9 @@ source "https://rubygems.org" group :test, optional: true do gem 'rake', RUBY_VERSION <= '1.9.3' ? '~> 11.3.0' : '~> 12.3.0' gem 'rspec' - gem 'rspec-mocks' + gem 'logging' gem 'rdoc', '~> 5.1.0' + gem 'rspec-mocks' gem 'pry' gem 'addressable', '~> 2.3.8' gem 'delayed_job' if RUBY_VERSION >= '2.2.2' diff --git a/example/rails-42/app/controllers/application_controller.rb b/example/rails-42/app/controllers/application_controller.rb index 33f2701ff..fbe55337a 100644 --- a/example/rails-42/app/controllers/application_controller.rb +++ b/example/rails-42/app/controllers/application_controller.rb @@ -56,4 +56,10 @@ def severity end @text = msg end + + def crash_after_log + @test_model = TestModel.new :foo => "Foo" + @test_model.save + raise "Crash" + end end diff --git a/example/rails-42/app/models/test_model.rb b/example/rails-42/app/models/test_model.rb new file mode 100644 index 000000000..39bf2b8f1 --- /dev/null +++ b/example/rails-42/app/models/test_model.rb @@ -0,0 +1,2 @@ +class TestModel < ActiveRecord::Base +end diff --git a/example/rails-42/config/routes.rb b/example/rails-42/config/routes.rb index 9a3a1b9f0..95e2c938c 100644 --- a/example/rails-42/config/routes.rb +++ b/example/rails-42/config/routes.rb @@ -1,5 +1,6 @@ Rails.application.routes.draw do root :to => 'application#index' + get "log" => 'application#crash_after_log' get 'crash' => 'application#crash' get 'crash_with_callback' => 'application#callback' diff --git a/example/rails-42/db/migrate/20171009132509_create_test_models.rb b/example/rails-42/db/migrate/20171009132509_create_test_models.rb new file mode 100644 index 000000000..bebac26f9 --- /dev/null +++ b/example/rails-42/db/migrate/20171009132509_create_test_models.rb @@ -0,0 +1,9 @@ +class CreateTestModels < ActiveRecord::Migration + def change + create_table :test_models do |t| + t.string :foo + + t.timestamps null: false + end + end +end diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index d0d0bd614..77d1eea2b 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -124,7 +124,7 @@ def notify(exception, auto_notify=false, &block) ## # Records a breadcrumb to give context to notifications - def leave_breadcrumb(name, type=nil, metadata={}) + def leave_breadcrumb(name, metadata={}, type=Bugsnag::Breadcrumbs::MANUAL_TYPE) configuration.recorder.add_breadcrumb(Bugsnag::Breadcrumbs::Breadcrumb.new(name, type, metadata)) end diff --git a/lib/bugsnag/breadcrumbs/appender.rb b/lib/bugsnag/breadcrumbs/appender.rb new file mode 100644 index 000000000..96c5e1f56 --- /dev/null +++ b/lib/bugsnag/breadcrumbs/appender.rb @@ -0,0 +1,29 @@ +require_relative "logger" +require "logging" + +module Bugsnag::Breadcrumbs + class Appender < Logging::Appender + def initialize(level = Logger::INFO) + super "Bugsnag", { :level => level } + end + + def <<(message) + return if closed? + Bugsnag::Breadcrumbs::Logger.log_breadcrumb(message) + end + + def append(event) + return if closed? || !allow(event) + + message = event.data.to_s + metadata = { + :method => event.method.to_s, + :file => event.file.to_s, + :line => event.line.to_s + }.delete_if {|k,v| v == ""} + + severity = Bugsnag::Breadcrumbs::Logger.get_severity_name(event.level) + Bugsnag::Breadcrumbs::Logger.log_breadcrumb(message, metadata, severity) + end + end +end diff --git a/lib/bugsnag/breadcrumbs/logger.rb b/lib/bugsnag/breadcrumbs/logger.rb new file mode 100644 index 000000000..54cfa87fe --- /dev/null +++ b/lib/bugsnag/breadcrumbs/logger.rb @@ -0,0 +1,70 @@ +require "logger" +require "bugsnag" + +module Bugsnag::Breadcrumbs + class Logger < Logger + SEVERITIES = [ + "debug", + "info", + "warn", + "error", + "fatal", + "unknown" + ] + + def self.get_severity_name(severity) + if (0..5).cover? severity + SEVERITIES[severity] + else + severity + end + end + + def self.log_breadcrumb(message, data = nil, severity = "unknown") + metadata = { + :severity => severity, + :message => message + } + if data.is_a? Hash + metadata.merge!(data) + elsif !data.nil? + metadata[:data] = data.to_s + end + + Bugsnag.leave_breadcrumb("Log output", metadata, Bugsnag::Breadcrumbs::LOG_TYPE) + end + + def initialize(level = Logger::INFO) + @open = true + super nil, level + end + + def add(severity, message = nil, progname = nil) + return unless @open + + if block_given? + message = yield message + elsif message.nil? + message = progname + end + + Bugsnag::Breadcrumbs::Logger.log_breadcrumb(message, { :progname => progname }, Bugsnag::Breadcrumbs::Logger.get_severity_name(severity)) if severity >= level + end + alias log add + + def <<(message) + return unless @open + Bugsnag::Breadcrumbs::Logger.log_breadcrumb(message) + end + + def close + @open = false + true + end + + def reopen + @open = true + true + end + end +end diff --git a/spec/appender_spec.rb b/spec/appender_spec.rb new file mode 100644 index 000000000..01ee55ceb --- /dev/null +++ b/spec/appender_spec.rb @@ -0,0 +1,64 @@ +# encoding: utf-8 + +# Necessary to avoid monkey patching thread methods +ENV["LOGGING_INHERIT_CONTEXT"] = "false" + +require 'spec_helper' +require 'logger' +require 'logging' +require 'bugsnag/breadcrumbs/appender' + +describe Bugsnag::Breadcrumbs::Appender do + + before do + @appender = Bugsnag::Breadcrumbs::Appender.new + end + + it "writes breadcrumbs" do + expect(Bugsnag).to receive(:leave_breadcrumb).with( + "Log output", + { + :severity => "unknown", + :message => "message" + }, + "log" + ) + @appender << "message" + end + + it "write breadcrumbs from a logevent" do + expect(Bugsnag).to receive(:leave_breadcrumb).with( + "Log output", + { + :message => ["message1", "message2"].to_s, + :severity => "info", + }, + "log" + ) + logevent = Logging::LogEvent.new("testLogger", Logger::INFO, ["message1", "message2"], false) + @appender.append logevent + end + + it "adds trace metadata if available" do + expect(Bugsnag).to receive(:leave_breadcrumb) do |name, metadata, severity| + expect(name).to eq("Log output") + expect(metadata).to include(:message, :severity, :method, :file, :line) + expect(metadata).to include(:message => ["message1", "message2"].to_s, :severity => "info") + expect(severity).to eq("log") + end + logevent = Logging::LogEvent.new("testLogger", Logger::INFO, ["message1", "message2"], true) + @appender.append logevent + end + + it "doesn't write if closed" do + expect(Bugsnag).to_not receive(:leave_breadcrumb) + @appender.close + @appender << "message" + logevent = Logging::LogEvent.new("testLogger", Logger::INFO, ["message1", "message2"], false) + @appender.append logevent + end + + it "is an appender and a bugsnag appender" do + expect(@appender.class.ancestors).to include(Bugsnag::Breadcrumbs::Appender, Logging::Appender) + end +end diff --git a/spec/logger_spec.rb b/spec/logger_spec.rb new file mode 100644 index 000000000..b4d404f08 --- /dev/null +++ b/spec/logger_spec.rb @@ -0,0 +1,61 @@ +# encoding: utf-8 + +require 'spec_helper' +require 'logger' +require 'bugsnag/breadcrumbs/logger' + +describe Bugsnag::Breadcrumbs::Logger do + + before do + @logger = Bugsnag::Breadcrumbs::Logger.new + end + + it "writes by default" do + expect(Bugsnag).to receive(:leave_breadcrumb).with( + "Log output", + { + :severity => "unknown", + :message => "message" + }, + "log" + ) + @logger << "message" + end + + it "doesn't write when closed" do + expect(Bugsnag).to_not receive(:leave_breadcrumb) + @logger.close + @logger << "message" + end + + it "writes after being re-opened" do + expect(Bugsnag).to receive(:leave_breadcrumb).with( + "Log output", + { + :severity => "unknown", + :message => "message" + }, + "log" + ) + @logger.close + @logger.reopen + @logger << "message" + end + + it "allows a progname and severity" do + expect(Bugsnag).to receive(:leave_breadcrumb).with( + "Log output", + { + :progname => "logTests", + :severity => "info", + :message => "message" + }, + "log" + ) + @logger.info("logTests") { "message" } + end + + it "is a logger and a bugsnag logger" do + expect(@logger.class.ancestors).to include(Bugsnag::Breadcrumbs::Logger, Logger) + end +end diff --git a/spec/report_spec.rb b/spec/report_spec.rb index 0b7db97c7..1a52551b2 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -119,7 +119,7 @@ def gloops end it "attaches added breadcrumbs" do - Bugsnag.leave_breadcrumb("Test", Bugsnag::Breadcrumbs::MANUAL_TYPE, {:foo => "foo", :bar => "bar"}) + Bugsnag.leave_breadcrumb("Test", {:foo => "foo", :bar => "bar"}, Bugsnag::Breadcrumbs::MANUAL_TYPE) Bugsnag.notify(BugsnagTestException.new("It crashed")) expect(Bugsnag).to have_sent_notification{ |payload, headers| breadcrumb = get_breadcrumb_from_payload(payload)