module System.Logger.Backend.Handle
(
LoggerHandleConfig(..)
, loggerHandleConfigText
, readLoggerHandleConfig
, validateLoggerHandleConfig
, pLoggerHandleConfig
, pLoggerHandleConfig_
, HandleBackendConfig(..)
, handleBackendConfigHandle
, handleBackendConfigColor
, defaultHandleBackendConfig
, validateHandleBackendConfig
, pHandleBackendConfig
, pHandleBackendConfig_
, withHandleBackend
, withHandleBackend_
, handleBackend
, handleBackend_
) where
import Configuration.Utils hiding (Lens', Error)
import Configuration.Utils.Validation
import Control.DeepSeq
import Control.Lens hiding ((.=))
import Control.Monad.Except
import Control.Monad.Trans.Control
import Control.Monad.Writer
import qualified Data.CaseInsensitive as CI
import qualified Data.List as L
import Data.Monoid.Unicode
import Data.String
import qualified Data.Text as T
import Data.Text.Lens
import qualified Data.Text.IO as T
import Data.Typeable
import GHC.Generics
import qualified Options.Applicative as O
import Prelude.Unicode
import qualified System.Console.ANSI as A
import System.IO
import System.Logger.Backend.ColorOption
import System.Logger.Internal
import System.Logger.Types
data LoggerHandleConfig
= StdOut
| StdErr
| FileHandle FilePath
deriving (Show, Read, Eq, Ord, Typeable, Generic)
instance NFData LoggerHandleConfig
readLoggerHandleConfig
∷ (MonadError e m, Eq a, Show a, CI.FoldCase a, IsText a, IsString e, Monoid e)
⇒ a
→ m LoggerHandleConfig
readLoggerHandleConfig x = case CI.mk tx of
"stdout" → return StdOut
"stderr" → return StdErr
_ | CI.mk (L.take 5 tx) ≡ "file:" → return $ FileHandle (L.drop 5 tx)
e → throwError $ "unexpected logger handle value: "
⊕ fromString (show e)
⊕ ", expected \"stdout\", \"stderr\", or \"file:<FILENAME>\""
where
tx = packed # x
loggerHandleConfigText
∷ (IsString a, Monoid a)
⇒ LoggerHandleConfig
→ a
loggerHandleConfigText StdOut = "stdout"
loggerHandleConfigText StdErr = "stderr"
loggerHandleConfigText (FileHandle f) = "file:" ⊕ fromString f
validateLoggerHandleConfig ∷ ConfigValidation LoggerHandleConfig λ
validateLoggerHandleConfig (FileHandle filepath) = validateFileWritable "file handle" filepath
validateLoggerHandleConfig _ = return ()
instance ToJSON LoggerHandleConfig where
toJSON = String ∘ loggerHandleConfigText
instance FromJSON LoggerHandleConfig where
parseJSON = withText "LoggerHandleConfig" $ either fail return ∘ readLoggerHandleConfig
pLoggerHandleConfig ∷ O.Parser LoggerHandleConfig
pLoggerHandleConfig = pLoggerHandleConfig_ ""
pLoggerHandleConfig_
∷ T.Text
→ O.Parser LoggerHandleConfig
pLoggerHandleConfig_ prefix = option (eitherReader readLoggerHandleConfig)
× long (T.unpack prefix ⊕ "logger-backend-handle")
⊕ metavar "stdout|stderr|file:<FILENAME>"
⊕ help "handle where the logs are written"
data HandleBackendConfig = HandleBackendConfig
{ _handleBackendConfigColor ∷ !ColorOption
, _handleBackendConfigHandle ∷ !LoggerHandleConfig
}
deriving (Show, Read, Eq, Ord, Typeable, Generic)
handleBackendConfigColor ∷ Lens' HandleBackendConfig ColorOption
handleBackendConfigColor = lens _handleBackendConfigColor $ \a b → a { _handleBackendConfigColor = b }
handleBackendConfigHandle ∷ Lens' HandleBackendConfig LoggerHandleConfig
handleBackendConfigHandle = lens _handleBackendConfigHandle $ \a b → a { _handleBackendConfigHandle = b }
instance NFData HandleBackendConfig
defaultHandleBackendConfig ∷ HandleBackendConfig
defaultHandleBackendConfig = HandleBackendConfig
{ _handleBackendConfigColor = defaultColorOption
, _handleBackendConfigHandle = StdOut
}
validateHandleBackendConfig ∷ ConfigValidation HandleBackendConfig []
validateHandleBackendConfig HandleBackendConfig{..} = do
validateLoggerHandleConfig _handleBackendConfigHandle
case (_handleBackendConfigHandle, _handleBackendConfigColor) of
(FileHandle _, ColorTrue) →
tell ["log messages are formatted using ANSI color escape codes but are written to a file"]
_ → return ()
instance ToJSON HandleBackendConfig where
toJSON HandleBackendConfig{..} = object
[ "color" .= _handleBackendConfigColor
, "handle" .= _handleBackendConfigHandle
]
instance FromJSON (HandleBackendConfig → HandleBackendConfig) where
parseJSON = withObject "HandleBackendConfig" $ \o → id
<$< handleBackendConfigColor ..: "color" × o
<*< handleBackendConfigHandle ..: "handle" × o
pHandleBackendConfig ∷ MParser HandleBackendConfig
pHandleBackendConfig = pHandleBackendConfig_ ""
pHandleBackendConfig_
∷ T.Text
→ MParser HandleBackendConfig
pHandleBackendConfig_ prefix = id
<$< handleBackendConfigColor .:: pColorOption_ prefix
<*< handleBackendConfigHandle .:: pLoggerHandleConfig_ prefix
withHandleBackend
∷ (MonadIO m, MonadBaseControl IO m)
⇒ HandleBackendConfig
→ (LoggerBackend T.Text → m α)
→ m α
withHandleBackend = withHandleBackend_ id
withHandleBackend_
∷ (MonadIO m, MonadBaseControl IO m)
⇒ (msg → T.Text)
→ HandleBackendConfig
→ (LoggerBackend msg → m α)
→ m α
withHandleBackend_ format conf inner =
case conf ^. handleBackendConfigHandle of
StdErr → run stderr
StdOut → run stdout
FileHandle f → liftBaseOp (withFile f AppendMode) run
where
run h = do
colored ← liftIO $ useColor (conf ^. handleBackendConfigColor) h
inner $ handleBackend_ format h colored
handleBackend
∷ Handle
→ Bool
→ LoggerBackend T.Text
handleBackend = handleBackend_ id
handleBackend_
∷ (msg → T.Text)
→ Handle
→ Bool
→ LoggerBackend msg
handleBackend_ format h colored eitherMsg = do
T.hPutStrLn h
$ formatIso8601Milli (msg ^. logMsgTime) ⊕ " "
⊕ inLevelColor colored ("[" ⊕ sshow level ⊕ "] ")
⊕ inScopeColor colored ("[" ⊕ formatedScope ⊕ "] ")
⊕ (msg ^. logMsg)
where
msg = either id (logMsg %~ format) eitherMsg
level = msg ^. logMsgLevel
formatedScope = T.intercalate "|" ∘ L.map formatLabel ∘ reverse $ msg ^. logMsgScope
formatLabel (key, val) = key ⊕ "=" ⊕ val
inScopeColor True = inBlue
inScopeColor False = id
inLevelColor True = case level of
Error → inRed
Warn → inOrange
Info → inGreen
_ → id
inLevelColor False = id
inColor ∷ A.ColorIntensity → A.Color → T.Text → T.Text
inColor i c t = T.pack (A.setSGRCode [A.SetColor A.Foreground i c]) ⊕ t ⊕ T.pack (A.setSGRCode [A.Reset])
inRed ∷ T.Text → T.Text
inRed = inColor A.Vivid A.Red
inOrange ∷ T.Text → T.Text
inOrange = inColor A.Dull A.Red
inGreen ∷ T.Text → T.Text
inGreen = inColor A.Dull A.Green
inBlue ∷ T.Text → T.Text
inBlue = inColor A.Dull A.Blue