Formatters
Formatters control how log entries are serialized for output. dry-logger includes three built-in formatters optimized for different use cases.
Built-in formatters
String formatter (default)
The :string formatter outputs human-readable text in key=value format. It's ideal for development and when you need to read logs directly.
logger = Dry.Logger(:my_app, formatter: :string)
logger.info("User logged in", user_id: 42, role: "admin")
# User logged in user_id=42 role="admin"
The string formatter:
- Formats payloads as
key=valuepairs - Quotes string values
- Supports colorized output
- Handles exceptions with full backtraces
- Works with templates for customization
JSON formatter
The :json formatter outputs structured JSON, perfect for production environments and log aggregation tools.
logger = Dry.Logger(:my_app, formatter: :json)
logger.info("User logged in", user_id: 42, role: "admin")
# {"progname":"my_app","severity":"INFO","time":"2023-10-15T14:23:45Z","message":"User logged in","user_id":42,"role":"admin"}
The JSON formatter:
- Outputs valid JSON on each line
- Converts timestamps to UTC ISO8601 format
- Flattens exception data into the JSON structure
- Perfect for tools like Elasticsearch, Splunk, or CloudWatch
Example with exception:
begin
raise ArgumentError, "Invalid input"
rescue => e
logger.error(e)
end
# {"progname":"my_app","severity":"ERROR","time":"2023-10-15T14:24:12Z","exception":"ArgumentError","message":"Invalid input","backtrace":["..."]}
Rack formatter
The :rack formatter is specialized for logging HTTP requests in web applications.
logger = Dry.Logger(:my_app, formatter: :rack)
logger.info(
verb: "GET",
path: "/users/42",
status: 200,
elapsed: "12ms",
ip: "192.168.1.1",
length: 1024,
params: {page: 1}
)
# [my_app] [INFO] [2023-10-15 14:25:30 +0000] GET 200 12ms 192.168.1.1 /users/42 1024
# {"page":1}
The rack formatter uses a predefined template that formats common HTTP request fields in a compact, readable format.
Per-backend formatters
Different backends can use different formatters:
logger = Dry.Logger(:my_app) do |setup|
# Human-readable logs to stdout for development
setup.add_backend(
stream: $stdout,
formatter: :string,
template: :details
)
# JSON logs to file for production analysis
setup.add_backend(
stream: "logs/app.json",
formatter: :json
)
# Rack-formatted HTTP logs
setup.add_backend(
stream: "logs/requests.log",
formatter: :rack,
log_if: -> (entry) { entry.key?(:verb) }
)
end
# Now this logs to all three backends in different formats
logger.info("Starting server", port: 3000)
Formatter options
String formatter options
The string formatter supports several customization options:
Templates
Control the output format using templates (see Templates for details):
logger = Dry.Logger(:my_app,
formatter: :string,
template: "[%<severity>s] %<time>s - %<message>s %<payload>s"
)
logger.info("Server started", port: 3000)
# [INFO] 2023-10-15 14:30:00 +0000 - Server started port=3000
Colorization
Enable colorized output for better readability in terminals:
logger = Dry.Logger(:my_app,
formatter: :string,
colorize: true
)
logger.debug("Debug message") # Cyan
logger.info("Info message") # Magenta
logger.warn("Warning") # Yellow
logger.error("Error") # Red
logger.fatal("Fatal error") # Red
Custom severity colors
Customize which colors are used for each severity level:
logger = Dry.Logger(:my_app,
formatter: :string,
colorize: true,
severity_colors: {
debug: :gray,
info: :green,
warn: :yellow,
error: :red,
fatal: :magenta
}
)
Available colors:
:black:red:green:yellow:blue:magenta:cyan:gray
JSON formatter options
The JSON formatter automatically:
- Converts all timestamps to UTC
- Formats timestamps as ISO8601 strings
- Includes all metadata (progname, severity, time)
- Merges payload data into the root object
No additional configuration is needed for the JSON formatter.
Exception formatting
String formatter
Exceptions are formatted with full details:
logger = Dry.Logger(:my_app, formatter: :string, template: :details)
begin
File.read("missing.txt")
rescue => e
logger.error(e)
end
# [my_app] [ERROR] [2023-10-15 14:35:12 +0000]
# No such file or directory @ rb_sysopen - missing.txt (Errno::ENOENT)
# /path/to/file.rb:10:in `read'
# /path/to/file.rb:10:in `<main>'
JSON formatter
Exceptions are serialized as JSON fields:
logger = Dry.Logger(:my_app, formatter: :json)
begin
File.read("missing.txt")
rescue => e
logger.error(e)
end
# {"progname":"my_app","severity":"ERROR","time":"2023-10-15T14:35:12Z","exception":"Errno::ENOENT","message":"No such file or directory @ rb_sysopen - missing.txt","backtrace":["..."]}
Custom formatters
Create formatters for specialized output needs.
Simple custom formatter
class CustomFormatter
def call(_severity, _time, _progname, entry)
# Must return a string
"#{entry.severity} | #{entry.message} | #{entry.payload.inspect}\n"
end
end
logger = Dry.Logger(:my_app, formatter: CustomFormatter.new)
logger.info("Test", user_id: 42)
# INFO | Test | {:user_id=>42}
Extending built-in formatters
class MyFormatter < Dry::Logger::Formatters::String
private
# Customize how specific fields are formatted
def format_user_id(value)
"USER:#{value}"
end
def format_duration(value)
"#{value}ms"
end
end
# Register your formatter
Dry::Logger.register_formatter(:my_formatter, MyFormatter)
logger = Dry.Logger(:my_app,
formatter: :my_formatter,
template: "%<user_id>s took %<duration>s"
)
logger.info(user_id: 42, duration: 150)
# USER:42 took 150ms
For complete, realistic configuration examples, see the Examples page.