packages feed

katip 0.8.8.0 → 0.8.8.1

raw patch · 5 files changed

+87/−13 lines, 5 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Katip: Item :: Namespace -> Environment -> Severity -> ThreadIdText -> HostName -> ProcessID -> a -> LogStr -> UTCTime -> Namespace -> Maybe Loc -> Item a
+ Katip: Item :: Namespace -> Environment -> {-# UNPACK #-} !Severity -> ThreadIdText -> HostName -> ProcessID -> a -> LogStr -> UTCTime -> Namespace -> Maybe Loc -> Item a
- Katip: Scribe :: (forall a. LogItem a => Item a -> IO ()) -> IO () -> PermitFunc -> Scribe
+ Katip: Scribe :: (forall a. LogItem a => Item a -> IO ()) -> IO () -> !PermitFunc -> Scribe
- Katip: [_itemSeverity] :: Item a -> Severity
+ Katip: [_itemSeverity] :: Item a -> {-# UNPACK #-} !Severity
- Katip: [scribePermitItem] :: Scribe -> PermitFunc
+ Katip: [scribePermitItem] :: Scribe -> !PermitFunc
- Katip: itemApp :: forall a_aasr. Lens' (Item a_aasr) Namespace
+ Katip: itemApp :: forall a_abaT. Lens' (Item a_abaT) Namespace
- Katip: itemEnv :: forall a_aasr. Lens' (Item a_aasr) Environment
+ Katip: itemEnv :: forall a_abaT. Lens' (Item a_abaT) Environment
- Katip: itemHost :: forall a_aasr. Lens' (Item a_aasr) HostName
+ Katip: itemHost :: forall a_abaT. Lens' (Item a_abaT) HostName
- Katip: itemLoc :: forall a_aasr. Lens' (Item a_aasr) (Maybe Loc)
+ Katip: itemLoc :: forall a_abaT. Lens' (Item a_abaT) (Maybe Loc)
- Katip: itemMessage :: forall a_aasr. Lens' (Item a_aasr) LogStr
+ Katip: itemMessage :: forall a_abaT. Lens' (Item a_abaT) LogStr
- Katip: itemNamespace :: forall a_aasr. Lens' (Item a_aasr) Namespace
+ Katip: itemNamespace :: forall a_abaT. Lens' (Item a_abaT) Namespace
- Katip: itemPayload :: forall a_aasr a_adON. Lens (Item a_aasr) (Item a_adON) a_aasr a_adON
+ Katip: itemPayload :: forall a_abaT a_ae8P. Lens (Item a_abaT) (Item a_ae8P) a_abaT a_ae8P
- Katip: itemProcess :: forall a_aasr. Lens' (Item a_aasr) ProcessID
+ Katip: itemProcess :: forall a_abaT. Lens' (Item a_abaT) ProcessID
- Katip: itemSeverity :: forall a_aasr. Lens' (Item a_aasr) Severity
+ Katip: itemSeverity :: forall a_abaT. Lens' (Item a_abaT) Severity
- Katip: itemThread :: forall a_aasr. Lens' (Item a_aasr) ThreadIdText
+ Katip: itemThread :: forall a_abaT. Lens' (Item a_abaT) ThreadIdText
- Katip: itemTime :: forall a_aasr. Lens' (Item a_aasr) UTCTime
+ Katip: itemTime :: forall a_abaT. Lens' (Item a_abaT) UTCTime
- Katip.Core: Item :: Namespace -> Environment -> Severity -> ThreadIdText -> HostName -> ProcessID -> a -> LogStr -> UTCTime -> Namespace -> Maybe Loc -> Item a
+ Katip.Core: Item :: Namespace -> Environment -> {-# UNPACK #-} !Severity -> ThreadIdText -> HostName -> ProcessID -> a -> LogStr -> UTCTime -> Namespace -> Maybe Loc -> Item a
- Katip.Core: Scribe :: (forall a. LogItem a => Item a -> IO ()) -> IO () -> PermitFunc -> Scribe
+ Katip.Core: Scribe :: (forall a. LogItem a => Item a -> IO ()) -> IO () -> !PermitFunc -> Scribe
- Katip.Core: [_itemSeverity] :: Item a -> Severity
+ Katip.Core: [_itemSeverity] :: Item a -> {-# UNPACK #-} !Severity
- Katip.Core: [scribePermitItem] :: Scribe -> PermitFunc
+ Katip.Core: [scribePermitItem] :: Scribe -> !PermitFunc
- Katip.Core: itemApp :: forall a_aasr. Lens' (Item a_aasr) Namespace
+ Katip.Core: itemApp :: forall a_abaT. Lens' (Item a_abaT) Namespace
- Katip.Core: itemEnv :: forall a_aasr. Lens' (Item a_aasr) Environment
+ Katip.Core: itemEnv :: forall a_abaT. Lens' (Item a_abaT) Environment
- Katip.Core: itemHost :: forall a_aasr. Lens' (Item a_aasr) HostName
+ Katip.Core: itemHost :: forall a_abaT. Lens' (Item a_abaT) HostName
- Katip.Core: itemLoc :: forall a_aasr. Lens' (Item a_aasr) (Maybe Loc)
+ Katip.Core: itemLoc :: forall a_abaT. Lens' (Item a_abaT) (Maybe Loc)
- Katip.Core: itemMessage :: forall a_aasr. Lens' (Item a_aasr) LogStr
+ Katip.Core: itemMessage :: forall a_abaT. Lens' (Item a_abaT) LogStr
- Katip.Core: itemNamespace :: forall a_aasr. Lens' (Item a_aasr) Namespace
+ Katip.Core: itemNamespace :: forall a_abaT. Lens' (Item a_abaT) Namespace
- Katip.Core: itemPayload :: forall a_aasr a_adON. Lens (Item a_aasr) (Item a_adON) a_aasr a_adON
+ Katip.Core: itemPayload :: forall a_abaT a_ae8P. Lens (Item a_abaT) (Item a_ae8P) a_abaT a_ae8P
- Katip.Core: itemProcess :: forall a_aasr. Lens' (Item a_aasr) ProcessID
+ Katip.Core: itemProcess :: forall a_abaT. Lens' (Item a_abaT) ProcessID
- Katip.Core: itemSeverity :: forall a_aasr. Lens' (Item a_aasr) Severity
+ Katip.Core: itemSeverity :: forall a_abaT. Lens' (Item a_abaT) Severity
- Katip.Core: itemThread :: forall a_aasr. Lens' (Item a_aasr) ThreadIdText
+ Katip.Core: itemThread :: forall a_abaT. Lens' (Item a_abaT) ThreadIdText
- Katip.Core: itemTime :: forall a_aasr. Lens' (Item a_aasr) UTCTime
+ Katip.Core: itemTime :: forall a_abaT. Lens' (Item a_abaT) UTCTime

Files

changelog.md view
@@ -1,3 +1,7 @@+0.8.8.1+=======+* Improve logging performance (4x) via inlining [#151](https://github.com/Soostone/katip/pull/151)+ 0.8.8.0 ======= * Add support for rendering arrays in the log context to the handle scribe [#150](https://github.com/Soostone/katip/pull/150)
katip.cabal view
@@ -1,5 +1,5 @@ name:                katip-version:             0.8.8.0+version:             0.8.8.1 synopsis:            A structured logging framework. description:   Katip is a structured logging framework. See README.md for more details.@@ -84,7 +84,7 @@    hs-source-dirs:      src   default-language:    Haskell2010-  ghc-options:        -Wall+  ghc-options:        -Wall -O2   if flag(lib-Werror)     ghc-options: -Werror   if os(windows)@@ -133,7 +133,7 @@   main-is: Main.hs   hs-source-dirs: bench   default-language:    Haskell2010-  ghc-options: -O2 -Wall -threaded -rtsopts -with-rtsopts=-N+  ghc-options: -O2 -Wall -threaded -rtsopts "-with-rtsopts=-N"   if flag(lib-Werror)     ghc-options: -Werror   build-depends:
src/Katip/Core.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveLift #-}+{-# LANGUAGE BangPatterns               #-} {-# LANGUAGE CPP                        #-} {-# LANGUAGE DefaultSignatures          #-} {-# LANGUAGE DeriveFunctor              #-}@@ -103,6 +104,7 @@               [x] -> Just x               []  -> Nothing -- no parse               _   -> Nothing -- Ambiguous parse+{-# INLINE readMay #-}   -------------------------------------------------------------------------------@@ -122,6 +124,7 @@ -- | Ready namespace for emission with dots to join the segments. intercalateNs :: Namespace -> [Text] intercalateNs (Namespace xs) = intersperse "." xs+{-# INLINE intercalateNs #-}   -------------------------------------------------------------------------------@@ -156,8 +159,9 @@   -------------------------------------------------------------------------------+{-# INLINE renderSeverity #-} renderSeverity :: Severity -> Text-renderSeverity s = case s of+renderSeverity !s = case s of       DebugS     -> "Debug"       InfoS      -> "Info"       NoticeS    -> "Notice"@@ -169,6 +173,7 @@   -------------------------------------------------------------------------------+{-# INLINE textToSeverity #-} textToSeverity :: Text -> Maybe Severity textToSeverity = go . T.toLower   where@@ -220,11 +225,13 @@  instance Semigroup LogStr where   (LogStr a) <> (LogStr b) = LogStr (a <> b)+  {-# INLINE (<>) #-}   instance Monoid LogStr where     mappend = (<>)     mempty = LogStr mempty+    {-# INLINE mempty #-}   instance FromJSON LogStr where@@ -238,18 +245,21 @@ -- lazy variants. logStr :: StringConv a Text => a -> LogStr logStr t = LogStr (B.fromText $ toS t)+{-# INLINE logStr #-}   ------------------------------------------------------------------------------- -- | Shorthand for 'logStr' ls :: StringConv a Text => a -> LogStr ls = logStr+{-# INLINE ls #-}   ------------------------------------------------------------------------------- -- | Convert any showable type into a 'LogStr'. showLS :: Show a => a -> LogStr showLS = ls . show+{-# INLINE showLS #-}   -------------------------------------------------------------------------------@@ -262,14 +272,14 @@ mkThreadIdText = ThreadIdText . stripPrefix' "ThreadId " . T.pack . show   where     stripPrefix' pfx t = fromMaybe t (T.stripPrefix pfx t)-+{-# INLINE mkThreadIdText #-}  ------------------------------------------------------------------------------- -- | This has everything each log message will contain. data Item a = Item {       _itemApp       :: Namespace     , _itemEnv       :: Environment-    , _itemSeverity  :: Severity+    , _itemSeverity  :: {-# UNPACK #-} !Severity     , _itemThread    :: ThreadIdText     , _itemHost      :: HostName     , _itemProcess   :: ProcessID@@ -398,10 +408,12 @@  processIDToText :: ProcessID -> Text processIDToText = toS . show+{-# INLINE processIDToText #-}   textToProcessID :: Text -> Maybe ProcessID textToProcessID = readMay . toS+{-# INLINE textToProcessID #-}   newtype ProcessIDJs = ProcessIDJs {@@ -445,6 +457,7 @@ equivalentPayloadSelection AllKeys AllKeys = True equivalentPayloadSelection (SomeKeys a) (SomeKeys b) = Set.fromList a == Set.fromList b equivalentPayloadSelection _ _ = False+{-# INLINE equivalentPayloadSelection #-}  ------------------------------------------------------------------------------- -- | Katip requires JSON objects to be logged as context. This@@ -516,6 +529,7 @@ toKey :: a -> a toKey = id #endif+{-# INLINE toKey #-}   instance ToObject SimpleLogPayload@@ -539,6 +553,7 @@ -- | Construct a simple log from any JSON item. sl :: ToJSON a => Text -> a -> SimpleLogPayload sl a b = SimpleLogPayload [(a, AnyLogPayload b)]+{-# INLINE sl #-}   -------------------------------------------------------------------------------@@ -548,6 +563,7 @@ payloadObject verb a = case FT.foldMap (flip payloadKeys a) [(V0)..verb] of     AllKeys     -> toObject a     SomeKeys ks -> filterElems ks $ toObject a+{-# INLINE payloadObject #-}  #if MIN_VERSION_aeson(2, 0, 0) filterElems :: [Text] -> KM.KeyMap v -> KM.KeyMap v@@ -556,6 +572,7 @@ filterElems :: [Text] -> HM.HashMap Text v -> HM.HashMap Text v filterElems ks = HM.filterWithKey (\ k _ -> k `FT.elem` ks) #endif+{-# INLINE filterElems #-}   -------------------------------------------------------------------------------@@ -564,6 +581,7 @@ -- messages should use this to obtain their payload. itemJson :: LogItem a => Verbosity -> Item a -> A.Value itemJson verb a = toJSON $ a & itemPayload %~ payloadObject verb+{-# INLINE itemJson #-}   -------------------------------------------------------------------------------@@ -611,12 +629,15 @@   -- | AND together 2 permit functions+ permitAND :: PermitFunc -> PermitFunc -> PermitFunc permitAND f1 f2 = \a -> liftA2 (&&) (f1 a) (f2 a)+{-# INLINE permitAND #-}  -- | OR together 2 permit functions permitOR :: PermitFunc -> PermitFunc -> PermitFunc permitOR f1 f2 = \a -> liftA2 (||) (f1 a) (f2 a)+{-# INLINE permitOR #-}   data Scribe = Scribe {@@ -626,7 +647,7 @@    -- ^ Provide a __blocking__ finalizer to call when your scribe is    -- removed. All pending writes should be flushed synchronously. If    -- this is not relevant to your scribe, return () is fine.-   , scribePermitItem :: PermitFunc+   , scribePermitItem :: !PermitFunc    -- ^ Provide a filtering function to allow the item to be logged,    --   or not.  It can check Severity or some string in item's    --   body. The initial value of this is usually created from@@ -637,7 +658,7 @@  whenM :: Monad m => m Bool -> m () -> m () whenM mbool = (>>=) mbool . flip when-+{-# INLINE whenM #-}  -- | Combine two scribes. Publishes to the left scribe if the left -- would permit the item and to the right scribe if the right would@@ -650,10 +671,12 @@            )            (finA `finally` finB)            (permitOR permitA permitB)+  {-# INLINE (<>) #-}   instance Monoid Scribe where     mempty = Scribe (const (return ())) (return ()) (permitItem DebugS)+    {-# INLINE mempty #-}     mappend = (<>)  @@ -675,6 +698,7 @@ -- Most new scribes will use this as a base for their 'PermitFunc' permitItem :: Monad m => Severity -> Item a -> m Bool permitItem sev item = return (_itemSeverity item >= sev)+{-# INLINE permitItem #-}   -------------------------------------------------------------------------------@@ -716,6 +740,7 @@   <*> pure env   <*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime, updateFreq = 1000 }   <*> pure mempty+{-# INLINE initLogEnv#-}   -------------------------------------------------------------------------------@@ -742,6 +767,7 @@    let sh = ScribeHandle (scribe { scribeFinalizer = fin }) queue   return (le & logEnvScribes %~ M.insert nm sh)+{-# INLINE registerScribe#-}   -------------------------------------------------------------------------------@@ -757,7 +783,7 @@           void (tryAny (write a))           go         PoisonPill -> return ()-+{-# INLINE spawnScribeWorker #-}  ------------------------------------------------------------------------------- data ScribeSettings = ScribeSettings {@@ -853,59 +879,81 @@  instance Katip m => Katip (ReaderT s m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = mapReaderT . localLogEnv+    {-# INLINE localLogEnv #-}   #if !MIN_VERSION_either(4, 5, 0) instance Katip m => Katip (EitherT s m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = mapEitherT . localLogEnv+    {-# INLINE localLogEnv #-} #endif   instance Katip m => Katip (ExceptT s m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = mapExceptT . localLogEnv+    {-# INLINE localLogEnv #-}   instance Katip m => Katip (MaybeT m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = mapMaybeT . localLogEnv+    {-# INLINE localLogEnv #-}   instance Katip m => Katip (StateT s m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = mapStateT . localLogEnv+    {-# INLINE localLogEnv #-}   instance (Katip m, Monoid w) => Katip (RWST r w s m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = mapRWST . localLogEnv+    {-# INLINE localLogEnv #-}   instance (Katip m, Monoid w) => Katip (Strict.RWST r w s m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = Strict.mapRWST . localLogEnv+    {-# INLINE localLogEnv #-}   instance Katip m => Katip (Strict.StateT s m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = Strict.mapStateT . localLogEnv+    {-# INLINE localLogEnv #-}   instance (Katip m, Monoid s) => Katip (WriterT s m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = mapWriterT . localLogEnv+    {-# INLINE localLogEnv #-}   instance (Katip m, Monoid s) => Katip (Strict.WriterT s m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = Strict.mapWriterT . localLogEnv+    {-# INLINE localLogEnv #-}   instance (Katip m) => Katip (ResourceT m) where     getLogEnv = lift getLogEnv+    {-# INLINE getLogEnv #-}     localLogEnv = transResourceT . localLogEnv+    {-# INLINE localLogEnv #-}   -------------------------------------------------------------------------------@@ -956,6 +1004,7 @@ -- | Execute 'KatipT' on a log env. runKatipT :: LogEnv -> KatipT m a -> m a runKatipT le (KatipT f) = runReaderT f le+{-# INLINE runKatipT #-}   -------------------------------------------------------------------------------@@ -994,6 +1043,7 @@             <*> _logEnvTimer             <*> pure (_logEnvApp <> ns)             <*> pure loc)+{-# INLINE logItem#-}  -- | Log already constructed 'Item'. This is the lowest level function that other log* --   functions use.@@ -1008,6 +1058,7 @@       FT.forM_ (M.elems _logEnvScribes) $ \ ScribeHandle {..} -> do         whenM (scribePermitItem shScribe item) $           void $ atomically (tryWriteTBQueue shChan (NewItem item))+{-# INLINE logKatipItem#-}  ------------------------------------------------------------------------------- tryWriteTBQueue@@ -1019,6 +1070,7 @@   full <- isFullTBQueue q   unless full (writeTBQueue q a)   return (not full)+{-# INLINE tryWriteTBQueue#-}   -------------------------------------------------------------------------------@@ -1035,6 +1087,7 @@   -- ^ The log message   -> m () logF a ns sev msg = logItem a ns Nothing sev msg+{-# INLINE logF#-}   
src/Katip/Format/Time.hs view
@@ -41,7 +41,7 @@     return (buf, 19)   where     toText (arr, len) = Text arr 0 len-{-# INLINEABLE formatAsLogTime #-}+{-# INLINE formatAsLogTime #-}  -- | Format 'UTCTime' into a Iso8601 format. --@@ -68,7 +68,7 @@     return (buf, next + 1)   where     toText (arr, len) = Text arr 0 len-{-# INLINEABLE formatAsIso8601 #-}+{-# INLINE formatAsIso8601 #-}  -- | Writes the @YYYY-MM-DD@ part of timestamp writeDay :: TA.MArray s -> Int -> Day -> ST s Int@@ -115,6 +115,7 @@     T s1 s2 = twoDigits (fromIntegral real)     (real, frac) = ss `quotRem` pico     pico = 1000000000000 -- number of picoseconds  in 1 second+{-# INLINE writeTimeOfDay #-}  writeFracSeconds :: TA.MArray s -> Int -> Int64 -> ST s Int writeFracSeconds buf off frac =@@ -129,6 +130,7 @@   where     (mics, mills) = frac `quotRem` micro     micro = 1000000 -- number of microseconds in 1 second+{-# INLINE writeFracSeconds #-}  writeDigit6 :: TA.MArray s -> Int -> Int -> ST s () writeDigit6 buf off i =
src/Katip/Scribes/Handle.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP, OverloadedStrings #-}  module Katip.Scribes.Handle where @@ -31,24 +31,30 @@  ------------------------------------------------------------------------------- brackets :: Builder -> Builder-brackets m = fromText "[" M.<> m <> fromText "]"+brackets m = "[" M.<> m <> "]"+{-# INLINE brackets #-}  ------------------------------------------------------------------------------- getKeys :: LogItem s => Verbosity -> s -> [Builder] getKeys verb a = concat (toBuilders (payloadObject verb a))+{-# INLINE getKeys #-}  #if MIN_VERSION_aeson(2, 0, 0) toBuilders :: KM.KeyMap Value -> [[Builder]] toBuilders = fmap (renderPair . first K.toText) . KM.toList+{-# INLINE toBuilders #-}  toTxtKeyList :: KM.KeyMap v -> [(Text, v)] toTxtKeyList mp = first K.toText <$> KM.toList mp+{-# INLINE toTxtKeyList #-} #else toBuilders :: HM.HashMap Text Value -> [[Builder]] toBuilders = fmap renderPair . HM.toList+{-# INLINE toBuilders #-}  toTxtKeyList :: HM.HashMap Text v -> [(Text, v)] toTxtKeyList = HM.toList+{-# INLINE toTxtKeyList #-} #endif  renderPair :: (Text, Value) -> [Builder]@@ -64,6 +70,7 @@     formatNumber :: Scientific -> String     formatNumber n =       formatScientific Generic (if isFloating n then Nothing else Just 0) n+{-# INLINE renderPair #-}  ------------------------------------------------------------------------------- data ColorStrategy@@ -87,6 +94,7 @@ -- handle. Handle mode is set to 'LineBuffering' automatically. mkHandleScribe :: ColorStrategy -> Handle -> PermitFunc -> Verbosity -> IO Scribe mkHandleScribe = mkHandleScribeWithFormatter bracketFormat+{-# INLINE mkHandleScribe #-}  -- | Logs to a file handle such as stdout, stderr, or a file. Takes a custom -- `ItemFormatter` that can be used to format `Item` as needed.@@ -110,6 +118,7 @@         bracket_ (takeMVar lock) (putMVar lock ()) $           T.hPutStrLn h $ toLazyText $ itemFormatter colorize verb i   return $ Scribe logger (hFlush h) permitF+{-# INLINE mkHandleScribeWithFormatter #-}  ------------------------------------------------------------------------------- @@ -123,6 +132,7 @@   h <- openFile f AppendMode   Scribe logger finalizer permit <- mkHandleScribe (ColorLog False) h permitF verb   return (Scribe logger (finalizer `finally` hClose h) permit)+{-# INLINE mkFileScribe #-}  ------------------------------------------------------------------------------- @@ -136,6 +146,7 @@ formatItem :: LogItem a => ItemFormatter a formatItem = bracketFormat {-# DEPRECATED formatItem "Use bracketFormat instead" #-}+{-# INLINE formatItem #-}  -- | A traditional 'bracketed' log format. Contexts and other information will -- be flattened out into bracketed fields. For example:@@ -160,6 +171,7 @@     ks = map brackets $ getKeys verb _itemPayload     renderSeverity' severity =       colorBySeverity withColor severity (renderSeverity severity)+{-# INLINE bracketFormat #-}  -- | Logs items as JSON. This can be useful in circumstances where you already -- have infrastructure that is expecting JSON to be logged to a standard stream@@ -173,6 +185,7 @@   fromText $     colorBySeverity withColor (_itemSeverity i) $       toStrict $ decodeUtf8 $ encode $ itemJson verb i+{-# INLINE jsonFormat #-}  -- | Color a text message based on `Severity`. `ErrorS` and more severe errors -- are colored red, `WarningS` is colored yellow, and all other messages are@@ -191,6 +204,7 @@     colorize c s       | withColor = "\ESC[" <> c <> "m" <> s <> "\ESC[0m"       | otherwise = s+{-# INLINE colorBySeverity #-}  -- | Provides a simple log environment with 1 scribe going to -- stdout. This is a decent example of how to build a LogEnv and is@@ -201,3 +215,4 @@   le <- initLogEnv "io" "io"   lh <- mkHandleScribe ColorIfTerminal stdout permit verb   registerScribe "stdout" lh defaultScribeSettings le+{-# INLINE ioLogEnv #-}