log4hs 0.8.0.0 → 0.9.0.0
raw patch · 21 files changed
+291/−283 lines, 21 filesdep +aeson-defaultdep +microlensdep −data-defaultdep −lensPVP ok
version bump matches the API change (PVP)
Dependencies added: aeson-default, microlens
Dependencies removed: data-default, lens
API changes (from Hackage documentation)
- Logging.Config.Type: data Config
- Logging.Config.Type: data Handler
- Logging.Config.Type: data Sink
- Logging.Config.Type: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Config.Type.Config
- Logging.Config.Type: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Config.Type.Handler
- Logging.Config.Type: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Config.Type.Sink
- Logging.Config.Type: instance GHC.Generics.Generic Logging.Config.Type.Config
- Logging.Config.Type: instance GHC.Generics.Generic Logging.Config.Type.Handler
- Logging.Config.Type: instance GHC.Generics.Generic Logging.Config.Type.Sink
- Logging.Config.Type: instance GHC.Show.Show Logging.Config.Type.Config
- Logging.Config.Type: instance GHC.Show.Show Logging.Config.Type.Handler
- Logging.Config.Type: instance GHC.Show.Show Logging.Config.Type.Sink
- Logging.Level: instance Data.Default.Class.Default Logging.Level.Level
+ Logging.Config.Type: data ConfigH f
+ Logging.Config.Type: data HandlerH f
+ Logging.Config.Type: data SinkH f
+ Logging.Config.Type: instance Data.Aeson.Default.Class.Default Logging.Config.Type.ConfigH
+ Logging.Config.Type: instance Data.Aeson.Default.Class.Default Logging.Config.Type.HandlerH
+ Logging.Config.Type: instance Data.Aeson.Default.Class.Default Logging.Config.Type.SinkH
+ Logging.Config.Type: instance Data.Aeson.Types.FromJSON.FromJSON (Logging.Config.Type.ConfigH GHC.Maybe.Maybe)
+ Logging.Config.Type: instance Data.Aeson.Types.FromJSON.FromJSON (Logging.Config.Type.HandlerH GHC.Maybe.Maybe)
+ Logging.Config.Type: instance Data.Aeson.Types.FromJSON.FromJSON (Logging.Config.Type.SinkH GHC.Maybe.Maybe)
+ Logging.Config.Type: instance GHC.Generics.Generic (Logging.Config.Type.ConfigH f)
+ Logging.Config.Type: instance GHC.Generics.Generic (Logging.Config.Type.HandlerH f)
+ Logging.Config.Type: instance GHC.Generics.Generic (Logging.Config.Type.SinkH f)
+ Logging.Config.Type: type Config = ConfigH Identity
+ Logging.Config.Type: type Handler = HandlerH Identity
+ Logging.Config.Type: type Sink = SinkH Identity
+ Logging.Filter: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Filter.Filter
+ Logging.Handler.TimeRotatingFileHandler: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Handler.TimeRotatingFileHandler.RotateTime
+ Logging.Level: instance Data.Aeson.Types.FromJSON.FromJSON Logging.Level.Level
+ Logging.Prelude: mkStdHandle :: String -> Handle
- Logging.Config.Type: Config :: Maybe (Map String Sink) -> Maybe (Map String Handler) -> Maybe (Map String String) -> Maybe String -> Maybe Bool -> Config
+ Logging.Config.Type: Config :: HKD (MapH String SinkH f) f -> HKD (MapH String HandlerH f) f -> HKD (Map String String) f -> Maybe String -> HKD Bool f -> ConfigH f
- Logging.Config.Type: FileHandler :: Maybe String -> Maybe [String] -> Maybe String -> FilePath -> Maybe String -> Handler
+ Logging.Config.Type: FileHandler :: HKD Level f -> HKD Filterer f -> HKD String f -> FilePath -> HKD String f -> HandlerH f
- Logging.Config.Type: RotatingFileHandler :: Maybe String -> Maybe [String] -> Maybe String -> FilePath -> Maybe String -> Maybe Int -> Maybe Int -> Handler
+ Logging.Config.Type: RotatingFileHandler :: HKD Level f -> HKD Filterer f -> HKD String f -> FilePath -> HKD String f -> HKD Int f -> HKD Int f -> HandlerH f
- Logging.Config.Type: Sink :: Maybe String -> Maybe [String] -> Maybe [String] -> Maybe Bool -> Maybe Bool -> Sink
+ Logging.Config.Type: Sink :: HKD Level f -> HKD Filterer f -> HKD [String] f -> HKD Bool f -> HKD Bool f -> SinkH f
- Logging.Config.Type: StreamHandler :: Maybe String -> Maybe [String] -> Maybe String -> Maybe String -> Handler
+ Logging.Config.Type: StreamHandler :: HKD Level f -> HKD Filterer f -> HKD String f -> HKD String f -> HandlerH f
- Logging.Config.Type: TimeRotatingFileHandler :: Maybe String -> Maybe [String] -> Maybe String -> FilePath -> Maybe String -> Maybe String -> Maybe String -> Maybe Int -> Handler
+ Logging.Config.Type: TimeRotatingFileHandler :: HKD Level f -> HKD Filterer f -> HKD String f -> FilePath -> HKD String f -> Maybe String -> HKD RotateTime f -> HKD Int f -> HandlerH f
- Logging.Config.Type: [$sel:backupCount:StreamHandler] :: Handler -> Maybe Int
+ Logging.Config.Type: [$sel:backupCount:StreamHandler] :: HandlerH f -> HKD Int f
- Logging.Config.Type: [$sel:disabled:Config] :: Config -> Maybe Bool
+ Logging.Config.Type: [$sel:disabled:Config] :: ConfigH f -> HKD Bool f
- Logging.Config.Type: [$sel:disabled:Sink] :: Sink -> Maybe Bool
+ Logging.Config.Type: [$sel:disabled:Sink] :: SinkH f -> HKD Bool f
- Logging.Config.Type: [$sel:encoding:StreamHandler] :: Handler -> Maybe String
+ Logging.Config.Type: [$sel:encoding:StreamHandler] :: HandlerH f -> HKD String f
- Logging.Config.Type: [$sel:file:StreamHandler] :: Handler -> FilePath
+ Logging.Config.Type: [$sel:file:StreamHandler] :: HandlerH f -> FilePath
- Logging.Config.Type: [$sel:filterer:Sink] :: Sink -> Maybe [String]
+ Logging.Config.Type: [$sel:filterer:Sink] :: SinkH f -> HKD Filterer f
- Logging.Config.Type: [$sel:filterer:StreamHandler] :: Handler -> Maybe [String]
+ Logging.Config.Type: [$sel:filterer:StreamHandler] :: HandlerH f -> HKD Filterer f
- Logging.Config.Type: [$sel:formatter:StreamHandler] :: Handler -> Maybe String
+ Logging.Config.Type: [$sel:formatter:StreamHandler] :: HandlerH f -> HKD String f
- Logging.Config.Type: [$sel:formatters:Config] :: Config -> Maybe (Map String String)
+ Logging.Config.Type: [$sel:formatters:Config] :: ConfigH f -> HKD (Map String String) f
- Logging.Config.Type: [$sel:handlers:Config] :: Config -> Maybe (Map String Handler)
+ Logging.Config.Type: [$sel:handlers:Config] :: ConfigH f -> HKD (MapH String HandlerH f) f
- Logging.Config.Type: [$sel:handlers:Sink] :: Sink -> Maybe [String]
+ Logging.Config.Type: [$sel:handlers:Sink] :: SinkH f -> HKD [String] f
- Logging.Config.Type: [$sel:level:Sink] :: Sink -> Maybe String
+ Logging.Config.Type: [$sel:level:Sink] :: SinkH f -> HKD Level f
- Logging.Config.Type: [$sel:level:StreamHandler] :: Handler -> Maybe String
+ Logging.Config.Type: [$sel:level:StreamHandler] :: HandlerH f -> HKD Level f
- Logging.Config.Type: [$sel:maxBytes:StreamHandler] :: Handler -> Maybe Int
+ Logging.Config.Type: [$sel:maxBytes:StreamHandler] :: HandlerH f -> HKD Int f
- Logging.Config.Type: [$sel:propagate:Sink] :: Sink -> Maybe Bool
+ Logging.Config.Type: [$sel:propagate:Sink] :: SinkH f -> HKD Bool f
- Logging.Config.Type: [$sel:rotateTime:StreamHandler] :: Handler -> Maybe String
+ Logging.Config.Type: [$sel:rotateTime:StreamHandler] :: HandlerH f -> HKD RotateTime f
- Logging.Config.Type: [$sel:sinks:Config] :: Config -> Maybe (Map String Sink)
+ Logging.Config.Type: [$sel:sinks:Config] :: ConfigH f -> HKD (MapH String SinkH f) f
- Logging.Config.Type: [$sel:stream:StreamHandler] :: Handler -> Maybe String
+ Logging.Config.Type: [$sel:stream:StreamHandler] :: HandlerH f -> HKD String f
- Logging.Config.Type: [$sel:timezone:Config] :: Config -> Maybe String
+ Logging.Config.Type: [$sel:timezone:Config] :: ConfigH f -> Maybe String
- Logging.Config.Type: [$sel:timezone:StreamHandler] :: Handler -> Maybe String
+ Logging.Config.Type: [$sel:timezone:StreamHandler] :: HandlerH f -> Maybe String
Files
- ChangeLog.md +6/−0
- README.md +28/−3
- bench/Main.hs +2/−1
- log4hs.cabal +20/−14
- src/Logging.hs +3/−40
- src/Logging/Class/Handler.hs +2/−1
- src/Logging/Config/Json.hs +11/−35
- src/Logging/Config/Type.hs +160/−117
- src/Logging/Config/Yaml.hs +14/−14
- src/Logging/Filter.hs +4/−0
- src/Logging/Global.hs +8/−5
- src/Logging/Global/Internal.hs +1/−0
- src/Logging/Handler/TimeRotatingFileHandler.hs +4/−0
- src/Logging/Level.hs +5/−5
- src/Logging/Monad.hs +8/−4
- src/Logging/Monad/Internal.hs +1/−2
- src/Logging/Prelude.hs +7/−0
- src/Logging/TH.hs +0/−5
- src/Logging/Types.hs +0/−23
- src/Logging/Utils.hs +0/−6
- test/LoggingTest/TypesSpec.hs +7/−8
ChangeLog.md view
@@ -1,5 +1,11 @@ # Changelog for log4hs +## V0.9.0+- Fix log `UserInterrupt` issue+- Improve config datatypes+- Improve document and deprecate `Logging` module+- Replace lens with macrolens and remove `data-default`+ ## V0.8.0 - Add `IsMessage` class - Deprecate `catchUncaughtException`
README.md view
@@ -1,13 +1,38 @@ # A python logging style log library. - ## Documentation -See [hackage](http://hackage.haskell.org/package/log4hs)+Please See http://hackage.haskell.org/package/log4hs +## Example+```haskell+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} +module Main ( main ) where++import Logging.Config.Json (getManager)+import Logging.Global (run)+import Logging.Global.TH (debug, error, fatal, info, logv, warn)+import Prelude hiding (error)++main :: IO ()+main = getManager "{}" >>= flip run app++logger = "Main"++app :: IO ()+app = do+ $(debug) logger "this is a test message"+ $(info) logger "this is a test message"+ $(warn) logger "this is a test message"+ $(error) logger "this is a test message"+ $(fatal) logger "this is a test message"+ $(logv) logger "LEVEL 100" "this is a test message"+```+ ## Contributing The source code will be opened to public after version 1.0.0 released. So, please wait.@@ -16,7 +41,7 @@ ## Issue Reporting -Please send email to log4hs@version.cloud for now.+Please send email to jorah@version.cloud for now.
bench/Main.hs view
@@ -16,8 +16,9 @@ import Data.Maybe import System.IO.Unsafe -import Logging import Logging.Config.Json+import Logging.Global+import Logging.Manager main :: IO ()
log4hs.cabal view
@@ -4,14 +4,23 @@ -- -- see: https://github.com/sol/hpack ----- hash: 8762d58dd571f8027cd0efeab16d060754fa1b0c76d87bd50f61fac31e7f116a+-- hash: 54029a04d065d8d1436594a85e50470a60c6e1632498264c0264328308d53be7 name: log4hs-version: 0.8.0.0+version: 0.9.0.0 synopsis: A python logging style log library-description: Please see the http://hackage.haskell.org/package/log4hs-category: logging-maintainer: Jorah Gao <log4hs@version.cloud>+description: @log4hs@ aims at providing a rich featured logging library for Haskell,+ just like @logging@ for Python and @log4j@ for Java.+ .+ .+ To get started, see the sample example in @README.md@ and document in+ hackage.+ .+ .+ It's still under development, so be carefull when using it in production+ environment.+category: logging, log, library+maintainer: Jorah Gao <jorah@version.cloud> copyright: (c) 2019 Version Cloud license: BSD3 license-file: LICENSE@@ -44,9 +53,6 @@ Logging.Prelude Logging.Record Logging.Sink- Logging.TH- Logging.Types- Logging.Utils other-modules: Logging.Class.Filterable Logging.Class.Handler@@ -58,14 +64,14 @@ src build-depends: aeson >=1.2 && <2.0+ , aeson-default >=0.9 && <1.0 , base >=4.7 && <5 , bytestring >=0.10 && <1.0 , containers >=0.5.10 && <1.0- , data-default >=0.5 && <1.0 , directory >=1.2 && <1.4 , filepath >=1.3 && <1.5 , generic-lens >=0.5 && <2.0- , lens >=4.15 && <5.0+ , microlens >=0.4 && <1.0 , mtl >=2.2 && <3.0 , template-haskell >=2.0 && <3.0 , text >=1.2 && <2.0@@ -91,18 +97,18 @@ build-depends: QuickCheck >=2.0 && <3.0 , aeson >=1.2 && <2.0+ , aeson-default >=0.9 && <1.0 , aeson-qq >=0.8 && <1.0 , base >=4.7 && <5 , bytestring >=0.10 && <1.0 , containers >=0.5.10 && <1.0- , data-default >=0.5 && <1.0 , directory >=1.2 && <1.4 , filepath >=1.3 && <1.5 , generic-lens >=0.5 && <2.0 , hspec >=2.1 && <3.0 , hspec-core >=2.1 && <3.0- , lens >=4.15 && <5.0 , log4hs+ , microlens >=0.4 && <1.0 , mtl >=2.2 && <3.0 , process >=1.2 && <2.0 , template-haskell >=2.0 && <3.0@@ -124,17 +130,17 @@ ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: aeson >=1.2 && <2.0+ , aeson-default >=0.9 && <1.0 , aeson-qq >=0.8 && <1.0 , base >=4.7 && <5 , bytestring >=0.10 && <1.0 , containers >=0.5.10 && <1.0 , criterion >=1.0 && <2.0- , data-default >=0.5 && <1.0 , directory >=1.2 && <1.4 , filepath >=1.3 && <1.5 , generic-lens >=0.5 && <2.0- , lens >=4.15 && <5.0 , log4hs+ , microlens >=0.4 && <1.0 , mtl >=2.2 && <3.0 , template-haskell >=2.0 && <3.0 , text >=1.2 && <2.0
src/Logging.hs view
@@ -2,49 +2,12 @@ Module : Logging Copyright : (c) 2019 Version Cloud License : BSD3-Maintainer : Jorah Gao <log4hs@version.cloud>+Maintainer : Jorah Gao <jorah@version.cloud> Stability : experimental Portability : portable--= A python logging style log library.--=== A simple example:--@- \{\-\# LANGUAGE OverloadedStrings \#\-\}- \{\-\# LANGUAGE TemplateHaskell \#\-\}-- module Main ( main ) where-- import Logging.Config.Json (getManager)- import Logging.Global (run)- import Logging.Global.TH (debug, error, fatal, info, logv, warn)- import Prelude hiding (error)-- main :: IO ()- main = getManager "{}" >>= flip run app-- logger = \"Main\"-- app :: IO ()- app = do- $(debug) logger \"this is a test message\"- $(info) logger \"this is a test message\"- $(warn) logger \"this is a test message\"- $(error) logger \"this is a test message\"- $(fatal) logger \"this is a test message\"- $(logv) logger \"LEVEL 100\" \"this is a test message\"-@--See "Logging.Config.Json" and "Logging.Config.Yaml" to lean more about-decoding 'Manager' from json or yaml -}-module Logging- ( module Logging.Types- , module Logging.TH- , module Logging.Global+module Logging {-# Deprecated "Use submodules directly" #-}+ ( module Logging.Global ) where import Logging.Global-import Logging.TH-import Logging.Types
src/Logging/Class/Handler.hs view
@@ -7,10 +7,11 @@ 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 Lens.Micro (set)+import Lens.Micro.Extras (view) import Prelude hiding (filter) import Text.Format
src/Logging/Config/Json.hs view
@@ -1,32 +1,9 @@-{-| This module provides a convenient way to decode a 'Manager' from json string--There is a 'Config' type in "Logging.Config.Type" module, it is an instance-of 'FromJSON', 1) decode 'Config' from json string, 2) construct an 'Manager'-from 'Config'.---All 'Maybe' fileds can be omitted, the default value will be used, e.g.--1) 'List' type field will use '[]' as default--2) 'Purelude.Bool' type filed will use False as default--3) if the field is an instance of 'Data.Default.Default', use 'def'--4) an appropriate value as default for other fields---In fact, you can decode 'Config' from an empty json (object) string,--@- manager <- getManager "{}" -- enable the 'OverloadedStrings' extension-@--it will create a manager with a root sink (i.e. 'Logging.defaultRoot'),-the 'Logging.defaultRoot' will be used when the root sink is omitted.+{-|This module provides a convenient way to decode a 'Manager' from json string -Here is a json string template:+Note: You are recommend to use "Logging.Config.Yaml", because of yaml is easier+to read and write. +=== Template @ { \"sinks\":@@ -61,7 +38,7 @@ \"level\": \"Level 100\", \"filterer\": [\"App.Json\"], \"formatter\": \"standard\",- \"file\": \"/etc\/my\/json.log\",+ \"file\": \"\/var\/log\/my\/json.log", \"encoding\": \"utf8\" }, \"rotate\":@@ -70,7 +47,7 @@ \"level\": \"INFO\", \"filterer\": [\"App.Yaml\"], \"formatter\": \"standard\",- \"file\": \"/etc\/my\/yaml.log\",+ \"file\": \"\/var\/log\/my\/json.log", \"encoding\": \"utf8\", \"maxBytes\": 1048576, \"backupCount\": 10@@ -81,13 +58,10 @@ \"simple\": \"{message}\", \"standard\": \"{asctime:%Y-%m-%dT%H:%M:%S%6Q%z} - {level} - {logger}] {message}\" },- \"disabled\": false,- \"catchUncaughtException\": true+ \"disabled\": false } @- -}- module Logging.Config.Json (getManager, getManagerFile) where @@ -100,7 +74,8 @@ import Logging.Manager --- | decode a 'Manager' from strict 'ByteString'+-- | decode a 'Manager' from strict 'ByteString', it+-- 1) decodes 'Config' from json string, 2) constructs an 'Manager' -- -- @since 0.4.0 getManager :: ByteString -> IO Manager@@ -110,7 +85,8 @@ Right config -> createManager config --- | decode a 'Manager' from a file+-- | decode a 'Manager' from a file, it+-- 1) reads json string from file, 2) calls getManager to construct an 'Manager' -- -- @since 0.4.0 getManagerFile :: FilePath -> IO Manager
src/Logging/Config/Type.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} @@ -8,6 +10,9 @@ , Sink(..) , Config(..) , ConfigException(..)+ , HandlerH(..)+ , SinkH(..)+ , ConfigH(..) , createManager ) where @@ -15,7 +20,10 @@ import Control.Concurrent.MVar import Control.Exception (Exception) import Data.Aeson-import Data.Default+import Data.Aeson.Default+import Data.Aeson.Default.HKD+import Data.Aeson.Default.Map.Strict+import Data.Functor.Identity import Data.IORef import Data.Map.Lazy import Data.Maybe@@ -23,92 +31,154 @@ import Data.Time.Clock import Data.Time.LocalTime import GHC.Generics-import System.IO- import Prelude hiding (map) import qualified Prelude as P+import System.IO import Text.Format hiding (defaultOptions) import qualified Logging.Class as T+import qualified Logging.Filter as T import qualified Logging.Handler.FileHandler as T import qualified Logging.Handler.RotatingFileHandler as T import qualified Logging.Handler.StreamHandler as T import qualified Logging.Handler.TimeRotatingFileHandler as T+import qualified Logging.Level as T import qualified Logging.Manager as T import Logging.Prelude import qualified Logging.Sink as T -data Handler = StreamHandler { level :: Maybe String- , filterer :: Maybe [String]- , formatter :: Maybe String- , stream :: Maybe String- }- | FileHandler { level :: Maybe String- , filterer :: Maybe [String]- , formatter :: Maybe String- , file :: FilePath- , encoding :: Maybe String- -- ^ See 'System.IO.mkTextEncoding',- -- default is utf8.- }- | RotatingFileHandler { level :: Maybe String- , filterer :: Maybe [String]- , formatter :: Maybe String- , file :: FilePath- , encoding :: Maybe String- -- ^ See 'System.IO.mkTextEncoding',- -- default is utf8.- , maxBytes :: Maybe Int- -- ^ Default is 100 MB.- , backupCount :: Maybe Int- -- ^ Default is 10.- }- | TimeRotatingFileHandler { level :: Maybe String- , filterer :: Maybe [String]- , formatter :: Maybe String- , file :: FilePath- , encoding :: Maybe String- , timezone :: Maybe String- -- ^ If not set, same as 'Manager''s- -- timezone.- , rotateTime :: Maybe String- -- ^ Indicates when to rotate file,- -- e.g. @D3@ means every 3 days,- -- @W4@ means at 0 clock of 'Thursday',- -- try showing 'RotateTime'.- , backupCount :: Maybe Int- }- -- ^ @since 0.7.0- deriving (Generic, Show)+{-| A datatype used to decode json or yaml into 'Handler' datatypes. -instance FromJSON Handler where+All 'HKD' wrapped and 'Maybe' fields are optional fields, they can be omitted.++There are tow config file templates in "Logging.Config.Json" and+"Logging.Config.Yaml".++Note: When decoding from json or yaml, use 'type' field to specify handler type.+-}+data HandlerH f = StreamHandler { level :: HKD T.Level f+ -- ^ Default is @NOTSET@.+ , filterer :: HKD T.Filterer f+ -- ^ Default is [].+ , formatter :: HKD String f+ -- ^ It represents key of 'ConfigH''s+ -- formatters.+ , stream :: HKD String f+ -- ^ Only support 'stderr' and 'stdout',+ -- default is 'stderr'.+ }++ | FileHandler { level :: HKD T.Level f+ -- ^ Default is @NOTSET@.+ , filterer :: HKD T.Filterer f+ -- ^ Default is [].+ , formatter :: HKD String f+ -- ^ It represents key of 'ConfigH''s+ -- formatters.+ , file :: FilePath+ , encoding :: HKD String f+ -- ^ See 'System.IO.mkTextEncoding',+ -- default is utf8.+ }+ | RotatingFileHandler { level :: HKD T.Level f+ -- ^ Default is @NOTSET@.+ , filterer :: HKD T.Filterer f+ -- ^ Default is [].+ , formatter :: HKD String f+ -- ^ It represents key of 'ConfigH''s+ -- formatters.+ , file :: FilePath+ , encoding :: HKD String f+ -- ^ See 'System.IO.mkTextEncoding',+ -- default is utf8.+ , maxBytes :: HKD Int f+ -- ^ Default is 100 MB.+ , backupCount :: HKD Int f+ -- ^ Default is 10.+ }+ | TimeRotatingFileHandler { level :: HKD T.Level f+ -- ^ Default is @NOTSET@.+ , filterer :: HKD T.Filterer f+ -- ^ Default is [].+ , formatter :: HKD String f+ -- ^ It represents key of 'ConfigH''s+ -- formatters.+ , file :: FilePath+ , encoding :: HKD String f+ , timezone :: Maybe String+ -- ^ If not set, same as 'Manager''s+ -- timezone.+ , rotateTime :: HKD T.RotateTime f+ -- ^ Indicates when to rotate file,+ -- e.g. @D3@ means every 3 days,+ -- @W4@ means at 0 clock of 'Thursday',+ -- try reading and showing+ -- 'RotateTime'.+ , backupCount :: HKD Int f+ }+ -- ^ @since 0.7.0+ deriving Generic++instance FromJSON (HandlerH Maybe) where parseJSON = genericParseJSON option where sumEncoding = defaultTaggedObject { tagFieldName = "type" } option = defaultOptions { sumEncoding = sumEncoding } +instance Default HandlerH where+ constrDef constr+ | constr == "StreamHandler" = StreamHandler{..}+ | constr == "FileHandler" = FileHandler{..}+ | constr == "RotatingFileHandler" = RotatingFileHandler{..}+ | constr == "TimeRotatingFileHandler" = TimeRotatingFileHandler{..}+ where+ (level, filterer, formatter) = ("NOTSET", [], "{message}")+ (stream, file, encoding) = ("stderr", "./logging.log", "UTF-8")+ (maxBytes, backupCount) = (104857600, 10)+ (timezone, rotateTime) = (Nothing, T.Day 1) -data Sink = Sink { level :: Maybe String- , filterer :: Maybe [String]- , handlers :: Maybe [String]- , propagate :: Maybe Bool- , disabled :: Maybe Bool- } deriving (Generic, Show)+type Handler = HandlerH Identity -instance FromJSON Sink +--------------------------------------------------------------------------------+data SinkH f = Sink { level :: HKD T.Level f+ -- Default is "NOTSET"+ , filterer :: HKD T.Filterer f+ -- Default is []+ , handlers :: HKD [String] f+ -- ^ A list of key of 'ConfigH''s handlers+ , propagate :: HKD Bool f+ , disabled :: HKD Bool f+ } deriving Generic -data Config = Config { sinks :: Maybe (Map String Sink)- , handlers :: Maybe (Map String Handler)- , formatters :: Maybe (Map String String)- , timezone :: Maybe String -- ^ @since 0.7.0- , disabled :: Maybe Bool- } deriving (Generic, Show)+instance FromJSON (SinkH Maybe) -instance FromJSON Config+instance Default SinkH where+ constrDef _ = Sink "NOTSET" [] [] True False +type Sink = SinkH Identity ++--------------------------------------------------------------------------------+data ConfigH f = Config { sinks :: HKD (MapH String SinkH f) f+ , handlers :: HKD (MapH String HandlerH f) f+ , formatters :: HKD (Map String String) f+ -- See "Format1" to learn how to write format string.+ , timezone :: Maybe String -- ^ @since 0.7.0+ -- Default is the result of 'getCurrentTimeZone'.+ , disabled :: HKD Bool f+ } deriving Generic++instance FromJSON (ConfigH Maybe)++instance Default ConfigH where+ constrDef _ = Config mempty mempty mempty Nothing False++type Config = ConfigH Identity+++-------------------------------------------------------------------------------- newtype ConfigException = ConfigException { message :: String } instance Show ConfigException where@@ -117,80 +187,53 @@ instance Exception ConfigException +--------------------------------------------------------------------------------+{-| Create "Logging.Manager.Manager" from 'Config'.++This function is only used by "Logging.Config.Json" and "Logging.Config.Yaml",+use functions provided in these two modules directly.+-} createManager :: Config -> IO T.Manager createManager Config{..} = do- handlers <- mapM (createHandler formatters') $ fromMaybe empty handlers- sinks <- sequence $ mapWithKey (createSink handlers) $ fromMaybe empty sinks- defaultTimezone <- getCurrentTimeZone-- let root = findWithDefault defaultRoot "root" sinks- sinks' = delete "root" sinks- timezone' = maybe defaultTimezone read timezone- disabled' = fromMaybe False disabled- return $ T.Manager root sinks' timezone' disabled' False+ hs <- mapM (createHandler $ map fromString formatters) $ unMapH handlers+ ss <- sequence $ mapWithKey (createSink hs) $ unMapH sinks+ timezone <- maybe getCurrentTimeZone (pure . read) timezone+ let root = findWithDefault defaultRoot "root" ss+ sinks = delete "root" ss+ catchUncaughtException = False -- TODO remove+ return $ T.Manager{..} where- formatters' :: Map String Format1- formatters' = maybe (singleton "" "{message}") (map fromString) formatters- stderrHandler :: T.SomeHandler- stderrHandler = T.toHandler $ T.StreamHandler def [] "{message}" stderr+ stderrHandler = T.toHandler $ T.StreamHandler "NOTSET" [] "{message}" stderr defaultRoot :: T.Sink defaultRoot = T.Sink "" "DEBUG" [] [stderrHandler] False False createSink :: Map String T.SomeHandler -> String -> Sink -> IO T.Sink createSink hs logger Sink{..} = do- let logger' = if logger == "root" then "" else logger- level' = maybe def read level- filterer' = maybe [] (P.map fromString) filterer- handlers' = [hs ! h | h <- fromMaybe [] handlers]- disabled' = fromMaybe False disabled- propagate' = fromMaybe False propagate- return $ T.Sink logger' level' filterer' handlers' disabled' propagate'-- getStream :: String -> Handle- getStream "stderr" = stderr- getStream "stdout" = stdout- getStream _ = error "Logging.Config: no parse (stream)"+ logger <- pure $ if logger == "root" then "" else logger+ handlers <- pure [hs ! h | h <- handlers]+ return $ T.Sink{..} createHandler :: Map String Format1 -> Handler -> IO T.SomeHandler createHandler fs StreamHandler{..} = do- let level' = maybe def read level- filterer' = maybe [] (P.map fromString) filterer- formatter' = maybe "{message}" ((!) fs) formatter- stream' = getStream $ fromMaybe "stderr" stream- return $ T.toHandler $ T.StreamHandler level' filterer' formatter' stream'+ formatter <- pure $ fs ! formatter+ stream <- pure $ mkStdHandle stream+ return $ T.toHandler $ T.StreamHandler{..} createHandler fs FileHandler{..} = do- let level' = maybe def read level- filterer' = maybe [] (P.map fromString) filterer- formatter' = maybe "{message}" ((!) fs) formatter- encoding' <- mkTextEncoding $ fromMaybe "utf8" encoding+ formatter <- pure $ fs ! formatter+ encoding <- mkTextEncoding encoding stream <- newIORef undefined- return $ T.toHandler $- T.FileHandler level' filterer' formatter' file encoding' stream+ return $ T.toHandler $ T.FileHandler{..} createHandler fs RotatingFileHandler{..} = do- let level' = maybe def read level- filterer' = maybe [] (P.map fromString) filterer- formatter' = maybe "{message}" ((!) fs) formatter- maxBytes' = fromMaybe 104857600 maxBytes- backupCount' = fromMaybe 10 backupCount- encoding' <- mkTextEncoding $ fromMaybe "utf8" encoding+ formatter <- return $ fs ! formatter+ encoding <- mkTextEncoding encoding stream <- newEmptyMVar- return $ T.toHandler $- T.RotatingFileHandler level' filterer' formatter' file encoding'- maxBytes' backupCount' stream+ return $ T.toHandler $ T.RotatingFileHandler{..} createHandler fs TimeRotatingFileHandler{timezone=tz, ..} = do- let level' = maybe def read level- filterer' = maybe [] (P.map fromString) filterer- formatter' = maybe "{message}" ((!) fs) formatter- tz' = mappend tz timezone- rotateTime' = maybe (T.Day 1) read rotateTime- backupCount' = fromMaybe 10 backupCount- encoding' <- mkTextEncoding $ fromMaybe "utf8" encoding- timezone' <- maybe getCurrentTimeZone (return . read) tz'+ formatter <- pure $ fs ! formatter+ timezone <- maybe getCurrentTimeZone (pure . read) $ mappend tz timezone+ encoding <- mkTextEncoding encoding rotateAt <- newIORef =<< getCurrentTime stream <- newEmptyMVar- return $ T.toHandler $- T.TimeRotatingFileHandler level' filterer' formatter' file encoding'- timezone' rotateTime' backupCount' rotateAt- stream+ return $ T.toHandler $ T.TimeRotatingFileHandler{..}
src/Logging/Config/Yaml.hs view
@@ -1,7 +1,6 @@-{-| This module provides a convenient way to decode a 'Manager' from yaml string-+{-|This module provides a convenient way to decode a 'Manager' from yaml string -Here is a yaml string template:+=== Template @ sinks:@@ -28,13 +27,13 @@ type: FileHandler level: "Level 100" formatter: standard- file: /etc/my/json.log+ file: \/var\/log\/my\/yaml.log encoding: utf8 rotate: type: RotatingFileHandler level: INFO formatter: standard- file: /etc/my/json.log+ file: \/var\/log\/my\/yaml.log encoding: utf8 maxBytes: 1048576 backupCount: 10@@ -44,10 +43,7 @@ standard: "{asctime:%Y-%m-%dT%H:%M:%S%6Q%z} - {level} - {logger}] {message}" disabled: false-- catchUncaughtException: true @- -} module Logging.Config.Yaml (getManager, getManagerFile) where @@ -60,9 +56,11 @@ import Logging.Manager --- | decode a 'Manager' from strict 'ByteString'------ @since 0.4.0+{-| decode a 'Manager' from strict 'ByteString', it+1) decodes 'Config' from json string, 2) constructs an 'Manager'++ @since 0.4.0+-} getManager :: ByteString -> IO Manager getManager bs = case decodeEither' bs of@@ -70,9 +68,11 @@ Right config -> createManager config --- | decode a 'Manager' from a file------ @since 0.4.0+{- | decode a 'Manager' from a file, it+1) reads json string from file, 2) calls 'getManager' to construct an 'Manager'++@since 0.4.0+-} getManagerFile :: FilePath -> IO Manager getManagerFile path = do res <- decodeFileEither path
src/Logging/Filter.hs view
@@ -3,6 +3,7 @@ module Logging.Filter ( Filter(..), Filterer ) where +import Data.Aeson import Data.List (stripPrefix) import Data.String import Prelude hiding (filter)@@ -49,6 +50,9 @@ Just ('.':_) -> True _ -> False filter (Filter _ (Just f)) rcd = f rcd++instance FromJSON Filter where+ parseJSON = (fmap read) . parseJSON -- |List of Filter
src/Logging/Global.hs view
@@ -1,11 +1,14 @@-{-| Run logging globally.+{-|+Copyright : (c) 2019 Version Cloud+License : BSD3+Maintainer : Jorah Gao <jorah@version.cloud>+Stability : experimental+Portability : portable +Run logging globally.+ The 'run' function will properly "initialize" and "terminate" the global "Manager".--If the "Manager"\'s 'catchUncaughtException' is True, the 'run' function will-set an uncaught exception handler that will log all uncaught exceptions,- and set the handler to the original one before 'run' is complete. -} module Logging.Global
src/Logging/Global/Internal.hs view
@@ -77,4 +77,5 @@ unknownLoc = ("unknown file", "unknown package", "unknown module", 0) logThrow :: SomeException -> IO a+ logThrow e | Just UserInterrupt <- fromException e = throw e logThrow e = log "" "ERROR" e Null unknownLoc >> throw e
src/Logging/Handler/TimeRotatingFileHandler.hs view
@@ -10,6 +10,7 @@ import Control.Concurrent.MVar import Control.Monad+import Data.Aeson import Data.Char import Data.IORef import Data.List@@ -144,6 +145,9 @@ show (Hour n) = 'H' : (show n) show (WeekDay n) = 'W' : (show $ fromEnum n) show (Day n) = 'D' : (show n)++instance FromJSON RotateTime where+ parseJSON = (fmap read) . parseJSON interval :: RotateTime -> NominalDiffTime
src/Logging/Level.hs view
@@ -2,8 +2,8 @@ module Logging.Level ( Level(..) ) where -import Data.Default-import Data.List (stripPrefix)+import Data.Aeson+import Data.List (stripPrefix) import Data.String import Text.Format @@ -53,8 +53,8 @@ toEnum = Level fromEnum (Level v) = v -instance Default Level where- def = "NOTSET"- instance FormatArg Level where formatArg = formatArg . show++instance FromJSON Level where+ parseJSON = (fmap read) . parseJSON
src/Logging/Monad.hs view
@@ -1,10 +1,14 @@-{-| Run logging in a 'Reader' monad.+{-|+Copyright : (c) 2019 Version Cloud+License : BSD3+Maintainer : Jorah Gao <jorah@version.cloud>+Stability : experimental+Portability : portable +Run logging in a 'Reader' monad.+ You should 'initialize' the 'Manager' before using it in the 'Reader' monad, and 'terminate' it when it is no longer needed.--If you want to log uncaught exceptions,-see "GHC.Conc.setUncaughtExceptionHandler". -}
src/Logging/Monad/Internal.hs view
@@ -14,12 +14,10 @@ import Control.Concurrent import Control.Exception (SomeException, bracket_)-import Control.Lens (view) import Control.Monad import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Reader import Data.Aeson-import Data.Default import Data.Generics.Product.Typed import Data.IORef import Data.List (dropWhileEnd, group)@@ -28,6 +26,7 @@ import Data.Time.Clock.POSIX import Data.Time.LocalTime import GHC.Conc (setUncaughtExceptionHandler)+import Lens.Micro.Extras (view) import Prelude hiding (filter, log) import System.FilePath import System.IO (stderr, stdout)
src/Logging/Prelude.hs view
@@ -16,6 +16,7 @@ , lastModifyTime , modifyBaseName , appendBaseName+ , mkStdHandle ) where import Control.Concurrent@@ -97,3 +98,9 @@ appendBaseName :: FilePath -> String -> FilePath appendBaseName file suffix = modifyBaseName file (++ suffix)+++mkStdHandle :: String -> Handle+mkStdHandle "stderr" = stderr+mkStdHandle "stdout" = stdout+mkStdHandle _ = error "unknown std handle name"
− src/Logging/TH.hs
@@ -1,5 +0,0 @@-module Logging.TH {-# DEPRECATED "Renamed as Logging.Global.TH" #-}- ( module Logging.Global.TH- ) where--import Logging.Global.TH
− src/Logging/Types.hs
@@ -1,23 +0,0 @@-module Logging.Types {-# DEPRECATED "This module has been flattened, use relevant modules directly" #-}- ( module Logging.Logger- , module Logging.Level- , module Logging.Filter- , module Logging.Record- , module Logging.Handler.FileHandler- , module Logging.Handler.RotatingFileHandler- , module Logging.Handler.StreamHandler- , module Logging.Sink- , module Logging.Manager- , module Logging.Class- ) where--import Logging.Class-import Logging.Filter-import Logging.Handler.FileHandler-import Logging.Handler.RotatingFileHandler-import Logging.Handler.StreamHandler-import Logging.Level-import Logging.Logger-import Logging.Manager-import Logging.Record-import Logging.Sink
− src/Logging/Utils.hs
@@ -1,6 +0,0 @@-module Logging.Utils {-# DEPRECATED "Renamed as Logging.Prelude" #-}- ( module Logging.Prelude- ) where---import Logging.Prelude
test/LoggingTest/TypesSpec.hs view
@@ -5,9 +5,7 @@ module LoggingTest.TypesSpec ( spec ) where import Control.Concurrent.MVar-import Control.Lens hiding (Level (..)) import Control.Monad-import Data.Default (def) import Data.Generics.Product.Typed import Data.IORef import Data.List (isPrefixOf)@@ -15,6 +13,7 @@ import Data.Time.Clock import Data.Time.Clock.POSIX import Data.Time.LocalTime+import Lens.Micro import Prelude hiding (filter) import System.Directory import System.FilePath@@ -76,15 +75,15 @@ timeRotateRef <- runIO $ newIORef undefined timeFileMVar <- runIO newEmptyMVar runIO $ hSetEncoding read utf8 >> hSetEncoding write utf8- let handler = toHandler $ StreamHandler def [] "{message}" write+ let handler = toHandler $ StreamHandler "NOTSET" [] "{message}" write fileHandler = toHandler $- FileHandler def [] "{message}" "/tmp/log4hs/file.log" utf8 fileRef+ FileHandler "NOTSET" [] "{message}" "/tmp/log4hs/file.log" utf8 fileRef rotateHandler = toHandler $- RotatingFileHandler def [] "{message}" "/tmp/log4hs/rotate.log" utf8- 1000 5 rotateFileMVar+ RotatingFileHandler "NOTSET" [] "{message}" "/tmp/log4hs/rotate.log"+ utf8 1000 5 rotateFileMVar timeHandler = toHandler $- TimeRotatingFileHandler def [] "{message}" "/tmp/log4hs/time.log" utf8- utc (Minute 5) 5 timeRotateRef timeFileMVar+ TimeRotatingFileHandler "NOTSET" [] "{message}" "/tmp/log4hs/time.log"+ utf8 utc (Minute 5) 5 timeRotateRef timeFileMVar runIO $ open handler >> open fileHandler >> open rotateHandler prop "filter" $ \(rcd@LogRecord{..}) -> monadicIO $ do