packages feed

caster 0.0.2.1 → 0.0.3.0

raw patch · 7 files changed

+351/−191 lines, 7 filesdep +mtlPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: mtl

API changes (from Hackage documentation)

- System.Log.Caster: instance GHC.Classes.Eq System.Log.Caster.LogLevel
- System.Log.Caster: instance GHC.Classes.Ord System.Log.Caster.LogLevel
- System.Log.Caster: instance GHC.Show.Show System.Log.Caster.LogLevel
- System.Log.Caster: instance GHC.Show.Show a => System.Log.Caster.ToBuilder a
- System.Log.Caster: instance System.Log.Caster.ToBuilder Data.ByteString.Builder.Internal.Builder
- System.Log.Caster: instance System.Log.Caster.ToBuilder Data.ByteString.FastBuilder.Internal.Builder
- System.Log.Caster: instance System.Log.Caster.ToBuilder Data.ByteString.Internal.ByteString
- System.Log.Caster: instance System.Log.Caster.ToBuilder Data.ByteString.Lazy.Internal.ByteString
- System.Log.Caster: instance System.Log.Caster.ToBuilder Data.Text.Internal.Lazy.Text
- System.Log.Caster: instance System.Log.Caster.ToBuilder Data.Text.Internal.Text
- System.Log.Caster: instance System.Log.Caster.ToBuilder GHC.Base.String
+ System.Log.Caster: terminalFormatterWith :: Builder -> Builder -> Builder -> Builder -> Builder -> Builder -> Builder -> Builder -> Formatter
+ System.Log.Caster.Core: ($:) :: ToBuilder b => (Builder -> b) -> Builder -> b
+ System.Log.Caster.Core: (<:>) :: (ToBuilder a, ToBuilder b) => a -> b -> Builder
+ System.Log.Caster.Core: LogAlert :: LogLevel
+ System.Log.Caster.Core: LogChan :: TChan LogMsg -> LogChan
+ System.Log.Caster.Core: LogCritical :: LogLevel
+ System.Log.Caster.Core: LogDebug :: LogLevel
+ System.Log.Caster.Core: LogEmergency :: LogLevel
+ System.Log.Caster.Core: LogError :: LogLevel
+ System.Log.Caster.Core: LogInfo :: LogLevel
+ System.Log.Caster.Core: LogMsg :: LogLevel -> UnixTime -> Builder -> LogMsg
+ System.Log.Caster.Core: LogNotice :: LogLevel
+ System.Log.Caster.Core: LogQueue :: TQueue LogMsg -> LogQueue
+ System.Log.Caster.Core: LogWarn :: LogLevel
+ System.Log.Caster.Core: [logMsgBuilder] :: LogMsg -> Builder
+ System.Log.Caster.Core: [logMsgLevel] :: LogMsg -> LogLevel
+ System.Log.Caster.Core: [logMsgTime] :: LogMsg -> UnixTime
+ System.Log.Caster.Core: broadcastLog :: LogQueue -> LogChan -> IO ()
+ System.Log.Caster.Core: class ToBuilder a
+ System.Log.Caster.Core: data LogLevel
+ System.Log.Caster.Core: data LogMsg
+ System.Log.Caster.Core: defaultFormatter :: Formatter
+ System.Log.Caster.Core: fix :: Builder -> Builder
+ System.Log.Caster.Core: handleListener :: Formatter -> Handle -> Listener
+ System.Log.Caster.Core: handleListenerFlush :: Formatter -> Handle -> Listener
+ System.Log.Caster.Core: infixr 0 $:
+ System.Log.Caster.Core: infixr 6 <:>
+ System.Log.Caster.Core: instance GHC.Classes.Eq System.Log.Caster.Core.LogLevel
+ System.Log.Caster.Core: instance GHC.Classes.Ord System.Log.Caster.Core.LogLevel
+ System.Log.Caster.Core: instance GHC.Show.Show System.Log.Caster.Core.LogLevel
+ System.Log.Caster.Core: instance GHC.Show.Show a => System.Log.Caster.Core.ToBuilder a
+ System.Log.Caster.Core: instance System.Log.Caster.Core.ToBuilder Data.ByteString.Builder.Internal.Builder
+ System.Log.Caster.Core: instance System.Log.Caster.Core.ToBuilder Data.ByteString.FastBuilder.Internal.Builder
+ System.Log.Caster.Core: instance System.Log.Caster.Core.ToBuilder Data.ByteString.Internal.ByteString
+ System.Log.Caster.Core: instance System.Log.Caster.Core.ToBuilder Data.ByteString.Lazy.Internal.ByteString
+ System.Log.Caster.Core: instance System.Log.Caster.Core.ToBuilder Data.Text.Internal.Lazy.Text
+ System.Log.Caster.Core: instance System.Log.Caster.Core.ToBuilder Data.Text.Internal.Text
+ System.Log.Caster.Core: instance System.Log.Caster.Core.ToBuilder GHC.Base.String
+ System.Log.Caster.Core: newLogChan :: IO LogChan
+ System.Log.Caster.Core: newLogQueue :: IO LogQueue
+ System.Log.Caster.Core: newtype LogChan
+ System.Log.Caster.Core: newtype LogQueue
+ System.Log.Caster.Core: relayLog :: LogChan -> LogLevel -> Listener -> IO ()
+ System.Log.Caster.Core: stdoutListener :: Listener
+ System.Log.Caster.Core: stdoutListenerWith :: Formatter -> Listener
+ System.Log.Caster.Core: terminalFormatter :: Formatter
+ System.Log.Caster.Core: terminalFormatterWith :: Builder -> Builder -> Builder -> Builder -> Builder -> Builder -> Builder -> Builder -> Formatter
+ System.Log.Caster.Core: terminalListener :: Listener
+ System.Log.Caster.Core: toBuilder :: ToBuilder a => a -> Builder
+ System.Log.Caster.Core: type Formatter = LogMsg -> Builder
+ System.Log.Caster.Core: type Listener = LogMsg -> IO ()
+ System.Log.Caster.Monad: ($:) :: ToBuilder b => (Builder -> b) -> Builder -> b
+ System.Log.Caster.Monad: (<:>) :: (ToBuilder a, ToBuilder b) => a -> b -> Builder
+ System.Log.Caster.Monad: LogAlert :: LogLevel
+ System.Log.Caster.Monad: LogChan :: TChan LogMsg -> LogChan
+ System.Log.Caster.Monad: LogCritical :: LogLevel
+ System.Log.Caster.Monad: LogDebug :: LogLevel
+ System.Log.Caster.Monad: LogEmergency :: LogLevel
+ System.Log.Caster.Monad: LogError :: LogLevel
+ System.Log.Caster.Monad: LogInfo :: LogLevel
+ System.Log.Caster.Monad: LogMsg :: LogLevel -> UnixTime -> Builder -> LogMsg
+ System.Log.Caster.Monad: LogNotice :: LogLevel
+ System.Log.Caster.Monad: LogQueue :: TQueue LogMsg -> LogQueue
+ System.Log.Caster.Monad: LogWarn :: LogLevel
+ System.Log.Caster.Monad: [logMsgBuilder] :: LogMsg -> Builder
+ System.Log.Caster.Monad: [logMsgLevel] :: LogMsg -> LogLevel
+ System.Log.Caster.Monad: [logMsgTime] :: LogMsg -> UnixTime
+ System.Log.Caster.Monad: alert :: (MonadCaster m, ToBuilder s) => s -> m ()
+ System.Log.Caster.Monad: broadcastLog :: LogQueue -> LogChan -> IO ()
+ System.Log.Caster.Monad: class MonadIO m => MonadCaster m
+ System.Log.Caster.Monad: class ToBuilder a
+ System.Log.Caster.Monad: critical :: (MonadCaster m, ToBuilder s) => s -> m ()
+ System.Log.Caster.Monad: data LogLevel
+ System.Log.Caster.Monad: data LogMsg
+ System.Log.Caster.Monad: debug :: (MonadCaster m, ToBuilder s) => s -> m ()
+ System.Log.Caster.Monad: defaultFormatter :: Formatter
+ System.Log.Caster.Monad: emergency :: (MonadCaster m, ToBuilder s) => s -> m ()
+ System.Log.Caster.Monad: err :: (MonadCaster m, ToBuilder s) => s -> m ()
+ System.Log.Caster.Monad: fix :: Builder -> Builder
+ System.Log.Caster.Monad: getLogQueue :: MonadCaster m => m LogQueue
+ System.Log.Caster.Monad: handleListener :: Formatter -> Handle -> Listener
+ System.Log.Caster.Monad: handleListenerFlush :: Formatter -> Handle -> Listener
+ System.Log.Caster.Monad: infixr 0 $:
+ System.Log.Caster.Monad: infixr 6 <:>
+ System.Log.Caster.Monad: info :: (MonadCaster m, ToBuilder s) => s -> m ()
+ System.Log.Caster.Monad: logAs :: (MonadCaster m, ToBuilder s) => LogLevel -> s -> m ()
+ System.Log.Caster.Monad: newLogChan :: IO LogChan
+ System.Log.Caster.Monad: newLogQueue :: IO LogQueue
+ System.Log.Caster.Monad: newtype LogChan
+ System.Log.Caster.Monad: newtype LogQueue
+ System.Log.Caster.Monad: notice :: (MonadCaster m, ToBuilder s) => s -> m ()
+ System.Log.Caster.Monad: relayLog :: LogChan -> LogLevel -> Listener -> IO ()
+ System.Log.Caster.Monad: stdoutListener :: Listener
+ System.Log.Caster.Monad: stdoutListenerWith :: Formatter -> Listener
+ System.Log.Caster.Monad: terminalFormatter :: Formatter
+ System.Log.Caster.Monad: terminalFormatterWith :: Builder -> Builder -> Builder -> Builder -> Builder -> Builder -> Builder -> Builder -> Formatter
+ System.Log.Caster.Monad: terminalListener :: Listener
+ System.Log.Caster.Monad: toBuilder :: ToBuilder a => a -> Builder
+ System.Log.Caster.Monad: type Formatter = LogMsg -> Builder
+ System.Log.Caster.Monad: type Listener = LogMsg -> IO ()
+ System.Log.Caster.Monad: warn :: (MonadCaster m, ToBuilder s) => s -> m ()

Files

CHANGELOG.md view
@@ -1,5 +1,11 @@ # Change Log +## [0.0.3 -- 2019-05-06]++### Added++- MonadCaster for ReaderT pattern.+ ## [0.0.2 -- 2019-05-04]  ### Added
README.md view
@@ -76,8 +76,9 @@  The logger requires to be given `LogQueue`,  so functions which log need to take `LogQueue` as a parameter. -You can inject `LogQueue` with Reader Monad and so on, -but I recommend to use easily [Data\.Reflection](https://hackage.haskell.org/package/reflection).+You can inject `LogQueue` with Reader Monad and so on.+`System.Log.Caster.Monad` provides `MonadCaster` for ReaderT patteren.+Another way, you can use [Data\.Reflection](https://hackage.haskell.org/package/reflection).   ```haskell
caster.cabal view
@@ -1,5 +1,5 @@ name:                caster-version:             0.0.2.1+version:             0.0.3.0 synopsis:            Multicast, thread-safe, and fast logger. description:         Please see the README on GitHub at <https://github.com/kirisaki/caster#readme> homepage:            https://github.com/kirisaki/caster#readme@@ -20,6 +20,8 @@  library   exposed-modules: System.Log.Caster+                 , System.Log.Caster.Core+                 , System.Log.Caster.Monad   build-depends:       base >= 4.10                      , bytestring >= 0.10.8.2                      , fast-builder >= 0.1.0.0@@ -45,11 +47,13 @@   hs-source-dirs:                  test   default-extensions: LambdaCase+                    , FlexibleInstances   build-depends:                 base >= 4 && < 5               , caster               , bytestring               , directory+              , mtl               , text               , fast-builder               , QuickCheck
src/System/Log/Caster.hs view
@@ -7,7 +7,6 @@ Maintainer  : Akihito KIRISAKI Stability   : experimental -}-{-# LANGUAGE UndecidableInstances #-} module System.Log.Caster   ( -- * Basics     LogMsg(..)@@ -28,6 +27,7 @@   -- * Formatter   , defaultFormatter   , terminalFormatter+  , terminalFormatterWith   -- * Log levels   , LogLevel(..)   , logAs@@ -46,195 +46,11 @@   , (<:>)   ) where +import           System.Log.Caster.Core  import           Control.Concurrent.STM-import           Control.Monad-import           Control.Monad.IO.Class      (MonadIO (..))-import qualified Data.ByteString             as SBS-import qualified Data.ByteString.Builder     as BB-import qualified Data.ByteString.FastBuilder as FB-import qualified Data.ByteString.Lazy        as LBS-import           Data.Semigroup-import qualified Data.Text                   as ST-import qualified Data.Text.Encoding          as STE-import qualified Data.Text.Lazy              as LT-import qualified Data.Text.Lazy.Encoding     as LTE-import           Data.UnixTime               (Format, UnixTime (..),-                                              formatUnixTime, getUnixTime)-import           GHC.IO.Unsafe               (unsafePerformIO)-import           System.IO                   (Handle, hFlush, stdout)---- | Types which are able to be converted into @'FB.Builder' Builder@---   @toBuilde@ encodes @String@ and @Text@ as utf-8.-class ToBuilder a where-  toBuilder :: a -> FB.Builder--instance ToBuilder FB.Builder where-  toBuilder = id--instance ToBuilder String where-  toBuilder = FB.stringUtf8--instance ToBuilder ST.Text where-  toBuilder = FB.byteString . STE.encodeUtf8--instance ToBuilder LT.Text where-  toBuilder = FB.byteString . LBS.toStrict . LTE.encodeUtf8--instance ToBuilder SBS.ByteString where-  toBuilder = FB.byteString--instance ToBuilder LBS.ByteString where-  toBuilder = FB.byteString . LBS.toStrict--instance ToBuilder BB.Builder where-  toBuilder = FB.byteString . LBS.toStrict . BB.toLazyByteString--instance {-# OVERLAPPABLE #-} Show a => ToBuilder a where-  toBuilder = FB.stringUtf8 . show---- | If you turn @OverloadedStrings@ extension on, GHC can't deduce the type of string literal.---   This function fix the type to @'FB.Builder' Builder@ without type annotation.-fix :: FB.Builder -> FB.Builder-fix = id---- | Infix version of @fix@.-infixr 0 $:-($:) :: ToBuilder b => (FB.Builder -> b) -> FB.Builder -> b-($:) = ($)---- | Concat @ToBuilder@ strings as @'FB.Builder' Builder@.-infixr 6 <:>-(<:>) :: (ToBuilder a, ToBuilder b) => a -> b -> FB.Builder-a <:> b = toBuilder a <> toBuilder b---- |Log levels. These are matched to syslog.-data LogLevel-  = LogDebug-  | LogInfo-  | LogNotice-  | LogWarn-  | LogError-  | LogCritical-  | LogAlert-  | LogEmergency-  deriving (Show, Eq, Ord)---- | Log message.-data LogMsg = LogMsg-  { logMsgLevel   :: LogLevel-  , logMsgTime    :: UnixTime-  , logMsgBuilder :: FB.Builder-  }---- | Queue of @LogMsg@.-newtype LogQueue = LogQueue (TQueue LogMsg)---- | Channel of @LogMsg@.-newtype LogChan = LogChan (TChan LogMsg)---- | Make new @LogQueue@-newLogQueue :: IO LogQueue-newLogQueue = LogQueue <$> newTQueueIO---- | Make new @LogChan@.-newLogChan :: IO LogChan-newLogChan = LogChan <$> newBroadcastTChanIO---- | Connect @LogQueue@ and @TChan@ @LogMsg@.-broadcastLog :: LogQueue -> LogChan -> IO ()-broadcastLog (LogQueue q) (LogChan c) =  forever $-  atomically $ readTQueue q >>= writeTChan c---- | Formatter.-type Formatter = LogMsg -> FB.Builder---- | IO function takes @LogMsg@.-type Listener = LogMsg -> IO ()---- | Listen @LogChan@ and give the @LogMsg@ to given @Listener@.-relayLog :: LogChan -> LogLevel -> Listener -> IO ()-relayLog (LogChan bchan) logLevel listener = do-  chan <- atomically $ dupTChan bchan-  forever $ do-    msg <- atomically $ readTChan chan-    when (logMsgLevel msg >= logLevel) $ listener msg---- | Make @Listener@ from @Handle@-handleListener :: Formatter -> Handle -> Listener-handleListener f h = FB.hPutBuilder h . f---- | Make @Listener@ flushing buffer after getting @LogMsg@-handleListenerFlush :: Formatter -> Handle -> Listener-handleListenerFlush f h msg = FB.hPutBuilder h (f msg) >> hFlush h---- | Stdout listener with @Formatter@.-stdoutListenerWith :: Formatter -> Listener-stdoutListenerWith f = handleListenerFlush f stdout---- | Stdout listener.-stdoutListener :: Listener-stdoutListener = stdoutListenerWith defaultFormatter---- | Terminal listener. Log levels are colored.-terminalListener :: Listener-terminalListener = stdoutListenerWith terminalFormatter---- | Default log formatter.-defaultFormatter :: Formatter-defaultFormatter (LogMsg lev ut str) =-  formatTime ut <> " - [" <> logLevelToBuilder lev <> "] " <> str <> "\n"---- | Formatter for term.---   It provides colored logs.-terminalFormatter :: Formatter-terminalFormatter = terminalFormatterWith-                    "\ESC[32m"-                    "\ESC[36m"-                    "\ESC[4m\ESC[36m"-                    "\ESC[4m\ESC[33m"-                    "\ESC[4m\ESC[31m"-                    "\ESC[1m\ESC[31m"-                    "\ESC[1m\ESC[35m"-                    "\ESC[5m\ESC[35m"---- | Formatter with specified colors for log levels.---   Parameters are just @FB.Builder@, so you can decorate as you like with ansi escaping.-terminalFormatterWith :: FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> Formatter-terminalFormatterWith fDebug fInfo fNotice fWarn fError fCritical fAlert fEmergency (LogMsg lev ut str) =-  formatTime ut <> " - " <> fmt <>  "[" <> logLevelToBuilder lev <> "]\ESC[0m " <> str <> "\n"-  where-    fmt = case lev of-      LogDebug     -> fDebug-      LogInfo      -> fInfo-      LogNotice    -> fNotice-      LogWarn      -> fWarn-      LogError     -> fError-      LogCritical  -> fCritical-      LogAlert     -> fAlert-      LogEmergency -> fEmergency---logLevelToBuilder :: LogLevel -> FB.Builder-logLevelToBuilder = \case-  LogDebug -> "DEBUG"-  LogInfo  -> "INFO"-  LogNotice  -> "NOTICE"-  LogWarn  -> "WARN"-  LogError -> "ERROR"-  LogCritical -> "CRITICAL"-  LogAlert -> "ALERT"-  LogEmergency -> "EMERGENCY"---{-# NOINLINE formatTime #-}-formatTime :: UnixTime -> FB.Builder-formatTime ut =-  let-    ut' = FB.byteString . unsafePerformIO $ formatUnixTime "%Y-%m-%d %T" ut-    utMilli = FB.string7 . tail . show $ utMicroSeconds ut `div` 1000 + 1000-  in-    ut' <> "." <> utMilli+import           Control.Monad.IO.Class (MonadIO (..))+import           Data.UnixTime          (getUnixTime)  -- | Push a message to @LogQueue@. logAs :: (MonadIO m, ToBuilder s) => LogQueue -> LogLevel -> s -> m ()
+ src/System/Log/Caster/Core.hs view
@@ -0,0 +1,227 @@+{-|+Module      : System.Log.Caster.Core+Description : Multicast, thread-safe, and not slow logger.+Copyright   : (c) Akihito KIRISAKI++License     : BSD3+Maintainer  : Akihito KIRISAKI+Stability   : experimental+-}+{-# LANGUAGE UndecidableInstances #-}+module System.Log.Caster.Core+  ( -- * Basics+    LogMsg(..)+  , broadcastLog+  , LogQueue(..)+  , newLogQueue+  , LogChan(..)+  , newLogChan+  , Formatter+  , Listener+  , relayLog+  -- * Listeners+  , stdoutListener+  , stdoutListenerWith+  , terminalListener+  , handleListener+  , handleListenerFlush+  -- * Formatter+  , defaultFormatter+  , terminalFormatter+  , terminalFormatterWith+  -- * Log levels+  , LogLevel(..)+  -- * Useful string class and operator+  , ToBuilder(..)+  , fix+  , ($:)+  , (<:>)+  ) where++import           Control.Concurrent.STM+import           Control.Monad+import qualified Data.ByteString             as SBS+import qualified Data.ByteString.Builder     as BB+import qualified Data.ByteString.FastBuilder as FB+import qualified Data.ByteString.Lazy        as LBS+import           Data.Semigroup+import qualified Data.Text                   as ST+import qualified Data.Text.Encoding          as STE+import qualified Data.Text.Lazy              as LT+import qualified Data.Text.Lazy.Encoding     as LTE+import           Data.UnixTime               (Format, UnixTime (..),+                                              formatUnixTime)+import           GHC.IO.Unsafe               (unsafePerformIO)+import           System.IO                   (Handle, hFlush, stdout)++-- | Types which are able to be converted into @'FB.Builder' Builder@+--   @toBuilde@ encodes @String@ and @Text@ as utf-8.+class ToBuilder a where+  toBuilder :: a -> FB.Builder++instance ToBuilder FB.Builder where+  toBuilder = id++instance ToBuilder String where+  toBuilder = FB.stringUtf8++instance ToBuilder ST.Text where+  toBuilder = FB.byteString . STE.encodeUtf8++instance ToBuilder LT.Text where+  toBuilder = FB.byteString . LBS.toStrict . LTE.encodeUtf8++instance ToBuilder SBS.ByteString where+  toBuilder = FB.byteString++instance ToBuilder LBS.ByteString where+  toBuilder = FB.byteString . LBS.toStrict++instance ToBuilder BB.Builder where+  toBuilder = FB.byteString . LBS.toStrict . BB.toLazyByteString++instance {-# OVERLAPPABLE #-} Show a => ToBuilder a where+  toBuilder = FB.stringUtf8 . show++-- | If you turn @OverloadedStrings@ extension on, GHC can't deduce the type of string literal.+--   This function fix the type to @'FB.Builder' Builder@ without type annotation.+fix :: FB.Builder -> FB.Builder+fix = id++-- | Infix version of @fix@.+infixr 0 $:+($:) :: ToBuilder b => (FB.Builder -> b) -> FB.Builder -> b+($:) = ($)++-- | Concat @ToBuilder@ strings as @'FB.Builder' Builder@.+infixr 6 <:>+(<:>) :: (ToBuilder a, ToBuilder b) => a -> b -> FB.Builder+a <:> b = toBuilder a <> toBuilder b++-- |Log levels. These are matched to syslog.+data LogLevel+  = LogDebug+  | LogInfo+  | LogNotice+  | LogWarn+  | LogError+  | LogCritical+  | LogAlert+  | LogEmergency+  deriving (Show, Eq, Ord)++-- | Log message.+data LogMsg = LogMsg+  { logMsgLevel   :: LogLevel+  , logMsgTime    :: UnixTime+  , logMsgBuilder :: FB.Builder+  }++-- | Queue of @LogMsg@.+newtype LogQueue = LogQueue (TQueue LogMsg)++-- | Channel of @LogMsg@.+newtype LogChan = LogChan (TChan LogMsg)++-- | Make new @LogQueue@+newLogQueue :: IO LogQueue+newLogQueue = LogQueue <$> newTQueueIO++-- | Make new @LogChan@.+newLogChan :: IO LogChan+newLogChan = LogChan <$> newBroadcastTChanIO++-- | Connect @LogQueue@ and @TChan@ @LogMsg@.+broadcastLog :: LogQueue -> LogChan -> IO ()+broadcastLog (LogQueue q) (LogChan c) =  forever $+  atomically $ readTQueue q >>= writeTChan c++-- | Formatter.+type Formatter = LogMsg -> FB.Builder++-- | IO function takes @LogMsg@.+type Listener = LogMsg -> IO ()++-- | Listen @LogChan@ and give the @LogMsg@ to given @Listener@.+relayLog :: LogChan -> LogLevel -> Listener -> IO ()+relayLog (LogChan bchan) logLevel listener = do+  chan <- atomically $ dupTChan bchan+  forever $ do+    msg <- atomically $ readTChan chan+    when (logMsgLevel msg >= logLevel) $ listener msg++-- | Make @Listener@ from @Handle@+handleListener :: Formatter -> Handle -> Listener+handleListener f h = FB.hPutBuilder h . f++-- | Make @Listener@ flushing buffer after getting @LogMsg@+handleListenerFlush :: Formatter -> Handle -> Listener+handleListenerFlush f h msg = FB.hPutBuilder h (f msg) >> hFlush h++-- | Stdout listener with @Formatter@.+stdoutListenerWith :: Formatter -> Listener+stdoutListenerWith f = handleListenerFlush f stdout++-- | Stdout listener.+stdoutListener :: Listener+stdoutListener = stdoutListenerWith defaultFormatter++-- | Terminal listener. Log levels are colored.+terminalListener :: Listener+terminalListener = stdoutListenerWith terminalFormatter++-- | Default log formatter.+defaultFormatter :: Formatter+defaultFormatter (LogMsg lev ut str) =+  formatTime ut <> " - [" <> logLevelToBuilder lev <> "] " <> str <> "\n"++-- | Formatter for term.+--   It provides colored logs.+terminalFormatter :: Formatter+terminalFormatter = terminalFormatterWith+                    "\ESC[32m"+                    "\ESC[36m"+                    "\ESC[4m\ESC[36m"+                    "\ESC[4m\ESC[33m"+                    "\ESC[4m\ESC[31m"+                    "\ESC[1m\ESC[31m"+                    "\ESC[1m\ESC[35m"+                    "\ESC[5m\ESC[35m"++-- | Formatter with specified colors for log levels.+--   Parameters are just @FB.Builder@, so you can decorate as you like with ansi escaping.+terminalFormatterWith :: FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> FB.Builder -> Formatter+terminalFormatterWith fDebug fInfo fNotice fWarn fError fCritical fAlert fEmergency (LogMsg lev ut str) =+  formatTime ut <> " - " <> fmt <>  "[" <> logLevelToBuilder lev <> "]\ESC[0m " <> str <> "\n"+  where+    fmt = case lev of+      LogDebug     -> fDebug+      LogInfo      -> fInfo+      LogNotice    -> fNotice+      LogWarn      -> fWarn+      LogError     -> fError+      LogCritical  -> fCritical+      LogAlert     -> fAlert+      LogEmergency -> fEmergency+++logLevelToBuilder :: LogLevel -> FB.Builder+logLevelToBuilder = \case+  LogDebug -> "DEBUG"+  LogInfo  -> "INFO"+  LogNotice  -> "NOTICE"+  LogWarn  -> "WARN"+  LogError -> "ERROR"+  LogCritical -> "CRITICAL"+  LogAlert -> "ALERT"+  LogEmergency -> "EMERGENCY"+++{-# NOINLINE formatTime #-}+formatTime :: UnixTime -> FB.Builder+formatTime ut =+  let+    ut' = FB.byteString . unsafePerformIO $ formatUnixTime "%Y-%m-%d %T" ut+    utMilli = FB.string7 . tail . show $ utMicroSeconds ut `div` 1000 + 1000+  in+    ut' <> "." <> utMilli
+ src/System/Log/Caster/Monad.hs view
@@ -0,0 +1,79 @@+{-|+Module      : System.Log.Caster.Monad+Description : Multicast, thread-safe, and not slow logger.+Copyright   : (c) Akihito KIRISAKI++License     : BSD3+Maintainer  : Akihito KIRISAKI+Stability   : experimental+-}+module System.Log.Caster.Monad+  ( -- * Basics+    LogMsg(..)+  , broadcastLog+  , LogQueue(..)+  , newLogQueue+  , LogChan(..)+  , newLogChan+  , Formatter+  , Listener+  , relayLog+  -- * Listeners+  , stdoutListener+  , stdoutListenerWith+  , terminalListener+  , handleListener+  , handleListenerFlush+  -- * Formatter+  , defaultFormatter+  , terminalFormatter+  , terminalFormatterWith+  -- * Log levels+  , LogLevel(..)+  , MonadCaster(..)+  -- * Useful string class and operator+  , ToBuilder(..)+  , fix+  , ($:)+  , (<:>)+  ) where++import           System.Log.Caster.Core++import           Control.Concurrent.STM+import           Control.Monad.IO.Class (MonadIO (..))+import           Data.UnixTime          (getUnixTime)++-- | Logging monad for ReaderT pattern.+class MonadIO m => MonadCaster m where+  getLogQueue :: m LogQueue++  logAs :: ToBuilder s => LogLevel -> s -> m ()+  logAs l s = do+    ut <- liftIO getUnixTime+    LogQueue q <- getLogQueue+    liftIO . atomically $ writeTQueue q (LogMsg l ut (toBuilder s))++  debug :: ToBuilder s => s -> m ()+  debug = logAs LogDebug++  info :: ToBuilder s => s -> m ()+  info = logAs LogInfo++  notice :: ToBuilder s => s -> m ()+  notice = logAs LogNotice++  warn :: ToBuilder s => s -> m ()+  warn = logAs LogWarn++  err :: ToBuilder s => s -> m ()+  err = logAs LogError++  critical :: ToBuilder s => s -> m ()+  critical = logAs LogCritical++  alert :: ToBuilder s => s -> m ()+  alert = logAs LogAlert++  emergency :: ToBuilder s => s -> m ()+  emergency = logAs LogEmergency
test/CasterTest.hs view
@@ -2,6 +2,8 @@ module CasterTest where  import           System.Log.Caster+import qualified System.Log.Caster.Monad     as CM+ import           Test.QuickCheck.Instances import           Test.QuickCheck.Monadic     as QCM import           Test.Tasty@@ -11,6 +13,8 @@ import           Control.Concurrent import           Control.Concurrent.STM import           Control.Monad+import           Control.Monad.Reader        (MonadReader (..), ReaderT,+                                              runReaderT) import qualified Data.ByteString             as SBS import qualified Data.ByteString.Builder     as BB import qualified Data.ByteString.FastBuilder as FB@@ -121,4 +125,27 @@   threadDelay 100000   killThread thread0   killThread threadb+++instance CM.MonadCaster (ReaderT LogQueue IO) where+  getLogQueue = ask++unit_monad :: IO ()+unit_monad = do+  chan <- newLogChan+  lq <- newLogQueue+  thread0 <- forkIO $ relayLog chan LogDebug stdoutListener+  flip runReaderT lq $ do+    CM.debug "mona"+    CM.info "mona"+    CM.notice "mona"+    CM.warn "mona"+    CM.err "mona"+    CM.critical "mona"+    CM.alert "mona"+    CM.emergency "mona"+  threadb <- forkIO $ broadcastLog lq chan+  threadDelay 100001+  killThread threadb+  killThread thread0