1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
|
---
title: Backends
layout: gem-single
name: dry-logger
---
Backends are responsible for writing log entries to specific destinations. dry-logger supports multiple backends, allowing you to log to several destinations simultaneously with different configurations for each.
## Understanding backends
A backend combines:
- An **output destination** (stdout, file, external logger, etc.)
- A **formatter** (how entries are formatted)
- Optional **conditional logging** (when to log)
## Multiple backends
### Adding backends
Add backends to the default logger:
```ruby
logger = Dry.Logger(:my_app)
.add_backend(stream: "logs/application.log")
.add_backend(stream: "logs/errors.log", log_if: :error?)
logger.info("User logged in")
# Goes to stdout and logs/application.log
logger.error("Database connection failed")
# Goes to stdout, logs/application.log, and logs/errors.log
```
### Block-based configuration
For more control, configure all backends in a block:
```ruby
logger = Dry.Logger(:my_app) do |setup|
setup.add_backend(stream: "logs/application.log", template: :details)
setup.add_backend(stream: "logs/json.log", formatter: :json)
end
# Only logs to the files you configured (no stdout)
```
When you use a block, dry-logger skips creating the default stdout backend, giving you complete control.
## Conditional logging
The `log_if` option controls when a backend should log an entry. This is useful for routing different log levels to different destinations.
### Using severity methods
Filter by log level using the entry's severity methods:
```ruby
logger = Dry.Logger(:my_app)
.add_backend(
stream: "logs/errors.log",
log_if: :error? # Only log ERROR and FATAL
)
logger.info("Normal operation") # Not logged to errors.log
logger.error("Something broke") # Logged to errors.log
```
Available severity methods:
- `:debug?` - Only DEBUG messages
- `:info?` - Only INFO messages
- `:warn?` - Only WARN messages
- `:error?` - Only ERROR messages
- `:fatal?` - Only FATAL messages
### Using custom procs
For more complex filtering, use a proc that receives the log entry:
```ruby
logger = Dry.Logger(:my_app)
.add_backend(
stream: "logs/requests.log",
log_if: -> (entry) { entry.key?(:request) }
)
logger.info("User logged in", request: true, path: "/login")
# Logged to logs/requests.log
logger.info("Cache cleared")
# Not logged to logs/requests.log
```
The entry object provides several useful methods:
```ruby
log_if: -> (entry) {
# Check severity
entry.error? || entry.fatal?
# Check for specific keys in payload
entry.key?(:database)
# Access payload values
entry[:user_id] == 123
# Check tags
entry.tag?(:production)
}
```
### Multiple conditions example
Route different types of logs to different files:
```ruby
logger = Dry.Logger(:my_app) do |setup|
# All logs in detailed format
setup.add_backend(
stream: "logs/all.log",
template: :details
)
# Only errors in JSON format for monitoring tools
setup.add_backend(
stream: "logs/errors.json",
formatter: :json,
log_if: -> (entry) { entry.error? || entry.fatal? }
)
# Only HTTP requests
setup.add_backend(
stream: "logs/requests.log",
formatter: :rack,
log_if: -> (entry) { entry.key?(:verb) && entry.key?(:path) }
)
# Performance logs
setup.add_backend(
stream: "logs/performance.log",
log_if: -> (entry) { entry.key?(:elapsed) }
)
end
```
## Backend configuration options
Each backend supports several configuration options:
```ruby
logger.add_backend(
stream: "logs/app.log", # Output destination
formatter: :json, # Formatter to use (:string, :json, :rack)
template: :details, # Template for string formatter
level: :warn, # Minimum level for this backend
log_if: :error?, # Conditional logging
shift_age: 5, # Log rotation: number of old files
shift_size: 1048576, # Log rotation: max file size
colorize: true, # Enable colorized output
severity_colors: { # Custom severity colors
error: :red,
warn: :yellow
}
)
```
## Using external loggers
### Standard library logger
You can use Ruby's standard library `Logger` as a backend:
```ruby
require "logger"
stdlib_logger = Logger.new("logs/stdlib.log")
stdlib_logger.formatter = proc { |severity, datetime, progname, msg|
"[#{severity}] #{msg}\n"
}
logger = Dry.Logger(:my_app).add_backend(stdlib_logger)
logger.info("Test message")
# Written to both dry-logger default output and stdlib.log
```
### Conditional external loggers
Apply conditional logging to external loggers too:
```ruby
error_logger = Logger.new("logs/errors.log")
logger = Dry.Logger(:my_app)
.add_backend(error_logger) { |backend|
backend.log_if = :error?.to_proc
}
```
## Log rotation
dry-logger supports Ruby's `Logger` log rotation features for file-based backends.
### Size-based rotation
Keep a fixed number of log files with a maximum size:
```ruby
# Keep 5 log files, 10MB each
logger = Dry.Logger(:my_app,
stream: "logs/app.log",
shift_age: 5,
shift_size: 10_485_760 # 10 megabytes
)
```
When `app.log` reaches 10MB, it's renamed to `app.log.1`, and a new `app.log` is created. This continues up to `app.log.5`, at which point the oldest file is deleted.
### Time-based rotation
Rotate logs by time period:
```ruby
# Rotate daily
logger = Dry.Logger(:my_app,
stream: "logs/app.log",
shift_age: "daily"
)
# Rotate weekly
logger = Dry.Logger(:my_app,
stream: "logs/app.log",
shift_age: "weekly"
)
# Rotate monthly
logger = Dry.Logger(:my_app,
stream: "logs/app.log",
shift_age: "monthly"
)
```
Rotated files are named with timestamps (e.g., `app.log.20231015`).
### Custom rotation suffix
Customize the timestamp format for rotated files:
```ruby
logger = Dry.Logger(:my_app,
stream: "logs/app.log",
shift_age: "monthly",
shift_period_suffix: "month%m" # e.g., app.log.month10
)
```
## Managing backends
### Closing backends
When you're done with a logger, close all backends to flush buffers and release file handles:
```ruby
logger = Dry.Logger(:my_app, stream: "logs/app.log")
logger.info("Final message")
logger.close # Flushes and closes all backends
```
### Inspecting backends
View configured backends:
```ruby
logger = Dry.Logger(:my_app)
.add_backend(stream: "logs/app.log")
logger.backends
# => [#<Dry::Logger::Backends::Stream...>, #<Dry::Logger::Backends::File...>]
logger.backends.size
# => 2
```
|