Deployment

Logging

Configure and manage application logs

Implement effective logging strategies for debugging, monitoring, and auditing your application.

Quick Start

Basic Logging

// Log messages at different levels
logger()->debug('Debugging info', ['user_id' => 123]);
logger()->info('User registered', ['email' => '[email protected]']);
logger()->warning('Cache miss', ['key' => 'users:active']);
logger()->error('Payment failed', ['order_id' => 'abc123', 'error' => $e->getMessage()]);
logger()->critical('Database connection lost');

Structured Logging

Always include context:

logger()->info('Order created', [
    'order_id' => $order->id,
    'user_id' => $order->user_id,
    'total' => $order->total,
    'items_count' => count($order->items),
    'payment_method' => $order->payment_method,
]);

Channel Logging

Log to a specific channel (e.g., api, app, framework):

// If your app exposes the LogManager via helper
logger()->channel('api')->info('API request', ['path' => $request->path()]);

// Or via the container/DI
use Glueful\Logging\LogManager;
/** @var LogManager $log */
$log = container()->get(LogManager::class);
$log->channel('framework')->warning('Slow request', ['duration_ms' => 1250]);

Configuration

Log Channels

config/logging.php (summary of defaults):

return [
  'framework' => [
    'enabled' => env('FRAMEWORK_LOGGING_ENABLED', true),
    'level' => env('FRAMEWORK_LOG_LEVEL', 'info'),
    // feature toggles: log_exceptions, log_deprecations, etc.
  ],

  'application' => [
    'default_channel' => env('LOG_CHANNEL', 'app'),
    'level' => env('LOG_LEVEL', match (env('APP_ENV')) {
      'production' => 'error',
      'staging' => 'warning',
      default => 'debug'
    }),
    'log_to_file' => env('LOG_TO_FILE', true),
    'log_to_db' => env('LOG_TO_DB', true),
  ],

  'paths' => [
    'log_directory' => env('LOG_FILE_PATH', base_path('storage/logs') . '/'),
  ],

  'rotation' => [
    'days' => env('LOG_ROTATION_DAYS', 30),
    'strategy' => env('LOG_ROTATION_STRATEGY', 'daily'),
  ],

  'channels' => [
    'framework' => [ 'driver' => 'daily', 'path' => base_path('storage/logs') . '/framework.log' ],
    'app'       => [ 'driver' => 'daily', 'path' => base_path('storage/logs') . '/app.log' ],
    'api'       => [ 'driver' => 'daily', 'path' => base_path('storage/logs') . '/api.log' ],
    'error'     => [ 'driver' => 'daily', 'path' => base_path('storage/logs') . '/error.log', 'level' => 'error' ],
    'debug'     => [ 'driver' => 'daily', 'path' => base_path('storage/logs') . '/debug.log', 'level' => 'debug' ],
  ],
];

Environment Variables

# Application logging
LOG_CHANNEL=app
LOG_LEVEL=info
LOG_TO_FILE=true
LOG_TO_DB=true

# Framework logging
FRAMEWORK_LOGGING_ENABLED=true
FRAMEWORK_LOG_LEVEL=info

# Files and rotation
LOG_FILE_PATH=/var/www/your-app/storage/logs/
LOG_ROTATION_DAYS=30
LOG_ROTATION_STRATEGY=daily

File Locations

Default files under storage/logs/:

  • app.log — application events
  • api.log — API request/response details
  • framework.log — framework lifecycle/errors
  • error.log — errors-only channel
  • debug.log — verbose debugging when enabled

Log Levels

When to Use Each Level

// DEBUG - Development debugging only
logger()->debug('SQL query executed', [
    'sql' => $sql,
    'bindings' => $bindings,
    'duration' => $duration,
]);

// INFO - Significant events
logger()->info('User logged in', [
    'user_id' => $user->id,
    'ip' => $request->ip(),
]);

// WARNING - Unexpected but handled situations
logger()->warning('API rate limit approaching', [
    'user_id' => $user->id,
    'requests' => $count,
    'limit' => $limit,
]);

// ERROR - Runtime errors that need attention
logger()->error('External API failed', [
    'service' => 'stripe',
    'endpoint' => '/charges',
    'error' => $exception->getMessage(),
    'request_id' => $requestId,
]);

// CRITICAL - System failures requiring immediate action
logger()->critical('Database connection failed', [
    'host' => config('database.host'),
    'error' => $exception->getMessage(),
]);

Contextual Logging

Request Context

Add request context to all logs in a request:

class LogContextMiddleware
{
    public function handle(Request $request, callable $next)
    {
        $context = [
            'request_id' => uniqid(),
            'user_id' => $request->user()?->id,
            'ip' => $request->ip(),
            'user_agent' => $request->userAgent(),
        ];

        // Add context to all subsequent logs
        logger()->withContext($context);

        return $next($request);
    }
}

Application Context

logger()->info('Processing job', [
    'job' => get_class($job),
    'queue' => $queue,
    'attempt' => $attempt,
    'max_attempts' => $maxAttempts,
]);

Logging Patterns

API Requests

class ApiLoggingMiddleware
{
    public function handle(Request $request, callable $next)
    {
        $start = microtime(true);

        logger()->info('API request started', [
            'method' => $request->method(),
            'path' => $request->path(),
            'query' => $request->query(),
        ]);

        $response = $next($request);

        logger()->info('API request completed', [
            'method' => $request->method(),
            'path' => $request->path(),
            'status' => $response->getStatusCode(),
            'duration' => round((microtime(true) - $start) * 1000, 2) . 'ms',
        ]);

        return $response;
    }
}

Database Queries

class QueryLogger
{
    public function log(string $sql, array $bindings, float $duration): void
    {
        if ($duration > 1.0) {
            logger()->warning('Slow query detected', [
                'sql' => $sql,
                'bindings' => $bindings,
                'duration' => $duration . 's',
            ]);
        }

        if (config('app.debug')) {
            logger()->debug('Query executed', [
                'sql' => $sql,
                'bindings' => $bindings,
                'duration' => $duration . 's',
            ]);
        }
    }
}

Queue Jobs

class ProcessOrderJob extends Job
{
    public function handle(): void
    {
        logger()->info('Processing order', [
            'order_id' => $this->orderId,
            'job_id' => $this->job->id(),
        ]);

        try {
            $this->processOrder();

            logger()->info('Order processed successfully', [
                'order_id' => $this->orderId,
            ]);
        } catch (\Exception $e) {
            logger()->error('Order processing failed', [
                'order_id' => $this->orderId,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            throw $e;
        }
    }
}

Authentication Events

// Successful login
logger()->info('User logged in', [
    'user_id' => $user->id,
    'email' => $user->email,
    'ip' => $request->ip(),
]);

// Failed login
logger()->warning('Failed login attempt', [
    'email' => $request->input('email'),
    'ip' => $request->ip(),
    'reason' => 'invalid_credentials',
]);

// Account lockout
logger()->critical('Account locked', [
    'user_id' => $user->id,
    'failed_attempts' => $attempts,
]);

Log Rotation

Configure Rotation

/etc/logrotate.d/glueful:

/var/www/app/storage/logs/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data www-data
    sharedscripts
    postrotate
        # Optionally reload PHP-FPM
        systemctl reload php8.2-fpm > /dev/null 2>&1 || true
    endscript
}

Tip: The daily driver rotates files automatically and keeps the last N days, controlled by the days option (see config/logging.php and LOG_ROTATION_DAYS). Use system logrotate in addition to (or instead of) application rotation if you centralize logs or need OS-level policies.

Manual Rotation

# Force rotation
logrotate -f /etc/logrotate.d/glueful

# Test configuration
logrotate -d /etc/logrotate.d/glueful

Centralized Logging

Send to External Service

Logtail/Better Stack

class LogtailLogger
{
    private string $sourceToken;

    public function log(string $level, string $message, array $context = []): void
    {
        $payload = [
            'dt' => date('c'),
            'level' => $level,
            'message' => $message,
            'context' => $context,
        ];

        $ch = curl_init('https://in.logtail.com');
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->sourceToken,
        ]);
        curl_exec($ch);
        curl_close($ch);
    }
}

ELK Stack (Elasticsearch)

class ElasticsearchLogger
{
    public function log(string $level, string $message, array $context = []): void
    {
        $document = [
            'timestamp' => date('c'),
            'level' => $level,
            'message' => $message,
            'context' => $context,
            'app' => config('app.name'),
            'env' => config('app.env'),
        ];

        $client = new \Elasticsearch\Client([
            'hosts' => [config('logging.elasticsearch.host')],
        ]);

        $client->index([
            'index' => 'logs-' . date('Y.m.d'),
            'body' => $document,
        ]);
    }
}

Sensitive Data

Redact Sensitive Information

class SecureLogger
{
    private array $redactKeys = [
        'password',
        'token',
        'secret',
        'api_key',
        'credit_card',
        'ssn',
    ];

    public function log(string $level, string $message, array $context = []): void
    {
        $context = $this->redactSensitiveData($context);
        logger()->log($level, $message, $context);
    }

    private function redactSensitiveData(array $data): array
    {
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                $data[$key] = $this->redactSensitiveData($value);
            } elseif ($this->isSensitiveKey($key)) {
                $data[$key] = '***REDACTED***';
            }
        }

        return $data;
    }

    private function isSensitiveKey(string $key): bool
    {
        foreach ($this->redactKeys as $redactKey) {
            if (stripos($key, $redactKey) !== false) {
                return true;
            }
        }

        return false;
    }
}

Usage

$secureLogger = new SecureLogger();

$secureLogger->log('info', 'User updated', [
    'user_id' => 123,
    'email' => '[email protected]',
    'password' => 'secret123', // Will be redacted
    'api_key' => 'sk_test_123', // Will be redacted
]);

Performance

Async Logging

Queue log messages for async processing:

class AsyncLogger
{
    public function log(string $level, string $message, array $context = []): void
    {
        Queue::push(new LogMessageJob($level, $message, $context));
    }
}

class LogMessageJob extends Job
{
    public function __construct(
        private string $level,
        private string $message,
        private array $context
    ) {}

    public function handle(): void
    {
        logger()->log($this->level, $this->message, $this->context);
    }
}

Buffered Logging

class BufferedLogger
{
    private array $buffer = [];
    private int $flushThreshold = 100;

    public function log(string $level, string $message, array $context = []): void
    {
        $this->buffer[] = [$level, $message, $context];

        if (count($this->buffer) >= $this->flushThreshold) {
            $this->flush();
        }
    }

    public function flush(): void
    {
        foreach ($this->buffer as [$level, $message, $context]) {
            logger()->log($level, $message, $context);
        }

        $this->buffer = [];
    }

    public function __destruct()
    {
        $this->flush();
    }
}

Debugging

Enable Debug Mode

APP_DEBUG=true
LOG_LEVEL=debug

Debug Specific Components

if (config('app.debug')) {
    logger()->debug('Cache operation', [
        'operation' => 'get',
        'key' => $key,
        'hit' => $hit,
        'ttl' => $ttl,
    ]);
}

Conditional Logging

// Log only in specific environments
if (in_array(config('app.env'), ['development', 'staging'])) {
    logger()->debug('Development log', $data);
}

// Log only for specific users
if ($request->user()?->is_admin) {
    logger()->info('Admin action', ['action' => $action]);
}

Best Practices

1. Use Appropriate Log Levels

// ✅ Good - correct levels
logger()->info('User registered');  // Normal event
logger()->error('Payment failed'); // Error condition

// ❌ Bad - wrong levels
logger()->error('User registered');  // Not an error
logger()->info('Database crashed');  // Too severe

2. Include Context

// ✅ Good - rich context
logger()->error('Order failed', [
    'order_id' => $order->id,
    'user_id' => $user->id,
    'error' => $e->getMessage(),
]);

// ❌ Bad - no context
logger()->error('Order failed');

3. Be Consistent

// ✅ Good - consistent format
logger()->info('User action', ['action' => 'login', 'user_id' => 123]);
logger()->info('User action', ['action' => 'logout', 'user_id' => 123]);

// ❌ Bad - inconsistent
logger()->info('User logged in', ['user' => 123]);
logger()->info('Logout: 123');

4. Avoid Excessive Logging

// ✅ Good - meaningful logs
logger()->info('Batch processed', ['count' => 1000]);

// ❌ Bad - noisy
foreach ($items as $item) {
    logger()->info('Processing item', ['id' => $item->id]);
}

Troubleshooting

Logs not appearing?

  • Check LOG_LEVEL in .env
  • Verify log file permissions
  • Check disk space
  • Review log path configuration

Log files too large?

  • Enable log rotation
  • Lower LOG_LEVEL in production
  • Implement sampling for high-volume logs

Performance impact?

  • Use async logging
  • Reduce log verbosity
  • Sample high-frequency logs

Next Steps