Mengamati server Swoole dari luar: membaca stats dan status worker, event loop lag metrics, strategi logging yang benar, mode debugging dengan Xdebug di OpenSwoole, serta export metrik ke Prometheus dan OpenTelemetry.

Setelah di episode 18 kita mengamankan lalu lintas dengan TLS — pada episode kali ini kita belajar mengamati. Server yang tidak terpantau adalah bom waktu: memory naik perlahan, koneksi membengkak, event loop mulai lambat — semua terjadi diam-diam sampai user yang komplain.
Mengapa episode ini penting? Karena Swoole adalah server long-running — ia bisa hidup berminggu-minggu tanpa restart, dan justru di situlah bahayanya. Kebocoran memory atau loop yang melambat menumpuk seiring waktu. Monitoring bukan pemanis: ia adalah deteksi dini sebelum server kalian kolaps di jam sibuk.
Swoole menyediakan data statistik internal langsung dari proses Master:
$server->on('Request', function ($req, $res) use ($server) {
$stats = $server->stats();
$res->header('Content-Type', 'application/json');
$res->end(json_encode($stats));
});Contoh isi stats():
| Kunci | Arti |
|---|---|
start_time | Waktu server mulai |
connection_num | Koneksi aktif saat ini |
accept_count | Total koneksi diterima |
request_count | Total request diproses |
tasking_num | Task sedang antre/diproses |
worker_request_count | Request per worker |
Perhatikan perbedaan connection_num (koneksi yang sedang hidup) vs accept_count (total sejak start) — keduanya menandakan gejala berbeda saat naik.
Tip
Lapisi stats() dengan endpoint /stats (dilindungi auth/network internal saja) — jangan pernah expose mentah ke internet. Ini sekaligus menjadi fondasi endpoint /metrics Prometheus di bagian akhir episode ini.
Memantau kesehatan worker — bukan hanya jumlahnya:
$workerStatus = $server->getWorkerStatus(); // array status per worker
foreach ($workerStatus as $id => $status) {
echo "Worker $id: $status\n";
}Event loop metrics adalah pengukur paling jujur: berapa lama event loop macet. OpenSwoole 26.2 memperkenalkan event loop lag metrics — angka yang menunjukkan delay aktual siklus event loop. Bila lag melewati ratusan milidetik, ada pekerjaan blocking di worker. Di Swoole klasik, pendekatan manual: ukur selisih waktu antara tick timer:
use Swoole\Timer;
Timer::tick(1000, function () {
static $last = 0;
$now = microtime(true);
if ($last > 0) {
$drift = $now - $last - 1.0; // harusnya ~0
if ($drift > 0.2) {
error_log("Event loop lambat: +{$drift}s");
}
}
$last = $now;
});Drift mendadak > 0.2s menandakan ada CPU-bound work di worker — saatnya pindah ke task worker (episode 11).
Logging di Swoole punya satu aturan emas: jangan blokir. echo/fwrite ke stderr bisa memperlambat worker saat beban tinggi. Strategi berlapis:
$server->on('Request', function ($req, $res) use ($server) {
$server->task(['log' => ['ts' => time(), 'path' => $req->server['request_uri'] ?? '/']]);
$res->end('ok');
});
$server->on('Task', function ($server, $taskId, $workerId, $data) {
file_put_contents(
'/var/log/app/access.log',
json_encode($data['log']) . "\n",
FILE_APPEND
);
return true;
});Format log terstruktur (JSON) lebih baik daripada teks bebas — karena bisa diparsing log pipeline (Loki, ELK) secara otomatis. Untuk proyek besar, gunakan PSR-3 logger (monolog) dengan handler async.
Tantangan klasik debugging Swoole: kode berjalan di dalam coroutine, dan Xdebug tradisional tidak suka dengan context switching. Situasinya membaik di 2026:
var_dump ke stderr, karena error selalu dicetak ke terminal server.Pola debugging praktis yang jalan di keduanya:
$server->on('Request', function ($req, $res) {
try {
$result = $someService->process($req->get);
} catch (Throwable $e) {
error_log(json_encode([
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'coroutine' => Swoole\Coroutine::getCid(),
]));
$res->status(500);
$res->end('Error');
return;
}
$res->end($result);
});Swoole\Coroutine::getCid() memberi ID coroutine — berguna untuk mencocokkan baris log dengan coroutine tertentu saat ada ratusan request bersamaan.
Pola standar: Swoole menyediakan data, kita format menjadi metrik, lalu exporter menariknya.
$server->on('Request', function ($req, $res) use ($server) {
if (($req->server['request_uri'] ?? '/') !== '/metrics') {
$res->end('ok');
return;
}
$s = $server->stats();
$body = "# HELP swoole_connections Koneksi aktif\n";
$body .= "# TYPE swoole_connections gauge\n";
$body .= "swoole_connections {$s['connection_num']}\n";
$body .= "swoole_total_requests {$s['request_count']}\n";
$body .= "swoole_tasking {$s['tasking_num']}\n";
$res->header('Content-Type', 'text/plain; version=0.0.4');
$res->end($body);
});Lalu target Prometheus scrape_configs menunjuk ke endpoint ini.
Untuk tracing distribusi, gunakan SDK OpenTelemetry PHP:
use OpenTelemetry\API\Trace\TracerProviderInterface;
$server->on('Request', function ($req, $res) use ($tracer) {
$span = $tracer->spanBuilder('http.request')->startSpan();
try {
$res->end(handle($req));
} finally {
$span->end();
}
});Tracing memberi kalian gambaran lintas service — request dari client → Swoole → DB → service lain, satu timeline utuh. Sangat membantu saat microservices (episode 16) mulai banyak.
| Masalah | Penyebab | Solusi |
|---|---|---|
| Metrik tidak pernah berubah | Server lain yang dilirik (bukan Swoole) | Pastikan scrape ke port/endpoint yang benar |
| Log hilang saat crash | FILE_APPEND tanpa buffer flush | Gunakan logger dengan buffer + sink (Loki/ELK) |
| Drift event loop besar di jam sibuk | CPU-heavy di worker | Pindahkan ke task worker / naikkan worker_num |
| Xdebug step tidak jalan | Swoole klasik tidak mendukung | Pakai log, atau OpenSwoole 26.2 |
Pada episode 19 ini, kalian telah belajar mengamati server Swoole.
Inti yang harus dibawa pulang:
$server->stats() memberi data koneksi, request, dan task — lapisi di endpoint /stats internal./metrics ke Prometheus dan tracing via OpenTelemetry.Di episode 20 selanjutnya, kita memeras performa: Performance Tuning & Benchmark — worker_num, max_request, dispatch_mode, kernel settings, dan perbandingan Swoole vs Nginx+PHP-FPM vs Go. Sampai jumpa di episode 20!