PSR-3 w praktyce: Logger Interface w PHP
Standard PSR-3 definiuje wspólny interfejs dla logowania w aplikacjach PHP. Dzięki niemu różne biblioteki i frameworki mogą korzystać z jednolitego kontraktu, niezależnie od tego, jaki mechanizm logowania stoi pod spodem.
Dlaczego PSR-3?
- Ujednolicony sposób logowania zdarzeń, błędów i informacji diagnostycznych,
- Łatwe podmienianie implementacji (np. Monolog, własny logger),
- Prostsza integracja z frameworkami i narzędziami,
- Możliwość stosowania standardowych poziomów logów (
debug
,info
,warning
,error
itd.).
Jak działa PSR-3?
Logger zgodny z PSR-3 implementuje interfejs Psr\Log\LoggerInterface
.
Określa on metody takie jak debug()
, info()
, warning()
,
czy error()
, a także ogólną metodę log()
, przyjmującą poziom logowania
jako parametr.
Kluczowe klasy w implementacji PSR-3
1. Logger
Główna klasa odpowiedzialna za rejestrowanie zdarzeń. Udostępnia metody zgodne z interfejsem PSR-3 i odpowiada za delegowanie wpisów logów do właściwego mechanizmu zapisu. W tej klasie możesz zastosować wzorzec Adapter, który umożliwia podłączenie różnych backendów logowania (np. plik, baza danych, system monitoringu).
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Log;
use DJWeb\Framework\Enums\Log\LogLevel;
use DJWeb\Framework\Log\Contracts\HandlerContract;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use Stringable;
final class Logger implements LoggerInterface
{
use LoggerTrait;
/**
* @param array<int, HandlerContract> $handlers
* @param PlaceholderProcessor|null $placeholderProcessor
*/
public function __construct(
public private(set) array $handlers = [],
private ?PlaceholderProcessor $placeholderProcessor = null
)
{
/** @phpstan-ignore-next-line */
$this->handlers = array_filter($this->handlers, static fn ($handler) => $handler instanceof HandlerContract);
$this->placeholderProcessor ??= new PlaceholderProcessor();
}
/**
* @param LogLevel $level
* @param Stringable|string $message
* @param array<string, mixed> $context
*
* @return void
*/
public function log($level, Stringable|string $message, array $context = []): void
{
$message = is_string($message) ? $message : (string) $message;
$processedMessage = new Message(
level: $level,
message: $this->placeholderProcessor?->process($message, new Context($context)) ?? '',
context: new Context($context),
metadata: Metadata::create()
);
array_walk($this->handlers, static fn (HandlerContract $handler) => $handler->handle($processedMessage));
}
}
2. Log
Klasa Log
pełni rolę Fasady — udostępnia prosty punkt wejścia
do systemu logowania, wywołując pod spodem właściwego Loggera
.
Dzięki temu kod aplikacji może korzystać z jednolitego interfejsu
bez konieczności bezpośredniego odwoływania się do szczegółów implementacyjnych.
To klasyczny przykład wzorca
Fasada, który upraszcza interakcję z bardziej
złożonym systemem.
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Log;
use DJWeb\Framework\Base\Application;
use Psr\Log\LoggerInterface;
final class Log
{
private static LoggerInterface $logger;
/**
* @param string $message
* @param array<int|string, mixed> $context
*
* @return void
*/
public static function emergency(string $message, array $context = []): void
{
self::logger()->emergency($message, $context);
}
/**
* @param string $message
* @param array<int|string, mixed> $context
*
* @return void
*/
public static function alert(string $message, array $context = []): void
{
self::logger()->alert($message, $context);
}
/**
* @param string $message
* @param array<int|string, mixed> $context
*
* @return void
*/
public static function critical(string $message, array $context = []): void
{
self::logger()->critical($message, $context);
}
/**
* @param string $message
* @param array<int|string, mixed> $context
*
* @return void
*/
public static function error(string $message, array $context = []): void
{
self::logger()->error($message, $context);
}
/**
* @param string $message
* @param array<int|string, mixed> $context
*
* @return void
*/
public static function warning(string $message, array $context = []): void
{
self::logger()->warning($message, $context);
}
/**
* @param string $message
* @param array<int|string, mixed> $context
*
* @return void
*/
public static function notice(string $message, array $context = []): void
{
self::logger()->notice($message, $context);
}
/**
* @param string $message
* @param array<int|string, mixed> $context
*
* @return void
*/
public static function info(string $message, array $context = []): void
{
self::logger()->info($message, $context);
}
/**
* @param string $message
* @param array<int|string, mixed> $context
*
* @return void
*/
public static function debug(string $message, array $context = []): void
{
self::logger()->debug($message, $context);
}
private static function logger(): LoggerInterface
{
return self::$logger ??= Application::getInstance()->logger;
}
}
3. LoggerFactory
Fabryka odpowiedzialna za tworzenie instancji loggera. Umożliwia konfigurację zależności, wybór kanału logowania czy strategii zapisu. W tym przypadku naturalnie wykorzystujemy wzorzec Factory Method, dzięki czemu kod aplikacji nie musi wiedzieć, jak konkretnie skonstruować loggera.
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Log;
use DJWeb\Framework\Config\Config;
use DJWeb\Framework\Exceptions\Log\LoggerError;
use DJWeb\Framework\Log\Formatters\JsonFormatter;
use DJWeb\Framework\Log\Formatters\TextFormatter;
use DJWeb\Framework\Log\Formatters\XmlFormatter;
use DJWeb\Framework\Log\Handlers\DatabaseHandler;
use DJWeb\Framework\Log\Handlers\FileHandler;
use DJWeb\Framework\Log\Rotators\DailyRotator;
use Psr\Log\LoggerInterface;
class LoggerFactory
{
public static function create(): LoggerInterface
{
$config = Config::get('logging');
$handlers = [];
foreach ($config['channels'] as $settings) {
$handlers[] = match ($settings['handler']) {
'database' => new DatabaseHandler(),
'file' => new FileHandler(
logPath: $settings['path'],
formatter: match ($settings['formatter']) {
'json' => new JsonFormatter(),
'xml' => new XmlFormatter(),
default => new TextFormatter()
},
rotator: new DailyRotator(
maxDays: $settings['max_days'] ?? 7
)
),
default => throw new LoggerError("Unknown handler type: {$settings['handler']}")
};
}
return new Logger($handlers);
}
}
Definicje pozostałych klas powiązanych z PSR-3 znajdziesz w kodzie źródłowym kursu, który jest publicznie dostępny na GitHubie.
Gdzie wykorzystasz PSR-3?
- Logowanie zdarzeń w aplikacjach webowych i API,
- Rejestrowanie błędów w aplikacjach CLI,
- Integracja z zewnętrznymi systemami monitoringu,
- Debugowanie i analiza przepływu aplikacji.
Podsumowanie
PSR-3 to prosty, ale bardzo ważny standard – dzięki niemu zamiast uzależniać się od konkretnego pakietu logowania, możesz pisać kod oparty na wspólnym interfejsie. W praktyce oznacza to elastyczność i łatwość wymiany komponentów.
📖 Oficjalna specyfikacja PSR-3: https://www.php-fig.org/psr/psr-3/
👉 To tylko fragment pełnego kursu MasterPHP!
W kursie znajdziesz kompletną implementację PSR-3 wraz z przykładami:
- tworzenie loggera krok po kroku,
- integracja z biblioteką Monolog,
- łączenie logowania z frameworkami,
- najlepsze praktyki pracy z logami.