2021-04-05 17:40:31 +00:00
|
|
|
{- Debug output
|
|
|
|
-
|
|
|
|
- Copyright 2021 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- License: BSD-2-clause
|
|
|
|
-}
|
|
|
|
|
|
|
|
{-# LANGUAGE OverloadedStrings, FlexibleInstances, TypeSynonymInstances #-}
|
2021-04-06 16:45:30 +00:00
|
|
|
{-# LANGUAGE LambdaCase #-}
|
2021-04-05 17:40:31 +00:00
|
|
|
{-# OPTIONS_GHC -fno-warn-tabs -w #-}
|
|
|
|
|
|
|
|
module Utility.Debug (
|
|
|
|
DebugSource(..),
|
|
|
|
DebugSelector(..),
|
|
|
|
configureDebug,
|
|
|
|
getDebugSelector,
|
|
|
|
debug,
|
|
|
|
fastDebug
|
|
|
|
) where
|
|
|
|
|
|
|
|
import qualified Data.ByteString as S
|
|
|
|
import Data.IORef
|
|
|
|
import Data.String
|
|
|
|
import Data.Time
|
|
|
|
import System.IO.Unsafe (unsafePerformIO)
|
2021-04-06 16:45:30 +00:00
|
|
|
import qualified Data.Semigroup as Sem
|
|
|
|
import Prelude
|
2021-04-05 17:40:31 +00:00
|
|
|
|
|
|
|
import Utility.FileSystemEncoding
|
|
|
|
|
|
|
|
-- | The source of a debug message. For example, this could be a module or
|
|
|
|
-- function name.
|
|
|
|
newtype DebugSource = DebugSource S.ByteString
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
instance IsString DebugSource where
|
|
|
|
fromString = DebugSource . encodeBS'
|
|
|
|
|
|
|
|
-- | Selects whether to display a message from a source.
|
2021-04-06 16:45:30 +00:00
|
|
|
data DebugSelector
|
|
|
|
= DebugSelector (DebugSource -> Bool)
|
|
|
|
| NoDebugSelector
|
|
|
|
|
|
|
|
instance Sem.Semigroup DebugSelector where
|
|
|
|
DebugSelector a <> DebugSelector b = DebugSelector (\v -> a v || b v)
|
|
|
|
NoDebugSelector <> NoDebugSelector = NoDebugSelector
|
|
|
|
NoDebugSelector <> b = b
|
|
|
|
a <> NoDebugSelector = a
|
|
|
|
|
|
|
|
instance Monoid DebugSelector where
|
|
|
|
mempty = NoDebugSelector
|
2021-04-05 17:40:31 +00:00
|
|
|
|
|
|
|
-- | Configures debugging.
|
|
|
|
configureDebug
|
|
|
|
:: (S.ByteString -> IO ())
|
|
|
|
-- ^ Used to display debug output.
|
|
|
|
-> DebugSelector
|
|
|
|
-> IO ()
|
|
|
|
configureDebug src p = writeIORef debugConfigGlobal (src, p)
|
|
|
|
|
|
|
|
-- | Gets the currently configured DebugSelector.
|
|
|
|
getDebugSelector :: IO DebugSelector
|
|
|
|
getDebugSelector = snd <$> readIORef debugConfigGlobal
|
|
|
|
|
|
|
|
-- A global variable for the debug configuration.
|
|
|
|
{-# NOINLINE debugConfigGlobal #-}
|
|
|
|
debugConfigGlobal :: IORef (S.ByteString -> IO (), DebugSelector)
|
|
|
|
debugConfigGlobal = unsafePerformIO $ newIORef (dontshow, selectnone)
|
|
|
|
where
|
|
|
|
dontshow _ = return ()
|
2021-04-06 16:45:30 +00:00
|
|
|
selectnone = NoDebugSelector
|
2021-04-05 17:40:31 +00:00
|
|
|
|
|
|
|
-- | Displays a debug message, if that has been enabled by configureDebug.
|
|
|
|
--
|
|
|
|
-- This is reasonably fast when debugging is not enabled, but since it does
|
|
|
|
-- have to consult a IORef each time, using it in a tight loop may slow
|
|
|
|
-- down the program.
|
|
|
|
debug :: DebugSource -> String -> IO ()
|
2021-04-06 16:45:30 +00:00
|
|
|
debug src msg = readIORef debugConfigGlobal >>= \case
|
|
|
|
(displayer, NoDebugSelector) ->
|
|
|
|
displayer =<< formatDebugMessage src msg
|
|
|
|
(displayer, DebugSelector p)
|
|
|
|
| p src -> displayer =<< formatDebugMessage src msg
|
|
|
|
| otherwise -> return ()
|
2021-04-05 17:40:31 +00:00
|
|
|
|
|
|
|
-- | Displays a debug message, if the DebugSelector allows.
|
|
|
|
--
|
|
|
|
-- When the DebugSelector does not let the message be displayed, this runs
|
|
|
|
-- very quickly, allowing it to be used inside tight loops.
|
|
|
|
fastDebug :: DebugSelector -> DebugSource -> String -> IO ()
|
2021-04-06 16:45:30 +00:00
|
|
|
fastDebug NoDebugSelector src msg = do
|
|
|
|
(displayer, _) <- readIORef debugConfigGlobal
|
|
|
|
displayer =<< formatDebugMessage src msg
|
2021-04-05 17:40:31 +00:00
|
|
|
fastDebug (DebugSelector p) src msg
|
2021-04-06 16:45:30 +00:00
|
|
|
| p src = fastDebug NoDebugSelector src msg
|
2021-04-05 17:40:31 +00:00
|
|
|
| otherwise = return ()
|
|
|
|
|
|
|
|
formatDebugMessage :: DebugSource -> String -> IO S.ByteString
|
|
|
|
formatDebugMessage (DebugSource src) msg = do
|
|
|
|
t <- encodeBS' . formatTime defaultTimeLocale "[%F %X%Q]"
|
|
|
|
<$> getZonedTime
|
|
|
|
return (t <> " (" <> src <> ") " <> encodeBS msg)
|