module Hyena.Logging
(
AccessLogger,
LogRequest(..),
ErrorLogger,
startAccessLogger,
stopAccessLogger,
logAccess,
startErrorLogger,
stopErrorLogger,
logError,
) where
import qualified Data.ByteString.Char8 as C
import Control.Concurrent (forkIO)
import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
import Network.Socket (HostAddress, inet_ntoa)
import Network.Wai (Method(..))
import Prelude hiding (log)
import System.IO (Handle, hFlush, hPutStr, hPutStrLn)
import Text.Printf (printf)
import Hyena.Http (Request(..), Response(..))
data Logger a = Logger
{ channel :: Chan (Maybe a)
, finished :: MVar ()
}
data LogRequest = LogRequest
{ hostAddress :: HostAddress
, request :: Request
, response :: Response
}
newtype AccessLogger = AccessLogger (Logger LogRequest)
newtype ErrorLogger = ErrorLogger (Logger String)
startLogger :: (Handle -> a -> IO ()) -> Handle -> IO (Logger a)
startLogger writer logHandle = do
chan <- newChan
finished' <- newEmptyMVar
forkIO $ logMessages chan finished'
return Logger { channel = chan
, finished = finished'
}
where
logMessages chan finished' = do
msg <- readChan chan
case msg of
Just msg' -> writer logHandle msg' >>
logMessages chan finished'
Nothing -> putMVar finished' ()
stopLogger :: Logger a -> IO ()
stopLogger logger = do
writeChan (channel logger) Nothing
takeMVar (finished logger)
startAccessLogger :: Handle -> IO AccessLogger
startAccessLogger = fmap AccessLogger . startLogger writeAccess
stopAccessLogger :: AccessLogger -> IO ()
stopAccessLogger (AccessLogger logger) = stopLogger logger
startErrorLogger :: Handle -> IO ErrorLogger
startErrorLogger = fmap ErrorLogger . startLogger writeError
stopErrorLogger :: ErrorLogger -> IO ()
stopErrorLogger (ErrorLogger logger) = stopLogger logger
logError :: ErrorLogger -> String -> IO ()
logError (ErrorLogger logger) = writeChan (channel logger) . Just
writeError :: Handle -> String -> IO ()
writeError handle msg = hPutStr handle msg >> hFlush handle
logAccess :: AccessLogger -> Request -> Response -> HostAddress -> IO ()
logAccess (AccessLogger logger) req resp haddr =
writeChan (channel logger) $ Just
LogRequest
{ hostAddress = haddr
, request = req
, response = resp
}
writeAccess :: Handle -> LogRequest -> IO ()
writeAccess h logReq = do
host <- inet_ntoa (hostAddress logReq)
let requestLine = printf "\"%s %s HTTP/%s\"" method' uri version
response' = show (statusCode $ response logReq) ++ " " ++ show length'
hPutStrLn h $ host ++ " " ++ requestLine ++ " " ++ response'
where
(major, minor) = httpVersion $ request logReq
version = show major ++ "." ++ show minor
method' = prettyPrint $ method $ request logReq
uri = C.unpack $ requestUri $ request logReq
respHeaders = responseHeaders $ response logReq
length' :: Int
length' = maybe 0 (read . C.unpack)
(lookup (C.pack "Content-Length") respHeaders)
class PrettyPrint a where
prettyPrint :: a -> String
instance PrettyPrint Method where
prettyPrint Options = "OPTIONS"
prettyPrint Get = "GET"
prettyPrint Head = "HEAD"
prettyPrint Post = "POST"
prettyPrint Put = "PUT"
prettyPrint Delete = "DELETE"
prettyPrint Trace = "TRACE"
prettyPrint Connect = "CONNECT"