Add optional syslog logrus hook (#343)

* add optional syslog logrus hook

* document syslog
This commit is contained in:
tobi 2021-12-12 18:00:20 +01:00 committed by GitHub
commit c111b239f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 2242 additions and 37 deletions

View file

@ -0,0 +1,23 @@
Copyright (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.

View file

@ -0,0 +1,4 @@
Syslogparser
============
This is a fork for [github.com/jeromer/syslogparser](https://github.com/jeromer/syslogparser), since this library is intensively used by `go-syslog`, now is integrated as a `internal` package.

View file

@ -0,0 +1,292 @@
package rfc3164
import (
"bytes"
"os"
"time"
"gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser"
)
type Parser struct {
buff []byte
cursor int
l int
priority syslogparser.Priority
version int
header header
message rfc3164message
location *time.Location
skipTag bool
}
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 {
tcursor := p.cursor
pri, err := p.parsePriority()
if err != nil {
// RFC3164 sec 4.3.3
p.priority = syslogparser.Priority{13, syslogparser.Facility{Value: 1}, syslogparser.Severity{Value: 5}}
p.cursor = tcursor
content, err := p.parseContent()
p.header.timestamp = time.Now().Round(time.Second)
if err != syslogparser.ErrEOL {
return err
}
p.message = rfc3164message{content: content}
return nil
}
tcursor = p.cursor
hdr, err := p.parseHeader()
if err == syslogparser.ErrTimestampUnknownFormat {
// RFC3164 sec 4.3.2.
hdr.timestamp = time.Now().Round(time.Second)
// No tag processing should be done
p.skipTag = true
// Reset cursor for content read
p.cursor = tcursor
} else if err != nil {
return err
} else {
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
if !p.skipTag {
tag, err := p.parseTag()
if err != nil {
return msg, err
}
msg.tag = tag
}
content, err := p.parseContent()
if err != syslogparser.ErrEOL {
return msg, err
}
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{
time.Stamp,
time.RFC3339,
}
// if timestamps starts with numeric try formats with different order
// it is more likely that timestamp is in RFC3339 format then
if c := p.buff[p.cursor]; c > '0' && c < '9' {
tsFmts = []string{
time.RFC3339,
time.Stamp,
}
}
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 = len(time.Stamp)
// 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) {
oldcursor := p.cursor
hostname, err := syslogparser.ParseHostname(p.buff, &p.cursor, p.l)
if err == nil && len(hostname) > 0 && string(hostname[len(hostname)-1]) == ":" { // not an hostname! we found a GNU implementation of syslog()
p.cursor = oldcursor - 1
myhostname, err := os.Hostname()
if err == nil {
return myhostname, nil
}
return "", nil
}
return hostname, err
}
// 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 {
if p.cursor == p.l {
// no tag found, reset cursor for content
p.cursor = from
return "", nil
}
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
}

View file

@ -0,0 +1,606 @@
// 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"
"math"
"strconv"
"time"
"gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser"
)
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.cursor >= p.l {
return ts, ErrInvalidTimeFormat
}
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.cursor >= p.l || 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 *cursor >= l || buff[*cursor] != '-' {
return fd, syslogparser.ErrTimestampUnknownFormat
}
*cursor++
month, err := parseMonth(buff, cursor, l)
if err != nil {
return fd, err
}
if *cursor >= l || 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 *cursor >= l || 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 *cursor >= l || 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 *cursor >= l || 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 *cursor >= l || 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 *cursor >= l {
return "-", nil
}
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])
} else if to > max {
to = max // don't go past max
}
*cursor = to
if found {
return result, nil
}
return "", e
}

View file

@ -0,0 +1,213 @@
package 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"}
ErrHostnameTooShort = &ParserError{"Hostname field too short"}
)
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
if from >= l {
return "", ErrHostnameTooShort
}
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
}