Hanami logging lab

See how a Hanami app's logs land on Laravel Cloud.

Each card writes to the logs in a different way. Every action reports back the request_id so you can find the exact lines in the log viewer.

Environment
production
Hanami / Ruby
3.0.2 ยท 4.0.7
Log level
info
Log format
json
Request id from
X-Request-Id header

The logger

Hanami's built-in logger, Dry::Logger, injected into actions and operations with Deps.

01

logger

Deps["logger"]

default

Hanami's standard logger. In production it writes JSON to STDOUT at info level. This app adds the request id to the logger's context, so every line in the request carries it.

include Deps["logger"]

logger.info "message"
logger.debug { expensive_string }
02

Payloads

Structured data

Keyword arguments become structured fields. In JSON format they're top-level keys, and in string format they're printed as key=value.

logger.info("Charged card", user_id: 42, plan: "pro")
03

Tagged

logger.tagged

Tags every line inside the block. Hanami uses tags to route entries (its request lines are tagged :rack), but its default log templates don't print tags, so the lines appear without them.

logger.tagged(:billing) do
  logger.info "message"
end

Requests

What Hanami logs on its own for every request.

04

Request log

Hanami::Web::RackLogger

One line per request with the verb, path, status, elapsed time and params, written through the same logger (tagged :rack). It's logged after the request finishes, so it has no request id. Pick a status to see how the platform treats it.

{"verb":"GET","status":503,"path":"/status","elapsed":0.3,"elapsed_unit":"ms",...}

Errors

Logged exceptions, and real crashes with multi-line backtraces.

05

Logged exception

logger.error(exception)

Dry::Logger accepts an exception directly and records its class, message and backtrace as structured fields. A payload can be added too.

rescue => e
  logger.error(e)
  logger.error(e, order_id: 1234)
06

Unhandled exception

render_errors

Hanami logs the exception through the request logger at error level, then renders public/500.html. Use the browser back button to return.

raise "boom"

Threads & stdio

Lines written outside the request, or outside the logger altogether.

07

Background thread

Outside the request

Logs from a thread after the response has gone, so there's no request id. Threads, timers and job libraries all log this way.

Thread.new { logger.info "message" }
08

Raw STDOUT / STDERR

Bypasses the logger

Writes straight to the process streams. Checks whether the platform captures both, and whether it treats stderr as errors.

$stdout.puts "message"
$stderr.puts "message"
warn "message"

Edge cases

Inputs that tend to break log pipelines.

BurstMany lines in one request
Huge lineOne line of N bytes
Multi-lineEmbedded newlines
Unicode + ANSIEmoji, CJK, colour codes
Every leveldebug through fatal