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

21
vendor/gopkg.in/mcuadros/go-syslog.v2/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,21 @@
language: go
go:
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- 1.9
- "1.10"
- "1.11"
- "1.12"
- tip
matrix:
allow_failures:
- go: tip
go_import_path: gopkg.in/mcuadros/go-syslog.v2
install:
- go get -v -t ./...

19
vendor/gopkg.in/mcuadros/go-syslog.v2/LICENSE generated vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2013 Máximo Cuadros
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.

48
vendor/gopkg.in/mcuadros/go-syslog.v2/README.md generated vendored Normal file
View file

@ -0,0 +1,48 @@
go-syslog [![Build Status](https://travis-ci.org/mcuadros/go-syslog.svg?branch=master)](https://travis-ci.org/mcuadros/go-syslog) [![GoDoc](http://godoc.org/github.com/mcuadros/go-syslog?status.svg)](hhttps://godoc.org/gopkg.in/mcuadros/go-syslog.v2) [![GitHub release](https://img.shields.io/github/release/mcuadros/go-syslog.svg)](https://github.com/mcuadros/go-syslog/releases)
==============================
Syslog server library for go, build easy your custom syslog server over UDP, TCP or Unix sockets using RFC3164, RFC6587 or RFC5424
Installation
------------
The recommended way to install go-syslog
```
go get gopkg.in/mcuadros/go-syslog.v2
```
Examples
--------
How import the package
```go
import "gopkg.in/mcuadros/go-syslog.v2"
```
Example of a basic syslog [UDP server](example/basic_udp.go):
```go
channel := make(syslog.LogPartsChannel)
handler := syslog.NewChannelHandler(channel)
server := syslog.NewServer()
server.SetFormat(syslog.RFC5424)
server.SetHandler(handler)
server.ListenUDP("0.0.0.0:514")
server.Boot()
go func(channel syslog.LogPartsChannel) {
for logParts := range channel {
fmt.Println(logParts)
}
}(channel)
server.Wait()
```
License
-------
MIT, see [LICENSE](LICENSE)

5
vendor/gopkg.in/mcuadros/go-syslog.v2/doc.go generated vendored Normal file
View file

@ -0,0 +1,5 @@
/*
Syslog server library for go, build easy your custom syslog server
over UDP, TCP or Unix sockets using RFC3164, RFC5424 and RFC6587
*/
package syslog // import "gopkg.in/mcuadros/go-syslog.v2"

View file

@ -0,0 +1,104 @@
package format
import (
"bufio"
"bytes"
"strconv"
"gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164"
"gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424"
)
/* Selecting an 'Automatic' format detects incoming format (i.e. RFC3164 vs RFC5424) and Framing
* (i.e. RFC6587 s3.4.1 octet counting as described here as RFC6587, and either no framing or
* RFC6587 s3.4.2 octet stuffing / non-transparent framing, described here as either RFC3164
* or RFC6587).
*
* In essence if you don't know which format to select, or have multiple incoming formats, this
* is the one to go for. There is a theoretical performance penalty (it has to look at a few bytes
* at the start of the frame), and a risk that you may parse things you don't want to parse
* (rogue syslog clients using other formats), so if you can be absolutely sure of your syslog
* format, it would be best to select it explicitly.
*/
type Automatic struct{}
const (
detectedUnknown = iota
detectedRFC3164 = iota
detectedRFC5424 = iota
detectedRFC6587 = iota
)
/*
* Will always fallback to rfc3164 (see section 4.3.3)
*/
func detect(data []byte) int {
// all formats have a sapce somewhere
if i := bytes.IndexByte(data, ' '); i > 0 {
pLength := data[0:i]
if _, err := strconv.Atoi(string(pLength)); err == nil {
return detectedRFC6587
}
// are we starting with <
if data[0] != '<' {
return detectedRFC3164
}
// is there a close angle bracket before the ' '? there should be
angle := bytes.IndexByte(data, '>')
if (angle < 0) || (angle >= i) {
return detectedRFC3164
}
// if a single digit immediately follows the angle bracket, then a space
// it is RFC5424, as RFC3164 must begin with a letter (month name)
if (angle+2 == i) && (data[angle+1] >= '0') && (data[angle+1] <= '9') {
return detectedRFC5424
} else {
return detectedRFC3164
}
}
// fallback to rfc 3164 section 4.3.3
return detectedRFC3164
}
func (f *Automatic) GetParser(line []byte) LogParser {
switch format := detect(line); format {
case detectedRFC3164:
return &parserWrapper{rfc3164.NewParser(line)}
case detectedRFC5424:
return &parserWrapper{rfc5424.NewParser(line)}
default:
// If the line was an RFC6587 line, the splitter should already have removed the length,
// so one of the above two will be chosen if the line is correctly formed. However, it
// may have a second length illegally placed at the start, in which case the detector
// will return detectedRFC6587. The line may also simply be malformed after the length in
// which case we will have detectedUnknown. In this case we return the simplest parser so
// the illegally formatted line is properly handled
return &parserWrapper{rfc3164.NewParser(line)}
}
}
func (f *Automatic) GetSplitFunc() bufio.SplitFunc {
return f.automaticScannerSplit
}
func (f *Automatic) automaticScannerSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
switch format := detect(data); format {
case detectedRFC6587:
return rfc6587ScannerSplit(data, atEOF)
case detectedRFC3164, detectedRFC5424:
// the default
return bufio.ScanLines(data, atEOF)
default:
if err != nil {
return 0, nil, err
}
// Request more data
return 0, nil, nil
}
}

29
vendor/gopkg.in/mcuadros/go-syslog.v2/format/format.go generated vendored Normal file
View file

@ -0,0 +1,29 @@
package format
import (
"bufio"
"time"
"gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser"
)
type LogParts map[string]interface{}
type LogParser interface {
Parse() error
Dump() LogParts
Location(*time.Location)
}
type Format interface {
GetParser([]byte) LogParser
GetSplitFunc() bufio.SplitFunc
}
type parserWrapper struct {
syslogparser.LogParser
}
func (w *parserWrapper) Dump() LogParts {
return LogParts(w.LogParser.Dump())
}

View file

@ -0,0 +1,17 @@
package format
import (
"bufio"
"gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164"
)
type RFC3164 struct{}
func (f *RFC3164) GetParser(line []byte) LogParser {
return &parserWrapper{rfc3164.NewParser(line)}
}
func (f *RFC3164) GetSplitFunc() bufio.SplitFunc {
return nil
}

View file

@ -0,0 +1,17 @@
package format
import (
"bufio"
"gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424"
)
type RFC5424 struct{}
func (f *RFC5424) GetParser(line []byte) LogParser {
return &parserWrapper{rfc5424.NewParser(line)}
}
func (f *RFC5424) GetSplitFunc() bufio.SplitFunc {
return nil
}

View file

@ -0,0 +1,45 @@
package format
import (
"bufio"
"bytes"
"strconv"
"gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424"
)
type RFC6587 struct{}
func (f *RFC6587) GetParser(line []byte) LogParser {
return &parserWrapper{rfc5424.NewParser(line)}
}
func (f *RFC6587) GetSplitFunc() bufio.SplitFunc {
return rfc6587ScannerSplit
}
func rfc6587ScannerSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, ' '); i > 0 {
pLength := data[0:i]
length, err := strconv.Atoi(string(pLength))
if err != nil {
if string(data[0:1]) == "<" {
// Assume this frame uses non-transparent-framing
return len(data), data, nil
}
return 0, nil, err
}
end := length + i + 1
if len(data) >= end {
// Return the frame with the length removed
return end, data[i+1 : end], nil
}
}
// Request more data
return 0, nil, nil
}

35
vendor/gopkg.in/mcuadros/go-syslog.v2/handler.go generated vendored Normal file
View file

@ -0,0 +1,35 @@
package syslog
import (
"gopkg.in/mcuadros/go-syslog.v2/format"
)
//The handler receive every syslog entry at Handle method
type Handler interface {
Handle(format.LogParts, int64, error)
}
type LogPartsChannel chan format.LogParts
//The ChannelHandler will send all the syslog entries into the given channel
type ChannelHandler struct {
channel LogPartsChannel
}
//NewChannelHandler returns a new ChannelHandler
func NewChannelHandler(channel LogPartsChannel) *ChannelHandler {
handler := new(ChannelHandler)
handler.SetChannel(channel)
return handler
}
//The channel to be used
func (h *ChannelHandler) SetChannel(channel LogPartsChannel) {
h.channel = channel
}
//Syslog entry receiver
func (h *ChannelHandler) Handle(logParts format.LogParts, messageLength int64, err error) {
h.channel <- logParts
}

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
}

378
vendor/gopkg.in/mcuadros/go-syslog.v2/server.go generated vendored Normal file
View file

@ -0,0 +1,378 @@
package syslog
import (
"bufio"
"crypto/tls"
"errors"
"net"
"strings"
"sync"
"time"
"gopkg.in/mcuadros/go-syslog.v2/format"
)
var (
RFC3164 = &format.RFC3164{} // RFC3164: http://www.ietf.org/rfc/rfc3164.txt
RFC5424 = &format.RFC5424{} // RFC5424: http://www.ietf.org/rfc/rfc5424.txt
RFC6587 = &format.RFC6587{} // RFC6587: http://www.ietf.org/rfc/rfc6587.txt - octet counting variant
Automatic = &format.Automatic{} // Automatically identify the format
)
const (
datagramChannelBufferSize = 10
datagramReadBufferSize = 64 * 1024
)
// A function type which gets the TLS peer name from the connection. Can return
// ok=false to terminate the connection
type TlsPeerNameFunc func(tlsConn *tls.Conn) (tlsPeer string, ok bool)
type Server struct {
listeners []net.Listener
connections []net.PacketConn
wait sync.WaitGroup
doneTcp chan bool
datagramChannel chan DatagramMessage
format format.Format
handler Handler
lastError error
readTimeoutMilliseconds int64
tlsPeerNameFunc TlsPeerNameFunc
datagramPool sync.Pool
}
//NewServer returns a new Server
func NewServer() *Server {
return &Server{tlsPeerNameFunc: defaultTlsPeerName, datagramPool: sync.Pool{
New: func() interface{} {
return make([]byte, 65536)
},
}}
}
//Sets the syslog format (RFC3164 or RFC5424 or RFC6587)
func (s *Server) SetFormat(f format.Format) {
s.format = f
}
//Sets the handler, this handler with receive every syslog entry
func (s *Server) SetHandler(handler Handler) {
s.handler = handler
}
//Sets the connection timeout for TCP connections, in milliseconds
func (s *Server) SetTimeout(millseconds int64) {
s.readTimeoutMilliseconds = millseconds
}
// Set the function that extracts a TLS peer name from the TLS connection
func (s *Server) SetTlsPeerNameFunc(tlsPeerNameFunc TlsPeerNameFunc) {
s.tlsPeerNameFunc = tlsPeerNameFunc
}
// Default TLS peer name function - returns the CN of the certificate
func defaultTlsPeerName(tlsConn *tls.Conn) (tlsPeer string, ok bool) {
state := tlsConn.ConnectionState()
if len(state.PeerCertificates) <= 0 {
return "", false
}
cn := state.PeerCertificates[0].Subject.CommonName
return cn, true
}
//Configure the server for listen on an UDP addr
func (s *Server) ListenUDP(addr string) error {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return err
}
connection, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return err
}
connection.SetReadBuffer(datagramReadBufferSize)
s.connections = append(s.connections, connection)
return nil
}
//Configure the server for listen on an unix socket
func (s *Server) ListenUnixgram(addr string) error {
unixAddr, err := net.ResolveUnixAddr("unixgram", addr)
if err != nil {
return err
}
connection, err := net.ListenUnixgram("unixgram", unixAddr)
if err != nil {
return err
}
connection.SetReadBuffer(datagramReadBufferSize)
s.connections = append(s.connections, connection)
return nil
}
//Configure the server for listen on a TCP addr
func (s *Server) ListenTCP(addr string) error {
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return err
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
return err
}
s.doneTcp = make(chan bool)
s.listeners = append(s.listeners, listener)
return nil
}
//Configure the server for listen on a TCP addr for TLS
func (s *Server) ListenTCPTLS(addr string, config *tls.Config) error {
listener, err := tls.Listen("tcp", addr, config)
if err != nil {
return err
}
s.doneTcp = make(chan bool)
s.listeners = append(s.listeners, listener)
return nil
}
//Starts the server, all the go routines goes to live
func (s *Server) Boot() error {
if s.format == nil {
return errors.New("please set a valid format")
}
if s.handler == nil {
return errors.New("please set a valid handler")
}
for _, listener := range s.listeners {
s.goAcceptConnection(listener)
}
if len(s.connections) > 0 {
s.goParseDatagrams()
}
for _, connection := range s.connections {
s.goReceiveDatagrams(connection)
}
return nil
}
func (s *Server) goAcceptConnection(listener net.Listener) {
s.wait.Add(1)
go func(listener net.Listener) {
loop:
for {
select {
case <-s.doneTcp:
break loop
default:
}
connection, err := listener.Accept()
if err != nil {
continue
}
s.goScanConnection(connection)
}
s.wait.Done()
}(listener)
}
func (s *Server) goScanConnection(connection net.Conn) {
scanner := bufio.NewScanner(connection)
if sf := s.format.GetSplitFunc(); sf != nil {
scanner.Split(sf)
}
remoteAddr := connection.RemoteAddr()
var client string
if remoteAddr != nil {
client = remoteAddr.String()
}
tlsPeer := ""
if tlsConn, ok := connection.(*tls.Conn); ok {
// Handshake now so we get the TLS peer information
if err := tlsConn.Handshake(); err != nil {
connection.Close()
return
}
if s.tlsPeerNameFunc != nil {
var ok bool
tlsPeer, ok = s.tlsPeerNameFunc(tlsConn)
if !ok {
connection.Close()
return
}
}
}
var scanCloser *ScanCloser
scanCloser = &ScanCloser{scanner, connection}
s.wait.Add(1)
go s.scan(scanCloser, client, tlsPeer)
}
func (s *Server) scan(scanCloser *ScanCloser, client string, tlsPeer string) {
loop:
for {
select {
case <-s.doneTcp:
break loop
default:
}
if s.readTimeoutMilliseconds > 0 {
scanCloser.closer.SetReadDeadline(time.Now().Add(time.Duration(s.readTimeoutMilliseconds) * time.Millisecond))
}
if scanCloser.Scan() {
s.parser([]byte(scanCloser.Text()), client, tlsPeer)
} else {
break loop
}
}
scanCloser.closer.Close()
s.wait.Done()
}
func (s *Server) parser(line []byte, client string, tlsPeer string) {
parser := s.format.GetParser(line)
err := parser.Parse()
if err != nil {
s.lastError = err
}
logParts := parser.Dump()
logParts["client"] = client
if logParts["hostname"] == "" && (s.format == RFC3164 || s.format == Automatic) {
if i := strings.Index(client, ":"); i > 1 {
logParts["hostname"] = client[:i]
} else {
logParts["hostname"] = client
}
}
logParts["tls_peer"] = tlsPeer
s.handler.Handle(logParts, int64(len(line)), err)
}
//Returns the last error
func (s *Server) GetLastError() error {
return s.lastError
}
//Kill the server
func (s *Server) Kill() error {
for _, connection := range s.connections {
err := connection.Close()
if err != nil {
return err
}
}
for _, listener := range s.listeners {
err := listener.Close()
if err != nil {
return err
}
}
// Only need to close channel once to broadcast to all waiting
if s.doneTcp != nil {
close(s.doneTcp)
}
if s.datagramChannel != nil {
close(s.datagramChannel)
}
return nil
}
//Waits until the server stops
func (s *Server) Wait() {
s.wait.Wait()
}
type TimeoutCloser interface {
Close() error
SetReadDeadline(t time.Time) error
}
type ScanCloser struct {
*bufio.Scanner
closer TimeoutCloser
}
type DatagramMessage struct {
message []byte
client string
}
func (s *Server) goReceiveDatagrams(packetconn net.PacketConn) {
s.wait.Add(1)
go func() {
defer s.wait.Done()
for {
buf := s.datagramPool.Get().([]byte)
n, addr, err := packetconn.ReadFrom(buf)
if err == nil {
// Ignore trailing control characters and NULs
for ; (n > 0) && (buf[n-1] < 32); n-- {
}
if n > 0 {
var address string
if addr != nil {
address = addr.String()
}
s.datagramChannel <- DatagramMessage{buf[:n], address}
}
} else {
// there has been an error. Either the server has been killed
// or may be getting a transitory error due to (e.g.) the
// interface being shutdown in which case sleep() to avoid busy wait.
opError, ok := err.(*net.OpError)
if (ok) && !opError.Temporary() && !opError.Timeout() {
return
}
time.Sleep(10 * time.Millisecond)
}
}
}()
}
func (s *Server) goParseDatagrams() {
s.datagramChannel = make(chan DatagramMessage, datagramChannelBufferSize)
s.wait.Add(1)
go func() {
defer s.wait.Done()
for {
select {
case msg, ok := (<-s.datagramChannel):
if !ok {
return
}
if sf := s.format.GetSplitFunc(); sf != nil {
if _, token, err := sf(msg.message, true); err == nil {
s.parser(token, msg.client, "")
}
} else {
s.parser(msg.message, msg.client, "")
}
s.datagramPool.Put(msg.message[:cap(msg.message)])
}
}
}()
}