di-monad-1.1: mtl flavoured typeful hierarchical structured logging for di-core.

Contents

Description

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)

Synopsis

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, MonadDi m is a “reader monad” that carries as its environment a Di and natural transformation from STM to m.

Minimal complete definition

local

Methods

ask :: m (Di level path msg) Source #

Get the Di inside m, unmodified.

Idempotence law:

ask >> ask  ==  ask


ask :: (MonadTrans t, MonadDi level path msg n, m ~ t n) => m (Di level path msg) Source #

Get the Di inside m, unmodified.

Idempotence law:

ask >> ask  ==  ask


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

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 are handled by attempting to log the message to stderr as a fallback if possible. Asynchronous exceptions happening as part of the committing process will be thrown in a different thread, and are not not explicitly handled. Pure exceptions originating from the filter function will be thrown here. In practical terms, this means that unless you know what you are doing, you should just call log' without worrying about it ever throwing exceptions.

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.

push :: MonadDi level path msg m => path -> m a -> m a Source #

Run the given action under a deeper path.

Arguments

 :: MonadDi level path msg m => (level -> Seq path -> msg -> Bool) Whether a particular log entry with the given level, paths and msg should be logged.The given paths indicate where the log call was made from, with an empty Seq representing log calls made at the current depth level (see push). The leftmost path in the Seq is the most immediate child, while the rightmost is the most distand child (i.e., the path closest to the place where log call actually took place). -> 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


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 throwM 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 Seq path will extend the path 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, paths 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 DiT level path msg m is a “reader monad” that carries as its environment a Di level path msg and natural transformation from STM to m.

The most primitive way to build a DiT is through diT.

The most primitive way to run a DiT is through runDiT'.

Instances
 Monad m => MonadDi level path msg (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodsask :: DiT level path msg m (Di level path msg) Source #local :: (Di level path msg -> Di level path msg) -> DiT level path msg m a -> DiT level path msg m a Source #natSTM :: STM a -> DiT level path msg m a Source # MonadWriter w m => MonadWriter w (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodswriter :: (a, w) -> DiT level path msg m a #tell :: w -> DiT level path msg m () #listen :: DiT level path msg m a -> DiT level path msg m (a, w) #pass :: DiT level path msg m (a, w -> w) -> DiT level path msg m a # MonadState s m => MonadState s (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodsget :: DiT level path msg m s #put :: s -> DiT level path msg m () #state :: (s -> (a, s)) -> DiT level path msg m a # MonadReader r m => MonadReader r (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodsask :: DiT level path msg m r #local :: (r -> r) -> DiT level path msg m a -> DiT level path msg m a #reader :: (r -> a) -> DiT level path msg m a # MonadTrans (DiT level path msg) Source # Instance detailsDefined in Di.Monad Methodslift :: Monad m => m a -> DiT level path msg m a # Monad m => Monad (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methods(>>=) :: DiT level path msg m a -> (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 b #return :: a -> DiT level path msg m a #fail :: String -> DiT level path msg m a # Functor m => Functor (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodsfmap :: (a -> b) -> DiT level path msg m a -> DiT level path msg m b #(<$) :: a -> DiT level path msg m b -> DiT level path msg m a # MonadFix m => MonadFix (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodsmfix :: (a -> DiT level path msg m a) -> DiT level path msg m a # MonadFail m => MonadFail (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodsfail :: String -> DiT level path msg m a # Applicative m => Applicative (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodspure :: 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 # Instance detailsDefined in Di.Monad Methodsmzip :: DiT level path msg m a -> DiT level path msg m b -> DiT level path msg m (a, b) #mzipWith :: (a -> b -> c) -> DiT level path msg m a -> DiT level path msg m b -> DiT level path msg m c #munzip :: DiT level path msg m (a, b) -> (DiT level path msg m a, DiT level path msg m b) # MonadIO m => MonadIO (DiT level path msg m) Source # Instance detailsDefined in Di.Monad MethodsliftIO :: IO a -> DiT level path msg m a # Alternative m => Alternative (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodsempty :: DiT level path msg m a #(<|>) :: DiT level path msg m a -> DiT level path msg m a -> DiT level path msg m a #some :: DiT level path msg m a -> DiT level path msg m [a] #many :: DiT level path msg m a -> DiT level path msg m [a] # MonadPlus m => MonadPlus (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodsmzero :: DiT level path msg m a #mplus :: DiT level path msg m a -> DiT level path msg m a -> DiT level path msg m a # MonadMask m => MonadMask (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodsmask :: ((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) # MonadThrow (DiT level path msg STM) Source # This instance doesn't log exceptions before throwing them. Instance detailsDefined in Di.Monad MethodsthrowM :: Exception e => e -> DiT level path msg STM a # MonadMask m => MonadThrow (DiT level path msg m) 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.Note: Any new exception that might happen as part of the logging process is silenced, so that the originally thrown exception is the one that has precendence.Note that the MonadMask superclass prevents m's base monad to be STM. There is another instance where m ~ STM, but you will need to write your own instance or use throw' directly if m is a wrapper around STM and not STM itself. On the other hand, the MonadMask constraint should be easy to satisfy by all wrappers around IO, even those that don't implement MonadIO. Instance detailsDefined in Di.Monad MethodsthrowM :: Exception e => e -> DiT level path msg m a # (MonadThrow (DiT level path msg m), MonadCatch m) => MonadCatch (DiT level path msg m) Source # Instance detailsDefined in Di.Monad Methodscatch :: Exception e => DiT level path msg m a -> (e -> DiT level path msg m a) -> DiT level path msg m a # MonadCont m => MonadCont (DiT level path msg m) Source # Instance detailsDefined in Di.Monad MethodscallCC :: ((a -> DiT level path msg m b) -> DiT level path msg m a) -> 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 Source # Build a DiT. forall nat di. runDiT' nat di (diT (\nat' di' -> pure (nat', di'))) == pure (nat, di)  Arguments  :: MonadIO m => Di level path msg -> DiT level path msg m a -> m a 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 DiT m to m. Arguments  :: (forall x. STM x -> m x) -> Di level path msg -> DiT level path msg m a -> m a 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 msg STM 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 DiT level path msg m to m 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).

Arguments

 :: (forall x. n x -> m x) Natural transformation from n to m. -> (forall x. m x -> n x) Monad morphism from m to n. -> DiT level path msg m a -> DiT level path msg n a Monad morphism from DiT m to DiT n.

Lift a monad morphism from m to n to a monad morphism from DiT level path msg m to DiT 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 nat :: forall x. n x -> m x. That is:

forall nat.  such that nat is a natural transformation
hoistDiT nat  ==  hoist


In practical terms, it means that most times you can “hoist” a DiT anyway, just not through hoist.

Arguments

 :: (Di level path msg -> Di level' path' msg') -> DiT level' path' msg' m a -> DiT level path msg m a

Run a DiT with a modified Di:

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