dfg-airbrake 5.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake.rb +57 -0
  3. data/lib/airbrake/capistrano/tasks.rb +65 -0
  4. data/lib/airbrake/delayed_job/plugin1.rb +52 -0
  5. data/lib/airbrake/rack/middleware.rb +59 -0
  6. data/lib/airbrake/rack/notice_builder.rb +125 -0
  7. data/lib/airbrake/rack/user.rb +61 -0
  8. data/lib/airbrake/rails/action_controller.rb +37 -0
  9. data/lib/airbrake/rails/active_job.rb +35 -0
  10. data/lib/airbrake/rails/active_record.rb +36 -0
  11. data/lib/airbrake/rails/railtie.rb +79 -0
  12. data/lib/airbrake/rake/task_ext.rb +66 -0
  13. data/lib/airbrake/rake/tasks.rb +118 -0
  14. data/lib/airbrake/resque/failure.rb +19 -0
  15. data/lib/airbrake/sidekiq/error_handler.rb +35 -0
  16. data/lib/airbrake/version.rb +6 -0
  17. data/lib/generators/airbrake_generator.rb +25 -0
  18. data/lib/generators/airbrake_initializer.rb.erb +68 -0
  19. data/spec/airbrake_spec.rb +17 -0
  20. data/spec/apps/rack/dummy_app.rb +17 -0
  21. data/spec/apps/rails/dummy_app.rb +156 -0
  22. data/spec/apps/rails/dummy_task.rake +20 -0
  23. data/spec/apps/sinatra/composite_app/sinatra_app1.rb +11 -0
  24. data/spec/apps/sinatra/composite_app/sinatra_app2.rb +11 -0
  25. data/spec/apps/sinatra/dummy_app.rb +12 -0
  26. data/spec/integration/rack/rack_spec.rb +17 -0
  27. data/spec/integration/rails/rails_spec.rb +216 -0
  28. data/spec/integration/rails/rake_spec.rb +160 -0
  29. data/spec/integration/shared_examples/rack_examples.rb +126 -0
  30. data/spec/integration/sinatra/sinatra_spec.rb +77 -0
  31. data/spec/spec_helper.rb +116 -0
  32. data/spec/unit/rack/middleware_spec.rb +136 -0
  33. data/spec/unit/rack/notice_builder_spec.rb +157 -0
  34. data/spec/unit/rack/user_spec.rb +172 -0
  35. data/spec/unit/rake/tasks_spec.rb +67 -0
  36. data/spec/unit/sidekiq/error_handler_spec.rb +33 -0
  37. metadata +247 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 060fe33fe27f61ec2e753fc7b3fc753b4f8fd4a7
4
+ data.tar.gz: 14a5b8ba79abbd1d6b19ffb053e2e04828e2a834
5
+ SHA512:
6
+ metadata.gz: d6a5aebe268567624f63f359e99b391158fc306b733c94f9e414f8755bfe164dba5fa8448671a386ba3f7799a07106d38324d53a66b96e5a0a3e482924dfa2d6
7
+ data.tar.gz: af44f375f3653d02d0f5a203ff8feec03dc05823bbc6e4c1c2486a023d5144677a4c927e5f1af372dc41e15bb681269d5e7ab8672feaeea492b57dfb6f99a7ea
@@ -0,0 +1,57 @@
1
+ require 'shellwords'
2
+ require 'English'
3
+
4
+ # Core library that sends notices.
5
+ # See: https://github.com/airbrake/airbrake-ruby
6
+ require 'airbrake-ruby'
7
+
8
+ require 'airbrake/version'
9
+
10
+ # Automatically load needed files for the environment the library is running in.
11
+ if defined?(Rack)
12
+ require 'airbrake/rack/user'
13
+ require 'airbrake/rack/notice_builder'
14
+ require 'airbrake/rack/middleware'
15
+
16
+ require 'airbrake/rails/railtie' if defined?(Rails)
17
+ end
18
+
19
+ require 'airbrake/rake/task_ext' if defined?(Rake::Task)
20
+ require 'airbrake/resque/failure' if defined?(Resque)
21
+ require 'airbrake/sidekiq/error_handler' if defined?(Sidekiq)
22
+ # 2016-12-19
23
+ # https://meta.discourse.org/t/54462
24
+ # «The Plugin::Instance.find_all method incorrectly treats every file with the «plugin.rb» name
25
+ # as a Discourse plugin».
26
+ require 'airbrake/delayed_job/plugin1' if defined?(Delayed)
27
+
28
+ ##
29
+ # This module reopens the original Airbrake module from airbrake-ruby and adds
30
+ # integration specific methods.
31
+ module Airbrake
32
+ class << self
33
+ ##
34
+ # Attaches a callback (builder) that runs every time the Rack integration
35
+ # reports an error. Can be used to attach additional data from the Rack
36
+ # request.
37
+ #
38
+ # @example Adding remote IP from the Rack environment
39
+ # Airbrake.add_rack_builder do |notice, request|
40
+ # notice[:params][:remoteIp] = request.env['REMOTE_IP']
41
+ # end
42
+ #
43
+ # @yieldparam notice [Airbrake::Notice] notice that will be sent to Airbrake
44
+ # @yieldparam request [Rack::Request] current rack request
45
+ # @yieldreturn [void]
46
+ # @return [void]
47
+ # @since 5.1.0
48
+ def add_rack_builder(&block)
49
+ Airbrake::Rack::NoticeBuilder.add_builder(&block)
50
+ end
51
+ end
52
+ end
53
+
54
+ # Notify of unhandled exceptions, if there were any, but ignore SystemExit.
55
+ at_exit do
56
+ Airbrake.notify_sync($ERROR_INFO) if $ERROR_INFO
57
+ end
@@ -0,0 +1,65 @@
1
+ if defined?(Capistrano::VERSION) &&
2
+ Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
3
+ namespace :airbrake do
4
+ desc "Notify Airbrake of the deploy"
5
+ task :deploy do
6
+ role = roles(:all, select: :primary).first || roles(:all).first
7
+ on role do
8
+ within release_path do
9
+ with rails_env: fetch(:rails_env, fetch(:stage)) do
10
+ execute :bundle, :exec, :rake, <<-CMD
11
+ airbrake:deploy USERNAME=#{Shellwords.shellescape(local_user)} \
12
+ ENVIRONMENT=#{fetch(:airbrake_env, fetch(:rails_env, fetch(:stage)))} \
13
+ REVISION=#{fetch(:current_revision)} \
14
+ REPOSITORY=#{fetch(:repo_url)} \
15
+ VERSION=#{fetch(:app_version)}
16
+ CMD
17
+
18
+ info 'Notified Airbrake of the deploy'
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ else
25
+ module Airbrake
26
+ ##
27
+ # The Capistrano v2 integration.
28
+ module Capistrano
29
+ # rubocop:disable Metrics/AbcSize
30
+ def self.load_into(config)
31
+ config.load do
32
+ after 'deploy', 'airbrake:deploy'
33
+ after 'deploy:migrations', 'airbrake:deploy'
34
+ after 'deploy:cold', 'airbrake:deploy'
35
+
36
+ namespace :airbrake do
37
+ desc "Notify Airbrake of the deploy"
38
+ task :deploy, except: { no_release: true }, on_error: :continue do
39
+ username = Shellwords.shellescape(ENV['USER'] || ENV['USERNAME'])
40
+ command = <<-CMD
41
+ cd #{config.release_path} && \
42
+
43
+ RACK_ENV=#{fetch(:rack_env, nil)} \
44
+ RAILS_ENV=#{fetch(:rails_env, nil)} \
45
+
46
+ bundle exec rake airbrake:deploy \
47
+ USERNAME=#{username} \
48
+ ENVIRONMENT=#{fetch(:rails_env, 'production')} \
49
+ REVISION=#{current_revision.strip} \
50
+ REPOSITORY=#{repository} \
51
+ VERSION=#{fetch(:app_version, nil)}
52
+ CMD
53
+
54
+ run(command, once: true)
55
+ logger.info 'Notified Airbrake of the deploy'
56
+ end
57
+ end
58
+ end
59
+ end
60
+ # rubocop:enable Metrics/AbcSize
61
+ end
62
+ end
63
+
64
+ Airbrake::Capistrano.load_into(Capistrano::Configuration.instance)
65
+ end
@@ -0,0 +1,52 @@
1
+ # 2016-12-19
2
+ # https://meta.discourse.org/t/54462
3
+ # «The Plugin::Instance.find_all method incorrectly treats every file with the «plugin.rb» name
4
+ # as a Discourse plugin».
5
+ module Delayed
6
+ module Plugins
7
+ ##
8
+ # Provides integration with Delayed Job.
9
+ # rubocop:disable Lint/RescueException
10
+ class Airbrake < ::Delayed::Plugin
11
+ callbacks do |lifecycle|
12
+ lifecycle.around(:invoke_job) do |job, *args, &block|
13
+ begin
14
+ # Forward the call to the next callback in the callback chain
15
+ block.call(job, *args)
16
+ rescue Exception => exception
17
+ params = job.as_json.merge(
18
+ component: 'delayed_job',
19
+ action: job.payload_object.class.name
20
+ )
21
+
22
+ # If DelayedJob is used through ActiveJob, it contains extra info.
23
+ if job.payload_object.respond_to?(:job_data)
24
+ params[:active_job] = job.payload_object.job_data
25
+ end
26
+
27
+ ::Airbrake.notify(exception, params)
28
+ raise exception
29
+ end
30
+ end
31
+ end
32
+ end
33
+ # rubocop:enable Lint/RescueException
34
+ end
35
+ end
36
+
37
+ if RUBY_ENGINE == 'jruby' && defined?(Delayed::Backend::ActiveRecord::Job)
38
+ ##
39
+ # Workaround against JRuby bug:
40
+ # https://github.com/jruby/jruby/issues/3338
41
+ # rubocop:disable Style/ClassAndModuleChildren
42
+ class Delayed::Backend::ActiveRecord::Job
43
+ alias old_to_ary to_ary
44
+
45
+ def to_ary
46
+ old_to_ary || [self]
47
+ end
48
+ end
49
+ # rubocop:enable Style/ClassAndModuleChildren
50
+ end
51
+
52
+ Delayed::Worker.plugins << Delayed::Plugins::Airbrake
@@ -0,0 +1,59 @@
1
+ module Airbrake
2
+ module Rack
3
+ ##
4
+ # Airbrake Rack middleware for Rails and Sinatra applications (or any other
5
+ # Rack-compliant app). Any errors raised by the upstream application will be
6
+ # delivered to Airbrake and re-raised.
7
+ #
8
+ # The middleware automatically sends information about the framework that
9
+ # uses it (name and version).
10
+ class Middleware
11
+ def initialize(app, notifier_name = :default)
12
+ @app = app
13
+ @notifier_name = notifier_name
14
+ end
15
+
16
+ ##
17
+ # Rescues any exceptions, sends them to Airbrake and re-raises the
18
+ # exception.
19
+ # @param [Hash] env the Rack environment
20
+ def call(env)
21
+ # rubocop:disable Lint/RescueException
22
+ begin
23
+ response = @app.call(env)
24
+ rescue Exception => ex
25
+ notify_airbrake(ex, env)
26
+ raise ex
27
+ end
28
+ # rubocop:enable Lint/RescueException
29
+
30
+ exception = framework_exception(env)
31
+ notify_airbrake(exception, env) if exception
32
+
33
+ response
34
+ end
35
+
36
+ private
37
+
38
+ def notify_airbrake(exception, env)
39
+ notice = NoticeBuilder.new(env, @notifier_name).build_notice(exception)
40
+ return unless notice
41
+
42
+ Airbrake.notify(notice, {}, @notifier_name)
43
+ end
44
+
45
+ ##
46
+ # Web framework middlewares often store rescued exceptions inside the
47
+ # Rack env, but Rack doesn't have a standard key for it:
48
+ #
49
+ # - Rails uses action_dispatch.exception: https://goo.gl/Kd694n
50
+ # - Sinatra uses sinatra.error: https://goo.gl/LLkVL9
51
+ # - Goliath uses rack.exception: https://goo.gl/i7e1nA
52
+ def framework_exception(env)
53
+ env['action_dispatch.exception'] ||
54
+ env['sinatra.error'] ||
55
+ env['rack.exception']
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,125 @@
1
+ module Airbrake
2
+ module Rack
3
+ ##
4
+ # A helper class for filling notices with all sorts of useful information
5
+ # coming from the Rack environment.
6
+ class NoticeBuilder
7
+ @builders = []
8
+
9
+ class << self
10
+ ##
11
+ # @return [Array<Proc>] the list of notice builders
12
+ attr_reader :builders
13
+
14
+ ##
15
+ # Adds user defined builders to the chain.
16
+ def add_builder(&block)
17
+ @builders << block
18
+ end
19
+ end
20
+
21
+ ##
22
+ # @return [Array<String>] the prefixes of the majority of HTTP headers in
23
+ # Rack (some prefixes match the header names for simplicity)
24
+ HTTP_HEADER_PREFIXES = [
25
+ 'HTTP_'.freeze,
26
+ 'CONTENT_TYPE'.freeze,
27
+ 'CONTENT_LENGTH'.freeze
28
+ ].freeze
29
+
30
+ ##
31
+ # @param [Hash{String=>Object}] rack_env The Rack environment
32
+ def initialize(rack_env, notifier_name = :default)
33
+ @rack_env = rack_env
34
+ @notifier_name = notifier_name
35
+ @request = ::Rack::Request.new(rack_env)
36
+ end
37
+
38
+ ##
39
+ # Adds context, session, params and other fields based on the Rack env.
40
+ #
41
+ # @param [Exception] exception
42
+ # @return [Airbrake::Notice] the notice with extra information
43
+ def build_notice(exception)
44
+ return unless (notice = Airbrake.build_notice(exception, {}, @notifier_name))
45
+
46
+ NoticeBuilder.builders.each { |builder| builder.call(notice, @request) }
47
+ notice
48
+ end
49
+
50
+ ##
51
+ # Adds context (URL, User-Agent, framework version, controller and more).
52
+ add_builder do |notice, request|
53
+ context = notice[:context]
54
+
55
+ context[:url] = request.url
56
+ context[:userAgent] = request.user_agent
57
+
58
+ framework_version =
59
+ if defined?(::Rails)
60
+ "Rails/#{::Rails.version}"
61
+ elsif defined?(::Sinatra)
62
+ "Sinatra/#{Sinatra::VERSION}"
63
+ else
64
+ "Rack.version/#{::Rack.version} Rack.release/#{::Rack.release}"
65
+ end.freeze
66
+
67
+ if context.key?(:version)
68
+ context[:version] += " #{framework_version}"
69
+ else
70
+ context[:version] = framework_version
71
+ end
72
+
73
+ controller = request.env['action_controller.instance']
74
+ if controller
75
+ context[:component] = controller.controller_name
76
+ context[:action] = controller.action_name
77
+ end
78
+
79
+ user = Airbrake::Rack::User.extract(request.env)
80
+ notice[:context].merge!(user.as_json) if user
81
+ end
82
+
83
+ ##
84
+ # Adds session.
85
+ add_builder do |notice, request|
86
+ session = request.session
87
+ notice[:session] = session if session
88
+ end
89
+
90
+ ##
91
+ # Adds HTTP request parameters.
92
+ add_builder do |notice, request|
93
+ notice[:params] = request.params
94
+
95
+ rails_params = request.env['action_dispatch.request.parameters']
96
+ notice[:params].merge!(rails_params) if rails_params
97
+ end
98
+
99
+ ##
100
+ # Adds HTTP referer, method and headers to the environment.
101
+ add_builder do |notice, request|
102
+ http_headers = request.env.map.with_object({}) do |(key, value), headers|
103
+ if HTTP_HEADER_PREFIXES.any? { |prefix| key.to_s.start_with?(prefix) }
104
+ headers[key] = value
105
+ end
106
+
107
+ headers
108
+ end
109
+
110
+ notice[:environment].merge!(
111
+ httpMethod: request.request_method,
112
+ referer: request.referer,
113
+ headers: http_headers
114
+ )
115
+
116
+ if request.body
117
+ notice[:environment][:body] = request.body.read(4096)
118
+ request.body.rewind
119
+ end
120
+
121
+ notice[:environment]
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,61 @@
1
+ module Airbrake
2
+ module Rack
3
+ ##
4
+ # Represents an authenticated user, which can be converted to Airbrake's
5
+ # payload format. Supports Warden and Omniauth authentication frameworks.
6
+ class User
7
+ # Finds the user in the Rack environment and creates a new user wrapper.
8
+ #
9
+ # @param [Hash{String=>Object}] rack_env The Rack environment
10
+ # @return [Airbrake::Rack::User, nil]
11
+ def self.extract(rack_env)
12
+ # Warden support (including Devise).
13
+ if (warden = rack_env['warden'])
14
+ if (user = warden.user(run_callbacks: false))
15
+ return new(user) if user
16
+ end
17
+ end
18
+
19
+ # Fallback mode (OmniAuth support included). Works only for Rails.
20
+ user = try_current_user(rack_env)
21
+ new(user) if user
22
+ end
23
+
24
+ def self.try_current_user(rack_env)
25
+ controller = rack_env['action_controller.instance']
26
+ return unless controller.respond_to?(:current_user)
27
+ return unless [-1, 0].include?(controller.method(:current_user).arity)
28
+ controller.current_user
29
+ end
30
+ private_class_method :try_current_user
31
+
32
+ def initialize(user)
33
+ @user = user
34
+ end
35
+
36
+ def as_json
37
+ user = {}
38
+
39
+ user[:id] = try_to_get(:id)
40
+ user[:name] = full_name
41
+ user[:username] = try_to_get(:username)
42
+ user[:email] = try_to_get(:email)
43
+
44
+ user = user.delete_if { |_key, val| val.nil? }
45
+ user.empty? ? user : { user: user }
46
+ end
47
+
48
+ private
49
+
50
+ def try_to_get(key)
51
+ String(@user.__send__(key)) if @user.respond_to?(key)
52
+ end
53
+
54
+ def full_name
55
+ # Try to get first and last names. If that fails, try to get just 'name'.
56
+ name = [try_to_get(:first_name), try_to_get(:last_name)].compact.join(' ')
57
+ name.empty? ? try_to_get(:name) : name
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ module Airbrake
2
+ module Rails
3
+ ##
4
+ # Contains helper methods that can be used inside Rails controllers to send
5
+ # notices to Airbrake. The main benefit of using them instead of the direct
6
+ # API is that they automatically add information from the Rack environment
7
+ # to notices.
8
+ module ActionController
9
+ private
10
+
11
+ ##
12
+ # A helper method for sending notices to Airbrake *asynchronously*.
13
+ # Attaches information from the Rack env.
14
+ # @see Airbrake#notify, #notify_airbrake_sync
15
+ def notify_airbrake(exception, parameters = {}, notifier = :default)
16
+ return unless (notice = build_notice(exception))
17
+ Airbrake.notify(notice, parameters, notifier)
18
+ end
19
+
20
+ ##
21
+ # A helper method for sending notices to Airbrake *synchronously*.
22
+ # Attaches information from the Rack env.
23
+ # @see Airbrake#notify_sync, #notify_airbrake
24
+ def notify_airbrake_sync(exception, parameters = {}, notifier = :default)
25
+ return unless (notice = build_notice(exception))
26
+ Airbrake.notify_sync(notice, parameters, notifier)
27
+ end
28
+
29
+ ##
30
+ # @param [Exception] exception
31
+ # @return [Airbrake::Notice] the notice with information from the Rack env
32
+ def build_notice(exception)
33
+ Airbrake::Rack::NoticeBuilder.new(request.env).build_notice(exception)
34
+ end
35
+ end
36
+ end
37
+ end