PSR-3 Logger Interface

Common interface standard for logging libraries by PHP-FIG. Defines 8 log levels (emergency, alert, critical, error, warning, notice, info, debug). Abstraction layer promoting interoperability and code reusability between libraries.

Logging LibraryPHPPSR-3Standard InterfaceInteroperability

Library

PSR-3 Logger Interface

Overview

PSR-3 Logger Interface is a common interface standard for logging libraries established by PHP-FIG (PHP Framework Interop Group). It defines eight log levels (emergency, alert, critical, error, warning, notice, info, debug) and functions as an abstraction layer promoting interoperability and code reusability between different logging implementations. As of 2025, it has become completely established as an essential standard in the PHP ecosystem, serving as a unified foundation for frameworks and libraries.

Details

PSR-3 was designed with the goal of allowing "libraries to receive a Psr\Log\LoggerInterface object and log to it in a simple and universal way." It provides eight log level methods compliant with RFC 5424 and a log method that accepts arbitrary levels. Message placeholder functionality enables dynamic log message generation using context array values. It enables writing code independent of implementation-specific logging libraries, providing flexibility to choose appropriate implementations at deployment time.

Key Features

  • Standard Interface: RFC 5424 compliant 8-level logging
  • Message Placeholders: Dynamic context insertion functionality
  • Implementation Independent: Compatibility with any PSR-3 compliant library
  • Dependency Injection Support: Clean architecture support
  • NullLogger Pattern: Safe fallback for testing and development
  • LoggerAwareTrait: Easy logger integration

Pros and Cons

Pros

  • Overwhelming adoption and unification across the entire PHP ecosystem
  • Complete interoperability between frameworks and libraries
  • Free choice and switching of implementation libraries
  • Testable code design through dependency injection patterns
  • Rich implementation choices like Monolog and Symfony Logger
  • Extremely easy integration with external modules

Cons

  • Interface only, actual logging functionality required separately
  • Performance optimization depends on implementation libraries
  • Advanced features (structured logging, etc.) limited to library-specific functionality
  • Slight overhead due to interface abstraction
  • Learning cost depends on complexity of used implementation library
  • Type safety subject to PHP language feature constraints

Reference Pages

Usage Examples

Interface Implementation and Setup

<?php
// Install PSR-3 package
// composer require psr/log

use Psr\Log\LoggerInterface;
use Psr\Log\AbstractLogger;
use Psr\Log\LogLevel;

// Basic custom logger implementation
class SimpleLogger extends AbstractLogger
{
    public function log($level, $message, array $context = [])
    {
        $timestamp = date('Y-m-d H:i:s');
        $interpolated = $this->interpolate($message, $context);
        
        echo sprintf(
            "[%s] %s: %s\n", 
            $timestamp, 
            strtoupper($level), 
            $interpolated
        );
    }
    
    private function interpolate($message, array $context = [])
    {
        $replace = [];
        foreach ($context as $key => $val) {
            if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
                $replace['{'.$key.'}'] = $val;
            }
        }
        return strtr($message, $replace);
    }
}

// Using LoggerAwareTrait
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

class UserService
{
    use LoggerAwareTrait;
    
    public function __construct()
    {
        // Set NullLogger as default
        $this->logger = new NullLogger();
    }
    
    public function createUser(array $userData)
    {
        $this->logger->info('User creation started', ['data' => $userData]);
        
        try {
            // User creation processing
            $userId = $this->doCreateUser($userData);
            $this->logger->info('User creation completed', ['user_id' => $userId]);
            return $userId;
        } catch (Exception $e) {
            $this->logger->error('User creation failed', [
                'exception' => $e,
                'user_data' => $userData
            ]);
            throw $e;
        }
    }
}

Log Levels and Context Usage

<?php
// Usage examples of eight log levels
class ApplicationLogger
{
    private LoggerInterface $logger;
    
    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
    
    public function demonstrateLogLevels()
    {
        // Emergency: System is unusable
        $this->logger->emergency('Database connection completely stopped', [
            'error_code' => 'DB_FATAL',
            'timestamp' => time()
        ]);
        
        // Alert: Action must be taken immediately
        $this->logger->alert('Disk usage reached critical threshold', [
            'disk_usage' => '98%',
            'available_space' => '100MB'
        ]);
        
        // Critical: Critical conditions
        $this->logger->critical('Critical error in payment processing', [
            'payment_id' => 'pay_123456',
            'amount' => 15000,
            'currency' => 'JPY'
        ]);
        
        // Error: Runtime errors (no immediate action required)
        $this->logger->error('File upload failed', [
            'file_name' => 'document.pdf',
            'file_size' => 2048000,
            'error' => 'Permission denied'
        ]);
        
        // Warning: Exceptional but not necessarily error conditions
        $this->logger->warning('API response time exceeded threshold', [
            'endpoint' => '/api/users',
            'response_time' => 5200,
            'threshold' => 3000
        ]);
        
        // Notice: Normal but significant conditions
        $this->logger->notice('New user registration completed', [
            'user_id' => 12345,
            'registration_method' => 'email',
            'user_ip' => '192.168.1.100'
        ]);
        
        // Info: Informational messages
        $this->logger->info('User {username} logged in', [
            'username' => 'john_doe',
            'login_time' => date('c'),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
        ]);
        
        // Debug: Debug information
        $this->logger->debug('SQL query executed', [
            'query' => 'SELECT * FROM users WHERE active = ?',
            'params' => [true],
            'execution_time' => 0.045
        ]);
    }
    
    // Usage of generic log method
    public function logWithDynamicLevel(string $level, string $message, array $context = [])
    {
        $this->logger->log($level, $message, $context);
    }
}

Framework Integration Patterns

<?php
// Symfony usage example
use Symfony\Component\DependencyInjection\ContainerInterface;
use Psr\Log\LoggerInterface;

class SymfonyIntegrationExample
{
    private LoggerInterface $logger;
    
    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
    
    public function processOrder(array $orderData)
    {
        $this->logger->info('Order processing started', ['order' => $orderData]);
        
        // Order processing logic
        return $this->handleOrderProcessing($orderData);
    }
}

// Configuration in services.yaml
/*
services:
    App\Service\OrderService:
        arguments:
            $logger: '@monolog.logger.order'
*/

// Laravel PSR-3 usage example
use Illuminate\Support\Facades\Log;
use Psr\Log\LoggerInterface;

class LaravelIntegrationExample
{
    private LoggerInterface $logger;
    
    public function __construct()
    {
        // Laravel's Log facade is also PSR-3 compliant
        $this->logger = Log::channel('application');
    }
    
    public function handlePayment(float $amount, string $currency)
    {
        $this->logger->info('Payment processing started', [
            'amount' => $amount,
            'currency' => $currency,
            'timestamp' => now()->toISOString()
        ]);
        
        try {
            $result = $this->processPayment($amount, $currency);
            $this->logger->info('Payment processing successful', ['result' => $result]);
            return $result;
        } catch (PaymentException $e) {
            $this->logger->error('Payment processing failed', [
                'exception' => $e->getMessage(),
                'amount' => $amount,
                'currency' => $currency
            ]);
            throw $e;
        }
    }
}

Dependency Injection and Testing Patterns

<?php
use Psr\Log\LoggerInterface;
use Psr\Log\Test\TestLogger;
use PHPUnit\Framework\TestCase;

// Production class
class EmailService
{
    private LoggerInterface $logger;
    private MailerInterface $mailer;
    
    public function __construct(LoggerInterface $logger, MailerInterface $mailer)
    {
        $this->logger = $logger;
        $this->mailer = $mailer;
    }
    
    public function sendEmail(string $to, string $subject, string $body): bool
    {
        $this->logger->info('Email sending started', [
            'to' => $to,
            'subject' => $subject
        ]);
        
        try {
            $result = $this->mailer->send($to, $subject, $body);
            
            if ($result) {
                $this->logger->info('Email sent successfully', ['to' => $to]);
            } else {
                $this->logger->warning('Email sending failed', ['to' => $to]);
            }
            
            return $result;
        } catch (Exception $e) {
            $this->logger->error('Email sending error', [
                'to' => $to,
                'exception' => $e->getMessage()
            ]);
            return false;
        }
    }
}

// Test class
class EmailServiceTest extends TestCase
{
    public function testSuccessfulEmailSending()
    {
        // Use TestLogger to test log output
        $logger = new TestLogger();
        $mailer = $this->createMock(MailerInterface::class);
        $mailer->method('send')->willReturn(true);
        
        $emailService = new EmailService($logger, $mailer);
        
        $result = $emailService->sendEmail(
            '[email protected]',
            'Test Email',
            'This is a test'
        );
        
        $this->assertTrue($result);
        
        // Verify log output
        $this->assertTrue($logger->hasInfo('Email sending started'));
        $this->assertTrue($logger->hasInfo('Email sent successfully'));
        
        // Verify log context
        $records = $logger->records;
        $this->assertEquals('[email protected]', $records[0]['context']['to']);
        $this->assertEquals('Test Email', $records[0]['context']['subject']);
    }
    
    public function testEmailSendingWithException()
    {
        $logger = new TestLogger();
        $mailer = $this->createMock(MailerInterface::class);
        $mailer->method('send')->willThrowException(new Exception('SMTP error'));
        
        $emailService = new EmailService($logger, $mailer);
        
        $result = $emailService->sendEmail('[email protected]', 'Test', 'Test');
        
        $this->assertFalse($result);
        $this->assertTrue($logger->hasError('Email sending error'));
    }
}

Integration with Monolog Implementation

<?php
// PSR-3 implementation example using Monolog
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Formatter\JsonFormatter;

class MonologIntegrationExample
{
    public static function createApplicationLogger(): LoggerInterface
    {
        $logger = new Logger('application');
        
        // Console output handler
        $consoleHandler = new StreamHandler('php://stdout', Logger::DEBUG);
        $logger->pushHandler($consoleHandler);
        
        // Rotating file handler
        $fileHandler = new RotatingFileHandler(
            '/var/log/application.log',
            30, // Keep for 30 days
            Logger::INFO
        );
        $fileHandler->setFormatter(new JsonFormatter());
        $logger->pushHandler($fileHandler);
        
        // Add processor (file name and line number info)
        $logger->pushProcessor(new IntrospectionProcessor());
        
        return $logger;
    }
    
    public static function createTestLogger(): LoggerInterface
    {
        // Simple logger for test environment
        $logger = new Logger('test');
        $handler = new StreamHandler('/tmp/test.log', Logger::DEBUG);
        $logger->pushHandler($handler);
        
        return $logger;
    }
}

// Usage example
$logger = MonologIntegrationExample::createApplicationLogger();
$userService = new UserService();
$userService->setLogger($logger);

// Use through PSR-3 interface
$logger->info('Application startup', [
    'version' => '1.0.0',
    'environment' => 'production',
    'memory_usage' => memory_get_usage(true)
]);

Advanced Context Processing

<?php
// Advanced context processing and placeholders
class AdvancedLoggingExample
{
    private LoggerInterface $logger;
    
    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
    
    public function processComplexOperation(array $data)
    {
        $operationId = uniqid('op_');
        $startTime = microtime(true);
        
        // Start log (structured context)
        $this->logger->info('Starting complex operation: {operation_id}', [
            'operation_id' => $operationId,
            'input_data_count' => count($data),
            'start_time' => $startTime,
            'memory_before' => memory_get_usage(),
            'process_id' => getmypid()
        ]);
        
        try {
            foreach ($data as $index => $item) {
                $this->processItem($item, $operationId, $index);
            }
            
            $endTime = microtime(true);
            $duration = $endTime - $startTime;
            
            // Success log (including performance info)
            $this->logger->info('Operation completed: {operation_id}', [
                'operation_id' => $operationId,
                'duration_seconds' => round($duration, 3),
                'items_processed' => count($data),
                'memory_peak' => memory_get_peak_usage(),
                'memory_after' => memory_get_usage()
            ]);
            
        } catch (Exception $e) {
            $this->logger->error('Error occurred during processing: {operation_id}', [
                'operation_id' => $operationId,
                'exception_class' => get_class($e),
                'exception_message' => $e->getMessage(),
                'exception_file' => $e->getFile(),
                'exception_line' => $e->getLine(),
                'stack_trace' => $e->getTraceAsString()
            ]);
            throw $e;
        }
    }
    
    private function processItem($item, string $operationId, int $index)
    {
        $this->logger->debug('Processing item: {operation_id}[{index}]', [
            'operation_id' => $operationId,
            'index' => $index,
            'item_type' => gettype($item),
            'item_size' => is_string($item) ? strlen($item) : null
        ]);
        
        // Item processing logic
        if (is_array($item) && !empty($item['error'])) {
            $this->logger->warning('Problematic item: {operation_id}[{index}]', [
                'operation_id' => $operationId,
                'index' => $index,
                'error_details' => $item['error']
            ]);
        }
    }
}

// Usage with multiple logger configurations
class ConfigurableLoggingService
{
    private LoggerInterface $logger;
    
    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
    
    public function performBusinessOperation(array $parameters)
    {
        // Comprehensive logging with business context
        $this->logger->info('Business operation initiated', [
            'operation_type' => 'data_processing',
            'user_id' => $parameters['user_id'] ?? null,
            'session_id' => $parameters['session_id'] ?? null,
            'request_id' => $parameters['request_id'] ?? null,
            'client_ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'timestamp' => date('c'),
            'parameters_count' => count($parameters)
        ]);
        
        try {
            $result = $this->executeBusinessLogic($parameters);
            
            $this->logger->info('Business operation successful', [
                'operation_type' => 'data_processing',
                'result_size' => strlen(serialize($result)),
                'execution_time' => microtime(true) - ($_SERVER['REQUEST_TIME_FLOAT'] ?? 0)
            ]);
            
            return $result;
            
        } catch (BusinessException $e) {
            $this->logger->warning('Business logic exception', [
                'exception_type' => 'business',
                'error_code' => $e->getCode(),
                'error_message' => $e->getMessage(),
                'recoverable' => $e->isRecoverable()
            ]);
            throw $e;
            
        } catch (SystemException $e) {
            $this->logger->critical('System exception occurred', [
                'exception_type' => 'system',
                'system_component' => $e->getComponent(),
                'error_details' => $e->getErrorDetails(),
                'requires_immediate_attention' => true
            ]);
            throw $e;
        }
    }
    
    private function executeBusinessLogic(array $parameters)
    {
        // Complex business logic simulation
        $this->logger->debug('Executing core business logic', [
            'parameters' => array_keys($parameters),
            'memory_usage' => memory_get_usage()
        ]);
        
        // Return processed result
        return ['status' => 'success', 'data' => $parameters];
    }
}