Skip to content

Commit 6e57788

Browse files
goodlyrottenapplegithub-actions
andauthored
Improve JSON logging (#3858)
This PR does the following: * aligns JSON logs across the two backends * removes implifyjson logs in favour of contextual JSON logging * adds a custom syntax for `--log-context` also removing `--no-log-context` (see the docs/logging.md change for details) * switches the ceil analysis to contextual logs --------- Co-authored-by: github-actions <[email protected]>
1 parent 31bf501 commit 6e57788

File tree

30 files changed

+629
-34131
lines changed

30 files changed

+629
-34131
lines changed

booster/library/Booster/CLOptions.hs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Text.Casing (fromHumps, fromKebab, toKebab, toPascal)
2222
import Text.Read (readMaybe)
2323

2424
import Booster.GlobalState (EquationOptions (..))
25+
import Booster.Log.Context (ContextFilter, readContextFilter)
2526
import Booster.SMT.Interface (SMTOptions (..), defaultSMTOptions)
2627
import Booster.SMT.LowLevelCodec qualified as SMT (parseSExpr)
2728

@@ -33,8 +34,7 @@ data CLOptions = CLOptions
3334
, logLevels :: [LogLevel]
3435
, logTimeStamps :: Bool
3536
, logFormat :: LogFormat
36-
, logContexts :: [String]
37-
, notLogContexts :: [String]
37+
, logContexts :: [ContextFilter]
3838
, simplificationLogFile :: Maybe FilePath
3939
, smtOptions :: Maybe SMTOptions
4040
, equationOptions :: EquationOptions
@@ -106,23 +106,14 @@ clOptionsParser =
106106
)
107107
<*> many
108108
( option
109-
str
109+
(eitherReader readContextFilter)
110110
( metavar "CONTEXT"
111111
<> long "log-context"
112112
<> short 'c'
113113
<> help
114114
"Log context"
115115
)
116116
)
117-
<*> many
118-
( option
119-
str
120-
( metavar "CONTEXT"
121-
<> long "not-log-context"
122-
<> help
123-
"Not in log context"
124-
)
125-
)
126117
<*> optional
127118
( strOption
128119
( metavar "JSON_LOG_FILE"
@@ -186,7 +177,6 @@ allowedLogLevels =
186177
, ("Depth", "Log the current depth of the state")
187178
, ("SMT", "Log the SMT-solver interactions")
188179
, ("ErrorDetails", "Log error conditions with extensive details")
189-
, ("Ceil", "Log results of the ceil analysis")
190180
]
191181

192182
-- Partition provided log levels into standard and custom ones, and

booster/library/Booster/Definition/Attributes/Base.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ module Booster.Definition.Attributes.Base (
4646
) where
4747

4848
import Control.DeepSeq (NFData (..))
49+
import Data.Aeson (ToJSON (..))
50+
import Data.Aeson qualified as JSON
4951
import Data.ByteString (ByteString)
5052
import Data.Data (Data)
5153
import Data.Hashable (Hashable)
@@ -111,6 +113,9 @@ instance Pretty NotPreservesDefinednessReason where
111113
pretty = \case
112114
UndefinedSymbol name -> "non-total symbol " <> (pretty $ Text.decodeUtf8 $ Util.decodeLabel' name)
113115

116+
instance ToJSON NotPreservesDefinednessReason where
117+
toJSON (UndefinedSymbol n) = JSON.String $ Text.decodeUtf8 n
118+
114119
type Label = Text
115120

116121
newtype UniqueId = UniqueId {getUniqueId :: Text}

booster/library/Booster/Definition/Ceil.hs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ instance Pretty ComputeCeilSummary where
7474
]
7575

7676
computeCeilsDefinition ::
77-
LoggerMIO io => Maybe LLVM.API -> KoreDefinition -> io (KoreDefinition, [ComputeCeilSummary])
77+
LoggerMIO io =>
78+
MonadLoggerIO io =>
79+
Maybe LLVM.API ->
80+
KoreDefinition ->
81+
io (KoreDefinition, [ComputeCeilSummary])
7882
computeCeilsDefinition mllvm def@KoreDefinition{rewriteTheory} = do
7983
(rewriteTheory', ceilSummaries) <-
8084
runWriterT $
@@ -89,6 +93,7 @@ computeCeilsDefinition mllvm def@KoreDefinition{rewriteTheory} = do
8993

9094
computeCeilRule ::
9195
LoggerMIO io =>
96+
MonadLoggerIO io =>
9297
Maybe LLVM.API ->
9398
KoreDefinition ->
9499
RewriteRule.RewriteRule "Rewrite" ->

booster/library/Booster/JsonRpc.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ import Kore.Syntax.Json.Types qualified as Syntax
9696
respond ::
9797
forall m.
9898
LoggerMIO m =>
99+
Log.MonadLoggerIO m =>
99100
MVar ServerState ->
100101
Respond (RpcTypes.API 'RpcTypes.Req) m (RpcTypes.API 'RpcTypes.Res)
101102
respond stateVar =
@@ -302,7 +303,6 @@ respond stateVar =
302303
Right
303304
(addHeader $ Syntax.KJTop (fromMaybe (error "not a predicate") $ sortOfJson req.state.term), [])
304305
| otherwise -> do
305-
Log.logInfoNS "booster" "Simplifying predicates"
306306
unless (null ps.unsupported) $ do
307307
withKorePatternContext (KoreJson.KJAnd (externaliseSort $ SortApp "SortBool" []) ps.unsupported) $ do
308308
logMessage ("ignoring unsupported predicate parts" :: Text)

booster/library/Booster/Log.hs

Lines changed: 81 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,19 @@
55

66
module Booster.Log (module Booster.Log) where
77

8-
import Booster.Definition.Attributes.Base
9-
import Booster.Definition.Base (RewriteRule (..), SourceRef (..), sourceRef)
10-
import Booster.Pattern.Base (
11-
Pattern (..),
12-
Predicate (..),
13-
Term (..),
14-
TermAttributes (hash),
15-
pattern AndTerm,
16-
)
17-
import Booster.Prettyprinter (renderOneLineText)
18-
import Booster.Syntax.Json (KorePattern, prettyPattern)
8+
import Control.Monad (when)
199
import Control.Monad.IO.Class
20-
import Control.Monad.Logger (
21-
LogLevel (..),
22-
MonadLogger,
23-
MonadLoggerIO (askLoggerIO),
24-
NoLoggingT,
25-
ToLogStr (toLogStr),
26-
defaultLoc,
27-
)
10+
import Control.Monad.Logger qualified
2811
import Control.Monad.Trans.Class qualified as Trans
2912
import Control.Monad.Trans.Except (ExceptT (..))
3013
import Control.Monad.Trans.Maybe (MaybeT (..))
3114
import Control.Monad.Trans.Reader (ReaderT (..), ask, withReaderT)
3215
import Control.Monad.Trans.State (StateT (..))
3316
import Control.Monad.Trans.State.Strict qualified as Strict
17+
import Data.Aeson (ToJSON (..), Value (..), (.=))
18+
import Data.Aeson.Encode.Pretty (Config (confIndent), Indent (Spaces), encodePretty')
19+
import Data.Aeson.Key qualified as Key
20+
import Data.Aeson.Types (object)
3421
import Data.Coerce (coerce)
3522
import Data.Data (Proxy (..))
3623
import Data.Hashable qualified
@@ -42,15 +29,28 @@ import Data.Text (Text, pack)
4229
import Data.Text.Lazy qualified as LazyText
4330
import GHC.Exts (IsString (..))
4431
import GHC.TypeLits (KnownSymbol, symbolVal)
45-
import Kore.Util (showHashHex)
4632
import Prettyprinter (Pretty, pretty)
4733

34+
import Booster.Definition.Attributes.Base
35+
import Booster.Definition.Base (RewriteRule (..), SourceRef (..), sourceRef)
36+
import Booster.Pattern.Base (
37+
Pattern (..),
38+
Predicate (..),
39+
Term (..),
40+
TermAttributes (hash),
41+
pattern AndTerm,
42+
)
43+
import Booster.Prettyprinter (renderOneLineText)
44+
import Booster.Syntax.Json (KorePattern, addHeader, prettyPattern)
45+
import Booster.Syntax.Json.Externalise (externaliseTerm)
46+
import Kore.JsonRpc.Types (rpcJsonConfig)
47+
import Kore.Util (showHashHex)
48+
4849
newtype Logger a = Logger (a -> IO ())
4950

5051
class ToLogFormat a where
5152
toTextualLog :: a -> Text
52-
53-
-- toJSONLog :: a -> Value
53+
toJSONLog :: a -> Value
5454

5555
data LogContext = forall a. ToLogFormat a => LogContext a
5656

@@ -60,7 +60,7 @@ instance IsString LogContext where
6060
data LogMessage where
6161
LogMessage :: ToLogFormat a => [LogContext] -> a -> LogMessage
6262

63-
class MonadLoggerIO m => LoggerMIO m where
63+
class MonadIO m => LoggerMIO m where
6464
getLogger :: m (Logger LogMessage)
6565
default getLogger :: (Trans.MonadTrans t, LoggerMIO n, m ~ t n) => m (Logger LogMessage)
6666
getLogger = Trans.lift getLogger
@@ -78,7 +78,7 @@ instance LoggerMIO m => LoggerMIO (StateT s m) where
7878
instance LoggerMIO m => LoggerMIO (Strict.StateT s m) where
7979
withLogger modL (Strict.StateT m) = Strict.StateT $ \s -> withLogger modL $ m s
8080

81-
instance MonadIO m => LoggerMIO (NoLoggingT m) where
81+
instance MonadIO m => LoggerMIO (Control.Monad.Logger.NoLoggingT m) where
8282
getLogger = pure $ Logger $ \_ -> pure ()
8383
withLogger _ = id
8484

@@ -97,22 +97,25 @@ newtype TermCtxt = TermCtxt Int
9797

9898
instance ToLogFormat TermCtxt where
9999
toTextualLog (TermCtxt hsh) = "term " <> (showHashHex hsh)
100+
toJSONLog (TermCtxt hsh) = object ["term" .= showHashHex hsh]
101+
102+
newtype HookCtxt = HookCtxt Text
100103

101-
-- toJSONLog (TermCtxt hsh) = object [ "term" .= hsh ]
104+
instance ToLogFormat HookCtxt where
105+
toTextualLog (HookCtxt h) = "hook " <> h
106+
toJSONLog (HookCtxt h) = object ["hook" .= h]
102107

103108
instance ToLogFormat Term where
104109
toTextualLog t = renderOneLineText $ pretty t
105-
106-
-- toJSONLog t = toJSON $ externaliseTerm t
110+
toJSONLog t = toJSON $ addHeader $ externaliseTerm t
107111

108112
instance ToLogFormat Text where
109113
toTextualLog t = t
110-
111-
-- toJSONLog t = String t
114+
toJSONLog t = String t
112115

113116
withTermContext :: LoggerMIO m => Term -> m a -> m a
114117
withTermContext t@(Term attrs _) m = withContext (LogContext $ TermCtxt attrs.hash) $ do
115-
withContext "detail" $ logMessage t
118+
withContext "kore-term" $ logMessage t
116119
m
117120

118121
withPatternContext :: LoggerMIO m => Pattern -> m a -> m a
@@ -122,22 +125,29 @@ withPatternContext Pattern{term, constraints} m =
122125

123126
instance ToLogFormat KorePattern where
124127
toTextualLog = prettyPattern
128+
toJSONLog p = toJSON p
125129

126130
newtype KorePatternCtxt = KorePatternCtxt KorePattern
127131

128132
instance ToLogFormat KorePatternCtxt where
129133
toTextualLog (KorePatternCtxt t) = "term " <> (showHashHex $ Data.Hashable.hash $ prettyPattern t)
134+
toJSONLog (KorePatternCtxt t) = object ["term" .= (showHashHex $ Data.Hashable.hash $ prettyPattern t)]
130135

131136
instance KnownSymbol k => ToLogFormat (RewriteRule k) where
132137
toTextualLog RewriteRule{attributes} =
133138
LazyText.toStrict $
134139
(LazyText.toLower $ LazyText.pack $ symbolVal (Proxy :: Proxy k))
135140
<> " "
136141
<> maybe "UNKNOWN" (LazyText.take 7 . LazyText.fromStrict . coerce) attributes.uniqueId
142+
toJSONLog RewriteRule{attributes} =
143+
object
144+
[ (Key.fromText $ LazyText.toStrict $ LazyText.toLower $ LazyText.pack $ symbolVal (Proxy :: Proxy k))
145+
.= ((maybe "UNKNOWN" coerce attributes.uniqueId) :: Text)
146+
]
137147

138148
withKorePatternContext :: LoggerMIO m => KorePattern -> m a -> m a
139149
withKorePatternContext t m = withContext (LogContext $ KorePatternCtxt t) $ do
140-
withContext "detail" $ logMessage t
150+
withContext "kore-term" $ logMessage t
141151
m
142152

143153
withRuleContext :: KnownSymbol tag => LoggerMIO m => RewriteRule tag -> m a -> m a
@@ -149,16 +159,47 @@ withRuleContext rule m = withContext (LogContext rule) $ do
149159
loc -> loc
150160
m
151161

152-
newtype LoggerT m a = LoggerT {unLoggerT :: ReaderT (Logger LogMessage) m a}
153-
deriving newtype (Applicative, Functor, Monad, MonadIO, MonadLogger, MonadLoggerIO)
162+
data WithJsonMessage where
163+
WithJsonMessage :: ToLogFormat a => Value -> a -> WithJsonMessage
164+
165+
instance ToLogFormat WithJsonMessage where
166+
toTextualLog (WithJsonMessage _ a) = toTextualLog a
167+
toJSONLog (WithJsonMessage v _) = v
154168

155-
instance MonadLoggerIO m => LoggerMIO (LoggerT m) where
169+
newtype LoggerT m a = LoggerT {unLoggerT :: ReaderT (Logger LogMessage) m a}
170+
deriving newtype
171+
( Applicative
172+
, Functor
173+
, Monad
174+
, MonadIO
175+
, Control.Monad.Logger.MonadLogger
176+
, Control.Monad.Logger.MonadLoggerIO
177+
)
178+
179+
instance MonadIO m => LoggerMIO (LoggerT m) where
156180
getLogger = LoggerT ask
157181
withLogger modL (LoggerT m) = LoggerT $ withReaderT modL m
158182

159-
runLogger :: MonadLoggerIO m => LoggerT m a -> m a
160-
runLogger (LoggerT m) = do
161-
l <- askLoggerIO
162-
runReaderT m $ Logger $ \(LogMessage ctxts msg) ->
163-
let logLevel = mconcat $ intersperse "][" $ map (\(LogContext lc) -> toTextualLog lc) ctxts
164-
in l defaultLoc "" (LevelOther logLevel) $ toLogStr $ toTextualLog msg
183+
textLogger :: (Control.Monad.Logger.LogStr -> IO ()) -> Logger LogMessage
184+
textLogger l = Logger $ \(LogMessage ctxts msg) ->
185+
let logLevel = mconcat $ intersperse "][" $ map (\(LogContext lc) -> toTextualLog lc) ctxts
186+
in l $
187+
"["
188+
<> (Control.Monad.Logger.toLogStr logLevel)
189+
<> "] "
190+
<> (Control.Monad.Logger.toLogStr $ toTextualLog msg)
191+
<> "\n"
192+
193+
jsonLogger :: (Control.Monad.Logger.LogStr -> IO ()) -> Logger LogMessage
194+
jsonLogger l = Logger $ \(LogMessage ctxts msg) ->
195+
let ctxt = toJSON $ map (\(LogContext lc) -> toJSONLog lc) ctxts
196+
in liftIO $
197+
l $
198+
( Control.Monad.Logger.toLogStr $
199+
encodePretty' rpcJsonConfig{confIndent = Spaces 0} $
200+
object ["context" .= ctxt, "message" .= toJSONLog msg]
201+
)
202+
<> "\n"
203+
204+
filterLogger :: (LogMessage -> Bool) -> Logger LogMessage -> Logger LogMessage
205+
filterLogger p (Logger l) = Logger $ \m -> when (p m) $ l m
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
module Booster.Log.Context (ContextFilter, mustMatch, readContextFilter, readMatch) where
2+
3+
import Control.Applicative ((<|>))
4+
import Data.Attoparsec.ByteString.Char8 qualified as A
5+
import Data.ByteString.Char8 qualified as BS
6+
import Data.Char (isSpace)
7+
import Data.List.Extra (replace)
8+
9+
data ContextFilterSingle
10+
= Exact BS.ByteString
11+
| Prefix BS.ByteString
12+
| Suffix BS.ByteString
13+
| Infix BS.ByteString
14+
| Negate ContextFilterSingle
15+
deriving (Show)
16+
17+
data ContextFilter
18+
= First [ContextFilterSingle]
19+
| ThenDirectChild [ContextFilterSingle] ContextFilter
20+
| ThenChild [ContextFilterSingle] ContextFilter
21+
| Last [ContextFilterSingle]
22+
deriving (Show)
23+
24+
reserved :: String
25+
reserved = "|*!>,."
26+
27+
stringP :: A.Parser BS.ByteString
28+
stringP = A.takeWhile1 (not . (`elem` reserved))
29+
30+
singleP :: A.Parser ContextFilterSingle
31+
singleP =
32+
A.char '!' *> A.skipSpace *> (Negate <$> singleP)
33+
<|> A.char '*' *> (Infix <$> stringP) <* A.char '*'
34+
-- we want to allow * being parsed as `Suffix ""` so we allow the empty string via `takeWhile`
35+
<|> A.char '*' *> (Suffix . BS.dropWhileEnd isSpace <$> A.takeWhile (not . (`elem` reserved)))
36+
<|> Prefix . BS.dropWhile isSpace <$> stringP <* A.char '*'
37+
<|> Exact . BS.strip <$> stringP
38+
39+
orP :: A.Parser [ContextFilterSingle]
40+
orP = singleP `A.sepBy` (A.char '|')
41+
42+
contextFilterP :: A.Parser ContextFilter
43+
contextFilterP =
44+
A.skipSpace
45+
*> ( ThenChild <$> (orP <* A.skipSpace <* A.char '>') <*> contextFilterP
46+
<|> ThenDirectChild <$> (orP <* A.skipSpace <* A.char ',') <*> contextFilterP
47+
<|> Last <$> (orP <* A.skipSpace <* A.char '.')
48+
<|> First <$> orP
49+
)
50+
51+
readContextFilter :: String -> Either String ContextFilter
52+
readContextFilter =
53+
A.parseOnly (contextFilterP <* A.skipSpace <* A.endOfInput) . BS.pack . replace "\"" ""
54+
55+
matchSingle :: ContextFilterSingle -> BS.ByteString -> Bool
56+
matchSingle (Exact c) s = c == s
57+
matchSingle (Prefix c) s = BS.isPrefixOf c s
58+
matchSingle (Suffix c) s = BS.isSuffixOf c s
59+
matchSingle (Infix c) s = BS.isInfixOf c s
60+
matchSingle (Negate c) s = not $ matchSingle c s
61+
62+
mustMatch :: ContextFilter -> [BS.ByteString] -> Bool
63+
mustMatch (First cs) [] = any (flip matchSingle "") cs
64+
mustMatch (First cs) (x : _) = any (flip matchSingle x) cs
65+
mustMatch (Last cs) [x] = any (flip matchSingle x) cs
66+
mustMatch Last{} _ = False
67+
mustMatch (_ `ThenDirectChild` _) [] = False
68+
mustMatch (cs `ThenDirectChild` css) (x : xs) =
69+
any (flip matchSingle x) cs && mustMatch css xs
70+
mustMatch (_ `ThenChild` _) [] = False
71+
mustMatch (cs `ThenChild` css) (x : xs) =
72+
any (flip matchSingle x) cs && mayMatch css xs
73+
74+
mayMatch :: ContextFilter -> [BS.ByteString] -> Bool
75+
mayMatch c [] = mustMatch c []
76+
mayMatch c (x : xs) = mustMatch c (x : xs) || mayMatch c xs
77+
78+
readMatch :: BS.ByteString -> [BS.ByteString] -> Either String Bool
79+
readMatch pat' xs = do
80+
pat <- A.parseOnly (contextFilterP <* A.skipSpace <* A.endOfInput) pat'
81+
pure $ mustMatch pat xs

0 commit comments

Comments
 (0)