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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# RequestStore is a thin (and limited) wrapper around *Thread.current* that
|
|
6
|
+
# allows writing and reading thread-local variables under the +:celerbrake+
|
|
7
|
+
# key.
|
|
8
|
+
# @api private
|
|
9
|
+
# @since v8.1.3
|
|
10
|
+
module RequestStore
|
|
11
|
+
class << self
|
|
12
|
+
# @return [Hash] a hash for all request-related data
|
|
13
|
+
def store
|
|
14
|
+
Thread.current[:celerbrake] ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [void]
|
|
18
|
+
def []=(key, value)
|
|
19
|
+
store[key] = value
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Object]
|
|
23
|
+
def [](key)
|
|
24
|
+
store[key]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [void]
|
|
28
|
+
def clear
|
|
29
|
+
Thread.current[:celerbrake] = {}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'celerbrake/rails/app'
|
|
4
|
+
|
|
5
|
+
module Celerbrake
|
|
6
|
+
module Rack
|
|
7
|
+
# Adds route slugs to context/route.
|
|
8
|
+
# @since v7.5.0
|
|
9
|
+
class RouteFilter
|
|
10
|
+
attr_reader :weight
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@weight = 100
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(notice)
|
|
17
|
+
return unless (request = notice.stash[:rack_request])
|
|
18
|
+
|
|
19
|
+
notice[:context][:route] =
|
|
20
|
+
if action_dispatch_request?(request)
|
|
21
|
+
rails_route(request)
|
|
22
|
+
elsif sinatra_request?(request)
|
|
23
|
+
sinatra_route(request)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def rails_route(request)
|
|
30
|
+
return unless (route = Celerbrake::Rails::App.recognize_route(request))
|
|
31
|
+
|
|
32
|
+
route.path
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def sinatra_route(request)
|
|
36
|
+
return unless (route = request.env['sinatra.route'])
|
|
37
|
+
|
|
38
|
+
route.split.drop(1).join(' ')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def action_dispatch_request?(request)
|
|
42
|
+
defined?(ActionDispatch::Request) &&
|
|
43
|
+
request.instance_of?(ActionDispatch::Request)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def sinatra_request?(request)
|
|
47
|
+
defined?(Sinatra::Request) && request.instance_of?(Sinatra::Request)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# Adds HTTP session.
|
|
6
|
+
#
|
|
7
|
+
# @since v5.7.0
|
|
8
|
+
class SessionFilter
|
|
9
|
+
# @return [Integer]
|
|
10
|
+
attr_reader :weight
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@weight = 96
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @see Celerbrake::FilterChain#refine
|
|
17
|
+
def call(notice)
|
|
18
|
+
return unless (request = notice.stash[:rack_request])
|
|
19
|
+
|
|
20
|
+
session = request.session
|
|
21
|
+
notice[:session] = session if session
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# Represents an authenticated user, which can be converted to Celerbrake's
|
|
6
|
+
# payload format. Supports Warden and Omniauth authentication frameworks.
|
|
7
|
+
class User
|
|
8
|
+
# Finds the user in the Rack environment and creates a new user wrapper.
|
|
9
|
+
#
|
|
10
|
+
# @param [Hash{String=>Object}] rack_env The Rack environment
|
|
11
|
+
# @return [Celerbrake::Rack::User, nil]
|
|
12
|
+
def self.extract(rack_env)
|
|
13
|
+
# Warden support (including Devise).
|
|
14
|
+
if (warden = rack_env['warden'])
|
|
15
|
+
user = warden.user(run_callbacks: false)
|
|
16
|
+
# Early return to prevent unwanted possible authentication via
|
|
17
|
+
# calling the `current_user` method later.
|
|
18
|
+
# See: https://github.com/celerbrake/celerbrake/issues/641
|
|
19
|
+
return user ? new(user) : nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Fallback mode (OmniAuth support included). Works only for Rails.
|
|
23
|
+
user = try_current_user(rack_env)
|
|
24
|
+
new(user) if user
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.try_current_user(rack_env)
|
|
28
|
+
controller = rack_env['action_controller.instance']
|
|
29
|
+
return unless controller.respond_to?(:current_user, true)
|
|
30
|
+
return unless [-1, 0].include?(controller.method(:current_user).arity)
|
|
31
|
+
|
|
32
|
+
begin
|
|
33
|
+
controller.__send__(:current_user)
|
|
34
|
+
rescue Exception => _e # rubocop:disable Lint/RescueException
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
private_class_method :try_current_user
|
|
39
|
+
|
|
40
|
+
def initialize(user)
|
|
41
|
+
@user = user
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def as_json
|
|
45
|
+
user = {}
|
|
46
|
+
|
|
47
|
+
user[:id] = try_to_get(:id)
|
|
48
|
+
user[:name] = full_name
|
|
49
|
+
user[:username] = try_to_get(:username)
|
|
50
|
+
user[:email] = try_to_get(:email)
|
|
51
|
+
|
|
52
|
+
user = user.delete_if { |_key, val| val.nil? }
|
|
53
|
+
user.empty? ? user : { user: user }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def try_to_get(key)
|
|
59
|
+
return unless @user.respond_to?(key)
|
|
60
|
+
# try methods with no arguments or with variable number of arguments,
|
|
61
|
+
# where none of them are required
|
|
62
|
+
return unless @user.method(key).arity.between?(-1, 0)
|
|
63
|
+
|
|
64
|
+
String(@user.__send__(key))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def full_name
|
|
68
|
+
# Try to get first and last names. If that fails, try to get just 'name'.
|
|
69
|
+
name = [try_to_get(:first_name), try_to_get(:last_name)].compact.join(' ')
|
|
70
|
+
name.empty? ? try_to_get(:name) : name
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rack
|
|
5
|
+
# Adds current user information.
|
|
6
|
+
#
|
|
7
|
+
# @since v8.0.1
|
|
8
|
+
class UserFilter
|
|
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
|
+
user = Celerbrake::Rack::User.extract(request.env)
|
|
21
|
+
notice[:context].merge!(user.as_json) if user
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'celerbrake/rack/user'
|
|
4
|
+
require 'celerbrake/rack/user_filter'
|
|
5
|
+
require 'celerbrake/rack/context_filter'
|
|
6
|
+
require 'celerbrake/rack/session_filter'
|
|
7
|
+
require 'celerbrake/rack/http_params_filter'
|
|
8
|
+
require 'celerbrake/rack/http_headers_filter'
|
|
9
|
+
require 'celerbrake/rack/request_body_filter'
|
|
10
|
+
require 'celerbrake/rack/route_filter'
|
|
11
|
+
require 'celerbrake/rack/middleware'
|
|
12
|
+
require 'celerbrake/rack/request_store'
|
|
13
|
+
require 'celerbrake/rack/instrumentable'
|
|
14
|
+
|
|
15
|
+
module Celerbrake
|
|
16
|
+
# Rack is a namespace for all Rack-related code.
|
|
17
|
+
module Rack
|
|
18
|
+
# @since v9.2.0
|
|
19
|
+
# @api public
|
|
20
|
+
def self.capture_timing(label)
|
|
21
|
+
return yield unless Celerbrake::Config.instance.performance_stats
|
|
22
|
+
|
|
23
|
+
routes = Celerbrake::Rack::RequestStore[:routes]
|
|
24
|
+
if !routes || routes.none?
|
|
25
|
+
result = yield
|
|
26
|
+
else
|
|
27
|
+
timed_trace = Celerbrake::TimedTrace.span(label) do
|
|
28
|
+
result = yield
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
routes.each do |_route_path, params|
|
|
32
|
+
params[:groups].merge!(timed_trace.spans)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
result
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rails
|
|
5
|
+
module ActionCable
|
|
6
|
+
# @since v8.3.0
|
|
7
|
+
# @api private
|
|
8
|
+
class NotifyCallback
|
|
9
|
+
def self.call(channel, block)
|
|
10
|
+
block.call
|
|
11
|
+
rescue Exception => ex # rubocop:disable Lint/RescueException
|
|
12
|
+
notice = Celerbrake.build_notice(ex)
|
|
13
|
+
notice[:context][:component] = 'action_cable'
|
|
14
|
+
notice[:context][:action] = channel.channel_name
|
|
15
|
+
Celerbrake.notify(notice)
|
|
16
|
+
|
|
17
|
+
raise ex
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'celerbrake/rails/action_cable/notify_callback'
|
|
4
|
+
|
|
5
|
+
%i[subscribe unsubscribe].each do |callback_name|
|
|
6
|
+
ActionCable::Channel::Base.set_callback(
|
|
7
|
+
callback_name, :around, prepend: true
|
|
8
|
+
) do |channel, inner|
|
|
9
|
+
Celerbrake::Rails::ActionCable::NotifyCallback.call(channel, inner)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module Celerbrake
|
|
14
|
+
module ActionCable
|
|
15
|
+
module Channel
|
|
16
|
+
# @since v8.3.0
|
|
17
|
+
# @api private
|
|
18
|
+
# @see https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/channel/base.rb
|
|
19
|
+
module Base
|
|
20
|
+
def perform_action(*args, &block)
|
|
21
|
+
super(*args, &block)
|
|
22
|
+
rescue Exception => ex # rubocop:disable Lint/RescueException
|
|
23
|
+
Celerbrake.notify(ex) do |notice|
|
|
24
|
+
notice.stash[:action_cable_connection] = connection
|
|
25
|
+
notice[:context][:component] = self.class
|
|
26
|
+
notice[:context][:action] = args.first['action']
|
|
27
|
+
notice[:params].merge!(args.first)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
raise ex
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
ActionCable::Channel::Base.prepend(Celerbrake::ActionCable::Channel::Base)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rails
|
|
5
|
+
# Contains helper methods that can be used inside Rails controllers to send
|
|
6
|
+
# notices to Celerbrake. The main benefit of using them instead of the direct
|
|
7
|
+
# API is that they automatically add information from the Rack environment
|
|
8
|
+
# to notices.
|
|
9
|
+
module ActionController
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# A helper method for sending notices to Celerbrake *asynchronously*.
|
|
13
|
+
# Attaches information from the Rack env.
|
|
14
|
+
# @see Celerbrake#notify, #notify_celerbrake_sync
|
|
15
|
+
def notify_celerbrake(exception, params = {}, &block)
|
|
16
|
+
return unless (notice = build_notice(exception, params))
|
|
17
|
+
|
|
18
|
+
Celerbrake.notify(notice, params, &block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# A helper method for sending notices to Celerbrake *synchronously*.
|
|
22
|
+
# Attaches information from the Rack env.
|
|
23
|
+
# @see Celerbrake#notify_sync, #notify_celerbrake
|
|
24
|
+
def notify_celerbrake_sync(exception, params = {}, &block)
|
|
25
|
+
return unless (notice = build_notice(exception, params))
|
|
26
|
+
|
|
27
|
+
Celerbrake.notify_sync(notice, params, &block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param [Exception] exception
|
|
31
|
+
# @return [Celerbrake::Notice] the notice with information from the Rack env
|
|
32
|
+
def build_notice(exception, params = {})
|
|
33
|
+
return unless (notice = Celerbrake.build_notice(exception, params))
|
|
34
|
+
|
|
35
|
+
notice.stash[:rack_request] = request
|
|
36
|
+
notice
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'celerbrake/rails/event'
|
|
4
|
+
|
|
5
|
+
module Celerbrake
|
|
6
|
+
module Rails
|
|
7
|
+
# ActionControllerNotifySubscriber sends route stat information, including
|
|
8
|
+
# performance data.
|
|
9
|
+
#
|
|
10
|
+
# @since v8.0.0
|
|
11
|
+
class ActionControllerNotifySubscriber
|
|
12
|
+
def call(*args)
|
|
13
|
+
return unless Celerbrake::Config.instance.performance_stats
|
|
14
|
+
|
|
15
|
+
routes = Celerbrake::Rack::RequestStore[:routes]
|
|
16
|
+
return if !routes || routes.none?
|
|
17
|
+
|
|
18
|
+
event = Celerbrake::Rails::Event.new(*args)
|
|
19
|
+
|
|
20
|
+
routes.each do |route, _params|
|
|
21
|
+
Celerbrake.notify_request(
|
|
22
|
+
method: event.method,
|
|
23
|
+
route: route,
|
|
24
|
+
status_code: event.status_code,
|
|
25
|
+
timing: event.duration,
|
|
26
|
+
time: event.time,
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'celerbrake/rails/event'
|
|
4
|
+
|
|
5
|
+
module Celerbrake
|
|
6
|
+
module Rails
|
|
7
|
+
# @since v8.3.0
|
|
8
|
+
class ActionControllerPerformanceBreakdownSubscriber
|
|
9
|
+
def call(*args)
|
|
10
|
+
return unless Celerbrake::Config.instance.performance_stats
|
|
11
|
+
|
|
12
|
+
routes = Celerbrake::Rack::RequestStore[:routes]
|
|
13
|
+
return if !routes || routes.none?
|
|
14
|
+
|
|
15
|
+
event = Celerbrake::Rails::Event.new(*args)
|
|
16
|
+
stash = build_stash
|
|
17
|
+
|
|
18
|
+
routes.each do |route, params|
|
|
19
|
+
groups = event.groups.merge(params[:groups])
|
|
20
|
+
next if groups.none?
|
|
21
|
+
|
|
22
|
+
breakdown_info = {
|
|
23
|
+
method: event.method,
|
|
24
|
+
route: route,
|
|
25
|
+
response_type: event.response_type,
|
|
26
|
+
groups: groups,
|
|
27
|
+
timing: event.duration,
|
|
28
|
+
time: event.time,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Celerbrake.notify_performance_breakdown(breakdown_info, stash)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def build_stash
|
|
38
|
+
stash = {}
|
|
39
|
+
request = Celerbrake::Rack::RequestStore[:request]
|
|
40
|
+
return stash unless request
|
|
41
|
+
|
|
42
|
+
stash[:request] = request
|
|
43
|
+
if (user = Celerbrake::Rack::User.extract(request.env))
|
|
44
|
+
stash.merge!(user.as_json)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
stash
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'celerbrake/rails/event'
|
|
4
|
+
require 'celerbrake/rails/app'
|
|
5
|
+
|
|
6
|
+
module Celerbrake
|
|
7
|
+
module Rails
|
|
8
|
+
# ActionControllerRouteSubscriber sends route stat information, including
|
|
9
|
+
# performance data.
|
|
10
|
+
#
|
|
11
|
+
# @since v8.0.0
|
|
12
|
+
class ActionControllerRouteSubscriber
|
|
13
|
+
def call(*args)
|
|
14
|
+
return unless Celerbrake::Config.instance.performance_stats
|
|
15
|
+
|
|
16
|
+
# We don't track routeless events.
|
|
17
|
+
return unless (routes = Celerbrake::Rack::RequestStore[:routes])
|
|
18
|
+
|
|
19
|
+
event = Celerbrake::Rails::Event.new(*args)
|
|
20
|
+
route = Celerbrake::Rails::App.recognize_route(
|
|
21
|
+
Celerbrake::Rack::RequestStore[:request],
|
|
22
|
+
)
|
|
23
|
+
return unless route
|
|
24
|
+
|
|
25
|
+
routes[route.path] = {
|
|
26
|
+
method: event.method,
|
|
27
|
+
response_type: event.response_type,
|
|
28
|
+
groups: {},
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rails
|
|
5
|
+
# Enables support for exceptions occurring in ActiveJob jobs.
|
|
6
|
+
module ActiveJob
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
def self.notify_celerbrake(exception, job)
|
|
10
|
+
notice = Celerbrake.build_notice(exception)
|
|
11
|
+
notice[:context][:component] = 'active_job'
|
|
12
|
+
notice[:context][:action] = job.class.name
|
|
13
|
+
notice[:params].merge!(job.serialize)
|
|
14
|
+
|
|
15
|
+
Celerbrake.notify(notice)
|
|
16
|
+
|
|
17
|
+
raise exception
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.perform(job, block)
|
|
21
|
+
timing = Celerbrake::Benchmark.measure do
|
|
22
|
+
block.call
|
|
23
|
+
end
|
|
24
|
+
rescue StandardError => exception
|
|
25
|
+
Celerbrake.notify_queue(
|
|
26
|
+
queue: job.class.name,
|
|
27
|
+
error_count: 1,
|
|
28
|
+
timing: 0.01,
|
|
29
|
+
)
|
|
30
|
+
raise exception
|
|
31
|
+
else
|
|
32
|
+
Celerbrake.notify_queue(
|
|
33
|
+
queue: job.class.name,
|
|
34
|
+
error_count: 0,
|
|
35
|
+
timing: timing,
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
included do
|
|
40
|
+
rescue_from(Exception) do |exception|
|
|
41
|
+
Celerbrake::Rails::ActiveJob.notify_celerbrake(exception, self)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
around_perform do |job, block|
|
|
45
|
+
Celerbrake::Rails::ActiveJob.perform(job, block)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rails
|
|
5
|
+
# Rails <4.2 has a bug with regard to swallowing exceptions in the
|
|
6
|
+
# +after_commit+ and the +after_rollback+ hooks: it doesn't bubble up
|
|
7
|
+
# exceptions from there.
|
|
8
|
+
#
|
|
9
|
+
# This module makes it possible to report exceptions occurring there.
|
|
10
|
+
#
|
|
11
|
+
# @see https://github.com/rails/rails/pull/14488 Detailed description of the
|
|
12
|
+
# bug and the fix
|
|
13
|
+
# @see https://goo.gl/348lor Rails 4.2+ implementation (fixed)
|
|
14
|
+
# @see https://goo.gl/ddFNg7 Rails <4.2 implementation (bugged)
|
|
15
|
+
module ActiveRecord
|
|
16
|
+
# Patches default +run_callbacks+ with our version, which is capable of
|
|
17
|
+
# notifying about exceptions.
|
|
18
|
+
#
|
|
19
|
+
# rubocop:disable Lint/RescueException
|
|
20
|
+
def run_callbacks(kind, *args, &block)
|
|
21
|
+
# Let the post process handle the exception if it's not a bugged hook.
|
|
22
|
+
return super unless %i[commit rollback].include?(kind)
|
|
23
|
+
|
|
24
|
+
# Handle the exception ourselves. The 'ex' exception won't be
|
|
25
|
+
# propagated, therefore we must notify it here.
|
|
26
|
+
begin
|
|
27
|
+
super
|
|
28
|
+
rescue Exception => ex
|
|
29
|
+
Celerbrake.notify(ex)
|
|
30
|
+
raise ex
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
# rubocop:enable Lint/RescueException
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'celerbrake/rails/event'
|
|
4
|
+
require 'celerbrake/rails/backtrace_cleaner'
|
|
5
|
+
|
|
6
|
+
module Celerbrake
|
|
7
|
+
module Rails
|
|
8
|
+
# ActiveRecordSubscriber sends SQL information, including performance data.
|
|
9
|
+
#
|
|
10
|
+
# @since v8.1.0
|
|
11
|
+
class ActiveRecordSubscriber
|
|
12
|
+
def call(*args)
|
|
13
|
+
return unless Celerbrake::Config.instance.query_stats
|
|
14
|
+
|
|
15
|
+
routes = Celerbrake::Rack::RequestStore[:routes]
|
|
16
|
+
return if !routes || routes.none?
|
|
17
|
+
|
|
18
|
+
event = Celerbrake::Rails::Event.new(*args)
|
|
19
|
+
frame = last_caller
|
|
20
|
+
|
|
21
|
+
routes.each do |route, params|
|
|
22
|
+
Celerbrake.notify_query(
|
|
23
|
+
route: route,
|
|
24
|
+
method: params[:method],
|
|
25
|
+
query: event.sql,
|
|
26
|
+
func: frame[:function],
|
|
27
|
+
file: frame[:file],
|
|
28
|
+
line: frame[:line],
|
|
29
|
+
timing: event.duration,
|
|
30
|
+
time: event.time,
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def last_caller
|
|
38
|
+
exception = StandardError.new
|
|
39
|
+
exception.set_backtrace(
|
|
40
|
+
Celerbrake::Rails::BacktraceCleaner.clean(Kernel.caller),
|
|
41
|
+
)
|
|
42
|
+
Celerbrake::Backtrace.parse(exception).first || {}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Celerbrake
|
|
4
|
+
module Rails
|
|
5
|
+
# App is a wrapper around Rails.application.
|
|
6
|
+
#
|
|
7
|
+
# @since v9.0.3
|
|
8
|
+
# @api private
|
|
9
|
+
class App
|
|
10
|
+
Route = Struct.new(:path)
|
|
11
|
+
|
|
12
|
+
# @param [] request
|
|
13
|
+
# @return [Celerbrake::Rails::App::Route, nil]
|
|
14
|
+
# rubocop:disable Metrics/AbcSize
|
|
15
|
+
def self.recognize_route(request)
|
|
16
|
+
# Duplicate `request` because `recognize` *can* strip the request's
|
|
17
|
+
# `path_info`, which results in broken engine links (when the engine has
|
|
18
|
+
# an isolated namespace).
|
|
19
|
+
request_copy = request.dup
|
|
20
|
+
|
|
21
|
+
# Save original script name because `router.recognize(request)` mutates
|
|
22
|
+
# it. It's a Rails bug. More info in:
|
|
23
|
+
# * https://github.com/celerbrake/celerbrake/issues/1072
|
|
24
|
+
# * https://github.com/rails/rails/issues/31152
|
|
25
|
+
original_script_name = request.env['SCRIPT_NAME']
|
|
26
|
+
|
|
27
|
+
# We must search every engine individually to find a concrete route. If
|
|
28
|
+
# we rely only on the `Rails.application.routes.router`, then the
|
|
29
|
+
# recognize call would return the root route, neglecting PATH_INFO
|
|
30
|
+
# completely. For example:
|
|
31
|
+
# * a request is made to `marketing#pricing`
|
|
32
|
+
# * `Rails.application` recognizes it as `marketing#/` (incorrect)
|
|
33
|
+
# * `Marketing::Engine` recognizes it as `marketing#/pricing` (correct)
|
|
34
|
+
engines.each do |engine|
|
|
35
|
+
engine.routes.router.recognize(request_copy) do |route, _params|
|
|
36
|
+
# Restore original script name. Remove this code when/if the Rails
|
|
37
|
+
# bug is fixed: https://github.com/celerbrake/celerbrake/issues/1072
|
|
38
|
+
request.env['SCRIPT_NAME'] = original_script_name
|
|
39
|
+
|
|
40
|
+
# Skip "catch-all" routes such as:
|
|
41
|
+
# get '*path => 'pages#about'
|
|
42
|
+
#
|
|
43
|
+
# Ideally, we should be using `route.glob?` but in Rails 7+ this
|
|
44
|
+
# call would fail with a `NoMethodError`. This is because in
|
|
45
|
+
# Rails 7+ the AST for the route is not kept in memory anymore.
|
|
46
|
+
#
|
|
47
|
+
# See: https://github.com/rails/rails/pull/43006#discussion_r783895766
|
|
48
|
+
next if route.path.spec.any?(ActionDispatch::Journey::Nodes::Star)
|
|
49
|
+
|
|
50
|
+
path =
|
|
51
|
+
if engine == ::Rails.application
|
|
52
|
+
route.path.spec.to_s
|
|
53
|
+
else
|
|
54
|
+
"#{engine.engine_name}##{route.path.spec}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Rails can recognize multiple routes for the given request. For
|
|
58
|
+
# example, if we visit /users/2/edit, then Rails sees these routes:
|
|
59
|
+
# * "/users/:id/edit(.:format)"
|
|
60
|
+
# * "/"
|
|
61
|
+
#
|
|
62
|
+
# We return the first route as, what it seems, the most optimal
|
|
63
|
+
# approach.
|
|
64
|
+
return Route.new(path)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
# rubocop:enable Metrics/AbcSize
|
|
71
|
+
|
|
72
|
+
def self.engines
|
|
73
|
+
@engines ||= [*::Rails::Engine.subclasses, ::Rails.application]
|
|
74
|
+
end
|
|
75
|
+
private_class_method :engines
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|