celerbrake 0.1.0

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/lib/celerbrake/capistrano/capistrano2.rb +40 -0
  3. data/lib/celerbrake/capistrano/capistrano3.rb +23 -0
  4. data/lib/celerbrake/capistrano.rb +8 -0
  5. data/lib/celerbrake/delayed_job.rb +64 -0
  6. data/lib/celerbrake/logger.rb +105 -0
  7. data/lib/celerbrake/rack/context_filter.rb +69 -0
  8. data/lib/celerbrake/rack/http_headers_filter.rb +44 -0
  9. data/lib/celerbrake/rack/http_params_filter.rb +27 -0
  10. data/lib/celerbrake/rack/instrumentable.rb +140 -0
  11. data/lib/celerbrake/rack/middleware.rb +102 -0
  12. data/lib/celerbrake/rack/request_body_filter.rb +33 -0
  13. data/lib/celerbrake/rack/request_store.rb +34 -0
  14. data/lib/celerbrake/rack/route_filter.rb +51 -0
  15. data/lib/celerbrake/rack/session_filter.rb +25 -0
  16. data/lib/celerbrake/rack/user.rb +74 -0
  17. data/lib/celerbrake/rack/user_filter.rb +25 -0
  18. data/lib/celerbrake/rack.rb +39 -0
  19. data/lib/celerbrake/rails/action_cable/notify_callback.rb +22 -0
  20. data/lib/celerbrake/rails/action_cable.rb +37 -0
  21. data/lib/celerbrake/rails/action_controller.rb +40 -0
  22. data/lib/celerbrake/rails/action_controller_notify_subscriber.rb +32 -0
  23. data/lib/celerbrake/rails/action_controller_performance_breakdown_subscriber.rb +51 -0
  24. data/lib/celerbrake/rails/action_controller_route_subscriber.rb +33 -0
  25. data/lib/celerbrake/rails/active_job.rb +50 -0
  26. data/lib/celerbrake/rails/active_record.rb +36 -0
  27. data/lib/celerbrake/rails/active_record_subscriber.rb +46 -0
  28. data/lib/celerbrake/rails/app.rb +78 -0
  29. data/lib/celerbrake/rails/backtrace_cleaner.rb +23 -0
  30. data/lib/celerbrake/rails/curb.rb +32 -0
  31. data/lib/celerbrake/rails/event.rb +93 -0
  32. data/lib/celerbrake/rails/excon_subscriber.rb +25 -0
  33. data/lib/celerbrake/rails/http.rb +18 -0
  34. data/lib/celerbrake/rails/http_client.rb +16 -0
  35. data/lib/celerbrake/rails/net_http.rb +18 -0
  36. data/lib/celerbrake/rails/railtie.rb +54 -0
  37. data/lib/celerbrake/rails/railties/action_controller_tie.rb +90 -0
  38. data/lib/celerbrake/rails/railties/active_record_tie.rb +74 -0
  39. data/lib/celerbrake/rails/railties/middleware_tie.rb +62 -0
  40. data/lib/celerbrake/rails/typhoeus.rb +16 -0
  41. data/lib/celerbrake/rails.rb +32 -0
  42. data/lib/celerbrake/rake/tasks.rb +112 -0
  43. data/lib/celerbrake/rake.rb +66 -0
  44. data/lib/celerbrake/resque.rb +62 -0
  45. data/lib/celerbrake/shoryuken.rb +52 -0
  46. data/lib/celerbrake/sidekiq/retryable_jobs_filter.rb +53 -0
  47. data/lib/celerbrake/sidekiq.rb +53 -0
  48. data/lib/celerbrake/sneakers.rb +72 -0
  49. data/lib/celerbrake/version.rb +7 -0
  50. data/lib/celerbrake.rb +32 -0
  51. data/lib/generators/celerbrake_generator.rb +22 -0
  52. data/lib/generators/celerbrake_initializer.rb.erb +80 -0
  53. metadata +380 -0
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is not bulletproof, but if this file is executed before a task
4
+ # definition, we can grab tasks descriptions and locations.
5
+ # See: https://goo.gl/ksn6PE
6
+ Rake::TaskManager.record_task_metadata = true
7
+
8
+ module Celerbrake
9
+ module Rake
10
+ # Redefine +Rake::Task#execute+, so it can report errors to Celerbrake.
11
+ module Task
12
+ # A wrapper around the original +#execute+, that catches all errors and
13
+ # notifies Celerbrake.
14
+ #
15
+ # rubocop:disable Lint/RescueException
16
+ def execute(args = nil)
17
+ super(args)
18
+ rescue Exception => ex
19
+ notify_celerbrake(ex, args)
20
+ raise ex
21
+ end
22
+ # rubocop:enable Lint/RescueException
23
+
24
+ private
25
+
26
+ def notify_celerbrake(exception, args)
27
+ notice = Celerbrake.build_notice(exception)
28
+ notice[:context][:component] = 'rake'
29
+ notice[:context][:action] = name
30
+ notice[:params].merge!(
31
+ rake_task: task_info,
32
+ execute_args: args,
33
+ argv: ARGV.join(' '),
34
+ )
35
+
36
+ Celerbrake.notify_sync(notice)
37
+ end
38
+
39
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
40
+ def task_info
41
+ info = {}
42
+
43
+ info[:name] = name
44
+ info[:timestamp] = timestamp.to_s
45
+ info[:investigation] = investigation
46
+
47
+ info[:full_comment] = full_comment if full_comment
48
+ info[:arg_names] = arg_names if arg_names.any?
49
+ info[:arg_description] = arg_description if arg_description
50
+ info[:locations] = locations if locations.any?
51
+ info[:sources] = sources if sources.any?
52
+
53
+ if prerequisite_tasks.any?
54
+ info[:prerequisite_tasks] = prerequisite_tasks.map do |p|
55
+ p.__send__(:task_info)
56
+ end
57
+ end
58
+
59
+ info
60
+ end
61
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize
62
+ end
63
+ end
64
+ end
65
+
66
+ Rake::Task.prepend(Celerbrake::Rake::Task)
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resque
4
+ module Failure
5
+ # Provides Resque integration with Celerbrake.
6
+ #
7
+ # @since v5.0.0
8
+ # @see https://github.com/resque/resque/wiki/Failure-Backends
9
+ class Celerbrake < Base
10
+ def save
11
+ ::Celerbrake.notify_sync(exception, payload) do |notice|
12
+ notice[:context][:component] = 'resque'
13
+ notice[:context][:action] = action(payload)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ # @return [String] job's name. When ActiveJob is present, retrieve
20
+ # job_class. When used directly, use worker's name
21
+ def action(payload)
22
+ active_job_args = payload['args'].first if payload['args']
23
+ if active_job_args.is_a?(Hash) && active_job_args['job_class']
24
+ active_job_args['job_class']
25
+ else
26
+ payload['class'].to_s
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ module Celerbrake
34
+ module Resque
35
+ # Measures elapsed time of a job and notifies Celerbrake of the execution
36
+ # status.
37
+ #
38
+ # @since v9.6.0
39
+ module Job
40
+ def perform
41
+ timing = Celerbrake::Benchmark.measure do
42
+ super
43
+ end
44
+ rescue StandardError => exception
45
+ Celerbrake.notify_queue_sync(
46
+ queue: payload['class'],
47
+ error_count: 1,
48
+ timing: 0.01,
49
+ )
50
+ raise exception
51
+ else
52
+ Celerbrake.notify_queue_sync(
53
+ queue: payload['class'],
54
+ error_count: 0,
55
+ timing: timing,
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ Resque::Job.prepend(Celerbrake::Resque::Job)
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Shoryuken
5
+ # Provides integration with Shoryuken.
6
+ class ErrorHandler
7
+ # rubocop:disable Lint/RescueException
8
+ def call(worker, queue, _sqs_msg, body, &block)
9
+ timing = Celerbrake::Benchmark.measure(&block)
10
+ rescue Exception => exception
11
+ notify_celerbrake(exception, worker, queue, body)
12
+ Celerbrake.notify_queue(
13
+ queue: worker.class.to_s,
14
+ error_count: 1,
15
+ timing: 0.01,
16
+ )
17
+ raise exception
18
+ else
19
+ Celerbrake.notify_queue(
20
+ queue: worker.class.to_s,
21
+ error_count: 0,
22
+ timing: timing,
23
+ )
24
+ end
25
+ # rubocop:enable Lint/RescueException
26
+
27
+ private
28
+
29
+ def notify_celerbrake(exception, worker, queue, body)
30
+ Celerbrake.notify(exception, notice_context(queue, body)) do |notice|
31
+ notice[:context][:component] = 'shoryuken'
32
+ notice[:context][:action] = worker.class.to_s
33
+ end
34
+ end
35
+
36
+ def notice_context(queue, body)
37
+ {
38
+ queue: queue,
39
+ body: body.is_a?(Array) ? { batch: body } : { body: body },
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ if defined?(::Shoryuken)
47
+ Shoryuken.configure_server do |config|
48
+ config.server_middleware do |chain|
49
+ chain.add Celerbrake::Shoryuken::ErrorHandler
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Sidekiq
5
+ # Filter that can ignore notices from jobs that failed but will be retried
6
+ # by Sidekiq
7
+ # @since v7.3.0
8
+ class RetryableJobsFilter
9
+ if Gem::Version.new(::Sidekiq::VERSION) < Gem::Version.new('5.0.0')
10
+ require 'sidekiq/middleware/server/retry_jobs'
11
+ DEFAULT_MAX_RETRY_ATTEMPTS = \
12
+ ::Sidekiq::Middleware::Server::RetryJobs::DEFAULT_MAX_RETRY_ATTEMPTS
13
+ else
14
+ require 'sidekiq/job_retry'
15
+ DEFAULT_MAX_RETRY_ATTEMPTS = ::Sidekiq::JobRetry::DEFAULT_MAX_RETRY_ATTEMPTS
16
+ end
17
+
18
+ def initialize(max_retries: nil)
19
+ @max_retries = max_retries
20
+ end
21
+
22
+ def call(notice)
23
+ job = notice[:params][:job]
24
+
25
+ notice.ignore! if retryable?(job)
26
+ end
27
+
28
+ private
29
+
30
+ def retryable?(job)
31
+ return false unless job && job['retry']
32
+
33
+ max_attempts = max_attempts_for(job)
34
+ retry_count = (job['retry_count'] || -1) + 1
35
+ retry_count < max_attempts
36
+ end
37
+
38
+ def max_attempts_for(job)
39
+ if @max_retries
40
+ @max_retries
41
+ elsif job['retry'].is_a?(Integer)
42
+ job['retry']
43
+ else
44
+ max_retries
45
+ end
46
+ end
47
+
48
+ def max_retries
49
+ @max_retries ||= ::Sidekiq.options[:max_retries] || DEFAULT_MAX_RETRY_ATTEMPTS
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'celerbrake/sidekiq/retryable_jobs_filter'
4
+
5
+ module Celerbrake
6
+ module Sidekiq
7
+ # Provides integration with Sidekiq v2+.
8
+ class ErrorHandler
9
+ def call(_worker, context, _queue, &block)
10
+ timing = Celerbrake::Benchmark.measure(&block)
11
+ rescue Exception => exception # rubocop:disable Lint/RescueException
12
+ notify_celerbrake(exception, context)
13
+ Celerbrake.notify_queue(
14
+ queue: context['class'],
15
+ error_count: 1,
16
+ timing: 0.01,
17
+ )
18
+ raise exception
19
+ else
20
+ Celerbrake.notify_queue(
21
+ queue: context['class'],
22
+ error_count: 0,
23
+ timing: timing,
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def notify_celerbrake(exception, context)
30
+ Celerbrake.notify(exception, job: context) do |notice|
31
+ notice[:context][:component] = 'sidekiq'
32
+ notice[:context][:action] = action(context)
33
+ end
34
+ end
35
+
36
+ # @return [String] job's name. When ActiveJob is present, retrieve
37
+ # job_class. When used directly, use worker's name
38
+ def action(context)
39
+ klass = context['class'] || (context[:job] && context[:job]['class'])
40
+ return klass unless context[:job] && context[:job]['args'].first.is_a?(Hash)
41
+ return klass unless (job_class = context[:job]['args'].first['job_class'])
42
+
43
+ job_class
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ Sidekiq.configure_server do |config|
50
+ config.server_middleware do |chain|
51
+ chain.add(Celerbrake::Sidekiq::ErrorHandler)
52
+ end
53
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Sneakers
5
+ # Provides integration with Sneakers.
6
+ #
7
+ # @see https://github.com/jondot/sneakers
8
+ # @since v7.2.0
9
+ class ErrorReporter
10
+ # @return [Array<Symbol>] ignored keys values of which raise
11
+ # SystemStackError when `as_json` is called on them
12
+ # @see https://github.com/celerbrake/celerbrake/issues/850
13
+ IGNORED_KEYS = %i[delivery_tag consumer channel].freeze
14
+
15
+ # rubocop:disable Style/OptionalArguments
16
+ def call(exception, worker = nil, context)
17
+ # Later versions add a middle argument.
18
+ Celerbrake.notify(exception, filter_context(context)) do |notice|
19
+ notice[:context][:component] = 'sneakers'
20
+ notice[:context][:action] = worker.class.to_s
21
+ end
22
+ end
23
+ # rubocop:enable Style/OptionalArguments
24
+
25
+ private
26
+
27
+ def filter_context(context)
28
+ return context unless context[:delivery_info]
29
+
30
+ h = context.dup
31
+ h[:delivery_info] = context[:delivery_info].reject do |k, _v|
32
+ IGNORED_KEYS.include?(k)
33
+ end
34
+ h
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ Sneakers.error_reporters << Celerbrake::Sneakers::ErrorReporter.new
41
+
42
+ module Celerbrake
43
+ module Sneakers
44
+ # @todo Migrate to Sneakers v2.12.0 middleware API when it's released
45
+ # @see https://github.com/jondot/sneakers/pull/364
46
+ module Worker
47
+ # Sneakers v2.7.0+ renamed `do_work` to `process_work`.
48
+ define_method(
49
+ ::Sneakers::Worker.method_defined?(:process_work) ? :process_work : :do_work,
50
+ ) do |delivery_info, metadata, msg, handler|
51
+ timing = Celerbrake::Benchmark.measure do
52
+ super(delivery_info, metadata, msg, handler)
53
+ end
54
+ rescue Exception => exception # rubocop:disable Lint/RescueException
55
+ Celerbrake.notify_queue(
56
+ queue: self.class.to_s,
57
+ error_count: 1,
58
+ timing: 0.01,
59
+ )
60
+ raise exception
61
+ else
62
+ Celerbrake.notify_queue(
63
+ queue: self.class.to_s,
64
+ error_count: 0,
65
+ timing: timing,
66
+ )
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ Sneakers::Worker.prepend(Celerbrake::Sneakers::Worker)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # We use Semantic Versioning v2.0.0
4
+ # More information: http://semver.org/
5
+ module Celerbrake
6
+ CELERBRAKE_VERSION = '0.1.0'
7
+ end
data/lib/celerbrake.rb ADDED
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require 'English'
5
+
6
+ # Core library that sends notices.
7
+ # See: https://github.com/celerbrake/celerbrake-ruby
8
+ require 'celerbrake-ruby'
9
+
10
+ require 'celerbrake/version'
11
+
12
+ # Automatically load needed files for the environment the library is running in.
13
+ if defined?(Rack)
14
+ require 'celerbrake/rack'
15
+
16
+ require 'celerbrake/rails' if defined?(Rails)
17
+ end
18
+
19
+ require 'celerbrake/rake' if defined?(Rake::Task)
20
+ require 'celerbrake/resque' if defined?(Resque)
21
+ require 'celerbrake/sidekiq' if defined?(Sidekiq)
22
+ require 'celerbrake/shoryuken' if defined?(Shoryuken)
23
+ require 'celerbrake/delayed_job' if defined?(Delayed)
24
+ require 'celerbrake/sneakers' if defined?(Sneakers)
25
+
26
+ require 'celerbrake/logger'
27
+
28
+ # Notify of unhandled exceptions, if there were any, but ignore SystemExit.
29
+ at_exit do
30
+ Celerbrake.notify_sync($ERROR_INFO) if $ERROR_INFO
31
+ Celerbrake.close
32
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Creates the Celerbrake initializer file for Rails apps.
4
+ #
5
+ # @example Invokation from terminal
6
+ # rails generate celerbrake [NAME]
7
+ #
8
+ class CelerbrakeGenerator < Rails::Generators::Base
9
+ # Adds current directory to source paths, so we can find the template file.
10
+ source_root File.expand_path(__dir__)
11
+
12
+ # Makes the NAME option optional, which allows to subclass from Base, so we
13
+ # can pass arguments to the ERB template.
14
+ #
15
+ # @see https://asciicasts.com/episodes/218-making-generators-in-rails-3.html
16
+ argument :name, type: :string, default: 'application'
17
+
18
+ desc 'Configures the Celerbrake notifier'
19
+ def generate_layout
20
+ template 'celerbrake_initializer.rb.erb', 'config/initializers/celerbrake.rb'
21
+ end
22
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Celerbrake is an online tool that provides robust exception tracking in your
4
+ # Rails applications. In doing so, it allows you to easily review errors, tie an
5
+ # error to an individual piece of code, and trace the cause back to recent
6
+ # changes. Celerbrake enables for easy categorization, searching, and
7
+ # prioritization of exceptions so that when errors occur, your team can quickly
8
+ # determine the root cause.
9
+ #
10
+ # Configuration details:
11
+ # https://github.com/celerbrake/celerbrake-ruby#configuration
12
+ if (project_id = ENV['CELERBRAKE_PROJECT_ID']) &&
13
+ project_key = (ENV['CELERBRAKE_PROJECT_KEY'] || ENV['CELERBRAKE_API_KEY'])
14
+ Celerbrake.configure do |c|
15
+ # You must set both project_id & project_key. To find your project_id and
16
+ # project_key navigate to your project's General Settings and copy the
17
+ # values from the right sidebar.
18
+ # https://github.com/celerbrake/celerbrake-ruby#project_id--project_key
19
+ c.project_id = project_id
20
+ c.project_key = project_key
21
+
22
+ # Configures the root directory of your project. Expects a String or a
23
+ # Pathname, which represents the path to your project. Providing this option
24
+ # helps us to filter out repetitive data from backtrace frames and link to
25
+ # GitHub files from our dashboard.
26
+ # https://github.com/celerbrake/celerbrake-ruby#root_directory
27
+ c.root_directory = Rails.root
28
+
29
+ # By default, Celerbrake Ruby outputs to STDOUT. In Rails apps it makes sense
30
+ # to use the Rails' logger.
31
+ # https://github.com/celerbrake/celerbrake-ruby#logger
32
+ c.logger = Celerbrake::Rails.logger
33
+
34
+ # Configures the environment the application is running in. Helps the
35
+ # Celerbrake dashboard to distinguish between exceptions occurring in
36
+ # different environments.
37
+ # NOTE: This option must be set in order to make the 'ignore_environments'
38
+ # option work.
39
+ # https://github.com/celerbrake/celerbrake-ruby#environment
40
+ c.environment = Rails.env
41
+
42
+ # Setting this option allows Celerbrake to filter exceptions occurring in
43
+ # unwanted environments such as :test. NOTE: This option *does not* work if
44
+ # you don't set the 'environment' option.
45
+ # https://github.com/celerbrake/celerbrake-ruby#ignore_environments
46
+ c.ignore_environments = %w[test]
47
+
48
+ # A list of parameters that should be filtered out of what is sent to
49
+ # Celerbrake. By default, all "password" attributes will have their contents
50
+ # replaced.
51
+ # https://github.com/celerbrake/celerbrake-ruby#blocklist_keys
52
+ c.blocklist_keys = [/password/i, /authorization/i]
53
+
54
+ # Alternatively, you can integrate with Rails' filter_parameters.
55
+ # Read more: https://goo.gl/gqQ1xS
56
+ # c.blocklist_keys = Rails.application.config.filter_parameters
57
+ end
58
+
59
+ # A filter that collects request body information. Enable it if you are sure you
60
+ # don't send sensitive information to Celerbrake in your body (such as passwords).
61
+ # https://github.com/celerbrake/celerbrake#requestbodyfilter
62
+ # Celerbrake.add_filter(Celerbrake::Rack::RequestBodyFilter.new)
63
+
64
+ # Attaches thread & fiber local variables along with general thread information.
65
+ # Celerbrake.add_filter(Celerbrake::Filters::ThreadFilter.new)
66
+
67
+ # Attaches loaded dependencies to the notice object
68
+ # (under context/versions/dependencies).
69
+ # Celerbrake.add_filter(Celerbrake::Filters::DependencyFilter.new)
70
+
71
+ # If you want to convert your log messages to Celerbrake errors, we offer an
72
+ # integration with the Logger class from stdlib.
73
+ # https://github.com/celerbrake/celerbrake#logger
74
+ # Rails.logger = Celerbrake::CelerbrakeLogger.new(Rails.logger)
75
+ else
76
+ Rails.logger.warn(
77
+ "#{__FILE__}: Celerbrake project id or project key is not set. " \
78
+ "Skipping Celerbrake configuration"
79
+ )
80
+ end