RRelayer
Home/Features

Logger#

A PSR-3 logger that uses Monolog as its implementation. The app depends on the standard Psr\Log\LoggerInterface, so it shares the same binding with third-party libraries that log via PSR-3.

It is always registered in DI with no configuration required (auto-registered, the same as the HTTP client). The output goes to STDERR by default; LOG_FILE directs it to a path, and LOG_LEVEL changes the threshold. The Monolog implementation follows a thin policy that does not add client-builder-style complexity.

Enabling#

The logger is always registered. As with HttpClient, there is no required configuration, so any page/component can receive Psr\Log\LoggerInterface as a dependency with no extra setup. There are two optional environment variables.

LOG_LEVEL=info              # threshold. one of the 8 PSR-3 levels
LOG_FILE=/var/log/app.log   # output path. php://stderr if unset
  • The default of LOG_LEVEL is dev=debug / production=info. It

accepts the 8 PSR-3 levels (debug info notice warning error critical alert emergency). A misspelling soft-fails and falls back to the default (so Monolog is not made to throw).

  • If unset, logs go to STDERR (12-factor: collected by

docker logs, journald, or a platform log drain). Only deployments that want file output set LOG_FILE — directory creation, .gitignore, and rotation are your responsibility in that case.

→ See also Environment Variables.

Usage#

Just receive Psr\Log\LoggerInterface by type in the page/component constructor (or as a function-page argument).

<?php
use Polidog\Relayer\Router\Component\PageComponent;
use Polidog\Relayer\Router\Component\Element;
use Psr\Log\LoggerInterface;

final class CheckoutPage extends PageComponent
{
    public function __construct(private readonly LoggerInterface $log) {}

    public function render(): Element
    {
        $this->log->info('checkout started for {user}', ['user' => $userId]);
        // ...
    }
}

{placeholder} interpolation (PSR-3 §1.2) is applied to the output (Monolog itself does not interpolate, so PsrLogMessageProcessor handles it). The conventional ['exception' => $e] context key is formatted by Monolog.

What you get automatically (profiler integration)#

In dev, the concrete LoggerInterface is wrapped with TraceableLogger (a PSR-3 decorator), and each entry is mirrored onto the profiler timeline as a log event (labeled with the PSR-3 level). It has the same Traceable* shape as Database / HttpClient / Auth, so log lines appear on the timeline without writing an if profiler branch.

The profiler-side copy is redacted.

  • Values whose key name contains pass / pwd / secret / token /

api_key / auth are masked to ***.

  • A Throwable is shortened to Class: message (raw JSON is not

useful otherwise).

  • Strings over 120 bytes are truncated.

This redaction is only the profiler copy (the profile is raw JSON under var/cache/profiler/ — the same policy as TraceableDatabase masking bound values). The actual Monolog output receives the original context exactly as the app chose it.

In production (APP_ENV other than dev) the alias points directly at Monolog with no decorator, so it is zero-cost like the Profiler. Recording happens before the write, so even if the destination (e.g. an unwritable LOG_FILE) fails, the entry remains on the timeline.

Last updated: 2026-05-19
Change history (1)
  • Created