feat: log connection pool events on log-level=info
This commit is contained in:
committed by
Steve Chavez
parent
9d1dc783bf
commit
1bf0c54dd6
@@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- #3171, #3046, Log schema cache stats to stderr - @steve-chavez
|
- #3171, #3046, Log schema cache stats to stderr - @steve-chavez
|
||||||
- #3210, Dump schema cache through admin API - @taimoorzaeem
|
- #3210, Dump schema cache through admin API - @taimoorzaeem
|
||||||
- #2676, Performance improvement on bulk json inserts, around 10% increase on requests per second by removing `json_typeof` from write queries - @steve-chavez
|
- #2676, Performance improvement on bulk json inserts, around 10% increase on requests per second by removing `json_typeof` from write queries - @steve-chavez
|
||||||
|
- #3214, Log connection pool events on log-level=info - @steve-chavez
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
index-state: hackage.haskell.org 2024-03-13T22:43:26Z
|
index-state: hackage.haskell.org 2024-04-15T20:28:44Z
|
||||||
|
|||||||
@@ -35,11 +35,14 @@ let
|
|||||||
# - <subpath> is usually "."
|
# - <subpath> is usually "."
|
||||||
# - When adding a new library version here, postgrest.cabal and stack.yaml must also be updated
|
# - When adding a new library version here, postgrest.cabal and stack.yaml must also be updated
|
||||||
#
|
#
|
||||||
# Note:
|
# Notes:
|
||||||
# - This should NOT be the first place to start managing dependencies. Check postgrest.cabal.
|
# - This should NOT be the first place to start managing dependencies. Check postgrest.cabal.
|
||||||
|
# - When adding a new package version here, you have to update stack:
|
||||||
|
# + For stack.yml add:
|
||||||
|
# extra-deps:
|
||||||
|
# - <package>-<ver>
|
||||||
|
# + For stack.yml.lock, CI should report an error with the correct lock, copy/paste that one into the file
|
||||||
# - To modify and try packages locally, see "Working with locally modified Haskell packages" in the Nix README.
|
# - To modify and try packages locally, see "Working with locally modified Haskell packages" in the Nix README.
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
configurator-pg =
|
configurator-pg =
|
||||||
prev.callHackageDirect
|
prev.callHackageDirect
|
||||||
@@ -60,18 +63,26 @@ let
|
|||||||
}
|
}
|
||||||
{ };
|
{ };
|
||||||
|
|
||||||
|
hasql-pool =
|
||||||
|
lib.dontCheck (prev.callHackageDirect
|
||||||
|
{
|
||||||
|
pkg = "hasql-pool";
|
||||||
|
ver = "1.0.1";
|
||||||
|
sha256 = "sha256-Hf1f7lX0LWkjrb25SDBovCYPRdmUP1H6pAxzi7kT4Gg=";
|
||||||
|
}
|
||||||
|
{ }
|
||||||
|
);
|
||||||
|
|
||||||
postgresql-libpq = lib.dontCheck
|
postgresql-libpq = lib.dontCheck
|
||||||
(prev.postgresql-libpq_0_10_0_0.override {
|
(prev.postgresql-libpq_0_10_0_0.override {
|
||||||
postgresql = super.libpq;
|
postgresql = super.libpq;
|
||||||
});
|
});
|
||||||
|
|
||||||
hasql-pool = lib.dontCheck prev.hasql-pool_0_10;
|
|
||||||
|
|
||||||
hasql-notifications = lib.dontCheck (prev.callHackageDirect
|
hasql-notifications = lib.dontCheck (prev.callHackageDirect
|
||||||
{
|
{
|
||||||
pkg = "hasql-notifications";
|
pkg = "hasql-notifications";
|
||||||
ver = "0.2.1.0";
|
ver = "0.2.1.1";
|
||||||
sha256 = "sha256-MEIirDKR81KpiBOnWJbVInWevL6Kdb/XD1Qtd8e6KsQ=";
|
sha256 = "sha256-oPhKA/pSQGJvgQyhsi7CVr9iDT7uWpKUz0iJfXsaxXo=";
|
||||||
}
|
}
|
||||||
{ }
|
{ }
|
||||||
);
|
);
|
||||||
|
|||||||
+3
-3
@@ -108,8 +108,8 @@ library
|
|||||||
, gitrev >= 1.2 && < 1.4
|
, gitrev >= 1.2 && < 1.4
|
||||||
, hasql >= 1.6.1.1 && < 1.7
|
, hasql >= 1.6.1.1 && < 1.7
|
||||||
, hasql-dynamic-statements >= 0.3.1 && < 0.4
|
, hasql-dynamic-statements >= 0.3.1 && < 0.4
|
||||||
, hasql-notifications >= 0.2.1.0 && < 0.3
|
, hasql-notifications >= 0.2.1.1 && < 0.3
|
||||||
, hasql-pool >= 0.10 && < 0.11
|
, hasql-pool >= 1.0.1 && < 1.1
|
||||||
, hasql-transaction >= 1.0.1 && < 1.1
|
, hasql-transaction >= 1.0.1 && < 1.1
|
||||||
, heredoc >= 0.2 && < 0.3
|
, heredoc >= 0.2 && < 0.3
|
||||||
, http-types >= 0.12.2 && < 0.13
|
, http-types >= 0.12.2 && < 0.13
|
||||||
@@ -254,7 +254,7 @@ test-suite spec
|
|||||||
, bytestring >= 0.10.8 && < 0.13
|
, bytestring >= 0.10.8 && < 0.13
|
||||||
, case-insensitive >= 1.2 && < 1.3
|
, case-insensitive >= 1.2 && < 1.3
|
||||||
, containers >= 0.5.7 && < 0.7
|
, containers >= 0.5.7 && < 0.7
|
||||||
, hasql-pool >= 0.10 && < 0.11
|
, hasql-pool >= 1.0.1 && < 1.1
|
||||||
, hasql-transaction >= 1.0.1 && < 1.1
|
, hasql-transaction >= 1.0.1 && < 1.1
|
||||||
, heredoc >= 0.2 && < 0.3
|
, heredoc >= 0.2 && < 0.3
|
||||||
, hspec >= 2.3 && < 2.12
|
, hspec >= 2.3 && < 2.12
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import qualified Data.Text as T (unpack)
|
|||||||
import Hasql.Connection (acquire)
|
import Hasql.Connection (acquire)
|
||||||
import qualified Hasql.Notifications as SQL
|
import qualified Hasql.Notifications as SQL
|
||||||
import qualified Hasql.Pool as SQL
|
import qualified Hasql.Pool as SQL
|
||||||
|
import qualified Hasql.Pool.Config as SQL
|
||||||
import qualified Hasql.Session as SQL
|
import qualified Hasql.Session as SQL
|
||||||
import qualified Hasql.Transaction.Sessions as SQL
|
import qualified Hasql.Transaction.Sessions as SQL
|
||||||
import qualified Network.HTTP.Types.Status as HTTP
|
import qualified Network.HTTP.Types.Status as HTTP
|
||||||
@@ -122,7 +123,7 @@ init :: AppConfig -> IO AppState
|
|||||||
init conf@AppConfig{configLogLevel} = do
|
init conf@AppConfig{configLogLevel} = do
|
||||||
loggerState <- Logger.init
|
loggerState <- Logger.init
|
||||||
let observer = Logger.observationLogger loggerState configLogLevel
|
let observer = Logger.observationLogger loggerState configLogLevel
|
||||||
pool <- initPool conf
|
pool <- initPool conf observer
|
||||||
(sock, adminSock) <- initSockets conf
|
(sock, adminSock) <- initSockets conf
|
||||||
state' <- initWithPool (sock, adminSock) pool conf loggerState observer
|
state' <- initWithPool (sock, adminSock) pool conf loggerState observer
|
||||||
pure state' { stateSocketREST = sock, stateSocketAdmin = adminSock}
|
pure state' { stateSocketREST = sock, stateSocketAdmin = adminSock}
|
||||||
@@ -193,14 +194,16 @@ initSockets AppConfig{..} = do
|
|||||||
|
|
||||||
pure (sock, adminSock)
|
pure (sock, adminSock)
|
||||||
|
|
||||||
initPool :: AppConfig -> IO SQL.Pool
|
initPool :: AppConfig -> ObservationHandler -> IO SQL.Pool
|
||||||
initPool AppConfig{..} =
|
initPool AppConfig{..} observer =
|
||||||
SQL.acquire
|
SQL.acquire $ SQL.settings
|
||||||
configDbPoolSize
|
[ SQL.size configDbPoolSize
|
||||||
(fromIntegral configDbPoolAcquisitionTimeout)
|
, SQL.acquisitionTimeout $ fromIntegral configDbPoolAcquisitionTimeout
|
||||||
(fromIntegral configDbPoolMaxLifetime)
|
, SQL.agingTimeout $ fromIntegral configDbPoolMaxLifetime
|
||||||
(fromIntegral configDbPoolMaxIdletime)
|
, SQL.idlenessTimeout $ fromIntegral configDbPoolMaxIdletime
|
||||||
(toUtf8 $ addFallbackAppName prettyVersion configDbUri)
|
, SQL.staticConnectionSettings (toUtf8 $ addFallbackAppName prettyVersion configDbUri)
|
||||||
|
, SQL.observationHandler $ observer . HasqlPoolObs
|
||||||
|
]
|
||||||
|
|
||||||
-- | Run an action with a database connection.
|
-- | Run an action with a database connection.
|
||||||
usePool :: AppState -> SQL.Session a -> IO (Either SQL.UsageError a)
|
usePool :: AppState -> SQL.Session a -> IO (Either SQL.UsageError a)
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ observationLogger loggerState logLevel obs = case obs of
|
|||||||
o@(QueryErrorCodeHighObs _) -> do
|
o@(QueryErrorCodeHighObs _) -> do
|
||||||
when (logLevel >= LogError) $ do
|
when (logLevel >= LogError) $ do
|
||||||
logWithZTime loggerState $ observationMessage o
|
logWithZTime loggerState $ observationMessage o
|
||||||
|
o@(HasqlPoolObs _) -> do
|
||||||
|
when (logLevel >= LogInfo) $ do
|
||||||
|
logWithZTime loggerState $ observationMessage o
|
||||||
o ->
|
o ->
|
||||||
logWithZTime loggerState $ observationMessage o
|
logWithZTime loggerState $ observationMessage o
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import qualified Data.Text as T
|
|||||||
import qualified Data.Text.Encoding as T
|
import qualified Data.Text.Encoding as T
|
||||||
import qualified Hasql.Connection as SQL
|
import qualified Hasql.Connection as SQL
|
||||||
import qualified Hasql.Pool as SQL
|
import qualified Hasql.Pool as SQL
|
||||||
|
import qualified Hasql.Pool.Observation as SQL
|
||||||
import qualified Network.Socket as NS
|
import qualified Network.Socket as NS
|
||||||
import Numeric (showFFloat)
|
import Numeric (showFFloat)
|
||||||
import qualified PostgREST.Error as Error
|
import qualified PostgREST.Error as Error
|
||||||
@@ -50,6 +51,7 @@ data Observation
|
|||||||
| QueryRoleSettingsErrorObs SQL.UsageError
|
| QueryRoleSettingsErrorObs SQL.UsageError
|
||||||
| QueryErrorCodeHighObs SQL.UsageError
|
| QueryErrorCodeHighObs SQL.UsageError
|
||||||
| PoolAcqTimeoutObs SQL.UsageError
|
| PoolAcqTimeoutObs SQL.UsageError
|
||||||
|
| HasqlPoolObs SQL.Observation
|
||||||
|
|
||||||
type ObservationHandler = Observation -> IO ()
|
type ObservationHandler = Observation -> IO ()
|
||||||
|
|
||||||
@@ -111,6 +113,18 @@ observationMessage = \case
|
|||||||
"Config reloaded"
|
"Config reloaded"
|
||||||
PoolAcqTimeoutObs usageErr ->
|
PoolAcqTimeoutObs usageErr ->
|
||||||
jsonMessage usageErr
|
jsonMessage usageErr
|
||||||
|
HasqlPoolObs (SQL.ConnectionObservation uuid status) ->
|
||||||
|
"Connection " <> show uuid <> (
|
||||||
|
case status of
|
||||||
|
SQL.ConnectingConnectionStatus -> " is being established"
|
||||||
|
SQL.ReadyForUseConnectionStatus -> " is available"
|
||||||
|
SQL.InUseConnectionStatus -> " is used"
|
||||||
|
SQL.TerminatedConnectionStatus reason -> " is terminated due to " <> case reason of
|
||||||
|
SQL.AgingConnectionTerminationReason -> "max lifetime"
|
||||||
|
SQL.IdlenessConnectionTerminationReason -> "max idletime"
|
||||||
|
SQL.ReleaseConnectionTerminationReason -> "release"
|
||||||
|
SQL.NetworkErrorConnectionTerminationReason _ -> "network error" -- usage error is already logged, no need to repeat the same message.
|
||||||
|
)
|
||||||
where
|
where
|
||||||
showMillis :: Double -> Text
|
showMillis :: Double -> Text
|
||||||
showMillis x = toS $ showFFloat (Just 1) (x * 1000) ""
|
showMillis x = toS $ showFFloat (Just 1) (x * 1000) ""
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ nix:
|
|||||||
extra-deps:
|
extra-deps:
|
||||||
- configurator-pg-0.2.7
|
- configurator-pg-0.2.7
|
||||||
- fuzzyset-0.2.4
|
- fuzzyset-0.2.4
|
||||||
- hasql-notifications-0.2.1.0
|
- hasql-notifications-0.2.1.1
|
||||||
- hasql-pool-0.10
|
- hasql-pool-1.0.1
|
||||||
- megaparsec-9.2.2
|
- megaparsec-9.2.2
|
||||||
- postgresql-libpq-0.10.0.0
|
- postgresql-libpq-0.10.0.0
|
||||||
|
|||||||
+7
-7
@@ -19,19 +19,19 @@ packages:
|
|||||||
original:
|
original:
|
||||||
hackage: fuzzyset-0.2.4
|
hackage: fuzzyset-0.2.4
|
||||||
- completed:
|
- completed:
|
||||||
hackage: hasql-notifications-0.2.1.0@sha256:c9c0ba3ac0866142836d2d0a08a0d10a945bcbe1c163ee1ef6aed407d046a24e,2022
|
hackage: hasql-notifications-0.2.1.1@sha256:e4f41f462e96f3af3f088b747daeeb2275ceaebd0e4b0d05187bd10d329afada,2021
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
sha256: 65a646ed0a77ee1dfe902faedc590103ee385eaa78f407c4cf02a1eb5783d464
|
sha256: f9041b1c242436324372f6be9a4f2e5cf70203c3efad36a4e9ea4cca5113a2ec
|
||||||
size: 452
|
size: 452
|
||||||
original:
|
original:
|
||||||
hackage: hasql-notifications-0.2.1.0
|
hackage: hasql-notifications-0.2.1.1
|
||||||
- completed:
|
- completed:
|
||||||
hackage: hasql-pool-0.10@sha256:912197a328acb85505f98bb9700d61f366b87659ca45126c5c2d636687b801c3,2112
|
hackage: hasql-pool-1.0.1@sha256:3cfb4c7153a6c536ac7e126c17723e6d26ee03794954deed2d72bcc826d05a40,2302
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
sha256: b655c540a49764a8d16b62941137e295b936b96edc0785eb9250972f0f92dc47
|
sha256: d98e1269bdd60989b0eb0b84e1d5357eaa9f92821439d9f206663b7251ee95b2
|
||||||
size: 346
|
size: 799
|
||||||
original:
|
original:
|
||||||
hackage: hasql-pool-0.10
|
hackage: hasql-pool-1.0.1
|
||||||
- completed:
|
- completed:
|
||||||
hackage: megaparsec-9.2.2@sha256:c306a135ec25d91d252032c6128f03598a00e87ea12fcf5fc4878fdffc75c768,3219
|
hackage: megaparsec-9.2.2@sha256:c306a135ec25d91d252032c6128f03598a00e87ea12fcf5fc4878fdffc75c768,3219
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
|
|||||||
+16
-7
@@ -593,13 +593,13 @@ def test_pool_acquisition_timeout(level, defaultenv, metapostgrest):
|
|||||||
assert data["message"] == "Timed out acquiring connection from connection pool."
|
assert data["message"] == "Timed out acquiring connection from connection pool."
|
||||||
|
|
||||||
# ensure the message appears on the logs as well
|
# ensure the message appears on the logs as well
|
||||||
output = sorted(postgrest.read_stdout(nlines=2))
|
output = sorted(postgrest.read_stdout(nlines=3))
|
||||||
|
|
||||||
if level == "crit":
|
if level == "crit":
|
||||||
assert len(output) == 0
|
assert len(output) == 0
|
||||||
else:
|
elif level == "info":
|
||||||
assert " 504 " in output[0]
|
assert " 504 " in output[0]
|
||||||
assert "Timed out acquiring connection from connection pool." in output[1]
|
assert "Timed out acquiring connection from connection pool." in output[2]
|
||||||
|
|
||||||
|
|
||||||
def test_change_statement_timeout_held_connection(defaultenv, metapostgrest):
|
def test_change_statement_timeout_held_connection(defaultenv, metapostgrest):
|
||||||
@@ -792,7 +792,7 @@ def test_log_level(level, defaultenv):
|
|||||||
response = postgrest.session.get("/")
|
response = postgrest.session.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
output = sorted(postgrest.read_stdout(nlines=3))
|
output = sorted(postgrest.read_stdout(nlines=7))
|
||||||
|
|
||||||
if level == "crit":
|
if level == "crit":
|
||||||
assert len(output) == 0
|
assert len(output) == 0
|
||||||
@@ -825,7 +825,11 @@ def test_log_level(level, defaultenv):
|
|||||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 - "" "python-requests/.+"',
|
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 - "" "python-requests/.+"',
|
||||||
output[2],
|
output[2],
|
||||||
)
|
)
|
||||||
assert len(output) == 3
|
assert "Connection" and "is available" in output[3]
|
||||||
|
assert "Connection" and "is available" in output[4]
|
||||||
|
assert "Connection" and "is used" in output[5]
|
||||||
|
assert "Connection" and "is used" in output[6]
|
||||||
|
assert len(output) == 7
|
||||||
|
|
||||||
|
|
||||||
def test_no_pool_connection_required_on_bad_http_logic(defaultenv):
|
def test_no_pool_connection_required_on_bad_http_logic(defaultenv):
|
||||||
@@ -1083,12 +1087,14 @@ def test_get_pgrst_version_with_keyval_connection_string(defaultenv):
|
|||||||
def test_log_postgrest_version(defaultenv):
|
def test_log_postgrest_version(defaultenv):
|
||||||
"Should show the PostgREST version in the logs"
|
"Should show the PostgREST version in the logs"
|
||||||
|
|
||||||
|
env = {**defaultenv, "PGRST_LOG_LEVEL": "crit"}
|
||||||
|
|
||||||
with run(env=defaultenv, no_startup_stdout=False) as postgrest:
|
with run(env=defaultenv, no_startup_stdout=False) as postgrest:
|
||||||
version = postgrest.session.head("/").headers["Server"].split("/")[1]
|
version = postgrest.session.head("/").headers["Server"].split("/")[1]
|
||||||
|
|
||||||
output = sorted(postgrest.read_stdout(nlines=5))
|
output = sorted(postgrest.read_stdout(nlines=5))
|
||||||
|
|
||||||
assert "Starting PostgREST %s..." % version in output[3]
|
assert "Starting PostgREST %s..." % version in output[4]
|
||||||
|
|
||||||
|
|
||||||
def test_succeed_w_role_having_superuser_settings(defaultenv):
|
def test_succeed_w_role_having_superuser_settings(defaultenv):
|
||||||
@@ -1371,10 +1377,13 @@ def test_db_error_logging_to_stderr(level, defaultenv, metapostgrest):
|
|||||||
assert response.status_code == 500
|
assert response.status_code == 500
|
||||||
|
|
||||||
# ensure the message appears on the logs
|
# ensure the message appears on the logs
|
||||||
output = sorted(postgrest.read_stdout(nlines=2))
|
output = sorted(postgrest.read_stdout(nlines=4))
|
||||||
|
|
||||||
if level == "crit":
|
if level == "crit":
|
||||||
assert len(output) == 0
|
assert len(output) == 0
|
||||||
|
elif level == "info":
|
||||||
|
assert " 500 " in output[0]
|
||||||
|
assert "canceling statement due to statement timeout" in output[3]
|
||||||
else:
|
else:
|
||||||
assert " 500 " in output[0]
|
assert " 500 " in output[0]
|
||||||
assert "canceling statement due to statement timeout" in output[1]
|
assert "canceling statement due to statement timeout" in output[1]
|
||||||
|
|||||||
+8
-1
@@ -1,6 +1,7 @@
|
|||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
import qualified Hasql.Pool as P
|
import qualified Hasql.Pool as P
|
||||||
|
import qualified Hasql.Pool.Config as P
|
||||||
import qualified Hasql.Transaction.Sessions as HT
|
import qualified Hasql.Transaction.Sessions as HT
|
||||||
|
|
||||||
import Data.Function (id)
|
import Data.Function (id)
|
||||||
@@ -70,7 +71,13 @@ import qualified Feature.RpcPreRequestGucsSpec
|
|||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
let observer = const $ pure ()
|
let observer = const $ pure ()
|
||||||
pool <- P.acquire 3 10 60 60 $ toUtf8 $ configDbUri testCfg
|
pool <- P.acquire $ P.settings
|
||||||
|
[ P.size 3
|
||||||
|
, P.acquisitionTimeout 10
|
||||||
|
, P.agingTimeout 60
|
||||||
|
, P.idlenessTimeout 60
|
||||||
|
, P.staticConnectionSettings (toUtf8 $ configDbUri testCfg)
|
||||||
|
]
|
||||||
|
|
||||||
actualPgVersion <- either (panic . show) id <$> P.use pool (queryPgVersion False)
|
actualPgVersion <- either (panic . show) id <$> P.use pool (queryPgVersion False)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user