Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redirect logger output for a specific controller in Rails 3

We want to have a collection of controllers where we route logger output from all actions and downstream methods to a separate log file. This is a Rails 3 project. In Rails 2 we did this by redefining the "logger" method but in Rails 3, the way to log is using "Rails.logger". I tried putting

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

at the top of the controller, but only the specific cases where Rails.logger is used specifically in the action is getting sent to the specified log file, all of the default log output for the controller is still routing to the main log file.

How can we route all log output for a specific controller to a specific log file to include all default controller output?

By default controller output, I am referring to all of the messages that start with the very beginning of the inbound request

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"}}

and all log output that might follow from the inbound request and outbound response in the controller, etc.

Basically I want all logging for the reports controller to be in the reports_controller.log and I don't want any messages for traffic to the reports controller to show up in the main log (i.e. production.log if in production)

Update:

Thanks to @mudasobwa's help with his answer and chat, I was able to solve this using middleware as his answer depicts (although I had to change my insert_before to be before Rails::Rack::Logger)

the revised answer from him that solved it for me is below and it lives in 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
like image 274
Streamline Avatar asked Mar 02 '15 23:03

Streamline


2 Answers

Have you tried prepending an 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
like image 141
messanjah Avatar answered Sep 28 '22 10:09

messanjah


The reason that not all the stuff is redirected by controller filter, is that these “Started...” etc are written by rack middleware, that is executed before the controller was even instantiated.

So, to grab and redirect everything related to some condition, one should interfere deeper. Below is a [possibly incomplete] example on how to hack into the pipeline.

Define the middleware to switch loggers

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

Add an initializer somewhere in config/initializers/my_logger.rb

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

Please note, that Rails’ logger is a nested beast:

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=...

One might want to set a specific formatter on the logger, or even filter messages using regular expression there (though it should not be considered a good practice.)

like image 26
Aleksei Matiushkin Avatar answered Sep 28 '22 10:09

Aleksei Matiushkin