@@ -27,10 +27,11 @@ import Control.Applicative as A
2727import Control.AutoUpdate
2828import Control.Concurrent
2929import qualified Control.Concurrent.Async as Async
30−import qualified Control.Concurrent.Chan.Unagi.Bounded as UB
31−import Control.Monad (void)
30+import Control.Concurrent.STM
31+import qualified Control.Concurrent.STM.TBQueue as BQ
32+import Control.Exception.Safe
33+import Control.Monad (unless, void)
3234import Control.Monad.Base
33−import Control.Monad.Catch
3435import Control.Monad.IO.Class
3536import Control.Monad.Trans.Class
3637import Control.Monad.Trans.Control
@@ -38,7 +39,7 @@ import Control.Monad.Trans.Either
3839import Control.Monad.Trans.Except
3940import Control.Monad.Trans.Maybe
4041import Control.Monad.Trans.Reader
41−import Control.Monad.Trans.Resource
42+import Control.Monad.Trans.Resource (ResourceT)
4243import Control.Monad.Trans.State
4344import Control.Monad.Trans.Writer
4445import Data.Aeson (FromJSON (..),
@@ -503,7 +504,7 @@ itemJson verb a = toJSON $ a & itemPayload %~ payloadObject verb
503504-- application's shutdown routine to ensure you never miss any log
504505-- messages on shutdown.
505506data Scribe = Scribe {
506− liPush :: forall a. LogItem a => Item a -> IO ()
507+ liPush :: forall a. LogItem a => Item a -> IO ()
507508 , scribeFinalizer :: IO ()
508509 -- ^ Provide a *blocking* finalizer to call when your scribe is
509510 -- removed. If this is not relevant to your scribe, return () is
@@ -513,7 +514,7 @@ data Scribe = Scribe {
513514
514515instance Semigroup Scribe where
515516 (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)
517518
518519
519520instance Monoid Scribe where
@@ -524,7 +525,7 @@ instance Monoid Scribe where
524525-------------------------------------------------------------------------------
525526data ScribeHandle = ScribeHandle {
526527 shScribe :: Scribe
527− , shChan :: UB.InChan WorkerMessage
528+ , shChan :: BQ.TBQueue WorkerMessage
528529 }
529530
530531
@@ -592,41 +593,42 @@ registerScribe
592593 -> LogEnv
593594 -> IO LogEnv
594595registerScribe nm scribe ScribeSettings {..} le = do
595− (inChan, outChan) <- UB.newChan scribeBufferSize
596− worker <- spawnScribeWorker scribe outChan
596+ queue <- atomically (BQ.newTBQueue _scribeBufferSize)
597+ worker <- spawnScribeWorker scribe queue
597598 let fin = do
598− UB.writeChan inChan PoisonPill
599+ atomically (BQ.writeTBQueue queue PoisonPill)
599600 -- wait for our worker to finish final write
600601 void (Async.waitCatch worker)
601− --TODO: safe exceptions
602602 -- wait for scribe to finish final write
603− scribeFinalizer scribe
603+ void (tryAny (scribeFinalizer scribe))
604604
605− let sh = ScribeHandle (scribe { scribeFinalizer = fin }) inChan
605+ let sh = ScribeHandle (scribe { scribeFinalizer = fin }) queue
606606 return (le & logEnvScribes %~ M.insert nm sh)
607607
608608
609609-------------------------------------------------------------------------------
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 ())
612−spawnScribeWorker (Scribe write _) outChan = Async.async go
610+spawnScribeWorker :: Scribe -> BQ.TBQueue WorkerMessage -> IO (Async.Async ())
611+spawnScribeWorker (Scribe write _) queue = Async.async go
613612 where
614613 go = do
615− newCmd <- UB.readChan outChan
614+ newCmd <- atomically (BQ.readTBQueue queue)
616615 case newCmd of
617616 NewItem a -> do
618− write a
617+ -- Swallow any direct exceptions from the
618+ -- scribe. safe-exceptions won't catch async exceptions.
619+ void (tryAny (write a))
619620 go
620621 PoisonPill -> return ()
621622
622623
623624-------------------------------------------------------------------------------
624625data ScribeSettings = ScribeSettings {
625− scribeBufferSize :: Int
626+ _scribeBufferSize :: Int
626627 }
627628
629+makeLenses ''ScribeSettings
630+
628631
629−--TODO: standardize on american or british spelling of finali{z,s}er
630632-- | Reasonable defaults for a scribe. Buffer
631633-- size of 4096.
632634defaultScribeSettings :: ScribeSettings
@@ -634,30 +636,52 @@ defaultScribeSettings = ScribeSettings 4096
634636
635637
636638-------------------------------------------------------------------------------
637−-- | Remove a scribe from the list. All future log calls will no
638−-- longer use this scribe. If there is a finalizer, it will be called
639−-- in a blocking manner.
639+-- | Remove a scribe from the environment. This does *not* finalize
640+-- the scribe. This mainly only makes sense to use with something like
641+-- MonadReader's @local@ function to temporarily disavow a single
642+-- logger for a block of code.
640643unregisterScribe
644+ :: Text
645+ -- ^ Name of the scribe
646+ -> LogEnv
647+ -> LogEnv
648+unregisterScribe nm = logEnvScribes %~ M.delete nm
649+
650+
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.
656+clearScribes
657+ :: LogEnv
658+ -> LogEnv
659+clearScribes = logEnvScribes .~ mempty
660+
661+
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.
665+closeScribe
641666 :: Text
642667 -- ^ Name of the scribe
643668 -> LogEnv
644669 -> IO LogEnv
645−unregisterScribe nm le = do
670+closeScribe nm le = do
646671 maybe (return ()) (scribeFinalizer . shScribe) (M.lookup nm (_logEnvScribes le))
647672 return (le & logEnvScribes %~ M.delete nm)
648673
649674
650675-------------------------------------------------------------------------------
651−-- | Unregister *all* scribes. Logs will go off into space from this
652−-- point onward until new scribes are added. Note that you could use
653−-- this with `local` if you're using a Reader based stack to
654−-- temporarily disable log output. See `katipNoLogging` for an
655−-- example.
656−clearScribes
676+-- | Call this at the end of your program. This is a blocking call
677+-- that stop writing to a scribe's queue, waits for the queue to
678+-- empty, finalizes each scribe in the log environment and then
679+-- removes it.
680+closeScribes
657681 :: LogEnv
658682 -> IO LogEnv
659−clearScribes le =
660− foldrM unregisterScribe le (M.keys (_logEnvScribes le))
683+closeScribes le =
684+ foldrM closeScribe le (M.keys (_logEnvScribes le))
661685
662686
663687-------------------------------------------------------------------------------
@@ -756,9 +780,21 @@ logItem a ns loc sev msg = do
756780 <*> _logEnvTimer
757781 <*> pure (_logEnvApp <> ns)
758782 <*> 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
760785
761786
787+-------------------------------------------------------------------------------
788+tryWriteTBQueue
789+ :: TBQueue a
790+ -> a
791+ -> STM Bool
792+ -- ^ Did we write?
793+tryWriteTBQueue q a = do
794+ full <- isFullTBQueue q
795+ unless full (writeTBQueue q a)
796+ return (not full)
797+
762798-------------------------------------------------------------------------------
763799-- | Log with full context, but without any code location.
764800logF
@@ -788,7 +824,7 @@ logException
788824 -> Severity -- ^ Severity
789825 -> m b -- ^ Main action being run
790826 -> m b
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
792828 where
793829 f e = logF a ns sev (msg e)
794830 msg e = ls (T.pack "An exception has occured: ") <> showLS e