@@ -27,10 +27,11 @@ import Control.Applicative as A
27import Control.AutoUpdate
27import Control.AutoUpdate
28import Control.Concurrent
28import Control.Concurrent
29import qualified Control.Concurrent.Async as Async
29import qualified Control.Concurrent.Async as Async
30+import Control.Concurrent.STM
30−import qualified Control.Concurrent.Chan.Unagi.Bounded as UB
31+import qualified Control.Concurrent.STM.TBQueue as BQ
32+import Control.Exception.Safe
31−import Control.Monad (void)
33+import Control.Monad (unless, void)
32import Control.Monad.Base
34import Control.Monad.Base
33−import Control.Monad.Catch
34import Control.Monad.IO.Class
35import Control.Monad.IO.Class
35import Control.Monad.Trans.Class
36import Control.Monad.Trans.Class
36import Control.Monad.Trans.Control
37import Control.Monad.Trans.Control
@@ -38,7 +39,7 @@ import Control.Monad.Trans.Either
38import Control.Monad.Trans.Except
39import Control.Monad.Trans.Except
39import Control.Monad.Trans.Maybe
40import Control.Monad.Trans.Maybe
40import Control.Monad.Trans.Reader
41import Control.Monad.Trans.Reader
41−import Control.Monad.Trans.Resource
42+import Control.Monad.Trans.Resource (ResourceT)
42import Control.Monad.Trans.State
43import Control.Monad.Trans.State
43import Control.Monad.Trans.Writer
44import Control.Monad.Trans.Writer
44import Data.Aeson (FromJSON (..),
45import Data.Aeson (FromJSON (..),
@@ -503,7 +504,7 @@ itemJson verb a = toJSON $ a & itemPayload %~ payloadObject verb
503-- application's shutdown routine to ensure you never miss any log
504-- application's shutdown routine to ensure you never miss any log
504-- messages on shutdown.
505-- messages on shutdown.
505data Scribe = Scribe {
506data Scribe = Scribe {
506− liPush :: forall a. LogItem a => Item a -> IO ()
507+ liPush :: forall a. LogItem a => Item a -> IO ()
507 , scribeFinalizer :: IO ()
508 , scribeFinalizer :: IO ()
508 -- ^ Provide a *blocking* finalizer to call when your scribe is
509 -- ^ Provide a *blocking* finalizer to call when your scribe is
509 -- removed. If this is not relevant to your scribe, return () is
510 -- removed. If this is not relevant to your scribe, return () is
@@ -513,7 +514,7 @@ data Scribe = Scribe {
514instance Semigroup Scribe where
515instance Semigroup Scribe where
515 (Scribe pushA finA) <> (Scribe pushB finB) =
516 (Scribe pushA finA) <> (Scribe pushB finB) =
516− Scribe (\item -> pushA item >> pushB item) (finA >> finB)
517+ Scribe (\item -> pushA item >> pushB item) (finA `finally` finB)
519instance Monoid Scribe where
520instance Monoid Scribe where
@@ -524,7 +525,7 @@ instance Monoid Scribe where
524-------------------------------------------------------------------------------
525-------------------------------------------------------------------------------
525data ScribeHandle = ScribeHandle {
526data ScribeHandle = ScribeHandle {
526 shScribe :: Scribe
527 shScribe :: Scribe
527− , shChan :: UB.InChan WorkerMessage
528+ , shChan :: BQ.TBQueue WorkerMessage
@@ -592,41 +593,42 @@ registerScribe
592 -> LogEnv
593 -> LogEnv
593 -> IO LogEnv
594 -> IO LogEnv
594registerScribe nm scribe ScribeSettings {..} le = do
595registerScribe nm scribe ScribeSettings {..} le = do
595− (inChan, outChan) <- UB.newChan scribeBufferSize
596+ queue <- atomically (BQ.newTBQueue _scribeBufferSize)
596− worker <- spawnScribeWorker scribe outChan
597+ worker <- spawnScribeWorker scribe queue
597 let fin = do
598 let fin = do
598− UB.writeChan inChan PoisonPill
599+ atomically (BQ.writeTBQueue queue PoisonPill)
599 -- wait for our worker to finish final write
600 -- wait for our worker to finish final write
600 void (Async.waitCatch worker)
601 void (Async.waitCatch worker)
601− --TODO: safe exceptions
602 -- wait for scribe to finish final write
602 -- wait for scribe to finish final write
603− scribeFinalizer scribe
603+ void (tryAny (scribeFinalizer scribe))
605− let sh = ScribeHandle (scribe { scribeFinalizer = fin }) inChan
605+ let sh = ScribeHandle (scribe { scribeFinalizer = fin }) queue
606 return (le & logEnvScribes %~ M.insert nm sh)
606 return (le & logEnvScribes %~ M.insert nm sh)
609-------------------------------------------------------------------------------
609-------------------------------------------------------------------------------
610−--TODO: should do some reasonable exception handling in the scribe, don't want to crash the loop
611−spawnScribeWorker :: Scribe -> UB.OutChan WorkerMessage -> IO (Async.Async ())
610+spawnScribeWorker :: Scribe -> BQ.TBQueue WorkerMessage -> IO (Async.Async ())
612−spawnScribeWorker (Scribe write _) outChan = Async.async go
611+spawnScribeWorker (Scribe write _) queue = Async.async go
615− newCmd <- UB.readChan outChan
614+ newCmd <- atomically (BQ.readTBQueue queue)
616 case newCmd of
615 case newCmd of
617 NewItem a -> do
616 NewItem a -> do
617+ -- Swallow any direct exceptions from the
618+ -- scribe. safe-exceptions won't catch async exceptions.
618− write a
619+ void (tryAny (write a))
620 PoisonPill -> return ()
621 PoisonPill -> return ()
623-------------------------------------------------------------------------------
624-------------------------------------------------------------------------------
624data ScribeSettings = ScribeSettings {
625data ScribeSettings = ScribeSettings {
625− scribeBufferSize :: Int
626+ _scribeBufferSize :: Int
629+makeLenses ''ScribeSettings
629−--TODO: standardize on american or british spelling of finali{z,s}er
630-- | Reasonable defaults for a scribe. Buffer
632-- | Reasonable defaults for a scribe. Buffer
631-- size of 4096.
633-- size of 4096.
632defaultScribeSettings :: ScribeSettings
634defaultScribeSettings :: ScribeSettings
@@ -634,30 +636,52 @@ defaultScribeSettings = ScribeSettings 4096
636-------------------------------------------------------------------------------
638-------------------------------------------------------------------------------
637−-- | Remove a scribe from the list. All future log calls will no
639+-- | Remove a scribe from the environment. This does *not* finalize
638−-- longer use this scribe. If there is a finalizer, it will be called
640+-- the scribe. This mainly only makes sense to use with something like
639−-- in a blocking manner.
641+-- MonadReader's @local@ function to temporarily disavow a single
642+-- logger for a block of code.
640unregisterScribe
643unregisterScribe
645+ -- ^ Name of the scribe
648+unregisterScribe nm = logEnvScribes %~ M.delete nm
651+-------------------------------------------------------------------------------
652+-- | Unregister *all* scribes. Note that this is *not* for closing or
653+-- finalizing scribes, use 'closeScribes' for that. This mainly only
654+-- makes sense to use with something like MonadReader's @local@
655+-- function to temporarily disavow any loggers for a block of code.
659+clearScribes = logEnvScribes .~ mempty
662+-------------------------------------------------------------------------------
663+-- | Finalize a scribe. The scribe is removed from the environment,
664+-- its finalizer is called and it can never be written to again.
642 -- ^ Name of the scribe
667 -- ^ Name of the scribe
643 -> LogEnv
668 -> LogEnv
644 -> IO LogEnv
669 -> IO LogEnv
645−unregisterScribe nm le = do
670+closeScribe nm le = do
646 maybe (return ()) (scribeFinalizer . shScribe) (M.lookup nm (_logEnvScribes le))
671 maybe (return ()) (scribeFinalizer . shScribe) (M.lookup nm (_logEnvScribes le))
647 return (le & logEnvScribes %~ M.delete nm)
672 return (le & logEnvScribes %~ M.delete nm)
650-------------------------------------------------------------------------------
675-------------------------------------------------------------------------------
651−-- | Unregister *all* scribes. Logs will go off into space from this
676+-- | Call this at the end of your program. This is a blocking call
652−-- point onward until new scribes are added. Note that you could use
677+-- that stop writing to a scribe's queue, waits for the queue to
653−-- this with `local` if you're using a Reader based stack to
678+-- empty, finalizes each scribe in the log environment and then
654−-- temporarily disable log output. See `katipNoLogging` for an
679+-- removes it.
655−-- example.
680+closeScribes
657 :: LogEnv
681 :: LogEnv
658 -> IO LogEnv
682 -> IO LogEnv
659−clearScribes le =
683+closeScribes le =
660− foldrM unregisterScribe le (M.keys (_logEnvScribes le))
684+ foldrM closeScribe le (M.keys (_logEnvScribes le))
663-------------------------------------------------------------------------------
687-------------------------------------------------------------------------------
@@ -756,9 +780,21 @@ logItem a ns loc sev msg = do
756 <*> _logEnvTimer
780 <*> _logEnvTimer
757 <*> pure (_logEnvApp <> ns)
781 <*> pure (_logEnvApp <> ns)
758 <*> pure loc
782 <*> pure loc
759− FT.forM_ (M.elems _logEnvScribes) $ \ ScribeHandle {..} -> UB.tryWriteChan shChan (NewItem item)
783+ FT.forM_ (M.elems _logEnvScribes) $ \ ScribeHandle {..} -> atomically (tryWriteTBQueue shChan (NewItem item))
784+ --TODO: if we use tbqueue, write an atomic tryWriteTBQueue
787+-------------------------------------------------------------------------------
793+tryWriteTBQueue q a = do
794+ full <- isFullTBQueue q
795+ unless full (writeTBQueue q a)
762-------------------------------------------------------------------------------
798-------------------------------------------------------------------------------
763-- | Log with full context, but without any code location.
799-- | Log with full context, but without any code location.
@@ -788,7 +824,7 @@ logException
788 -> Severity -- ^ Severity
824 -> Severity -- ^ Severity
789 -> m b -- ^ Main action being run
825 -> m b -- ^ Main action being run
791−logException a ns sev action = action `catchAll` \e -> f e >> throwM e
827+logException a ns sev action = action `catchAny` \e -> f e >> throwM e
793 f e = logF a ns sev (msg e)
829 f e = logF a ns sev (msg e)
794 msg e = ls (T.pack "An exception has occured: ") <> showLS e
830 msg e = ls (T.pack "An exception has occured: ") <> showLS e