Manuel Meurer
Manuel Meurer

Reputation: 3478

Rails 7.1, log to STDOUT and log/production.log

In a new Rails 7.1.2 app, the following lines can be found in config/environments/production.rb:

config.logger = ActiveSupport::Logger.new(STDOUT)
  .tap  { |logger| logger.formatter = ::Logger::Formatter.new }
  .then { |logger| ActiveSupport::TaggedLogging.new(logger) }

This tells the Rails logger to log to STDOUT.

I would like to configure it so that it ALSO logs to log/production.log, but I can't for the life of me figure it out...

In this article by Fly.io it says to add these lines:

logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
volume_logger = ActiveSupport::Logger.new("/logs/production.log", 3)
logger = logger.extend ActiveSupport::Logger.broadcast(volume_logger)

But it seems like these instructions are for Rails < 7.1, since I get the error NoMethodError: undefined method broadcast' for ActiveSupport::Logger:Class`.

How can I do this in Rails 7.1?

Upvotes: 10

Views: 4793

Answers (2)

Vadim Kononov
Vadim Kononov

Reputation: 2184

The accepted answer causes the following issue in production when sending email with Action Mailer using deliver_later:

activejob-7.1.3.2/lib/active_job/logging.rb:32:in `logger_tagged_by_active_job?': undefined method `current_tags' for nil (NoMethodError)

        logger.formatter.current_tags.include?("ActiveJob")

The issue is with BroadcastLogger, which means both the shorthand and the long versions of the accepted answer will be broken. The issue isn't just limited to the activejob version above, and is present in multiple Ruby versions I've tested.

Solution:

config.logger = ActiveSupport::Logger.new("log/#{Rails.env}.log")
  .tap  { |logger| logger.formatter = ::Logger::Formatter.new }
  .then { |logger| ActiveSupport::TaggedLogging.new(logger) }

Alternative Solution:

Removing the entire logger block also logs to production.log by default.

Upvotes: 1

Alex
Alex

Reputation: 30003

Rails v7.1 added new BroadcastLogger class to handle broadcasting:

stdout_logger           = ActiveSupport::Logger.new(STDOUT)
stdout_logger.formatter = ::Logger::Formatter.new

file_logger             = ActiveSupport::Logger.new("log/production.log")
file_logger.formatter   = ::Logger::Formatter.new

tagged_stdout_logger    = ActiveSupport::TaggedLogging.new(stdout_logger)
tagged_file_logger      = ActiveSupport::TaggedLogging.new(file_logger)

broadcast_logger = ActiveSupport::BroadcastLogger.new(tagged_stdout_logger, tagged_file_logger)
config.logger    = broadcast_logger

https://api.rubyonrails.org/classes/ActiveSupport/BroadcastLogger.html

https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#rails-logger-now-returns-an-activesupport-broadcastlogger-instance


Since rails v7.1 you could pass formatter to new, which makes the setup much cleaner: https://github.com/rails/rails/commit/3b012a52540f7e4564d70f1955785bde32269a3d:

config.logger = ActiveSupport::BroadcastLogger.new(
  ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout,              formatter: Logger::Formatter.new)),
  ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new("log/production.log", formatter: Logger::Formatter.new))
)

Upvotes: 15

Related Questions