mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
server: improve event trigger logging
https://github.com/hasura/graphql-engine-mono/pull/2286 GitOrigin-RevId: b3232a08dd7ec2aa0c9e7a2fada0e05e34a1897b
This commit is contained in:
parent
d358d06e68
commit
ccf97ab6b0
@ -3,6 +3,12 @@
|
||||
## Next release
|
||||
(Add entries below in the order of server, console, cli, docs, others)
|
||||
|
||||
|
||||
- server: improve the event trigger logging on errors
|
||||
NOTE: This change introduces a breaking change, earlier when there
|
||||
was a client error when trying to process an event, then the status was reported as 1000. Now, the status 1000 has been removed and if any status was received by the graphql-engine from the webhook, the status
|
||||
of the invocation will be the same otherwise it will be `NULL`.
|
||||
- server: support `extensions` field in error responses from action webhook endpoints (fix #4001)
|
||||
- server: fix custom-check based permissions for MSSQL (#7429)
|
||||
- server: remove identity notion for table columns (fix #7557)
|
||||
- server: add webhook transformations for Actions and EventTriggers
|
||||
@ -15,8 +21,6 @@
|
||||
- server: prevent empty subscription roots in the schema (#6898)
|
||||
- console: support tracking of functions with return a single row
|
||||
|
||||
- server: support `extensions` field in error responses from action webhook endpoints (fix #4001)
|
||||
|
||||
## v2.0.9
|
||||
|
||||
- server: fix export_metadata V2 bug which included cron triggers with `include_in_metadata: false`
|
||||
|
@ -227,7 +227,7 @@ insertInvocation invo = do
|
||||
INSERT INTO hdb_catalog.event_invocation_logs (event_id, status, request, response)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
|] ( iEventId invo
|
||||
, fromIntegral $ iStatus invo :: Int64
|
||||
, fromIntegral <$> iStatus invo :: Maybe Int64
|
||||
, Q.AltJ $ toJSON $ iRequest invo
|
||||
, Q.AltJ $ toJSON $ iResponse invo) True
|
||||
Q.unitQE defaultTxErrorHandler [Q.sql|
|
||||
|
@ -79,6 +79,7 @@ import Hasura.Backends.Postgres.SQL.Types hiding (TableName)
|
||||
import Hasura.Base.Error
|
||||
import Hasura.Eventing.Common
|
||||
import Hasura.Eventing.HTTP
|
||||
import Hasura.HTTP (getHTTPExceptionStatus)
|
||||
import Hasura.RQL.DDL.Headers
|
||||
import Hasura.RQL.DDL.RequestTransform
|
||||
import Hasura.RQL.Types
|
||||
@ -372,7 +373,7 @@ processEventQueue logger logBehavior httpMgr getSchemaCache EventEngineCtx{..} L
|
||||
runExceptT (setRetry sourceConfig e (addUTCTime 60 currentTime) maintenanceModeVersion) >>=
|
||||
flip onLeft logQErr
|
||||
Right eti -> runTraceT (spanName eti) do
|
||||
let webhookUrl = T.unpack $ wciCachedValue $ etiWebhookInfo eti
|
||||
let webhook = wciCachedValue $ etiWebhookInfo eti
|
||||
retryConf = etiRetryConf eti
|
||||
timeoutSeconds = fromMaybe defaultTimeoutSeconds (rcTimeoutSec retryConf)
|
||||
httpTimeout = HTTP.responseTimeoutMicro (timeoutSeconds * 1000000)
|
||||
@ -385,7 +386,7 @@ processEventQueue logger logBehavior httpMgr getSchemaCache EventEngineCtx{..} L
|
||||
res <- runExceptT $ do
|
||||
-- reqDetails contains the pre and post transformation
|
||||
-- request for logging purposes.
|
||||
reqDetails <- mkRequest headers httpTimeout payload dataTransform webhookUrl
|
||||
reqDetails <- mkRequest headers httpTimeout payload dataTransform webhook
|
||||
let logger' res details = logHTTPForET res extraLogCtx details logBehavior
|
||||
-- Event Triggers have a configuration parameter called
|
||||
-- HASURA_GRAPHQL_EVENTS_HTTP_WORKERS, which is used
|
||||
@ -432,7 +433,7 @@ processSuccess sourceConfig e reqHeaders ep maintenanceModeVersion resp = do
|
||||
let respBody = hrsBody resp
|
||||
respHeaders = hrsHeaders resp
|
||||
respStatus = hrsStatus resp
|
||||
invocation = mkInvocation ep respStatus reqHeaders respBody respHeaders
|
||||
invocation = mkInvocation ep (Just respStatus) reqHeaders respBody respHeaders
|
||||
recordSuccess @b sourceConfig e invocation maintenanceModeVersion
|
||||
|
||||
processError
|
||||
@ -450,20 +451,17 @@ processError
|
||||
-> m (Either QErr ())
|
||||
processError sourceConfig e retryConf reqHeaders ep maintenanceModeVersion err = do
|
||||
let invocation = case err of
|
||||
HClient excp -> do
|
||||
let errMsg = TBS.fromLBS $ encode $ show excp
|
||||
mkInvocation ep 1000 reqHeaders errMsg []
|
||||
HParse _ detail -> do
|
||||
let errMsg = TBS.fromLBS $ encode detail
|
||||
mkInvocation ep 1001 reqHeaders errMsg []
|
||||
HClient httpException ->
|
||||
let statusMaybe = getHTTPExceptionStatus httpException
|
||||
in mkInvocation ep statusMaybe reqHeaders (TBS.fromLBS (encode httpException)) []
|
||||
HStatus errResp -> do
|
||||
let respPayload = hrsBody errResp
|
||||
respHeaders = hrsHeaders errResp
|
||||
respStatus = hrsStatus errResp
|
||||
mkInvocation ep respStatus reqHeaders respPayload respHeaders
|
||||
mkInvocation ep (Just respStatus) reqHeaders respPayload respHeaders
|
||||
HOther detail -> do
|
||||
let errMsg = TBS.fromLBS $ encode detail
|
||||
mkInvocation ep 500 reqHeaders errMsg []
|
||||
mkInvocation ep (Just 500) reqHeaders errMsg []
|
||||
retryOrError <- retryOrSetError e retryConf err
|
||||
recordError @b sourceConfig e invocation retryOrError maintenanceModeVersion
|
||||
|
||||
@ -497,21 +495,25 @@ retryOrSetError e retryConf err = do
|
||||
mkInvocation
|
||||
:: Backend b
|
||||
=> EventPayload b
|
||||
-> Int
|
||||
-> Maybe Int
|
||||
-> [HeaderConf]
|
||||
-> TBS.TByteString
|
||||
-> [HeaderConf]
|
||||
-> Invocation 'EventType
|
||||
mkInvocation ep status reqHeaders respBody respHeaders
|
||||
= let resp = if isClientError status
|
||||
then mkClientErr respBody
|
||||
else mkResp status respBody respHeaders
|
||||
in
|
||||
Invocation
|
||||
(epId ep)
|
||||
status
|
||||
(mkWebhookReq (toJSON ep) reqHeaders invocationVersionET)
|
||||
resp
|
||||
mkInvocation eventPayload statusMaybe reqHeaders respBody respHeaders =
|
||||
let resp =
|
||||
case statusMaybe of
|
||||
Nothing -> mkClientErr respBody
|
||||
Just status ->
|
||||
if status >= 200 && status < 300
|
||||
then mkResp status respBody respHeaders
|
||||
else mkClientErr respBody
|
||||
in
|
||||
Invocation
|
||||
(epId eventPayload)
|
||||
statusMaybe
|
||||
(mkWebhookReq (toJSON eventPayload) reqHeaders invocationVersionET)
|
||||
resp
|
||||
|
||||
logQErr :: ( MonadReader r m, Has (L.Logger L.Hasura) r, MonadIO m) => QErr -> m ()
|
||||
logQErr err = do
|
||||
|
@ -27,6 +27,7 @@ module Hasura.Eventing.HTTP
|
||||
, mkClientErr
|
||||
, mkWebhookReq
|
||||
, mkResp
|
||||
, mkInvocationResp
|
||||
, LogBehavior(..)
|
||||
, ResponseLogBehavior(..)
|
||||
, HeaderLogBehavior(..)
|
||||
@ -57,11 +58,12 @@ import Data.Aeson.TH
|
||||
import Data.Either
|
||||
import Data.Has
|
||||
import Data.Int (Int64)
|
||||
import Hasura.HTTP (addDefaultHeaders)
|
||||
import Hasura.HTTP (HttpException (..), addDefaultHeaders)
|
||||
import Hasura.Logging
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.DDL.Headers
|
||||
import Hasura.RQL.DDL.RequestTransform (RequestTransform, applyRequestTransform)
|
||||
import Hasura.RQL.Types.Common (ResolvedWebhook (..))
|
||||
import Hasura.RQL.Types.EventTrigger
|
||||
import Hasura.RQL.Types.Eventing
|
||||
import Hasura.Server.Version (HasVersion)
|
||||
@ -95,7 +97,6 @@ data HTTPResp (a :: TriggerTypes)
|
||||
, hrsBody :: !TBS.TByteString
|
||||
, hrsSize :: !Int64
|
||||
} deriving (Show, Eq)
|
||||
|
||||
$(deriveToJSON hasuraJSON{omitNothingFields=True} ''HTTPResp)
|
||||
|
||||
instance ToEngineLog (HTTPResp 'EventType) Hasura where
|
||||
@ -105,22 +106,18 @@ instance ToEngineLog (HTTPResp 'ScheduledType) Hasura where
|
||||
toEngineLog resp = (LevelInfo, scheduledTriggerLogType, toJSON resp)
|
||||
|
||||
data HTTPErr (a :: TriggerTypes)
|
||||
= HClient !HTTP.HttpException
|
||||
| HParse !HTTP.Status !String
|
||||
= HClient !HttpException
|
||||
| HStatus !(HTTPResp a)
|
||||
| HOther !String
|
||||
deriving (Show)
|
||||
|
||||
instance ToJSON (HTTPErr a) where
|
||||
toJSON err = toObj $ case err of
|
||||
(HClient e) -> ("client", toJSON $ show e)
|
||||
(HParse st e) ->
|
||||
( "parse"
|
||||
, toJSON (HTTP.statusCode st, show e)
|
||||
)
|
||||
(HClient httpException) ->
|
||||
("client", toJSON httpException)
|
||||
(HStatus resp) ->
|
||||
("status", toJSON resp)
|
||||
(HOther e) -> ("internal", toJSON $ show e)
|
||||
(HOther e) -> ("internal", toJSON e)
|
||||
where
|
||||
toObj :: (Text, Value) -> Value
|
||||
toObj (k, v) = object [ "type" .= k
|
||||
@ -200,12 +197,13 @@ isNetworkError = \case
|
||||
HClient he -> isNetworkErrorHC he
|
||||
_ -> False
|
||||
|
||||
isNetworkErrorHC :: HTTP.HttpException -> Bool
|
||||
isNetworkErrorHC = \case
|
||||
HTTP.HttpExceptionRequest _ (HTTP.ConnectionFailure _) -> True
|
||||
HTTP.HttpExceptionRequest _ HTTP.ConnectionTimeout -> True
|
||||
HTTP.HttpExceptionRequest _ HTTP.ResponseTimeout -> True
|
||||
_ -> False
|
||||
isNetworkErrorHC :: HttpException -> Bool
|
||||
isNetworkErrorHC (HttpException exception) =
|
||||
case exception of
|
||||
HTTP.HttpExceptionRequest _ (HTTP.ConnectionFailure _) -> True
|
||||
HTTP.HttpExceptionRequest _ HTTP.ConnectionTimeout -> True
|
||||
HTTP.HttpExceptionRequest _ HTTP.ResponseTimeout -> True
|
||||
_ -> False
|
||||
|
||||
anyBodyParser :: HTTP.Response LBS.ByteString -> Either (HTTPErr a) (HTTPResp a)
|
||||
anyBodyParser resp = do
|
||||
@ -261,7 +259,7 @@ logHTTPForST eitherResp extraLogCtx reqDetails logBehavior = do
|
||||
runHTTP :: (MonadIO m) => HTTP.Manager -> HTTP.Request -> m (Either (HTTPErr a) (HTTPResp a))
|
||||
runHTTP manager req = do
|
||||
res <- liftIO $ try $ HTTP.performRequest req manager
|
||||
return $ either (Left . HClient) anyBodyParser res
|
||||
return $ either (Left . HClient . HttpException) anyBodyParser res
|
||||
|
||||
mkRequest ::
|
||||
MonadError (HTTPErr a) m
|
||||
@ -272,11 +270,11 @@ mkRequest ::
|
||||
-- log the request size. As the logging happens outside the function, we pass
|
||||
-- it the final request body, instead of 'Value'
|
||||
-> Maybe RequestTransform
|
||||
-> String
|
||||
-> ResolvedWebhook
|
||||
-> m RequestDetails
|
||||
mkRequest headers timeout payload mRequestTransform webhook =
|
||||
case HTTP.mkRequestEither (T.pack webhook) of
|
||||
Left excp -> throwError $ HClient excp
|
||||
mkRequest headers timeout payload mRequestTransform (ResolvedWebhook webhook) =
|
||||
case HTTP.mkRequestEither webhook of
|
||||
Left excp -> throwError $ HClient $ HttpException excp
|
||||
Right initReq ->
|
||||
let req = initReq & set HTTP.method "POST"
|
||||
& set HTTP.headers headers
|
||||
@ -319,8 +317,17 @@ mkClientErr message =
|
||||
mkWebhookReq :: Value -> [HeaderConf] -> InvocationVersion -> WebhookRequest
|
||||
mkWebhookReq payload headers = WebhookRequest payload headers
|
||||
|
||||
mkInvocationResp :: Maybe Int -> TBS.TByteString -> [HeaderConf] -> Response a
|
||||
mkInvocationResp statusMaybe responseBody responseHeaders =
|
||||
case statusMaybe of
|
||||
Nothing -> mkClientErr responseBody
|
||||
Just status ->
|
||||
if isClientError status
|
||||
then mkClientErr responseBody
|
||||
else mkResp status responseBody responseHeaders
|
||||
|
||||
isClientError :: Int -> Bool
|
||||
isClientError status = status >= 1000
|
||||
isClientError status = status >= 300
|
||||
|
||||
encodeHeader :: EventHeaderInfo -> HTTP.Header
|
||||
encodeHeader (EventHeaderInfo hconf cache) =
|
||||
|
@ -117,7 +117,6 @@ import qualified Data.HashMap.Strict as Map
|
||||
import qualified Data.List.NonEmpty as NE
|
||||
import qualified Data.Set as Set
|
||||
import qualified Data.TByteString as TBS
|
||||
import qualified Data.Text as T
|
||||
import qualified Database.PG.Query as Q
|
||||
import qualified Network.HTTP.Client.Transformable as HTTP
|
||||
import qualified Text.Builder as TB
|
||||
@ -140,6 +139,7 @@ import Hasura.Base.Error
|
||||
import Hasura.Eventing.Common
|
||||
import Hasura.Eventing.HTTP
|
||||
import Hasura.Eventing.ScheduledTrigger.Types
|
||||
import Hasura.HTTP (getHTTPExceptionStatus)
|
||||
import Hasura.Metadata.Class
|
||||
import Hasura.RQL.DDL.EventTrigger (getHeaderInfosFromConf)
|
||||
import Hasura.RQL.DDL.Headers
|
||||
@ -147,7 +147,6 @@ import Hasura.RQL.Types
|
||||
import Hasura.SQL.Types
|
||||
import Hasura.Server.Version (HasVersion)
|
||||
|
||||
|
||||
-- | runCronEventsGenerator makes sure that all the cron triggers
|
||||
-- have an adequate buffer of cron events.
|
||||
runCronEventsGenerator
|
||||
@ -240,14 +239,13 @@ processCronEvents logger logBehavior httpMgr cronEvents getSC lockedCronEvents =
|
||||
Nothing -> logInternalError $
|
||||
err500 Unexpected "could not find cron trigger in cache"
|
||||
Just CronTriggerInfo{..} -> do
|
||||
let webhookUrl = unResolvedWebhook ctiWebhookInfo
|
||||
payload = ScheduledEventWebhookPayload id' (Just name) st
|
||||
let payload = ScheduledEventWebhookPayload id' (Just name) st
|
||||
(fromMaybe J.Null ctiPayload) ctiComment
|
||||
Nothing
|
||||
retryCtx = RetryContext tries ctiRetryConf
|
||||
finally <- runMetadataStorageT $ flip runReaderT (logger, httpMgr) $
|
||||
processScheduledEvent logBehavior id' ctiHeaders retryCtx
|
||||
payload webhookUrl Cron
|
||||
payload ctiWebhookInfo Cron
|
||||
removeEventFromLockedEvents id' lockedCronEvents
|
||||
onLeft finally logInternalError
|
||||
where
|
||||
@ -276,14 +274,13 @@ processOneOffScheduledEvents env logger logBehavior httpMgr
|
||||
(either logInternalError pure) =<< runMetadataStorageT do
|
||||
webhookInfo <- resolveWebhook env _ooseWebhookConf
|
||||
headerInfo <- getHeaderInfosFromConf env _ooseHeaderConf
|
||||
let webhookUrl = unResolvedWebhook webhookInfo
|
||||
payload = ScheduledEventWebhookPayload _ooseId Nothing
|
||||
let payload = ScheduledEventWebhookPayload _ooseId Nothing
|
||||
_ooseScheduledTime (fromMaybe J.Null _oosePayload)
|
||||
_ooseComment (Just _ooseCreatedAt)
|
||||
retryCtx = RetryContext _ooseTries _ooseRetryConf
|
||||
|
||||
flip runReaderT (logger, httpMgr) $
|
||||
processScheduledEvent logBehavior _ooseId headerInfo retryCtx payload webhookUrl OneOff
|
||||
processScheduledEvent logBehavior _ooseId headerInfo retryCtx payload webhookInfo OneOff
|
||||
removeEventFromLockedEvents _ooseId lockedOneOffScheduledEvents
|
||||
where
|
||||
logInternalError err = liftIO . L.unLogger logger $ ScheduledTriggerInternalErr err
|
||||
@ -331,7 +328,7 @@ processScheduledEvent
|
||||
-> [EventHeaderInfo]
|
||||
-> RetryContext
|
||||
-> ScheduledEventWebhookPayload
|
||||
-> Text
|
||||
-> ResolvedWebhook
|
||||
-> ScheduledEventType
|
||||
-> m ()
|
||||
processScheduledEvent logBehavior eventId eventHeaders retryCtx payload webhookUrl type'
|
||||
@ -350,11 +347,10 @@ processScheduledEvent logBehavior eventId eventHeaders retryCtx payload webhookU
|
||||
extraLogCtx = ExtraLogContext eventId (sewpName payload)
|
||||
webhookReqBodyJson = J.toJSON payload
|
||||
webhookReqBody = J.encode webhookReqBodyJson
|
||||
|
||||
eitherRes <- runExceptT $ do
|
||||
-- reqDetails contains the pre and post transformation
|
||||
-- request for logging purposes.
|
||||
reqDetails <- mkRequest headers httpTimeout webhookReqBody Nothing (T.unpack webhookUrl)
|
||||
reqDetails <- mkRequest headers httpTimeout webhookReqBody Nothing webhookUrl
|
||||
let logger e d = logHTTPForST e extraLogCtx d logBehavior
|
||||
hoistEither =<< lift (invokeRequest reqDetails logger)
|
||||
case eitherRes of
|
||||
@ -376,20 +372,17 @@ processError
|
||||
-> m ()
|
||||
processError eventId retryCtx decodedHeaders type' reqJson err = do
|
||||
let invocation = case err of
|
||||
HClient excp -> do
|
||||
let errMsg = TBS.fromLBS $ J.encode $ show excp
|
||||
mkInvocation eventId 1000 decodedHeaders errMsg [] reqJson
|
||||
HParse _ detail -> do
|
||||
let errMsg = TBS.fromLBS $ J.encode detail
|
||||
mkInvocation eventId 1001 decodedHeaders errMsg [] reqJson
|
||||
HClient httpException ->
|
||||
let statusMaybe = getHTTPExceptionStatus httpException
|
||||
in mkInvocation eventId statusMaybe decodedHeaders (TBS.fromLBS $ J.encode httpException) [] reqJson
|
||||
HStatus errResp -> do
|
||||
let respPayload = hrsBody errResp
|
||||
respHeaders = hrsHeaders errResp
|
||||
respStatus = hrsStatus errResp
|
||||
mkInvocation eventId respStatus decodedHeaders respPayload respHeaders reqJson
|
||||
mkInvocation eventId (Just respStatus) decodedHeaders respPayload respHeaders reqJson
|
||||
HOther detail -> do
|
||||
let errMsg = (TBS.fromLBS $ J.encode detail)
|
||||
mkInvocation eventId 500 decodedHeaders errMsg [] reqJson
|
||||
mkInvocation eventId (Just 500) decodedHeaders errMsg [] reqJson
|
||||
insertScheduledEventInvocation invocation type'
|
||||
retryOrMarkError eventId retryCtx err type'
|
||||
|
||||
@ -461,7 +454,7 @@ processSuccess eventId decodedHeaders type' reqBodyJson resp = do
|
||||
let respBody = hrsBody resp
|
||||
respHeaders = hrsHeaders resp
|
||||
respStatus = hrsStatus resp
|
||||
invocation = mkInvocation eventId respStatus decodedHeaders respBody respHeaders reqBodyJson
|
||||
invocation = mkInvocation eventId (Just respStatus) decodedHeaders respBody respHeaders reqBodyJson
|
||||
insertScheduledEventInvocation invocation type'
|
||||
setScheduledEventOp eventId (SEOpStatus SESDelivered) type'
|
||||
|
||||
@ -473,22 +466,18 @@ processDead eventId type' =
|
||||
|
||||
mkInvocation
|
||||
:: ScheduledEventId
|
||||
-> Int
|
||||
-> Maybe Int
|
||||
-> [HeaderConf]
|
||||
-> TBS.TByteString
|
||||
-> [HeaderConf]
|
||||
-> J.Value
|
||||
-> (Invocation 'ScheduledType)
|
||||
mkInvocation eventId status reqHeaders respBody respHeaders reqBodyJson
|
||||
= let resp = if isClientError status
|
||||
then mkClientErr respBody
|
||||
else mkResp status respBody respHeaders
|
||||
in
|
||||
Invocation
|
||||
eventId
|
||||
status
|
||||
(mkWebhookReq reqBodyJson reqHeaders invocationVersionST)
|
||||
resp
|
||||
mkInvocation eventId status reqHeaders respBody respHeaders reqBodyJson =
|
||||
Invocation
|
||||
eventId
|
||||
status
|
||||
(mkWebhookReq reqBodyJson reqHeaders invocationVersionST)
|
||||
(mkInvocationResp status respBody respHeaders)
|
||||
|
||||
-- metadata database transactions
|
||||
|
||||
@ -580,7 +569,7 @@ insertInvocationTx invo type' = do
|
||||
(event_id, status, request, response)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
|] ( iEventId invo
|
||||
, fromIntegral $ iStatus invo :: Int64
|
||||
, fromIntegral <$> iStatus invo :: Maybe Int64
|
||||
, Q.AltJ $ J.toJSON $ iRequest invo
|
||||
, Q.AltJ $ J.toJSON $ iResponse invo) True
|
||||
Q.unitQE defaultTxErrorHandler [Q.sql|
|
||||
@ -595,7 +584,7 @@ insertInvocationTx invo type' = do
|
||||
(event_id, status, request, response)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
|] ( iEventId invo
|
||||
, fromIntegral $ iStatus invo :: Int64
|
||||
, fromIntegral <$> iStatus invo :: Maybe Int64
|
||||
, Q.AltJ $ J.toJSON $ iRequest invo
|
||||
, Q.AltJ $ J.toJSON $ iResponse invo) True
|
||||
Q.unitQE defaultTxErrorHandler [Q.sql|
|
||||
|
@ -5,6 +5,8 @@ module Hasura.HTTP
|
||||
, addDefaultHeaders
|
||||
, HttpResponse(..)
|
||||
, addHttpResponseHeaders
|
||||
, getHTTPExceptionStatus
|
||||
, serializeHTTPExceptionMessage
|
||||
) where
|
||||
|
||||
import Hasura.Prelude
|
||||
@ -14,10 +16,14 @@ import Data.CaseInsensitive (original)
|
||||
import Data.Text.Conversions (UTF8 (..), convertText)
|
||||
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.HashMap.Strict as M
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as TE
|
||||
import qualified Network.HTTP.Client as HTTP
|
||||
import qualified Network.HTTP.Types as HTTP
|
||||
import qualified Network.Wreq as Wreq
|
||||
|
||||
import Hasura.Server.Utils (redactSensitiveHeader)
|
||||
import Hasura.Server.Version (HasVersion, currentVersion)
|
||||
|
||||
hdrsToText :: [HTTP.Header] -> [(Text, Text)]
|
||||
@ -55,16 +61,68 @@ newtype HttpException
|
||||
{ unHttpException :: HTTP.HttpException }
|
||||
deriving (Show)
|
||||
|
||||
getHTTPExceptionStatus :: HttpException -> Maybe Int
|
||||
getHTTPExceptionStatus = \case
|
||||
(HttpException (HTTP.HttpExceptionRequest _ httpExceptionContent)) ->
|
||||
case httpExceptionContent of
|
||||
HTTP.StatusCodeException response _ -> Just $ HTTP.statusCode $ HTTP.responseStatus response
|
||||
HTTP.ProxyConnectException _ _ status -> Just $ HTTP.statusCode status
|
||||
_ -> Nothing
|
||||
(HttpException (HTTP.InvalidUrlException _ _)) -> Nothing
|
||||
|
||||
serializeHTTPExceptionMessage :: HttpException -> Text
|
||||
serializeHTTPExceptionMessage (HttpException (HTTP.HttpExceptionRequest _ httpExceptionContent)) =
|
||||
case httpExceptionContent of
|
||||
HTTP.StatusCodeException _ _ -> "unexpected"
|
||||
HTTP.TooManyRedirects _ -> "Too many redirects"
|
||||
HTTP.OverlongHeaders -> "Overlong headers"
|
||||
HTTP.ResponseTimeout -> "Response timeout"
|
||||
HTTP.ConnectionTimeout -> "Connection timeout"
|
||||
HTTP.ConnectionFailure _ -> "Connection failure"
|
||||
HTTP.InvalidStatusLine _ -> "Invalid HTTP Status Line"
|
||||
HTTP.InternalException _ -> "Internal Exception"
|
||||
HTTP.ProxyConnectException _ _ _ -> "Proxy connection exception"
|
||||
HTTP.NoResponseDataReceived -> "No response data received"
|
||||
HTTP.TlsNotSupported -> "TLS not supported"
|
||||
HTTP.InvalidDestinationHost _ -> "Invalid destination host"
|
||||
HTTP.InvalidHeader _ -> "Invalid Header"
|
||||
HTTP.InvalidRequestHeader _ -> "Invalid Request Header"
|
||||
HTTP.WrongRequestBodyStreamSize _ _ -> "Wrong request body stream size"
|
||||
HTTP.ResponseBodyTooShort _ _ -> "Response body too short"
|
||||
HTTP.InvalidChunkHeaders -> "Invalid chunk headers"
|
||||
HTTP.IncompleteHeaders -> "Incomplete headers"
|
||||
_ -> "unexpected"
|
||||
serializeHTTPExceptionMessage (HttpException (HTTP.InvalidUrlException url reason)) = T.pack $ "URL: " <> url <> " is invalid because " <> reason
|
||||
|
||||
encodeHTTPRequestJSON :: HTTP.Request -> J.Value
|
||||
encodeHTTPRequestJSON request =
|
||||
J.Object $ M.fromList
|
||||
[ ("host", J.toJSON $ TE.decodeUtf8 $ HTTP.host request)
|
||||
, ("port" , J.toJSON $ HTTP.port request)
|
||||
, ("secure", J.toJSON $ HTTP.secure request)
|
||||
, ("requestHeaders" , J.toJSON $ M.fromList $ hdrsToText $ map redactSensitiveHeader $ HTTP.requestHeaders request)
|
||||
, ("path" , J.toJSON $ TE.decodeUtf8 $ HTTP.path request)
|
||||
, ("queryString", J.toJSON $ TE.decodeUtf8 $ HTTP.queryString request)
|
||||
, ("method", J.toJSON $ TE.decodeUtf8 $ HTTP.method request)
|
||||
, ("responseTimeout", J.String $ tshow $ HTTP.responseTimeout request)
|
||||
]
|
||||
|
||||
instance J.ToJSON HttpException where
|
||||
toJSON = \case
|
||||
(HttpException (HTTP.InvalidUrlException _ e)) ->
|
||||
J.object [ "type" J..= ("invalid_url" :: Text)
|
||||
, "message" J..= e
|
||||
]
|
||||
(HttpException (HTTP.HttpExceptionRequest _ cont)) ->
|
||||
J.object [ "type" J..= ("http_exception" :: Text)
|
||||
, "message" J..= show cont
|
||||
]
|
||||
toJSON httpException =
|
||||
case httpException of
|
||||
(HttpException (HTTP.InvalidUrlException _ e)) ->
|
||||
J.object [ "type" J..= ("invalid_url" :: Text)
|
||||
, "message" J..= e
|
||||
]
|
||||
(HttpException (HTTP.HttpExceptionRequest req _)) ->
|
||||
let statusMaybe = getHTTPExceptionStatus httpException
|
||||
exceptionContent = serializeHTTPExceptionMessage httpException
|
||||
reqJSON = encodeHTTPRequestJSON req
|
||||
in
|
||||
J.object $ [ "type" J..= ("http_exception" :: Text)
|
||||
, "message" J..= exceptionContent
|
||||
, "request" J..= reqJSON
|
||||
] <> maybe mempty (\status -> [ "status" J..= status]) statusMaybe
|
||||
|
||||
data HttpResponse a
|
||||
= HttpResponse
|
||||
|
@ -302,13 +302,13 @@ getWebhookInfoFromConf
|
||||
=> Env.Environment
|
||||
-> WebhookConf
|
||||
-> m WebhookConfInfo
|
||||
getWebhookInfoFromConf env wc = case wc of
|
||||
getWebhookInfoFromConf env webhookConf = case webhookConf of
|
||||
WCValue w -> do
|
||||
resolvedWebhook <- resolveWebhook env w
|
||||
return $ WebhookConfInfo wc $ unResolvedWebhook resolvedWebhook
|
||||
WCEnv we -> do
|
||||
envVal <- getEnv env we
|
||||
return $ WebhookConfInfo wc envVal
|
||||
return $ WebhookConfInfo webhookConf resolvedWebhook
|
||||
WCEnv webhookEnvVar -> do
|
||||
envVal <- getEnv env webhookEnvVar
|
||||
return $ WebhookConfInfo webhookConf (ResolvedWebhook envVal)
|
||||
|
||||
buildEventTriggerInfo
|
||||
:: forall b m
|
||||
|
@ -254,8 +254,10 @@ instance FromJSON NonNegativeDiffTime where
|
||||
False -> fail "negative value not allowed"
|
||||
|
||||
newtype ResolvedWebhook
|
||||
= ResolvedWebhook { unResolvedWebhook :: Text }
|
||||
deriving ( Show, Eq, FromJSON, ToJSON, Hashable, ToTxt)
|
||||
= ResolvedWebhook { unResolvedWebhook :: Text}
|
||||
deriving ( Show, Eq, FromJSON, ToJSON, Hashable, ToTxt, Generic)
|
||||
instance NFData ResolvedWebhook
|
||||
instance Cacheable ResolvedWebhook
|
||||
|
||||
newtype InputWebhook
|
||||
= InputWebhook {unInputWebhook :: URLTemplate}
|
||||
|
@ -43,7 +43,7 @@ import Hasura.Incremental (Cacheable)
|
||||
import Hasura.RQL.DDL.Headers
|
||||
import Hasura.RQL.DDL.RequestTransform (MetadataTransform)
|
||||
import Hasura.RQL.Types.Backend
|
||||
import Hasura.RQL.Types.Common (InputWebhook, SourceName)
|
||||
import Hasura.RQL.Types.Common (InputWebhook, ResolvedWebhook, SourceName)
|
||||
import Hasura.RQL.Types.Eventing
|
||||
import Hasura.SQL.Backend
|
||||
|
||||
@ -143,9 +143,10 @@ instance FromJSON WebhookConf where
|
||||
data WebhookConfInfo
|
||||
= WebhookConfInfo
|
||||
{ wciWebhookConf :: !WebhookConf
|
||||
, wciCachedValue :: !Text
|
||||
, wciCachedValue :: !ResolvedWebhook
|
||||
} deriving (Show, Eq, Generic)
|
||||
instance NFData WebhookConfInfo
|
||||
instance Cacheable WebhookConfInfo
|
||||
$(deriveToJSON hasuraJSON{omitNothingFields=True} ''WebhookConfInfo)
|
||||
|
||||
-- | The table operations on which the event trigger will be invoked.
|
||||
|
@ -81,7 +81,7 @@ instance ToJSON (Response 'ScheduledType) where
|
||||
data Invocation (a :: TriggerTypes)
|
||||
= Invocation
|
||||
{ iEventId :: EventId
|
||||
, iStatus :: Int
|
||||
, iStatus :: Maybe Int
|
||||
, iRequest :: WebhookRequest
|
||||
, iResponse :: Response a
|
||||
}
|
||||
|
@ -307,3 +307,12 @@ deprecatedEnvVars = DeprecatedEnvVars
|
||||
, "HASURA_GRAPHQL_QUERY_PLAN_CACHE_SIZE"
|
||||
, "HASURA_GRAPHQL_STRIPES_PER_READ_REPLICA"
|
||||
]
|
||||
|
||||
sensitiveHeaders :: HashSet HTTP.HeaderName
|
||||
sensitiveHeaders = Set.fromList
|
||||
[ "Authorization"
|
||||
, "Cookie"
|
||||
]
|
||||
|
||||
redactSensitiveHeader :: HTTP.Header -> HTTP.Header
|
||||
redactSensitiveHeader (headerName, value) = (headerName, if headerName `elem` sensitiveHeaders then "<REDACTED>" else value)
|
||||
|
@ -664,4 +664,4 @@ class TestActionTimeout:
|
||||
time.sleep(4)
|
||||
response, _ = check_query(hge_ctx, conf)
|
||||
assert 'errors' in response['data']['create_user']
|
||||
assert 'ResponseTimeout' == response['data']['create_user']['errors']['internal']['error']['message']
|
||||
assert 'Response timeout' == response['data']['create_user']['errors']['internal']['error']['message']
|
||||
|
Loading…
Reference in New Issue
Block a user