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:
- PSR-3 Compliant - Standard logging interface
- Multiple Handlers - File, database, custom handlers
- Multiple Formats - Text, JSON, XML
- Log Rotation - Automatic daily rotation with cleanup
- Context Support - Add contextual data to logs
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
- Use appropriate log levels (info, error, debug, etc.)
- Add context to provide useful information
- Use structured logging (arrays) instead of string concatenation
- Implement log rotation to manage disk space
- Log exceptions with stack traces
- Use JSON formatter for log aggregation tools
Don't
- Don't log sensitive data (passwords, API keys, credit cards)
- Don't log in tight loops (performance impact)
- Don't use wrong log levels (debug for errors, etc.)
- Don't forget to implement log rotation in production
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')
]);