packages feed

log4hs 0.0.2.0 → 0.0.3.0

raw patch · 10 files changed

+283/−207 lines, 10 filesdep −unordered-containersPVP ok

version bump matches the API change (PVP)

Dependencies removed: unordered-containers

API changes (from Hackage documentation)

+ Logging.Aeson: instance Data.Aeson.Types.FromJSON.FromJSON (Data.Map.Internal.Map GHC.Base.String Logging.Types.Formatter -> GHC.Types.IO Logging.Types.HandlerT)
+ Logging.Aeson: instance Data.Aeson.Types.FromJSON.FromJSON (GHC.Base.String -> Data.Map.Internal.Map GHC.Base.String Logging.Types.HandlerT -> Logging.Types.Sink)
+ Logging.Aeson: instance Data.Aeson.Types.FromJSON.FromJSON (GHC.Types.IO Logging.Types.HandlerT)
+ Logging.Aeson: instance Data.Aeson.Types.FromJSON.FromJSON (GHC.Types.IO Logging.Types.Manager)
+ Logging.Aeson: instance Data.Aeson.Types.FromJSON.FromJSON (GHC.Types.IO Logging.Types.StreamHandler)
+ Logging.Aeson: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Types.Filter
+ Logging.Aeson: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Types.Formatter
+ Logging.Aeson: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Types.Level
+ Logging.Aeson: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Types.Sink
+ Logging.Types: Filter :: String -> Int -> Filter
+ Logging.Types: Formatter :: String -> String -> Formatter
+ Logging.Types: Level :: Int -> Level
+ Logging.Types: LogRecord :: Logger -> Level -> String -> String -> String -> String -> Int -> ZonedTime -> LogRecord
+ Logging.Types: Manager :: Sink -> Map String Sink -> Bool -> Bool -> Manager
+ Logging.Types: Sink :: Logger -> Level -> Filterer -> [HandlerT] -> Bool -> Bool -> Sink
+ Logging.Types: StreamHandler :: Handle -> Level -> Filterer -> Formatter -> MVar () -> StreamHandler
+ Logging.Types: [$sel:catchUncaughtException:Manager] :: Manager -> Bool
+ Logging.Types: [$sel:created:LogRecord] :: LogRecord -> ZonedTime
+ Logging.Types: [$sel:datefmt:Formatter] :: Formatter -> String
+ Logging.Types: [$sel:disabled:Manager] :: Manager -> Bool
+ Logging.Types: [$sel:disabled:Sink] :: Sink -> Bool
+ Logging.Types: [$sel:filename:LogRecord] :: LogRecord -> String
+ Logging.Types: [$sel:filterer:Sink] :: Sink -> Filterer
+ Logging.Types: [$sel:filterer:StreamHandler] :: StreamHandler -> Filterer
+ Logging.Types: [$sel:fmt:Formatter] :: Formatter -> String
+ Logging.Types: [$sel:formatter:StreamHandler] :: StreamHandler -> Formatter
+ Logging.Types: [$sel:handlers:Sink] :: Sink -> [HandlerT]
+ Logging.Types: [$sel:level:LogRecord] :: LogRecord -> Level
+ Logging.Types: [$sel:level:Sink] :: Sink -> Level
+ Logging.Types: [$sel:level:StreamHandler] :: StreamHandler -> Level
+ Logging.Types: [$sel:lineno:LogRecord] :: LogRecord -> Int
+ Logging.Types: [$sel:lock:StreamHandler] :: StreamHandler -> MVar ()
+ Logging.Types: [$sel:logger:LogRecord] :: LogRecord -> Logger
+ Logging.Types: [$sel:logger:Sink] :: Sink -> Logger
+ Logging.Types: [$sel:message:LogRecord] :: LogRecord -> String
+ Logging.Types: [$sel:modulename:LogRecord] :: LogRecord -> String
+ Logging.Types: [$sel:name:Filter] :: Filter -> String
+ Logging.Types: [$sel:nlen:Filter] :: Filter -> Int
+ Logging.Types: [$sel:packagename:LogRecord] :: LogRecord -> String
+ Logging.Types: [$sel:propagate:Sink] :: Sink -> Bool
+ Logging.Types: [$sel:root:Manager] :: Manager -> Sink
+ Logging.Types: [$sel:sinks:Manager] :: Manager -> Map String Sink
+ Logging.Types: [$sel:stream:StreamHandler] :: StreamHandler -> Handle
+ Logging.Types: [HandlerT] :: Handler a => a -> HandlerT
+ Logging.Types: acquire :: Handler a => a -> IO ()
+ Logging.Types: class Filterable a
+ Logging.Types: class Formattable a
+ Logging.Types: class Handler a
+ Logging.Types: close :: Handler a => a -> IO ()
+ Logging.Types: data Filter
+ Logging.Types: data Formatter
+ Logging.Types: data HandlerT
+ Logging.Types: data LogRecord
+ Logging.Types: data Manager
+ Logging.Types: data Sink
+ Logging.Types: data StreamHandler
+ Logging.Types: emit :: Handler a => a -> LogRecord -> IO ()
+ Logging.Types: filter :: Filterable a => a -> LogRecord -> Bool
+ Logging.Types: flush :: Handler a => a -> IO ()
+ Logging.Types: format :: Formattable a => a -> LogRecord -> String
+ Logging.Types: formatTime :: Formattable a => a -> LogRecord -> String
+ Logging.Types: getFilterer :: Handler a => a -> Filterer
+ Logging.Types: getFormatter :: Handler a => a -> Formatter
+ Logging.Types: getLevel :: Handler a => a -> Level
+ Logging.Types: handle :: Handler a => a -> LogRecord -> IO Bool
+ Logging.Types: instance Data.Default.Class.Default Logging.Types.Formatter
+ Logging.Types: instance Data.Default.Class.Default Logging.Types.Level
+ Logging.Types: instance Data.String.IsString Logging.Types.Filter
+ Logging.Types: instance Data.String.IsString Logging.Types.Level
+ Logging.Types: instance GHC.Classes.Eq Logging.Types.Filter
+ Logging.Types: instance GHC.Classes.Eq Logging.Types.Formatter
+ Logging.Types: instance GHC.Classes.Eq Logging.Types.Level
+ Logging.Types: instance GHC.Classes.Ord Logging.Types.Level
+ Logging.Types: instance GHC.Enum.Enum Logging.Types.Level
+ Logging.Types: instance GHC.Read.Read Logging.Types.Level
+ Logging.Types: instance GHC.Show.Show Logging.Types.Level
+ Logging.Types: instance Language.Haskell.TH.Syntax.Lift Logging.Types.Level
+ Logging.Types: instance Logging.Types.Filterable Logging.Types.Filter
+ Logging.Types: instance Logging.Types.Filterable Logging.Types.Sink
+ Logging.Types: instance Logging.Types.Filterable a => Logging.Types.Filterable [a]
+ Logging.Types: instance Logging.Types.Formattable Logging.Types.Formatter
+ Logging.Types: instance Logging.Types.Handler Logging.Types.StreamHandler
+ Logging.Types: newtype Level
+ Logging.Types: release :: Handler a => a -> IO ()
+ Logging.Types: setFilterer :: Handler a => a -> Filterer -> a
+ Logging.Types: setFormatter :: Handler a => a -> Formatter -> a
+ Logging.Types: setLevel :: Handler a => a -> Level -> a
+ Logging.Types: type Filterer = [Filter]
+ Logging.Types: type Logger = String
+ Logging.Types: with :: Handler a => a -> (a -> IO b) -> IO b

Files

ChangeLog.md view
@@ -1,3 +1,3 @@-# Changelog for logging+# Changelog for log4hs  ## Unreleased changes
log4hs.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: a3438c9a7d132145f0468cc5eb8a4ebe54bd68bf61668f53e134e25e013f332c+-- hash: 98410eaec949ad056db17a215e79111cd33b233aeedfd41caeecb8c3207823ec  name:           log4hs-version:        0.0.2.0+version:        0.0.3.0 synopsis:       A python logging style log library description:    Please see the README category:       logging@@ -23,11 +23,12 @@ library   exposed-modules:       Logging+      Logging.Aeson+      Logging.Types   other-modules:-      Data.Aeson.Extra+      Logging.Deprecated       Logging.Internal       Logging.TH-      Logging.Types       Paths_log4hs   hs-source-dirs:       src@@ -41,7 +42,6 @@     , template-haskell >=2.0 && <3.0     , text >=1.2 && <2.0     , time >=1.4 && <2.0-    , unordered-containers >=0.2 && <1.0   default-language: Haskell2010  test-suite log4hs-test@@ -67,5 +67,4 @@     , template-haskell >=2.0 && <3.0     , text >=1.2 && <2.0     , time >=1.4 && <2.0-    , unordered-containers >=0.2 && <1.0   default-language: Haskell2010
− src/Data/Aeson/Extra.hs
@@ -1,32 +0,0 @@-module Data.Aeson.Extra-  ( lookupObject-  , lookupArray-  , lookupString-  , lookupBool-  ) where---import           Data.Aeson-import qualified Data.HashMap.Strict as HM-import qualified Data.Text           as T--lookupObject key kvs =-  case (HM.lookup key kvs) of-    Just (Object obj) ->-      HM.fromList $ map (\(k, v) -> (T.unpack k, v)) $ HM.toList obj-    _                 -> HM.empty--lookupArray key kvs =-  case (HM.lookup key kvs) of-    Just (Array arr) -> foldr (:) [] arr-    _                -> []--lookupString def key kvs =-  case (HM.lookup key kvs) of-    Just (String t) -> T.unpack t-    _               -> def--lookupBool def key kvs =-  case (HM.lookup key kvs) of-    Just (Bool b) -> b-    _             -> def
src/Logging.hs view
@@ -8,81 +8,49 @@  = A python logging style log library. -=== A full example:+=== A simple example:  @   \{\-\# LANGUAGE OverloadedStrings \#\-\}-  \{\-\# LANGUAGE QuasiQuotes       \#\-\}-  \{\-\# LANGUAGE RecordWildCards   \#\-\}   \{\-\# LANGUAGE TemplateHaskell   \#\-\} --  module Main (main) where+  module Main ( main ) where -  import           Data.Aeson.QQ.Simple (aesonQQ)-  import           Prelude hiding (error)-  import           Logging (runJson, debug, info, warn, error, fatal, logv)+  import           Data.Aeson+  import           Data.Maybe+  import           Logging+  import           Prelude    hiding (error)    main :: IO ()-  main = 'runJson' manager app+  main = fromJust (decode \"{}\") >>= flip run app    myLogger = \"MyLogger.Main\"    app :: IO ()   app = do-    \$(debug) myLogger \"this message should print into MyLogger\"-    \$(info) myLogger \"this message should print into MyLogger\"-    \$(warn) myLogger \"this message should print into MyLogger\"-    \$(error) myLogger \"this message should print into MyLogger\"-    \$(fatal) myLogger \"this message should print into MyLogger\"-    \$(logv) myLogger "LEVEL 100" \"this message should print into MyLogger\"--  \-\- The best practice is putting all config into a separate file,-  \-\- e.g "Logging.json"-  manager = [aesonQQ|{-    \"loggers\": {-      \"root\": {-        \"level\": \"DEBUG\",-        \"handlers\": [\"console\"],-        \"propagate\": false-      },-      \"MyLogger\": {-        \"level\": \"INFO\",-        \"filterer\": [\"MyLogger.Main\"],-        \"handlers\": [\"file\"],-        \"propagate\": false-      }-    },-    \"handlers\": {-      \"console\": {-        \"type\": \"StreamHandler\",-        \"stream\": \"stderr\",-        \"level\": \"DEBUG\",-        \"formatter\": \"defaultFormatter\"-      },-      \"file\": {-        \"type\": \"FileHandler\",-        \"level\": \"INFO\",-        \"formatter\": \"defaultFormatter\",-        \"file\": \"./default.log\"-      }-    },-    \"formatters\": {-      \"defaultFormatter\": {-        \"fmt\": \"%(asctime)s - %(level)s - %(logger)s - %(pathname)s/%(filename)s:%(lineno)d] %(message)s\"-      }-    }-  }|]+    $(debug) myLogger \"this is a test message\"+    $(info) myLogger \"this is a test message\"+    $(warn) myLogger \"this is a test message\"+    $(error) myLogger \"this is a test message\"+    $(fatal) myLogger \"this is a test message\"+    $(logv) myLogger \"LEVEL 100\" \"this is a test message\" @+++__See 'Logging.Aeson' to lean more about decoding json into 'Manager'__ -} module Logging-  ( module Logging.Internal+  ( module Logging.Aeson+  , module Logging.Deprecated+  , module Logging.Internal   -- ** Logging THs   , module Logging.TH   -- ** Types   , module Logging.Types   ) where -import           Logging.Internal hiding (log)+import           Logging.Aeson+import           Logging.Deprecated+import           Logging.Internal   hiding (log) import           Logging.TH-import           Logging.Types    hiding (Filterable (..), Formattable (..))+import           Logging.Types      hiding (Filterable (..), Formattable (..))
+ src/Logging/Aeson.hs view
@@ -0,0 +1,197 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE ScopedTypeVariables   #-}++module Logging.Aeson+  (+  -- * Aeson Instances+  --+  -- $aesondoc+  ) where++import           Control.Applicative     (pure)+import           Control.Concurrent.MVar+import           Data.Aeson+import           Data.Aeson.Types        (Parser, typeMismatch)+import           Data.Default+import           Data.Map.Lazy           (Map, (!))+import qualified Data.Map.Lazy           as M+import qualified Data.Text               as T+import           System.Directory+import           System.FilePath+import           System.IO++import           Logging.Internal+import           Logging.Types+++mapAp :: (Applicative f1, Applicative f2) => f1 (a -> b) -> f2 a -> (f1 (f2 b))+mapAp f x = ((<*> x) . pure) <$> f++mapAp2 :: (Applicative f1, Applicative f2) => f1 (a -> b -> c) -> f2 a -> f2 b -> (f1 (f2 c))+mapAp2 f x y = ((<*> y). (<*> x) . pure) <$> f+++{- $aesondoc++By using 'Data.Aeson', we can decode json string into 'Manager'.++__A basic 'Manager' json format:__++@+  {+    \"loggers\": {\"root\": {}, \"MyLogger\": {}},+    \"handlers\": {\"console\": {}, \"file\": {}},+    \"formatters\": {\"default\": {}, \"simple\": {}}+  }+@+++In practice, a set of handlers share a formatter, and other handlers share+another one, so we define all the formatters in a map, handler refernces+the formatter throught its key.++So as handlers, sinks may share same handlers.++__Examples of 'Formatter' json__++@+  -- a standard format+  {+    \"fmt\": \"%(message)s\",+    \"datefmt\": \"%Y-%m-%dT%H:%M:%S\"+  }++  -- missing field will use default value+  {+    \"fmt": "%(message)s\",+    \"datefmt\": \"%Y-%m-%dT%H:%M:%S\"+  }++  -- it works as well, just a string+  \"%(message)s\"+@++__Examples of 'Handler' json__++Note: Besides some common field, handler's other fields depend on its type.++@+  -- a standard format+  {+    \"type\": \"StreamHandler\",+    \"stream\": \"stderr\",+    \"level\": \"DEBUG\",+    \"filterer\": [\"Package.Module.Submodule\"],+    \"formatter\": \"default\",+  }++  -- FileHandler is just a StreamHandler, the stream is created by openFile+  {+    \"type\": \"FileHandler\",+    \"file\": \"./default.log\",+    \"level\": \"INFO\",+    \"filterer\": [],+    \"formatter\": \"simple\",+  }+@+-}++instance FromJSON Level where+  parseJSON v = read <$> parseJSON v+++instance FromJSON Filter where+  parseJSON v = (\s -> Filter s $ length s) <$> parseJSON v+++instance FromJSON Formatter where+  parseJSON (Object v) = Formatter <$> v .:? "fmt" .!= (fmt def)+                                   <*> v .:? "datefmt" .!= (datefmt def)+  parseJSON (String v) = (\fmt -> def {fmt = fmt}) <$> parseJSON (String v)+  parseJSON invalid = typeMismatch "Object" invalid+++instance FromJSON (IO StreamHandler) where+  parseJSON = withObject "StreamHandler" $ \v -> flip mapAp (newMVar ()) $+      StreamHandler <$> (parseStream <$> (v .:? "stream" .!= "stderr"))+                    <*> v .:? "level" .!= def+                    <*> v .:? "filterer" .!= []+                    <*> v .:? "formatter" .!= def+    where+      parseStream :: String -> Handle+      parseStream "stderr" = stderr+      parseStream "stdout" = stdout+      parseStream _        = error "Logging.Aeson: no parse (stream)"+++instance FromJSON (IO HandlerT) where+  parseJSON = withObject "Handler" $ \v -> (v .: "type") >>= (`parseHandler` v)+    where+      openLogFile :: FilePath -> IO Handle+      openLogFile file = do+        file' <- makeAbsolute file+        createDirectoryIfMissing True $ takeDirectory file'+        stream <- openFile file AppendMode+        hSetEncoding stream utf8+        return stream++      parseHandler :: String -> Object -> Parser (IO HandlerT)+      parseHandler "StreamHandler" v = do+        hdl <- parseJSON (Object v)+        return $ HandlerT <$> (hdl :: IO StreamHandler)+      parseHandler "FileHandler" v = do+        hdl :: (IO StreamHandler) <- parseJSON (Object v)+        stream <- openLogFile <$> (v .: "file" .!= "default.log")+        hdl' <- mapAp2 (pure $ \h s -> h {stream = s}) hdl stream+        return $ HandlerT <$> hdl'+      parseHandler t _ = error $ "Logging.Aeson: no parse (Handler" ++ t ++")"+++instance FromJSON (Map String Formatter -> IO HandlerT) where+  parseJSON = withObject "Handler" $ \v -> do+    hdlio <- parseJSON (Object v)+    key <- v .:? "formatter" .!= ""+    return $ \fs -> hdlio >>= \(HandlerT hdl) -> do+      return $ HandlerT $ setFormatter hdl (M.findWithDefault def key fs)+++instance FromJSON (Sink) where+  parseJSON = withObject "Sink" $ \v ->+    Sink <$> v .:? "logger" .!= "placeholder"+         <*> v .:? "level" .!= def+         <*> v .:? "filterer" .!= []+         <*> (return [])+         <*> v .:? "disabled" .!= False+         <*> v .:? "propagate" .!= False+++instance FromJSON (String -> Map String HandlerT -> Sink) where+  parseJSON = withObject "Sink" $ \v -> do+    sink <- parseJSON (Object v)+    keys <- v .:? "handlers" .!= []+    return $ \lgr hs -> sink { logger = if lgr == "root" then "" else lgr+                             , handlers = [hs ! k | k <- keys]+                             }+++type Formatters = Map String Formatter+type HandlerTsMakerIO = Map String (Formatters -> IO HandlerT)+type SinksMaker = Map String (String -> Map String HandlerT -> Sink)++instance FromJSON (IO Manager) where+  parseJSON = withObject "Manager" $ \v -> do+    fmts :: Formatters <- v .:? "formatters" .!= (object []) >>= parseJSON+    hdls :: HandlerTsMakerIO <- v .:? "handlers" .!= (object []) >>= parseJSON+    sinks :: SinksMaker <- v .:? "loggers" .!= (object []) >>= parseJSON++    disabled <- v .:? "disabled" .!= False+    catchUncaughtException <- v .:? "catchUncaughtException" .!= False++    return $ do+      hdls' <- sequence $ M.map (\f -> f fmts) hdls+      let sinks' = M.mapWithKey (\k f -> f k hdls') sinks+          root = M.findWithDefault defaultRoot "root" sinks'+          sinks'' = M.delete "root" sinks'+      return $ Manager root sinks'' disabled catchUncaughtException
+ src/Logging/Deprecated.hs view
@@ -0,0 +1,38 @@+module Logging.Deprecated+  ( runJson+  , jsonToManager+  ) where+++import           Data.Aeson++import           Logging.Aeson+import           Logging.Internal+import           Logging.Types++-- |Run a logging environment from JSON 'Value'.+--+-- A combinator of 'run' and 'fromJSON'+--+-- A combinator of 'jsonToManager' and 'run'+--+-- See 'Logging.Aeson'+{-# DEPRECATED runJson "will be removed in 1.0.0" #-}+runJson :: Value -> IO a -> IO a+runJson v io = sequence (fromJSON v) >>= \result ->+  case result of+    Error _     -> error "Logging.Aeson: no parse (manager)"+    Success mgr -> run mgr io+++-- |Make a 'Manager' from JSON 'Value'+--+-- Decode 'Aeson.Value' into 'Manager'+--+-- See 'Logging.Aeson'+{-# DEPRECATED jsonToManager "will be removed in 1.0.0" #-}+jsonToManager :: Value -> IO Manager+jsonToManager v = sequence (fromJSON v) >>= \result ->+  case result of+    Error _     -> error "Logging.Aeson: no parse (manager)"+    Success mgr -> return mgr
src/Logging/Internal.hs view
@@ -5,37 +5,27 @@  module Logging.Internal   ( run-  , runJson-  , jsonToManager   , log   , stderrHandler   , stdoutHandler   , defaultRoot   ) where -import           Control.Concurrent.MVar (MVar, newMVar)+import           Control.Concurrent.MVar import           Control.Exception       (SomeException, bracket_)-import           Control.Monad           (forM_, sequence, void, when)+import           Control.Monad           (forM_, void, when) import           Control.Monad.IO.Class  (MonadIO (..))-import           Data.Aeson              (Value (..)) import           Data.Default-import qualified Data.HashMap.Strict     as HM import           Data.IORef import           Data.List               (dropWhileEnd)-import           Data.Map.Lazy           (Map, delete, fromList, (!?))-import qualified Data.Text               as T+import           Data.Map.Lazy           ((!?)) import           Data.Time.Clock import           Data.Time.LocalTime import           GHC.Conc                (setUncaughtExceptionHandler) import           Prelude                 hiding (filter, log)-import           System.Directory        (createDirectoryIfMissing,-                                          makeAbsolute)-import           System.FilePath-import           System.IO               (Handle, IOMode (..), hSetEncoding,-                                          openFile, stderr, stdout, utf8)+import           System.IO               (Handle, stderr, stdout) import           System.IO.Unsafe        (unsafePerformIO) -import           Data.Aeson.Extra import           Logging.Types  {-# NOINLINE _mgr #-}@@ -69,96 +59,6 @@      closeHandlers :: Sink -> IO ()     closeHandlers Sink{..} = forM_ handlers $ \(HandlerT hdl) -> close hdl----- |Run a logging environment from JSON 'Value'.------ A combinator of 'run' and 'jsonToManager'.----runJson :: Value -> IO a -> IO a-runJson val io = jsonToManager val >>= (`run` io)---- | Parse JSON to Formatter-jsonToFormatter :: Value -> Formatter-jsonToFormatter (Object obj) =-    Formatter (lookupString fmt "fmt" obj) (lookupString datefmt "datefmt" obj)-  where-    Formatter{..} = def-jsonToFormatter (String fmt) = def {fmt = T.unpack fmt}-jsonToFormatter _ = def----- | Parse JSON to Filter-jsonToFilter :: Value -> Filter-jsonToFilter (String s) = let s' = T.unpack s in Filter s' (length s')-jsonToFilter _          = error "Logging.Internal: no parse (Filter)"----- | Parse JSON to Handler(T)-jsonToHandler :: Value -> (String -> Formatter) -> IO HandlerT-jsonToHandler (Object obj) lookupFmt = jsonToHandler' type_-  where-    type_ = lookupString "" "type" obj-    level = read $ lookupString "NOTSET" "level" obj-    filterer = map jsonToFilter $ lookupArray "filterer" obj-    formatter = lookupFmt $ lookupString "" "formatter" obj--    lock :: IO (MVar ())-    lock = newMVar ()--    nameToStream :: String -> Handle-    nameToStream "stderr" = stderr-    nameToStream "stdout" = stdout-    nameToStream _        = error "Logging.Internal: no parse (stream)"--    jsonToHandler' :: String -> IO HandlerT-    jsonToHandler' "StreamHandler" = do-      let stream = nameToStream $ lookupString "stderr" "stream" obj-      (HandlerT . (StreamHandler stream level filterer formatter)) <$> lock-    jsonToHandler' "FileHandler" = do-      file <- makeAbsolute $ lookupString "default.log" "file" obj-      createDirectoryIfMissing True $ takeDirectory file-      stream <- openFile file AppendMode-      hSetEncoding stream utf8-      (HandlerT . (StreamHandler stream level filterer formatter)) <$> lock-    jsonToHandler' _ = error $ "Logging.Internal: no parse (Handler)"-jsonToHandler _ _ = undefined----- | Parse JSON to Sink-jsonToSink :: (String, Value) -> (String -> HandlerT) -> Sink-jsonToSink (logger, Object obj) lookupHdl =-    Sink logger' level filterer handlers disabled propagate-  where-    logger' = if logger == "root" then "" else logger-    level = read $ lookupString "NOTSET" "level" obj-    filterer = map jsonToFilter $ lookupArray "filterer" obj-    handlers = [lookupHdl (T.unpack v) | (String v) <- lookupArray "handlers" obj]-    disabled = lookupBool False "disabled" obj-    propagate = lookupBool False "propagate" obj-jsonToSink _ _ = error "Logging.Internal: no parse (Logger)"----- |Make a 'Manager' from JSON 'Value'.-jsonToManager :: Value -> IO Manager-jsonToManager (Object obj) = do-  let formatters = HM.map jsonToFormatter $ lookupObject "formatters" obj-      lookupFmt k = HM.lookupDefault def k formatters-      handlerNames = lookupObject "handlers" obj--  handlers <- sequence $ HM.map (`jsonToHandler` lookupFmt) handlerNames--  let lookupHdl = (HM.!) handlers-      sinkVals = lookupObject "loggers" obj-      sinks = HM.mapWithKey (curry (`jsonToSink` lookupHdl)) sinkVals--      root = HM.lookupDefault defaultRoot "root" sinks-      sinks' = delete "root" $ fromList $ HM.toList sinks-      disabled = lookupBool False "disabled" obj-      catchUncaughtException = lookupBool False "catchUncaughtException" obj--  return $ Manager root sinks' disabled catchUncaughtException-jsonToManager _ = error "Logging.Internal: no parse (Manager)"   -- |Low-level logging routine which creates a LogRecord and then calls
src/Logging/TH.hs view
@@ -11,12 +11,13 @@   , fatal   ) where +import           Control.Monad.IO.Class (MonadIO) import           Language.Haskell.TH  import           Logging.Internal import           Logging.Types -import           Prelude             hiding (error, log)+import           Prelude                hiding (error, log)  -- | Log "message" with the severity "level". --
src/Logging/Types.hs view
@@ -161,6 +161,7 @@ -- Note that this class does not close the stream when the stream is a -- terminal device, e.g. 'stderr' and 'stdout'. --+-- Note: 'FileHandler' is an alias of 'StreamHandler' data StreamHandler = StreamHandler { stream    :: Handle                                    , level     :: Level                                    , filterer  :: Filterer
test/Spec.hs view
@@ -5,6 +5,7 @@  import           Control.Concurrent.MVar import           Control.Monad+import           Data.Aeson                 as A import           Data.Aeson.QQ.Simple       (aesonQQ) import           Data.Default               (def) import           Data.List                  (intercalate, lines)@@ -20,6 +21,7 @@ import qualified Test.QuickCheck.Monadic    as QC  import           Logging+import           Logging.Aeson  main :: IO () main = hspec $ do@@ -100,14 +102,16 @@   QC.assert (msg == format rcd)  testJsonToManager :: IO ()-testJsonToManager = do-    mgr@Manager{..} <- jsonToManager managerJson-    -- root-    testSink "root" root-    -- sinks-    length sinks `shouldBe` 1-    M.member "MyLogger" sinks `shouldBe` True-    testSink "MyLogger" $ sinks M.! "MyLogger"+testJsonToManager = (sequence $ A.fromJSON managerJson) >>= \result ->+    case result of+      A.Error msg               -> expectationFailure msg+      A.Success mgr@Manager{..} -> do+        -- root+        testSink "root" root+        -- sinks+        length sinks `shouldBe` 1+        M.member "MyLogger" sinks `shouldBe` True+        testSink "MyLogger" $ sinks M.! "MyLogger"   where     testSink :: Logger -> Sink -> IO ()     testSink "root" Sink{..} = do