Skip to content

Instantly share code, notes, and snippets.

@NeQuissimus
Last active June 15, 2016 13:04
Show Gist options
  • Save NeQuissimus/d682c8183c7b686b1c07 to your computer and use it in GitHub Desktop.
Save NeQuissimus/d682c8183c7b686b1c07 to your computer and use it in GitHub Desktop.
Playing with parboiled2 to parse router logs
import org.parboiled2._
import scala.util.{ Success, Failure }
import java.net.{ InetAddress, Inet4Address }
import java.time.{ LocalDateTime => LDT }
import java.time.format.{ DateTimeFormatter => DTF }
object Main extends App {
import Logs._
List(l1, l2, l3, l4, l5) foreach { l =>
println(l)
val p = LogParser(l)
p.InputLine.run() match {
case Success(v) => println(s"Expression output: $v")
case Failure(e: ParseError) => println(s"Expression invalid: ${p.formatError(e)} || Stack: ${p.valueStack}")
case Failure(e) => println(s"Error: $e")
}
println()
}
}
object Logs {
val l1 = "Jan 1 00:13:44 10.30.26.1 Got new client [18:B4:30:23:AA:67] associated from BAND24G-1.1 (2.4 Ghz)"
val l2 = "Jan 11 00:13:53 10.30.26.2 DHCP: Server receive DISCOVER from 18:b4:30:23:aa:67."
val l3 = "Jan 1 22:13:53 10.30.26.3 DHCP: Server sending OFFER of 10.30.26.115 for static DHCP client."
val l4 = "Jan 3 18:13:53 10.30.26.4 DHCP: Server receive REQUEST from 18:b4:30:23:aa:67."
val l5 = "Feb 22 12:13:53 10.30.26.5 DHCP: Server sending ACK to 10.30.26.115. (Lease time = -1)"
}
case class LogParser(val input: ParserInput) extends Parser {
def InputLine = rule { DateMonth3NoYear ~ Whitespace ~ HHMMSS ~ Whitespace ~ IPv4 ~ Whitespace ~ ((
"DHCP: Server " ~ (
"receive " ~ (
"DISCOVER from " ~ MAC ~ "." ~ EOI ~> os.linux.dhcp.Discover
| "REQUEST from " ~ MAC ~ "." ~ EOI ~> os.linux.dhcp.Request
)
| "sending " ~ (
"OFFER of " ~ IPv4 ~ " for static DHCP client." ~ EOI ~> os.linux.dhcp.Offer
| "ACK to " ~ IPv4 ~ ". (Lease time = " ~ Number ~ ")" ~ EOI ~> os.linux.dhcp.Ack
)
))
| (Anything ~ EOI ~> os.linux.UnknownMessage)
)}
def DateMonth3NoYear = rule { capture(Month3 ~ Whitespace ~ DayOfMonth) }
def Month3 = rule { "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec" }
def DayOfMonth = rule { Digits ~> (i => test(i >= 1 && i <= 31)) }
def ZeroTo23 = rule { Digits ~> (i => test(i >= 0 && i <= 23)) }
def ZeroTo59 = rule { Digits ~> (i => test(i >= 0 && i <= 59)) }
def HHMMSS = rule { capture(ZeroTo23 ~ ':' ~ ZeroTo59 ~ ':' ~ ZeroTo59) }
def IPv4 = rule { capture(4.times(IPv4Octet).separatedBy('.')) }
def IPv4Octet = rule { Digits ~> (i => test(i >= 0 && i <= 255)) }
def MAC = rule { capture(6.times(2.times(CharPredicate.HexDigit)).separatedBy(':')) }
def Number = rule { capture(optional('-') ~ DigitsStr) }
def Digit = rule { capture(CharPredicate.Digit) ~> (_.toInt) }
def Digits = rule { capture(DigitsStr) ~> (_.toInt) }
def DigitsStr = rule { oneOrMore(CharPredicate.Digit) }
def Whitespace = rule { zeroOrMore(' ') }
def Anything = rule { capture(zeroOrMore(CharPredicate.Printable)) }
}
trait ParsedDate { self: {val dateStr: String; val timeStr: String} =>
lazy val date = LDT.parse("2014 " + dateStr + ' ' + timeStr, DTF.ofPattern("yyyy MMM [ ]d HH:mm:ss"))
}
trait ParsedIp { self: {val ipStr: String} =>
lazy val ip: InetAddress = InetAddress.getByName(ipStr)
}
package os.linux.dhcp {
case class Ack(dateStr: String, timeStr: String, ipStr: String, targetIpStr: String, leaseStr: String) extends ParsedDate with ParsedIp {
lazy val targetIp = InetAddress.getByName(targetIpStr)
lazy val lease = leaseStr.toLong
override def toString = s"dhcp.Ack[$date, $ip, $targetIp, $lease]"
}
case class Discover(dateStr: String, timeStr: String, ipStr: String, macStr: String) extends ParsedDate with ParsedIp {
override def toString = s"dhcp.Discover[$date, $ip, $macStr]"
}
case class Offer(dateStr: String, timeStr: String, ipStr: String, offeredIpStr: String) extends ParsedDate with ParsedIp {
lazy val offeredIp = InetAddress.getByName(offeredIpStr)
override def toString = s"dhcp.Offer[$date, $ip, $offeredIp]"
}
case class Request(dateStr: String, timeStr: String, ipStr: String, macStr: String) extends ParsedDate with ParsedIp {
override def toString = s"dhcp.Request[$date, $ip, $macStr]"
}
}
package os.linux {
case class UnknownMessage(dateStr: String, timeStr: String, ipStr: String, rest: String) extends ParsedDate with ParsedIp {
override def toString = s"UnknownMessage[$date, $ip, $rest]"
}
}
@NeQuissimus
Copy link
Author

[info] Running Main
Jan 1 00:13:44 10.30.26.1 Got new client [18:B4:30:23:AA:67] associated from BAND24G-1.1 (2.4 Ghz)
Expression output: UnknownMessage[2014-01-01T00:13:44, /10.30.26.1, Got new client [18:B4:30:23:AA:67] associated from BAND24G-1.1 (2.4 Ghz)]

Jan 11 00:13:53 10.30.26.2 DHCP: Server receive DISCOVER from 18:b4:30:23:aa:67.
Expression output: dhcp.Discover[2014-01-11T00:13:53, /10.30.26.2, 18:b4:30:23:aa:67]

Jan 1 22:13:53 10.30.26.3 DHCP: Server sending OFFER of 10.30.26.115 for static DHCP client.
Expression output: dhcp.Offer[2014-01-01T22:13:53, /10.30.26.3, /10.30.26.115]

Jan 3 18:13:53 10.30.26.4 DHCP: Server receive REQUEST from 18:b4:30:23:aa:67.
Expression output: dhcp.Request[2014-01-03T18:13:53, /10.30.26.4, 18:b4:30:23:aa:67]

Feb 22 12:13:53 10.30.26.5 DHCP: Server sending ACK to 10.30.26.115. (Lease time = -1)
Expression output: dhcp.Ack[2014-02-22T12:13:53, /10.30.26.5, /10.30.26.115, -1]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment