packages feed

porcupine-core-0.1.0.0: src/System/TaskPipeline/Logger.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

module System.TaskPipeline.Logger
  ( LoggerScribeParams(..)
  , LoggerFormat(..)
  , Severity(..)
  , Verbosity(..)
  , maxVerbosityLoggerScribeParams
  , warningsAndErrorsLoggerScribeParams
  , log
  , runLogger
  ) where

import           Control.Exception.Safe
import           Control.Monad.IO.Class   (MonadIO, liftIO)
import           Data.Aeson
import           Data.Aeson.Encode.Pretty (encodePrettyToTextBuilder)
import           Data.Aeson.Text          (encodeToTextBuilder)
import qualified Data.HashMap.Strict      as HM
import           Data.String
import           Data.Text                (Text)
import           Data.Text.Lazy.Builder   hiding (fromString)
import           Katip
import           Katip.Core
import           System.IO                (stdout)


-- | Switch between the different type of formatters for the log
data LoggerFormat
  = PrettyLog  -- ^ Just shows the log messages, colored, with namespace and
               -- pretty-prints . For human consumption.
  | CompactLog  -- ^ Like pretty, but prints JSON context just on one line
  | JSONLog  -- ^ JSON-formatted log, from katip
  | BracketLog  -- ^ Regular bracket log, from katip
  deriving (Eq, Show)

-- | Scribe parameters for Logger. Define a severity threshold and a verbosity level.
data LoggerScribeParams = LoggerScribeParams
  { loggerSeverityThreshold :: Severity
  , loggerVerbosity         :: Verbosity
  , loggerFormat            :: LoggerFormat
  }
  deriving (Eq, Show)

-- | Show log message from Debug level, with V2 verbosity.
maxVerbosityLoggerScribeParams :: LoggerScribeParams
maxVerbosityLoggerScribeParams = LoggerScribeParams DebugS V2 PrettyLog

-- | Show log message from Warning level, with V2 verbosity and one-line logs.
warningsAndErrorsLoggerScribeParams :: LoggerScribeParams
warningsAndErrorsLoggerScribeParams = LoggerScribeParams WarningS V2 CompactLog

-- | Starts a logger.
runLogger
  :: (MonadMask m, MonadIO m)
  => String
  -> LoggerScribeParams
  -> KatipContextT m a
  -> m a
runLogger progName (LoggerScribeParams sev verb logFmt) x = do
    let logFmt' :: LogItem t => ItemFormatter t
        logFmt' = case logFmt of
          PrettyLog  -> prettyFormat True
          CompactLog -> prettyFormat False
          BracketLog -> bracketFormat
          JSONLog    -> jsonFormat
    handleScribe <- liftIO $
      mkHandleScribeWithFormatter logFmt' ColorIfTerminal stdout (permitItem sev) verb
    let mkLogEnv = liftIO $
          registerScribe "stdout" handleScribe defaultScribeSettings
          =<< initLogEnv (fromString progName) "devel"
    bracket mkLogEnv (liftIO . closeScribes) $ \le ->
        runKatipContextT le () "main" $ x

-- | Doesn't log time, host, file location etc. Colors the whole message and
-- displays context AFTER the message.
prettyFormat :: LogItem a => Bool -> ItemFormatter a
prettyFormat usePrettyJSON withColor verb Item{..} =
    colorize withColor "40" (mconcat $ map fromText $ intercalateNs _itemNamespace) <>
    fromText " " <>
    colorBySeverity' withColor _itemSeverity (mbSeverity <> unLogStr _itemMessage) <>
    colorize withColor "2" ctx
  where
    ctx = case toJSON $ payloadObject verb _itemPayload of
      Object hm | HM.null hm -> mempty
      c -> if usePrettyJSON
        then fromText "\n" <> encodePrettyToTextBuilder c
        else fromText " " <> encodeToTextBuilder c
    -- We display severity levels not distinguished by color
    mbSeverity = case _itemSeverity of
      CriticalS  -> fromText "[CRITICAL] "
      AlertS     -> fromText "[ALERT] "
      EmergencyS -> fromText "[EMERGENCY] "
      _          -> mempty

-- | Like 'colorBySeverity' from katip, but works on builders
colorBySeverity' :: Bool -> Severity -> Builder -> Builder
colorBySeverity' withColor severity msg = case severity of
  EmergencyS -> red msg
  AlertS     -> red msg
  CriticalS  -> red msg
  ErrorS     -> red msg
  WarningS   -> yellow msg
  NoticeS    -> bold msg
  DebugS     -> grey msg
  _          -> msg
  where
    bold = colorize withColor "1"
    red = colorize withColor "31"
    yellow = colorize withColor "33"
    grey = colorize withColor "2"

colorize :: Bool -> Text -> Builder -> Builder
colorize withColor c s
  | withColor = fromText "\ESC[" <> fromText c <> fromText "m" <> s <> fromText "\ESC[0m"
  | otherwise = s