{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} module LoggingSpec ( spec ) where import Control.Monad import Data.Default (def) import Data.List (intercalate) import qualified Data.Map as M import Data.Word import Prelude hiding (error) import System.IO import System.IO.Unsafe (unsafePerformIO) import System.Process (createPipe) import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck hiding (run) import qualified Test.QuickCheck.Monadic as Q import Text.Format import Logging spec :: Spec spec = basicSpec >> formatSpec >> filterSpec >> propagateSpec basicSpec :: Spec basicSpec = describe "Basic" $ modifyMaxSize (const 1000) $ do prop "log to root" $ \(PrintableString msg) -> runLog "" "DEBUG" msg "debug" (== msg) prop "log to Log4hs" $ \(PrintableString msg) -> runLog "Log4hs" "INFO" msg "debug" (== msg) prop "log to Log4hs.Json" $ \(PrintableString msg) -> runLog "Log4hs.Json" "WARN" msg "debug" (== msg) prop "disabled some" $ \(PrintableString msg) level -> runLog "Log10000hs" level msg "debug" (== "") prop "disabled all" $ \(PrintableString msg) loggers level -> let logger = intercalate "." $ [getPrintableString x | x <- loggers] in runDisabledLog logger level msg formatSpec :: Spec formatSpec = describe "Format" $ modifyMaxSize (const 1000) $ do prop "simple" $ \(PrintableString msg) -> runLog "" "DEBUG" msg "debug" (== msg) prop "logger" $ \(PrintableString msg) (Log4hsLogger logger) -> do runLog logger "WARN" msg "info" (== logger ++ ": " ++ msg) prop "level" $ \(PrintableString msg) (Log4hsLogger logger) fmtlevel -> let (Log4hsHighLevel level) = fmtlevel out = logger ++ " " ++ (show level) ++ "] " ++ msg in runLog logger level msg "error" (== out) filterSpec :: Spec filterSpec = describe "Filter" $ modifyMaxSize (const 1000) $ do prop "root sink && debug handler" $ \(PrintableString msg) level -> runLog "" level msg "debug" $ (== if level >= "DEBUG" then msg else "") prop "Log4hs sink && info handler" $ \(PrintableString msg) level -> let out = if level >= "INFO" then "Log4hs: " ++ msg else "" in runLog "Log4hs" level msg "info" $ (== out) prop "Log4hs.Json sink && error handler" $ \(PrintableString msg) level -> let out = if level >= "ERROR" then "Log4hs.Json " ++ (show level) ++ "] " ++ msg else "" in runLog "Log4hs.Json" level msg "error" $ (== out) prop "handler's filterer" $ \(PrintableString msg) (Small v)-> let logger = "Log4hs.V" ++ (show v) out = if v == (3 :: Word8) then logger ++ ": " ++ msg else "" in runLog logger "INFO" msg "infov3" (== out) propagateSpec :: Spec propagateSpec = describe "Propagate" $ modifyMaxSize (const 1000) $ do prop "to root (real)" $ \(PrintableString msg) -> runLog "Log8hs" "WARN" msg "debug" $ (== msg) prop "to root (virtual)" $ \(PrintableString msg) -> runLog "Log0hs" "WARN" msg "debug" $ (== msg) prop "to parent (real)" $ \(PrintableString msg) -> runLog "Log4hs.Json" "WARN" msg "info" $ (== "Log4hs.Json: " ++ msg) prop "to parent (virtual)" $ \(PrintableString msg) -> runLog "Log4hs.Ini" "WARN" msg "info" $ (== "Log4hs.Ini: " ++ msg) prop "to grandparent (real)" $ \(PrintableString msg) -> runLog "Log8hs.Json" "WARN" msg "debug" $ (== msg) prop "to grandparent (virtual)" $ \(PrintableString msg) -> runLog "Log0hs.Json" "WARN" msg "debug" $ (== msg) prop "disabled" $ \(PrintableString msg) -> runLog "Log4hs" "FATAL" msg "fatal" $ (== "") runLog :: Logger -> Level -> String -> String -> (String -> Bool) -> Property runLog logger level msg stream test = Q.monadicIO $ do out <- Q.run $ createManager False >>= \(mgr, streams) -> run mgr $ $(logv) logger level msg >> hTryGetLine (streams M.! stream) Q.assert $ test out runDisabledLog :: Logger -> Level -> String -> Property runDisabledLog logger level msg = Q.monadicIO $ do outs <- Q.run $ createManager True >>= \(mgr, streams) -> run mgr $ $(logv) logger level msg >> sequence (M.map hTryGetLine streams) Q.assert $ M.empty == M.filter (/= "") outs createManager :: Bool -> IO (Manager, M.Map String Handle) createManager disabled = do debugPh <- createPipeHandler "DEBUG" [] (formatters M.! "simple") infoPh <- createPipeHandler "INFO" [] (formatters M.! "logger") infov3Ph <- createPipeHandler "INFO" ["Log4hs.V3"] (formatters M.! "logger") errorPh <- createPipeHandler "ERROR" [] (formatters M.! "level") fatalPh <- createPipeHandler "FATAL" [] (formatters M.! "logger") let root = Sink "" "DEBUG" [] [fst debugPh, fst fatalPh] False False handlers = [fst debugPh, fst infoPh, fst infov3Ph, fst errorPh] log4hs = Sink "Log4hs" "INFO" [] handlers False False log4hsJson = Sink "Log4hs.Json" "WARN" [] [] False True log4hsYaml = Sink "Log4hs.Yaml" "WARN" [] [] False True log8hs = Sink "Log8hs" "INFO" [] [] False True log8hsJson = Sink "Log8hs.Json" "INFO" [] [] False True log10000hs = Sink "Log8hs.Json" "INFO" [] [fst debugPh] True False sinks = M.fromList [ ("Log4hs", log4hs) , ("Log4hs.Json", log4hsJson) , ("Log4hs.Yaml", log4hsYaml) , ("Log8hs", log8hs) , ("Log8hs.Json", log8hsJson) , ("Log10000hs", log10000hs) ] streams = M.fromList $ [ ("debug", snd debugPh) , ("info", snd infoPh) , ("infov3", snd infov3Ph) , ("error", snd errorPh) , ("fatal", snd fatalPh) ] return (Manager root sinks disabled False, streams) formatters :: M.Map String Format1 formatters = M.fromList [ ("simple", "{message}") , ("logger", "{logger}: {message}") , ("level", "{logger} {level}] {message}") ] createPipeHandler :: Level -> Filterer -> Format1 -> IO (SomeHandler, Handle) createPipeHandler level filterer formatter = do (read, write) <- createPipe hSetEncoding read utf8 hSetEncoding write utf8 return $ ( toHandler $ StreamHandler level filterer formatter write , read ) hTryGetLine :: Handle -> IO String hTryGetLine hdl = do ready <- hReady hdl if ready then hGetLine hdl else return "" instance Arbitrary Level where arbitrary = Level <$> arbitrary newtype Log4hsLogger = Log4hsLogger Logger deriving Show instance Arbitrary Log4hsLogger where arbitrary = elements $ map Log4hsLogger ["Log4hs", "Log4hs.Json", "Log4hs.Yaml"] newtype Log4hsHighLevel = Log4hsHighLevel Level deriving Show instance Arbitrary Log4hsHighLevel where arbitrary = elements $ map Log4hsHighLevel ["ERROR", "FATAL", Level 100]