Safe Haskell | None |
---|---|
Language | Haskell2010 |
This module offers a monadic alternative to the “bare” logging API offered by Di.Core.
Whereas Di.Core expects you to explicitly pass around a Di
object,
Di.Monad offers a MonadDi
typeclass, as well functions operating on
MonadDi
instances, as its public facing API.
Di.Monad exports MonadDi
instances for all of the monad transformer
types in transformers
and pipes.
Nevertheless, be aware that these two APIs are compatible, so you may
choose to use the monadic API for some parts of your application, the
“bare” API for some other parts, and everything will compose and behave
as expected. Usually, runDiT
is the boundary between these two APIs
.
Di.Monad also provides a DiT
monad transformer that has an instance
of the MonadDi
typeclass and you can readily use out of the box. DiT
also implements instances for all of the typeclasses in
base,
mtl, and
exceptions.
Import this module as follows:
import Di.Core as Di (new
)
import Di.Monad as Di
Synopsis
- class Monad m => MonadDi level path msg m | m -> level path msg where
- log :: MonadDi level path msg m => level -> msg -> m ()
- flush :: MonadDi level path msg m => m ()
- push :: MonadDi level path msg m => path -> m a -> m a
- filter :: MonadDi level path msg m => (level -> Seq path -> msg -> Bool) -> m a -> m a
- throw :: (MonadDi level path msg m, Exception e) => e -> m a
- onException :: MonadDi level path msg m => (SomeException -> Maybe (level, Seq path, msg)) -> m a -> m a
- data DiT level path msg m a
- diT :: ((forall x. STM x -> m x) -> Di level path msg -> m a) -> DiT level path msg m a
- runDiT :: MonadIO m => Di level path msg -> DiT level path msg m a -> m a
- runDiT' :: (forall x. STM x -> m x) -> Di level path msg -> DiT level path msg m a -> m a
- hoistDiT :: (forall x. n x -> m x) -> (forall x. m x -> n x) -> DiT level path msg m a -> DiT level path msg n a
- localDiT :: (Di level path msg -> Di level' path' msg') -> DiT level' path' msg' m a -> DiT level path msg m a
MonadDi
class Monad m => MonadDi level path msg m | m -> level path msg where Source #
A MonadDi
allows interacting with a Di
through a
mtl-like monadic API, rather
than through the “bare” API proposed by Di.Core.
Nevertheless, be aware that these two APIs are compatible, so you may
choose to use the monadic API for some parts of your application, the
“bare” API for some other parts, and everything will compose and behave
as expected. Usually, runDiT
is the boundary between these two APIs
,
although not necessarily.
Semantically,
is a “reader monad” that carries as its
environment a MonadDi
mDi
and natural transformation from STM
to m
.
ask :: m (Di level path msg) Source #
ask :: (MonadTrans t, MonadDi level path msg n, m ~ t n) => m (Di level path msg) Source #
local :: (Di level path msg -> Di level path msg) -> m a -> m a Source #
Run m a
with a modified Di
:
local
(const
x)ask
==pure
x
Identity law:
local
id
x == x
Distributive law:
local
f.
local
g ==local
(f.
g)
Idempotence law:
local
f (pure
())>>
x == x
natSTM :: STM a -> m a Source #
Natural transformation from STM
to m
.
Notice that it is not necessary for this natural transformation to be a
monad morphism as well. That is, atomically
is acceptable.
natSTM :: (MonadTrans t, MonadDi level path msg n, m ~ t n) => STM a -> m a Source #
Natural transformation from STM
to m
.
Notice that it is not necessary for this natural transformation to be a
monad morphism as well. That is, atomically
is acceptable.
Instances
MonadDi level path msg m => MonadDi level path msg (ListT m) Source # | |
MonadDi level path msg m => MonadDi level path msg (MaybeT m) Source # | |
MonadDi level path msg m => MonadDi level path msg (SelectT r m) Source # | |
(Monoid w, MonadDi level path msg m) => MonadDi level path msg (AccumT w m) Source # | |
MonadDi level path msg m => MonadDi level path msg (IdentityT m) Source # | |
MonadDi level path msg m => MonadDi level path msg (ExceptT e m) Source # | |
(Monoid w, MonadDi level path msg m) => MonadDi level path msg (WriterT w m) Source # | |
(Monoid w, MonadDi level path msg m) => MonadDi level path msg (WriterT w m) Source # | |
MonadDi level path msg m => MonadDi level path msg (StateT s m) Source # | |
MonadDi level path msg m => MonadDi level path msg (StateT s m) Source # | |
MonadDi level path msg m => MonadDi level path msg (ContT r m) Source # | |
MonadDi level path msg m => MonadDi level path msg (ReaderT r m) Source # | |
(Monoid w, MonadDi level path msg m) => MonadDi level path msg (RWST r w s m) Source # | |
(Monoid w, MonadDi level path msg m) => MonadDi level path msg (RWST r w s m) Source # | |
Monad m => MonadDi level path msg (DiT level path msg m) Source # | |
MonadDi level path msg m => MonadDi level path msg (Proxy a' a b' b m) Source # | |
log :: MonadDi level path msg m => level -> msg -> m () Source #
Log a message with the given importance level
.
This function returns immediately after queing the message for logging. The
actual printing of the log message will happen in a different thread,
asynchronously. If you want to explicitly wait for the message to be logged,
then call flush
afterwards.
Log messages are rendered in FIFO order, and their timestamp records the
time when this log
function was called, rather than the time when the log
message is printed in the future.
Note regarding exceptions: Any exception thrown by natSTM
will be thrown
here. Synchronous exceptions that happen due to failures in the actual
committing of the log message, which itself is performed in a different
thread, are ignored (they should be handled in the function passed to new
instead). If an asynchronous exception kills the logging thread, then you
will synchronously get ExceptionInLoggingWorker
here, but by the time
that happens, that same exception will have already already been thrown
asynchronously to this same thread anyway, so unless you did something funny
to recover from that exception, you will have died already.
flush :: MonadDi level path msg m => m () Source #
Block until all messages being logged have finished processing.
Manually calling flush
is not usually necessary because all log messages
are processed as soon as possible, and with
ensures that no log message is
left unprocessed. However, the actual printing of log messages happens
asynchronously, meaning there might be log messages still waiting to be
processed. A call to flush
will block until all pending log messages have
been processed.
Please see log
to understand how exceptions behave in this function (hint:
they behave unsurprisingly).
push :: MonadDi level path msg m => path -> m a -> m a Source #
Run the given action under a deeper path
.
:: MonadDi level path msg m | |
=> (level -> Seq path -> msg -> Bool) | Whether a particular log entry with the given The given |
-> m a | |
-> m a |
Require that any logged messages within the given action satisfy the given predicate in order to be accepted for processing. Logged messages that don't satisfy the predicate will be silently discarded.
Identity:
filter
(\_ _ _ ->True
) ==id
Composition:
filter
(\l ps m -> f l ps m&&
g l ps m) ==filter
f .filter
g
Commutativity:
filter
f .filter
g ==filter
g .filter
f
throw :: (MonadDi level path msg m, Exception e) => e -> m a Source #
Throw an Exception
, but not without logging it first according to the
rules established by onException
, and further restricted by the rules
established by filter
.
If the exception doesn't need to be logged, according to the policy set with
onException
, then this function behaves just as
throwSTM
.
WARNING: Note that when m
is STM
, or ultimately runs on STM
, then
throw
will not log the exception, just throw it. This might change in
the future if we figure out how to make it work safely.
onException :: MonadDi level path msg m => (SomeException -> Maybe (level, Seq path, msg)) -> m a -> m a Source #
Within the passed given m a
, exceptions thrown with throw
could could
be logged as a msg
with a particular level
if both the passed in function
returns Just
, and filter
so allows it afterwards.
If the given function returns Nothing
, then no logging is performed.
The returned
will extend the Seq
pathpath
at the throw
call site
before sending the log. The leftmost path
is closest to the root.
Composition:
onException
f .onException
g ==onException
(g e *> f e)
Notice that the level
, path
s and msg
resulting from g
are discarded,
yet its policy regarding whether to log or not is preserved in the same way
as filter
. That is, onException
can't accept an exception already
rejected by a previous use of onException
, but it can reject a previously
accepted one.
DiT
data DiT level path msg m a Source #
A
is a “reader monad” that carries as its
environment a DiT
level path msg m
and natural transformation from Di
level path msgSTM
to
m
.
Instances
Monad m => MonadDi level path msg (DiT level path msg m) Source # | |
MonadWriter w m => MonadWriter w (DiT level path msg m) Source # | |
MonadState s m => MonadState s (DiT level path msg m) Source # | |
MonadReader r m => MonadReader r (DiT level path msg m) Source # | |
MonadError e m => MonadError e (DiT level path msg m) Source # | |
Defined in Di.Monad throwError :: e -> DiT level path msg m a # catchError :: DiT level path msg m a -> (e -> DiT level path msg m a) -> DiT level path msg m a # | |
MonadTrans (DiT level path msg) Source # | |
Monad m => Monad (DiT level path msg m) Source # | |
Functor m => Functor (DiT level path msg m) Source # | |
MonadFix m => MonadFix (DiT level path msg m) Source # | |
MonadFail m => MonadFail (DiT level path msg m) Source # | |
Applicative m => Applicative (DiT level path msg m) Source # | |
Defined in Di.Monad pure :: a -> DiT level path msg m a # (<*>) :: DiT level path msg m (a -> b) -> DiT level path msg m a -> DiT level path msg m b # liftA2 :: (a -> b -> c) -> DiT level path msg m a -> DiT level path msg m b -> DiT level path msg m c # (*>) :: DiT level path msg m a -> DiT level path msg m b -> DiT level path msg m b # (<*) :: DiT level path msg m a -> DiT level path msg m b -> DiT level path msg m a # | |
MonadZip m => MonadZip (DiT level path msg m) Source # | |
Defined in Di.Monad | |
MonadIO m => MonadIO (DiT level path msg m) Source # | |
Alternative m => Alternative (DiT level path msg m) Source # | |
MonadPlus m => MonadPlus (DiT level path msg m) Source # | |
MonadThrow m => MonadThrow (DiT level path msg m) Source # | Throw an If you want to log the |
MonadCatch m => MonadCatch (DiT level path msg m) Source # | |
MonadMask m => MonadMask (DiT level path msg m) Source # | |
Defined in Di.Monad mask :: ((forall a. DiT level path msg m a -> DiT level path msg m a) -> DiT level path msg m b) -> DiT level path msg m b # uninterruptibleMask :: ((forall a. DiT level path msg m a -> DiT level path msg m a) -> DiT level path msg m b) -> DiT level path msg m b # generalBracket :: DiT level path msg m a -> (a -> ExitCase b -> DiT level path msg m c) -> (a -> DiT level path msg m b) -> DiT level path msg m (b, c) # | |
MonadCont m => MonadCont (DiT level path msg m) Source # | |
Run a DiT
.
forall di.runDiT
di (diT
(\nat' di' -> pure (nat', di'))) ==pure
(natSTM
, di)
This is like runDiT'
, but specialized to run with an underlying MonadIO
.
runDiT
==runDiT'
(liftIO
.atomically
)
Please notice that runDiT
doesn't perform a flush
on the given Di
before returning. You are responsible for doing that (or, more likely,
new
will do it for you).
Also, notice that runDiT
is a monad morphism from
to DiT
mm
.
Run a DiT
.
forall nat di.runDiT'
nat di (diT
(\nat' di' -> pure (nat', di'))) ==pure
(nat, di)
runDiT'
is like runDiT
. However it doesn't require a MonadIO
constraint. Instead, it takes the natural transformation that will be used
by natSTM
as an argument.
First, this allows any monad that wraps IO
without necessarily having a
MonadIO
instance to work with MonadDi
. For example:
newtype Foo = Foo (IO
a) deriving (Functor
,Applicative
,Monad
)runDiT'
(Foo .atomically
) ::Di
level path msg ->DiT
level path msg Foo a -> Foo a
Second, this allows m
to be STM
itself:
runDiT'
id
::Di
level path msg ->DiT
level path msgSTM
a ->STM
a
The semantics of logging from within STM
are those of any other STM
transaction: That is, a log message is commited only once to the outside
world if and when the STM
transaction succeeds. That is, the following
example will only ever commit the log containing ly
and my
, and not
the one containing lx
and mx
.
atomically
$runDiT'
id
$ do (log
id
lx mx >>lift
retry
) <|> (log
id
ly my)
Of course, runDiT'
works as well if you decide to wrap STM
with your own
monad type:
newtype Bar = Bar (STM
a) deriving (Functor
,Applicative
,Monad
)runDiT'
Bar ::Di
level path msg ->DiT
level path msg Bar a -> Bar a
Additionally, notice that runDiT'
itself is a monad morphism from
to DiT
level path msg mm
which doesn't perform any side effects
of its own. Particularly, the given Di
remains unaffected. So you can use
it as many times you want.
forall f di x.runDiT'
f di (lift
x) == x
Please notice that runDiT
doesn't perform a flush
on the given Di
before returning. You are responsible for doing that (or, more likely,
new
will do it for you).
:: (forall x. n x -> m x) | Natural transformation from |
-> (forall x. m x -> n x) | Monad morphism from |
-> DiT level path msg m a -> DiT level path msg n a |
Lift a monad morphism from m
to n
to a monad morphism from
to DiT
level path msg m
.DiT
level path msg n
Notice that DiT
itself is not a functor in the category of monads,
so it can't be an instance of MFunctor
from the
mmorph package.
However, it becomes one if you pair it with a natural transformation
. That is:nat
:: forall x. n x -> m x
forall nat. such thatnat
is a natural transformationhoistDiT
nat ==hoist
In practical terms, it means that most times you can “hoist” a DiT
anyway, just not through hoist
.
:: (Di level path msg -> Di level' path' msg') | |
-> DiT level' path' msg' m a | |
-> DiT level path msg m a |
localDiT
(const
x)ask
==pure
x
Notice that, contrary to local
, this allows changing the type of Di
,
which means that you can use localDiT
with contralevel
,
contrapath
or contramsg
to change the types of level
,
path
, or message
you DiT
works with.
localDiT
(contralevel
(f :: level -> level')) ::DiT
level' path msg m a ->DiT
level path msg m a
localDiT
(contrapath
(f :: path -> path')) ::DiT
level path' msg m a ->DiT
level path msg m a
localDiT
(contramsg
(f :: msg -> msg')) ::DiT
level path msg' m a ->DiT
level path msg m a
Identity law:
localDiT
id
x == x
Distributive law:
localDiT
f.
localDiT
g ==localDiT
(f.
g)
Idempotence law:
localDiT
f (pure
())>>
x == x