Skip to content
Ori Pekelman edited this page May 11, 2026 · 1 revision

Tep::Logger

Levelled logger. Same surface as Ruby's stdlib Logger, scoped to what fits a single-binary HTTP server: stderr by default, optional append to a file, four levels (debug, info, warn, error).

Quick start

log = Tep::Logger.new
log.info("hello")
log.warn("upstream slow: " + ms.to_s + "ms")
log.error("boom")

Output goes to stderr with a [LEVEL] prefix:

[INFO] hello
[WARN] upstream slow: 234ms
[ERROR] boom

Levels

log.set_level("debug")   # default "info"
log.debug("verbose")
log.info("normal")
log.warn("notable")
log.error("bad")

The threshold is inclusive — set_level("warn") suppresses debug and info calls. Level names are case-insensitive strings; Tep::Logger.level_value("info") returns the numeric threshold (0 debug → 3 error) if you need to compare.

Output redirection

to_file(path) switches to append-mode file output:

log = Tep::Logger.new
log.to_file("/var/log/app/access.log")
log.info("request: " + request.path)

to_stderr restores stderr-write (useful from a configure block that wants stderr in dev, file in prod).

There's no log rotation; pipe through logrotate from the host, or use to_file against a path that's recreated by an external rotator.

Best practices

  • One logger per process, hoisted at boot. Spawning a fresh Logger.new per request is fine — the construction cost is tiny — but it makes file-output configuration awkward. Hoist to a module constant set in on_start.
  • No format string. All logger methods take a single String. Use + or String#<< to compose; the framework's hot path doesn't need printf-style formatting.
  • No structured logging. If you want JSON lines for observability, encode the line yourself with Tep::Json and pass it as the message.

Cookbook

Request-time access log

LOG = Tep::Logger.new
LOG.to_file("/var/log/app/access.log")

before do
  LOG.info(request.verb + " " + request.path)
end

Structured (JSON-line) errors

LOG = Tep::Logger.new

after do
  if response.status >= 500
    h = Tep.str_hash
    h["verb"]   = request.verb
    h["path"]   = request.path
    h["status"] = response.status.to_s
    LOG.error(Tep::Json.from_str_hash(h))
  end
end

Pitfalls

  • Log level is per-instance. Two Tep::Logger.new instances start at "info" independently — set_level doesn't propagate.
  • to_file is append-only. The file is opened for append on every write (no buffering). Fine for low-rate logs; for very high rates, consider piping the binary's stderr through logger -t or your platform's journal collector.
  • Stderr is the worker's stderr. Under prefork (--workers N), each worker writes independently. If two workers append to the same file concurrently, line interleaving is the kernel's job — on Linux, writes under PIPE_BUF (4 KiB) are atomic.

Clone this wiki locally