Sinatra logging lab

See how a Sinatra 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.

Ruby
4.0.7
Sinatra / Rack
4.2.1 ยท 3.2.7
Environment
production
LOG_LEVEL
info
LOG_FORMAT
text
Request id from
X-Request-Id header

Loggers

Sinatra's built-in logger helper, and the app-wide Logger most Sinatra apps add.

01

logger

Sinatra's request logger

default

With enable :logging, routes get a logger helper. It writes to env["rack.errors"], so by default these lines go to STDERR, in Ruby Logger's default format and without a request id.

enable :logging

get "/" do
  logger.info "message"
end
02

Logger

App-wide stdlib ::Logger on STDOUT

The common replacement: a single Logger on STDOUT shared across the app. Levels below LOG_LEVEL are dropped, and the block form is lazy. The app's formatter adds the request id.

LOGGER = Logger.new($stdout, level: :info)
LOGGER.info "message"
LOGGER.debug { expensive_string }

Structured

Named loggers with data attached instead of free-form text.

03

SemanticLogger

semantic_logger gem

popular

The most used structured logging gem outside Rails. Supports named loggers, payload hashes, tags and timing blocks. It writes from a background thread by default. Set LOG_FORMAT=json for JSON output.

logger = SemanticLogger["Checkout"]
SemanticLogger.tagged("billing") do
  logger.info("Charged card", user_id: 42)
  logger.measure_info("Called API") { ... }
end

Requests

What Sinatra logs on its own for every request.

04

Access log

Sinatra::CommonLogger

Also turned on by enable :logging: one Apache-style line per request, written to STDERR. Pick a status to see how the platform treats it.

enable :logging
# 1.2.3.4 - - [12/Oct/2026:02:23:12 +0000] "GET /status?code=503 HTTP/1.1" 503 69 0.0004

Errors

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

05

Logged exception

Rescued and logged

Passing an exception object to the logger. Stdlib prints the message, class and backtrace as one multi-line entry. SemanticLogger records it as a structured exception.

rescue => e
  LOGGER.error(e)
  logger.error(e) # Sinatra helper, to STDERR
  SemanticLogger["Demo"].error("Caught an error", e)
06

Unhandled exception

dump_errors + error block

Sinatra's dump_errors (on by default outside tests) prints the exception and its full backtrace to STDERR. Then the error block renders the 500 page, and here it also logs a line to STDOUT.

set :dump_errors, true

error do
  LOGGER.error "Rendered 500 for #{env["sinatra.error"].class}"
end

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. This is how plain Ruby apps usually do background work.

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 unknown