imp:lib:Hledger.Utils.Debug: simpler, more consistent dbg* names

This commit is contained in:
Simon Michael 2025-05-20 17:04:07 -10:00
parent a0204404c9
commit 820a44eb07
12 changed files with 315 additions and 253 deletions

View File

@ -311,7 +311,7 @@ journalRenumberAccountDeclarations j = j{jdeclaredaccounts=jdas'}
-- (at debug level 5+).
dbgJournalAcctDeclOrder :: String -> Journal -> Journal
dbgJournalAcctDeclOrder prefix =
traceOrLogAtWith 5 ((prefix++) . showAcctDeclsSummary . jdeclaredaccounts)
dbg5With ((prefix++) . showAcctDeclsSummary . jdeclaredaccounts)
where
showAcctDeclsSummary :: [(AccountName,AccountDeclarationInfo)] -> String
showAcctDeclsSummary adis

View File

@ -97,7 +97,7 @@ journalPriceOracle infer Journal{jpricedirectives, jinferredmarketprices} =
declaredprices = map priceDirectiveToMarketPrice jpricedirectives
inferredprices =
(if infer then jinferredmarketprices else [])
& traceOrLogAt 2 ("use prices inferred from costs? " <> if infer then "yes" else "no")
& dbg2Msg ("use prices inferred from costs? " <> if infer then "yes" else "no")
makepricegraph = memo $ makePriceGraph declaredprices inferredprices
in
memo $ uncurry3 $ priceLookup makepricegraph
@ -264,7 +264,7 @@ priceLookup makepricegraph d from mto =
,pgEdgesRev=allprices
,pgDefaultValuationCommodities=defaultdests
} =
traceOrLogAt 1 ("valuation date: "++show d) $ makepricegraph d
dbg1Msg ("valuation date: "++show d) $ makepricegraph d
mto' = mto <|> mdefaultto
where
mdefaultto = dbg1 ("default valuation commodity for "++T.unpack from) $
@ -279,12 +279,12 @@ priceLookup makepricegraph d from mto =
let
msg = printf "seeking %s to %s price" (showCommoditySymbol from) (showCommoditySymbol to)
prices =
(traceOrLogAt 2 (msg++" using forward prices") $
traceOrLogAt 2 ("forward prices:\n" <> showMarketPrices forwardprices) $
(dbg2Msg (msg++" using forward prices") $
dbg2Msg ("forward prices:\n" <> showMarketPrices forwardprices) $
pricesShortestPath from to forwardprices)
<|>
(traceOrLogAt 2 (msg++" using forward and reverse prices") $
traceOrLogAt 2 ("forward and reverse prices:\n" <> showMarketPrices allprices) $
(dbg2Msg (msg++" using forward and reverse prices") $
dbg2Msg ("forward and reverse prices:\n" <> showMarketPrices allprices) $
pricesShortestPath from to $ dbg5 "all forward and reverse prices" allprices)
in case prices of
Nothing -> Nothing
@ -375,13 +375,8 @@ pricesShortestPath start end edges =
case concatMap extend paths of
[] -> Nothing
_ | pathlength > maxpathlength ->
-- XXX This is unusual:
-- 1. A warning, not an error, which we usually avoid
-- 2. Not a debug message (when triggered, we always print it)
-- 3. Printed either to stdout or (eg in hledger-ui) to the debug log file.
-- This is the only place we use traceOrLog like this.
-- Also before 1.32.2, traceOrLog was doing the opposite of what it should [#2134].
traceOrLog ("gave up searching for a price chain at length "++show maxpathlength++", please report a bug")
-- Print a non-fatal warning to stderr, something we usually avoid.
warn ("gave up searching for a price chain at length "++show maxpathlength++", please report a bug")
Nothing
where
pathlength = 2 + maybe 0 (length . fst) (headMay paths)

View File

@ -299,7 +299,7 @@ readJournalFileAndLatestDates iopts prefixedfile = do
iopts' = iopts{mformat_=asum [mfmt, mformat_ iopts]}
liftIO $ requireJournalFileExists f
h <-
traceOrLogAt 6 ("readJournalFile: "++takeFileName f) $
dbg6Msg ("readJournalFile: "++takeFileName f) $
liftIO $ openFileOrStdin f
-- <- T.readFile f -- or without line ending translation, for testing
j <- readJournal iopts' (Just f) h
@ -331,7 +331,7 @@ readJournalFiles :: InputOpts -> [PrefixedFilePath] -> ExceptT String IO Journal
readJournalFiles iopts@InputOpts{strict_, new_, new_save_} prefixedfiles = do
let iopts' = iopts{_defer=True}
(j, latestdatesforfiles) <-
traceOrLogAt 6 ("readJournalFiles: "++show prefixedfiles) $
dbg6Msg ("readJournalFiles: "++show prefixedfiles) $
readJournalFilesAndLatestDates iopts' prefixedfiles
when strict_ $ liftEither $ journalStrictChecks j
when (new_ && new_save_) $ liftIO $ saveLatestDatesForFiles latestdatesforfiles

View File

@ -386,7 +386,7 @@ journalFinalise iopts@InputOpts{auto_,balancingopts_,infer_costs_,infer_equity_,
<&> (if infer_equity_ then journalInferEquityFromCosts verbose_tags_ else id) -- With --infer-equity, infer equity postings from costs where possible
<&> dbg9With (lbl "amounts after equity-inferring".showJournalAmountsDebug)
<&> journalInferMarketPricesFromTransactions -- infer market prices from commodity-exchanging transactions
-- <&> traceOrLogAt 6 fname -- debug logging
-- <&> dbg6Msg fname -- debug logging
<&> dbgJournalAcctDeclOrder (fname <> ": acct decls : ")
<&> journalRenumberAccountDeclarations
<&> dbgJournalAcctDeclOrder (fname <> ": acct decls renumbered: ")

View File

@ -338,7 +338,7 @@ includedirectivep = do
Fail.fail ("Cyclic include: " ++ filepath)
childInput <-
traceOrLogAt 6 ("parseChild: "++takeFileName filepath) $
dbg6Msg ("parseChild: "++takeFileName filepath) $
lift $ readFilePortably filepath
`orRethrowIOError` (show parentpos ++ " reading " ++ filepath)
let initChildj = newJournalWithParseStateFrom filepath parentj

View File

@ -125,16 +125,16 @@ accountTransactionsReport rspec@ReportSpec{_rsReportOpts=ropts} j thisacctq = it
-- want to keep prices around, so we can toggle between cost and no cost quickly. We can use
-- the show_costs_ flag to be efficient when we can, and detailed when we have to.
(if show_costs_ ropts then id else journalMapPostingAmounts mixedAmountStripCosts)
. traceOrLogAtWith 5 (("ts3:\n"++).pshowTransactions.jtxns)
. dbg5With (("ts3:\n"++).pshowTransactions.jtxns)
-- maybe convert these transactions to cost or value
. journalApplyValuationFromOpts rspec
. traceOrLogAtWith 5 (("ts2:\n"++).pshowTransactions.jtxns)
. dbg5With (("ts2:\n"++).pshowTransactions.jtxns)
-- apply any cur: or amt: filters in reportq
. (if queryIsNull amtq then id else filterJournalAmounts amtq)
-- only consider transactions which match thisacctq (possibly excluding postings
-- which are not real or have the wrong status)
. traceOrLogAt 3 ("thisacctq: "++show thisacctq)
$ traceOrLogAtWith 5 (("ts1:\n"++).pshowTransactions.jtxns)
. dbg3Msg ("thisacctq: "++show thisacctq)
$ dbg5With (("ts1:\n"++).pshowTransactions.jtxns)
j{jtxns = filter (matchesTransaction thisacctq . relevantPostings) $ jtxns j}
where
relevantPostings
@ -159,7 +159,7 @@ accountTransactionsReport rspec@ReportSpec{_rsReportOpts=ropts} j thisacctq = it
items =
accountTransactionsReportItems reportq thisacctq startbal maNegate (journalAccountType j)
-- sort by the transaction's register date, then index, for accurate starting balance
. traceAtWith 5 (("ts4:\n"++).pshowTransactions.map snd)
. dbg5With (("ts4:\n"++).pshowTransactions.map snd)
. sortBy (comparing (Down . fst) <> comparing (Down . tindex . snd))
. map (\t -> (transactionRegisterDate wd reportq thisacctq t, t))
. map (if invert_ ropts then (\t -> t{tpostings = map postingNegateMainAmount $ tpostings t}) else id)

View File

@ -2,42 +2,13 @@
Here are fancier versions of Debug.Trace, with these features:
- unsafePerformIO-based for easy usage in pure code, IO code, and program startup code
- reasonably short and memorable function names
- pretty-printing haskell values, with or without colour, using pretty-simple
- short, memorable, greppable function names
- pretty-printing of haskell values, using pretty-simple
- optional ANSI colour
- enabling/disabling debug output with --debug
- multiple debug verbosity levels, from 1 to 9
- sending debug output to stderr or to a log file
- enabling logging based on program name
The basic "trace" functions print to stderr.
This debug output will be interleaved with the program's normal output, which can be
useful for understanding when code executes.
The "Log" functions log to a file instead.
The need for these is arguable, since a technically savvy user can redirect
stderr output to a log file, eg: @CMD 2>debug.log@.
But here is how they currently work:
The "traceLog" functions log to the program's debug log file,
which is @PROGNAME.log@ in the current directory,
where PROGNAME is the program name returned by @getProgName@.
When using this logging feature you should call @withProgName@ explicitly
at the start of your program to ensure a stable program name,
otherwise it can change to "<interactive>" eg when running in GHCI.
Eg: @main = withProgName "MYPROG" $ do ...@.
The "OrLog" functions can either print to stderr or log to a file.
- By default, they print to stderr.
- If the program name has been set (with @withProgName) to something ending with ".log", they log to that file instead.
This can be useful for programs which should never print to stderr, eg TUI programs like hledger-ui.
The "At" functions produce output only when the program was run with a
sufficiently high debug level, as set by a @--debug[=N]@ command line option.
N ranges from 1 (least debug output) to 9 (most debug output),
@--debug@ with no argument means 1.
- debug output levels from 1 to 9, selected by --debug N option
- --debug detected with unsafePerformIO for easy use in pure/IO/startup code
- debug output can be logged instead (for TUI apps)
The "dbgN*" functions are intended to be the most convenient API, to be embedded
at points of interest in your code. They combine the conditional output of "At",
@ -98,34 +69,79 @@ val
module Hledger.Utils.Debug (
debugLevel
-- * The program's debug level, from 0 (least debug output) to 9 (most).
-- This is parsed from a command line --debug N option, or --debug meaning 1.
-- The command line is read (once) by unsafePerformIO, allowing this to be used
-- easily anywhere in your program.
debugLevel
-- * Tracing to stderr
,traceWith
,traceAt
,traceAtWith
,ptrace
,ptraceAt
,ptraceAtIO
-- These print to stderr.
-- This output will be interleaved with the program's normal output,
-- which can be helpful for understanding code execution.
--
-- ,traceWith
-- ,traceAt
-- ,traceAtWith
-- ,ptrace
-- ,ptraceAt
-- ,ptraceAtIO
-- * Logging to PROGNAME.log
,traceLog
,traceLogAt
,traceLogIO
,traceLogAtIO
,traceLogWith
,traceLogAtWith
,ptraceLogAt
,ptraceLogAtIO
-- * Logging to a log file
-- These append to a PROGRAM.log file in the current directory.
-- PROGRAM is normally the name of the executable, but it can change
-- eg when running in GHCI. So when using these, you should call
-- @withProgName@ to ensure a stable program name.
-- Eg: @main = withProgName "PROGRAM" $ do ...@.
--
-- ,log'
-- ,logAt
-- ,logIO
-- ,logAtIO
-- ,logWith
-- ,logAtWith
-- ,plogAt
-- ,plogAtIO
-- * Tracing or logging based on shouldLog
,traceOrLog
,traceOrLogAt
,ptraceOrLogAt
,ptraceOrLogAtIO
,traceOrLogAtWith
-- All @dbg*@ functions normally trace to stderr,
-- but they will log to PROGRAM.log instead if the (internal) program name ends with ".log".
-- Eg: @main = withProgName "PROGRAM.log" $ do ...@.
-- This is intended for TUI programs where stderr output is hard to see.
--
-- They have an effect only when the program's debug level is at or above the
-- level specified by an argument or by the function name.
-- The many variants follow a consistent pattern and aim to reduce typing and cognitive load.
-- * Pretty tracing/logging in pure code
-- * Trace/log a string
,dbgMsg
,dbg0Msg
,dbg1Msg
,dbg2Msg
,dbg3Msg
,dbg4Msg
,dbg5Msg
,dbg6Msg
,dbg7Msg
,dbg8Msg
,dbg9Msg
-- * In IO
,dbgMsgIO
,dbg0MsgIO
,dbg1MsgIO
,dbg2MsgIO
,dbg3MsgIO
,dbg4MsgIO
,dbg5MsgIO
,dbg6MsgIO
,dbg7MsgIO
,dbg8MsgIO
,dbg9MsgIO
-- * Trace/log a showable value, pretty-printed
-- @dbg@ here clashes with Text.Megaparsec.Debug (dbg), so that module or this one
-- should be imported qualified if you are using both.
,dbg
,dbg0
,dbg1
,dbg2
@ -136,9 +152,9 @@ module Hledger.Utils.Debug (
,dbg7
,dbg8
,dbg9
,dbgExit
-- * Pretty tracing/logging in IO
-- * In IO
,dbgIO
,dbg0IO
,dbg1IO
,dbg2IO
@ -150,7 +166,8 @@ module Hledger.Utils.Debug (
,dbg8IO
,dbg9IO
-- * Tracing/logging with a show function
-- * With a custom show function
,dbgWith
,dbg0With
,dbg1With
,dbg2With
@ -162,12 +179,14 @@ module Hledger.Utils.Debug (
,dbg8With
,dbg9With
-- * Utilities
-- * Utilities, ghc-debug
,ghcDebugSupportedInLib
,GhcDebugMode(..)
,ghcDebugMode
,withGhcDebug'
,ghcDebugPause'
-- * Utilities, other
,lbl_
,progName
@ -182,14 +201,13 @@ import Control.DeepSeq (force)
import Control.Exception (evaluate)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.List hiding (uncons)
-- import Debug.Breakpoint
import Debug.Trace (trace, traceIO, traceShowId)
#ifdef GHCDEBUG
import GHC.Debug.Stub (pause, withGhcDebug)
#endif
import Safe (readDef)
import System.Environment (getProgName)
import System.Exit (exitFailure)
-- import System.Exit (exitFailure)
import System.IO.Unsafe (unsafePerformIO)
import Hledger.Utils.IO (progArgs, pshow, pshow')
@ -285,16 +303,8 @@ ghcDebugPause' =
return ()
#endif
-- | Trace a value with the given show function before returning it.
traceWith :: (a -> String) -> a -> a
traceWith f a = trace (f a) a
-- | Pretty-trace a showable value before returning it.
-- Like Debug.Trace.traceShowId, but pretty-printing and easier to type.
ptrace :: Show a => a -> a
ptrace = traceWith pshow
-- | Trace (print to stderr) a string if the global debug level is at
-- | Trace (print to stderr) a string if the program debug level is at
-- or above the specified level. At level 0, always prints. Otherwise,
-- uses unsafePerformIO.
traceAt :: Int -> String -> a -> a
@ -302,14 +312,30 @@ traceAt level
| level > 0 && debugLevel < level = const id
| otherwise = trace
-- | Like traceAt, but sequences properly in IO.
traceAtIO :: (MonadIO m) => Int -> String -> m ()
traceAtIO level msg =
if level > 0 && debugLevel < level
then return ()
else liftIO $ traceIO msg
-- -- | Trace a value with the given show function before returning it.
-- traceWith :: (a -> String) -> a -> a
-- traceWith f a = trace (f a) a
-- | Trace (print to stderr) a showable value using a custom show function,
-- if the global debug level is at or above the specified level.
-- if the program debug level is at or above the specified level.
-- At level 0, always prints. Otherwise, uses unsafePerformIO.
traceAtWith :: Int -> (a -> String) -> a -> a
traceAtWith level f a = traceAt level (f a) a
-- -- | Pretty-trace a showable value before returning it.
-- -- Like Debug.Trace.traceShowId, but pretty-printing and easier to type.
-- ptrace :: Show a => a -> a
-- ptrace = traceWith pshow
-- | Pretty-print a label and a showable value to the console
-- if the global debug level is at or above the specified level.
-- if the program debug level is at or above the specified level.
-- At level 0, always prints. Otherwise, uses unsafePerformIO.
ptraceAt :: Show a => Int -> String -> a -> a
ptraceAt level
@ -326,206 +352,257 @@ labelledPretty allowcolour lbl a = lbl ++ ":" ++ nlorspace ++ intercalate "\n" l
ls' | length ls > 1 = map (' ':) ls
| otherwise = ls
-- | Like ptraceAt, but convenient to insert in an IO monad and
-- enforces monadic sequencing.
-- | Like ptraceAt, but sequences properly in IO.
ptraceAtIO :: (MonadIO m, Show a) => Int -> String -> a -> m ()
ptraceAtIO level label a =
if level > 0 && debugLevel < level
then return ()
else liftIO $ traceIO (labelledPretty True label a)
-- | Should the "trace or log" functions output to a file instead of stderr ?
-- True if the program name ends with ".log".
shouldLog :: Bool
shouldLog = ".log" `isSuffixOf` modifiedProgName
-- | The debug log file: PROGNAME.log in the current directory.
-- See modifiedProgName.
debugLogFile :: FilePath
debugLogFile = progName ++ ".log"
-- -- | The debug log file: debug.log in the current directory.
-- debugLogFile :: FilePath
-- debugLogFile = "debug.log"
-- | Log a string to the debug log before returning the second argument.
-- Uses unsafePerformIO.
traceLog :: String -> a -> a
traceLog s x = unsafePerformIO $ do
log' :: String -> a -> a
log' s x = unsafePerformIO $ do
evaluate (force s) -- to complete any previous logging before we attempt more
appendFile debugLogFile (s ++ "\n")
return x
-- | Log a string to the debug log before returning the second argument,
-- if the global debug level is at or above the specified level.
-- if the program debug level is at or above the specified level.
-- At level 0, always logs. Otherwise, uses unsafePerformIO.
traceLogAt :: Int -> String -> a -> a
traceLogAt level str
logAt :: Int -> String -> a -> a
logAt level str
| level > 0 && debugLevel < level = id
| otherwise = traceLog str
| otherwise = log' str
-- | Like traceLog but sequences properly in IO.
traceLogIO :: MonadIO m => String -> m ()
traceLogIO s = do
-- | Like log' but sequences properly in IO.
logIO :: MonadIO m => String -> m ()
logIO s = do
liftIO $ evaluate (force s) -- to complete any previous logging before we attempt more
liftIO $ appendFile debugLogFile (s ++ "\n")
-- | Like traceLogAt, but convenient to use in IO.
traceLogAtIO :: MonadIO m => Int -> String -> m ()
traceLogAtIO level str
-- | Like logAt, but convenient to use in IO.
logAtIO :: (MonadIO m) => Int -> String -> m ()
logAtIO level str
| level > 0 && debugLevel < level = return ()
| otherwise = traceLogIO str
| otherwise = logIO str
-- | Log a value to the debug log with the given show function before returning it.
traceLogWith :: (a -> String) -> a -> a
traceLogWith f a = traceLog (f a) a
-- -- | Log a value to the debug log with the given show function before returning it.
-- logWith :: (a -> String) -> a -> a
-- logWith f a = log' (f a) a
-- | Log a string to the debug log before returning the second argument,
-- if the global debug level is at or above the specified level.
-- if the program debug level is at or above the specified level.
-- At level 0, always logs. Otherwise, uses unsafePerformIO.
traceLogAtWith :: Int -> (a -> String) -> a -> a
traceLogAtWith level f a = traceLogAt level (f a) a
logAtWith :: Int -> (a -> String) -> a -> a
logAtWith level f a = logAt level (f a) a
-- | Pretty-log a label and showable value to the debug log,
-- if the global debug level is at or above the specified level.
-- if the program debug level is at or above the specified level.
-- At level 0, always prints. Otherwise, uses unsafePerformIO.
ptraceLogAt :: Show a => Int -> String -> a -> a
ptraceLogAt level
plogAt :: (Show a) => Int -> String -> a -> a
plogAt level
| level > 0 && debugLevel < level = const id
| otherwise = \lbl a -> traceLog (labelledPretty False lbl a) a
| otherwise = \lbl a -> log' (labelledPretty False lbl a) a
-- | Like ptraceAt, but convenient to insert in an IO monad and
-- enforces monadic sequencing.
ptraceLogAtIO :: (MonadIO m, Show a) => Int -> String -> a -> m ()
ptraceLogAtIO level label a =
-- | Like ptraceAt, but sequences properly in IO.
plogAtIO :: (MonadIO m, Show a) => Int -> String -> a -> m ()
plogAtIO level label a =
if level > 0 && debugLevel < level
then return ()
else traceLogIO (labelledPretty False label a)
else logIO (labelledPretty False label a)
-- | Trace or log a string depending on shouldLog,
-- before returning the second argument.
traceOrLog :: String -> a -> a
traceOrLog = if shouldLog then traceLog else trace
-- | Trace or log a string depending on shouldLog,
-- when global debug level is at or above the specified level,
-- before returning the second argument.
traceOrLogAt :: Int -> String -> a -> a
traceOrLogAt = if shouldLog then traceLogAt else traceAt
-- | Should dbg* log to a file instead of tracing to stderr ?
-- True if the (internal) program name ends with ".log".
shouldLog :: Bool
shouldLog = ".log" `isSuffixOf` modifiedProgName
-- | Pretty-trace or log depending on shouldLog, when global debug level
-- is at or above the specified level.
ptraceOrLogAt :: Show a => Int -> String -> a -> a
ptraceOrLogAt = if shouldLog then ptraceLogAt else ptraceAt
-- | Like ptraceOrLogAt, but convenient in IO.
ptraceOrLogAtIO :: (MonadIO m, Show a) => Int -> String -> a -> m ()
ptraceOrLogAtIO = if shouldLog then ptraceLogAtIO else ptraceAtIO
-- | Trace or log a string if the program debug level is at or above the specified level,
-- then return the second argument.
dbgMsg :: Int -> String -> a -> a
dbgMsg = if shouldLog then logAt else traceAt
-- | Trace or log, with a show function, depending on shouldLog.
traceOrLogAtWith :: Int -> (a -> String) -> a -> a
traceOrLogAtWith = if shouldLog then traceLogAtWith else traceAtWith
dbg0Msg :: String -> a -> a
dbg0Msg = dbgMsg 0
dbg1Msg :: String -> a -> a
dbg1Msg = dbgMsg 1
dbg2Msg :: String -> a -> a
dbg2Msg = dbgMsg 2
dbg3Msg :: String -> a -> a
dbg3Msg = dbgMsg 3
dbg4Msg :: String -> a -> a
dbg4Msg = dbgMsg 4
dbg5Msg :: String -> a -> a
dbg5Msg = dbgMsg 5
dbg6Msg :: String -> a -> a
dbg6Msg = dbgMsg 6
dbg7Msg :: String -> a -> a
dbg7Msg = dbgMsg 7
dbg8Msg :: String -> a -> a
dbg8Msg = dbgMsg 8
dbg9Msg :: String -> a -> a
dbg9Msg = dbgMsg 9
-- | Like dbgMsg, but sequences properly in IO.
dbgMsgIO :: (MonadIO m) => Int -> String -> m ()
dbgMsgIO = if shouldLog then logAtIO else traceAtIO
dbg0MsgIO :: (MonadIO m) => String -> m ()
dbg0MsgIO = dbgMsgIO 0
dbg1MsgIO :: (MonadIO m) => String -> m ()
dbg1MsgIO = dbgMsgIO 1
dbg2MsgIO :: (MonadIO m) => String -> m ()
dbg2MsgIO = dbgMsgIO 2
dbg3MsgIO :: (MonadIO m) => String -> m ()
dbg3MsgIO = dbgMsgIO 3
dbg4MsgIO :: (MonadIO m) => String -> m ()
dbg4MsgIO = dbgMsgIO 4
dbg5MsgIO :: (MonadIO m) => String -> m ()
dbg5MsgIO = dbgMsgIO 5
dbg6MsgIO :: (MonadIO m) => String -> m ()
dbg6MsgIO = dbgMsgIO 6
dbg7MsgIO :: (MonadIO m) => String -> m ()
dbg7MsgIO = dbgMsgIO 7
dbg8MsgIO :: (MonadIO m) => String -> m ()
dbg8MsgIO = dbgMsgIO 8
dbg9MsgIO :: (MonadIO m) => String -> m ()
dbg9MsgIO = dbgMsgIO 9
-- | Trace or log a label and showable value, pretty-printed,
-- if the program debug level is at or above the specified level;
-- then return the value.
-- Traces to stderr or logs to a file depending on shouldLog.
dbg :: (Show a) => Int -> String -> a -> a
dbg = if shouldLog then plogAt else ptraceAt
-- | Pretty-trace to stderr (or log to debug log) a label and showable value,
-- then return it.
dbg0 :: Show a => String -> a -> a
dbg0 = ptraceOrLogAt 0
dbg0 = dbg 0
-- | Pretty-trace to stderr (or log to debug log) a label and showable value
-- if the --debug level is high enough, then return the value.
-- Uses unsafePerformIO.
dbg1 :: Show a => String -> a -> a
dbg1 = ptraceOrLogAt 1
dbg1 = dbg 1
dbg2 :: Show a => String -> a -> a
dbg2 = ptraceOrLogAt 2
dbg2 = dbg 2
dbg3 :: Show a => String -> a -> a
dbg3 = ptraceOrLogAt 3
dbg3 = dbg 3
dbg4 :: Show a => String -> a -> a
dbg4 = ptraceOrLogAt 4
dbg4 = dbg 4
dbg5 :: Show a => String -> a -> a
dbg5 = ptraceOrLogAt 5
dbg5 = dbg 5
dbg6 :: Show a => String -> a -> a
dbg6 = ptraceOrLogAt 6
dbg6 = dbg 6
dbg7 :: Show a => String -> a -> a
dbg7 = ptraceOrLogAt 7
dbg7 = dbg 7
dbg8 :: Show a => String -> a -> a
dbg8 = ptraceOrLogAt 8
dbg8 = dbg 8
dbg9 :: Show a => String -> a -> a
dbg9 = ptraceOrLogAt 9
dbg9 = dbg 9
-- | Like dbg0, but also exit the program. Uses unsafePerformIO.
dbgExit :: Show a => String -> a -> a
dbgExit label a = unsafePerformIO $ dbg0IO label a >> exitFailure
-- | Like dbgN, but convenient to use in IO.
-- | Like dbg, but sequences properly in IO.
dbgIO :: (MonadIO m, Show a) => Int -> String -> a -> m ()
dbgIO = if shouldLog then plogAtIO else ptraceAtIO
dbg0IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg0IO = ptraceOrLogAtIO 0
dbg0IO = dbgIO 0
dbg1IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg1IO = ptraceOrLogAtIO 1
dbg1IO = dbgIO 1
dbg2IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg2IO = ptraceOrLogAtIO 2
dbg2IO = dbgIO 2
dbg3IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg3IO = ptraceOrLogAtIO 3
dbg3IO = dbgIO 3
dbg4IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg4IO = ptraceOrLogAtIO 4
dbg4IO = dbgIO 4
dbg5IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg5IO = ptraceOrLogAtIO 5
dbg5IO = dbgIO 5
dbg6IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg6IO = ptraceOrLogAtIO 6
dbg6IO = dbgIO 6
dbg7IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg7IO = ptraceOrLogAtIO 7
dbg7IO = dbgIO 7
dbg8IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg8IO = ptraceOrLogAtIO 8
dbg8IO = dbgIO 8
dbg9IO :: (MonadIO m, Show a) => String -> a -> m ()
dbg9IO = ptraceOrLogAtIO 9
dbg9IO = dbgIO 9
-- | Like dbgWith, but with a custom show function.
dbgWith :: Int -> (a -> String) -> a -> a
dbgWith = if shouldLog then logAtWith else traceAtWith
-- | Like dbgN, but taking a show function instead of a label.
dbg0With :: (a -> String) -> a -> a
dbg0With = traceOrLogAtWith 0
dbg0With = dbgWith 0
dbg1With :: Show a => (a -> String) -> a -> a
dbg1With = traceOrLogAtWith 1
dbg1With = dbgWith 1
dbg2With :: Show a => (a -> String) -> a -> a
dbg2With = traceOrLogAtWith 2
dbg2With = dbgWith 2
dbg3With :: Show a => (a -> String) -> a -> a
dbg3With = traceOrLogAtWith 3
dbg3With = dbgWith 3
dbg4With :: Show a => (a -> String) -> a -> a
dbg4With = traceOrLogAtWith 4
dbg4With = dbgWith 4
dbg5With :: Show a => (a -> String) -> a -> a
dbg5With = traceOrLogAtWith 5
dbg5With = dbgWith 5
dbg6With :: Show a => (a -> String) -> a -> a
dbg6With = traceOrLogAtWith 6
dbg6With = dbgWith 6
dbg7With :: Show a => (a -> String) -> a -> a
dbg7With = traceOrLogAtWith 7
dbg7With = dbgWith 7
dbg8With :: Show a => (a -> String) -> a -> a
dbg8With = traceOrLogAtWith 8
dbg8With = dbgWith 8
dbg9With :: Show a => (a -> String) -> a -> a
dbg9With = traceOrLogAtWith 9
dbg9With = dbgWith 9
-- | Helper for producing debug messages:
-- concatenates a name (eg a function name),
@ -547,5 +624,5 @@ lbl_ name desc val = name <> ": " <> desc <> ":" <> " " <> val
-- dbg_ :: forall a. Show a => Int -> String -> (String -> (a -> String) -> a -> a)
-- dbg_ level topic =
-- \desc showfn val ->
-- traceOrLogAtWith level (lbl_ topic desc . showfn) val
-- dbgWith level (lbl_ topic desc . showfn) val
-- {-# HLINT ignore "Redundant lambda" #-}

View File

@ -47,7 +47,6 @@ module Hledger.Utils.Parse (
-- ** Trace the state of hledger parsers
dbgparse,
traceOrLogParse,
-- * More helpers, previously in Text.Megaparsec.Custom
@ -111,7 +110,7 @@ import qualified Data.List.NonEmpty as NE
import Data.Monoid (Alt(..))
import qualified Data.Set as S
import Hledger.Utils.Debug (debugLevel, traceOrLog)
import Hledger.Utils.Debug (debugLevel, dbg0Msg)
-- | A parser of string to some type.
type SimpleStringParser a = Parsec HledgerParseErrorData String a
@ -128,23 +127,17 @@ type TextParser m a = ParsecT HledgerParseErrorData Text m a
-- | Trace to stderr or log to debug log the provided label (if non-null)
-- and current parser state (position and next input),
-- if the global debug level is at or above the specified level.
-- Uses unsafePerformIO.
dbgparse :: Int -> String -> TextParser m ()
dbgparse level msg = when (level <= debugLevel) $ traceOrLogParse msg
-- | Trace to stderr or log to debug log the provided label (if non-null)
-- and current parser state (position and next input).
-- See also: Hledger.Utils.Debug, megaparsec's dbg.
-- Uses unsafePerformIO.
-- XXX Can be hard to make this evaluate.
traceOrLogParse :: String -> TextParser m ()
traceOrLogParse msg = do
dbgparse :: Int -> String -> TextParser m ()
dbgparse level msg = when (level <= debugLevel) $ do
pos <- getSourcePos
next <- (T.take peeklength) `fmap` getInput
let (l,c) = (sourceLine pos, sourceColumn pos)
s = printf "at line %2d col %2d: %s" (unPos l) (unPos c) (show next) :: String
s' = printf ("%-"++show (peeklength+30)++"s") s ++ " " ++ msg
traceOrLog s' $ return ()
dbg0Msg s' $ return ()
where
peeklength = 30

View File

@ -92,7 +92,7 @@ hledgerUiMain = exitOnError $ withGhcDebug' $ withProgName "hledger-ui.log" $ do
#endif
#endif
traceLogAtIO 1 "\n\n\n\n==== hledger-ui start"
dbg1MsgIO "\n\n\n\n==== hledger-ui start"
dbg1IO "args" progArgs
dbg1IO "debugLevel" debugLevel

View File

@ -451,15 +451,15 @@ listScrollPushingSelection name listheight scrollamt = do
-- logs the string to hledger-ui.log before returning the second argument.
-- Uses unsafePerformIO.
dbgui :: String -> a -> a
dbgui = traceLogAt 1
dbgui = dbg1Msg
-- | Like dbgui, but convenient to use in IO.
dbguiIO :: String -> IO ()
dbguiIO = traceLogAtIO 1
dbguiIO = dbg1MsgIO
-- | Like dbgui, but convenient to use in EventM handlers.
dbguiEv :: String -> EventM Name s ()
dbguiEv s = dbgui s $ return ()
dbguiEv s = dbg1Msg s $ return ()
-- | Like dbguiEv, but log a compact view of the current screen stack.
-- See showScreenStack.

View File

@ -218,13 +218,10 @@ main = exitOnError $ withGhcDebug' $ do
-- 0. let's go!
let
-- Trace helpers. These always trace to stderr, even when running `hledger ui`;
-- that's ok as conf is a hledger cli feature for now.
dbgIO, dbgIO1, dbgIO2 :: Show a => String -> a -> IO () -- this signature is needed
dbgIO = ptraceAtIO verboseDebugLevel
dbgIO1 = ptraceAtIO 1
dbgIO2 = ptraceAtIO 2
dbgIO "running" prognameandversion
dbgio :: Show a => String -> a -> IO ()
dbgio = dbgIO verboseDebugLevel
dbgio "running" prognameandversion
starttime <- getPOSIXTime
-- give ghc-debug a chance to take control
when (ghcDebugMode == GDPauseAtStart) $ ghcDebugPause'
@ -235,7 +232,7 @@ main = exitOnError $ withGhcDebug' $ do
addons <- addonCommandNames
---------------------------------------------------------------
dbgIO "\n1. Preliminary command line parsing" ()
dbgio "\n1. Preliminary command line parsing" ()
-- Naming notes:
-- "arg" often has the most general meaning, including things like: -f, --flag, flagvalue, arg, >file, &, etc.
@ -252,15 +249,15 @@ main = exitOnError $ withGhcDebug' $ do
(clicmdarg, cliargswithoutcmd, cliargswithcmdfirst) = moveFlagsAfterCommand cliargs
cliargswithcmdfirstwithoutclispecific = dropCliSpecificOpts cliargswithcmdfirst
(cliargsbeforecmd, cliargsaftercmd) = second (drop 1) $ break (==clicmdarg) cliargs
dbgIO "cli args with preprocessing" cliargs
dbgio "cli args with preprocessing" cliargs
dbg1IO "cli args with preprocessing and options moved after command" cliargswithcmdfirst
dbgIO "cli command argument found" clicmdarg
dbgIO "cli args before command" cliargsbeforecmd
dbgIO "cli args after command" cliargsaftercmd
-- dbgIO "cli args without command" cliargswithoutcmd
dbgio "cli command argument found" clicmdarg
dbgio "cli args before command" cliargsbeforecmd
dbgio "cli args after command" cliargsaftercmd
-- dbgio "cli args without command" cliargswithoutcmd
---------------------------------------------------------------
dbgIO "\n2. Read the config file if any" ()
dbgio "\n2. Read the config file if any" ()
-- Identify any --conf/--no-conf options.
-- Run cmdargs on just the args that look conf-related.
@ -276,7 +273,7 @@ main = exitOnError $ withGhcDebug' $ do
else getConf' cliconfrawopts
---------------------------------------------------------------
dbgIO "\n3. Identify a command name from config file or command line" ()
dbgio "\n3. Identify a command name from config file or command line" ()
-- Try to identify the subcommand name,
-- from the first non-flag general argument in the config file,
@ -310,15 +307,15 @@ main = exitOnError $ withGhcDebug' $ do
when (isJust mconffile) $ do
unless (null confcmdarg) $
dbgIO1 "using command name argument from config file" confcmdarg
dbgIO "cli args with command first and no cli-specific opts" cliargswithcmdfirstwithoutclispecific
dbgIO1 "command found" cmdname
dbgIO "no command provided" nocmdprovided
dbgIO "bad command provided" badcmdprovided
dbgIO "is addon command" isaddoncmd
dbg1IO "using command name argument from config file" confcmdarg
dbgio "cli args with command first and no cli-specific opts" cliargswithcmdfirstwithoutclispecific
dbg1IO "command found" cmdname
dbgio "no command provided" nocmdprovided
dbgio "bad command provided" badcmdprovided
dbgio "is addon command" isaddoncmd
---------------------------------------------------------------
dbgIO "\n4. Get applicable options/arguments from config file" ()
dbgio "\n4. Get applicable options/arguments from config file" ()
-- Ignore any general opts or cli-specific opts not known to be supported by the command.
let
@ -337,13 +334,13 @@ main = exitOnError $ withGhcDebug' $ do
& if isaddoncmd then ("--":) else id
when (isJust mconffile) $ do
dbgIO1 "using general args from config file" confothergenargs
dbg1IO "using general args from config file" confothergenargs
unless (null excludedgenargsfromconf) $
dbgIO1 "excluded general args from config file, not supported by this command" excludedgenargsfromconf
dbgIO1 "using subcommand args from config file" confcmdargs
dbg1IO "excluded general args from config file, not supported by this command" excludedgenargsfromconf
dbg1IO "using subcommand args from config file" confcmdargs
---------------------------------------------------------------
dbgIO "\n5. Combine config file and command line args" ()
dbgio "\n5. Combine config file and command line args" ()
let
finalargs =
@ -355,7 +352,7 @@ main = exitOnError $ withGhcDebug' $ do
& replaceNumericFlags -- convert any -NUM opts from the config file
-- finalargs' <- expandArgsAt finalargs -- expand @ARGFILEs in the config file ? don't bother
dbgIO1 "final args" finalargs
dbg1IO "final args" finalargs
-- Run cmdargs on command name + supported conf general args + conf subcommand args + cli args to get the final options.
-- A bad flag or flag argument will cause the program to exit with an error here.
@ -363,7 +360,7 @@ main = exitOnError $ withGhcDebug' $ do
---------------------------------------------------------------
seq rawopts $ -- order debug output
dbgIO "\n6. Select an action and run it" ()
dbgio "\n6. Select an action and run it" ()
-- We check for the help/doc/version flags first, since they are a high priority.
-- (A perfectionist might think they should be so high priority that adding -h
@ -381,10 +378,10 @@ main = exitOnError $ withGhcDebug' $ do
-- validate opts/args more and convert to CliOpts
opts <- rawOptsToCliOpts rawopts >>= \opts0 -> return opts0{progstarttime_=starttime}
dbgIO2 "processed opts" opts
dbgIO "period from opts" (period_ . _rsReportOpts $ reportspec_ opts)
dbgIO "interval from opts" (interval_ . _rsReportOpts $ reportspec_ opts)
dbgIO "query from opts & args" (_rsQuery $ reportspec_ opts)
dbg2IO "processed opts" opts
dbgio "period from opts" (period_ . _rsReportOpts $ reportspec_ opts)
dbgio "interval from opts" (interval_ . _rsReportOpts $ reportspec_ opts)
dbgio "query from opts & args" (_rsQuery $ reportspec_ opts)
-- Ensure that anything calling getArgs later will see all args, including config file args.
-- Some things (--color, --debug, some checks in journalFinalise) are detected by unsafePerformIO,
@ -406,13 +403,13 @@ main = exitOnError $ withGhcDebug' $ do
-- 6.4. no command found, nothing else to do - show the commands list
| nocmdprovided -> do
dbgIO1 "no command, showing commands list" ()
dbg1IO "no command, showing commands list" ()
commands opts (ignoredjournal "commands")
-- 6.5. builtin command found
| Just (cmdmode, cmdaction) <- mbuiltincmdaction -> do
let mmodecmdname = headMay $ modeNames cmdmode
dbgIO1 "running builtin command mode" $ fromMaybe "" mmodecmdname
dbg1IO "running builtin command mode" $ fromMaybe "" mmodecmdname
-- run the builtin command according to its type
if
@ -453,9 +450,9 @@ main = exitOnError $ withGhcDebug' $ do
addonargs0 = filter (/="--") $ supportedgenargsfromconf <> confcmdargs <> cliargswithoutcmd
addonargs = dropCliSpecificOpts addonargs0
shellcmd = printf "%s-%s %s" progname cmdname (unwords' addonargs) :: String
dbgIO "addon command selected" cmdname
dbgIO "addon command arguments after removing cli-specific opts" (map quoteIfNeeded addonargs)
dbgIO1 "running addon" shellcmd
dbgio "addon command selected" cmdname
dbgio "addon command arguments after removing cli-specific opts" (map quoteIfNeeded addonargs)
dbg1IO "running addon" shellcmd
system shellcmd >>= exitWith
-- deprecated command found
@ -496,7 +493,7 @@ cmdargsParse :: String -> Mode RawOpts -> [String] -> RawOpts
cmdargsParse desc m args0 = process m (ensureDebugFlagHasVal args0)
& either
(\e -> error' $ e <> "\n* while parsing the following args, " <> desc <> ":\n* " <> unwords (map quoteIfNeeded args0))
(traceOrLogAt verboseDebugLevel ("cmdargs: parsing " <> desc <> ": " <> show args0))
(dbgMsg verboseDebugLevel ("cmdargs: parsing " <> desc <> ": " <> show args0))
-- XXX better error message when cmdargs fails (eg spaced/quoted/malformed flag values) ?
-- | cmdargs does not allow options to appear before the subcommand argument.
@ -558,12 +555,12 @@ moveFlagsAfterCommand args =
moveFlagAndVal :: ([String], [String]) -> ([String], [String])
moveFlagAndVal ((a:b:cs), moved) =
case isMovableFlagArg a (Just b) of
2 -> traceOrLogAt lvl ("moving 2: "<>a<>" "<>b) $ moveFlagAndVal (cs, moved++[a,b])
1 -> traceOrLogAt lvl ("moving 1: "<>a) $ moveFlagAndVal (b:cs, moved++[a])
2 -> dbgMsg lvl ("moving 2: "<>a<>" "<>b) $ moveFlagAndVal (cs, moved++[a,b])
1 -> dbgMsg lvl ("moving 1: "<>a) $ moveFlagAndVal (b:cs, moved++[a])
_ -> (a:b:cs, moved)
moveFlagAndVal ([a], moved) =
case isMovableFlagArg a Nothing of
1 -> traceOrLogAt lvl ("moving 1: "<>a) ([], moved++[a])
1 -> dbgMsg lvl ("moving 1: "<>a) ([], moved++[a])
_ -> ([a], moved)
moveFlagAndVal ([], moved) = ([], moved)
lvl = 8

View File

@ -107,13 +107,13 @@ getConf rawopts = do
-- As in Cli.hs, conf debug output always goes to stderr;
-- that's ok as conf is a hledger cli feature for now.
case confFileSpecFromRawOpts rawopts of
NoConfFile -> return $ Right $ traceAt 1 "ignoring config files" (nullconf, Nothing)
NoConfFile -> return $ Right $ dbg1Msg "ignoring config files" (nullconf, Nothing)
SomeConfFile f -> getCurrentDirectory >>= flip expandPath f >>= readConfFile . dbg1 "using specified config file"
AutoConfFile -> do
fs <- confFiles
case fs of
f:_ -> dbg8IO "found config files" fs >> dbg1IO "using nearest config file" f >> readConfFile f
[] -> return $ Right $ traceAt 1 "no config file found" (nullconf, Nothing)
[] -> return $ Right $ dbg1Msg "no config file found" (nullconf, Nothing)
-- | Like getConf but throws an error on failure.
getConf' :: RawOpts -> IO (Conf, Maybe FilePath)