Logging (PSR-3)

Track application events and errors with Larafony's PSR-3 compliant logging system.

PSR-3 Compliant: Fully implements Psr\Log\LoggerInterface with all eight log levels.

Overview

The logging system provides:

Basic Usage

Simple Logging

use Larafony\Framework\Log\Log;

// Info level
Log::info('User logged in', ['user_id' => 123]);

// Error level
Log::error('Database connection failed', [
    'database' => 'production',
    'error' => $exception->getMessage()
]);

// Debug level
Log::debug('Cache miss', ['key' => 'user:123:profile']);

All Log Levels

// Emergency: System is unusable
Log::emergency('System is down');

// Alert: Action must be taken immediately
Log::alert('Disk space critical');

// Critical: Critical conditions
Log::critical('Application crashed');

// Error: Runtime errors
Log::error('Failed to process payment');

// Warning: Exceptional occurrences that are not errors
Log::warning('High memory usage detected');

// Notice: Normal but significant events
Log::notice('Configuration updated');

// Info: Interesting events
Log::info('User registered');

// Debug: Detailed debug information
Log::debug('Query executed', ['sql' => $sql]);

Log Context

Adding Context Data

// Context provides additional information
Log::info('Order placed', [
    'order_id' => 12345,
    'user_id' => 67,
    'total' => 99.99,
    'items' => ['product_1', 'product_2']
]);

// Context with exception
try {
    // Something that might fail
} catch (Exception $e) {
    Log::error('Operation failed', [
        'exception' => $e->getMessage(),
        'trace' => $e->getTraceAsString(),
        'file' => $e->getFile(),
        'line' => $e->getLine()
    ]);
}

Placeholder Interpolation

// PSR-3 placeholder syntax
Log::info('User {username} performed {action}', [
    'username' => 'john.doe',
    'action' => 'logout'
]);
// Output: "User john.doe performed logout"

// Works with all types
Log::warning('Failed login from {ip} with data {data}', [
    'ip' => '10.0.0.1',
    'data' => ['username' => 'admin', 'attempts' => 5]
]);

Configuration

File Handler Configuration

// config/logging.php
return [
    'channels' => [
        [
            'handler' => 'file',
            'path' => storage_path('logs/app.log'),
            'formatter' => 'text',
            'max_days' => 14
        ],
        [
            'handler' => 'file',
            'path' => storage_path('logs/errors.log'),
            'formatter' => 'json',
            'max_days' => 30
        ]
    ]
];

Creating Logger Programmatically

use Larafony\Framework\Log\Logger;
use Larafony\Framework\Log\Handlers\FileHandler;
use Larafony\Framework\Log\Formatters\JsonFormatter;
use Larafony\Framework\Log\Formatters\TextFormatter;
use Larafony\Framework\Log\Rotators\DailyRotator;

$logger = new Logger([
    // JSON logs with 30-day rotation
    new FileHandler(
        logPath: '/var/log/app/application.log',
        formatter: new JsonFormatter(),
        rotator: new DailyRotator(maxDays: 30)
    ),

    // Text logs with 7-day rotation
    new FileHandler(
        logPath: '/var/log/app/debug.log',
        formatter: new TextFormatter(),
        rotator: new DailyRotator(maxDays: 7)
    ),
]);

Handlers

File Handler

use Larafony\Framework\Log\Handlers\FileHandler;
use Larafony\Framework\Log\Formatters\TextFormatter;

$handler = new FileHandler(
    logPath: '/var/log/app/app.log',
    formatter: new TextFormatter()
);

$logger = new Logger([$handler]);

Database Handler

use Larafony\Framework\Log\Handlers\DatabaseHandler;

// Stores logs in database
$handler = new DatabaseHandler();

$logger = new Logger([$handler]);

// Logs are automatically saved to the database
$logger->error('Critical error', ['details' => 'Some error']);

Formatters

Text Formatter

use Larafony\Framework\Log\Formatters\TextFormatter;

$formatter = new TextFormatter();

// Output format:
// [2025-01-15 14:30:45] ERROR: Database connection failed
// Context: {"database":"production","error":"Connection timeout"}
// Metadata: {"timestamp":"2025-01-15T14:30:45+00:00"}

JSON Formatter

use Larafony\Framework\Log\Formatters\JsonFormatter;

$formatter = new JsonFormatter();

// Output format (pretty-printed JSON):
// {
//   "level": "error",
//   "message": "Database connection failed",
//   "context": {
//     "database": "production",
//     "error": "Connection timeout"
//   },
//   "metadata": {
//     "timestamp": "2025-01-15T14:30:45+00:00"
//   }
// }

XML Formatter

use Larafony\Framework\Log\Formatters\XmlFormatter;

$formatter = new XmlFormatter();

// Output format:
// 
// 
//   error
//   Database connection failed
//   
//     production
//     Connection timeout
//   
// 

Log Rotation

Daily Rotation

use Larafony\Framework\Log\Rotators\DailyRotator;

// Rotate daily, keep logs for 14 days
$rotator = new DailyRotator(maxDays: 14);

// Logs are automatically rotated:
// app.log          (current)
// app-2025-01-14.log
// app-2025-01-13.log
// ...
// (older logs automatically deleted)

Practical Examples

Example 1: Application Logging

class UserService
{
    public function createUser(array $data): User
    {
        Log::info('Creating new user', [
            'email' => $data['email']
        ]);

        try {
            $user = User::create($data);

            Log::info('User created successfully', [
                'user_id' => $user->id,
                'email' => $user->email
            ]);

            return $user;
        } catch (Exception $e) {
            Log::error('Failed to create user', [
                'email' => $data['email'],
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            throw $e;
        }
    }
}

Example 2: API Request Logging

class ApiMiddleware
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $startTime = microtime(true);

        Log::info('API request started', [
            'method' => $request->getMethod(),
            'uri' => (string) $request->getUri(),
            'user_agent' => $request->getHeaderLine('User-Agent')
        ]);

        try {
            $response = $this->next($request);

            $duration = microtime(true) - $startTime;

            Log::info('API request completed', [
                'method' => $request->getMethod(),
                'uri' => (string) $request->getUri(),
                'status' => $response->getStatusCode(),
                'duration' => round($duration * 1000, 2) . 'ms'
            ]);

            return $response;
        } catch (Exception $e) {
            Log::error('API request failed', [
                'method' => $request->getMethod(),
                'uri' => (string) $request->getUri(),
                'error' => $e->getMessage()
            ]);

            throw $e;
        }
    }
}

Example 3: Performance Monitoring

class PerformanceLogger
{
    public static function logSlowQuery(string $sql, float $duration): void
    {
        if ($duration > 1.0) {
            Log::warning('Slow query detected', [
                'sql' => $sql,
                'duration' => $duration,
                'threshold' => 1.0
            ]);
        }
    }

    public static function logHighMemoryUsage(): void
    {
        $memory = memory_get_usage(true) / 1024 / 1024;

        if ($memory > 100) {
            Log::warning('High memory usage', [
                'memory_mb' => round($memory, 2),
                'threshold' => 100
            ]);
        }
    }
}

Custom Handler

Creating Custom Handler

use Larafony\Framework\Log\Contracts\HandlerContract;
use Larafony\Framework\Log\Message;

class SlackHandler implements HandlerContract
{
    public function __construct(
        private readonly string $webhookUrl,
        private readonly string $channel
    ) {}

    public function handle(Message $message): void
    {
        // Only send critical logs to Slack
        if ($message->level->value !== 'critical') {
            return;
        }

        $payload = json_encode([
            'channel' => $this->channel,
            'text' => $message->message,
            'attachments' => [[
                'color' => 'danger',
                'fields' => [
                    ['title' => 'Level', 'value' => $message->level->value],
                    ['title' => 'Time', 'value' => $message->metadata->timestamp]
                ]
            ]]
        ]);

        // Send to Slack webhook
        $ch = curl_init($this->webhookUrl);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_exec($ch);
        curl_close($ch);
    }
}

// Usage
$logger = new Logger([
    new FileHandler('/var/log/app.log', new TextFormatter()),
    new SlackHandler('https://hooks.slack.com/...', '#alerts')
]);

Best Practices

Do

Don't

Security Considerations

Security Warning: Never log sensitive data like passwords, API keys, or credit card numbers.
// WRONG - Logs sensitive data
Log::info('User login', [
    'email' => $email,
    'password' => $password  // NEVER DO THIS!
]);

// CORRECT - Exclude sensitive data
Log::info('User login attempt', [
    'email' => $email,
    'ip' => $request->getAttribute('ip_address')
]);

Next Steps

Configuration

Configure logging handlers and formatters.

Read Guide

Container

Use dependency injection for logger instances.

Read Guide