packages feed

fast-logger 3.1.2 → 3.2.0

raw patch · 11 files changed

+403/−165 lines, 11 filesdep +asyncdep +stmPVP ok

version bump matches the API change (PVP)

Dependencies added: async, stm

API changes (from Hackage documentation)

- System.Log.FastLogger.Internal: Logger :: IORef LogStr -> Logger
- System.Log.FastLogger.Internal: flushLog :: IORef FD -> BufSize -> MVar Buffer -> Logger -> IO ()
- System.Log.FastLogger.Internal: newLogger :: IO Logger
- System.Log.FastLogger.Internal: newtype Logger
+ System.Log.FastLogger.Internal: class Loggers a
+ System.Log.FastLogger.Internal: data MultiLogger
+ System.Log.FastLogger.Internal: data SingleLogger
+ System.Log.FastLogger.Internal: flushAllLog :: Loggers a => a -> IO ()
+ System.Log.FastLogger.Internal: newMultiLogger :: Int -> BufSize -> IORef FD -> IO MultiLogger
+ System.Log.FastLogger.Internal: newSingleLogger :: BufSize -> IORef FD -> IO SingleLogger
+ System.Log.FastLogger.Internal: stopLoggers :: Loggers a => a -> IO ()
+ System.Log.FastLogger.Internal: writeBigLogStr :: IORef FD -> LogStr -> IO ()
+ System.Log.FastLogger.Internal: writeLogStr :: Buffer -> IORef FD -> LogStr -> IO ()
- System.Log.FastLogger.Internal: pushLog :: IORef FD -> BufSize -> MVar Buffer -> Logger -> LogStr -> IO ()
+ System.Log.FastLogger.Internal: pushLog :: Loggers a => a -> LogStr -> IO ()

Files

ChangeLog.md view
@@ -1,3 +1,8 @@+## 3.2.0++* newFastLogger1 ensures the ordering of logs+  [#207](https://github.com/kazu-yamamoto/logger/pull/207)+ ## 3.1.2  * Require unix-compat >= 0.2
System/Log/FastLogger.hs view
@@ -109,8 +109,9 @@ newFastLogger typ = newFastLoggerCore Nothing typ  -- | Like `newFastLogger`, but creating a logger that uses only 1--- capability. This scales less well on multi-core machines,--- but provides time-ordered output.+-- internal builder. This scales less on multi-core machines and+-- consumes more memory because of an internal queue but provides+-- time-ordered output. newFastLogger1 :: LogType' v -> IO (v -> IO (), IO ()) newFastLogger1 typ = newFastLoggerCore (Just 1) typ 
System/Log/FastLogger/Internal.hs view
@@ -4,12 +4,16 @@   ( module System.Log.FastLogger.IO   , module System.Log.FastLogger.FileIO   , module System.Log.FastLogger.LogStr-  , module System.Log.FastLogger.Logger+  , module System.Log.FastLogger.SingleLogger+  , module System.Log.FastLogger.MultiLogger+  , module System.Log.FastLogger.Write   , module System.Log.FastLogger.LoggerSet   ) where  import System.Log.FastLogger.IO import System.Log.FastLogger.FileIO import System.Log.FastLogger.LogStr-import System.Log.FastLogger.Logger+import System.Log.FastLogger.SingleLogger+import System.Log.FastLogger.MultiLogger+import System.Log.FastLogger.Write import System.Log.FastLogger.LoggerSet
System/Log/FastLogger/LogStr.hs view
@@ -50,12 +50,14 @@ instance Semi.Semigroup LogStr where     {-# INLINE (<>) #-}     LogStr s1 b1 <> LogStr s2 b2 = LogStr (s1 + s2) (b1 <> b2)-#endif- instance Monoid LogStr where     mempty = LogStr 0 (toBuilder BS.empty)+#else+instance Monoid LogStr where+    mempty = LogStr 0 (toBuilder BS.empty)     {-# INLINE mappend #-}     LogStr s1 b1 `mappend` LogStr s2 b2 = LogStr (s1 + s2) (b1 <> b2)+#endif  instance IsString LogStr where     {-# INLINE fromString #-}
− System/Log/FastLogger/Logger.hs
@@ -1,85 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE Safe #-}--module System.Log.FastLogger.Logger (-    Logger(..)-  , newLogger-  , pushLog-  , flushLog-  ) where---import Control.Concurrent (MVar, withMVar)-import Foreign.Marshal.Alloc (allocaBytes)-import Foreign.Ptr (plusPtr)--import System.Log.FastLogger.FileIO-import System.Log.FastLogger.IO-import System.Log.FastLogger.Imports-import System.Log.FastLogger.LogStr--------------------------------------------------------------------newtype Logger = Logger (IORef LogStr)--------------------------------------------------------------------newLogger :: IO Logger-newLogger = Logger <$> newIORef mempty--------------------------------------------------------------------pushLog :: IORef FD -> BufSize -> MVar Buffer -> Logger -> LogStr -> IO ()-pushLog fdref size mbuf logger@(Logger ref) nlogmsg@(LogStr nlen nbuilder)-  | nlen > size = do-      flushLog fdref size mbuf logger-      -- Make sure we have a large enough buffer to hold the entire-      -- contents, thereby allowing for a single write system call and-      -- avoiding interleaving. This does not address the possibility-      -- of write not writing the entire buffer at once.-      allocaBytes nlen $ \buf -> withMVar mbuf $ \_ ->-        toBufIOWith buf nlen (write fdref) nbuilder-  | otherwise = do-    mmsg <- atomicModifyIORef' ref checkBuf-    case mmsg of-        Nothing  -> return ()-        Just msg -> withMVar mbuf $ \buf -> writeLogStr fdref buf size msg-  where-    checkBuf ologmsg@(LogStr olen _)-      | size < olen + nlen = (nlogmsg, Just ologmsg)-      | otherwise          = (ologmsg <> nlogmsg, Nothing)--------------------------------------------------------------------flushLog :: IORef FD -> BufSize -> MVar Buffer -> Logger -> IO ()-flushLog fdref size mbuf (Logger lref) = do-    logmsg <- atomicModifyIORef' lref (\old -> (mempty, old))-    -- If a special buffer is prepared for flusher, this MVar could-    -- be removed. But such a code does not contribute logging speed-    -- according to experiment. And even with the special buffer,-    -- there is no grantee that this function is exclusively called-    -- for a buffer. So, we use MVar here.-    -- This is safe and speed penalty can be ignored.-    withMVar mbuf $ \buf -> writeLogStr fdref buf size logmsg---------------------------------------------------------------------- | Writting 'LogStr' using a buffer in blocking mode.---   The size of 'LogStr' must be smaller or equal to---   the size of buffer.-writeLogStr :: IORef FD-            -> Buffer-            -> BufSize-            -> LogStr-            -> IO ()-writeLogStr fdref buf size (LogStr len builder)-  | size < len = error "writeLogStr"-  | otherwise  = toBufIOWith buf size (write fdref) builder--write :: IORef FD -> Buffer -> Int -> IO ()-write fdref buf len' = loop buf (fromIntegral len')-  where-    loop bf len = do-        written <- writeRawBufferPtr2FD fdref bf len-        when (0 <= written && written < len) $-            loop (bf `plusPtr` fromIntegral written) (len - written)
System/Log/FastLogger/LoggerSet.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}  module System.Log.FastLogger.LoggerSet (   -- * Creating a logger set@@ -23,26 +24,35 @@   , replaceLoggerSet   ) where -import Control.Concurrent (MVar, getNumCapabilities, myThreadId, threadCapability, takeMVar, newMVar)+import Control.Concurrent (getNumCapabilities) import Control.Debounce (mkDebounce, defaultDebounceSettings, debounceAction)-import Data.Array (Array, listArray, (!), bounds)  import System.Log.FastLogger.FileIO import System.Log.FastLogger.IO import System.Log.FastLogger.Imports import System.Log.FastLogger.LogStr-import System.Log.FastLogger.Logger+import System.Log.FastLogger.MultiLogger (MultiLogger)+import qualified System.Log.FastLogger.MultiLogger as M+import System.Log.FastLogger.SingleLogger (SingleLogger)+import qualified System.Log.FastLogger.SingleLogger as S+import System.Log.FastLogger.Write  ---------------------------------------------------------------- +data Logger = SL SingleLogger | ML MultiLogger++----------------------------------------------------------------+ -- | A set of loggers. --   The number of loggers is the capabilities of GHC RTS. --   You can specify it with \"+RTS -N\<x\>\". --   A buffer is prepared for each capability.-data LoggerSet = LoggerSet (Maybe FilePath) (IORef FD)-                           BufSize (MVar Buffer)-                           (Array Int Logger)-                           (IO ())+data LoggerSet = LoggerSet {+    lgrsetFilePath :: Maybe FilePath+  , lgrsetFdRef    :: IORef FD+  , lgrsetLogger   :: Logger+  , lgrsetDebounce :: IO ()+  }  -- | Creating a new 'LoggerSet' using a file. --@@ -90,31 +100,33 @@     n <- case mn of       Just n' -> return n'       Nothing -> getNumCapabilities-    loggers <- replicateM n newLogger-    let arr = listArray (0,n-1) loggers-    fref <- newIORef fd+    fdref <- newIORef fd     let bufsiz = max 1 size-    mbuf <- getBuffer bufsiz >>= newMVar+    logger <- if n == 1 && mn == Just 1 then+                  SL <$> S.newSingleLogger bufsiz fdref+                else do+                  ML <$> M.newMultiLogger n bufsiz fdref     flush <- mkDebounce defaultDebounceSettings-        { debounceAction = flushLogStrRaw fref bufsiz mbuf arr+        { debounceAction = flushLogStrRaw logger         }-    return $ LoggerSet mfile fref bufsiz mbuf arr flush+    return $ LoggerSet {+        lgrsetFilePath = mfile+      , lgrsetFdRef    = fdref+      , lgrsetLogger   = logger+      , lgrsetDebounce = flush+      }  -- | Writing a log message to the corresponding buffer. --   If the buffer becomes full, the log messages in the buffer --   are written to its corresponding file, stdout, or stderr. pushLogStr :: LoggerSet -> LogStr -> IO ()-pushLogStr (LoggerSet _ fdref size mbuf arr flush) logmsg = do-    (i, _) <- myThreadId >>= threadCapability-    -- The number of capability could be dynamically changed.-    -- So, let's check the upper boundary of the array.-    let u = snd $ bounds arr-        lim = u + 1-        j | i < lim   = i-          | otherwise = i `mod` lim-    let logger = arr ! j-    pushLog fdref size mbuf logger logmsg-    flush+pushLogStr LoggerSet{..} logmsg = case lgrsetLogger of+  SL sl -> do+      pushLog sl logmsg+      lgrsetDebounce+  ML ml -> do+      pushLog ml logmsg+      lgrsetDebounce  -- | Same as 'pushLogStr' but also appends a newline. pushLogStrLn :: LoggerSet -> LogStr -> IO ()@@ -130,41 +142,36 @@ --   function can be used to force flushing outside of the debounced --   flush calls. flushLogStr :: LoggerSet -> IO ()-flushLogStr (LoggerSet _ fref size mbuf arr _) = flushLogStrRaw fref size mbuf arr+flushLogStr LoggerSet{..} = flushLogStrRaw lgrsetLogger -flushLogStrRaw :: IORef FD -> BufSize -> MVar Buffer -> Array Int Logger -> IO ()-flushLogStrRaw fdref size mbuf arr = do-    let (l,u) = bounds arr-    mapM_ flushIt [l .. u]-  where-    flushIt i = flushLog fdref size mbuf (arr ! i)+flushLogStrRaw :: Logger -> IO ()+flushLogStrRaw (SL sl) = flushAllLog sl+flushLogStrRaw (ML ml) = flushAllLog ml  -- | Renewing the internal file information in 'LoggerSet'. --   This does nothing for stdout and stderr. renewLoggerSet :: LoggerSet -> IO ()-renewLoggerSet (LoggerSet Nothing     _    _ _ _ _) = return ()-renewLoggerSet (LoggerSet (Just file) fref _ _ _ _) = do-    newfd <- openFileFD file-    oldfd <- atomicModifyIORef' fref (\fd -> (newfd, fd))-    closeFD oldfd+renewLoggerSet LoggerSet{..} = case lgrsetFilePath of+  Nothing -> return ()+  Just file -> do+      newfd <- openFileFD file+      oldfd <- atomicModifyIORef' lgrsetFdRef (\fd -> (newfd, fd))+      closeFD oldfd  -- | Flushing the buffers, closing the internal file information --   and freeing the buffers. rmLoggerSet :: LoggerSet -> IO ()-rmLoggerSet (LoggerSet mfile fdref size mbuf arr _) = do-    fd <- readIORef fdref+rmLoggerSet LoggerSet{..} = do+    fd <- readIORef lgrsetFdRef     when (isFDValid fd) $ do-        let (l,u) = bounds arr-        let nums = [l .. u]-        mapM_ flushIt nums-        takeMVar mbuf >>= freeBuffer-        when (isJust mfile) $ closeFD fd-        writeIORef fdref invalidFD-  where-    flushIt i = flushLog fdref size mbuf(arr ! i)+        case lgrsetLogger of+          SL sl -> stopLoggers sl+          ML ml -> stopLoggers ml+        when (isJust lgrsetFilePath) $ closeFD fd+        writeIORef lgrsetFdRef invalidFD  -- | Replacing the file path in 'LoggerSet' and returning a new --   'LoggerSet' and the old file path. replaceLoggerSet :: LoggerSet -> FilePath -> (LoggerSet, Maybe FilePath)-replaceLoggerSet (LoggerSet current_path a b c d e) new_file_path =-    (LoggerSet (Just new_file_path) a b c d e, current_path)+replaceLoggerSet lgrset@LoggerSet{..} new_file_path =+    (lgrset { lgrsetFilePath = Just new_file_path }, lgrsetFilePath)
+ System/Log/FastLogger/MultiLogger.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE RecordWildCards #-}++module System.Log.FastLogger.MultiLogger (+    MultiLogger+  , newMultiLogger+  ) where+++import Control.Concurrent (myThreadId, threadCapability, MVar, newMVar, withMVar, takeMVar)+import Data.Array (Array, listArray, (!), bounds)++import System.Log.FastLogger.FileIO+import System.Log.FastLogger.IO+import System.Log.FastLogger.Imports+import System.Log.FastLogger.LogStr+import System.Log.FastLogger.Write++----------------------------------------------------------------++newtype MLogger = MLogger {+    lgrRef :: IORef LogStr+  }++-- | A scale but non-time-ordered logger.+data MultiLogger = MultiLogger {+    mlgrArray   :: Array Int MLogger+  , mlgrMBuffer :: MVar Buffer+  , mlgrBufSize :: BufSize+  , mlgrFdRef   :: IORef FD+  }++instance Loggers MultiLogger where+    stopLoggers = System.Log.FastLogger.MultiLogger.stopLoggers+    pushLog     = System.Log.FastLogger.MultiLogger.pushLog+    flushAllLog = System.Log.FastLogger.MultiLogger.flushAllLog++----------------------------------------------------------------++newMLogger :: IO MLogger+newMLogger = MLogger <$> newIORef mempty++-- | Creating `MultiLogger`.+--   The first argument is the number of the internal builders.+newMultiLogger :: Int -> BufSize -> IORef FD -> IO MultiLogger+newMultiLogger n bufsize fdref= do+    mbuf <- getBuffer bufsize >>= newMVar+    arr <- listArray (0,n-1) <$> replicateM n newMLogger+    return $ MultiLogger {+        mlgrArray   = arr+      , mlgrMBuffer = mbuf+      , mlgrBufSize = bufsize+      , mlgrFdRef   = fdref+      }++----------------------------------------------------------------++pushLog :: MultiLogger -> LogStr -> IO ()+pushLog ml@MultiLogger{..} logmsg = do+    (i, _) <- myThreadId >>= threadCapability+    -- The number of capability could be dynamically changed.+    -- So, let's check the upper boundary of the array.+    let u = snd $ bounds mlgrArray+        lim = u + 1+        j | i < lim   = i+          | otherwise = i `mod` lim+    let logger = mlgrArray ! j+    pushLog' logger logmsg+  where+    pushLog' logger@MLogger{..} nlogmsg@(LogStr nlen _)+      | nlen > mlgrBufSize = do+          flushLog ml logger+          -- Make sure we have a large enough buffer to hold the entire+          -- contents, thereby allowing for a single write system call and+          -- avoiding interleaving. This does not address the possibility+          -- of write not writing the entire buffer at once.+          writeBigLogStr' ml nlogmsg+      | otherwise = do+        action <- atomicModifyIORef' lgrRef checkBuf+        action+      where+        checkBuf ologmsg@(LogStr olen _)+          | mlgrBufSize < olen + nlen = (nlogmsg, writeLogStr' ml ologmsg)+          | otherwise                 = (ologmsg <> nlogmsg, return ())++----------------------------------------------------------------++flushAllLog :: MultiLogger -> IO ()+flushAllLog ml@MultiLogger{..} = do+    let flushIt i = flushLog ml (mlgrArray ! i)+        (l,u) = bounds mlgrArray+        nums = [l .. u]+    mapM_ flushIt nums++flushLog :: MultiLogger -> MLogger -> IO ()+flushLog ml MLogger{..} = do+    -- If a special buffer is prepared for flusher, this MVar could+    -- be removed. But such a code does not contribute logging speed+    -- according to experiment. And even with the special buffer,+    -- there is no grantee that this function is exclusively called+    -- for a buffer. So, we use MVar here.+    -- This is safe and speed penalty can be ignored.+    old <- atomicModifyIORef' lgrRef (\old -> (mempty, old))+    writeLogStr' ml old++----------------------------------------------------------------++stopLoggers :: MultiLogger -> IO ()+stopLoggers ml@MultiLogger{..} = do+  System.Log.FastLogger.MultiLogger.flushAllLog ml+  takeMVar mlgrMBuffer >>= freeBuffer++----------------------------------------------------------------++writeLogStr' :: MultiLogger -> LogStr -> IO ()+writeLogStr' MultiLogger{..} logstr =+    withMVar mlgrMBuffer $ \buf -> writeLogStr buf mlgrFdRef logstr++writeBigLogStr' :: MultiLogger -> LogStr -> IO ()+writeBigLogStr' MultiLogger{..} logstr =+    withMVar mlgrMBuffer $ \_ -> writeBigLogStr mlgrFdRef logstr
+ System/Log/FastLogger/SingleLogger.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE RecordWildCards #-}++module System.Log.FastLogger.SingleLogger (+    SingleLogger+  , newSingleLogger+  ) where++import Control.Concurrent (forkIO, newEmptyMVar, MVar, takeMVar, putMVar)+import Control.Concurrent.STM++import System.Log.FastLogger.FileIO+import System.Log.FastLogger.IO+import System.Log.FastLogger.Imports+import System.Log.FastLogger.LogStr+import System.Log.FastLogger.Write++----------------------------------------------------------------++-- | A non-scale but time-ordered logger.+data SingleLogger = SingleLogger {+    slgrRef     :: IORef (LogStr+                         ,[LogStr])-- writer queue+  , slgrKill    :: IO ()+  , slgrWakeup  :: IO ()+  , slgrBuffer  :: Buffer+  , slgrBufSize :: BufSize+  , slgrFdRef   :: IORef FD+  }++instance Loggers SingleLogger where+    stopLoggers = System.Log.FastLogger.SingleLogger.stopLoggers+    pushLog     = System.Log.FastLogger.SingleLogger.pushLog+    flushAllLog = System.Log.FastLogger.SingleLogger.flushAllLog++----------------------------------------------------------------++writer :: BufSize -> Buffer -> IORef FD -> TVar Int -> IORef (LogStr, [LogStr]) -> MVar () -> IO ()+writer bufsize buf fdref tvar ref mvar = loop (0 :: Int)+  where+    loop cnt = do+        cnt' <- atomically $ do+            n <- readTVar tvar+            check (n /= cnt)+            return n+        msgs <- reverse <$> atomicModifyIORef' ref (\(msg,q) -> ((msg,[]),q))+        cont <- go msgs+        if cont then+            loop cnt'+          else+            putMVar mvar ()+    go [] = return True+    go (msg@(LogStr len _):msgs)+      | len <  0       = return False+      | len <= bufsize = writeLogStr buf fdref msg >> go msgs+      | otherwise      = writeBigLogStr  fdref msg >> go msgs++----------------------------------------------------------------++-- | Creating `SingleLogger`.+newSingleLogger :: BufSize -> IORef FD -> IO SingleLogger+newSingleLogger bufsize fdref = do+    tvar <- newTVarIO 0+    ref <- newIORef (mempty,[])+    mvar <- newEmptyMVar+    buf <- getBuffer bufsize+    _ <- forkIO $ writer bufsize buf fdref tvar ref mvar+    let kill = do+            let fin = LogStr (-1) mempty+            atomicModifyIORef' ref (\(old,q) -> ((mempty,fin:old:q),()))+            takeMVar mvar+        wakeup = atomically $ modifyTVar' tvar (+ 1)+    return $ SingleLogger {+        slgrRef     = ref+      , slgrKill    = kill+      , slgrWakeup  = wakeup+      , slgrBuffer  = buf+      , slgrBufSize = bufsize+      , slgrFdRef   = fdref+      }++----------------------------------------------------------------++pushLog :: SingleLogger -> LogStr -> IO ()+pushLog SingleLogger{..} nlogmsg@(LogStr nlen _)+  | nlen > slgrBufSize = do+        atomicModifyIORef' slgrRef (\(old,q) -> ((mempty,nlogmsg:old:q),()))+        slgrWakeup+  | otherwise = do+        wake <- atomicModifyIORef' slgrRef checkBuf+        when wake slgrWakeup+  where+    checkBuf (ologmsg@(LogStr olen _),q)+      | slgrBufSize < olen + nlen = ((nlogmsg, nlogmsg:q), True)+      | otherwise                 = ((ologmsg <> nlogmsg, q), False)++flushAllLog :: SingleLogger -> IO ()+flushAllLog SingleLogger{..} = do+    atomicModifyIORef' slgrRef (\(old,q) -> ((mempty,old:q),()))+    slgrWakeup++stopLoggers :: SingleLogger -> IO ()+stopLoggers SingleLogger{..} = do+    slgrKill+    freeBuffer slgrBuffer
+ System/Log/FastLogger/Write.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE RecordWildCards #-}++module System.Log.FastLogger.Write (+    writeLogStr+  , writeBigLogStr+  , Loggers(..)+  ) where++import Foreign.Marshal.Alloc (allocaBytes)+import Foreign.Ptr (plusPtr)++import System.Log.FastLogger.FileIO+import System.Log.FastLogger.IO+import System.Log.FastLogger.Imports+import System.Log.FastLogger.LogStr++----------------------------------------------------------------++-- | Writting 'LogStr' using a buffer in blocking mode.+--   The size of 'LogStr' must be smaller or equal to+--   the size of buffer.+writeLogStr :: Buffer -> IORef FD -> LogStr -> IO ()+writeLogStr buf fdref (LogStr len builder) =+    toBufIOWith buf len (write fdref) builder++-- | Writting 'LogStr' using a temporary buffer.+writeBigLogStr :: IORef FD -> LogStr -> IO ()+writeBigLogStr fdref (LogStr len builder) =  allocaBytes len $ \buf ->+    toBufIOWith buf len (write fdref) builder++write :: IORef FD -> Buffer -> Int -> IO ()+write fdref buf len' = loop buf (fromIntegral len')+  where+    loop bf len = do+        written <- writeRawBufferPtr2FD fdref bf len+        when (0 <= written && written < len) $+            loop (bf `plusPtr` fromIntegral written) (len - written)++----------------------------------------------------------------++-- | A class for internal loggers.+class Loggers a where+    stopLoggers :: a -> IO ()+    pushLog     :: a -> LogStr -> IO ()+    flushAllLog :: a -> IO ()
fast-logger.cabal view
@@ -1,5 +1,5 @@ Name:                   fast-logger-Version:                3.1.2+Version:                3.2.0 Author:                 Kazu Yamamoto <kazu@iij.ad.jp> Maintainer:             Kazu Yamamoto <kazu@iij.ad.jp> License:                BSD3@@ -23,10 +23,12 @@                         System.Log.FastLogger.LoggerSet                         System.Log.FastLogger.Types   Other-Modules:        System.Log.FastLogger.Imports-                        System.Log.FastLogger.IO                         System.Log.FastLogger.FileIO+                        System.Log.FastLogger.IO                         System.Log.FastLogger.LogStr-                        System.Log.FastLogger.Logger+                        System.Log.FastLogger.MultiLogger+                        System.Log.FastLogger.SingleLogger+                        System.Log.FastLogger.Write   Build-Depends:        base >= 4.9 && < 5                       , array                       , auto-update >= 0.1.2@@ -34,6 +36,7 @@                       , bytestring >= 0.10.4                       , directory                       , filepath+                      , stm                       , text                       , unix-time >= 0.4.4                       , unix-compat >= 0.2@@ -48,10 +51,11 @@   Default-Language: Haskell2010   Type:             exitcode-stdio-1.0 -  Ghc-Options:      -Wall -threaded+  Ghc-Options:      -Wall -threaded -rtsopts -with-rtsopts=-N   Other-Modules:    FastLoggerSpec   Build-Tools:      hspec-discover >= 2.6   Build-Depends:    base >= 4 && < 5+                  , async                   , bytestring >= 0.10.4                   , directory                   , fast-logger
test/FastLoggerSpec.hs view
@@ -1,20 +1,29 @@-{-# LANGUAGE OverloadedStrings, BangPatterns, CPP #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-} -module FastLoggerSpec where+module FastLoggerSpec (spec) where  #if __GLASGOW_HASKELL__ < 709 import Control.Applicative ((<$>)) #endif import Control.Exception (finally)-import Control.Monad (when)+import Control.Concurrent (getNumCapabilities)+import Control.Concurrent.Async (forConcurrently_)+import Control.Monad (when, forM_) import qualified Data.ByteString.Char8 as BS+import Data.List (sort)+#if !MIN_VERSION_base(4,11,0) import Data.Monoid ((<>))+#endif import Data.String (IsString(fromString)) import System.Directory (doesFileExist, removeFile)-import System.Log.FastLogger+import Text.Printf (printf)+ import Test.Hspec import Test.Hspec.QuickCheck (prop) +import System.Log.FastLogger+ spec :: Spec spec = do   describe "instance Show LogStr" $ do@@ -22,12 +31,14 @@       let logstr :: LogStr           logstr = fromString str       in show logstr == show str+   describe "instance Eq LogStr" $ do     prop "it should be consistent with instance IsString" $ \str1 str2 ->       let logstr1, logstr2 :: LogStr           logstr1 = fromString str1           logstr2 = fromString str2       in (logstr1 == logstr2) == (str1 == str2)+   describe "pushLogMsg" $ do     it "is safe for a large message" $ safeForLarge [         100@@ -38,29 +49,26 @@       ]     it "logs all messages" logAllMsgs -nullLogger :: IO LoggerSet-#ifdef mingw32_HOST_OS-nullLogger = newFileLoggerSet 4096 "nul"-#else-nullLogger = newFileLoggerSet 4096 "/dev/null"-#endif+  describe "fastlogger 1" $ do+    it "maintains the ordering of log messages" logOrdering +tempFile :: FilePath+tempFile = "test/temp.txt"+ safeForLarge :: [Int] -> IO ()-safeForLarge ns = mapM_ safeForLarge' ns+safeForLarge = mapM_ safeForLarge'  safeForLarge' :: Int -> IO ()-safeForLarge' n = flip finally (cleanup tmpfile) $ do-    cleanup tmpfile-    lgrset <- newFileLoggerSet defaultBufSize tmpfile+safeForLarge' n = flip finally (cleanup tempFile) $ do+    cleanup tempFile+    lgrset <- newFileLoggerSet defaultBufSize tempFile     let xs = toLogStr $ BS.pack $ take (abs n) (cycle ['a'..'z'])         lf = "x"     pushLogStr lgrset $ xs <> lf     flushLogStr lgrset     rmLoggerSet lgrset-    bs <- BS.readFile tmpfile+    bs <- BS.readFile tempFile     bs `shouldBe` BS.pack (take (abs n) (cycle ['a'..'z']) <> "x")-    where-        tmpfile = "test/temp"  cleanup :: FilePath -> IO () cleanup file = do@@ -68,16 +76,39 @@     when exist $ removeFile file  logAllMsgs :: IO ()-logAllMsgs = logAll "LICENSE" `finally` cleanup tmpfile+logAllMsgs = logAll "LICENSE" `finally` cleanup tempFile   where-    tmpfile = "test/temp"     logAll file = do-        cleanup tmpfile-        lgrset <- newFileLoggerSet 512 tmpfile+        cleanup tempFile+        lgrset <- newFileLoggerSet 512 tempFile         src <- BS.readFile file         let bs = (<> "\n") . toLogStr <$> BS.lines src         mapM_ (pushLogStr lgrset) bs         flushLogStr lgrset         rmLoggerSet lgrset-        dst <- BS.readFile tmpfile+        dst <- BS.readFile tempFile         dst `shouldBe` src++logOrdering :: IO ()+logOrdering = flip finally (cleanup tempFile) $ do+    cleanup tempFile+    -- 128 is small enough for out-of-ordering+    (pushlog, teardown) <- newFastLogger1 $ LogFileNoRotate tempFile 128+    numCapabilities <- getNumCapabilities+    let concurrency = numCapabilities * 200 :: Int+        logEntriesCount = 100 :: Int+    forConcurrently_ [0 .. concurrency - 1] $ \t ->+      forM_ [0 .. logEntriesCount - 1] $ \i -> do+        let tag = mktag t+            cnt = printf "%02d" i :: String+            logmsg = toLogStr tag <> "log line nr: " <> toLogStr cnt <> "\n"+        pushlog logmsg+    teardown+    xs <- BS.lines <$> BS.readFile tempFile+    forM_ [0 .. concurrency - 1] $ \t -> do+        let tag = BS.pack $ mktag t+            msgs = filter (tag `BS.isPrefixOf`) xs+        sort msgs `shouldBe` msgs+  where+    mktag :: Int -> String+    mktag t = "thread id: " <> show t <> " "