fix: do not log internal db errors like 'acquisition timeout' when log-level=crit

This commit is contained in:
Laurence Isla
2023-11-27 23:06:40 -05:00
committed by Steve Chavez
parent 33891e3a73
commit dfa875c8c7
7 changed files with 47 additions and 32 deletions
+1
View File
@@ -35,6 +35,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
+ Shows `set_config('pgrst.setting_name', $1)` instead of `setconfig($1, $2)` + Shows `set_config('pgrst.setting_name', $1)` instead of `setconfig($1, $2)`
+ Does not apply to role settings and `app.settings.*` + Does not apply to role settings and `app.settings.*`
- #2420, Fix bogus message when listening on port 0 - @develop7 - #2420, Fix bogus message when listening on port 0 - @develop7
- #3067, Fix Acquision Timeout errors logging to stderr when `log-level=crit` - @laurenceisla
### Changed ### Changed
+1 -2
View File
@@ -38,7 +38,7 @@ admin appState appConfig req respond = do
isConnectionUp <- isConnectionUp <-
if configDbChannelEnabled appConfig if configDbChannelEnabled appConfig
then AppState.getIsListenerOn appState then AppState.getIsListenerOn appState
else isRight <$> AppState.usePool appState (SQL.sql "SELECT 1") else isRight <$> AppState.usePool appState appConfig (SQL.sql "SELECT 1")
case Wai.pathInfo req of case Wai.pathInfo req of
["ready"] -> ["ready"] ->
@@ -62,4 +62,3 @@ reachMainApp appSock = do
addrFamily (SockAddrInet _ _) = AF_INET addrFamily (SockAddrInet _ _) = AF_INET
addrFamily (SockAddrInet6 {}) = AF_INET6 addrFamily (SockAddrInet6 {}) = AF_INET6
addrFamily (SockAddrUnix _) = AF_UNIX addrFamily (SockAddrUnix _) = AF_UNIX
+4 -4
View File
@@ -157,11 +157,11 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache pgVer authResult@
handleRequest authResult conf appState (Just authRole /= configDbAnonRole) configDbPreparedStatements pgVer apiRequest sCache jwtAndParseTiming handleRequest authResult conf appState (Just authRole /= configDbAnonRole) configDbPreparedStatements pgVer apiRequest sCache jwtAndParseTiming
runDbHandler :: AppState.AppState -> SQL.IsolationLevel -> SQL.Mode -> Bool -> Bool -> DbHandler b -> Handler IO b runDbHandler :: AppState.AppState -> AppConfig -> SQL.IsolationLevel -> SQL.Mode -> Bool -> Bool -> DbHandler b -> Handler IO b
runDbHandler appState isoLvl mode authenticated prepared handler = do runDbHandler appState config isoLvl mode authenticated prepared handler = do
dbResp <- lift $ do dbResp <- lift $ do
let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction
AppState.usePool appState . transaction isoLvl mode $ runExceptT handler AppState.usePool appState config . transaction isoLvl mode $ runExceptT handler
resp <- resp <-
liftEither . mapLeft Error.PgErr $ liftEither . mapLeft Error.PgErr $
@@ -245,7 +245,7 @@ handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@A
roleSettings = fromMaybe mempty (HM.lookup authRole $ configRoleSettings conf) roleSettings = fromMaybe mempty (HM.lookup authRole $ configRoleSettings conf)
roleIsoLvl = HM.findWithDefault SQL.ReadCommitted authRole $ configRoleIsoLvl conf roleIsoLvl = HM.findWithDefault SQL.ReadCommitted authRole $ configRoleIsoLvl conf
runQuery isoLvl timeout mode query = runQuery isoLvl timeout mode query =
runDbHandler appState isoLvl mode authenticated prepared $ do runDbHandler appState conf isoLvl mode authenticated prepared $ do
Query.setPgLocals conf authClaims authRole (HM.toList roleSettings) apiReq timeout Query.setPgLocals conf authClaims authRole (HM.toList roleSettings) apiReq timeout
Query.runPreReq conf Query.runPreReq conf
query query
+19 -17
View File
@@ -59,6 +59,7 @@ import Data.Time (ZonedTime, defaultTimeLocale, formatTime,
import Data.Time.Clock (UTCTime, getCurrentTime) import Data.Time.Clock (UTCTime, getCurrentTime)
import PostgREST.Config (AppConfig (..), import PostgREST.Config (AppConfig (..),
LogLevel (..),
addFallbackAppName, addFallbackAppName,
readAppConfig) readAppConfig)
import PostgREST.Config.Database (queryDbSettings, import PostgREST.Config.Database (queryDbSettings,
@@ -205,16 +206,17 @@ initPool AppConfig{..} =
(toUtf8 $ addFallbackAppName prettyVersion configDbUri) (toUtf8 $ addFallbackAppName prettyVersion configDbUri)
-- | 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 -> AppConfig -> SQL.Session a -> IO (Either SQL.UsageError a)
usePool appState@AppState{..} x = do usePool appState@AppState{..} AppConfig{configLogLevel} x = do
res <- SQL.use statePool x res <- SQL.use statePool x
whenLeft res (\case when (configLogLevel > LogCrit) $ do
SQL.AcquisitionTimeoutUsageError -> debounceLogAcquisitionTimeout -- this can happen rapidly for many requests, so we debounce whenLeft res (\case
error SQL.AcquisitionTimeoutUsageError -> debounceLogAcquisitionTimeout -- this can happen rapidly for many requests, so we debounce
-- TODO We're using the 500 HTTP status for getting all internal db errors but there's no response here. We need a new intermediate type to not rely on the HTTP status. error
| Error.status (Error.PgError False error) >= HTTP.status500 -> logPgrstError appState error -- TODO We're using the 500 HTTP status for getting all internal db errors but there's no response here. We need a new intermediate type to not rely on the HTTP status.
| otherwise -> pure ()) | Error.status (Error.PgError False error) >= HTTP.status500 -> logPgrstError appState error
| otherwise -> pure ())
return res return res
@@ -307,7 +309,7 @@ loadSchemaCache appState = do
conf@AppConfig{..} <- getConfig appState conf@AppConfig{..} <- getConfig appState
result <- result <-
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
usePool appState . transaction SQL.ReadCommitted SQL.Read $ usePool appState conf . transaction SQL.ReadCommitted SQL.Read $
querySchemaCache conf querySchemaCache conf
case result of case result of
Left e -> do Left e -> do
@@ -350,10 +352,10 @@ internalConnectionWorker :: AppState -> IO ()
internalConnectionWorker appState = work internalConnectionWorker appState = work
where where
work = do work = do
AppConfig{..} <- getConfig appState config@AppConfig{..} <- getConfig appState
logWithZTime appState $ "Starting PostgREST " <> T.decodeUtf8 prettyVersion <> "..." logWithZTime appState $ "Starting PostgREST " <> T.decodeUtf8 prettyVersion <> "..."
logWithZTime appState "Attempting to connect to the database..." logWithZTime appState "Attempting to connect to the database..."
connected <- establishConnection appState connected <- establishConnection appState config
case connected of case connected of
FatalConnectionError reason -> FatalConnectionError reason ->
-- Fatal error when connecting -- Fatal error when connecting
@@ -393,8 +395,8 @@ internalConnectionWorker appState = work
-- --
-- The connection tries are capped, but if the connection times out no error is -- The connection tries are capped, but if the connection times out no error is
-- thrown, just 'False' is returned. -- thrown, just 'False' is returned.
establishConnection :: AppState -> IO ConnectionStatus establishConnection :: AppState -> AppConfig -> IO ConnectionStatus
establishConnection appState = establishConnection appState config =
retrying retrySettings shouldRetry $ retrying retrySettings shouldRetry $
const $ flushPool appState >> getConnectionStatus const $ flushPool appState >> getConnectionStatus
where where
@@ -404,7 +406,7 @@ establishConnection appState =
getConnectionStatus :: IO ConnectionStatus getConnectionStatus :: IO ConnectionStatus
getConnectionStatus = do getConnectionStatus = do
pgVersion <- usePool appState $ queryPgVersion False -- No need to prepare the query here, as the connection might not be established pgVersion <- usePool appState config $ queryPgVersion False -- No need to prepare the query here, as the connection might not be established
case pgVersion of case pgVersion of
Left e -> do Left e -> do
logPgrstError appState e logPgrstError appState e
@@ -437,11 +439,11 @@ establishConnection appState =
-- | Re-reads the config plus config options from the db -- | Re-reads the config plus config options from the db
reReadConfig :: Bool -> AppState -> IO () reReadConfig :: Bool -> AppState -> IO ()
reReadConfig startingUp appState = do reReadConfig startingUp appState = do
AppConfig{..} <- getConfig appState config@AppConfig{..} <- getConfig appState
pgVer <- getPgVersion appState pgVer <- getPgVersion appState
dbSettings <- dbSettings <-
if configDbConfig then do if configDbConfig then do
qDbSettings <- usePool appState $ queryDbSettings (dumpQi <$> configDbPreConfig) configDbPreparedStatements qDbSettings <- usePool appState config $ queryDbSettings (dumpQi <$> configDbPreConfig) configDbPreparedStatements
case qDbSettings of case qDbSettings of
Left e -> do Left e -> do
logWithZTime appState logWithZTime appState
@@ -459,7 +461,7 @@ reReadConfig startingUp appState = do
pure mempty pure mempty
(roleSettings, roleIsolationLvl) <- (roleSettings, roleIsolationLvl) <-
if configDbConfig then do if configDbConfig then do
rSettings <- usePool appState $ queryRoleSettings pgVer configDbPreparedStatements rSettings <- usePool appState config $ queryRoleSettings pgVer configDbPreparedStatements
case rSettings of case rSettings of
Left e -> do Left e -> do
logWithZTime appState "An error ocurred when trying to query the role settings" logWithZTime appState "An error ocurred when trying to query the role settings"
+1 -1
View File
@@ -53,7 +53,7 @@ dumpSchema appState = do
conf@AppConfig{..} <- AppState.getConfig appState conf@AppConfig{..} <- AppState.getConfig appState
result <- result <-
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
AppState.usePool appState $ AppState.usePool appState conf $
transaction SQL.ReadCommitted SQL.Read $ transaction SQL.ReadCommitted SQL.Read $
querySchemaCache conf querySchemaCache conf
case result of case result of
+1
View File
@@ -115,6 +115,7 @@ data AppConfig = AppConfig
} }
data LogLevel = LogCrit | LogError | LogWarn | LogInfo data LogLevel = LogCrit | LogError | LogWarn | LogInfo
deriving (Eq, Ord)
dumpLogLevel :: LogLevel -> Text dumpLogLevel :: LogLevel -> Text
dumpLogLevel = \case dumpLogLevel = \case
+20 -8
View File
@@ -555,13 +555,15 @@ def test_pool_size(defaultenv, metapostgrest):
assert delta > 1 and delta < 1.5 assert delta > 1 and delta < 1.5
def test_pool_acquisition_timeout(defaultenv, metapostgrest): @pytest.mark.parametrize("level", ["crit", "error", "warn", "info"])
def test_pool_acquisition_timeout(level, defaultenv, metapostgrest):
"Verify that PGRST_DB_POOL_ACQUISITION_TIMEOUT times out when the pool is empty" "Verify that PGRST_DB_POOL_ACQUISITION_TIMEOUT times out when the pool is empty"
env = { env = {
**defaultenv, **defaultenv,
"PGRST_DB_POOL": "1", "PGRST_DB_POOL": "1",
"PGRST_DB_POOL_ACQUISITION_TIMEOUT": "1", # 1 second "PGRST_DB_POOL_ACQUISITION_TIMEOUT": "1", # 1 second
"PGRST_LOG_LEVEL": level,
} }
with run(env=env, no_pool_connection_available=True) as postgrest: with run(env=env, no_pool_connection_available=True) as postgrest:
@@ -572,8 +574,12 @@ def test_pool_acquisition_timeout(defaultenv, metapostgrest):
# 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=2))
assert " 504 " in output[0]
assert "Timed out acquiring connection from connection pool." in output[1] if level == "crit":
assert len(output) == 0
else:
assert " 504 " in output[0]
assert "Timed out acquiring connection from connection pool." in output[1]
def test_change_statement_timeout_held_connection(defaultenv, metapostgrest): def test_change_statement_timeout_held_connection(defaultenv, metapostgrest):
@@ -1343,23 +1349,29 @@ def test_passes_with_3_sec_statement_and_4_sec_statement_timeout(defaultenv):
assert response.status_code == 204 assert response.status_code == 204
def test_db_error_logging_to_stderr(defaultenv, metapostgrest): @pytest.mark.parametrize("level", ["crit", "error", "warn", "info"])
def test_db_error_logging_to_stderr(level, defaultenv, metapostgrest):
"verify that DB errors are logged to stderr" "verify that DB errors are logged to stderr"
role = "timeout_authenticator" role = "timeout_authenticator"
set_statement_timeout(metapostgrest, role, 1000) set_statement_timeout(metapostgrest, role, 500)
env = { env = {
**defaultenv, **defaultenv,
"PGUSER": role, "PGUSER": role,
"PGRST_DB_ANON_ROLE": role, "PGRST_DB_ANON_ROLE": role,
"PGRST_LOG_LEVEL": level,
} }
with run(env=env) as postgrest: with run(env=env) as postgrest:
response = postgrest.session.get("/rpc/sleep?seconds=1.5") response = postgrest.session.get("/rpc/sleep?seconds=1")
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=2))
assert " 500 " in output[0]
assert "canceling statement due to statement timeout" in output[1] if level == "crit":
assert len(output) == 0
else:
assert " 500 " in output[0]
assert "canceling statement due to statement timeout" in output[1]