如何在Ruby on Rails中按操作禁用日志记录?

49

我有一个Rails应用程序,其中一个操作被频繁调用,这在开发时非常不方便,因为它会产生很多我不关心的额外日志输出。如何让Rails不记录任何关于这个操作的日志(控制器、操作、参数、完成时间等)?我也想根据RAILS_ENV进行条件设置,以便在生产环境中记录完整的日志。

谢谢!


不知道你是否可以使用Rack中间件 - 这样当请求匹配您的模式时,您可以在完整的请求-响应周围抛出“Rails.logger.silence”块。 - user65663
我简要地看了一下。可能有些遗漏,但在当时看来,似乎涉及到了Rack中间件,它不知道正在调用哪个控制器/操作。 - archbishop
ActiveSupport::Benchmarkable#silence已被弃用,因为它缺乏线程安全性。它将在Rails 4.1中被删除,没有替代品。 - Anatortoise House
10个回答

29

你可以将Rails日志对象静音:

def action
  Rails.logger.silence do
    # Things within this block will not be logged...
  end
end

11
这是一种通常有用的模式,但是ActionController::Base会在动作方法执行之前和之后记录信息,因此这并不能解决我的特殊问题。 - archbishop
2
这在Rails 3中会引发弃用警告。您可以使用 silence do ... end 来代替。 - Simon Woker

20

使用lograge宝石。

Gemfile:



gem 'lograge'

config/application.rb:

config.lograge.enabled = true
config.lograge.ignore_actions = ['StatusController#nginx', ...]

14
以下方法适用于至少Rails 3.1.0:
创建一个可被静音的自定义日志记录器。
# selective_logger.rb
class SelectiveLogger < Rails::Rack::Logger

  def initialize  app, opts = {}
    @app = app
    @opts = opts
    @opts[:silenced] ||= []
  end

  def call  env
    if @opts[:silenced].include?(env['PATH_INFO']) || @opts[:silenced].any? {|silencer| silencer.is_a?( Regexp) && silencer.match( env['PATH_INFO']) }
      Rails.logger.silence do
        @app.call env
      end
    else
      super env
    end                        
  end

end
告诉Rails使用它:
# application.rb
config.middleware.swap Rails::Rack::Logger, SelectiveLogger, :silenced => ["/remote/every_minute", %r"^/assets/"]

上面的示例展示了静音资产服务请求,在开发环境中这意味着需要更少的(有时甚至不需要)向上滚动来查看实际请求。


2
不幸的是,这似乎与quiet_assets gem(https://github.com/evrone/quiet_assets)冲突。启用两者后,我会收到来自logger.rb“compute_tags”的错误,例如```NoMethodError: undefined method `collect' for nil:NilClass```。 - gmcnaughton

10
答案比我预期的要困难得多,因为Rails确实没有提供任何钩子来实现这一点。相反,您需要包装ActionController::Base的某些内部函数。在我的控制器的通用基类中,我这样做:
def silent?(action)
  false
end

# this knows more than I'd like about the internals of process, but
# the other options require knowing even more.  It would have been
# nice to be able to use logger.silence, but there isn't a good
# method to hook that around, due to the way benchmarking logs.

def log_processing_with_silence_logs
  if logger && silent?(action_name) then
    @old_logger_level, logger.level = logger.level, Logger::ERROR
  end

  log_processing_without_silence_logs
end

def process_with_silence_logs(request, response, method = :perform_action, *arguments)
  ret = process_without_silence_logs(request, response, method, *arguments)
  if logger && silent?(action_name) then
    logger.level = @old_logger_level
  end
  ret
end

alias_method_chain :log_processing, :silence_logs
alias_method_chain :process, :silence_logs

然后,在我想要抑制日志记录的控制器中使用该方法:

def silent?(action)
  RAILS_ENV == "development" && ['my_noisy_action'].include?(action)
end

alias_method_chain 在 Ruby 2.0 中已不再使用。 - Kees Briggs

5
您可以将宝石添加到Gemfile中silencer
gem 'silencer', '>= 1.0.1'

在config/initializers/silencer.rb文件中:

  require 'silencer/logger'

  Rails.application.configure do
    config.middleware.swap Rails::Rack::Logger, Silencer::Logger, silence: ['/api/notifications']
  end

由于某些原因,我无法在Rails 5.0.0.1中使其工作。我收到以下错误信息:No such middleware to insert before: Rails::Rack::Logger (RuntimeError) - Daniel
已更新以支持Rails 5.x @pguardiario。现在应该可以工作了;-) - micred

1

以下内容适用于Rails 2.3.14:

创建一个可以被静音的自定义日志记录器:

#selective_logger.rb  
require "active_support"

class SelectiveLogger < ActiveSupport::BufferedLogger

  attr_accessor :silent

  def initialize path_to_log_file
    super path_to_log_file
  end

  def add severity, message = nil, progname = nil, &block
    super unless @silent
  end
end

告诉Rails使用它:

#environment.rb
  config.logger = SelectiveLogger.new  config.log_path

在每个操作开始时拦截日志输出,并根据操作是否应该静默重新配置记录器:
#application_controller.rb
  # This method is invoked in order to log the lines that begin "Processing..."
  # for each new request.
  def log_processing
    logger.silent = %w"ping time_zone_table".include? params[:action]
    super
  end

1

@neil-stockbridge的回答在Rails 6.0上无法使用,我进行了一些修改使其可用

# selective_logger.rb
class SelectiveLogger

  def initialize  app, opts = {}
    @app = app
    @opts = opts
    @opts[:silenced] ||= []
  end

  def call  env
    if @opts[:silenced].include?(env['PATH_INFO']) || @opts[:silenced].any? {|silencer| silencer.is_a?( Regexp) && silencer.match( env['PATH_INFO']) }
      Rails.logger.silence do
        @app.call env
      end
    else
        @app.call env
    end                        
  end

end

测试Rails应用以使用它:

# application.rb
config.middleware.swap Rails::Rack::Logger, SelectiveLogger, :silenced => ["/remote/every_minute", %r"^/assets/"]

1

Rails 5让请求处理变得更加复杂,需要在多个类中记录日志。首先,我们需要覆盖Logger类中的call_app方法,让我们将此文件命名为lib/logger.rb

# original class:
# https://github.com/rails/rails/blob/master/railties/lib/rails/rack/logger.rb
require 'rails/rack/logger'
module Rails
  module Rack
    class Logger < ActiveSupport::LogSubscriber

      def call_app(request, env) # :doc:
        unless Rails.configuration.logger_exclude.call(request.filtered_path)
          instrumenter = ActiveSupport::Notifications.instrumenter
          instrumenter.start "request.action_dispatch", request: request
          logger.info { started_request_message(request) }
        end
        status, headers, body = @app.call(env)
        body = ::Rack::BodyProxy.new(body) { finish(request) }
        [status, headers, body]
      rescue Exception
        finish(request)
        raise
      ensure
        ActiveSupport::LogSubscriber.flush_all!
      end

    end
  end
end

然后跟着 lib/silent_log_subscriber.rb:

require 'active_support/log_subscriber'
require 'action_view/log_subscriber'
require 'action_controller/log_subscriber'
# original class:
# https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/log_subscriber.rb
class SilentLogSubscriber < ActiveSupport::LogSubscriber

  def start_processing(event)
    return unless logger.info?

    payload = event.payload
    return if Rails.configuration.logger_exclude.call(payload[:path])

    params  = payload[:params].except(*ActionController::LogSubscriber::INTERNAL_PARAMS)
    format  = payload[:format]
    format  = format.to_s.upcase if format.is_a?(Symbol)
    info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
    info "  Parameters: #{params.inspect}" unless params.empty?
  end

  def process_action(event)
    return if Rails.configuration.logger_exclude.call(event.payload[:path])

    info do
      payload = event.payload
      additions = ActionController::Base.log_process_action(payload)
      status = payload[:status]

      if status.nil? && payload[:exception].present?
        exception_class_name = payload[:exception].first
        status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
      end

      additions << "Allocations: #{event.allocations}" if event.respond_to? :allocations

      message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
      message << " (#{additions.join(" | ")})" unless additions.empty?
      message << "\n\n" if defined?(Rails.env) && Rails.env.development?

      message
    end
  end

  def self.setup
    # unsubscribe default processors
    ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
      case subscriber
      when ActionView::LogSubscriber
        self.unsubscribe(:action_view, subscriber)
      when ActionController::LogSubscriber
        self.unsubscribe(:action_controller, subscriber)
      end
    end
  end

  def self.unsubscribe(component, subscriber)
    events = subscriber.public_methods(false).reject { |method| method.to_s == 'call' }
    events.each do |event|
      ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener|
        if listener.instance_variable_get('@delegate') == subscriber
          ActiveSupport::Notifications.unsubscribe listener
        end
      end
    end
  end
end
# subscribe this class
SilentLogSubscriber.attach_to :action_controller
SilentLogSubscriber.setup

请确保在加载rails之后,在config/application.rb中加载修改过的模块。
require_relative '../lib/logger'
require_relative '../lib/silent_log_subscriber'

最后配置排除的路径:
Rails.application.configure do
  config.logger_exclude = ->(path) { path == "/health" }
end

作为我们修改Rails核心代码的一部分,检查您使用的Rails版本中的原始类始终是一个好主意。
如果这看起来像太多的修改,您可以简单地使用lograge宝石,它几乎以相同的方式进行了一些其他修改。尽管Rack::Loggger自Rails 3以来已更改,因此您可能会失去某些功能。

这个选项非常好用,但是在运行测试时导入正确的库似乎有些问题。它经常找不到一些 action_dispatch 模块(已尝试多种不同的 action_dispatch 导入方式)。 - Kkulikovskis
没关系,我找到问题了。问题是通过调用修改后的日志记录器类文件 logger.rb,然后在应用程序中要求它会导致一些冲突。我将其重命名为 silence_logger.rb,所有问题都得到解决。 - Kkulikovskis

0
Rails 6。我必须将其放在config/application.rb中,放在我的应用程序类定义内部:
require 'silencer/logger'

initializer 'my_app_name.silence_health_check_request_logging' do |app|
  app.config.middleware.swap(
    Rails::Rack::Logger,
    Silencer::Logger,
    app.config.log_tags,
    silence: %w[/my_health_check_path /my_other_health_check_path],
  )
end

这样就保留了log_tags配置,并在中间件被冻结之前进行修改。我想把它放在config/initializers/里面,但还没有想出如何实现。


0

Sprockets-rails gem 从版本3.1.0开始引入了quiet assets的实现。不幸的是,目前它还不够灵活,但可以很容易地进行扩展。

创建config/initializers/custom_quiet_assets.rb文件:

class CustomQuietAssets < ::Sprockets::Rails::QuietAssets
  def initialize(app)
    super
    @assets_regex = %r(\A/{0,2}#{quiet_paths})
  end

  def quiet_paths
    [
      ::Rails.application.config.assets.prefix, # remove if you don't need to quiet assets
      '/ping',
    ].join('|')
  end
end

将其添加到config/application.rb中间件:

# NOTE: that config.assets.quiet must be set to false (its default value).
initializer :quiet_assets do |app|
  app.middleware.insert_before ::Rails::Rack::Logger, CustomQuietAssets
end

已在Rails 4.2上进行测试


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接