debuggable-0.1.0: src/Debug/NonInterleavedIO.hs
-- | Functions for non-interleaved output
--
-- Intended for qualifed import.
--
-- > import Debug.NonInterleavedIO qualified as NIIO
--
-- Alternatively, you can import "Debug.NonInterleavedIO.Trace" as a drop-in
-- replacement for "Debug.Trace".
--
-- The functions in this module can all be called concurrently, without
-- resulting in interleaved output: each function call is atomic.
--
-- The first time any of these functions is called, we lookup the @NIIO_OUTPUT@
-- environment variable. If set, we will write to the file specified (if the
-- file already exists, it will be overwritten). If @NIIO_OUTPUT@ is not set, a
-- temporary file will be created in the system temporary directory; typically
-- such a file will be called @/tmp/niio<number>@. The name of this file is
-- written to @stderr@ (this is the /only/ output origiating from functions in
-- this module that is not written to the file).
module Debug.NonInterleavedIO (
-- * Output functions
putStr
, putStrLn
, print
-- * Tracing functions
, trace
, traceShow
, traceShowId
, traceM
, traceShowM
) where
import Prelude hiding (putStr, putStrLn, print)
import Control.Concurrent
import Control.Exception
import Control.Monad.IO.Class
import System.Environment
import System.IO qualified as IO
import System.IO.Temp (getCanonicalTemporaryDirectory)
import System.IO.Unsafe
{-------------------------------------------------------------------------------
Output functions
-------------------------------------------------------------------------------}
-- | Non-interleaved version of 'Prelude.putStr'
putStr :: MonadIO m => String -> m ()
putStr str = liftIO $ withMVar globalHandle $ \h -> do
IO.hPutStr h str
IO.hFlush h
-- | Non-interleaved version of 'Prelude.putStrLn'
putStrLn :: MonadIO m => String -> m ()
putStrLn = putStr . (++ "\n")
-- | Non-interleaved version of 'Prelude.print'
print :: MonadIO m => Show a => a -> m ()
print = putStrLn . show
{-------------------------------------------------------------------------------
Tracing
-------------------------------------------------------------------------------}
-- | Non-interleaved version of 'Debug.Trace.trace'
trace :: String -> a -> a
trace str a = unsafePerformIO $ putStrLn str >> return a
-- | Non-interleaved version of 'Debug.Trace.traceShow'
traceShow :: Show a => a -> b -> b
traceShow = trace . show
-- | Non-interleaved version of 'Debug.Trace.traceShowId'
traceShowId :: Show a => a -> a
traceShowId a = traceShow (show a) a
-- | Non-interleaved version of 'Debug.Trace.traceM'
traceM :: Applicative m => String -> m ()
traceM str = trace str $ pure ()
-- | Non-interleaved version of 'Debug.Trace.traceShowM'
traceShowM :: (Applicative m, Show a) => a -> m ()
traceShowM = traceM . show
{-------------------------------------------------------------------------------
Internal: globals
-------------------------------------------------------------------------------}
globalHandle :: MVar IO.Handle
{-# NOINLINE globalHandle #-}
globalHandle = unsafePerformIO $ uninterruptibleMask_ $ do
mOutput <- lookupEnv "NIIO_OUTPUT"
(fp, h) <- case mOutput of
Nothing -> do
tmpDir <- getCanonicalTemporaryDirectory
IO.openTempFile tmpDir "niio"
Just fp -> do
(fp,) <$> IO.openFile fp IO.WriteMode
IO.hPutStrLn IO.stderr $ "niio output to " ++ fp
IO.hFlush IO.stderr
newMVar h