From 34a767a5cc36fe777ce0ecdcb057a258a4d8a5f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C5=82eczek?= Date: Tue, 27 Jan 2026 11:04:51 +0100 Subject: [PATCH] add: Log actual host and port of listener connection Diagnosing problems with listener channel notifications not being handled properly by PostgREST connected to read replicas is difficult. Issues might be related to lost connections and listener not being connected to the right host after failover or database server restarts. This patch adds logging of actual host:port used by libpq connection opened by the listener. It should make it easier to find out if PostgREST is connected to the right host. --- CHANGELOG.md | 4 ++++ src/PostgREST/Listener.hs | 11 +++++++---- src/PostgREST/Observation.hs | 6 +++--- test/io/test_io.py | 17 +++++++++++++++++ test/observability/ObsHelper.hs | 2 +- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 283e9e8fa..6442b9c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. From versio ## Unreleased +### Added + +- Log the actual host and port of listener database connection by @mkleczek in #4617 + ## [14.8] - 2026-04-03 ### Added diff --git a/src/PostgREST/Listener.hs b/src/PostgREST/Listener.hs index a5d12aa1a..2872cdc1d 100644 --- a/src/PostgREST/Listener.hs +++ b/src/PostgREST/Listener.hs @@ -17,8 +17,11 @@ import PostgREST.Version (prettyVersion) import qualified PostgREST.AppState as AppState import qualified PostgREST.Config as Config -import Data.Either.Combinators (whenRight) -import Protolude +import Control.Arrow ((&&&)) +import Data.Bitraversable (bisequence) +import Data.Either.Combinators (whenRight) +import qualified Database.PostgreSQL.LibPQ as LibPQ +import Protolude -- | Starts the Listener in a thread runListener :: AppState -> IO () @@ -72,8 +75,8 @@ retryingListen appState = do -- reset the delay AppState.putNextListenerDelay appState 1 - observer $ DBListenStart dbChannel - + (pqHost, pqPort) <- SQL.withLibPQConnection db $ bisequence . (LibPQ.host &&& LibPQ.port) + observer $ DBListenStart pqHost pqPort dbChannel -- wait for notifications -- this will never return, in case of an error it will throw and be caught by onError forever $ SQL.waitForNotifications handleNotification db diff --git a/src/PostgREST/Observation.hs b/src/PostgREST/Observation.hs index 8c033f176..1169897d6 100644 --- a/src/PostgREST/Observation.hs +++ b/src/PostgREST/Observation.hs @@ -45,7 +45,7 @@ data Observation | SchemaCacheSummaryObs Text | SchemaCacheLoadedObs Double | ConnectionRetryObs Int - | DBListenStart Text + | DBListenStart (Maybe ByteString) (Maybe ByteString) Text -- host, port, channel | DBListenFail Text (Either SQL.ConnectionError SomeException) | DBListenRetry Int | DBListenBugHint -- https://github.com/PostgREST/postgrest/issues/3147 @@ -114,8 +114,8 @@ observationMessage = \case "Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..." QueryPgVersionError usageErr -> "Failed to query the PostgreSQL version. " <> jsonMessage usageErr - DBListenStart channel -> do - "Listening for database notifications on the " <> show channel <> " channel" + DBListenStart host port channel -> do + "Listener connected to " <> show (fold $ host <> fmap (":" <>) port) <> " and listening for database notifications on the " <> show channel <> " channel" DBListenFail channel listenErr -> "Failed listening for database notifications on the " <> show channel <> " channel. " <> either showListenerConnError showListenerException listenErr diff --git a/test/io/test_io.py b/test/io/test_io.py index 1a2327df5..641476283 100644 --- a/test/io/test_io.py +++ b/test/io/test_io.py @@ -1658,6 +1658,23 @@ def test_log_listener_connection_errors(defaultenv): ) +def test_log_listener_connection_start(defaultenv): + "The logs should show the listener connection start message in a single line" + + env = { + **defaultenv, + "PGRST_DB_CHANNEL_ENABLED": "true", + } + + with run(env=env, no_startup_stdout=False, wait_for_readiness=True) as postgrest: + output = postgrest.read_stdout(nlines=5) + assert any( + f'Listener connected to "{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel' + in line + for line in output + ) + + def test_db_pre_config_with_pg_reserved_words(defaultenv): "The db-pre-config should not fail unexpectedly when function name is a postgres reserved word" diff --git a/test/observability/ObsHelper.hs b/test/observability/ObsHelper.hs index d53cc81d8..e1ff88bc0 100644 --- a/test/observability/ObsHelper.hs +++ b/test/observability/ObsHelper.hs @@ -206,5 +206,5 @@ waitForObs (ObsChan orig copy) t msg f = obsDiagMessage :: Observation -> Text obsDiagMessage = \case (HasqlPoolObs o) -> show o - o@(DBListenStart channel) -> constrName o <> show channel + o@(DBListenStart host port channel) -> constrName o <> show (host, port, channel) o -> constrName o