{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
module Knit.Effect.Logger
(
LogSeverity(..)
, LogEntry(..)
, Logger(..)
, PrefixLog
, log
, logLE
, wrapPrefix
, filteredLogEntriesToIO
, logAll
, nonDiagnostic
, LogWithPrefixes
, LogWithPrefixesLE
, Sem
, Member
, Handler
)
where
import qualified Polysemy as P
import Polysemy ( Member
, Sem
)
import Polysemy.Internal ( send )
import qualified Polysemy.State as P
import Control.Monad ( when )
import Control.Monad.IO.Class ( MonadIO(..) )
import Control.Monad.Log ( Handler )
import qualified Control.Monad.Log as ML
import qualified Data.List as List
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Text.Prettyprint.Doc as PP
import qualified Data.Text.Prettyprint.Doc.Render.Text
as PP
import Prelude hiding ( log )
data LogSeverity = Diagnostic | Info | Warning | Error deriving (Show, Eq, Ord, Enum, Bounded)
logSeverityToSeverity :: LogSeverity -> ML.Severity
logSeverityToSeverity Diagnostic = ML.Debug
logSeverityToSeverity Info = ML.Informational
logSeverityToSeverity Warning = ML.Warning
logSeverityToSeverity Error = ML.Error
data LogEntry = LogEntry { severity :: LogSeverity, message :: T.Text }
logEntryToWithSeverity :: LogEntry -> ML.WithSeverity T.Text
logEntryToWithSeverity (LogEntry s t) =
ML.WithSeverity (logSeverityToSeverity s) t
logAll :: [LogSeverity]
logAll = [minBound .. maxBound]
nonDiagnostic :: [LogSeverity]
nonDiagnostic = List.tail logAll
data Logger a m r where
Log :: a -> Logger a m ()
log :: P.Member (Logger a) effs => a -> P.Sem effs ()
log = send . Log
logLE
:: P.Member (Logger LogEntry) effs => LogSeverity -> T.Text -> P.Sem effs ()
logLE ls lm = log (LogEntry ls lm)
logWithHandler
:: Handler (P.Sem effs) a -> P.Sem (Logger a ': effs) x -> P.Sem effs x
logWithHandler handler = P.interpret (\(Log a) -> handler a)
data PrefixLog m r where
AddPrefix :: T.Text -> PrefixLog m ()
RemovePrefix :: PrefixLog m ()
GetPrefix :: PrefixLog m T.Text
addPrefix :: P.Member PrefixLog effs => T.Text -> P.Sem effs ()
addPrefix = send . AddPrefix
removePrefix :: P.Member PrefixLog effs => P.Sem effs ()
removePrefix = send RemovePrefix
getPrefix :: P.Member PrefixLog effs => P.Sem effs T.Text
getPrefix = send $ GetPrefix
wrapPrefix :: P.Member PrefixLog effs => T.Text -> P.Sem effs a -> P.Sem effs a
wrapPrefix p l = do
addPrefix p
res <- l
removePrefix
return res
prefixInState
:: forall effs a
. P.Sem (PrefixLog ': effs) a
-> P.Sem (P.State [T.Text] ': effs) a
prefixInState = P.reinterpret $ \case
AddPrefix t -> P.modify (t :)
RemovePrefix -> P.modify @[T.Text] tail
GetPrefix -> fmap (T.intercalate "." . List.reverse) P.get
runPrefix :: P.Sem (PrefixLog ': effs) a -> P.Sem effs a
runPrefix = fmap snd . P.runState [] . prefixInState
data WithPrefix a = WithPrefix { msgPrefix :: T.Text, discardPrefix :: a }
renderWithPrefix :: (a -> PP.Doc ann) -> WithPrefix a -> PP.Doc ann
renderWithPrefix k (WithPrefix pr a) = PP.pretty pr PP.<+> PP.align (k a)
logPrefixed
:: P.Member PrefixLog effs
=> P.Sem (Logger a ': effs) x
-> P.Sem (Logger (WithPrefix a) ': effs) x
logPrefixed =
P.reinterpret (\(Log a) -> getPrefix >>= (\p -> log (WithPrefix p a)))
logAndHandlePrefixed
:: forall effs a x
. Handler (P.Sem effs) (WithPrefix a)
-> P.Sem (Logger a ': (PrefixLog ': effs)) x
-> P.Sem effs x
logAndHandlePrefixed handler =
runPrefix
. logWithHandler (P.raise . handler)
. logPrefixed @(PrefixLog ': effs)
filterLog
:: Monad m
=> ([LogSeverity] -> a -> Bool)
-> [LogSeverity]
-> Handler m a
-> Handler m a
filterLog filterF lss h a = when (filterF lss a) $ h a
logToIO :: MonadIO m => (a -> T.Text) -> Handler m a
logToIO toText = liftIO . T.putStrLn . toText
prefixedLogEntryToIO :: MonadIO m => Handler m (WithPrefix LogEntry)
prefixedLogEntryToIO = logToIO
(PP.renderStrict . PP.layoutPretty PP.defaultLayoutOptions . renderWithPrefix
(ML.renderWithSeverity PP.pretty . logEntryToWithSeverity)
)
filteredLogEntriesToIO
:: MonadIO (P.Sem effs)
=> [LogSeverity]
-> P.Sem (Logger LogEntry ': (PrefixLog ': effs)) x
-> P.Sem effs x
filteredLogEntriesToIO lss = logAndHandlePrefixed
(filterLog f lss $ prefixedLogEntryToIO)
where f lss' a = (severity $ discardPrefix a) `List.elem` lss'
type LogWithPrefixes a effs = (P.Member PrefixLog effs, P.Member (Logger a) effs)
type LogWithPrefixesLE effs = LogWithPrefixes LogEntry effs