Monitoring and Observability
Baander provides several built-in endpoints and commands for monitoring system health, background jobs, and performance metrics. This page covers what is available and how to use it.
Health Checks
Baander exposes three HTTP endpoints for health probing. None of these require authentication.
| Endpoint | Purpose | Healthy status | Unhealthy status |
|---|---|---|---|
GET /health |
Full system health (PostgreSQL, Redis, Swoole, memory) | 200 with "status": "healthy" |
503 with "status": "unhealthy" |
GET /ready |
Dependency readiness (PostgreSQL, Redis, memory) | 200 with "status": "ready" |
503 with "status": "not_ready" |
GET /live |
Process liveness (Swoole worker alive) | 200 with "status": "alive" |
N/A (always 200 if responding) |
These are designed for orchestration use. The /health endpoint is used by the Docker healthcheck defined in docker-compose.yml:
healthcheck:
test: ["CMD", "php", "bin/console", "app:health:check"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
CLI health check
You can also run the health check from the command line inside the container. It prints a table with per-component status and latency:
make exec cmd="php bin/console app:health:check"
This runs the same checks as the /health endpoint and exits with code 0 on success or 1 if any component is unhealthy. See the full command reference.
What is checked
The health check verifies four components:
| Component | What it checks |
|---|---|
| PostgreSQL | Connection to the database via SELECT 1 |
| Redis | PING command and database size |
| Swoole | Whether the Swoole extension is loaded and the worker process is running |
| Memory | Current usage, peak usage, and real memory allocation |
Job Monitoring
Background jobs (library scans, transcoding, notifications) are tracked by a job monitoring system. All job endpoints require authentication with ROLE_ADMIN.
Status overview
Get a quick summary of all jobs grouped by status, plus a list of currently running jobs:
curl -s -H "Authorization: Bearer $TOKEN" https://baander.test/api/monitor/status | jq .
The response includes counts (jobs per status) and running (active jobs with progress).
Job list with filtering
Browse jobs with filtering, sorting, and cursor-based pagination:
# Failed jobs, sorted by creation date
curl -s -H "Authorization: Bearer $TOKEN" \
"https://baander.test/api/monitor/jobs?status=failed&sort=createdAt&direction=desc&limit=20" | jq .
# Scan jobs
curl -s -H "Authorization: Bearer $TOKEN" \
"https://baander.test/api/monitor/jobs?name=scan" | jq .
Available filters: status (queued, running, finished, failed, cancelled), name (partial match), queue (exact match).
Job detail
Get the full record for a specific job, including exception details for failures:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://baander.test/api/monitor/jobs/<jobId>" | jq .
Retry and cancel
You can retry failed jobs or cancel running/queued jobs through the API:
# Retry a failed job
curl -X POST -s -H "Authorization: Bearer $TOKEN" \
"https://baander.test/api/monitor/jobs/<jobId>/retry" | jq .
# Cancel a running or queued job
curl -X POST -s -H "Authorization: Bearer $TOKEN" \
"https://baander.test/api/monitor/jobs/<jobId>/cancel" | jq .
Cancellation is cooperative -- the job handler must check for the cancellation flag at its next checkpoint. Queued jobs are flagged before the worker picks them up.
Job Analytics
The analytics endpoints provide aggregate insights into job performance over a time range. All require ROLE_ADMIN.
Summary
Status counts, job type breakdown, success rate, and throughput:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://baander.test/api/monitor/analytics/summary?from=2026-04-20T00:00:00Z&to=2026-04-21T00:00:00Z" | jq .
Defaults to the last 24 hours if from and to are omitted. Maximum range is 90 days.
Timing
Average, median, and P95 execution times per job type, plus average queue latency:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://baander.test/api/monitor/analytics/timing" | jq .
Failures
Top failing job types, top exception classes, retry frequency, and recent failure details:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://baander.test/api/monitor/analytics/failures?limit=20" | jq .
| Endpoint | Description |
|---|---|
GET /api/monitor/analytics/summary |
Status counts, success rate, throughput per hour |
GET /api/monitor/analytics/timing |
Execution time stats and queue latency per job type |
GET /api/monitor/analytics/failures |
Top failures, exception classes, retry stats |
Server Stats
The server stats endpoint returns a snapshot of the current worker process state. Requires ROLE_ADMIN.
curl -s -H "Authorization: Bearer $TOKEN" https://baander.test/api/debug/stats | jq .
The response includes:
| Section | Contents |
|---|---|
memory |
Current usage, peak, real usage, real peak (all in MB) |
process |
PID, UID, GID, user, uptime |
swoole |
Swoole VM status (coroutine count, connections, request count) |
pools |
Swoole connection pool stats (active, free, limit per connection) |
doctrine |
Identity map size, scheduled inserts/updates/deletes, EM open status |
redis |
Connected status, ping result, DB size, client count, memory usage |
sse |
Active SSE connection count |
Use this for lightweight debugging of memory leaks, connection exhaustion, or hangs without setting up external monitoring.
Prometheus Metrics
Baander exposes a Prometheus-compatible metrics endpoint at GET /metrics. No authentication is required.
curl -s https://baander.test/metrics
The endpoint returns metrics in Prometheus text exposition format (text/plain; version=0.0.4). Available metrics:
| Metric | Type | Description |
|---|---|---|
swoole_up |
gauge | Whether the Swoole worker is running (1 = up) |
swoole_coroutine_num |
gauge | Current number of active coroutines |
swoole_request_count |
gauge | Total request count |
swoole_connection_num |
gauge | Current connection count |
swoole_worker_num |
gauge | Number of worker processes |
swoole_pool_active{connection="N"} |
gauge | Active connections in pool N |
swoole_pool_free{connection="N"} |
gauge | Free connections in pool N |
swoole_pool_limit{connection="N"} |
gauge | Connection limit for pool N |
Prometheus scrape configuration
Point your Prometheus scrape config to the /metrics endpoint:
scrape_configs:
- job_name: 'baander'
scrape_interval: 15s
static_configs:
- targets: ['baander-app:9501']
metrics_path: '/metrics'
Grafana dashboard setup is out of scope for this guide.
Rate Limiter Monitor
Inspect and manage configured rate limiters. Requires ROLE_ADMIN.
List all rate limiters
curl -s -H "Authorization: Bearer $TOKEN" https://baander.test/api/monitor/rate-limiters | jq .
This returns the full catalog of configured rate limiters with their policy, limit, interval, and description. It reads from a static catalog that must be kept in sync with config/packages/framework.yaml.
Clear rate limiter state
To unblock rate-limited users (for example, after a configuration change or during an incident), clear the shared rate limiter cache pool:
curl -X DELETE -s -H "Authorization: Bearer $TOKEN" \
"https://baander.test/api/monitor/rate-limiters/auth_login_ip/clear?confirm=true" | jq .
Since all rate limiters share the same Redis-backed cache pool, this clears state for every limiter, not just the named one.
Configuration Check
Validate your application configuration from the CLI or the API.
CLI
make exec cmd="php bin/console app:config:validate"
This runs all configuration checks and prints a table of results. See the full command reference.
API
curl -s -H "Authorization: Bearer $TOKEN" https://baander.test/api/debug/config-check | jq .
The endpoint validates environment variables, key file existence, secret strength, and framework config. It returns a summary with errors, warnings, and passed counts. Rate-limited to prevent abuse.
What is checked
| Check | Severity when failing |
|---|---|
Required env vars (DATABASE_URL, REDIS_URL, APP_SECRET, APP_URL, APP_DOMAIN) |
Error |
Production-only vars (REDIS_PASSWORD, OAUTH_ENCRYPTION_KEY) |
Error (prod only) |
DATABASE_URL and REDIS_URL format |
Error |
APP_URL uses HTTPS |
Error (prod only) |
APP_SECRET is not the default placeholder |
Error (prod only) |
| OAuth key file existence | Warning (dev) / Error (prod) |
| OAuth encryption key validity | Error (prod only) |
| VAPID key pair consistency | Warning |
| External API key lengths | Warning |
Logs
Container names
| Container | Service |
|---|---|
baander-app |
PHP/Symfony application (Swoole) |
baander-nginx |
Nginx reverse proxy |
baander-postgres |
PostgreSQL database |
baander-redis |
Redis (cache, messenger, rate limiting) |
Makefile commands
The Makefile provides several targets for viewing logs:
| Command | Description |
|---|---|
make logs |
Follow app container logs (docker logs -f baander-app) |
make logs-nginx |
Follow nginx container logs |
make hl-logs |
Show last 1 hour of app logs, syntax-highlighted |
make hl-logs-f |
Follow app logs, syntax-highlighted |
make hl-logs-nginx |
Show last 1 hour of nginx logs, syntax-highlighted |
make hl-logs-nginx-f |
Follow nginx logs, syntax-highlighted |
make hl-logs-all |
Show last 1 hour of all container logs, syntax-highlighted |
make hl-logs-all-f |
Follow all container logs, syntax-highlighted |
make hl-logs-error |
Show error-level app logs only |
make hl-logs-warn |
Show warnings and above from app logs |
The hl-* commands pipe through ./hl for colorized output. If hl is not installed, use the plain docker logs commands directly.
Direct Docker commands
# Follow app logs
docker logs -f baander-app
# Last 100 lines of postgres logs
docker logs --tail 100 baander-postgres
# Redis logs since a specific time
docker logs --since 2026-04-21T10:00:00 baander-redis
# All containers at once
docker compose logs -f
Log locations inside containers
| Container | Log path |
|---|---|
baander-app |
stdout/stderr (captured by Docker), Swoole access logs to stdout |
baander-nginx |
stdout/stderr (captured by Docker), access log at /var/log/nginx/access.log |
baander-postgres |
stdout/stderr (captured by Docker), also at /var/lib/postgresql/data/log/ |
baander-redis |
stdout/stderr (captured by Docker) |
To view nginx access logs inside the container:
make exec cmd="tail -100 /var/log/nginx/access.log"
Quick Reference
| Command / Endpoint | Description |
|---|---|
GET /health |
System health check (no auth) |
GET /ready |
Readiness probe (no auth) |
GET /live |
Liveness probe (no auth) |
GET /metrics |
Prometheus metrics (no auth) |
GET /api/debug/stats |
Server diagnostics (admin) |
GET /api/debug/config-check |
Configuration validation (admin) |
GET /api/monitor/status |
Job status summary (admin) |
GET /api/monitor/jobs |
Job list with filtering (admin) |
GET /api/monitor/jobs/{jobId} |
Job detail (admin) |
POST /api/monitor/jobs/{jobId}/retry |
Retry a failed job (admin) |
POST /api/monitor/jobs/{jobId}/cancel |
Cancel a job (admin) |
GET /api/monitor/analytics/summary |
Job analytics summary (admin) |
GET /api/monitor/analytics/timing |
Job timing analytics (admin) |
GET /api/monitor/analytics/failures |
Job failure analytics (admin) |
GET /api/monitor/rate-limiters |
Rate limiter catalog (admin) |
DELETE /api/monitor/rate-limiters/{name}/clear?confirm=true |
Clear rate limiter state (admin) |
make exec cmd="php bin/console app:health:check" |
CLI health check |
make exec cmd="php bin/console app:config:validate" |
CLI configuration validation |
make logs |
Follow app container logs |
make hl-logs |
Highlighted app logs (last hour) |
make info |
PHP, Symfony, and Composer version info |