Logging
Prerequisites: Environment variables and configuration
puts is fine while you're learning, but real programs log:
they write timestamped messages with a severity level, which can be
filtered and sent to files or monitoring services.
Ruby's standard library includes Logger. (It ships with Ruby 3.4;
newer Ruby versions distribute it as a bundled gem, so in a Bundler
project list gem "logger" in your Gemfile.)
Example
require "logger"
logger = Logger.new($stdout)
logger.level = Logger::INFO
logger.formatter = proc do |severity, _time, _progname, message|
"#{severity}: #{message}\n"
end
logger.debug("Connecting to cache") # below INFO, so hidden
logger.info("Server started on port 3000")
logger.warn("Disk usage at 85%")
logger.error("Payment failed for order 42")
Expected output
INFO: Server started on port 3000
WARN: Disk usage at 85%
ERROR: Payment failed for order 42
The custom formatter keeps this example's output short. Without it, each line also includes a timestamp and the process ID:
I, [2025-01-15T10:30:00.123456 #4242] INFO -- : Server started on port 3000
Levels
From least to most severe: DEBUG, INFO, WARN, ERROR, FATAL.
Setting logger.level hides everything below that level — so
detailed debug messages can stay in the code and only appear when
you need them (for example, when ENV["LOG_LEVEL"] is DEBUG).
Where to log
Logger.new($stdout)— standard output. This is the right default for apps in containers and most cloud platforms, which collect standard output for you.Logger.new("app.log")— a file.
Common mistakes
- Logging secrets — passwords, tokens, full credit card numbers. Logs are often stored for a long time and read by many people.
- Logging an exception without its class and backtrace. Log
"#{e.class}: #{e.message}"and, at debug level,e.backtrace.first(5).