logplex-parse (empty) → 0.1.0.0
raw patch · 6 files changed
+270/−0 lines, 6 filesdep +basedep +hspecdep +iso8601-timesetup-changed
Dependencies added: base, hspec, iso8601-time, logplex-parse, parsec, text, time
Files
- LICENSE +20/−0
- Setup.hs +2/−0
- logplex-parse.cabal +89/−0
- spec/Spec.hs +1/−0
- src/Text/Logplex/Parser.hs +35/−0
- src/Text/Syslog/Parser.hs +123/−0
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2015 Keith Duncan++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ logplex-parse.cabal view
@@ -0,0 +1,89 @@+-- Initial logplex-parse.cabal generated by cabal init. For further+-- documentation, see http://haskell.org/cabal/users-guide/++-- The name of the package.+name: logplex-parse++-- The package version. See the Haskell package versioning policy (PVP)+-- for standards guiding when and how versions should be incremented.+-- http://www.haskell.org/haskellwiki/Package_versioning_policy+-- PVP summary: +-+------- breaking API changes+-- | | +----- non-breaking API additions+-- | | | +--- code changes with no API change+version: 0.1.0.0++-- A short (one-line) description of the package.+synopsis: Parse Heroku application/logplex documents++-- A longer description of the package.+-- description:++-- URL for the project homepage or repository.+homepage: https://github.com/keithduncan/logplex-parse++-- The license under which the package is released.+license: MIT++-- The file containing the license text.+license-file: LICENSE++-- The package author(s).+author: Keith Duncan++-- An email address to which users can send suggestions, bug reports, and+-- patches.+maintainer: keith.duncan@github.com++-- A copyright notice.+-- copyright:++category: Text++build-type: Simple++-- Extra files to be distributed with the package, such as examples or a+-- README.+-- extra-source-files:++-- Constraint on the version of Cabal needed to build this package.+cabal-version: >=1.10++source-repository head+ type: git+ location: https://github.com/keithduncan/logplex-parse++library+ -- Modules exported by the library.+ exposed-modules: Text.Logplex.Parser,+ Text.Syslog.Parser++ -- Modules included in this library but not exported.+ -- other-modules:++ -- LANGUAGE extensions used by modules in this package.+ -- other-extensions:++ -- Other library packages from which modules are imported.+ build-depends: base >=4.8 && <4.9,+ parsec >= 3.1.9 && <3.2,+ text >= 1.2.1.3 && <1.3,+ iso8601-time >= 0.1.4 && <0.2,+ time >= 1.5.0.1 && <1.6++ -- Directories containing source files.+ hs-source-dirs: src++ -- Base language which the package is written in.+ default-language: Haskell2010++test-suite tests+ ghc-options: -Wall+ default-extensions: OverloadedStrings+ type: exitcode-stdio-1.0+ hs-source-dirs: spec+ main-is: Spec.hs+ build-depends: base,+ logplex-parse,+ hspec >= 1.8,+ time >= 1.5.0.1 && <1.6+ default-language: Haskell2010
+ spec/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ src/Text/Logplex/Parser.hs view
@@ -0,0 +1,35 @@+module Text.Logplex.Parser (+ parseLogplex,+) where++import Control.Monad+import Data.Maybe++import Text.ParserCombinators.Parsec+import Text.ParserCombinators.Parsec.Error++import Text.Syslog.Parser++parseLogplex :: String -> Either ParseError [LogEntry]+parseLogplex = parse logplexDocument "(unknown)"++logplexDocument :: GenParser Char st [LogEntry]+logplexDocument = many frame <* eof++{-+ <https://tools.ietf.org/html/rfc6587#section-3.4.1>+-}++frame :: GenParser Char st LogEntry+frame = do+ len <- read <$> msgLen+ space+ frameContent <- replicateM len anyChar++ case parseSyslog frameContent of+ -- should probably include all the errors?+ Left err -> fail . head $ messageString <$> errorMessages err+ Right le -> return le++msgLen :: GenParser Char st String+msgLen = liftM2 (:) nonZeroDigit (many digit)
+ src/Text/Syslog/Parser.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE FlexibleContexts #-}++module Text.Syslog.Parser (+ parseSyslog,++ LogEntry(..),++ StructuredData(..),+ Key,+ Value,+ Param,++ nonZeroDigit,+) where++import Control.Monad+import Data.Maybe++import Text.ParserCombinators.Parsec++import Data.Time.ISO8601+import Data.Time.Clock+import Text.Printf++data LogEntry = LogEntry { getPriority :: String+ , getVersion :: String+ , getTimestamp :: UTCTime+ , getHostname :: String+ , getAppname :: String+ , getProcessId :: String+ , getMessageId :: String+ , getStructuredData :: [StructuredData]+ , getMessage :: Maybe String+ } deriving (Show, Eq)++data StructuredData = StructuredData { getId :: String+ , getParams :: [Param]+ } deriving (Show, Eq)++type Key = String+type Value = String+type Param = (Key, Value)++parseSyslog :: String -> Either ParseError LogEntry+parseSyslog = parse syslogLine "(unknown)"++syslogLine = LogEntry <$>+ pri <*>+ version <*>+ (space >> timestamp) <*>+ (space >> hostname) <*>+ (space >> appName) <*>+ (space >> procid) <*>+ (space >> msgid) <*>+ (space >> structuredData) <*>+ optionMaybe (space >> message) <*+ eof++pri = between (char '<') (char '>') (countBetween 1 3 digit) <?> "priority value"+nonZeroDigit = oneOf "123456789"+version = liftM2 (:) nonZeroDigit (countBetween 0 2 digit) <?> "syslog protocol version"++timestamp = do+ time <- (nilvalue >> return "") <|> (mconcat <$> sequence [fullDate, string "T", fullTime]) <?> "ISO8601 full date time timestamp with timezone"+ case parseISO8601 time of+ Nothing -> fail $ printf "invalid timestamp `%s`" time+ Just t -> return t++nilvalue = string "-"++fullDate = mconcat <$> sequence [count 4 digit, string "-", count 2 digit, string "-", count 2 digit]++fullTime = liftM2 (++) partialTime timeOffset++partialTime = mconcat <$> sequence [time, string ":", second, fromMaybe "" <$> optionMaybe (liftM2 (++) (string ".") (countBetween 1 6 digit))]++timeHour = count 2 digit+timeMinute = count 2 digit+second = count 2 digit+timeOffset = string "Z" <|> timeNumOffset+timeNumOffset = liftM2 (:) (oneOf "+-") time+time = mconcat <$> sequence [timeHour, string ":", timeMinute]++hostname = nilvalue <|> countBetween 1 255 printascii <?> "hostname"++printascii = oneOf printasciiSet+printasciiSet = toEnum <$> [33..126] :: String++appName = nilvalue <|> countBetween 1 48 printascii <?> "application name"+procid = nilvalue <|> countBetween 1 128 printascii <?> "process id"+msgid = nilvalue <|> countBetween 1 32 printascii <?> "message id"++structuredData = (nilvalue >> return []) <|> many1 sdElement++sdElement = between (char '[') (char ']') (liftM2 StructuredData sdId (many (space >> sdParam)))++sdId = sdName++sdName = countBetween 1 32 (oneOf (filter (`notElem` "= ]\"") printasciiSet))++sdParam = liftM2 (,) paramName (string "=" >> quoted (many1 $ escaped '\\' "\"]"))++quoted = between doubleQuote doubleQuote+doubleQuote = char '"'++paramName = sdName++escaped echar chars = let echars = echar:chars+ in noneOf echars <|> choice ((try . (char echar >>) . char) <$> echars)++message = msgUtf8 <|> msgAny <?> "message"++msgUtf8 = try (bom >> utf8String)+utf8String = many anyChar+bom = string ['\xEF', '\xBB', '\xBF']++msgAny = utf8String++{-# CONTRACT countBetween :: { x y | x < y } -> Ok #-}+countBetween min' max' parser+ | min' > max' = error "min occurences cannot be greater than max occurrences"+ | min' == max' = count max' parser+ | otherwise = try (count max' parser) <|> countBetween min' (max'-1) parser