This demo shows how to achieve JSON-formatted boot messages from Puma using existing configuration options. This is in response to GitHub Issue #3865.
Puma already supports custom log formatting via two DSL options:
log_formatter- A block that transforms each log message stringcustom_logger- A custom logger object with awrite(str)method
Using these options, you can get JSON output for all Puma boot messages without any changes to Puma itself.
The simplest approach - wraps each message in a JSON object:
log_formatter do |str|
JSON.generate({
timestamp: Time.now.utc.iso8601(3),
pid: Process.pid,
message: str.strip
})
endOutput:
{"timestamp":"2026-01-21T20:28:15.597Z","pid":83692,"message":"Puma starting in cluster mode..."}
{"timestamp":"2026-01-21T20:28:15.598Z","pid":83692,"message":"* Puma version: 7.2.0 (\"On The Corner\")"}Parses Puma's boot messages and extracts structured data:
custom_logger StructuredJsonLogger.new($stdout)Output:
{"timestamp":"2026-01-21T20:29:05.395Z","pid":84032,"event":"puma_starting","mode":"cluster"}
{"timestamp":"2026-01-21T20:29:05.395Z","pid":84032,"event":"version_info","component":"puma","version":"7.2.0 (\"On The Corner\")"}
{"timestamp":"2026-01-21T20:29:05.395Z","pid":84032,"event":"config","setting":"min_threads","value":1}
{"timestamp":"2026-01-21T20:29:05.396Z","pid":84032,"event":"listening","address":"http://0.0.0.0:9292"}
{"timestamp":"2026-01-21T20:29:05.398Z","pid":84061,"event":"worker_booted","worker":0,"worker_pid":84061,"boot_time_seconds":0.0,"phase":0}Uses the semantic_logger gem for enterprise-grade structured logging:
require 'semantic_logger'
SemanticLogger.add_appender(io: $stdout, formatter: :json)
custom_logger SemanticLoggerAdapter.new(SemanticLogger['Puma'])Output:
{"host":"server.local","application":"Semantic Logger","timestamp":"2026-01-21T20:30:35.864997Z","level":"info","level_index":2,"pid":85667,"thread":"376","name":"Puma","message":"Puma starting in cluster mode..."}Uses Ruby's built-in Logger with a JSON formatter (no external dependencies):
custom_logger JsonLogger.new($stdout)cd tmp/json_logging_demo
# Basic JSON formatter
bundle exec puma -C puma_json_basic.rb
# Structured JSON with message parsing
bundle exec puma -C puma_json_structured.rb
# Single mode (no workers)
bundle exec puma -C puma_json_single.rb
# With semantic_logger (requires: gem install semantic_logger)
ruby -I../../lib -rpuma/cli -e 'Puma::CLI.new(["--config", "puma_semantic_logger.rb"]).run'
# Stdlib Logger approach
bundle exec puma -C puma_stdlib_json.rb-
Each line is valid JSON - This is what makes log ingestion pipelines happy. Each boot message becomes a separate JSON object on its own line.
-
The limitation - Puma generates boot messages as individual text strings, not as structured data. So you can't get a single JSON object with all config in one message (like the OP's ideal example) without modifying Puma internals.
-
What IS possible today:
- Each log line as valid JSON ✓
- Timestamp, PID, level fields ✓
- Message parsing to extract structured data ✓
- Integration with logging libraries ✓
-
What would require Puma changes:
- Consolidated boot message (all config in one JSON object)
- Native structured logging with typed fields from the start
The OP can achieve JSON-formatted boot messages TODAY using log_formatter or
custom_logger. The structured parser approach (puma_json_structured.rb)
gets close to what was requested, parsing boot messages into typed events.
If Puma wants to go further, it could:
- Document these existing options better
- Add a built-in JSON formatter option (e.g.,
log_format :json) - Refactor internal logging to use structured events (bigger change)