Skip to content

Commit f0f4d0f

Browse files
committed
Remove Rails monkey-patching where feasible.
Monkey patching Rails is a PIA, swap out the built-in rails subscribers with custom ones.
1 parent 42e34dd commit f0f4d0f

23 files changed

+533
-298
lines changed

.travis.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ language: ruby
33
gemfile:
44
- gemfiles/rails_4.2.gemfile
55
- gemfiles/rails_5.0.2.gemfile
6-
- gemfiles/rails_5.0.gemfile
7-
- gemfiles/rails_5.1.gemfile
6+
- gemfiles/rails_5.0.6.gemfile
7+
- gemfiles/rails_5.0.7.gemfile
8+
- gemfiles/rails_5.1.4.gemfile
9+
- gemfiles/rails_5.1.5.gemfile
810
- gemfiles/rails_5.2.gemfile
911

1012
rvm:

Appraisals

+13-6
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,22 @@ appraise 'rails_5.0.2' do
88
gem 'rails', '5.0.2'
99
end
1010

11-
# Breaking changes in Rails 5.0.3
12-
appraise 'rails_5.0' do
13-
gem 'rails', '~> 5.0.0', '>= 5.0.3'
11+
appraise 'rails_5.0.6' do
12+
gem 'rails', '5.0.6'
1413
end
1514

16-
appraise 'rails_5.1' do
17-
gem 'rails', '~> 5.1.0'
15+
appraise 'rails_5.0.7' do
16+
gem 'rails', '~> 5.0.7'
17+
end
18+
19+
appraise 'rails_5.1.4' do
20+
gem 'rails', '5.1.4'
21+
end
22+
23+
appraise 'rails_5.1.5' do
24+
gem 'rails', '~> 5.1.5'
1825
end
1926

2027
appraise 'rails_5.2' do
21-
gem 'rails', '5.2.0.rc2'
28+
gem 'rails', '~> 5.2.0'
2229
end

Gemfile

+1-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ gemspec
55
gem 'rake'
66
gem 'minitest'
77
gem 'minitest-rails'
8-
gem 'minitest-reporters'
9-
gem 'minitest-stub_any_instance'
108
gem 'awesome_print'
119
gem 'active_model_serializers'
1210

@@ -16,4 +14,4 @@ gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby
1614
gem 'appraisal'
1715
gem 'puma'
1816

19-
gem 'rails', '~> 4.2.0'
17+
gem 'rails', '~> 5.2.0'

gemfiles/rails_5.1.gemfile renamed to gemfiles/rails_5.0.6.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ gem "jdbc-sqlite3", platform: :jruby
1414
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
1515
gem "appraisal"
1616
gem "puma"
17-
gem "rails", "~> 5.1.0"
17+
gem "rails", "5.0.6"
1818

1919
gemspec path: "../"

gemfiles/rails_5.0.gemfile renamed to gemfiles/rails_5.0.7.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ gem "jdbc-sqlite3", platform: :jruby
1414
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
1515
gem "appraisal"
1616
gem "puma"
17-
gem "rails", "~> 5.0.0", ">= 5.0.3"
17+
gem "rails", "~> 5.0.7"
1818

1919
gemspec path: "../"

gemfiles/rails_5.1.4.gemfile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This file was generated by Appraisal
2+
3+
source "https://rubygems.org"
4+
5+
gem "rake"
6+
gem "minitest"
7+
gem "minitest-rails"
8+
gem "minitest-reporters"
9+
gem "minitest-stub_any_instance"
10+
gem "awesome_print"
11+
gem "active_model_serializers"
12+
gem "sqlite3", platform: :ruby
13+
gem "jdbc-sqlite3", platform: :jruby
14+
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
15+
gem "appraisal"
16+
gem "puma"
17+
gem "rails", "5.1.4"
18+
19+
gemspec path: "../"

gemfiles/rails_5.1.5.gemfile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This file was generated by Appraisal
2+
3+
source "https://rubygems.org"
4+
5+
gem "rake"
6+
gem "minitest"
7+
gem "minitest-rails"
8+
gem "minitest-reporters"
9+
gem "minitest-stub_any_instance"
10+
gem "awesome_print"
11+
gem "active_model_serializers"
12+
gem "sqlite3", platform: :ruby
13+
gem "jdbc-sqlite3", platform: :jruby
14+
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
15+
gem "appraisal"
16+
gem "puma"
17+
gem "rails", "~> 5.1.5"
18+
19+
gemspec path: "../"

gemfiles/rails_5.2.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ gem "jdbc-sqlite3", platform: :jruby
1414
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
1515
gem "appraisal"
1616
gem "puma"
17-
gem "rails", "5.2.0.rc2"
17+
gem "rails", "~> 5.2.0"
1818

1919
gemspec path: "../"

lib/rails_semantic_logger.rb

+31
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,38 @@
33
require 'rails_semantic_logger/engine'
44

55
module RailsSemanticLogger
6+
module ActionController
7+
autoload :LogSubscriber, 'rails_semantic_logger/action_controller/log_subscriber'
8+
end
9+
module ActionView
10+
autoload :LogSubscriber, 'rails_semantic_logger/action_view/log_subscriber'
11+
end
12+
module ActiveRecord
13+
autoload :LogSubscriber, 'rails_semantic_logger/active_record/log_subscriber'
14+
end
615
module Rack
716
autoload :Logger, 'rails_semantic_logger/rack/logger'
817
end
18+
19+
# Swap an existing subscriber with a new one
20+
def self.swap_subscriber(old_class, new_class, notifier)
21+
subscribers = ActiveSupport::LogSubscriber.subscribers.select { |s| s.kind_of?(old_class) }
22+
subscribers.each { |subscriber| unattach(subscriber) }
23+
24+
new_class.attach_to(notifier)
25+
end
26+
27+
private
28+
29+
def self.unattach(subscriber)
30+
subscriber.patterns.each do |event|
31+
ActiveSupport::Notifications.notifier.listeners_for(event).each do |sub|
32+
next unless sub.instance_variable_get(:@delegate) == subscriber
33+
ActiveSupport::Notifications.unsubscribe(sub)
34+
end
35+
end
36+
37+
ActiveSupport::LogSubscriber.subscribers.delete(subscriber)
38+
end
39+
940
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
require 'action_controller/log_subscriber'
2+
ActionController::LogSubscriber
3+
4+
module RailsSemanticLogger
5+
module ActionController
6+
class LogSubscriber < ActiveSupport::LogSubscriber
7+
INTERNAL_PARAMS = %w(controller action format _method only_path)
8+
9+
# Log as debug to hide Processing messages in production
10+
def start_processing(event)
11+
controller_logger(event).debug { "Processing ##{event.payload[:action]}" }
12+
end
13+
14+
def process_action(event)
15+
controller_logger(event).info do
16+
payload = event.payload.dup
17+
18+
# Unused, but needed for Devise 401 status code monkey patch to still work.
19+
::ActionController::Base.log_process_action(payload)
20+
21+
# According to PR https://github.com/rocketjob/rails_semantic_logger/pull/37/files
22+
# payload[:params] is not always a Hash.
23+
payload[:params] = payload[:params].to_unsafe_h unless payload[:params].is_a?(Hash)
24+
payload[:params].except!(*INTERNAL_PARAMS)
25+
payload.delete(:params) if payload[:params].empty?
26+
27+
format = payload[:format]
28+
payload[:format] = format.to_s.upcase if format.is_a?(Symbol)
29+
30+
payload[:path] = extract_path(payload[:path]) if payload.has_key?(:path)
31+
32+
exception = payload.delete(:exception)
33+
if payload[:status].nil? && exception.present?
34+
exception_class_name = exception.first
35+
payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
36+
end
37+
38+
# Rounds off the runtimes. For example, :view_runtime, :mongo_runtime, etc.
39+
payload.keys.each do |key|
40+
payload[key] = payload[key].to_f.round(2) if key.to_s.match(/(.*)_runtime/)
41+
end
42+
43+
payload[:status_message] = ::Rack::Utils::HTTP_STATUS_CODES[payload[:status]] if payload[:status].present?
44+
# Causes excessive log output with Rails 5 RC1
45+
payload.delete(:headers)
46+
47+
{
48+
message: "Completed ##{payload[:action]}",
49+
duration: event.duration,
50+
payload: payload
51+
}
52+
end
53+
end
54+
55+
def halted_callback(event)
56+
controller_logger(event).info { "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected" }
57+
end
58+
59+
def send_file(event)
60+
controller_logger(event).info('Sent file') { {path: event.payload[:path], duration: event.duration} }
61+
end
62+
63+
def redirect_to(event)
64+
controller_logger(event).info('Redirected to') { {location: event.payload[:location]} }
65+
end
66+
67+
def send_data(event)
68+
controller_logger(event).info('Sent data') { {file_name: event.payload[:filename], duration: event.duration} }
69+
end
70+
71+
def unpermitted_parameters(event)
72+
controller_logger(event).debug do
73+
unpermitted_keys = event.payload[:keys]
74+
"Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}"
75+
end
76+
end
77+
78+
%w(write_fragment read_fragment exist_fragment?
79+
expire_fragment expire_page write_page).each do |method|
80+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
81+
def #{method}(event)
82+
# enable_fragment_cache_logging as of Rails 5
83+
return if ::ActionController::Base.respond_to?(:enable_fragment_cache_logging) && !::ActionController::Base.enable_fragment_cache_logging
84+
controller_logger(event).info do
85+
key_or_path = event.payload[:key] || event.payload[:path]
86+
{message: "#{method.to_s.humanize} \#{key_or_path}", duration: event.duration}
87+
end
88+
end
89+
METHOD
90+
end
91+
92+
private
93+
94+
# Returns the logger for the supplied event.
95+
# Returns ActionController::Base.logger if no controller is present
96+
def controller_logger(event)
97+
controller = event.payload[:controller]
98+
return ::ActionController::Base.logger unless controller
99+
100+
controller.constantize.logger || ::ActionController::Base.logger
101+
rescue NameError
102+
::ActionController::Base.logger
103+
end
104+
105+
def extract_path(path)
106+
index = path.index('?')
107+
index ? path[0, index] : path
108+
end
109+
110+
end
111+
end
112+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
require 'active_support/log_subscriber'
2+
3+
module RailsSemanticLogger
4+
module ActionView
5+
# Output Semantic logs from Action View.
6+
class LogSubscriber < ActiveSupport::LogSubscriber
7+
VIEWS_PATTERN = /^app\/views\//
8+
9+
class << self
10+
attr_reader :logger
11+
attr_accessor :rendered_log_level
12+
end
13+
14+
def initialize
15+
@root = nil
16+
super
17+
end
18+
19+
def render_template(event)
20+
return unless self.class.should_log?
21+
22+
payload = {
23+
template: from_rails_root(event.payload[:identifier])
24+
}
25+
payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
26+
27+
logger.measure(
28+
self.class.rendered_log_level,
29+
'Rendered',
30+
payload: payload,
31+
duration: event.duration
32+
)
33+
end
34+
35+
def render_partial(event)
36+
return unless self.class.should_log?
37+
38+
payload = {
39+
partial: from_rails_root(event.payload[:identifier])
40+
}
41+
payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
42+
payload[:cache] = payload[:cache_hit] unless event.payload[:cache_hit].nil?
43+
44+
logger.measure(
45+
self.class.rendered_log_level,
46+
'Rendered',
47+
payload: payload,
48+
duration: event.duration
49+
)
50+
end
51+
52+
def render_collection(event)
53+
return unless self.class.should_log?
54+
55+
identifier = event.payload[:identifier] || 'templates'
56+
57+
payload = {
58+
template: from_rails_root(identifier),
59+
count: payload[:count]
60+
}
61+
payload[:cache_hits] = payload[:cache_hits] if payload[:cache_hits]
62+
63+
logger.measure(
64+
self.class.rendered_log_level,
65+
'Rendered',
66+
payload: payload,
67+
duration: event.duration
68+
)
69+
end
70+
71+
def start(name, id, payload)
72+
if (name == 'render_template.action_view') && self.class.should_log?
73+
payload = {template: from_rails_root(payload[:identifier])}
74+
payload[:within] = from_rails_root(payload[:layout]) if payload[:layout]
75+
76+
logger.send(self.class.rendered_log_level, message: 'Rendering', payload: payload)
77+
end
78+
79+
super
80+
end
81+
82+
private
83+
84+
@logger = SemanticLogger['ActionView']
85+
@rendered_log_level = :debug
86+
87+
EMPTY = ''
88+
89+
def self.should_log?
90+
logger.send("#{rendered_log_level}?")
91+
end
92+
93+
def from_rails_root(string)
94+
string = string.sub(rails_root, EMPTY)
95+
string.sub!(VIEWS_PATTERN, EMPTY)
96+
string
97+
end
98+
99+
def rails_root
100+
@root ||= "#{Rails.root}/"
101+
end
102+
103+
def logger
104+
self.class.logger
105+
end
106+
end
107+
end
108+
end

0 commit comments

Comments
 (0)