rails_band 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []