pax_global_header00006660000000000000000000000064125521021700014505gustar00rootroot0000000000000052 comment=ff71fe7a7d5279df4b964b31f7ee4adf117277f6 syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/000077500000000000000000000000001255210217000214405ustar00rootroot00000000000000syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/.gitignore000066400000000000000000000000071255210217000234250ustar00rootroot00000000000000*.test syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/LICENSE000066400000000000000000000024211255210217000224440ustar00rootroot00000000000000Copyright (c) 2013, Jérôme Renard All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/Makefile000066400000000000000000000010641255210217000231010ustar00rootroot00000000000000SUBPACKAGES=. rfc3164 rfc5424 help: @echo "Available targets:" @echo "- tests: run tests" @echo "- installdependencies: installs dependencies declared in dependencies.txt" @echo "- clean: cleans directory" @echo "- benchmarks: run benchmarks" installdependencies: @cat dependencies.txt | xargs go get tests: installdependencies @for pkg in $(SUBPACKAGES); do cd $$pkg && go test -i && go test ; cd -;done clean: find . -type 'f' -name '*.test' -print | xargs rm -f benchmarks: @for pkg in $(SUBPACKAGES); do cd $$pkg && go test -gocheck.b ; cd -;done syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/README000066400000000000000000000040211255210217000223150ustar00rootroot00000000000000Syslogparser ============ This is a syslog parser for the Go programming language. Installing ---------- go get github.com/jeromer/syslogparser Supported RFCs -------------- RFC 3164 : https://tools.ietf.org/html/rfc3164 RFC 5424 : https://tools.ietf.org/html/rfc5424 Not all features described in RFCs above are supported but only the most part of it. For exaple SDIDs are not supported in RFC5424 and STRUCTURED-DATA are parsed as a whole string. This parser should solve 80% of use cases. If your use cases are in the 20% remaining ones I would recommend you to fully test what you want to achieve and provide a patch if you want. Parsing an RFC 3164 syslog message ---------------------------------- b := "<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8" buff := []byte(b) p := rfc3164.NewParser(buff) err := p.Parse() if err != nil { panic(err) } for k, v := range p.Dump() { fmt.Println(k, ":", v) } You should see timestamp : 2013-10-11 22:14:15 +0000 UTC hostname : mymachine tag : su content : 'su root' failed for lonvick on /dev/pts/8 priority : 34 facility : 4 severity : 2 Parsing an RFC 5424 syslog message ---------------------------------- b := `<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] An application event log entry...` buff := []byte(b) p := rfc5424.NewParser(buff) err := p.Parse() if err != nil { panic(err) } for k, v := range p.Dump() { fmt.Println(k, ":", v) } You should see version : 1 timestamp : 2003-10-11 22:14:15.003 +0000 UTC app_name : evntslog msg_id : ID47 message : An application event log entry... priority : 165 facility : 20 severity : 5 hostname : mymachine.example.com proc_id : - structured_data : [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] Running tests ------------- make tests Running benchmarks ------------------ make benchmarks syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/dependencies.txt000066400000000000000000000000261255210217000246250ustar00rootroot00000000000000launchpad.net/gocheck syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/rfc3164/000077500000000000000000000000001255210217000225305ustar00rootroot00000000000000syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/rfc3164/example_test.go000066400000000000000000000005021255210217000255460ustar00rootroot00000000000000package rfc3164_test import ( "fmt" "github.com/jeromer/syslogparser/rfc3164" ) func ExampleNewParser() { b := "<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8" buff := []byte(b) p := rfc3164.NewParser(buff) err := p.Parse() if err != nil { panic(err) } fmt.Println(p.Dump()) } syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/rfc3164/rfc3164.go000066400000000000000000000101411255210217000241440ustar00rootroot00000000000000package rfc3164 import ( "bytes" "github.com/jeromer/syslogparser" "time" ) type Parser struct { buff []byte cursor int l int priority syslogparser.Priority version int header header message rfc3164message location *time.Location } type header struct { timestamp time.Time hostname string } type rfc3164message struct { tag string content string } func NewParser(buff []byte) *Parser { return &Parser{ buff: buff, cursor: 0, l: len(buff), location: time.UTC, } } func (p *Parser) Location(location *time.Location) { p.location = location } func (p *Parser) Parse() error { pri, err := p.parsePriority() if err != nil { return err } hdr, err := p.parseHeader() if err != nil { return err } p.cursor++ msg, err := p.parsemessage() if err != syslogparser.ErrEOL { return err } p.priority = pri p.version = syslogparser.NO_VERSION p.header = hdr p.message = msg return nil } func (p *Parser) Dump() syslogparser.LogParts { return syslogparser.LogParts{ "timestamp": p.header.timestamp, "hostname": p.header.hostname, "tag": p.message.tag, "content": p.message.content, "priority": p.priority.P, "facility": p.priority.F.Value, "severity": p.priority.S.Value, } } func (p *Parser) parsePriority() (syslogparser.Priority, error) { return syslogparser.ParsePriority(p.buff, &p.cursor, p.l) } func (p *Parser) parseHeader() (header, error) { hdr := header{} var err error ts, err := p.parseTimestamp() if err != nil { return hdr, err } hostname, err := p.parseHostname() if err != nil { return hdr, err } hdr.timestamp = ts hdr.hostname = hostname return hdr, nil } func (p *Parser) parsemessage() (rfc3164message, error) { msg := rfc3164message{} var err error tag, err := p.parseTag() if err != nil { return msg, err } content, err := p.parseContent() if err != syslogparser.ErrEOL { return msg, err } msg.tag = tag msg.content = content return msg, err } // https://tools.ietf.org/html/rfc3164#section-4.1.2 func (p *Parser) parseTimestamp() (time.Time, error) { var ts time.Time var err error var tsFmtLen int var sub []byte tsFmts := []string{ "Jan 02 15:04:05", "Jan 2 15:04:05", } found := false for _, tsFmt := range tsFmts { tsFmtLen = len(tsFmt) if p.cursor+tsFmtLen > p.l { continue } sub = p.buff[p.cursor : tsFmtLen+p.cursor] ts, err = time.ParseInLocation(tsFmt, string(sub), p.location) if err == nil { found = true break } } if !found { p.cursor = tsFmtLen // XXX : If the timestamp is invalid we try to push the cursor one byte // XXX : further, in case it is a space if (p.cursor < p.l) && (p.buff[p.cursor] == ' ') { p.cursor++ } return ts, syslogparser.ErrTimestampUnknownFormat } fixTimestampIfNeeded(&ts) p.cursor += tsFmtLen if (p.cursor < p.l) && (p.buff[p.cursor] == ' ') { p.cursor++ } return ts, nil } func (p *Parser) parseHostname() (string, error) { return syslogparser.ParseHostname(p.buff, &p.cursor, p.l) } // http://tools.ietf.org/html/rfc3164#section-4.1.3 func (p *Parser) parseTag() (string, error) { var b byte var endOfTag bool var bracketOpen bool var tag []byte var err error var found bool from := p.cursor for { b = p.buff[p.cursor] bracketOpen = (b == '[') endOfTag = (b == ':' || b == ' ') // XXX : parse PID ? if bracketOpen { tag = p.buff[from:p.cursor] found = true } if endOfTag { if !found { tag = p.buff[from:p.cursor] found = true } p.cursor++ break } p.cursor++ } if (p.cursor < p.l) && (p.buff[p.cursor] == ' ') { p.cursor++ } return string(tag), err } func (p *Parser) parseContent() (string, error) { if p.cursor > p.l { return "", syslogparser.ErrEOL } content := bytes.Trim(p.buff[p.cursor:p.l], " ") p.cursor += len(content) return string(content), syslogparser.ErrEOL } func fixTimestampIfNeeded(ts *time.Time) { now := time.Now() y := ts.Year() if ts.Year() == 0 { y = now.Year() } newTs := time.Date(y, ts.Month(), ts.Day(), ts.Hour(), ts.Minute(), ts.Second(), ts.Nanosecond(), ts.Location()) *ts = newTs } syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/rfc3164/rfc3164_test.go000066400000000000000000000140031255210217000252040ustar00rootroot00000000000000package rfc3164 import ( "bytes" "github.com/jeromer/syslogparser" . "launchpad.net/gocheck" "testing" "time" ) // Hooks up gocheck into the gotest runner. func Test(t *testing.T) { TestingT(t) } type Rfc3164TestSuite struct { } var ( _ = Suite(&Rfc3164TestSuite{}) // XXX : corresponds to the length of the last tried timestamp format // XXX : Jan 2 15:04:05 lastTriedTimestampLen = 15 ) func (s *Rfc3164TestSuite) TestParser_Valid(c *C) { buff := []byte("<34>Oct 11 22:14:15 mymachine very.large.syslog.message.tag: 'su root' failed for lonvick on /dev/pts/8") p := NewParser(buff) expectedP := &Parser{ buff: buff, cursor: 0, l: len(buff), } c.Assert(p, DeepEquals, expectedP) err := p.Parse() c.Assert(err, IsNil) now := time.Now() obtained := p.Dump() expected := syslogparser.LogParts{ "timestamp": time.Date(now.Year(), time.October, 11, 22, 14, 15, 0, time.UTC), "hostname": "mymachine", "tag": "very.large.syslog.message.tag", "content": "'su root' failed for lonvick on /dev/pts/8", "priority": 34, "facility": 4, "severity": 2, } c.Assert(obtained, DeepEquals, expected) } func (s *Rfc3164TestSuite) TestParseHeader_Valid(c *C) { buff := []byte("Oct 11 22:14:15 mymachine ") now := time.Now() hdr := header{ timestamp: time.Date(now.Year(), time.October, 11, 22, 14, 15, 0, time.UTC), hostname: "mymachine", } s.assertRfc3164Header(c, hdr, buff, 25, nil) } func (s *Rfc3164TestSuite) TestParseHeader_InvalidTimestamp(c *C) { buff := []byte("Oct 34 32:72:82 mymachine ") hdr := header{} s.assertRfc3164Header(c, hdr, buff, lastTriedTimestampLen+1, syslogparser.ErrTimestampUnknownFormat) } func (s *Rfc3164TestSuite) TestParsemessage_Valid(c *C) { content := "foo bar baz blah quux" buff := []byte("sometag[123]: " + content) hdr := rfc3164message{ tag: "sometag", content: content, } s.assertRfc3164message(c, hdr, buff, len(buff), syslogparser.ErrEOL) } func (s *Rfc3164TestSuite) TestParseTimestamp_Invalid(c *C) { buff := []byte("Oct 34 32:72:82") ts := new(time.Time) s.assertTimestamp(c, *ts, buff, lastTriedTimestampLen, syslogparser.ErrTimestampUnknownFormat) } func (s *Rfc3164TestSuite) TestParseTimestamp_TrailingSpace(c *C) { // XXX : no year specified. Assumed current year // XXX : no timezone specified. Assume UTC buff := []byte("Oct 11 22:14:15 ") now := time.Now() ts := time.Date(now.Year(), time.October, 11, 22, 14, 15, 0, time.UTC) s.assertTimestamp(c, ts, buff, len(buff), nil) } func (s *Rfc3164TestSuite) TestParseTimestamp_OneDigitForMonths(c *C) { // XXX : no year specified. Assumed current year // XXX : no timezone specified. Assume UTC buff := []byte("Oct 1 22:14:15") now := time.Now() ts := time.Date(now.Year(), time.October, 1, 22, 14, 15, 0, time.UTC) s.assertTimestamp(c, ts, buff, len(buff), nil) } func (s *Rfc3164TestSuite) TestParseTimestamp_Valid(c *C) { // XXX : no year specified. Assumed current year // XXX : no timezone specified. Assume UTC buff := []byte("Oct 11 22:14:15") now := time.Now() ts := time.Date(now.Year(), time.October, 11, 22, 14, 15, 0, time.UTC) s.assertTimestamp(c, ts, buff, len(buff), nil) } func (s *Rfc3164TestSuite) TestParseTag_Pid(c *C) { buff := []byte("apache2[10]:") tag := "apache2" s.assertTag(c, tag, buff, len(buff), nil) } func (s *Rfc3164TestSuite) TestParseTag_NoPid(c *C) { buff := []byte("apache2:") tag := "apache2" s.assertTag(c, tag, buff, len(buff), nil) } func (s *Rfc3164TestSuite) TestParseTag_TrailingSpace(c *C) { buff := []byte("apache2: ") tag := "apache2" s.assertTag(c, tag, buff, len(buff), nil) } func (s *Rfc3164TestSuite) TestParseContent_Valid(c *C) { buff := []byte(" foo bar baz quux ") content := string(bytes.Trim(buff, " ")) p := NewParser(buff) obtained, err := p.parseContent() c.Assert(err, Equals, syslogparser.ErrEOL) c.Assert(obtained, Equals, content) c.Assert(p.cursor, Equals, len(content)) } func (s *Rfc3164TestSuite) BenchmarkParseTimestamp(c *C) { buff := []byte("Oct 11 22:14:15") p := NewParser(buff) for i := 0; i < c.N; i++ { _, err := p.parseTimestamp() if err != nil { panic(err) } p.cursor = 0 } } func (s *Rfc3164TestSuite) BenchmarkParseHostname(c *C) { buff := []byte("gimli.local") p := NewParser(buff) for i := 0; i < c.N; i++ { _, err := p.parseHostname() if err != nil { panic(err) } p.cursor = 0 } } func (s *Rfc3164TestSuite) BenchmarkParseTag(c *C) { buff := []byte("apache2[10]:") p := NewParser(buff) for i := 0; i < c.N; i++ { _, err := p.parseTag() if err != nil { panic(err) } p.cursor = 0 } } func (s *Rfc3164TestSuite) BenchmarkParseHeader(c *C) { buff := []byte("Oct 11 22:14:15 mymachine ") p := NewParser(buff) for i := 0; i < c.N; i++ { _, err := p.parseHeader() if err != nil { panic(err) } p.cursor = 0 } } func (s *Rfc3164TestSuite) BenchmarkParsemessage(c *C) { buff := []byte("sometag[123]: foo bar baz blah quux") p := NewParser(buff) for i := 0; i < c.N; i++ { _, err := p.parsemessage() if err != syslogparser.ErrEOL { panic(err) } p.cursor = 0 } } func (s *Rfc3164TestSuite) assertTimestamp(c *C, ts time.Time, b []byte, expC int, e error) { p := NewParser(b) obtained, err := p.parseTimestamp() c.Assert(obtained, Equals, ts) c.Assert(p.cursor, Equals, expC) c.Assert(err, Equals, e) } func (s *Rfc3164TestSuite) assertTag(c *C, t string, b []byte, expC int, e error) { p := NewParser(b) obtained, err := p.parseTag() c.Assert(obtained, Equals, t) c.Assert(p.cursor, Equals, expC) c.Assert(err, Equals, e) } func (s *Rfc3164TestSuite) assertRfc3164Header(c *C, hdr header, b []byte, expC int, e error) { p := NewParser(b) obtained, err := p.parseHeader() c.Assert(err, Equals, e) c.Assert(obtained, Equals, hdr) c.Assert(p.cursor, Equals, expC) } func (s *Rfc3164TestSuite) assertRfc3164message(c *C, msg rfc3164message, b []byte, expC int, e error) { p := NewParser(b) obtained, err := p.parsemessage() c.Assert(err, Equals, e) c.Assert(obtained, Equals, msg) c.Assert(p.cursor, Equals, expC) } syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/rfc5424/000077500000000000000000000000001255210217000225315ustar00rootroot00000000000000syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/rfc5424/example_test.go000066400000000000000000000006421255210217000255540ustar00rootroot00000000000000package rfc5424_test import ( "fmt" "github.com/jeromer/syslogparser/rfc5424" ) func ExampleNewParser() { b := `<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] An application event log entry...` buff := []byte(b) p := rfc5424.NewParser(buff) err := p.Parse() if err != nil { panic(err) } fmt.Println(p.Dump()) } syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/rfc5424/rfc5424.go000066400000000000000000000273641255210217000241650ustar00rootroot00000000000000// Note to self : never try to code while looking after your kids // The result might look like this : https://pbs.twimg.com/media/BXqSuYXIEAAscVA.png package rfc5424 import ( "fmt" "github.com/jeromer/syslogparser" "math" "strconv" "time" ) const ( NILVALUE = '-' ) var ( ErrYearInvalid = &syslogparser.ParserError{"Invalid year in timestamp"} ErrMonthInvalid = &syslogparser.ParserError{"Invalid month in timestamp"} ErrDayInvalid = &syslogparser.ParserError{"Invalid day in timestamp"} ErrHourInvalid = &syslogparser.ParserError{"Invalid hour in timestamp"} ErrMinuteInvalid = &syslogparser.ParserError{"Invalid minute in timestamp"} ErrSecondInvalid = &syslogparser.ParserError{"Invalid second in timestamp"} ErrSecFracInvalid = &syslogparser.ParserError{"Invalid fraction of second in timestamp"} ErrTimeZoneInvalid = &syslogparser.ParserError{"Invalid time zone in timestamp"} ErrInvalidTimeFormat = &syslogparser.ParserError{"Invalid time format"} ErrInvalidAppName = &syslogparser.ParserError{"Invalid app name"} ErrInvalidProcId = &syslogparser.ParserError{"Invalid proc ID"} ErrInvalidMsgId = &syslogparser.ParserError{"Invalid msg ID"} ErrNoStructuredData = &syslogparser.ParserError{"No structured data"} ) type Parser struct { buff []byte cursor int l int header header structuredData string message string } type header struct { priority syslogparser.Priority version int timestamp time.Time hostname string appName string procId string msgId string } type partialTime struct { hour int minute int seconds int secFrac float64 } type fullTime struct { pt partialTime loc *time.Location } type fullDate struct { year int month int day int } func NewParser(buff []byte) *Parser { return &Parser{ buff: buff, cursor: 0, l: len(buff), } } func (p *Parser) Location(location *time.Location) { // Ignore as RFC5424 syslog always has a timezone } func (p *Parser) Parse() error { hdr, err := p.parseHeader() if err != nil { return err } p.header = hdr sd, err := p.parseStructuredData() if err != nil { return err } p.structuredData = sd p.cursor++ if p.cursor < p.l { p.message = string(p.buff[p.cursor:]) } return nil } func (p *Parser) Dump() syslogparser.LogParts { return syslogparser.LogParts{ "priority": p.header.priority.P, "facility": p.header.priority.F.Value, "severity": p.header.priority.S.Value, "version": p.header.version, "timestamp": p.header.timestamp, "hostname": p.header.hostname, "app_name": p.header.appName, "proc_id": p.header.procId, "msg_id": p.header.msgId, "structured_data": p.structuredData, "message": p.message, } } // HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID func (p *Parser) parseHeader() (header, error) { hdr := header{} pri, err := p.parsePriority() if err != nil { return hdr, err } hdr.priority = pri ver, err := p.parseVersion() if err != nil { return hdr, err } hdr.version = ver p.cursor++ ts, err := p.parseTimestamp() if err != nil { return hdr, err } hdr.timestamp = ts p.cursor++ host, err := p.parseHostname() if err != nil { return hdr, err } hdr.hostname = host p.cursor++ appName, err := p.parseAppName() if err != nil { return hdr, err } hdr.appName = appName p.cursor++ procId, err := p.parseProcId() if err != nil { return hdr, nil } hdr.procId = procId p.cursor++ msgId, err := p.parseMsgId() if err != nil { return hdr, nil } hdr.msgId = msgId p.cursor++ return hdr, nil } func (p *Parser) parsePriority() (syslogparser.Priority, error) { return syslogparser.ParsePriority(p.buff, &p.cursor, p.l) } func (p *Parser) parseVersion() (int, error) { return syslogparser.ParseVersion(p.buff, &p.cursor, p.l) } // https://tools.ietf.org/html/rfc5424#section-6.2.3 func (p *Parser) parseTimestamp() (time.Time, error) { var ts time.Time if p.buff[p.cursor] == NILVALUE { p.cursor++ return ts, nil } fd, err := parseFullDate(p.buff, &p.cursor, p.l) if err != nil { return ts, err } if p.buff[p.cursor] != 'T' { return ts, ErrInvalidTimeFormat } p.cursor++ ft, err := parseFullTime(p.buff, &p.cursor, p.l) if err != nil { return ts, syslogparser.ErrTimestampUnknownFormat } nSec, err := toNSec(ft.pt.secFrac) if err != nil { return ts, err } ts = time.Date( fd.year, time.Month(fd.month), fd.day, ft.pt.hour, ft.pt.minute, ft.pt.seconds, nSec, ft.loc, ) return ts, nil } // HOSTNAME = NILVALUE / 1*255PRINTUSASCII func (p *Parser) parseHostname() (string, error) { return syslogparser.ParseHostname(p.buff, &p.cursor, p.l) } // APP-NAME = NILVALUE / 1*48PRINTUSASCII func (p *Parser) parseAppName() (string, error) { return parseUpToLen(p.buff, &p.cursor, p.l, 48, ErrInvalidAppName) } // PROCID = NILVALUE / 1*128PRINTUSASCII func (p *Parser) parseProcId() (string, error) { return parseUpToLen(p.buff, &p.cursor, p.l, 128, ErrInvalidProcId) } // MSGID = NILVALUE / 1*32PRINTUSASCII func (p *Parser) parseMsgId() (string, error) { return parseUpToLen(p.buff, &p.cursor, p.l, 32, ErrInvalidMsgId) } func (p *Parser) parseStructuredData() (string, error) { return parseStructuredData(p.buff, &p.cursor, p.l) } // ---------------------------------------------- // https://tools.ietf.org/html/rfc5424#section-6 // ---------------------------------------------- // XXX : bind them to Parser ? // FULL-DATE : DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY func parseFullDate(buff []byte, cursor *int, l int) (fullDate, error) { var fd fullDate year, err := parseYear(buff, cursor, l) if err != nil { return fd, err } if buff[*cursor] != '-' { return fd, syslogparser.ErrTimestampUnknownFormat } *cursor++ month, err := parseMonth(buff, cursor, l) if err != nil { return fd, err } if buff[*cursor] != '-' { return fd, syslogparser.ErrTimestampUnknownFormat } *cursor++ day, err := parseDay(buff, cursor, l) if err != nil { return fd, err } fd = fullDate{ year: year, month: month, day: day, } return fd, nil } // DATE-FULLYEAR = 4DIGIT func parseYear(buff []byte, cursor *int, l int) (int, error) { yearLen := 4 if *cursor+yearLen > l { return 0, syslogparser.ErrEOL } // XXX : we do not check for a valid year (ie. 1999, 2013 etc) // XXX : we only checks the format is correct sub := string(buff[*cursor : *cursor+yearLen]) *cursor += yearLen year, err := strconv.Atoi(sub) if err != nil { return 0, ErrYearInvalid } return year, nil } // DATE-MONTH = 2DIGIT ; 01-12 func parseMonth(buff []byte, cursor *int, l int) (int, error) { return syslogparser.Parse2Digits(buff, cursor, l, 1, 12, ErrMonthInvalid) } // DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year func parseDay(buff []byte, cursor *int, l int) (int, error) { // XXX : this is a relaxed constraint // XXX : we do not check if valid regarding February or leap years // XXX : we only checks that day is in range [01 -> 31] // XXX : in other words this function will not rant if you provide Feb 31th return syslogparser.Parse2Digits(buff, cursor, l, 1, 31, ErrDayInvalid) } // FULL-TIME = PARTIAL-TIME TIME-OFFSET func parseFullTime(buff []byte, cursor *int, l int) (fullTime, error) { var loc = new(time.Location) var ft fullTime pt, err := parsePartialTime(buff, cursor, l) if err != nil { return ft, err } loc, err = parseTimeOffset(buff, cursor, l) if err != nil { return ft, err } ft = fullTime{ pt: pt, loc: loc, } return ft, nil } // PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND[TIME-SECFRAC] func parsePartialTime(buff []byte, cursor *int, l int) (partialTime, error) { var pt partialTime hour, minute, err := getHourMinute(buff, cursor, l) if err != nil { return pt, err } if buff[*cursor] != ':' { return pt, ErrInvalidTimeFormat } *cursor++ // ---- seconds, err := parseSecond(buff, cursor, l) if err != nil { return pt, err } pt = partialTime{ hour: hour, minute: minute, seconds: seconds, } // ---- if buff[*cursor] != '.' { return pt, nil } *cursor++ secFrac, err := parseSecFrac(buff, cursor, l) if err != nil { return pt, nil } pt.secFrac = secFrac return pt, nil } // TIME-HOUR = 2DIGIT ; 00-23 func parseHour(buff []byte, cursor *int, l int) (int, error) { return syslogparser.Parse2Digits(buff, cursor, l, 0, 23, ErrHourInvalid) } // TIME-MINUTE = 2DIGIT ; 00-59 func parseMinute(buff []byte, cursor *int, l int) (int, error) { return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrMinuteInvalid) } // TIME-SECOND = 2DIGIT ; 00-59 func parseSecond(buff []byte, cursor *int, l int) (int, error) { return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrSecondInvalid) } // TIME-SECFRAC = "." 1*6DIGIT func parseSecFrac(buff []byte, cursor *int, l int) (float64, error) { maxDigitLen := 6 max := *cursor + maxDigitLen from := *cursor to := from for to = from; to < max; to++ { if to >= l { break } c := buff[to] if !syslogparser.IsDigit(c) { break } } sub := string(buff[from:to]) if len(sub) == 0 { return 0, ErrSecFracInvalid } secFrac, err := strconv.ParseFloat("0."+sub, 64) *cursor = to if err != nil { return 0, ErrSecFracInvalid } return secFrac, nil } // TIME-OFFSET = "Z" / TIME-NUMOFFSET func parseTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) { if buff[*cursor] == 'Z' { *cursor++ return time.UTC, nil } return parseNumericalTimeOffset(buff, cursor, l) } // TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE func parseNumericalTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) { var loc = new(time.Location) sign := buff[*cursor] if (sign != '+') && (sign != '-') { return loc, ErrTimeZoneInvalid } *cursor++ hour, minute, err := getHourMinute(buff, cursor, l) if err != nil { return loc, err } tzStr := fmt.Sprintf("%s%02d:%02d", string(sign), hour, minute) tmpTs, err := time.Parse("-07:00", tzStr) if err != nil { return loc, err } return tmpTs.Location(), nil } func getHourMinute(buff []byte, cursor *int, l int) (int, int, error) { hour, err := parseHour(buff, cursor, l) if err != nil { return 0, 0, err } if buff[*cursor] != ':' { return 0, 0, ErrInvalidTimeFormat } *cursor++ minute, err := parseMinute(buff, cursor, l) if err != nil { return 0, 0, err } return hour, minute, nil } func toNSec(sec float64) (int, error) { _, frac := math.Modf(sec) fracStr := strconv.FormatFloat(frac, 'f', 9, 64) fracInt, err := strconv.Atoi(fracStr[2:]) if err != nil { return 0, err } return fracInt, nil } // ------------------------------------------------ // https://tools.ietf.org/html/rfc5424#section-6.3 // ------------------------------------------------ func parseStructuredData(buff []byte, cursor *int, l int) (string, error) { var sdData string var found bool if buff[*cursor] == NILVALUE { *cursor++ return "-", nil } if buff[*cursor] != '[' { return sdData, ErrNoStructuredData } from := *cursor to := from for to = from; to < l; to++ { if found { break } b := buff[to] if b == ']' { switch t := to + 1; { case t == l: found = true case t <= l && buff[t] == ' ': found = true } } } if found { *cursor = to return string(buff[from:to]), nil } return sdData, ErrNoStructuredData } func parseUpToLen(buff []byte, cursor *int, l int, maxLen int, e error) (string, error) { var to int var found bool var result string max := *cursor + maxLen for to = *cursor; (to < max) && (to < l); to++ { if buff[to] == ' ' { found = true break } } if found { result = string(buff[*cursor:to]) } *cursor = to if found { return result, nil } return "", e } syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/rfc5424/rfc5424_test.go000066400000000000000000000517621255210217000252230ustar00rootroot00000000000000package rfc5424 import ( "fmt" "github.com/jeromer/syslogparser" . "launchpad.net/gocheck" "testing" "time" ) // Hooks up gocheck into the gotest runner. func Test(t *testing.T) { TestingT(t) } type Rfc5424TestSuite struct { } var _ = Suite(&Rfc5424TestSuite{}) func (s *Rfc5424TestSuite) TestParser_Valid(c *C) { fixtures := []string{ // no STRUCTURED-DATA "<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - 'su root' failed for lonvick on /dev/pts/8", "<165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1 myproc 8710 - - %% It's time to make the do-nuts.", // with STRUCTURED-DATA `<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] An application event log entry...`, // STRUCTURED-DATA Only `<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource= "Application" eventID="1011"][examplePriority@32473 class="high"]`, } tmpTs, err := time.Parse("-07:00", "-07:00") c.Assert(err, IsNil) expected := []syslogparser.LogParts{ syslogparser.LogParts{ "priority": 34, "facility": 4, "severity": 2, "version": 1, "timestamp": time.Date(2003, time.October, 11, 22, 14, 15, 3*10e5, time.UTC), "hostname": "mymachine.example.com", "app_name": "su", "proc_id": "-", "msg_id": "ID47", "structured_data": "-", "message": "'su root' failed for lonvick on /dev/pts/8", }, syslogparser.LogParts{ "priority": 165, "facility": 20, "severity": 5, "version": 1, "timestamp": time.Date(2003, time.August, 24, 5, 14, 15, 3*10e2, tmpTs.Location()), "hostname": "192.0.2.1", "app_name": "myproc", "proc_id": "8710", "msg_id": "-", "structured_data": "-", "message": "%% It's time to make the do-nuts.", }, syslogparser.LogParts{ "priority": 165, "facility": 20, "severity": 5, "version": 1, "timestamp": time.Date(2003, time.October, 11, 22, 14, 15, 3*10e5, time.UTC), "hostname": "mymachine.example.com", "app_name": "evntslog", "proc_id": "-", "msg_id": "ID47", "structured_data": `[exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"]`, "message": "An application event log entry...", }, syslogparser.LogParts{ "priority": 165, "facility": 20, "severity": 5, "version": 1, "timestamp": time.Date(2003, time.October, 11, 22, 14, 15, 3*10e5, time.UTC), "hostname": "mymachine.example.com", "app_name": "evntslog", "proc_id": "-", "msg_id": "ID47", "structured_data": `[exampleSDID@32473 iut="3" eventSource= "Application" eventID="1011"][examplePriority@32473 class="high"]`, "message": "", }, } c.Assert(len(fixtures), Equals, len(expected)) start := 0 for i, buff := range fixtures { expectedP := &Parser{ buff: []byte(buff), cursor: start, l: len(buff), } p := NewParser([]byte(buff)) c.Assert(p, DeepEquals, expectedP) err := p.Parse() c.Assert(err, IsNil) obtained := p.Dump() for k, v := range obtained { c.Assert(v, DeepEquals, expected[i][k]) } } } func (s *Rfc5424TestSuite) TestParseHeader_Valid(c *C) { ts := time.Date(2003, time.October, 11, 22, 14, 15, 3*10e5, time.UTC) tsString := "2003-10-11T22:14:15.003Z" hostname := "mymachine.example.com" appName := "su" procId := "123" msgId := "ID47" nilValue := string(NILVALUE) headerFmt := "<165>1 %s %s %s %s %s " fixtures := []string{ // HEADER complete fmt.Sprintf(headerFmt, tsString, hostname, appName, procId, msgId), // TIMESTAMP as NILVALUE fmt.Sprintf(headerFmt, nilValue, hostname, appName, procId, msgId), // HOSTNAME as NILVALUE fmt.Sprintf(headerFmt, tsString, nilValue, appName, procId, msgId), // APP-NAME as NILVALUE fmt.Sprintf(headerFmt, tsString, hostname, nilValue, procId, msgId), // PROCID as NILVALUE fmt.Sprintf(headerFmt, tsString, hostname, appName, nilValue, msgId), // MSGID as NILVALUE fmt.Sprintf(headerFmt, tsString, hostname, appName, procId, nilValue), } pri := syslogparser.Priority{ P: 165, F: syslogparser.Facility{Value: 20}, S: syslogparser.Severity{Value: 5}, } expected := []header{ // HEADER complete header{ priority: pri, version: 1, timestamp: ts, hostname: hostname, appName: appName, procId: procId, msgId: msgId, }, // TIMESTAMP as NILVALUE header{ priority: pri, version: 1, timestamp: *new(time.Time), hostname: hostname, appName: appName, procId: procId, msgId: msgId, }, // HOSTNAME as NILVALUE header{ priority: pri, version: 1, timestamp: ts, hostname: nilValue, appName: appName, procId: procId, msgId: msgId, }, // APP-NAME as NILVALUE header{ priority: pri, version: 1, timestamp: ts, hostname: hostname, appName: nilValue, procId: procId, msgId: msgId, }, // PROCID as NILVALUE header{ priority: pri, version: 1, timestamp: ts, hostname: hostname, appName: appName, procId: nilValue, msgId: msgId, }, // MSGID as NILVALUE header{ priority: pri, version: 1, timestamp: ts, hostname: hostname, appName: appName, procId: procId, msgId: nilValue, }, } for i, f := range fixtures { p := NewParser([]byte(f)) obtained, err := p.parseHeader() c.Assert(err, IsNil) c.Assert(obtained, Equals, expected[i]) c.Assert(p.cursor, Equals, len(f)) } } func (s *Rfc5424TestSuite) TestParseTimestamp_UTC(c *C) { buff := []byte("1985-04-12T23:20:50.52Z") ts := time.Date(1985, time.April, 12, 23, 20, 50, 52*10e6, time.UTC) s.assertTimestamp(c, ts, buff, 23, nil) } func (s *Rfc5424TestSuite) TestParseTimestamp_NumericTimezone(c *C) { tz := "-04:00" buff := []byte("1985-04-12T19:20:50.52" + tz) tmpTs, err := time.Parse("-07:00", tz) c.Assert(err, IsNil) ts := time.Date(1985, time.April, 12, 19, 20, 50, 52*10e6, tmpTs.Location()) s.assertTimestamp(c, ts, buff, len(buff), nil) } func (s *Rfc5424TestSuite) TestParseTimestamp_MilliSeconds(c *C) { buff := []byte("2003-10-11T22:14:15.003Z") ts := time.Date(2003, time.October, 11, 22, 14, 15, 3*10e5, time.UTC) s.assertTimestamp(c, ts, buff, len(buff), nil) } func (s *Rfc5424TestSuite) TestParseTimestamp_MicroSeconds(c *C) { tz := "-07:00" buff := []byte("2003-08-24T05:14:15.000003" + tz) tmpTs, err := time.Parse("-07:00", tz) c.Assert(err, IsNil) ts := time.Date(2003, time.August, 24, 5, 14, 15, 3*10e2, tmpTs.Location()) s.assertTimestamp(c, ts, buff, len(buff), nil) } func (s *Rfc5424TestSuite) TestParseTimestamp_NanoSeconds(c *C) { buff := []byte("2003-08-24T05:14:15.000000003-07:00") ts := new(time.Time) s.assertTimestamp(c, *ts, buff, 26, syslogparser.ErrTimestampUnknownFormat) } func (s *Rfc5424TestSuite) TestParseTimestamp_NilValue(c *C) { buff := []byte("-") ts := new(time.Time) s.assertTimestamp(c, *ts, buff, 1, nil) } func (s *Rfc5424TestSuite) TestFindNextSpace_NoSpace(c *C) { buff := []byte("aaaaaa") s.assertFindNextSpace(c, 0, buff, syslogparser.ErrNoSpace) } func (s *Rfc5424TestSuite) TestFindNextSpace_SpaceFound(c *C) { buff := []byte("foo bar baz") s.assertFindNextSpace(c, 4, buff, nil) } func (s *Rfc5424TestSuite) TestParseYear_Invalid(c *C) { buff := []byte("1a2b") expected := 0 s.assertParseYear(c, expected, buff, 4, ErrYearInvalid) } func (s *Rfc5424TestSuite) TestParseYear_TooShort(c *C) { buff := []byte("123") expected := 0 s.assertParseYear(c, expected, buff, 0, syslogparser.ErrEOL) } func (s *Rfc5424TestSuite) TestParseYear_Valid(c *C) { buff := []byte("2013") expected := 2013 s.assertParseYear(c, expected, buff, 4, nil) } func (s *Rfc5424TestSuite) TestParseMonth_InvalidString(c *C) { buff := []byte("ab") expected := 0 s.assertParseMonth(c, expected, buff, 2, ErrMonthInvalid) } func (s *Rfc5424TestSuite) TestParseMonth_InvalidRange(c *C) { buff := []byte("00") expected := 0 s.assertParseMonth(c, expected, buff, 2, ErrMonthInvalid) // ---- buff = []byte("13") s.assertParseMonth(c, expected, buff, 2, ErrMonthInvalid) } func (s *Rfc5424TestSuite) TestParseMonth_TooShort(c *C) { buff := []byte("1") expected := 0 s.assertParseMonth(c, expected, buff, 0, syslogparser.ErrEOL) } func (s *Rfc5424TestSuite) TestParseMonth_Valid(c *C) { buff := []byte("02") expected := 2 s.assertParseMonth(c, expected, buff, 2, nil) } func (s *Rfc5424TestSuite) TestParseDay_InvalidString(c *C) { buff := []byte("ab") expected := 0 s.assertParseDay(c, expected, buff, 2, ErrDayInvalid) } func (s *Rfc5424TestSuite) TestParseDay_TooShort(c *C) { buff := []byte("1") expected := 0 s.assertParseDay(c, expected, buff, 0, syslogparser.ErrEOL) } func (s *Rfc5424TestSuite) TestParseDay_InvalidRange(c *C) { buff := []byte("00") expected := 0 s.assertParseDay(c, expected, buff, 2, ErrDayInvalid) // ---- buff = []byte("32") s.assertParseDay(c, expected, buff, 2, ErrDayInvalid) } func (s *Rfc5424TestSuite) TestParseDay_Valid(c *C) { buff := []byte("02") expected := 2 s.assertParseDay(c, expected, buff, 2, nil) } func (s *Rfc5424TestSuite) TestParseFullDate_Invalid(c *C) { buff := []byte("2013+10-28") fd := fullDate{} s.assertParseFullDate(c, fd, buff, 4, syslogparser.ErrTimestampUnknownFormat) // --- buff = []byte("2013-10+28") s.assertParseFullDate(c, fd, buff, 7, syslogparser.ErrTimestampUnknownFormat) } func (s *Rfc5424TestSuite) TestParseFullDate_Valid(c *C) { buff := []byte("2013-10-28") fd := fullDate{ year: 2013, month: 10, day: 28, } s.assertParseFullDate(c, fd, buff, len(buff), nil) } func (s *Rfc5424TestSuite) TestParseHour_InvalidString(c *C) { buff := []byte("azer") expected := 0 s.assertParseHour(c, expected, buff, 2, ErrHourInvalid) } func (s *Rfc5424TestSuite) TestParseHour_TooShort(c *C) { buff := []byte("1") expected := 0 s.assertParseHour(c, expected, buff, 0, syslogparser.ErrEOL) } func (s *Rfc5424TestSuite) TestParseHour_InvalidRange(c *C) { buff := []byte("-1") expected := 0 s.assertParseHour(c, expected, buff, 2, ErrHourInvalid) // ---- buff = []byte("24") s.assertParseHour(c, expected, buff, 2, ErrHourInvalid) } func (s *Rfc5424TestSuite) TestParseHour_Valid(c *C) { buff := []byte("12") expected := 12 s.assertParseHour(c, expected, buff, 2, nil) } func (s *Rfc5424TestSuite) TestParseMinute_InvalidString(c *C) { buff := []byte("azer") expected := 0 s.assertParseMinute(c, expected, buff, 2, ErrMinuteInvalid) } func (s *Rfc5424TestSuite) TestParseMinute_TooShort(c *C) { buff := []byte("1") expected := 0 s.assertParseMinute(c, expected, buff, 0, syslogparser.ErrEOL) } func (s *Rfc5424TestSuite) TestParseMinute_InvalidRange(c *C) { buff := []byte("-1") expected := 0 s.assertParseMinute(c, expected, buff, 2, ErrMinuteInvalid) // ---- buff = []byte("60") s.assertParseMinute(c, expected, buff, 2, ErrMinuteInvalid) } func (s *Rfc5424TestSuite) TestParseMinute_Valid(c *C) { buff := []byte("12") expected := 12 s.assertParseMinute(c, expected, buff, 2, nil) } func (s *Rfc5424TestSuite) TestParseSecond_InvalidString(c *C) { buff := []byte("azer") expected := 0 s.assertParseSecond(c, expected, buff, 2, ErrSecondInvalid) } func (s *Rfc5424TestSuite) TestParseSecond_TooShort(c *C) { buff := []byte("1") expected := 0 s.assertParseSecond(c, expected, buff, 0, syslogparser.ErrEOL) } func (s *Rfc5424TestSuite) TestParseSecond_InvalidRange(c *C) { buff := []byte("-1") expected := 0 s.assertParseSecond(c, expected, buff, 2, ErrSecondInvalid) // ---- buff = []byte("60") s.assertParseSecond(c, expected, buff, 2, ErrSecondInvalid) } func (s *Rfc5424TestSuite) TestParseSecond_Valid(c *C) { buff := []byte("12") expected := 12 s.assertParseSecond(c, expected, buff, 2, nil) } func (s *Rfc5424TestSuite) TestParseSecFrac_InvalidString(c *C) { buff := []byte("azerty") expected := 0.0 s.assertParseSecFrac(c, expected, buff, 0, ErrSecFracInvalid) } func (s *Rfc5424TestSuite) TestParseSecFrac_NanoSeconds(c *C) { buff := []byte("123456789") expected := 0.123456 s.assertParseSecFrac(c, expected, buff, 6, nil) } func (s *Rfc5424TestSuite) TestParseSecFrac_Valid(c *C) { buff := []byte("0") expected := 0.0 s.assertParseSecFrac(c, expected, buff, 1, nil) buff = []byte("52") expected = 0.52 s.assertParseSecFrac(c, expected, buff, 2, nil) buff = []byte("003") expected = 0.003 s.assertParseSecFrac(c, expected, buff, 3, nil) buff = []byte("000003") expected = 0.000003 s.assertParseSecFrac(c, expected, buff, 6, nil) } func (s *Rfc5424TestSuite) TestParseNumericalTimeOffset_Valid(c *C) { buff := []byte("+02:00") cursor := 0 l := len(buff) tmpTs, err := time.Parse("-07:00", string(buff)) c.Assert(err, IsNil) obtained, err := parseNumericalTimeOffset(buff, &cursor, l) c.Assert(err, IsNil) expected := tmpTs.Location() c.Assert(obtained, DeepEquals, expected) c.Assert(cursor, Equals, 6) } func (s *Rfc5424TestSuite) TestParseTimeOffset_Valid(c *C) { buff := []byte("Z") cursor := 0 l := len(buff) obtained, err := parseTimeOffset(buff, &cursor, l) c.Assert(err, IsNil) c.Assert(obtained, DeepEquals, time.UTC) c.Assert(cursor, Equals, 1) } func (s *Rfc5424TestSuite) TestGetHourMin_Valid(c *C) { buff := []byte("12:34") cursor := 0 l := len(buff) expectedHour := 12 expectedMinute := 34 obtainedHour, obtainedMinute, err := getHourMinute(buff, &cursor, l) c.Assert(err, IsNil) c.Assert(obtainedHour, Equals, expectedHour) c.Assert(obtainedMinute, Equals, expectedMinute) c.Assert(cursor, Equals, l) } func (s *Rfc5424TestSuite) TestParsePartialTime_Valid(c *C) { buff := []byte("05:14:15.000003") cursor := 0 l := len(buff) obtained, err := parsePartialTime(buff, &cursor, l) expected := partialTime{ hour: 5, minute: 14, seconds: 15, secFrac: 0.000003, } c.Assert(err, IsNil) c.Assert(obtained, DeepEquals, expected) c.Assert(cursor, Equals, l) } func (s *Rfc5424TestSuite) TestParseFullTime_Valid(c *C) { tz := "-02:00" buff := []byte("05:14:15.000003" + tz) cursor := 0 l := len(buff) tmpTs, err := time.Parse("-07:00", string(tz)) c.Assert(err, IsNil) obtainedFt, err := parseFullTime(buff, &cursor, l) expectedFt := fullTime{ pt: partialTime{ hour: 5, minute: 14, seconds: 15, secFrac: 0.000003, }, loc: tmpTs.Location(), } c.Assert(err, IsNil) c.Assert(obtainedFt, DeepEquals, expectedFt) c.Assert(cursor, Equals, 21) } func (s *Rfc5424TestSuite) TestToNSec(c *C) { fixtures := []float64{ 0.52, 0.003, 0.000003, } expected := []int{ 520000000, 3000000, 3000, } c.Assert(len(fixtures), Equals, len(expected)) for i, f := range fixtures { obtained, err := toNSec(f) c.Assert(err, IsNil) c.Assert(obtained, Equals, expected[i]) } } func (s *Rfc5424TestSuite) TestParseAppName_Valid(c *C) { buff := []byte("su ") appName := "su" s.assertParseAppName(c, appName, buff, 2, nil) } func (s *Rfc5424TestSuite) TestParseAppName_TooLong(c *C) { // > 48chars buff := []byte("suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu ") appName := "" s.assertParseAppName(c, appName, buff, 48, ErrInvalidAppName) } func (s *Rfc5424TestSuite) TestParseProcId_Valid(c *C) { buff := []byte("123foo ") procId := "123foo" s.assertParseProcId(c, procId, buff, 6, nil) } func (s *Rfc5424TestSuite) TestParseProcId_TooLong(c *C) { // > 128chars buff := []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab ") procId := "" s.assertParseProcId(c, procId, buff, 128, ErrInvalidProcId) } func (s *Rfc5424TestSuite) TestParseMsgId_Valid(c *C) { buff := []byte("123foo ") procId := "123foo" s.assertParseMsgId(c, procId, buff, 6, nil) } func (s *Rfc5424TestSuite) TestParseMsgId_TooLong(c *C) { // > 32chars buff := []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ") procId := "" s.assertParseMsgId(c, procId, buff, 32, ErrInvalidMsgId) } func (s *Rfc5424TestSuite) TestParseStructuredData_NilValue(c *C) { // > 32chars buff := []byte("-") sdData := "-" s.assertParseSdName(c, sdData, buff, 1, nil) } func (s *Rfc5424TestSuite) TestParseStructuredData_SingleStructuredData(c *C) { sdData := `[exampleSDID@32473 iut="3" eventSource="Application"eventID="1011"]` buff := []byte(sdData) s.assertParseSdName(c, sdData, buff, len(buff), nil) } func (s *Rfc5424TestSuite) TestParseStructuredData_MultipleStructuredData(c *C) { sdData := `[exampleSDID@32473 iut="3" eventSource="Application"eventID="1011"][examplePriority@32473 class="high"]` buff := []byte(sdData) s.assertParseSdName(c, sdData, buff, len(buff), nil) } func (s *Rfc5424TestSuite) TestParseStructuredData_MultipleStructuredDataInvalid(c *C) { a := `[exampleSDID@32473 iut="3" eventSource="Application"eventID="1011"]` sdData := a + ` [examplePriority@32473 class="high"]` buff := []byte(sdData) s.assertParseSdName(c, a, buff, len(a), nil) } // ------------- func (s *Rfc5424TestSuite) BenchmarkParseTimestamp(c *C) { buff := []byte("2003-08-24T05:14:15.000003-07:00") p := NewParser(buff) for i := 0; i < c.N; i++ { _, err := p.parseTimestamp() if err != nil { panic(err) } p.cursor = 0 } } func (s *Rfc5424TestSuite) BenchmarkParseHeader(c *C) { buff := []byte("<165>1 2003-10-11T22:14:15.003Z mymachine.example.com su 123 ID47") p := NewParser(buff) for i := 0; i < c.N; i++ { _, err := p.parseHeader() if err != nil { panic(err) } p.cursor = 0 } } // ------------- func (s *Rfc5424TestSuite) assertTimestamp(c *C, ts time.Time, b []byte, expC int, e error) { p := NewParser(b) obtained, err := p.parseTimestamp() c.Assert(err, Equals, e) tFmt := time.RFC3339Nano c.Assert(obtained.Format(tFmt), Equals, ts.Format(tFmt)) c.Assert(p.cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertFindNextSpace(c *C, nextSpace int, b []byte, e error) { obtained, err := syslogparser.FindNextSpace(b, 0, len(b)) c.Assert(obtained, Equals, nextSpace) c.Assert(err, Equals, e) } func (s *Rfc5424TestSuite) assertParseYear(c *C, year int, b []byte, expC int, e error) { cursor := 0 obtained, err := parseYear(b, &cursor, len(b)) c.Assert(obtained, Equals, year) c.Assert(err, Equals, e) c.Assert(cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseMonth(c *C, month int, b []byte, expC int, e error) { cursor := 0 obtained, err := parseMonth(b, &cursor, len(b)) c.Assert(obtained, Equals, month) c.Assert(err, Equals, e) c.Assert(cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseDay(c *C, day int, b []byte, expC int, e error) { cursor := 0 obtained, err := parseDay(b, &cursor, len(b)) c.Assert(obtained, Equals, day) c.Assert(err, Equals, e) c.Assert(cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseFullDate(c *C, fd fullDate, b []byte, expC int, e error) { cursor := 0 obtained, err := parseFullDate(b, &cursor, len(b)) c.Assert(err, Equals, e) c.Assert(obtained, Equals, fd) c.Assert(cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseHour(c *C, hour int, b []byte, expC int, e error) { cursor := 0 obtained, err := parseHour(b, &cursor, len(b)) c.Assert(obtained, Equals, hour) c.Assert(err, Equals, e) c.Assert(cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseMinute(c *C, minute int, b []byte, expC int, e error) { cursor := 0 obtained, err := parseMinute(b, &cursor, len(b)) c.Assert(obtained, Equals, minute) c.Assert(err, Equals, e) c.Assert(cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseSecond(c *C, second int, b []byte, expC int, e error) { cursor := 0 obtained, err := parseSecond(b, &cursor, len(b)) c.Assert(obtained, Equals, second) c.Assert(err, Equals, e) c.Assert(cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseSecFrac(c *C, secFrac float64, b []byte, expC int, e error) { cursor := 0 obtained, err := parseSecFrac(b, &cursor, len(b)) c.Assert(obtained, Equals, secFrac) c.Assert(err, Equals, e) c.Assert(cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseAppName(c *C, appName string, b []byte, expC int, e error) { p := NewParser(b) obtained, err := p.parseAppName() c.Assert(err, Equals, e) c.Assert(obtained, Equals, appName) c.Assert(p.cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseProcId(c *C, procId string, b []byte, expC int, e error) { p := NewParser(b) obtained, err := p.parseProcId() c.Assert(err, Equals, e) c.Assert(obtained, Equals, procId) c.Assert(p.cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseMsgId(c *C, msgId string, b []byte, expC int, e error) { p := NewParser(b) obtained, err := p.parseMsgId() c.Assert(err, Equals, e) c.Assert(obtained, Equals, msgId) c.Assert(p.cursor, Equals, expC) } func (s *Rfc5424TestSuite) assertParseSdName(c *C, sdData string, b []byte, expC int, e error) { cursor := 0 obtained, err := parseStructuredData(b, &cursor, len(b)) c.Assert(err, Equals, e) c.Assert(obtained, Equals, sdData) c.Assert(cursor, Equals, expC) } syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/syslogparser.go000066400000000000000000000070161255210217000245300ustar00rootroot00000000000000package syslogparser import ( "fmt" "strconv" "strings" "time" ) const ( PRI_PART_START = '<' PRI_PART_END = '>' NO_VERSION = -1 ) var ( ErrEOL = &ParserError{"End of log line"} ErrNoSpace = &ParserError{"No space found"} ErrPriorityNoStart = &ParserError{"No start char found for priority"} ErrPriorityEmpty = &ParserError{"Priority field empty"} ErrPriorityNoEnd = &ParserError{"No end char found for priority"} ErrPriorityTooShort = &ParserError{"Priority field too short"} ErrPriorityTooLong = &ParserError{"Priority field too long"} ErrPriorityNonDigit = &ParserError{"Non digit found in priority"} ErrVersionNotFound = &ParserError{"Can not find version"} ErrTimestampUnknownFormat = &ParserError{"Timestamp format unknown"} ) type LogParser interface { Parse() error Dump() LogParts Location(*time.Location) } type ParserError struct { ErrorString string } type Priority struct { P int F Facility S Severity } type Facility struct { Value int } type Severity struct { Value int } type LogParts map[string]interface{} // https://tools.ietf.org/html/rfc3164#section-4.1 func ParsePriority(buff []byte, cursor *int, l int) (Priority, error) { pri := newPriority(0) if l <= 0 { return pri, ErrPriorityEmpty } if buff[*cursor] != PRI_PART_START { return pri, ErrPriorityNoStart } i := 1 priDigit := 0 for i < l { if i >= 5 { return pri, ErrPriorityTooLong } c := buff[i] if c == PRI_PART_END { if i == 1 { return pri, ErrPriorityTooShort } *cursor = i + 1 return newPriority(priDigit), nil } if IsDigit(c) { v, e := strconv.Atoi(string(c)) if e != nil { return pri, e } priDigit = (priDigit * 10) + v } else { return pri, ErrPriorityNonDigit } i++ } return pri, ErrPriorityNoEnd } // https://tools.ietf.org/html/rfc5424#section-6.2.2 func ParseVersion(buff []byte, cursor *int, l int) (int, error) { if *cursor >= l { return NO_VERSION, ErrVersionNotFound } c := buff[*cursor] *cursor++ // XXX : not a version, not an error though as RFC 3164 does not support it if !IsDigit(c) { return NO_VERSION, nil } v, e := strconv.Atoi(string(c)) if e != nil { *cursor-- return NO_VERSION, e } return v, nil } func IsDigit(c byte) bool { return c >= '0' && c <= '9' } func newPriority(p int) Priority { // The Priority value is calculated by first multiplying the Facility // number by 8 and then adding the numerical value of the Severity. return Priority{ P: p, F: Facility{Value: p / 8}, S: Severity{Value: p % 8}, } } func FindNextSpace(buff []byte, from int, l int) (int, error) { var to int for to = from; to < l; to++ { if buff[to] == ' ' { to++ return to, nil } } return 0, ErrNoSpace } func Parse2Digits(buff []byte, cursor *int, l int, min int, max int, e error) (int, error) { digitLen := 2 if *cursor+digitLen > l { return 0, ErrEOL } sub := string(buff[*cursor : *cursor+digitLen]) *cursor += digitLen i, err := strconv.Atoi(sub) if err != nil { return 0, e } if i >= min && i <= max { return i, nil } return 0, e } func ParseHostname(buff []byte, cursor *int, l int) (string, error) { from := *cursor var to int for to = from; to < l; to++ { if buff[to] == ' ' { break } } hostname := buff[from:to] *cursor = to return string(hostname), nil } func ShowCursorPos(buff []byte, cursor int) { fmt.Println(string(buff)) padding := strings.Repeat("-", cursor) fmt.Println(padding + "↑\n") } func (err *ParserError) Error() string { return err.ErrorString } syslogparser-ff71fe7a7d5279df4b964b31f7ee4adf117277f6/syslogparser_test.go000066400000000000000000000076451255210217000255770ustar00rootroot00000000000000package syslogparser import ( . "launchpad.net/gocheck" "testing" ) // Hooks up gocheck into the gotest runner. func Test(t *testing.T) { TestingT(t) } type CommonTestSuite struct { } var _ = Suite(&CommonTestSuite{}) func (s *CommonTestSuite) TestParsePriority_Empty(c *C) { pri := newPriority(0) buff := []byte("") start := 0 s.assertPriority(c, pri, buff, start, start, ErrPriorityEmpty) } func (s *CommonTestSuite) TestParsePriority_NoStart(c *C) { pri := newPriority(0) buff := []byte("7>") start := 0 s.assertPriority(c, pri, buff, start, start, ErrPriorityNoStart) } func (s *CommonTestSuite) TestParsePriority_NoEnd(c *C) { pri := newPriority(0) buff := []byte("<77") start := 0 s.assertPriority(c, pri, buff, start, start, ErrPriorityNoEnd) } func (s *CommonTestSuite) TestParsePriority_TooShort(c *C) { pri := newPriority(0) buff := []byte("<>") start := 0 s.assertPriority(c, pri, buff, start, start, ErrPriorityTooShort) } func (s *CommonTestSuite) TestParsePriority_TooLong(c *C) { pri := newPriority(0) buff := []byte("<1233>") start := 0 s.assertPriority(c, pri, buff, start, start, ErrPriorityTooLong) } func (s *CommonTestSuite) TestParsePriority_NoDigits(c *C) { pri := newPriority(0) buff := []byte("<7a8>") start := 0 s.assertPriority(c, pri, buff, start, start, ErrPriorityNonDigit) } func (s *CommonTestSuite) TestParsePriority_Ok(c *C) { pri := newPriority(190) buff := []byte("<190>") start := 0 s.assertPriority(c, pri, buff, start, start+5, nil) } func (s *CommonTestSuite) TestNewPriority(c *C) { obtained := newPriority(165) expected := Priority{ P: 165, F: Facility{Value: 20}, S: Severity{Value: 5}, } c.Assert(obtained, DeepEquals, expected) } func (s *CommonTestSuite) TestParseVersion_NotFound(c *C) { buff := []byte("<123>") start := 5 s.assertVersion(c, NO_VERSION, buff, start, start, ErrVersionNotFound) } func (s *CommonTestSuite) TestParseVersion_NonDigit(c *C) { buff := []byte("<123>a") start := 5 s.assertVersion(c, NO_VERSION, buff, start, start+1, nil) } func (s *CommonTestSuite) TestParseVersion_Ok(c *C) { buff := []byte("<123>1") start := 5 s.assertVersion(c, 1, buff, start, start+1, nil) } func (s *CommonTestSuite) TestParseHostname_Invalid(c *C) { // XXX : no year specified. Assumed current year // XXX : no timezone specified. Assume UTC buff := []byte("foo name") start := 0 hostname := "foo" s.assertHostname(c, hostname, buff, start, 3, nil) } func (s *CommonTestSuite) TestParseHostname_Valid(c *C) { // XXX : no year specified. Assumed current year // XXX : no timezone specified. Assume UTC hostname := "ubuntu11.somehost.com" buff := []byte(hostname + " ") start := 0 s.assertHostname(c, hostname, buff, start, len(hostname), nil) } func (s *CommonTestSuite) BenchmarkParsePriority(c *C) { buff := []byte("<190>") var start int l := len(buff) for i := 0; i < c.N; i++ { start = 0 _, err := ParsePriority(buff, &start, l) if err != nil { panic(err) } } } func (s *CommonTestSuite) BenchmarkParseVersion(c *C) { buff := []byte("<123>1") start := 5 l := len(buff) for i := 0; i < c.N; i++ { start = 0 _, err := ParseVersion(buff, &start, l) if err != nil { panic(err) } } } func (s *CommonTestSuite) assertPriority(c *C, p Priority, b []byte, cursor int, expC int, e error) { obtained, err := ParsePriority(b, &cursor, len(b)) c.Assert(obtained, DeepEquals, p) c.Assert(cursor, Equals, expC) c.Assert(err, Equals, e) } func (s *CommonTestSuite) assertVersion(c *C, version int, b []byte, cursor int, expC int, e error) { obtained, err := ParseVersion(b, &cursor, len(b)) c.Assert(obtained, Equals, version) c.Assert(cursor, Equals, expC) c.Assert(err, Equals, e) } func (s *CommonTestSuite) assertHostname(c *C, h string, b []byte, cursor int, expC int, e error) { obtained, err := ParseHostname(b, &cursor, len(b)) c.Assert(obtained, Equals, h) c.Assert(cursor, Equals, expC) c.Assert(err, Equals, e) }