1

しばらく前に書いたログ ファイル パーサーをコンジットに変換しようとしていますが、問題が発生しています。質問とは関係ないので、パーサー自体の詳細を単純化します。次のようなログ ファイルがあります。

200 GET
404 POST
500 GET
FOO
301 PUT
302 GET
201 POST

したがって、解析コードは非常に簡単です。

data SimpleLogEntry = SimpleLogEntry {
      status :: Int
    , method :: String
} deriving (Show, Eq)


parseHTTPStatus :: Parser Int
parseHTTPStatus = validate <$> decimal
    where validate d = if (d >= 200 && d < 999) then d else 100


parseHTTPMethod :: Parser String
parseHTTPMethod =
        (stringCI "GET" *> return "Get")
    <|> (stringCI "POST" *> return "Post")
    <|> (stringCI "PUT" *> return "Put")
    <|> return "Unknown"


parseLogLine :: Parser SimpleLogEntry
parseLogLine = fmap SimpleLogEntry
        parseHTTPStatus
    <*> (space *> parseHTTPMethod)

ここまでは順調ですね。コンジットでこれを実装する方法は次のとおりです。

import Prelude hiding (lines)

import Control.Applicative
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Resource (runResourceT, ResourceT)
import Data.Attoparsec.ByteString.Char8
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import Data.Conduit
import qualified Data.Conduit.Attoparsec as CA
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.List as CL


logLines:: Source (ResourceT IO) B.ByteString
logLines = CB.sourceFile "~/test.log" $= CB.lines


parseEntry :: ConduitM B8.ByteString SimpleLogEntry (ResourceT IO) ()
parseEntry = CA.conduitParserEither parseLogLine =$= awaitForever go
    where
        go (Left err) = liftIO $ putStrLn ("Got an error: " ++ CA.errorMessage err)
        go (Right (_, logEntry)) = yield logEntry


sink :: Sink SimpleLogEntry (ResourceT IO) ()
sink = CL.mapM_ (\t -> liftIO $ putStrLn $ "Got a status: " ++ (show . status) t)


main :: IO ()
main = runResourceT $ logLines $= parseEntry $$ sink

実行するmainと、次の出力が得られます。

Got a status: 200
Got a status: 404
Got a status: 500
Got an error: Failed reading: takeWhile1

私がやりたいように、ファイルの次の行を解析し続けるのではなく、パイプラインがこの時点で終了する理由を理解するのに苦労しています。のドキュメントを読むとData.Conduit.Attoparsec、これはまさにユースケースconduitParserEitherが設計されたようです。

アップデート

@Fabian によると、それはconduitParserEither私がここで望んでいたものではなかったことがわかりました。parseEntryこれが私がやりたかったことの定義です:

parseEntry' :: ConduitM B8.ByteString SimpleLogEntry (ResourceT IO) ()
parseEntry' = (CL.map (parseOnly parseLogLine)) =$= awaitForever go
    where
        go (Left err) = liftIO $ putStrLn ("Got an error: " ++ err)
        go (Right logEntry) = yield logEntry
4

1 に答える 1

0

conduitParser(または) は、1 行で複数のトークンを消費することもできます。conduitParserEitherたとえば、次の入力は同じ結果を生成します。

200 GET404 POST
500 GET
FOO
301 PUT
302 GET
201 POST

したがって、次のトークンがどこから始まるかがわからないため、パーサーが続行しないのは理にかなっています。

于 2015-12-09T19:09:29.893 に答える