module System.Log.FastLogger (
LoggerSet
, BufSize
, defaultBufSize
, logOpen
, newLoggerSet
, renewLoggerSet
, rmLoggerSet
, LogStr
, ToLogStr(..)
, logStrLength
, logStrBuilder
, pushLogStr
, flushLogStr
, module System.Log.FastLogger.File
) where
import Blaze.ByteString.Builder.Internal.Types (Builder(..), BuildSignal(..), BufRange(..), runBuildStep, buildStep)
import Control.Applicative ((<$>))
import Control.Concurrent (getNumCapabilities, myThreadId, threadCapability, MVar, newMVar, takeMVar, putMVar)
import Control.Monad (when, replicateM)
import Data.Array (Array, listArray, (!))
import Data.ByteString.Internal (ByteString(..))
import Data.IORef
import Data.Monoid (Monoid, mempty, mappend)
import qualified Blaze.ByteString.Builder as BB
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy as L
#if MIN_VERSION_base(4,5,0)
import Data.Monoid ((<>))
#endif
import Data.String (IsString(..))
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import Data.Word (Word8)
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Marshal.Alloc (mallocBytes, free)
import Foreign.Ptr (Ptr, minusPtr, plusPtr)
import GHC.IO.Device (close)
import GHC.IO.FD (FD(..), openFile, writeRawBufferPtr)
import GHC.IO.IOMode (IOMode(..))
import System.Log.FastLogger.File
#if !MIN_VERSION_base(4,5,0)
(<>) :: Monoid m => m -> m -> m
(<>) = mappend
#endif
type Buffer = Ptr Word8
type BufSize = Int
defaultBufSize :: BufSize
defaultBufSize = 4096
data LogStr = LogStr {
logStrLength :: !Int
, logStrBuilder :: Builder
}
instance Monoid LogStr where
mempty = LogStr 0 (BB.fromByteString BS.empty)
LogStr s1 b1 `mappend` LogStr s2 b2 = LogStr (s1 + s2) (b1 <> b2)
instance IsString LogStr where
fromString = toLogStr . TL.pack
class ToLogStr msg where
toLogStr :: msg -> LogStr
instance ToLogStr LogStr where
toLogStr = id
instance ToLogStr S8.ByteString where
toLogStr = fromByteString
instance ToLogStr L.ByteString where
toLogStr = fromByteString . S8.concat . L.toChunks
instance ToLogStr String where
toLogStr = toLogStr . TL.pack
instance ToLogStr T.Text where
toLogStr = toLogStr . T.encodeUtf8
instance ToLogStr TL.Text where
toLogStr = toLogStr . TL.encodeUtf8
fromByteString :: ByteString -> LogStr
fromByteString bs = LogStr (BS.length bs) (BB.fromByteString bs)
writeLogStr :: FD
-> Buffer
-> BufSize
-> LogStr
-> IO ()
writeLogStr fd buf size (LogStr len builder)
| size < len = error "writeLogStr"
| otherwise = toBufIOWith buf size (write fd) builder
toBufIOWith :: Buffer -> BufSize -> (Buffer -> Int -> IO a) -> Builder -> IO a
toBufIOWith buf !size io (Builder build) = do
signal <- runBuildStep step bufRange
case signal of
Done ptr _ -> io buf (ptr `minusPtr` buf)
_ -> error "toBufIOWith"
where
!step = build (buildStep finalStep)
!bufRange = BufRange buf (buf `plusPtr` size)
finalStep !(BufRange p _) = return $ Done p ()
write :: FD -> Buffer -> Int -> IO ()
write fd buf len' = loop buf (fromIntegral len')
where
loop bf !len = do
written <- writeRawBufferPtr "write" fd bf 0 (fromIntegral len)
when (written < len) $
loop (bf `plusPtr` fromIntegral written) (len written)
data Logger = Logger (MVar Buffer) !BufSize (IORef LogStr)
newLogger :: BufSize -> IO Logger
newLogger size = do
buf <- getBuffer size
mbuf <- newMVar buf
lref <- newIORef mempty
return $ Logger mbuf size lref
pushLog :: FD -> Logger -> LogStr -> IO ()
pushLog fd logger@(Logger _ size ref) nlogmsg@(LogStr nlen nbuilder)
| nlen > size = do
flushLog fd logger
BB.toByteStringIO (writeByteString fd) nbuilder
| otherwise = do
needFlush <- atomicModifyIORef' ref checkBuf
when needFlush $ do
flushLog fd logger
pushLog fd logger nlogmsg
where
checkBuf ologmsg@(LogStr olen _)
| size < olen + nlen = (ologmsg, True)
| otherwise = (ologmsg <> nlogmsg, False)
writeByteString :: FD -> ByteString -> IO ()
writeByteString fd (PS ps s l) = withForeignPtr ps $ \p ->
write fd (p `plusPtr` s) l
flushLog :: FD -> Logger -> IO ()
flushLog fd (Logger mbuf size lref) = do
logmsg <- atomicModifyIORef' lref (\old -> (mempty, old))
buf <- takeMVar mbuf
writeLogStr fd buf size logmsg
putMVar mbuf buf
logOpen :: FilePath -> IO FD
logOpen file = fst <$> openFile file AppendMode False
getBuffer :: BufSize -> IO Buffer
getBuffer = mallocBytes
data LoggerSet = LoggerSet (IORef FD) (Array Int Logger)
newLoggerSet :: BufSize -> FD -> IO LoggerSet
newLoggerSet size fd = do
n <- getNumCapabilities
loggers <- replicateM n $ newLogger size
let arr = listArray (0,n1) loggers
fref <- newIORef fd
return $ LoggerSet fref arr
pushLogStr :: LoggerSet -> LogStr -> IO ()
pushLogStr (LoggerSet fref arr) logmsg = do
(i, _) <- myThreadId >>= threadCapability
let logger = arr ! i
fd <- readIORef fref
pushLog fd logger logmsg
flushLogStr :: LoggerSet -> IO ()
flushLogStr (LoggerSet fref arr) = do
n <- getNumCapabilities
fd <- readIORef fref
mapM_ (flushIt fd) [0..n1]
where
flushIt fd i = flushLog fd (arr ! i)
renewLoggerSet :: LoggerSet -> FD -> IO ()
renewLoggerSet (LoggerSet fref _) newfd = do
oldfd <- atomicModifyIORef' fref (\fd -> (newfd, fd))
close oldfd
rmLoggerSet :: LoggerSet -> IO ()
rmLoggerSet (LoggerSet fref arr) = do
n <- getNumCapabilities
fd <- readIORef fref
let nums = [0..n1]
mapM_ (flushIt fd) nums
mapM_ freeIt nums
when (fdFD fd /= 1) $ close fd
where
flushIt fd i = flushLog fd (arr ! i)
freeIt i = do
let (Logger mbuf _ _) = arr ! i
takeMVar mbuf >>= free
#if !MIN_VERSION_base(4,6,0)
atomicModifyIORef' :: IORef a -> (a -> (a,b)) -> IO b
atomicModifyIORef' ref f = do
c <- atomicModifyIORef ref
(\x -> let (a, b) = f x
in (a, a `seq` b))
c `seq` return c
#endif