Skip to content

Commit

Permalink
Merge adf1510 into e7acc4f
Browse files Browse the repository at this point in the history
  • Loading branch information
Cawllec committed Dec 12, 2018
2 parents e7acc4f + adf1510 commit 6460736
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 3 deletions.
4 changes: 2 additions & 2 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ Metrics/BlockNesting:
# Offense count: 3
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 149
Max: 170

# Offense count: 12
Metrics/CyclomaticComplexity:
Expand All @@ -302,7 +302,7 @@ Metrics/MethodLength:
# Offense count: 1
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 125
Max: 140
Exclude:
- 'lib/bugsnag/helpers.rb'

Expand Down
34 changes: 34 additions & 0 deletions lib/bugsnag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
require "bugsnag/middleware/classify_error"
require "bugsnag/middleware/delayed_job"

require "bugsnag/breadcrumbs/validator"
require "bugsnag/breadcrumbs/breadcrumb"
require "bugsnag/breadcrumbs/breadcrumbs"

module Bugsnag
LOCK = Mutex.new
INTEGRATIONS = [:resque, :sidekiq, :mailman, :delayed_job, :shoryuken, :que]
Expand Down Expand Up @@ -189,6 +193,36 @@ def load_integration(integration)
end
end

##
# Leave a breadcrumb to be attached to subsequent reports
#
# @param name [String] the main breadcrumb name/message
# @param meta_data [Hash] String, Numeric, or Boolean meta data to attach
# @param type [String] the breadcrumb type, from Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES
# @param auto [Symbol] set to :auto if the breadcrumb is automatically created
def leave_breadcrumb(name, meta_data={}, type=Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE, auto=:manual)
breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(name, type, meta_data, auto)
validator = Bugsnag::Breadcrumbs::Validator.new(configuration)

# Initial validation
validator.validate(breadcrumb)

# Skip if it's already invalid
unless breadcrumb.ignore?
# Run callbacks
configuration.before_breadcrumb_callbacks.each do |c|
(c.arity > 0 ? c.call(breadcrumb) : c.call)
break if breadcrumb.ignore?
end

# Validate again incase of callback alteration
validator.validate(breadcrumb)

# Add to breadcrumbs buffer if still valid
configuration.breadcrumbs << breadcrumb unless breadcrumb.ignore?
end
end

private

def deliver_notification?(exception, auto_notify)
Expand Down
2 changes: 2 additions & 0 deletions lib/bugsnag/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require "bugsnag/middleware/suggestion_data"
require "bugsnag/middleware/classify_error"
require "bugsnag/middleware/session_data"
require "bugsnag/middleware/breadcrumbs"
require "bugsnag/utility/circular_buffer"
require "bugsnag/breadcrumbs/breadcrumbs"

Expand Down Expand Up @@ -118,6 +119,7 @@ def initialize
self.internal_middleware.use Bugsnag::Middleware::SuggestionData
self.internal_middleware.use Bugsnag::Middleware::ClassifyError
self.internal_middleware.use Bugsnag::Middleware::SessionData
self.internal_middleware.use Bugsnag::Middleware::Breadcrumbs

self.middleware = Bugsnag::MiddlewareStack.new
self.middleware.use Bugsnag::Middleware::Callbacks
Expand Down
21 changes: 21 additions & 0 deletions lib/bugsnag/middleware/breadcrumbs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Bugsnag::Middleware
##
# Adds breadcrumbs to the report
class Breadcrumbs
##
# @param next_callable [#call] the next callable middleware
def initialize(next_callable)
@next = next_callable
end

##
# Execute this middleware
#
# @param report [Bugsnag::Report] the report being iterated over
def call(report)
breadcrumbs = report.configuration.breadcrumbs.to_a
report.breadcrumbs = breadcrumbs unless breadcrumbs.empty?
@next.call(report)
end
end
end
29 changes: 28 additions & 1 deletion lib/bugsnag/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ class Report
attr_accessor :api_key
attr_accessor :app_type
attr_accessor :app_version
attr_accessor :breadcrumbs
attr_accessor :configuration
attr_accessor :context
attr_accessor :delivery_method
attr_accessor :exceptions
attr_accessor :hostname
attr_accessor :grouping_hash
attr_accessor :message
attr_accessor :meta_data
attr_accessor :name
attr_accessor :raw_exceptions
attr_accessor :release_stage
attr_accessor :session
Expand All @@ -51,9 +54,14 @@ def initialize(exception, passed_configuration, auto_notify=false)
self.api_key = configuration.api_key
self.app_type = configuration.app_type
self.app_version = configuration.app_version
self.breadcrumbs = []
self.delivery_method = configuration.delivery_method
self.hostname = configuration.hostname
self.message = defined?(exception.message) ? exception.message : exception.to_s
self.meta_data = {}

# Notified strings display as RuntimeErrors in the dashboard
self.name = exception.is_a?(Exception) ? exception.class.to_s : RuntimeError.to_s
self.release_stage = configuration.release_stage
self.severity = auto_notify ? "error" : "warning"
self.severity_reason = auto_notify ? {:type => UNHANDLED_EXCEPTION} : {:type => HANDLED_EXCEPTION}
Expand Down Expand Up @@ -110,7 +118,14 @@ def as_json
payload_event = Bugsnag::Cleaner.clean_object_encoding(payload_event)

# filter out sensitive values in (and cleanup encodings) metaData
payload_event[:metaData] = Bugsnag::Cleaner.new(configuration.meta_data_filters).clean_object(meta_data)
filter_cleaner = Bugsnag::Cleaner.new(configuration.meta_data_filters)
payload_event[:metaData] = filter_cleaner.clean_object(meta_data)
payload_event[:breadcrumbs] = breadcrumbs.map do |breadcrumb|
breadcrumb_hash = breadcrumb.to_h
breadcrumb_hash[:metaData] = filter_cleaner.clean_object(breadcrumb_hash[:metaData])
breadcrumb_hash
end

payload_event.reject! {|k,v| v.nil? }

# return the payload hash
Expand Down Expand Up @@ -153,6 +168,18 @@ def ignore!
@should_ignore = true
end

##
# Generates a summary to be attached as a breadcrumb
#
# @return [Hash] a Hash containing the report's name, message, and severity
def summary
{
:name => name,
:message => message,
:severity => severity
}
end

private

def generate_exception_list
Expand Down
149 changes: 149 additions & 0 deletions spec/bugsnag_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,153 @@ module Kernel
Kernel.send(:remove_const, :REQUIRED)
end
end

describe "#leave_breadcrumb" do

let(:breadcrumbs) { Bugsnag.configuration.breadcrumbs }
let(:timestamp_regex) { /^\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:[\d\.]+Z$/ }

it "requires only a name argument" do
Bugsnag.leave_breadcrumb("TestName")
expect(breadcrumbs.to_a.size).to eq(1)
expect(breadcrumbs.first.to_h).to match({
:name => "TestName",
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
:metaData => {},
:timestamp => match(timestamp_regex)
})
end

it "accepts meta_data" do
Bugsnag.leave_breadcrumb("TestName", { :a => 1, :b => "2" })
expect(breadcrumbs.to_a.size).to eq(1)
expect(breadcrumbs.first.to_h).to match({
:name => "TestName",
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
:metaData => { :a => 1, :b => "2" },
:timestamp => match(timestamp_regex)
})
end

it "allows different message types" do
Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE)
expect(breadcrumbs.to_a.size).to eq(1)
expect(breadcrumbs.first.to_h).to match({
:name => "TestName",
:type => Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE,
:metaData => {},
:timestamp => match(timestamp_regex)
})
end

it "validates before leaving" do
Bugsnag.leave_breadcrumb(
"123123123123123123123123123123456456456456456456456456456456",
{
:a => 1,
:b => [1, 2, 3, 4],
:c => {
:test => true,
:test2 => false
}
},
"Not a real type"
)
expect(breadcrumbs.to_a.size).to eq(1)
expect(breadcrumbs.first.to_h).to match({
:name => "123123123123123123123123123123",
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
:metaData => {
:a => 1
},
:timestamp => match(timestamp_regex)
})
end

it "runs callbacks before leaving" do
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new { |breadcrumb|
breadcrumb.meta_data = {
:callback => true
}
}
Bugsnag.leave_breadcrumb("TestName")
expect(breadcrumbs.to_a.size).to eq(1)
expect(breadcrumbs.first.to_h).to match({
:name => "TestName",
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
:metaData => {
:callback => true
},
:timestamp => match(timestamp_regex)
})
end

it "validates after callbacks" do
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new { |breadcrumb|
breadcrumb.meta_data = {
:int => 1,
:array => [1, 2, 3],
:hash => {
:a => 1,
:b => 2
}
}
breadcrumb.type = "Not a real type"
breadcrumb.name = "123123123123123123123123123123456456456456456"
}
Bugsnag.leave_breadcrumb("TestName")
expect(breadcrumbs.to_a.size).to eq(1)
expect(breadcrumbs.first.to_h).to match({
:name => "123123123123123123123123123123",
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
:metaData => {
:int => 1
},
:timestamp => match(timestamp_regex)
})
end

it "doesn't add when ignored by the validator" do
Bugsnag.configuration.automatic_breadcrumb_types = []
Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE, :auto)
expect(breadcrumbs.to_a.size).to eq(0)
end

it "doesn't add if ignored in a callback" do
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new { |breadcrumb|
breadcrumb.ignore!
}
Bugsnag.leave_breadcrumb("TestName")
expect(breadcrumbs.to_a.size).to eq(0)
end

it "doesn't add when ignored after the callbacks" do
Bugsnag.configuration.automatic_breadcrumb_types = [
Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE
]
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new { |breadcrumb|
breadcrumb.type = Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE
}
Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE, :auto)
expect(breadcrumbs.to_a.size).to eq(0)
end

it "doesn't call callbacks if ignored early" do
Bugsnag.configuration.automatic_breadcrumb_types = []
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new { |breadcrumb|
fail "This shouldn't be called"
}
Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE, :auto)
end

it "doesn't continue to call callbacks if ignored in them" do
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new { |breadcrumb|
breadcrumb.ignore!
}
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new { |breadcrumb|
fail "This shouldn't be called"
}
Bugsnag.leave_breadcrumb("TestName")
end
end
end
Loading

0 comments on commit 6460736

Please sign in to comment.