packages feed

log4hs 0.5.0.0 → 0.6.0.0

raw patch · 14 files changed

+437/−310 lines, 14 filesdep +mtlPVP ok

version bump matches the API change (PVP)

Dependencies added: mtl

API changes (from Hackage documentation)

- Logging: defaultRoot :: Sink
- Logging: run :: Manager -> IO a -> IO a
- Logging: stderrHandler :: StreamHandler
- Logging: stdoutHandler :: StreamHandler
- Logging.TH: debug :: ExpQ
- Logging.TH: error :: ExpQ
- Logging.TH: fatal :: ExpQ
- Logging.TH: info :: ExpQ
- Logging.TH: logv :: ExpQ
- Logging.TH: warn :: ExpQ
+ Logging.Global: run :: Manager -> IO a -> IO a
+ Logging.Global.TH: debug :: ExpQ
+ Logging.Global.TH: error :: ExpQ
+ Logging.Global.TH: fatal :: ExpQ
+ Logging.Global.TH: info :: ExpQ
+ Logging.Global.TH: logv :: ExpQ
+ Logging.Global.TH: warn :: ExpQ
+ Logging.Monad: runLoggingT :: MonadIO m => LoggingT m a -> Manager -> m a
+ Logging.Monad: type LoggingT m a = ReaderT Manager m a
+ Logging.Monad.TH: debug :: ExpQ
+ Logging.Monad.TH: error :: ExpQ
+ Logging.Monad.TH: fatal :: ExpQ
+ Logging.Monad.TH: info :: ExpQ
+ Logging.Monad.TH: logv :: ExpQ
+ Logging.Monad.TH: warn :: ExpQ
+ Logging.Types: initialize :: Manager -> IO ()
+ Logging.Types: terminate :: Manager -> IO ()

Files

ChangeLog.md view
@@ -17,3 +17,8 @@ - Fix `RotatingFileHandler` bug - Refine tests - Fix `callHandler` redundant check++## V0.6.0+- Add local logging (run in monad)+- Redesign global logging+- Remove `stderrHandler` `stdoutHandler` `defaultRoot`
log4hs.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 8bbb99f215db2c4adcdb2ab401f23a78b09e5a05c3c874cbb6102403fa6d387d+-- hash: 621a17085d5d95bc8f29ab4cb601c416c58065b9290beebafdcf7544be009732  name:           log4hs-version:        0.5.0.0+version:        0.6.0.0 synopsis:       A python logging style log library description:    Please see the http://hackage.haskell.org/package/log4hs category:       logging@@ -26,12 +26,17 @@       Logging.Config.Json       Logging.Config.Type       Logging.Config.Yaml+      Logging.Global+      Logging.Global.TH+      Logging.Monad+      Logging.Monad.TH       Logging.Prelude       Logging.Types       Logging.TH       Logging.Utils   other-modules:-      Logging.Internal+      Logging.Global.Internal+      Logging.Monad.Internal       Logging.Types.Class       Logging.Types.Class.Filterable       Logging.Types.Class.Handler@@ -58,6 +63,7 @@     , filepath >=1.3 && <1.5     , generic-lens >=0.5 && <2.0     , lens >=4.15 && <5.0+    , mtl >=2.2 && <3.0     , template-haskell >=2.0 && <3.0     , text >=1.2 && <2.0     , time >=1.4 && <2.0@@ -70,7 +76,7 @@   main-is: Main.hs   other-modules:       LoggingTest.ConfigSpec-      LoggingTest.InternalSpec+      LoggingTest.GlobalSpec       LoggingTest.Prelude       LoggingTest.TypesSpec       Paths_log4hs@@ -92,6 +98,7 @@     , hspec-core >=2.1 && <3.0     , lens >=4.15 && <5.0     , log4hs+    , mtl >=2.2 && <3.0     , process >=1.2 && <2.0     , template-haskell >=2.0 && <3.0     , text >=1.2 && <2.0@@ -121,6 +128,7 @@     , generic-lens >=0.5 && <2.0     , lens >=4.15 && <5.0     , log4hs+    , mtl >=2.2 && <3.0     , template-haskell >=2.0 && <3.0     , text >=1.2 && <2.0     , time >=1.4 && <2.0
src/Logging.hs view
@@ -16,24 +16,24 @@    module Main ( main ) where -  import           Logging             (run)   import           Logging.Config.Json (getManager)-  import           Logging.TH          (debug, error, fatal, info, logv, warn)+  import           Logging.Global      (run)+  import           Logging.Global.TH   (debug, error, fatal, info, logv, warn)   import           Prelude             hiding (error)    main :: IO ()   main = getManager "{}" >>= flip run app -  myLogger = \"MyLogger.Main\"+  logger = \"Main\"    app :: IO ()   app = do-    $(debug) myLogger \"this is a test message\"-    $(info) myLogger \"this is a test message\"-    $(warn) myLogger \"this is a test message\"-    $(error) myLogger \"this is a test message\"-    $(fatal) myLogger \"this is a test message\"-    $(logv) myLogger \"LEVEL 100\" \"this is a test message\"+    $(debug) logger \"this is a test message\"+    $(info) logger \"this is a test message\"+    $(warn) logger \"this is a test message\"+    $(error) logger \"this is a test message\"+    $(fatal) logger \"this is a test message\"+    $(logv) logger \"LEVEL 100\" \"this is a test message\" @  See "Logging.Config.Json" and "Logging.Config.Yaml" to lean more about@@ -42,9 +42,9 @@ module Logging   ( module Logging.Types   , module Logging.TH-  , module Logging.Internal+  , module Logging.Global   ) where -import           Logging.Internal hiding (log)+import           Logging.Global import           Logging.TH import           Logging.Types
+ src/Logging/Global.hs view
@@ -0,0 +1,19 @@+{-| Run logging globally.++The 'run' function will properly "initialize" and "terminate" the+global "Manager".++If the "Manager"\'s 'catchUncaughtException' is True, the 'run' function will+set an uncaught exception handler that will log all uncaught exceptions,+ and set the handler to the original one before 'run' is complete.+-}++module Logging.Global+  ( run+  , module Logging.Global.TH+  ) where+++import           Logging.Global.Internal+import           Logging.Global.TH+import           Logging.Types
+ src/Logging/Global/Internal.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}++module Logging.Global.Internal+  ( log+  , run+  ) where+++import           Control.Exception+import           Control.Monad+import           Control.Monad.IO.Class+import           Data.IORef+import           GHC.Conc+import           Prelude                hiding (log)+import           System.IO.Unsafe++import qualified Logging.Monad.Internal as M+import           Logging.Types++{-# NOINLINE ref #-}+ref :: IORef Manager+ref = unsafePerformIO $ newIORef undefined+++log :: MonadIO m+     => Logger -> Level -> String -> (String, String, String, Int)+     -> m ()+log logger level message location = liftIO $ do+  manager <- readIORef ref+  M.runLoggingT (M.log logger level message location) manager+++run :: Manager -> IO a -> IO a+run manager@Manager{..} io = do+    oldHandler <- getUncaughtExceptionHandler+    when catchUncaughtException $ setUncaughtExceptionHandler logException+    bracket_ (initialize manager >> atomicWriteIORef ref manager)+             (terminate manager >> setUncaughtExceptionHandler oldHandler)+             io+  where+    unknownLoc = ("unknown file", "unknown package", "unknown module", 0)++    logException :: SomeException -> IO ()+    logException e = log "" "ERROR" (show e) unknownLoc
+ src/Logging/Global/TH.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE TemplateHaskell   #-}++{-| A copy of "Logging.Monad.TH"+-}++module Logging.Global.TH+  ( logv+  , debug+  , info+  , warn+  , error+  , fatal+  ) where++import           Control.Monad.IO.Class  (MonadIO)+import           Language.Haskell.TH++import           Logging.Global.Internal+import           Logging.Types++import           Prelude                 hiding (error, log)++-- | Log "message" with the severity "level".+--+-- The missing type signature:+-- 'MonadIO' m => 'Logger' -> 'Level' -> 'String' -> m ()+logv :: ExpQ+logv = do+  loc <- location+  let filename = loc_filename loc+      packagename = loc_package loc+      modulename = loc_module loc+      lineno = fst $ loc_start loc+      location = (filename, packagename, modulename, lineno)+  [| \logger level msg -> log logger level msg location |]++-- | Log "message" with a specific severity.+--+-- The missing type signature: 'MonadIO' m => 'Logger' -> 'String' -> m ()+debug, info, warn, error, fatal :: ExpQ+debug = [| \logger -> $(logv) logger $ read "DEBUG" |]+info  = [| \logger -> $(logv) logger $ read "INFO" |]+warn  = [| \logger -> $(logv) logger $ read "WARN" |]+error = [| \logger -> $(logv) logger $ read "ERROR" |]+fatal = [| \logger -> $(logv) logger $ read "FATAL" |]
− src/Logging/Internal.hs
@@ -1,134 +0,0 @@-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE OverloadedStrings     #-}-{-# LANGUAGE RecordWildCards       #-}-{-# LANGUAGE ScopedTypeVariables   #-}-{-# LANGUAGE TypeApplications      #-}--module Logging.Internal-  ( run-  , log-  , stderrHandler-  , stdoutHandler-  , defaultRoot-  ) where--import           Control.Exception           (SomeException, bracket_)-import           Control.Lens                (view)-import           Control.Monad               (forM_, void, when)-import           Control.Monad.IO.Class      (MonadIO (..))-import           Data.Default-import           Data.Generics.Product.Typed-import           Data.IORef-import           Data.List                   (dropWhileEnd, group)-import           Data.Map.Lazy               (elems, (!?))-import           Data.Time.Clock-import           Data.Time.Clock.POSIX-import           Data.Time.LocalTime-import           GHC.Conc                    (setUncaughtExceptionHandler)-import           Prelude                     hiding (filter, log)-import           System.FilePath-import           System.IO                   (stderr, stdout)-import           System.IO.Unsafe            (unsafePerformIO)--import           Logging.Prelude-import           Logging.Types--{-# NOINLINE _mgr #-}-_mgr :: IORef Manager-_mgr = unsafePerformIO $ newIORef undefined----- |Run a logging environment.------ You should always write you application inside a logging environment.------ 1. rename "main" function to "originMain" (or whatever you call it)--- 2. write "main" as below------ > main :: IO ()--- > main = run manager originMain--- > ...----run :: Manager -> IO a -> IO a-run mgr@Manager{..} io = do-    when catchUncaughtException $ setUncaughtExceptionHandler uceHandler-    bracket_ (atomicWriteIORef _mgr mgr >> start) shutdown io-  where-    unknownLoc = ("unknown file", "unknown package", "unknown module", 0)--    uceHandler :: SomeException -> IO ()-    uceHandler e = log "" "ERROR" (show e) unknownLoc--    allHandlers = map head $ group $-      concat [ handlers s | s <- (root : (elems sinks)) ]--    start :: IO ()-    start = forM_ allHandlers open--    shutdown :: IO ()-    shutdown = forM_ allHandlers close----- |Low-level logging routine which creates a LogRecord and then calls--- all the handlers of this logger to handle the record.-log :: MonadIO m-     => Logger -> Level -> String -> (String, String, String, Int) -> m ()-log logger level message location = liftIO $ do-    mgr@Manager{..} <- readIORef _mgr-    asctime <- getZonedTime--    let (pathname, pkgname, modulename, lineno) = location-        filename = takeFileName pathname-        utctime = zonedTimeToUTC asctime-        diffTime = utcTimeToPOSIXSeconds utctime-        created = timestamp diffTime-        msecs = microseconds diffTime--    when (not disabled) $ process logger mgr $-      LogRecord logger level message pathname filename pkgname modulename-                lineno asctime utctime created msecs-  where-    process :: Logger -> Manager -> LogRecord -> IO ()-    process logger mgr rcd =-      case lookupSink logger mgr of-        Just sink@Sink{..} -> do-          when (isSinkEnabledFor sink rcd) $ callHandlers handlers rcd-          let parentLogger = parent logger-              shouldPropagate = propagate && logger /= parentLogger-          when shouldPropagate $ process parentLogger mgr rcd-        Nothing -> process (parent logger) mgr rcd--    parent :: Logger -> Logger-    parent = dropWhileEnd (== '.') . dropWhileEnd (/= '.')--    lookupSink :: Logger -> Manager -> Maybe Sink-    lookupSink logger mgr@Manager{root=root@Sink{logger=rootLogger}, ..}-      | logger `elem` ["", rootLogger] = Just root-      | otherwise = sinks !? logger--    callHandlers :: [SomeHandler] -> LogRecord -> IO ()-    callHandlers handlers rcd = forM_ handlers $ \hdl ->-      Logging.Types.handle hdl rcd--    isSinkEnabledFor :: Sink -> LogRecord -> Bool-    isSinkEnabledFor sink@Sink{..} rcd@LogRecord{level=level'}-      | disabled = False-      | level' < level = False-      | otherwise = filter sink rcd---{-# DEPRECATED stderrHandler, stdoutHandler, defaultRoot "Will be removed" #-}---- |A 'StreamHandler' bound to 'stderr'-stderrHandler :: StreamHandler-stderrHandler = StreamHandler def [] "{message}" stderr---- |A 'StreamHandler' bound to 'stdout'-stdoutHandler :: StreamHandler-stdoutHandler = StreamHandler def [] "{message}" stdout---- |Default root sink which is used by "Logging.Config.Json" and--- "Logging.Config.Yaml" when __root__ sink is omitted.----defaultRoot :: Sink-defaultRoot = Sink "" "DEBUG" [] [toHandler stderrHandler] False False
+ src/Logging/Monad.hs view
@@ -0,0 +1,22 @@+{-| Run logging in a 'Reader' monad.++You should 'initialize' the 'Manager' before using it in the 'Reader'+monad, and 'terminate' it when it is no longer needed.++If you want to log uncaught exceptions,+see "GHC.Conc.setUncaughtExceptionHandler".+-}++++module Logging.Monad+  ( LoggingT+  , runLoggingT+  -- ** Log routines+  , module Logging.Monad.TH+  ) where+++import           Logging.Monad.Internal+import           Logging.Monad.TH+import           Logging.Types
+ src/Logging/Monad/Internal.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE RecordWildCards       #-}+{-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE TypeApplications      #-}++module Logging.Monad.Internal+  ( LoggingT+  , runLoggingT+  , log+  ) where+++import           Control.Exception           (SomeException, bracket_)+import           Control.Lens                (view)+import           Control.Monad+import           Control.Monad.IO.Class      (MonadIO (..))+import           Control.Monad.Reader+import           Data.Default+import           Data.Generics.Product.Typed+import           Data.IORef+import           Data.List                   (dropWhileEnd, group)+import           Data.Map.Lazy               (elems, (!?))+import           Data.Time.Clock+import           Data.Time.Clock.POSIX+import           Data.Time.LocalTime+import           GHC.Conc                    (setUncaughtExceptionHandler)+import           Prelude                     hiding (filter, log)+import           System.FilePath+import           System.IO                   (stderr, stdout)+import           System.IO.Unsafe            (unsafePerformIO)++import           Logging.Prelude+import           Logging.Types+++type LoggingT m a = ReaderT Manager m a+++runLoggingT :: MonadIO m => LoggingT m a -> Manager -> m a+runLoggingT = runReaderT+++log :: MonadIO m+    => Logger -> Level -> String -> (String, String, String, Int)+    -> LoggingT m ()+log logger level message location = do+    manager@Manager{..} <- ask+    asctime <- lift $ liftIO $ getZonedTime++    let (pathname, pkgname, modulename, lineno) = location+        filename = takeFileName pathname+        utctime = zonedTimeToUTC asctime+        diffTime = utcTimeToPOSIXSeconds utctime+        created = timestamp diffTime+        msecs = microseconds diffTime++    when (not disabled) $ lift $ liftIO $ process logger manager $+      LogRecord logger level message pathname filename pkgname modulename+                lineno asctime utctime created msecs+  where+    process :: Logger -> Manager -> LogRecord -> IO ()+    process logger manager rcd =+      case lookupSink logger manager of+        Just sink@Sink{..} -> do+          when (isSinkEnabledFor sink rcd) $ callHandlers handlers rcd+          let parentLogger = parent logger+              shouldPropagate = propagate && logger /= parentLogger+          when shouldPropagate $ process parentLogger manager rcd+        Nothing -> process (parent logger) manager rcd++    parent :: Logger -> Logger+    parent = dropWhileEnd (== '.') . dropWhileEnd (/= '.')++    lookupSink :: Logger -> Manager -> Maybe Sink+    lookupSink logger manager@Manager{root=root@Sink{logger=rootLogger}, ..}+      | logger `elem` ["", rootLogger] = Just root+      | otherwise = sinks !? logger++    callHandlers :: [SomeHandler] -> LogRecord -> IO ()+    callHandlers handlers rcd = forM_ handlers $ \hdl ->+      Logging.Types.handle hdl rcd++    isSinkEnabledFor :: Sink -> LogRecord -> Bool+    isSinkEnabledFor sink@Sink{..} rcd@LogRecord{level=level'}+      | disabled = False+      | level' < level = False+      | otherwise = filter sink rcd
+ src/Logging/Monad/TH.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE TemplateHaskell   #-}++{-| This module provides a series of log routines that create a 'LogRecord'+ and then emit a log event.++The log routines use "Language.Haskell.TH" to obtain some fields related to+where they are called,+e.g. __filename__, __pkgname__, __modulename__, __lineno__++When use these log routines, you should enable __TemplateHaskell__+language extension.+-}++module Logging.Monad.TH+  ( logv+  , debug+  , info+  , warn+  , error+  , fatal+  ) where++import           Control.Monad.IO.Class (MonadIO)+import           Language.Haskell.TH++import           Logging.Monad.Internal+import           Logging.Types++import           Prelude                hiding (error, log)++-- | Log "message" with the severity "level".+--+-- The missing type signature:+-- 'MonadIO' m => 'Logger' -> 'Level' -> 'String' -> 'LoggingT' m ()+logv :: ExpQ+logv = do+  loc <- location+  let filename = loc_filename loc+      packagename = loc_package loc+      modulename = loc_module loc+      lineno = fst $ loc_start loc+      location = (filename, packagename, modulename, lineno)+  [| \logger level msg -> log logger level msg location |]++-- | Log "message" with a specific severity.+--+-- The missing type signature:+-- 'MonadIO' m => 'Logger' -> 'String' -> 'LoggingT' m ()+debug, info, warn, error, fatal :: ExpQ+debug = [| \logger -> $(logv) logger $ read "DEBUG" |]+info  = [| \logger -> $(logv) logger $ read "INFO" |]+warn  = [| \logger -> $(logv) logger $ read "WARN" |]+error = [| \logger -> $(logv) logger $ read "ERROR" |]+fatal = [| \logger -> $(logv) logger $ read "FATAL" |]
src/Logging/TH.hs view
@@ -1,54 +1,5 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes       #-}-{-# LANGUAGE TemplateHaskell   #-}--{-| This module provides a series of log routines that create a 'LogRecord'- and then emit a log event.--The log routines use "Language.Haskell.TH" to obtain some fields related to-where they are called,-e.g. __filename__, __pkgname__, __modulename__, __lineno__--When use these log routines, you should enable __TemplateHaskell__-language extension.--}--module Logging.TH-  ( logv-  , debug-  , info-  , warn-  , error-  , fatal+module Logging.TH {-# DEPRECATED "Renamed as Logging.TH.Global.TH" #-}+  ( module Logging.Global.TH   ) where -import           Control.Monad.IO.Class (MonadIO)-import           Language.Haskell.TH--import           Logging.Internal-import           Logging.Types--import           Prelude                hiding (error, log)---- | Log "message" with the severity "level".------ The missing type signature: 'MonadIO' m => 'Logger' -> 'Level' -> 'String' -> m ()-logv :: ExpQ-logv = do-  loc <- location-  let filename = loc_filename loc-      packagename = loc_package loc-      modulename = loc_module loc-      lineno = fst $ loc_start loc-      location = (filename, packagename, modulename, lineno)-  [| \logger level msg -> log logger level msg location |]---- | Log "message" with a specific severity.------ The missing type signature: 'MonadIO' m => 'Logger' -> 'String' -> m ()-debug, info, warn, error, fatal :: ExpQ-debug = [| \logger -> $(logv) logger $ read "DEBUG" |]-info = [| \logger -> $(logv) logger $ read "INFO" |]-warn = [| \logger -> $(logv) logger $ read "WARN" |]-error = [| \logger -> $(logv) logger $ read "ERROR" |]-fatal = [| \logger -> $(logv) logger $ read "FATAL" |]+import           Logging.Global.TH
src/Logging/Types/Manager.hs view
@@ -1,7 +1,15 @@-module Logging.Types.Manager ( Manager(..) ) where+{-# LANGUAGE RecordWildCards #-} -import           Data.Map.Lazy      (Map)+module Logging.Types.Manager+  ( Manager(..)+  , initialize+  , terminate+  ) where +import           Data.List                   (nub)+import           Data.Map.Lazy               (Map, elems)++import           Logging.Types.Class.Handler import           Logging.Types.Sink  @@ -12,3 +20,15 @@                        , disabled               :: Bool                        , catchUncaughtException :: Bool                        }+++-- | Initialize a 'Manager', open all its handlers.+initialize :: Manager -> IO ()+initialize Manager{..} = mapM_ open $+  nub $ concat [ handlers | Sink{..} <- (root : (elems sinks)) ]+++-- | Terminate a 'Manager', close all its handlers.+terminate :: Manager -> IO ()+terminate Manager{..} = mapM_ close $+  nub $ concat [ handlers | Sink{..} <- (root : (elems sinks)) ]
+ test/LoggingTest/GlobalSpec.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE TemplateHaskell   #-}++module LoggingTest.GlobalSpec ( spec ) where++import           Control.Monad+import           Data.Default            (def)+import           Data.List               (intercalate)+import qualified Data.Map                as M+import           Data.Word+import           Prelude                 hiding (error)+import           System.IO+import           System.IO.Unsafe        (unsafePerformIO)+import           System.Process          (createPipe)+import           Test.Hspec+import           Test.Hspec.QuickCheck+import           Test.QuickCheck+import           Test.QuickCheck.Monadic+import           Text.Format++import           Logging                 hiding (run)+import           Logging.TH+import           LoggingTest.Prelude++spec :: Spec+spec = describe "run & log" $ do+  let levels = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"]+      loggers = [ ("root", "DEBUG", [], ["DEBUG"], False, False)+                , ("Disabled", "DEBUG", [], ["DEBUG"], True, False)+                , ("Debug", "DEBUG", [], ["DEBUG", "INFO"], False, False)+                , ("Info", "INFO", ["Info.A"], ["DEBUG", "INFO"], False, False)+                , ("Warn", "WARN", [], ["WARN"], False, False)+                , ("Warn.Error", "ERROR", [], ["ERROR"], False, False)+                , ("Error", "ERROR", [], ["ERROR"], False, True)+                , ("Error.Fatal", "FATAL", [], ["FATAL"], False, True)+                ]+  handlers <- fmap M.fromList $ runIO $ forM levels $ \level -> do+    (read, write) <- createPipe+    hSetEncoding read utf8 >> hSetEncoding write utf8+    let handler = toHandler $ StreamHandler level [] "{message}" write+    return $ (level, (handler, read))+  sinks <- fmap M.fromList $ runIO $ forM loggers $ \item -> do+    let (logger, level, fs, hs, disabled, propagate) = item+        logger' = if logger == "root" then "" else logger+        hs' = [fst (handlers M.! h) | h <- hs]+        sink = Sink logger' level fs hs' disabled propagate+    return (logger, sink)++  let root = sinks M.! "root"+      sinks' = M.delete "root" sinks+      disabledManager = Manager root sinks' True False+      manager = Manager root sinks' False False++  prop "filter" $ \(MessageString message) -> monadicIO $ do+    -- manager disabled+    run $ runLog disabledManager $ $(info) "Debug" message+    msgs <- run $ forM ["DEBUG", "INFO"] $ \level ->+      hTryGetLine $ snd $ handlers M.! level+    assert $ ["", ""] ==  msgs+    -- sink disabled+    run $ runLog manager $ $(debug) "Disabled" message+    msg1 <- run $ hTryGetLine $ snd $ handlers M.! "DEBUG"+    assert $ msg1 == ""+    -- sink level reject+    run $ runLog manager $ $(debug) "Info.A" message+    msgs1 <- run $ forM ["DEBUG", "INFO"] $ \level ->+      hTryGetLine $ snd $ handlers M.! level+    assert $ ["", ""] == msgs1+    -- sink filterer reject+    run $ runLog manager $ $(info) "Info.B" message+    msgs2 <- run $ forM ["DEBUG", "INFO"] $ \level ->+      hTryGetLine $ snd $ handlers M.! level+    assert $ ["", ""] ==  msgs2+    -- pass 1+    run $ runLog manager $ $(info) "Debug" message+    msgs3 <- run $ forM ["DEBUG", "INFO"] $ \level ->+      hTryGetLine $ snd $ handlers M.! level+    assert $ [message, message] == msgs3+    -- pass 2+    run $ runLog manager $ $(info) "Info.A" message+    msgs4 <- run $ forM ["DEBUG", "INFO"] $ \level ->+      hTryGetLine $ snd $ handlers M.! level+    assert $ [message, message] == msgs4++  prop "propagate" $ \(MessageString message) -> monadicIO $ do+    -- propagation disabled 1+    run $ runLog manager $ $(warn) "Warn" message+    msgs <- run $ forM ["DEBUG", "WARN"] $ \level ->+      hTryGetLine $ snd $ handlers M.! level+    assert $ ["", message] == msgs+    -- propagation disabled 2+    run $ runLog manager $ $(error) "Warn.Error" message+    msgs1 <- run $ forM ["DEBUG", "WARN", "ERROR"] $ \level ->+      hTryGetLine $ snd $ handlers M.! level+    assert $ ["", "", message] == msgs1+    -- propagation enabled 1+    run $ runLog manager $ $(error) "Error" message+    msgs2 <- run $ forM ["DEBUG", "ERROR"] $ \level ->+      hTryGetLine $ snd $ handlers M.! level+    assert $ [message, message] == msgs2+    -- propagation enabled 2+    run $ runLog manager $ $(fatal) "Error.Fatal" message+    msgs3 <- run $ forM ["DEBUG", "ERROR", "FATAL"] $ \level ->+      hTryGetLine $ snd $ handlers M.! level+    assert $ [message, message, message] == msgs3
− test/LoggingTest/InternalSpec.hs
@@ -1,107 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes       #-}-{-# LANGUAGE RecordWildCards   #-}-{-# LANGUAGE TemplateHaskell   #-}--module LoggingTest.InternalSpec ( spec ) where--import           Control.Monad-import           Data.Default            (def)-import           Data.List               (intercalate)-import qualified Data.Map                as M-import           Data.Word-import           Prelude                 hiding (error)-import           System.IO-import           System.IO.Unsafe        (unsafePerformIO)-import           System.Process          (createPipe)-import           Test.Hspec-import           Test.Hspec.QuickCheck-import           Test.QuickCheck-import           Test.QuickCheck.Monadic-import           Text.Format--import           Logging                 hiding (run)-import           Logging.TH-import           LoggingTest.Prelude--spec :: Spec-spec = describe "run & log" $ do-  let levels = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"]-      loggers = [ ("root", "DEBUG", [], ["DEBUG"], False, False)-                , ("Disabled", "DEBUG", [], ["DEBUG"], True, False)-                , ("Debug", "DEBUG", [], ["DEBUG", "INFO"], False, False)-                , ("Info", "INFO", ["Info.A"], ["DEBUG", "INFO"], False, False)-                , ("Warn", "WARN", [], ["WARN"], False, False)-                , ("Warn.Error", "ERROR", [], ["ERROR"], False, False)-                , ("Error", "ERROR", [], ["ERROR"], False, True)-                , ("Error.Fatal", "FATAL", [], ["FATAL"], False, True)-                ]-  handlers <- fmap M.fromList $ runIO $ forM levels $ \level -> do-    (read, write) <- createPipe-    hSetEncoding read utf8 >> hSetEncoding write utf8-    let handler = toHandler $ StreamHandler level [] "{message}" write-    return $ (level, (handler, read))-  sinks <- fmap M.fromList $ runIO $ forM loggers $ \item -> do-    let (logger, level, fs, hs, disabled, propagate) = item-        logger' = if logger == "root" then "" else logger-        hs' = [fst (handlers M.! h) | h <- hs]-        sink = Sink logger' level fs hs' disabled propagate-    return (logger, sink)--  let root = sinks M.! "root"-      sinks' = M.delete "root" sinks-      disabledManager = Manager root sinks' True False-      manager = Manager root sinks' False False--  prop "filter" $ \(MessageString message) -> monadicIO $ do-    -- manager disabled-    run $ runLog disabledManager $ $(info) "Debug" message-    msgs <- run $ forM ["DEBUG", "INFO"] $ \level ->-      hTryGetLine $ snd $ handlers M.! level-    assert $ ["", ""] ==  msgs-    -- sink disabled-    run $ runLog manager $ $(debug) "Disabled" message-    msg1 <- run $ hTryGetLine $ snd $ handlers M.! "DEBUG"-    assert $ msg1 == ""-    -- sink level reject-    run $ runLog manager $ $(debug) "Info.A" message-    msgs1 <- run $ forM ["DEBUG", "INFO"] $ \level ->-      hTryGetLine $ snd $ handlers M.! level-    assert $ ["", ""] == msgs1-    -- sink filterer reject-    run $ runLog manager $ $(info) "Info.B" message-    msgs2 <- run $ forM ["DEBUG", "INFO"] $ \level ->-      hTryGetLine $ snd $ handlers M.! level-    assert $ ["", ""] ==  msgs2-    -- pass 1-    run $ runLog manager $ $(info) "Debug" message-    msgs3 <- run $ forM ["DEBUG", "INFO"] $ \level ->-      hTryGetLine $ snd $ handlers M.! level-    assert $ [message, message] == msgs3-    -- pass 2-    run $ runLog manager $ $(info) "Info.A" message-    msgs4 <- run $ forM ["DEBUG", "INFO"] $ \level ->-      hTryGetLine $ snd $ handlers M.! level-    assert $ [message, message] == msgs4--  prop "propagate" $ \(MessageString message) -> monadicIO $ do-    -- propagation disabled 1-    run $ runLog manager $ $(warn) "Warn" message-    msgs <- run $ forM ["DEBUG", "WARN"] $ \level ->-      hTryGetLine $ snd $ handlers M.! level-    assert $ ["", message] == msgs-    -- propagation disabled 2-    run $ runLog manager $ $(error) "Warn.Error" message-    msgs1 <- run $ forM ["DEBUG", "WARN", "ERROR"] $ \level ->-      hTryGetLine $ snd $ handlers M.! level-    assert $ ["", "", message] == msgs1-    -- propagation enabled 1-    run $ runLog manager $ $(error) "Error" message-    msgs2 <- run $ forM ["DEBUG", "ERROR"] $ \level ->-      hTryGetLine $ snd $ handlers M.! level-    assert $ [message, message] == msgs2-    -- propagation enabled 2-    run $ runLog manager $ $(fatal) "Error.Fatal" message-    msgs3 <- run $ forM ["DEBUG", "ERROR", "FATAL"] $ \level ->-      hTryGetLine $ snd $ handlers M.! level-    assert $ [message, message, message] == msgs3