Logger

Unlike Csrf/RateLimiter/CORS/Jsonify, Logger has no __invoke() — it's filed under Middlewares because that's where Clarity ships it, but it's a plain PSR-3 logger (extends Psr\Log\AbstractLogger) you construct directly and call wherever logging is needed, never registered via ->middleware().

Construction

new Logger(
    string $channel,
    string $path,
    bool $json = false,
    ?DateTimeZone $timezone = null,
    int $maxBytesBeforeRotation = 10_485_760,
    int $maxRotatedFiles = 5,
)

This site's own app/Middlewares/Logger.php is the app-level error channel, handed straight to Mediator::configure():

final class Logger extends \Monad\Clarity\Middlewares\Logger
{
    public function __construct()
    {
        parent::__construct(channel: 'app', path: PATH['error_log'] . 'app.log');
    }
}

Mediator::configure(debug: false, logger: new Logger());

DeploymentTopology.md names three channels: error/app.log, error/db.log, event/timeline.log — construct one Logger instance per channel wherever that logging happens, rather than overloading one instance with all three.

Every PSR-3 method, for free

Inherited from AbstractLogger: emergency(), alert(), critical(), error(), warning(), notice(), info(), debug(), and the general-purpose log($level, $message, $context).

$logger->info('User {email} logged in', ['email' => $user->email, 'request_id' => $requestId]);
$logger->error('Payment failed', ['exception' => $exception]);

Real output for the two calls above:

[2026-08-08T02:22:37+00:00] app.INFO: User marshal@example.com logged in request_id=abc123 {"email":"marshal@example.com"}
[2026-08-08T02:22:37+00:00] app.ERROR: Payment failed exception=RuntimeException("Card declined" at ...)

{email} is PSR-3 message interpolation — replaced from $context automatically. request_id, user_id, and exception in $context are promoted to their own fields rather than nested; every other key passes through Utils\Redactor before being appended as JSON.

Rotation

Size-based: once the current file exceeds maxBytesBeforeRotation, it shifts path.1path.2 → … up the chain and starts a fresh path, discarding anything past maxRotatedFiles.

Next steps

  • Mediator — the main consumer of a configured Logger.