{-# LANGUAGE CPP #-} -- | Apache style logger for WAI applications. -- -- An example: -- -- > {-# LANGUAGE OverloadedStrings #-} -- > module Main where -- > -- > import Blaze.ByteString.Builder (fromByteString) -- > import Control.Monad.IO.Class (liftIO) -- > import qualified Data.ByteString.Char8 as BS -- > import Network.HTTP.Types (status200) -- > import Network.Wai (Application, responseBuilder) -- > import Network.Wai.Handler.Warp (run) -- > import Network.Wai.Logger (withStdoutLogger, ApacheLogger) -- > -- > main :: IO () -- > main = withStdoutLogger $ \aplogger -> -- > run 3000 $ logApp aplogger -- > -- > logApp :: ApacheLogger -> Application -- > logApp aplogger req response = do -- > liftIO $ aplogger req status (Just len) -- > response $ responseBuilder status hdr msg -- > where -- > status = status200 -- > hdr = [("Content-Type", "text/plain")] -- > pong = "PONG" -- > msg = fromByteString pong -- > len = fromIntegral $ BS.length pong module Network.Wai.Logger ( -- * High level functions ApacheLogger , withStdoutLogger -- * Creating a logger , ApacheLoggerActions(..) , initLogger -- * Types , IPAddrSource(..) , LogType(..) , FileLogSpec(..) -- * Utilities , showSockAddr , logCheck -- * Backward compability , clockDateCacher , ZonedDate , DateCacheGetter , DateCacheUpdater ) where #if __GLASGOW_HASKELL__ < 709 import Control.Applicative ((<$>)) #endif import Control.Exception (bracket) import Control.Monad (void) import Network.HTTP.Types (Status) import Network.Wai (Request) import System.Log.FastLogger import Network.Wai.Logger.Apache import Network.Wai.Logger.IP (showSockAddr) ---------------------------------------------------------------- -- | Executing a function which takes 'ApacheLogger'. -- This 'ApacheLogger' writes log message to stdout. -- Each buffer (4K bytes) is flushed every second. withStdoutLogger :: (ApacheLogger -> IO a) -> IO a withStdoutLogger app = bracket setup teardown $ \(aplogger, _) -> app aplogger where setup = do tgetter <- newTimeCache simpleTimeFormat apf <- initLogger FromFallback (LogStdout 4096) tgetter let aplogger = apacheLogger apf remover = logRemover apf return (aplogger, remover) teardown (_, remover) = void remover ---------------------------------------------------------------- -- | Apache style logger. type ApacheLogger = Request -> Status -> Maybe Integer -> IO () data ApacheLoggerActions = ApacheLoggerActions { apacheLogger :: ApacheLogger -- | This is obsoleted. Rotation is done on-demand. -- So, this is now an empty action. , logRotator :: IO () -- | Removing resources relating Apache logger. -- E.g. flushing and deallocating internal buffers. , logRemover :: IO () } ---------------------------------------------------------------- -- | Creating 'ApacheLogger' according to 'LogType'. initLogger :: IPAddrSource -> LogType -> IO FormattedTime -> IO ApacheLoggerActions initLogger ipsrc typ tgetter = do (fl, cleanUp) <- newFastLogger typ return $ ApacheLoggerActions (apache fl ipsrc tgetter) (return ()) cleanUp --- | Checking if a log file can be written if 'LogType' is 'LogFileNoRotate' or 'LogFile'. logCheck :: LogType -> IO () logCheck LogNone = return () logCheck (LogStdout _) = return () logCheck (LogStderr _) = return () logCheck (LogFileNoRotate fp _) = check fp logCheck (LogFile spec _) = check (log_file spec) logCheck (LogCallback _ _) = return () ---------------------------------------------------------------- apache :: (LogStr -> IO ()) -> IPAddrSource -> IO FormattedTime -> ApacheLogger apache cb ipsrc dateget req st mlen = do zdata <- dateget cb (apacheLogStr ipsrc zdata req st mlen) --------------------------------------------------------------- -- | Getting cached 'ZonedDate'. type DateCacheGetter = IO ZonedDate -- | Updateing cached 'ZonedDate'. This should be called every second. -- See the source code of 'withStdoutLogger'. type DateCacheUpdater = IO () -- | A type for zoned date. type ZonedDate = FormattedTime -- | -- Returning 'DateCacheGetter' and 'DateCacheUpdater'. -- -- Note: Since version 2.1.2, this function uses the auto-update package -- internally, and therefore the @DateCacheUpdater@ value returned need -- not be called. To wit, the return value is in fact an empty action. clockDateCacher :: IO (DateCacheGetter, DateCacheUpdater) clockDateCacher = do tgetter <- newTimeCache simpleTimeFormat return (tgetter, return ())