Bayangkan kamu lagi makan malam santai, tiba-tiba HP berbunyi. Notifikasi WhatsApp dari buyer: "Mas, saya sudah bayar tapi produknya belum bisa didownload." Terus satu lagi masuk. Lalu satu lagi.
Kamu buka laptop, SSH ke server, langsung tail -f storage/logs/laravel.log — tapi file-nya sudah 1.8GB dan scrolling terlalu cepat untuk dibaca. Kamu coba grep "ERROR" tapi hasilnya ratusan baris. Masalah sudah terjadi sejak 40 menit lalu. Beberapa buyer sudah minta refund.
Kalau kamu pernah ada di posisi ini — atau tidak mau pernah ada di posisi ini — artikel ini untuk kamu.
Masalah dengan "Nanti Kalau Ada Masalah Baru Dicek"
Pendekatan reactive itu mahal. Bukan hanya soal waktu debugging, tapi juga:
- Revenue hilang selama downtime — toko tidak bisa menerima order
- Trust rusak — buyer yang sudah bayar dan tidak dapat produknya tidak akan balik
- Waktu terbuang untuk forensik log yang seharusnya bisa dicegah
Dan masalah terbesar dari debugging tanpa monitoring: kamu tidak tahu kapan masalah mulai terjadi, seberapa parah dampaknya, dan apakah sudah benar-benar selesai setelah kamu fix.
Tiga Pilar Observability
Observability adalah kemampuan untuk memahami kondisi internal sistem hanya dari output yang bisa diobservasi dari luar. Ada tiga pilar:
┌─────────────────┬──────────────────────────────────────────────────┐
│ TRACES │ Rekaman perjalanan satu request dari masuk │
│ │ sampai keluar. Tahu persis: query mana yang │
│ │ lambat, API mana yang timeout. │
├─────────────────┼──────────────────────────────────────────────────┤
│ LOGS │ Catatan event yang terjadi di aplikasi. │
│ │ "Order #ORD-001 berhasil dibuat", "Webhook │
│ │ Xendit diterima", "Payment gagal: invalid card". │
├─────────────────┼──────────────────────────────────────────────────┤
│ METRICS │ Angka agregat yang menggambarkan kondisi sistem. │
│ │ Berapa request per detik, berapa % error, │
│ │ berapa lama rata-rata checkout selesai. │
└─────────────────┴──────────────────────────────────────────────────┘
Ketiganya saling melengkapi. Metric yang anomali memberi sinyal ada masalah. Trace membantu kamu temukan di mana masalahnya. Log memberi detail apa yang terjadi.
Kenapa Datadog
Ada banyak tools monitoring. Ini perbandingan singkat untuk konteks developer Indonesia yang deploy ke VPS atau cloud:
| Tool | Kelebihan | Kekurangan |
|---|---|---|
| Datadog | All-in-one: APM + logs + metrics + alerts. Auto-instrument Laravel. Setup cepat. | Biaya bisa mahal di scale besar |
| Sentry | Excellent untuk error tracking. Free tier generous. | Fokus ke errors, bukan full observability |
| Grafana + Prometheus | Self-hosted, gratis, powerful | Perlu kelola infrastruktur monitoring sendiri |
| New Relic | Mirip Datadog, mature | UI lebih kompleks, pricing model berbeda |
| Laravel Telescope | Built-in, zero setup | Hanya untuk local/staging, tidak untuk production |
Untuk toko online yang mau setup monitoring cepat tanpa kelola infrastruktur tambahan, Datadog adalah pilihan paling pragmatis. Free tier sudah cukup untuk mulai: 1 host, retensi log 1 hari, unlimited custom metrics.
Yang Akan Kita Setup
Sampai akhir artikel ini, toko online Laravel 12 kamu akan punya:
- APM: setiap request di-trace — lihat mana yang lambat, query mana yang jadi bottleneck
- Log Management: semua Laravel log masuk ke Datadog, bisa search dan filter tanpa SSH
- Custom Metrics: counter bisnis seperti berapa order masuk per jam, berapa checkout gagal
- Alerts: notifikasi otomatis kalau error rate naik atau response time melebihi threshold
- Dashboard: satu halaman untuk pantau kesehatan toko — bisa dibuka dari HP
Kita akan bangun semua ini dengan pendekatan yang sama seperti artikel-artikel sebelumnya: tulis prompt ke AI → review hasilnya → pakai kode yang sudah divalidasi.
Di bagian selanjutnya kita mulai dari fondasi: daftar akun Datadog, install agent di server, dan install PHP extension untuk APM.
Bagian 2: Setup Datadog Account dan Agent
Setup ini terasa banyak langkahnya di awal, tapi setelah selesai kamu tidak perlu sentuh lagi. Kita kerjakan satu per satu.
Daftar Akun Datadog
Buka app.datadoghq.com → klik "Get Started Free". Trial 14 hari tidak perlu kartu kredit. Setelah trial, free tier masih bisa dipakai dengan batasan: 1 host dan retensi log 1 hari — cukup untuk mulai.
Saat setup akun, pilih region US1. Kalau ada kebutuhan data residency di Eropa, pilih EU. Setelah masuk, ambil API Key dari Organization Settings → API Keys → New Key. Simpan key ini — kita pakai di langkah berikutnya.
Install Datadog Agent di Server
Agent adalah proses yang jalan di server kamu — tugasnya mengumpulkan data dari aplikasi dan mengirimnya ke Datadog. Tanpa agent, tidak ada data yang masuk ke dashboard.
Prompt yang saya pakai:
Buatkan bash script untuk install dan konfigurasi Datadog Agent
di Ubuntu 22.04 (DigitalOcean droplet).
Yang harus dilakukan:
1. Install Datadog Agent via official one-step install script
Gunakan DD_API_KEY dari environment variable, bukan hardcoded
2. Buat atau update /etc/datadog-agent/datadog.yaml dengan:
- api_key dari environment variable DD_API_KEY
- site: datadoghq.com
- hostname: ambil dari output hostname command
- env: production
- tags: service:toko-online, team:backend
- apm_config.enabled: true
- logs_enabled: true
3. Enable dan start service datadog-agent
4. Jalankan verifikasi: datadog-agent status
Tampilkan pesan sukses atau gagal yang jelas
Tambahkan pengecekan di setiap langkah — kalau langkah gagal, script berhenti
dan tampilkan pesan error yang informatif.
Cara review hasilnya:
- Pastikan API key dibaca dari environment variable
$DD_API_KEY— tidak boleh hardcoded di script. Kalau script di-commit ke Git dengan API key di dalamnya, akun Datadog kamu bisa diakses orang lain. - Cek bahwa
apm_config.enabled: truedanlogs_enabled: trueada di yaml — dua ini tidak aktif secara default di beberapa versi agent. - Script harus pakai
set -edi awal supaya berhenti otomatis kalau ada langkah yang gagal, bukan lanjut terus sampai akhir lalu error tidak ketahuan. - Setelah
datadog-agent status, cek output-nya ada baris "API key validation" dengan status OK — ini konfirmasi agent bisa terhubung ke Datadog.
Hasil generate (sudah divalidasi):
#!/bin/bash
# install-datadog.sh — Install dan konfigurasi Datadog Agent di Ubuntu 22.04
# Usage: DD_API_KEY=your_key ./install-datadog.sh
set -e # Berhenti kalau ada command yang gagal
# Validasi API key tersedia
if [ -z "$DD_API_KEY" ]; then
echo "ERROR: DD_API_KEY environment variable tidak di-set."
echo "Usage: DD_API_KEY=your_key ./install-datadog.sh"
exit 1
fi
echo "==> [1/4] Install Datadog Agent..."
DD_API_KEY=$DD_API_KEY DD_SITE="datadoghq.com" bash -c "$(curl -L <https://s3.amazonaws.com/dd-agent/scripts/install_script_agent7.sh>)"
echo "==> [2/4] Konfigurasi datadog.yaml..."
cat > /etc/datadog-agent/datadog.yaml << EOF
api_key: ${DD_API_KEY}
site: datadoghq.com
hostname: $(hostname)
env: production
tags:
- service:toko-online
- team:backend
# APM — trace request Laravel
apm_config:
enabled: true
apm_non_local_traffic: false # hanya terima trace dari localhost
# Logs — aktifkan pengiriman log ke Datadog
logs_enabled: true
EOF
echo "==> [3/4] Enable dan start datadog-agent..."
systemctl enable datadog-agent
systemctl restart datadog-agent
# Tunggu beberapa detik supaya agent sempat connect
sleep 5
echo "==> [4/4] Verifikasi agent..."
if datadog-agent status | grep -q "API Keys status: API Key valid"; then
echo "✓ Datadog Agent berhasil terhubung ke Datadog."
else
echo "⚠ Agent berjalan tapi belum bisa verifikasi koneksi."
echo " Cek manual: datadog-agent status"
fi
echo ""
echo "Setup selesai. Buka <https://app.datadoghq.com/infrastructure> untuk lihat server."
Jalankan:
DD_API_KEY=your_api_key_here sudo ./install-datadog.sh
Install PHP APM Extension
PHP APM bukan Composer package biasa — ini C extension yang perlu diinstall di level sistem. Cara installnya berbeda dari composer require.
Prompt yang saya pakai:
Buatkan bash script untuk install datadog PHP APM extension (dd-trace-php)
di server Ubuntu 22.04 dengan PHP 8.2 dan PHP-FPM.
Steps:
1. Deteksi versi PHP yang aktif secara otomatis via php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;'
2. Download installer dd-trace-php dari GitHub release terbaru
3. Jalankan installer: php datadog-setup.php --php-bin=php
4. Verifikasi extension terpasang: php -m | grep ddtrace
Kalau tidak muncul, tampilkan error dan exit
5. Restart PHP-FPM dengan versi yang terdeteksi di langkah 1
6. Bersihkan file installer
Tambahkan error checking di tiap langkah.
Jangan assume versi PHP — deteksi otomatis.
Cara review hasilnya:
- Deteksi versi PHP otomatis penting — kalau server kamu punya PHP 8.2 tapi script hardcode
php8.1-fpm, restart service akan gagal. - Setelah install, verifikasi dengan
php -m | grep ddtracesebelum restart FPM. Kalau extension tidak muncul, ada yang salah saat install dan restart FPM tidak akan memperbaikinya. - Download dari GitHub releases harus pakai versi spesifik, bukan selalu
latest—latestbisa breaking kalau Datadog release versi major baru.
Hasil generate (sudah divalidasi):
#!/bin/bash
# install-ddtrace-php.sh — Install Datadog PHP APM extension
# Jalankan sebagai root
set -e
echo "==> [1/5] Deteksi versi PHP..."
PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
echo " PHP versi terdeteksi: ${PHP_VERSION}"
echo "==> [2/5] Download installer dd-trace-php..."
# Gunakan versi stabil terbaru — update angka ini sesuai release terbaru
DD_TRACE_VERSION="1.4.0"
curl -LO "<https://github.com/DataDog/dd-trace-php/releases/download/${DD_TRACE_VERSION}/datadog-setup.php>"
echo "==> [3/5] Install extension..."
php datadog-setup.php --php-bin=php
echo "==> [4/5] Verifikasi extension..."
if php -m | grep -q "ddtrace"; then
echo " ✓ Extension ddtrace berhasil terinstall."
else
echo " ERROR: Extension ddtrace tidak ditemukan setelah install."
echo " Coba jalankan manual: php datadog-setup.php --php-bin=php --verbose"
exit 1
fi
echo "==> [5/5] Restart PHP-FPM..."
systemctl restart "php${PHP_VERSION}-fpm"
echo " ✓ PHP-FPM ${PHP_VERSION} sudah di-restart."
# Bersihkan installer
rm -f datadog-setup.php
echo ""
echo "PHP APM extension berhasil diinstall."
echo "Trace akan muncul di Datadog APM dalam beberapa menit setelah ada request masuk."
Konfigurasi Environment Variables di Laravel
Tambahkan ke .env production:
# Datadog APM Configuration
DD_SERVICE=toko-online
DD_ENV=production
DD_VERSION=1.0.0
# Agent connection — default port, tidak perlu diubah kalau agent di server yang sama
DD_AGENT_HOST=localhost
DD_TRACE_AGENT_PORT=8126
# Auto-inject trace_id ke semua log — penting untuk connect log dengan trace
DD_LOGS_INJECTION=true
# Sample rate: 1.0 = trace semua request. Turunkan ke 0.1 kalau traffic sudah tinggi
DD_TRACE_SAMPLE_RATE=1.0
Satu catatan: DD_LOGS_INJECTION=true adalah fitur yang sangat berguna — secara otomatis menambahkan dd.trace_id dan dd.span_id ke setiap log entry. Jadi nanti kalau kamu lihat error di log Datadog, kamu bisa langsung klik "View Trace" dan lihat context request lengkapnya. Kita akan lihat ini bekerja di Bagian 4.
Di bagian selanjutnya kita masuk ke APM — melihat trace pertama dari toko online kita dan mengkonfigurasi custom spans untuk checkout flow supaya kita tahu persis bagian mana yang lambat.
Bagian 3: APM — Trace Request Laravel
Begitu PHP extension terinstall dan agent berjalan, sesuatu yang menarik terjadi secara otomatis: setiap HTTP request ke toko online sudah mulai di-trace tanpa kamu ubah satu baris kode pun. Buka Datadog → APM → Traces, dan kamu akan lihat request-request yang masuk beserta breakdown waktunya.
Tapi auto-instrumentation hanya sampai level tertentu. Untuk checkout flow yang kompleks — validasi cart, create order, panggil Xendit API — kita butuh custom spans supaya tahu persis bagian mana yang jadi bottleneck.
Apa yang Auto-Ter-Trace
Setelah install, ini yang sudah otomatis ter-trace tanpa konfigurasi tambahan:
HTTP Request masuk ke Laravel
└── Route matching
└── Middleware execution
└── Controller method
└── Eloquent queries (setiap query ke MySQL)
└── Redis commands (cache, session, queue)
└── HTTP client calls (kalau pakai Http::, Guzzle)
└── Response
Di Datadog APM, ini terlihat sebagai flame graph — bar horizontal yang menunjukkan berapa lama setiap operasi. Kalau product listing page kamu lambat, kamu akan langsung lihat: "oh, ada 47 query yang berjalan di sini" (classic N+1 problem).
Konfigurasi APM di Laravel
Auto-instrumentation sudah jalan, tapi ada beberapa konfigurasi yang perlu ditambahkan supaya trace lebih informatif — terutama untuk menambahkan context bisnis ke setiap trace.
Prompt yang saya pakai:
Saya punya toko online Laravel 12. Datadog PHP extension sudah terinstall.
Buatkan konfigurasi APM Datadog untuk Laravel dengan:
1. File config/datadog.php
- service: dari env DD_SERVICE (default: 'toko-online')
- env: dari env DD_ENV (default: 'local')
- version: dari env DD_VERSION (default: '1.0.0')
- enabled: true hanya di production dan staging (bukan local)
2. DatadogServiceProvider di app/Providers/DatadogServiceProvider.php
- Daftarkan di bootstrap/app.php
- Kalau DDTrace tidak tersedia (local dev tanpa agent), skip semua konfigurasi
- Tambahkan global tag ke semua trace: app_version dari config DD_VERSION
3. Middleware DatadogRequestMiddleware di app/Http/Middleware/
- Jalankan di semua HTTP request
- Tambahkan tag ke active span: route_name, http_method
- Tambahkan user_id kalau user sudah login (jangan tambahkan email atau data sensitif)
- Kalau response status >= 500, tandai span sebagai error
Semua DDTrace calls harus dibungkus class_exists('DDTrace\\GlobalTracer') check
supaya tidak error di local development yang tidak punya agent.
Cara review hasilnya:
class_exists('DDTrace\\GlobalTracer')check harus ada di setiap tempat yang menggunakan DDTrace API — termasuk di middleware dan service provider. Satu yang kelewat bisa throwFatal error: Class 'DDTrace\\GlobalTracer' not founddi local.- User ID boleh ditambahkan ke tag, tapi jangan tambahkan email, nama, atau data PII lainnya. Datadog adalah third-party service — data sensitif tidak boleh keluar dari server kamu.
- Middleware harus di-register di web middleware group, bukan global — kalau global, console commands juga kena dan tidak relevan.
Hasil generate (sudah divalidasi):
<?php
// config/datadog.php
return [
'service' => env('DD_SERVICE', 'toko-online'),
'env' => env('DD_ENV', 'local'),
'version' => env('DD_VERSION', '1.0.0'),
// Hanya aktifkan tracing di environment non-local
'enabled' => in_array(env('DD_ENV', 'local'), ['production', 'staging']),
];
<?php
// app/Providers/DatadogServiceProvider.php
namespace App\\Providers;
use Illuminate\\Support\\ServiceProvider;
class DatadogServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Skip kalau DDTrace extension tidak tersedia (local dev)
if (!class_exists('DDTrace\\GlobalTracer')) {
return;
}
// Skip kalau Datadog tidak di-enable di config
if (!config('datadog.enabled')) {
return;
}
// Tambahkan tag global ke semua trace
$tracer = \\DDTrace\\GlobalTracer::get();
$rootSpan = $tracer->getRootScope()?->getSpan();
if ($rootSpan) {
$rootSpan->setTag('app.version', config('datadog.version'));
$rootSpan->setTag('app.env', config('datadog.env'));
}
}
}
Daftarkan di bootstrap/app.php:
// bootstrap/app.php
->withProviders([
App\\Providers\\DatadogServiceProvider::class,
])
<?php
// app/Http/Middleware/DatadogRequestMiddleware.php
namespace App\\Http\\Middleware;
use Closure;
use Illuminate\\Http\\Request;
use Symfony\\Component\\HttpFoundation\\Response;
class DatadogRequestMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Skip kalau DDTrace tidak tersedia
if (!class_exists('DDTrace\\GlobalTracer')) {
return $response;
}
$tracer = \\DDTrace\\GlobalTracer::get();
$rootSpan = $tracer->getRootScope()?->getSpan();
if ($rootSpan) {
// Tag kontekstual per request
$rootSpan->setTag('http.route', $request->route()?->getName() ?? 'unknown');
$rootSpan->setTag('http.method', $request->method());
// User ID — boleh. Email atau nama — jangan.
if (auth()->check()) {
$rootSpan->setTag('user.id', auth()->id());
$rootSpan->setTag('user.role', auth()->user()->is_admin ? 'admin' : 'buyer');
}
// Tandai sebagai error kalau server error
if ($response->getStatusCode() >= 500) {
$rootSpan->setError(true);
$rootSpan->setTag('error.type', 'http_' . $response->getStatusCode());
}
}
return $response;
}
}
Daftarkan middleware di bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\\App\\Http\\Middleware\\DatadogRequestMiddleware::class,
]);
})
Custom Spans untuk Checkout Flow
Auto-instrumentation sudah trace request secara keseluruhan. Tapi untuk checkout yang kompleks, kita mau tahu lebih detail: dari total waktu 2.3 detik, berapa lama di validasi cart? Berapa lama di Xendit API call?
Inilah kegunaan custom spans.
Prompt yang saya pakai:
Toko online Laravel 12. CheckoutController sudah ada dengan method process()
yang melakukan: validasi cart → DB transaction (create order + order items) →
panggil XenditService->createInvoice() → clear cart → redirect ke Xendit.
Tambahkan manual tracing DDTrace ke method process() dengan struktur:
1. Span utama: "checkout.process"
Tag: user_id, cart_item_count, cart_total
2. Child span: "checkout.create_order"
Wrap DB transaction
Tag: order_number setelah order berhasil dibuat
3. Child span: "checkout.xendit_invoice"
Wrap panggilan XenditService->createInvoice()
Tag: xendit_invoice_id dari response, invoice_amount
Aturan penting:
- Setiap span harus di-finish di finally block supaya tidak memory leak
meski ada exception
- Kalau ada exception, tandai span sebagai error dengan pesan exception
- Semua DDTrace calls harus dalam class_exists() guard
- Jangan ubah logic bisnis yang sudah ada, hanya tambahkan tracing
Cara review hasilnya:
finallyblock untukspan->finish()adalah yang paling krusial. Kalau exception terjadi dan span tidak di-finish, trace akan terlihat terpotong di Datadog — dan lebih parahnya, bisa memory leak karena span object tidak pernah dilepas.- Child span harus di-finish sebelum parent span di-finish — urutan ini penting untuk flame graph yang benar.
- Tag
cart_totalharus integer dalam Rupiah — jangan kirim formatted string seperti "Rp 150.000" ke Datadog, susah di-aggregate.
Hasil generate (sudah divalidasi):
<?php
// app/Http/Controllers/CheckoutController.php
// Hanya menampilkan bagian yang berubah — logic bisnis tetap sama
public function process(Request $request, CartService $cart, XenditService $xendit)
{
$items = $cart->get();
if (empty($items)) {
return back()->with('error', 'Cart kamu kosong.');
}
// Helper: cek apakah DDTrace tersedia
$ddAvailable = class_exists('DDTrace\\GlobalTracer');
// Mulai span utama checkout
$checkoutSpan = null;
if ($ddAvailable) {
$tracer = \\DDTrace\\GlobalTracer::get();
$checkoutSpan = $tracer->startActiveSpan('checkout.process')->getSpan();
$checkoutSpan->setTag('user.id', auth()->id());
$checkoutSpan->setTag('cart.item_count', count($items));
$checkoutSpan->setTag('cart.total', $cart->total()); // integer Rupiah
}
try {
DB::transaction(function () use ($items, $cart, $xendit, $ddAvailable, &$invoiceUrl) {
// --- Child span: create order ---
$orderSpan = null;
if ($ddAvailable) {
$orderSpan = \\DDTrace\\GlobalTracer::get()
->startActiveSpan('checkout.create_order')
->getSpan();
}
try {
// Generate order number dan buat order
do {
$orderNumber = 'ORD-' . now()->format('Ymd') . '-' . strtoupper(Str::random(6));
} while (Order::where('order_number', $orderNumber)->exists());
$order = Order::create([
'user_id' => auth()->id(),
'affiliate_id' => $this->resolveAffiliateId($request),
'order_number' => $orderNumber,
'total' => $cart->total(),
'status' => 'pending',
]);
foreach ($items as $item) {
$order->items()->create([
'product_id' => $item['product_id'],
'price' => $item['price'],
]);
}
if ($orderSpan) {
$orderSpan->setTag('order.number', $order->order_number);
}
} catch (\\Exception $e) {
if ($orderSpan) $orderSpan->setError($e);
throw $e;
} finally {
// Finish child span sebelum lanjut ke child span berikutnya
if ($orderSpan) $orderSpan->finish();
}
// --- Child span: Xendit invoice ---
$xenditSpan = null;
if ($ddAvailable) {
$xenditSpan = \\DDTrace\\GlobalTracer::get()
->startActiveSpan('checkout.xendit_invoice')
->getSpan();
}
try {
$invoice = $xendit->createInvoice($order);
$order->update([
'xendit_invoice_id' => $invoice['id'],
'xendit_invoice_url' => $invoice['invoice_url'],
]);
if ($xenditSpan) {
$xenditSpan->setTag('xendit.invoice_id', $invoice['id']);
$xenditSpan->setTag('xendit.amount', $order->total);
}
$invoiceUrl = $invoice['invoice_url'];
} catch (\\Exception $e) {
if ($xenditSpan) $xenditSpan->setError($e);
throw $e;
} finally {
if ($xenditSpan) $xenditSpan->finish();
}
$cart->clear();
});
return Inertia::location($invoiceUrl);
} catch (\\Exception $e) {
if ($checkoutSpan) $checkoutSpan->setError($e);
return back()->with('error', $e->getMessage());
} finally {
// Parent span di-finish paling terakhir
if ($checkoutSpan) $checkoutSpan->finish();
}
}
Membaca Trace di Datadog
Setelah ada beberapa request masuk, buka APM → Traces di Datadog. Filter by service:toko-online. Klik salah satu trace checkout yang muncul.
Yang akan kamu lihat adalah flame graph seperti ini:
checkout.process [============================] 1.24s
├── checkout.create_order [====] 0.18s
│ └── mysql.query (INSERT orders) [=] 0.04s
│ └── mysql.query (INSERT order_items) [=] 0.03s
├── checkout.xendit_invoice [==================] 0.89s ← bottleneck!
└── mysql.query (UPDATE orders) [=] 0.02s
Dari sini langsung ketahuan: 89% waktu checkout habis menunggu response dari Xendit API. Itu normal untuk external API call. Yang tidak normal adalah kalau angka itu tiba-tiba naik dari 0.9 detik ke 4 detik — dan dengan Datadog, kamu akan tahu sebelum ada buyer yang komplain.
Di bagian selanjutnya kita setup log management — supaya semua Laravel log bisa disearch dari Datadog tanpa perlu SSH ke server, dan bisa di-connect langsung ke trace yang kita setup barusan.
Bagian 4: Log Management — Kirim Laravel Logs ke Datadog
Sekarang kita punya trace untuk setiap request. Tapi trace saja tidak cukup — kita juga butuh logs untuk tahu apa yang terjadi di dalam request tersebut. Payment webhook masuk, order dibuat, email dikirim — semua itu perlu tercatat dan bisa dicari kapan saja.
Masalah dengan setup default Laravel: log tersimpan di file di server. Mau baca? SSH dulu. Mau cari error tertentu? grep di file yang mungkin sudah di-rotate. Mau korelasi log dengan trace? Tidak bisa.
Dengan Datadog Log Management, semua itu berubah.
Dua Cara Kirim Log ke Datadog
Ada dua pendekatan:
Via Datadog Agent (yang kita pakai): Agent membaca file log Laravel secara real-time dan mengirimkannya ke Datadog. Tidak ada perubahan di aplikasi kamu, agent yang kerja. Ini pendekatan yang lebih production-ready karena tidak ada coupling antara aplikasi dan Datadog.
Via HTTP API langsung: Laravel POST log ke Datadog API setiap kali ada log entry baru. Lebih simple setup-nya, tapi ada risiko latency tambahan dan kalau Datadog API down, log bisa hilang.
Kita pakai opsi pertama.
Konfigurasi Agent untuk Baca Laravel Log
Prompt yang saya pakai:
Buatkan konfigurasi lengkap untuk Datadog Agent agar bisa membaca
dan parse Laravel log files di server Ubuntu.
Yang dibutuhkan:
1. File /etc/datadog-agent/conf.d/laravel.d/conf.yaml
- Path log: /var/www/toko-online/storage/logs/laravel-*.log
(wildcard untuk capture semua rotated log files)
- Source: php
- Service: toko-online
- Grok parsing rule untuk format log Laravel standar:
[YYYY-MM-DD HH:MM:SS] local.ERROR: message {"context"} {"extra"}
2. Update config/logging.php di Laravel untuk:
- Tambahkan channel baru bernama 'datadog' dengan:
driver: daily
path: storage/logs/laravel.log
formatter: Monolog\\Formatter\\JsonFormatter
(JSON format supaya Datadog bisa parse attribute dengan benar)
- Update stack default: gunakan ['daily', 'stderr'] tapi tambahkan
channel 'datadog' sebagai opsi
Sebenarnya kita hanya perlu satu file log dengan format JSON.
Tunjukkan cara ganti default channel ke JSON formatter.
Sertakan perintah untuk restart agent setelah konfigurasi.
Cara review hasilnya:
- Path di
conf.yamlharus sesuai persis denganstorage_path('logs')di Laravel kamu. Kalau deploy di/var/www/toko-online, path-nya/var/www/toko-online/storage/logs/laravel-*.log. Kalau salah, agent tidak akan menemukan file log. JsonFormatterdari Monolog akan mengubah format log dari string biasa ke JSON. Ini yang memungkinkan Datadog parse field sepertilevel,message,contextsebagai attribute terpisah — bukan satu blob teks.- Wildcard
laravel-*.logpenting untuk capture log yang sudah di-rotate (laravel-2025-03-01.log, laravel-2025-03-02.log, dll). - Setelah ubah konfigurasi agent, wajib restart:
systemctl restart datadog-agent.
Hasil generate (sudah divalidasi):
# /etc/datadog-agent/conf.d/laravel.d/conf.yaml
logs:
- type: file
path: /var/www/toko-online/storage/logs/laravel-*.log
service: toko-online
source: php
# Grok rule untuk parse format log Laravel
# Format asli: [2025-03-01 14:23:45] local.ERROR: Pesan {"key":"value"} []
log_processing_rules:
- type: multi_line
name: new_log_start_with_date
pattern: \\[\\d{4}-\\d{2}-\\d{2}
- type: mask_sequences
name: mask_sensitive_data
replace_placeholder: "[MASKED]"
pattern: "(?i)(password|secret|token|api_key)\\":\\\\s*\\"[^\\"]*\\""
<?php
// config/logging.php — update channel 'daily' untuk pakai JSON formatter
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
'ignore_exceptions' => false,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
'replace_placeholders' => true,
// JSON formatter supaya Datadog bisa parse attributes
'formatter' => Monolog\\Formatter\\JsonFormatter::class,
'formatter_with' => [
'includeStacktraces' => true,
],
],
// ... channels lain tetap sama
],
Restart agent:
sudo systemctl restart datadog-agent
# Verifikasi agent membaca file log
sudo datadog-agent status | grep -A 10 "Logs Agent"
Structured Logging — Log yang Berguna di Datadog
Sekarang log sudah masuk ke Datadog. Tapi ada perbedaan besar antara log yang hanya masuk dan log yang benar-benar berguna.
Log yang tidak berguna:
Log::error('Checkout gagal');
Log yang berguna:
Log::error('Checkout gagal', [
'user_id' => auth()->id(),
'cart_total' => $cart->total(),
'error_message'=> $e->getMessage(),
'error_code' => $e->getCode(),
]);
Yang kedua bisa di-search di Datadog dengan query @user_id:123 atau @error_code:500 — langsung dapat semua kejadian yang relevan.
Prompt yang saya pakai:
Toko online Laravel 12. Tunjukkan contoh structured logging yang optimal
untuk Datadog di empat skenario bisnis berikut.
Untuk setiap skenario, gunakan Laravel Log facade dengan context array.
Context key harus snake_case dan konsisten.
Jangan log data sensitif: password, nomor kartu, full token.
Skenario:
1. Order berhasil dibuat (level: info)
Data yang berguna: order_id, order_number, user_id, total_amount,
item_count, payment_method yang dipilih user
2. Xendit webhook diterima (level: info)
Data: xendit_invoice_id, payment_status, payment_channel,
paid_amount, order_id yang terkait
3. Checkout gagal karena exception (level: error)
Data: user_id, cart_item_count, cart_total, exception class,
exception message (max 500 char), file dan line number
4. Queue job email konfirmasi gagal (level: warning)
Data: job_id, order_id, user_email (hanya domain, bukan full email),
attempt_number, max_attempts, exception message
Setelah contoh kode, tunjukkan query Datadog untuk mencari
masing-masing log tersebut.
Cara review hasilnya:
user_emailtidak boleh full email address — ini PII. Cukup domain (gmail.com) atau hash. AI sering generate dengan full email.- Exception message harus di-truncate —
substr($e->getMessage(), 0, 500). Stack trace penuh bisa sangat panjang dan tidak perlu di context, sudah ada diincludeStacktracesformatter. - Semua context key harus konsisten di seluruh codebase. Kalau di satu tempat pakai
order_iddan di tempat lain pakaiorderIdatauorder, search di Datadog jadi tidak konsisten.
Hasil generate (sudah divalidasi):
<?php
// app/Http/Controllers/CheckoutController.php
// Skenario 1: Order berhasil dibuat
Log::info('Order berhasil dibuat', [
'order_id' => $order->id,
'order_number' => $order->order_number,
'user_id' => auth()->id(),
'total_amount' => $order->total, // integer Rupiah
'item_count' => count($items),
]);
// Skenario 3: Checkout gagal
} catch (\\Exception $e) {
Log::error('Checkout gagal', [
'user_id' => auth()->id(),
'cart_item_count' => count($items),
'cart_total' => $cart->total(),
'exception_class' => get_class($e),
'exception_msg' => substr($e->getMessage(), 0, 500), // truncate
'exception_file' => $e->getFile(),
'exception_line' => $e->getLine(),
]);
}
<?php
// app/Http/Controllers/XenditWebhookController.php
// Skenario 2: Webhook diterima
Log::info('Xendit webhook diterima', [
'xendit_invoice_id' => $request->id,
'payment_status' => $request->status,
'payment_channel' => $request->payment_channel ?? 'unknown',
'paid_amount' => $request->paid_amount ?? 0,
'order_id' => $order?->id,
]);
<?php
// app/Jobs/SendOrderConfirmationEmail.php
// Skenario 4: Queue job gagal
public function failed(\\Throwable $exception): void
{
// Ambil domain email saja, bukan full address
$emailDomain = substr(strrchr($this->order->user->email, '@'), 1);
Log::warning('Email konfirmasi order gagal dikirim', [
'job_id' => $this->job?->getJobId(),
'order_id' => $this->order->id,
'email_domain' => $emailDomain, // 'gmail.com', bukan full email
'attempt_number' => $this->attempts(),
'max_attempts' => $this->tries,
'exception_msg' => substr($exception->getMessage(), 0, 500),
]);
}
Search Log di Datadog
Setelah log masuk, ini query yang berguna:
| Query | Fungsi |
|---|---|
service:toko-online status:error | Semua error dari toko online |
service:toko-online @order_id:uuid-xxxxx | Semua log untuk satu order |
service:toko-online @user_id:123 status:error | Semua error yang dialami user tertentu |
service:toko-online "Xendit webhook" | Semua webhook events |
service:toko-online @payment_status:PAID | Semua pembayaran sukses |
service:toko-online status:error @exception_class:*Exception | Error dengan exception |
Dan fitur terbaik yang sudah kita setup: kalau log punya dd.trace_id (otomatis karena DD_LOGS_INJECTION=true), kamu bisa klik satu log entry dan lihat tombol "View Trace" — langsung jump ke APM trace yang generate log tersebut. Dari error di log, ke root cause di trace, dalam satu klik.
Di bagian selanjutnya kita setup custom metrics — supaya kita bisa pantau event bisnis seperti berapa order masuk per jam, berapa checkout yang gagal, dan berapa revenue hari ini, langsung dari Datadog dashboard.
Bagian 5: Custom Metrics — Pantau Event Bisnis
APM kasih kamu technical health. Logs kasih kamu detail event. Tapi ada satu pertanyaan yang tidak bisa dijawab keduanya: "Bisnis kita hari ini gimana?"
Berapa order masuk per jam? Berapa persen checkout yang gagal? Revenue hari ini sudah berapa? Ini business metrics — dan inilah kegunaan custom metrics di Datadog.
Jenis Metrics dan Kapan Pakainya
Sebelum nulis kode, pahami dulu tiga tipe metric yang tersedia:
| Tipe | Gunakan untuk | Contoh di toko online |
|---|---|---|
| Counter | Hitung kejadian yang terus bertambah | orders.created, checkouts.failed, payments.received |
| Gauge | Nilai yang naik-turun, snapshot saat ini | queue.pending_jobs, active_carts_count |
| Histogram | Distribusi nilai — min, max, average, percentile | checkout.duration_seconds, api.response_time |
Counter naik terus — tidak pernah dikurangi. Gauge bisa naik dan turun. Histogram bagus untuk latency karena kamu bisa query "berapa p95 checkout time hari ini" — bukan hanya rata-ratanya.
DatadogMetricsService
Kita tidak butuh package Composer tambahan untuk kirim metrics — cukup pakai UDP socket bawaan PHP via protokol DogStatsD.
Prompt yang saya pakai:
Buatkan DatadogMetricsService untuk toko online Laravel 12
di app/Services/DatadogMetricsService.php.
Gunakan protokol DogStatsD — kirim UDP ke Datadog Agent di localhost:8125.
Jangan install package tambahan, gunakan PHP socket native (fsockopen UDP).
Format DogStatsD yang benar:
"metric_name:value|type|#tag1:value1,tag2:value2\\n"
Type: c (counter), g (gauge), h (histogram)
Method:
1. increment(string $metric, int $value = 1, array $tags = []): void
Tambah counter. Tags dalam format ['key:value', 'key2:value2'].
2. gauge(string $metric, float $value, array $tags = []): void
Set nilai gauge.
3. histogram(string $metric, float $value, array $tags = []): void
Record distribusi nilai.
Tambahkan:
- Prefix semua metric dengan 'toko_online.' otomatis
- Static factory method: DatadogMetricsService::make() untuk akses tanpa DI
- Graceful fail: kalau UDP socket gagal dibuka, Log::warning dan return
tanpa throw exception — metric service tidak boleh bikin request gagal
- Sanitize metric name: ganti karakter selain huruf, angka, titik, underscore
dengan underscore
Host dan port ambil dari env: DD_STATSD_HOST (default: 127.0.0.1)
dan DD_STATSD_PORT (default: 8125).
Cara review hasilnya:
- Format DogStatsD sangat spesifik:
metric:value|type|#tag1:val1,tag2:val2dengan newline di akhir. Satu karakter salah dan metric tidak akan tercatat di Datadog tanpa error message apapun. - Tags harus dalam format
['key:value']— array of strings, bukan associative array['key' => 'value']. AI sering generate associative array yang salah format. fsockopen('udp://127.0.0.1', 8125)—udp://prefix wajib ada. Tanpa ini, akan default ke TCP dan gagal konek ke DogStatsD port.- Graceful fail adalah non-negotiable. Kalau metric service throw exception, checkout process bisa gagal hanya karena Datadog agent tidak jalan — itu tidak acceptable.
Hasil generate (sudah divalidasi):
<?php
// app/Services/DatadogMetricsService.php
namespace App\\Services;
use Illuminate\\Support\\Facades\\Log;
class DatadogMetricsService
{
private string $host;
private int $port;
private string $prefix = 'toko_online.';
public function __construct()
{
$this->host = env('DD_STATSD_HOST', '127.0.0.1');
$this->port = (int) env('DD_STATSD_PORT', 8125);
}
// Static factory — untuk dipakai di queue jobs tanpa DI container
public static function make(): self
{
return new self();
}
public function increment(string $metric, int $value = 1, array $tags = []): void
{
$this->send($metric, $value, 'c', $tags);
}
public function gauge(string $metric, float $value, array $tags = []): void
{
$this->send($metric, $value, 'g', $tags);
}
public function histogram(string $metric, float $value, array $tags = []): void
{
$this->send($metric, $value, 'h', $tags);
}
private function send(string $metric, float $value, string $type, array $tags): void
{
try {
// Sanitize metric name — hanya boleh huruf, angka, titik, underscore
$cleanMetric = preg_replace('/[^a-zA-Z0-9._]/', '_', $metric);
// Build DogStatsD payload
$payload = $this->prefix . $cleanMetric . ':' . $value . '|' . $type;
// Tambahkan tags kalau ada
// Format: #tag1:val1,tag2:val2
if (!empty($tags)) {
// Tags harus array of strings: ['env:production', 'service:toko-online']
$payload .= '|#' . implode(',', $tags);
}
$payload .= "\\n";
// Kirim via UDP — fire and forget
$socket = fsockopen('udp://' . $this->host, $this->port, $errno, $errstr, 1);
if (!$socket) {
// Graceful fail — jangan throw exception
Log::warning('DatadogMetrics: gagal buka UDP socket', [
'host' => $this->host,
'port' => $this->port,
'error' => $errstr,
]);
return;
}
fwrite($socket, $payload);
fclose($socket);
} catch (\\Throwable $e) {
// Catch semua error — metric service tidak boleh crash aplikasi
Log::warning('DatadogMetrics: gagal kirim metric', [
'metric' => $metric,
'error' => $e->getMessage(),
]);
}
}
}
Daftarkan sebagai singleton di AppServiceProvider:
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->singleton(DatadogMetricsService::class);
}
Integrasi ke Controllers dan Jobs
Prompt yang saya pakai:
Tunjukkan cara integrasi DatadogMetricsService ke tiga titik
di toko online Laravel 12.
1. CheckoutController->process()
- increment 'checkouts.initiated' saat checkout dimulai
- increment 'checkouts.completed' dengan tag payment_method saat berhasil
- increment 'checkouts.failed' dengan tag error_type saat gagal
- histogram 'checkout.duration_seconds' dengan waktu eksekusi (microtime)
2. XenditWebhookController->handle()
- increment 'payments.received' dengan tag status (PAID/FAILED/EXPIRED)
- Kalau PAID: increment 'payments.paid' dan gauge 'revenue.daily_total'
dengan nilai sum order->total dari hari ini
3. SendOrderConfirmationEmail job
- increment 'emails.confirmation.sent' saat berhasil di handle()
- increment 'emails.confirmation.failed' saat gagal di failed()
Inject DatadogMetricsService via constructor di controllers.
Di queue job gunakan DatadogMetricsService::make() karena DI tidak reliable di jobs.
Tags dalam format array of strings: ['key:value'].
Cara review hasilnya:
- Timing checkout harus pakai
microtime(true)di awal method, bukanCarbon::now()— presisi milidetik penting untuk histogram. revenue.daily_totaladalah gauge — nilainya adalah total revenue hari ini, bukan increment. Cara hitung:Order::paid()->whereDate('paid_at', today())->sum('total'). Ini sedikit expensive tapi jarang dipanggil (hanya saat webhook PAID masuk).- Di queue job, DI container tidak selalu tersedia tergantung cara job dijalankan.
DatadogMetricsService::make()lebih safe. - Tag
payment_methodharus normalize — Xendit bisa returnVIRTUAL_ACCOUNTatauVA, pastikan konsisten.
Hasil generate (sudah divalidasi):
<?php
// app/Http/Controllers/CheckoutController.php
class CheckoutController extends Controller
{
public function __construct(private DatadogMetricsService $metrics) {}
public function process(Request $request, CartService $cart, XenditService $xendit)
{
$startTime = microtime(true); // catat waktu mulai
$items = $cart->get();
if (empty($items)) {
return back()->with('error', 'Cart kamu kosong.');
}
// Catat bahwa checkout dimulai
$this->metrics->increment('checkouts.initiated');
try {
// ... logic checkout yang sudah ada ...
// Checkout berhasil
$duration = microtime(true) - $startTime;
$this->metrics->increment('checkouts.completed', 1, [
'payment_initiated:xendit', // method akan ketahuan setelah user bayar
]);
$this->metrics->histogram('checkout.duration_seconds', $duration);
return Inertia::location($invoiceUrl);
} catch (\\Exception $e) {
$this->metrics->increment('checkouts.failed', 1, [
'error_type:' . class_basename($e),
]);
return back()->with('error', $e->getMessage());
}
}
}
<?php
// app/Http/Controllers/XenditWebhookController.php
class XenditWebhookController extends Controller
{
public function __construct(private DatadogMetricsService $metrics) {}
public function handle(Request $request)
{
// ... verifikasi token dan cari order ...
// Catat semua webhook yang masuk
$this->metrics->increment('payments.received', 1, [
'status:' . strtolower($request->status),
'channel:' . strtolower($request->payment_channel ?? 'unknown'),
]);
if ($request->status === 'PAID' && $order->status !== 'paid') {
// ... update order dan create downloads ...
$this->metrics->increment('payments.paid');
// Gauge: total revenue hari ini — snapshot nilai saat ini
$dailyRevenue = Order::paid()
->whereDate('paid_at', today())
->sum('total');
$this->metrics->gauge('revenue.daily_total', $dailyRevenue);
}
return response()->json(['status' => 'ok']);
}
}
<?php
// app/Jobs/SendOrderConfirmationEmail.php
public function handle(): void
{
$this->order->load('items.product');
Mail::to($this->order->user->email)->send(new OrderConfirmationMail($this->order));
// Gunakan ::make() di job — DI container tidak reliable
DatadogMetricsService::make()->increment('emails.confirmation.sent');
}
public function failed(\\Throwable $exception): void
{
DatadogMetricsService::make()->increment('emails.confirmation.failed', 1, [
'attempt:' . $this->attempts(),
]);
Log::warning('Email konfirmasi gagal', [
'order_id' => $this->order->id,
'email_domain' => substr(strrchr($this->order->user->email, '@'), 1),
'attempt_number' => $this->attempts(),
'exception_msg' => substr($exception->getMessage(), 0, 500),
]);
}
Lihat Metrics di Datadog
Buka Metrics → Explorer. Ketik toko_online.checkouts.completed — kamu akan lihat grafik berapa checkout berhasil per interval waktu.
Beberapa query berguna:
# Checkout conversion rate: completed / initiated
sum:toko_online.checkouts.completed{*}.as_rate() /
sum:toko_online.checkouts.initiated{*}.as_rate()
# Payment breakdown by channel
sum:toko_online.payments.paid{*} by {channel}
# Revenue hari ini (ambil nilai gauge terbaru)
max:toko_online.revenue.daily_total{*}
Di bagian selanjutnya kita setup alerts supaya kamu tidak perlu pantau dashboard terus — sistem yang akan kasih tahu kalau ada masalah, sebelum buyer yang laporan duluan.
Bagian 6: Alerts dan Dashboard
Monitoring yang baik bukan berarti kamu harus pantau dashboard sepanjang waktu. Justru sebaliknya — setup yang bagus akan kasih tahu kamu kalau ada masalah, tanpa kamu harus lihat layar terus. Ini yang dikerjakan alerts.
Dashboard di sisi lain adalah untuk waktu kamu memang mau lihat kondisi toko — review pagi sebelum mulai kerja, atau waktu troubleshoot insiden. Satu halaman, semua data relevan.
Alerts — Tiga yang Wajib Dipasang
Kita setup tiga alert sebagai fondasi. Setelah ini jalan, kamu bisa tambah sesuai kebutuhan.
Prompt yang saya pakai:
Buatkan konfigurasi tiga Datadog Monitor untuk toko online Laravel 12.
Jelaskan cara setup masing-masing via Datadog UI (bukan Terraform).
Alert 1: High Error Rate
Trigger kalau error rate request > 5% dalam 5 menit terakhir.
Metric: APM trace errors vs total requests untuk service toko-online.
Severity: Critical.
Alert 2: Slow Response Time
Trigger kalau p95 response time > 3 detik dalam 10 menit terakhir.
Metric: p95 dari APM trace untuk service toko-online.
Severity: Warning.
Catatan: APM metrics pakai satuan nanoseconds, bukan milliseconds.
3 detik = 3.000.000.000 nanoseconds.
Alert 3: Checkout Failures Spike
Trigger kalau checkouts.failed > 10 dalam 15 menit terakhir.
Metric: custom metric toko_online.checkouts.failed.
Severity: Critical.
Untuk setiap alert, sertakan:
- Langkah-langkah setup di UI Datadog
- Query metric yang benar
- Threshold yang tepat
- Pesan notifikasi yang informatif (bukan hanya "alert triggered")
- Recovery message
Cara review hasilnya:
- Satuan APM untuk response time adalah nanoseconds — ini yang paling sering salah. 3 detik bukan
3000(milliseconds) dan bukan3(seconds) — tapi3000000000nanoseconds. Kalau threshold salah, alert tidak pernah trigger atau trigger terus-menerus. - Error rate query harus pakai
.as_rate()pada kedua metric (errors dan hits) supaya perbandingannya benar. Kalau hanya satu yang pakai.as_rate(), hasilnya nonsense. - Recovery message penting — kalau alert trigger jam 3 pagi lalu selesai sendiri jam 3.15, kamu mau tahu bahwa sudah resolved tanpa harus cek manual.
Hasil generate (sudah divalidasi):
Alert 1: High Error Rate
Di Datadog → Monitors → New Monitor → Metric.
Query:
sum(last_5m):
sum:trace.web.request.errors{service:toko-online}.as_rate()
/
sum:trace.web.request.hits{service:toko-online}.as_rate()
> 0.05
Threshold:
Critical: > 0.05 (5%)
Warning: > 0.02 (2%)
Alert message:
⚠️ ERROR RATE TINGGI — Toko Online
Error rate sekarang: {{value}}%
Service: {{service.name}}
Environment: {{env}}
Cek APM traces: <https://app.datadoghq.com/apm/traces?service=toko-online>
Cek logs: <https://app.datadoghq.com/logs?query=service:toko-online> status:error
@slack-alerts-production
Recovery message:
✅ Error rate kembali normal ({{value}}%)
Alert 2: Slow Response Time
Query:
avg(last_10m):
p95:trace.web.request{service:toko-online}
> 3000000000
# 3 detik = 3.000.000.000 nanoseconds
# APM metrics SELALU dalam nanoseconds
Threshold:
Critical: > 3000000000 (3 detik)
Warning: > 1500000000 (1.5 detik)
Alert message:
🐌 RESPONSE TIME LAMBAT — Toko Online
P95 response time: {{value}}ns ({{eval "value/1000000000"}}s)
Threshold: 3 detik
Cek APM untuk identifikasi bottleneck:
<https://app.datadoghq.com/apm/traces?service=toko-online&sort=duration>
@slack-alerts-production
Alert 3: Checkout Failures Spike
Query:
sum(last_15m):
sum:toko_online.checkouts.failed{*}
> 10
Threshold:
Critical: > 10
Warning: > 5
Alert message:
🚨 CHECKOUT FAILURES SPIKE — Toko Online
{{value}} checkout gagal dalam 15 menit terakhir.
Kemungkinan penyebab:
- Xendit API bermasalah → cek <https://status.xendit.co>
- Bug di checkout flow → cek logs: service:toko-online "Checkout gagal"
- Cart kosong atau produk tidak tersedia
@slack-alerts-production @pagerduty-critical
Recovery message:
✅ Checkout failure rate kembali normal.
Untuk kirim ke Slack: setup dulu di Integrations → Slack di Datadog, lalu gunakan @slack-channel-name di message.
Dashboard — Satu Halaman untuk Semua
Dashboard yang baik menjawab dua pertanyaan dalam 10 detik: "Sistem sehat?" dan "Bisnis hari ini gimana?"
Layout yang kita buat:
┌─────────────────────────────────────────────────────────────────┐
│ TOKO ONLINE — PRODUCTION DASHBOARD [Last 1 hour] │
├─────────────────┬───────────────┬───────────────┬───────────────┤
│ Total Requests │ Error Rate │ P95 Resp Time│ Active Users │
│ 1,234 req │ 0.8% │ 420ms │ 47 │
│ [timeseries] │ [timeseries] │ [timeseries] │ [gauge] │
├─────────────────┴───────────────┴───────────────┴───────────────┤
│ ROW 2 — BUSINESS │
├─────────────────┬───────────────┬───────────────┬───────────────┤
│ Orders / Hour │ Revenue Today │ Conv. Rate │ Payment by │
│ [bar chart] │ Rp 4.200.000 │ 68% │ Channel │
│ │ [query val] │ [calc metric]│ [pie chart] │
├─────────────────┴───────────────┴───────────────┴───────────────┤
│ ROW 3 — INFRASTRUCTURE │
├─────────────────┬───────────────┬───────────────┬───────────────┤
│ Queue Pending │ Queue Failed │ DB Query Time│ Memory Usage │
│ [gauge] │ [timeseries] │ [timeseries] │ [timeseries] │
└─────────────────┴───────────────┴───────────────┴───────────────┘
Cara buat di Datadog:
- Buka Dashboards → New Dashboard → pilih "New Timeboard"
- Klik Add Widgets untuk tambah setiap widget
- Untuk widget "Orders per Hour": pilih Metric → query
sum:toko_online.orders_created{*}.as_count()→ visualization: Bar Chart - Untuk "Revenue Today": pilih Query Value → query
max:toko_online.revenue.daily_total{*}→ format sebagai currency - Untuk "Checkout Conversion Rate": pilih Query Value → formula
(query_a / query_b) * 100dimana query_a adalahcheckouts.completeddan query_b adalahcheckouts.initiated
Simpan Dashboard sebagai JSON:
Setelah dashboard jadi, klik Settings (gear icon) → Export Dashboard JSON. Simpan file ini di repository Git kamu:
# Di repo Laravel
mkdir -p .datadog
# Paste JSON ke file ini
vim .datadog/dashboard-production.json
git add .datadog/
git commit -m "chore: add Datadog production dashboard config"
Ini infrastructure as code untuk monitoring — kalau setup production baru, import JSON ini dan dashboard langsung jadi tanpa rebuild dari nol.
Tips: Variable di Dashboard
Tambahkan Template Variable di dashboard untuk filter per environment:
Variable name: env
Tag key: env
Default value: production
Semua widget yang pakai $env di query-nya akan ikut ter-filter. Berguna kalau kamu punya staging dan production di Datadog yang sama — satu dashboard bisa dipakai untuk keduanya.
Di bagian terakhir kita wrap up semua yang sudah disetup dan saya kasih rekomendasi kelas BuildWithAngga yang paling relevan untuk kamu yang mau lanjut lebih dalam ke production engineering.
Bagian 7: Apa Selanjutnya?
Kita sudah bangun monitoring yang cukup lengkap dari nol — dari install agent sampai dashboard yang bisa dibuka dari HP. Tapi lebih penting dari tools yang terpasang, ada perubahan cara berpikir yang terjadi.
Yang Sudah Kamu Kuasai
Observability mindset. Sebelum Datadog, debugging adalah kerja reaktif: ada masalah → SSH ke server → grep log → coba tebak penyebabnya. Sekarang kamu punya data untuk kerja proaktif: lihat dashboard pagi hari, notice error rate naik sedikit sejak kemarin, trace-nya menunjuk ke query yang mulai lambat seiring data makin banyak. Fix sebelum jadi insiden.
Dari sisi teknis, kamu sekarang sudah familiar dengan:
- Datadog Agent dan PHP APM extension — bagaimana trace otomatis terjadi tanpa ubah kode
- Manual spans dengan DDTrace — cara instrument bagian kode yang penting secara bisnis
- Structured logging — perbedaan log yang bisa dicari vs log yang hanya jadi noise
- DogStatsD dan custom metrics — cara translate business events jadi angka yang bisa di-track
- Alerts dan dashboard — monitoring yang bekerja bahkan saat kamu tidur
Next Level
Setup yang kita bangun ini adalah fondasi yang solid. Kalau mau dikembangkan lebih jauh:
Distributed Tracing — kalau arsitektur berkembang ke microservices atau ada background service terpisah, Datadog bisa trace request yang melewati banyak service sekaligus. Satu trace bisa span dari web app ke queue worker ke service notifikasi.
Real User Monitoring (RUM) — APM trace dari sisi server. RUM trace dari sisi browser user. Kamu bisa lihat: berapa lama halaman product listing selesai di-load di browser user, di device apa, dari kota mana, sebelum mereka checkout.
Synthetic Monitoring — test otomatis yang Datadog jalankan setiap beberapa menit: buka homepage, klik produk, add to cart. Kalau test ini gagal, kamu dapat alert sebelum ada user yang experience masalah yang sama.
Datadog Terraform Provider — manage semua konfigurasi Datadog (monitors, dashboards, users) via Terraform. Ini langkah lanjutan dari "simpan dashboard JSON di Git" yang kita lakukan tadi — semua infrastruktur monitoring jadi reproducible dan reviewable via pull request.
Kelas Unggulan di BuildWithAngga
Datadog yang sudah kita setup ini paling valuable ketika aplikasi yang kita monitor memang production-grade dan punya traffic nyata. Kelas-kelas berikut akan bantu kamu sampai di titik itu.
| Kelas Unggulan | Mengapa Relevan |
|---|---|
| Laravel E-Commerce Complete | Toko online yang kita pakai sebagai contoh di artikel ini — dibangun dari nol sampai production. Checkout, payment gateway, manajemen produk, dan order management yang worth dimonitor dengan Datadog. |
| Full-Stack Laravel + Inertia + Vue | Bangun aplikasi Laravel modern dengan stack yang sama yang dipakai di artikel ini. Fondasi yang kuat sebelum bicara soal monitoring dan production engineering. |
| DevOps untuk Laravel Developer | Server management, deployment pipeline, Nginx, Supervisor, dan monitoring. Melengkapi apa yang kita bahas di artikel ini — dari sisi infrastruktur production-nya. |
| SaaS dengan Laravel | Aplikasi SaaS butuh observability yang matang sejak awal. Multi-tenancy, subscription, dan usage tracking — Datadog adalah bagian standar dari stack monitoring-nya. |
┌─────────────────────────────────────────────────────────────────┐
│ BENEFIT KELAS PREMIUM BWA │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 📦 PROJECT PORTFOLIO-READY │
│ Source code lengkap dari nol sampai deploy │
│ Bisa langsung dipakai atau dijadikan base project │
│ │
│ ♾️ AKSES SEUMUR HIDUP │
│ Beli sekali, akses selamanya │
│ Update materi gratis kalau ada perubahan stack │
│ │
│ 👨🏫 KONSULTASI MENTOR │
│ Tanya langsung via forum diskusi │
│ Code review dari praktisi yang kerja di industri │
│ │
│ 📜 SERTIFIKAT RESMI │
│ Bukti kompetensi yang bisa di-share ke LinkedIn │
│ Nilai tambah nyata untuk CV dan portofolio │
│ │
│ 👥 KOMUNITAS 900.000+ STUDENTS │
│ Networking sesama developer Indonesia │
│ Info project freelance dan lowongan kerja │
│ │
└─────────────────────────────────────────────────────────────────┘
Penutup
Monitoring bukan fitur yang kamu tambahkan setelah aplikasi jadi. Monitoring adalah bagian dari definisi "aplikasi yang siap production."
Toko online tanpa monitoring seperti toko fisik tanpa CCTV dan tanpa catatan kasir — kamu tidak tahu kapan ada yang curi, tidak tahu produk mana yang paling laku, dan tidak tahu kenapa revenue bulan ini turun.
Setup Datadog sekarang, sebelum ada insiden. Bukan sesudah.
Artikel ini adalah starting point — eksplorasi fitur Datadog yang lain sesuai kebutuhan proyekmu, dan jangan ragu untuk iterate konfigurasinya seiring aplikasi berkembang.
Angga Risky Setiawan Founder, BuildWithAngga