Chapter Server Log sudah menunjukkan ke mana output logging PostgreSQL mengalir: file teks di server, dirotasi per waktu atau ukuran. Pertanyaan administrasi yang tersisa justru yang paling memengaruhi isi log harian Anda: apa yang layak dicatat? Server yang mencatat segalanya akan menghasilkan file log raksasa dan query yang melambat karena setiap perintah harus ditulis ke disk; server yang tidak mencatat apa pun akan membuat investigasi insiden mustahil. Chapter ini membedah parameter-parameter penyetel di sisi tengah: memilih statement mana yang dicatat lewat log_statement, menangkap query lambat lewat log_min_duration_statement, membaca kode format log_line_prefix, dan mengaktifkan jejak koneksi — semuanya diuji langsung terhadap server latihan yang lognya bisa kita intip.

Membaca Konfigurasi Logging Saat Ini #

Semua parameter logging hidup di pg_settings dan bisa diaudit lewat query — cara paling aman untuk mengetahui keadaan server tanpa membuka file konfigurasi. Query berikut mengambil delapan parameter yang akan kita bahas sepanjang chapter:

-- Konfigurasi logging server saat ini, dibaca dari pg_settings.
-- Setiap baris adalah parameter yang mengendalikan APA yang ditulis
-- ke server log, BUKAN log itu sendiri.
SELECT name, setting, unit
FROM pg_settings
WHERE name IN ('log_destination',
               'logging_collector',
               'log_filename',
               'log_rotation_age',
               'log_rotation_size',
               'log_line_prefix',
               'log_statement',
               'log_min_duration_statement')
ORDER BY name;

Output yang Dihasilkan:

text
            name            |            setting             | unit 
----------------------------+--------------------------------+------
 log_destination            | stderr                         | 
 log_filename               | postgresql-%Y-%m-%d_%H%M%S.log | 
 log_line_prefix            | %m [%p]                        | 
 log_min_duration_statement | -1                             | ms
 log_rotation_age           | 1440                           | min
 log_rotation_size          | 10240                          | kB
 log_statement              | none                           | 
 logging_collector          | off                            | 
(8 rows)

Dua baris pertama output menegaskan arsitektur logging server latihan: log_destination = stderr berarti pesan log ditulis ke standard error proses server, dan logging_collector = off berarti tidak ada proses penampung yang memutarnya menjadi file bernama postgresql-YYYY-MM-DD_HHMMSS.log seperti pola di log_filename. Baris log_rotation_age (1440 menit) dan log_rotation_size (10240 kB) adalah pengatur rotasi yang dibahas ulang di akhir chapter. Tiga parameter sisanya — log_statement, log_min_duration_statement, dan log_line_prefix — adalah bahan utama chapter ini.

Menyeleksi Statement yang Dicatat: log_statement #

Parameter log_statement memilih kelas statement yang ditulis utuh ke server log. Empat levelnya membentuk tangga seleksi:

LevelYang dicatatCocok untuk
none (bawaan)Tidak ada statementServer produksi sehat
ddlCREATE, ALTER, DROP dan kawan-kawanMelacak perubahan skema
modSemua DDL + INSERT, UPDATE, DELETEAudit perubahan data
allSemua statement termasuk SELECTInvestigasi singkat, jangan permanen

Parameter ini berkonteks superuser, sehingga bisa dipasang untuk satu sesi dengan SET dan dikembalikan kapan saja — bentuk latihan yang paling aman karena tidak menyentuh konfigurasi global. File berikut menaiki tangganya level demi level sambil memeriksa level aktif lewat SHOW:

-- Tur level log_statement dalam satu sesi.
-- SHOW setelah SET memperlihatkan level yang sedang aktif.
SET log_statement = 'ddl';
SHOW log_statement;
CREATE TABLE pelatihan_ddl (id integer);

SET log_statement = 'mod';
SHOW log_statement;
INSERT INTO pelatihan_ddl VALUES (1);
UPDATE pelatihan_ddl SET id = 2;
SELECT id FROM pelatihan_ddl;

SET log_statement = 'all';
SHOW log_statement;
SELECT count(*) FROM pelatihan_ddl;

SET log_statement = 'none';
SHOW log_statement;
DELETE FROM pelatihan_ddl;

Output yang Dihasilkan:

text
SET
 log_statement 
---------------
 ddl
(1 row)

CREATE TABLE
SET
 log_statement 
---------------
 mod
(1 row)

INSERT 0 1
UPDATE 1
 id 
----
  2
(1 row)

SET
 log_statement 
---------------
 all
(1 row)

 count 
-------
     1
(1 row)

SET
 log_statement 
---------------
 none
(1 row)

DELETE 1

Output di atas adalah sisi klien: SHOW memperlihatkan level yang sedang aktif dan psql mencetak tag perintah seperti biasa. Tidak ada yang aneh di layar — dan justru itulah intinya: logging statement tidak mengubah apa pun yang dilihat klien. Jejaknya muncul di server log. File berikut menjalankan statement yang sama, tetapi output yang ditampilkan berasal dari file server log sungguhan, bukan layar psql:

-- File yang sama dengan log-statement.sql, dijalankan ulang
-- untuk menangkap jejaknya di server log.
-- Output blok ini bukan layar psql, melainkan potongan nyata
-- dari file server log -- timestamp, pid, dan nomor baris
-- akan berbeda di mesin Anda.
SET log_statement = 'ddl';
CREATE TABLE pelatihan_ddl (id integer);

SET log_statement = 'mod';
INSERT INTO pelatihan_ddl VALUES (1);
UPDATE pelatihan_ddl SET id = 2;
SELECT id FROM pelatihan_ddl;

SET log_statement = 'all';
SELECT count(*) FROM pelatihan_ddl;

SET log_statement = 'none';
DELETE FROM pelatihan_ddl;

Output yang Dihasilkan:

text
2026-10-14 15:45:40.232 UTC [8175] LOG:  statement: CREATE TABLE pelatihan_ddl (id integer);
2026-10-14 15:45:40.232 UTC [8175] LOG:  statement: INSERT INTO pelatihan_ddl VALUES (1);
2026-10-14 15:45:40.232 UTC [8175] LOG:  statement: UPDATE pelatihan_ddl SET id = 2;
2026-10-14 15:45:40.233 UTC [8175] LOG:  statement: SELECT count(*) FROM pelatihan_ddl;
2026-10-14 15:45:40.233 UTC [8175] LOG:  statement: SET log_statement = 'none';

Mari kita cocokkan kelima baris log itu dengan tangga levelnya:

  1. CREATE TABLE tercatat karena berjalan pada level ddl.
  2. INSERT dan UPDATE tercatat karena level sudah dinaikkan ke mod — level ini menambahkan semua perubahan data.
  3. SELECT id FROM pelatihan_ddl tidak ada di log karena level mod memang tidak mencatat query baca.
  4. SELECT count(*) tercatat karena level sudah all.
  5. DELETE FROM pelatihan_ddl tidak ada di log — level sudah none saat perintah itu berjalan.

Ada satu kejutan yang layak dibedah: baris kelima mencatat SET log_statement = 'none', padahal SET bukan statement DDL maupun modifikasi data. Penjelasannya ada pada urutan evaluasi: perintah SET itu dieksekusi ketika level masih all, dan level baru none baru berlaku setelah perintah selesai — jadi perintah penurunan level itu sendiri sempat terekam.

Menangkap Query Lambat: log_min_duration_statement #

Jika log_statement menjawab “statement apa yang dicatat”, log_min_duration_statement menjawab “statement yang berapa lama yang dicatat”. Nilainya adalah ambang milidetik: statement yang selesai di bawah ambang tidak diapa-apakan, yang melewatinya dicatat beserta durasinya. Nilai -1 (bawaan) mematikan fitur ini sepenuhnya. File berikut memasang ambang 500 ms lalu menjalankan satu query kilat dan satu query sengaja dimotorkan lambat dengan pg_sleep(0.6):

-- Ambang 500 ms: query yang lebih cepat tidak dicatat,
-- query yang lebih lambat dicatat beserta durasinya.
SET log_min_duration_statement = 500;

SELECT 1 AS secepat_kilat;

SELECT pg_sleep(0.6);

-- Kembalikan ke nilai server (-1 = tidak ada ambang).
SET log_min_duration_statement = -1;

Output yang Dihasilkan:

text
SET
 secepat_kilat 
---------------
             1
(1 row)

 pg_sleep 
----------
 
(1 row)

SET

Di sisi klien kedua query tampak sama-sama biasa. Sisi server log berkata lain — file berikut dijalankan ulang dan potongan log-nya ditampilkan:

-- File yang sama dengan log-min-duration.sql, dijalankan ulang
-- untuk menangkap baris duration di server log.
-- Angka durasi (mis. 601.547 ms) bervariasi antar mesin dan
-- antar eksekusi; ambang 500 ms-nya yang tetap.
SET log_min_duration_statement = 500;

SELECT 1 AS secepat_kilat;

SELECT pg_sleep(0.6);

SET log_min_duration_statement = -1;

Output yang Dihasilkan:

text
2026-10-14 15:45:40.840 UTC [8177] LOG:  duration: 601.879 ms  statement: SELECT pg_sleep(0.6);

Hanya satu baris yang muncul. SELECT 1 selesai jauh di bawah 500 ms sehingga tidak menyentuh log sama sekali, sedangkan SELECT pg_sleep(0.6) melewati ambang dan tercatat sebagai baris duration — bentuk 601.879 ms di sini bervariasi antar mesin dan antar eksekusi, tetapi strukturnya selalu duration: <angka> ms statement: <query>. Inilah mekanisme yang dipakai administrator untuk berburu query lambat: pasang ambang (misalnya 1000 ms di server produksi), biarkan berjalan satu hari, lalu baca kumpulan baris duration untuk menemukan kandidat optimalisasi — tanpa perlu mencatat ribuan query cepat yang tidak relevan.

Membaca Kode Format log_line_prefix #

Setiap baris log pada output sebelumnya diawali 2026-10-14 15:45:40.232 UTC [8177] — bukan kebetulan, melainkan hasil log_line_prefix = '%m [%p]' yang terlihat di query pg_settings pembuka chapter. Setiap kode % dalam nilai parameter tersebut menghasilkan satu potongan informasi di awal baris:

KodeArtiContoh keluaran
%mTimestamp dengan milidetik dan zona waktu2026-10-14 15:45:40.232 UTC
%uNama user koneksipostgres
%dNama databasetoko_maju
%pProcess ID backend8177
%lNomor urut baris log per sesi17
%cToken unik sesi (pid.starttime)6629.6604d4f0
%vVirtual transaction ID3/44
%xTransaction ID (0 bila kosong)0

Prefix bawaan %m [%p] sudah cukup untuk log sederhana, tetapi administrator produksi biasanya memasang prefix yang lebih kaya seperti berikut:

text
log_line_prefix = '%m [%p] %u@%d %l '

Dengan prefix tersebut setiap baris log menyebut user dan database-nya, sehingga satu file log dari banyak aplikasi tetap bisa difilter per klien. Pengubahannya dilakukan di postgresql.conf atau lewat ALTER SYSTEM, berlaku setelah reload konfigurasi — mekanisme yang dibahas tuntas di chapter PostgreSQL Configuration Files .

Jejak Koneksi Masuk dan Keluar #

Dua parameter saudara mengurus siklus hidup koneksi: log_connections mencatat setiap upaya koneksi dan keberhasilan autentikasinya, sedangkan log_disconnections mencatat akhir sesi beserta durasinya. Keduanya berkonteks khusus: hanya bisa dipasang oleh superuser saat koneksi dibuat — tidak bisa di-SET di tengah sesi. Percobaan ALTER ROLE postgres SET log_connections di server latihan ditolak dengan pesan ERROR: parameter "log_connections" cannot be set after connection start. Jalur yang tersedia untuk satu koneksi adalah paket startup; psql menyuplainya lewat environment variable PGOPTIONS:

bash log-koneksi.sh
# log_disconnections hanya boleh dipasang saat koneksi dibuat,
# jadi psql dijalankan dengan PGOPTIONS yang membawa parameter itu
# di paket startup koneksi.
PGOPTIONS="-c log_disconnections=on" psql -X -c "SELECT 1 AS uji_koneksi;"

Output yang Dihasilkan:

text
 uji_koneksi 
-------------
           1
(1 row)

Output klien hanya menampilkan angka 1 — tetapi di server log, akhir sesi kini tercatat:

text
2026-10-14 15:46:03.360 UTC [8247] LOG:  disconnection: session time: 0:00:00.001 user=postgres database=probe_adm2c host=[local]

Baris tersebut menunjukkan bentuk lengkap pesan disconnection: durasi sesi (session time: 0:00:00.001), user, database, dan host asal ([local] berarti koneksi lewat unix domain socket). Satu kejujuran observasional perlu dicatat: pada server latihan kami (PostgreSQL 17, koneksi socket), menyuplai log_connections=on lewat PGOPTIONS ternyata tidak menghasilkan baris connection received/connection authorized — keduanya baru tertulis bila parameter diaktifkan dari postgresql.conf sebelum koneksi dibuat. Baris disconnection sebaliknya berhasil ditangkap karena acaranya terjadi di ujung sesi, jauh setelah opsi startup diterapkan. Angka durasi sesi pada baris log juga akan berbeda di setiap eksekusi.

Format Terstruktur: csvlog dan jsonlog #

Teks baris demi baris nyaman dibaca manusia, tetapi tools analisis log lebih suka format terstruktur. PostgreSQL menyediakan dua pilihan lewat log_destination:

Nilai log_destinationBentuk keluaranSyarat
stderrBaris teks ke standard errorTanpa syarat khusus
csvlogBaris CSV berkoma dengan file header .csvlogging_collector = on
jsonlogSatu objek JSON per barislogging_collector = on

Kedua format terstruktur memecah setiap pesan menjadi kolom tetap — timestamp, user, database, pid, level pesan, teks pesan — sehingga log bisa langsung dimuat ke tabel atau dikirim ke pipeline log tanpa parsing regex. Keduanya hanya bekerja bersama logging collector, proses yang menampung stderr server dan menuliskannya ke file dengan pola log_filename serta rotasi otomatis. Menaikkan logging_collector memerlukan restart server — itulah sebabnya server latihan tetap memakai stderr dan tidak mendemokan pergantian format ini langsung; mekanisme collector dan rotasinya dibahas di chapter Server Log .

Jebakan Logging Berlebihan #

Keseimbangan volume dijaga oleh rotasi yang sudah terlihat di query pembuka: log_rotation_age = 1440 menit (satu hari) dan log_rotation_size = 10240 kB memicu file log baru, dengan pola nama dari log_filename. Aturan berapa lama file lama disimpan — penghapusan setelah 30 hari, misalnya — bukan bagian dari parameter ini dan dijalankan lewat cron atau skrip housekeeping, topik yang bersambung dengan seluruh rutinitas pemeliharaan lain di chapter Database Maintenance .

Log yang terkurasi baik adalah sumber kebenaran pasca-kejadian, tetapi log tetap bersifat reaktif — semuanya tercatat setelah terjadi. Pekerjaan administrasi berikutnya bersifat proaktif: memastikan server tidak tumbang oleh banjir koneksi aplikasi, yang kita tangani lewat connection pooling di chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

log-koneksi.sh

Unduh berkas
bash
# log_disconnections hanya boleh dipasang saat koneksi dibuat,
# jadi psql dijalankan dengan PGOPTIONS yang membawa parameter itu
# di paket startup koneksi.
PGOPTIONS="-c log_disconnections=on" psql -X -c "SELECT 1 AS uji_koneksi;"

log-min-duration-server.sql

Unduh berkas
sql
-- File yang sama dengan log-min-duration.sql, dijalankan ulang
-- untuk menangkap baris duration di server log.
-- Angka durasi (mis. 601.547 ms) bervariasi antar mesin dan
-- antar eksekusi; ambang 500 ms-nya yang tetap.
SET log_min_duration_statement = 500;

SELECT 1 AS secepat_kilat;

SELECT pg_sleep(0.6);

SET log_min_duration_statement = -1;

log-min-duration.sql

Unduh berkas
sql
-- Ambang 500 ms: query yang lebih cepat tidak dicatat,
-- query yang lebih lambat dicatat beserta durasinya.
SET log_min_duration_statement = 500;

SELECT 1 AS secepat_kilat;

SELECT pg_sleep(0.6);

-- Kembalikan ke nilai server (-1 = tidak ada ambang).
SET log_min_duration_statement = -1;

log-statement-server.sql

Unduh berkas
sql
-- File yang sama dengan log-statement.sql, dijalankan ulang
-- untuk menangkap jejaknya di server log.
-- Output blok ini bukan layar psql, melainkan potongan nyata
-- dari file server log -- timestamp, pid, dan nomor baris
-- akan berbeda di mesin Anda.
SET log_statement = 'ddl';
CREATE TABLE pelatihan_ddl (id integer);

SET log_statement = 'mod';
INSERT INTO pelatihan_ddl VALUES (1);
UPDATE pelatihan_ddl SET id = 2;
SELECT id FROM pelatihan_ddl;

SET log_statement = 'all';
SELECT count(*) FROM pelatihan_ddl;

SET log_statement = 'none';
DELETE FROM pelatihan_ddl;

log-statement.sql

Unduh berkas
sql
-- Tur level log_statement dalam satu sesi.
-- SHOW setelah SET memperlihatkan level yang sedang aktif.
SET log_statement = 'ddl';
SHOW log_statement;
CREATE TABLE pelatihan_ddl (id integer);

SET log_statement = 'mod';
SHOW log_statement;
INSERT INTO pelatihan_ddl VALUES (1);
UPDATE pelatihan_ddl SET id = 2;
SELECT id FROM pelatihan_ddl;

SET log_statement = 'all';
SHOW log_statement;
SELECT count(*) FROM pelatihan_ddl;

SET log_statement = 'none';
SHOW log_statement;
DELETE FROM pelatihan_ddl;

pengaturan-log.sql

Unduh berkas
sql
-- Konfigurasi logging server saat ini, dibaca dari pg_settings.
-- Setiap baris adalah parameter yang mengendalikan APA yang ditulis
-- ke server log, BUKAN log itu sendiri.
SELECT name, setting, unit
FROM pg_settings
WHERE name IN ('log_destination',
               'logging_collector',
               'log_filename',
               'log_rotation_age',
               'log_rotation_size',
               'log_line_prefix',
               'log_statement',
               'log_min_duration_statement')
ORDER BY name;