Skip to content

Commit 23f21e0

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 ebc0d6b commit 23f21e0

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
@@ -42,6 +42,7 @@
4242
#include "storage/ipc.h"
4343
#include "storage/latch.h"
4444
#include "storage/pg_shmem.h"
45+
#include "tcop/tcopprot.h"
4546
#include "utils/guc.h"
4647
#include "utils/ps_status.h"
4748
#include "utils/timestamp.h"
@@ -133,7 +134,6 @@ static void syslogger_parseArgs(int argc, char *argv[]);
133134
NON_EXEC_STATIC void SysLoggerMain(int argc, char *argv[]) __attribute__((noreturn));
134135
static void process_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
135136
static void flush_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
136-
static void open_csvlogfile(void);
137137
static FILE *logfile_open(const char *filename, const char *mode,
138138
bool allow_errors);
139139

@@ -285,11 +285,13 @@ SysLoggerMain(int argc, char *argv[])
285285
#endif /* WIN32 */
286286

287287
/*
288-
* Remember active logfile's name. We recompute this from the reference
288+
* Remember active logfiles' name(s). We recompute 'em from the reference
289289
* time because passing down just the pg_time_t is a lot cheaper than
290290
* passing a whole file path in the EXEC_BACKEND case.
291291
*/
292292
last_file_name = logfile_getname(first_syslogger_file_time, NULL);
293+
if (csvlogFile != NULL)
294+
last_csv_file_name = logfile_getname(first_syslogger_file_time, ".csv");
293295

294296
/* remember active logfile parameters */
295297
currentLogDir = pstrdup(Log_directory);
@@ -298,6 +300,13 @@ SysLoggerMain(int argc, char *argv[])
298300
/* set next planned rotation time */
299301
set_next_rotation_time();
300302

303+
/*
304+
* Reset whereToSendOutput, as the postmaster will do (but hasn't yet, at
305+
* the point where we forked). This prevents duplicate output of messages
306+
* from syslogger itself.
307+
*/
308+
whereToSendOutput = DestNone;
309+
301310
/* main worker loop */
302311
for (;;)
303312
{
@@ -344,6 +353,14 @@ SysLoggerMain(int argc, char *argv[])
344353
rotation_requested = true;
345354
}
346355

356+
/*
357+
* Force a rotation if CSVLOG output was just turned on or off and
358+
* we need to open or close csvlogFile accordingly.
359+
*/
360+
if (((Log_destination & LOG_DESTINATION_CSVLOG) != 0) !=
361+
(csvlogFile != NULL))
362+
rotation_requested = true;
363+
347364
/*
348365
* If rotation time parameter changed, reset next rotation time,
349366
* but don't immediately force a rotation.
@@ -583,12 +600,27 @@ SysLogger_Start(void)
583600
* a time-based rotation.
584601
*/
585602
first_syslogger_file_time = time(NULL);
603+
586604
filename = logfile_getname(first_syslogger_file_time, NULL);
587605

588606
syslogFile = logfile_open(filename, "a", false);
589607

590608
pfree(filename);
591609

610+
/*
611+
* Likewise for the initial CSV log file, if that's enabled. (Note that
612+
* we open syslogFile even when only CSV output is nominally enabled,
613+
* since some code paths will write to syslogFile anyway.)
614+
*/
615+
if (Log_destination & LOG_DESTINATION_CSVLOG)
616+
{
617+
filename = logfile_getname(first_syslogger_file_time, ".csv");
618+
619+
csvlogFile = logfile_open(filename, "a", false);
620+
621+
pfree(filename);
622+
}
623+
592624
#ifdef EXEC_BACKEND
593625
switch ((sysloggerPid = syslogger_forkexec()))
594626
#else
@@ -677,9 +709,14 @@ SysLogger_Start(void)
677709
redirection_done = true;
678710
}
679711

680-
/* postmaster will never write the file; close it */
712+
/* postmaster will never write the file(s); close 'em */
681713
fclose(syslogFile);
682714
syslogFile = NULL;
715+
if (csvlogFile != NULL)
716+
{
717+
fclose(csvlogFile);
718+
csvlogFile = NULL;
719+
}
683720
return (int) sysloggerPid;
684721
}
685722

@@ -701,6 +738,7 @@ syslogger_forkexec(void)
701738
char *av[10];
702739
int ac = 0;
703740
char filenobuf[32];
741+
char csvfilenobuf[32];
704742

705743
av[ac++] = "postgres";
706744
av[ac++] = "--forklog";
@@ -722,6 +760,21 @@ syslogger_forkexec(void)
722760
#endif /* WIN32 */
723761
av[ac++] = filenobuf;
724762

763+
#ifndef WIN32
764+
if (csvlogFile != NULL)
765+
snprintf(csvfilenobuf, sizeof(csvfilenobuf), "%d",
766+
fileno(csvlogFile));
767+
else
768+
strcpy(csvfilenobuf, "-1");
769+
#else /* WIN32 */
770+
if (csvlogFile != NULL)
771+
snprintf(csvfilenobuf, sizeof(csvfilenobuf), "%ld",
772+
(long) _get_osfhandle(_fileno(csvlogFile)));
773+
else
774+
strcpy(csvfilenobuf, "0");
775+
#endif /* WIN32 */
776+
av[ac++] = csvfilenobuf;
777+
725778
av[ac] = NULL;
726779
Assert(ac < lengthof(av));
727780

@@ -738,16 +791,29 @@ syslogger_parseArgs(int argc, char *argv[])
738791
{
739792
int fd;
740793

741-
Assert(argc == 4);
794+
Assert(argc == 5);
742795
argv += 3;
743796

797+
/*
798+
* Re-open the error output files that were opened by SysLogger_Start().
799+
*
800+
* We expect this will always succeed, which is too optimistic, but if it
801+
* fails there's not a lot we can do to report the problem anyway. As
802+
* coded, we'll just crash on a null pointer dereference after failure...
803+
*/
744804
#ifndef WIN32
745805
fd = atoi(*argv++);
746806
if (fd != -1)
747807
{
748808
syslogFile = fdopen(fd, "a");
749809
setvbuf(syslogFile, NULL, PG_IOLBF, 0);
750810
}
811+
fd = atoi(*argv++);
812+
if (fd != -1)
813+
{
814+
csvlogFile = fdopen(fd, "a");
815+
setvbuf(csvlogFile, NULL, PG_IOLBF, 0);
816+
}
751817
#else /* WIN32 */
752818
fd = atoi(*argv++);
753819
if (fd != 0)
@@ -759,6 +825,16 @@ syslogger_parseArgs(int argc, char *argv[])
759825
setvbuf(syslogFile, NULL, PG_IOLBF, 0);
760826
}
761827
}
828+
fd = atoi(*argv++);
829+
if (fd != 0)
830+
{
831+
fd = _open_osfhandle(fd, _O_APPEND | _O_TEXT);
832+
if (fd > 0)
833+
{
834+
csvlogFile = fdopen(fd, "a");
835+
setvbuf(csvlogFile, NULL, PG_IOLBF, 0);
836+
}
837+
}
762838
#endif /* WIN32 */
763839
}
764840
#endif /* EXEC_BACKEND */
@@ -1000,13 +1076,29 @@ write_syslogger_file(const char *buffer, int count, int destination)
10001076
int rc;
10011077
FILE *logfile;
10021078

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

1006-
logfile = destination == LOG_DESTINATION_CSVLOG ? csvlogFile : syslogFile;
10071094
rc = fwrite(buffer, 1, count, logfile);
10081095

1009-
/* can't use ereport here because of possible recursion */
1096+
/*
1097+
* Try to report any failure. We mustn't use ereport because it would
1098+
* just recurse right back here, but write_stderr is OK: it will write
1099+
* either to the postmaster's original stderr, or to /dev/null, but never
1100+
* to our input pipe which would result in a different sort of looping.
1101+
*/
10101102
if (rc != count)
10111103
write_stderr("could not write to log file: %s\n", strerror(errno));
10121104
}
@@ -1089,29 +1181,6 @@ pipeThread(void *arg)
10891181
}
10901182
#endif /* WIN32 */
10911183

1092-
/*
1093-
* Open the csv log file - we do this opportunistically, because
1094-
* we don't know if CSV logging will be wanted.
1095-
*
1096-
* This is only used the first time we open the csv log in a given syslogger
1097-
* process, not during rotations. As with opening the main log file, we
1098-
* always append in this situation.
1099-
*/
1100-
static void
1101-
open_csvlogfile(void)
1102-
{
1103-
char *filename;
1104-
1105-
filename = logfile_getname(time(NULL), ".csv");
1106-
1107-
csvlogFile = logfile_open(filename, "a", false);
1108-
1109-
if (last_csv_file_name != NULL) /* probably shouldn't happen */
1110-
pfree(last_csv_file_name);
1111-
1112-
last_csv_file_name = filename;
1113-
}
1114-
11151184
/*
11161185
* Open a new logfile with proper permissions and buffering options.
11171186
*
@@ -1179,7 +1248,7 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
11791248
else
11801249
fntime = time(NULL);
11811250
filename = logfile_getname(fntime, NULL);
1182-
if (csvlogFile != NULL)
1251+
if (Log_destination & LOG_DESTINATION_CSVLOG)
11831252
csvfilename = logfile_getname(fntime, ".csv");
11841253

11851254
/*
@@ -1231,10 +1300,16 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12311300
filename = NULL;
12321301
}
12331302

1234-
/* Same as above, but for csv file. */
1235-
1236-
if (csvlogFile != NULL &&
1237-
(time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
1303+
/*
1304+
* Same as above, but for csv file. Note that if LOG_DESTINATION_CSVLOG
1305+
* was just turned on, we might have to open csvlogFile here though it was
1306+
* not open before. In such a case we'll append not overwrite (since
1307+
* last_csv_file_name will be NULL); that is consistent with the normal
1308+
* rules since it's not a time-based rotation.
1309+
*/
1310+
if ((Log_destination & LOG_DESTINATION_CSVLOG) &&
1311+
(csvlogFile == NULL ||
1312+
time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
12381313
{
12391314
if (Log_truncate_on_rotation && time_based_rotation &&
12401315
last_csv_file_name != NULL &&
@@ -1265,7 +1340,8 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12651340
return;
12661341
}
12671342

1268-
fclose(csvlogFile);
1343+
if (csvlogFile != NULL)
1344+
fclose(csvlogFile);
12691345
csvlogFile = fh;
12701346

12711347
/* instead of pfree'ing filename, remember it for next time */
@@ -1274,6 +1350,16 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12741350
last_csv_file_name = csvfilename;
12751351
csvfilename = NULL;
12761352
}
1353+
else if (!(Log_destination & LOG_DESTINATION_CSVLOG) &&
1354+
csvlogFile != NULL)
1355+
{
1356+
/* CSVLOG was just turned off, so close the old file */
1357+
fclose(csvlogFile);
1358+
csvlogFile = NULL;
1359+
if (last_csv_file_name != NULL)
1360+
pfree(last_csv_file_name);
1361+
last_csv_file_name = NULL;
1362+
}
12771363

12781364
if (filename)
12791365
pfree(filename);

0 commit comments

Comments
 (0)