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:
+1
-2
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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()
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user