wai-extra-1.1.0: Network/Wai/Middleware/RequestLogger.hs
{-# LANGUAGE OverloadedStrings #-}
module Network.Wai.Middleware.RequestLogger
( logStdout
, logCallback
, logStdoutDev
, logCallbackDev
-- * Deprecated
, logHandle
, logHandleDev
) where
import System.IO (stdout, hFlush)
import qualified Data.ByteString as BS
import Data.ByteString.Char8 (pack)
import Control.Monad.IO.Class (liftIO)
import Network.Wai (Request(..), Middleware)
import System.Log.FastLogger
import Network.HTTP.Types as H
import Network.Wai.Parse (sinkRequestBody, lbsBackEnd, fileName, Param, File, getRequestBodyType)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Conduit as C
import qualified Data.Conduit.List as CL
logHandle :: (BS.ByteString -> IO ()) -> Middleware
logHandle = logCallback
{-# DEPRECATED logHandle "Please use logCallback instead." #-}
logHandleDev :: (BS.ByteString -> IO ()) -> Middleware
logHandleDev = logCallbackDev
{-# DEPRECATED logHandleDev "Please use logCallbackDev instead." #-}
-- | Production request logger middleware.
-- Implemented on top of "logCallback", but prints to 'stdout'
logStdout :: Middleware
logStdout = logHandle $ \bs -> hPutLogStr stdout [LB bs]
-- | Development request logger middleware.
-- Implemented on top of "logCallbackDev", but prints to 'stdout'
--
-- Flushes 'stdout' on each request, which would be inefficient in production use.
-- Use "logStdout" in production.
logStdoutDev :: Middleware
logStdoutDev = logHandleDev $ \bs -> hPutLogStr stdout [LB bs] >> hFlush stdout
-- | Prints a message using the given callback function for each request.
-- Designed for fast production use at the expense of convenience.
-- In particular, no POST parameter information is currently given
--
-- This is lower-level - use "logStdout" unless you need this greater control
logCallback :: (BS.ByteString -> IO ()) -- ^ A function that logs the ByteString log message.
-> Middleware
logCallback cb app req = do
liftIO $ cb $ BS.concat
[ requestMethod req
, " "
, rawPathInfo req
, rawQueryString req
, " "
, "Accept: "
, maybe "" toBS $ lookup "Accept" $ requestHeaders req
, "\n"
]
app req
toBS :: H.Ascii -> BS.ByteString
toBS = id
-- | Prints a message using the given callback function for each request.
-- This is not for serious production use- it is inefficient.
-- It immediately consumes a POST body and fills it back in and is otherwise inefficient
--
-- This is lower-level - use "logStdoutDev" unless you need this greater control
logCallbackDev :: (BS.ByteString -> IO ()) -- ^ A function that logs the ByteString log message.
-> Middleware
logCallbackDev cb app req = do
body <- requestBody req C.$$ CL.consume
postParams <- if any (requestMethod req ==) ["GET", "HEAD"]
then return []
else do postParams <- liftIO $ allPostParams body
return $ collectPostParams postParams
let getParams = map emptyGetParam $ queryString req
liftIO $ cb $ BS.concat
[ requestMethod req
, " "
, rawPathInfo req
, "\n"
, "Accept: "
, maybe "" id $ lookup "Accept" $ requestHeaders req
, paramsToBS "GET " getParams
, paramsToBS "POST " postParams
, "\n"
]
-- The body was consumed. Fill it back up so it is available again
app req { requestBody = CL.sourceList body }
where
paramsToBS prefix params =
if null params then ""
else BS.concat ["\n", prefix, pack (show params)]
allPostParams body =
case getRequestBodyType req of
Nothing -> return ([], [])
Just rbt -> C.runResourceT $ CL.sourceList body C.$$ sinkRequestBody lbsBackEnd rbt
emptyGetParam :: (BS.ByteString, Maybe BS.ByteString) -> (BS.ByteString, BS.ByteString)
emptyGetParam (k, Just v) = (k,v)
emptyGetParam (k, Nothing) = (k,"")
collectPostParams :: ([Param], [File LBS.ByteString]) -> [Param]
collectPostParams (postParams, files) = postParams ++
(map (\(k,v) -> (k, BS.append "FILE: " (fileName v))) files)