packages feed

katip-effectful (empty) → 0.0.1

raw patch · 6 files changed

+684/−0 lines, 6 filesdep +QuickCheckdep +basedep +containers

Dependencies added: QuickCheck, base, containers, effectful-core, effectful-plugin, katip, katip-effectful, quickcheck-instances, tasty, tasty-golden, tasty-hunit, tasty-quickcheck, template-haskell, text, time, unix

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for placeholder++## 0.1.0.0 -- 2023-10-26++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2023, Eldritch Cookie++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Eldritch Cookie nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ katip-effectful.cabal view
@@ -0,0 +1,113 @@+cabal-version:   3.4++-- The cabal-version field refers to the version of the .cabal specification,+-- and can be different from the cabal-install (the tool) version and the+-- Cabal (the library) version you are using. As such, the Cabal (the library)+-- version used must be equal or greater than the version stated in this field.+-- Starting from the specification version 2.2, the cabal-version field must be+-- the first thing in the cabal file.++-- Initial package description 'placeholder' generated by+-- 'cabal init'. For further documentation, see:+--   http://haskell.org/cabal/users-guide/+--+-- The name of the package.+name:            katip-effectful++-- The package version.+-- See the Haskell package versioning policy (PVP) for standards+-- guiding when and how versions should be incremented.+-- https://pvp.haskell.org+version:         0.0.1++-- A short (one-line) description of the package.+synopsis:        Katip integration for Effectful++-- A longer description of the package.+description:+  Provides a Katip and KatipContext instance for the Eff Monad and re-exports all of Katip's API++homepage:        https://github.com/eldritch-cookie/katip-effectful++-- The license under which the package is released.+license:         BSD-3-Clause++-- The file containing the license text.+license-file:    LICENSE++-- The package author(s).+author:          Eldritch Cookie++-- An email address to which users can send suggestions, bug reports, and patches.+maintainer:      eldritch.cookie@disroot.org++-- A copyright notice.+-- copyright:+build-type:      Simple+bug-reports:     https://github.com/eldritch-cookie/katip-effectful/issues++-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.+extra-doc-files: CHANGELOG.md+category:        library, logging, data, bsd3++-- Extra source files to be distributed with the package, such as examples, or a tutorial module.+-- extra-source-files:++common deps+  ghc-options:        -Wall+  build-depends:+    , base              >=4.17.2   && <5+    , effectful-core    >=2.2.2    && <3+    , effectful-plugin  >=1.1.0.2  && <1.2+    , katip             >=0.8.7    && <1+    , template-haskell  >=2.19.0.0 && <2.20+    , text              >=2.0.2    && <3++  default-extensions:+    DataKinds+    DerivingStrategies+    DerivingVia+    DuplicateRecordFields+    FunctionalDependencies+    NoCUSKs+    NoFieldSelectors+    NoStarIsType+    OverloadedRecordDot+    OverloadedStrings+    QuantifiedConstraints+    ScopedTypeVariables+    TypeFamilyDependencies+    TypeOperators++  default-language:   GHC2021++library+  import:          deps+  exposed-modules: Effectful.Katip+  hs-source-dirs:  src++test-suite katip-effectful-test+  import:             deps+  type:               exitcode-stdio-1.0+  hs-source-dirs:     test+  main-is:            Main.hs+  other-modules:      OutputTest+  ghc-options:        -threaded+  build-depends:+    , containers            >=0.6.7    && <1+    , katip-effectful+    , QuickCheck            >=2.14.3   && <3+    , quickcheck-instances  >=0.3.29.1 && <0.4+    , tasty                 >=1.4.3    && <2+    , tasty-golden          >=2.3.5    && <3+    , tasty-hunit           >=0.10.1   && <1+    , tasty-quickcheck      >=0.10.2   && <1+    , text                  >=2.0.2    && <3+    , time                  >=1.12.2   && <2+    , unix                  >=2.7.3    && <3++  build-tool-depends: tasty-discover:tasty-discover++source-repository head+  type:     git+  location: https://github.com/eldritch-cookie/katip-effectful
+ src/Effectful/Katip.hs view
@@ -0,0 +1,424 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -Wno-orphans #-}++{- |+Module: Effectful.Katip+Description: Effect to use Katip+-}+module Effectful.Katip (+  -- * Framework Types+  Namespace (..),+  Environment (..),+  Severity (..),+  renderSeverity,+  textToSeverity,+  Verbosity (..),+  ToObject (..),+  LogItem (..),+  Item (..),+  ThreadIdText (..),+  PayloadSelection (..),+  Scribe (..),+  LogEnv (..),+  SimpleLogPayload,+  sl,+  defaultScribeSettings,+  ScribeSettings,+  scribeBufferSize,+  _scribeBufferSize,++  -- ** @lens@-Compatible Lenses+  itemApp,+  itemEnv,+  itemSeverity,+  itemThread,+  itemHost,+  itemProcess,+  itemPayload,+  itemMessage,+  itemTime,+  itemNamespace,+  itemLoc,+  logEnvHost,+  logEnvPid,+  logEnvApp,+  logEnvEnv,+  logEnvTimer,+  logEnvScribes,++  -- * Effect+  KatipE,++  -- ** Running The Effect+  runKatipE,+  runKatipContextE,+  startKatipE,+  startKatipContextE,++  -- * Initializing Loggers+  registerScribe,++  -- * Dropping Scribes Temporarily+  unregisterScribe,+  clearScribes,++  -- * Finalizing Scribes At Shutdown+  closeScribe,++  -- * Logging Functions+  LogStr (..),+  logStr,+  ls,+  showLS,++  -- ** Katip Logging Functions+  getLogEnv,+  localLogEnv,+  logF,+  logMsg,+  logT,+  logLoc,+  logItem,+  logKatipItem,+  logException,++  -- ** KatipContext Logging Functions+  getKatipContext,+  localKatipContext,+  getKatipNamespace,+  localKatipNamespace,+  logFM,+  logTM,+  logLocM,+  logItemM,+  logExceptionM,+  AnyLogContext,+  LogContexts,+  liftPayload,++  -- *** Temporarily Changing Log Behaviour+  katipAddNamespace,+  katipAddContext,+  katipNoLogging,++  -- * Included Scribes+  mkHandleScribe,+  mkHandleScribeWithFormatter,+  mkFileScribe,+  ColorStrategy (..),+  ItemFormatter,+  bracketFormat,+  jsonFormat,++  -- * Tools For Implementing Scribes+  PermitFunc,+  permitAND,+  permitOR,+  permitItem,+  payloadObject,+  itemJson,+) where++import Effectful+import Effectful.Dispatch.Static+import Katip (+  --  logging++  -- logContext+  AnyLogContext,+  -- formatting+  ColorStrategy (..),+  Environment (..),+  Item (..),+  ItemFormatter,+  LogContexts,+  LogEnv (..),+  LogItem (..),+  LogStr (..),+  Namespace (..),+  PayloadSelection (..),+  -- permission+  PermitFunc,+  Scribe (..),+  ScribeSettings,+  Severity (..),+  SimpleLogPayload,+  ThreadIdText (..),+  ToObject (..),+  Verbosity (..),+  bracketFormat,+  -- removing scribe++  clearScribes,+  defaultScribeSettings,+  itemApp,+  itemEnv,+  itemHost,+  itemJson,+  itemLoc,+  itemMessage,+  itemNamespace,+  itemPayload,+  itemProcess,+  itemSeverity,+  itemThread,+  itemTime,+  jsonFormat,+  liftPayload,+  logEnvApp,+  logEnvEnv,+  logEnvHost,+  logEnvPid,+  logEnvScribes,+  logEnvTimer,+  logStr,+  ls,+  payloadObject,+  permitAND,+  permitItem,+  permitOR,+  renderSeverity,+  scribeBufferSize,+  showLS,+  sl,+  textToSeverity,+  unregisterScribe,+  _scribeBufferSize,+ )++import Data.Kind+import Data.Text (Text)+import Katip qualified as K+import Katip.Core (getLocTH)+import Language.Haskell.TH (Loc)+import Language.Haskell.TH.Lib+import System.IO (Handle)+import Unsafe.Coerce (unsafeCoerce)++-- | A Effect you can use to run logging actions. there is only one effect as we can't have duplicated instances.+type KatipE :: Effect+data KatipE m a++type instance DispatchOf KatipE = Static WithSideEffects++data instance StaticRep KatipE = MkKatipE !LogEnv !LogContexts !Namespace++-- | Run a KatipE Effect without a 'Namespace' or a 'LogContexts'. This also calls closeScribes+runKatipE :: forall es a. (IOE :> es) => LogEnv -> Eff (KatipE : es) a -> Eff es a+runKatipE l act = evalStaticRep (MkKatipE l mempty mempty) act <* liftIO (K.closeScribes l)++-- | Run a KatipE Effect with a 'Namespace' and a 'LogContexts'. this also calls closeScribes+runKatipContextE :: forall es a a1. (LogItem a, IOE :> es) => LogEnv -> a -> Namespace -> Eff (KatipE : es) a1 -> Eff es a1+runKatipContextE l pl ns act = evalStaticRep (MkKatipE l (liftPayload pl) ns) act <* liftIO (K.closeScribes l)++instance forall es. (IOE :> es, KatipE :> es) => K.Katip (Eff es) where+  getLogEnv :: (IOE :> es, KatipE :> es) => Eff es LogEnv+  getLogEnv = getLogEnv+  localLogEnv ::+    (IOE :> es, KatipE :> es) =>+    (LogEnv -> LogEnv) ->+    Eff es a ->+    Eff es a+  localLogEnv = localLogEnv+instance forall es. (IOE :> es, KatipE :> es) => K.KatipContext (Eff es) where+  getKatipContext :: Eff es LogContexts+  getKatipContext = getKatipContext+  localKatipContext ::+    (IOE :> es, KatipE :> es) =>+    (LogContexts -> LogContexts) ->+    Eff es a ->+    Eff es a+  localKatipContext = localKatipContext+  getKatipNamespace :: (IOE :> es, KatipE :> es) => Eff es Namespace+  getKatipNamespace = getKatipNamespace+  localKatipNamespace ::+    (IOE :> es, KatipE :> es) =>+    (Namespace -> Namespace) ->+    Eff es a ->+    Eff es a+  localKatipNamespace = localKatipNamespace++-- | Run a KatipE Effect without a 'Namespace' or a 'LogContexts' and creating a 'LogEnv'+startKatipE :: (IOE :> es) => Namespace -> Environment -> Eff (KatipE : es) a -> Eff es a+startKatipE ns env act = liftIO (K.initLogEnv ns env) >>= \lenv -> runKatipE lenv act++-- | Run a KatipE Effect with a 'Namespace' and a 'LogContexts' and creating a 'LogEnv'+startKatipContextE :: (IOE :> es, LogItem a) => Environment -> a -> Namespace -> Eff (KatipE : es) a1 -> Eff es a1+startKatipContextE env a ns act = liftIO (K.initLogEnv ns env) >>= \e -> runKatipContextE e a ns act++{- | Add a scribe to the list.+All future log calls will go to this scribe in addition to the others.+Writes will be buffered per the ScribeSettings to prevent slow scribes from slowing down logging.+Writes will be dropped if the buffer fills.+-}+registerScribe :: (KatipE :> es) => Text -> Scribe -> ScribeSettings -> Eff es ()+registerScribe txt scrb scrbs = do+  MkKatipE lenv lctx ns <- getStaticRep+  nlenv <- unsafeEff_ $ K.registerScribe txt scrb scrbs lenv+  putStaticRep $ MkKatipE nlenv lctx ns++-- | Finalize a scribe early. Note that it isn't necessary to call this as both 'runKatipE' and 'runKatipContextE' call 'K.closeScribes'+closeScribe :: (KatipE :> es) => Text -> Eff es ()+closeScribe name = do+  MkKatipE le lc ns <- getStaticRep+  newle <- unsafeEff_ $ K.closeScribe name le+  putStaticRep $ MkKatipE newle lc ns+-- | get the 'KatipE' 'LogEnv'+getLogEnv :: forall es. (KatipE :> es) => Eff es LogEnv+getLogEnv = do+  s <- getStaticRep @KatipE+  case s of+    MkKatipE le _ _ -> return le+-- | temporarily modify the 'LogEnv'+localLogEnv :: forall es a. (KatipE :> es) => (LogEnv -> LogEnv) -> Eff es a -> Eff es a+localLogEnv f = localStaticRep @KatipE $ \(MkKatipE le lc ns) -> MkKatipE (f le) lc ns+-- | get the 'KatipE' 'LogContexts'+getKatipContext :: (KatipE :> es) => Eff es LogContexts+getKatipContext = do+  s <- getStaticRep @KatipE+  case s of+    MkKatipE _ lc _ -> return lc+-- | temporarily modify the 'LogContexts'+localKatipContext :: forall es a. (KatipE :> es) => (LogContexts -> LogContexts) -> Eff es a -> Eff es a+localKatipContext f = localStaticRep @KatipE $ \(MkKatipE le lc ns) -> MkKatipE le (f lc) ns+getKatipNamespace :: forall es. (KatipE :> es) => Eff es Namespace+-- | get the 'KatipE' 'Namespace'+getKatipNamespace = do+  s <- getStaticRep @KatipE+  case s of+    MkKatipE _ _ ns -> return ns+-- | temporarily modify the 'Namespace'+localKatipNamespace :: forall es a. (KatipE :> es) => (Namespace -> Namespace) -> Eff es a -> Eff es a+localKatipNamespace f = localStaticRep @KatipE $ \(MkKatipE le lc ns) -> MkKatipE le lc $ f ns++-- | escape hatch for implementing your own scribes+unsafeEmbedIOE :: forall es a. (KatipE :> es) => ((KatipE :> es, IOE :> es) => Eff es a) -> Eff es a+unsafeEmbedIOE act = useDict (unsafeCoerce (MkDict @(KatipE :> es)) :: Dict (KatipE :> es, IOE :> es)) act++type Dict :: Constraint -> Type+data Dict a where+  MkDict :: (a) => Dict a++useDict :: forall a r. Dict a -> ((a) => r) -> r+useDict MkDict r = r+-- | Log with full context, but without any code location.+logF :: forall a es. (LogItem a, KatipE :> es) => a -> Namespace -> Severity -> LogStr -> Eff es ()+logF a ns sev logs = unsafeEmbedIOE $ K.logF a ns sev logs++-- | Log a message without any payload/context or code location.+logMsg :: forall es. (KatipE :> es) => Namespace -> Severity -> LogStr -> Eff es ()+logMsg ns sev logs = unsafeEmbedIOE $ K.logMsg ns sev logs++{-# INLINE logT #-}+-- | Loc-tagged logging when using template-haskell. +-- @+-- $(logT) obj mempty InfoS "Hello world"+-- @+logT :: ExpQ+logT = [|\a ns sev msg -> logItem a ns (Just $(getLocTH)) sev msg|]++{-# INLINE logLoc #-}+-- | 'Loc'-tagged logging using 'GHC.Stack.Stack' when available.+-- This function does not require template-haskell as it automatically uses implicit-callstacks when the code is compiled using GHC > 7.8.+-- Using an older version of the compiler will result in the emission of a log line without any location information, so be aware of it.+-- @+-- logLoc obj mempty InfoS "Hello world"+-- @+logLoc :: (LogItem a, KatipE :> es, HasCallStack) => a -> Namespace -> Severity -> LogStr -> Eff es ()+logLoc a ns sev logs = unsafeEmbedIOE $ K.logLoc a ns sev logs+-- | Log with everything, including a source code location.+-- This is very low level and you typically can use 'logT' in its place.+logItem :: (LogItem a, KatipE :> es) => a -> Namespace -> Maybe Loc -> Severity -> LogStr -> Eff es ()+logItem a ns loc sev logs = unsafeEmbedIOE $ K.logItem a ns loc sev logs+-- | Log an already constructed 'Item'.+-- This is the lowest level function that other log* functions use.+-- It can be useful when implementing centralised logging services.+logKatipItem :: (LogItem a, KatipE :> es) => Item a -> Eff es ()+logKatipItem item = unsafeEmbedIOE $ K.logKatipItem item+-- | Perform an action while logging any exceptions that may occur.+-- @+-- >>> > logException () mempty ErrorS (error "foo")+-- @+logException :: (LogItem a, KatipE :> es) => a -> Namespace -> Severity -> Eff es b -> Eff es b+logException a ns sev act = unsafeEmbedIOE $ K.logException a ns sev act+-- | Log with full context, but without any code location. +-- Automatically supplies payload and namespace.+logFM :: (KatipE :> es) => Severity -> LogStr -> Eff es ()+logFM sev logs = unsafeEmbedIOE $ K.logFM sev logs++{-# INLINE logTM #-}+-- | 'Loc'-tagged logging when using template-haskell.+-- Automatically supplies payload and namespace.+-- @+-- $(logTM) InfoS "Hello world"+-- @+logTM :: ExpQ+logTM = [|logItemM (Just $(getLocTH))|]+{-#INLINE logLocM #-}+-- | 'Loc'-tagged logging when using 'GHC.Stack.getCallStack' implicit-callstacks.+-- Automatically supplies payload and namespace.+-- Same consideration as 'logLoc' applies+-- By default, location will be logged from the module that invokes logLocM.+-- If you want to use logLocM in a helper, +-- wrap the entire helper in withFrozenCallStack to retain the callsite of the helper in the logs.+-- This function does not require template-haskell. +-- @+-- logLocM InfoS "Hello world"+-- @+logLocM :: (KatipE :> es, HasCallStack) => Severity -> LogStr -> Eff es ()+logLocM sev logs = unsafeEmbedIOE $ K.logLocM sev logs+-- | Log with everything, including a source code location. +-- This is very low level and you typically can use 'logTM' in its place.+-- Automatically supplies payload and namespace.+logItemM :: (KatipE :> es, HasCallStack) => Maybe Loc -> Severity -> LogStr -> Eff es ()+logItemM loc sev logs = unsafeEmbedIOE $ K.logItemM loc sev logs+-- | Perform an action while logging any exceptions that may occur.+-- @+-- >>> > error "foo" `logExceptionM` ErrorS+-- @+logExceptionM :: (KatipE :> es) => Eff es a -> Severity -> Eff es a+logExceptionM act sev = unsafeEmbedIOE $ K.logExceptionM act sev++-- | Append a namespace segment to the current namespace for the given monadic action,+-- then restore the previous state afterwards.+katipAddNamespace :: (KatipE :> es) => Namespace -> Eff es a -> Eff es a+katipAddNamespace ns = localKatipNamespace (<> ns)+-- | Append some context to the current context for the given monadic action,+-- then restore the previous state afterwards. +-- Important note: be careful using this in a loop. +-- If you're using something like forever or replicateM_ that does explicit sharing to avoid a memory leak,+-- youll be fine as it will *sequence* calls to katipAddNamespace,+-- so each loop will get the same context added.+-- If you instead roll your own recursion and you're recursing in the action you provide,+-- you'll instead accumulate tons of redundant contexts and even if they all merge on log,+-- they are stored in a sequence and will leak memory.+katipAddContext :: (KatipE :> es, LogItem i) => i -> Eff es a -> Eff es a+katipAddContext ctx = localKatipContext (<> liftPayload ctx)+-- | Disable all scribes for the given monadic action,+-- then restore them afterwards.+katipNoLogging :: (KatipE :> es) => Eff es a -> Eff es a+katipNoLogging = localLogEnv $ \lenv -> lenv{_logEnvScribes = mempty}++-- | Logs to a file handle such as stdout, stderr, or a file.+-- Contexts and other information will be flattened out into bracketed fields. +-- For example:]+-- @+-- [2016-05-11 21:01:15][MyApp][Info][myhost.example.com][PID 1724][ThreadId 1154][main:Helpers.Logging Helpers/Logging.hs:32:7] Started+-- [2016-05-11 21:01:15][MyApp.confrabulation][Debug][myhost.example.com][PID 1724][ThreadId 1154][confrab_factor:42.0][main:Helpers.Logging Helpers/Logging.hs:41:9] Confrabulating widgets, with extra namespace and context+-- [2016-05-11 21:01:15][MyApp][Info][myhost.example.com][PID 1724][ThreadId 1154][main:Helpers.Logging Helpers/Logging.hs:43:7] Namespace and context are back to normal+-- @+-- Returns the newly-created 'Scribe'. The finalizer flushes the handle. Handle mode is set to 'System.IO.LineBuffering' automatically.+mkHandleScribe :: forall es. (KatipE :> es) => ColorStrategy -> Handle -> PermitFunc -> Verbosity -> Eff es Scribe+mkHandleScribe cs h pf v = unsafeEmbedIOE $ liftIO $ K.mkHandleScribe cs h pf v+-- | 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.+-- Returns the newly-created 'Scribe'.+-- The finalizer flushes the handle.+-- Handle mode is set to 'System.IO.LineBuffering' automatically.+mkHandleScribeWithFormatter :: forall es. (KatipE :> es) => (forall a. (LogItem a) => ItemFormatter a) -> ColorStrategy -> Handle -> PermitFunc -> Verbosity -> Eff es Scribe+mkHandleScribeWithFormatter ifa cs h pf v = unsafeEmbedIOE $ liftIO $ K.mkHandleScribeWithFormatter ifa cs h pf v+-- | A specialization of 'mkHandleScribe' that takes a 'FilePath' instead of a 'Handle'. It is responsible for opening the file in 'System.IO.AppendMode' and will close the file handle on closeScribe/closeScribes. Does not do log coloring. Sets handle to 'System.IO.LineBuffering' mode.+mkFileScribe :: forall es. (KatipE :> es) => FilePath -> PermitFunc -> Verbosity -> Eff es Scribe+mkFileScribe fp pf v = unsafeEmbedIOE $ liftIO $ K.mkFileScribe fp pf v
+ test/Main.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF tasty-discover #-}
+ test/OutputTest.hs view
@@ -0,0 +1,111 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module OutputTest where++import Control.Monad (replicateM)+import Data.Text.Lazy.Builder (Builder, fromText)+import Effectful (runEff)+import Effectful.Katip+import System.IO (Handle, IOMode (..), hGetContents', openFile)+import Test.QuickCheck.Instances ()+import Test.Tasty.QuickCheck++import Data.Text (Text)+import Katip (closeScribes, runKatipContextT)+import Katip qualified as K++getInteger :: Int -> Gen Int+getInteger s = chooseInt (0, s)++instance Arbitrary Namespace where+  arbitrary = do+    num <- sized getInteger+    Namespace <$> replicateM num arbitrary+  shrink = genericShrink+instance Arbitrary Environment where+  arbitrary = Environment <$> arbitrary+  shrink = genericShrink+instance Arbitrary Severity where+  arbitrary = chooseEnum (minBound, maxBound)+  shrink = genericShrink+instance Arbitrary Verbosity where+  arbitrary = chooseEnum (minBound, maxBound)+  shrink = genericShrink+instance Arbitrary LogStr where+  arbitrary = LogStr <$> arbitrary+  shrink = genericShrink+instance Arbitrary Builder where+  arbitrary = fromText <$> arbitrary+instance Arbitrary SimpleLogPayload where+  arbitrary = sl @Text <$> arbitrary <*> arbitrary++instance Show SimpleLogPayload where+  show = show . toObject++prop_EqualOutputLFM :: Severity -> Namespace -> LogStr -> Property+prop_EqualOutputLFM severity ns str = ioProperty $ do+  h1 <- openFile "scribeKCT.log" ReadWriteMode+  le1 <- logEnvWithScribe h1+  runKatipContextT le1 () ns $ K.logFM severity str+  _ <- closeScribes le1+  s1 <- hGetContents' h1+  h2 <- openFile "scribeEff.log" ReadWriteMode+  le2 <- logEnvWithScribe h2+  runEff . runKatipContextE le2 () ns $ logFM severity str+  _ <- closeScribes le2+  s2 <- hGetContents' h2+  cleanup+  return $ s1 == s2+prop_EqualOutputLF :: SimpleLogPayload -> Namespace -> Severity -> LogStr -> Property+prop_EqualOutputLF slp ns severity str = ioProperty $ do+  h1 <- openFile "scribeKCT.log" ReadWriteMode+  le1 <- logEnvWithScribe h1+  runKatipContextT le1 () ns $ K.logF slp ns severity str+  _ <- closeScribes le1+  s1 <- hGetContents' h1+  h2 <- openFile "scribeEff.log" ReadWriteMode+  le2 <- logEnvWithScribe h2+  runEff . runKatipContextE le2 () ns $ logF slp ns severity str+  _ <- closeScribes le2+  s2 <- hGetContents' h2+  cleanup+  return $ s1 == s2++prop_EqualOutputLMsg :: Namespace -> Severity -> LogStr -> Property+prop_EqualOutputLMsg ns severity str = ioProperty $ do+  h1 <- openFile "scribeKCT.log" ReadWriteMode+  le1 <- logEnvWithScribe h1+  runKatipContextT le1 () ns $ K.logMsg ns severity str+  _ <- closeScribes le1+  s1 <- hGetContents' h1+  h2 <- openFile "scribeEff.log" ReadWriteMode+  le2 <- logEnvWithScribe h2+  runEff . runKatipContextE le2 () ns $ logMsg ns severity str+  _ <- closeScribes le2+  s2 <- hGetContents' h2+  cleanup+  return $ s1 == s2+prop_EqualOutputLLoc :: SimpleLogPayload -> Namespace -> Severity -> LogStr -> Property+prop_EqualOutputLLoc slp ns severity str = ioProperty $ do+  h1 <- openFile "scribeKCT.log" ReadWriteMode+  le1 <- logEnvWithScribe h1+  runKatipContextT le1 () ns $ K.logLoc slp ns severity str+  _ <- closeScribes le1+  s1 <- hGetContents' h1+  h2 <- openFile "scribeEff.log" ReadWriteMode+  le2 <- logEnvWithScribe h2+  runEff . runKatipContextE le2 () ns $ logLoc slp ns severity str+  _ <- closeScribes le2+  s2 <- hGetContents' h2+  cleanup+  return $ s1 == s2+cleanup :: IO ()+cleanup = do+  writeFile "scribeKCT.log" []+  writeFile "scribeEff.log" []++logEnvWithScribe :: Handle -> IO LogEnv+logEnvWithScribe fp = do+  initialLE <- K.initLogEnv "testing" "testing"+  s <- K.mkHandleScribe ColorIfTerminal fp (\_ -> pure True) V3+  K.registerScribe "test" s defaultScribeSettings initialLE