dfg-airbrake 5.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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