Rails 7.1, log to STDOUT and log/production.log
Asked Answered
C

2

10

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?

Capable answered 15/11, 2023 at 10:1 Comment(0)
W
14

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))
)
Waterborne answered 15/11, 2023 at 14:18 Comment(4)
Great, thank you for the explanation and links, Alex! This is the condensed version I ended up with: # config/environments/production.rb config.logger = [STDOUT, "log/production.log"].map do |destination| ActiveSupport::Logger.new(destination) .tap { _1.formatter = ::Logger::Formatter.new } .then { ActiveSupport::TaggedLogging.new _1 } end.then { ActiveSupport::BroadcastLogger.new(*_1) } (pff, no Markdown or line breaks in comments??)Capable
In Rails 7.1.1, there seems to be a problem with multiple loggers: github.com/rails/rails/issues/49745 If you get "no implicit conversion of String into Integer", wait for Rails 7.1.2Capable
FYI, I am still getting the "no implicit conversion of String into Integer" issue here with Rails 7.1.3Penult
Beware! This answer causes this error in production: plaintext gems/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") when sending emails using deliver_laterOrdovician
O
1

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

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

Ordovician answered 12/9 at 19:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.