unclogging
View repository: https://git.mangoiv.com/mangoiv/unclogging
Simple, yet extensible concurrent logging system based on publish/subscribe.
The frontend to this library is based on importing a specific logging frontend. Currently there are Control.Effect.Unclog.{Json,Text}
and Unclog.IO.{Json,Text}
for aeson
and text
based logging and use with fused-effects
and MonadUnliftIO
respectively,
exposing functions with the same names, debug
, info
, warn
and fatal
for the four common log levels.
Subscribers watch a channel of logs and are constructed and destructed by the toplevel logging handler, like runUnclogging
or
withLoggingWithSubscribers
. To construct your own subscribers, check out the Unclog.Subscriber
module, more specifically, the
mkSubscriber
, mkSubscriberSimple
and bracketSubscriber
functions.
The library makes use of template haskell to efficiently create static information at compile time. This means that the logging frontend functions are
of type Q Exp
, which isn't very telling. Due to limitations for typed template haskell we currently cannot perform better though. If you're familiar with
typed template haskell, you can imagine the logging functions to have a type similar to Code m (frontend -> m ())
(more about this right below).
In reality, as soon as you're splicing in a logging function, e.g. by using $info
, the type will "materialise" and you will obtain a function
of type frontend -> m ()
where frontend
is either Data.Aeson.Object
if you're using the json frontend or Data.Text.Text
if you're using the
text frontend. m
will be something according to the effect system you're using. E.g. in the fused-effects case it will emit a Has Log sig m
constraint
and in the io case it will emit a MonadUnliftIO
constraint.
example for standalone usage, i.e. MonadUnliftIO
and text
frontend
-- import utilities that are useful for logging
import Unclog
-- the logging frontend has to be specific to the effect system used
import Unclog.Text
import Unclog.Subscribers (withLoggingWithSubscribers)
main :: IO ()
main = do
let subs = [colourSubscriber Info stdout, simpleSubscriber Fatal stderr, fileSubscriber Debug "bla.log"]
withLoggingWithSubscribers subs \logger -> do
info logger "info"
debug logger "some important debug info"
example for use with fused-effects
and aeson
frontend
import Unclog
-- logging frontend specific to fused-effects
import Control.Effect.Unclog.Json
import Control.Carrier.Unclog
-- for the construction of JSON objects.
import Data.Aeson
main :: IO ()
main = do
let subs = [colourSubscriber Info stdout, simpleSubscriber Fatal stderr, fileSubscriber Debug "bla.log"]
-- as you can see, we don't have to pass around the logger explicitly, which makes it a bit more concise and
-- also a tad more safe
runUnclogging subs do
info ["msg" .= String "hello world"]
debug ["msg" .= String "message with number", "number" .= Number 1]
more verbose example with multiple threads involved
import Unclog
import Unclog.Json qualified as Log
import Data.Aeson
import UnliftIO
import Data.String (fromString)
import Control.Monad (replicateM_, void)
main :: IO ()
main = do
let subs = [colourSubscriber Info stdout, simpleSubscriber Fatal stderr, fileSubscriber Debug "bla.log"]
withLoggingWithSubscribers subs \logger -> do
-- spawn a couple of tasks that write to the logger channel concurrently
let spawnTask (i :: Int) = do
$Log.info logger ["msg" .= String ("I am task " <> fromString (show i))]
replicateM_ 2 do
replicateM_ 2 do
$Log.debug logger ["msg" .= String ("debugging from " <> fromString (show i))]
$Log.warn logger ["warning" .= String ("warning from " <> fromString (show i))]
$Log.fatal logger ["error" .= String ("fatal from " <> fromString (show i))]
void $ runConcurrently $ traverse @[] (Concurrently . spawnTask) [1 .. 5]
the looks of it
This is how the coloured logging looks with above example, as you can see, both the simple logger writes the fatal events to stderr witout colouring them, and the coloured logger writes everything from Info
upwards.