-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add debugLog and defaultExceptionResponse #34
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,39 @@ | ||
{-# LANGUAGE BangPatterns #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE RecordWildCards #-} | ||
|
||
module Web.Template.Log | ||
( bcdlog | ||
, bcdlog400 | ||
, debugLog | ||
, debugLogHandler | ||
|
||
, logMiddlewareCustom | ||
, AccessLogRecord(..) | ||
, formatTimeIso | ||
|
||
, userIdVaultKey | ||
, tokenVaultKey | ||
, pTokenVaultKey | ||
) where | ||
|
||
import Control.Monad (forM_, when) | ||
import Crypto.JWT (ClaimsSet) | ||
import Data.Aeson (fromEncoding, pairs, (.=)) | ||
import Data.ByteString.Builder (hPutBuilder, toLazyByteString) | ||
import Data.IORef (IORef, newIORef, readIORef) | ||
import Data.Text as T (Text, pack, unwords) | ||
import Data.Text.Encoding (decodeUtf8) | ||
import qualified Data.Text.Encoding.Error as TE | ||
import qualified Data.Text.IO as TIO | ||
import qualified Data.Text.Lazy as TL | ||
import qualified Data.Text.Lazy.Encoding as TLE (decodeUtf8With) | ||
import qualified Data.Text.Lazy.IO as TLIO | ||
import Data.Time (ZonedTime, defaultTimeLocale, formatTime, | ||
nominalDiffTimeToSeconds, utcToLocalZonedTime) | ||
import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime, posixSecondsToUTCTime) | ||
import Data.Vault.Lazy (Key, insert, newKey) | ||
import GHC.Generics (Generic) | ||
import Network.HTTP.Types.Status (Status (..)) | ||
import Network.Wai (Middleware, rawPathInfo, requestMethod, responseStatus, | ||
vault) | ||
|
@@ -42,14 +54,33 @@ pTokenVaultKey :: Key (IORef (Maybe ClaimsSet)) | |
pTokenVaultKey = unsafePerformIO newKey | ||
{-# NOINLINE pTokenVaultKey #-} | ||
|
||
data AccessLogRecord | ||
= AccessLogRecord | ||
{ alStart :: !POSIXTime | ||
, alFinishApp :: !POSIXTime | ||
, alFinishNetwork :: !POSIXTime | ||
, alMsg :: !Text | ||
, alStatus :: !Int | ||
, alURL :: !Text | ||
, alUserId :: !(Maybe Text) | ||
, alResponseBody :: Maybe TL.Text | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. а зачем тут специально ленивый текст? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Потому что из респонса возвращается ленивый |
||
} | ||
deriving (Show, Generic) | ||
|
||
bcdlog :: Middleware | ||
bcdlog = logMiddleware False | ||
|
||
bcdlog400 :: Middleware | ||
bcdlog400 = logMiddleware True | ||
|
||
debugLog :: Middleware | ||
debugLog = logMiddlewareCustom True $ Just debugLogHandler | ||
|
||
logMiddleware :: Bool -> Middleware | ||
logMiddleware log400 app request respond = do | ||
logMiddleware log400 = logMiddlewareCustom log400 Nothing | ||
|
||
logMiddlewareCustom :: Bool -> Maybe (AccessLogRecord -> IO ()) -> Middleware | ||
logMiddlewareCustom log400 mLogAction app request respond = do | ||
let | ||
url = decodeUtf8 $ rawPathInfo request | ||
method = decodeUtf8 $ requestMethod request | ||
|
@@ -85,29 +116,63 @@ logMiddleware log400 app request respond = do | |
-- but those may be big. | ||
ResponseBuilder _ _ b -> Just b | ||
_ -> Nothing | ||
logLine = pairs | ||
( "datetime" .= toIso startZoned | ||
<> "timestamp" .= floor @_ @Int (toMs start) | ||
<> "duration" .= toMs (finishApp - start) | ||
<> "send_duration" .= toMs (finishNetwork - finishApp) | ||
<> "level" .= INFO | ||
<> "app" .= ("scotty" :: Text) | ||
<> "msg" .= msg' | ||
<> "status" .= statusC | ||
<> "url" .= url | ||
<> maybe mempty ("userId" .=) userId | ||
<> if log400 && statusC >= 400 | ||
then maybe mempty (\b -> "response" .= TLE.decodeUtf8With TE.lenientDecode (toLazyByteString b)) responseBody | ||
else mempty | ||
) | ||
|
||
hPutBuilder stdout (fromEncoding logLine <> "\n") | ||
hFlush stdout | ||
responseBodyText = TLE.decodeUtf8With TE.lenientDecode . toLazyByteString <$> responseBody | ||
|
||
case mLogAction of | ||
Nothing -> do | ||
let | ||
logLine = pairs | ||
( "datetime" .= formatTimeIso startZoned | ||
<> "timestamp" .= floor @_ @Int (toMs start) | ||
<> "duration" .= toMs (finishApp - start) | ||
<> "send_duration" .= toMs (finishNetwork - finishApp) | ||
<> "level" .= INFO | ||
<> "app" .= ("scotty" :: Text) | ||
<> "msg" .= msg' | ||
<> "status" .= statusC | ||
<> "url" .= url | ||
<> maybe mempty ("userId" .=) userId | ||
<> if log400 && statusC >= 400 | ||
then maybe mempty ("response" .=) responseBodyText | ||
else mempty | ||
) | ||
|
||
hPutBuilder stdout (fromEncoding logLine <> "\n") | ||
hFlush stdout | ||
Just logAction -> do | ||
logAction AccessLogRecord | ||
{ alStart = start | ||
, alFinishApp = finishApp | ||
, alFinishNetwork = finishNetwork | ||
, alMsg = msg' | ||
, alStatus = statusC | ||
, alURL = url | ||
, alUserId = userId | ||
, alResponseBody = responseBodyText | ||
} | ||
|
||
return rcv | ||
where | ||
toIso :: ZonedTime -> Text | ||
toIso = pack . formatTime defaultTimeLocale "%FT%T%z" | ||
|
||
toMs :: POSIXTime -> Double | ||
toMs = realToFrac . (1000 *) . nominalDiffTimeToSeconds | ||
formatTimeIso :: ZonedTime -> Text | ||
formatTimeIso = pack . formatTime defaultTimeLocale "%FT%T%z" | ||
|
||
toMs :: POSIXTime -> Double | ||
toMs = realToFrac . (1000 *) . nominalDiffTimeToSeconds | ||
|
||
debugLogHandler :: AccessLogRecord -> IO () | ||
debugLogHandler AccessLogRecord{..} = do | ||
let | ||
duration = toMs (alFinishApp - alStart) | ||
sendDuration = toMs (alFinishNetwork - alFinishApp) | ||
|
||
startZoned <- utcToLocalZonedTime $ posixSecondsToUTCTime alStart | ||
TIO.putStrLn $ | ||
formatTimeIso startZoned | ||
<> " INFO " | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. если это debug log There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. debug в смысле форматирования, а не уровня) |
||
<> alMsg | ||
<> " " <> pack (show duration) | ||
<> " " <> pack (show sendDuration) | ||
when (alStatus >= 400) $ | ||
forM_ alResponseBody TLIO.putStrLn | ||
|
||
hFlush stdout |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,29 @@ | ||
module Web.Template.Wai | ||
where | ||
( logMiddlewareCustom | ||
, debugLogHandler | ||
, debugLog | ||
, formatTimeIso | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Пустота холодна..." |
||
, module Web.Template.Wai | ||
) where | ||
|
||
import Control.Exception (SomeException, fromException) | ||
import Data.Text (Text) | ||
import Network.HTTP.Types (Header) | ||
import Network.Wai (Middleware, Request, mapResponseHeaders, modifyResponse) | ||
import Network.Wai.Handler.Warp (InvalidRequest (..), Port, Settings, defaultSettings, | ||
exceptionResponseForDebug, setOnException, setOnExceptionResponse, | ||
setPort) | ||
import Control.Exception (SomeException, displayException, fromException) | ||
import Data.Aeson (fromEncoding, object, pairs, (.=)) | ||
import Data.Text (Text, pack) | ||
import Data.Text.Encoding (decodeUtf8) | ||
import qualified Data.Text.IO as TIO | ||
import Data.Time (defaultTimeLocale, formatTime, utcToLocalZonedTime) | ||
import Data.Time.Clock.POSIX (getPOSIXTime, posixSecondsToUTCTime) | ||
import Network.HTTP.Types (Header, status500) | ||
import Network.Wai (Middleware, Request (..), Response, mapResponseHeaders, | ||
modifyResponse, responseBuilder) | ||
import Network.Wai.Handler.Warp (InvalidRequest (..), Port, Settings, defaultSettings, | ||
setOnException, setOnExceptionResponse, setPort) | ||
import System.IO (hFlush, stdout) | ||
|
||
import System.BCD.Log (error') | ||
|
||
import Web.Template.Log (bcdlog, bcdlog400) | ||
import Web.Template.Log (bcdlog, bcdlog400, debugLog, debugLogHandler, logMiddlewareCustom, formatTimeIso) | ||
|
||
defaultHandleLog :: Middleware | ||
defaultHandleLog = bcdlog | ||
|
@@ -37,10 +49,38 @@ defaultOnException _ e = | |
Just ConnectionClosedByPeer -> return () | ||
_ -> error' ("scotty" :: Text) $ show e | ||
|
||
debugOnException :: Maybe Request -> SomeException -> IO () | ||
debugOnException req e = | ||
case fromException e of | ||
Just ConnectionClosedByPeer -> return () | ||
_ -> do | ||
time <- getPOSIXTime >>= utcToLocalZonedTime . posixSecondsToUTCTime | ||
let | ||
msg = pack (formatTime defaultTimeLocale "%FT%T%z" time) <> " ERROR" | ||
exc = pack $ displayException e | ||
reqMsg = | ||
case req of | ||
Nothing -> "" | ||
Just request -> let | ||
url = decodeUtf8 $ rawPathInfo request | ||
method = decodeUtf8 $ requestMethod request | ||
in " " <> method <> " " <> url | ||
|
||
TIO.putStrLn $ msg <> reqMsg <> "\n" <> exc | ||
hFlush stdout | ||
|
||
defaultExceptionResponse :: SomeException -> Response | ||
defaultExceptionResponse e = responseBuilder status500 [] $ fromEncoding $ pairs | ||
( "error" .= ("exception" :: Text) | ||
<> ("params" .= object | ||
[ "message" .= displayException e | ||
]) | ||
) | ||
|
||
warpSettings :: Port -> (Settings -> Settings) -> Settings | ||
warpSettings port userSettings = | ||
setOnException defaultOnException | ||
. setOnExceptionResponse exceptionResponseForDebug | ||
userSettings | ||
. setOnException defaultOnException | ||
. setOnExceptionResponse defaultExceptionResponse | ||
. setPort port | ||
. userSettings | ||
$ defaultSettings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
пустое место!!!!!!!!!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
или это специально так?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ага, типа на группы делю