log4hs-0.9.0.0: src/Logging/Class/Handler.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeApplications #-}
module Logging.Class.Handler ( SomeHandler(..), Handler(..) ) where
import Control.Exception (SomeException, catch)
import Control.Monad (when)
import Data.Generics.Product.Typed
import Data.Typeable
import Lens.Micro (set)
import Lens.Micro.Extras (view)
import Prelude hiding (filter)
import Text.Format
import Logging.Class.Filterable
import Logging.Filter
import Logging.Level
import Logging.Record
-- | Generalised 'Handler' instance, it wraps all other 'Handler' instances
-- into one type.
--
-- The 'SomeHandler' type is the root of the handler type hierarchy.
data SomeHandler where
SomeHandler :: Handler h => h -> SomeHandler
instance {-# OVERLAPPING #-} HasType Level SomeHandler where
getTyped (SomeHandler h) = view (typed @Level) h
setTyped v (SomeHandler h) = SomeHandler $ set (typed @Level) v h
instance {-# OVERLAPPING #-} HasType Filterer SomeHandler where
getTyped (SomeHandler h) = view (typed @Filterer) h
setTyped v (SomeHandler h) = SomeHandler $ set (typed @Filterer) v h
instance {-# OVERLAPPING #-} HasType Format1 SomeHandler where
getTyped (SomeHandler h) = view (typed @Format1) h
setTyped v (SomeHandler h) = SomeHandler $ set (typed @Format1) v h
instance Eq SomeHandler where
(SomeHandler h1) == h2 = Just h1 == fromHandler h2
instance Handler SomeHandler where
open (SomeHandler h) = open h
emit (SomeHandler h) = emit h
close (SomeHandler h) = close h
handle (SomeHandler h) = handle h
fromHandler = Just . id
toHandler = id
-- |A type class that abstracts the characteristics of a 'Handler'
class ( HasType Level a
, HasType Filterer a
, HasType Format1 a
, Typeable a
, Eq a
) => Handler a where
-- | Initialize the 'Handler' instance
open :: a -> IO ()
open _ = return ()
-- | Emit log event, prepare log data, and send to bancked.
--
-- e.g. 1) Format 'LogRecord' into data in specific format (json, html, etc.),
-- 2) write the data to a file or send the data to a server.
emit :: a -> LogRecord -> IO ()
-- | Terminate the 'Handler' instance
close :: a -> IO ()
close _ = return ()
-- | Handle 'LogRecord' and decide whether to call 'emit'.
--
-- The default implementation is to filter 'LogRecord' by level and
-- "Handler"'s filterer, if rejected, do nothing and return False,
-- otherwise call emit and return True.
--
-- Note: You can override the default implementation.
handle :: a -> LogRecord -> IO Bool
handle hdl rcd@LogRecord{level=level', message=message}
| level' < view (typed @Level) hdl = return False
| not (filter (view (typed @Filterer) hdl) rcd) = return False
| otherwise = catch (emit hdl rcd >> return True) handleError
where
-- TODO How to test
handleError :: SomeException -> IO Bool
handleError e = do
putStrLn "--- Logging error ---"
putStrLn $ show e
putStrLn $ "Message: " ++ message
return False
fromHandler :: SomeHandler -> Maybe a
fromHandler (SomeHandler h) = cast h
toHandler :: a -> SomeHandler
toHandler = SomeHandler