mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge remote branch 'jose/perf'
This commit is contained in:
commit
12e43494a7
28 changed files with 373 additions and 306 deletions
|
@ -9,25 +9,5 @@ module AbstractController
|
|||
cattr_accessor :logger
|
||||
extend ActiveSupport::Benchmarkable
|
||||
end
|
||||
|
||||
# A class that allows you to defer expensive processing
|
||||
# until the logger actually tries to log. Otherwise, you are
|
||||
# forced to do the processing in advance, and send the
|
||||
# entire processed String to the logger, which might
|
||||
# just discard the String if the log level is too low.
|
||||
#
|
||||
# TODO: Require that Rails loggers accept a block.
|
||||
class DelayedLog < ActiveSupport::BasicObject
|
||||
def initialize(&block)
|
||||
@str, @block = nil, block
|
||||
end
|
||||
|
||||
def method_missing(*args, &block)
|
||||
unless @str
|
||||
@str, @block = @block.call, nil
|
||||
end
|
||||
@str.send(*args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,6 @@ module ActionController
|
|||
include ActionController::ConditionalGet
|
||||
include ActionController::RackDelegation
|
||||
include ActionController::Logger
|
||||
include ActionController::Benchmarking
|
||||
include ActionController::Configuration
|
||||
|
||||
# Legacy modules
|
||||
|
|
|
@ -60,6 +60,17 @@ module ActionController #:nodoc:
|
|||
def cache_configured?
|
||||
perform_caching && cache_store
|
||||
end
|
||||
|
||||
def log_event(name, before, after, instrumenter_id, payload)
|
||||
if name.to_s =~ /(read|write|cache|expire|exist)_(fragment|page)\??/
|
||||
key_or_path = payload[:key] || payload[:path]
|
||||
human_name = name.to_s.humanize
|
||||
duration = (after - before) * 1000
|
||||
logger.info("#{human_name} #{key_or_path.inspect} (%.1fms)" % duration)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def caching_allowed?
|
||||
|
|
|
@ -74,7 +74,7 @@ module ActionController #:nodoc:
|
|||
return unless cache_configured?
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
ActiveSupport::Notifications.instrument(:fragment_exist?, :key => key) do
|
||||
ActiveSupport::Notifications.instrument(:exist_fragment?, :key => key) do
|
||||
cache_store.exist?(key, options)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
require 'active_support/core_ext/benchmark'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# The benchmarking module times the performance of actions and reports to the logger. If the Active Record
|
||||
# package has been included, a separate timing section for database calls will be added as well.
|
||||
module Benchmarking #:nodoc:
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
protected
|
||||
def render(*args, &block)
|
||||
if logger
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
end
|
||||
|
||||
render_output = nil
|
||||
@view_runtime = Benchmark.ms { render_output = super }
|
||||
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
@db_rt_before_render = db_runtime
|
||||
@db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
|
||||
@view_runtime -= @db_rt_after_render
|
||||
end
|
||||
|
||||
render_output
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def process_action(*args)
|
||||
if logger
|
||||
ms = [Benchmark.ms { super }, 0.01].max
|
||||
logging_view = defined?(@view_runtime)
|
||||
logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
|
||||
log_message = 'Completed in %.0fms' % ms
|
||||
|
||||
if logging_view || logging_active_record
|
||||
log_message << " ("
|
||||
log_message << view_runtime if logging_view
|
||||
|
||||
if logging_active_record
|
||||
log_message << ", " if logging_view
|
||||
log_message << active_record_runtime + ")"
|
||||
else
|
||||
")"
|
||||
end
|
||||
end
|
||||
|
||||
log_message << " | #{response.status}"
|
||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||
|
||||
logger.info(log_message)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def view_runtime
|
||||
"View: %.0f" % @view_runtime
|
||||
end
|
||||
|
||||
def active_record_runtime
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
db_runtime += @db_rt_before_render if @db_rt_before_render
|
||||
db_runtime += @db_rt_after_render if @db_rt_after_render
|
||||
"DB: %.0f" % db_runtime
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,8 +2,6 @@ module ActionController
|
|||
module FilterParameterLogging
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include AbstractController::Logger
|
||||
|
||||
module ClassMethods
|
||||
# Replace sensitive parameter data from the request log.
|
||||
# Filters parameters that have any of the arguments as a substring.
|
||||
|
@ -54,23 +52,25 @@ module ActionController
|
|||
end
|
||||
protected :filter_parameters
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Overwrite log_process_action to include parameters information.
|
||||
# If this method is invoked, it means logger is defined, so don't
|
||||
# worry with such scenario here.
|
||||
def log_process_action(controller) #:nodoc:
|
||||
params = controller.send(:filter_parameters, controller.request.params)
|
||||
logger.info " Parameters: #{params.inspect}" unless params.empty?
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path]
|
||||
|
||||
def process(*)
|
||||
response = super
|
||||
if logger
|
||||
parameters = filter_parameters(params).except!(*INTERNAL_PARAMS)
|
||||
logger.info { " Parameters: #{parameters.inspect}" } unless parameters.empty?
|
||||
end
|
||||
response
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def filter_parameters(params)
|
||||
params.dup
|
||||
params.dup.except!(*INTERNAL_PARAMS)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,34 +1,85 @@
|
|||
require 'abstract_controller/logger'
|
||||
|
||||
module ActionController
|
||||
# Adds instrumentation to <tt>process_action</tt> and a <tt>log_event</tt> method
|
||||
# responsible to log events from ActiveSupport::Notifications. This module handles
|
||||
# :process_action and :render_template events but allows any other module to hook
|
||||
# into log_event and provide its own logging facilities (as in ActionController::Caching).
|
||||
module Logger
|
||||
# Override process_action in the AbstractController::Base
|
||||
# to log details about the method.
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
attr_internal :view_runtime
|
||||
|
||||
def process_action(action)
|
||||
result = ActiveSupport::Notifications.instrument(:process_action,
|
||||
:controller => self, :action => action) do
|
||||
ActiveSupport::Notifications.instrument(:process_action, :controller => self, :action => action) do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def render(*args, &block)
|
||||
if logger
|
||||
log = AbstractController::Logger::DelayedLog.new do
|
||||
"\n\nProcessing #{self.class.name}\##{action_name} " \
|
||||
"to #{request.formats} (for #{request_origin}) " \
|
||||
"[#{request.method.to_s.upcase}]"
|
||||
render_output = nil
|
||||
|
||||
self.view_runtime = cleanup_view_runtime do
|
||||
Benchmark.ms { render_output = super }
|
||||
end
|
||||
|
||||
logger.info(log)
|
||||
render_output
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# If you want to remove any time taken into account in :view_runtime
|
||||
# wrongly, you can do it here:
|
||||
#
|
||||
# def cleanup_view_runtime
|
||||
# super - time_taken_in_something_expensive
|
||||
# end
|
||||
#
|
||||
# :api: plugin
|
||||
def cleanup_view_runtime #:nodoc:
|
||||
yield
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# This is the hook invoked by ActiveSupport::Notifications.subscribe.
|
||||
# If you need to log any event, overwrite the method and do it here.
|
||||
def log_event(name, before, after, instrumenter_id, payload) #:nodoc:
|
||||
if name == :process_action
|
||||
duration = [(after - before) * 1000, 0.01].max
|
||||
controller = payload[:controller]
|
||||
request = controller.request
|
||||
|
||||
logger.info "\n\nProcessed #{controller.class.name}##{payload[:action]} " \
|
||||
"to #{request.formats} (for #{request.remote_ip} at #{before.to_s(:db)}) " \
|
||||
"[#{request.method.to_s.upcase}]"
|
||||
|
||||
log_process_action(controller)
|
||||
|
||||
message = "Completed in %.0fms" % duration
|
||||
message << " | #{controller.response.status}"
|
||||
message << " [#{request.request_uri rescue "unknown"}]"
|
||||
|
||||
logger.info(message)
|
||||
elsif name == :render_template
|
||||
# TODO Make render_template logging work if you are using just ActionView
|
||||
duration = (after - before) * 1000
|
||||
message = "Rendered #{payload[:identifier]}"
|
||||
message << " within #{payload[:layout]}" if payload[:layout]
|
||||
message << (" (%.1fms)" % duration)
|
||||
logger.info(message)
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
protected
|
||||
|
||||
private
|
||||
|
||||
# Returns the request origin with the IP and time. This needs to be cached,
|
||||
# otherwise we would get different results for each time it calls.
|
||||
def request_origin
|
||||
@request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}"
|
||||
# A hook which allows logging what happened during controller process action.
|
||||
# :api: plugin
|
||||
def log_process_action(controller) #:nodoc:
|
||||
view_runtime = controller.send :view_runtime
|
||||
logger.info(" View runtime: %.1fms" % view_runtime.to_f) if view_runtime
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
module ActionController
|
||||
class Plugin < Rails::Plugin
|
||||
plugin_name :action_controller
|
||||
include_modules_in "ActionController::Base"
|
||||
|
||||
initializer "action_controller.set_configs" do |app|
|
||||
app.config.action_controller.each do |k,v|
|
||||
|
@ -14,10 +15,6 @@ module ActionController
|
|||
end
|
||||
|
||||
# Routing must be initialized after plugins to allow the former to extend the routes
|
||||
# ---
|
||||
# If Action Controller is not one of the loaded frameworks (Configuration#frameworks)
|
||||
# this does nothing. Otherwise, it loads the routing definitions and sets up
|
||||
# loading module used to lazily load controllers (Configuration#controller_paths).
|
||||
initializer "action_controller.initialize_routing" do |app|
|
||||
app.route_configuration_files << app.config.routes_configuration_file
|
||||
app.route_configuration_files << app.config.builtin_routes_configuration_file
|
||||
|
@ -88,13 +85,8 @@ module ActionController
|
|||
initializer "action_controller.notifications" do |app|
|
||||
require 'active_support/notifications'
|
||||
|
||||
ActiveSupport::Notifications.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |*args|
|
||||
event = ActiveSupport::Notifications::Event.new(*args)
|
||||
|
||||
if logger = ActionController::Base.logger
|
||||
human_name = event.name.to_s.humanize
|
||||
logger.info("#{human_name} (%.1fms)" % event.duration)
|
||||
end
|
||||
ActiveSupport::Notifications.subscribe do |*args|
|
||||
ActionController::Base.log_event(*args) if ActionController::Base.logger
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -191,45 +191,60 @@ module ActionView
|
|||
def setup(options, block)
|
||||
partial = options[:partial]
|
||||
|
||||
@options = options
|
||||
@locals = options[:locals] || {}
|
||||
@block = block
|
||||
@options = options
|
||||
@locals = options[:locals] || {}
|
||||
@block = block
|
||||
|
||||
if String === partial
|
||||
@object = options[:object]
|
||||
@path = partial
|
||||
@object = options[:object]
|
||||
@path = partial
|
||||
@collection = collection
|
||||
else
|
||||
@object = partial
|
||||
@path = partial_path(partial)
|
||||
|
||||
if @collection = collection
|
||||
paths = @collection_paths = @collection.map { |o| partial_path(o) }
|
||||
@path = paths.uniq.size == 1 ? paths.first : nil
|
||||
else
|
||||
@path = partial_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render
|
||||
if @collection = collection
|
||||
render_collection
|
||||
options = @options
|
||||
|
||||
if @collection
|
||||
ActiveSupport::Notifications.instrument(:render_collection, :path => @path,
|
||||
:count => @collection.size) do
|
||||
render_collection
|
||||
end
|
||||
else
|
||||
@template = template = find_template
|
||||
render_template(template, @object || @locals[template.variable_name])
|
||||
content = ActiveSupport::Notifications.instrument(:render_partial, :path => @path) do
|
||||
render_partial
|
||||
end
|
||||
|
||||
if !@block && options[:layout]
|
||||
content = @view._render_layout(find_template(options[:layout]), @locals){ content }
|
||||
end
|
||||
content
|
||||
end
|
||||
end
|
||||
|
||||
def render_collection
|
||||
@template = template = find_template
|
||||
|
||||
return nil if @collection.blank?
|
||||
|
||||
if @options.key?(:spacer_template)
|
||||
spacer = find_template(@options[:spacer_template]).render(@view, @locals)
|
||||
end
|
||||
|
||||
result = template ? collection_with_template(template) : collection_without_template
|
||||
result = template ? collection_with_template : collection_without_template
|
||||
result.join(spacer).html_safe!
|
||||
end
|
||||
|
||||
def collection_with_template(template)
|
||||
options = @options
|
||||
|
||||
segments, locals, as = [], @locals, options[:as] || template.variable_name
|
||||
def collection_with_template(template = @template)
|
||||
segments, locals, as = [], @locals, @options[:as] || template.variable_name
|
||||
|
||||
counter_name = template.counter_name
|
||||
locals[counter_name] = -1
|
||||
|
@ -245,16 +260,14 @@ module ActionView
|
|||
segments
|
||||
end
|
||||
|
||||
def collection_without_template
|
||||
options = @options
|
||||
|
||||
segments, locals, as = [], @locals, options[:as]
|
||||
def collection_without_template(collection_paths = @collection_paths)
|
||||
segments, locals, as = [], @locals, @options[:as]
|
||||
index, template = -1, nil
|
||||
|
||||
@collection.each do |object|
|
||||
template = find_template(partial_path(object))
|
||||
@collection.each_with_index do |object, i|
|
||||
template = find_template(collection_paths[i])
|
||||
locals[template.counter_name] = (index += 1)
|
||||
locals[template.variable_name] = object
|
||||
locals[as || template.variable_name] = object
|
||||
|
||||
segments << template.render(@view, locals)
|
||||
end
|
||||
|
@ -263,18 +276,15 @@ module ActionView
|
|||
segments
|
||||
end
|
||||
|
||||
def render_template(template, object = @object)
|
||||
options, locals, view = @options, @locals, @view
|
||||
locals[options[:as] || template.variable_name] = object
|
||||
def render_partial(object = @object)
|
||||
@template = template = find_template
|
||||
locals, view = @locals, @view
|
||||
|
||||
content = template.render(view, locals) do |*name|
|
||||
@view._layout_for(*name, &@block)
|
||||
end
|
||||
object ||= locals[template.variable_name]
|
||||
locals[@options[:as] || template.variable_name] = object
|
||||
|
||||
if @block || !options[:layout]
|
||||
content
|
||||
else
|
||||
find_template(options[:layout]).render(@view, @locals) { content }
|
||||
template.render(view, locals) do |*name|
|
||||
view._layout_for(*name, &@block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -305,9 +315,9 @@ module ActionView
|
|||
|
||||
def partial_path(object = @object)
|
||||
@partial_names[object.class] ||= begin
|
||||
return nil unless object.respond_to?(:to_model)
|
||||
object = object.to_model if object.respond_to?(:to_model)
|
||||
|
||||
object.to_model.class.model_name.partial_path.dup.tap do |partial|
|
||||
object.class.model_name.partial_path.dup.tap do |partial|
|
||||
path = @view.controller_path
|
||||
partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/)
|
||||
end
|
||||
|
|
|
@ -73,25 +73,24 @@ module ActionView
|
|||
# would be <html>Hello David</html>.
|
||||
def _layout_for(name = nil, &block)
|
||||
return @_content_for[name || :layout] if !block_given? || name
|
||||
|
||||
capture(&block)
|
||||
end
|
||||
|
||||
def _render_inline(inline, layout, options)
|
||||
handler = Template.handler_class_for_extension(options[:type] || "erb")
|
||||
template = Template.new(options[:inline], "inline template", handler, {})
|
||||
locals = options[:locals]
|
||||
|
||||
locals = options[:locals]
|
||||
content = template.render(self, locals)
|
||||
content = ActiveSupport::Notifications.instrument(:render_inline) do
|
||||
handler = Template.handler_class_for_extension(options[:type] || "erb")
|
||||
template = Template.new(options[:inline], "inline template", handler, {})
|
||||
template.render(self, locals)
|
||||
end
|
||||
|
||||
_render_text(content, layout, locals)
|
||||
end
|
||||
|
||||
def _render_text(content, layout, locals)
|
||||
content = layout.render(self, locals) do |*name|
|
||||
_layout_for(*name) { content }
|
||||
end if layout
|
||||
|
||||
ActiveSupport::Notifications.instrument(:render_text)
|
||||
content = _render_layout(layout, locals){ content } if layout
|
||||
content
|
||||
end
|
||||
|
||||
|
@ -108,23 +107,27 @@ module ActionView
|
|||
end
|
||||
|
||||
def _render_template(template, layout = nil, options = {}, partial = nil)
|
||||
logger && logger.info do
|
||||
msg = "Rendering #{template.inspect}"
|
||||
msg << " (#{options[:status]})" if options[:status]
|
||||
msg
|
||||
locals = options[:locals] || {}
|
||||
|
||||
content = ActiveSupport::Notifications.instrument(:render_template,
|
||||
:identifier => template.identifier, :layout => (layout ? layout.identifier : nil)) do
|
||||
partial ? _render_partial_object(template, options) : template.render(self, locals)
|
||||
end
|
||||
|
||||
locals = options[:locals] || {}
|
||||
content = partial ? _render_partial_object(template, options) : template.render(self, locals)
|
||||
@_content_for[:layout] = content
|
||||
|
||||
if layout
|
||||
@_layout = layout.identifier
|
||||
logger.info("Rendering template within #{layout.inspect}") if logger
|
||||
content = layout.render(self, locals) { |*name| _layout_for(*name) }
|
||||
content = _render_layout(layout, locals)
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def _render_layout(layout, locals, &block)
|
||||
ActiveSupport::Notifications.instrument(:render_layout, :identifier => layout.identifier) do
|
||||
layout.render(self, locals){ |*name| _layout_for(*name, &block) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,10 +36,8 @@ module ActionView
|
|||
end
|
||||
|
||||
def render(view, locals, &block)
|
||||
ActiveSupport::Notifications.instrument(:render_template, :identifier => identifier) do
|
||||
method_name = compile(locals, view)
|
||||
view.send(method_name, locals, &block)
|
||||
end
|
||||
method_name = compile(locals, view)
|
||||
view.send(method_name, locals, &block)
|
||||
rescue Exception => e
|
||||
if e.is_a?(Template::Error)
|
||||
e.sub_template_of(self)
|
||||
|
|
|
@ -50,6 +50,14 @@ ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
|
|||
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
|
||||
FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
|
||||
|
||||
# Turn on notifications
|
||||
require 'active_support/notifications'
|
||||
Thread.abort_on_exception = true
|
||||
|
||||
ActiveSupport::Notifications.subscribe do |*args|
|
||||
ActionController::Base.log_event(*args) if ActionController::Base.logger
|
||||
end
|
||||
|
||||
module SetupOnce
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
|
@ -87,6 +95,21 @@ class ActiveSupport::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
class MockLogger
|
||||
attr_reader :logged
|
||||
attr_accessor :level
|
||||
|
||||
def initialize
|
||||
@level = Logger::DEBUG
|
||||
@logged = []
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &blk)
|
||||
@logged << args.first
|
||||
@logged << blk.call if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
class ActionController::IntegrationTest < ActiveSupport::TestCase
|
||||
def self.build_app(routes = nil)
|
||||
ActionDispatch::MiddlewareStack.new { |middleware|
|
||||
|
|
|
@ -158,13 +158,6 @@ class PerformActionTest < ActionController::TestCase
|
|||
assert_raise(ActionController::UnknownAction) { get :hidden_action }
|
||||
assert_raise(ActionController::UnknownAction) { get :another_hidden_action }
|
||||
end
|
||||
|
||||
def test_namespaced_action_should_log_module_name
|
||||
use_controller Submodule::ContainedNonEmptyController
|
||||
@controller.logger = MockLogger.new
|
||||
get :public_action
|
||||
assert_match /Processing\sSubmodule::ContainedNonEmptyController#public_action/, @controller.logger.logged[1]
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultUrlOptionsTest < ActionController::TestCase
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
# Provide some static controllers.
|
||||
class BenchmarkedController < ActionController::Base
|
||||
def public_action
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def rescue_action(e)
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
class BenchmarkTest < ActionController::TestCase
|
||||
tests BenchmarkedController
|
||||
|
||||
class MockLogger
|
||||
def method_missing(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
super
|
||||
# benchmark doesn't do anything unless a logger is set
|
||||
@controller.logger = MockLogger.new
|
||||
@request.host = "test.actioncontroller.i"
|
||||
end
|
||||
|
||||
def test_with_http_1_0_request
|
||||
@request.host = nil
|
||||
assert_nothing_raised { get :public_action }
|
||||
end
|
||||
end
|
|
@ -640,7 +640,7 @@ class FragmentCachingTest < ActionController::TestCase
|
|||
assert fragment_computed
|
||||
assert_equal 'generated till now -> ', buffer
|
||||
ActiveSupport::Notifications.notifier.wait
|
||||
assert_equal [:fragment_exist?, :write_fragment], events.map(&:first)
|
||||
assert_equal [:exist_fragment?, :write_fragment], events.map(&:first)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -70,9 +70,9 @@ class FilterParamTest < ActionController::TestCase
|
|||
FilterParamController.filter_parameter_logging(:lifo, :amount)
|
||||
|
||||
get :payment, :lifo => 'Pratik', :amount => '420', :step => '1'
|
||||
ActiveSupport::Notifications.notifier.wait
|
||||
|
||||
filtered_params_logs = logs.detect {|l| l =~ /\AParameters/ }
|
||||
|
||||
assert filtered_params_logs.index('"amount"=>"[FILTERED]"')
|
||||
assert filtered_params_logs.index('"lifo"=>"[FILTERED]"')
|
||||
assert filtered_params_logs.index('"step"=>"1"')
|
||||
|
|
|
@ -1,48 +1,74 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class LoggingController < ActionController::Base
|
||||
def show
|
||||
render :nothing => true
|
||||
module Another
|
||||
class LoggingController < ActionController::Base
|
||||
layout "layouts/standard"
|
||||
|
||||
def show
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def with_layout
|
||||
render :template => "test/hello_world", :layout => true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class LoggingTest < ActionController::TestCase
|
||||
tests LoggingController
|
||||
|
||||
class MockLogger
|
||||
attr_reader :logged
|
||||
attr_accessor :level
|
||||
|
||||
def initialize
|
||||
@level = Logger::DEBUG
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &blk)
|
||||
@logged ||= []
|
||||
@logged << args.first
|
||||
@logged << blk.call if block_given?
|
||||
end
|
||||
end
|
||||
tests Another::LoggingController
|
||||
|
||||
def setup
|
||||
super
|
||||
set_logger
|
||||
end
|
||||
|
||||
def get(*args)
|
||||
super
|
||||
wait
|
||||
end
|
||||
|
||||
def wait
|
||||
ActiveSupport::Notifications.notifier.wait
|
||||
end
|
||||
|
||||
def test_logging_without_parameters
|
||||
get :show
|
||||
assert_equal 3, logs.size
|
||||
assert_equal 4, logs.size
|
||||
assert_nil logs.detect {|l| l =~ /Parameters/ }
|
||||
end
|
||||
|
||||
def test_logging_with_parameters
|
||||
get :show, :id => '10'
|
||||
assert_equal 4, logs.size
|
||||
assert_equal 5, logs.size
|
||||
|
||||
params = logs.detect {|l| l =~ /Parameters/ }
|
||||
assert_equal 'Parameters: {"id"=>"10"}', params
|
||||
end
|
||||
|
||||
def test_log_controller_with_namespace_and_action
|
||||
get :show
|
||||
assert_match /Processed\sAnother::LoggingController#show/, logs[1]
|
||||
end
|
||||
|
||||
def test_log_view_runtime
|
||||
get :show
|
||||
assert_match /View runtime/, logs[2]
|
||||
end
|
||||
|
||||
def test_log_completed_status_and_request_uri
|
||||
get :show
|
||||
last = logs.last
|
||||
assert_match /Completed/, last
|
||||
assert_match /200/, last
|
||||
assert_match /another\/logging\/show/, last
|
||||
end
|
||||
|
||||
def test_logger_prints_layout_and_template_rendering_info
|
||||
get :with_layout
|
||||
logged = logs.find {|l| l =~ /render/i }
|
||||
assert_match /Rendered (.*)test\/hello_world.erb within (.*)layouts\/standard.html.erb/, logged
|
||||
end
|
||||
|
||||
private
|
||||
def set_logger
|
||||
@controller.logger = MockLogger.new
|
||||
|
|
|
@ -10,19 +10,6 @@ module Fun
|
|||
end
|
||||
end
|
||||
|
||||
class MockLogger
|
||||
attr_reader :logged
|
||||
|
||||
def initialize
|
||||
@logged = []
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &blk)
|
||||
@logged << args.first
|
||||
@logged << blk.call if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
class TestController < ActionController::Base
|
||||
protect_from_forgery
|
||||
|
||||
|
@ -1500,21 +1487,4 @@ class LastModifiedRenderTest < ActionController::TestCase
|
|||
get :conditional_hello_with_bangs
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
class RenderingLoggingTest < ActionController::TestCase
|
||||
tests TestController
|
||||
|
||||
def setup
|
||||
super
|
||||
@request.host = "www.nextangle.com"
|
||||
end
|
||||
|
||||
def test_logger_prints_layout_and_template_rendering_info
|
||||
@controller.logger = MockLogger.new
|
||||
get :layout_test
|
||||
logged = @controller.logger.logged.find_all {|l| l =~ /render/i }
|
||||
assert logged[0] =~ %r{Rendering.*test/hello_world}
|
||||
assert logged[1] =~ %r{Rendering template within.*layouts/standard}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -52,6 +52,7 @@ module ActiveRecord
|
|||
autoload :Batches
|
||||
autoload :Calculations
|
||||
autoload :Callbacks
|
||||
autoload :ControllerRuntime
|
||||
autoload :DynamicFinderMatch
|
||||
autoload :DynamicScopeMatch
|
||||
autoload :Migration
|
||||
|
|
27
activerecord/lib/active_record/controller_runtime.rb
Normal file
27
activerecord/lib/active_record/controller_runtime.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module ActiveRecord
|
||||
module ControllerRuntime
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
attr_internal :db_runtime
|
||||
|
||||
def cleanup_view_runtime
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
db_rt_before_render = ActiveRecord::Base.connection.reset_runtime
|
||||
runtime = super
|
||||
db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
|
||||
self.db_runtime = db_rt_before_render + db_rt_after_render
|
||||
runtime - db_rt_after_render
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def process_log_action(controller)
|
||||
super
|
||||
db_runtime = controller.send :db_runtime
|
||||
logger.info(" ActiveRecord runtime: %.1fms" % db_runtime.to_f) if db_runtime
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,6 +7,9 @@ require "action_controller/rails"
|
|||
module ActiveRecord
|
||||
class Plugin < Rails::Plugin
|
||||
plugin_name :active_record
|
||||
include_modules_in "ActiveRecord::Base"
|
||||
|
||||
config.action_controller.include "ActiveRecord::ControllerRuntime"
|
||||
|
||||
initializer "active_record.set_configs" do |app|
|
||||
app.config.active_record.each do |k,v|
|
||||
|
@ -50,8 +53,8 @@ module ActiveRecord
|
|||
initializer "active_record.notifications" do
|
||||
require 'active_support/notifications'
|
||||
|
||||
ActiveSupport::Notifications.subscribe("sql") do |name, before, after, result, instrumenter_id, payload|
|
||||
ActiveRecord::Base.connection.log_info(payload[:sql], name, after - before)
|
||||
ActiveSupport::Notifications.subscribe("sql") do |name, before, after, instrumenter_id, payload|
|
||||
ActiveRecord::Base.connection.log_info(payload[:sql], payload[:name], (after - before) * 1000)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
class Object
|
||||
def html_safe?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class Fixnum
|
||||
def html_safe?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class String
|
||||
attr_accessor :_rails_html_safe
|
||||
alias html_safe? _rails_html_safe
|
||||
|
|
|
@ -10,10 +10,10 @@ module ActiveSupport
|
|||
end
|
||||
|
||||
def instrument(name, payload={})
|
||||
time = Time.now
|
||||
result = yield if block_given?
|
||||
time = Time.now
|
||||
yield if block_given?
|
||||
ensure
|
||||
@notifier.publish(name, time, Time.now, result, @id, payload)
|
||||
@notifier.publish(name, time, Time.now, @id, payload)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -23,15 +23,14 @@ module ActiveSupport
|
|||
end
|
||||
|
||||
class Event
|
||||
attr_reader :name, :time, :end, :transaction_id, :result, :payload
|
||||
attr_reader :name, :time, :end, :transaction_id, :payload
|
||||
|
||||
def initialize(name, start, ending, result, transaction_id, payload)
|
||||
def initialize(name, start, ending, transaction_id, payload)
|
||||
@name = name
|
||||
@payload = payload.dup
|
||||
@time = start
|
||||
@transaction_id = transaction_id
|
||||
@end = ending
|
||||
@result = result
|
||||
end
|
||||
|
||||
def duration
|
||||
|
|
|
@ -350,6 +350,24 @@ class OutputSafetyTest < ActiveSupport::TestCase
|
|||
assert_equal @string, @string.html_safe!
|
||||
end
|
||||
|
||||
test "A fixnum is safe by default" do
|
||||
assert 5.html_safe?
|
||||
end
|
||||
|
||||
test "An object is unsafe by default" do
|
||||
klass = Class.new(Object) do
|
||||
def to_str
|
||||
"other"
|
||||
end
|
||||
end
|
||||
|
||||
@string.html_safe!
|
||||
@string << klass.new
|
||||
|
||||
assert_equal "helloother", @string
|
||||
assert !@string.html_safe?
|
||||
end
|
||||
|
||||
test "Adding a safe string to another safe string returns a safe string" do
|
||||
@other_string = "other".html_safe!
|
||||
@string.html_safe!
|
||||
|
@ -416,4 +434,10 @@ class OutputSafetyTest < ActiveSupport::TestCase
|
|||
@other_string << @string
|
||||
assert @other_string.html_safe?
|
||||
end
|
||||
|
||||
test "Concatting a fixnum to safe always yields safe" do
|
||||
@string.html_safe!
|
||||
@string.concat(13)
|
||||
assert @string.html_safe?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -134,27 +134,25 @@ module Notifications
|
|||
|
||||
class EventTest < TestCase
|
||||
def test_events_are_initialized_with_details
|
||||
event = event(:foo, Time.now, Time.now + 1, 1, random_id, :payload => :bar)
|
||||
time = Time.now
|
||||
event = event(:foo, time, time + 0.01, random_id, {})
|
||||
|
||||
assert_equal :foo, event.name
|
||||
assert_equal Hash[:payload => :bar], event.payload
|
||||
assert_equal time, event.time
|
||||
assert_equal 10.0, event.duration
|
||||
end
|
||||
|
||||
def test_events_consumes_information_given_as_payload
|
||||
time = Time.now
|
||||
event = event(:foo, time, time + 0.01, 1, random_id, {})
|
||||
|
||||
assert_equal Hash.new, event.payload
|
||||
assert_equal time, event.time
|
||||
assert_equal 1, event.result
|
||||
assert_equal 10.0, event.duration
|
||||
event = event(:foo, Time.now, Time.now + 1, random_id, :payload => :bar)
|
||||
assert_equal Hash[:payload => :bar], event.payload
|
||||
end
|
||||
|
||||
def test_event_is_parent_based_on_time_frame
|
||||
time = Time.utc(2009, 01, 01, 0, 0, 1)
|
||||
|
||||
parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, nil, random_id, {})
|
||||
child = event(:foo, time, time + 10, nil, random_id, {})
|
||||
not_child = event(:foo, time, time + 100, nil, random_id, {})
|
||||
parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, random_id, {})
|
||||
child = event(:foo, time, time + 10, random_id, {})
|
||||
not_child = event(:foo, time, time + 100, random_id, {})
|
||||
|
||||
assert parent.parent_of?(child)
|
||||
assert !child.parent_of?(parent)
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
require 'active_support/ordered_options'
|
||||
|
||||
module Rails
|
||||
# Create a Plugin::Options from ActiveSuppot::OrderedOptions,
|
||||
# which support the following syntax:
|
||||
#
|
||||
# controller.action_controller.include FooBar
|
||||
#
|
||||
class Plugin::Options < ActiveSupport::OrderedOptions #:nodoc:
|
||||
attr_reader :includes
|
||||
|
||||
def initialize(*args)
|
||||
@includes = []
|
||||
super
|
||||
end
|
||||
|
||||
def include(*args)
|
||||
@includes.concat(args)
|
||||
end
|
||||
end
|
||||
|
||||
# Temporarily separate the plugin configuration class from the main
|
||||
# configuration class while this bit is being cleaned up.
|
||||
class Plugin::Configuration
|
||||
|
@ -16,7 +34,7 @@ module Rails
|
|||
@options = base.options.dup
|
||||
@middleware = base.middleware.dup
|
||||
else
|
||||
@options = Hash.new { |h,k| h[k] = ActiveSupport::OrderedOptions.new }
|
||||
@options = Hash.new { |h,k| h[k] = Rails::Plugin::Options.new }
|
||||
@middleware = ActionDispatch::MiddlewareStack.new
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,6 +25,24 @@ module Rails
|
|||
Configuration.default
|
||||
end
|
||||
|
||||
# Creates an initializer which includes all given modules to the given class.
|
||||
#
|
||||
# module Rails
|
||||
# class ActionController < Rails::Plugin
|
||||
# plugin_name :action_controller
|
||||
# include_modules_in "ActionController::Base"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def self.include_modules_in(klass, from=plugin_name)
|
||||
self.initializer :"#{from}.include_modules" do |app|
|
||||
klass = klass.constantize if klass.is_a?(String)
|
||||
app.config.send(from).includes.each do |mod|
|
||||
klass.send(:include, mod.is_a?(String) ? mod.constantize : mod)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Vendored < Plugin
|
||||
def self.all(list, paths)
|
||||
plugins = []
|
||||
|
|
|
@ -8,6 +8,10 @@ module PluginsTest
|
|||
require "rails"
|
||||
end
|
||||
|
||||
module Bar; end
|
||||
module Baz; end
|
||||
module All; end
|
||||
|
||||
test "config is available to plugins" do
|
||||
class Foo < Rails::Plugin ; end
|
||||
assert_nil Foo.config.action_controller.foo
|
||||
|
@ -24,6 +28,18 @@ module PluginsTest
|
|||
assert_equal "hello", AppTemplate::Application.config.foo.greetings
|
||||
end
|
||||
|
||||
test "plugin configurations allow modules to be given" do
|
||||
class Foo < Rails::Plugin ; config.foo.include(Bar, Baz) ; end
|
||||
assert_equal [Bar, Baz], Foo.config.foo.includes
|
||||
end
|
||||
|
||||
test "plugin includes given modules in given class" do
|
||||
class Foo < Rails::Plugin ; config.foo.include(Bar, "PluginsTest::ConfigurationTest::Baz") ; include_modules_in All ; end
|
||||
Foo.new.run_initializers(Foo)
|
||||
assert All.ancestors.include?(Bar)
|
||||
assert All.ancestors.include?(Baz)
|
||||
end
|
||||
|
||||
test "plugin config merges are deep" do
|
||||
class Foo < Rails::Plugin ; config.foo.greetings = 'hello' ; end
|
||||
class MyApp < Rails::Application
|
||||
|
|
Loading…
Reference in a new issue