Block-based lazy logging mixin. Extracted from kremis and dark-lantern where it was duplicated verbatim (only the module namespace differed).
Log methods take blocks, not strings. The block is only evaluated if the log level is enabled. This matters when the log message involves expensive computation (serializing objects, string interpolation with method calls).
include Wanderland::Logging
debug { "Expensive #{JSON.generate(large_object)} only runs at debug" }
info { "Processing #{slug}" }
warn { "Something looks off: #{details}" }
error { "Failed: #{exception.message}" }
# At boot time
Wanderland.configure_logging(
level: :info, # :debug, :info, :warn, :error, :fatal
output: nil, # nil/:stdout, :stderr, "/path/to/file", "/path/to/dir/"
name: "lantern" # used for log filename when output is a directory
)
Output resolution:
nil or :stdout → $stdout:stderr → $stderr{name}.log in that directory.write → used directly (StringIO for testing)[2026-04-01 21:26:17] INFO [Lantern::RouteMatch] Matched /node/:slug
[timestamp] SEVERITY [class_name] message
The class name comes from self.class.name on the including class. Falls back to "Wanderland" if anonymous.
module Wanderland::Logging
def self.included(base)
base.extend(ClassMethods) # class-level .logger
end
def logger = Wanderland.logger # instance-level
def debug(&block) # delegates to logger with lazy eval
logger.debug(log_prefix) { block.call } if logger.debug?
end
end
Both class methods and instance methods delegate to the singleton Wanderland.logger. The singleton is configured once at boot and shared across the process.