Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 93ca07f

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 c2ea62f commit 93ca07f

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"
@@ -133,7 +134,6 @@ static void syslogger_parseArgs(int argc, char *argv[]);
133134
NON_EXEC_STATIC void SysLoggerMain(int argc, char *argv[]) pg_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

@@ -269,11 +269,13 @@ SysLoggerMain(int argc, char *argv[])
269269
#endif /* WIN32 */
270270

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

278280
/* remember active logfile parameters */
279281
currentLogDir = pstrdup(Log_directory);
@@ -282,6 +284,13 @@ SysLoggerMain(int argc, char *argv[])
282284
/* set next planned rotation time */
283285
set_next_rotation_time();
284286

287+
/*
288+
* Reset whereToSendOutput, as the postmaster will do (but hasn't yet, at
289+
* the point where we forked). This prevents duplicate output of messages
290+
* from syslogger itself.
291+
*/
292+
whereToSendOutput = DestNone;
293+
285294
/* main worker loop */
286295
for (;;)
287296
{
@@ -328,6 +337,14 @@ SysLoggerMain(int argc, char *argv[])
328337
rotation_requested = true;
329338
}
330339

340+
/*
341+
* Force a rotation if CSVLOG output was just turned on or off and
342+
* we need to open or close csvlogFile accordingly.
343+
*/
344+
if (((Log_destination & LOG_DESTINATION_CSVLOG) != 0) !=
345+
(csvlogFile != NULL))
346+
rotation_requested = true;
347+
331348
/*
332349
* If rotation time parameter changed, reset next rotation time,
333350
* but don't immediately force a rotation.
@@ -567,12 +584,27 @@ SysLogger_Start(void)
567584
* a time-based rotation.
568585
*/
569586
first_syslogger_file_time = time(NULL);
587+
570588
filename = logfile_getname(first_syslogger_file_time, NULL);
571589

572590
syslogFile = logfile_open(filename, "a", false);
573591

574592
pfree(filename);
575593

594+
/*
595+
* Likewise for the initial CSV log file, if that's enabled. (Note that
596+
* we open syslogFile even when only CSV output is nominally enabled,
597+
* since some code paths will write to syslogFile anyway.)
598+
*/
599+
if (Log_destination & LOG_DESTINATION_CSVLOG)
600+
{
601+
filename = logfile_getname(first_syslogger_file_time, ".csv");
602+
603+
csvlogFile = logfile_open(filename, "a", false);
604+
605+
pfree(filename);
606+
}
607+
576608
#ifdef EXEC_BACKEND
577609
switch ((sysloggerPid = syslogger_forkexec()))
578610
#else
@@ -662,9 +694,14 @@ SysLogger_Start(void)
662694
redirection_done = true;
663695
}
664696

665-
/* postmaster will never write the file; close it */
697+
/* postmaster will never write the file(s); close 'em */
666698
fclose(syslogFile);
667699
syslogFile = NULL;
700+
if (csvlogFile != NULL)
701+
{
702+
fclose(csvlogFile);
703+
csvlogFile = NULL;
704+
}
668705
return (int) sysloggerPid;
669706
}
670707

@@ -686,6 +723,7 @@ syslogger_forkexec(void)
686723
char *av[10];
687724
int ac = 0;
688725
char filenobuf[32];
726+
char csvfilenobuf[32];
689727

690728
av[ac++] = "postgres";
691729
av[ac++] = "--forklog";
@@ -707,6 +745,21 @@ syslogger_forkexec(void)
707745
#endif /* WIN32 */
708746
av[ac++] = filenobuf;
709747

748+
#ifndef WIN32
749+
if (csvlogFile != NULL)
750+
snprintf(csvfilenobuf, sizeof(csvfilenobuf), "%d",
751+
fileno(csvlogFile));
752+
else
753+
strcpy(csvfilenobuf, "-1");
754+
#else /* WIN32 */
755+
if (csvlogFile != NULL)
756+
snprintf(csvfilenobuf, sizeof(csvfilenobuf), "%ld",
757+
(long) _get_osfhandle(_fileno(csvlogFile)));
758+
else
759+
strcpy(csvfilenobuf, "0");
760+
#endif /* WIN32 */
761+
av[ac++] = csvfilenobuf;
762+
710763
av[ac] = NULL;
711764
Assert(ac < lengthof(av));
712765

@@ -723,16 +776,29 @@ syslogger_parseArgs(int argc, char *argv[])
723776
{
724777
int fd;
725778

726-
Assert(argc == 4);
779+
Assert(argc == 5);
727780
argv += 3;
728781

782+
/*
783+
* Re-open the error output files that were opened by SysLogger_Start().
784+
*
785+
* We expect this will always succeed, which is too optimistic, but if it
786+
* fails there's not a lot we can do to report the problem anyway. As
787+
* coded, we'll just crash on a null pointer dereference after failure...
788+
*/
729789
#ifndef WIN32
730790
fd = atoi(*argv++);
731791
if (fd != -1)
732792
{
733793
syslogFile = fdopen(fd, "a");
734794
setvbuf(syslogFile, NULL, PG_IOLBF, 0);
735795
}
796+
fd = atoi(*argv++);
797+
if (fd != -1)
798+
{
799+
csvlogFile = fdopen(fd, "a");
800+
setvbuf(csvlogFile, NULL, PG_IOLBF, 0);
801+
}
736802
#else /* WIN32 */
737803
fd = atoi(*argv++);
738804
if (fd != 0)
@@ -744,6 +810,16 @@ syslogger_parseArgs(int argc, char *argv[])
744810
setvbuf(syslogFile, NULL, PG_IOLBF, 0);
745811
}
746812
}
813+
fd = atoi(*argv++);
814+
if (fd != 0)
815+
{
816+
fd = _open_osfhandle(fd, _O_APPEND | _O_TEXT);
817+
if (fd > 0)
818+
{
819+
csvlogFile = fdopen(fd, "a");
820+
setvbuf(csvlogFile, NULL, PG_IOLBF, 0);
821+
}
822+
}
747823
#endif /* WIN32 */
748824
}
749825
#endif /* EXEC_BACKEND */
@@ -985,13 +1061,29 @@ write_syslogger_file(const char *buffer, int count, int destination)
9851061
int rc;
9861062
FILE *logfile;
9871063

988-
if (destination == LOG_DESTINATION_CSVLOG && csvlogFile == NULL)
989-
open_csvlogfile();
1064+
/*
1065+
* If we're told to write to csvlogFile, but it's not open, dump the data
1066+
* to syslogFile (which is always open) instead. This can happen if CSV
1067+
* output is enabled after postmaster start and we've been unable to open
1068+
* csvlogFile. There are also race conditions during a parameter change
1069+
* whereby backends might send us CSV output before we open csvlogFile or
1070+
* after we close it. Writing CSV-formatted output to the regular log
1071+
* file isn't great, but it beats dropping log output on the floor.
1072+
*
1073+
* Think not to improve this by trying to open csvlogFile on-the-fly. Any
1074+
* failure in that would lead to recursion.
1075+
*/
1076+
logfile = (destination == LOG_DESTINATION_CSVLOG &&
1077+
csvlogFile != NULL) ? csvlogFile : syslogFile;
9901078

991-
logfile = destination == LOG_DESTINATION_CSVLOG ? csvlogFile : syslogFile;
9921079
rc = fwrite(buffer, 1, count, logfile);
9931080

994-
/* can't use ereport here because of possible recursion */
1081+
/*
1082+
* Try to report any failure. We mustn't use ereport because it would
1083+
* just recurse right back here, but write_stderr is OK: it will write
1084+
* either to the postmaster's original stderr, or to /dev/null, but never
1085+
* to our input pipe which would result in a different sort of looping.
1086+
*/
9951087
if (rc != count)
9961088
write_stderr("could not write to log file: %s\n", strerror(errno));
9971089
}
@@ -1074,29 +1166,6 @@ pipeThread(void *arg)
10741166
}
10751167
#endif /* WIN32 */
10761168

1077-
/*
1078-
* Open the csv log file - we do this opportunistically, because
1079-
* we don't know if CSV logging will be wanted.
1080-
*
1081-
* This is only used the first time we open the csv log in a given syslogger
1082-
* process, not during rotations. As with opening the main log file, we
1083-
* always append in this situation.
1084-
*/
1085-
static void
1086-
open_csvlogfile(void)
1087-
{
1088-
char *filename;
1089-
1090-
filename = logfile_getname(time(NULL), ".csv");
1091-
1092-
csvlogFile = logfile_open(filename, "a", false);
1093-
1094-
if (last_csv_file_name != NULL) /* probably shouldn't happen */
1095-
pfree(last_csv_file_name);
1096-
1097-
last_csv_file_name = filename;
1098-
}
1099-
11001169
/*
11011170
* Open a new logfile with proper permissions and buffering options.
11021171
*
@@ -1164,7 +1233,7 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
11641233
else
11651234
fntime = time(NULL);
11661235
filename = logfile_getname(fntime, NULL);
1167-
if (csvlogFile != NULL)
1236+
if (Log_destination & LOG_DESTINATION_CSVLOG)
11681237
csvfilename = logfile_getname(fntime, ".csv");
11691238

11701239
/*
@@ -1216,10 +1285,16 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12161285
filename = NULL;
12171286
}
12181287

1219-
/* Same as above, but for csv file. */
1220-
1221-
if (csvlogFile != NULL &&
1222-
(time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
1288+
/*
1289+
* Same as above, but for csv file. Note that if LOG_DESTINATION_CSVLOG
1290+
* was just turned on, we might have to open csvlogFile here though it was
1291+
* not open before. In such a case we'll append not overwrite (since
1292+
* last_csv_file_name will be NULL); that is consistent with the normal
1293+
* rules since it's not a time-based rotation.
1294+
*/
1295+
if ((Log_destination & LOG_DESTINATION_CSVLOG) &&
1296+
(csvlogFile == NULL ||
1297+
time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
12231298
{
12241299
if (Log_truncate_on_rotation && time_based_rotation &&
12251300
last_csv_file_name != NULL &&
@@ -1250,7 +1325,8 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12501325
return;
12511326
}
12521327

1253-
fclose(csvlogFile);
1328+
if (csvlogFile != NULL)
1329+
fclose(csvlogFile);
12541330
csvlogFile = fh;
12551331

12561332
/* instead of pfree'ing filename, remember it for next time */
@@ -1259,6 +1335,16 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
12591335
last_csv_file_name = csvfilename;
12601336
csvfilename = NULL;
12611337
}
1338+
else if (!(Log_destination & LOG_DESTINATION_CSVLOG) &&
1339+
csvlogFile != NULL)
1340+
{
1341+
/* CSVLOG was just turned off, so close the old file */
1342+
fclose(csvlogFile);
1343+
csvlogFile = NULL;
1344+
if (last_csv_file_name != NULL)
1345+
pfree(last_csv_file_name);
1346+
last_csv_file_name = NULL;
1347+
}
12621348

12631349
if (filename)
12641350
pfree(filename);

0 commit comments

Comments
 (0)