Observability

Logging

Logging channels, PSR logger bindings, log file path convention and practical usage patterns.

Provider wiring

LoggingServiceProvider registers LogFilePathResolver, LogManager, alias log, alias log.app, alias log.benchmark and maps Psr\Log\LoggerInterface to log.app. The provider also sets the container diagnostic logger to the app logger channel.

$container->singleton(LogFilePathResolver::class, LogFilePathResolver::class);
$container->singleton(LogManager::class, LogManager::class);
$container->singleton('log', LogManager::class);

$container->singleton('log.app', static function (ContainerInterface $container): LoggerInterface {
    $manager = $container->get(LogManager::class);
    $logger = $manager->app();

    $container->setDiagnosticLogger($logger);

    return $logger;
});

$container->singleton('log.benchmark', static fn (ContainerInterface $container): LoggerInterface => $container->get(LogManager::class)->benchmark());

$container->singleton(LoggerInterface::class, static fn (ContainerInterface $container): LoggerInterface => $container->get('log.app'));

Configuration

Logging channels are configured via merged application config (typically app/Config/Logging.php) with keys like app.log.*, error.log.*, request.log.* and benchmark.log.*. Channel methods on LogManager are app(), error(), request() and benchmark().

APP_LOG_FILE=app.log
ERROR_LOG_FILE=error.log
REQUEST_LOG_FILE=request.log
BENCHMARK_LOG_FILE=benchmark.log

Log file path convention

Log filenames are resolved through LogFilePathResolver. Relative filenames such as app.log are stored under storage/writable/logs. Absolute paths are preserved.

Framework internal logging

Kernel and CLI failures are logged via ExceptionLogger. Container diagnostics also use the logger configured by LoggingServiceProvider through the container diagnostic logger binding.

Channel behavior

Disabled channels resolve to Psr\Log\NullLogger, so application code can log safely without per-call channel availability checks.

Usage through dependency injection

Prefer constructor injection of Psr\Log\LoggerInterface for application services. Use LogManager only when explicit channel selection is required.

final class ImportService
{
    public function __construct(
        private readonly LoggerInterface $logger,
    ) {
    }

    public function run(): void
    {
        $this->logger->info('Import started');
    }
}