elynx-tools-0.3.0: src/ELynx/Tools/Logger.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
-- |
-- Module : ELynx.Tools.Logger
-- Description : Monad logger utility functions
-- Copyright : (c) Dominik Schrempf 2020
-- License : GPL-3.0-or-later
--
-- Maintainer : dominik.schrempf@gmail.com
-- Stability : unstable
-- Portability : portable
--
-- Creation date: Fri Sep 6 14:43:19 2019.
module ELynx.Tools.Logger
( -- * Logger
logNewSection,
eLynxWrapper,
)
where
import Control.Monad.Base (liftBase)
import Control.Monad.IO.Class
( MonadIO,
liftIO,
)
import Control.Monad.Logger
( Loc,
LogLevel,
LogSource,
LoggingT,
MonadLogger,
filterLogger,
logInfo,
runLoggingT,
)
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Monad.Trans.Reader (ReaderT (runReaderT))
import qualified Data.ByteString.Char8 as B
import Data.Text
( Text,
pack,
)
import ELynx.Tools.InputOutput (openFile')
import ELynx.Tools.Reproduction
( Arguments (..),
ELynx,
Force,
GlobalArguments (..),
Reproducible (..),
Seed (..),
ToJSON,
logFooter,
logHeader,
toLogLevel,
writeReproduction,
)
import System.IO
( BufferMode (LineBuffering),
Handle,
IOMode (WriteMode),
hClose,
hSetBuffering,
stderr,
)
import System.Log.FastLogger
( LogStr,
fromLogStr,
)
import System.Random.MWC
( createSystemRandom,
fromSeed,
save,
)
-- | Unified way of creating a new section in the log.
logNewSection :: MonadLogger m => Text -> m ()
logNewSection s = $(logInfo) $ "== " <> s
-- | The 'ReaderT' and 'LoggingT' wrapper for ELynx. Prints a header and a
-- footer, logs to 'stderr' if no file is provided. Initializes the seed if none
-- is provided. If a log file is provided, log to the file and to 'stderr'.
eLynxWrapper ::
forall a b.
(Eq a, Show a, Reproducible a, ToJSON a) =>
Arguments a ->
(Arguments a -> Arguments b) ->
ELynx b () ->
IO ()
eLynxWrapper args f worker = do
-- Arguments.
let gArgs = global args
lArgs = local args
let lvl = toLogLevel $ verbosity gArgs
rd = forceReanalysis gArgs
outBn = outFileBaseName gArgs
logFile = (++ ".log") <$> outBn
runELynxLoggingT lvl rd logFile $ do
-- Header.
h <- liftIO $ logHeader (cmdName @a) (cmdDsc @a)
$(logInfo) $ pack $ h ++ "\n"
-- Fix seed.
lArgs' <- case getSeed lArgs of
Nothing -> return lArgs
Just Random -> do
-- XXX: Have to go via a generator here, since creation of seed is not
-- supported.
g <- liftIO createSystemRandom
s <- liftIO $ fromSeed <$> save g
$(logInfo) $ pack $ "Seed: random; set to " <> show s <> "."
return $ setSeed lArgs s
Just (Fixed s) -> do
$(logInfo) $ pack $ "Seed: " <> show s <> "."
return lArgs
let args' = Arguments gArgs lArgs'
-- Run the worker with the fixed seed.
runReaderT worker $ f args'
-- Reproduction file.
case outBn of
Nothing ->
$(logInfo)
"No output file given --- skip writing ELynx file for reproducible runs."
Just bn -> do
$(logInfo) "Write ELynx reproduction file."
liftIO $ writeReproduction bn args'
-- Footer.
ftr <- liftIO logFooter
$(logInfo) $ pack ftr
runELynxLoggingT ::
(MonadBaseControl IO m, MonadIO m) =>
LogLevel ->
Force ->
Maybe FilePath ->
LoggingT m a ->
m a
runELynxLoggingT lvl _ Nothing =
runELynxStderrLoggingT . filterLogger (\_ l -> l >= lvl)
runELynxLoggingT lvl frc (Just fn) =
runELynxFileLoggingT frc fn . filterLogger (\_ l -> l >= lvl)
runELynxFileLoggingT ::
MonadBaseControl IO m => Force -> FilePath -> LoggingT m a -> m a
runELynxFileLoggingT frc fp logger = do
h <- liftBase $ openFile' frc fp WriteMode
liftBase (hSetBuffering h LineBuffering)
r <- runLoggingT logger (output2H stderr h)
liftBase (hClose h)
return r
runELynxStderrLoggingT :: MonadIO m => LoggingT m a -> m a
runELynxStderrLoggingT = (`runLoggingT` output stderr)
output :: Handle -> Loc -> LogSource -> LogLevel -> LogStr -> IO ()
output h _ _ _ msg = B.hPutStrLn h ls where ls = fromLogStr msg
output2H :: Handle -> Handle -> Loc -> LogSource -> LogLevel -> LogStr -> IO ()
output2H h1 h2 _ _ _ msg = do
B.hPutStrLn h1 ls
B.hPutStrLn h2 ls
where
ls = fromLogStr msg