feat: Add time to startup/worker logs (#1872)

BREAKING CHANGE

Sends startup/worker logs to stderr to differentiate them from
access logs, which go to stdout
This commit is contained in:
Steve Chavez
2021-06-19 15:55:18 -05:00
committed by GitHub
parent 59e0b006a5
commit 2944cf94c7
7 changed files with 57 additions and 43 deletions
+1 -2
View File
@@ -44,5 +44,4 @@ setBuffering = do
-- output, the buffer overflows, a hFlush is issued or the handle is closed -- output, the buffer overflows, a hFlush is issued or the handle is closed
hSetBuffering stdout LineBuffering hSetBuffering stdout LineBuffering
hSetBuffering stdin LineBuffering hSetBuffering stdin LineBuffering
-- NoBuffering: output is written immediately and never stored in the buffer hSetBuffering stderr LineBuffering
hSetBuffering stderr NoBuffering
+3 -2
View File
@@ -116,13 +116,14 @@ run installHandlers maybeRunWithSocket appState = do
Just socket -> Just socket ->
-- run the postgrest application with user defined socket. Only for UNIX systems -- run the postgrest application with user defined socket. Only for UNIX systems
case maybeRunWithSocket of case maybeRunWithSocket of
Just runWithSocket -> Just runWithSocket -> do
AppState.logWithZTime appState $ "Listening on unix socket " <> show socket
runWithSocket (serverSettings conf) app configServerUnixSocketMode socket runWithSocket (serverSettings conf) app configServerUnixSocketMode socket
Nothing -> Nothing ->
panic "Cannot run with socket on non-unix plattforms." panic "Cannot run with socket on non-unix plattforms."
Nothing -> Nothing ->
do do
putStrLn $ ("Listening on port " :: Text) <> show configServerPort AppState.logWithZTime appState $ "Listening on port " <> show configServerPort
Warp.runSettings (serverSettings conf) app Warp.runSettings (serverSettings conf) app
serverSettings :: AppConfig -> Warp.Settings serverSettings :: AppConfig -> Warp.Settings
+15 -2
View File
@@ -12,6 +12,7 @@ module PostgREST.AppState
, getTime , getTime
, init , init
, initWithPool , initWithPool
, logWithZTime
, putConfig , putConfig
, putDbStructure , putDbStructure
, putIsWorkerOn , putIsWorkerOn
@@ -28,6 +29,8 @@ import Control.AutoUpdate (defaultUpdateSettings, mkAutoUpdate,
updateAction) updateAction)
import Data.IORef (IORef, atomicWriteIORef, newIORef, import Data.IORef (IORef, atomicWriteIORef, newIORef,
readIORef) readIORef)
import Data.Time (ZonedTime, defaultTimeLocale, formatTime,
getZonedTime)
import Data.Time.Clock (UTCTime, getCurrentTime) import Data.Time.Clock (UTCTime, getCurrentTime)
import PostgREST.Config (AppConfig (..)) import PostgREST.Config (AppConfig (..))
@@ -51,7 +54,11 @@ data AppState = AppState
, stateListener :: MVar () , stateListener :: MVar ()
-- | Config that can change at runtime -- | Config that can change at runtime
, stateConf :: IORef AppConfig , stateConf :: IORef AppConfig
-- | Time used for verifying JWT expiration
, stateGetTime :: IO UTCTime , stateGetTime :: IO UTCTime
-- | Time with time zone used for worker logs
, stateGetZTime :: IO ZonedTime
-- | Used for killing the main thread in case a subthread fails
, stateMainThreadId :: ThreadId , stateMainThreadId :: ThreadId
} }
@@ -63,14 +70,14 @@ init conf = do
initWithPool :: P.Pool -> AppConfig -> IO AppState initWithPool :: P.Pool -> AppConfig -> IO AppState
initWithPool newPool conf = initWithPool newPool conf =
AppState newPool AppState newPool
-- assume we're in a supported version when starting, this will be corrected on a later step <$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
<$> newIORef minimumPgVersion
<*> newIORef Nothing <*> newIORef Nothing
<*> newIORef mempty <*> newIORef mempty
<*> newIORef False <*> newIORef False
<*> newEmptyMVar <*> newEmptyMVar
<*> newIORef conf <*> newIORef conf
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime } <*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getZonedTime }
<*> myThreadId <*> myThreadId
initPool :: AppConfig -> IO P.Pool initPool :: AppConfig -> IO P.Pool
@@ -117,6 +124,12 @@ putConfig = atomicWriteIORef . stateConf
getTime :: AppState -> IO UTCTime getTime :: AppState -> IO UTCTime
getTime = stateGetTime getTime = stateGetTime
-- | Log to stderr with local time
logWithZTime :: AppState -> Text -> IO ()
logWithZTime appState txt = do
zTime <- stateGetZTime appState
hPutStrLn stderr $ toS (formatTime defaultTimeLocale "%d/%b/%Y:%T %z: " zTime) <> txt
getMainThreadId :: AppState -> ThreadId getMainThreadId :: AppState -> ThreadId
getMainThreadId = stateMainThreadId getMainThreadId = stateMainThreadId
+5 -14
View File
@@ -15,10 +15,9 @@ import qualified Hasql.Statement as H
import qualified Hasql.Transaction as HT import qualified Hasql.Transaction as HT
import qualified Hasql.Transaction.Sessions as HT import qualified Hasql.Transaction.Sessions as HT
import Data.Text.IO (hPutStrLn)
import Text.InterpolatedString.Perl6 (q) import Text.InterpolatedString.Perl6 (q)
import Protolude hiding (hPutStrLn) import Protolude
queryPgVersion :: H.Session PgVersion queryPgVersion :: H.Session PgVersion
queryPgVersion = H.statement mempty $ H.Statement sql HE.noParams versionRow False queryPgVersion = H.statement mempty $ H.Statement sql HE.noParams versionRow False
@@ -26,18 +25,10 @@ queryPgVersion = H.statement mempty $ H.Statement sql HE.noParams versionRow Fal
sql = "SELECT current_setting('server_version_num')::integer, current_setting('server_version')" sql = "SELECT current_setting('server_version_num')::integer, current_setting('server_version')"
versionRow = HD.singleRow $ PgVersion <$> column HD.int4 <*> column HD.text versionRow = HD.singleRow $ PgVersion <$> column HD.int4 <*> column HD.text
queryDbSettings :: P.Pool -> IO [(Text, Text)] queryDbSettings :: P.Pool -> IO (Either P.UsageError [(Text, Text)])
queryDbSettings pool = do queryDbSettings pool =
result <- P.use pool . HT.transaction HT.ReadCommitted HT.Read $
P.use pool . HT.transaction HT.ReadCommitted HT.Read $ HT.statement mempty dbSettingsStatement
HT.statement mempty dbSettingsStatement
case result of
Left e -> do
hPutStrLn stderr $
"An error ocurred when trying to query database settings for the config parameters:\n"
<> show e
pure []
Right x -> pure x
-- | Get db settings from the connection role. Global settings will be overridden by database specific settings. -- | Get db settings from the connection role. Global settings will be overridden by database specific settings.
dbSettingsStatement :: H.Statement () [(Text, Text)] dbSettingsStatement :: H.Statement () [(Text, Text)]
-1
View File
@@ -23,7 +23,6 @@ import Protolude
runAppWithSocket :: Warp.Settings -> Application -> FileMode -> FilePath -> IO () runAppWithSocket :: Warp.Settings -> Application -> FileMode -> FilePath -> IO ()
runAppWithSocket settings app socketFileMode socketFilePath = runAppWithSocket settings app socketFileMode socketFilePath =
bracket createAndBindSocket Socket.close $ \socket -> do bracket createAndBindSocket Socket.close $ \socket -> do
putStrLn $ ("Listening on unix socket " :: Text) <> show socketFilePath
Socket.listen socket Socket.maxListenQueue Socket.listen socket Socket.maxListenQueue
Warp.runSettingsSocket settings socket app Warp.runSettingsSocket settings socket app
where where
+29 -22
View File
@@ -16,7 +16,6 @@ import qualified Hasql.Transaction.Sessions as HT
import Control.Retry (RetryStatus, capDelay, exponentialBackoff, import Control.Retry (RetryStatus, capDelay, exponentialBackoff,
retrying, rsPreviousDelay) retrying, rsPreviousDelay)
import Data.Text.IO (hPutStrLn)
import PostgREST.AppState (AppState) import PostgREST.AppState (AppState)
import PostgREST.Config (AppConfig (..), readAppConfig) import PostgREST.Config (AppConfig (..), readAppConfig)
@@ -28,7 +27,7 @@ import PostgREST.Error (PgError (PgError), checkIsFatal,
import qualified PostgREST.AppState as AppState import qualified PostgREST.AppState as AppState
import Protolude hiding (hPutStrLn, head, toS) import Protolude hiding (head, toS)
import Protolude.Conv (toS) import Protolude.Conv (toS)
@@ -67,12 +66,12 @@ connectionWorker appState = do
where where
work = do work = do
AppConfig{..} <- AppState.getConfig appState AppConfig{..} <- AppState.getConfig appState
putStrLn ("Attempting to connect to the database..." :: Text) AppState.logWithZTime appState "Attempting to connect to the database..."
connected <- connectionStatus $ AppState.getPool appState connected <- connectionStatus appState
case connected of case connected of
FatalConnectionError reason -> FatalConnectionError reason ->
-- Fatal error when connecting -- Fatal error when connecting
hPutStrLn stderr reason >> killThread (AppState.getMainThreadId appState) AppState.logWithZTime appState reason >> killThread (AppState.getMainThreadId appState)
NotConnected -> NotConnected ->
-- Unreachable because connectionStatus will keep trying to connect -- Unreachable because connectionStatus will keep trying to connect
return () return ()
@@ -81,7 +80,7 @@ connectionWorker appState = do
AppState.putPgVersion appState actualPgVersion AppState.putPgVersion appState actualPgVersion
when configDbChannelEnabled $ when configDbChannelEnabled $
AppState.signalListener appState AppState.signalListener appState
putStrLn ("Connection successful" :: Text) AppState.logWithZTime appState "Connection successful"
-- this could be fail because the connection drops, but the -- this could be fail because the connection drops, but the
-- loadSchemaCache will pick the error and retry again -- loadSchemaCache will pick the error and retry again
when configDbConfig $ reReadConfig False appState when configDbConfig $ reReadConfig False appState
@@ -106,11 +105,12 @@ connectionWorker appState = do
-- --
-- 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.
connectionStatus :: P.Pool -> IO ConnectionStatus connectionStatus :: AppState -> IO ConnectionStatus
connectionStatus pool = connectionStatus appState =
retrying retrySettings shouldRetry $ retrying retrySettings shouldRetry $
const $ P.release pool >> getConnectionStatus const $ P.release pool >> getConnectionStatus
where where
pool = AppState.getPool appState
retrySettings = capDelay delayMicroseconds $ exponentialBackoff backoffMicroseconds retrySettings = capDelay delayMicroseconds $ exponentialBackoff backoffMicroseconds
delayMicroseconds = 32000000 -- 32 seconds delayMicroseconds = 32000000 -- 32 seconds
backoffMicroseconds = 1000000 -- 1 second backoffMicroseconds = 1000000 -- 1 second
@@ -121,7 +121,7 @@ connectionStatus pool =
case pgVersion of case pgVersion of
Left e -> do Left e -> do
let err = PgError False e let err = PgError False e
hPutStrLn stderr . toS $ errorPayload err AppState.logWithZTime appState . toS $ errorPayload err
case checkIsFatal err of case checkIsFatal err of
Just reason -> Just reason ->
return $ FatalConnectionError reason return $ FatalConnectionError reason
@@ -140,7 +140,7 @@ connectionStatus pool =
let let
delay = fromMaybe 0 (rsPreviousDelay rs) `div` backoffMicroseconds delay = fromMaybe 0 (rsPreviousDelay rs) `div` backoffMicroseconds
itShould = NotConnected == isConnSucc itShould = NotConnected == isConnSucc
when itShould . putStrLn $ when itShould . AppState.logWithZTime appState $
"Attempting to reconnect to the database in " "Attempting to reconnect to the database in "
<> (show delay::Text) <> (show delay::Text)
<> " seconds..." <> " seconds..."
@@ -157,17 +157,17 @@ loadSchemaCache appState = do
Left e -> do Left e -> do
let let
err = PgError False e err = PgError False e
putErr = hPutStrLn stderr . toS . errorPayload $ err putErr = AppState.logWithZTime appState . toS $ errorPayload err
case checkIsFatal err of case checkIsFatal err of
Just _ -> do Just _ -> do
hPutStrLn stderr "A fatal error ocurred when loading the schema cache" AppState.logWithZTime appState "A fatal error ocurred when loading the schema cache"
putErr putErr
hPutStrLn stderr $ AppState.logWithZTime appState $
"This is probably a bug in PostgREST, please report it at " "This is probably a bug in PostgREST, please report it at "
<> "https://github.com/PostgREST/postgrest/issues" <> "https://github.com/PostgREST/postgrest/issues"
return SCFatalFail return SCFatalFail
Nothing -> do Nothing -> do
hPutStrLn stderr "An error ocurred when loading the schema cache" AppState.logWithZTime appState "An error ocurred when loading the schema cache"
putErr putErr
return SCOnRetry return SCOnRetry
@@ -175,7 +175,7 @@ loadSchemaCache appState = do
AppState.putDbStructure appState dbStructure AppState.putDbStructure appState dbStructure
when (isJust configDbRootSpec) $ when (isJust configDbRootSpec) $
AppState.putJsonDbS appState $ toS $ JSON.encode dbStructure AppState.putJsonDbS appState $ toS $ JSON.encode dbStructure
putStrLn ("Schema cache loaded" :: Text) AppState.logWithZTime appState "Schema cache loaded"
return SCLoaded return SCLoaded
-- | Starts a dedicated pg connection to LISTEN for notifications. When a -- | Starts a dedicated pg connection to LISTEN for notifications. When a
@@ -189,7 +189,7 @@ listener appState = do
-- The listener has to wait for a signal from the connectionWorker. -- The listener has to wait for a signal from the connectionWorker.
-- This is because when the connection to the db is lost, the listener also -- This is because when the connection to the db is lost, the listener also
-- tries to recover the connection, but not with the same pace as the connectionWorker. -- tries to recover the connection, but not with the same pace as the connectionWorker.
-- Not waiting makes stdout quickly fill with connection retries messages from the listener. -- Not waiting makes stderr quickly fill with connection retries messages from the listener.
AppState.waitListener appState AppState.waitListener appState
-- forkFinally allows to detect if the thread dies -- forkFinally allows to detect if the thread dies
@@ -197,7 +197,7 @@ listener appState = do
dbOrError <- C.acquire $ toS configDbUri dbOrError <- C.acquire $ toS configDbUri
case dbOrError of case dbOrError of
Right db -> do Right db -> do
putStrLn $ "Listening for notifications on the " <> dbChannel <> " channel" AppState.logWithZTime appState $ "Listening for notifications on the " <> dbChannel <> " channel"
N.listen db $ N.toPgIdentifier dbChannel N.listen db $ N.toPgIdentifier dbChannel
N.waitForNotifications handleNotification db N.waitForNotifications handleNotification db
_ -> _ ->
@@ -205,7 +205,7 @@ listener appState = do
where where
handleFinally dbChannel _ = do handleFinally dbChannel _ = do
-- if the thread dies, we try to recover -- if the thread dies, we try to recover
putStrLn $ "Retrying listening for notifications on the " <> dbChannel <> " channel.." AppState.logWithZTime appState $ "Retrying listening for notifications on the " <> dbChannel <> " channel.."
-- assume the pool connection was also lost, call the connection worker -- assume the pool connection was also lost, call the connection worker
connectionWorker appState connectionWorker appState
-- retry the listener -- retry the listener
@@ -228,8 +228,15 @@ reReadConfig :: Bool -> AppState -> IO ()
reReadConfig startingUp appState = do reReadConfig startingUp appState = do
AppConfig{..} <- AppState.getConfig appState AppConfig{..} <- AppState.getConfig appState
dbSettings <- dbSettings <-
if configDbConfig then if configDbConfig then do
queryDbSettings (AppState.getPool appState) qDbSettings <- queryDbSettings $ AppState.getPool appState
case qDbSettings of
Left e -> do
AppState.logWithZTime appState $
"An error ocurred when trying to query database settings for the config parameters:\n"
<> show e
pure []
Right x -> pure x
else else
pure mempty pure mempty
readAppConfig dbSettings configFilePath (Just configDbUri) >>= \case readAppConfig dbSettings configFilePath (Just configDbUri) >>= \case
@@ -237,10 +244,10 @@ reReadConfig startingUp appState = do
if startingUp then if startingUp then
panic err -- die on invalid config if the program is starting up panic err -- die on invalid config if the program is starting up
else else
hPutStrLn stderr $ "Failed re-loading config: " <> err AppState.logWithZTime appState $ "Failed re-loading config: " <> err
Right newConf -> do Right newConf -> do
AppState.putConfig appState newConf AppState.putConfig appState newConf
if startingUp then if startingUp then
pass pass
else else
putStrLn ("Config re-loaded" :: Text) AppState.logWithZTime appState "Config re-loaded"
+4
View File
@@ -684,6 +684,10 @@ def test_invalid_role_claim_key_notify_reload(defaultenv):
with run(env=env) as postgrest: with run(env=env) as postgrest:
postgrest.session.post("/rpc/invalid_role_claim_key_reload") postgrest.session.post("/rpc/invalid_role_claim_key_reload")
# skips the first lines from stderr, the "Attempting to connect to database", "Connection successful", etc.
# this is a hack to avoid readline() from locking up the test
for _ in range(6):
postgrest.process.stderr.readline()
assert "failed to parse role-claim-key value" in str( assert "failed to parse role-claim-key value" in str(
postgrest.process.stderr.readline() postgrest.process.stderr.readline()
) )