Modularize log_connections output
authorMelanie Plageman
Wed, 12 Mar 2025 15:33:01 +0000 (11:33 -0400)
committerMelanie Plageman
Wed, 12 Mar 2025 15:35:21 +0000 (11:35 -0400)
Convert the boolean log_connections GUC into a list GUC comprised of the
connection aspects to log.

This gives users more control over the volume and kind of connection
logging.

The current log_connections options are 'receipt', 'authentication', and
'authorization'. The empty string disables all connection logging. 'all'
enables all available connection logging.

For backwards compatibility, the most common values for the
log_connections boolean are still supported (on, off, 1, 0, true, false,
yes, no). Note that previously supported substrings of on, off, true,
false, yes, and no are no longer supported.

Author: Melanie Plageman 
Reviewed-by: Bertrand Drouvot
Reviewed-by: Fujii Masao
Reviewed-by: Daniel Gustafsson
Discussion: https://postgr.es/m/flat/CAAKRu_b_smAHK0ZjrnL5GRxnAVWujEXQWpLXYzGbmpcZd3nLYw%40mail.gmail.com

12 files changed:
doc/src/sgml/config.sgml
src/backend/libpq/auth.c
src/backend/postmaster/postmaster.c
src/backend/tcop/backend_startup.c
src/backend/utils/init/postinit.c
src/backend/utils/misc/guc_tables.c
src/backend/utils/misc/postgresql.conf.sample
src/include/postmaster/postmaster.h
src/include/tcop/backend_startup.h
src/include/utils/guc_hooks.h
src/test/authentication/t/001_password.pl
src/tools/pgindent/typedefs.list

index d2fa5f7d1a9f15dbf3e0be25bf73f7b2c4973de5..2ce93fbfa36adef512f78b5b030c4b048fef9f64 100644 (file)
@@ -7315,20 +7315,92 @@ local0.*    /var/log/postgresql
      
 
      
-      log_connections (boolean)
+      log_connections (string)
       
        log_connections configuration parameter
       
       
       
        
-        Causes each attempted connection to the server to be logged,
-        as well as successful completion of both client authentication (if
-        necessary) and authorization.
+        Causes aspects of each connection to the server to be logged.
+        The default is the empty string, '', which
+        disables all connection logging. The following options may be
+        specified alone or in a comma-separated list:
+       
+
+       
+        Log Connection Options
+        
+         
+         
+         
+          
+           Name
+           Description
+          
+         
+         
+          
+           receipt
+           Logs receipt of a connection.
+          
+
+          
+           authentication
+           
+            Logs the original identity used by an authentication method
+            to identify a user. In most cases, the identity string
+            matches the PostgreSQL username,
+            but some third-party authentication methods may alter the
+            original user identifier before the server stores it. Failed
+            authentication is always logged regardless of the value of
+            this setting.
+           
+          
+
+          
+           authorization
+           
+            Logs successful completion of authorization. At this point
+            the connection has been established but the backend is not
+            yet fully set up. The log message includes the authorized
+            username as well as the database name and application name,
+            if applicable.
+           
+          
+
+          
+           all
+           
+            A convenience alias equivalent to specifying all options. If
+            all is specified in a list of other
+            options, all connection aspects will be logged.
+           
+          
+
+         
+        
+       
+
+       
+        Disconnection logging is separately controlled by 
+        linkend="guc-log-disconnections"/>.
+       
+
+       
+        For the purposes of backwards compatibility, on,
+        offtrue,
+        falseyes,
+        no1, and 0
+        are still supported. The positive values are equivalent to specifying
+        the receiptauthentication, and
+        authorization options.
+       
+
+       
         Only superusers and users with the appropriate SET
         privilege can change this parameter at session start,
         and it cannot be changed at all within a session.
-        The default is off.
        
 
        
index 81e2f8184e30de3688bf0d5bd9aca09cebf11bdd..e18683c47e726226bcb540fd372dd1934de2636f 100644 (file)
@@ -38,6 +38,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
+#include "tcop/backend_startup.h"
 #include "utils/memutils.h"
 
 /*----------------------------------------------------------------
@@ -317,7 +318,8 @@ auth_failed(Port *port, int status, const char *logdetail)
 /*
  * Sets the authenticated identity for the current user.  The provided string
  * will be stored into MyClientConnectionInfo, alongside the current HBA
- * method in use.  The ID will be logged if log_connections is enabled.
+ * method in use.  The ID will be logged if log_connections has the
+ * 'authentication' option specified.
  *
  * Auth methods should call this routine exactly once, as soon as the user is
  * successfully authenticated, even if they have reasons to know that
@@ -349,7 +351,7 @@ set_authn_id(Port *port, const char *id)
    MyClientConnectionInfo.authn_id = MemoryContextStrdup(TopMemoryContext, id);
    MyClientConnectionInfo.auth_method = port->hba->auth_method;
 
-   if (Log_connections)
+   if (log_connections & LOG_CONNECTION_AUTHENTICATION)
    {
        ereport(LOG,
                errmsg("connection authenticated: identity=\"%s\" method=%s "
@@ -633,7 +635,8 @@ ClientAuthentication(Port *port)
 #endif
    }
 
-   if (Log_connections && status == STATUS_OK &&
+   if ((log_connections & LOG_CONNECTION_AUTHENTICATION) &&
+       status == STATUS_OK &&
        !MyClientConnectionInfo.authn_id)
    {
        /*
index d2a7a7add6fa0294f30b8edb053c0d80fca784a3..b4f34c57a1b7f52197546d54821a55a3f573a3bc 100644 (file)
@@ -237,7 +237,6 @@ int         PreAuthDelay = 0;
 int            AuthenticationTimeout = 60;
 
 bool       log_hostname;       /* for ps display and logging */
-bool       Log_connections = false;
 
 bool       enable_bonjour = false;
 char      *bonjour_name;
index c70746fa562e437c58083d60b19e2914c365bf75..962b6e6000275794c039bd2adb42a533c9b91efc 100644 (file)
 #include "tcop/backend_startup.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "utils/timeout.h"
+#include "utils/varlena.h"
 
 /* GUCs */
 bool       Trace_connection_negotiation = false;
+uint32     log_connections = 0;
+char      *log_connections_string = NULL;
 
 static void BackendInitialize(ClientSocket *client_sock, CAC_state cac);
 static int ProcessSSLStartup(Port *port);
@@ -48,6 +52,7 @@ static int    ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done);
 static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
 static void process_startup_packet_die(SIGNAL_ARGS);
 static void StartupPacketTimeoutHandler(void);
+static bool validate_log_connections_options(List *elemlist, uint32 *flags);
 
 /*
  * Entry point for a new backend process.
@@ -201,8 +206,8 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
    port->remote_host = MemoryContextStrdup(TopMemoryContext, remote_host);
    port->remote_port = MemoryContextStrdup(TopMemoryContext, remote_port);
 
-   /* And now we can issue the Log_connections message, if wanted */
-   if (Log_connections)
+   /* And now we can log that the connection was received, if enabled */
+   if (log_connections & LOG_CONNECTION_RECEIPT)
    {
        if (remote_port[0])
            ereport(LOG,
@@ -924,3 +929,155 @@ StartupPacketTimeoutHandler(void)
 {
    _exit(1);
 }
+
+/*
+ * Helper for the log_connections GUC check hook.
+ *
+ * `elemlist` is a listified version of the string input passed to the
+ * log_connections GUC check hook, check_log_connections().
+ * check_log_connections() is responsible for cleaning up `elemlist`.
+ *
+ * validate_log_connections_options() returns false if an error was
+ * encountered and the GUC input could not be validated and true otherwise.
+ *
+ * `flags` returns the flags that should be stored in the log_connections GUC
+ * by its assign hook.
+ */
+static bool
+validate_log_connections_options(List *elemlist, uint32 *flags)
+{
+   ListCell   *l;
+   char       *item;
+
+   /*
+    * For backwards compatibility, we accept these tokens by themselves.
+    *
+    * Prior to PostgreSQL 18, log_connections was a boolean GUC that accepted
+    * any unambiguous substring of 'true', 'false', 'yes', 'no', 'on', and
+    * 'off'. Since log_connections became a list of strings in 18, we only
+    * accept complete option strings.
+    */
+   static const struct config_enum_entry compat_options[] = {
+       {"off", 0},
+       {"false", 0},
+       {"no", 0},
+       {"0", 0},
+       {"on", LOG_CONNECTION_ON},
+       {"true", LOG_CONNECTION_ON},
+       {"yes", LOG_CONNECTION_ON},
+       {"1", LOG_CONNECTION_ON},
+   };
+
+   *flags = 0;
+
+   /* If an empty string was passed, we're done */
+   if (list_length(elemlist) == 0)
+       return true;
+
+   /*
+    * Now check for the backwards compatibility options. They must always be
+    * specified on their own, so we error out if the first option is a
+    * backwards compatibility option and other options are also specified.
+    */
+   item = linitial(elemlist);
+
+   for (size_t i = 0; i < lengthof(compat_options); i++)
+   {
+       struct config_enum_entry option = compat_options[i];
+
+       if (pg_strcasecmp(item, option.name) != 0)
+           continue;
+
+       if (list_length(elemlist) > 1)
+       {
+           GUC_check_errdetail("Cannot specify log_connections option \"%s\" in a list with other options.",
+                               item);
+           return false;
+       }
+
+       *flags = option.val;
+       return true;
+   }
+
+   /* Now check the aspect options. The empty string was already handled */
+   foreach(l, elemlist)
+   {
+       static const struct config_enum_entry options[] = {
+           {"receipt", LOG_CONNECTION_RECEIPT},
+           {"authentication", LOG_CONNECTION_AUTHENTICATION},
+           {"authorization", LOG_CONNECTION_AUTHORIZATION},
+           {"all", LOG_CONNECTION_ALL},
+       };
+
+       item = lfirst(l);
+       for (size_t i = 0; i < lengthof(options); i++)
+       {
+           struct config_enum_entry option = options[i];
+
+           if (pg_strcasecmp(item, option.name) == 0)
+           {
+               *flags |= option.val;
+               goto next;
+           }
+       }
+
+       GUC_check_errdetail("Invalid option \"%s\".", item);
+       return false;
+
+next:  ;
+   }
+
+   return true;
+}
+
+
+/*
+ * GUC check hook for log_connections
+ */
+bool
+check_log_connections(char **newval, void **extra, GucSource source)
+{
+   uint32      flags;
+   char       *rawstring;
+   List       *elemlist;
+   bool        success;
+
+   /* Need a modifiable copy of string */
+   rawstring = pstrdup(*newval);
+
+   if (!SplitIdentifierString(rawstring, ',', &elemlist))
+   {
+       GUC_check_errdetail("Invalid list syntax in parameter \"log_connections\".");
+       pfree(rawstring);
+       list_free(elemlist);
+       return false;
+   }
+
+   /* Validation logic is all in the helper */
+   success = validate_log_connections_options(elemlist, &flags);
+
+   /* Time for cleanup */
+   pfree(rawstring);
+   list_free(elemlist);
+
+   if (!success)
+       return false;
+
+   /*
+    * We succeeded, so allocate `extra` and save the flags there for use by
+    * assign_log_connections().
+    */
+   *extra = guc_malloc(ERROR, sizeof(int));
+   *((int *) *extra) = flags;
+
+   return true;
+}
+
+/*
+ * GUC assign hook for log_connections
+ */
+void
+assign_log_connections(const char *newval, void *extra)
+{
+   log_connections = *((int *) extra);
+}
index ee1a9d5d98bbbf8a0cceee0f52962441804d3a0a..6c207e1776888858864a5d651fbe54c41ad1e456 100644 (file)
@@ -54,6 +54,7 @@
 #include "storage/sinvaladt.h"
 #include "storage/smgr.h"
 #include "storage/sync.h"
+#include "tcop/backend_startup.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -252,7 +253,7 @@ PerformAuthentication(Port *port)
     */
    disable_timeout(STATEMENT_TIMEOUT, false);
 
-   if (Log_connections)
+   if (log_connections & LOG_CONNECTION_AUTHORIZATION)
    {
        StringInfoData logmsg;
 
index ad25cbb39c555ec567f2049d03ff1a100b24c28c..508970680d15d5750cc68470b4a056246f93fa9a 100644 (file)
@@ -1219,15 +1219,6 @@ struct config_bool ConfigureNamesBool[] =
        true,
        NULL, NULL, NULL
    },
-   {
-       {"log_connections", PGC_SU_BACKEND, LOGGING_WHAT,
-           gettext_noop("Logs each successful connection."),
-           NULL
-       },
-       &Log_connections,
-       false,
-       NULL, NULL, NULL
-   },
    {
        {"trace_connection_negotiation", PGC_POSTMASTER, DEVELOPER_OPTIONS,
            gettext_noop("Logs details of pre-authentication connection handshake."),
@@ -4886,6 +4877,18 @@ struct config_string ConfigureNamesString[] =
        NULL, NULL, NULL
    },
 
+   {
+       {"log_connections", PGC_SU_BACKEND, LOGGING_WHAT,
+           gettext_noop("Logs specified aspects of connection establishment and setup."),
+           NULL,
+           GUC_LIST_INPUT
+       },
+       &log_connections_string,
+       "",
+       check_log_connections, assign_log_connections, NULL
+   },
+
+
    /* End-of-list marker */
    {
        {NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
index 2d1de9c37bd17b8dc2c4f8fda597655bdbd6021f..c291c05d18145d91e5d2cc9b161d2ed0b2e3db03 100644 (file)
@@ -21,7 +21,7 @@
 # require a server shutdown and restart to take effect.
 #
 # Any parameter can also be given as a command-line option to the server, e.g.,
-# "postgres -c log_connections=on".  Some parameters can be changed at run time
+# "postgres -c log_connections=all".  Some parameters can be changed at run time
 # with the "SET" SQL command.
 #
 # Memory units:  B  = bytes            Time units:  us  = microseconds
                    # actions running at least this number
                    # of milliseconds.
 #log_checkpoints = on
-#log_connections = off
+#log_connections = '' # log aspects of connection setup
+          # options include receipt, authentication, authorization,
+          # and all to log all of these aspects
 #log_disconnections = off
-#log_duration = off
+#log_duration = off # log statement duration
 #log_error_verbosity = default     # terse, default, or verbose messages
 #log_hostname = off
 #log_line_prefix = '%m [%p] '      # special values:
index b6a3f275a1b4b1427dc8a0c7239715f022e6b232..aa786bfacf360bd0fc477db7c1f4ac692ebcebf1 100644 (file)
@@ -63,7 +63,6 @@ extern PGDLLIMPORT char *ListenAddresses;
 extern PGDLLIMPORT bool ClientAuthInProgress;
 extern PGDLLIMPORT int PreAuthDelay;
 extern PGDLLIMPORT int AuthenticationTimeout;
-extern PGDLLIMPORT bool Log_connections;
 extern PGDLLIMPORT bool log_hostname;
 extern PGDLLIMPORT bool enable_bonjour;
 extern PGDLLIMPORT char *bonjour_name;
index 7328561120324973c45aa20b0f432a2022b6be64..e00851a004e4bf4492cbcbaaa5bbfcdc99b3b544 100644 (file)
@@ -16,6 +16,8 @@
 
 /* GUCs */
 extern PGDLLIMPORT bool Trace_connection_negotiation;
+extern PGDLLIMPORT uint32 log_connections;
+extern PGDLLIMPORT char *log_connections_string;
 
 /*
  * CAC_state is passed from postmaster to the backend process, to indicate
@@ -39,6 +41,33 @@ typedef struct BackendStartupData
    CAC_state   canAcceptConnections;
 } BackendStartupData;
 
+/*
+ * Granular control over which messages to log for the log_connections GUC.
+ *
+ * RECEIPT, AUTHENTICATION, and AUTHORIZATION are different aspects of
+ * connection establishment and backend setup for which we may emit a log
+ * message.
+ *
+ * ALL is a convenience alias equivalent to all of the above aspects.
+ *
+ * ON is backwards compatibility alias for the connection aspects that were
+ * logged in Postgres versions < 18.
+ */
+typedef enum LogConnectionOption
+{
+   LOG_CONNECTION_RECEIPT = (1 << 0),
+   LOG_CONNECTION_AUTHENTICATION = (1 << 1),
+   LOG_CONNECTION_AUTHORIZATION = (1 << 2),
+   LOG_CONNECTION_ON =
+       LOG_CONNECTION_RECEIPT |
+       LOG_CONNECTION_AUTHENTICATION |
+       LOG_CONNECTION_AUTHORIZATION,
+   LOG_CONNECTION_ALL =
+       LOG_CONNECTION_RECEIPT |
+       LOG_CONNECTION_AUTHENTICATION |
+       LOG_CONNECTION_AUTHORIZATION,
+} LogConnectionOption;
+
 extern void BackendMain(const void *startup_data, size_t startup_data_len) pg_attribute_noreturn();
 
 #endif                         /* BACKEND_STARTUP_H */
index 951451a9765f4da5a7dded76640d69edd731c827..9a0d8ec85c715a4a542380c2115840e7e4c1b037 100644 (file)
@@ -51,6 +51,8 @@ extern bool check_datestyle(char **newval, void **extra, GucSource source);
 extern void assign_datestyle(const char *newval, void *extra);
 extern bool check_debug_io_direct(char **newval, void **extra, GucSource source);
 extern void assign_debug_io_direct(const char *newval, void *extra);
+extern bool check_log_connections(char **newval, void **extra, GucSource source);
+extern void assign_log_connections(const char *newval, void *extra);
 extern bool check_default_table_access_method(char **newval, void **extra,
                                              GucSource source);
 extern bool check_default_tablespace(char **newval, void **extra,
index 4ce22ccbdf260b151cc967d162eab88dbdcc62b1..e307dee5c486ed5c5c0aebef8c0fe84e57dee31a 100644 (file)
@@ -7,6 +7,8 @@
 # - MD5-encrypted
 # - SCRAM-encrypted
 # This test can only run with Unix-domain sockets.
+#
+# There's also a few tests of the log_connections GUC here.
 
 use strict;
 use warnings FATAL => 'all';
@@ -66,6 +68,42 @@ $node->init;
 $node->append_conf('postgresql.conf', "log_connections = on\n");
 $node->start;
 
+# Test behavior of log_connections GUC
+#
+# There wasn't another test file where these tests obviously fit, and we don't
+# want to incur the cost of spinning up a new cluster just to test one GUC.
+
+# Make a database for the log_connections tests to avoid test fragility if
+# other tests are added to this file in the future
+$node->safe_psql('postgres', "CREATE DATABASE test_log_connections");
+
+$node->safe_psql('test_log_connections',
+   q[ALTER SYSTEM SET log_connections = receipt,authorization;
+                  SELECT pg_reload_conf();]);
+
+$node->connect_ok('test_log_connections',
+   q(log_connections with subset of specified options logs only those aspects),
+   log_like => [
+       qr/connection received/,
+       qr/connection authorized: user=\S+ database=test_log_connections/,
+   ],
+   log_unlike => [
+       qr/connection authenticated/,
+   ],);
+
+$node->safe_psql('test_log_connections',
+   qq(ALTER SYSTEM SET log_connections = 'all'; SELECT pg_reload_conf();));
+
+$node->connect_ok('test_log_connections',
+   qq(log_connections 'all' logs all available connection aspects),
+   log_like => [
+       qr/connection received/,
+       qr/connection authenticated/,
+       qr/connection authorized: user=\S+ database=test_log_connections/,
+   ],);
+
+# Authentication tests
+
 # could fail in FIPS mode
 my $md5_works = ($node->psql('postgres', "select md5('')") == 0);
 
index dfe2690bdd3e28d0b777829c607de0793250d63f..9e7f58adb18ecab64a1a9bf6db23de7481a9ed6d 100644 (file)
@@ -1556,6 +1556,7 @@ LockTupleMode
 LockViewRecurse_context
 LockWaitPolicy
 LockingClause
+LogConnectionOption
 LogOpts
 LogStmtLevel
 LogicalDecodeBeginCB