在 Rails 3 中为特定控制器重定向记录器输出

Posted

技术标签:

【中文标题】在 Rails 3 中为特定控制器重定向记录器输出【英文标题】:Redirect logger output for a specific controller in Rails 3 【发布时间】:2015-05-03 12:08:35 【问题描述】:

我们希望有一个控制器集合,我们将所有操作和下游方法的记录器输出路由到单独的日志文件。这是一个 Rails 3 项目。在 Rails 2 中,我们通过重新定义“logger”方法来做到这一点,但在 Rails 3 中,记录日志的方式是使用“Rails.logger”。我试着把

Rails::logger = Logger.new(File.join(Rails.root, 'log', "reports_controller.log"), 10, 1000000) 

在控制器的顶部,但只有在操作中专门使用 Rails.logger 的特定情况才会发送到指定的日志文件,控制器的所有默认日志输出仍然路由到主日志文件。

我们如何将特定控制器的所有日志输出路由到特定日志文件以包含所有默认控制器输出?

默认控制器输出,我指的是所有以入站请求开头的消息

Started POST "/api/v1/reports.json" for 10.XXX.XX.XX at 2015-03-07 01:30:22 +0000
Processing by Api::V1::ReportsController#create as JSON
  Parameters: "report"=>"cloud_file_path"=>"report.zip", "company_id"=>nil, "created_at"=>"2015-03-07T01:30:17Z", "type_id"=>"2", "updated_at"=>"2015-03-07T01:30:17Z", "master"=>"1"

以及控制器中的入站请求和出站响应等可能遵循的所有日志输出。

基本上,我希望报告控制器的所有日志记录都在 reports_controller.log 中,并且我不希望报告控制器的任何流量消息显示在主日志中(即 production.log,如果在生产中)

更新:

感谢@mudasobwa 对他的回答和聊天的帮助,我能够使用中间件解决这个问题,正如他的回答所描述的那样(尽管我必须将我的 insert_before 更改为 Rails::Rack::Logger 之前)

他为我解决的修改后的答案如下,它位于config/initializers/logger_middleware.rb

module MyApp
  class LoggerMiddleware

    REPORTS_API_CONTROLLER_PATH = %r|\A/api/v.*/reports/.*|
    REPORTS_API_CONTROLLER_LOGFILE = "reports_controller.log"

    def initialize(app)
      @app, @logger = app, Rails::logger.instance_variable_get(:@logger).instance_variable_get(:@log)
      @reports_api_controller_logger = Logger.new(Rails.root.join('log', REPORTS_API_CONTROLLER_LOGFILE), 10, 1000000)
    end

    def call(env)
      Rails::logger
           .instance_variable_get(:@logger)
           .instance_variable_set(:@log,
               case env['PATH_INFO']
               when REPORTS_API_CONTROLLER_PATH then
                 @reports_api_controller_logger
               else
                 @logger
               end
           )
      @app.call(env)
    end
  end
end

Rails.application.middleware.insert_before Rails::Rack::Logger, MyApp::LoggerMiddleware

【问题讨论】:

【参考方案1】:

您是否尝试过预先添加一个 around_filter?

class MyController < ApplicationController
  prepend_around_filter :set_logger

  private

  def set_logger
    old_logger = Rails::logger
    Rails::logger = Logger.new(Rails.root.join('log', "reports_controller.log"), 10, 1000000) 
    yield
    Rails.logger = old_logger
  end
end

【讨论】:

结果与我的代码示例相同,显式记录器调用确实被路由到新的控制器日志文件,但是,控制器操作的所有默认记录器消息仍然被路由到环境默认日志文件。同样,默认情况下,我指的是 rails 自己编写的消息,例如“开始...”、“由...处理”并包括数据库(即,如果环境是开发环境)消息等。 Hmm - 这个响应激发了我更多的修补,我扩展了方法以包括 ActiveRecord::Base.logger = new_logger (并在产量后重置),这将活动记录数据库日志记录到新的特定记录器 - 但我似乎无法将 Started...Processing by...Completed... 消息路由到特定日志文件 @Streamline - 你有没有想过,如何在另一个日志文件中记录“开始...”和“处理...”? @Mattherick - 不幸的是没有。我希望尽快重新审视这一点,因为生产日志超载它没有用。我正在评论的这个答案实际上只有助于基本级别的记录器重定向,而不是仍然路由到 production.log 的其余提到的项目【参考方案2】:

不是所有的东西都被controller filter重定向的原因,是这些“Started...”等是rack middleware写的,也就是before > 控制器甚至被实例化了。

因此,要获取和重定向与某些条件相关的所有内容,应该进行更深入的干预。下面是一个[可能不完整的]关于如何侵入管道的示例。

定义切换记录器的中间件

module MyApp
  class MyMiddleware

    def initialize(app)
      @app, @logger = app, Rails.logger
                                .instance_variable_get(:@logger)
                                .instance_variable_get(:@log)
      @my_logger = Logger.new('reports_controller.log', ...)
    end

    def call(env)
      # env['action_dispatch.logger'].inspect
      #⇒ <TaggedLogging... @logger=#<BufferedLogger...> @log_dest=...>

      # here we do not have a controller name
      Rails.logger
           .instance_variable_get(:@logger)
           .instance_variable_set(:@log,
               case env['PATH_INFO'] # or PATH_INFO, or whatever
               when %r|\A/api/v1/| then @my_logger
               else @logger
               end
           )

      @app.call(env)
    end
  end
end

config/initializers/my_logger.rb的某处添加一个初始化器

Rails.application.middleware.insert_before \
    Rails::Rack::Logger, MyApp::MyMiddleware

请注意,Rails 的记录器是一个嵌套的野兽:

Rails::logger
#⇒ #<ActiveSupport::TaggedLogging:0x000000020f5ad0 @logger=...
Rails::logger.instance_variable_get(:@logger)
#⇒ #<ActiveSupport::BufferedLogger:0x000000020f6188 @log_dest=...
Rails::logger.instance_variable_get(:@logger)
             .instance_variable_get(:@log)
#⇒ #<Logger:0x000000020f6138 @progname=nil, @level=0, @default_formatter=...

人们可能想在记录器上设置一个特定的formatter,甚至在那里使用正则表达式过滤消息(尽管这不应该被认为是一种好的做法。)

【讨论】:

那么这种通过在中间件中设置 Rails.logger 基于评估 REQUEST_URI 的方法会影响从该点开始的所有日志记录吗?或者这只是一个额外的位置来捕捉更多?例如,在另一个答案之后,我注意到如果我希望也路由数据库日志记录,我必须单独重定向 ActiveRecord::Base.logger。机架中间件是否覆盖并设置记录器以及其他定义记录器的位置,或者我需要增强它以在该点(或更上游?)包含更多记录器。 设置Rails::logger.instance_variable_get(:@logger).instance_variable_get(:@log) 会影响一切(我确实在生产中使用它。)仅设置Rails::logger 取决于我没有深入研究的smth(简而言之:我建议设置底层Logger .) 当您说现在在生产中设置 Rails::logger.instance_variable_get(:@logger).instance_variable_get(:@log) 时,您是否以与通过中间件和只是用这个when %r|\A/api/v1/| then Rails::logger.instance_variable_get(:@logger).instance_variable_set(:@log,@my_logger)替换上面的这一行when %r|\A/api/v1/| then Rails::logger = @my_logger 没错。坦率地说,我不记得我为什么要这样做。也许我在设置Rails::logger 时遇到了一些小故障,也许还有其他原因。我也重置了格式化程序,也许这就是原因。无论如何,我建议您按照上述方式尝试,如果失败,请尝试使用logger.logger.log 我认为我已经接近可测试的情况了,但是 1)我的中间件列表中没有 Rack::Cache(可能是因为这是 Rails 3.2?)所以而不是 insert_before Rack::缓存我将它更改为 insert_after Rails::Rack::Logger 像这样Rails.application.middleware.insert_after Rails::Rack::Logger, MyApp::LoggerMiddleWare, :my_app 2) 我在文档中找不到最后一个参数应该是什么以及它抛出的异常,比如undefined method 'info' for :my_app:Symbol - 那么第三个参数应该是什么是你有 :my_app 上面的地方,它在哪里记录?【参考方案3】:

要添加@messanjah 的答案,您可以覆盖这些记录器: Rails::logger, ActionView::Base.logger, ActionController::Base.logger, ActiveRecord::Base.logger.

通过抑制这些记录器,您可以删除大多数消息。但是正如@mudasobwa 指出的那样 Started ... 消息是在控制器之前写入的。

如果您不想使用中间件,您可以随时使用锤子和monkeypatch Rails::Rack::Logger 来广泛使用您的记录器应用程序。或者您可以检查路径并在那里拒绝某些消息。

【讨论】:

当你说 monkeypatch Rails::Rack::Logger 应用程序范围时,这将如何发生 1) 与 @mudasobwa 建议使用中间件的方式不同,以及 2) 如何对每个控制器调用进行选择性甚至尽管它是在调用控制器之前而不是解析 REQUEST_URI?

以上是关于在 Rails 3 中为特定控制器重定向记录器输出的主要内容,如果未能解决你的问题,请参考以下文章

服务器重定向和客户端重定向

在spring boot中将控制从一个控制器重定向到另一个控制器

如果来自特定区域,则 Google Cloud 负载平衡器重定向

文件上传进度条干扰控制器重定向

从基本控制器重定向到操作方法

Cakephp 内部从控制器重定向到另一个控制器