junixlabs / laravel-observatory
Observability toolkit for Laravel applications - Monitor HTTP requests, outbound calls, queue jobs, and scheduled tasks with Prometheus and SidMonitor support
Requires
- php: ^8.0
- illuminate/console: ^9.0|^10.0|^11.0|^12.0
- illuminate/contracts: ^9.0|^10.0|^11.0|^12.0
- illuminate/http: ^9.0|^10.0|^11.0|^12.0
- illuminate/log: ^9.0|^10.0|^11.0|^12.0
- illuminate/queue: ^9.0|^10.0|^11.0|^12.0
- illuminate/routing: ^9.0|^10.0|^11.0|^12.0
- illuminate/support: ^9.0|^10.0|^11.0|^12.0
- promphp/prometheus_client_php: ^2.6
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpstan/phpstan: ^1.10|^2.0
- phpunit/phpunit: ^10.0|^11.0
- promphp/prometheus_client_php: ^2.6
README
A comprehensive observability toolkit for Laravel applications. Monitor HTTP requests, outbound API calls, queue jobs, scheduled tasks, and exceptions with structured logging and optional Prometheus or SidMonitor metrics.
Features
- Inbound Request Logging - Automatically log all incoming HTTP requests
- Outbound HTTP Logging - Log external API calls with service detection
- Queue Job Logging - Track job execution with duration and memory usage
- Scheduled Task Logging - Monitor artisan scheduled tasks lifecycle (start, finish, fail, skip)
- Exception Logging - Structured exception logging with stack traces
- Request ID Tracking - Correlation IDs for distributed tracing
- Sensitive Data Masking - Automatic masking of passwords, tokens, and PII
- Dual Exporter Support - Choose between Prometheus (pull) or SidMonitor (push) metrics
- Circuit Breaker - Resilient push-based exporting with automatic backoff
- Zero Configuration - Works out of the box with sensible defaults
- Grafana Dashboards - Pre-built dashboard templates included
Requirements
- PHP 8.0+
- Laravel 9.0, 10.0, 11.0, or 12.0
Installation
composer require junixlabs/laravel-observatory
The package auto-registers and works immediately - no configuration needed!
Quick Start
After installation, Observatory automatically:
- Logs all incoming HTTP requests to
storage/logs/observatory.log - Logs outbound HTTP calls via Laravel's HTTP client
- Logs queue job execution
- Logs scheduled task execution
- Logs exceptions with context
View Your Logs
tail -f storage/logs/observatory.log | jq
Configuration
Publish Config (Optional)
php artisan vendor:publish --tag=observatory-config
Environment Variables
All features are enabled by default. Only set these if you need to change defaults:
# Disable Observatory entirely OBSERVATORY_ENABLED=false # Change log channel (default: 'observatory' -> storage/logs/observatory.log) # Use 'stderr' for Docker/K8s OBSERVATORY_LOG_CHANNEL=stderr # Disable specific loggers OBSERVATORY_INBOUND_ENABLED=false OBSERVATORY_OUTBOUND_ENABLED=false OBSERVATORY_JOBS_ENABLED=false OBSERVATORY_SCHEDULED_TASKS_ENABLED=false OBSERVATORY_EXCEPTIONS_ENABLED=false # Log request/response bodies (disabled by default - can be large) OBSERVATORY_LOG_BODY=true # Only log slow requests (0 = log all) OBSERVATORY_SLOW_THRESHOLD_MS=1000
Exporters
Observatory supports two metrics exporters. Set via OBSERVATORY_EXPORTER env var.
Prometheus (default, pull-based)
OBSERVATORY_EXPORTER=prometheus OBSERVATORY_PROMETHEUS_ENABLED=true OBSERVATORY_PROMETHEUS_STORAGE=apcu # or 'redis', 'memory'
Exposes a /metrics endpoint scraped by Prometheus.
SidMonitor (push-based)
OBSERVATORY_EXPORTER=sidmonitor SIDMONITOR_ENDPOINT=https://api.sidmonitor.com SIDMONITOR_API_KEY=your-api-key
Buffers data in-memory and flushes in batches to the SidMonitor backend. Includes a circuit breaker that pauses sending after consecutive failures to avoid blocking your application.
# Batch settings SIDMONITOR_BATCH_SIZE=100 SIDMONITOR_BATCH_INTERVAL=10 # Circuit breaker SIDMONITOR_CIRCUIT_BREAKER_THRESHOLD=3 # failures before opening SIDMONITOR_CIRCUIT_BREAKER_COOLDOWN=30 # seconds before retry
Log Channel
Observatory auto-registers the observatory log channel:
- File:
storage/logs/observatory.log - Format: JSON (Loki/ELK compatible)
- Rotation: Daily, 14 days retention
For Docker/Kubernetes, use stderr:
OBSERVATORY_LOG_CHANNEL=stderr
Custom Headers
Extract custom headers into logs (multi-tenant, workspace, etc.):
// config/observatory.php 'inbound' => [ 'custom_headers' => [ 'X-Workspace-Id' => 'workspace_id', 'X-Tenant-Id' => 'tenant_id', 'X-Correlation-Id' => 'correlation_id', ], ],
Result in logs:
{
"request_id": "abc-123",
"method": "POST",
"path": "/api/users",
"workspace_id": "ws-456",
"tenant_id": "tenant-789"
}
Service Detection
Identify external services in outbound logs:
// config/observatory.php 'outbound' => [ 'services' => [ '*.stripe.com' => 'stripe', '*.amazonaws.com' => 'aws', '*.sendgrid.com' => 'sendgrid', 'api.myservice.com' => 'my_service', ], ],
Excluding Paths/Jobs/Tasks
// config/observatory.php 'inbound' => [ 'exclude_paths' => [ 'telescope*', 'horizon*', 'health', 'metrics', ], ], 'jobs' => [ 'exclude_jobs' => [ 'App\Jobs\InternalHealthCheck', ], ], 'scheduled_tasks' => [ 'exclude_commands' => [ 'schedule:run', ], ],
Structured Log Examples
Inbound Request
{
"message": "HTTP_REQUEST",
"context": {
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "inbound",
"method": "POST",
"url": "https://example.com/api/v1/orders",
"path": "api/v1/orders",
"route": "orders.store",
"status_code": 201,
"duration_ms": 145.23,
"ip": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"user_id": 123,
"memory_mb": 45.2,
"environment": "production"
}
}
Outbound Request
{
"message": "HTTP_OUTBOUND",
"context": {
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "outbound",
"service": "stripe",
"method": "POST",
"url": "https://api.stripe.com/v1/charges",
"host": "api.stripe.com",
"status_code": 200,
"duration_ms": 523.45,
"environment": "production"
}
}
Job Processed
{
"message": "JOB_PROCESSED",
"context": {
"job_id": "123",
"job_name": "App\\Jobs\\ProcessOrder",
"queue": "orders",
"status": "processed",
"duration_ms": 1234.56,
"attempts": 1,
"memory": {
"used_mb": 12.5,
"peak_mb": 45.2
},
"environment": "production"
}
}
Scheduled Task
{
"message": "SCHEDULED_TASK",
"context": {
"command": "reports:generate",
"description": "Generate daily reports",
"expression": "0 2 * * *",
"status": "completed",
"duration_ms": 4523.12,
"exit_code": 0,
"memory": {
"used_mb": 32.1,
"peak_mb": 64.5
},
"environment": "production"
}
}
Exception
{
"message": "EXCEPTION",
"context": {
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"exception_class": "App\\Exceptions\\PaymentException",
"message": "Payment declined",
"code": 402,
"file": "/app/Services/PaymentService.php",
"line": 145,
"request": {
"method": "POST",
"url": "https://example.com/api/orders",
"path": "api/orders"
},
"user": {
"id": 123
},
"trace": ["..."],
"environment": "production"
}
}
Prometheus Metrics (Optional)
OBSERVATORY_PROMETHEUS_ENABLED=true OBSERVATORY_PROMETHEUS_STORAGE=apcu # or 'redis', 'memory'
Visit http://your-app.test/metrics to see metrics.
Available Metrics
| Metric | Type | Description |
|---|---|---|
{app}_http_requests_total |
Counter | Total HTTP requests |
{app}_http_request_duration_seconds |
Histogram | Request latency |
{app}_http_outbound_requests_total |
Counter | Outbound HTTP requests |
{app}_jobs_processed_total |
Counter | Queue jobs processed |
{app}_scheduled_tasks_total |
Counter | Scheduled tasks executed |
{app}_exceptions_total |
Counter | Exceptions count |
Prometheus Auth
OBSERVATORY_METRICS_AUTH=true OBSERVATORY_METRICS_USER=prometheus OBSERVATORY_METRICS_PASS=secret
Grafana Dashboards
Import pre-built dashboards from the dashboards/ directory:
| Dashboard | Data Source | Description |
|---|---|---|
observatory-dashboard.json |
Loki | Request logs, user analytics, exceptions |
prometheus-dashboard.json |
Prometheus | Metrics overview, latency percentiles |
LogQL Query Examples
# All requests
{job="laravel-observatory"} | json | message="HTTP_REQUEST"
# Errors only
{job="laravel-observatory"} | json | status_code >= 400
# By user
{job="laravel-observatory"} | json | user_id="123"
# Slow requests (>1s)
{job="laravel-observatory"} | json | duration_ms > 1000
# External service calls
{job="laravel-observatory"} | json | type="outbound" | service="stripe"
# Scheduled tasks
{job="laravel-observatory"} | json | message="SCHEDULED_TASK"
# Exceptions
{job="laravel-observatory"} | json | message="EXCEPTION"
Kubernetes Deployment
See k8s/README.md for Kubernetes deployment with Loki stack.
Upgrading from v1.x
Breaking Changes in v2.0
- SidMonitor env vars renamed:
OBSERVATORY_SIDMONITOR_*is nowSIDMONITOR_* - ExporterInterface: New
recordScheduledTask()method required for custom exporters
Update your .env file if using SidMonitor:
# Before (v1.x) OBSERVATORY_SIDMONITOR_ENDPOINT=... OBSERVATORY_SIDMONITOR_API_KEY=... # After (v2.0) SIDMONITOR_ENDPOINT=... SIDMONITOR_API_KEY=...
Re-publish config if upgrading:
php artisan vendor:publish --tag=observatory-config --force
Testing
composer test
Changelog
See CHANGELOG for recent changes.
Contributing
See CONTRIBUTING for details.
Security
Report security issues to chuongld@canawan.com instead of the issue tracker.
Credits
License
MIT License. See LICENSE for details.