ZetCode

Ruby Logger

last modified October 18, 2026

The logger library in Ruby provides a simple, configurable way to record messages from a program. It supports multiple severity levels, custom formatting, program names, exception logging, and log file rotation. This guide covers the common Logger features with examples.

Basic Logging

The logger library is part of the Ruby standard library, so it is loaded with require 'logger'. A Logger writes messages to any IO object or file. Here we pass $stdout, and we set a custom formatter so that each line shows only the severity and the message. The default format includes a timestamp and process ID, which change on every run; the custom formatter keeps the output the same each time.

basic_logging.rb
#!/usr/bin/env ruby

require 'logger'

logger = Logger.new($stdout)
logger.formatter = proc do |severity, time, progname, msg|
  "#{severity}: #{msg}\n"
end

logger.info("Application started")
logger.warn("Disk space is low")
logger.error("Failed to open file")

The Logger.new($stdout) call creates a logger that writes to standard output. The formatter is a proc receiving four arguments: the severity string, the time, the program name, and the message. Here the proc returns "#{severity}: #{msg}\n", so only the severity and message appear. The info, warn, and error methods each log one message at the corresponding level.

$ ./basic_logging.rb
INFO: Application started
WARN: Disk space is low
ERROR: Failed to open file

Log Levels

Logger supports six levels in increasing order of severity: DEBUG, INFO, WARN, ERROR, FATAL, and UNKNOWN. A message is written only when its level is at or above the logger's level. The level= method changes the threshold, and it accepts either a constant such as Logger::INFO or a symbol.

levels.rb
#!/usr/bin/env ruby

require 'logger'

logger = Logger.new($stdout)
logger.formatter = proc do |severity, time, progname, msg|
  "#{severity}: #{msg}\n"
end

logger.level = Logger::INFO

logger.debug("This debug message is hidden")
logger.info("Info message is shown")
logger.warn("Warning message is shown")
logger.error("Error message is shown")
logger.fatal("Fatal message is shown")
logger.unknown("Unknown message is shown")

puts "Current level: #{logger.level}"

Setting logger.level = Logger::INFO hides the DEBUG message, so it is not printed. The remaining levels are printed. The UNKNOWN level is labeled ANY in the output, because the default label for that severity is ANY. The final puts prints the numeric value of the current level, where INFO is 1.

$ ./levels.rb
INFO: Info message is shown
WARN: Warning message is shown
ERROR: Error message is shown
FATAL: Fatal message is shown
ANY: Unknown message is shown
Current level: 1

Program Name

The progname identifies the source of a log message, for example a service or a module. It is set with logger.progname=, and the formatter receives it as the third argument. Including the program name makes it easier to filter logs from a larger application.

progname.rb
#!/usr/bin/env ruby

require 'logger'

logger = Logger.new($stdout)
logger.formatter = proc do |severity, time, progname, msg|
  "#{severity} [#{progname}]: #{msg}\n"
end

logger.progname = "ServiceA"
logger.info("Service started")

logger.progname = "Auth"
logger.warn("Token expires soon")

logger.progname = "Database"
logger.error("Connection lost")

The formatter here prints the program name in square brackets. Before each group of messages, logger.progname is set to a different name, and every subsequent message uses that name until it is changed again. The output shows each severity with its associated program name.

$ ./progname.rb
INFO [ServiceA]: Service started
WARN [Auth]: Token expires soon
ERROR [Database]: Connection lost

Custom Formatter and File Logging

Passing a file name to Logger.new writes the log to that file instead of standard output. The formatter works the same way for files. Logger can also rotate log files: a second argument of 'daily' or a number of old files to keep, optionally followed by a maximum size in bytes, controls rotation. The rotation configuration is shown in the configure_rotation method below; it is defined but not called, so it produces no output.

file_logging.rb
#!/usr/bin/env ruby

require 'logger'

log_file = 'app.log'
File.delete(log_file) if File.exist?(log_file)

logger = Logger.new(log_file)
logger.formatter = proc do |severity, time, progname, msg|
  "#{severity}: #{msg}\n"
end

logger.info("Written to a file")
logger.warn("Second line in the file")
logger.close

# Skip the header line that Logger writes with a timestamp
File.foreach(log_file) do |line|
  puts line unless line.start_with?('#')
end
File.delete(log_file)

# Rotation configuration (not called here)
def configure_rotation
  Logger.new('app_daily.log', 'daily')          # rotate every day
  Logger.new('app_age.log', 5)                  # keep 5 old files
  Logger.new('app_size.log', 3, 1024 * 1024)    # keep 3 files, rotate at 1 MB
end

The example logs two messages to app.log, closes the logger, and reads the file back. Logger writes a header line containing a timestamp when a file is created; the loop skips lines starting with # so the printed output stays the same on every run. The configure_rotation method shows the rotation forms: 'daily' rotates once a day, 5 keeps five old files, and 3, 1024 * 1024 keeps three files and rotates when a file reaches 1 MB.

$ ./file_logging.rb
INFO: Written to a file
WARN: Second line in the file

Logging Exceptions

When an exception is rescued, it can be logged with its class and message. Logging the error inside the rescue block records the problem and lets the program continue. The message is built from e.class and e.message.

exceptions.rb
#!/usr/bin/env ruby

require 'logger'

logger = Logger.new($stdout)
logger.formatter = proc do |severity, time, progname, msg|
  "#{severity}: #{msg}\n"
end

def divide(a, b)
  a / b
end

begin
  divide(10, 0)
rescue ZeroDivisionError => e
  logger.error("#{e.class}: #{e.message}")
end

logger.info("Program continues after the exception")

Dividing by zero raises ZeroDivisionError. The rescue clause catches it and logs the class and message at the ERROR level. Execution continues after the begin block, so the final info call runs as well.

$ ./exceptions.rb
ERROR: ZeroDivisionError: divided by 0
INFO: Program continues after the exception

List all Ruby tutorials.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.