rails_band 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +54 -0
- data/Rakefile +15 -0
- data/lib/rails_band/action_controller/event/exist_fragment.rb +14 -0
- data/lib/rails_band/action_controller/event/expire_fragment.rb +14 -0
- data/lib/rails_band/action_controller/event/halted_callback.rb +14 -0
- data/lib/rails_band/action_controller/event/process_action.rb +72 -0
- data/lib/rails_band/action_controller/event/read_fragment.rb +14 -0
- data/lib/rails_band/action_controller/event/redirect_to.rb +24 -0
- data/lib/rails_band/action_controller/event/send_data.rb +34 -0
- data/lib/rails_band/action_controller/event/send_file.rb +44 -0
- data/lib/rails_band/action_controller/event/start_processing.rb +44 -0
- data/lib/rails_band/action_controller/event/unpermitted_parameters.rb +42 -0
- data/lib/rails_band/action_controller/event/write_fragment.rb +14 -0
- data/lib/rails_band/action_controller/log_subscriber.rb +76 -0
- data/lib/rails_band/action_view/event/render_collection.rb +34 -0
- data/lib/rails_band/action_view/event/render_partial.rb +28 -0
- data/lib/rails_band/action_view/event/render_template.rb +24 -0
- data/lib/rails_band/action_view/from_views.rb +18 -0
- data/lib/rails_band/action_view/log_subscriber.rb +32 -0
- data/lib/rails_band/active_record/event/instantiation.rb +18 -0
- data/lib/rails_band/active_record/event/sql.rb +49 -0
- data/lib/rails_band/active_record/event/strict_loading_violation.rb +18 -0
- data/lib/rails_band/active_record/log_subscriber.rb +32 -0
- data/lib/rails_band/base_event.rb +44 -0
- data/lib/rails_band/configuration.rb +55 -0
- data/lib/rails_band/railtie.rb +29 -0
- data/lib/rails_band/version.rb +5 -0
- data/lib/rails_band.rb +22 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 97783d31ff40a28fa21181d954f365769bfcaf03a08557572363d90399e32347
|
4
|
+
data.tar.gz: 9e32469800f4a9b9e818e928283278975fd50408f24bee7d0241a7018e86e1f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e63696203569e2126281fa79e7ec42ad4c8a09fdd04597eb41ae73cc1fd98b977776513e4f701304a8eb58a1597cb5940f2f920b3fe683c8df7262bc40adfffe
|
7
|
+
data.tar.gz: f267534e31f135a5a216eab5e41a0298f4b59c9ba17f4a683fb212bb1d10bf8eb2e024646fae669e16b315399a0e412f814dbd7d432222b60a2e6345b44fa0c5
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Yutaka Kamei
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# rails_band
|
2
|
+
|
3
|
+
Easy-to-use Rails Instrumentation API.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'rails_band'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```console
|
16
|
+
bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```console
|
21
|
+
gem install rails_band
|
22
|
+
```
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
rails_band automatically replaces each `LogSubscriber` with its own ones after it's loaded as a gem.
|
27
|
+
And then, you should configure how to consume Instrumentation hooks from core Rails implementations like this:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
Rails.application.config.rails_band.consumers = ->(event) { Rails.logger.info(event.to_h) }
|
31
|
+
```
|
32
|
+
|
33
|
+
You can also configure it by specifying event names:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
Rails.application.config.rails_band.consumers = {
|
37
|
+
default: ->(event) { Rails.logger.info(event.to_h) },
|
38
|
+
action_controller: ->(event) { Rails.logger.info(event.slice(:name, :method, :path, :status, :controller, :action)) },
|
39
|
+
'sql.active_record': ->(event) { Rails.logger.debug("#{event.sql_name}: #{event.sql}") },
|
40
|
+
}
|
41
|
+
```
|
42
|
+
|
43
|
+
Note `:default` is the fallback of other non-specific event names. Other events will be ignored without `:default`.
|
44
|
+
In other words, you can consume only events that you want to really consume without `:default`.
|
45
|
+
|
46
|
+
rails_band does not limit you only to use logging purposes. Enjoy with Rails Instrumentation hooks!
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
Contributing is welcome 😄 Please open a pull request!
|
51
|
+
|
52
|
+
## License
|
53
|
+
|
54
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
|
5
|
+
require 'bundler/gem_tasks'
|
6
|
+
|
7
|
+
require 'rake/testtask'
|
8
|
+
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'test'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = false
|
13
|
+
end
|
14
|
+
|
15
|
+
task default: :test
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `exist_fragment?.action_controller`.
|
7
|
+
class ExistFragment < BaseEvent
|
8
|
+
def key
|
9
|
+
@key ||= @event.payload.fetch(:key)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `expire_fragment.action_controller`.
|
7
|
+
class ExpireFragment < BaseEvent
|
8
|
+
def key
|
9
|
+
@key ||= @event.payload.fetch(:key)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `halted_callback.action_controller`.
|
7
|
+
class HaltedCallback < BaseEvent
|
8
|
+
def filter
|
9
|
+
@filter ||= @event.payload.fetch(:filter)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `process_action.action_controller`.
|
7
|
+
class ProcessAction < BaseEvent
|
8
|
+
def controller
|
9
|
+
@controller ||= @event.payload.fetch(:controller)
|
10
|
+
end
|
11
|
+
|
12
|
+
def action
|
13
|
+
@action ||= @event.payload.fetch(:action)
|
14
|
+
end
|
15
|
+
|
16
|
+
def params
|
17
|
+
@params ||= @event.payload.fetch(:params).except(*INTERNAL_PARAMS)
|
18
|
+
end
|
19
|
+
|
20
|
+
def headers
|
21
|
+
@headers ||= @event.payload.fetch(:headers)
|
22
|
+
end
|
23
|
+
|
24
|
+
def format
|
25
|
+
@format ||= @event.payload.fetch(:format)
|
26
|
+
end
|
27
|
+
|
28
|
+
def method
|
29
|
+
@method ||= @event.payload.fetch(:method)
|
30
|
+
end
|
31
|
+
|
32
|
+
def path
|
33
|
+
@path ||= @event.payload.fetch(:path).split('?', 2).first
|
34
|
+
end
|
35
|
+
|
36
|
+
def status
|
37
|
+
@status ||=
|
38
|
+
begin
|
39
|
+
status = @event.payload[:status]
|
40
|
+
|
41
|
+
if status.nil? && (exception_class_name = @event.payload[:exception]&.first)
|
42
|
+
status = ::ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
|
43
|
+
end
|
44
|
+
status
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def view_runtime
|
49
|
+
@view_runtime ||= @event.payload.fetch(:view_runtime)
|
50
|
+
end
|
51
|
+
|
52
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('6.1')
|
53
|
+
define_method(:request) do
|
54
|
+
@request ||= @event.payload[:request]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('6.1')
|
59
|
+
define_method(:response) do
|
60
|
+
@response ||= @event.payload[:response]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def db_runtime
|
65
|
+
return @db_runtime if defined? @db_runtime
|
66
|
+
|
67
|
+
@db_runtime = @event.payload[:db_runtime]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `read_fragment.action_controller`.
|
7
|
+
class ReadFragment < BaseEvent
|
8
|
+
def key
|
9
|
+
@key ||= @event.payload.fetch(:key)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `redirect_to.action_controller`.
|
7
|
+
class RedirectTo < BaseEvent
|
8
|
+
def status
|
9
|
+
@status ||= @event.payload.fetch(:status)
|
10
|
+
end
|
11
|
+
|
12
|
+
def location
|
13
|
+
@location ||= @event.payload.fetch(:location)
|
14
|
+
end
|
15
|
+
|
16
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('6.1')
|
17
|
+
define_method(:request) do
|
18
|
+
@request ||= @event.payload[:request]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `send_data.action_controller`.
|
7
|
+
class SendData < BaseEvent
|
8
|
+
def filename
|
9
|
+
return @filename if defined? @filename
|
10
|
+
|
11
|
+
@filename = @event.payload[:filename]
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
return @type if defined? @type
|
16
|
+
|
17
|
+
@type = @event.payload[:type]
|
18
|
+
end
|
19
|
+
|
20
|
+
def disposition
|
21
|
+
return @disposition if defined? @disposition
|
22
|
+
|
23
|
+
@disposition = @event.payload[:disposition]
|
24
|
+
end
|
25
|
+
|
26
|
+
def status
|
27
|
+
return @status if defined? @status
|
28
|
+
|
29
|
+
@status = @event.payload[:status]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `send_file.action_controller`.
|
7
|
+
class SendFile < BaseEvent
|
8
|
+
def path
|
9
|
+
@path ||= @event.payload.fetch(:path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def filename
|
13
|
+
return @filename if defined? @filename
|
14
|
+
|
15
|
+
@filename = @event.payload[:filename]
|
16
|
+
end
|
17
|
+
|
18
|
+
def type
|
19
|
+
return @type if defined? @type
|
20
|
+
|
21
|
+
@type = @event.payload[:type]
|
22
|
+
end
|
23
|
+
|
24
|
+
def disposition
|
25
|
+
return @disposition if defined? @disposition
|
26
|
+
|
27
|
+
@disposition = @event.payload[:disposition]
|
28
|
+
end
|
29
|
+
|
30
|
+
def status
|
31
|
+
return @status if defined? @status
|
32
|
+
|
33
|
+
@status = @event.payload[:status]
|
34
|
+
end
|
35
|
+
|
36
|
+
def url_based_filename
|
37
|
+
return @url_based_filename if defined? @url_based_filename
|
38
|
+
|
39
|
+
@url_based_filename = @event.payload[:url_based_filename]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `start_processing.action_controller`.
|
7
|
+
class StartProcessing < BaseEvent
|
8
|
+
def controller
|
9
|
+
@controller ||= @event.payload.fetch(:controller)
|
10
|
+
end
|
11
|
+
|
12
|
+
def action
|
13
|
+
@action ||= @event.payload.fetch(:action)
|
14
|
+
end
|
15
|
+
|
16
|
+
def params
|
17
|
+
@params ||= @event.payload.fetch(:params).except(*INTERNAL_PARAMS)
|
18
|
+
end
|
19
|
+
|
20
|
+
def headers
|
21
|
+
@headers ||= @event.payload.fetch(:headers)
|
22
|
+
end
|
23
|
+
|
24
|
+
def format
|
25
|
+
@format ||= @event.payload.fetch(:format)
|
26
|
+
end
|
27
|
+
|
28
|
+
def method
|
29
|
+
@method ||= @event.payload.fetch(:method)
|
30
|
+
end
|
31
|
+
|
32
|
+
def path
|
33
|
+
@path ||= @event.payload.fetch(:path).split('?', 2).first
|
34
|
+
end
|
35
|
+
|
36
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('6.1')
|
37
|
+
define_method(:request) do
|
38
|
+
@request ||= @event.payload[:request]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `unpermitted_parameters.action_controller`.
|
7
|
+
class UnpermittedParameters < BaseEvent
|
8
|
+
def keys
|
9
|
+
@keys ||= @event.payload.fetch(:keys)
|
10
|
+
end
|
11
|
+
|
12
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('7.0')
|
13
|
+
# @see https://github.com/rails/rails/pull/41809
|
14
|
+
define_method(:controller) do
|
15
|
+
@controller ||= @event.payload.dig(:context, :controller)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('7.0')
|
20
|
+
# @see https://github.com/rails/rails/pull/41809
|
21
|
+
define_method(:action) do
|
22
|
+
@action ||= @event.payload.dig(:context, :action)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('7.0')
|
27
|
+
# @see https://github.com/rails/rails/pull/41809
|
28
|
+
define_method(:request) do
|
29
|
+
@request ||= @event.payload.dig(:context, :request)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('7.0')
|
34
|
+
# @see https://github.com/rails/rails/pull/41809
|
35
|
+
define_method(:params) do
|
36
|
+
@params ||= @event.payload.dig(:context, :params)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionController
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `write_fragment.action_controller`.
|
7
|
+
class WriteFragment < BaseEvent
|
8
|
+
def key
|
9
|
+
@key ||= @event.payload.fetch(:key)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_band/action_controller/event/write_fragment'
|
4
|
+
require 'rails_band/action_controller/event/read_fragment'
|
5
|
+
require 'rails_band/action_controller/event/expire_fragment'
|
6
|
+
require 'rails_band/action_controller/event/exist_fragment'
|
7
|
+
require 'rails_band/action_controller/event/start_processing'
|
8
|
+
require 'rails_band/action_controller/event/process_action'
|
9
|
+
require 'rails_band/action_controller/event/send_file'
|
10
|
+
require 'rails_band/action_controller/event/send_data'
|
11
|
+
require 'rails_band/action_controller/event/redirect_to'
|
12
|
+
require 'rails_band/action_controller/event/halted_callback'
|
13
|
+
require 'rails_band/action_controller/event/unpermitted_parameters'
|
14
|
+
|
15
|
+
module RailsBand
|
16
|
+
module ActionController
|
17
|
+
# This comes from ::ActionController::LogSubscriber
|
18
|
+
# @see https://github.com/rails/rails/blob/53000f3a2df5c59252d019bbb8d46728b291ec74/actionpack/lib/action_controller/log_subscriber.rb#L5
|
19
|
+
INTERNAL_PARAMS = %w[controller action format _method only_path].freeze
|
20
|
+
|
21
|
+
# The custom LogSubscriber for ActionController.
|
22
|
+
class LogSubscriber < ::ActiveSupport::LogSubscriber
|
23
|
+
mattr_accessor :consumers
|
24
|
+
|
25
|
+
def write_fragment(event)
|
26
|
+
consumer_of(__method__)&.call(Event::WriteFragment.new(event))
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_fragment(event)
|
30
|
+
consumer_of(__method__)&.call(Event::ReadFragment.new(event))
|
31
|
+
end
|
32
|
+
|
33
|
+
def expire_fragment(event)
|
34
|
+
consumer_of(__method__)&.call(Event::ExpireFragment.new(event))
|
35
|
+
end
|
36
|
+
|
37
|
+
def exist_fragment?(event)
|
38
|
+
consumer_of(__method__)&.call(Event::ExistFragment.new(event))
|
39
|
+
end
|
40
|
+
|
41
|
+
def start_processing(event)
|
42
|
+
consumer_of(__method__)&.call(Event::StartProcessing.new(event))
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_action(event)
|
46
|
+
consumer_of(__method__)&.call(Event::ProcessAction.new(event))
|
47
|
+
end
|
48
|
+
|
49
|
+
def send_file(event)
|
50
|
+
consumer_of(__method__)&.call(Event::SendFile.new(event))
|
51
|
+
end
|
52
|
+
|
53
|
+
def send_data(event)
|
54
|
+
consumer_of(__method__)&.call(Event::SendData.new(event))
|
55
|
+
end
|
56
|
+
|
57
|
+
def redirect_to(event)
|
58
|
+
consumer_of(__method__)&.call(Event::RedirectTo.new(event))
|
59
|
+
end
|
60
|
+
|
61
|
+
def halted_callback(event)
|
62
|
+
consumer_of(__method__)&.call(Event::HaltedCallback.new(event))
|
63
|
+
end
|
64
|
+
|
65
|
+
def unpermitted_parameters(event)
|
66
|
+
consumer_of(__method__)&.call(Event::UnpermittedParameters.new(event))
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def consumer_of(sub_event)
|
72
|
+
consumers[:"#{sub_event}.action_controller"] || consumers[:action_controller] || consumers[:default]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_band/action_view/from_views'
|
4
|
+
|
5
|
+
module RailsBand
|
6
|
+
module ActionView
|
7
|
+
module Event
|
8
|
+
# A wrapper for the event that is passed to `render_collection.action_view`.
|
9
|
+
class RenderCollection < BaseEvent
|
10
|
+
include FromViews
|
11
|
+
|
12
|
+
def identifier
|
13
|
+
@identifier ||= from_views(@event.payload.fetch(:identifier))
|
14
|
+
end
|
15
|
+
|
16
|
+
def layout
|
17
|
+
return @layout if defined? @layout
|
18
|
+
|
19
|
+
@layout = @event.payload[:layout]&.yield_self { |layout| from_views(layout) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def count
|
23
|
+
@count ||= @event.payload.fetch(:count)
|
24
|
+
end
|
25
|
+
|
26
|
+
def cache_hits
|
27
|
+
return @cache_hits if defined? @cache_hits
|
28
|
+
|
29
|
+
@cache_hits = @event.payload[:cache_hits]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_band/action_view/from_views'
|
4
|
+
|
5
|
+
module RailsBand
|
6
|
+
module ActionView
|
7
|
+
module Event
|
8
|
+
# A wrapper for the event that is passed to `render_partial.action_view`.
|
9
|
+
class RenderPartial < BaseEvent
|
10
|
+
include FromViews
|
11
|
+
|
12
|
+
def identifier
|
13
|
+
@identifier ||= from_views(@event.payload.fetch(:identifier))
|
14
|
+
end
|
15
|
+
|
16
|
+
def layout
|
17
|
+
return @layout if defined? @layout
|
18
|
+
|
19
|
+
@layout = @event.payload[:layout]&.yield_self { |layout| from_views(layout) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def cache_hit
|
23
|
+
@cache_hit ||= @event.payload.fetch(:cache_hit)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_band/action_view/from_views'
|
4
|
+
|
5
|
+
module RailsBand
|
6
|
+
module ActionView
|
7
|
+
module Event
|
8
|
+
# A wrapper for the event that is passed to `render_template.action_view`.
|
9
|
+
class RenderTemplate < BaseEvent
|
10
|
+
include FromViews
|
11
|
+
|
12
|
+
def identifier
|
13
|
+
@identifier ||= from_views(@event.payload.fetch(:identifier))
|
14
|
+
end
|
15
|
+
|
16
|
+
def layout
|
17
|
+
return @layout if defined? @layout
|
18
|
+
|
19
|
+
@layout = @event.payload[:layout]&.yield_self { |layout| from_views(layout) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActionView
|
5
|
+
# FromViews provides a sole method #from_views to delete the prefix of 'app/views/' from the passed string.
|
6
|
+
module FromViews
|
7
|
+
private
|
8
|
+
|
9
|
+
def from_views(string)
|
10
|
+
string.delete_prefix(views_prefix)
|
11
|
+
end
|
12
|
+
|
13
|
+
def views_prefix
|
14
|
+
@views_prefix ||= Rails.root.join('app/views/').to_path
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_band/action_view/event/render_template'
|
4
|
+
require 'rails_band/action_view/event/render_partial'
|
5
|
+
require 'rails_band/action_view/event/render_collection'
|
6
|
+
|
7
|
+
module RailsBand
|
8
|
+
module ActionView
|
9
|
+
# The custom LogSubscriber for ActionView.
|
10
|
+
class LogSubscriber < ::ActiveSupport::LogSubscriber
|
11
|
+
mattr_accessor :consumers
|
12
|
+
|
13
|
+
def render_template(event)
|
14
|
+
consumer_of(__method__)&.call(Event::RenderTemplate.new(event))
|
15
|
+
end
|
16
|
+
|
17
|
+
def render_partial(event)
|
18
|
+
consumer_of(__method__)&.call(Event::RenderPartial.new(event))
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_collection(event)
|
22
|
+
consumer_of(__method__)&.call(Event::RenderCollection.new(event))
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def consumer_of(sub_event)
|
28
|
+
consumers[:"#{sub_event}.action_view"] || consumers[:action_view] || consumers[:default]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActiveRecord
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `instantiation.active_record`.
|
7
|
+
class Instantiation < BaseEvent
|
8
|
+
def record_count
|
9
|
+
@record_count ||= @event.payload.fetch(:record_count)
|
10
|
+
end
|
11
|
+
|
12
|
+
def class_name
|
13
|
+
@class_name ||= @event.payload.fetch(:class_name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActiveRecord
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `sql.active_record`.
|
7
|
+
class Sql < BaseEvent
|
8
|
+
def sql
|
9
|
+
@sql ||= @event.payload.fetch(:sql)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @note This method is renamed in order to avoid conflicts with BaseEvent#name.
|
13
|
+
def sql_name
|
14
|
+
@sql_name ||= @event.payload.fetch(:name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def binds
|
18
|
+
@binds ||= @event.payload.fetch(:binds)
|
19
|
+
end
|
20
|
+
|
21
|
+
def type_casted_binds
|
22
|
+
@type_casted_binds ||= @event.payload.fetch(:type_casted_binds)
|
23
|
+
end
|
24
|
+
|
25
|
+
def connection
|
26
|
+
@connection ||= @event.payload.fetch(:connection)
|
27
|
+
end
|
28
|
+
|
29
|
+
def statement_name
|
30
|
+
return @statement_name if defined? @statement_name
|
31
|
+
|
32
|
+
@statement_name = @event.payload[:statement_name]
|
33
|
+
end
|
34
|
+
|
35
|
+
def async
|
36
|
+
return @async if defined? @async
|
37
|
+
|
38
|
+
@async = @event.payload[:async]
|
39
|
+
end
|
40
|
+
|
41
|
+
def cached
|
42
|
+
return @cached if defined? @cached
|
43
|
+
|
44
|
+
@cached = @event.payload[:cached]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
module ActiveRecord
|
5
|
+
module Event
|
6
|
+
# A wrapper for the event that is passed to `strict_loading_violation.active_record`.
|
7
|
+
class StrictLoadingViolation < BaseEvent
|
8
|
+
def owner
|
9
|
+
@owner ||= @event.payload.fetch(:owner)
|
10
|
+
end
|
11
|
+
|
12
|
+
def reflection
|
13
|
+
@reflection ||= @event.payload.fetch(:reflection)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_band/active_record/event/sql'
|
4
|
+
require 'rails_band/active_record/event/instantiation'
|
5
|
+
require 'rails_band/active_record/event/strict_loading_violation'
|
6
|
+
|
7
|
+
module RailsBand
|
8
|
+
module ActiveRecord
|
9
|
+
# The custom LogSubscriber for ActiveRecord.
|
10
|
+
class LogSubscriber < ::ActiveSupport::LogSubscriber
|
11
|
+
mattr_accessor :consumers
|
12
|
+
|
13
|
+
def strict_loading_violation(event)
|
14
|
+
consumer_of(__method__)&.call(Event::StrictLoadingViolation.new(event))
|
15
|
+
end
|
16
|
+
|
17
|
+
def sql(event)
|
18
|
+
consumer_of(__method__)&.call(Event::Sql.new(event))
|
19
|
+
end
|
20
|
+
|
21
|
+
def instantiation(event)
|
22
|
+
consumer_of(__method__)&.call(Event::Instantiation.new(event))
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def consumer_of(sub_event)
|
28
|
+
consumers[:"#{sub_event}.active_record"] || consumers[:active_record] || consumers[:default]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
# The base class of each Event class.
|
5
|
+
class BaseEvent
|
6
|
+
attr_reader :name, :time, :end, :transaction_id, :children,
|
7
|
+
:cpu_time, :idle_time, :allocations, :duration
|
8
|
+
|
9
|
+
# @param event [ActiveSupport::Notifications::Event]
|
10
|
+
def initialize(event)
|
11
|
+
@event = event
|
12
|
+
@name = event.name
|
13
|
+
@time = event.time
|
14
|
+
@end = event.end
|
15
|
+
@transaction_id = event.transaction_id
|
16
|
+
@children = event.children
|
17
|
+
@cpu_time = event.cpu_time
|
18
|
+
@idle_time = event.idle_time
|
19
|
+
@allocations = event.allocations
|
20
|
+
@duration = event.duration
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
@to_h ||= {
|
25
|
+
name: @name, time: @time, end: @end, transaction_id: @transaction_id, children: @children,
|
26
|
+
cpu_time: @cpu_time, idle_time: @idle_time, allocations: @allocations, duration: @duration
|
27
|
+
}.merge!(
|
28
|
+
public_methods(false).reject { |meth| non_hash_keys.include?(meth) }.each_with_object({}) do |meth, h|
|
29
|
+
h[meth] = public_send(meth)
|
30
|
+
end
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def slice(*args)
|
35
|
+
to_h.slice(*args)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def non_hash_keys
|
41
|
+
@non_hash_keys ||= []
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBand
|
4
|
+
# RailsBand::Configuration is responsible for storing user-specified configuration.
|
5
|
+
class Configuration
|
6
|
+
# Consumers is a wrapper of ActiveSupport::HashWithIndifferentAccess, which validates the value on #[]=.
|
7
|
+
class Consumers < ActiveSupport::HashWithIndifferentAccess
|
8
|
+
def []=(key, value)
|
9
|
+
unless value.respond_to?(:call)
|
10
|
+
raise ArgumentError, "The value for `#{key.inspect}` must have #call: the passed one is `#{value.inspect}`"
|
11
|
+
end
|
12
|
+
|
13
|
+
super(key, value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Consumers]
|
18
|
+
attr_reader :consumers
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@consumers = Consumers.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param value [Hash, #call]
|
25
|
+
# Consumer(s) to be called when instrumentation APIs are dispatched. If you pass a single value that has `#call`
|
26
|
+
# method, the value is going to be always called for any instrumentation API events. You can also specify
|
27
|
+
# consumers, assigning a Hash, the keys of which are instrumentation API event names. When the value is a Hash,
|
28
|
+
# you are able to set a key, named `:default`, which is called for the rest of instrumentation API events
|
29
|
+
# you don't assign.
|
30
|
+
#
|
31
|
+
# Instrumentation API event names can be full event names or just namespaces, such as `:action_controller`.
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# config = RailsBand::Configuration.new
|
35
|
+
# config.consumers = ->(e) { Rails.logger.info(e) }
|
36
|
+
# config.consumers = {
|
37
|
+
# default: ->(e) { Rails.logger.info(e) }
|
38
|
+
# action_controller: ->(e) { Rails.logger.info("ActionController! #{e}") }
|
39
|
+
# 'render_template.action_view': ->(e) { Rails.logger.debug("RenderTemplate! #{e}") }
|
40
|
+
# }
|
41
|
+
#
|
42
|
+
# @see https://guides.rubyonrails.org/active_support_instrumentation.html
|
43
|
+
def consumers=(value)
|
44
|
+
@consumers =
|
45
|
+
case value
|
46
|
+
when Hash
|
47
|
+
value.each_with_object(Consumers.new) do |(k, v), c|
|
48
|
+
c[k] = v
|
49
|
+
end
|
50
|
+
else
|
51
|
+
Consumers.new.tap { |c| c[:default] = value }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_controller/log_subscriber'
|
4
|
+
require 'action_view/log_subscriber'
|
5
|
+
|
6
|
+
module RailsBand
|
7
|
+
# RailsBand::Railtie is responsible for preparing its configuration and accepting user-specified configs.
|
8
|
+
class Railtie < ::Rails::Railtie
|
9
|
+
config.rails_band = Configuration.new
|
10
|
+
|
11
|
+
config.after_initialize do |app|
|
12
|
+
consumers = app.config.rails_band.consumers
|
13
|
+
|
14
|
+
swap = lambda { |old_class, new_class, namespace|
|
15
|
+
old_class.detach_from namespace
|
16
|
+
new_class.consumers = consumers
|
17
|
+
new_class.attach_to namespace
|
18
|
+
}
|
19
|
+
|
20
|
+
swap.call(::ActionController::LogSubscriber, RailsBand::ActionController::LogSubscriber, :action_controller)
|
21
|
+
swap.call(::ActionView::LogSubscriber, RailsBand::ActionView::LogSubscriber, :action_view)
|
22
|
+
|
23
|
+
if defined?(::ActiveRecord)
|
24
|
+
require 'active_record/log_subscriber'
|
25
|
+
swap.call(::ActiveRecord::LogSubscriber, RailsBand::ActiveRecord::LogSubscriber, :active_record)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/rails_band.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_band/version'
|
4
|
+
require 'rails_band/configuration'
|
5
|
+
require 'rails_band/base_event'
|
6
|
+
require 'rails_band/railtie'
|
7
|
+
|
8
|
+
# Rails::Band unsubscribes all default LogSubscribers from Rails Instrumentation API,
|
9
|
+
# and it subscribes our own custom LogSubscribers to make it easy to access Rails Instrumentation API.
|
10
|
+
module RailsBand
|
11
|
+
module ActionController
|
12
|
+
autoload :LogSubscriber, 'rails_band/action_controller/log_subscriber'
|
13
|
+
end
|
14
|
+
|
15
|
+
module ActionView
|
16
|
+
autoload :LogSubscriber, 'rails_band/action_view/log_subscriber'
|
17
|
+
end
|
18
|
+
|
19
|
+
module ActiveRecord
|
20
|
+
autoload :LogSubscriber, 'rails_band/active_record/log_subscriber'
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails_band
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yutaka Kamei
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-10-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.0'
|
27
|
+
description: A Rails plugin to facilitate the use of Rails Instrumentation API.
|
28
|
+
email:
|
29
|
+
- kamei@yykamei.me
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lib/rails_band.rb
|
38
|
+
- lib/rails_band/action_controller/event/exist_fragment.rb
|
39
|
+
- lib/rails_band/action_controller/event/expire_fragment.rb
|
40
|
+
- lib/rails_band/action_controller/event/halted_callback.rb
|
41
|
+
- lib/rails_band/action_controller/event/process_action.rb
|
42
|
+
- lib/rails_band/action_controller/event/read_fragment.rb
|
43
|
+
- lib/rails_band/action_controller/event/redirect_to.rb
|
44
|
+
- lib/rails_band/action_controller/event/send_data.rb
|
45
|
+
- lib/rails_band/action_controller/event/send_file.rb
|
46
|
+
- lib/rails_band/action_controller/event/start_processing.rb
|
47
|
+
- lib/rails_band/action_controller/event/unpermitted_parameters.rb
|
48
|
+
- lib/rails_band/action_controller/event/write_fragment.rb
|
49
|
+
- lib/rails_band/action_controller/log_subscriber.rb
|
50
|
+
- lib/rails_band/action_view/event/render_collection.rb
|
51
|
+
- lib/rails_band/action_view/event/render_partial.rb
|
52
|
+
- lib/rails_band/action_view/event/render_template.rb
|
53
|
+
- lib/rails_band/action_view/from_views.rb
|
54
|
+
- lib/rails_band/action_view/log_subscriber.rb
|
55
|
+
- lib/rails_band/active_record/event/instantiation.rb
|
56
|
+
- lib/rails_band/active_record/event/sql.rb
|
57
|
+
- lib/rails_band/active_record/event/strict_loading_violation.rb
|
58
|
+
- lib/rails_band/active_record/log_subscriber.rb
|
59
|
+
- lib/rails_band/base_event.rb
|
60
|
+
- lib/rails_band/configuration.rb
|
61
|
+
- lib/rails_band/railtie.rb
|
62
|
+
- lib/rails_band/version.rb
|
63
|
+
homepage: https://github.com/yykamei/rails_band
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
metadata:
|
67
|
+
homepage_uri: https://github.com/yykamei/rails_band
|
68
|
+
source_code_uri: https://github.com/yykamei/rails_band
|
69
|
+
changelog_uri: https://github.com/yykamei/rails_band/blob/main/CHANGELOG.md
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '2.6'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubygems_version: 3.2.22
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Easy-to-use Rails Instrumentation API
|
89
|
+
test_files: []
|