Monolog

De facto standard logging library for PHP. Default adoption in Laravel, Symfony, and Lumen. Implements PSR-3 interface, provides multiple handlers (file, database, email, Slack, etc.), processors, and formatters.

Logging LibraryPHPPSR-3LaravelSymfonyHierarchical Logging

Library

Monolog

Overview

Monolog is the standard logging library for PHP complying with PSR-3 logging specification. It supports hierarchical log levels, diverse handlers, and metadata addition through processors. Standard adoption in major PHP frameworks like Laravel and Symfony, with rich handlers supporting various output destinations including files, databases, email, and Slack. Enables modern log management through structured logging, context information, and custom formatters.

Details

Monolog 3.x maintains unshakable position as standard logging solution in PHP ecosystem in 2025. The latest version requiring PHP 8.0+ provides enterprise-grade logging functionality through improved type safety, performance enhancements, and utilization of modern PHP features. High compatibility with other libraries through PSR-3 compliance, with rich handlers (File, Database, Mail, Slack, Elasticsearch, etc.) and processors (IP, User, Memory, etc.) supporting everything from small-scale applications to large enterprise systems.

Key Features

  • PSR-3 Compliance: Complete adherence to PHP standard logging specification for high compatibility
  • Rich Handlers: 20+ output destinations including files, databases, email, Slack, and cloud services
  • Hierarchical Loggers: Detailed log management and level control by channels
  • Processor Support: Automatic addition of metadata like IP, user, and memory usage
  • Framework Integration: Standard adoption in Laravel and Symfony with zero-configuration usage
  • Structured Logging: Modern log format support through JSON format and context information

Pros and Cons

Pros

  • High compatibility with other PHP libraries through PSR-3 compliance
  • Immediate usage start due to standard adoption in Laravel and Symfony
  • Support for diverse output destinations through 20+ rich handlers
  • Detailed log information capture through automatic metadata addition via processors
  • Separated management of purpose-specific loggers through channel functionality
  • Integration with cloud services (AWS CloudWatch, Google Cloud Logging, etc.)
  • Affinity with modern log analysis platforms through JSON format support

Cons

  • Features may be overkill for lightweight applications
  • Performance overhead may occur when using multiple handlers
  • Array-based configuration can become complex in advanced setups
  • Limited asynchronous logging support compared to other libraries
  • Cannot maintain consistency in multi-language projects as it's PHP-specific
  • Somewhat high learning cost when creating custom handlers

References

Code Examples

Installation and Basic Setup

# Installation via Composer
composer require monolog/monolog

# Specify version (PHP 8.0+ recommended)
composer require monolog/monolog:^3.0
<?php
// Load Composer autoloader
require_once 'vendor/autoload.php';

// Simplest usage example
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create logger instance
$logger = new Logger('app');

// Add handler (file output)
$logger->pushHandler(new StreamHandler('app.log', Logger::DEBUG));

// Basic log output
$logger->debug('Debug message');
$logger->info('Info message');
$logger->notice('Notice message');
$logger->warning('Warning message');
$logger->error('Error message');
$logger->critical('Critical message');
$logger->alert('Alert message');
$logger->emergency('Emergency message');

Basic Logging Operations (Levels, Formatting)

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;

// Logger setup with multiple handlers
$logger = new Logger('myapp');

// Rotating file handler (daily rotation)
$fileHandler = new RotatingFileHandler('logs/app.log', 30, Logger::DEBUG);

// Custom formatter setup
$formatter = new LineFormatter(
    "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
    'Y-m-d H:i:s'
);
$fileHandler->setFormatter($formatter);
$logger->pushHandler($fileHandler);

// Error-specific handler
$errorHandler = new StreamHandler('logs/error.log', Logger::ERROR);
$logger->pushHandler($errorHandler);

// Log with context information
$logger->info('User action', [
    'user_id' => 12345,
    'action' => 'login',
    'ip_address' => '192.168.1.100',
    'user_agent' => 'Mozilla/5.0...'
]);

// Message with placeholders
$logger->warning('Failed login attempt for user {username} from {ip}', [
    'username' => 'john_doe',
    'ip' => '192.168.1.200'
]);

// Exception logging
try {
    $result = 10 / 0;
} catch (Exception $e) {
    $logger->error('Division by zero error', [
        'exception' => $e,
        'trace' => $e->getTraceAsString()
    ]);
}

// Array/object data logging
$userData = [
    'id' => 123,
    'name' => 'John Doe',
    'email' => '[email protected]'
];

$logger->info('User data processed', ['user_data' => $userData]);

// Conditional logging (performance consideration)
if ($logger->isHandling(Logger::DEBUG)) {
    $expensiveData = generateExpensiveDebugData();
    $logger->debug('Expensive debug information', ['data' => $expensiveData]);
}

Advanced Configuration and Customization (Handlers, Processors)

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\MailHandler;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Handler\DatabaseHandler;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\WebProcessor;
use Monolog\Processor\MemoryUsageProcessor;
use Monolog\Formatter\JsonFormatter;

// Logger with advanced configuration
$logger = new Logger('production');

// JSON format file handler
$jsonHandler = new StreamHandler('logs/app.json', Logger::INFO);
$jsonHandler->setFormatter(new JsonFormatter());
$logger->pushHandler($jsonHandler);

// Mail handler (for critical error notifications)
$mailHandler = new MailHandler(
    '[email protected]',
    'Critical Error Alert',
    '[email protected]',
    Logger::CRITICAL
);
$logger->pushHandler($mailHandler);

// Slack handler (for team notifications)
$slackHandler = new SlackWebhookHandler(
    'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK',
    '#alerts',
    'MonologBot',
    true,
    null,
    Logger::ERROR
);
$logger->pushHandler($slackHandler);

// Database handler
$pdo = new PDO('mysql:host=localhost;dbname=logs', 'user', 'password');
$dbHandler = new DatabaseHandler($pdo, 'logs', [], Logger::WARNING);
$logger->pushHandler($dbHandler);

// Add processors
$logger->pushProcessor(new IntrospectionProcessor()); // File/line number info
$logger->pushProcessor(new WebProcessor()); // HTTP information
$logger->pushProcessor(new MemoryUsageProcessor()); // Memory usage

// Add custom processor
$logger->pushProcessor(function ($record) {
    $record['extra']['application_version'] = '1.2.3';
    $record['extra']['environment'] = $_ENV['APP_ENV'] ?? 'production';
    $record['extra']['server_name'] = gethostname();
    return $record;
});

// Usage example
$logger->error('Database connection failed', [
    'database' => 'main',
    'host' => 'localhost',
    'timeout' => 30
]);

Structured Logging and Modern Observability Support

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\ElasticaHandler;
use Monolog\Formatter\ElasticaFormatter;
use Monolog\Formatter\JsonFormatter;
use Elasticsearch\ClientBuilder;

// Elasticsearch-enabled logger
$client = ClientBuilder::create()
    ->setHosts(['localhost:9200'])
    ->build();

$logger = new Logger('observability');

// Elasticsearch handler
$elasticHandler = new ElasticaHandler($client, [
    'index' => 'application-logs',
    'type' => '_doc'
], Logger::INFO);
$elasticHandler->setFormatter(new ElasticaFormatter('application', 'logs'));
$logger->pushHandler($elasticHandler);

// Structured log file
$structuredHandler = new StreamHandler('logs/structured.json', Logger::DEBUG);
$structuredHandler->setFormatter(new JsonFormatter());
$logger->pushHandler($structuredHandler);

// Distributed tracing support
function logWithTracing($logger, $traceId, $spanId, $message, $context = []) {
    $context['trace_id'] = $traceId;
    $context['span_id'] = $spanId;
    $context['service_name'] = 'user-service';
    $context['service_version'] = '1.0.0';
    
    $logger->info($message, $context);
}

// Business metrics logging
function logBusinessMetric($logger, $metricName, $value, $labels = []) {
    $logger->info('business_metric', [
        'metric_name' => $metricName,
        'metric_value' => $value,
        'metric_type' => 'counter',
        'labels' => $labels,
        'timestamp' => time()
    ]);
}

// Usage examples
$traceId = uniqid('trace_');
$spanId = uniqid('span_');

logWithTracing($logger, $traceId, $spanId, 'User order processing started', [
    'user_id' => 12345,
    'order_id' => 'order_67890',
    'amount' => 99.99,
    'currency' => 'USD'
]);

// Security event logging
$logger->warning('security_event', [
    'event_type' => 'suspicious_login',
    'user_id' => 12345,
    'ip_address' => '192.168.1.100',
    'user_agent' => 'curl/7.68.0',
    'severity' => 'medium',
    'detection_method' => 'rate_limiting'
]);

// Performance metrics
$startTime = microtime(true);
// ... process execution ...
$endTime = microtime(true);

$logger->info('performance_metric', [
    'operation' => 'database_query',
    'duration_ms' => round(($endTime - $startTime) * 1000, 2),
    'query_type' => 'SELECT',
    'table' => 'users',
    'result_count' => 150
]);

// Business metrics recording
logBusinessMetric($logger, 'orders_completed', 1, [
    'payment_method' => 'credit_card',
    'customer_tier' => 'premium'
]);

Error Handling and Performance Optimization

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\BufferHandler;
use Monolog\Handler\FingersCrossedHandler;
use Monolog\Handler\GroupHandler;
use Monolog\Handler\FilterHandler;

// Performance-optimized logger setup
$logger = new Logger('optimized');

// Buffer handler (batch processing)
$fileHandler = new StreamHandler('logs/app.log', Logger::DEBUG);
$bufferHandler = new BufferHandler($fileHandler, 100, Logger::DEBUG, true, true);
$logger->pushHandler($bufferHandler);

// FingersCrossed handler (detailed logs only on errors)
$debugHandler = new StreamHandler('logs/debug.log', Logger::DEBUG);
$fingersCrossedHandler = new FingersCrossedHandler(
    $debugHandler,
    Logger::ERROR,
    100,
    true,
    true,
    Logger::INFO
);
$logger->pushHandler($fingersCrossedHandler);

// Error level-specific handler
$errorHandler = new FilterHandler(
    new StreamHandler('logs/error.log', Logger::ERROR),
    Logger::ERROR,
    Logger::EMERGENCY
);
$logger->pushHandler($errorHandler);

// Performance-aware logging function
function performanceAwareLog($logger, $level, $message, $context = []) {
    // Avoid unnecessary processing with log level check
    if (!$logger->isHandling($level)) {
        return;
    }
    
    // Execute heavy processing only when necessary
    if (isset($context['include_stack_trace']) && $context['include_stack_trace']) {
        $context['stack_trace'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
    }
    
    $logger->log($level, $message, $context);
}

// Log processing with retry functionality
function logWithRetry($logger, $level, $message, $context = [], $maxRetries = 3) {
    $attempt = 0;
    
    while ($attempt < $maxRetries) {
        try {
            $logger->log($level, $message, $context);
            return true;
        } catch (Exception $e) {
            $attempt++;
            
            if ($attempt >= $maxRetries) {
                // Last resort: record to system log or error log file
                error_log("Monolog failed after {$maxRetries} attempts: " . $e->getMessage());
                return false;
            }
            
            // Wait with exponential backoff
            usleep(pow(2, $attempt) * 100000); // 0.1s, 0.2s, 0.4s...
        }
    }
    
    return false;
}

// Bulk processing log function
function logBulkOperations($logger, $operations) {
    $startTime = microtime(true);
    $successCount = 0;
    $errorCount = 0;
    $errors = [];
    
    foreach ($operations as $operation) {
        try {
            processOperation($operation);
            $successCount++;
        } catch (Exception $e) {
            $errorCount++;
            $errors[] = [
                'operation' => $operation,
                'error' => $e->getMessage()
            ];
            
            $logger->error('Bulk operation failed', [
                'operation_id' => $operation['id'] ?? 'unknown',
                'error' => $e->getMessage(),
                'exception' => $e
            ]);
        }
    }
    
    $duration = microtime(true) - $startTime;
    
    $logger->info('Bulk processing completed', [
        'total_operations' => count($operations),
        'successful' => $successCount,
        'failed' => $errorCount,
        'success_rate' => $successCount / count($operations),
        'duration_seconds' => round($duration, 3),
        'errors' => $errors
    ]);
}

// Usage examples
performanceAwareLog($logger, Logger::DEBUG, 'Debug information', [
    'user_id' => 123,
    'include_stack_trace' => true
]);

logWithRetry($logger, Logger::CRITICAL, 'Critical system error', [
    'system' => 'payment',
    'error_code' => 'PAYMENT_001'
]);

Framework Integration and Practical Examples

<?php
// Laravel integration example
// config/logging.php configuration
return [
    'default' => env('LOG_CHANNEL', 'stack'),
    
    'channels' => [
        'stack' => [
            'driver' => 'stack',
            'channels' => ['daily', 'slack'],
        ],
        
        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/laravel.log'),
            'level' => 'debug',
            'days' => 14,
        ],
        
        'slack' => [
            'driver' => 'slack',
            'url' => env('LOG_SLACK_WEBHOOK_URL'),
            'username' => 'Laravel Log',
            'emoji' => ':boom:',
            'level' => 'error',
        ],
        
        'custom' => [
            'driver' => 'custom',
            'via' => App\Logging\CustomLoggerFactory::class,
        ],
    ],
];

// Usage in Laravel
use Illuminate\Support\Facades\Log;

class UserController extends Controller
{
    public function store(Request $request)
    {
        Log::info('User creation started', [
            'request_data' => $request->all(),
            'ip_address' => $request->ip(),
            'user_agent' => $request->userAgent()
        ]);
        
        try {
            $user = User::create($request->validated());
            
            Log::info('User created successfully', [
                'user_id' => $user->id,
                'email' => $user->email
            ]);
            
            return response()->json($user, 201);
            
        } catch (Exception $e) {
            Log::error('User creation failed', [
                'error' => $e->getMessage(),
                'request_data' => $request->all(),
                'exception' => $e
            ]);
            
            return response()->json(['error' => 'User creation failed'], 500);
        }
    }
}
<?php
// Symfony integration example
// config/packages/monolog.yaml
monolog:
    channels: ['app', 'security', 'performance']
    handlers:
        main:
            type: stream
            path: '%kernel.logs_dir%/%kernel.environment%.log'
            level: debug
            channels: ['!event']
            
        security:
            type: stream
            path: '%kernel.logs_dir%/security.log'
            level: info
            channels: [security]
            
        performance:
            type: stream
            path: '%kernel.logs_dir%/performance.log'
            level: info
            channels: [performance]
            formatter: monolog.formatter.json

// Usage in Symfony
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class ApiController extends AbstractController
{
    private LoggerInterface $logger;
    private LoggerInterface $securityLogger;
    
    public function __construct(
        LoggerInterface $logger,
        LoggerInterface $securityLogger
    ) {
        $this->logger = $logger;
        $this->securityLogger = $securityLogger;
    }
    
    public function getUserData(Request $request, string $userId): Response
    {
        $this->logger->info('User data request', [
            'user_id' => $userId,
            'request_ip' => $request->getClientIp(),
            'request_time' => time()
        ]);
        
        // Security logging
        $this->securityLogger->info('API access', [
            'endpoint' => '/api/user/' . $userId,
            'ip_address' => $request->getClientIp(),
            'user_agent' => $request->headers->get('User-Agent'),
            'authorization' => $request->headers->has('Authorization')
        ]);
        
        try {
            $userData = $this->userService->getUserData($userId);
            
            $this->logger->info('User data retrieved successfully', [
                'user_id' => $userId,
                'data_size' => strlen(json_encode($userData))
            ]);
            
            return $this->json($userData);
            
        } catch (UserNotFoundException $e) {
            $this->logger->warning('User not found', [
                'user_id' => $userId,
                'requested_by_ip' => $request->getClientIp()
            ]);
            
            return $this->json(['error' => 'User not found'], 404);
            
        } catch (Exception $e) {
            $this->logger->error('Unexpected error in user data retrieval', [
                'user_id' => $userId,
                'error' => $e->getMessage(),
                'exception' => $e
            ]);
            
            return $this->json(['error' => 'Internal server error'], 500);
        }
    }
}
<?php
// Custom service class usage example
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RedisHandler;
use Predis\Client as RedisClient;

class PaymentService
{
    private Logger $logger;
    
    public function __construct()
    {
        $this->logger = new Logger('payment');
        
        // Multiple handler setup
        $this->logger->pushHandler(new StreamHandler('logs/payment.log', Logger::INFO));
        
        // Redis handler (for real-time monitoring)
        $redis = new RedisClient(['host' => 'localhost', 'port' => 6379]);
        $this->logger->pushHandler(new RedisHandler($redis, 'payment_logs'));
    }
    
    public function processPayment(array $paymentData): array
    {
        $transactionId = uniqid('txn_');
        
        $this->logger->info('Payment processing started', [
            'transaction_id' => $transactionId,
            'amount' => $paymentData['amount'],
            'currency' => $paymentData['currency'],
            'payment_method' => $paymentData['method'],
            'user_id' => $paymentData['user_id']
        ]);
        
        try {
            // Payment processing
            $result = $this->executePayment($paymentData);
            
            $this->logger->info('Payment completed successfully', [
                'transaction_id' => $transactionId,
                'payment_gateway_response' => $result,
                'processing_time_ms' => $result['processing_time']
            ]);
            
            return $result;
            
        } catch (PaymentException $e) {
            $this->logger->error('Payment failed', [
                'transaction_id' => $transactionId,
                'error_code' => $e->getCode(),
                'error_message' => $e->getMessage(),
                'payment_data' => $paymentData,
                'exception' => $e
            ]);
            
            throw $e;
        }
    }
    
    private function executePayment(array $data): array
    {
        // Actual payment processing logic
        return ['status' => 'success', 'processing_time' => 150];
    }
}