{-# LANGUAGE OverloadedStrings #-} module Log where import Control.Concurrent import Control.Monad import qualified Data.ByteString.Char8 as BS import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy.Char8 as BL import Data.Time import Network.HTTP.Types import Network.Wai import Network.Wai.Application.Classic import System.Directory import System.Exit import System.FilePath import System.IO import System.Locale import System.Posix data FileLogSpec = FileLogSpec { log_file :: String , log_file_size :: Integer , log_backup_number :: Int , log_buffer_size :: Int , log_flush_period :: Int } fileCheck :: FileLogSpec -> IO () fileCheck spec = do dirperm <- getPermissions dir unless (writable dirperm) $ exit $ dir ++ " is not writable" fileexist <- doesFileExist file when fileexist $ do fileperm <- getPermissions file unless (writable fileperm) $ exit $ file ++ " is not writable" where file = log_file spec dir = takeDirectory file exit msg = hPutStrLn stderr msg >> exitFailure fileInit :: FileLogSpec -> IO (Chan ByteString) fileInit spec = do hdl <- open spec mvar <- newMVar hdl chan <- newChan forkIO $ fileFlusher mvar spec forkIO $ fileSerializer chan mvar let handler = fileFlushHandler mvar installHandler sigTERM handler Nothing installHandler sigKILL handler Nothing return chan fileFlushHandler :: MVar Handle -> Handler fileFlushHandler mvar = Catch $ do hdl <- takeMVar mvar hFlush hdl putMVar mvar hdl exitImmediately ExitSuccess fileFlusher :: MVar Handle -> FileLogSpec -> IO () fileFlusher mvar spec = forever $ do threadDelay $ log_flush_period spec hdl <- takeMVar mvar hFlush hdl size <- hFileSize hdl if size > log_file_size spec then do hClose hdl locate spec newhdl <- open spec putMVar mvar newhdl else putMVar mvar hdl fileSerializer :: Chan ByteString -> MVar Handle -> IO () fileSerializer chan mvar = forever $ do xs <- readChan chan hdl <- takeMVar mvar BL.hPut hdl xs putMVar mvar hdl open :: FileLogSpec -> IO Handle open spec = do hdl <- openFile file AppendMode setFileMode file 0o644 hSetEncoding hdl latin1 hSetBuffering hdl $ BlockBuffering (Just $ log_buffer_size spec) return hdl where file = log_file spec locate :: FileLogSpec -> IO () locate spec = mapM_ move srcdsts where path = log_file spec n = log_backup_number spec dsts' = reverse . ("":) . map (('.':). show) $ [0..n-1] dsts = map (path++) dsts' srcs = tail dsts srcdsts = zip srcs dsts move (src,dst) = do exist <- doesFileExist src when exist $ renameFile src dst ---------------------------------------------------------------- stdoutInit :: IO (Chan ByteString) stdoutInit = do chan <- newChan forkIO $ stdoutSerializer chan return chan stdoutSerializer :: Chan ByteString -> IO () stdoutSerializer chan = forever $ readChan chan >>= BL.putStr ---------------------------------------------------------------- mightyLogger :: Chan ByteString -> Request -> Status -> Maybe Integer -> IO () mightyLogger chan req st msize = do zt <- getZonedTime addr <- getPeerAddr (remoteHost req) writeChan chan $ BL.fromChunks (logmsg addr zt) where logmsg addr zt = [ BS.pack addr , " - - [" , BS.pack (formatTime defaultTimeLocale "%d/%b/%Y:%T %z" zt) , "] \"" , requestMethod req , " " , rawPathInfo req , "\" " , BS.pack (show . statusCode $ st) , " " , BS.pack (maybe "-" show msize) , " \"" -- size , lookupRequestField' "referer" req , "\" \"" , lookupRequestField' "user-agent" req , "\"\n" ]