Logging to multiple destinations is something I seem to have to do all the time, and until now, I’ve never really been able to find an approach I’m happy with. Almost always, I’m writing some kind of developer-script, and I want to write messages to both the terminal (to let the dev know what is going on), and to a log file (for future reference/audit). I don’t normally want to use Rails.logger for this - I prefer to have a brand new logger specifically for my output. Having said that, I have verified that the approach described in this post does work with a standard Rails logger (defined in Rails.logger or config.logger depending on where you’re trying to work with it).

This approach relies on a method added to ActiveSupport version ~> 4.0. Versions of ActiveSupport map directly to Rails versions (ActiveSupport is part of the rails/rails repository after all), so Rails 4+ will also work here.

When working with logging in Ruby, the Logger class is normally the one is used. This is completely reasonable, as it’s part of the standard library, and is almost always sufficient. For those scenarios where something more is needed, ActiveSupport::Logger is available. The API for ActiveSupport’s Logger is broadly the same as Ruby’s Logger, with a few bonuses, such as tagged logging and the ability to extend loggers with other loggers!

Extending any logger is possible using the extend and broadcast method available on ActiveSupport::Logger. Unfortunately, I can’t link to documentation for either of these methods, as they don’t seem to be documented.

Here’s an example not using any Rails stuff, just ActiveSupport, as an example:

require "active_support/logger"
console_logger = ActiveSupport::Logger.new(STDOUT)
file_logger = ActiveSupport::Logger.new("my_log.log")
combined_logger = console_logger.extend(ActiveSupport::Logger.broadcast(file_logger))

combined_logger.debug "Debug level"
combined_logger.info "Info level"
combined_logger.error "Error level"

With the console output being:

Debug level
Info level
Error level

And mylog.log containing:

# Logfile created on 2018-08-16 15:24:10 +1200 by logger.rb/61378
Debug level
Info level
Error level

Usage in Rails really isn’t that different, the only difference is that you will already have a logger set up to log somewhere defined in Rails.logger (or config.logger if you are modifying a config file). You can just go ahead and extend that logger to add another logging destination:

# NOTE: The actual config file doesn't really matter here.
# config/application.rb
extra_logger = ActiveSupport::Logger.new("extra.log")
config.logger.extend(ActiveSupport::Logger.broadcast(extra_logger))

Multiple logging is a handy trick to keep in mind, as there’s a few places it can come in handy. As mentioned at the start of this post, my usual use case is to log to a file and STDOUT at the same time, but there’s all sorts of times you might want to post a log message multiple places. Maybe you want logs to go to a third party service, as well as local log file, as well as STDOUT for good measure? Sure!