{-# 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