Security & Performance

Monitoring and Observability

18 min Lesson 28 of 35

Monitoring and Observability

Monitoring and observability help you understand system behavior, detect issues early, and maintain application health in production.

Application Monitoring Basics

Key Monitoring Concepts:
  • Metrics: Numerical measurements over time (CPU, memory, requests/sec)
  • Logs: Event records with timestamps and context
  • Traces: Request journey through distributed systems
  • Alerts: Notifications when metrics exceed thresholds
  • Dashboards: Visual representation of system health

Prometheus & Grafana Setup

Prometheus collects metrics, Grafana visualizes them:

# docker-compose.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'

grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana

node_exporter:
image: prom/node-exporter:latest
ports:
- "9100:9100"

volumes:
prometheus_data:
grafana_data:

Prometheus Configuration

Define scrape targets and intervals:

# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s

scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']

- job_name: 'node_exporter'
static_configs:
- targets: ['node_exporter:9100']

- job_name: 'laravel_app'
static_configs:
- targets: ['app:9091']
metrics_path: '/metrics'

Exposing Metrics in Laravel

Create a metrics endpoint:

// routes/web.php
Route::get('/metrics', [MetricsController::class, 'index'])
->middleware('metrics.auth'); // Secure with IP whitelist

// app/Http/Controllers/MetricsController.php
use Illuminate\Support\Facades\DB;

class MetricsController extends Controller
{
public function index()
{
$metrics = [];

// HTTP request count
$metrics[] = '# HELP http_requests_total Total HTTP requests';
$metrics[] = '# TYPE http_requests_total counter';
$metrics[] = 'http_requests_total ' . Cache::get('http_requests', 0);

// Database connections
$metrics[] = '# HELP db_connections Active database connections';
$metrics[] = '# TYPE db_connections gauge';
$metrics[] = 'db_connections ' . DB::connection()->select('SHOW STATUS LIKE "Threads_connected"')[0]->Value;

// Memory usage
$metrics[] = '# HELP memory_usage_bytes Memory usage in bytes';
$metrics[] = '# TYPE memory_usage_bytes gauge';
$metrics[] = 'memory_usage_bytes ' . memory_get_usage();

return response(implode("\n", $metrics), 200)
->header('Content-Type', 'text/plain');
}
}

Error Tracking with Sentry

Sentry captures and organizes errors:

# Install Sentry SDK
composer require sentry/sentry-laravel

# Publish config
php artisan sentry:publish --dsn=your-dsn-here

# .env
SENTRY_LARAVEL_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
SENTRY_TRACES_SAMPLE_RATE=1.0 # 100% of transactions
SENTRY_ENVIRONMENT=production

Configure error handling:

// app/Exceptions/Handler.php
use Sentry\Laravel\Integration;

public function register()
{
$this->reportable(function (Throwable $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
});
}

// Manually capture errors
try {
$this->processPayment();
} catch (\Exception $e) {
\Sentry\captureException($e);
\Sentry\captureMessage('Payment processing failed', [
'user_id' => auth()->id(),
'amount' => $amount,
]);
}

Uptime Monitoring

Monitor application availability:

// Using Laravel Pulse (built-in monitoring)
composer require laravel/pulse
php artisan pulse:install
php artisan migrate

// config/pulse.php
return [
'recorders' => [
Recorders\Servers::class => [
'server_name' => env('PULSE_SERVER_NAME', gethostname()),
],
Recorders\Requests::class => [
'enabled' => env('PULSE_REQUESTS_ENABLED', true),
'sample_rate' => env('PULSE_REQUESTS_SAMPLE_RATE', 1),
],
Recorders\SlowQueries::class => [
'enabled' => env('PULSE_SLOW_QUERIES_ENABLED', true),
'threshold' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000),
],
],
];

// Access dashboard at /pulse
Route::get('/pulse', [PulseController::class, 'index']);

Performance Dashboards

Create Grafana dashboard panels:

// Grafana PromQL queries examples

// Request rate (requests per second)
rate(http_requests_total[5m])

// Error rate percentage
(rate(http_requests_total{status=~"5.."}[5m]) /
rate(http_requests_total[5m])) * 100

// Average response time
rate(http_request_duration_seconds_sum[5m]) /
rate(http_request_duration_seconds_count[5m])

// Database connection pool usage
(db_connections / db_max_connections) * 100

// Memory usage percentage
(memory_usage_bytes / memory_limit_bytes) * 100

Alerting Strategies

Configure alerts in Prometheus:

# alert.rules.yml
groups:
- name: application_alerts
interval: 30s
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value }} errors/sec"

- alert: HighResponseTime
expr: http_request_duration_seconds > 2
for: 10m
labels:
severity: warning
annotations:
summary: "High response time"
description: "Response time is {{ $value }}s"

- alert: DatabaseConnectionsHigh
expr: (db_connections / db_max_connections) > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "Database connection pool 80% full"

Log Aggregation

Centralized logging with ELK stack:

# filebeat.yml (ship logs to Elasticsearch)
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/www/storage/logs/*.log
json.keys_under_root: true
json.add_error_key: true

output.elasticsearch:
hosts: ["localhost:9200"]
index: "laravel-%{+yyyy.MM.dd}"

# Laravel log format for structured logging
// config/logging.php
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'sentry'],
'formatter' => \Monolog\Formatter\JsonFormatter::class,
],
Best Practice: Monitor the Four Golden Signals: Latency, Traffic, Errors, and Saturation. Set up alerts for actionable issues only to avoid alert fatigue.
Exercise: Set up basic monitoring for your Laravel app. Install Laravel Pulse or create a custom metrics endpoint. Configure one critical alert (e.g., error rate threshold). Test the alerting by triggering the condition.