Logger
The Logger class provides a flexible logging utility for Ruby applications. It allows you to record messages at different severity levels and direct output to various destinations like files, standard output, or custom IO objects.
Most Ruby projects use Logger as a light tracing layer rather than a full logging stack. That is usually enough for scripts, services, and small apps that need readable output without a lot of setup code.
Overview
Logger is built into Ruby’s standard library and offers:
- Severity levels: DEBUG, INFO, WARN, ERROR, FATAL
- Multiple outputs: Files, STDOUT, STDERR, or any IO object
- Log rotation: Automatic rotation by size or time
- Formatting: Customizable log message formats
Creating a logger
require 'logger'
# Log to a file
logger = Logger.new('application.log')
# Log to standard output
logger = Logger.new(STDOUT)
# Log to standard error (default)
logger = Logger.new(STDERR)
# Log to a custom IO object
logger = Logger.new(File.open('app.log', 'a'))
Logger.new Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
logdev | String or IO | Required | File path or IO object |
shift_age | Integer | 0 | Number of old log files to keep |
shift_size | Integer | 1048576 | Max size before rotation (bytes) |
level | Integer | DEBUG | Minimum log level |
Logging severity levels
Logger defines five severity levels:
require 'logger'
logger = Logger.new(STDOUT)
logger.debug("Debug message - detailed diagnostic info")
logger.info("Info message - general operational events")
logger.warn("Warning message - something might be wrong")
logger.error("Error message - something failed")
logger.fatal("Fatal message - critical failure")
Level Methods
| Method | Level | Description |
|---|---|---|
debug | 0 | Detailed diagnostic information |
info | 1 | General informational messages |
warn | 2 | Warning messages |
error | 3 | Error messages |
fatal | 4 | Fatal/critical messages |
You can also use the numeric level directly:
logger.level = Logger::DEBUG # 0
logger.level = Logger::INFO # 1
logger.level = Logger::WARN # 2
logger.level = Logger::ERROR # 3
logger.level = Logger::FATAL # 4
To disable logging entirely, set the level to Logger::UNKNOWN or any integer value greater than Logger::FATAL. Disabling is occasionally useful in tests where you want to suppress expected error output, but in most applications you will keep logging enabled and adjust the threshold instead:
logger.level = Logger::UNKNOWN # or any value > FATAL
In a real project, the most useful log level is often the one that matches the environment. Development can stay noisy for troubleshooting, while production usually benefits from warnings and errors that are easy to scan.
Setting the log level
Control which messages get logged:
logger = Logger.new(STDOUT)
# Only log warnings and errors
logger.level = Logger::WARN
logger.debug("This won't show") # Skipped
logger.info("This won't show") # Skipped
logger.warn("Warning shown") # => "W -- : Warning shown"
logger.error("Error shown") # => "E -- : Error shown"
A custom formatter gives you control over timestamps, severity labels, and message layout. This is where you can match your organization’s logging conventions or prepare structured output for log aggregation tools like ELK or Datadog.
Log formatting
Customize how log messages appear:
logger = Logger.new(STDOUT)
# Default format
# "D, [.ms] # -- : message"
# Custom format
logger.formatter = proc do |severity, datetime, progname, msg|
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity.ljust(5)} - #{msg}\n"
end
logger.info("Application started")
# => [2026-03-13 12:00:00] INFO - Application started
Formatter Parameters
| Parameter | Description |
|---|---|
severity | Level name (DEBUG, INFO, etc.) |
datetime | Time object of the log entry |
progname | Program name (from progname= method) |
msg | The actual message |
Log rotation
Automatically rotate logs to prevent disk space issues:
# Rotate when file exceeds 1MB, keep 5 old files
logger = Logger.new('app.log', 5, 1048576)
# Rotate daily, keep 7 days of logs
logger = Logger.new('app.log', 7)
The rotation behaviour is controlled by two optional constructor arguments: the number of old files to keep and the maximum file size. Together they give you a simple but effective log retention policy without external dependencies.
Rotation Parameters
| Parameter | Description |
|---|---|
shift_age | Number of old files to keep, or frequency (daily, weekly) |
shift_size | Maximum size in bytes before rotation |
# Weekly rotation
logger = Logger.new('weekly.log', 'weekly')
# Size-based rotation (10MB max)
logger = Logger.new('large.log', 3, 10 * 1024 * 1024)
The next example demonstrates another aspect of this Ruby method in practice, showing different input types, edge cases, and usage patterns that are worth understanding before you use it in production code.
Practical examples
Application logging
Wrapping a Logger instance inside an application class gives you consistent log formatting and level control across all components. The logger becomes a shared resource that each part of the application can use without reconfiguring it separately:
require 'logger'
class Application
def initialize
@logger = Logger.new('app.log')
@logger.level = Logger::INFO
@logger.progname = 'MyApp'
end
def run
@logger.info("Application starting")
# ... application code ...
@logger.info("Application finished")
rescue => e
@logger.error("Application failed: #{e.message}")
end
end
Separate loggers for different purposes let you route debug output to one destination and production warnings to another. This keeps detailed diagnostic logs from drowning out the operational signals in your production log files, making both easier to search.
Multiple loggers
require 'logger'
# Debug log - detailed
debug_log = Logger.new('debug.log')
debug_log.level = Logger::DEBUG
# Production log - warnings and above
prod_log = Logger.new('production.log')
prod_log.level = Logger::WARN
Subclassing Logger lets you customize the default format without repeating the configuration in every file. A single subclass can define the format once and be reused wherever you create a new logger instance, which reduces boilerplate in larger applications.
Rails-like logging
require 'logger'
class CustomLogger < Logger
def format_message(level, datetime, progname, msg)
"#{level} [#{datetime}]: #{msg}\n"
end
end
logger = CustomLogger.new('custom.log')
logger.info("Custom format message")
The block form defers message construction until the logger knows the message will actually be written. This avoids expensive string interpolation or computation when the log level would discard the message, making it safe to leave debug blocks in production code paths.
Logging with block syntax
logger = Logger.new(STDOUT)
# Only evaluates block if level is enabled
logger.debug { "Expensive debug: #{expensive_computation}" }
logger.info { "User #{current_user.id} logged in" }
The block form is especially helpful when building the message is expensive. Ruby only evaluates the block when the level is enabled, so you can keep diagnostic code nearby without paying for it on every run.
Logger constants
| Constant | Value | Description |
|---|---|---|
DEBUG | 0 | Debug level |
INFO | 1 | Info level |
WARN | 2 | Warning level |
ERROR | 3 | Error level |
FATAL | 4 | Fatal level |
UNKNOWN | 5 | Unknown/severe level |
Ruby version history
- Ruby 1.8: Introduced the Logger class
- Ruby 2.1: Improved thread safety
- Ruby 2.5: Added support for log rotation by time period
If you use Logger across several files, keep the formatter and level choices consistent. A predictable format makes logs easier to search, compare, and hand off when you need help.