Skip to content
AgileLogics
4 min read

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