gotosocial/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164/rfc3164.go
tobi c111b239f7
Add optional syslog logrus hook (#343)
* add optional syslog logrus hook

* document syslog
2021-12-12 18:00:20 +01:00

293 lines
5.4 KiB
Go

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
}