Add pg_current_logfile() function.
authorRobert Haas <rhaas@postgresql.org>
Fri, 3 Mar 2017 06:02:45 +0000 (11:32 +0530)
committerRobert Haas <rhaas@postgresql.org>
Fri, 3 Mar 2017 06:13:11 +0000 (11:43 +0530)
The syslogger will write out the current stderr and csvlog names, if
it's running and there are any, to a new file in the data directory
called "current_logfiles".  We take care to remove this file when it
might no longer be valid (but not at shutdown).  The function
pg_current_logfile() can be used to read the entries in the file.

Gilles Darold, reviewed and modified by Karl O.  Pinc, Michael
Paquier, and me.  Further review by รlvaro Herrera and Christoph Berg.

12 files changed:
doc/src/sgml/config.sgml
doc/src/sgml/func.sgml
doc/src/sgml/storage.sgml
src/backend/catalog/system_views.sql
src/backend/postmaster/postmaster.c
src/backend/postmaster/syslogger.c
src/backend/replication/basebackup.c
src/backend/utils/adt/misc.c
src/bin/pg_basebackup/t/010_pg_basebackup.pl
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/include/postmaster/syslogger.h

index 1b390a257ae793410f79920f07ad986b3625f17f..cd82c04b051ccb644c7c8aec53bf184783c84f26 100644 (file)
@@ -4280,6 +4280,11 @@ SELECT * FROM parent WHERE key = 2400;
       <primary>where to log</primary>
      </indexterm>
 
+     <indexterm>
+       <primary>current_logfiles</primary>
+       <secondary>and the log_destination configuration parameter</secondary>
+     </indexterm>
+
      <variablelist>
 
      <varlistentry id="guc-log-destination" xreflabel="log_destination">
@@ -4310,6 +4315,27 @@ SELECT * FROM parent WHERE key = 2400;
         <xref linkend="guc-logging-collector"> must be enabled to generate
         CSV-format log output.
        </para>
+       <para>
+        When either <systemitem>stderr</systemitem> or
+        <systemitem>csvlog</systemitem> are included, the file
+        <filename>current_logfiles</> is created to record the location
+        of the log file(s) currently in use by the logging collector and the
+        associated logging destination. This provides a convenient way to
+        find the logs currently in use by the instance. Here is an example of
+        this file's content:
+<programlisting>
+stderr pg_log/postgresql.log
+csvlog pg_log/postgresql.csv
+</programlisting>
+
+        <filename>current_logfiles</filename> is recreated when a new log file
+        is created as an effect of rotation, and
+        when <varname>log_destination</> is reloaded.  It is removed when
+        neither <systemitem>stderr</systemitem>
+        nor <systemitem>csvlog</systemitem> are included
+        in <varname>log_destination</>, and when the logging collector is
+        disabled.
+       </para>
 
        <note>
         <para>
index 71ad729ab01db495f68cf9f66fbba377b0074e53..9e084adc1aca9cf09df5586e125d34eec33b78b9 100644 (file)
@@ -15478,6 +15478,13 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        <entry>configuration load time</entry>
       </row>
 
+      <row>
+       <entry><literal><function>pg_current_logfile(<optional><type>text</></optional>)</function></literal></entry>
+       <entry><type>text</type></entry>
+       <entry>Primary log file name, or log in the requested format,
+       currently in use by the logging collector</entry>
+      </row>
+
       <row>
        <entry><literal><function>pg_my_temp_schema()</function></literal></entry>
        <entry><type>oid</type></entry>
@@ -15696,6 +15703,45 @@ SET search_path TO <replaceable>schema</> <optional>, <replaceable>schema</>, ..
     the time when the postmaster process re-read the configuration files.)
    </para>
 
+   <indexterm>
+    <primary>pg_current_logfile</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>Logging</primary>
+    <secondary>pg_current_logfile function</secondary>
+   </indexterm>
+
+   <indexterm>
+     <primary>current_logfiles</primary>
+     <secondary>and the pg_current_logfile function</secondary>
+   </indexterm>
+
+   <indexterm>
+    <primary>Logging</primary>
+    <secondary>current_logfiles file and the pg_current_logfile
+    function</secondary>
+   </indexterm>
+
+   <para>
+    <function>pg_current_logfile</function> returns, as <type>text</type>,
+    the path of the log file(s) currently in use by the logging collector.
+    The path includes the <xref linkend="guc-log-directory"> directory
+    and the log file name.  Log collection must be enabled or the return value
+    is <literal>NULL</literal>.  When multiple log files exist, each in a
+    different format, <function>pg_current_logfile</function> called
+    without arguments returns the path of the file having the first format
+    found in the ordered list: <systemitem>stderr</>, <systemitem>csvlog</>.
+    <literal>NULL</literal> is returned when no log file has any of these
+    formats.  To request a specific file format supply, as <type>text</type>,
+    either <systemitem>csvlog</> or <systemitem>stderr</> as the value of the
+    optional parameter. The return value is <literal>NULL</literal> when the
+    log format requested is not a configured
+    <xref linkend="guc-log-destination">.  The
+    <function>pg_current_logfiles</function> reflects the contents of the
+    <filename>current_logfiles</> file.
+   </para>
+
    <indexterm>
     <primary>pg_my_temp_schema</primary>
    </indexterm>
index 127b759c14f03edd0825a7ba07e89014dfa46384..e0a89861f86687e7ef333ecdca69441725a87717 100644 (file)
@@ -60,6 +60,12 @@ Item
  <entry>Subdirectory containing per-database subdirectories</entry>
 </row>
 
+<row>
+ <entry><filename>current_logfiles</></entry>
+ <entry>File recording the log file(s) currently written to by the logging
+  collector</entry>
+</row>
+
 <row>
  <entry><filename>global</></entry>
  <entry>Subdirectory containing cluster-wide tables, such as
index 38be9cf1a0cb8475f760bd3a5d7170673c6bdefe..ada542c530b2f67d64ef37bf3ad978f9551f89d1 100644 (file)
@@ -1091,6 +1091,8 @@ REVOKE EXECUTE ON FUNCTION pg_wal_replay_pause() FROM public;
 REVOKE EXECUTE ON FUNCTION pg_wal_replay_resume() FROM public;
 REVOKE EXECUTE ON FUNCTION pg_rotate_logfile() FROM public;
 REVOKE EXECUTE ON FUNCTION pg_reload_conf() FROM public;
+REVOKE EXECUTE ON FUNCTION pg_current_logfile() FROM public;
+REVOKE EXECUTE ON FUNCTION pg_current_logfile(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_stat_reset() FROM public;
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_shared(text) FROM public;
index 2cf17ac42e00a8d95a7dd278a772c3e0e8d2fb80..68313424ee26e3462d060bea9afebd3e17da861d 100644 (file)
@@ -1232,6 +1232,13 @@ PostmasterMain(int argc, char *argv[])
         */
        RemovePromoteSignalFiles();
 
+       /* Remove any outdated file holding the current log filenames. */
+       if (unlink(LOG_METAINFO_DATAFILE) < 0 && errno != ENOENT)
+               ereport(LOG,
+                               (errcode_for_file_access(),
+                                errmsg("could not remove file \"%s\": %m",
+                                       LOG_METAINFO_DATAFILE)));
+
        /*
         * If enabled, start up syslogger collection subprocess
         */
index 13a03014ebb86f5168596c51101e317fe4b2b173..aaefdaebad9f8536e1bd7ff3681f100a30fccbf2 100644 (file)
@@ -146,6 +146,7 @@ static char *logfile_getname(pg_time_t timestamp, const char *suffix);
 static void set_next_rotation_time(void);
 static void sigHupHandler(SIGNAL_ARGS);
 static void sigUsr1Handler(SIGNAL_ARGS);
+static void update_metainfo_datafile(void);
 
 
 /*
@@ -282,6 +283,7 @@ SysLoggerMain(int argc, char *argv[])
        currentLogRotationAge = Log_RotationAge;
        /* set next planned rotation time */
        set_next_rotation_time();
+       update_metainfo_datafile();
 
        /* main worker loop */
        for (;;)
@@ -348,6 +350,13 @@ SysLoggerMain(int argc, char *argv[])
                                rotation_disabled = false;
                                rotation_requested = true;
                        }
+
+                       /*
+                        * Force rewriting last log filename when reloading configuration.
+                        * Even if rotation_requested is false, log_destination may have
+                        * been changed and we don't want to wait the next file rotation.
+                        */
+                       update_metainfo_datafile();
                }
 
                if (Log_RotationAge > 0 && !rotation_disabled)
@@ -1098,6 +1107,8 @@ open_csvlogfile(void)
                pfree(last_csv_file_name);
 
        last_csv_file_name = filename;
+
+       update_metainfo_datafile();
 }
 
 /*
@@ -1268,6 +1279,8 @@ logfile_rotate(bool time_based_rotation, int size_rotation_for)
        if (csvfilename)
                pfree(csvfilename);
 
+       update_metainfo_datafile();
+
        set_next_rotation_time();
 }
 
@@ -1337,6 +1350,72 @@ set_next_rotation_time(void)
        next_rotation_time = now;
 }
 
+/*
+ * Store the name of the file(s) where the log collector, when enabled, writes
+ * log messages.  Useful for finding the name(s) of the current log file(s)
+ * when there is time-based logfile rotation.  Filenames are stored in a
+ * temporary file and which is renamed into the final destination for
+ * atomicity.
+ */
+static void
+update_metainfo_datafile(void)
+{
+       FILE    *fh;
+
+       if (!(Log_destination & LOG_DESTINATION_STDERR) &&
+               !(Log_destination & LOG_DESTINATION_CSVLOG))
+       {
+               if (unlink(LOG_METAINFO_DATAFILE) < 0 && errno != ENOENT)
+                       ereport(LOG,
+                                       (errcode_for_file_access(),
+                                        errmsg("could not remove file \"%s\": %m",
+                                               LOG_METAINFO_DATAFILE)));
+               return;
+       }
+
+       if ((fh = logfile_open(LOG_METAINFO_DATAFILE_TMP, "w", true)) == NULL)
+       {
+               ereport(LOG,
+                               (errcode_for_file_access(),
+                                errmsg("could not open file \"%s\": %m",
+                                       LOG_METAINFO_DATAFILE_TMP)));
+               return;
+       }
+
+       if (last_file_name && (Log_destination & LOG_DESTINATION_STDERR))
+       {
+               if (fprintf(fh, "stderr %s\n", last_file_name) < 0)
+               {
+                       ereport(LOG,
+                                       (errcode_for_file_access(),
+                                       errmsg("could not write file \"%s\": %m",
+                                                       LOG_METAINFO_DATAFILE_TMP)));
+                       fclose(fh);
+                       return;
+               }
+       }
+
+       if (last_csv_file_name && (Log_destination & LOG_DESTINATION_CSVLOG))
+       {
+               if (fprintf(fh, "csvlog %s\n", last_csv_file_name) < 0)
+               {
+                       ereport(LOG,
+                                       (errcode_for_file_access(),
+                                       errmsg("could not write file \"%s\": %m",
+                                                       LOG_METAINFO_DATAFILE_TMP)));
+                       fclose(fh);
+                       return;
+               }
+       }
+       fclose(fh);
+
+       if (rename(LOG_METAINFO_DATAFILE_TMP, LOG_METAINFO_DATAFILE) != 0)
+               ereport(LOG,
+                               (errcode_for_file_access(),
+                               errmsg("could not rename file \"%s\" to \"%s\": %m",
+                                          LOG_METAINFO_DATAFILE_TMP, LOG_METAINFO_DATAFILE)));
+}
+
 /* --------------------------------
  *             signal handler routines
  * --------------------------------
index 7414048f4eb54c80ed7075a6a57180fd31b64c85..e3a7ad5e9ab20fc622d1351c28c4524743402e13 100644 (file)
@@ -26,6 +26,7 @@
 #include "nodes/pg_list.h"
 #include "pgtar.h"
 #include "pgstat.h"
+#include "postmaster/syslogger.h"
 #include "replication/basebackup.h"
 #include "replication/walsender.h"
 #include "replication/walsender_private.h"
@@ -147,6 +148,9 @@ static const char *excludeFiles[] =
        /* Skip auto conf temporary file. */
        PG_AUTOCONF_FILENAME ".tmp",
 
+       /* Skip current log file temporary file */
+       LOG_METAINFO_DATAFILE_TMP,
+
        /*
         * If there's a backup_label or tablespace_map file, it belongs to a
         * backup started by the user with pg_start_backup().  It is *not* correct
index 8f7c1f81fd44cdd1b1c64ce31c320ba7d497cedc..ff6a25d2b60233a356afb60616443bd67fd8f8f2 100644 (file)
@@ -885,3 +885,106 @@ parse_ident(PG_FUNCTION_ARGS)
 
        PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
 }
+
+/*
+ * pg_current_logfile
+ *
+ * Report current log file used by log collector by scanning current_logfiles.
+ */
+Datum
+pg_current_logfile(PG_FUNCTION_ARGS)
+{
+       FILE       *fd;
+       char            lbuffer[MAXPGPATH];
+       char       *logfmt;
+       char       *log_filepath;
+       char       *log_format = lbuffer;
+       char       *nlpos;
+
+       /* The log format parameter is optional */
+       if (PG_NARGS() == 0 || PG_ARGISNULL(0))
+               logfmt = NULL;
+       else
+       {
+               logfmt = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+               if (strcmp(logfmt, "stderr") != 0 && strcmp(logfmt, "csvlog") != 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("log format \"%s\" is not supported", logfmt),
+                                        errhint("The supported log formats are \"stderr\" and \"csvlog\".")));
+       }
+
+       fd = AllocateFile(LOG_METAINFO_DATAFILE, "r");
+       if (fd == NULL)
+       {
+               if (errno != ENOENT)
+                       ereport(ERROR,
+                                       (errcode_for_file_access(),
+                                        errmsg("could not read file \"%s\": %m",
+                                                       LOG_METAINFO_DATAFILE)));
+               PG_RETURN_NULL();
+       }
+
+       /*
+        * Read the file to gather current log filename(s) registered by the
+        * syslogger.
+        */
+       while (fgets(lbuffer, sizeof(lbuffer), fd) != NULL)
+       {
+               /*
+                * Extract log format and log file path from the line; lbuffer ==
+                * log_format, they share storage.
+                */
+               log_filepath = strchr(lbuffer, ' ');
+               if (log_filepath == NULL)
+               {
+                       /*
+                        * No space found, file content is corrupted.  Return NULL to the
+                        * caller and inform him on the situation.
+                        */
+                       elog(ERROR,
+                                "missing space character in \"%s\"", LOG_METAINFO_DATAFILE);
+                       break;
+               }
+
+               *log_filepath = '\0';
+               log_filepath++;
+               nlpos = strchr(log_filepath, '\n');
+               if (nlpos == NULL)
+               {
+                       /*
+                        * No newlinei found, file content is corrupted.  Return NULL to
+                        * the caller and inform him on the situation.
+                        */
+                       elog(ERROR,
+                          "missing newline character in \"%s\"", LOG_METAINFO_DATAFILE);
+                       break;
+               }
+               *nlpos = '\0';
+
+               if (logfmt == NULL || strcmp(logfmt, log_format) == 0)
+               {
+                       FreeFile(fd);
+                       PG_RETURN_TEXT_P(cstring_to_text(log_filepath));
+               }
+       }
+
+       /* Close the current log filename file. */
+       FreeFile(fd);
+
+       PG_RETURN_NULL();
+}
+
+/*
+ * Report current log file used by log collector (1 argument version)
+ *
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments
+ */
+Datum
+pg_current_logfile_1arg(PG_FUNCTION_ARGS)
+{
+       return pg_current_logfile(fcinfo);
+}
index 29f519d8c99bd002ce8eee6c41407e3a7f03aae6..aafb138fd53a3804e0d4ab54238c8a322300e213 100644 (file)
@@ -4,7 +4,7 @@ use Cwd;
 use Config;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 72;
+use Test::More tests => 73;
 
 program_help_ok('pg_basebackup');
 program_version_ok('pg_basebackup');
@@ -56,7 +56,7 @@ close CONF;
 $node->restart;
 
 # Write some files to test that they are not copied.
-foreach my $filename (qw(backup_label tablespace_map postgresql.auto.conf.tmp))
+foreach my $filename (qw(backup_label tablespace_map postgresql.auto.conf.tmp current_logfiles.tmp))
 {
        open FILE, ">>$pgdata/$filename";
        print FILE "DONOTCOPY";
@@ -83,7 +83,7 @@ foreach my $dirname (qw(pg_dynshmem pg_notify pg_replslot pg_serial pg_snapshots
 }
 
 # These files should not be copied.
-foreach my $filename (qw(postgresql.auto.conf.tmp postmaster.opts postmaster.pid tablespace_map))
+foreach my $filename (qw(postgresql.auto.conf.tmp postmaster.opts postmaster.pid tablespace_map current_logfiles.tmp))
 {
        ok(! -f "$tempdir/backup/$filename", "$filename not copied");
 }
index 90456fa668f4454306d643263724097cca2ce075..57fbc9509ed745a8569c967756defc90bda9cb3c 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201703011
+#define CATALOG_VERSION_NO     201703031
 
 #endif
index 4b9c6e75b0529d9145764b372e507c748cc30bb2..0c8b5c630d84d135a8d9e8cae8e990e11003718e 100644 (file)
@@ -3191,6 +3191,10 @@ DATA(insert OID = 2621 ( pg_reload_conf                  PGNSP PGUID 12 1 0 0 0 f f f f t f v s
 DESCR("reload configuration files");
 DATA(insert OID = 2622 ( pg_rotate_logfile             PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 16 "" _null_ _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ ));
 DESCR("rotate log file");
+DATA(insert OID = 3800 ( pg_current_logfile             PGNSP PGUID 12 1 0 0 0 f f f f f f v s 0 0 25 "" _null_ _null_ _null_ _null_ _null_ pg_current_logfile _null_ _null_ _null_ ));
+DESCR("current logging collector file location");
+DATA(insert OID = 3801 ( pg_current_logfile             PGNSP PGUID 12 1 0 0 0 f f f f f f v s 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_current_logfile_1arg _null_ _null_ _null_ ));
+DESCR("current logging collector file location");
 
 DATA(insert OID = 2623 ( pg_stat_file          PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ _null_ pg_stat_file_1arg _null_ _null_ _null_ ));
 DESCR("get information about file");
index c187a5f23ed25cdd512d18e62ba4eaea9e476a50..94d7eac347f54e655fe637bddb4fe9fbd3798762 100644 (file)
@@ -87,4 +87,11 @@ extern void write_syslogger_file(const char *buffer, int count, int dest);
 extern void SysLoggerMain(int argc, char *argv[]) pg_attribute_noreturn();
 #endif
 
+/*
+ * Name of files saving meta-data information about the log
+ * files currently in use by the syslogger
+ */
+#define LOG_METAINFO_DATAFILE  "current_logfiles"
+#define LOG_METAINFO_DATAFILE_TMP  LOG_METAINFO_DATAFILE ".tmp"
+
 #endif   /* _SYSLOGGER_H */