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.
- checksums.yaml +7 -0
- data/lib/celerbrake/capistrano/capistrano2.rb +40 -0
- data/lib/celerbrake/capistrano/capistrano3.rb +23 -0
- data/lib/celerbrake/capistrano.rb +8 -0
- data/lib/celerbrake/delayed_job.rb +64 -0
- data/lib/celerbrake/logger.rb +105 -0
- data/lib/celerbrake/rack/context_filter.rb +69 -0
- data/lib/celerbrake/rack/http_headers_filter.rb +44 -0
- data/lib/celerbrake/rack/http_params_filter.rb +27 -0
- data/lib/celerbrake/rack/instrumentable.rb +140 -0
- data/lib/celerbrake/rack/middleware.rb +102 -0
- data/lib/celerbrake/rack/request_body_filter.rb +33 -0
- data/lib/celerbrake/rack/request_store.rb +34 -0
- data/lib/celerbrake/rack/route_filter.rb +51 -0
- data/lib/celerbrake/rack/session_filter.rb +25 -0
- data/lib/celerbrake/rack/user.rb +74 -0
- data/lib/celerbrake/rack/user_filter.rb +25 -0
- data/lib/celerbrake/rack.rb +39 -0
- data/lib/celerbrake/rails/action_cable/notify_callback.rb +22 -0
- data/lib/celerbrake/rails/action_cable.rb +37 -0
- data/lib/celerbrake/rails/action_controller.rb +40 -0
- data/lib/celerbrake/rails/action_controller_notify_subscriber.rb +32 -0
- data/lib/celerbrake/rails/action_controller_performance_breakdown_subscriber.rb +51 -0
- data/lib/celerbrake/rails/action_controller_route_subscriber.rb +33 -0
- data/lib/celerbrake/rails/active_job.rb +50 -0
- data/lib/celerbrake/rails/active_record.rb +36 -0
- data/lib/celerbrake/rails/active_record_subscriber.rb +46 -0
- data/lib/celerbrake/rails/app.rb +78 -0
- data/lib/celerbrake/rails/backtrace_cleaner.rb +23 -0
- data/lib/celerbrake/rails/curb.rb +32 -0
- data/lib/celerbrake/rails/event.rb +93 -0
- data/lib/celerbrake/rails/excon_subscriber.rb +25 -0
- data/lib/celerbrake/rails/http.rb +18 -0
- data/lib/celerbrake/rails/http_client.rb +16 -0
- data/lib/celerbrake/rails/net_http.rb +18 -0
- data/lib/celerbrake/rails/railtie.rb +54 -0
- data/lib/celerbrake/rails/railties/action_controller_tie.rb +90 -0
- data/lib/celerbrake/rails/railties/active_record_tie.rb +74 -0
- data/lib/celerbrake/rails/railties/middleware_tie.rb +62 -0
- data/lib/celerbrake/rails/typhoeus.rb +16 -0
- data/lib/celerbrake/rails.rb +32 -0
- data/lib/celerbrake/rake/tasks.rb +112 -0
- data/lib/celerbrake/rake.rb +66 -0
- data/lib/celerbrake/resque.rb +62 -0
- data/lib/celerbrake/shoryuken.rb +52 -0
- data/lib/celerbrake/sidekiq/retryable_jobs_filter.rb +53 -0
- data/lib/celerbrake/sidekiq.rb +53 -0
- data/lib/celerbrake/sneakers.rb +72 -0
- data/lib/celerbrake/version.rb +7 -0
- data/lib/celerbrake.rb +32 -0
- data/lib/generators/celerbrake_generator.rb +22 -0
- data/lib/generators/celerbrake_initializer.rb.erb +80 -0
- metadata +380 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f04c2e12ebb741062851f7f603a5eec5457ab3e5a12a03e6b3ea7d50b934bbb7
|
|
4
|
+
data.tar.gz: 2d8e37ce50181434ed2b8baf785907d1b5d5d8d46f3e57b7680efcedcdde2604
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 927a1d719d732e119aab50304d69083de97783b590beff37bd270bb2d5277d25a376051f8f344fac27b37800eb3a7584c5c357445672f99ef7b32b12eb085e74
|
|
7
|
+
data.tar.gz: c8b0b139d4077f2afd93a9414ac7b917e09d0f59ce6e7bb13a66a3cef3bbd8658b8d18f8e11704a03aaac4d40f4d406a0560163abf89b2ca6095aea88dff1972
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
# The Capistrano v2 integration.
|
|
5
|
+
module Capistrano
|
|
6
|
+
# rubocop:disable Metrics/AbcSize
|
|
7
|
+
def self.load_into(config)
|
|
8
|
+
config.load do
|
|
9
|
+
after 'deploy', 'celerbrake:deploy'
|
|
10
|
+
after 'deploy:migrations', 'celerbrake:deploy'
|
|
11
|
+
after 'deploy:cold', 'celerbrake:deploy'
|
|
12
|
+
|
|
13
|
+
namespace :celerbrake do
|
|
14
|
+
desc "Notify Celerbrake of the deploy"
|
|
15
|
+
task :deploy, except: { no_release: true }, on_error: :continue do
|
|
16
|
+
run(
|
|
17
|
+
<<-CMD, once: true
|
|
18
|
+
cd #{config.release_path} && \
|
|
19
|
+
|
|
20
|
+
RACK_ENV=#{fetch(:rack_env, nil)} \
|
|
21
|
+
RAILS_ENV=#{fetch(:rails_env, nil)} \
|
|
22
|
+
|
|
23
|
+
bundle exec rake celerbrake:deploy \
|
|
24
|
+
USERNAME=#{Shellwords.shellescape(ENV.fetch('USER', nil) || ENV.fetch('USERNAME', nil))} \
|
|
25
|
+
ENVIRONMENT=#{fetch(:celerbrake_env, fetch(:rails_env, 'production'))} \
|
|
26
|
+
REVISION=#{current_revision.strip} \
|
|
27
|
+
REPOSITORY=#{repository} \
|
|
28
|
+
VERSION=#{fetch(:app_version, nil)}
|
|
29
|
+
CMD
|
|
30
|
+
)
|
|
31
|
+
logger.info 'Notified Celerbrake of the deploy'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
# rubocop:enable Metrics/AbcSize
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Celerbrake::Capistrano.load_into(Capistrano::Configuration.instance)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :celerbrake do
|
|
4
|
+
desc "Notify Celerbrake 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
|
+
celerbrake:deploy USERNAME=#{Shellwords.shellescape(local_user)} \
|
|
12
|
+
ENVIRONMENT=#{fetch(:celerbrake_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 Celerbrake of the deploy'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Delayed
|
|
4
|
+
module Plugins
|
|
5
|
+
# Provides integration with Delayed Job.
|
|
6
|
+
# rubocop:disable Metrics/BlockLength
|
|
7
|
+
class Celerbrake < ::Delayed::Plugin
|
|
8
|
+
callbacks do |lifecycle|
|
|
9
|
+
lifecycle.around(:invoke_job) do |job, *args, &block|
|
|
10
|
+
timing = ::Celerbrake::Benchmark.measure do
|
|
11
|
+
# Forward the call to the next callback in the callback chain
|
|
12
|
+
block.call(job, *args)
|
|
13
|
+
end
|
|
14
|
+
rescue Exception => exception # rubocop:disable Lint/RescueException
|
|
15
|
+
params = job.as_json
|
|
16
|
+
|
|
17
|
+
# If DelayedJob is used through ActiveJob, it contains extra info.
|
|
18
|
+
if job.payload_object.respond_to?(:job_data)
|
|
19
|
+
params[:active_job] = job.payload_object.job_data
|
|
20
|
+
job_class = job.payload_object.job_data['job_class']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
action = job_class || job.payload_object.class.name
|
|
24
|
+
|
|
25
|
+
::Celerbrake.notify(exception, params) do |notice|
|
|
26
|
+
notice[:context][:component] = 'delayed_job'
|
|
27
|
+
notice[:context][:action] = action
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
::Celerbrake.notify_queue(
|
|
31
|
+
queue: action,
|
|
32
|
+
error_count: 1,
|
|
33
|
+
timing: 0.01,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
raise exception
|
|
37
|
+
else
|
|
38
|
+
::Celerbrake.notify_queue(
|
|
39
|
+
queue: job_class || job.payload_object.class.name,
|
|
40
|
+
error_count: 0,
|
|
41
|
+
timing: timing,
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
# rubocop:enable Metrics/BlockLength
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if RUBY_ENGINE == 'jruby' && defined?(Delayed::Backend::ActiveRecord::Job)
|
|
51
|
+
# Workaround against JRuby bug:
|
|
52
|
+
# https://github.com/jruby/jruby/issues/3338
|
|
53
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
|
54
|
+
class Delayed::Backend::ActiveRecord::Job
|
|
55
|
+
alias old_to_ary to_ary
|
|
56
|
+
|
|
57
|
+
def to_ary
|
|
58
|
+
old_to_ary || [self]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
Delayed::Worker.plugins << Delayed::Plugins::Celerbrake
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'delegate'
|
|
5
|
+
|
|
6
|
+
module Celerbrake
|
|
7
|
+
# Decorator for +Logger+ from stdlib. Endows loggers the ability to both log
|
|
8
|
+
# and report errors to Celerbrake.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # Create a logger like you normally do and decorate it.
|
|
12
|
+
# logger = Celerbrake::CelerbrakeLogger.new(Logger.new($stdout))
|
|
13
|
+
#
|
|
14
|
+
# # Just use the logger like you normally do.
|
|
15
|
+
# logger.fatal('oops')
|
|
16
|
+
class CelerbrakeLogger < SimpleDelegator
|
|
17
|
+
# @example
|
|
18
|
+
# # Assign a custom Celerbrake notifier
|
|
19
|
+
# logger.celerbrake_notifier = Celerbrake::NoticeNotifier.new
|
|
20
|
+
# @return [Celerbrake::Notifier] notifier to be used to send notices
|
|
21
|
+
attr_accessor :celerbrake_notifier
|
|
22
|
+
|
|
23
|
+
# @return [Integer]
|
|
24
|
+
attr_reader :celerbrake_level
|
|
25
|
+
|
|
26
|
+
def initialize(logger)
|
|
27
|
+
super
|
|
28
|
+
|
|
29
|
+
__setobj__(logger)
|
|
30
|
+
@celerbrake_notifier = Celerbrake
|
|
31
|
+
self.level = logger.level
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @see Logger#warn
|
|
35
|
+
def warn(progname = nil, &block)
|
|
36
|
+
notify_celerbrake(Logger::WARN, progname)
|
|
37
|
+
super
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @see Logger#error
|
|
41
|
+
def error(progname = nil, &block)
|
|
42
|
+
notify_celerbrake(Logger::ERROR, progname)
|
|
43
|
+
super
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @see Logger#fatal
|
|
47
|
+
def fatal(progname = nil, &block)
|
|
48
|
+
notify_celerbrake(Logger::FATAL, progname)
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @see Logger#unknown
|
|
53
|
+
def unknown(progname = nil, &block)
|
|
54
|
+
notify_celerbrake(Logger::UNKNOWN, progname)
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @see Logger#level=
|
|
59
|
+
def level=(value)
|
|
60
|
+
self.celerbrake_level = value < Logger::WARN ? Logger::WARN : value
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Sets celerbrake severity level. Does not permit values below `Logger::WARN`.
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# logger.celerbrake_level = Logger::FATAL
|
|
68
|
+
# @return [void]
|
|
69
|
+
def celerbrake_level=(level)
|
|
70
|
+
if level < Logger::WARN
|
|
71
|
+
raise "Celerbrake severity level #{level} is not allowed. " \
|
|
72
|
+
"Minimum allowed level is #{Logger::WARN}"
|
|
73
|
+
end
|
|
74
|
+
@celerbrake_level = level
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def notify_celerbrake(severity, progname)
|
|
80
|
+
return if severity < @celerbrake_level || !@celerbrake_notifier
|
|
81
|
+
|
|
82
|
+
@celerbrake_notifier.notify(progname) do |notice|
|
|
83
|
+
# Get rid of unwanted internal Logger frames. Examples:
|
|
84
|
+
# * /ruby-2.4.0/lib/ruby/2.4.0/logger.rb
|
|
85
|
+
# * /gems/activesupport-4.2.7.1/lib/active_support/logger.rb
|
|
86
|
+
backtrace = notice[:errors].first[:backtrace]
|
|
87
|
+
notice[:errors].first[:backtrace] =
|
|
88
|
+
backtrace.drop_while { |frame| frame[:file] =~ %r{/logger.rb\z} }
|
|
89
|
+
|
|
90
|
+
notice[:context][:component] = 'log'
|
|
91
|
+
notice[:context][:severity] = normalize_severity(severity)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def normalize_severity(severity)
|
|
96
|
+
(case severity
|
|
97
|
+
when Logger::WARN then 'warning'
|
|
98
|
+
when Logger::ERROR, Logger::UNKNOWN then 'error'
|
|
99
|
+
when Logger::FATAL then 'critical'
|
|
100
|
+
else
|
|
101
|
+
raise "Unknown celerbrake severity: #{severity}"
|
|
102
|
+
end).freeze
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# Adds context (URL, User-Agent, framework version, controller and more).
|
|
6
|
+
#
|
|
7
|
+
# @since v5.7.0
|
|
8
|
+
class ContextFilter
|
|
9
|
+
# @return [Integer]
|
|
10
|
+
attr_reader :weight
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@weight = 99
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @see Celerbrake::FilterChain#refine
|
|
17
|
+
def call(notice)
|
|
18
|
+
return unless (request = notice.stash[:rack_request])
|
|
19
|
+
|
|
20
|
+
context = notice[:context]
|
|
21
|
+
|
|
22
|
+
context[:url] = request.url
|
|
23
|
+
context[:userAgent] = request.user_agent
|
|
24
|
+
|
|
25
|
+
add_ip(context, request)
|
|
26
|
+
add_framework_version(context)
|
|
27
|
+
|
|
28
|
+
controller = request.env['action_controller.instance']
|
|
29
|
+
return unless controller
|
|
30
|
+
|
|
31
|
+
context[:component] = controller.controller_name
|
|
32
|
+
context[:action] = controller.action_name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def add_framework_version(context)
|
|
38
|
+
if context.key?(:versions)
|
|
39
|
+
context[:versions].merge!(framework_version)
|
|
40
|
+
else
|
|
41
|
+
context[:versions] = framework_version
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def framework_version
|
|
46
|
+
@framework_version ||=
|
|
47
|
+
if defined?(::Rails) && ::Rails.respond_to?(:version)
|
|
48
|
+
{ 'rails' => ::Rails.version }
|
|
49
|
+
elsif defined?(::Sinatra)
|
|
50
|
+
{ 'sinatra' => Sinatra::VERSION }
|
|
51
|
+
else
|
|
52
|
+
{
|
|
53
|
+
'rack_version' => ::Rack.version,
|
|
54
|
+
'rack_release' => ::Rack.release,
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def add_ip(context, request)
|
|
60
|
+
context[:userAddr] =
|
|
61
|
+
if request.respond_to?(:remote_ip)
|
|
62
|
+
request.remote_ip
|
|
63
|
+
else
|
|
64
|
+
request.ip
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# Adds HTTP request parameters.
|
|
6
|
+
#
|
|
7
|
+
# @since v5.7.0
|
|
8
|
+
class HttpHeadersFilter
|
|
9
|
+
# @return [Array<String>] the prefixes of the majority of HTTP headers in
|
|
10
|
+
# Rack (some prefixes match the header names for simplicity)
|
|
11
|
+
HTTP_HEADER_PREFIXES = %w[
|
|
12
|
+
HTTP_
|
|
13
|
+
CONTENT_TYPE
|
|
14
|
+
CONTENT_LENGTH
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
# @return [Integer]
|
|
18
|
+
attr_reader :weight
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@weight = 98
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @see Celerbrake::FilterChain#refine
|
|
25
|
+
def call(notice)
|
|
26
|
+
return unless (request = notice.stash[:rack_request])
|
|
27
|
+
|
|
28
|
+
http_headers = request.env.map.with_object({}) do |(key, value), headers|
|
|
29
|
+
if HTTP_HEADER_PREFIXES.any? { |prefix| key.to_s.start_with?(prefix) }
|
|
30
|
+
headers[key] = value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
headers
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
notice[:context].merge!(
|
|
37
|
+
httpMethod: request.request_method,
|
|
38
|
+
referer: request.referer,
|
|
39
|
+
headers: http_headers,
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# Adds HTTP request parameters.
|
|
6
|
+
#
|
|
7
|
+
# @since v5.7.0
|
|
8
|
+
class HttpParamsFilter
|
|
9
|
+
# @return [Integer]
|
|
10
|
+
attr_reader :weight
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@weight = 97
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @see Celerbrake::FilterChain#refine
|
|
17
|
+
def call(notice)
|
|
18
|
+
return unless (request = notice.stash[:rack_request])
|
|
19
|
+
|
|
20
|
+
notice[:params].merge!(request.params)
|
|
21
|
+
|
|
22
|
+
rails_params = request.env['action_dispatch.request.parameters']
|
|
23
|
+
notice[:params].merge!(rails_params) if rails_params
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# Instrumentable holds methods that simplify instrumenting Rack apps.
|
|
6
|
+
# @example
|
|
7
|
+
# class UsersController
|
|
8
|
+
# extend Celerbrake::Rack::Instrumentable
|
|
9
|
+
#
|
|
10
|
+
# def index
|
|
11
|
+
# # ...
|
|
12
|
+
# end
|
|
13
|
+
# celerbrake_capture_timing :index
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# @api public
|
|
17
|
+
# @since v9.2.0
|
|
18
|
+
module Instrumentable
|
|
19
|
+
def celerbrake_capture_timing(method_name, label: method_name.to_s)
|
|
20
|
+
instrumentable = ::Celerbrake::Rack::Instrumentable
|
|
21
|
+
if instrumentable.should_prepend?(self, method_name)
|
|
22
|
+
instrumentable.prepend_capture_timing(self, method_name, label)
|
|
23
|
+
else
|
|
24
|
+
instrumentable.chain_capture_timing(self, method_name, label)
|
|
25
|
+
end
|
|
26
|
+
method_name
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @api private
|
|
30
|
+
def __celerbrake_capture_timing_module__
|
|
31
|
+
# Module used to store prepended wrapper methods, saved as an instance
|
|
32
|
+
# variable so each target class/module gets its own module. This just
|
|
33
|
+
# a convenience to avoid prepending a lot of anonymous modules.
|
|
34
|
+
@__celerbrake_capture_timing_module__ ||= ::Module.new
|
|
35
|
+
end
|
|
36
|
+
private :__celerbrake_capture_timing_module__
|
|
37
|
+
|
|
38
|
+
# Using api private self methods so they don't get defined in the target
|
|
39
|
+
# class or module, but can still be called by the above method.
|
|
40
|
+
|
|
41
|
+
# @api private
|
|
42
|
+
def self.should_prepend?(klass, method_name)
|
|
43
|
+
# Don't chain already-prepended or operator methods.
|
|
44
|
+
klass.module_exec do
|
|
45
|
+
self_class_idx = ancestors.index(self)
|
|
46
|
+
method_owner_idx = ancestors.index(instance_method(method_name).owner)
|
|
47
|
+
method_owner_idx < self_class_idx || !(/\A\W/ =~ method_name).nil?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @api private
|
|
52
|
+
def self.prepend_capture_timing(klass, method_name, label)
|
|
53
|
+
args = method_signature
|
|
54
|
+
visibility = method_visibility(klass, method_name)
|
|
55
|
+
|
|
56
|
+
# Generate the wrapper method.
|
|
57
|
+
klass.module_exec do
|
|
58
|
+
mod = __celerbrake_capture_timing_module__
|
|
59
|
+
mod.module_exec do
|
|
60
|
+
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
|
61
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
62
|
+
def #{method_name}(#{args})
|
|
63
|
+
Celerbrake::Rack.capture_timing(#{label.to_s.inspect}) do
|
|
64
|
+
super
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
#{visibility} :#{method_name}
|
|
68
|
+
RUBY
|
|
69
|
+
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
|
70
|
+
end
|
|
71
|
+
prepend mod
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @api private
|
|
76
|
+
def self.chain_capture_timing(klass, method_name, label)
|
|
77
|
+
args = method_signature
|
|
78
|
+
visibility = method_visibility(klass, method_name)
|
|
79
|
+
|
|
80
|
+
# Generate the wrapper method.
|
|
81
|
+
aliased = method_name.to_s.sub(/([?!=])$/, '')
|
|
82
|
+
punctuation = Regexp.last_match(1)
|
|
83
|
+
wrapped_method_name = "#{aliased}_without_celerbrake#{punctuation}"
|
|
84
|
+
needs_removal = method_needs_removal(klass, method_name)
|
|
85
|
+
klass.module_exec do
|
|
86
|
+
alias_method wrapped_method_name, method_name
|
|
87
|
+
remove_method method_name if needs_removal
|
|
88
|
+
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
|
89
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
90
|
+
def #{method_name}(#{args})
|
|
91
|
+
Celerbrake::Rack.capture_timing(#{label.to_s.inspect}) do
|
|
92
|
+
__send__("#{aliased}_without_celerbrake#{punctuation}", #{args})
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
#{visibility} :#{method_name}
|
|
96
|
+
RUBY
|
|
97
|
+
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @api private
|
|
102
|
+
def self.method_visibility(klass, method_name)
|
|
103
|
+
klass.module_exec do
|
|
104
|
+
if protected_method_defined?(method_name)
|
|
105
|
+
"protected"
|
|
106
|
+
elsif private_method_defined?(method_name)
|
|
107
|
+
"private"
|
|
108
|
+
else
|
|
109
|
+
"public"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @api private
|
|
115
|
+
# A method instead of a constant so it isn't accessible in the target.
|
|
116
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7")
|
|
117
|
+
def self.method_signature
|
|
118
|
+
"*args, **kw_args, &block"
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
def self.method_signature
|
|
122
|
+
"*args, &block"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @api private
|
|
127
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6")
|
|
128
|
+
def self.method_needs_removal(klass, method_name)
|
|
129
|
+
klass.method_defined?(method_name, false) ||
|
|
130
|
+
klass.private_method_defined?(method_name, false)
|
|
131
|
+
end
|
|
132
|
+
else
|
|
133
|
+
def self.method_needs_removal(klass, method_name)
|
|
134
|
+
klass.instance_methods(false).include?(method_name) ||
|
|
135
|
+
klass.private_instance_methods(false).include?(method_name)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# Celerbrake Rack middleware for Rails and Sinatra applications (or any other
|
|
6
|
+
# Rack-compliant app). Any errors raised by the upstream application will be
|
|
7
|
+
# delivered to Celerbrake and re-raised.
|
|
8
|
+
#
|
|
9
|
+
# The middleware automatically sends information about the framework that
|
|
10
|
+
# uses it (name and version).
|
|
11
|
+
#
|
|
12
|
+
# For Rails apps the middleware collects route performance statistics.
|
|
13
|
+
class Middleware
|
|
14
|
+
def initialize(app)
|
|
15
|
+
@app = app
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Thread-safe {call!}.
|
|
19
|
+
#
|
|
20
|
+
# @param [Hash] env the Rack environment
|
|
21
|
+
# @see https://github.com/celerbrake/celerbrake/issues/904
|
|
22
|
+
def call(env)
|
|
23
|
+
dup.call!(env)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Rescues any exceptions, sends them to Celerbrake and re-raises the
|
|
27
|
+
# exception. We also duplicate middleware to guarantee thread-safety.
|
|
28
|
+
#
|
|
29
|
+
# @param [Hash] env the Rack environment
|
|
30
|
+
def call!(env)
|
|
31
|
+
before_call(env)
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
response = @app.call(env)
|
|
35
|
+
rescue Exception => ex # rubocop:disable Lint/RescueException
|
|
36
|
+
notify_celerbrake(ex)
|
|
37
|
+
raise ex
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
exception = framework_exception(env)
|
|
41
|
+
notify_celerbrake(exception) if exception
|
|
42
|
+
|
|
43
|
+
response
|
|
44
|
+
ensure
|
|
45
|
+
# Clear routes for the next request.
|
|
46
|
+
RequestStore.clear
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def before_call(env)
|
|
52
|
+
# Rails hooks such as ActionControllerRouteSubscriber rely on this.
|
|
53
|
+
RequestStore[:routes] = {}
|
|
54
|
+
RequestStore[:request] = find_request(env)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def find_request(env)
|
|
58
|
+
if defined?(ActionDispatch::Request)
|
|
59
|
+
ActionDispatch::Request.new(env)
|
|
60
|
+
elsif defined?(Sinatra::Request)
|
|
61
|
+
Sinatra::Request.new(env)
|
|
62
|
+
else
|
|
63
|
+
::Rack::Request.new(env)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def notify_celerbrake(exception)
|
|
68
|
+
notice = Celerbrake.build_notice(exception)
|
|
69
|
+
return unless notice
|
|
70
|
+
|
|
71
|
+
# ActionDispatch::Request correctly captures server port when using SSL:
|
|
72
|
+
# See: https://github.com/celerbrake/celerbrake/issues/802
|
|
73
|
+
notice.stash[:rack_request] = RequestStore[:request]
|
|
74
|
+
|
|
75
|
+
Celerbrake.notify(notice)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Web framework middlewares often store rescued exceptions inside the
|
|
79
|
+
# Rack env, but Rack doesn't have a standard key for it:
|
|
80
|
+
#
|
|
81
|
+
# - Rails uses action_dispatch.exception: https://goo.gl/Kd694n
|
|
82
|
+
# - Sinatra uses sinatra.error: https://goo.gl/LLkVL9
|
|
83
|
+
# - Goliath uses rack.exception: https://goo.gl/i7e1nA
|
|
84
|
+
def framework_exception(env)
|
|
85
|
+
env['action_dispatch.exception'] ||
|
|
86
|
+
env['sinatra.error'] ||
|
|
87
|
+
env['rack.exception']
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
[
|
|
94
|
+
Celerbrake::Rack::ContextFilter,
|
|
95
|
+
Celerbrake::Rack::UserFilter,
|
|
96
|
+
Celerbrake::Rack::SessionFilter,
|
|
97
|
+
Celerbrake::Rack::HttpParamsFilter,
|
|
98
|
+
Celerbrake::Rack::HttpHeadersFilter,
|
|
99
|
+
Celerbrake::Rack::RouteFilter,
|
|
100
|
+
].each do |filter|
|
|
101
|
+
Celerbrake.add_filter(filter.new)
|
|
102
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# A filter that appends Rack request body to the notice.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # Read and append up to 512 bytes from Rack request's body.
|
|
9
|
+
# Celerbrake.add_filter(Celerbrake::Rack::RequestBodyFilter.new(512))
|
|
10
|
+
#
|
|
11
|
+
# @since v5.7.0
|
|
12
|
+
# @note This filter is *not* used by default.
|
|
13
|
+
class RequestBodyFilter
|
|
14
|
+
# @return [Integer]
|
|
15
|
+
attr_reader :weight
|
|
16
|
+
|
|
17
|
+
# @param [Integer] length The maximum number of bytes to read
|
|
18
|
+
def initialize(length = 4096)
|
|
19
|
+
@length = length
|
|
20
|
+
@weight = 95
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @see Celerbrake::FilterChain#refine
|
|
24
|
+
def call(notice)
|
|
25
|
+
return unless (request = notice.stash[:rack_request])
|
|
26
|
+
return unless request.body
|
|
27
|
+
|
|
28
|
+
notice[:environment][:body] = request.body.read(@length)
|
|
29
|
+
request.body.rewind
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|