Deployment

Monitoring

Monitor your application in production

Track your application's health, performance, and errors in production.

Quick Start

Built-in Health Endpoints

Glueful provides production-ready health routes out of the box; no custom route is required.

  • Liveness: GET /healthz
  • Overall health: GET /health
  • Readiness: GET /ready (IP allowlist via HEALTH_IP_ALLOWLIST)
  • Detailed metrics: GET /health/detailed (requires auth + permissions)
  • Queue health: GET /health/queue (pending jobs, worker activity, reserved jobs, issues)

Quick checks:

curl -fsS http://localhost/healthz
curl -fsS http://localhost/health | jq '.data.status'
curl -I http://localhost/ready
curl -fsS http://localhost/health/queue | jq '.'

Liveness vs Readiness

  • Liveness answers “is the process up?” → use GET /healthz
  • Readiness answers “can this instance serve traffic now?” → use GET /ready
    • Restrict access via HEALTH_IP_ALLOWLIST in .env

Kubernetes Probes

Add liveness and readiness probes to your Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: glueful-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: your-registry/glueful:latest
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 15
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /ready
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3

Key Metrics

Application Metrics

Track these critical metrics:

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

        $response = $next($request);

        $duration = microtime(true) - $start;
        $memoryUsed = memory_get_usage() - $memoryStart;

        // Log metrics
        logger()->info('Request completed', [
            'method' => $request->method(),
            'path' => $request->path(),
            'status' => $response->getStatusCode(),
            'duration' => round($duration * 1000, 2) . 'ms',
            'memory' => round($memoryUsed / 1024 / 1024, 2) . 'MB',
        ]);

        // Add headers
        $response->headers->set('X-Response-Time', round($duration * 1000, 2));
        $response->headers->set('X-Memory-Usage', round($memoryUsed / 1024 / 1024, 2));

        return $response;
    }
}

Track:

  • Response time (p50, p95, p99)
  • Error rate
  • Request throughput
  • Memory usage
  • Database query time
  • Cache hit rate
  • Queue depth
  • Worker health

Database Metrics

class DatabaseMetrics
{
    public static function track(): array
    {
        $start = microtime(true);

        // Run test query
        db()->table('users')->limit(1)->get();

        return [
            'response_time' => microtime(true) - $start,
            'connections' => db()->getConnectionCount(),
        ];
    }
}

Queue Metrics

Glueful’s Redis queue driver uses a prefix (default: glueful:queue:) and these keys:

  • List: queue:{name} (immediate jobs)
  • Sorted sets: queue:{name}:delayed, queue:{name}:reserved
class QueueMetrics
{
    public static function depth(string $queue = 'default'): int
    {
        $prefix = config('queue.connections.redis.prefix', 'glueful:queue:');
        $immediate = redis()->lLen("{$prefix}queue:{$queue}");
        $delayed = redis()->zCard("{$prefix}queue:{$queue}:delayed");
        $reserved = redis()->zCard("{$prefix}queue:{$queue}:reserved");
        return $immediate + $delayed + $reserved;
    }

    public static function failed(): int
    {
        return db()->table('queue_failed_jobs')->count();
    }
}

Logging for Monitoring

Structured Logging

logger()->info('Order processed', [
    'order_id' => $order->id,
    'user_id' => $order->user_id,
    'total' => $order->total,
    'duration' => $duration,
    'payment_method' => $order->payment_method,
]);

logger()->error('Payment failed', [
    'order_id' => $order->id,
    'error' => $exception->getMessage(),
    'gateway_response' => $gatewayResponse,
]);

Log Levels

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

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

// WARNING - Unusual but handled
logger()->warning('Cache miss', ['key' => $cacheKey]);

// ERROR - Errors that need attention
logger()->error('API call failed', ['service' => 'stripe', 'error' => $e]);

// CRITICAL - System failures
logger()->critical('Database connection lost');

Error Tracking

Exception Handler

class ExceptionHandler
{
    public function report(\Throwable $exception): void
    {
        // Log locally
        logger()->error($exception->getMessage(), [
            'exception' => get_class($exception),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'trace' => $exception->getTraceAsString(),
        ]);

        // Send to error tracking service (Sentry, Bugsnag, etc.)
        if (config('app.env') === 'production') {
            $this->reportToSentry($exception);
        }
    }

    private function reportToSentry(\Throwable $exception): void
    {
        // Sentry example
        \Sentry\captureException($exception);
    }
}

Track Custom Events

// Business metrics
logger()->info('Subscription purchased', [
    'plan' => 'pro',
    'revenue' => 99.00,
    'user_id' => $user->id,
]);

// Performance issues
if ($duration > 5.0) {
    logger()->warning('Slow query detected', [
        'query' => $sql,
        'duration' => $duration,
    ]);
}

Alerting

Custom Alerts

class AlertService
{
    public function checkThresholds(): void
    {
        // High error rate
        $errorRate = $this->getErrorRate();
        if ($errorRate > 0.05) { // 5%
            $this->alert('High error rate: ' . ($errorRate * 100) . '%');
        }

        // Queue depth
        $queueDepth = QueueMetrics::depth();
        if ($queueDepth > 1000) {
            $this->alert("Queue depth high: {$queueDepth} jobs");
        }

        // Failed jobs
        $failed = QueueMetrics::failed();
        if ($failed > 100) {
            $this->alert("{$failed} failed jobs need attention");
        }

        // Slow database
        $dbMetrics = DatabaseMetrics::track();
        if ($dbMetrics['response_time'] > 1.0) {
            $this->alert('Database slow: ' . $dbMetrics['response_time'] . 's');
        }
    }

    private function alert(string $message): void
    {
        // Send to Slack, PagerDuty, etc.
        logger()->critical($message);

        // Example: Slack webhook
        $this->sendToSlack($message);
    }

    private function sendToSlack(string $message): void
    {
        $webhook = config('monitoring.slack_webhook');

        $ch = curl_init($webhook);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
            'text' => "🚨 *Alert*: {$message}",
        ]));
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_exec($ch);
        curl_close($ch);
    }
}

Schedule Health Checks

// config/scheduler.php
[
    'name' => 'health-check',
    'schedule' => '*/5 * * * *', // Every 5 minutes
    'handler_class' => 'App\\Jobs\\HealthCheckJob',
]

Performance Monitoring

APM Integration

Integrate with Application Performance Monitoring tools:

// Example: New Relic
if (extension_loaded('newrelic')) {
    newrelic_set_appname('Glueful API');
    newrelic_name_transaction('API/' . $request->path());
}

// Example: Datadog
class DatadogMiddleware
{
    public function handle(Request $request, callable $next)
    {
        $span = \DDTrace\start_span();
        $span->name = 'http.request';
        $span->resource = $request->method() . ' ' . $request->path();

        $response = $next($request);

        $span->meta['http.status_code'] = $response->getStatusCode();
        \DDTrace\close_span();

        return $response;
    }
}

Tip: You can also enable distributed tracing via config/observability.php using .env variables like TRACING_ENABLED=true and TRACING_DRIVER=otel|datadog|newrelic.

Custom Metrics

class Metrics
{
    public static function increment(string $metric, array $tags = []): void
    {
        // StatsD example
        $statsd = new \Domnikl\Statsd\Client(
            new \Domnikl\Statsd\Connection\UdpSocket('localhost', 8125)
        );

        $statsd->increment($metric, 1, 1.0, $tags);
    }

    public static function timing(string $metric, float $time, array $tags = []): void
    {
        $statsd = new \Domnikl\Statsd\Client(
            new \Domnikl\Statsd\Connection\UdpSocket('localhost', 8125)
        );

        $statsd->timing($metric, $time * 1000, 1.0, $tags);
    }
}

// Usage
Metrics::increment('api.requests', ['endpoint' => '/users']);
Metrics::timing('api.response_time', $duration, ['endpoint' => '/users']);

Dashboards

Metrics to Dashboard

Create dashboards for:

System Health:

  • CPU usage
  • Memory usage
  • Disk usage
  • Network I/O

Application:

  • Request rate
  • Error rate
  • Response times (p50, p95, p99)
  • Active users

Database:

  • Query time
  • Connection pool
  • Slow queries

Queue:

  • Queue depth
  • Processing rate
  • Failed jobs
  • Average wait time

Business:

  • Registrations
  • Orders
  • Revenue
  • Active subscriptions

Tools

Open Source

  • Prometheus - Metrics collection
  • Grafana - Dashboards
  • ELK Stack - Logging (Elasticsearch, Logstash, Kibana)
  • Jaeger - Distributed tracing

SaaS

  • Datadog - Full monitoring
  • New Relic - APM
  • Sentry - Error tracking
  • Loggly - Log management
  • PagerDuty - Alerting

Best Practices

1. Monitor What Matters

// ✅ Good - business-critical metrics
Metrics::increment('orders.completed');
Metrics::increment('payments.failed');

// ❌ Bad - noisy metrics
Metrics::increment('function.called');

2. Set Meaningful Thresholds

// ✅ Good - actionable alerts
if ($errorRate > 0.05) alert();  // 5% errors

// ❌ Bad - alert fatigue
if ($errorRate > 0) alert();  // Too sensitive

3. Include Context

// ✅ Good - rich context
logger()->error('Payment failed', [
    'order_id' => $order->id,
    'user_id' => $user->id,
    'amount' => $amount,
    'gateway' => 'stripe',
    'error_code' => $error->code,
]);

// ❌ Bad - minimal context
logger()->error('Payment failed');

4. Use Sampling for High Volume

// Sample 10% of requests
if (rand(1, 100) <= 10) {
    Metrics::timing('api.response_time', $duration);
}

Troubleshooting

High response times?

  • Check database query performance
  • Review slow log
  • Check cache hit rate

High error rate?

  • Check error logs
  • Review recent deployments
  • Check external service status

Queue backing up?

  • Scale workers
  • Check for failed jobs
  • Review job processing time

Memory issues?

  • Check for memory leaks
  • Review large data processing
  • Optimize queries

Next Steps