-- | Basic logging, copied from log-warper (it has too many -- dependencies). module OrgStat.Logging ( Severity (..) , initLogging , logDebug , logInfo , logNotice , logWarning , logError , logMessage ) where import Universum import Control.Concurrent.MVar (modifyMVar_, withMVar) import qualified Data.Text as T import Data.Time.Clock (UTCTime (..), getCurrentTime) import Fmt (fmt, padRightF, (+|), (|+), (|++|)) import Fmt.Time (dateDashF, hmsF, subsecondF, tzNameF) import System.Console.ANSI (Color (Blue, Green, Magenta, Red, Yellow), ColorIntensity (Vivid), ConsoleLayer (Foreground), SGR (Reset, SetColor), setSGRCode) import System.IO.Unsafe (unsafePerformIO) -- | Severity is level of log message importance. It uniquely -- determines which messages to print. data Severity = Debug -- ^ Debug messages | Info -- ^ Information | Notice -- ^ Important (more than average) information | Warning -- ^ General warnings | Error -- ^ General errors/severe errors deriving (Eq, Ord, Show) -- | Internal information about logging. data LogInternalState = LogInternalState { lisMinSeverity :: Severity } deriving Show -- | Internal logging state. {-# NOINLINE loggingState #-} loggingState :: MVar LogInternalState loggingState = unsafePerformIO $ newMVar $ LogInternalState Debug -- | Initialise logging state. initLogging :: Severity -> IO () initLogging sev = modifyMVar_ loggingState $ const $ pure $ LogInternalState sev -- | Colorizes "Text". colorizer :: Severity -> Text -> Text colorizer pr s = let (before, after) = table pr in toText before <> s <> toText after where -- | Defines pre- and post-printed characters for printing colorized text. table :: Severity -> (String, String) table severity = case severity of Error -> (setColor Red , reset) Debug -> (setColor Green , reset) Notice -> (setColor Magenta , reset) Warning -> (setColor Yellow , reset) Info -> (setColor Blue , reset) where setColor color = setSGRCode [SetColor Foreground Vivid color] reset = setSGRCode [Reset] -- | Formats UTC time in next format: "%Y-%m-%d %H:%M:%S%Q %Z" -- but %Q part show only in centiseconds (always 2 digits). centiUtcTimeF :: UTCTime -> Text centiUtcTimeF t = dateDashF t |+ " " +| hmsF t |++| centiSecondF t |+ " " +| tzNameF t |+ "" where centiSecondF = padRightF 3 '0' . T.take 3 . fmt . subsecondF -- | Logs message with specified severity using logger name in context. logMessage :: (MonadIO m) => Severity -> Text -> m () logMessage severity msg = liftIO $ withMVar loggingState $ \LogInternalState{..} -> do time <- liftIO $ getCurrentTime let text = format time when (severity >= lisMinSeverity) $ putStrLn text where format time = mconcat [ colorizer severity $ "[" <> show severity <> "]" , " [" , centiUtcTimeF time , "] " , msg ] -- | Shortcuts for 'logMessage' to use according severity. logDebug, logInfo, logNotice, logWarning, logError :: MonadIO m => Text -> m () logDebug = logMessage Debug logInfo = logMessage Info logNotice = logMessage Notice logWarning = logMessage Warning logError = logMessage Error