fix: do not log internal db errors like 'acquisition timeout' when log-level=crit
This commit is contained in:
committed by
Steve Chavez
parent
33891e3a73
commit
dfa875c8c7
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user