|
1 | 1 | {-# LANGUAGE OverloadedRecordDot #-}
|
2 | 2 |
|
| 3 | +-- | Parse hydra-node logs format more easy on the eyes. Parser works with regular json logs as well as journalctl format. |
3 | 4 | module Main where
|
4 | 5 |
|
5 | 6 | import Hydra.Cardano.Api (Tx)
|
6 |
| -import Hydra.Prelude hiding (encodeUtf8) |
7 |
| -import Hydra.Prelude qualified as P |
| 7 | +import Hydra.Prelude hiding (encodeUtf8, takeWhile) |
8 | 8 |
|
9 | 9 | import Conduit
|
10 | 10 | import Control.Lens ((^?))
|
11 | 11 | import Control.Monad (foldM)
|
12 | 12 | import Data.Aeson (eitherDecode')
|
13 | 13 | import Data.Aeson.Lens (key, _String)
|
| 14 | +import Data.Attoparsec.ByteString |
| 15 | +import Data.Attoparsec.ByteString qualified as A |
| 16 | +import Data.Attoparsec.ByteString.Char8 (char8, endOfLine, isEndOfLine) |
14 | 17 | import Data.Text.Encoding (encodeUtf8)
|
15 | 18 | import Hydra.Chain (ChainEvent (..))
|
16 | 19 | import Hydra.HeadLogic (Effect (..), Input (..), Outcome (..), StateChanged (..))
|
17 | 20 | import Hydra.Logging (Envelope (..))
|
18 | 21 | import Hydra.Logging.Messages (HydraLog (..))
|
19 | 22 | import Hydra.Node (HydraNodeLog (..))
|
20 |
| -import Options.Applicative |
| 23 | +import Options.Applicative hiding (Parser) |
| 24 | +import Options.Applicative qualified as Options |
21 | 25 |
|
22 | 26 | data InfoLine = InfoLine {toplabel :: LogType, details :: Text} deriving (Eq, Show)
|
23 | 27 |
|
@@ -79,7 +83,7 @@ newtype Options = Options
|
79 | 83 | }
|
80 | 84 | deriving (Show)
|
81 | 85 |
|
82 |
| -options :: Parser Options |
| 86 | +options :: Options.Parser Options |
83 | 87 | options =
|
84 | 88 | Options
|
85 | 89 | <$> many
|
@@ -117,13 +121,43 @@ visualize paths = do
|
117 | 121 |
|
118 | 122 | decodeAndProcess :: ByteString -> ResourceT IO (Decoded Tx)
|
119 | 123 | decodeAndProcess l =
|
120 |
| - case l ^? key "message" . _String of |
121 |
| - Nothing -> P.error "Failed to find key 'message' which was expected" |
122 |
| - Just line -> |
123 |
| - let envelope = fromStrict $ encodeUtf8 line |
124 |
| - in case eitherDecode' envelope :: Either String (Envelope (HydraLog Tx)) of |
125 |
| - Left e -> P.error $ "Decoding failed" <> show e <> "for line: " <> line |
126 |
| - Right decoded -> lift $ processLogs decoded |
| 124 | + case inCurlyBraces l of |
| 125 | + Left _ -> lift $ pure DropLog |
| 126 | + Right incomingLine -> |
| 127 | + case incomingLine of |
| 128 | + Nothing -> lift $ pure DropLog |
| 129 | + Just jsonLine -> |
| 130 | + case jsonLine ^? key "message" . _String of |
| 131 | + Nothing -> process jsonLine |
| 132 | + Just line -> process $ encodeUtf8 line |
| 133 | + where |
| 134 | + process :: ByteString -> ResourceT IO (Decoded Tx) |
| 135 | + process line = |
| 136 | + case eitherDecode' (fromStrict line) of |
| 137 | + Left e -> do |
| 138 | + putTextLn $ red <> "Decoding failed for line: " <> decodeUtf8 l <> "\nError: " <> show e |
| 139 | + pure DropLog |
| 140 | + Right decoded -> lift $ processLogs decoded |
| 141 | + |
| 142 | +charToWord8 :: Char -> Word8 |
| 143 | +charToWord8 = fromIntegral . ord |
| 144 | + |
| 145 | +textBetweenBraces :: A.Parser ByteString |
| 146 | +textBetweenBraces = do |
| 147 | + skipWhile (/= charToWord8 '{') |
| 148 | + _ <- char8 '{' |
| 149 | + jsonStr <- takeWhile (not . isEndOfLine) |
| 150 | + pure $ "{" <> jsonStr |
| 151 | + |
| 152 | +lineParser :: A.Parser (Maybe ByteString) |
| 153 | +lineParser = do |
| 154 | + result <- optional textBetweenBraces |
| 155 | + skipWhile (not . isEndOfLine) |
| 156 | + endOfLine <|> endOfInput |
| 157 | + return result |
| 158 | + |
| 159 | +inCurlyBraces :: ByteString -> Either String (Maybe ByteString) |
| 160 | +inCurlyBraces = parseOnly lineParser |
127 | 161 |
|
128 | 162 | -- | Ideally we would have Data instances for all types so we could get data type string representation
|
129 | 163 | -- instead of providing strings directly but that would add some compilation time overhead so not worth it.
|
|
0 commit comments