Modify LogSubscriber for single partial's cache message.

Implement naive partial caching mechanism.

Add test for LogSubscriber

Use ActionView::Base#log_payload to store log_subscriber's payload, so we can pass cache result into it.

Fixed tests

Remove useless settings

Check if #log_payload exists before calling it. Because other classes also includes CacheHelper but don't have is attribute

Use @log_payload_for_partial_reder instead of #log_payload to carry ActionView's payload.

Update test's hash syntax

Add configuration to enable/disable fragment caching logging

Remove unless test and add new test to ensure cache info won't effect next rendering's log

Move :enable_fragment_cache_logging config from ActionView to ActionPack

Apply new config to tests

Update actionview's changelog

Update configuration guide

Improve actionview's changelog

Refactor PartialRenderer#render and log tests

Mute subscriber's log instead of disabling instrumentation.

Fix typo, remove useless comment and use new hash syntax

Improve actionpack's log_subscriber test

Fix rebase mistake

Apply new config to all caching intstrument actions
This commit is contained in:
Stan Lo 2016-07-14 18:38:16 +08:00
parent c4f8c45ba5
commit ab2af4dfcb
11 changed files with 157 additions and 31 deletions

View File

@ -172,6 +172,7 @@ class FunctionalFragmentCachingTest < BaseCachingTest
end
def test_fragment_cache_instrumentation
@mailer.enable_fragment_cache_logging = true
payload = nil
subscriber = proc do |*args|
@ -185,6 +186,8 @@ class FunctionalFragmentCachingTest < BaseCachingTest
assert_equal "caching_mailer", payload[:mailer]
assert_equal "views/caching/#{template_digest("caching_mailer/fragment_cache")}", payload[:key]
ensure
@mailer.enable_fragment_cache_logging = true
end
private

View File

@ -34,6 +34,9 @@ module AbstractController
config_accessor :perform_caching
self.perform_caching = true if perform_caching.nil?
config_accessor :enable_fragment_cache_logging
self.enable_fragment_cache_logging = false
class_attribute :_view_cache_dependencies
self._view_cache_dependencies = []
helper_method :view_cache_dependencies if respond_to?(:helper_method)

View File

@ -59,14 +59,13 @@ module ActionController
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{method}(event)
return unless logger.info?
return unless logger.info? && ActionController::Base.enable_fragment_cache_logging
key_or_path = event.payload[:key] || event.payload[:path]
human_name = #{method.to_s.humanize.inspect}
info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)")
end
METHOD
end
def logger
ActionController::Base.logger
end

View File

@ -184,6 +184,7 @@ class FunctionalFragmentCachingTest < ActionController::TestCase
@controller = FunctionalCachingController.new
@controller.perform_caching = true
@controller.cache_store = @store
@controller.enable_fragment_cache_logging = true
end
def test_fragment_caching

View File

@ -92,6 +92,7 @@ class ACLogSubscriberTest < ActionController::TestCase
def setup
super
@controller.enable_fragment_cache_logging = true
@old_logger = ActionController::Base.logger
@ -105,6 +106,7 @@ class ACLogSubscriberTest < ActionController::TestCase
ActiveSupport::LogSubscriber.log_subscribers.clear
FileUtils.rm_rf(@cache_path)
ActionController::Base.logger = @old_logger
ActionController::Base.enable_fragment_cache_logging = true
end
def set_logger(logger)
@ -258,6 +260,20 @@ class ACLogSubscriberTest < ActionController::TestCase
@controller.config.perform_caching = true
end
def test_with_fragment_cache_when_log_disabled
@controller.config.perform_caching = true
ActionController::Base.enable_fragment_cache_logging = false
get :with_fragment_cache
wait
assert_equal 2, logs.size
assert_equal "Processing by Another::LogSubscribersController#with_fragment_cache as HTML", logs[0]
assert_match(/Completed 200 OK in \d+ms/, logs[1])
ensure
@controller.config.perform_caching = true
ActionController::Base.enable_fragment_cache_logging = true
end
def test_with_fragment_cache_if_with_true
@controller.config.perform_caching = true
get :with_fragment_cache_if_with_true_condition

View File

@ -9,6 +9,30 @@
*Steven Harman*
* Show cache hits and misses when rendering partials.
Partials using the `cache` helper will show whether a render hit or missed
the cache:
```
Rendered messages/_message.html.erb in 1.2 ms [cache hit]
Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss]
```
This removes the need for the old fragment cache logging:
```
Read fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/d0bdf2974e1ef6d31685c3b392ad0b74 (0.6ms)
Rendered messages/_message.html.erb in 1.2 ms [cache hit]
Write fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/3b4e249ac9d168c617e32e84b99218b5 (1.1ms)
Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss]
```
Though that full output can be reenabled with
`config.action_controller.enable_fragment_cache_logging = true`.
*Stan Lo*
* New syntax for tag helpers. Avoid positional parameters and support HTML5 by default.
Example usage of tag helpers before:

View File

@ -226,7 +226,13 @@ module ActionView
# TODO: Create an object that has caching read/write on it
def fragment_for(name = {}, options = nil, &block) #:nodoc:
read_fragment_for(name, options) || write_fragment_for(name, options, &block)
if content = read_fragment_for(name, options)
@log_payload_for_partial_render[:cache_hit] = true if defined?(@log_payload_for_partial_render)
content
else
@log_payload_for_partial_render[:cache_hit] = false if defined?(@log_payload_for_partial_render)
write_fragment_for(name, options, &block)
end
end
def read_fragment_for(name, options) #:nodoc:

View File

@ -19,7 +19,16 @@ module ActionView
message << " (#{event.duration.round(1)}ms)"
end
end
alias :render_partial :render_template
def render_partial(event)
info do
message = " Rendered #{from_rails_root(event.payload[:identifier])}"
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
message << " (#{event.duration.round(1)}ms)"
message << " #{cache_message(event.payload)}" if event.payload.key?(:cache_hit)
message
end
end
def render_collection(event)
identifier = event.payload[:identifier] || "templates"
@ -63,6 +72,14 @@ module ActionView
end
end
def cache_message(payload)
if payload[:cache_hit]
"[cache hit]"
else
"[cache miss]"
end
end
private
def log_rendering_start(payload)

View File

@ -308,9 +308,7 @@ module ActionView
if @collection
render_collection
else
instrument(:partial) do
render_partial
end
render_partial
end
end
@ -331,22 +329,26 @@ module ActionView
end
def render_partial
view, locals, block = @view, @locals, @block
object, as = @object, @variable
instrument(:partial) do |payload|
view, locals, block = @view, @locals, @block
object, as = @object, @variable
if !block && (layout = @options[:layout])
layout = find_template(layout.to_s, @template_keys)
view.instance_variable_set(:@log_payload_for_partial_render, payload)
if !block && (layout = @options[:layout])
layout = find_template(layout.to_s, @template_keys)
end
object = locals[as] if object.nil? # Respect object when object is false
locals[as] = object if @has_object
content = @template.render(view, locals) do |*name|
view._layout_for(*name, &block)
end
content = layout.render(view, locals){ content } if layout
content
end
object = locals[as] if object.nil? # Respect object when object is false
locals[as] = object if @has_object
content = @template.render(view, locals) do |*name|
view._layout_for(*name, &block)
end
content = layout.render(view, locals){ content } if layout
content
end
# Sets up instance variables needed for rendering a partial. This method

View File

@ -30,6 +30,13 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
ActionView::Base.logger = logger
end
def set_cache_controller
controller = ActionController::Base.new
controller.perform_caching = true
controller.cache_store = ActiveSupport::Cache::MemoryStore.new
@view.controller = controller
end
def test_render_file_template
Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
@view.render(file: "test/hello_world")
@ -63,16 +70,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
end
end
def test_render_partial_template
Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
@view.render(partial: "test/customer")
wait
assert_equal 1, @logger.logged(:info).size
assert_match(/Rendered test\/_customer.erb/, @logger.logged(:info).last)
end
end
def test_render_partial_with_implicit_path
Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
@view.render(Customer.new("david"), greeting: "hi")
@ -83,6 +80,56 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
end
end
def test_render_partial_with_cache_missed
Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
def @view.view_cache_dependencies; []; end
def @view.fragment_cache_key(*); 'ahoy `controller` dependency'; end
set_cache_controller
@view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") })
wait
assert_equal 1, @logger.logged(:info).size
assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache miss\]/, @logger.logged(:info).last)
end
end
def test_render_partial_with_cache_hitted
Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
def @view.view_cache_dependencies; []; end
def @view.fragment_cache_key(*); 'ahoy `controller` dependency'; end
set_cache_controller
@view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") })
# Second render should hit cache.
@view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") })
wait
assert_equal 2, @logger.logged(:info).size
assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache hit\]/, @logger.logged(:info).last)
end
end
def test_render_partial_with_cache_hitted_and_missed
Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
def @view.view_cache_dependencies; []; end
def @view.fragment_cache_key(*); 'ahoy `controller` dependency'; end
set_cache_controller
@view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") })
wait
assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache miss\]/, @logger.logged(:info).last)
@view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") })
wait
assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache hit\]/, @logger.logged(:info).last)
@view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("Stan") })
wait
assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache miss\]/, @logger.logged(:info).last)
end
end
def test_render_collection_template
Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
@view.render(partial: "test/customer", collection: [ Customer.new("david"), Customer.new("mary") ])

View File

@ -516,6 +516,14 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to `true`.
* `config.action_view.enable_fragment_cache_logging` determines whether to log fragment cache reads and writes like:
```
Read fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/d0bdf2974e1ef6d31685c3b392ad0b74 (0.6ms)
```
Default value is false.
### Configuring Action Mailer
There are a number of settings available on `config.action_mailer`: