Skip to content

Commit 48bc1a5

Browse files
committed
Make syslogger more robust against failures in opening CSV log files.
The previous coding figured it'd be good enough to postpone opening the first CSV log file until we got a message we needed to write there. This is unsafe, though, because if the open fails we end up in infinite recursion trying to report the failure. Instead make the CSV log file management code look as nearly as possible like the longstanding logic for the stderr log file. In particular, open it immediately at postmaster startup (if enabled), or when we get a SIGHUP in which we find that log_destination has been changed to enable CSV logging. It seems OK to fail if a postmaster-start-time open attempt fails, as we've long done for the stderr log file. But we can't die if we fail to open a CSV log file during SIGHUP, so we're still left with a problem. In that case, write any output meant for the CSV log file to the stderr log file. (This will also cover race-condition cases in which backends send CSV log data before or after we have the CSV log file open.) This patch also fixes an ancient oversight that, if CSV logging was turned off during a SIGHUP, we never actually closed the last CSV log file. In passing, remember to reset whereToSendOutput = DestNone during syslogger start, since (unlike all other postmaster children) it's forked before the postmaster has done that. This made for a platform-dependent difference in error reporting behavior between the syslogger and other children: except on Windows, it'd report problems to the original postmaster stderr as well as the normal error log file(s). It's barely possible that that was intentional at some point; but it doesn't seem likely to be desirable in production, and the platform dependency definitely isn't desirable. Per report from Alexander Kukushkin. It's been like this for a long time, so back-patch to all supported branches. Discussion: https://postgr.es/m/CAFh8B==iLUD_gqC-dAENS0V+kVrCeGiKujtKqSQ7++S-caaChw@mail.gmail.com
1 parent fe6131b commit 48bc1a5

File tree

1 file changed

+123
-37
lines changed

1 file changed

+123
-37
lines changed

src/backend/postmaster/syslogger.c

Lines changed: 123 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "storage/ipc.h"
4444
#include "storage/latch.h"
4545
#include "storage/pg_shmem.h"
46+
#include "tcop/tcopprot.h"
4647
#include "utils/guc.h"
4748
#include "utils/ps_status.h"
4849
#include "utils/timestamp.h"
@@ -134,7 +135,6 @@ static void syslogger_parseArgs(int argc, char *argv[]);
134135
NON_EXEC_STATIC void SysLoggerMain(int argc, char *argv[]) __attribute__((noreturn));
135136
static void process_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
136137
static void flush_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
137-
static void open_csvlogfile(void);
138138
static FILE *logfile_open(const char *filename, const char *mode,
139139
bool allow_errors);
140140

@@ -291,11 +291,13 @@ SysLoggerMain(int argc, char *argv[])
291291
#endif /* WIN32 */
292292

293293
/*
294-
* Remember active logfile's name. We recompute this from the reference
294+
* Remember active logfiles' name(s). We recompute 'em from the reference
295295
* time because passing down just the pg_time_t is a lot cheaper than
296296
* passing a whole file path in the EXEC_BACKEND case.
297297
*/
298298
last_file_name = logfile_getname(first_syslogger_file_time, NULL);
299+
if (csvlogFile != NULL)
300+
last_csv_file_name = logfile_getname(first_syslogger_file_time, ".csv");
299301

300302
/* remember active logfile parameters */
301303
currentLogDir = pstrdup(Log_directory);
@@ -304,6 +306,13 @@ SysLoggerMain(int argc, char *argv[])
304306
/* set next planned rotation time */
305307
set_next_rotation_time();
306308

309+
/*
310+
* Reset whereToSendOutput, as the postmaster will do (but hasn't yet, at
311+
* the point where we forked). This prevents duplicate output of messages
312+
* from syslogger itself.
313+
*/
314+
whereToSendOutput = DestNone;
315+
307316
/* main worker loop */
308317
for (;;)
309318
{
@@ -350,6 +359,14 @@ SysLoggerMain(int argc, char *argv[])
350359
rotation_requested = true;
351360
}
352361

362+
/*
363+
* Force a rotation if CSVLOG output was just turned on or off and
364+
* we need to open or close csvlogFile accordingly.
365+
*/
366+
if (((Log_destination & LOG_DESTINATION_CSVLOG) != 0) !=
367+
(csvlogFile != NULL))
368+
rotation_requested = true;
369+
353370
/*
354371
* If rotation time parameter changed, reset next rotation time,
355372
* but don't immediately force a rotation.
@@ -589,12 +606,27 @@ SysLogger_Start(void)
589606
* a time-based rotation.
590607
*/
591608
first_syslogger_file_time = time(NULL);
609+
592610
filename = logfile_getname(first_syslogger_file_time, NULL);
593611

594612
syslogFile = logfile_open(filename, "a", false);
595613

596614
pfree(filename);
597615

616+
/*
617+
* Likewise for the initial CSV log file, if that's enabled. (Note that
618+
* we open syslogFile even when only CSV output is nominally enabled,
619+
* since some code paths will write to syslogFile anyway.)
620+
*/
621+
if (Log_destination & LOG_DESTINATION_CSVLOG)
622+
{
623+
filename = logfile_getname(first_syslogger_file_time, ".csv");
624+
625+
csvlogFile = logfile_open(filename, "a", false);
626+
627+
pfree(filename);
628+
}
629+
598630
#ifdef EXEC_BACKEND
599631
switch ((sysloggerPid = syslogger_forkexec()))
600632
#else
@@ -685,9 +717,14 @@ SysLogger_Start(void)
685717
redirection_done = true;
686718
}
687719

688-
/* postmaster will never write the file; close it */
720+
/* postmaster will never write the file(s); close 'em */
689721
fclose(syslogFile);
690722
syslogFile = NULL;
723+
if (csvlogFile != NULL)
724+
{
725+
fclose(csvlogFile);
726+
csvlogFile = NULL;
727+
}
691728
return (int) sysloggerPid;
692729
}
693730

@@ -709,6 +746,7 @@ syslogger_forkexec(void)
709746
char *av[10];
710747
int ac = 0;
711748
char filenobuf[32];
749+
char csvfilenobuf[32];
712750

713751
av[ac++] = "postgres";
714752
av[ac++] = "--forklog";
@@ -730,6 +768,21 @@ syslogger_forkexec(void)
730768
#endif /* WIN32 */
731769
av[ac++] = filenobuf;
732770

771+
#ifndef WIN32
772+
if (csvlogFile != NULL)
773+
snprintf(csvfilenobuf, sizeof(csvfilenobuf), "%d",
774+
fileno(csvlogFile));
775+
else
776+
strcpy(csvfilenobuf, "-1");
777+
#else /* WIN32 */
778+
if (csvlogFile != NULL)
779+
snprintf(csvfilenobuf, sizeof(csvfilenobuf), "%ld",
780+
(long) _get_osfhandle(_fileno(csvlogFile)));
781+
else
782+
strcpy(csvfilenobuf, "0");
783+
#endif /* WIN32 */
784+
av[ac++] = csvfilenobuf;
785+
733786
av[ac] = NULL;
734787
Assert(ac < lengthof(av));
735788

@@ -746,16 +799,29 @@ syslogger_parseArgs(int argc, char *argv[])
746799
{
747800
int fd;
748801

749-
Assert(argc == 4);
802+
Assert(argc == 5);
750803
argv += 3;
751804

805+
/*
806+
* Re-open the error output files that were opened by SysLogger_Start().
807+
*
808+
* We expect this will always succeed, which is too optimistic, but if it
809+
* fails there's not a lot we can do to report the problem anyway. As
810+
* coded, we'll just crash on a null pointer dereference after failure...
811+
*/
752812
#ifndef WIN32
753813
fd = atoi(*argv++);
754814
if (fd != -1)
755815
{
756816
syslogFile = fdopen(fd, "a");
757817
setvbuf(syslogFile, NULL, PG_IOLBF, 0);
758818
}
819+
fd = atoi(*argv++);
820+
if (fd != -1)
821+
{
822+
csvlogFile = fdopen(fd, "a");
823+
setvbuf(csvlogFile, NULL, PG_IOLBF, 0);
824+
}
759825
#else /* WIN32 */
760826
fd = atoi(*argv++);
761827
if (fd != 0)
@@ -767,6 +833,16 @@ syslogger_parseArgs(int argc, char *argv[])
767833
setvbuf(syslogFile, NULL, PG_IOLBF, 0);
768834
}
769835
}
836+
fd = atoi(*argv++);
837+
if (fd != 0)
838+
{
839+
fd = _open_osfhandle(fd, _O_APPEND | _O_TEXT);
840+
if (fd > 0)
841+
{
842+
csvlogFile = fdopen(fd, "a");
843+
setvbuf(csvlogFile, NULL, PG_IOLBF, 0);
844+
}
845+
}
770846
#endif /* WIN32 */
771847
}
772848
#endif /* EXEC_BACKEND */
@@ -1008,13 +1084,29 @@ write_syslogger_file(const char *buffer, int count, int destination)
10081084
int rc;
10091085
FILE *logfile;
10101086

1011-
if (destination == LOG_DESTINATION_CSVLOG && csvlogFile == NULL)
1012-
open_csvlogfile();
1087+
/*
1088+
* If we're told to write to csvlogFile, but it's not open, dump the data
1089+
* to syslogFile (which is always open) instead. This can happen if CSV
1090+
* output is enabled after postmaster start and we've been unable to open
1091+
* csvlogFile. There are also race conditions during a parameter change
1092+
* whereby backends might send us CSV output before we open csvlogFile or
1093+
* after we close it. Writing CSV-formatted output to the regular log
1094+
* file isn't great, but it beats dropping log output on the floor.
1095+
*
1096+
* Think not to improve this by trying to open csvlogFile on-the-fly. Any
1097+
* failure in that would lead to recursion.
1098+
*/
1099+
logfile = (destination == LOG_DESTINATION_CSVLOG &&
1100+
csvlogFile != NULL) ? csvlogFile : syslogFile;
10131101

1014-
logfile = destination == LOG_DESTINATION_CSVLOG ? csvlogFile : syslogFile;
10151102
rc = fwrite(buffer, 1, count, logfile);
10161103

1017-
/* can't use ereport here because of possible recursion */
1104+
/*
1105+
* Try to report any failure. We mustn't use ereport because it would
1106+
* just recurse right back here, but write_stderr is OK: it will write
1107+
* either to the postmaster's original stderr, or to /dev/null, but never
1108+
* to our input pipe which would result in a different sort of looping.
1109+
*/
10181110
if (rc != count)
10191111
write_stderr("could not write to log file: %s\n", strerror(errno));
10201112
}
@@ -1097,29 +1189,6 @@ pipeThread(void *arg)
10971189
}
10981190
#endif /* WIN32 */
10991191

1100-
/*
1101-
* Open the csv log file - we do this opportunistically, because
1102-
* we don't know if CSV logging will be wanted.
1103-
*
1104-
* This is only used the first time we open the csv log in a given syslogger
1105-
* process, not during rotations. As with opening the main log file, we
1106-
* always append in this situation.
1107-
*/
1108-
static void
1109-
open_csvlogfile(void)
1110-
{
1111-
char *filename;
1112-
1113-
filename = logfile_getname(time(NULL), ".csv");
1114-
1115-
csvlogFile = logfile_open(filename, "a", false);
1116-
1117-
if (last_csv_file_name != NULL) /* probably shouldn't happen */
1118-
pfree(last_csv_file_name);
1119-
1120-
last_csv_file_name = filename;
1121-
}
1122-
11231192
/*
11241193
* Open a new logfile with proper permissions and buffering options.
11251194
*
@@ -1187,7 +1256,7 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
11871256
else
11881257
fntime = time(NULL);
11891258
filename = logfile_getname(fntime, NULL);
1190-
if (csvlogFile != NULL)
1259+
if (Log_destination & LOG_DESTINATION_CSVLOG)
11911260
csvfilename = logfile_getname(fntime, ".csv");
11921261

11931262
/*
@@ -1239,10 +1308,16 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12391308
filename = NULL;
12401309
}
12411310

1242-
/* Same as above, but for csv file. */
1243-
1244-
if (csvlogFile != NULL &&
1245-
(time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
1311+
/*
1312+
* Same as above, but for csv file. Note that if LOG_DESTINATION_CSVLOG
1313+
* was just turned on, we might have to open csvlogFile here though it was
1314+
* not open before. In such a case we'll append not overwrite (since
1315+
* last_csv_file_name will be NULL); that is consistent with the normal
1316+
* rules since it's not a time-based rotation.
1317+
*/
1318+
if ((Log_destination & LOG_DESTINATION_CSVLOG) &&
1319+
(csvlogFile == NULL ||
1320+
time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
12461321
{
12471322
if (Log_truncate_on_rotation && time_based_rotation &&
12481323
last_csv_file_name != NULL &&
@@ -1273,7 +1348,8 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12731348
return;
12741349
}
12751350

1276-
fclose(csvlogFile);
1351+
if (csvlogFile != NULL)
1352+
fclose(csvlogFile);
12771353
csvlogFile = fh;
12781354

12791355
/* instead of pfree'ing filename, remember it for next time */
@@ -1282,6 +1358,16 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12821358
last_csv_file_name = csvfilename;
12831359
csvfilename = NULL;
12841360
}
1361+
else if (!(Log_destination & LOG_DESTINATION_CSVLOG) &&
1362+
csvlogFile != NULL)
1363+
{
1364+
/* CSVLOG was just turned off, so close the old file */
1365+
fclose(csvlogFile);
1366+
csvlogFile = NULL;
1367+
if (last_csv_file_name != NULL)
1368+
pfree(last_csv_file_name);
1369+
last_csv_file_name = NULL;
1370+
}
12851371

12861372
if (filename)
12871373
pfree(filename);

0 commit comments

Comments
 (0)