Belajar FrankenPHP - Logging, Observability & Metrics
Episode 12 of 25

Belajar FrankenPHP - Logging, Observability & Metrics

Membangun observability di FrankenPHP: access log Caddy, structured logging dari PHP dengan frankenphp_log(), log JSON yang siap di-ingest ke Datadog/Grafana Loki, serta metrik Prometheus untuk memantau thread, worker, dan kedalaman antrean request.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah episode 11 mengurus konfigurasi, kini tiba saatnya memastikan aplikasi bisa diamati ketika berjalan. Produksi tanpa observability seperti terbang tanpa instrumen: kalian tahu ada masalah hanya dari keluhan pengguna. Episode ini membangun tiga pilar observability di FrankenPHP — log, structured logging, dan metrics — dengan integrasi yang sudah disediakan langsung oleh Caddy dan FrankenPHP.

Mengapa penting? Mode worker mengubah perilaku log PHP: error_log() klasik tidak lagi muncul per-request di file yang sama. FrankenPHP merutekan log PHP ke logger Caddy, dan menyediakan frankenphp_log() untuk log terstruktur — kalian perlu memahami keduanya agar log tidak hilang atau sulit ditelusuri.

Access Log Caddy

Directive log mengaktifkan access log. Format default sudah cukup informatif — mengikuti Common Log Format Caddy:

Caddyfile - access log
localhost {
    log
 
    root * public/
    php_server
}

Untuk kontrol format, tentukan output dan format via blok log di global options:

Caddyfile - log dengan format
{
    log {
        output file /var/log/frankenphp/access.log {
            roll_size 100MiB
            roll_keep 10
        }
        format json
    }
}
 
localhost {
    root * public/
    php_server
}
  • output file ... — tulis ke file dengan rotasi (roll_size, roll_keep).
  • format json — setiap request menjadi satu baris JSON, siap di-ingest oleh aggregator (Loki, ELK, Datadog).

Tip

Default Caddy menulis log ke stderr dalam format JSON ketika stdout bukan TTY (misal di Docker). Untuk log yang "enak dibaca" di terminal development, jalankan container dengan --tty.

Structured Logging dari PHP: frankenphp_log()

Fungsi frankenphp_log() adalah jalan terbaik untuk log aplikasi: mendukung level dan context, dan menghasilkan JSON terstruktur karena dibangun di atas log/slog milik Go.

frankenphp_log() dengan context
<?php
frankenphp_log("Request selesai diproses");
 
frankenphp_log(
    "Memory usage tinggi",
    FRANKENPHP_LOG_LEVEL_WARN,
    [
        'current_usage' => memory_get_usage(),
        'peak_usage'    => memory_get_peak_usage(),
    ],
);

Level yang tersedia: FRANKENPHP_LOG_LEVEL_DEBUG (-4), INFO (0), WARN (4), ERROR (8). Output di sisi server berupa satu baris JSON:

Contoh output log JSON
{"level":"info","ts":1770000000,"logger":"frankenphp","msg":"Request selesai diproses"}
{"level":"warn","ts":1770000000,"logger":"frankenphp","msg":"Memory usage tinggi","current_usage":10485760,"peak_usage":12582912}

Karena formatnya JSON, log bisa langsung difilter berdasarkan level dan di-query berdasarkan field context di Grafana Loki atau Datadog.

error_log() yang Kompatibel

Untuk kode lama atau library yang memakai error_log(), FrankenPHP merutekan pesan ke logger Caddy saat $message_type = 4 (SAPI):

error_log dengan message_type 4
<?php
error_log("Koneksi database gagal", 4);

Pesan ini muncul di log Caddy sebagai teks tidak terstruktur — berguna untuk kompatibilitas, tetapi untuk produksi lebih baik pindah ke frankenphp_log() yang bisa difilter per level dan punya context.

Metrics Prometheus

FrankenPHP mengekspos metrik Prometheus-compatible begitu Caddy metrics diaktifkan. Aktifkan lewat global options:

Caddyfile - aktifkan metrics
{
    servers {
        metrics
    }
}

Lalu scrape dari admin endpoint:

Curl metrik FrankenPHP
curl -s http://localhost:2019/metrics | grep frankenphp

Metrik yang tersedia untuk thread dan worker:

MetrikArti
frankenphp_total_threadsJumlah total thread PHP
frankenphp_busy_threadsThread yang sedang memproses request
frankenphp_queue_depthAntrean request yang menunggu thread
frankenphp_total_workers{worker="..."}Total worker untuk sebuah worker script
frankenphp_busy_workers{worker="..."}Worker yang sedang sibuk
frankenphp_worker_request_timeWaktu pemrosesan request per worker
frankenphp_worker_request_countJumlah request diproses per worker
frankenphp_worker_crashesBerapa kali worker mati tidak wajar
frankenphp_worker_restartsBerapa kali worker di-restart sengaja
frankenphp_worker_queue_depthAntrean request per worker

Metrik paling penting untuk dipantau: frankenphp_queue_depth (indikasi thread menipis) dan frankenphp_worker_crashes (worker tidak stabil).

Integrasi Grafana / Prometheus

Contoh job scrape di prometheus.yml:

prometheus.yml - scrape FrankenPHP
scrape_configs:
  - job_name: frankenphp
    metrics_path: /metrics
    static_configs:
      - targets:
          - frankenphp-server:2019

Catatan: endpoint metrics berada di admin port Caddy (default 2019) — jangan expose port ini ke publik. Biarkan hanya bisa diakses dari jaringan monitoring.

Ember: Dashboard Real-Time

Untuk yang ingin dashboard tanpa menyusun Prometheus + Grafana sendiri, Ember adalah tool yang dirancang khusus untuk FrankenPHP. Ia terhubung ke admin API Caddy, menampilkan TUI real-time di development, dan bisa dijalankan sebagai daemon dengan mode --expose untuk mengekspor metrik gabungan (RPS, persentil latensi, error rate) ke Prometheus.

Jalankan Ember
ember

Ember sangat membantu memvisualisasikan health worker saat melakukan load test (episode 17).

Note

Metrik, log, dan Ember adalah bagian dari satu cerita observability: gunakan log untuk mengapa, metrik untuk kapan dan seberapa, dan Ember untuk memandang semuanya sekilas. Kita gabungkan ketiganya pada uji beban di episode 17.

Common Pitfalls

  • Port admin 2019 terexpose: siapa pun bisa mengakses config API dan restart worker. Batasi di firewall/network policy.
  • Log level terlalu rendah di produksi: debug membanjiri disk dan memperlambat. Gunakan info/warn untuk produksi.
  • frankenphp_log() tidak muncul: pastikan logging Caddy aktif (directive log atau default stderr).
  • Metrik kosong: metrics aktif hanya jika servers { metrics } dideklarasikan — ini bukan default.

Penutup

Pada episode 12 ini, kalian telah membangun observability untuk FrankenPHP.

Inti yang harus dibawa pulang:

  • Access log via directive log; format json siap untuk aggregator log.
  • frankenphp_log() untuk structured logging dari PHP dengan level + context.
  • error_log($msg, 4) tetap didukung untuk kompatibilitas.
  • Aktifkan servers { metrics } lalu scrape :2019/metrics dengan Prometheus.
  • Pantau queue_depth dan worker_crashes; jaga port admin tetap privat.

Di episode 13 selanjutnya kita membahas tantangan klasik PHP di mode worker: session, persistence & state — kenapa session file tidak cocok, cara memindahkan session ke Redis, dan batasan enable_post_data_reading. Sampai jumpa di episode 13!

Belajar FrankenPHP - Logging, Observability & Metrics | Belajar FrankenPHP