From a1b9e42dd74ae7e56c8e979241734b3203936712 Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 11:50:21 -0700 Subject: [PATCH 001/109] Start thinking about new interface --- .document | 5 - lib/bugsnag.rb | 95 ++----- lib/bugsnag/capistrano.rb | 7 - lib/bugsnag/capistrano2.rb | 32 --- lib/bugsnag/configuration.rb | 32 +-- lib/bugsnag/delay/resque.rb | 21 -- lib/bugsnag/deploy.rb | 34 --- lib/bugsnag/{ => integrations}/delayed_job.rb | 0 lib/bugsnag/{ => integrations}/mailman.rb | 4 +- lib/bugsnag/{ => integrations}/rack.rb | 12 +- .../rails/active_record_rescue.rb | 0 .../rails/controller_methods.rb | 9 - lib/bugsnag/{ => integrations}/railtie.rb | 12 +- lib/bugsnag/{ => integrations}/rake.rb | 4 +- lib/bugsnag/{ => integrations}/resque.rb | 0 lib/bugsnag/{ => integrations}/sidekiq.rb | 4 +- lib/bugsnag/meta_data.rb | 1 + lib/bugsnag/middleware/callbacks.rb | 4 - lib/bugsnag/middleware/rails2_request.rb | 52 ---- lib/bugsnag/rails.rb | 70 ----- lib/bugsnag/rails/action_controller_rescue.rb | 62 ----- lib/bugsnag/{notification.rb => report.rb} | 239 +++--------------- lib/bugsnag/stacktrace.rb | 116 +++++++++ lib/bugsnag/tasks/bugsnag.cap | 48 ---- lib/bugsnag/tasks/bugsnag.rake | 70 ----- 25 files changed, 180 insertions(+), 753 deletions(-) delete mode 100644 .document delete mode 100644 lib/bugsnag/capistrano.rb delete mode 100644 lib/bugsnag/capistrano2.rb delete mode 100644 lib/bugsnag/delay/resque.rb delete mode 100644 lib/bugsnag/deploy.rb rename lib/bugsnag/{ => integrations}/delayed_job.rb (100%) rename lib/bugsnag/{ => integrations}/mailman.rb (82%) rename lib/bugsnag/{ => integrations}/rack.rb (87%) rename lib/bugsnag/{ => integrations}/rails/active_record_rescue.rb (100%) rename lib/bugsnag/{ => integrations}/rails/controller_methods.rb (82%) rename lib/bugsnag/{ => integrations}/railtie.rb (82%) rename lib/bugsnag/{ => integrations}/rake.rb (78%) rename lib/bugsnag/{ => integrations}/resque.rb (100%) rename lib/bugsnag/{ => integrations}/sidekiq.rb (84%) delete mode 100644 lib/bugsnag/middleware/rails2_request.rb delete mode 100644 lib/bugsnag/rails.rb delete mode 100644 lib/bugsnag/rails/action_controller_rescue.rb rename lib/bugsnag/{notification.rb => report.rb} (51%) create mode 100644 lib/bugsnag/stacktrace.rb delete mode 100644 lib/bugsnag/tasks/bugsnag.cap diff --git a/.document b/.document deleted file mode 100644 index 3d618dd8f..000000000 --- a/.document +++ /dev/null @@ -1,5 +0,0 @@ -lib/**/*.rb -bin/* -- -features/**/*.feature -LICENSE.txt diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index b50dfdd10..00113521f 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -13,8 +13,8 @@ require "bugsnag/delivery/synchronous" require "bugsnag/delivery/thread_queue" -require "bugsnag/rack" -require "bugsnag/railtie" if defined?(Rails::Railtie) +require "bugsnag/integrations/rack" +require "bugsnag/integrations/railtie" if defined?(Rails::Railtie) require "bugsnag/middleware/rack_request" require "bugsnag/middleware/warden_user" @@ -26,78 +26,30 @@ require "bugsnag/middleware/callbacks" module Bugsnag - LOG_PREFIX = "** [Bugsnag] " LOCK = Mutex.new class << self # Configure the Bugsnag notifier application-wide settings. - def configure(config_hash=nil) - if config_hash - config_hash.each do |k,v| - configuration.send("#{k}=", v) rescue nil if configuration.respond_to?("#{k}=") - end - end - + def configure yield(configuration) if block_given? - - # Use resque for asynchronous notification if required - require "bugsnag/delay/resque" if configuration.delay_with_resque && defined?(Resque) - - # Log that we are ready to rock - @logged_ready = false unless defined?(@logged_ready) - - if configuration.api_key && !@logged_ready - log "Bugsnag exception handler #{VERSION} ready" - @logged_ready = true - end end # Explicitly notify of an exception - def notify(exception, overrides=nil, request_data=nil, &block) - notification = Notification.new(exception, configuration, overrides, request_data) + def notify(exception, &block) + #Build notification + notification = Notification.new(exception, configuration) + + #Check if config allows send + if configuration. + #Run internal middleware + #Run internal block? + #Run users middleware + #Run users block + #Deliver yield(notification) if block_given? notification.deliver - notification - end - - # Notify of an exception unless it should be ignored - def notify_or_ignore(exception, overrides=nil, request_data=nil, &block) - notification = Notification.new(exception, configuration, overrides, request_data) - - yield(notification) if block_given? - - unless notification.ignore? - notification.deliver - notification - else - false - end - end - - # Auto notify of an exception, called from rails and rack exception - # rescuers, unless auto notification is disabled, or we should ignore this - # error class - def auto_notify(exception, overrides=nil, request_data=nil, &block) - overrides ||= {} - overrides.merge!({:severity => "error"}) - notify_or_ignore(exception, overrides, request_data, &block) if configuration.auto_notify - end - - # Log wrapper - def log(message) - configuration.logger.info("#{LOG_PREFIX}#{message}") - end - - # Warning logger - def warn(message) - configuration.logger.warn("#{LOG_PREFIX}#{message}") - end - - # Debug logger - def debug(message) - configuration.logger.info("#{LOG_PREFIX}#{message}") if configuration.debug end # Configuration getters @@ -106,33 +58,16 @@ def configuration @configuration || LOCK.synchronize { @configuration ||= Bugsnag::Configuration.new } end - # Set "per-request" data, temporal data for use in bugsnag middleware - def set_request_data(key, value) - Bugsnag.configuration.set_request_data(key, value) - end - - # Clear all "per-request" data, temporal data for use in bugsnag middleware - # This method should be called after each distinct request or session ends - # Eg. After completing a page request in a web app - def clear_request_data - Bugsnag.configuration.clear_request_data - end - # Allow access to "before notify" callbacks def before_notify_callbacks Bugsnag.configuration.request_data[:before_callbacks] ||= [] end - - # Allow access to "after notify" callbacks - def after_notify_callbacks - Bugsnag.configuration.request_data[:after_callbacks] ||= [] - end end end [:resque, :sidekiq, :mailman, :delayed_job].each do |integration| begin - require "bugsnag/#{integration}" + require "bugsnag/integrations/#{integration}" rescue LoadError end end diff --git a/lib/bugsnag/capistrano.rb b/lib/bugsnag/capistrano.rb deleted file mode 100644 index 1b03a127e..000000000 --- a/lib/bugsnag/capistrano.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "bugsnag" - -if defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0') - load File.expand_path('../tasks/bugsnag.cap', __FILE__) -else - require_relative 'capistrano2' -end diff --git a/lib/bugsnag/capistrano2.rb b/lib/bugsnag/capistrano2.rb deleted file mode 100644 index 1f540072d..000000000 --- a/lib/bugsnag/capistrano2.rb +++ /dev/null @@ -1,32 +0,0 @@ -module Bugsnag - module Capistrano - def self.load_into(configuration) - configuration.load do - after "deploy", "bugsnag:deploy" - after "deploy:migrations", "bugsnag:deploy" - - namespace :bugsnag do - desc "Notify Bugsnag that new production code has been deployed" - task :deploy, :except => { :no_release => true }, :on_error => :continue do - begin - Bugsnag::Deploy.notify({ - :api_key => fetch(:bugsnag_api_key, ENV["BUGSNAG_API_KEY"]), - :release_stage => ENV["BUGSNAG_RELEASE_STAGE"] || fetch(:rails_env, "production"), - :revision => fetch(:current_revision, ENV["BUGSNAG_REVISION"]), - :repository => fetch(:repository, ENV["BUGSNAG_REPOSITORY"]), - :branch => fetch(:branch, ENV["BUGSNAG_BRANCH"], - :app_version => fetch(:app_version, ENV["BUGSNAG_APP_VERSION"])) - }) - rescue - logger.important("Bugnsag deploy notification failed, #{$!.inspect}") - end - - logger.info "Bugsnag deploy notification complete." - end - end - end - end - end -end - -Bugsnag::Capistrano.load_into(Capistrano::Configuration.instance) if Capistrano::Configuration.instance diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index 4ced1f5ca..eca6fa03c 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -14,7 +14,6 @@ class Configuration attr_accessor :send_environment attr_accessor :send_code attr_accessor :project_root - attr_accessor :vendor_paths attr_accessor :app_version attr_accessor :app_type attr_accessor :params_filters @@ -34,9 +33,9 @@ class Configuration attr_accessor :delivery_method attr_writer :ignore_classes - THREAD_LOCAL_NAME = "bugsnag_req_data" + LOG_PREFIX = "** [Bugsnag] " - DEFAULT_ENDPOINT = "notify.bugsnag.com" + THREAD_LOCAL_NAME = "bugsnag_req_data" DEFAULT_PARAMS_FILTERS = [ /authorization/i, @@ -46,40 +45,19 @@ class Configuration "rack.request.form_vars" ].freeze - DEFAULT_IGNORE_CLASSES = [ - "AbstractController::ActionNotFound", - "ActionController::InvalidAuthenticityToken", - "ActionController::ParameterMissing", - "ActionController::UnknownAction", - "ActionController::UnknownFormat", - "ActionController::UnknownHttpMethod", - "ActiveRecord::RecordNotFound", - "CGI::Session::CookieStore::TamperedWithCookie", - "Mongoid::Errors::DocumentNotFound", - "SignalException", - "SystemExit", - ].freeze - - DEFAULT_IGNORE_USER_AGENTS = [].freeze - - DEFAULT_DELIVERY_METHOD = :thread_queue - def initialize @mutex = Mutex.new # Set up the defaults self.auto_notify = true - self.use_ssl = true self.send_environment = false self.send_code = true self.params_filters = Set.new(DEFAULT_PARAMS_FILTERS) - self.ignore_classes = Set.new(DEFAULT_IGNORE_CLASSES) - self.ignore_user_agents = Set.new(DEFAULT_IGNORE_USER_AGENTS) - self.endpoint = DEFAULT_ENDPOINT + self.ignore_classes = Set.new([]) + self.endpoint = "https://notify.bugsnag.com" self.hostname = default_hostname - self.delivery_method = DEFAULT_DELIVERY_METHOD + self.delivery_method = :thread_queue self.timeout = 15 - self.vendor_paths = [%r{vendor/}] self.notify_release_stages = nil # Read the API key from the environment diff --git a/lib/bugsnag/delay/resque.rb b/lib/bugsnag/delay/resque.rb deleted file mode 100644 index 610bd137e..000000000 --- a/lib/bugsnag/delay/resque.rb +++ /dev/null @@ -1,21 +0,0 @@ -module Bugsnag - module Delay - class Resque - @queue = "bugsnag" - def self.perform(*args) - Bugsnag::Notification.deliver_exception_payload_without_resque(*args) - end - end - end -end - -Bugsnag::Notification.class_eval do - class << self - def deliver_exception_payload_with_resque(*args) - Resque.enqueue(Bugsnag::Delay::Resque, *args) - end - - alias_method :deliver_exception_payload_without_resque, :deliver_exception_payload - alias_method :deliver_exception_payload, :deliver_exception_payload_with_resque - end -end \ No newline at end of file diff --git a/lib/bugsnag/deploy.rb b/lib/bugsnag/deploy.rb deleted file mode 100644 index 1f5f5b1b1..000000000 --- a/lib/bugsnag/deploy.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "json" - -module Bugsnag - class Deploy - def self.notify(opts = {}) - - configuration = Bugsnag.configuration.dup - - # update configuration based on parameters passed in - [:api_key, :app_version, :release_stage, :endpoint, :use_ssl, - :proxy_host, :proxy_port, :proxy_user, :proxy_password].each do |param| - unless opts[param].nil? - configuration.send :"#{param}=", opts[param] - end - end - - endpoint = (configuration.use_ssl ? "https://" : "http://") + configuration.endpoint + "/deploy" - - parameters = { - "apiKey" => configuration.api_key, - "releaseStage" => configuration.release_stage, - "appVersion" => configuration.app_version, - "revision" => opts[:revision], - "repository" => opts[:repository], - "branch" => opts[:branch] - }.reject {|k,v| v == nil} - - raise RuntimeError.new("No API key found when notifying of deploy") if !parameters["apiKey"] || parameters["apiKey"].empty? - - payload_string = ::JSON.dump(parameters) - Bugsnag::Delivery::Synchronous.deliver(endpoint, payload_string, configuration) - end - end -end diff --git a/lib/bugsnag/delayed_job.rb b/lib/bugsnag/integrations/delayed_job.rb similarity index 100% rename from lib/bugsnag/delayed_job.rb rename to lib/bugsnag/integrations/delayed_job.rb diff --git a/lib/bugsnag/mailman.rb b/lib/bugsnag/integrations/mailman.rb similarity index 82% rename from lib/bugsnag/mailman.rb rename to lib/bugsnag/integrations/mailman.rb index 142f59267..bcf3606aa 100644 --- a/lib/bugsnag/mailman.rb +++ b/lib/bugsnag/integrations/mailman.rb @@ -10,7 +10,7 @@ def initialize def call(mail) begin - Bugsnag.set_request_data :mailman_msg, mail.to_s + Bugsnag.configuration.set_request_data :mailman_msg, mail.to_s yield rescue Exception => ex @@ -18,7 +18,7 @@ def call(mail) Bugsnag.auto_notify(ex) raise ensure - Bugsnag.clear_request_data + Bugsnag.configuration.clear_request_data end end end diff --git a/lib/bugsnag/rack.rb b/lib/bugsnag/integrations/rack.rb similarity index 87% rename from lib/bugsnag/rack.rb rename to lib/bugsnag/integrations/rack.rb index 73f0afdf5..a2a52a37f 100644 --- a/lib/bugsnag/rack.rb +++ b/lib/bugsnag/integrations/rack.rb @@ -6,7 +6,7 @@ def initialize(app) # Configure bugsnag rack defaults Bugsnag.configure do |config| # Try to set the release_stage automatically if it hasn't already been set - config.release_stage ||= release_stage + config.release_stage ||= ENV["RACK_ENV"] if ENV["RACK_ENV"] # Try to set the project_root if it hasn't already been set, or show a warning if we can't unless config.project_root && !config.project_root.to_s.empty? @@ -27,7 +27,7 @@ def initialize(app) def call(env) # Set the request data for bugsnag middleware to use - Bugsnag.set_request_data(:rack_env, env) + Bugsnag.configuration.set_request_data(:rack_env, env) begin response = @app.call(env) @@ -47,13 +47,7 @@ def call(env) response ensure # Clear per-request data after processing the each request - Bugsnag.clear_request_data - end - - private - - def release_stage - ENV["BUGSNAG_RELEASE_STAGE"] || ENV["RACK_ENV"] + Bugsnag.configuration.clear_request_data end end end diff --git a/lib/bugsnag/rails/active_record_rescue.rb b/lib/bugsnag/integrations/rails/active_record_rescue.rb similarity index 100% rename from lib/bugsnag/rails/active_record_rescue.rb rename to lib/bugsnag/integrations/rails/active_record_rescue.rb diff --git a/lib/bugsnag/rails/controller_methods.rb b/lib/bugsnag/integrations/rails/controller_methods.rb similarity index 82% rename from lib/bugsnag/rails/controller_methods.rb rename to lib/bugsnag/integrations/rails/controller_methods.rb index e4ce339f4..76739daa0 100644 --- a/lib/bugsnag/rails/controller_methods.rb +++ b/lib/bugsnag/integrations/rails/controller_methods.rb @@ -10,10 +10,6 @@ def before_bugsnag_notify(*methods, &block) _add_bugsnag_notify_callback(:before_callbacks, *methods, &block) end - def after_bugsnag_notify(*methods, &block) - _add_bugsnag_notify_callback(:after_callbacks, *methods, &block) - end - def _add_bugsnag_notify_callback(callback_key, *methods, &block) options = methods.last.is_a?(Hash) ? methods.pop : {} @@ -40,10 +36,5 @@ def _add_bugsnag_notify_callback(callback_key, *methods, &block) end end end - - private - def notify_bugsnag(exception, custom_data=nil) - Bugsnag.notify(exception, custom_data) - end end end diff --git a/lib/bugsnag/railtie.rb b/lib/bugsnag/integrations/railtie.rb similarity index 82% rename from lib/bugsnag/railtie.rb rename to lib/bugsnag/integrations/railtie.rb index 882f83497..ae0e4dcdb 100644 --- a/lib/bugsnag/railtie.rb +++ b/lib/bugsnag/integrations/railtie.rb @@ -8,7 +8,7 @@ module Bugsnag class Railtie < Rails::Railtie rake_tasks do - require "bugsnag/rake" + require "bugsnag/integrations/rake" load "bugsnag/tasks/bugsnag.rake" end @@ -32,20 +32,16 @@ class Railtie < Rails::Railtie config.middleware.insert_before Bugsnag::Middleware::Callbacks, Bugsnag::Middleware::Rails3Request end - # Auto-load configuration settings from config/bugsnag.yml if it exists - config_file = ::Rails.root.join("config", "bugsnag.yml") - config = YAML.load_file(config_file) if File.exists?(config_file) - Bugsnag.configure(config[::Rails.env] ? config[::Rails.env] : config) if config - if defined?(::ActionController::Base) - require "bugsnag/rails/controller_methods" + require "bugsnag/integrations/rails/controller_methods" ::ActionController::Base.send(:include, Bugsnag::Rails::ControllerMethods) end if defined?(ActionController::API) + require "bugsnag/integrations/rails/controller_methods" ActionController::API.send(:include, Bugsnag::Rails::ControllerMethods) end if defined?(ActiveRecord::Base) - require "bugsnag/rails/active_record_rescue" + require "bugsnag/integrations/rails/active_record_rescue" ActiveRecord::Base.send(:include, Bugsnag::Rails::ActiveRecordRescue) end diff --git a/lib/bugsnag/rake.rb b/lib/bugsnag/integrations/rake.rb similarity index 78% rename from lib/bugsnag/rake.rb rename to lib/bugsnag/integrations/rake.rb index f858052a6..8725ba278 100644 --- a/lib/bugsnag/rake.rb +++ b/lib/bugsnag/integrations/rake.rb @@ -7,7 +7,7 @@ class Rake::Task def execute_with_bugsnag(args=nil) Bugsnag.configuration.app_type = "rake" old_task = Bugsnag.configuration.request_data[:bugsnag_running_task] - Bugsnag.set_request_data :bugsnag_running_task, self + Bugsnag.configuration.set_request_data :bugsnag_running_task, self execute_without_bugsnag(args) @@ -15,7 +15,7 @@ def execute_with_bugsnag(args=nil) Bugsnag.auto_notify(ex) raise ensure - Bugsnag.set_request_data :bugsnag_running_task, old_task + Bugsnag.configuration.set_request_data :bugsnag_running_task, old_task end alias_method :execute_without_bugsnag, :execute diff --git a/lib/bugsnag/resque.rb b/lib/bugsnag/integrations/resque.rb similarity index 100% rename from lib/bugsnag/resque.rb rename to lib/bugsnag/integrations/resque.rb diff --git a/lib/bugsnag/sidekiq.rb b/lib/bugsnag/integrations/sidekiq.rb similarity index 84% rename from lib/bugsnag/sidekiq.rb rename to lib/bugsnag/integrations/sidekiq.rb index 65858bc0a..fbb5d1632 100644 --- a/lib/bugsnag/sidekiq.rb +++ b/lib/bugsnag/integrations/sidekiq.rb @@ -11,7 +11,7 @@ def initialize def call(worker, msg, queue) begin # store msg/queue in thread local state to be read by Bugsnag::Middleware::Sidekiq - Bugsnag.set_request_data :sidekiq, { :msg => msg, :queue => queue } + Bugsnag.configuration.set_request_data :sidekiq, { :msg => msg, :queue => queue } yield rescue Exception => ex @@ -19,7 +19,7 @@ def call(worker, msg, queue) Bugsnag.auto_notify(ex) raise ensure - Bugsnag.clear_request_data + Bugsnag.configuration.clear_request_data end end end diff --git a/lib/bugsnag/meta_data.rb b/lib/bugsnag/meta_data.rb index f4584df0f..51fcb01ac 100644 --- a/lib/bugsnag/meta_data.rb +++ b/lib/bugsnag/meta_data.rb @@ -3,5 +3,6 @@ module MetaData attr_accessor :bugsnag_meta_data attr_accessor :bugsnag_user_id attr_accessor :bugsnag_context + attr_accessor :bugsnag_grouping_hash end end diff --git a/lib/bugsnag/middleware/callbacks.rb b/lib/bugsnag/middleware/callbacks.rb index 20e4c1d35..9bde6b5a3 100644 --- a/lib/bugsnag/middleware/callbacks.rb +++ b/lib/bugsnag/middleware/callbacks.rb @@ -10,10 +10,6 @@ def call(notification) end @bugsnag.call(notification) - - if notification.request_data[:after_callbacks] - notification.request_data[:after_callbacks].each {|c| c.call(*[notification][0...c.arity]) } - end end end end diff --git a/lib/bugsnag/middleware/rails2_request.rb b/lib/bugsnag/middleware/rails2_request.rb deleted file mode 100644 index aadd2f212..000000000 --- a/lib/bugsnag/middleware/rails2_request.rb +++ /dev/null @@ -1,52 +0,0 @@ -module Bugsnag::Middleware - class Rails2Request - def initialize(bugsnag) - @bugsnag = bugsnag - end - - def call(notification) - if notification.request_data[:rails2_request] - request = notification.request_data[:rails2_request] - params = request.parameters || {} - session_data = request.session.respond_to?(:to_hash) ? request.session.to_hash : request.session.data - - # Set the context - notification.context = "#{params[:controller]}##{params[:action]}" - - # Set a sensible default for user_id - notification.user_id = request.remote_ip if request.respond_to?(:remote_ip) - - # Build the clean url - url = "#{request.protocol}#{request.host}" - url << ":#{request.port}" unless [80, 443].include?(request.port) - url << Bugsnag::Cleaner.new(notification.configuration.params_filters).clean_url(request.fullpath) - - # Add a request tab - notification.add_tab(:request, { - :url => url, - :params => params.to_hash, - :controller => params[:controller], - :action => params[:action] - }) - - # Add an environment tab - if request.env && notification.configuration.send_environment - notification.add_tab(:environment, request.env) - end - - # Add a session tab - notification.add_tab(:session, session_data) if session_data - - # Add a cookies tab - notification.add_tab(:cookies, request.cookies) if request.cookies - - # Add the rails version - notification.add_tab(:environment, { - :railsVersion => Rails::VERSION::STRING - }) - end - - @bugsnag.call(notification) - end - end -end diff --git a/lib/bugsnag/rails.rb b/lib/bugsnag/rails.rb deleted file mode 100644 index bab583f8d..000000000 --- a/lib/bugsnag/rails.rb +++ /dev/null @@ -1,70 +0,0 @@ -# Rails 2.x hooks -# For Rails 3+ hooks, see railtie.rb - -require "bugsnag" -require "bugsnag/rails/controller_methods" -require "bugsnag/rails/action_controller_rescue" -require "bugsnag/rails/active_record_rescue" -require "bugsnag/middleware/rails2_request" -require "bugsnag/middleware/callbacks" - -module Bugsnag - module Rails - def self.initialize - if defined?(ActionController::Base) - ActionController::Base.send(:include, Bugsnag::Rails::ActionControllerRescue) - ActionController::Base.send(:include, Bugsnag::Rails::ControllerMethods) - end - if defined?(ActiveRecord::Base) && Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("4.3") - unless ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ActiveRecord::Base.raise_in_transactional_callbacks - ActiveRecord::Base.send(:include, Bugsnag::Rails::ActiveRecordRescue) - end - end - - Bugsnag.configure do |config| - config.logger ||= rails_logger - config.release_stage = release_stage if release_stage - config.project_root = rails_root if rails_root - - config.middleware.insert_before(Bugsnag::Middleware::Callbacks,Bugsnag::Middleware::Rails2Request) - end - - # Auto-load configuration settings from config/bugsnag.yml if it exists - config_file = File.join(rails_root, "config", "bugsnag.yml") - config = YAML.load_file(config_file) if File.exists?(config_file) - Bugsnag.configure(config[rails_env] ? config[rails_env] : config) if config - - Bugsnag.configuration.app_type = "rails" - end - - def self.rails_logger - if defined?(::Rails.logger) - rails_logger = ::Rails.logger - elsif defined?(RAILS_DEFAULT_LOGGER) - rails_logger = RAILS_DEFAULT_LOGGER - end - end - - def self.release_stage - ENV["BUGSNAG_RELEASE_STAGE"] || rails_env - end - - def self.rails_env - if defined?(::Rails.env) - ::Rails.env - elsif defined?(RAILS_ENV) - RAILS_ENV - end - end - - def self.rails_root - if defined?(::Rails.root) - ::Rails.root - elsif defined?(RAILS_ROOT) - RAILS_ROOT - end - end - end -end - -Bugsnag::Rails.initialize diff --git a/lib/bugsnag/rails/action_controller_rescue.rb b/lib/bugsnag/rails/action_controller_rescue.rb deleted file mode 100644 index 6125e9cd6..000000000 --- a/lib/bugsnag/rails/action_controller_rescue.rb +++ /dev/null @@ -1,62 +0,0 @@ -# Rails 2.x only -module Bugsnag::Rails - module ActionControllerRescue - def self.included(base) - base.extend(ClassMethods) - - # Hook into rails exception rescue stack - base.send(:alias_method, :rescue_action_in_public_without_bugsnag, :rescue_action_in_public) - base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_bugsnag) - - base.send(:alias_method, :rescue_action_locally_without_bugsnag, :rescue_action_locally) - base.send(:alias_method, :rescue_action_locally, :rescue_action_locally_with_bugsnag) - - # Run filters on requests to capture request data - base.send(:prepend_before_filter, :set_bugsnag_request_data) - end - - private - def set_bugsnag_request_data - Bugsnag.clear_request_data - Bugsnag.set_request_data(:rails2_request, request) - end - - def rescue_action_in_public_with_bugsnag(exception) - Bugsnag.auto_notify(exception) - - rescue_action_in_public_without_bugsnag(exception) - end - - def rescue_action_locally_with_bugsnag(exception) - Bugsnag.auto_notify(exception) - - rescue_action_locally_without_bugsnag(exception) - end - - module ClassMethods - - def self.extended(base) - base.singleton_class.class_eval do - alias_method_chain :filter_parameter_logging, :bugsnag - end - end - - # Rails 2 does parameter filtering via a controller configuration method - # that dynamically defines a method on the controller, so the configured - # parameters aren't easily accessible. Intercept these parameters as - # they're configured so that the Bugsnag configuration can take them - # into account. - # - def filter_parameter_logging_with_bugsnag(*filter_words, &block) - if filter_words.length > 0 - Bugsnag.configure do |config| - # Use the same regular expression that Rails parameter filtering uses. - config.params_filters << Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) - end - end - filter_parameter_logging_without_bugsnag(*filter_words, &block) - end - end - - end -end \ No newline at end of file diff --git a/lib/bugsnag/notification.rb b/lib/bugsnag/report.rb similarity index 51% rename from lib/bugsnag/notification.rb rename to lib/bugsnag/report.rb index 3fa4762c3..1493a16fe 100644 --- a/lib/bugsnag/notification.rb +++ b/lib/bugsnag/report.rb @@ -1,75 +1,45 @@ require "json" - -if RUBY_VERSION =~ /^1\.8/ - begin - require "iconv" - rescue LoadError - end -end - require "pathname" module Bugsnag - class Notification + class Report NOTIFIER_NAME = "Ruby Bugsnag Notifier" NOTIFIER_VERSION = Bugsnag::VERSION NOTIFIER_URL = "http://www.bugsnag.com" API_KEY_REGEX = /[0-9a-f]{32}/i - # e.g. "org/jruby/RubyKernel.java:1264:in `catch'" - BACKTRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/ - - # e.g. "org.jruby.Ruby.runScript(Ruby.java:807)" - JAVA_BACKTRACE_REGEX = /^(.*)\((.*)(?::([0-9]+))?\)$/ - MAX_EXCEPTIONS_TO_UNWRAP = 5 - SUPPORTED_SEVERITIES = ["error", "warning", "info"] - CURRENT_PAYLOAD_VERSION = "2" - attr_accessor :context attr_reader :user + attr_accessor :api_key attr_accessor :configuration + attr_accessor :context + attr_accessor :delivery_method + attr_accessor :grouping_hash attr_accessor :meta_data + attr_accessor :severity + class << self - def deliver_exception_payload(url, payload, configuration=Bugsnag.configuration, delivery_method=nil) + def deliver_exception_payload(payload, configuration, delivery_method) payload_string = ::JSON.dump(Bugsnag::Helpers.trim_if_needed(payload)) - delivery_method = delivery_method || configuration.delivery_method Bugsnag::Delivery[delivery_method].deliver(url, payload_string, configuration) end end - def initialize(exception, configuration, overrides = nil, request_data = nil) - @configuration = configuration - @overrides = Bugsnag::Helpers.flatten_meta_data(overrides) || {} + def initialize(exception, configuration, request_data = nil) + configuration = configuration @request_data = request_data - @meta_data = {} @user = {} @should_ignore = false - @severity = nil - @grouping_hash = nil - @delivery_method = nil - self.severity = @overrides[:severity] - @overrides.delete :severity - - if @overrides.key? :grouping_hash - self.grouping_hash = @overrides[:grouping_hash] - @overrides.delete :grouping_hash - end - - if @overrides.key? :api_key - self.api_key = @overrides[:api_key] - @overrides.delete :api_key - end - - if @overrides.key? :delivery_method - @delivery_method = @overrides[:delivery_method] - @overrides.delete :delivery_method - end + self.api_key = configuration.api_key + self.delivery_method = configuration.delivery_method + self.meta_data = {} + self.severity = "warning" # Unwrap exceptions @exceptions = [] @@ -131,50 +101,15 @@ def remove_tab(name) @meta_data.delete(name.to_sym) end - def user_id=(user_id) - @user[:id] = user_id - end - - def user_id - @user[:id] - end - def user=(user = {}) return unless user.is_a? Hash @user.merge!(user).delete_if{|k,v| v == nil} end - def severity=(severity) - @severity = severity if SUPPORTED_SEVERITIES.include?(severity) - end - - def severity - @severity || "warning" - end - - def payload_version - CURRENT_PAYLOAD_VERSION - end - - def grouping_hash=(grouping_hash) - @grouping_hash = grouping_hash - end - - def grouping_hash - @grouping_hash || nil - end - - def api_key=(api_key) - @api_key = api_key - end - - def api_key - @api_key ||= @configuration.api_key - end - # Deliver this notification to bugsnag.com Also runs through the middleware as required. def deliver - return unless @configuration.should_notify? + return unless configuration.should_notify? + return if ignore? # Check we have at least an api_key if api_key.nil? @@ -185,10 +120,7 @@ def deliver return end - # Warn if no release_stage is set - Bugsnag.warn "You should set your app's release_stage (see https://bugsnag.com/docs/notifiers/ruby#release_stage)." unless @configuration.release_stage - - @configuration.internal_middleware.run(self) + configuration.internal_middleware.run(self) exceptions.each do |exception| if exception.class.include?(Bugsnag::MetaData) @@ -201,28 +133,19 @@ def deliver end end - [:user_id, :context, :user, :grouping_hash].each do |symbol| - if @overrides[symbol] - self.send("#{symbol}=", @overrides[symbol]) - @overrides.delete symbol - end - end - # make meta_data available to public middleware @meta_data = generate_meta_data(@exceptions, @overrides) # Run the middleware here (including Bugsnag::Middleware::Callbacks) # at the end of the middleware stack, execute the actual notification delivery - @configuration.middleware.run(self) do + configuration.middleware.run(self) do # This supports self.ignore! for before_notify_callbacks. - return if @should_ignore + return if ignore? - # Build the endpoint url - endpoint = (@configuration.use_ssl ? "https://" : "http://") + @configuration.endpoint - Bugsnag.log("Notifying #{endpoint} of #{@exceptions.last.class}") + Bugsnag.log("Notifying #{configuration.endpoint} of #{exceptions.last.class}") # Deliver the payload - self.class.deliver_exception_payload(endpoint, build_exception_payload, @configuration, @delivery_method) + self.class.deliver_exception_payload(configuration.endpoint, build_exception_payload, configuration) end end @@ -231,25 +154,25 @@ def build_exception_payload # Build the payload's exception event payload_event = { :app => { - :version => @configuration.app_version, - :releaseStage => @configuration.release_stage, - :type => @configuration.app_type + :version => configuration.app_version, + :releaseStage => configuration.release_stage, + :type => configuration.app_type }, :context => self.context, :user => @user, - :payloadVersion => payload_version, + :payloadVersion => CURRENT_PAYLOAD_VERSION, :exceptions => exception_list, :severity => self.severity, :groupingHash => self.grouping_hash, } - payload_event[:device] = {:hostname => @configuration.hostname} if @configuration.hostname + payload_event[:device] = {:hostname => configuration.hostname} if configuration.hostname # cleanup character encodings 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.params_filters).clean_object(@meta_data) + payload_event[:metaData] = Bugsnag::Cleaner.new(configuration.params_filters).clean_object(@meta_data) payload_event.reject! {|k,v| v.nil? } # return the payload hash @@ -286,15 +209,15 @@ def ignore_exception_class? @exceptions.any? do |ex| ancestor_chain = ex.class.ancestors.select { |ancestor| ancestor.is_a?(Class) }.map { |ancestor| error_class(ancestor) }.to_set - @configuration.ignore_classes.any? do |to_ignore| + configuration.ignore_classes.any? do |to_ignore| to_ignore.is_a?(Proc) ? to_ignore.call(ex) : ancestor_chain.include?(to_ignore) end end end def ignore_user_agent? - if @configuration.request_data && @configuration.request_data[:rack_env] && (agent = @configuration.request_data[:rack_env]["HTTP_USER_AGENT"]) - @configuration.ignore_user_agents.any? do |to_ignore| + if configuration.request_data && configuration.request_data[:rack_env] && (agent = configuration.request_data[:rack_env]["HTTP_USER_AGENT"]) + configuration.ignore_user_agents.any? do |to_ignore| agent =~ to_ignore end end @@ -353,107 +276,5 @@ def error_class(exception) # which throw the error class instead of an instance (exception.is_a? Class) ? exception.name : exception.class.name end - - def stacktrace(backtrace) - backtrace = caller if !backtrace || backtrace.empty? - backtrace.map do |trace| - if trace.match(BACKTRACE_LINE_REGEX) - file, line_str, method = [$1, $2, $3] - elsif trace.match(JAVA_BACKTRACE_REGEX) - method, file, line_str = [$1, $2, $3] - end - - # Parse the stacktrace line - - # Skip stacktrace lines inside lib/bugsnag - next(nil) if file.nil? || file =~ %r{lib/bugsnag(/|\.rb)} - - # Expand relative paths - p = Pathname.new(file) - if p.relative? - file = p.realpath.to_s rescue file - end - - # Generate the stacktrace line hash - trace_hash = {} - trace_hash[:inProject] = true if in_project?(file) - trace_hash[:lineNumber] = line_str.to_i - - if @configuration.send_code - trace_hash[:code] = code(file, trace_hash[:lineNumber]) - end - - # Clean up the file path in the stacktrace - if defined?(Bugsnag.configuration.project_root) && Bugsnag.configuration.project_root.to_s != '' - file.sub!(/#{Bugsnag.configuration.project_root}\//, "") - end - - # Strip common gem path prefixes - if defined?(Gem) - file = Gem.path.inject(file) {|line, path| line.sub(/#{path}\//, "") } - end - - trace_hash[:file] = file - - # Add a method if we have it - trace_hash[:method] = method if method && (method =~ /^__bind/).nil? - - if trace_hash[:file] && !trace_hash[:file].empty? - trace_hash - else - nil - end - end.compact - end - - def in_project?(line) - return false if @configuration.vendor_paths && @configuration.vendor_paths.any? do |vendor_path| - if vendor_path.is_a?(String) - line.include?(vendor_path) - else - line =~ vendor_path - end - end - @configuration.project_root && line.start_with?(@configuration.project_root.to_s) - end - - def code(file, line_number, num_lines = 7) - code_hash = {} - - from_line = [line_number - num_lines, 1].max - - # don't try and open '(irb)' or '-e' - return unless File.exist?(file) - - # Populate code hash with line numbers and code lines - File.open(file) do |f| - current_line_number = 0 - f.each_line do |line| - current_line_number += 1 - - next if current_line_number < from_line - - code_hash[current_line_number] = line[0...200].rstrip - - break if code_hash.length >= ( num_lines * 1.5 ).ceil - end - end - - while code_hash.length > num_lines - last_line = code_hash.keys.max - first_line = code_hash.keys.min - - if (last_line - line_number) > (line_number - first_line) - code_hash.delete(last_line) - else - code_hash.delete(first_line) - end - end - - code_hash - rescue - Bugsnag.warn("Error fetching code: #{$!.inspect}") - nil - end end end diff --git a/lib/bugsnag/stacktrace.rb b/lib/bugsnag/stacktrace.rb new file mode 100644 index 000000000..03ae77a3e --- /dev/null +++ b/lib/bugsnag/stacktrace.rb @@ -0,0 +1,116 @@ +module Bugsnag + class Stacktrace + + # e.g. "org/jruby/RubyKernel.java:1264:in `catch'" + BACKTRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/ + + # e.g. "org.jruby.Ruby.runScript(Ruby.java:807)" + JAVA_BACKTRACE_REGEX = /^(.*)\((.*)(?::([0-9]+))?\)$/ + + def initialize(backtrace) + backtrace = caller if !backtrace || backtrace.empty? + backtrace.map do |trace| + if trace.match(BACKTRACE_LINE_REGEX) + file, line_str, method = [$1, $2, $3] + elsif trace.match(JAVA_BACKTRACE_REGEX) + method, file, line_str = [$1, $2, $3] + end + + # Parse the stacktrace line + + # Skip stacktrace lines inside lib/bugsnag + next(nil) if file.nil? || file =~ %r{lib/bugsnag(/|\.rb)} + + # Expand relative paths + p = Pathname.new(file) + if p.relative? + file = p.realpath.to_s rescue file + end + + # Generate the stacktrace line hash + trace_hash = {} + trace_hash[:inProject] = true if in_project?(file) + trace_hash[:lineNumber] = line_str.to_i + + if configuration.send_code + trace_hash[:code] = code(file, trace_hash[:lineNumber]) + end + + # Clean up the file path in the stacktrace + if defined?(Bugsnag.configuration.project_root) && Bugsnag.configuration.project_root.to_s != '' + file.sub!(/#{Bugsnag.configuration.project_root}\//, "") + end + + # Strip common gem path prefixes + if defined?(Gem) + file = Gem.path.inject(file) {|line, path| line.sub(/#{path}\//, "") } + end + + trace_hash[:file] = file + + # Add a method if we have it + trace_hash[:method] = method if method && (method =~ /^__bind/).nil? + + if trace_hash[:file] && !trace_hash[:file].empty? + trace_hash + else + nil + end + end.compact + end + + + + private + + def in_project?(line) + return false if configuration.vendor_paths && configuration.vendor_paths.any? do |vendor_path| + if vendor_path.is_a?(String) + line.include?(vendor_path) + else + line =~ vendor_path + end + end + configuration.project_root && line.start_with?(configuration.project_root.to_s) + end + + def code(file, line_number, num_lines = 7) + code_hash = {} + + from_line = [line_number - num_lines, 1].max + + # don't try and open '(irb)' or '-e' + return unless File.exist?(file) + + # Populate code hash with line numbers and code lines + File.open(file) do |f| + current_line_number = 0 + f.each_line do |line| + current_line_number += 1 + + next if current_line_number < from_line + + code_hash[current_line_number] = line[0...200].rstrip + + break if code_hash.length >= ( num_lines * 1.5 ).ceil + end + end + + while code_hash.length > num_lines + last_line = code_hash.keys.max + first_line = code_hash.keys.min + + if (last_line - line_number) > (line_number - first_line) + code_hash.delete(last_line) + else + code_hash.delete(first_line) + end + end + + code_hash + rescue + Bugsnag.warn("Error fetching code: #{$!.inspect}") + nil + end + end +end diff --git a/lib/bugsnag/tasks/bugsnag.cap b/lib/bugsnag/tasks/bugsnag.cap deleted file mode 100644 index 4a05a7ddd..000000000 --- a/lib/bugsnag/tasks/bugsnag.cap +++ /dev/null @@ -1,48 +0,0 @@ -namespace :load do - - task :defaults do - - set :bugsnag_default_hooks, ->{ true } - - end - -end - -namespace :deploy do - - before :starting, :bugsnag_hooks do - invoke 'bugsnag:add_default_hooks' if fetch(:bugsnag_default_hooks) - end - -end - -namespace :bugsnag do - - task :add_default_hooks do - after 'deploy:published', 'bugsnag:deploy' - end - - desc 'Notify Bugsnag that new production code has been deployed' - task :deploy do - run_locally do - begin - Bugsnag::Deploy.notify({ - :api_key => fetch(:bugsnag_api_key, ENV["BUGSNAG_API_KEY"]), - :release_stage => fetch(:bugsnag_env) || ENV["BUGSNAG_RELEASE_STAGE"] || fetch(:rails_env) || fetch(:stage) || "production", - :revision => fetch(:current_revision, ENV["BUGSNAG_REVISION"]), - :repository => fetch(:repo_url, ENV["BUGSNAG_REPOSITORY"]), - :branch => fetch(:branch, ENV["BUGSNAG_BRANCH"]), - :app_version => fetch(:app_version, ENV["BUGSNAG_APP_VERSION"]), - :endpoint => fetch(:bugsnag_endpoint, Bugsnag::Configuration::DEFAULT_ENDPOINT), - :use_ssl => fetch(:bugsnag_use_ssl, true) - }) - rescue - error "Bugsnag deploy notification failed, #{$!.inspect}" - end - - info 'Bugsnag deploy notification complete.' - end - end - -end -# vi:ft=ruby diff --git a/lib/bugsnag/tasks/bugsnag.rake b/lib/bugsnag/tasks/bugsnag.rake index bad09c133..25618bef2 100644 --- a/lib/bugsnag/tasks/bugsnag.rake +++ b/lib/bugsnag/tasks/bugsnag.rake @@ -1,27 +1,6 @@ require "bugsnag" namespace :bugsnag do - desc "Notify Bugsnag of a new deploy." - task :deploy do - api_key = ENV["BUGSNAG_API_KEY"] - release_stage = ENV["BUGSNAG_RELEASE_STAGE"] - app_version = ENV["BUGSNAG_APP_VERSION"] - revision = ENV["BUGSNAG_REVISION"] - repository = ENV["BUGSNAG_REPOSITORY"] - branch = ENV["BUGSNAG_BRANCH"] - - Rake::Task["load"].invoke unless api_key - - Bugsnag::Deploy.notify({ - :api_key => api_key, - :release_stage => release_stage, - :app_version => app_version, - :revision => revision, - :repository => repository, - :branch => branch - }) - end - desc "Send a test exception to Bugsnag." task :test_exception => :load do begin @@ -30,55 +9,6 @@ namespace :bugsnag do Bugsnag.notify(e, {:context => "rake#test_exception"}) end end - - desc "Show the bugsnag middleware stack" - task :middleware => :load do - Bugsnag.configuration.middleware.each {|m| puts m.to_s} - end - - namespace :heroku do - desc "Add a heroku deploy hook to notify Bugsnag of deploys" - task :add_deploy_hook => :load do - # Wrapper to run command safely even in bundler - run_command = lambda { |command| - defined?(Bundler.with_clean_env) ? Bundler.with_clean_env { `#{command}` } : `#{command}` - } - - # Fetch heroku config settings - config_command = "heroku config --shell" - config_command += " --app #{ENV["HEROKU_APP"]}" if ENV["HEROKU_APP"] - heroku_env = run_command.call(config_command).split(/[\n\r]/).each_with_object({}) do |c, obj| - k,v = c.split("=") - obj[k] = (v.nil? || v.strip.empty?) ? nil : v - end - - # Check for Bugsnag API key (required) - api_key = heroku_env["BUGSNAG_API_KEY"] || Bugsnag.configuration.api_key || ENV["BUGSNAG_API_KEY"] - unless api_key - puts "Error: No API key found, have you run 'heroku config:set BUGSNAG_API_KEY=your-api-key'?" - next - end - - # Build the request, making use of deploy hook variables - # (https://devcenter.heroku.com/articles/deploy-hooks#customizing-messages) - params = { - :apiKey => api_key, - :branch => "master", - :revision => "{{head_long}}", - :releaseStage => heroku_env["RAILS_ENV"] || ENV["RAILS_ENV"] || "production" - } - repo = `git config --get remote.origin.url`.strip - params[:repository] = repo unless repo.empty? - - # Add the hook - url = "https://notify.bugsnag.com/deploy?" + params.map {|k,v| "#{k}=#{v}"}.join("&") - command = "heroku addons:add deployhooks:http --url=\"#{url}\"" - command += " --app #{ENV["HEROKU_APP"]}" if ENV["HEROKU_APP"] - - puts "$ #{command}" - run_command.call(command) - end - end end task :load do From 9f504dcc5a87d9b6d77fecb2b6c3014109028d99 Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 13:03:50 -0700 Subject: [PATCH 002/109] Progress --- lib/bugsnag.rb | 50 +++++++++++++++++++++------- lib/bugsnag/configuration.rb | 46 ++++++++++++++++++------- lib/bugsnag/delivery/synchronous.rb | 10 +++--- lib/bugsnag/delivery/thread_queue.rb | 4 ++- lib/bugsnag/helpers.rb | 2 +- lib/bugsnag/middleware_stack.rb | 6 ++-- lib/bugsnag/report.rb | 46 ++----------------------- 7 files changed, 85 insertions(+), 79 deletions(-) diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index 00113521f..0dbe74835 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -35,21 +35,47 @@ def configure end # Explicitly notify of an exception - def notify(exception, &block) - #Build notification - notification = Notification.new(exception, configuration) + def notify(exception, auto_notify=false, &block) + return unless configuration.valid_api_key? && configuration.should_notify_release_stage? - #Check if config allows send - if configuration. - #Run internal middleware - #Run internal block? - #Run users middleware - #Run users block - #Deliver + report = Report.new(exception, configuration) + return if report.ignore? - yield(notification) if block_given? + # Run internal middleware + configuration.internal_middleware.run(report) + return if report.ignore? - notification.deliver + # If this is an auto_notify we yield the block before the user's middleware is run + # so that they get to see the final copy of the report there + yield(report) if block_given? && auto_notify + return if report.ignore? + + # Apply the user's information attached to the exceptions + exceptions.each do |exception| + if exception.class.include?(Bugsnag::MetaData) + if exception.bugsnag_user_id.is_a?(String) + self.user_id = exception.bugsnag_user_id + end + if exception.bugsnag_context.is_a?(String) + self.context = exception.bugsnag_context + end + end + end + + # Run users middleware + configuration.middleware.run(report) do + return if report.ignore? + + # If this is not an auto_notify then the block was provided by the user. This should be the last + # block that is run as it is the users "most specific" block. + yield(report) if block_given? && !auto_notify + return if report.ignore? + + # Deliver + configuration.info("Notifying #{configuration.endpoint} of #{exceptions.last.class}") + payload_string = ::JSON.dump(Bugsnag::Helpers.trim_if_needed(report.as_json)) + Bugsnag::Delivery[configuration.delivery_method].deliver(configuration.endpoint, payload_string, configuration) + end end # Configuration getters diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index eca6fa03c..2a1a79648 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -9,21 +9,17 @@ class Configuration attr_accessor :release_stage attr_accessor :notify_release_stages attr_accessor :auto_notify - attr_accessor :use_ssl attr_accessor :ca_file attr_accessor :send_environment attr_accessor :send_code attr_accessor :project_root attr_accessor :app_version attr_accessor :app_type - attr_accessor :params_filters - attr_accessor :ignore_user_agents + attr_accessor :meta_data_filters attr_accessor :endpoint attr_accessor :logger attr_accessor :middleware attr_accessor :internal_middleware - attr_accessor :delay_with_resque - attr_accessor :debug attr_accessor :proxy_host attr_accessor :proxy_port attr_accessor :proxy_user @@ -31,7 +27,7 @@ class Configuration attr_accessor :timeout attr_accessor :hostname attr_accessor :delivery_method - attr_writer :ignore_classes + attr_accessor :ignore_classes LOG_PREFIX = "** [Bugsnag] " @@ -65,7 +61,7 @@ def initialize # Set up logging self.logger = Logger.new(STDOUT) - self.logger.level = Logger::WARN + self.logger.level = Logger::INFO # Configure the bugsnag middleware stack self.internal_middleware = Bugsnag::MiddlewareStack.new @@ -74,13 +70,25 @@ def initialize self.middleware.use Bugsnag::Middleware::Callbacks end - # Accept both String and Class instances as an ignored class - def ignore_classes - @mutex.synchronize { @ignore_classes.map! { |klass| klass.is_a?(Class) ? klass.name : klass } } + def should_notify_release_stage? + if @release_stage.nil? || @notify_release_stages.nil? || @notify_release_stages.include?(@release_stage) + return true + else + warn "Not notifying in release stage #{@release_stage}" + return false + end end - def should_notify? - @release_stage.nil? || @notify_release_stages.nil? || @notify_release_stages.include?(@release_stage) + def valid_api_key? + if api_key.nil? + warn "No API key configured, couldn't notify" + return false + elsif api_key !~ API_KEY_REGEX + warn "Your API key (#{api_key}) is not valid, couldn't notify" + return false + end + + return true end def request_data @@ -99,6 +107,20 @@ def clear_request_data Thread.current[THREAD_LOCAL_NAME] = nil end + def info(message) + configuration.logger.info("#{LOG_PREFIX}#{message}") + end + + # Warning logger + def warn(message) + configuration.logger.warn("#{LOG_PREFIX}#{message}") + end + + # Debug logger + def debug(message) + configuration.logger.debug("#{LOG_PREFIX}#{message}") + end + private def default_hostname diff --git a/lib/bugsnag/delivery/synchronous.rb b/lib/bugsnag/delivery/synchronous.rb index f8d990fcf..9c7488195 100644 --- a/lib/bugsnag/delivery/synchronous.rb +++ b/lib/bugsnag/delivery/synchronous.rb @@ -10,13 +10,13 @@ class << self def deliver(url, body, configuration) begin response = request(url, body, configuration) - Bugsnag.debug("Notification to #{url} finished, response was #{response.code}, payload was #{body}") + configuration.debug("Notification to #{url} finished, response was #{response.code}, payload was #{body}") rescue StandardError => e # KLUDGE: Since we don't re-raise http exceptions, this breaks rspec raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError" - Bugsnag.warn("Notification to #{url} failed, #{e.inspect}") - Bugsnag.warn(e.backtrace) + configuration.warn("Notification to #{url} failed, #{e.inspect}") + configuration.warn(e.backtrace) end end @@ -30,8 +30,6 @@ def request(url, body, configuration) if uri.scheme == "https" http.use_ssl = true - # the default in 1.9+, but required for 1.8 - http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.ca_file = configuration.ca_file if configuration.ca_file end @@ -48,4 +46,4 @@ def path(uri) end end -Bugsnag::Delivery.register(:synchronous, Bugsnag::Delivery::Synchronous) \ No newline at end of file +Bugsnag::Delivery.register(:synchronous, Bugsnag::Delivery::Synchronous) diff --git a/lib/bugsnag/delivery/thread_queue.rb b/lib/bugsnag/delivery/thread_queue.rb index 18bfd5031..9927d0608 100644 --- a/lib/bugsnag/delivery/thread_queue.rb +++ b/lib/bugsnag/delivery/thread_queue.rb @@ -9,6 +9,8 @@ class ThreadQueue < Synchronous class << self def deliver(url, body, configuration) + @configuration = configuration + start_once! if @queue.length > MAX_OUTSTANDING_REQUESTS @@ -38,7 +40,7 @@ def start_once! end at_exit do - Bugsnag.warn("Waiting for #{@queue.length} outstanding request(s)") unless @queue.empty? + @configuration.warn("Waiting for #{@queue.length} outstanding request(s)") unless @queue.empty? @queue.push STOP worker_thread.join end diff --git a/lib/bugsnag/helpers.rb b/lib/bugsnag/helpers.rb index 54fcfbaf6..46b9ae322 100644 --- a/lib/bugsnag/helpers.rb +++ b/lib/bugsnag/helpers.rb @@ -6,7 +6,7 @@ module Bugsnag module Helpers MAX_STRING_LENGTH = 3072 - MAX_PAYLOAD_LENGTH = 128000 + MAX_PAYLOAD_LENGTH = 256000 MAX_ARRAY_LENGTH = 40 RAW_DATA_TYPES = [Numeric, TrueClass, FalseClass] diff --git a/lib/bugsnag/middleware_stack.rb b/lib/bugsnag/middleware_stack.rb index 0f788f744..ff76df3c5 100644 --- a/lib/bugsnag/middleware_stack.rb +++ b/lib/bugsnag/middleware_stack.rb @@ -63,7 +63,7 @@ def method_missing(method, *args, &block) end # Runs the middleware stack and calls - def run(notification) + def run(report) # The final lambda is the termination of the middleware stack. It calls deliver on the notification lambda_has_run = false notify_lambda = lambda do |notif| @@ -73,7 +73,7 @@ def run(notification) begin # We reverse them, so we can call "call" on the first middleware - middleware_procs.reverse.inject(notify_lambda) { |n,e| e.call(n) }.call(notification) + middleware_procs.reverse.inject(notify_lambda) { |n,e| e.call(n) }.call(report) rescue StandardError => e # KLUDGE: Since we don't re-raise middleware exceptions, this breaks rspec raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError" @@ -85,7 +85,7 @@ def run(notification) end # Ensure that the deliver has been performed, and no middleware has botched it - notify_lambda.call(notification) unless lambda_has_run + notify_lambda.call(report) unless lambda_has_run end private diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 1493a16fe..bdef28dc8 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -22,14 +22,6 @@ class Report attr_accessor :meta_data attr_accessor :severity - - class << self - def deliver_exception_payload(payload, configuration, delivery_method) - payload_string = ::JSON.dump(Bugsnag::Helpers.trim_if_needed(payload)) - Bugsnag::Delivery[delivery_method].deliver(url, payload_string, configuration) - end - end - def initialize(exception, configuration, request_data = nil) configuration = configuration @request_data = request_data @@ -108,49 +100,15 @@ def user=(user = {}) # Deliver this notification to bugsnag.com Also runs through the middleware as required. def deliver - return unless configuration.should_notify? - return if ignore? - # Check we have at least an api_key - if api_key.nil? - Bugsnag.warn "No API key configured, couldn't notify" - return - elsif api_key !~ API_KEY_REGEX - Bugsnag.warn "Your API key (#{api_key}) is not valid, couldn't notify" - return - end - - configuration.internal_middleware.run(self) - - exceptions.each do |exception| - if exception.class.include?(Bugsnag::MetaData) - if exception.bugsnag_user_id.is_a?(String) - self.user_id = exception.bugsnag_user_id - end - if exception.bugsnag_context.is_a?(String) - self.context = exception.bugsnag_context - end - end - end + # make meta_data available to public middleware @meta_data = generate_meta_data(@exceptions, @overrides) - - # Run the middleware here (including Bugsnag::Middleware::Callbacks) - # at the end of the middleware stack, execute the actual notification delivery - configuration.middleware.run(self) do - # This supports self.ignore! for before_notify_callbacks. - return if ignore? - - Bugsnag.log("Notifying #{configuration.endpoint} of #{exceptions.last.class}") - - # Deliver the payload - self.class.deliver_exception_payload(configuration.endpoint, build_exception_payload, configuration) - end end # Build an exception payload - def build_exception_payload + def as_json # Build the payload's exception event payload_event = { :app => { From 2c8bae6a8991923e43c69bf6fea0ecf05c6a6335 Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 13:12:26 -0700 Subject: [PATCH 003/109] Progress --- lib/bugsnag.rb | 12 ---------- lib/bugsnag/middleware/callbacks.rb | 8 +++---- lib/bugsnag/middleware/exception_meta_data.rb | 23 ++++++++++++++++++ lib/bugsnag/middleware/mailman.rb | 8 +++---- lib/bugsnag/middleware/rack_request.rb | 24 +++++++++---------- lib/bugsnag/middleware/rails3_request.rb | 20 ++++++++-------- lib/bugsnag/middleware/rake.rb | 10 ++++---- lib/bugsnag/middleware/sidekiq.rb | 10 ++++---- lib/bugsnag/middleware/warden_user.rb | 12 +++++----- 9 files changed, 69 insertions(+), 58 deletions(-) create mode 100644 lib/bugsnag/middleware/exception_meta_data.rb diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index 0dbe74835..e73bd52bb 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -50,18 +50,6 @@ def notify(exception, auto_notify=false, &block) yield(report) if block_given? && auto_notify return if report.ignore? - # Apply the user's information attached to the exceptions - exceptions.each do |exception| - if exception.class.include?(Bugsnag::MetaData) - if exception.bugsnag_user_id.is_a?(String) - self.user_id = exception.bugsnag_user_id - end - if exception.bugsnag_context.is_a?(String) - self.context = exception.bugsnag_context - end - end - end - # Run users middleware configuration.middleware.run(report) do return if report.ignore? diff --git a/lib/bugsnag/middleware/callbacks.rb b/lib/bugsnag/middleware/callbacks.rb index 9bde6b5a3..be7c87d93 100644 --- a/lib/bugsnag/middleware/callbacks.rb +++ b/lib/bugsnag/middleware/callbacks.rb @@ -4,12 +4,12 @@ def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - if notification.request_data[:before_callbacks] - notification.request_data[:before_callbacks].each {|c| c.call(*[notification][0...c.arity]) } + def call(report) + if report.request_data[:before_callbacks] + report.request_data[:before_callbacks].each {|c| c.call(*[report][0...c.arity]) } end - @bugsnag.call(notification) + @bugsnag.call(report) end end end diff --git a/lib/bugsnag/middleware/exception_meta_data.rb b/lib/bugsnag/middleware/exception_meta_data.rb new file mode 100644 index 000000000..709eb826d --- /dev/null +++ b/lib/bugsnag/middleware/exception_meta_data.rb @@ -0,0 +1,23 @@ +module Bugsnag::Middleware + class ExceptionMetaData + def initialize(bugsnag) + @bugsnag = bugsnag + end + + def call(report) + # Apply the user's information attached to the exceptions + report.exceptions.each do |exception| + if exception.class.include?(Bugsnag::MetaData) + if exception.bugsnag_user_id.is_a?(String) + report.user = {id: exception.bugsnag_user_id} + end + if exception.bugsnag_context.is_a?(String) + report.context = exception.bugsnag_context + end + end + end + + @bugsnag.call(report) + end + end +end diff --git a/lib/bugsnag/middleware/mailman.rb b/lib/bugsnag/middleware/mailman.rb index 64d4c814e..65d4ce7db 100644 --- a/lib/bugsnag/middleware/mailman.rb +++ b/lib/bugsnag/middleware/mailman.rb @@ -4,10 +4,10 @@ def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - mailman_msg = notification.request_data[:mailman_msg] - notification.add_tab(:mailman, {"message" => mailman_msg}) if mailman_msg - @bugsnag.call(notification) + def call(report) + mailman_msg = report.request_data[:mailman_msg] + report.add_tab(:mailman, {"message" => mailman_msg}) if mailman_msg + @bugsnag.call(report) end end end diff --git a/lib/bugsnag/middleware/rack_request.rb b/lib/bugsnag/middleware/rack_request.rb index 41cb045aa..8d595314b 100644 --- a/lib/bugsnag/middleware/rack_request.rb +++ b/lib/bugsnag/middleware/rack_request.rb @@ -4,9 +4,9 @@ def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - if notification.request_data[:rack_env] - env = notification.request_data[:rack_env] + def call(report) + if report.request_data[:rack_env] + env = report.request_data[:rack_env] request = ::Rack::Request.new(env) @@ -15,10 +15,10 @@ def call(notification) session = env["rack.session"] # Set the context - notification.context = "#{request.request_method} #{request.path}" + report.context = "#{request.request_method} #{request.path}" # Set a sensible default for user_id - notification.user_id = request.ip + report.user_id = request.ip # Build the clean url (hide the port if it is obvious) url = "#{request.scheme}://#{request.host}" @@ -26,7 +26,7 @@ def call(notification) # If app is passed a bad URL, this code will crash attempting to clean it begin - url << Bugsnag::Cleaner.new(notification.configuration.params_filters).clean_url(request.fullpath) + url << Bugsnag::Cleaner.new(report.configuration.params_filters).clean_url(request.fullpath) rescue StandardError => stde Bugsnag.log "RackRequest - Rescued error while cleaning request.fullpath: #{stde}" end @@ -46,7 +46,7 @@ def call(notification) end # Add a request tab - notification.add_tab(:request, { + report.add_tab(:request, { :url => url, :httpMethod => request.request_method, :params => params.to_hash, @@ -56,23 +56,23 @@ def call(notification) }) # Add an environment tab - if notification.configuration.send_environment - notification.add_tab(:environment, env) + if report.configuration.send_environment + report.add_tab(:environment, env) end # Add a session tab if session if session.is_a?(Hash) # Rails 3 - notification.add_tab(:session, session) + report.add_tab(:session, session) elsif session.respond_to?(:to_hash) # Rails 4 - notification.add_tab(:session, session.to_hash) + report.add_tab(:session, session.to_hash) end end end - @bugsnag.call(notification) + @bugsnag.call(report) end end end diff --git a/lib/bugsnag/middleware/rails3_request.rb b/lib/bugsnag/middleware/rails3_request.rb index c0b18d487..3583a2c85 100644 --- a/lib/bugsnag/middleware/rails3_request.rb +++ b/lib/bugsnag/middleware/rails3_request.rb @@ -4,39 +4,39 @@ def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - if notification.request_data[:rack_env] - env = notification.request_data[:rack_env] + def call(report) + if report.request_data[:rack_env] + env = report.request_data[:rack_env] params = env["action_dispatch.request.parameters"] if params # Set the context - notification.context = "#{params[:controller]}##{params[:action]}" + report.context = "#{params[:controller]}##{params[:action]}" # Augment the request tab - notification.add_tab(:request, { + report.add_tab(:request, { :railsAction => "#{params[:controller]}##{params[:action]}", :params => params }) end # Use action_dispatch.remote_ip for IP address fields and send request id - notification.add_tab(:request, { + report.add_tab(:request, { :clientIp => env["action_dispatch.remote_ip"], :requestId => env["action_dispatch.request_id"] }) - notification.user_id = env["action_dispatch.remote_ip"] + report.user_id = env["action_dispatch.remote_ip"] # Add the rails version - if notification.configuration.send_environment - notification.add_tab(:environment, { + if report.configuration.send_environment + report.add_tab(:environment, { :railsVersion => Rails::VERSION::STRING }) end end - @bugsnag.call(notification) + @bugsnag.call(report) end end end diff --git a/lib/bugsnag/middleware/rake.rb b/lib/bugsnag/middleware/rake.rb index 2ce641fa3..74c3d68f2 100644 --- a/lib/bugsnag/middleware/rake.rb +++ b/lib/bugsnag/middleware/rake.rb @@ -4,20 +4,20 @@ def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - task = notification.request_data[:bugsnag_running_task] + def call(report) + task = report.request_data[:bugsnag_running_task] if task - notification.add_tab(:rake_task, { + report.add_tab(:rake_task, { :name => task.name, :description => task.full_comment, :arguments => task.arg_description }) - notification.context ||= task.name + report.context ||= task.name end - @bugsnag.call(notification) + @bugsnag.call(report) end end end diff --git a/lib/bugsnag/middleware/sidekiq.rb b/lib/bugsnag/middleware/sidekiq.rb index 451113703..f5421f21d 100644 --- a/lib/bugsnag/middleware/sidekiq.rb +++ b/lib/bugsnag/middleware/sidekiq.rb @@ -4,13 +4,13 @@ def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - sidekiq = notification.request_data[:sidekiq] + def call(report) + sidekiq = report.request_data[:sidekiq] if sidekiq - notification.add_tab(:sidekiq, sidekiq) - notification.context ||= "#{sidekiq[:msg]['wrapper'] || sidekiq[:msg]['class']}@#{sidekiq[:msg]['queue']}" + report.add_tab(:sidekiq, sidekiq) + report.context ||= "#{sidekiq[:msg]['wrapper'] || sidekiq[:msg]['class']}@#{sidekiq[:msg]['queue']}" end - @bugsnag.call(notification) + @bugsnag.call(report) end end end diff --git a/lib/bugsnag/middleware/warden_user.rb b/lib/bugsnag/middleware/warden_user.rb index 4fd4d0633..1c4ed59b8 100644 --- a/lib/bugsnag/middleware/warden_user.rb +++ b/lib/bugsnag/middleware/warden_user.rb @@ -7,9 +7,9 @@ def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - if notification.request_data[:rack_env] && notification.request_data[:rack_env]["warden"] - env = notification.request_data[:rack_env] + def call(report) + if report.request_data[:rack_env] && report.request_data[:rack_env]["warden"] + env = report.request_data[:rack_env] session = env["rack.session"] || {} # Find all warden user scopes @@ -29,11 +29,11 @@ def call(notification) end # We merge the first warden scope down, so that it is the main "user" for the request - notification.user = user unless user.empty? + report.user = user unless user.empty? end end - @bugsnag.call(notification) + @bugsnag.call(report) end end -end \ No newline at end of file +end From 4e398c8ba242824dfd936a7af862858a552df562 Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 15:10:54 -0700 Subject: [PATCH 004/109] More progress --- lib/bugsnag.rb | 9 +- lib/bugsnag/configuration.rb | 3 + lib/bugsnag/delivery/thread_queue.rb | 2 +- lib/bugsnag/helpers.rb | 19 +- lib/bugsnag/integrations/rack.rb | 4 +- lib/bugsnag/middleware/exception_meta_data.rb | 3 + lib/bugsnag/middleware/ignore_error_class.rb | 21 +++ lib/bugsnag/middleware/rack_request.rb | 2 +- lib/bugsnag/middleware_stack.rb | 4 +- lib/bugsnag/report.rb | 171 ++++++++---------- lib/bugsnag/stacktrace.rb | 25 ++- rails/init.rb | 7 - 12 files changed, 132 insertions(+), 138 deletions(-) create mode 100644 lib/bugsnag/middleware/ignore_error_class.rb delete mode 100644 rails/init.rb diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index e73bd52bb..ae8b71d25 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -41,13 +41,12 @@ def notify(exception, auto_notify=false, &block) report = Report.new(exception, configuration) return if report.ignore? - # Run internal middleware - configuration.internal_middleware.run(report) + # If this is an auto_notify we yield the block before the any middleware is run + yield(report) if block_given? && auto_notify return if report.ignore? - # If this is an auto_notify we yield the block before the user's middleware is run - # so that they get to see the final copy of the report there - yield(report) if block_given? && auto_notify + # Run internal middleware + configuration.internal_middleware.run(report) return if report.ignore? # Run users middleware diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index f41b6c4fc..8c5037de3 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -29,6 +29,7 @@ class Configuration attr_accessor :delivery_method attr_accessor :ignore_classes + API_KEY_REGEX = /[0-9a-f]{32}/i THREAD_LOCAL_NAME = "bugsnag_req_data" DEFAULT_PARAMS_FILTERS = [ @@ -66,6 +67,8 @@ def initialize # Configure the bugsnag middleware stack self.internal_middleware = Bugsnag::MiddlewareStack.new + self.internal_middleware.use Bugsnag::Middleware::ExceptionMetaData + self.internal_middleware.use Bugsnag::Middleware::IgnoreErrorClass self.middleware = Bugsnag::MiddlewareStack.new self.middleware.use Bugsnag::Middleware::Callbacks diff --git a/lib/bugsnag/delivery/thread_queue.rb b/lib/bugsnag/delivery/thread_queue.rb index 9927d0608..52b65f697 100644 --- a/lib/bugsnag/delivery/thread_queue.rb +++ b/lib/bugsnag/delivery/thread_queue.rb @@ -14,7 +14,7 @@ def deliver(url, body, configuration) start_once! if @queue.length > MAX_OUTSTANDING_REQUESTS - Bugsnag.warn("Dropping notification, #{@queue.length} outstanding requests") + @configuration.warn("Dropping notification, #{@queue.length} outstanding requests") return end diff --git a/lib/bugsnag/helpers.rb b/lib/bugsnag/helpers.rb index 46b9ae322..d61a769a3 100644 --- a/lib/bugsnag/helpers.rb +++ b/lib/bugsnag/helpers.rb @@ -1,6 +1,6 @@ require 'uri' -require 'set' unless defined?(Set) -require 'json' unless defined?(JSON) +require 'set' +require 'json' module Bugsnag @@ -22,16 +22,9 @@ def self.trim_if_needed(value) remove_metadata_from_events(reduced_value) end - def self.flatten_meta_data(overrides) - return nil unless overrides + private - meta_data = overrides.delete(:meta_data) - if meta_data.is_a?(Hash) - overrides.merge(meta_data) - else - overrides - end - end + TRUNCATION_INFO = '[TRUNCATED]' # Check if a value is a raw type which should not be trimmed, truncated # or converted to a string @@ -39,10 +32,6 @@ def self.is_json_raw_type?(value) RAW_DATA_TYPES.detect {|klass| value.is_a?(klass)} != nil end - private - - TRUNCATION_INFO = '[TRUNCATED]' - # Shorten array until it fits within the payload size limit when serialized def self.truncate_array(array) return [] unless array.respond_to?(:slice) diff --git a/lib/bugsnag/integrations/rack.rb b/lib/bugsnag/integrations/rack.rb index a2a52a37f..6027de827 100644 --- a/lib/bugsnag/integrations/rack.rb +++ b/lib/bugsnag/integrations/rack.rb @@ -13,7 +13,7 @@ def initialize(app) if defined?(settings) config.project_root = settings.root else - Bugsnag.warn("You should set your app's project_root (see https://bugsnag.com/docs/notifiers/ruby#project_root).") + config.warn("You should set your app's project_root (see https://bugsnag.com/docs/notifiers/ruby#project_root).") end end @@ -21,7 +21,7 @@ def initialize(app) config.middleware.insert_before([Bugsnag::Middleware::Rails3Request,Bugsnag::Middleware::Callbacks], Bugsnag::Middleware::RackRequest) if defined?(::Rack) config.middleware.insert_before(Bugsnag::Middleware::Callbacks, Bugsnag::Middleware::WardenUser) if defined?(Warden) - Bugsnag.configuration.app_type ||= "rack" + config.app_type ||= "rack" end end diff --git a/lib/bugsnag/middleware/exception_meta_data.rb b/lib/bugsnag/middleware/exception_meta_data.rb index 34f44de92..618c1d358 100644 --- a/lib/bugsnag/middleware/exception_meta_data.rb +++ b/lib/bugsnag/middleware/exception_meta_data.rb @@ -11,12 +11,15 @@ def call(report) if exception.bugsnag_user_id.is_a?(String) report.user = {id: exception.bugsnag_user_id} end + if exception.bugsnag_context.is_a?(String) report.context = exception.bugsnag_context end + if exception.bugsnag_grouping_hash.is_a?(String) report.grouping_hash = exception.bugsnag_grouping_hash end + if exception.respond_to?(:bugsnag_meta_data) && exception.bugsnag_meta_data exception.bugsnag_meta_data.each do |key, value| report.add_to_tab key, value diff --git a/lib/bugsnag/middleware/ignore_error_class.rb b/lib/bugsnag/middleware/ignore_error_class.rb new file mode 100644 index 000000000..8f86b1884 --- /dev/null +++ b/lib/bugsnag/middleware/ignore_error_class.rb @@ -0,0 +1,21 @@ +module Bugsnag::Middleware + class IgnoreErrorClass + def initialize(bugsnag) + @bugsnag = bugsnag + end + + def call(report) + ignore_error_class = self.raw_exceptions.any? do |ex| + ancestor_chain = ex.class.ancestors.select { |ancestor| ancestor.is_a?(Class) }.to_set + + report.configuration.ignore_classes.any? do |to_ignore| + to_ignore.is_a?(Proc) ? to_ignore.call(ex) : ancestor_chain.include?(to_ignore) + end + end + + report.ignore! if ignore_error_class + + @bugsnag.call(report) + end + end +end diff --git a/lib/bugsnag/middleware/rack_request.rb b/lib/bugsnag/middleware/rack_request.rb index 8d595314b..f9f56f047 100644 --- a/lib/bugsnag/middleware/rack_request.rb +++ b/lib/bugsnag/middleware/rack_request.rb @@ -28,7 +28,7 @@ def call(report) begin url << Bugsnag::Cleaner.new(report.configuration.params_filters).clean_url(request.fullpath) rescue StandardError => stde - Bugsnag.log "RackRequest - Rescued error while cleaning request.fullpath: #{stde}" + Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.fullpath: #{stde}" end headers = {} diff --git a/lib/bugsnag/middleware_stack.rb b/lib/bugsnag/middleware_stack.rb index ff76df3c5..61bec481e 100644 --- a/lib/bugsnag/middleware_stack.rb +++ b/lib/bugsnag/middleware_stack.rb @@ -80,8 +80,8 @@ def run(report) # We dont notify, as we dont want to loop forever in the case of really broken middleware, we will # still send this notify - Bugsnag.warn "Bugsnag middleware error: #{e}" - Bugsnag.log "Middleware error stacktrace: #{e.backtrace.inspect}" + Bugsnag.configuration.warn "Bugsnag middleware error: #{e}" + Bugsnag.configuration.warn "Middleware error stacktrace: #{e.backtrace.inspect}" end # Ensure that the deliver has been performed, and no middleware has botched it diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 7b3553f75..2f0f096d0 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -1,5 +1,6 @@ require "json" require "pathname" +require "bugsnag/stacktrace" module Bugsnag class Report @@ -7,63 +8,42 @@ class Report NOTIFIER_VERSION = Bugsnag::VERSION NOTIFIER_URL = "http://www.bugsnag.com" - API_KEY_REGEX = /[0-9a-f]{32}/i - MAX_EXCEPTIONS_TO_UNWRAP = 5 CURRENT_PAYLOAD_VERSION = "2" - attr_reader :user attr_accessor :api_key + attr_accessor :app_type + attr_accessor :app_version attr_accessor :configuration attr_accessor :context attr_accessor :delivery_method + attr_accessor :exceptions + attr_accessor :hostname attr_accessor :grouping_hash attr_accessor :meta_data + attr_accessor :raw_exceptions + attr_accessor :release_stage attr_accessor :severity + attr_accessor :user - def initialize(exception, configuration) - @user = {} + def initialize(exception, passed_configuration) @should_ignore = false + self.configuration = passed_configuration + + self.raw_exceptions = generate_raw_exceptions(exception) + self.exceptions = generate_exception_list + self.api_key = configuration.api_key - self.configuration = configuration + self.app_type = configuration.app_type + self.app_version = configuration.app_version self.delivery_method = configuration.delivery_method + self.hostname = configuration.hostname self.meta_data = {} + self.release_stage = configuration.release_stage self.severity = "warning" - - # Unwrap exceptions - @exceptions = [] - - ex = exception - while ex != nil && !@exceptions.include?(ex) && @exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP - - unless ex.is_a? Exception - if ex.respond_to?(:to_exception) - ex = ex.to_exception - elsif ex.respond_to?(:exception) - ex = ex.exception - end - end - - unless ex.is_a?(Exception) || (defined?(Java::JavaLang::Throwable) && ex.is_a?(Java::JavaLang::Throwable)) - Bugsnag.warn("Converting non-Exception to RuntimeError: #{ex.inspect}") - ex = RuntimeError.new(ex.to_s) - ex.set_backtrace caller - end - - @exceptions << ex - - if ex.respond_to?(:cause) && ex.cause - ex = ex.cause - elsif ex.respond_to?(:continued_exception) && ex.continued_exception - ex = ex.continued_exception - elsif ex.respond_to?(:original_exception) && ex.original_exception - ex = ex.original_exception - else - ex = nil - end - end + self.user = {} end # Add a new tab to this notification @@ -71,11 +51,10 @@ def add_tab(name, value) return if name.nil? if value.is_a? Hash - @meta_data[name.to_sym] ||= {} - @meta_data[name.to_sym].merge! value + meta_data[name.to_s] ||= {} + meta_data[name.to_s].merge! value else - self.add_custom_data(name, value) - Bugsnag.warn "Adding a tab requires a hash, adding to custom tab instead (name=#{name})" + meta_data["custom"][name.to_s] = value end end @@ -83,38 +62,34 @@ def add_tab(name, value) def remove_tab(name) return if name.nil? - @meta_data.delete(name.to_sym) - end - - def user=(user = {}) - return unless user.is_a? Hash - @user.merge!(user).delete_if{|k,v| v == nil} + meta_data.delete(name.to_s) end # Build an exception payload def as_json # Build the payload's exception event payload_event = { - :app => { - :version => configuration.app_version, - :releaseStage => configuration.release_stage, - :type => configuration.app_type + app: { + version: app_version, + releaseStage: release_stage, + type: app_type + }, + context: context, + device: { + hostname: hostname }, - :context => self.context, - :user => @user, - :payloadVersion => CURRENT_PAYLOAD_VERSION, - :exceptions => exception_list, - :severity => self.severity, - :groupingHash => self.grouping_hash, + exceptions: exception_list, + groupingHash: grouping_hash, + payloadVersion: CURRENT_PAYLOAD_VERSION, + severity: severity, + user: user } - payload_event[:device] = {:hostname => configuration.hostname} if configuration.hostname - # cleanup character encodings 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.params_filters).clean_object(@meta_data) + payload_event[:metaData] = Bugsnag::Cleaner.new(configuration.params_filters).clean_object(meta_data) payload_event.reject! {|k,v| v.nil? } # return the payload hash @@ -130,15 +105,11 @@ def as_json end def ignore? - @should_ignore || ignore_exception_class? || ignore_user_agent? + @should_ignore end def request_data - @configuration.request_data - end - - def exceptions - @exceptions + configuration.request_data end def ignore! @@ -147,30 +118,12 @@ def ignore! private - def ignore_exception_class? - @exceptions.any? do |ex| - ancestor_chain = ex.class.ancestors.select { |ancestor| ancestor.is_a?(Class) }.map { |ancestor| error_class(ancestor) }.to_set - - configuration.ignore_classes.any? do |to_ignore| - to_ignore.is_a?(Proc) ? to_ignore.call(ex) : ancestor_chain.include?(to_ignore) - end - end - end - - def ignore_user_agent? - if configuration.request_data && configuration.request_data[:rack_env] && (agent = configuration.request_data[:rack_env]["HTTP_USER_AGENT"]) - configuration.ignore_user_agents.any? do |to_ignore| - agent =~ to_ignore - end - end - end - - def exception_list - @exceptions.map do |exception| + def generate_exception_list + raw_exceptions.map do |exception| { - :errorClass => error_class(exception), - :message => exception.message, - :stacktrace => stacktrace(exception.backtrace) + errorClass: error_class(exception), + message: exception.message, + stacktrace: Stacktrace.new(exception.backtrace).to_a } end end @@ -180,5 +133,41 @@ def error_class(exception) # which throw the error class instead of an instance (exception.is_a? Class) ? exception.name : exception.class.name end + + def generate_raw_exceptions(exception) + exceptions = [] + + ex = exception + while ex != nil && !exceptions.include?(ex) && exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP + + unless ex.is_a? Exception + if ex.respond_to?(:to_exception) + ex = ex.to_exception + elsif ex.respond_to?(:exception) + ex = ex.exception + end + end + + unless ex.is_a?(Exception) || (defined?(Java::JavaLang::Throwable) && ex.is_a?(Java::JavaLang::Throwable)) + configuration.warn("Converting non-Exception to RuntimeError: #{ex.inspect}") + ex = RuntimeError.new(ex.to_s) + ex.set_backtrace caller + end + + exceptions << ex + + if ex.respond_to?(:cause) && ex.cause + ex = ex.cause + elsif ex.respond_to?(:continued_exception) && ex.continued_exception + ex = ex.continued_exception + elsif ex.respond_to?(:original_exception) && ex.original_exception + ex = ex.original_exception + else + ex = nil + end + end + + exceptions + end end end diff --git a/lib/bugsnag/stacktrace.rb b/lib/bugsnag/stacktrace.rb index 03ae77a3e..fceabb004 100644 --- a/lib/bugsnag/stacktrace.rb +++ b/lib/bugsnag/stacktrace.rb @@ -7,9 +7,11 @@ class Stacktrace # e.g. "org.jruby.Ruby.runScript(Ruby.java:807)" JAVA_BACKTRACE_REGEX = /^(.*)\((.*)(?::([0-9]+))?\)$/ - def initialize(backtrace) + def initialize(backtrace, configuration) + @configuration = configuration + backtrace = caller if !backtrace || backtrace.empty? - backtrace.map do |trace| + @processed_backtrace = backtrace.map do |trace| if trace.match(BACKTRACE_LINE_REGEX) file, line_str, method = [$1, $2, $3] elsif trace.match(JAVA_BACKTRACE_REGEX) @@ -37,8 +39,8 @@ def initialize(backtrace) end # Clean up the file path in the stacktrace - if defined?(Bugsnag.configuration.project_root) && Bugsnag.configuration.project_root.to_s != '' - file.sub!(/#{Bugsnag.configuration.project_root}\//, "") + if defined?(@configuration.project_root) && @configuration.project_root.to_s != '' + file.sub!(/#{@configuration.project_root}\//, "") end # Strip common gem path prefixes @@ -59,19 +61,14 @@ def initialize(backtrace) end.compact end - + def to_a + @processed_backtrace + end private def in_project?(line) - return false if configuration.vendor_paths && configuration.vendor_paths.any? do |vendor_path| - if vendor_path.is_a?(String) - line.include?(vendor_path) - else - line =~ vendor_path - end - end - configuration.project_root && line.start_with?(configuration.project_root.to_s) + @configuration.project_root && line.start_with?(@configuration.project_root.to_s) end def code(file, line_number, num_lines = 7) @@ -109,7 +106,7 @@ def code(file, line_number, num_lines = 7) code_hash rescue - Bugsnag.warn("Error fetching code: #{$!.inspect}") + @configuration.warn("Error fetching code: #{$!.inspect}") nil end end diff --git a/rails/init.rb b/rails/init.rb deleted file mode 100644 index 51ddc10af..000000000 --- a/rails/init.rb +++ /dev/null @@ -1,7 +0,0 @@ -# On Rails 2.x GEM_ROOT/rails/init.rb is auto loaded for all gems -# so this is the place to initialize Rails 2.x plugin support -if Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("3.0") - require "bugsnag/rails" -else - Bugsnag.warn "Blocked attempt to initialize legacy Rails 2.x extensions" -end From 44ff0b0da158abfa07b44f522e7bfc0f72093cf4 Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 16:04:32 -0700 Subject: [PATCH 005/109] Specs are begining to come back together --- example/padrino/README.md | 5 +- example/sinatra/README.md | 4 +- lib/bugsnag.rb | 7 +- lib/bugsnag/configuration.rb | 11 +-- lib/bugsnag/integrations/delayed_job.rb | 6 +- lib/bugsnag/integrations/mailman.rb | 4 +- lib/bugsnag/integrations/rack.rb | 8 ++- .../rails/active_record_rescue.rb | 4 +- lib/bugsnag/integrations/railtie.rb | 4 +- lib/bugsnag/integrations/rake.rb | 4 +- lib/bugsnag/integrations/resque.rb | 5 +- lib/bugsnag/integrations/sidekiq.rb | 4 +- lib/bugsnag/middleware/ignore_error_class.rb | 2 +- lib/bugsnag/report.rb | 12 ++-- .../middleware/internal_info_setter.rb | 6 +- .../fixtures/middleware/public_info_setter.rb | 6 +- spec/fixtures/tasks/Rakefile | 5 +- spec/integration_spec.rb | 25 ++----- spec/middleware_spec.rb | 70 +++++++++---------- spec/rack_spec.rb | 2 +- spec/{notification_spec.rb => report_spec.rb} | 2 +- spec/spec_helper.rb | 2 +- spec/{code_spec.rb => stacktrace_spec.rb} | 2 +- 23 files changed, 106 insertions(+), 94 deletions(-) rename spec/{notification_spec.rb => report_spec.rb} (99%) rename spec/{code_spec.rb => stacktrace_spec.rb} (98%) diff --git a/example/padrino/README.md b/example/padrino/README.md index 33fd998a4..71aa0d87a 100644 --- a/example/padrino/README.md +++ b/example/padrino/README.md @@ -35,7 +35,10 @@ explicitly. ``` error 500 do - Bugsnag.auto_notify($!) + Bugsnag.notify($!) do |report| + report.severity = "error" + end + erb :'errors/500' end ``` diff --git a/example/sinatra/README.md b/example/sinatra/README.md index ed0268fa5..b724ef93b 100644 --- a/example/sinatra/README.md +++ b/example/sinatra/README.md @@ -33,7 +33,9 @@ explicitly. ``` error 500 do - Bugsnag.auto_notify($!) + Bugsnag.notify($!) do |report| + report.severity = "error" + end erb :'errors/500' end ``` diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index ae8b71d25..a8bf909dd 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -4,10 +4,9 @@ require "bugsnag/version" require "bugsnag/configuration" require "bugsnag/meta_data" -require "bugsnag/notification" +require "bugsnag/report" require "bugsnag/cleaner" require "bugsnag/helpers" -require "bugsnag/deploy" require "bugsnag/delivery" require "bugsnag/delivery/synchronous" @@ -23,7 +22,6 @@ require "bugsnag/middleware/sidekiq" require "bugsnag/middleware/mailman" require "bugsnag/middleware/rake" -require "bugsnag/middleware/callbacks" module Bugsnag LOCK = Mutex.new @@ -36,6 +34,7 @@ def configure # Explicitly notify of an exception def notify(exception, auto_notify=false, &block) + return if auto_notify && !configuration.auto_notify return unless configuration.valid_api_key? && configuration.should_notify_release_stage? report = Report.new(exception, configuration) @@ -59,7 +58,7 @@ def notify(exception, auto_notify=false, &block) return if report.ignore? # Deliver - configuration.info("Notifying #{configuration.endpoint} of #{exceptions.last.class}") + configuration.info("Notifying #{configuration.endpoint} of #{report.exceptions.last[:errorClass]}") payload_string = ::JSON.dump(Bugsnag::Helpers.trim_if_needed(report.as_json)) Bugsnag::Delivery[configuration.delivery_method].deliver(configuration.endpoint, payload_string, configuration) end diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index 8c5037de3..eec5c3fca 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -2,6 +2,9 @@ require "socket" require "logger" require "bugsnag/middleware_stack" +require "bugsnag/middleware/callbacks" +require "bugsnag/middleware/exception_meta_data" +require "bugsnag/middleware/ignore_error_class" module Bugsnag class Configuration @@ -47,7 +50,7 @@ def initialize self.auto_notify = true self.send_environment = false self.send_code = true - self.params_filters = Set.new(DEFAULT_PARAMS_FILTERS) + self.meta_data_filters = Set.new(DEFAULT_PARAMS_FILTERS) self.ignore_classes = Set.new([]) self.endpoint = "https://notify.bugsnag.com" self.hostname = default_hostname @@ -112,17 +115,17 @@ def clear_request_data end def info(message) - configuration.logger.info("#{LOG_PREFIX}#{message}") + logger.info(message) end # Warning logger def warn(message) - configuration.logger.warn("#{LOG_PREFIX}#{message}") + logger.warn(message) end # Debug logger def debug(message) - configuration.logger.debug("#{LOG_PREFIX}#{message}") + logger.debug(message) end private diff --git a/lib/bugsnag/integrations/delayed_job.rb b/lib/bugsnag/integrations/delayed_job.rb index 41a4c7c89..fbe20270f 100644 --- a/lib/bugsnag/integrations/delayed_job.rb +++ b/lib/bugsnag/integrations/delayed_job.rb @@ -13,6 +13,7 @@ module Plugins class Bugsnag < Plugin module Notify def error(job, error) + # TODO:SM Pull overrides into a delayed job middleware overrides = { :job => { :class => job.class.name, @@ -36,7 +37,10 @@ def error(job, error) overrides[:job][:payload] = p end - ::Bugsnag.auto_notify(error, overrides) + ::Bugsnag.notify(error, true) do |report| + report.severity = "error" + report.meta_data.merge! overrides + end super if defined?(super) end diff --git a/lib/bugsnag/integrations/mailman.rb b/lib/bugsnag/integrations/mailman.rb index bcf3606aa..3d33a708a 100644 --- a/lib/bugsnag/integrations/mailman.rb +++ b/lib/bugsnag/integrations/mailman.rb @@ -15,7 +15,9 @@ def call(mail) yield rescue Exception => ex raise ex if [Interrupt, SystemExit, SignalException].include? ex.class - Bugsnag.auto_notify(ex) + Bugsnag.notify(ex, true) do |report| + report.severity = "error" + end raise ensure Bugsnag.configuration.clear_request_data diff --git a/lib/bugsnag/integrations/rack.rb b/lib/bugsnag/integrations/rack.rb index 6027de827..121628146 100644 --- a/lib/bugsnag/integrations/rack.rb +++ b/lib/bugsnag/integrations/rack.rb @@ -33,7 +33,9 @@ def call(env) response = @app.call(env) rescue Exception => raised # Notify bugsnag of rack exceptions - Bugsnag.auto_notify(raised) + Bugsnag.notify(raised, true) do |report| + report.severity = "error" + end # Re-raise the exception raise @@ -41,7 +43,9 @@ def call(env) # Notify bugsnag of rack exceptions if env["rack.exception"] - Bugsnag.auto_notify(env["rack.exception"]) + Bugsnag.notify(env["rack.exception"], true) do |report| + report.severity = "error" + end end response diff --git a/lib/bugsnag/integrations/rails/active_record_rescue.rb b/lib/bugsnag/integrations/rails/active_record_rescue.rb index dfc9e4745..aee619ad7 100644 --- a/lib/bugsnag/integrations/rails/active_record_rescue.rb +++ b/lib/bugsnag/integrations/rails/active_record_rescue.rb @@ -8,7 +8,9 @@ def run_callbacks(kind, *args, &block) super rescue StandardError => exception # This exception will NOT be escalated, so notify it here. - Bugsnag.auto_notify(exception) + Bugsnag.notify(exception, true) do |report| + report.severity = "error" + end raise end else diff --git a/lib/bugsnag/integrations/railtie.rb b/lib/bugsnag/integrations/railtie.rb index ae0e4dcdb..6c3c5e582 100644 --- a/lib/bugsnag/integrations/railtie.rb +++ b/lib/bugsnag/integrations/railtie.rb @@ -17,7 +17,9 @@ class Railtie < Rails::Railtie runner do at_exit do if $! - Bugsnag.auto_notify($!) + Bugsnag.notify($!, true) do |report| + report.severity = "error" + end end end end diff --git a/lib/bugsnag/integrations/rake.rb b/lib/bugsnag/integrations/rake.rb index 8725ba278..a33e5eaea 100644 --- a/lib/bugsnag/integrations/rake.rb +++ b/lib/bugsnag/integrations/rake.rb @@ -12,7 +12,9 @@ def execute_with_bugsnag(args=nil) execute_without_bugsnag(args) rescue Exception => ex - Bugsnag.auto_notify(ex) + Bugsnag.notify(ex, true) do |report| + report.severity = "error" + end raise ensure Bugsnag.configuration.set_request_data :bugsnag_running_task, old_task diff --git a/lib/bugsnag/integrations/resque.rb b/lib/bugsnag/integrations/resque.rb index e5fae6b50..9abba7814 100644 --- a/lib/bugsnag/integrations/resque.rb +++ b/lib/bugsnag/integrations/resque.rb @@ -26,7 +26,10 @@ def self.add_failure_backend end def save - Bugsnag.auto_notify(exception, {:context => "#{payload['class']}@#{queue}", :payload => payload, :delivery_method => :synchronous}) + Bugsnag.notify(exception, true) do |report| + report.severity = "error" + report.meta_data.merge!({:context => "#{payload['class']}@#{queue}", :payload => payload, :delivery_method => :synchronous}) + end end end end diff --git a/lib/bugsnag/integrations/sidekiq.rb b/lib/bugsnag/integrations/sidekiq.rb index fbb5d1632..f403afcb1 100644 --- a/lib/bugsnag/integrations/sidekiq.rb +++ b/lib/bugsnag/integrations/sidekiq.rb @@ -16,7 +16,9 @@ def call(worker, msg, queue) yield rescue Exception => ex raise ex if [Interrupt, SystemExit, SignalException].include? ex.class - Bugsnag.auto_notify(ex) + Bugsnag.notify(ex, true) do |report| + report.severity = "error" + end raise ensure Bugsnag.configuration.clear_request_data diff --git a/lib/bugsnag/middleware/ignore_error_class.rb b/lib/bugsnag/middleware/ignore_error_class.rb index 8f86b1884..c6e3110a8 100644 --- a/lib/bugsnag/middleware/ignore_error_class.rb +++ b/lib/bugsnag/middleware/ignore_error_class.rb @@ -5,7 +5,7 @@ def initialize(bugsnag) end def call(report) - ignore_error_class = self.raw_exceptions.any? do |ex| + ignore_error_class = report.raw_exceptions.any? do |ex| ancestor_chain = ex.class.ancestors.select { |ancestor| ancestor.is_a?(Class) }.to_set report.configuration.ignore_classes.any? do |to_ignore| diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 2f0f096d0..63dae4f28 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -51,8 +51,8 @@ def add_tab(name, value) return if name.nil? if value.is_a? Hash - meta_data[name.to_s] ||= {} - meta_data[name.to_s].merge! value + meta_data[name] ||= {} + meta_data[name].merge! value else meta_data["custom"][name.to_s] = value end @@ -62,7 +62,7 @@ def add_tab(name, value) def remove_tab(name) return if name.nil? - meta_data.delete(name.to_s) + meta_data.delete(name) end # Build an exception payload @@ -78,7 +78,7 @@ def as_json device: { hostname: hostname }, - exceptions: exception_list, + exceptions: exceptions, groupingHash: grouping_hash, payloadVersion: CURRENT_PAYLOAD_VERSION, severity: severity, @@ -89,7 +89,7 @@ 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.params_filters).clean_object(meta_data) + payload_event[:metaData] = Bugsnag::Cleaner.new(configuration.meta_data_filters).clean_object(meta_data) payload_event.reject! {|k,v| v.nil? } # return the payload hash @@ -123,7 +123,7 @@ def generate_exception_list { errorClass: error_class(exception), message: exception.message, - stacktrace: Stacktrace.new(exception.backtrace).to_a + stacktrace: Stacktrace.new(exception.backtrace, configuration).to_a } end end diff --git a/spec/fixtures/middleware/internal_info_setter.rb b/spec/fixtures/middleware/internal_info_setter.rb index 5dee13588..8a49ab1bc 100644 --- a/spec/fixtures/middleware/internal_info_setter.rb +++ b/spec/fixtures/middleware/internal_info_setter.rb @@ -4,8 +4,8 @@ def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - notification.meta_data[:custom][:info] = MESSAGE - @bugsnag.call(notification) + def call(report) + report.meta_data.merge!({custom: {info: MESSAGE}}) + @bugsnag.call(report) end end diff --git a/spec/fixtures/middleware/public_info_setter.rb b/spec/fixtures/middleware/public_info_setter.rb index 94bca2c1c..8c4bc3a1d 100644 --- a/spec/fixtures/middleware/public_info_setter.rb +++ b/spec/fixtures/middleware/public_info_setter.rb @@ -4,8 +4,8 @@ def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - notification.meta_data[:custom][:info] = MESSAGE - @bugsnag.call(notification) + def call(report) + report.meta_data.merge!({custom: {info: MESSAGE}}) + @bugsnag.call(report) end end diff --git a/spec/fixtures/tasks/Rakefile b/spec/fixtures/tasks/Rakefile index 5da3820ea..385b11049 100644 --- a/spec/fixtures/tasks/Rakefile +++ b/spec/fixtures/tasks/Rakefile @@ -1,13 +1,12 @@ -require "bugsnag/rake" +require "bugsnag/integrations/rake" namespace :test do desc "used by integration_spec to test that Bugsnag::Middleware::Rake runs properly" task :crash do port = ENV['BUGSNAG_TEST_SERVER_PORT'] Bugsnag.configure do |config| - config.endpoint = "localhost:#{port}" + config.endpoint = "http://localhost:#{port}" config.api_key = "0" * 32 - config.use_ssl = false end raise diff --git a/spec/integration_spec.rb b/spec/integration_spec.rb index 64a94dc1c..1d143be9d 100644 --- a/spec/integration_spec.rb +++ b/spec/integration_spec.rb @@ -15,6 +15,7 @@ end Thread.new{ server.start } end + after do server.stop queue.clear @@ -34,8 +35,7 @@ it 'should send notifications over the wire' do Bugsnag.configure do |config| - config.endpoint = "localhost:#{server.config[:Port]}" - config.use_ssl = false + config.endpoint = "http://localhost:#{server.config[:Port]}" end WebMock.allow_net_connect! @@ -44,22 +44,9 @@ expect(request['events'][0]['exceptions'][0]['message']).to eq('yo') end - it 'should send deploys over the wire' do - Bugsnag.configure do |config| - config.endpoint = "localhost:#{server.config[:Port]}" - config.use_ssl = false - end - WebMock.allow_net_connect! - - Bugsnag::Deploy.notify :app_version => '1.1.1' - - expect(request['appVersion']).to eq('1.1.1') - end - it 'should work with threadpool delivery' do Bugsnag.configure do |config| - config.endpoint = "localhost:#{server.config[:Port]}" - config.use_ssl = false + config.endpoint = "http://localhost:#{server.config[:Port]}" config.delivery_method = :thread_queue end WebMock.allow_net_connect! @@ -73,8 +60,7 @@ is_jruby = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' unless is_jruby #jruby doesn't support fork, so this test doesn't apply Bugsnag.configure do |config| - config.endpoint = "localhost:#{server.config[:Port]}" - config.use_ssl = false + config.endpoint = "http://localhost:#{server.config[:Port]}" config.delivery_method = :thread_queue end WebMock.allow_net_connect! @@ -112,8 +98,7 @@ it 'should use a proxy when configured' do Bugsnag.configure do |config| - config.endpoint = "localhost:#{server.config[:Port]}" - config.use_ssl = false + config.endpoint = "http://localhost:#{server.config[:Port]}" config.proxy_host = 'localhost' config.proxy_port = proxy.config[:Port] diff --git a/spec/middleware_spec.rb b/spec/middleware_spec.rb index e94a17c55..24b9f8f91 100644 --- a/spec/middleware_spec.rb +++ b/spec/middleware_spec.rb @@ -28,8 +28,8 @@ it "runs before_bugsnag_notify callbacks, adding custom data" do callback_run_count = 0 Bugsnag.before_notify_callbacks << lambda {|notif| - notif.add_custom_data(:info, "here") - notif.add_custom_data(:data, "also here") + notif.add_tab(:custom, {info: "here"}) + notif.add_tab(:custom, {data: "also here"}) callback_run_count += 1 } @@ -67,34 +67,35 @@ end - it "allows overrides to override values set by internal middleware" do + it "allows block to override values set by internal middleware" do Bugsnag.configuration.internal_middleware.use(InternalInfoSetter) - Bugsnag.notify(BugsnagTestException.new("It crashed"), {:info => "overridden"}) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({custom: {info: 'overridden'}}) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) expect(event["metaData"]["custom"]).not_to be_nil - expect(event["metaData"]["custom"]["info"]).not_to eq(InternalInfoSetter::MESSAGE) expect(event["metaData"]["custom"]["info"]).to eq("overridden") } end - it "doesn't allow overrides to override public middleware" do + it "allows block to override public middleware" do Bugsnag.configuration.middleware.use(PublicInfoSetter) - Bugsnag.notify(BugsnagTestException.new("It crashed"), {:info => "overridden"}) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({custom: {info: 'overridden'}}) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) expect(event["metaData"]["custom"]).not_to be_nil - expect(event["metaData"]["custom"]["info"]).not_to eq("overridden") - expect(event["metaData"]["custom"]["info"]).to eq(PublicInfoSetter::MESSAGE) + expect(event["metaData"]["custom"]["info"]).to eq("overridden") } end - it "does not have have before or after callbacks by default" do + it "does not have have before callbacks by default" do expect(Bugsnag.before_notify_callbacks.size).to eq(0) - expect(Bugsnag.after_notify_callbacks.size).to eq(0) Bugsnag.notify(BugsnagTestException.new("It crashed")) expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -102,18 +103,6 @@ } end - it "runs after_bugsnag_notify callbacks" do - callback_run_count = 0 - Bugsnag.after_notify_callbacks << lambda {|notif| - callback_run_count += 1 - } - - Bugsnag.notify(BugsnagTestException.new("It crashed")) - - expect(callback_run_count).to eq(1) - expect(Bugsnag::Notification).to have_sent_notification { } - end - it "does not execute disabled bugsnag middleware" do callback_run_count = 0 Bugsnag.configure do |config| @@ -133,7 +122,7 @@ notif.ignore! end Bugsnag.notify(BugsnagTestException.new("It crashed")) - expect(Bugsnag::Notification).not_to have_sent_notification { } + expect(Bugsnag).not_to have_sent_notification end it "allows inspection of meta_data before ignoring exception" do @@ -148,34 +137,45 @@ end Bugsnag.notify(BugsnagTestException.new("It crashed")) - expect(Bugsnag::Notification).not_to have_sent_notification + expect(Bugsnag).not_to have_sent_notification end it "allows meta_data to be modified in a middleware" do + MetaDataAdder = Class.new do + def initialize(bugsnag) + @bugsnag = bugsnag + end + + def call(report) + report.meta_data = {test: {value: "abcdef123456abcdef123456abcdef123456"}} + @bugsnag.call(report) + end + end + MetaDataMunger = Class.new do def initialize(bugsnag) @bugsnag = bugsnag end - def call(notification) - token = notification.meta_data[:sidekiq][:args].first - notification.meta_data[:sidekiq][:args] = ["#{token[0...6]}*****#{token[-4..-1]}"] - @bugsnag.call(notification) + def call(report) + token = report.meta_data[:test][:value] + report.meta_data[:test][:value] = "#{token[0...6]}*****#{token[-4..-1]}" + @bugsnag.call(report) end end Bugsnag.configure do |c| + c.middleware.use MetaDataAdder c.middleware.use MetaDataMunger end - notification = Bugsnag.notify(BugsnagTestException.new("It crashed"), { - :sidekiq => { - :args => ["abcdef123456abcdef123456abcdef123456"] - } - }) + Bugsnag.notify(BugsnagTestException.new("It crashed")) - expect(notification.meta_data[:sidekiq][:args]).to eq(["abcdef*****3456"]) + expect(Bugsnag).to have_sent_notification{ |payload| + event = get_event_from_payload(payload) + expect(event["metaData"]['test']['value']).to eq("abcdef*****3456") + } end end diff --git a/spec/rack_spec.rb b/spec/rack_spec.rb index 6eb6c9309..6fa76c264 100644 --- a/spec/rack_spec.rb +++ b/spec/rack_spec.rb @@ -39,7 +39,7 @@ rack_stack.call(rack_env) rescue nil - expect(Bugsnag::Notification).not_to have_sent_notification + expect(Bugsnag).not_to have_sent_notification end end diff --git a/spec/notification_spec.rb b/spec/report_spec.rb similarity index 99% rename from spec/notification_spec.rb rename to spec/report_spec.rb index 5b50ca18e..12847833c 100644 --- a/spec/notification_spec.rb +++ b/spec/report_spec.rb @@ -27,7 +27,7 @@ def gloops end end -describe Bugsnag::Notification do +describe Bugsnag::Report do it "should contain an api_key if one is set" do Bugsnag.notify(BugsnagTestException.new("It crashed")) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5e735b113..2329ff3b2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,7 +32,7 @@ def notify_test_exception(*args) bugsnag.release_stage = "production" bugsnag.delivery_method = :synchronous # silence logger in tests - bugsnag.logger = Logger.new(StringIO.new) + #bugsnag.logger = Logger.new(StringIO.new) end end diff --git a/spec/code_spec.rb b/spec/stacktrace_spec.rb similarity index 98% rename from spec/code_spec.rb rename to spec/stacktrace_spec.rb index bbd38546d..b9b1e5134 100644 --- a/spec/code_spec.rb +++ b/spec/stacktrace_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Bugsnag::Notification do +describe Bugsnag::Stacktrace do it "includes code in the stack trace" do _a = 1 _b = 2 From edf1a5a21b5b06fa49ec1827f17d6c6cad2cb452 Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 17:21:40 -0700 Subject: [PATCH 006/109] Tests pass localy --- lib/bugsnag.rb | 1 + lib/bugsnag/configuration.rb | 4 +- lib/bugsnag/integrations/railtie.rb | 4 +- lib/bugsnag/middleware/exception_meta_data.rb | 4 +- lib/bugsnag/middleware/rack_request.rb | 2 +- lib/bugsnag/report.rb | 1 + spec/report_spec.rb | 263 +++++++----------- 7 files changed, 111 insertions(+), 168 deletions(-) diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index a8bf909dd..cefcdd42d 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -60,6 +60,7 @@ def notify(exception, auto_notify=false, &block) # Deliver configuration.info("Notifying #{configuration.endpoint} of #{report.exceptions.last[:errorClass]}") payload_string = ::JSON.dump(Bugsnag::Helpers.trim_if_needed(report.as_json)) + #TODO:SM Should endpoint add http: by default? Bugsnag::Delivery[configuration.delivery_method].deliver(configuration.endpoint, payload_string, configuration) end end diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index eec5c3fca..c6a3bb9f6 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -35,7 +35,7 @@ class Configuration API_KEY_REGEX = /[0-9a-f]{32}/i THREAD_LOCAL_NAME = "bugsnag_req_data" - DEFAULT_PARAMS_FILTERS = [ + DEFAULT_META_DATA_FILTERS = [ /authorization/i, /cookie/i, /password/i, @@ -50,7 +50,7 @@ def initialize self.auto_notify = true self.send_environment = false self.send_code = true - self.meta_data_filters = Set.new(DEFAULT_PARAMS_FILTERS) + self.meta_data_filters = Set.new(DEFAULT_META_DATA_FILTERS) self.ignore_classes = Set.new([]) self.endpoint = "https://notify.bugsnag.com" self.hostname = default_hostname diff --git a/lib/bugsnag/integrations/railtie.rb b/lib/bugsnag/integrations/railtie.rb index 6c3c5e582..f2047f394 100644 --- a/lib/bugsnag/integrations/railtie.rb +++ b/lib/bugsnag/integrations/railtie.rb @@ -50,11 +50,11 @@ class Railtie < Rails::Railtie Bugsnag.configuration.app_type = "rails" end - # Configure params_filters after initialization, so that rails initializers + # Configure meta_data_filters after initialization, so that rails initializers # may set filter_parameters which will be picked up by Bugsnag. config.after_initialize do Bugsnag.configure do |config| - config.params_filters += ::Rails.configuration.filter_parameters.map do |filter| + config.meta_data_filters += ::Rails.configuration.filter_parameters.map do |filter| case filter when String, Symbol /\A#{filter}\z/ diff --git a/lib/bugsnag/middleware/exception_meta_data.rb b/lib/bugsnag/middleware/exception_meta_data.rb index 618c1d358..4df478860 100644 --- a/lib/bugsnag/middleware/exception_meta_data.rb +++ b/lib/bugsnag/middleware/exception_meta_data.rb @@ -6,7 +6,7 @@ def initialize(bugsnag) def call(report) # Apply the user's information attached to the exceptions - report.exceptions.each do |exception| + report.raw_exceptions.each do |exception| if exception.class.include?(Bugsnag::MetaData) if exception.bugsnag_user_id.is_a?(String) report.user = {id: exception.bugsnag_user_id} @@ -22,7 +22,7 @@ def call(report) if exception.respond_to?(:bugsnag_meta_data) && exception.bugsnag_meta_data exception.bugsnag_meta_data.each do |key, value| - report.add_to_tab key, value + report.add_tab key, value end end end diff --git a/lib/bugsnag/middleware/rack_request.rb b/lib/bugsnag/middleware/rack_request.rb index f9f56f047..3f2765762 100644 --- a/lib/bugsnag/middleware/rack_request.rb +++ b/lib/bugsnag/middleware/rack_request.rb @@ -26,7 +26,7 @@ def call(report) # If app is passed a bad URL, this code will crash attempting to clean it begin - url << Bugsnag::Cleaner.new(report.configuration.params_filters).clean_url(request.fullpath) + url << Bugsnag::Cleaner.new(report.configuration.meta_data_filters).clean_url(request.fullpath) rescue StandardError => stde Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.fullpath: #{stde}" end diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 63dae4f28..6cbad1af6 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -51,6 +51,7 @@ def add_tab(name, value) return if name.nil? if value.is_a? Hash + #TODO:SM Should we to_s here? meta_data[name] ||= {} meta_data[name].merge! value else diff --git a/spec/report_spec.rb b/spec/report_spec.rb index 12847833c..e0b34d00d 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -53,7 +53,9 @@ def gloops end it "lets you override the api_key" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), :api_key => "9d84383f9be2ca94902e45c756a9979d") + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.api_key = "9d84383f9be2ca94902e45c756a9979d" + end expect(Bugsnag).to have_sent_notification{ |payload| expect(payload["apiKey"]).to eq("9d84383f9be2ca94902e45c756a9979d") @@ -62,7 +64,9 @@ def gloops it "lets you override the groupingHash" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), {:grouping_hash => "this is my grouping hash"}) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.grouping_hash = "this is my grouping hash" + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -116,12 +120,14 @@ def gloops # TODO: nested context it "accepts tabs in overrides and adds them to metaData" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), { - :some_tab => { - :info => "here", - :data => "also here" - } - }) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({ + some_tab: { + info: "here", + data: "also here" + } + }) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -132,27 +138,12 @@ def gloops } end - it "accepts non-hash overrides and adds them to the custom tab in metaData" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), { - :info => "here", - :data => "also here" - }) - - expect(Bugsnag).to have_sent_notification{ |payload| - event = get_event_from_payload(payload) - expect(event["metaData"]["custom"]).to eq( - "info" => "here", - "data" => "also here" - ) - } - end - it "accepts meta data from an exception that mixes in Bugsnag::MetaData" do exception = BugsnagTestExceptionWithMetaData.new("It crashed") exception.bugsnag_meta_data = { - :some_tab => { - :info => "here", - :data => "also here" + some_tab: { + info: "here", + data: "also here" } } @@ -176,7 +167,9 @@ def gloops } } - Bugsnag.notify(exception, {:some_tab => {:info => "overridden"}}) + Bugsnag.notify(exception) do |report| + report.add_tab(:some_tab, {:info => "overridden"}) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -203,7 +196,9 @@ def gloops exception = BugsnagTestExceptionWithMetaData.new("It crashed") exception.bugsnag_user_id = "exception_user_id" - Bugsnag.notify(exception, {:user_id => "override_user_id"}) + Bugsnag.notify(exception) do |report| + report.user.merge!({:id => "override_user_id"}) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -240,7 +235,9 @@ def gloops exception = BugsnagTestExceptionWithMetaData.new("It crashed") exception.bugsnag_context = "exception_context" - Bugsnag.notify(exception, {:context => "override_context"}) + Bugsnag.notify(exception) do |report| + report.context = "override_context" + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -249,14 +246,14 @@ def gloops end it "accepts meta_data in overrides (for backwards compatibility) and merge it into metaData" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), { - :meta_data => { - :some_tab => { - :info => "here", - :data => "also here" + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({ + some_tab: { + info: "here", + data: "also here" } - } - }) + }) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -268,14 +265,14 @@ def gloops end it "truncates large meta_data before sending" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), { - :meta_data => { - :some_tab => { - :giant => SecureRandom.hex(500_000/2), - :mega => SecureRandom.hex(500_000/2) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({ + some_tab: { + giant: SecureRandom.hex(500_000/2), + mega: SecureRandom.hex(500_000/2) } - } - }) + }) + end expect(Bugsnag).to have_sent_notification{ |payload| # Truncated body should be no bigger than @@ -285,14 +282,14 @@ def gloops end it "truncates large messages before sending" do - Bugsnag.notify(BugsnagTestException.new(SecureRandom.hex(500_000)), { - :meta_data => { - :some_tab => { - :giant => SecureRandom.hex(500_000/2), - :mega => SecureRandom.hex(500_000/2) + Bugsnag.notify(BugsnagTestException.new(SecureRandom.hex(500_000))) do |report| + report.meta_data.merge!({ + some_tab: { + giant: SecureRandom.hex(500_000/2), + mega: SecureRandom.hex(500_000/2) } - } - }) + }) + end expect(Bugsnag).to have_sent_notification{ |payload| # Truncated body should be no bigger than @@ -316,9 +313,9 @@ def gloops end it "accepts a severity in overrides" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), { - :severity => "info" - }) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.severity = "info" + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -336,30 +333,10 @@ def gloops } end - it "does not accept a bad severity in overrides" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), { - :severity => "fatal" - }) - - expect(Bugsnag).to have_sent_notification{ |payload| - event = get_event_from_payload(payload) - expect(event["severity"]).to eq("warning") - } - end - - it "lets you override severity using block syntax" do - Bugsnag.notify(BugsnagTestException.new("It crashed")) do |notification| - notification.severity = "info" - end - - expect(Bugsnag).to have_sent_notification{ |payload| - event = get_event_from_payload(payload) - expect(event["severity"]).to eq("info") - } - end - it "autonotifies errors" do - Bugsnag.auto_notify(BugsnagTestException.new("It crashed")) + Bugsnag.notify(BugsnagTestException.new("It crashed"), true) do |report| + report.severity = "error" + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -369,9 +346,9 @@ def gloops it "accepts a context in overrides" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), { - :context => "test_context" - }) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.context = 'test_context' + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -380,9 +357,9 @@ def gloops end it "accepts a user_id in overrides" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), { - :user_id => "test_user" - }) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.user = {id: 'test_user'} + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -395,7 +372,7 @@ def gloops config.auto_notify = false end - Bugsnag.auto_notify(BugsnagTestException.new("It crashed")) + Bugsnag.notify(BugsnagTestException.new("It crashed"), true) expect(Bugsnag).not_to have_sent_notification end @@ -405,7 +382,7 @@ def gloops config.release_stage = "production" end - Bugsnag.auto_notify(BugsnagTestException.new("It crashed")) + Bugsnag.notify(BugsnagTestException.new("It crashed")) expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -439,27 +416,6 @@ def gloops expect(WebMock).to have_requested(:post, "https://notify.bugsnag.com") end - it "uses ssl when use_ssl is true" do - Bugsnag.configuration.use_ssl = true - Bugsnag.notify(BugsnagTestException.new("It crashed")) - - expect(WebMock).to have_requested(:post, "https://notify.bugsnag.com") - end - - it "does not use ssl when use_ssl is false" do - stub_request(:post, "http://notify.bugsnag.com/") - Bugsnag.configuration.use_ssl = false - Bugsnag.notify(BugsnagTestException.new("It crashed")) - - expect(WebMock).to have_requested(:post, "http://notify.bugsnag.com") - end - - it "uses ssl when use_ssl is unset" do - Bugsnag.notify(BugsnagTestException.new("It crashed")) - - expect(WebMock).to have_requested(:post, "https://notify.bugsnag.com") - end - it "does not mark the top-most stacktrace line as inProject if out of project" do Bugsnag.configuration.project_root = "/Random/location/here" Bugsnag.notify(BugsnagTestException.new("It crashed")) @@ -471,19 +427,6 @@ def gloops } end - it "does not mark the top-most stacktrace line as inProject if it matches a vendor path" do - Bugsnag.configuration.project_root = File.expand_path('../../', __FILE__) - Bugsnag.configuration.vendor_paths = [File.expand_path('../', __FILE__)] - - Bugsnag.notify(BugsnagTestException.new("It crashed")) - - expect(Bugsnag).to have_sent_notification{ |payload| - exception = get_exception_from_payload(payload) - expect(exception["stacktrace"].size).to be >= 1 - expect(exception["stacktrace"].first["inProject"]).to be_nil - } - end - it "marks the top-most stacktrace line as inProject if necessary" do Bugsnag.configuration.project_root = File.expand_path File.dirname(__FILE__) Bugsnag.notify(BugsnagTestException.new("It crashed")) @@ -505,9 +448,11 @@ def gloops } end - it "filters params from all payload hashes if they are set in default params_filters" do + it "filters params from all payload hashes if they are set in default meta_data_filters" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:password => "1234", :other_password => "12345", :other_data => "123456"}}}) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({:request => {:params => {:password => "1234", :other_password => "12345", :other_data => "123456"}}}) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -520,10 +465,12 @@ def gloops } end - it "filters params from all payload hashes if they are added to params_filters" do + it "filters params from all payload hashes if they are added to meta_data_filters" do - Bugsnag.configuration.params_filters << "other_data" - Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}}) + Bugsnag.configuration.meta_data_filters << "other_data" + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}}) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -536,10 +483,12 @@ def gloops } end - it "filters params from all payload hashes if they are added to params_filters as regex" do + it "filters params from all payload hashes if they are added to meta_data_filters as regex" do - Bugsnag.configuration.params_filters << /other_data/ - Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}}) + Bugsnag.configuration.meta_data_filters << /other_data/ + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}}) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -552,10 +501,12 @@ def gloops } end - it "filters params from all payload hashes if they are added to params_filters as partial regex" do + it "filters params from all payload hashes if they are added to meta_data_filters as partial regex" do - Bugsnag.configuration.params_filters << /r_data/ - Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}}) + Bugsnag.configuration.meta_data_filters << /r_data/ + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}}) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -569,7 +520,9 @@ def gloops end it "does not filter params from payload hashes if their values are nil" do - Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:nil_param => nil}}}) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({:request => {:params => {:nil_param => nil}}}) + end expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -581,56 +534,39 @@ def gloops end it "does not notify if the exception class is in the default ignore_classes list" do - Bugsnag.notify_or_ignore(ActiveRecord::RecordNotFound.new("It crashed")) + Bugsnag.configuration.ignore_classes << ActiveRecord::RecordNotFound + Bugsnag.notify(ActiveRecord::RecordNotFound.new("It crashed")) expect(Bugsnag).not_to have_sent_notification end it "does not notify if the non-default exception class is added to the ignore_classes" do - Bugsnag.configuration.ignore_classes << "BugsnagTestException" + Bugsnag.configuration.ignore_classes << BugsnagTestException - Bugsnag.notify_or_ignore(BugsnagTestException.new("It crashed")) + Bugsnag.notify(BugsnagTestException.new("It crashed")) expect(Bugsnag).not_to have_sent_notification end it "does not notify if exception's ancestor is an ignored class" do - Bugsnag.configuration.ignore_classes << "BugsnagTestException" + Bugsnag.configuration.ignore_classes << BugsnagTestException - Bugsnag.notify_or_ignore(BugsnagSubclassTestException.new("It crashed")) + Bugsnag.notify(BugsnagSubclassTestException.new("It crashed")) expect(Bugsnag).not_to have_sent_notification end it "does not notify if any caused exception is an ignored class" do - Bugsnag.configuration.ignore_classes << "NestedException" + Bugsnag.configuration.ignore_classes << NestedException ex = NestedException.new("Self-referential exception") ex.original_exception = BugsnagTestException.new("It crashed") - Bugsnag.notify_or_ignore(ex) - - expect(Bugsnag).not_to have_sent_notification - end - - it "accepts both String and Class instances as an ignored class" do - Bugsnag.configuration.ignore_classes << BugsnagTestException - - Bugsnag.notify_or_ignore(BugsnagTestException.new("It crashed")) + Bugsnag.notify(ex) expect(Bugsnag).not_to have_sent_notification end - it "does not notify if the user agent is present and matches a regex in ignore_user_agents" do - Bugsnag.configuration.ignore_user_agents << %r{BugsnagUserAgent} - - ((Thread.current["bugsnag_req_data"] ||= {})[:rack_env] ||= {})["HTTP_USER_AGENT"] = "BugsnagUserAgent" - - Bugsnag.notify_or_ignore(BugsnagTestException.new("It crashed")) - - expect(Bugsnag::Notification).not_to have_sent_notification - end - it "sends the cause of the exception" do begin begin @@ -652,7 +588,7 @@ def gloops ex = NestedException.new("Self-referential exception") ex.original_exception = ex - Bugsnag.notify_or_ignore(ex) + Bugsnag.notify(ex) expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) @@ -667,7 +603,7 @@ def gloops ex = ex.original_exception = NestedException.new("Deep exception #{idx}") end - Bugsnag.notify_or_ignore(first_ex) + Bugsnag.notify(first_ex) expect(Bugsnag).to have_sent_notification{ |payload| event = get_event_from_payload(payload) expect(event["exceptions"].size).to eq(5) @@ -748,13 +684,16 @@ def gloops invalid_data = "fl\xc3ff" invalid_data.force_encoding('BINARY') if invalid_data.respond_to?(:force_encoding) - notify_test_exception(:fluff => {:fluff => invalid_data}) + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.meta_data.merge!({fluff: {fluff: invalid_data}}) + end expect(Bugsnag).to have_sent_notification{ |payload| + event = get_event_from_payload(payload) if defined?(Encoding::UTF_8) - expect(payload.to_json).to match(/fl�ff/) + expect(event['metaData']['fluff']['fluff']).to match(/fl�ff/) else - expect(payload.to_json).to match(/flff/) + expect(event['metaData']['fluff']['fluff']).to match(/flff/) end } end @@ -785,7 +724,9 @@ def gloops begin raise rescue - Bugsnag.notify($!, { :context => invalid_data }) + Bugsnag.notify($!) do |report| + report.context = invalid_data + end end expect(Bugsnag).to have_sent_notification { |payload| From 011e6cf88b3060fb6e77872d2eae9657b8dc3ea1 Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 17:32:18 -0700 Subject: [PATCH 007/109] Silence logger --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2329ff3b2..5e735b113 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,7 +32,7 @@ def notify_test_exception(*args) bugsnag.release_stage = "production" bugsnag.delivery_method = :synchronous # silence logger in tests - #bugsnag.logger = Logger.new(StringIO.new) + bugsnag.logger = Logger.new(StringIO.new) end end From 96e6cd4761d09d368e77213e2b10e3970bfb62bf Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 17:45:26 -0700 Subject: [PATCH 008/109] Logging --- lib/bugsnag.rb | 39 ++++++++++++++++++++++++++++-------- lib/bugsnag/configuration.rb | 17 ++-------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index cefcdd42d..06273b318 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -34,33 +34,56 @@ def configure # Explicitly notify of an exception def notify(exception, auto_notify=false, &block) - return if auto_notify && !configuration.auto_notify - return unless configuration.valid_api_key? && configuration.should_notify_release_stage? + if auto_notify && !configuration.auto_notify + configuration.debug("Not notifying because auto_notify is disabled") + return + end + + if !configuration.valid_api_key? + configuration.debug("Not notifying due to an invalid api_key") + return + end + + if !configuration.should_notify_release_stage? + configuration.debug("Not notifying due to notify_release_stages :#{configuration.notify_release_stages.inspect}") + return + end report = Report.new(exception, configuration) - return if report.ignore? # If this is an auto_notify we yield the block before the any middleware is run yield(report) if block_given? && auto_notify - return if report.ignore? + if report.ignore? + configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in auto_notify block") + return + end # Run internal middleware configuration.internal_middleware.run(report) - return if report.ignore? + if report.ignore? + configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in internal middlewares") + return + end # Run users middleware configuration.middleware.run(report) do - return if report.ignore? + if report.ignore? + configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in user provided middleware") + return + end # If this is not an auto_notify then the block was provided by the user. This should be the last # block that is run as it is the users "most specific" block. yield(report) if block_given? && !auto_notify - return if report.ignore? + if report.ignore? + configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in user provided block") + return + end # Deliver configuration.info("Notifying #{configuration.endpoint} of #{report.exceptions.last[:errorClass]}") payload_string = ::JSON.dump(Bugsnag::Helpers.trim_if_needed(report.as_json)) - #TODO:SM Should endpoint add http: by default? + configuration.debug("Payload: #{payload_string}") Bugsnag::Delivery[configuration.delivery_method].deliver(configuration.endpoint, payload_string, configuration) end end diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index c6a3bb9f6..8b922061d 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -78,24 +78,11 @@ def initialize end def should_notify_release_stage? - if @release_stage.nil? || @notify_release_stages.nil? || @notify_release_stages.include?(@release_stage) - return true - else - warn "Not notifying in release stage #{@release_stage}" - return false - end + @release_stage.nil? || @notify_release_stages.nil? || @notify_release_stages.include?(@release_stage) end def valid_api_key? - if api_key.nil? - warn "No API key configured, couldn't notify" - return false - elsif api_key !~ API_KEY_REGEX - warn "Your API key (#{api_key}) is not valid, couldn't notify" - return false - end - - return true + !api_key.nil? && api_key =~ API_KEY_REGEX end def request_data From 713fad3b3d88da0259fdb5886e284be2342e7487 Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 17:50:45 -0700 Subject: [PATCH 009/109] Not appropriate --- lib/bugsnag/integrations/delayed_job.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/bugsnag/integrations/delayed_job.rb b/lib/bugsnag/integrations/delayed_job.rb index fbe20270f..235ca2098 100644 --- a/lib/bugsnag/integrations/delayed_job.rb +++ b/lib/bugsnag/integrations/delayed_job.rb @@ -8,12 +8,9 @@ unless defined? Delayed::Plugins::Bugsnag module Delayed module Plugins - - class Bugsnag < Plugin module Notify def error(job, error) - # TODO:SM Pull overrides into a delayed job middleware overrides = { :job => { :class => job.class.name, From 0e30d7aff05facdddbb9c3f2c7815826b480f228 Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 17:59:46 -0700 Subject: [PATCH 010/109] Remove these --- lib/bugsnag/report.rb | 1 - spec/report_spec.rb | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 6cbad1af6..63dae4f28 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -51,7 +51,6 @@ def add_tab(name, value) return if name.nil? if value.is_a? Hash - #TODO:SM Should we to_s here? meta_data[name] ||= {} meta_data[name].merge! value else diff --git a/spec/report_spec.rb b/spec/report_spec.rb index e0b34d00d..23681479c 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -117,8 +117,6 @@ def gloops } end - # TODO: nested context - it "accepts tabs in overrides and adds them to metaData" do Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| report.meta_data.merge!({ From 7577dc53738a4ddd840ae40136ad48c2331965fc Mon Sep 17 00:00:00 2001 From: Simon Maynard Date: Sat, 20 Aug 2016 18:03:04 -0700 Subject: [PATCH 011/109] Whitespace --- lib/bugsnag/integrations/mailman.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/bugsnag/integrations/mailman.rb b/lib/bugsnag/integrations/mailman.rb index 3d33a708a..c0dc7cd5f 100644 --- a/lib/bugsnag/integrations/mailman.rb +++ b/lib/bugsnag/integrations/mailman.rb @@ -9,9 +9,7 @@ def initialize def call(mail) begin - Bugsnag.configuration.set_request_data :mailman_msg, mail.to_s - yield rescue Exception => ex raise ex if [Interrupt, SystemExit, SignalException].include? ex.class From 514482476833a91338fa0887dc8f0eab8fb5f1f6 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 11 Aug 2017 11:49:48 -0700 Subject: [PATCH 012/109] Report code coverage --- bugsnag.gemspec | 1 + spec/spec_helper.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/bugsnag.gemspec b/bugsnag.gemspec index 2f6a0cac8..0e1599a2d 100644 --- a/bugsnag.gemspec +++ b/bugsnag.gemspec @@ -25,4 +25,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'pry' s.add_development_dependency 'addressable', '~> 2.3.8' s.add_development_dependency 'webmock' + s.add_development_dependency 'coveralls' end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5e735b113..d63e6782f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,6 @@ +require 'coveralls' +Coveralls.wear! + require 'bugsnag' require 'webmock/rspec' From 5286521661594f2d963c30a6e21d196e76dc35cc Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 11 Aug 2017 11:56:39 -0700 Subject: [PATCH 013/109] Add spec filter --- bugsnag.gemspec | 1 + spec/spec_helper.rb | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bugsnag.gemspec b/bugsnag.gemspec index 0e1599a2d..b19374410 100644 --- a/bugsnag.gemspec +++ b/bugsnag.gemspec @@ -25,5 +25,6 @@ Gem::Specification.new do |s| s.add_development_dependency 'pry' s.add_development_dependency 'addressable', '~> 2.3.8' s.add_development_dependency 'webmock' + s.add_development_dependency 'simplecov' s.add_development_dependency 'coveralls' end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d63e6782f..7a2295651 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,10 @@ +require 'simplecov' require 'coveralls' -Coveralls.wear! + +SimpleCov.formatter = Coveralls::SimpleCov::Formatter +SimpleCov.start do + add_filter 'spec' +end require 'bugsnag' From 83ddab5b6a3b67eb5c7e8a0a8d060566b76c4b5e Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 11 Aug 2017 12:08:34 -0700 Subject: [PATCH 014/109] Skip old ruby when generating coverage specs --- spec/spec_helper.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7a2295651..371ac62eb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,11 @@ -require 'simplecov' -require 'coveralls' +if RUBY_VERSION > "2.0.0" + require 'simplecov' + require 'coveralls' -SimpleCov.formatter = Coveralls::SimpleCov::Formatter -SimpleCov.start do - add_filter 'spec' + SimpleCov.formatter = Coveralls::SimpleCov::Formatter + SimpleCov.start do + add_filter 'spec' + end end require 'bugsnag' From c9555122031ab492db726db3988b1096f84a5daa Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 11 Aug 2017 13:32:26 -0700 Subject: [PATCH 015/109] Test canceling reports, cleaning objects Fix bug in custom metadata handling --- lib/bugsnag/report.rb | 2 ++ spec/cleaner_spec.rb | 6 ++++ spec/report_spec.rb | 64 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 63dae4f28..5f3c289eb 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -54,6 +54,8 @@ def add_tab(name, value) meta_data[name] ||= {} meta_data[name].merge! value else + meta_data["custom"] = {} unless meta_data["custom"] + meta_data["custom"][name.to_s] = value end end diff --git a/spec/cleaner_spec.rb b/spec/cleaner_spec.rb index cc2280cdd..a73a472ba 100644 --- a/spec/cleaner_spec.rb +++ b/spec/cleaner_spec.rb @@ -37,6 +37,12 @@ expect(subject.clean_object(obj)).to eq("André") end + it "cleans custom objects" do + class Macaron; end + a = Macaron.new + expect(subject.clean_object(a)).to eq('[OBJECT]') + end + it "cleans up binary strings properly" do if RUBY_VERSION > "1.9" obj = "Andr\xc7\xff" diff --git a/spec/report_spec.rb b/spec/report_spec.rb index 23681479c..d939337f4 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -156,6 +156,62 @@ def gloops } end + it "removes tabs" do + exception = BugsnagTestExceptionWithMetaData.new("It crashed") + exception.bugsnag_meta_data = { + :some_tab => { + :info => "here", + :data => "also here" + } + } + + Bugsnag.notify(exception) do |report| + report.remove_tab(:some_tab) + end + + expect(Bugsnag).to have_sent_notification{ |payload| + event = get_event_from_payload(payload) + expect(event["metaData"]["some_tab"]).to be_nil + } + end + + it "ignores removing nil tabs" do + exception = BugsnagTestExceptionWithMetaData.new("It crashed") + exception.bugsnag_meta_data = { + :some_tab => { + :info => "here", + :data => "also here" + } + } + + Bugsnag.notify(exception) do |report| + report.remove_tab(nil) + end + + expect(Bugsnag).to have_sent_notification{ |payload| + event = get_event_from_payload(payload) + expect(event["metaData"]["some_tab"]).to eq( + "info" => "here", + "data" => "also here" + ) + } + end + + it "Creates a custom tab for metadata which is not a Hash" do + exception = Exception.new("It crashed") + + Bugsnag.notify(exception) do |report| + report.add_tab(:some_tab, "added") + end + + expect(Bugsnag).to have_sent_notification{ |payload| + event = get_event_from_payload(payload) + expect(event["metaData"]["custom"]).to eq( + "some_tab" => "added", + ) + } + end + it "accepts meta data from an exception that mixes in Bugsnag::MetaData, but override using the overrides" do exception = BugsnagTestExceptionWithMetaData.new("It crashed") exception.bugsnag_meta_data = { @@ -531,6 +587,14 @@ def gloops } end + it "does not notify if report ignored" do + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report| + report.ignore! + end + + expect(Bugsnag).not_to have_sent_notification + end + it "does not notify if the exception class is in the default ignore_classes list" do Bugsnag.configuration.ignore_classes << ActiveRecord::RecordNotFound Bugsnag.notify(ActiveRecord::RecordNotFound.new("It crashed")) From 6838fde69688ac168055a4fc9d89b84b95e13ba6 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 11 Aug 2017 15:24:39 -0700 Subject: [PATCH 016/109] Test sidekiq integration --- bugsnag.gemspec | 1 + spec/integrations/sidekiq_spec.rb | 34 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 spec/integrations/sidekiq_spec.rb diff --git a/bugsnag.gemspec b/bugsnag.gemspec index b19374410..31266b99b 100644 --- a/bugsnag.gemspec +++ b/bugsnag.gemspec @@ -27,4 +27,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'webmock' s.add_development_dependency 'simplecov' s.add_development_dependency 'coveralls' + s.add_development_dependency 'sidekiq', '~> 5.0.4' end diff --git a/spec/integrations/sidekiq_spec.rb b/spec/integrations/sidekiq_spec.rb new file mode 100644 index 000000000..f4993dd9a --- /dev/null +++ b/spec/integrations/sidekiq_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' +require 'sidekiq/testing' + +class FailingWorker + include Sidekiq::Worker + def perform(value) + puts "Work: #{100/value}" + end +end + +describe Bugsnag::Sidekiq do + before do + Sidekiq::Testing.inline! + Sidekiq::Testing.server_middleware do |chain| + chain.add Bugsnag::Sidekiq + end + end + + it "works" do + begin + FailingWorker.perform_async(-0) + fail("shouldn't be here") + rescue + end + + expect(Bugsnag).to have_sent_notification {|payload| + event = get_event_from_payload(payload) + expect(event["metaData"]["sidekiq"]["msg"]["class"]).to eq("FailingWorker") + expect(event["metaData"]["sidekiq"]["msg"]["args"]).to eq([-0]) + expect(event["metaData"]["sidekiq"]["msg"]["queue"]).to eq("default") + expect(event["severity"]).to eq("error") + } + end +end From 95b599dcca0d55435cb7dfc9960cc4875f4067e4 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 11 Aug 2017 15:25:22 -0700 Subject: [PATCH 017/109] Document how delivery works --- lib/bugsnag/delivery.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/bugsnag/delivery.rb b/lib/bugsnag/delivery.rb index 2ce55fbc8..a78577dd1 100644 --- a/lib/bugsnag/delivery.rb +++ b/lib/bugsnag/delivery.rb @@ -1,10 +1,19 @@ module Bugsnag module Delivery class << self + # Add a delivery method to the list of supported methods. Any registered + # method can then be used by name in Configuration. + # + # require 'bugsnag' + # Bugsnag::Delivery.register(:my_delivery_queue, MyDeliveryQueue) + # Bugsnag.configure do |config| + # config.delivery_method = :my_delivery_queue + # end def register(name, delivery_method) delivery_methods[name.to_sym] = delivery_method end + # Reference a delivery method by name def [](name) delivery_methods[name.to_sym] end From 03a1ebd7721775e4af1ee503b4e6f78cc44bdd3e Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 11 Aug 2017 15:25:38 -0700 Subject: [PATCH 018/109] Constantize the default endpoint value --- lib/bugsnag/configuration.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index 8b922061d..38b23e4cc 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -34,6 +34,7 @@ class Configuration API_KEY_REGEX = /[0-9a-f]{32}/i THREAD_LOCAL_NAME = "bugsnag_req_data" + DEFAULT_ENDPOINT = "https://notify.bugsnag.com" DEFAULT_META_DATA_FILTERS = [ /authorization/i, @@ -52,7 +53,7 @@ def initialize self.send_code = true self.meta_data_filters = Set.new(DEFAULT_META_DATA_FILTERS) self.ignore_classes = Set.new([]) - self.endpoint = "https://notify.bugsnag.com" + self.endpoint = DEFAULT_ENDPOINT self.hostname = default_hostname self.delivery_method = :thread_queue self.timeout = 15 From 64f6dd19310ba5a2295afbc1442dcc5e79ba5052 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Wed, 23 Aug 2017 01:14:09 +0100 Subject: [PATCH 019/109] Ensured default user_id set correctly in rack and rails3 middlewares (#363) --- lib/bugsnag/middleware/rack_request.rb | 2 +- lib/bugsnag/middleware/rails3_request.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bugsnag/middleware/rack_request.rb b/lib/bugsnag/middleware/rack_request.rb index 3f2765762..d1304c25c 100644 --- a/lib/bugsnag/middleware/rack_request.rb +++ b/lib/bugsnag/middleware/rack_request.rb @@ -18,7 +18,7 @@ def call(report) report.context = "#{request.request_method} #{request.path}" # Set a sensible default for user_id - report.user_id = request.ip + report.user["id"] = request.ip # Build the clean url (hide the port if it is obvious) url = "#{request.scheme}://#{request.host}" diff --git a/lib/bugsnag/middleware/rails3_request.rb b/lib/bugsnag/middleware/rails3_request.rb index 3583a2c85..cdc4c0319 100644 --- a/lib/bugsnag/middleware/rails3_request.rb +++ b/lib/bugsnag/middleware/rails3_request.rb @@ -26,7 +26,7 @@ def call(report) :requestId => env["action_dispatch.request_id"] }) - report.user_id = env["action_dispatch.remote_ip"] + report.user["id"] = env["action_dispatch.remote_ip"] # Add the rails version if report.configuration.send_environment From 35eafccac5a8e76a94922a7df4871c456ca2dec6 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 24 Aug 2017 14:52:13 +0100 Subject: [PATCH 020/109] Modified test and travis system to check dependency separation --- .travis.yml | 26 ++++++++++++++++++++++++++ Gemfile | 21 ++++++++++++++++++++- Rakefile | 9 +++++++-- bugsnag.gemspec | 10 ---------- spec/spec_helper.rb | 2 +- 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index b2e4c50fc..d51737aaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,3 +12,29 @@ rvm: before_install: - gem update --system 2.1.11 - gem --version + +install: +- bundle install $INSTALL_OPTS + +script: +- bundle exec rake -- $EXEC_OPTS + +matrix: + include: + - rvm: jruby-19mode + env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + - rvm: 1.9.3 + env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + - rvm: 2.0.0 + env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + - rvm: 2.0.0 + env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + - rvm: 2.2.4 + env: INSTALL_OPTS="--without coverage" + - rvm: 2.3.0 + env: INSTALL_OPTS="--without coverage" + - rvm: 2.3.0 + env: EXEC_OPTS="--coverage" + allow_failures: + - rvm: 2.3.0 + env: EXEC_OPTS="--coverage" \ No newline at end of file diff --git a/Gemfile b/Gemfile index 3be9c3cd8..57968c959 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,21 @@ source "https://rubygems.org" -gemspec + +group :test do + gem 'rake', '~> 10.1.1' + gem 'rspec' + gem 'rdoc' + gem 'pry' + gem 'addressable', '~> 2.3.8' + gem 'webmock' +end + +group :coverage do + gem 'simplecov' + gem 'coveralls' +end + +group :sidekiq do + gem 'sidekiq', '~> 5.0.4' +end + +gemspec \ No newline at end of file diff --git a/Rakefile b/Rakefile index b97f4124b..30742615f 100644 --- a/Rakefile +++ b/Rakefile @@ -3,8 +3,9 @@ require 'rubygems' require 'bundler' require 'bundler/gem_tasks' + begin - Bundler.setup(:default, :development) + Bundler.setup(:default) rescue Bundler::BundlerError => e $stderr.puts e.message $stderr.puts "Run `bundle install` to install missing gems" @@ -24,6 +25,10 @@ end # RSpec tasks require 'rspec/core' require "rspec/core/rake_task" -RSpec::Core::RakeTask.new(:spec) +RSpec::Core::RakeTask.new(:spec) do |task| + if ARGV.include? '--no-sidekiq' + task.rspec_opts = "--exclude-pattern **/integrations/sidekiq_spec.rb" + end +end task :default => :spec diff --git a/bugsnag.gemspec b/bugsnag.gemspec index 31266b99b..d2f04c77a 100644 --- a/bugsnag.gemspec +++ b/bugsnag.gemspec @@ -18,14 +18,4 @@ Gem::Specification.new do |s| ] s.require_paths = ["lib"] s.required_ruby_version = '>= 1.9.2' - - s.add_development_dependency 'rake', '~> 10.1.1' - s.add_development_dependency 'rspec' - s.add_development_dependency 'rdoc' - s.add_development_dependency 'pry' - s.add_development_dependency 'addressable', '~> 2.3.8' - s.add_development_dependency 'webmock' - s.add_development_dependency 'simplecov' - s.add_development_dependency 'coveralls' - s.add_development_dependency 'sidekiq', '~> 5.0.4' end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 371ac62eb..549e8e605 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,4 @@ -if RUBY_VERSION > "2.0.0" +if ARGV.include? "--coverage" require 'simplecov' require 'coveralls' From c9d9b2ba2f1cf227af84e94843414d47fac7c83e Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 24 Aug 2017 15:13:22 +0100 Subject: [PATCH 021/109] Changed matrix makeup --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index d51737aaa..03e71ec3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,11 @@ install: script: - bundle exec rake -- $EXEC_OPTS +env: +- INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" +- INSTALL_OPTS="--without coverage" +- EXEC_OPTS="--coverage" + matrix: include: - rvm: jruby-19mode From ca80cb21a2d68954c7ee2a185cb2f2d9192d874e Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 24 Aug 2017 15:16:05 +0100 Subject: [PATCH 022/109] Changed matrix makeup --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 03e71ec3c..bdc7b5623 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,13 @@ env: - EXEC_OPTS="--coverage" matrix: + exclude: + - rvm: jruby-19mode + - rvm: 1.9.3 + - rvm: 2.0.0 + - rvm: 2.1.8 + - rvm: 2.2.4 + - rvm: 2.3.0 include: - rvm: jruby-19mode env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" From a8ef3841d26cc0624241c132181ce7ac4d1b3861 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 24 Aug 2017 15:36:02 +0100 Subject: [PATCH 023/109] Ensured ruby-gems is up to date --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bdc7b5623..2e64ca45b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ rvm: - jruby-19mode before_install: -- gem update --system 2.1.11 +- gem update --system - gem --version install: From 893de5abb5026024ccb2f6276e247921a2f57e07 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 24 Aug 2017 15:46:18 +0100 Subject: [PATCH 024/109] Made more verbose --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e64ca45b..7f1e384ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,11 +10,11 @@ rvm: - jruby-19mode before_install: -- gem update --system +- gem update --system 2.2 - gem --version install: -- bundle install $INSTALL_OPTS +- bundle install --verbose $INSTALL_OPTS script: - bundle exec rake -- $EXEC_OPTS From fc011dbe38af31b6cf2d77412ee63f79c082fa8d Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 24 Aug 2017 15:55:52 +0100 Subject: [PATCH 025/109] Ensured rvm versions correct and specified gem in specific cases --- .travis.yml | 2 +- Gemfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f1e384ac..ad389bae2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ matrix: env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - rvm: 2.0.0 env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - - rvm: 2.0.0 + - rvm: 2.1.8 env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - rvm: 2.2.4 env: INSTALL_OPTS="--without coverage" diff --git a/Gemfile b/Gemfile index 57968c959..a3dd8b2f0 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ group :test do gem 'rdoc' gem 'pry' gem 'addressable', '~> 2.3.8' - gem 'webmock' + gem 'webmock', '2.3.2' if RUBY_VERSION <= '1.9.3' end group :coverage do From 8326a4b695fd50fe1c60c47ce43031213e4cd380 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 24 Aug 2017 16:01:40 +0100 Subject: [PATCH 026/109] Removed verbosity for easier reading --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ad389bae2..8adb10757 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ before_install: - gem --version install: -- bundle install --verbose $INSTALL_OPTS +- bundle install $INSTALL_OPTS script: - bundle exec rake -- $EXEC_OPTS From de1b8632326741020c5e4dd88063c35e29f9ecd0 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 24 Aug 2017 16:04:26 +0100 Subject: [PATCH 027/109] Ensured webmock installed correctly --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index a3dd8b2f0..60c7d5a79 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ group :test do gem 'rdoc' gem 'pry' gem 'addressable', '~> 2.3.8' - gem 'webmock', '2.3.2' if RUBY_VERSION <= '1.9.3' + gem 'webmock', RUBY_VERSION <= '1.9.3' ? '2.3.2': '>2.3.2' end group :coverage do From 065a25bf84481a757cacf8f1a025763f53640b41 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 24 Aug 2017 16:15:59 +0100 Subject: [PATCH 028/109] Reporting environment options in build --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8adb10757..aaed29f3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,11 @@ before_install: - gem --version install: +- echo $INSTALL_OPTS - bundle install $INSTALL_OPTS script: +- echo $EXEC_OPTS - bundle exec rake -- $EXEC_OPTS env: From b228264fcb5c3f4765ed29a22922773d84c35637 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Fri, 25 Aug 2017 13:57:52 +0100 Subject: [PATCH 029/109] Seeing if issue tracked down --- .travis.yml | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index aaed29f3b..d4886804e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ rvm: - jruby-19mode before_install: +- gem install bundler -v 1.7.6 - gem update --system 2.2 - gem --version @@ -35,20 +36,20 @@ matrix: - rvm: 2.2.4 - rvm: 2.3.0 include: - - rvm: jruby-19mode - env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - - rvm: 1.9.3 - env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - - rvm: 2.0.0 - env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + #- rvm: jruby-19mode + # env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + #- rvm: 1.9.3 + # env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + #- rvm: 2.0.0 + # env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - rvm: 2.1.8 env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - - rvm: 2.2.4 - env: INSTALL_OPTS="--without coverage" - - rvm: 2.3.0 - env: INSTALL_OPTS="--without coverage" - - rvm: 2.3.0 - env: EXEC_OPTS="--coverage" + #- rvm: 2.2.4 + # env: INSTALL_OPTS="--without coverage" + #- rvm: 2.3.0 + # env: INSTALL_OPTS="--without coverage" + #- rvm: 2.3.0 + # env: EXEC_OPTS="--coverage" allow_failures: - - rvm: 2.3.0 - env: EXEC_OPTS="--coverage" \ No newline at end of file + #- rvm: 2.3.0 + # env: EXEC_OPTS="--coverage" \ No newline at end of file From 95e68cb483bdd440d17f94749ef3b7e3b7c543bb Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Fri, 25 Aug 2017 14:01:14 +0100 Subject: [PATCH 030/109] Revert "Seeing if issue tracked down" This reverts commit b228264fcb5c3f4765ed29a22922773d84c35637. --- .travis.yml | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index d4886804e..aaed29f3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ rvm: - jruby-19mode before_install: -- gem install bundler -v 1.7.6 - gem update --system 2.2 - gem --version @@ -36,20 +35,20 @@ matrix: - rvm: 2.2.4 - rvm: 2.3.0 include: - #- rvm: jruby-19mode - # env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - #- rvm: 1.9.3 - # env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - #- rvm: 2.0.0 - # env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + - rvm: jruby-19mode + env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + - rvm: 1.9.3 + env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" + - rvm: 2.0.0 + env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - rvm: 2.1.8 env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - #- rvm: 2.2.4 - # env: INSTALL_OPTS="--without coverage" - #- rvm: 2.3.0 - # env: INSTALL_OPTS="--without coverage" - #- rvm: 2.3.0 - # env: EXEC_OPTS="--coverage" + - rvm: 2.2.4 + env: INSTALL_OPTS="--without coverage" + - rvm: 2.3.0 + env: INSTALL_OPTS="--without coverage" + - rvm: 2.3.0 + env: EXEC_OPTS="--coverage" allow_failures: - #- rvm: 2.3.0 - # env: EXEC_OPTS="--coverage" \ No newline at end of file + - rvm: 2.3.0 + env: EXEC_OPTS="--coverage" \ No newline at end of file From 0e5e9034f492543c807f1b7dee0c960aa454c5d2 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Fri, 25 Aug 2017 14:03:14 +0100 Subject: [PATCH 031/109] Removed bundler caching --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index aaed29f3b..859abd82a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ sudo: false language: ruby -cache: bundler + rvm: - 2.3.0 - 2.2.4 From 38a12e7a9d2b4fab6a953b01763225cd7cf5ae3e Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Fri, 25 Aug 2017 14:10:45 +0100 Subject: [PATCH 032/109] Revert "Removed bundler caching" This reverts commit 0e5e9034f492543c807f1b7dee0c960aa454c5d2. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 859abd82a..aaed29f3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ sudo: false language: ruby - +cache: bundler rvm: - 2.3.0 - 2.2.4 From 7c80ce917131e8947bcd869fbb62d1e400fc44b0 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Fri, 25 Aug 2017 16:59:54 +0100 Subject: [PATCH 033/109] Attempt to force bundler version --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index aaed29f3b..f6f140e0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,8 @@ rvm: - jruby-19mode before_install: +- yes | gem uninstall bundler +- gem install bundler -v 1.13.7 - gem update --system 2.2 - gem --version From fecaff09c566105c654ac56807cf5b5848bdcf9b Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Fri, 25 Aug 2017 17:04:12 +0100 Subject: [PATCH 034/109] Testing bundler without removal --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f6f140e0f..4590fc516 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ rvm: - jruby-19mode before_install: -- yes | gem uninstall bundler - gem install bundler -v 1.13.7 - gem update --system 2.2 - gem --version From 8373b1fb7b7815edff263b653b30f6b7dbaaaa78 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 25 Aug 2017 14:32:43 -0700 Subject: [PATCH 035/109] chore(ci): Switch gemset logic to be additive Simplify logic for installing gems by having the "test" set be the CI default, adding the other sets if needed. Changed to a `jobs` based config to make the setup more declarative and easier to read. --- .travis.yml | 65 +++++++++++++++++------------------------------------ Gemfile | 10 ++++----- Rakefile | 5 ++++- 3 files changed, 30 insertions(+), 50 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4590fc516..e60214bd8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,55 +1,32 @@ sudo: false language: ruby -cache: bundler -rvm: -- 2.3.0 -- 2.2.4 -- 2.1.8 -- 2.0.0 -- 1.9.3 -- jruby-19mode before_install: - gem install bundler -v 1.13.7 -- gem update --system 2.2 -- gem --version install: -- echo $INSTALL_OPTS -- bundle install $INSTALL_OPTS +- bundle install --with "$GEMSETS" script: -- echo $EXEC_OPTS -- bundle exec rake -- $EXEC_OPTS +- bundle exec rake -env: -- INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" -- INSTALL_OPTS="--without coverage" -- EXEC_OPTS="--coverage" - -matrix: - exclude: - - rvm: jruby-19mode - - rvm: 1.9.3 - - rvm: 2.0.0 - - rvm: 2.1.8 - - rvm: 2.2.4 - - rvm: 2.3.0 +jobs: include: - - rvm: jruby-19mode - env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - - rvm: 1.9.3 - env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - - rvm: 2.0.0 - env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - - rvm: 2.1.8 - env: INSTALL_OPTS="--without coverage sidekiq" EXEC_OPTS="--no-sidekiq" - - rvm: 2.2.4 - env: INSTALL_OPTS="--without coverage" - - rvm: 2.3.0 - env: INSTALL_OPTS="--without coverage" - - rvm: 2.3.0 - env: EXEC_OPTS="--coverage" - allow_failures: - - rvm: 2.3.0 - env: EXEC_OPTS="--coverage" \ No newline at end of file + - stage: test + env: GEMSETS=test + rvm: jruby-19mode + - stage: test + env: GEMSETS=test + rvm: 1.9.3 + - stage: test + env: GEMSETS=test + rvm: 2.0.0 + - stage: test + env: GEMSETS=test + rvm: 2.1.8 + - stage: test + env: GEMSETS="test sidekiq" + rvm: 2.2.4 + - stage: test + env: GEMSETS="test sidekiq coverage" + rvm: 2.3.0 diff --git a/Gemfile b/Gemfile index 60c7d5a79..b6eeb9a2c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,21 +1,21 @@ source "https://rubygems.org" -group :test do +group :test, optional: true do gem 'rake', '~> 10.1.1' gem 'rspec' gem 'rdoc' gem 'pry' gem 'addressable', '~> 2.3.8' - gem 'webmock', RUBY_VERSION <= '1.9.3' ? '2.3.2': '>2.3.2' + gem 'webmock', RUBY_VERSION <= '1.9.3' ? '2.3.2': '>2.3.2' end -group :coverage do +group :coverage, optional: true do gem 'simplecov' gem 'coveralls' end -group :sidekiq do +group :sidekiq, optional: true do gem 'sidekiq', '~> 5.0.4' end -gemspec \ No newline at end of file +gemspec diff --git a/Rakefile b/Rakefile index 30742615f..7dc636aa7 100644 --- a/Rakefile +++ b/Rakefile @@ -26,7 +26,10 @@ end require 'rspec/core' require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) do |task| - if ARGV.include? '--no-sidekiq' + begin + require 'sidekiq/testing' + rescue LoadError + puts "Skipping sidekiq tests, missing dependencies" task.rspec_opts = "--exclude-pattern **/integrations/sidekiq_spec.rb" end end From 770aff1df03e19d44ca4de2c6563b7886649f8fd Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 25 Aug 2017 15:17:07 -0700 Subject: [PATCH 036/109] chore(ci): Update Ruby 2.1.8 in build matrix to 2.1.10 Ruby 2.1.10 is the last release of the 2.1.x series, so a better testing benchmark. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e60214bd8..b1e44fdce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ jobs: rvm: 2.0.0 - stage: test env: GEMSETS=test - rvm: 2.1.8 + rvm: 2.1.10 - stage: test env: GEMSETS="test sidekiq" rvm: 2.2.4 From 39d5e69a33051031e029905891d08dee1ddb4171 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 25 Aug 2017 15:28:05 -0700 Subject: [PATCH 037/109] fix(ci): "Upgrade" bundler Dunno what's broken in bundler, but bundling using `--with tests` on 1.13-1.15.4 using Ruby 1.9-2.1 leads to some weirdness which pulls in things not in the optional group specified. Fixed by downgrading to bundler 1.12. > Bundler could not find compatible versions for gem "ruby": > In Gemfile: > ruby > bugsnag was resolved to 4.2.1, which depends on > ruby (>= 1.9.2) > sidekiq (~> 5.0.4) was resolved to 5.0.4, which depends on > ruby (>= 2.2.2) --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b1e44fdce..db24dfab1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,16 @@ sudo: false language: ruby before_install: -- gem install bundler -v 1.13.7 +- gem install bundler -v 1.12 +- gem update --system +- bundle --version +- gem --version install: -- bundle install --with "$GEMSETS" +- bundle _1.12.0_ install --with "$GEMSETS" script: -- bundle exec rake +- bundle _1.12.0_ exec rake jobs: include: From 7c02725eb460ff09310c76e453f3dd6cd9ae0408 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 25 Aug 2017 15:10:49 -0700 Subject: [PATCH 038/109] fix(spec): Skip test made irrelevant by Ruby 2.3.0 improvements Ruby 2.3.0 has a number of improvements to encoding listed in the changelog: https://github.com/ruby/ruby/blob/v2_3_0/ChangeLog --- spec/report_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/report_spec.rb b/spec/report_spec.rb index d939337f4..b3b8ed303 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -761,6 +761,7 @@ def gloops end it "should handle utf8 encoding errors in exceptions_list" do + skip "Irrelevant on newer ruby" if RUBY_VERSION >= '2.3.0' invalid_data = "\"foo\xEBbar\"" invalid_data = invalid_data.force_encoding("utf-8") if invalid_data.respond_to?(:force_encoding) From 2678efc07f77fd3b305aaa762243e0d7cedd4d11 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 25 Aug 2017 15:11:19 -0700 Subject: [PATCH 039/109] fix(spec): Update JRuby test to match impl change in JRuby --- spec/report_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/report_spec.rb b/spec/report_spec.rb index b3b8ed303..7a0947bc8 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -899,8 +899,8 @@ def gloops expect(Bugsnag).to have_sent_notification{ |payload| exception = get_exception_from_payload(payload) - expect(exception["errorClass"]).to eq('Java::JavaLang::ArrayIndexOutOfBoundsException') - expect(exception["message"]).to eq("2") + expect(exception["errorClass"]).to eq('Java::JavaLang::NullPointerException') + expect(exception["message"]).to eq("") expect(exception["stacktrace"].size).to be > 0 } end From f7502b7977875e43376e46262d97e77a556535d1 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Fri, 25 Aug 2017 16:08:27 -0700 Subject: [PATCH 040/109] fix(spec): Fix cleaner to be JSON+Ruby 1.9 compatible --- lib/bugsnag/helpers.rb | 6 +++++- spec/helper_spec.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/bugsnag/helpers.rb b/lib/bugsnag/helpers.rb index d61a769a3..f7f4711b8 100644 --- a/lib/bugsnag/helpers.rb +++ b/lib/bugsnag/helpers.rb @@ -56,7 +56,11 @@ def self.trim_strings_in_value(value) # Validate that the serialized JSON string value is below maximum payload # length def self.payload_too_long?(value) - ::JSON.dump(value).length >= MAX_PAYLOAD_LENGTH + if value.is_a? String + value.length >= MAX_PAYLOAD_LENGTH + else + ::JSON.dump(value).length >= MAX_PAYLOAD_LENGTH + end end def self.trim_strings_in_hash(hash) diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index dcef44be2..a9c486190 100644 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -62,7 +62,7 @@ context "value is a String" do it "trims length" do value = Bugsnag::Helpers.trim_if_needed(SecureRandom.hex(500_000/2)) - expect(::JSON.dump(value.length).length).to be < Bugsnag::Helpers::MAX_STRING_LENGTH + expect(value.length).to be <= Bugsnag::Helpers::MAX_STRING_LENGTH end end From 403d8faf6a6ec4342a5664f99d297754a03ca2ce Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Wed, 20 Sep 2017 09:20:54 -0700 Subject: [PATCH 041/109] Add an upgrade guide (#370) --- UPGRADING.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 UPGRADING.md diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 000000000..cc1e35499 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,57 @@ +# Upgrade Guide + +## 5.x to 6.x + +_Our Ruby library has gone through some major improvements and there are a few +changes required to use the new integrations_ + +#### Capistrano and deploys + +Support for notifying Bugsnag of deployments has been separated into a separate +gem named `bugsnag-capistrano`. See the [integration +guide](https://docs.bugsnag.com/platforms/ruby/capistrano) for more information. + + +#### Configuration + +* `Configuration.use_ssl` has been removed. Include the preferred protocol in `Configuration.endpoint` instead. + ```diff + Bugsnag.configure do |config| + - config.use_ssl = true + - config.endpoint = 'myserver.example.com' + + config.endpoint = 'https://myserver.example.com' + end + ``` +* `Configuration.ignore_classes` now no longer accepts strings. Use classes directly instead. +* `Configuration.delay_with_resque` has been removed +* `Configuration.vendor_paths` has been removed +* `Configuration.params_filters` has been renamed to `Configuration.meta_data_filters` to be clearer + +#### Notifying + +* `notify` now only supports block syntax. Replace usage of the overrides hash with a block + + ```diff + - Bugsnag.notify(e, {severity: 'info'}) + + Bugsnag.notify(e) do |report| + + report.severity = 'info' + + end + ``` + +* `Bugsnag.notify_or_ignore` and `Bugsnag.auto_notify` have been removed removed. Call `notify` directly instead. +* `after_notify_callbacks` has been removed +* `Bugsnag::Notification` has been renamed to `Bugsnag::Report` + +#### Logging + +* `config.debug` boolean has been removed. Set the logger level directly + + ```diff + + require 'logger' + + Bugsnag.configure do |config| + # .. set API key and other properties + - config.debug = true + + config.logger.level = Logger::DEBUG + end + ``` From ff8317a4ceb81870bf838f78161261cbf137e4b2 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Wed, 16 Aug 2017 14:32:08 +0100 Subject: [PATCH 042/109] Fixed incorrect path to bugsnag lib --- example/padrino/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/padrino/Gemfile b/example/padrino/Gemfile index 4b7262071..a7110ab65 100644 --- a/example/padrino/Gemfile +++ b/example/padrino/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' if ENV['INTEGRATION_LANGUAGE'] - gem 'bugsnag', path: '../../../../' + gem 'bugsnag', path: '../../../' else gem 'bugsnag' end From f6ca3bffb79f219539483916d8fc3139a0abba53 Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Wed, 16 Aug 2017 15:45:08 +0100 Subject: [PATCH 043/109] Ensured local bugsnag references correct library location --- example/padrino/Gemfile | 2 +- example/rack/Gemfile | 2 +- example/sinatra/Gemfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/padrino/Gemfile b/example/padrino/Gemfile index a7110ab65..c9c029aeb 100644 --- a/example/padrino/Gemfile +++ b/example/padrino/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' if ENV['INTEGRATION_LANGUAGE'] - gem 'bugsnag', path: '../../../' + gem 'bugsnag', path: '../../' else gem 'bugsnag' end diff --git a/example/rack/Gemfile b/example/rack/Gemfile index dc9792492..42b669e90 100644 --- a/example/rack/Gemfile +++ b/example/rack/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' if ENV['INTEGRATION_LANGUAGE'] - gem 'bugsnag', path: '../../../../' + gem 'bugsnag', path: '../../' else gem 'bugsnag' end diff --git a/example/sinatra/Gemfile b/example/sinatra/Gemfile index a7f9f2c1d..72cbdbaf2 100644 --- a/example/sinatra/Gemfile +++ b/example/sinatra/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' if ENV['INTEGRATION_LANGUAGE'] - gem 'bugsnag', path: '../../../../' + gem 'bugsnag', path: '../../' else gem 'bugsnag' end From 6d8e1744b0889950a06637aa3b44a53193ee06ed Mon Sep 17 00:00:00 2001 From: Alex Moinet Date: Thu, 28 Sep 2017 11:38:54 +0100 Subject: [PATCH 044/109] Updated padrino example --- example/padrino/Gemfile | 2 +- example/padrino/README.md | 95 +++++---------- example/padrino/app/app.rb | 30 ++--- example/padrino/app/templates/index.md | 25 ++++ example/padrino/config/boot.rb | 2 +- example/padrino/config/database.rb | 40 ------- example/padrino/public/favicon.ico | Bin 3784 -> 3942 bytes .../padrino/public/javascripts/application.js | 1 - .../padrino/public/javascripts/jquery-ujs.js | 109 ------------------ example/padrino/public/javascripts/jquery.js | 4 - example/padrino/test/test.rake | 13 --- example/padrino/test/test_config.rb | 32 ----- 12 files changed, 73 insertions(+), 280 deletions(-) create mode 100644 example/padrino/app/templates/index.md delete mode 100644 example/padrino/config/database.rb delete mode 100644 example/padrino/public/javascripts/application.js delete mode 100644 example/padrino/public/javascripts/jquery-ujs.js delete mode 100644 example/padrino/public/javascripts/jquery.js delete mode 100644 example/padrino/test/test.rake delete mode 100644 example/padrino/test/test_config.rb diff --git a/example/padrino/Gemfile b/example/padrino/Gemfile index c9c029aeb..f8d4ba1a1 100644 --- a/example/padrino/Gemfile +++ b/example/padrino/Gemfile @@ -38,7 +38,7 @@ gem 'riot', :group => 'test' gem 'rack-test', :require => 'rack/test', :group => 'test' # Padrino Stable Gem -gem 'padrino', '0.12.2' +gem 'padrino' # Or Padrino Edge # gem 'padrino', :github => 'padrino/padrino-framework' diff --git a/example/padrino/README.md b/example/padrino/README.md index 71aa0d87a..70c0de5aa 100644 --- a/example/padrino/README.md +++ b/example/padrino/README.md @@ -1,39 +1,42 @@ # Bugsnag Padrino demo -This Padrino application demonstrates how to use Bugsnag with Padrino. Before -testing it, open up the `config/boot.rb` file configure your API key inside -`Padrino.before_load`. +This Padrino application demonstrates how to use Bugsnag with Padrino. +Further details about integrating Bugsnag with Rack applications can be found [here.](https://docs.bugsnag.com/platforms/ruby/rack/) -``` -Padrino.before_load do - Bugsnag.configure do |config| - config.api_key = '0a6f5add590596f93a8d601ea89af841' - end -end +Install dependencies + +```shell +bundle install ``` -In the same file activate the Bugsnag Rack middleware. +## Configuring Bugsnag and Padrino -``` -Padrino.after_load do - Padrino.use Bugsnag::Rack -end -``` +1. Set up the Padrino Bugsnag configuration in ```config/boot.rb``` in the `before_load` call according to the [available configuration options](https://docs.bugsnag.com/platforms/ruby/rack/configuration-options/): + ```ruby + Padrino.before_load do + Bugsnag.configure do |config| + config.api_key = 'YOUR_API_KEY' + end + end + ``` -Open up `app/app.rb`, find two options and set them as follows: `raise_errors` -to `true` and `show_exceptions` to `false`. This enables automatic notifications -in the development environment. By default Padrino swallows exceptions from -Bugsnag (only in development, though). +2. Register the Rack middleware in ```config/boot.rb``` in the `after_load` call: + ```ruby + Padrino.after_load do + Padrino.use Bugsnag::Rack + end + ``` -``` +## Capturing errors and exceptions + +In `production` automatic notification of exceptions and errors will be enabled by default. If you want to enable notifications in `development`, open ```app/app.rb``` and set the following options: +```ruby set :raise_errors, true set :show_exceptions, false ``` -If you would like to use custom error handlers, then you need to notify Bugsnag -explicitly. - -``` +When using custom error handlers the errors will not be propogated to Bugsnag. If you still want to notify Bugsnag of these occurences use the `notify` function: +```ruby error 500 do Bugsnag.notify($!) do |report| report.severity = "error" @@ -43,48 +46,12 @@ error 500 do end ``` -Install dependencies. - -``` -bundle install -``` +## Running the example -Launch the Padrino application. +Run the example using: -``` +```shell bundle exec padrino start ``` -Next, open your project's dashboard on Bugsnag. - -1. [crash](http://localhost:9292/crash) -
-Crashes the application and sends a notification about the nature of the crash. -Basically, almost any unhandled exception sends a notification to Bugsnag. See -the line mentioning `get '/crash'` in `app/app.rb`. - -1. [crash and use callbacks](http://localhost:9292/crash_with_callback) -
-Before crashing, the application would append the Diagnostics tab with some -predefined information, attached by means of a callback. See the line mentioning -`get '/crash_with_callback'` in `app/app.rb`. - -1. [notify](http://localhost:9292/notify) -
-Bugsnag Ruby provides a way to send notifications on demand by means of -`Bugsnag.notify`. This API allows to send notifications manually, without -crashing your application. See the line mentioning `get '/notify'` in -`app/app.rb`. - -1. [notify with meta data](http://localhost:9292/notify_meta) -
-Same as `notify`, but also attaches meta data. The meta data is any additional -information you want to attach to an exception. In this artificial case -additional information with be sent and displayed in a new tab called -"Diagnostics". See the line mentioning `get '/notify_meta'` in `app/app.rb`. - -1. [severity](http://localhost:9292/severity) -
-Bugsnag supports three severities: 'error', 'warning' and 'info'. You can set -the severity by passing one of these objects as a string to '#notify'. See the -line mentioning `get '/severity'` in `app/app.rb`. +Once the server is running head to the default path for more information on Bugsnag logging examples. diff --git a/example/padrino/app/app.rb b/example/padrino/app/app.rb index 127e36ebf..3630647ef 100644 --- a/example/padrino/app/app.rb +++ b/example/padrino/app/app.rb @@ -67,7 +67,7 @@ class App < Padrino::Application fenced_code_blocks: true } renderer = Redcarpet::Markdown.new(Redcarpet::Render::HTML, opts) - renderer.render(File.read(File.expand_path('README.md'))) + renderer.render(File.read(File.expand_path('app/templates/index.md'))) end get '/crash' do @@ -76,12 +76,12 @@ class App < Padrino::Application end get '/crash_with_callback' do - Bugsnag.before_notify_callbacks << proc { |notification| + Bugsnag.before_notify_callbacks << proc { |report| new_tab = { message: 'Padrino demo says: Everything is great', code: 200 } - notification.add_tab(:diagnostics, new_tab) + report.add_tab(:diagnostics, new_tab) } msg = 'Bugsnag Padrino demo says: It crashed! But, due to the attached callback' + @@ -98,31 +98,31 @@ class App < Padrino::Application ' for a new notification.' end - get '/notify_meta' do - meta_data = { - :user => { + get '/notify_data' do + error = RuntimeError.new("Bugsnag Padrino demo says: False alarm, your application didn't crash") + Bugsnag.notify error do |report| + report.add_tab(:user, { :username => "bob-hoskins", :email => 'bugsnag@bugsnag.com', :registered_user => true - }, - - :diagnostics => { + }) + report.add_tab(:diagnostics, { :message => 'Padrino demo says: Everything is great', :code => 200 - } - } - error = RuntimeError.new("Bugsnag Padrino demo says: False alarm, your application didn't crash") - Bugsnag.notify(error, meta_data) + }) + end "Bugsnag Padrino demo says: It didn't crash! " + 'But still go check https://bugsnag.com' + ' for a new notification. Check out the User tab for the meta data' end - get '/severity' do + get '/notify_severity' do msg = "Bugsnag Padrino demo says: Look at the circle on the right side. It's different" error = RuntimeError.new(msg) - Bugsnag.notify(error, severity: 'info') + Bugsnag.notify error do |report| + report.severity = 'info' + end msg end end diff --git a/example/padrino/app/templates/index.md b/example/padrino/app/templates/index.md new file mode 100644 index 000000000..4f2d4be06 --- /dev/null +++ b/example/padrino/app/templates/index.md @@ -0,0 +1,25 @@ +# Bugsnag Padrino demo + +This application demonstrates the use of Bugsnag with the Padrino web framework. + +While testing the examples open [your dashboard](https://app.bugsnag.com) in order to see the example errors and exceptions being received. + +1. [Crash](/crash) +
+ Raises an error within the framework, generating a report in the Bugsnag dashboard. + +2. [Crash and use callbacks](/crash_with_callback) +
+ Raises an exception within the framework, but with additional data attached to the report. By registering a callback before the error occurs useful data can be attached as a tab in the Bugsnag dashboard. + +3. [Notify](/notify) +
+ Sends Bugsnag a report on demand using `bugsnag.notify`. Allows details of handled errors or information to be sent to the Bugsnag dashboard without crashing your code. + +4. [Notify with data](/notify_data) +
+ Same as `notify` but allows you to attach additional data within a `block`, similar to the `before_notify_callbacks` example above. In this case we're adding information about the user to go into the `user` tab, and additional diagnostics as a `diagnostics` tab. + +5. [Set the severity](/notify_severity) +
+ This uses the same mechanism as adding meta-data, but allows you to set he `severity` when notifying Bugsnag of the error. Valid severities are `error`, `warning`, and `info`. Have a look on the dashboard to see the difference in these severities. \ No newline at end of file diff --git a/example/padrino/config/boot.rb b/example/padrino/config/boot.rb index e6a8ac9d0..54e54347c 100644 --- a/example/padrino/config/boot.rb +++ b/example/padrino/config/boot.rb @@ -37,7 +37,7 @@ # Padrino.before_load do Bugsnag.configure do |config| - config.api_key = 'f35a2472bd230ac0ab0f52715bbdc65d' + config.api_key = 'YOUR_API_KEY' end end diff --git a/example/padrino/config/database.rb b/example/padrino/config/database.rb deleted file mode 100644 index 801a76d9b..000000000 --- a/example/padrino/config/database.rb +++ /dev/null @@ -1,40 +0,0 @@ -## -# A MySQL connection: -# DataMapper.setup(:default, 'mysql://user:password@localhost/the_database_name') -# -# # A Postgres connection: -# DataMapper.setup(:default, 'postgres://user:password@localhost/the_database_name') -# -# # A Sqlite3 connection -# DataMapper.setup(:default, "sqlite3://" + Padrino.root('db', "development.db")) -# -# # Setup DataMapper using config/database.yml -# DataMapper.setup(:default, YAML.load_file(Padrino.root('config/database.yml'))[RACK_ENV]) -# -# config/database.yml file: -# -# --- -# development: &defaults -# adapter: mysql -# database: example_development -# username: user -# password: Pa55w0rd -# host: 127.0.0.1 -# -# test: -# <<: *defaults -# database: example_test -# -# production: -# <<: *defaults -# database: example_production -# - -DataMapper.logger = logger -DataMapper::Property::String.length(255) - -case Padrino.env - when :development then DataMapper.setup(:default, "sqlite3://" + Padrino.root('db', "bugsnag_padrino_development.db")) - when :production then DataMapper.setup(:default, "sqlite3://" + Padrino.root('db', "bugsnag_padrino_production.db")) - when :test then DataMapper.setup(:default, "sqlite3://" + Padrino.root('db', "bugsnag_padrino_test.db")) -end diff --git a/example/padrino/public/favicon.ico b/example/padrino/public/favicon.ico index 4e26b1989902dcfd1644229471b18fd981860f1c..ae9b9f3f5cd5e0cfcac98e546a958ebc2e7dd3a3 100644 GIT binary patch literal 3942 zcmZu!2{=@L+dgAyBxTJ`W02k0m%-S#7}+Ug9c#=OjM0cGTNnx1ifBb65@pSrWX<2c z?|YFcd&)=u|NFk*`(5Afy3TKT&U4?-eLv@1=ekb3xtZZvdTx3E0L~g4=~*7HE+-e= zspEMrD^%rpq4Cm2Y6C!7@|iiI05KW>4vs^_6#xj40f03Z0Dxx! z00$z# zCj)-GKMBL2kUuHGei~4Fq&Y+vjYUBer4^)Qpa^;h1fq`h@Pu3H8T;D#6Ds&+tPko~?ujUbtok4N|BC%bocb|4To;RS4MtwILm>jEW#RA)T;9HmQqx_i; z4%&>itQlt7CO#O3#XUmX1rdMR!BNtF-HB2RyOxXVtSqFiE03^i-Q10>ecqzxM1D1#s8H{hv?< za5*zmGDuX)X0hdUgt_nRU%HNYA;=o z?p5H2w(m)d%G9X#5#!Mf0PPdTUCS%k#_7yP&V2Qrzp03=BrrWpqJt{lm;CBnz7x{i zjr_Vys=8_{$)-`5zBPVKakjekkplW-AC@MP&)3gnhE>I90JfH=VHD#RPdH`%$>+NF z1Sz59qy2@u1|$gva{c{*`+>OFXH~qAyldJcr1b#5r&FKKgE(FX;&S;U;~-;;G~*Zc z7P1u9DzSMTpR7FBB)f^#!ik&a!hLSzC9Lk2l;-px@p({FtHf90G${*0N9$gGaUgti zl~C^~?i}xT%DfBh@j4sicUg9t$pRFul>lT6)AXOiG!F0Ts+-BYrR%r-A?HrcNhw{Y zX^zDiS-e=~g@&l8I?TJ@QPj>eFiMk7i}_9av3ulWq=rWN)9qD03-9QRTcZ zO~CIdf7TxV$^EG|-zOHMn6vp+(@YEz0nZwX5)B}Oiyblqb9qN<(Gq=fsAw6}OcPR> z#$rWVSHFy;y3I`|GcZWnmP|EaV#8J<|CkuI?DX+BcYwteuofxkX z25<7yNc#h$s>h*`g}?EFnzxmVIJ_k-Cwj2I$<0q$qsx=W4-J` zbWwW;*Xg*ON5r7zhOgX$4Km2^W3iI~C{+>&lJoxC~?X>jl6w5@EEw z2Q1BF)m9w%rS?3|HR-S|5TyQ-Lg6$MK49na>^lmzAo)p20df$BVSAcHqY=KyBoueS zMIXPXleQC4GgElID!l#vl*k3isEv)@tlo_X5u*BM&XKLAp8~nn*!O7> zJgAtz9&uChS<6O*;aZDY2j91j2b||)7p5+y-uHSjTc=)~y)T1q>X#8dy0+3+{qXVL z7Qw5L=+L5sIykJ7VQok~wZ-*}4Y|z7c3H05kK-wO&l~D2U?0b2@g@79H~0IZ(=J0M zpDaQ-+|>r(ZqX7%5YK#po%_rgSljh;CnE$X8(tE9@1dM+e8wv!pl-Hj+C5Y^mXYuL z^Y0r3c~{EBGKQI#`{kEOGEDR?Ju{Nsn=S1yCUJRoQTexz<;(2umy@1n!-bnwzn49i zXiLizq>a+AvZGbgl;;oA3F0vdg!5G1=jf@L5#=xOWv+`NB_oU30;e7iY$ws+9KILTo8 zmlc0{_F>y^utbNmJbQ1Euv&H{DaG$d|M4ApdmLQ9%kjg{&GS<=4+#2j*~+ z7b7&gh}M4-%U0wUQQw=dee9PRU{07-ult$&xhv}&9jXg`p+O{&2lFyUI~Hu$0rL571F1%^WIkB zIJwa^;JIsF#6I)kf!+BDK=eD?rGsnJ&h?JlH_~;KGUsnT*}A3NQKuHDy=*LqZU$=^ z8?E(Yba=IH=Bqeb>^qG7oaHicdXQ=-+PSV;m8e#XN~+P#*j^v)L8+z-R|JC=-ti%B+#|Or5bh%=IWNK6ei>E^>s_bOA%@MyiX z)Ohw{O{7!0QtH+FBQ$E+Uo0BOj}!#rdB5;AtJ$EANzQq6a5z)LnjMS6uv( zZpK8nxRO71$u#&{F-Y=d3)$C>AcMmrZm(XBpO!? zT{YF8pI9Ke%x8EgmpQ?;sJ-@`8WZf;ZPLy1;vS2JHH5$Y*=V*>1#)JYGxeLk0$z=J zfYUG2Yq0UxBrAW7Z{>ff8NSu<_)Ss_Fw0wX`InsM`TAOWw)T64U*<&`Zik2rW60BQ z(IUNZ%CE*hp%aH=nHxkCEM{1b(iH)drjOeO^f@&u%)Lz)x#mw5- zX=s)rBet6buGT#8B05Uc7q>3-&gpAi7?uIwal3@7d%?T!O+&(*4bswo&VfdP`9$>G zy|Z=jEG0MSn|ZZNOZ?!{z3#Y`L%I*m@*SWa$rZYrT|B0Nkh4Ok4*w4qMQsc20Rcjl6^a$$Y|!7TrmBZMy=Ec9|gx~L1f>PoOvz+y)!pvC`w|c%5jdwam{JVOvu)=w3=7zQsuX) zHC>Sb6|=^_zOW=5?G3l4do~Tt&C_f9(w4vYC6{O1SF=-f(R_qiuMD#$@tZ~iTDKAKI2!eOz;_#$1IJW z+=~ibPjq%Hf0n@ED1Z14PGqn&TI z(F(F~XuNb@=c0JIdnPOS^U0U1YN5$%bGg?AQNC|?h3yw^i7xhnZqM!X(xbj{0SE$#u zYNM1h<}VetCEo}y^f;42K05TIO?R;IRXo?L6N9tVL-+3aAjL!%XC(3eKvO+k3bt0)fY5!dETA;;u`Cl>6PfX-2NZiH6-Ey literal 3784 zcmV;(4ma_MP)0kH!R2SOD{fxHQ_;w%vFhbsODmDC1e zKM(^TfY8GM1g=08zyL_$CM13#5Kq9BUjCzL27%9L{C-^N2?jLKqHjNx-Ug+YB3sIW zCcprVnAbpj2Z)t{SP&H6P_HpSV-h8?+($DY8;I`$aTE}XK%@USmRM#$4#fXx;-*ln z0F53?Af5rlx@h5sHMuGR@o8wvEQN}t0C5ozTLAGos8}1efCCmRV(8%k3e1hTT!0)PwP>1ipeb${nx_9qMGf2lP?^(&q$mQ2 zrvPy8#|$~aT;AM3zodT7l@sJ_!yP|&I01;$N~TX zD6e{uhGHOGUqKYAIJne5&_P`k1rbHuodq{R9i6+0;NWUqoD@W%T?N6RgM)&KI;ltp zwWC4{N^x-LP@XyY0v{ni&x7N=kX&*}?!NEh!vj+;z&uH$2`JnR4U9Z})5IS!EiOAK z;{Oym_D98Jn^u}9+s1z!*nrW!!{!GS#2)Tth~ibG&tlWcWazts*s0NF`A- z)Se>r7a|*Q%qxjq5C>KQyBUX;u(K{BIIEx--97-HU4c#0p1GGJtx_+)?|6xHRZM&ePw4{wLsT1=U`Pz8V8&h+cEV z#XTVh$`+u=QgNF{jkbt%j8UuA!BZmdO0Sg;qdh@$X=2#KS}Y5U1F?~(4eZL4IUKQ0 z$t+S;zGvXTip8!QF{EWjHfFMs29rrFZo~dHd{2OcZ@7XNa{ipgA1i${79Z1NJ2u<`j$1BV>9H|__($BtRLuYD z1-18CsY5SW3#Ok|1uHK^G~(HOcQ`(Xj+R9r7<0_EjjJ`^EAaLF*Fb%u);e$~ zVp6dJtvUL=MZl$WMEiaT<#|_vj4HQjIIg1GWz~X$N&8MVEW=s7!F0^JTT=-(w;D?w zdc!sGe7Tx8&xvLdY;G)_N-$}U5p=?FiZ0m0J!d%b9Iscw84V+@NZ7Qt#5wSl#Y_s` zC$-WH-8WWp?8C%HlFnIH5Hq2Ih2NkR%{u1%0({K7)Vw%a5a+;RAlMb;5m&v)!ip++ zZy;F_0UBpBNn+p(U91o3c^{iTMKg)pZgB)cSTXo|Wwn zhT~{B4p1E}0x}%3Q%K#dIMwKrdqe9t8{c&NqYH$4f_twb28PHAqph1?Yc_>r2UxH} zwA!ptO9sUCXzNWunT{yz-Xx@#G_UK>21a2%-Dr>jX_|I8@K;tvrkT@CsX%ZysTfzQ zR=k=`+99AX+2p#=fcgbwTkrU`ceomBoe~k52J=+rCxIowhv{JFIL8s&RStCXdxk8z76}&-4@=345}tSPyxF6w zrJ_LTL#3Eh(qPW36t&o{Ucj(`>p2cE{pi?yQ4(TufnsBA12C&g0MX0bv8CQtni=PB zBmEPB2}^Pva^V&nF;8WR_(APf^5|Pp9B?6t_wA5_cA&G|N!qGV-tCTYIAI0k|7wjzm{trx zRw%b+7RDarC6)(uz-`dsp&=77mQld#z&-ux+8lR^cp&Z3U#mL`qboIVzZrs1h+96c%ITo;)*raM3ox+y{q zmp3xo_K3#i|H2u=QfGs}yuAhlhH#!bj=T|WV46U`On6}h& zfN0soo^OXwoYsVe0c3~agnbg}CGMdaUPX~o5pm8k{s?h^bhmfqiCRB}Ui6HS{_M$(u2>++qHi8E~qOXYRFhjXV-L|46^@LEK zgV^A>GLE!I$gLYeZ+W}R3|;S}rf4`e#HTzy!M)uo^VsS+0CYUh5l|3Q-kN_<6PCA_ zXPhL=KY##T;IViw+l2$KgM?k|p(S4rh|X0$M*$<)zj4C1Jcox!Ke7cSt_TV(lYNrK zEYyWiYX45s-$CB=OX!s`o~QGZI?!Gs^#-EsO&H|3939Xl1AP|p=2lWr0-&pA;&pt# zlzl#o5Ki*lWe|}?9fl6SCJhS zQ#p>uDPe4NjKLQLxcGS{QOcfo&V|e-0yYuevYp-s;ooID+{~L@4rmHTkbcC62%S3t zx!ZZ;jg-G-MnU8yawbTbk~*E{Ns!X{bteK8QQ%hx3}=5I_K?1&F@by8%SD9!D8l#v z6m~Q6mms_xrr6C}p2=RW!eJXI$m)%8E(NdrjG8k?D?O%;~@=>oGDs`04=TL{Y~ zmhUB`-+<&)5ypE7{m0n%N$716QS>d>O3wcoiETT3x{d8KhU=duQ?yZb`&kP>*}yjR zB5kFpRA=SYJJ{RfJ(S;e3LXj7{T`LuFs;<=LFnj!42{;LwG_B9mhyL{ayw(?0P6s* z7me@-lqm`8^;w9}XiD&I%lH34(Ei9Kh}2siFoSfpfPrj>Q4*BIy(;sNV+oPw9A-*^ zA%Z7=ve;j>OrpSnO0X*7gHI(y@CM5@U&1&$^$-2}0`f+Gg3x%92;SsKW$)OmWicIE zv^mF)9|9X3WoI5(GDu~1^(B-)WpVQ2e=)BBWvA7OpAGCznc z?i`1-^#O*G9j1a6uNvP^*9cHrQ;a4WRCy$zIEufI5h)*OnBdUwB+)Y8GN*N{Evv>6 zr8PDJsoC};h25~iGU@{ true -**/ - -$(function(){ - $('form').on('submit', function(e) { - var element = $(this), message = element.data('confirm'); - if (message && !confirm(message)) { return false; } - if (element.data('remote') == true) { - e.preventDefault(); e.stopped = true; - JSAdapter.sendRequest(element, { - verb: element.data('method') || element.attr('method') || 'post', - url: element.attr('action'), - dataType: element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType) || 'script', - params: element.serializeArray() - }); - } - }); - - /* Confirmation Support - * link_to 'sign out', '/logout', :confirm => 'Log out?' - **/ - - $(document).on('click', 'a[data-confirm]', function(e) { - var message = $(this).data('confirm'); - if (!confirm(message)) { e.preventDefault(); e.stopped = true; } - }); - - /* - * Link Remote Support - * link_to 'add item', '/create', :remote => true - **/ - - $(document).on('click', 'a[data-remote=true]', function(e) { - var element = $(this); - if (e.stopped) return; - e.preventDefault(); e.stopped = true; - JSAdapter.sendRequest(element, { - verb: element.data('method') || 'get', - url: element.attr('href') - }); - }); - - /* - * Link Method Support - * link_to 'delete item', '/destroy', :method => :delete - **/ - - $(document).on('click', 'a[data-method]:not([data-remote])', function(e) { - if (e.stopped) return; - JSAdapter.sendMethod($(this)); - e.preventDefault(); e.stopped = true; - }); - - /* JSAdapter */ - var JSAdapter = { - // Sends an xhr request to the specified url with given verb and params - // JSAdapter.sendRequest(element, { verb: 'put', url : '...', params: {} }); - sendRequest: function(element, options) { - var verb = options.verb, url = options.url, params = options.params, dataType = options.dataType; - var event = element.trigger('ajax:before'); - if (event.stopped) return false; - $.ajax({ - url: url, - type: verb.toUpperCase() || 'POST', - data: params || [], - dataType: dataType, - - beforeSend: function(request) { element.trigger('ajax:loading', [ request ]); }, - complete: function(request) { element.trigger('ajax:complete', [ request ]); }, - success: function(request) { element.trigger('ajax:success', [ request ]); }, - error: function(request) { element.trigger('ajax:failure', [ request ]); } - }); - element.trigger('ajax:after'); - }, - // Triggers a particular method verb to be triggered in a form posting to the url - // JSAdapter.sendMethod(element); - sendMethod: function(element) { - var verb = element.data('method'); - var url = element.attr('href'); - var form = $('
'); - var csrf_token = $('meta[name=csrf-token]').attr('content'); - var csrf_param = $('meta[name=csrf-param]').attr('content'); - form.hide().appendTo('body'); - if (verb !== 'post') { - var field = ''; - form.append(field); - } - if (csrf_param !== undefined && csrf_token !== undefined) { - var field = ''; - form.append(field); - } - form.submit(); - } - }; - - // Every xhr request is sent along with the CSRF token. - $.ajaxPrefilter(function(options, originalOptions, xhr) { - if (options.verb !== 'GET') { - var token = $('meta[name="csrf-token"]').attr('content'); - if (token) xhr.setRequestHeader('X-CSRF-Token', token); - } - }); -}); diff --git a/example/padrino/public/javascripts/jquery.js b/example/padrino/public/javascripts/jquery.js deleted file mode 100644 index 50d1b22f2..000000000 --- a/example/padrino/public/javascripts/jquery.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("