PSR-3 w praktyce: Logger Interface w PHP | MasterPHP

27.07.2025

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.
Sprawdź pełny kurs