【问题标题】:How can I disable logging in Ruby on Rails on a per-action basis?如何在每个操作的基础上禁用 Ruby on Rails 的日志记录?
【发布时间】:2011-01-12 21:37:11
【问题描述】:

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

谢谢!

【问题讨论】:

  • 想知道您是否可以以某种方式使用机架中间件 - 这样您就可以在请求与您的模式匹配时将“Rails.logger.silence”块放在完整的请求响应周围。
  • 我简单地看了看这个。我可能错过了一些东西,但看起来在涉及机架中间件时,它不知道正在调用什么控制器/动作。
  • edgeguides.rubyonrails.org/4_0_release_notes.html ActiveSupport::Benchmarkable#silence 由于缺乏线程安全性已被弃用。在 Rails 4.1 中,它将被删除而无需替换。

标签: ruby-on-rails logging conditional action


【解决方案1】:

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

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

【讨论】:

  • 这是一个普遍有用的模式,但 ActionController::Base 在 action 方法执行之前和之后记录东西,所以这并不能解决我的特殊问题。
  • 这会在 Rails 3 中引发弃用警告。您可以改用 silence do ... end
【解决方案2】:

使用lograge gem。

宝石文件:

gem 'lograge'

config/application.rb:

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 不幸的是,这似乎与 quiet_assets gem (github.com/evrone/quiet_assets) 冲突。启用两者后,我会收到来自 logger.rb compute_tags 之类的错误 NoMethodError: undefined method `collect' for nil:NilClass
    【解决方案4】:

    事实证明,答案比我预期的要困难得多,因为 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 中不再使用。
    【解决方案5】:

    您可以将 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
    

    【讨论】:

    • 由于某种原因,我无法在 Rails 5.0.0.1 中完成这项工作。我得到:之前没有要插入的中间件:Rails::Rack::Logger (RuntimeError)
    • 已更新以支持 Rails 5.x @pguardiario。现在它应该可以工作了;-)
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      在 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
      

      确保加载修改后的模块,例如在config/application.rb 加载后rails:

      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::Logggercode has changed since Rails 3,所以你可能会失去一些功能。

      【讨论】:

      • 这个选项效果很好,虽然在运行测试时导入正确的库有些问题。它总是找不到一些 action_dispatch 模块(尝试了几种不同的 action_dispatch 要求)
      • 别管我的评论,找到问题了。问题是通过调用修改后的记录器类文件logger.rb 然后在应用程序中要求它,它会导致一些冲突。我把它重命名为silence_logger.rb,所有问题都解决了
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

        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 测试

        【讨论】:

          【解决方案10】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-19
            • 2011-09-12
            • 2012-08-24
            • 1970-01-01
            相关资源
            最近更新 更多