@@ -28,7 +28,7 @@ import Network.Wai (Request(..), Middleware, responseStatus, Response, responseH
28import System.Log.FastLogger
28import System.Log.FastLogger
29import Network.HTTP.Types as H
29import Network.HTTP.Types as H
30import Data.Maybe (fromMaybe)
30import Data.Maybe (fromMaybe)
31−import Data.Monoid (mconcat)
31+import Data.Monoid (mconcat, (<>))
32import Data.Time (getCurrentTime, diffUTCTime)
32import Data.Time (getCurrentTime, diffUTCTime)
34import Network.Wai.Parse (sinkRequestBody, lbsBackEnd, fileName, Param, File, getRequestBodyType)
34import Network.Wai.Parse (sinkRequestBody, lbsBackEnd, fileName, Param, File, getRequestBodyType)
@@ -125,20 +125,6 @@ logStdout = unsafePerformIO $ mkRequestLogger def { outputFormat = Apache FromSo
125logStdoutDev :: Middleware
125logStdoutDev :: Middleware
126logStdoutDev = unsafePerformIO $ mkRequestLogger def
126logStdoutDev = unsafePerformIO $ mkRequestLogger def
128−-- no black or white which are expected to be existing terminal colors.
138−rotateColors :: [Color] -> ([Color], Color)
139−rotateColors [] = error "Impossible! There must be colors!"
140−rotateColors (c:cs) = (cs ++ [c], c)
142-- | Prints a message using the given callback function for each request.
128-- | Prints a message using the given callback function for each request.
143-- This is not for serious production use- it is inefficient.
129-- This is not for serious production use- it is inefficient.
144-- It immediately consumes a POST body and fills it back in and is otherwise inefficient
130-- It immediately consumes a POST body and fills it back in and is otherwise inefficient
@@ -147,42 +133,60 @@ rotateColors (c:cs) = (cs ++ [c], c)
147-- This meanst that you can accurately see the interleaving of requests.
133-- This meanst that you can accurately see the interleaving of requests.
148-- And if the app crashes you have still logged the request.
134-- And if the app crashes you have still logged the request.
149-- However, if you are simulating 10 simultaneous users you may find this confusing.
135-- However, if you are simulating 10 simultaneous users you may find this confusing.
150−-- The request and response are connected by color on Unix and also by the request path.
152-- This is lower-level - use 'logStdoutDev' unless you need greater control.
137-- This is lower-level - use 'logStdoutDev' unless you need greater control.
154-- Example ouput:
139-- Example ouput:
156−-- > GET search :: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
142+-- > Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
158−-- > Status: 200 OK. search
143+-- > Status: 200 OK 0.010555s
160−-- > GET static/css/normalize.css :: text/css,*/*;q=0.1
161−-- > GET [("LXwioiBG","")]
145+-- > GET static/css/normalize.css
146+-- > Params: [("LXwioiBG","")]
147+-- > Accept: text/css,*/*;q=0.1
163−-- > Status: 304 Not Modified. static/css/normalize.css
148+-- > Status: 304 Not Modified 0.010555s
165detailedMiddleware :: Callback -> Bool -> IO Middleware
150detailedMiddleware :: Callback -> Bool -> IO Middleware
166−detailedMiddleware cb useColors = do
151+detailedMiddleware cb useColors =
167− getAddColor <-
152+ let (ansiColor, ansiMethod, ansiStatusCode) =
168− if not useColors then return (return return) else do
153+ if useColors
169− icolors <- newIORef colors0
154+ then (ansiColor', ansiMethod', ansiStatusCode')
170− return $ do
155+ else (\_ t -> [t], (:[]), \_ t -> [t])
171− color <- liftIO $ atomicModifyIORef icolors rotateColors
156+
172− return $ ansiColor color
173− return $ detailedMiddleware' cb getAddColor
157+ in return $ detailedMiddleware' cb ansiColor ansiMethod ansiStatusCode
175−ansiColor :: Color -> BS.ByteString -> [BS.ByteString]
159+ansiColor' :: Color -> BS.ByteString -> [BS.ByteString]
176−ansiColor color bs = [
160+ansiColor' color bs =
177− pack $ setSGRCode [SetColor Foreground Vivid color]
161+ [ pack $ setSGRCode [SetColor Foreground Dull color]
179− , pack $ setSGRCode [Reset]
163+ , pack $ setSGRCode [Reset]
166+-- | Tags http method with a unique color.
167+ansiMethod' :: BS.ByteString -> [BS.ByteString]
168+ansiMethod' m = case m of
169+ "GET" -> ansiColor' Cyan m
170+ "HEAD" -> ansiColor' Cyan m
171+ "PUT" -> ansiColor' Green m
172+ "POST" -> ansiColor' Yellow m
173+ "DELETE" -> ansiColor' Red m
174+ _ -> ansiColor' Magenta m
176+ansiStatusCode' :: BS.ByteString -> BS.ByteString -> [BS.ByteString]
177+ansiStatusCode' c t = case S8.take 1 c of
178+ "2" -> ansiColor' Green t
179+ "3" -> ansiColor' Yellow t
180+ "4" -> ansiColor' Red t
181+ "5" -> ansiColor' Magenta t
182+ _ -> ansiColor' Blue t
182detailedMiddleware' :: Callback
184detailedMiddleware' :: Callback
185+ -> (Color -> BS.ByteString -> [BS.ByteString])
186+ -> (BS.ByteString -> [BS.ByteString])
183− -> IO (BS.ByteString -> [BS.ByteString])
187+ -> (BS.ByteString -> BS.ByteString -> [BS.ByteString])
184 -> Middleware
188 -> Middleware
185−detailedMiddleware' cb getAddColor app req sendResponse = do
189+detailedMiddleware' cb ansiColor ansiMethod ansiStatusCode app req sendResponse = do
186 let mlen = lookup "content-length" (requestHeaders req) >>= readInt
190 let mlen = lookup "content-length" (requestHeaders req) >>= readInt
187 (req', body) <-
191 (req', body) <-
188 case mlen of
192 case mlen of
@@ -214,25 +218,22 @@ detailedMiddleware' cb getAddColor app req sendResponse = do
214 _ -> return (req, [])
218 _ -> return (req, [])
216 postParams <- if requestMethod req `elem` ["GET", "HEAD"]
220 postParams <- if requestMethod req `elem` ["GET", "HEAD"]
217− then return []
221+ then return []
218− else do postParams <- liftIO $ allPostParams body
222+ else do postParams <- liftIO $ allPostParams body
219− return $ collectPostParams postParams
223+ return $ collectPostParams postParams
221 let getParams = map emptyGetParam $ queryString req
225 let getParams = map emptyGetParam $ queryString req
223− addColor <- getAddColor
224− let accept = fromMaybe "" $ lookup "Accept" $ requestHeaders req
226+ accept = fromMaybe "" $ lookup "Accept" $ requestHeaders req
227+ params = let par | not $ null postParams = [pack (show postParams)]
228+ | not $ null getParams = [pack (show getParams)]
230+ in if null par then [""] else ansiColor White " Params: " <> par <> ["\n"]
226 -- log the request immediately.
232 -- log the request immediately.
227− liftIO $ cb $ mconcat $ map toLogStr $ addColor (requestMethod req) ++
233+ liftIO $ cb $ mconcat $ map toLogStr $
229− , rawPathInfo req
234+ ansiMethod (requestMethod req) ++ [" ", rawPathInfo req, "\n"] ++
230− , " :: "
235+ params ++
231− , accept
236+ ansiColor White " Accept: " ++ [accept, "\n"]
232− , paramsToBS "GET " getParams
233− , paramsToBS "POST " postParams
237 t0 <- getCurrentTime
238 t0 <- getCurrentTime
238 app req' $ \rsp -> do
239 app req' $ \rsp -> do
@@ -240,27 +241,18 @@ detailedMiddleware' cb getAddColor app req sendResponse = do
240 case rsp of
241 case rsp of
241 ResponseRaw{} -> True
242 ResponseRaw{} -> True
242 _ -> False
243 _ -> False
244+ stCode = statusBS rsp
243 t1 <- getCurrentTime
246 t1 <- getCurrentTime
245 -- log the status of the response
248 -- log the status of the response
246− -- this is color coordinated with the request logging
247− -- also includes the request path to connect it to the request
248 unless isRaw $ cb $ mconcat $ map toLogStr $
249 unless isRaw $ cb $ mconcat $ map toLogStr $
249− addColor "Status: " ++ statusBS rsp ++
250+ ansiColor White " Status: " ++
250− [ " "
251+ ansiStatusCode stCode (stCode <> " " <> stMsg) ++
253− , pack $ show $ diffUTCTime t1 t0
252+ [" ", pack $ show $ diffUTCTime t1 t0, "\n"]
255− , rawPathInfo req -- if you need help matching the 2 logging statements
258 sendResponse rsp
254 sendResponse rsp
260− paramsToBS prefix params =
261− if null params then ""
262− else BS.concat ["\n", prefix, pack (show params)]
264 allPostParams body =
256 allPostParams body =
265 case getRequestBodyType req of
257 case getRequestBodyType req of
266 Nothing -> return ([], [])
258 Nothing -> return ([], [])
@@ -278,19 +270,15 @@ detailedMiddleware' cb getAddColor app req sendResponse = do
279 collectPostParams :: ([Param], [File LBS.ByteString]) -> [Param]
271 collectPostParams :: ([Param], [File LBS.ByteString]) -> [Param]
280 collectPostParams (postParams, files) = postParams ++
272 collectPostParams (postParams, files) = postParams ++
281− map (\(k,v) -> (k, BS.append "FILE: " (fileName v))) files
273+ map (\(k,v) -> (k, "FILE: " <> fileName v)) files
283 readInt bs =
275 readInt bs =
284 case reads $ unpack bs of
276 case reads $ unpack bs of
285 (i, _):_ -> Just (i :: Int)
277 (i, _):_ -> Just (i :: Int)
286 [] -> Nothing
278 [] -> Nothing
288−statusBS :: Response -> [BS.ByteString]
280+statusBS :: Response -> BS.ByteString
290− if status >= 400 then ansiColor Red bs else [bs]
292− bs = pack $ show status
293− status = statusCode $ responseStatus rsp
281+statusBS = pack . show . statusCode . responseStatus
295msgBS :: Response -> BS.ByteString
283msgBS :: Response -> BS.ByteString
296msgBS = statusMessage . responseStatus
284msgBS = statusMessage . responseStatus