Skip to content

Commit 6fbbe33

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 993b5a7 commit 6fbbe33

File tree

1 file changed

+123
-39
lines changed

1 file changed

+123
-39
lines changed

src/backend/postmaster/syslogger.c

Lines changed: 123 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "storage/ipc.h"
4545
#include "storage/latch.h"
4646
#include "storage/pg_shmem.h"
47+
#include "tcop/tcopprot.h"
4748
#include "utils/guc.h"
4849
#include "utils/ps_status.h"
4950
#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[]) pg_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

@@ -271,11 +271,13 @@ SysLoggerMain(int argc, char *argv[])
271271
#endif /* WIN32 */
272272

273273
/*
274-
* Remember active logfile's name. We recompute this from the reference
274+
* Remember active logfiles' name(s). We recompute 'em from the reference
275275
* time because passing down just the pg_time_t is a lot cheaper than
276276
* passing a whole file path in the EXEC_BACKEND case.
277277
*/
278278
last_file_name = logfile_getname(first_syslogger_file_time, NULL);
279+
if (csvlogFile != NULL)
280+
last_csv_file_name = logfile_getname(first_syslogger_file_time, ".csv");
279281

280282
/* remember active logfile parameters */
281283
currentLogDir = pstrdup(Log_directory);
@@ -285,6 +287,13 @@ SysLoggerMain(int argc, char *argv[])
285287
set_next_rotation_time();
286288
update_metainfo_datafile();
287289

290+
/*
291+
* Reset whereToSendOutput, as the postmaster will do (but hasn't yet, at
292+
* the point where we forked). This prevents duplicate output of messages
293+
* from syslogger itself.
294+
*/
295+
whereToSendOutput = DestNone;
296+
288297
/* main worker loop */
289298
for (;;)
290299
{
@@ -331,6 +340,14 @@ SysLoggerMain(int argc, char *argv[])
331340
rotation_requested = true;
332341
}
333342

343+
/*
344+
* Force a rotation if CSVLOG output was just turned on or off and
345+
* we need to open or close csvlogFile accordingly.
346+
*/
347+
if (((Log_destination & LOG_DESTINATION_CSVLOG) != 0) !=
348+
(csvlogFile != NULL))
349+
rotation_requested = true;
350+
334351
/*
335352
* If rotation time parameter changed, reset next rotation time,
336353
* but don't immediately force a rotation.
@@ -579,12 +596,27 @@ SysLogger_Start(void)
579596
* a time-based rotation.
580597
*/
581598
first_syslogger_file_time = time(NULL);
599+
582600
filename = logfile_getname(first_syslogger_file_time, NULL);
583601

584602
syslogFile = logfile_open(filename, "a", false);
585603

586604
pfree(filename);
587605

606+
/*
607+
* Likewise for the initial CSV log file, if that's enabled. (Note that
608+
* we open syslogFile even when only CSV output is nominally enabled,
609+
* since some code paths will write to syslogFile anyway.)
610+
*/
611+
if (Log_destination & LOG_DESTINATION_CSVLOG)
612+
{
613+
filename = logfile_getname(first_syslogger_file_time, ".csv");
614+
615+
csvlogFile = logfile_open(filename, "a", false);
616+
617+
pfree(filename);
618+
}
619+
588620
#ifdef EXEC_BACKEND
589621
switch ((sysloggerPid = syslogger_forkexec()))
590622
#else
@@ -674,9 +706,14 @@ SysLogger_Start(void)
674706
redirection_done = true;
675707
}
676708

677-
/* postmaster will never write the file; close it */
709+
/* postmaster will never write the file(s); close 'em */
678710
fclose(syslogFile);
679711
syslogFile = NULL;
712+
if (csvlogFile != NULL)
713+
{
714+
fclose(csvlogFile);
715+
csvlogFile = NULL;
716+
}
680717
return (int) sysloggerPid;
681718
}
682719

@@ -698,6 +735,7 @@ syslogger_forkexec(void)
698735
char *av[10];
699736
int ac = 0;
700737
char filenobuf[32];
738+
char csvfilenobuf[32];
701739

702740
av[ac++] = "postgres";
703741
av[ac++] = "--forklog";
@@ -719,6 +757,21 @@ syslogger_forkexec(void)
719757
#endif /* WIN32 */
720758
av[ac++] = filenobuf;
721759

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

@@ -735,16 +788,29 @@ syslogger_parseArgs(int argc, char *argv[])
735788
{
736789
int fd;
737790

738-
Assert(argc == 4);
791+
Assert(argc == 5);
739792
argv += 3;
740793

794+
/*
795+
* Re-open the error output files that were opened by SysLogger_Start().
796+
*
797+
* We expect this will always succeed, which is too optimistic, but if it
798+
* fails there's not a lot we can do to report the problem anyway. As
799+
* coded, we'll just crash on a null pointer dereference after failure...
800+
*/
741801
#ifndef WIN32
742802
fd = atoi(*argv++);
743803
if (fd != -1)
744804
{
745805
syslogFile = fdopen(fd, "a");
746806
setvbuf(syslogFile, NULL, PG_IOLBF, 0);
747807
}
808+
fd = atoi(*argv++);
809+
if (fd != -1)
810+
{
811+
csvlogFile = fdopen(fd, "a");
812+
setvbuf(csvlogFile, NULL, PG_IOLBF, 0);
813+
}
748814
#else /* WIN32 */
749815
fd = atoi(*argv++);
750816
if (fd != 0)
@@ -756,6 +822,16 @@ syslogger_parseArgs(int argc, char *argv[])
756822
setvbuf(syslogFile, NULL, PG_IOLBF, 0);
757823
}
758824
}
825+
fd = atoi(*argv++);
826+
if (fd != 0)
827+
{
828+
fd = _open_osfhandle(fd, _O_APPEND | _O_TEXT);
829+
if (fd > 0)
830+
{
831+
csvlogFile = fdopen(fd, "a");
832+
setvbuf(csvlogFile, NULL, PG_IOLBF, 0);
833+
}
834+
}
759835
#endif /* WIN32 */
760836
}
761837
#endif /* EXEC_BACKEND */
@@ -997,13 +1073,29 @@ write_syslogger_file(const char *buffer, int count, int destination)
9971073
int rc;
9981074
FILE *logfile;
9991075

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

1003-
logfile = destination == LOG_DESTINATION_CSVLOG ? csvlogFile : syslogFile;
10041091
rc = fwrite(buffer, 1, count, logfile);
10051092

1006-
/* can't use ereport here because of possible recursion */
1093+
/*
1094+
* Try to report any failure. We mustn't use ereport because it would
1095+
* just recurse right back here, but write_stderr is OK: it will write
1096+
* either to the postmaster's original stderr, or to /dev/null, but never
1097+
* to our input pipe which would result in a different sort of looping.
1098+
*/
10071099
if (rc != count)
10081100
write_stderr("could not write to log file: %s\n", strerror(errno));
10091101
}
@@ -1086,31 +1178,6 @@ pipeThread(void *arg)
10861178
}
10871179
#endif /* WIN32 */
10881180

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

11841251
/*
@@ -1230,10 +1297,16 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12301297
filename = NULL;
12311298
}
12321299

1233-
/* Same as above, but for csv file. */
1234-
1235-
if (csvlogFile != NULL &&
1236-
(time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
1300+
/*
1301+
* Same as above, but for csv file. Note that if LOG_DESTINATION_CSVLOG
1302+
* was just turned on, we might have to open csvlogFile here though it was
1303+
* not open before. In such a case we'll append not overwrite (since
1304+
* last_csv_file_name will be NULL); that is consistent with the normal
1305+
* rules since it's not a time-based rotation.
1306+
*/
1307+
if ((Log_destination & LOG_DESTINATION_CSVLOG) &&
1308+
(csvlogFile == NULL ||
1309+
time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
12371310
{
12381311
if (Log_truncate_on_rotation && time_based_rotation &&
12391312
last_csv_file_name != NULL &&
@@ -1264,7 +1337,8 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12641337
return;
12651338
}
12661339

1267-
fclose(csvlogFile);
1340+
if (csvlogFile != NULL)
1341+
fclose(csvlogFile);
12681342
csvlogFile = fh;
12691343

12701344
/* instead of pfree'ing filename, remember it for next time */
@@ -1273,6 +1347,16 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12731347
last_csv_file_name = csvfilename;
12741348
csvfilename = NULL;
12751349
}
1350+
else if (!(Log_destination & LOG_DESTINATION_CSVLOG) &&
1351+
csvlogFile != NULL)
1352+
{
1353+
/* CSVLOG was just turned off, so close the old file */
1354+
fclose(csvlogFile);
1355+
csvlogFile = NULL;
1356+
if (last_csv_file_name != NULL)
1357+
pfree(last_csv_file_name);
1358+
last_csv_file_name = NULL;
1359+
}
12761360

12771361
if (filename)
12781362
pfree(filename);

0 commit comments

Comments
 (0)