如何在 Ruby on Rails 中基于每个操作禁用日志记录?

发布于 2024-08-20 08:30:38 字数 159 浏览 2 评论 0原文

我有一个 Rails 应用程序,该应用程序的某个操作被频繁调用,这在我开发时很不方便,因为它会导致大量我不关心的额外日志输出。我如何才能让 Rails 不记录这一操作的任何内容(控制器、操作、参数、完成时间等)?我也想在 RAILS_ENV 上对其进行条件化,以便生产中的日志完整。

谢谢!

I have a Rails application that has an action invoked frequently enough to be inconvenient when I am developing, as it results in a lot of extra log output I don't care about. How can I get rails not to log anything (controller, action, parameters, completion time, etc.) for just this one action? I'd like to conditionalize it on RAILS_ENV as well, so logs in production are complete.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

-小熊_ 2024-08-27 08:30:38

您可以使 Rails 记录器对象静音:

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

You can silence the Rails logger object:

def action
  Rails.logger.silence do
    # Things within this block will not be logged...
  end
end
江南月 2024-08-27 08:30:38

使用 lograge gem。

Gemfile:

gem 'lograge'

config/application.rb:

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

Use lograge gem.

Gemfile:

gem 'lograge'

config/application.rb:

config.lograge.enabled = true
config.lograge.ignore_actions = ['StatusController#nginx', ...]
狼亦尘 2024-08-27 08:30:38

以下至少适用于 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/"]

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

The following works with at least Rails 3.1.0:

Make a custom logger that can be silenced:

# 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

Tell Rails to use it:

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

The example above shows silencing asset serving requests, which in the development environment means less ( and sometimes no) scrolling back is required to see the actual request.

何止钟意 2024-08-27 08:30:38

答案比我预想的要困难得多,因为 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

The answer turns out to be a lot harder than I expected, since rails really does provide no hook to do this. Instead, you need to wrap some of the guts of ActionController::Base. In the common base class for my controllers, I do

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

then, in the controller with the method I want to suppress logging on:

def silent?(action)
  RAILS_ENV == "development" && ['my_noisy_action'].include?(action)
end
何以畏孤独 2024-08-27 08:30:38

您可以将 gem 添加到 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

You can add the gem to the Gemfile silencer.

gem 'silencer', '>= 1.0.1'

And in your config/initializers/silencer.rb :

  require 'silencer/logger'

  Rails.application.configure do
    config.middleware.swap Rails::Rack::Logger, Silencer::Logger, silence: ['/api/notifications']
  end
梦忆晨望 2024-08-27 08:30:38

以下适用于 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

The following works with Rails 2.3.14:

Make a custom logger that can be silenced:

#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

Tell Rails to use it:

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

Intercept the log output at the beginning of each action and (re)configure the logger depending on whether the action should be silent or not:

#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
2024-08-27 08:30:38

在 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 gem与其他一些修改几乎相同。虽然 Rack::Loggger 自 Rails 3 以来代码已发生更改,因此您可能会失去一些功能。

With Rails 5 it gets more complicated request processing is logged in several classes. Firstly we need to override call_app in Logger class, let's call this file 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

Then follow with 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

Make sure to load modified modules e.g. in config/application.rb after loading rails:

require_relative '../lib/logger'
require_relative '../lib/silent_log_subscriber'

Finally configure excluded paths:

Rails.application.configure do
  config.logger_exclude = ->(path) { path == "/health" }
end

As we're modifying core code of Rails it's always good idea to check original classes in Rails version you're using.

If this looks like too many modifications, you can simply use lograge gem which does pretty much the same with few other modifications. Although the Rack::Loggger code has changed since Rails 3, so you might be loosing some functionality.

提笔落墨 2024-08-27 08:30:38

@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/"]

@neil-stockbridge 's answer not worked for Rails 6.0, I edit some to make it work

# 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

Test rails app to use it:

# application.rb
config.middleware.swap Rails::Rack::Logger, SelectiveLogger, :silenced => ["/remote/every_minute", %r"^/assets/"]
送舟行 2024-08-27 08:30:38

Sprockets-rails gem 从 3.1.0 版本开始引入了以下实现安静资产。不幸的是,目前它还不灵活,但可以很容易地扩展。

创建 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 进行测试

Sprockets-rails gem starting from version 3.1.0 introduces implementation of quiet assets. Unfortunately it's not flexible at this moment, but can be extended easy enough.

Create config/initializers/custom_quiet_assets.rb file:

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

Add it to middleware in 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

Tested with Rails 4.2

肤浅与狂妄 2024-08-27 08:30:38

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/ 某个隐藏的地方,但还没有弄清楚如何做到这一点。

Rails 6. I had to put this in config/application.rb, inside my app's class definition:

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

That leaves the log_tags config intact and modifies the middleware before it gets frozen. I would like to put it in config/initializers/ somewhere tucked away but haven't figured out how to do that yet.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文