packages feed

extensible-effects-concurrent 0.19.1 → 0.20.0

raw patch · 5 files changed

+295/−211 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Control.Eff.Log.Handler: askLogWriter :: SetMember LogWriterReader (LogWriterReader h) e => Eff e (LogWriter h)
- Control.Eff.Log.Handler: data LogWriterReader h v
- Control.Eff.Log.Handler: instance (Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadThrow (Control.Eff.Internal.Eff e)) => Control.Monad.Catch.MonadThrow (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.LogWriterReader h : e))
- Control.Eff.Log.Handler: instance (Control.Monad.Base.MonadBase m m, Control.Eff.Internal.LiftedBase m e, Control.Eff.Log.Writer.SupportsLogger m (Control.Eff.Log.Handler.Logs : e), Data.OpenUnion.SetMember Control.Eff.Log.Handler.LogWriterReader (Control.Eff.Log.Handler.LogWriterReader m) (Control.Eff.Log.Handler.Logs : e)) => Control.Monad.Trans.Control.MonadBaseControl m (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.Logs : e))
- Control.Eff.Log.Handler: instance (Control.Monad.Base.MonadBase m m, Control.Eff.Internal.LiftedBase m r) => Control.Monad.Trans.Control.MonadBaseControl m (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.LogWriterReader h : r))
- Control.Eff.Log.Handler: instance (GHC.Base.Applicative m, Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadCatch (Control.Eff.Internal.Eff e)) => Control.Monad.Catch.MonadCatch (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.LogWriterReader h : e))
- Control.Eff.Log.Handler: instance (GHC.Base.Applicative m, Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadCatch (Control.Eff.Internal.Eff e), Control.Eff.Log.Writer.SupportsLogger m (Control.Eff.Log.Handler.Logs : e), Data.OpenUnion.SetMember Control.Eff.Log.Handler.LogWriterReader (Control.Eff.Log.Handler.LogWriterReader m) (Control.Eff.Log.Handler.Logs : e)) => Control.Monad.Catch.MonadCatch (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.Logs : e))
- Control.Eff.Log.Handler: instance (GHC.Base.Applicative m, Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadMask (Control.Eff.Internal.Eff e)) => Control.Monad.Catch.MonadMask (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.LogWriterReader h : e))
- Control.Eff.Log.Handler: instance (GHC.Base.Applicative m, Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadMask (Control.Eff.Internal.Eff e), Control.Eff.Log.Writer.SupportsLogger m (Control.Eff.Log.Handler.Logs : e), Data.OpenUnion.SetMember Control.Eff.Log.Handler.LogWriterReader (Control.Eff.Log.Handler.LogWriterReader m) (Control.Eff.Log.Handler.Logs : e)) => Control.Monad.Catch.MonadMask (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.Logs : e))
- Control.Eff.Log.Handler: instance Control.Eff.Internal.Handle (Control.Eff.Log.Handler.LogWriterReader h) e a (Control.Eff.Log.Writer.LogWriter h -> k)
- Control.Eff.Log.Handler: runLogWriterReader :: LogWriter h -> Eff (LogWriterReader h : e) a -> Eff e a
- Control.Eff.Log.Writer: class SupportsLogger h e
- Control.Eff.Log.Writer: instance Control.Eff.Internal.Lifted GHC.Types.IO e => Control.Eff.Log.Writer.SupportsLogger GHC.Types.IO e
- Control.Eff.Log.Writer: instance Control.Eff.Log.Writer.SupportsLogger Control.Eff.Log.Writer.PureLogWriter e
- Control.Eff.Log.Writer: instance Data.OpenUnion.Member Control.Eff.Log.Writer.CapturedLogsWriter e => Control.Eff.Log.Writer.SupportsLogger Control.Eff.Log.Writer.CaptureLogs e
- Control.Eff.Log.Writer: liftLogWriter :: SupportsLogger h e => LogWriter h -> LogMessage -> Eff e ()
+ Control.Eff.Log.Handler: instance (Control.Monad.Base.MonadBase m m, Control.Eff.Internal.LiftedBase m e, Control.Eff.Log.Handler.LogsTo m (Control.Eff.Log.Handler.Logs : e)) => Control.Monad.Trans.Control.MonadBaseControl m (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.Logs : e))
+ Control.Eff.Log.Handler: instance (GHC.Base.Applicative m, Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadCatch (Control.Eff.Internal.Eff e), Control.Eff.Log.Handler.LogsTo m (Control.Eff.Log.Handler.Logs : e)) => Control.Monad.Catch.MonadCatch (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.Logs : e))
+ Control.Eff.Log.Handler: instance (GHC.Base.Applicative m, Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadMask (Control.Eff.Internal.Eff e), Control.Eff.Log.Handler.LogsTo m (Control.Eff.Log.Handler.Logs : e)) => Control.Monad.Catch.MonadMask (Control.Eff.Internal.Eff (Control.Eff.Log.Handler.Logs : e))
+ Control.Eff.Log.Writer: -- example 'Lift IO' or '[]' for pure log writers.
+ Control.Eff.Log.Writer: -- | A list of effects that are required for writing the log messages. For
+ Control.Eff.Log.Writer: askLogWriter :: SetMember LogWriterReader (LogWriterReader h) e => Eff e (LogWriter h)
+ Control.Eff.Log.Writer: class HandleLogWriter (writerEff :: Type -> Type) where {
+ Control.Eff.Log.Writer: data LogWriterReader h v
+ Control.Eff.Log.Writer: handleLogWriterEffect :: (HandleLogWriter writerEff, LogWriterEffects writerEff <:: e) => writerEff () -> Eff e ()
+ Control.Eff.Log.Writer: instance (Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadThrow (Control.Eff.Internal.Eff e)) => Control.Monad.Catch.MonadThrow (Control.Eff.Internal.Eff (Control.Eff.Log.Writer.LogWriterReader h : e))
+ Control.Eff.Log.Writer: instance (Control.Monad.Base.MonadBase m m, Control.Eff.Internal.LiftedBase m r) => Control.Monad.Trans.Control.MonadBaseControl m (Control.Eff.Internal.Eff (Control.Eff.Log.Writer.LogWriterReader h : r))
+ Control.Eff.Log.Writer: instance (GHC.Base.Applicative m, Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadCatch (Control.Eff.Internal.Eff e)) => Control.Monad.Catch.MonadCatch (Control.Eff.Internal.Eff (Control.Eff.Log.Writer.LogWriterReader h : e))
+ Control.Eff.Log.Writer: instance (GHC.Base.Applicative m, Control.Eff.Internal.LiftedBase m e, Control.Monad.Catch.MonadMask (Control.Eff.Internal.Eff e)) => Control.Monad.Catch.MonadMask (Control.Eff.Internal.Eff (Control.Eff.Log.Writer.LogWriterReader h : e))
+ Control.Eff.Log.Writer: instance Control.Eff.Internal.Handle (Control.Eff.Log.Writer.LogWriterReader h) e a (Control.Eff.Log.Writer.LogWriter h -> k)
+ Control.Eff.Log.Writer: instance Control.Eff.Log.Writer.HandleLogWriter Control.Eff.Log.Writer.CaptureLogs
+ Control.Eff.Log.Writer: instance Control.Eff.Log.Writer.HandleLogWriter Control.Eff.Log.Writer.PureLogWriter
+ Control.Eff.Log.Writer: instance Control.Eff.Log.Writer.HandleLogWriter GHC.Types.IO
+ Control.Eff.Log.Writer: liftWriteLogMessage :: (HandleLogWriter writerEff, SetMember LogWriterReader (LogWriterReader writerEff) e, LogWriterEffects writerEff <:: e) => LogMessage -> Eff e ()
+ Control.Eff.Log.Writer: localLogWriterReader :: forall h e a. SetMember LogWriterReader (LogWriterReader h) e => (LogWriter h -> LogWriter h) -> Eff e a -> Eff e a
+ Control.Eff.Log.Writer: runLogWriterReader :: LogWriter h -> Eff (LogWriterReader h : e) a -> Eff e a
+ Control.Eff.Log.Writer: type family LogWriterEffects writerEff :: [Type -> Type];
+ Control.Eff.Log.Writer: }
- Control.Eff.Concurrent: fromServer :: forall api_aSTl api_aT2L. Iso (Server api_aSTl) (Server api_aT2L) ProcessId ProcessId
+ Control.Eff.Concurrent: fromServer :: forall api_aT1i api_aTaI. Iso (Server api_aT1i) (Server api_aTaI) ProcessId ProcessId
- Control.Eff.Concurrent.Api: fromServer :: forall api_aSTl api_aT2L. Iso (Server api_aSTl) (Server api_aT2L) ProcessId ProcessId
+ Control.Eff.Concurrent.Api: fromServer :: forall api_aT1i api_aTaI. Iso (Server api_aT1i) (Server api_aTaI) ProcessId ProcessId
- Control.Eff.Log.Handler: runLogs :: forall h e b. (LogsTo h (Logs : e), SupportsLogger h (Logs : e)) => LogPredicate -> Eff (Logs : e) b -> Eff e b
+ Control.Eff.Log.Handler: runLogs :: forall h e b. LogsTo h (Logs : e) => LogPredicate -> Eff (Logs : e) b -> Eff e b
- Control.Eff.Log.Handler: type LogsTo h e = (Member Logs e, SupportsLogger h e, SetMember LogWriterReader (LogWriterReader h) e)
+ Control.Eff.Log.Handler: type LogsTo h e = (Member Logs e, HandleLogWriter h, LogWriterEffects h <:: e, SetMember LogWriterReader (LogWriterReader h) e)

Files

ChangeLog.md view
@@ -1,5 +1,9 @@ # Changelog for extensible-effects-concurrent +## 0.20.0++- Rewrite Logging API so that usage is not as bloated+ ## 0.19.1  - Fix Travis build
README.md view
@@ -1,29 +1,80 @@ # extensible-effects-concurrent -Message passing concurrency with 'forkIO' and 'extensible-effects' inspired by Erlang.- [![Build Status](https://travis-ci.org/sheyll/extensible-effects-concurrent.svg?branch=master)](https://travis-ci.org/sheyll/extensible-effects-concurrent)  [![Hackage](https://img.shields.io/hackage/v/extensible-effects-concurrent.svg?style=flat)](http://hackage.haskell.org/package/extensible-effects-concurrent) -Also included:+## From Erlang to Haskell -- Logging+This project is an attempt to implement core ideas learned from the **Erlang/OTP**  +framework in Haskell using **`extensible-effects`**. -- Memory Leak Free `forever`+This library sketches my personal history of working on a large, real world Erlang+application, trying to bring some of the ideas over to Haskell. -## GHC Extensions+I know about cloud-haskell and transient, but I wanted something based on +'extensible-effects', and I also wanted to deepen my understanding of it. -In order to use the library you might need to activate some extension-in order to fight some ambiguous types, stemming from the flexibility to-choose different Scheduler implementations.+### Modeling an Application with Processes -- AllowAmbiguousTypes-- TypeApplications+The fundamental approach to modelling applications in Erlang is+based on the concept of concurrent, communicating processes, without+shared state.  +**`Processes`** are at the center of that contraption. All *actions*+happens in processes, and all *interactions* happen via messages sent+between processes.  -## Example+This is called **Message Passing Concurrency**;+in this library it is provided via the **`Process`** effect.  +The **`Process`** effect itself is just an *abstract interface*.++There are two schedulers, that *interpret* the `Process` effect:++- A *multi-threaded* scheduler, based on the `async`+- A *pure* single-threaded scheduler, based on coroutines++### Process Life-Cycles and Interprocess Links++All processes except the first process are **`spawned`** by existing +processes.++When a process **`spawns`** a new process, both are mutually **linked**, and+the former is called *parent* and the other *child*.++Process links form a trees.++When a parent process dies, the child processes dies as well.++If on the other hand a child dies, the parent will not die unless the+child *crashed*. ++A parent might also react by *restarting* the child from a defined starting+state.++Because processes never share memory, the internal - possibly broken - state of +a process is gone, when a process exits; hence restarting a process will not+be bothered by left-over, possibly inconsistent, state. ++Erlang such parent processes are call *supervisor* processes in Erlang.++In order to build **supervision trees** the `Process` effect allows:++- Interrupting and killing Processes+- Process Monitoring+- Process Linking+- Timers and Timeouts++These facilities are very important to build **non-defensive**, **let-it-crash**+applications, resilient to runtime errors.   ++Currently a custom **logging effect** is also part of the code base.++## Usage and Implementation++### Example Code+ ```haskell module Main where @@ -76,21 +127,18 @@ NOTICE    all processes cancelled                                                  ForkIOScheduler.hs line 179 ``` -## TODO--### Stackage+### Required GHC Extensions -Still todo...+In order to use the library you might need to activate some extension+in order to fight some ambiguous types, stemming from the flexibility to+choose different Scheduler implementations. -[![extensible-effects-concurrent LTS](http://stackage.org/package/extensible-effects-concurrent/badge/lts)](http://stackage.org/lts/package/extensible-effects-concurrent)+- AllowAmbiguousTypes+- TypeApplications  -### Other+## Planned Features -- Process Linking/Monitoring+- Stackage [![extensible-effects-concurrent LTS](http://stackage.org/package/extensible-effects-concurrent/badge/lts)](http://stackage.org/lts/package/extensible-effects-concurrent)  - Scheduler `ekg` Monitoring--- Timers and Timeouts (e.g. in `receive`)--- Rename stuff
extensible-effects-concurrent.cabal view
@@ -1,6 +1,6 @@ cabal-version:  2.0 name:           extensible-effects-concurrent-version:        0.19.1+version:        0.20.0 description:    Please see the README on GitHub at <https://github.com/sheyll/extensible-effects-concurrent#readme> synopsis:       Message passing concurrency as extensible-effect homepage:       https://github.com/sheyll/extensible-effects-concurrent#readme
src/Control/Eff/Log/Handler.hs view
@@ -29,15 +29,14 @@   -- * Log Handling API    -- ** Writing Logs-  , LogWriterReader   , setLogWriter   , addLogWriter+  , modifyLogWriter+   -- *** Log Message Modification   , withLogFileAppender   , censorLogs   , censorLogsM-  , askLogWriter-  , modifyLogWriter    -- ** 'Logs' Effect Handling   , Logs()@@ -52,8 +51,6 @@   , runLogs   , respondToLogMessage   , interceptLogMessages-  -- *** LogWriter Handling-  , runLogWriterReader    ) where@@ -104,13 +101,10 @@   handle h q AskLogFilter p         = h (q ^$ p ) p   handle h q (WriteLogMessage _) p  = h (q ^$ ()) p --- | This instance allows lifting to the 'Logs' effect, they do, however, need a--- 'LogWriter' in the base monad, in order to be able to handle 'logMsg' invocations.------ The 'LogWriterReader' effect is must be available to get to the 'LogWriter'.------ Otherwise there is no way to preserve to log messages.-instance forall m e. (MonadBase m m, LiftedBase m e, SupportsLogger m (Logs ': e), SetMember LogWriterReader (LogWriterReader m) (Logs ': e))+-- | This instance allows lifting the 'Logs' effect into a base monad, e.g. 'IO'.+-- This instance needs a 'LogWriterReader' in the base monad,+-- that is capable to handle 'logMsg' invocations.+instance forall m e. (MonadBase m m, LiftedBase m e, LogsTo m (Logs ': e))   => MonadBaseControl m (Eff (Logs ': e)) where     type StM (Eff (Logs ': e)) a =  StM (Eff e) a     liftBaseWith f = do@@ -122,7 +116,7 @@   => Catch.MonadThrow (Eff (Logs ': e)) where   throwM exception = raise (Catch.throwM exception) -instance (Applicative m, LiftedBase m e, Catch.MonadCatch (Eff e), SupportsLogger m (Logs ': e), SetMember LogWriterReader (LogWriterReader m) (Logs ': e))+instance (Applicative m, LiftedBase m e, Catch.MonadCatch (Eff e), LogsTo m (Logs ': e))   => Catch.MonadCatch (Eff (Logs ': e)) where   catch effect handler = do     lf <- askLogPredicate@@ -131,7 +125,7 @@         nestedHandler exception = lower (handler exception)     raise (Catch.catch nestedEffects nestedHandler) -instance (Applicative m, LiftedBase m e, Catch.MonadMask (Eff e), SupportsLogger m (Logs ': e), SetMember LogWriterReader (LogWriterReader m) (Logs ': e))+instance (Applicative m, LiftedBase m e, Catch.MonadMask (Eff e), LogsTo m (Logs ': e))   => Catch.MonadMask (Eff (Logs ': e)) where   mask maskedEffect = do     lf <- askLogPredicate@@ -173,17 +167,18 @@   -- | A constraint alias for effects that requires a 'LogWriterReader', as well as that the--- contained 'LogWriterReader' has a 'SupportsLogger' instance.+-- contained 'LogWriterReader' has a 'HandleLogWriter' instance. -- -- The requirements of this constraint are provided by:--- * 'withStdOutLogging'+--+-- * 'withConsoleLogging' -- * 'withIoLogging' -- * 'withLogging' -- * 'withSomeLogging' ---type LogsTo h e = (Member Logs e, SupportsLogger h e, SetMember LogWriterReader (LogWriterReader h) e)+type LogsTo h e = (Member Logs e, HandleLogWriter h, LogWriterEffects h <:: e, SetMember LogWriterReader (LogWriterReader h) e) --- | Enable logging to @stdout@ using the 'defaultIoLogWriter' in combination with+-- | Enable logging to @standard output@ using the 'defaultIoLogWriter' in combination with -- the 'consoleLogWriter'. -- -- Example:@@ -197,9 +192,9 @@ -- To vary the 'LogWriter' use 'withIoLogging'. withConsoleLogging   :: SetMember Lift (Lift IO) e-  => String-  -> Facility-  -> LogPredicate+  => String -- ^ The default application name to put into the 'lmAppName' field.+  -> Facility -- ^ The default RFC-5424 facility to put into the 'lmFacility' field.+  -> LogPredicate -- ^ The inital predicate for log messages, there are some pre-defined in "Control.Eff.Log.Message#PredefinedPredicates"   -> Eff (Logs : LogWriterReader IO : e) a   -> Eff e a withConsoleLogging = withIoLogging consoleLogWriter@@ -214,17 +209,17 @@ -- > exampleWithIoLogging = -- >     runLift -- >   $ withIoLogging consoleLogWriter---                     "my-app"---                     local7---                     (lmSeverityIsAtLeast informationalSeverity)+-- >                   "my-app"+-- >                   local7+-- >                   (lmSeverityIsAtLeast informationalSeverity) -- >   $ logInfo "Oh, hi there" -- withIoLogging   :: SetMember Lift (Lift IO) e-  => LogWriter IO-  -> String-  -> Facility-  -> LogPredicate+  => LogWriter IO -- ^ The 'LogWriter' that will be used to write log messages.+  -> String -- ^ The default application name to put into the 'lmAppName' field.+  -> Facility -- ^ The default RFC-5424 facility to put into the 'lmFacility' field.+  -> LogPredicate -- ^ The inital predicate for log messages, there are some pre-defined in "Control.Eff.Log.Message#PredefinedPredicates"   -> Eff (Logs : LogWriterReader IO : e) a   -> Eff e a withIoLogging lw appName facility defaultPredicate =@@ -278,7 +273,7 @@ -- Exposed for custom extensions, if in doubt use 'withLogging'. runLogs   :: forall h e b .-     (LogsTo h (Logs ': e), SupportsLogger h (Logs ': e))+     (LogsTo h (Logs ': e))   => LogPredicate   -> Eff (Logs ': e) b   -> Eff e b@@ -558,104 +553,17 @@  -- | Internal function. sendLogMessageToLogWriter-  :: forall h e b .-     (LogsTo h e, SupportsLogger h e, Member Logs e)-  => Eff e b -> Eff e b-sendLogMessageToLogWriter = respondToLogMessage messageCallback-  where-    messageCallback msg = do-      lw <- askLogWriter-      liftLogWriter lw msg---- | A Reader specialized for 'LogWriter's------ The existing @Reader@ couldn't be used together with 'SetMember', so this--- lazy reader was written, specialized to reading 'LogWriter'.-data LogWriterReader h v where-  AskLogWriter :: LogWriterReader h (LogWriter h)--instance Handle (LogWriterReader h) e a (LogWriter h -> k) where-  handle k q AskLogWriter lw = k (q ^$ lw) lw--instance forall h m r. (MonadBase m m, LiftedBase m r)-  => MonadBaseControl m (Eff (LogWriterReader h ': r)) where-    type StM (Eff (LogWriterReader h ': r)) a =  StM (Eff r) a-    liftBaseWith f = do-      lf <- askLogWriter-      raise (liftBaseWith (\runInBase -> f (runInBase . runLogWriterReader  lf)))-    restoreM = raise . restoreM--instance (LiftedBase m e, Catch.MonadThrow (Eff e))-  => Catch.MonadThrow (Eff (LogWriterReader h ': e)) where-  throwM exception = raise (Catch.throwM exception)--instance (Applicative m, LiftedBase m e, Catch.MonadCatch (Eff e))-  => Catch.MonadCatch (Eff (LogWriterReader h ': e)) where-  catch effect handler = do-    lf <- askLogWriter-    let lower                   = runLogWriterReader  lf-        nestedEffects           = lower effect-        nestedHandler exception = lower (handler exception)-    raise (Catch.catch nestedEffects nestedHandler)--instance (Applicative m, LiftedBase m e, Catch.MonadMask (Eff e))-  => Catch.MonadMask (Eff (LogWriterReader h ': e)) where-  mask maskedEffect = do-    lf <- askLogWriter-    let-      lower :: Eff (LogWriterReader h ': e) a -> Eff e a-      lower = runLogWriterReader  lf-    raise-        (Catch.mask-          (\nestedUnmask -> lower-            (maskedEffect-              ( raise . nestedUnmask . lower )-            )-          )-        )-  uninterruptibleMask maskedEffect = do-    lf <- askLogWriter-    let-      lower :: Eff (LogWriterReader h ': e) a -> Eff e a-      lower = runLogWriterReader  lf-    raise-        (Catch.uninterruptibleMask-          (\nestedUnmask -> lower-            (maskedEffect-              ( raise . nestedUnmask . lower )-            )-          )-        )-  generalBracket acquire release useIt = do-    lf <- askLogWriter-    let-      lower :: Eff (LogWriterReader h ': e) a -> Eff e a-      lower = runLogWriterReader  lf-    raise-        (Catch.generalBracket-          (lower acquire)-          (((.).(.)) lower release)-          (lower . useIt)-      )---- | Provide the 'LogWriter'------ Exposed for custom extensions, if in doubt use 'withLogging'.-runLogWriterReader :: LogWriter h -> Eff (LogWriterReader h ': e) a -> Eff e a-runLogWriterReader e m = fix (handle_relay (\x _ -> return x)) m e---- | Get the current 'LogWriter'.-askLogWriter :: SetMember LogWriterReader (LogWriterReader h) e => Eff e (LogWriter h)-askLogWriter = send AskLogWriter+  :: forall h e b . (LogsTo h e) => Eff e b -> Eff e b+sendLogMessageToLogWriter = respondToLogMessage liftWriteLogMessage  -- | Change the current 'LogWriter'. modifyLogWriter-  :: forall h e a. LogsTo h e => (LogWriter h -> LogWriter h) -> Eff e a -> Eff e a-modifyLogWriter f = localLogWriterReader . sendLogMessageToLogWriter-  where-    localLogWriterReader m =-      f <$> askLogWriter >>= fix (respond_relay @(LogWriterReader h) (\x _ -> return x)) m-+  :: forall h e a+   . LogsTo h e+  => (LogWriter h -> LogWriter h)+  -> Eff e a+  -> Eff e a+modifyLogWriter f = localLogWriterReader f . sendLogMessageToLogWriter  -- | Replace the current 'LogWriter'. -- To add an additional log message consumer use 'addLogWriter'@@ -679,7 +587,7 @@  -- | Combine the effects of a given 'LogWriter' and the existing one. ----- > +-- > -- > exampleAddLogWriter :: IO () -- > exampleAddLogWriter = go >>= putStrLn -- >  where go = fmap (unlines . map renderLogMessage . snd)
src/Control/Eff/Log/Writer.hs view
@@ -1,46 +1,73 @@ {-# LANGUAGE UndecidableInstances #-} -- | The 'LogWriter' type encapsulates an effectful function to write 'LogMessage's. ----- Used in conjunction with the 'SupportsLogger' class, it+-- Used in conjunction with the 'HandleLogWriter' class, it -- can be used to write messages from within an effectful -- computation. module Control.Eff.Log.Writer-  ( -- * 'LogWriter' Definition-    LogWriter(MkLogWriter, runLogWriter)-  , SupportsLogger(..)-  -- ** 'LogWriter' Zoo+  (+  -- * 'LogWriter' Definition+    LogWriter(..)+  -- * LogWriter Reader Effect+  , LogWriterReader+  , localLogWriterReader+  , askLogWriter+  , runLogWriterReader+  -- * LogWriter Handler Class+  , HandleLogWriter(..)+  -- ** 'LogWriter' Handler Instance Zoo   -- *** Pure Writer   , noOpLogWriter   , debugTraceLogWriter   , PureLogWriter(..)+  -- *** List Writer   , listLogWriter   , CaptureLogs(..)   , CapturedLogsWriter   , runCapturedLogsWriter-  -- ** IO Based 'LogWriter'+  -- *** IO Writer   , consoleLogWriter   , ioHandleLogWriter-  -- *** General Combinator+  -- ** Writer Combinator+  -- *** Pure Writer Combinator   , filteringLogWriter   , mappingLogWriter+  -- *** Impure Writer Combinator   , mappingLogWriterM   -- *** IO Based Combinator   , ioLogWriter   , defaultIoLogWriter-  ) where+  )+where -import Control.Eff-import Control.Eff.Log.Message-import Data.Default-import Debug.Trace-import GHC.Stack-import Control.Eff.Writer.Strict (Writer, tell, runListWriter)-import Data.Functor.Identity (Identity)-import Control.DeepSeq (deepseq)-import Data.Foldable (traverse_)-import System.IO-import Control.Monad ((>=>))-import Control.Lens+import           Control.Eff+import           Control.Eff.Extend+import           Control.Eff.Log.Message+import           Data.Default+import           Data.Function                  ( fix )+import           Debug.Trace+import           GHC.Stack+import           Control.Eff.Writer.Strict      ( Writer+                                                , tell+                                                , runListWriter+                                                )+import           Data.Functor.Identity          ( Identity )+import           Control.DeepSeq                ( force )+import           Data.Foldable                  ( traverse_ )+import qualified System.IO                     as IO+import           Control.Monad                  ( (>=>)+                                                , when+                                                )+import           Control.Monad.Base             ( MonadBase() )+import qualified Control.Monad.Catch           as Catch+import           Control.Monad.Trans.Control    ( MonadBaseControl+                                                  ( restoreM+                                                  , liftBaseWith+                                                  , StM+                                                  )+                                                )+import           Data.Kind+import           Control.Lens  -- | A function that takes a log message and returns an effect that -- /logs/ the message.@@ -51,32 +78,121 @@ instance Applicative w => Default (LogWriter w) where   def = MkLogWriter (const (pure ())) --- * LogWriter liftings+-- | Provide the 'LogWriter'+--+-- Exposed for custom extensions, if in doubt use 'withLogging'.+runLogWriterReader :: LogWriter h -> Eff (LogWriterReader h ': e) a -> Eff e a+runLogWriterReader e m = fix (handle_relay (\x _ -> return x)) m e --- | This class describes how to lift the log writer action into some monad.+-- | Get the current 'LogWriter'.+askLogWriter+  :: SetMember LogWriterReader (LogWriterReader h) e => Eff e (LogWriter h)+askLogWriter = send AskLogWriter++-- | Modify the current 'LogWriter'.+localLogWriterReader+  :: forall h e a+   . SetMember LogWriterReader (LogWriterReader h) e+  => (LogWriter h -> LogWriter h)+  -> Eff e a+  -> Eff e a+localLogWriterReader f m =+  f+    <$> askLogWriter+    >>= fix (respond_relay @(LogWriterReader h) (\x _ -> return x)) m++-- | A Reader specialized for 'LogWriter's ----- The second parameter is almost always @Eff x@--- so usually the method of this class lifts the log writer action into an effect monad.-class SupportsLogger h e where-  liftLogWriter :: LogWriter h -> LogMessage -> Eff e ()+-- The existing @Reader@ couldn't be used together with 'SetMember', so this+-- lazy reader was written, specialized to reading 'LogWriter'.+data LogWriterReader h v where+  AskLogWriter ::LogWriterReader h (LogWriter h) +instance Handle (LogWriterReader h) e a (LogWriter h -> k) where+  handle k q AskLogWriter lw = k (q ^$ lw) lw++instance forall h m r. (MonadBase m m, LiftedBase m r)+  => MonadBaseControl m (Eff (LogWriterReader h ': r)) where+  type StM (Eff (LogWriterReader h ': r)) a =  StM (Eff r) a+  liftBaseWith f = do+    lf <- askLogWriter+    raise (liftBaseWith (\runInBase -> f (runInBase . runLogWriterReader lf)))+  restoreM = raise . restoreM++instance (LiftedBase m e, Catch.MonadThrow (Eff e))+  => Catch.MonadThrow (Eff (LogWriterReader h ': e)) where+  throwM exception = raise (Catch.throwM exception)++instance (Applicative m, LiftedBase m e, Catch.MonadCatch (Eff e))+  => Catch.MonadCatch (Eff (LogWriterReader h ': e)) where+  catch effect handler = do+    lf <- askLogWriter+    let lower         = runLogWriterReader lf+        nestedEffects = lower effect+        nestedHandler exception = lower (handler exception)+    raise (Catch.catch nestedEffects nestedHandler)++instance (Applicative m, LiftedBase m e, Catch.MonadMask (Eff e))+  => Catch.MonadMask (Eff (LogWriterReader h ': e)) where+  mask maskedEffect = do+    lf <- askLogWriter+    let lower :: Eff (LogWriterReader h ': e) a -> Eff e a+        lower = runLogWriterReader lf+    raise+      (Catch.mask+        (\nestedUnmask -> lower (maskedEffect (raise . nestedUnmask . lower)))+      )+  uninterruptibleMask maskedEffect = do+    lf <- askLogWriter+    let lower :: Eff (LogWriterReader h ': e) a -> Eff e a+        lower = runLogWriterReader lf+    raise+      (Catch.uninterruptibleMask+        (\nestedUnmask -> lower (maskedEffect (raise . nestedUnmask . lower)))+      )+  generalBracket acquire release useIt = do+    lf <- askLogWriter+    let lower :: Eff (LogWriterReader h ': e) a -> Eff e a+        lower = runLogWriterReader lf+    raise+      (Catch.generalBracket (lower acquire)+                            (((.) . (.)) lower release)+                            (lower . useIt)+      )+ -- * 'LogWriter' Zoo +-- | The instances of this class are the monads that define (side-) effect(s) of writting logs.+class HandleLogWriter (writerEff :: Type -> Type) where+  -- | A list of effects that are required for writing the log messages.+  -- For example 'Lift IO' or '[]' for pure log writers.+  type LogWriterEffects writerEff :: [Type -> Type]++  -- | Run the side effect of a 'LogWriter' in a compatible 'Eff'.+  handleLogWriterEffect :: (LogWriterEffects writerEff <:: e) => writerEff () -> Eff e ()++  -- | Write a message using the 'LogWriter' found in the environment.+  --+  -- The semantics of this function are a combination of 'runLogWriter' and 'handleLogWriterEffect',+  -- with the 'LogWriter' read from a 'LogWriterReader'.+  liftWriteLogMessage :: ( SetMember LogWriterReader (LogWriterReader writerEff) e+                         , LogWriterEffects writerEff <:: e)+                      => LogMessage+                      -> Eff e ()+  liftWriteLogMessage m = do+    w <- askLogWriter+    handleLogWriterEffect (runLogWriter w m)+ -- ** Pure Log Writers --- | A base monad for all side effect free 'LogWriter'.------ This is only required e.g. when logs are only either discarded or traced.--- See 'debugTraceLogWriter' or 'noOpLogWriter'.------ This is just a wrapper around 'Identity' and serves as a type that has a special--- 'SupportsLogger' instance.+-- | A phantom type for the 'HandleLogWriter' class for /pure/ 'LogWriter's newtype PureLogWriter a = MkPureLogWriter { runPureLogWriter :: Identity a }-  deriving (Functor, Applicative, Monad)+  deriving (Applicative, Functor, Monad)  -- | A 'LogWriter' monad for 'Debug.Trace' based pure logging.-instance SupportsLogger PureLogWriter e where-  liftLogWriter lw msg = deepseq (runPureLogWriter (runLogWriter lw msg)) (return ())+instance HandleLogWriter PureLogWriter where+  type LogWriterEffects PureLogWriter = '[]+  handleLogWriterEffect lw = return (force (runIdentity (force (runPureLogWriter lw))))  -- | This 'LogWriter' will discard all messages. --@@ -102,14 +218,17 @@  -- | A 'LogWriter' monad for pure logging. ----- The 'SupportsLogger' instance for this type assumes a 'Writer' effect.-instance Member CapturedLogsWriter e => SupportsLogger CaptureLogs e where-  liftLogWriter lw = traverse_ (tell @LogMessage) . snd . run . runListWriter . unCaptureLogs . runLogWriter lw+-- The 'HandleLogWriter' instance for this type assumes a 'Writer' effect.+instance HandleLogWriter CaptureLogs where+  type LogWriterEffects CaptureLogs = '[CapturedLogsWriter]+  handleLogWriterEffect =+    traverse_ (tell @LogMessage) . snd . run . runListWriter . unCaptureLogs  -- | Run a 'Writer' for 'LogMessage's. -- -- Such a 'Writer' is needed for 'CaptureLogs'-runCapturedLogsWriter :: Eff (CapturedLogsWriter ': e) a -> Eff e (a, [LogMessage])+runCapturedLogsWriter+  :: Eff (CapturedLogsWriter ': e) a -> Eff e (a, [LogMessage]) runCapturedLogsWriter = runListWriter  -- | Alias for the 'Writer' that contains the captured 'LogMessage's from 'CaptureLogs'.@@ -118,23 +237,23 @@ -- | A 'LogWriter' that uses an 'IO' action to write the message. -- -- Example use cases for this function are the 'consoleLogWriter' and the 'ioHandleLogWriter'.-ioLogWriter :: HasCallStack => (LogMessage-> IO ()) -> LogWriter IO+ioLogWriter :: HasCallStack => (LogMessage -> IO ()) -> LogWriter IO ioLogWriter = MkLogWriter --- | A 'LogWriter' that uses an 'IO' action to write the message.-ioHandleLogWriter :: HasCallStack => Handle -> LogWriter IO-ioHandleLogWriter h = ioLogWriter (hPutStrLn h . renderLogMessage)+-- | A 'LogWriter' that renders 'LogMessage's to strings via 'renderLogMessage'+-- and prints them to an 'IO.Handle' using 'hPutStrLn'.+ioHandleLogWriter :: HasCallStack => IO.Handle -> LogWriter IO+ioHandleLogWriter h = ioLogWriter (IO.hPutStrLn h . renderLogMessage) -instance (Lifted IO e) => SupportsLogger IO e where-  liftLogWriter = (lift . ) . runLogWriter+instance HandleLogWriter IO where+  type LogWriterEffects IO = '[Lift IO]+  handleLogWriterEffect = send . Lift  -- | Write 'LogMessage's to standard output, formatted with 'printLogMessage'. consoleLogWriter :: LogWriter IO consoleLogWriter = ioLogWriter printLogMessage --- | Decorate an IO based 'LogWriter' to set important fields in log messages.------ ALl log messages are censored to include basic log message information:+-- | Decorate an IO based 'LogWriter' to fill out these fields in 'LogMessage's: -- -- * The messages will carry the given application name in the 'lmAppName' field. -- * The 'lmTimestamp' field contains the UTC time of the log event@@ -142,22 +261,26 @@ -- * The 'lmHostname' field contains the FQDN of the current host -- * The 'lmFacility' field contains the given 'Facility' ----- It installs the given 'LogWriter', wrapped using 'mappingLogWriterM'.-defaultIoLogWriter :: String -> Facility -> LogWriter IO -> LogWriter IO-defaultIoLogWriter appName facility =-  mappingLogWriterM-    (   setLogMessageThreadId-    >=> setLogMessageTimestamp-    >=> setLogMessageHostname-    >=> pure-         . set lmFacility facility-         . set lmAppName (Just appName)-    )+-- It works by using 'mappingLogWriterM'.+defaultIoLogWriter+  :: String -- ^ The default application name to put into the 'lmAppName' field.+  -> Facility -- ^ The default RFC-5424 facility to put into the 'lmFacility' field.+  -> LogWriter IO -- ^ The IO based writer to decorate+  -> LogWriter IO+defaultIoLogWriter appName facility = mappingLogWriterM+  (   setLogMessageThreadId+  >=> setLogMessageTimestamp+  >=> setLogMessageHostname+  >=> pure+  .   set lmFacility facility+  .   set lmAppName  (Just appName)+  )  -- | A 'LogWriter' that applies a predicate to the 'LogMessage' and delegates to -- to the given writer of the predicate is satisfied. filteringLogWriter :: Monad e => LogPredicate -> LogWriter e -> LogWriter e-filteringLogWriter p lw = MkLogWriter (\msg -> if p msg then (runLogWriter lw msg) else return ())+filteringLogWriter p lw =+  MkLogWriter (\msg -> when (p msg) (runLogWriter lw msg))  -- | A 'LogWriter' that applies a function to the 'LogMessage' and delegates the result to -- to the given writer.@@ -165,5 +288,6 @@ mappingLogWriter f lw = MkLogWriter (runLogWriter lw . f)  -- | Like 'mappingLogWriter' allow the function that changes the 'LogMessage' to have effects.-mappingLogWriterM :: Monad e => (LogMessage -> e LogMessage) -> LogWriter e -> LogWriter e+mappingLogWriterM+  :: Monad e => (LogMessage -> e LogMessage) -> LogWriter e -> LogWriter e mappingLogWriterM f lw = MkLogWriter (f >=> runLogWriter lw)