log4hs-0.7.1.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.Lens (set, view)
import Control.Monad (when)
import Data.Generics.Product.Typed
import Data.Typeable
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