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
WaitEventSetWaiton the pipe and its latch). - At the next
log_rotation_ageboundary. Time-based rotation is aligned to the clock: with1da new file starts at midnight (inlog_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 changeslog_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: loggerSHOW 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):
Support notes
logging_collectorneeds 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 inlog/inside PGDATA by default). Check before telling a customer where to look.- A relative
log_directoryis 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/eventlogdestinations the logger is not involved; messages go to the OS facility directly.
For developers: inside the source
Where it lives
syslogger.c: the whole process:SysLogger_Start()(run by the postmaster) andSysLoggerMain().syslogger.h: the pipe protocol (PipeProtoHeader,PipeProtoChunk,PIPE_CHUNK_SIZE).elog.c,csvlog.c,jsonlog.c: the sending side.
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.plcovers rotation,current_logfilesand csvlog/jsonlog. emit_log_hook(elog.h) sees everyErrorDatabefore 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