rails_band 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 (31) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +54 -0
  4. data/Rakefile +15 -0
  5. data/lib/rails_band/action_controller/event/exist_fragment.rb +14 -0
  6. data/lib/rails_band/action_controller/event/expire_fragment.rb +14 -0
  7. data/lib/rails_band/action_controller/event/halted_callback.rb +14 -0
  8. data/lib/rails_band/action_controller/event/process_action.rb +72 -0
  9. data/lib/rails_band/action_controller/event/read_fragment.rb +14 -0
  10. data/lib/rails_band/action_controller/event/redirect_to.rb +24 -0
  11. data/lib/rails_band/action_controller/event/send_data.rb +34 -0
  12. data/lib/rails_band/action_controller/event/send_file.rb +44 -0
  13. data/lib/rails_band/action_controller/event/start_processing.rb +44 -0
  14. data/lib/rails_band/action_controller/event/unpermitted_parameters.rb +42 -0
  15. data/lib/rails_band/action_controller/event/write_fragment.rb +14 -0
  16. data/lib/rails_band/action_controller/log_subscriber.rb +76 -0
  17. data/lib/rails_band/action_view/event/render_collection.rb +34 -0
  18. data/lib/rails_band/action_view/event/render_partial.rb +28 -0
  19. data/lib/rails_band/action_view/event/render_template.rb +24 -0
  20. data/lib/rails_band/action_view/from_views.rb +18 -0
  21. data/lib/rails_band/action_view/log_subscriber.rb +32 -0
  22. data/lib/rails_band/active_record/event/instantiation.rb +18 -0
  23. data/lib/rails_band/active_record/event/sql.rb +49 -0
  24. data/lib/rails_band/active_record/event/strict_loading_violation.rb +18 -0
  25. data/lib/rails_band/active_record/log_subscriber.rb +32 -0
  26. data/lib/rails_band/base_event.rb +44 -0
  27. data/lib/rails_band/configuration.rb +55 -0
  28. data/lib/rails_band/railtie.rb +29 -0
  29. data/lib/rails_band/version.rb +5 -0
  30. data/lib/rails_band.rb +22 -0
  31. 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsBand
4
+ VERSION = '0.1.0'
5
+ 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: []