DB Internals AtlasPostgreSQL 18.6 Home About

Background processes

Logger (logging collector)

With logging_collector = on, one extra process reads the log output of all other processes through a pipe and writes it to files in log_directory, rotating them by age or size.

In plain words

Every PostgreSQL process writes messages: errors, slow queries, checkpoints, connections. Without help, those messages simply go to the place the server was started from (the terminal, a file given to pg_ctl -l, or the systemd journal), all mixed together.

When logging_collector = on, the postmaster starts one more helper, the logger. All the other processes hand their messages to it through a pipe, and it is the only one that writes the log files. That means one tidy set of files in log_directory, with names like postgresql-2026-10-22_153409.log.

The logger also rotates the files: it starts a new one after log_rotation_age (default 1 day) or when the current one reaches log_rotation_size (default 10 MB). PostgreSQL never deletes old log files by itself, so plan for clean-up.

Try it: switch the collector off and on, change the rotation settings and the log volume, and watch the files appear.

For DBAs and operators

What it does

The postmaster creates a pipe before it forks anything else and redirects its own stderr into it, so every child inherits it. Backends write each message to the pipe in small chunks with a header (PID, length), and the logger reassembles chunks from different processes so that lines from concurrent processes are not interleaved mid-line. It then writes the result to the current stderr, csvlog (.csv) and/or jsonlog (.json, since PostgreSQL 15) file. CSV and JSON output need the collector: without it, those entries fall back to plain text on stderr. With the collector on, the plain .log file is always opened too, even when log_destination lists only csvlog/jsonlog; it receives output that does not go through the logging system.

When the collector starts, the postmaster itself still prints two lines on the original stderr:

LOG: redirecting log output to logging collector processHINT: Future log output will appear in directory "log".

When it wakes

  • Whenever data arrives on the pipe (it sleeps in WaitEventSetWait on the pipe and its latch).
  • At the next log_rotation_age boundary. Time-based rotation is aligned to the clock: with 1d a new file starts at midnight (in log_timezone).
  • After every write it checks the file size against log_rotation_size.
  • On SELECT pg_rotate_logfile();, pg_ctl logrotate, or a SIGHUP that changes log_directory/log_filename.

File names and truncation

log_filename is a strftime pattern; the default postgresql-%Y-%m-%d_%H%M%S.log gives a new name each rotation, so files accumulate forever. A pattern like postgresql-%a.log plus log_truncate_on_rotation = on keeps a rolling week of files. Truncation happens only on time-based rotation, never on size-based rotation or at server start (those append). The file permissions come from log_file_mode (0600).

Seeing it

The logger is not attached to shared memory, so it does not appear in pg_stat_activity. Use ps:

postgres  3296  3295  0 15:34 ?  00:00:00 postgres: logger
SHOW logging_collector;
SELECT pg_current_logfile();          -- first of: stderr, csvlog, jsonlog file
SELECT pg_current_logfile('csvlog');
SELECT * FROM pg_ls_logdir() ORDER BY modification DESC LIMIT 5;

current_logfiles in the data directory lists the files in use.

If it dies

The postmaster simply starts a new logger; it is not a crash of the cluster and other sessions are not affected. The new logger opens a log file named for the current time (with the default log_filename, a new file):

LOG: system logger process (PID 3296) was terminated by signal 9: Killed

Support notes

  • logging_collector needs a restart. Packages differ: Debian/Ubuntu (pg_ctlcluster) logs to /var/log/postgresql/ without the collector; other packages and container images may turn it on (then the files are in log/ inside PGDATA by default). Check before telling a customer where to look.
  • A relative log_directory is relative to the data directory, so a full log disk can fill the data volume.
  • If the logger cannot keep up (very verbose log_statement = all, slow disk), backends block writing to the pipe: queries slow down for no visible reason. Reduce logging or log to faster storage.
  • For syslog/eventlog destinations the logger is not involved; messages go to the OS facility directly.

For developers: inside the source

Where it lives

The pipe protocol

Once redirection_done is set, send_message_to_server_log() (and write_csvlog() / write_jsonlog()) call write_pipe_chunks(). It cuts the message into chunks of at most PIPE_CHUNK_SIZE bytes (PIPE_BUF, capped at 64 kB), so each write() is atomic. Every chunk carries a header:

char  nuls[2];   /* always \0\0 */
uint16 len;      /* payload bytes in this chunk */
int32 pid;       /* sender PID */
bits8 flags;     /* PIPE_PROTO_IS_LAST | PIPE_PROTO_DEST_{STDERR,CSVLOG,JSONLOG} */

On the reading side, process_pipe_input() validates headers, keeps partial messages per PID in save_buffer lists until the PIPE_PROTO_IS_LAST chunk arrives, then calls write_syslogger_file() with the destination. Bytes that do not look like a chunk (for example output of a crashing library writing straight to stderr) are written as-is to the stderr log file. flush_pipe_input() dumps whatever is left at EOF.

Main loop

SysLoggerMain() ignores SIGTERM/SIGINT/SIGQUIT and even postmaster death: it runs until read() on syslogPipe[0] returns EOF, i.e. every process holding the write end is gone, so it catches the last messages of dying backends. Each iteration: handle SIGHUP (a changed log_directory/log_filename or toggled csvlog/jsonlog forces a rotation), check next_rotation_time and ftello() against log_rotation_size, call logfile_rotate() → logfile_rotate_dest() if needed, then WaitEventSetWait() (wait event SysLoggerMain) with a timeout up to the next time-based rotation. After a rotation update_metainfo_datafile() rewrites current_logfiles.

Rotation requests: pg_rotate_logfile() (in signalfuncs.c) sends PMSIGNAL_ROTATE_LOGFILE; pg_ctl logrotate creates a logrotate file in PGDATA and signals the postmaster. Either way the postmaster forwards SIGUSR1 to the logger. On a restart the postmaster keeps the read end of the same pipe open, because running backends still write into it.

Experimenting

  • TAP test 004_logrotate.pl covers rotation, current_logfiles and csvlog/jsonlog.
  • emit_log_hook (elog.h) sees every ErrorData before it is formatted and sent, which is how extensions ship logs elsewhere; it runs in the process that raised the message, not in the logger.
  • Watch a backend's side with strace -e write -p <pid>: each log line is a write to fd 2 starting with two NUL bytes.

Parameters that shape this

logging_collector log_destination log_directory log_filename log_rotation_age log_rotation_size log_truncate_on_rotation log_file_mode log_line_prefix

Related topics

Postmaster Process model Client backend Checkpointer Physical layout on disk