packages feed

log-warper 0.3.1 → 0.4

raw patch · 6 files changed

+252/−135 lines, 6 filesdep ~data-defaultdep ~lens

Dependency ranges changed: data-default, lens

Files

examples/Playground.hs view
@@ -7,10 +7,10 @@ import           Data.Monoid       ((<>)) import qualified Data.Text         as T (pack) -import           System.Wlog       (CanLog, dispatchEvents, initLoggingFromYaml, logDebug,-                                    logError, logInfo, logNotice, logWarning,-                                    modifyLoggerName, releaseAllHandlers, runPureLog,-                                    usingLoggerName)+import           System.Wlog       (CanLog, buildAndSetupYamlLogging, dispatchEvents,+                                    logDebug, logError, logInfo, logNotice, logWarning,+                                    modifyLoggerName, prefixB, productionB,+                                    releaseAllHandlers, runPureLog, usingLoggerName)  testLogging :: (CanLog m) => m () testLogging = usingLoggerName "node" $ do@@ -35,6 +35,6 @@  main :: IO () main = bracket_-           (initLoggingFromYaml "logger-config-example.yaml" $ Just "logs")+           (buildAndSetupYamlLogging (productionB <> prefixB "logs") "logger-config-example.yaml")            releaseAllHandlers            (testLogging >> showPureLog)
log-warper.cabal view
@@ -1,5 +1,5 @@ name:                log-warper-version:             0.3.1+version:             0.4 synopsis:            Flexible, configurable, monadic and pretty logging homepage:            https://github.com/serokell/log-warper license:             MIT@@ -37,7 +37,6 @@                      , ansi-terminal        >= 0.6.2.3                      , base                 >= 4.7 && < 5                      , bytestring           >= 0.10.8.1-                     , data-default         >= 0.7.1                      , directory            >= 1.2.6.2                      , dlist                >= 0.8.0.2                      , errors               >= 2.1.2@@ -62,7 +61,7 @@    hs-source-dirs:      src   default-language:    Haskell2010-  ghc-options:         -Wall -fno-warn-orphans -fprint-potential-instances+  ghc-options:         -Wall -fno-warn-orphans    default-extensions:  GeneralizedNewtypeDeriving                        DeriveDataTypeable@@ -108,6 +107,7 @@                      , filepath                      , hspec                      , HUnit+                     , lens                      , log-warper                      , QuickCheck                      , universum
src/System/Wlog/Launcher.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE CPP                       #-} {-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE ViewPatterns              #-}+{-# LANGUAGE NoImplicitPrelude         #-}  -- | -- Module      : System.Wlog.Parser@@ -26,12 +26,14 @@ -- @node@ and @node.comm@.  module System.Wlog.Launcher-       ( initLoggingFromYaml-       , initLoggingFromYamlWithMapper+       ( buildAndSetupYamlLogging+       , initLoggingFromYaml        , parseLoggerConfig-       , traverseLoggerConfig+       , setupLogging        ) where +import           Universum+ #if PatakDebugSkovorodaBARDAQ import qualified Data.ByteString.Char8     as BS (putStrLn) import           Data.Yaml.Pretty          (defConfig, encodePretty)@@ -39,13 +41,10 @@  import           Control.Error.Util        ((?:)) import           Control.Exception         (throwIO)-import           Control.Monad             (join)-import           Control.Monad.Extra       (whenJust)+import           Control.Monad             (join, when) import           Control.Monad.IO.Class    (MonadIO (liftIO)) -import           Data.Foldable             (for_) import qualified Data.HashMap.Strict       as HM hiding (HashMap)-import           Data.Maybe                (fromMaybe) import           Data.Monoid               ((<>)) import           Data.Text                 (unpack) import           Data.Yaml                 (decodeFileEither)@@ -58,50 +57,50 @@ import           System.Log.Logger         (addHandler, updateGlobalLogger)  import           System.Wlog.Formatter     (setStdoutFormatter)-import           System.Wlog.LoggerConfig  (LoggerConfig (..), LoggerTree (..),-                                            RotationParameters)+import           System.Wlog.LoggerConfig  (LoggerConfig (..), LoggerTree (..)) import           System.Wlog.LoggerName    (LoggerName (..)) import           System.Wlog.Roller        (rotationFileHandler)-import           System.Wlog.Wrapper       (Severity (Debug, Warning), convertSeverity,-                                            initLogging, setSeverityMaybe)+import           System.Wlog.Wrapper       (Severity (Debug), convertSeverity,+                                            initTerminalLogging, setSeverityMaybe)  data HandlerFabric     = forall h . LogHandler h => HandlerFabric (FilePath -> Priority -> IO h)  -- | This function traverses 'LoggerConfig' initializing all subloggers--- with 'Severity' and redirecting output in file handlers. Also takes--- 'FilePath' prefix to create directory for file handlers output. Create--- logging rotation if appropriate arguments are passed.-traverseLoggerConfig-    :: MonadIO m-    => (LoggerName -> LoggerName)  -- ^ mapper to transform logger names during traversal-    -> Maybe RotationParameters    -- ^ rotation parameters for roller handle-    -> LoggerTree                  -- ^ given 'LoggerConfig' to traverse-    -> Maybe FilePath              -- ^ prefix for file handlers-    -> m ()-traverseLoggerConfig logMapper mrot tree (fromMaybe "." -> handlerPrefix) = do+-- with 'Severity' and redirecting output in file handlers.+-- See 'LoggerConfig' for more details.+setupLogging :: MonadIO m => LoggerConfig -> m ()+setupLogging LoggerConfig{..} = do     liftIO $ createDirectoryIfMissing True handlerPrefix-    initLogging Warning-    processLoggers mempty tree+    when consoleOutput $ initTerminalLogging isShowTime _lcTermSeverity+    processLoggers mempty _lcTree   where+    handlerPrefix = _lcFilePrefix ?: "."+    logMapper     = appEndo _lcMapper+    isShowTime    = getAny _lcShowTime+    consoleOutput = getAny _lcConsoleOutput+     handlerFabric :: HandlerFabric-    handlerFabric = case mrot of+    handlerFabric = case _lcRotation of         Nothing  -> HandlerFabric fileHandler         Just rot -> HandlerFabric $ rotationFileHandler rot      processLoggers :: MonadIO m => LoggerName -> LoggerTree -> m ()     processLoggers parent LoggerTree{..} = do-        setSeverityMaybe parent ltSeverity+        -- This prevents logger output to appear in terminal+        unless (parent == mempty && not consoleOutput) $+            setSeverityMaybe parent _ltSeverity -        whenJust ltFile $ \fileName -> liftIO $ do-            let filePriority   = convertSeverity $ ltSeverity ?: Debug+        whenJust _ltFile $ \fileName -> liftIO $ do+            let filePriority   = convertSeverity $ _ltSeverity ?: Debug             let handlerPath    = handlerPrefix </> fileName             case handlerFabric of-              HandlerFabric fabric -> do-                thisLoggerHandler <- setStdoutFormatter True <$> fabric handlerPath filePriority-                updateGlobalLogger (loggerName parent) $ addHandler thisLoggerHandler+                HandlerFabric fabric -> do+                    let handlerCreator = fabric handlerPath filePriority+                    thisLoggerHandler <- setStdoutFormatter isShowTime <$> handlerCreator+                    updateGlobalLogger (loggerName parent) $ addHandler thisLoggerHandler -        for_ (HM.toList ltSubloggers) $ \(name, loggerConfig) -> do+        for_ (HM.toList _ltSubloggers) $ \(name, loggerConfig) -> do             let thisLoggerName = LoggerName $ unpack name             let thisLogger     = parent <> logMapper thisLoggerName             processLoggers thisLogger loggerConfig@@ -111,22 +110,19 @@ parseLoggerConfig loggerConfigPath =     liftIO $ join $ either throwIO return <$> decodeFileEither loggerConfigPath --- | Initialize logger hierarchy from configuration file.--- See this module description.-initLoggingFromYamlWithMapper-    :: MonadIO m-    => (LoggerName -> LoggerName)-    -> FilePath-    -> Maybe FilePath-    -> m ()-initLoggingFromYamlWithMapper loggerMapper loggerConfigPath handlerPrefix = do+-- | Applies given builder to parsed logger config and initializes logging.+buildAndSetupYamlLogging :: MonadIO m => LoggerConfig -> FilePath -> m ()+buildAndSetupYamlLogging configBuilder loggerConfigPath = do     cfg@LoggerConfig{..} <- parseLoggerConfig loggerConfigPath+    let builtConfig       = cfg <> configBuilder  #if PatakDebugSkovorodaBARDAQ-    liftIO $ BS.putStrLn $ encodePretty defConfig cfg+    liftIO $ BS.putStrLn $ encodePretty defConfig builtConfig #endif -    traverseLoggerConfig loggerMapper lcRotation lcTree handlerPrefix+    setupLogging builtConfig -initLoggingFromYaml :: MonadIO m => FilePath -> Maybe FilePath -> m ()-initLoggingFromYaml = initLoggingFromYamlWithMapper id+-- | Initialize logger hierarchy from configuration file.+-- See this module description.+initLoggingFromYaml :: MonadIO m => FilePath -> m ()+initLoggingFromYaml = buildAndSetupYamlLogging mempty
src/System/Wlog/LoggerConfig.hs view
@@ -1,3 +1,7 @@+{-# LANGUAGE FlexibleContexts  #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE TemplateHaskell   #-}+ -- | -- Module      : System.Wlog.LoggerConfig -- Copyright   : (c) Serokell, 2016@@ -9,27 +13,58 @@ -- Logger configuration.  module System.Wlog.LoggerConfig-       ( LoggerConfig (..)-       , LoggerTree   (..)-       , LoggerMap+       ( LoggerMap        , RotationParameters (..)+       , fromScratch        , isValidRotation++         -- * Hierarchical tree of loggers (with lenses)+       , LoggerTree (..)+       , ltFile+       , ltSeverity+       , ltSubloggers++         -- * Global logger configuration+       , LoggerConfig (..)++         -- ** Lenses+       , lcConsoleOutput+       , lcFilePrefix+       , lcMapper+       , lcRotation+       , lcShowTime+       , lcTermSeverity+       , lcTree+       , zoomLogger++         -- ** Builders for 'LoggerConfig'+       , consoleOutB+       , mapperB+       , prefixB+       , productionB+       , showTimeB        ) where -import           Data.Aeson          (withObject)-import           Data.Default        (Default (def))-import           Data.HashMap.Strict (HashMap)-import qualified Data.HashMap.Strict as HM hiding (HashMap)-import           Data.Text           (Text)-import qualified Data.Text.Buildable as Buildable-import           Data.Traversable    (for)-import           Data.Word           (Word64)-import           Data.Yaml           (FromJSON (..), ToJSON, Value (Object), (.:), (.:?))-import           Formatting          (bprint, shown)-import           GHC.Generics        (Generic)+import           Universum -import           System.Wlog.Wrapper (Severity)+import           Control.Lens           (at, makeLenses, zoom, _Just)+import           Control.Monad.State    (put)+import           Data.Aeson             (withObject)+import qualified Data.HashMap.Strict    as HM hiding (HashMap)+import           Data.List              (notElem)+import           Data.Monoid            (Any (..))+import           Data.Text              (Text)+import qualified Data.Text.Buildable    as Buildable+import           Data.Traversable       (for)+import           Data.Word              (Word64)+import           Data.Yaml              (FromJSON (..), ToJSON (..), Value (Object),+                                         object, (.!=), (.:), (.:?), (.=))+import           Formatting             (bprint, shown)+import           GHC.Generics           (Generic) +import           System.Wlog.LoggerName (LoggerName)+import           System.Wlog.Wrapper    (Severity)+ ---------------------------------------------------------------------------- -- Utilites & helpers ----------------------------------------------------------------------------@@ -37,6 +72,10 @@ filterObject :: [Text] -> HashMap Text a -> HashMap Text a filterObject excluded = HM.filterWithKey $ \k _ -> k `notElem` excluded +-- | Useful lens combinator to be used for logging initialization.+fromScratch :: Monoid m => State m a -> m+fromScratch = executingState mempty+ ---------------------------------------------------------------------------- -- LoggerTree ----------------------------------------------------------------------------@@ -45,30 +84,44 @@  -- | Stores configuration for hierarchical loggers. data LoggerTree = LoggerTree-    { ltSubloggers :: !LoggerMap-    , ltFile       :: !(Maybe FilePath)-    , ltSeverity   :: !(Maybe Severity)+    { _ltSubloggers :: !LoggerMap+    , _ltFile       :: !(Maybe FilePath)+    , _ltSeverity   :: !(Maybe Severity)     } deriving (Generic, Show) -instance ToJSON LoggerTree+makeLenses ''LoggerTree -instance Default LoggerTree where-    def = LoggerTree-          { ltFile       = Nothing-          , ltSeverity   = Nothing-          , ltSubloggers = mempty-          }+-- TODO: QuickCheck tests on monoid laws+instance Monoid LoggerTree where+    mempty = LoggerTree+        { _ltFile       = Nothing+        , _ltSeverity   = Nothing+        , _ltSubloggers = mempty+        } +    lt1 `mappend` lt2 = LoggerTree+        { _ltFile       = _ltFile       lt1 <|> _ltFile       lt2+        , _ltSeverity   = _ltSeverity   lt1 <|> _ltSeverity   lt2+        , _ltSubloggers = _ltSubloggers lt1  <> _ltSubloggers lt2+        }+ nonLoggers :: [Text] nonLoggers = ["file", "severity"] +instance ToJSON LoggerTree instance FromJSON LoggerTree where     parseJSON = withObject "loggers tree" $ \o -> do-        ltFile       <- o .:? "file"-        ltSeverity   <- o .:? "severity"-        ltSubloggers <- for (filterObject nonLoggers o) parseJSON+        _ltFile       <- o .:? "file"+        _ltSeverity   <- o .:? "severity"+        _ltSubloggers <- for (filterObject nonLoggers o) parseJSON         return LoggerTree{..} +-- | Zooming into logger name with putting specific key.+zoomLogger :: Text -> State LoggerTree () -> State LoggerTree ()+zoomLogger loggerName initializer = zoom (ltSubloggers.at loggerName) $ do+    put $ Just mempty+    zoom _Just initializer+ ---------------------------------------------------------------------------- -- Logger rotattion ----------------------------------------------------------------------------@@ -90,6 +143,7 @@         rpKeepFiles <- o .: "keepFiles"         return RotationParameters{..} +-- | Checks if logger rotation parameters are valid. isValidRotation :: RotationParameters -> Bool isValidRotation RotationParameters{..} = rpLogLimit > 0 && rpKeepFiles > 0 @@ -99,20 +153,97 @@  -- | Logger configuration which keeps 'RotationParameters' and 'LoggerTree'. data LoggerConfig = LoggerConfig-    { lcRotation :: !(Maybe RotationParameters)-    , lcTree     :: !LoggerTree-    } deriving (Generic, Show)+    { -- | Rotation parameters for logger config. See 'System.Wlog.Roller'.+      _lcRotation      :: Maybe RotationParameters -instance Default LoggerConfig where-    def = LoggerConfig-          { lcRotation = Nothing-          , lcTree     = def-          }+      -- | Severity for terminal output. If @Nothing@ then 'Warning' is used.+    , _lcTermSeverity  :: Maybe Severity +      -- | Show time for non-error messages.+      -- Note that error messages always have timestamp.+    , _lcShowTime      :: Any++      -- | @True@ if we should also print output into console.+    , _lcConsoleOutput :: Any++      -- | Defines how to transform logger names in config.+    , _lcMapper        :: Endo LoggerName++      -- | Path prefix to add for each logger file+    , _lcFilePrefix    :: Maybe FilePath++      -- | Hierarchical tree of loggers.+    , _lcTree          :: LoggerTree+    }++makeLenses ''LoggerConfig++-- TODO: QuickCheck tests on monoid laws+instance Monoid LoggerConfig where+    mempty = LoggerConfig+        { _lcRotation      = Nothing+        , _lcTermSeverity  = Nothing+        , _lcShowTime      = mempty+        , _lcConsoleOutput = mempty+        , _lcMapper        = mempty+        , _lcFilePrefix    = mempty+        , _lcTree          = mempty+        }++    lc1 `mappend` lc2 = LoggerConfig+        { _lcRotation      = _lcRotation      lc1 <|> _lcRotation      lc2+        , _lcTermSeverity  = _lcTermSeverity  lc1 <|> _lcTermSeverity  lc2+        , _lcShowTime      = _lcShowTime      lc1  <> _lcShowTime      lc2+        , _lcConsoleOutput = _lcConsoleOutput lc1  <> _lcConsoleOutput lc2+        , _lcMapper        = _lcMapper        lc1  <> _lcMapper        lc2+        , _lcFilePrefix    = _lcFilePrefix    lc1 <|> _lcFilePrefix    lc2+        , _lcTree          = _lcTree          lc1  <> _lcTree          lc2+        }++topLevelParams :: [Text]+topLevelParams = ["rotation", "showTime", "printOutput", "filePrefix"]+ instance FromJSON LoggerConfig where     parseJSON = withObject "rotation params" $ \o -> do-        lcRotation <- o .:? "rotation"-        lcTree     <- parseJSON $ Object $ filterObject ["rotation"] o+        _lcRotation      <-         o .:? "rotation"+        _lcTermSeverity  <-         o .:? "termSeverity"+        _lcShowTime      <- Any <$> o .:? "showTime"    .!= False+        _lcConsoleOutput <- Any <$> o .:? "printOutput" .!= False+        _lcFilePrefix    <-         o .:? "filePrefix"+        _lcTree          <- parseJSON $ Object $ filterObject topLevelParams o+        let _lcMapper     = mempty         return LoggerConfig{..} -instance ToJSON LoggerConfig+-- | This instances violates @fromJSON . toJSON = identity@ rule but doesn't matter+-- because it is used only for debugging.+instance ToJSON LoggerConfig where+    toJSON LoggerConfig{..} = object+            [ "rotation"     .= _lcRotation+            , "termSeverity" .= _lcTermSeverity+            , "showTime"     .= getAny _lcShowTime+            , "printOutput"  .= getAny _lcConsoleOutput+            , "filePrefix"   .= _lcFilePrefix+            , ("logTree", toJSON _lcTree)+            ]++-- Builders for 'LoggerConfig'.++-- | Setup 'lcShowTime' inside 'LoggerConfig'.+showTimeB :: Bool -> LoggerConfig+showTimeB isShowTime = mempty { _lcShowTime = Any isShowTime }++-- | Setup 'lcConsoleOutput' inside 'LoggerConfig'.+consoleOutB :: Bool -> LoggerConfig+consoleOutB printToConsole = mempty { _lcConsoleOutput = Any printToConsole }++-- | Adds sensible predefined set of parameters to logger.+productionB :: LoggerConfig+productionB = showTimeB True <> consoleOutB True++-- | Setup 'lcMapper' inside 'LoggerConfig'.+mapperB :: (LoggerName -> LoggerName) -> LoggerConfig+mapperB loggerNameMapper = mempty { _lcMapper = Endo loggerNameMapper }++-- | Setup 'lcFilePrefix' inside 'LoggerConfig'.+prefixB :: FilePath -> LoggerConfig+prefixB filePrefix = mempty { _lcFilePrefix = Just filePrefix }
src/System/Wlog/Wrapper.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE ViewPatterns      #-}+ -- | -- Module      : System.Wlog.Wrapper -- Copyright   : (c) Serokell, 2016@@ -11,19 +14,17 @@ -- which allows to keep logger name in monadic context.  module System.Wlog.Wrapper-       ( LoggingFormat (..)-       , Severity (..)+       ( Severity (..)        , convertSeverity-       , initLogging-       , initLoggingWith+       , initTerminalLogging        , releaseAllHandlers        , setSeverity        , setSeverityMaybe        ) where +import           Universum+ import           Control.Concurrent.MVar   (MVar, newMVar, withMVar)-import           Control.Monad.Trans       (MonadIO (liftIO))-import           Data.Default              (Default (def)) import           System.IO                 (Handle, stderr, stdout) import           System.Log.Handler.Simple (GenericHandler (..), streamHandler) import           System.Log.Logger         (Priority (DEBUG, ERROR), clearLevel,@@ -34,17 +35,6 @@ import           System.Wlog.LoggerName    (LoggerName (..)) import           System.Wlog.Severity      (Severity (..), convertSeverity) ---- | Options determining formatting of messages.-data LoggingFormat = LoggingFormat-    { -- | Show time for non-error messages.-      -- Note that error messages always have timestamp.-      lfShowTime :: !Bool-    } deriving (Show)--instance Default LoggingFormat where-    def = LoggingFormat {lfShowTime = True}- -- | Like `streamHandler`, but syncronized using given `MVar` as lock -- (it should be filled before this function call). streamHandlerWithLock :: MVar () -> Handle -> Priority -> IO (GenericHandler Handle)@@ -58,10 +48,13 @@         , closeFunc = closeFunc         } --- | This function initializes global logging system. At high level, it sets--- severity which will be used by all loggers by default, sets default--- formatters and sets custom severity for given loggers (if any).+-- | This function initializes global logging system for terminal output.+-- At high level, it sets severity which will be used by all loggers by default,+-- sets default formatters and sets custom severity for given loggers (if any). --+-- NOTE: you probably don't want to use this function.+-- Consider 'System.Wlog.Launcher.setupLogging'.+-- -- On a lower level it does the following: -- 1. Removes default handler from root logger, sets two handlers such that: -- 1.1. All messages are printed to /stdout/.@@ -71,14 +64,12 @@ -- descendant loggers by default. -- 3. Applies `setSeverity` to given loggers. It can be done later using -- `setSeverity` directly.-initLoggingWith-    :: MonadIO m-    => LoggingFormat -> Severity -> m ()-initLoggingWith LoggingFormat {..} defaultSeverity = liftIO $ do+initTerminalLogging :: MonadIO m => Bool -> Maybe Severity -> m ()+initTerminalLogging isShowTime (fromMaybe Warning -> defaultSeverity) = liftIO $ do     lock <- liftIO $ newMVar ()     -- We set DEBUG here, to allow all messages by stdout handler.     -- They will be filtered by loggers.-    stdoutHandler <- setStdoutFormatter lfShowTime <$>+    stdoutHandler <- setStdoutFormatter isShowTime <$>         streamHandlerWithLock lock stdout DEBUG     stderrHandler <- setStderrFormatter <$>         streamHandlerWithLock lock stderr ERROR@@ -86,10 +77,6 @@         setHandlers [stderrHandler, stdoutHandler]     updateGlobalLogger rootLoggerName $         setLevel (convertSeverity defaultSeverity)---- | Version of initLoggingWith without any predefined loggers.-initLogging :: MonadIO m => Severity -> m ()-initLogging = initLoggingWith def  -- | Set severity for given logger. By default parent's severity is used. setSeverity :: MonadIO m => LoggerName -> Severity -> m ()
test/Test/Wlog/RollingSpec.hs view
@@ -7,8 +7,7 @@ import           Universum  import           Control.Concurrent.Async (mapConcurrently)-import           Data.Default             (def)-import qualified Data.HashMap.Strict      as HM (fromList)+import           Control.Lens             (zoom, (.=), (?=)) import qualified Prelude                  (read) import           System.Directory         (doesFileExist, removeFile) import           System.FilePath          (takeExtension)@@ -21,11 +20,12 @@ import           Test.QuickCheck.Monadic  (PropertyM, monadicIO, run)  import           System.Wlog              (InvalidRotation (..), LoggerConfig (..),-                                           LoggerTree (..), RotationParameters (..),-                                           Severity (..), isValidRotation, logDebug,-                                           logIndex, releaseAllHandlers,-                                           rotationFileHandler, traverseLoggerConfig,-                                           usingLoggerName, whenExist)+                                           RotationParameters (..), Severity (..),+                                           fromScratch, isValidRotation, lcFilePrefix,+                                           lcRotation, lcTree, logDebug, logIndex, ltFile,+                                           ltSeverity, releaseAllHandlers,+                                           rotationFileHandler, setupLogging,+                                           usingLoggerName, whenExist, zoomLogger)  spec :: Spec spec = do@@ -67,11 +67,13 @@ testLogFile = "patak.log"  testLoggerConfig :: RotationParameters -> LoggerConfig-testLoggerConfig (Just -> lcRotation) = LoggerConfig{..}-  where-    lcTree = def { ltSeverity   = Just Debug-                 , ltSubloggers = HM.fromList [("test", def { ltFile = Just testLogFile })]-                 }+testLoggerConfig (Just -> rotParam ) = fromScratch $ do+    lcRotation   .= rotParam+    lcFilePrefix ?= "logs"+    zoom lcTree $ do+        ltSeverity ?= Debug+        zoomLogger "test" $+            ltFile ?= testLogFile  logThreadsNum :: Word logThreadsNum = 4@@ -80,7 +82,7 @@ writeConcurrentLogs :: RotationParameters -> LinesToLog -> IO () writeConcurrentLogs rp@RotationParameters{..} (getNumberOfLinesToLog -> linesNum) =     bracket_-        (traverseLoggerConfig identity lcRotation lcTree $ Just "logs")+        (setupLogging $ testLoggerConfig rp)         releaseAllHandlers         concurrentWriting   where@@ -96,6 +98,7 @@ -- * it works when multiple threads trying to write to log file -- * size of each file is not very big -- * number of files is not bigger than allowed+-- * at least one (root) file with logs is created -- TODO: more properties? verifyLoggerRotation :: RotationParameters -> LinesToLog -> Property verifyLoggerRotation rp@RotationParameters{..} linesNum = isValidRotation rp ==> monadicIO $ do