diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## Unreleased changes
 
+## 0.1.5.1
+
+* Logging: add readCreateProcessWithLogging
+
 ## 0.1.5.0
 
 * GHC 9.6 support
diff --git a/sandwich.cabal b/sandwich.cabal
--- a/sandwich.cabal
+++ b/sandwich.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           sandwich
-version:        0.1.5.0
+version:        0.1.5.1
 synopsis:       Yet another test framework for Haskell
 description:    Please see the <https://codedownio.github.io/sandwich documentation>.
 category:       Testing
@@ -101,6 +101,7 @@
     , bytestring
     , colour
     , containers
+    , deepseq
     , directory
     , exceptions
     , filepath
@@ -178,6 +179,7 @@
     , bytestring
     , colour
     , containers
+    , deepseq
     , directory
     , exceptions
     , filepath
@@ -236,6 +238,7 @@
     , bytestring
     , colour
     , containers
+    , deepseq
     , directory
     , exceptions
     , filepath
@@ -299,6 +302,7 @@
     , bytestring
     , colour
     , containers
+    , deepseq
     , directory
     , exceptions
     , filepath
@@ -363,6 +367,7 @@
     , bytestring
     , colour
     , containers
+    , deepseq
     , directory
     , exceptions
     , filepath
diff --git a/src/Test/Sandwich/Logging.hs b/src/Test/Sandwich/Logging.hs
--- a/src/Test/Sandwich/Logging.hs
+++ b/src/Test/Sandwich/Logging.hs
@@ -2,9 +2,23 @@
 
 -- | Logging functions.
 
-module Test.Sandwich.Logging where
+module Test.Sandwich.Logging (
+  debug
+  , info
+  , warn
+  , Test.Sandwich.Logging.logError
+  , Test.Sandwich.Logging.logOther
 
+  -- * Process functions with logging
+  , createProcessWithLogging
+  , readCreateProcessWithLogging
+  , createProcessWithLoggingAndStdin
+  , callCommandWithLogging
+  ) where
+
+import Control.Concurrent
 import Control.Concurrent.Async.Lifted
+import Control.DeepSeq (rnf)
 import qualified Control.Exception as C
 import Control.Exception.Safe
 import Control.Monad
@@ -17,6 +31,7 @@
 import GHC.IO.Exception
 import GHC.Stack
 import System.IO
+import System.IO.Error (mkIOError)
 import System.Process
 
 #if !MIN_VERSION_base(4,13,0)
@@ -52,8 +67,8 @@
 --
 -- | Functions for launching processes while capturing their output in the logs.
 
--- | Spawn a process with its stdout and stderr connected to the logging system. Every line output by the process
--- will be fed to a 'debug' call.
+-- | Spawn a process with its stdout and stderr connected to the logging system.
+-- Every line output by the process will be fed to a 'debug' call.
 createProcessWithLogging :: (MonadIO m, MonadBaseControl IO m, MonadLogger m, HasCallStack) => CreateProcess -> m ProcessHandle
 createProcessWithLogging cp = do
   (hRead, hWrite) <- liftIO createPipe
@@ -69,8 +84,58 @@
   (_, _, _, p) <- liftIO $ createProcess (cp { std_out = UseHandle hWrite, std_err = UseHandle hWrite })
   return p
 
--- | Spawn a process with its stdout and stderr connected to the logging system. Every line output by the process
--- will be fed to a 'debug' call.
+-- | Like 'readCreateProcess', but capture the stderr output in the logs.
+-- Every line output by the process will be fed to a 'debug' call.
+readCreateProcessWithLogging :: (MonadIO m, MonadBaseControl IO m, MonadLogger m, HasCallStack) => CreateProcess -> String -> m String
+readCreateProcessWithLogging cp input = do
+  (hReadErr, hWriteErr) <- liftIO createPipe
+
+  let name = case cmdspec cp of
+        ShellCommand {} -> "shell"
+        RawCommand path _ -> path
+
+  _ <- async $ forever $ do
+    line <- liftIO $ hGetLine hReadErr
+    debug [i|#{name}: #{line}|]
+
+  -- Do this just like 'readCreateProcess'
+  -- https://hackage.haskell.org/package/process-1.6.17.0/docs/src/System.Process.html#readCreateProcess
+  (ex, output) <- liftIO $ withCreateProcess (cp { std_in = CreatePipe, std_out = CreatePipe, std_err = UseHandle hWriteErr }) $ \sin sout _ p -> do
+    case (sin, sout) of
+      (Just hIn, Just hOut) -> do
+        output  <- hGetContents hOut
+        withForkWait (C.evaluate $ rnf output) $ \waitOut -> do
+          -- now write any input
+          unless (Prelude.null input) $
+            ignoreSigPipe $ hPutStr hIn input
+          -- hClose performs implicit hFlush, and thus may trigger a SIGPIPE
+          ignoreSigPipe $ hClose hIn
+
+          -- wait on the output
+          waitOut
+          hClose hOut
+
+        -- wait on the process
+        ex <- waitForProcess p
+        return (ex, output)
+      (Nothing, _) -> liftIO $ throw $ userError "readCreateProcessWithStderrLogging: Failed to get a stdin handle."
+      (_, Nothing) -> liftIO $ throw $ userError "readCreateProcessWithStderrLogging: Failed to get a stdout handle."
+
+  case ex of
+    ExitSuccess -> return output
+    ExitFailure r -> liftIO $ processFailedException "readCreateProcessWithLogging" cmd args r
+
+  where
+    cmd = case cp of
+            CreateProcess { cmdspec = ShellCommand sc } -> sc
+            CreateProcess { cmdspec = RawCommand fp _ } -> fp
+    args = case cp of
+             CreateProcess { cmdspec = ShellCommand _ } -> []
+             CreateProcess { cmdspec = RawCommand _ args' } -> args'
+
+
+-- | Spawn a process with its stdout and stderr connected to the logging system.
+-- Every line output by the process will be fed to a 'debug' call.
 createProcessWithLoggingAndStdin :: (MonadIO m, MonadFail m, MonadBaseControl IO m, MonadLogger m, HasCallStack) => CreateProcess -> String -> m ProcessHandle
 createProcessWithLoggingAndStdin cp input = do
   (hRead, hWrite) <- liftIO createPipe
@@ -96,14 +161,6 @@
 
   return p
 
-  where
-    -- Copied from System.Process
-    ignoreSigPipe :: IO () -> IO ()
-    ignoreSigPipe = C.handle $ \case
-      IOError { ioe_type  = ResourceVanished, ioe_errno = Just ioe } | Errno ioe == ePIPE -> return ()
-      e -> throwIO e
-
-
 -- | Higher level version of 'createProcessWithLogging', accepting a shell command.
 callCommandWithLogging :: (MonadIO m, MonadBaseControl IO m, MonadLogger m) => String -> m ()
 callCommandWithLogging cmd = do
@@ -122,3 +179,29 @@
   liftIO (waitForProcess p) >>= \case
     ExitSuccess -> return ()
     ExitFailure r -> liftIO $ throw $ userError [i|callCommandWithLogging failed for '#{cmd}': '#{r}'|]
+
+
+-- * Util
+
+-- Copied from System.Process
+withForkWait :: IO () -> (IO () ->  IO a) -> IO a
+withForkWait async body = do
+  waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))
+  mask $ \restore -> do
+    tid <- forkIO $ try (restore async) >>= putMVar waitVar
+    let wait = takeMVar waitVar >>= either throwIO return
+    restore (body wait) `C.onException` killThread tid
+
+-- Copied from System.Process
+ignoreSigPipe :: IO () -> IO ()
+ignoreSigPipe = C.handle $ \case
+  IOError { ioe_type  = ResourceVanished, ioe_errno = Just ioe } | Errno ioe == ePIPE -> return ()
+  e -> throwIO e
+
+-- Copied from System.Process
+processFailedException :: String -> String -> [String] -> Int -> IO a
+processFailedException fun cmd args exit_code =
+      ioError (mkIOError OtherError (fun ++ ": " ++ cmd ++
+                                     Prelude.concatMap ((' ':) . show) args ++
+                                     " (exit " ++ show exit_code ++ ")")
+                                 Nothing Nothing)
diff --git a/unix-src/Test/Sandwich/Formatters/TerminalUI.hs b/unix-src/Test/Sandwich/Formatters/TerminalUI.hs
--- a/unix-src/Test/Sandwich/Formatters/TerminalUI.hs
+++ b/unix-src/Test/Sandwich/Formatters/TerminalUI.hs
@@ -313,7 +313,7 @@
       & appShowFileLocations %~ not
     V.EvKey c [] | c == toggleVisibilityThresholdsKey -> continue $ s
       & appShowVisibilityThresholds %~ not
-    V.EvKey c [] | c `elem` [V.KEsc, exitKey]-> do
+    V.EvKey c [] | c `elem` [V.KEsc, exitKey] -> do
       -- Cancel everything and wait for cleanups
       liftIO $ mapM_ cancelNode (s ^. appRunTreeBase)
       forM_ (s ^. appRunTreeBase) (liftIO . waitForTree)
diff --git a/unix-src/Test/Sandwich/Formatters/TerminalUI/Draw/Util.hs b/unix-src/Test/Sandwich/Formatters/TerminalUI/Draw/Util.hs
--- a/unix-src/Test/Sandwich/Formatters/TerminalUI/Draw/Util.hs
+++ b/unix-src/Test/Sandwich/Formatters/TerminalUI/Draw/Util.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE CPP #-}
--- |
 
 module Test.Sandwich.Formatters.TerminalUI.Draw.Util where
 
diff --git a/unix-src/Test/Sandwich/Formatters/TerminalUI/Keys.hs b/unix-src/Test/Sandwich/Formatters/TerminalUI/Keys.hs
--- a/unix-src/Test/Sandwich/Formatters/TerminalUI/Keys.hs
+++ b/unix-src/Test/Sandwich/Formatters/TerminalUI/Keys.hs
@@ -1,4 +1,3 @@
--- |
 
 module Test.Sandwich.Formatters.TerminalUI.Keys where
 
