rubyguides

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

ParameterTypeDefaultDescription
logdevString or IORequiredFile path or IO object
shift_ageInteger0Number of old log files to keep
shift_sizeInteger1048576Max size before rotation (bytes)
levelIntegerDEBUGMinimum 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

MethodLevelDescription
debug0Detailed diagnostic information
info1General informational messages
warn2Warning messages
error3Error messages
fatal4Fatal/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

ParameterDescription
severityLevel name (DEBUG, INFO, etc.)
datetimeTime object of the log entry
prognameProgram name (from progname= method)
msgThe 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

ParameterDescription
shift_ageNumber of old files to keep, or frequency (daily, weekly)
shift_sizeMaximum 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

ConstantValueDescription
DEBUG0Debug level
INFO1Info level
WARN2Warning level
ERROR3Error level
FATAL4Fatal level
UNKNOWN5Unknown/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.

See Also

  • File — file operations for log storage
  • Dir — directory operations
  • YAML — serialize log data