Skip to content

Commit

Permalink
Add support for rfc3164 messages
Browse files Browse the repository at this point in the history
  • Loading branch information
ysmilda committed Aug 21, 2024
1 parent cea827f commit d345210
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 0 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The goal of this library is to provide a simple and efficient way to parse syslo
## Supported RFCs

Currently, the library supports the following RFCs:
- [RFC3164](https://datatracker.ietf.org/doc/html/rfc3164)
- [RFC5424](https://datatracker.ietf.org/doc/html/rfc5424)

The implementation is close to feature complete for the RFC5424 format. The `SD-IDS` are not yet supported, however feel free to open an issue if you need them.
Expand All @@ -13,6 +14,15 @@ The implementation is close to feature complete for the RFC5424 format. The `SD-

The library is designed around the `io.ByteScanner` interface. This allows for parsing in a streaming fashion as well as from memory.

```go
parser := rfc3164.NewParser()
message := []byte("<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8")
msg, err := parser.Parse(bytes.NewReader(message))
if err != nil {
panic(err)
}
```

```go
parser := rfc5424.NewParser()
message := []byte("<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - 'su root' failed for lonvick on /dev/pts/8'")
Expand All @@ -22,6 +32,7 @@ if err != nil {
}
```


The parser will take options during initialisation to allow for customisation of the parsing process. The options are passed as variadic arguments to the `NewParser` function.

```go
Expand Down
9 changes: 9 additions & 0 deletions rfc3164/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package rfc3164

import "errors"

var (
ErrInvalidPRI = errors.New("invalid PRI")
ErrInvalidTimestamp = errors.New("invalid timestamp")
ErrInvalidHostname = errors.New("invalid hostname")
)
36 changes: 36 additions & 0 deletions rfc3164/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package rfc3164

import (
"time"
)

type Message struct {
PRI PRI
Timestamp time.Time
Hostname string
Tag string
Content string
}

// PRI represents the Priority value of a syslog message.
// The PRI is a single byte that encodes the facility and severity of the message.
type PRI struct {
value byte
}

func NewPRI(value byte) (PRI, error) {
if value > 191 {
return PRI{}, ErrInvalidPRI
}
return PRI{value: value}, nil
}

// Facility returns the facility value of the PRI.
func (p PRI) Facility() byte {
return p.value & 0xF8 >> 3
}

// Severity returns the severity value of the PRI.
func (p PRI) Severity() byte {
return p.value & 0x07
}
136 changes: 136 additions & 0 deletions rfc3164/rfc3164.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package rfc3164

import (
"io"
"strings"
"time"
)

type Parser struct{}

// NewParser creates a new Parser.
func NewParser() Parser {
return Parser{}
}

func Parse(input io.ByteScanner) (Message, error) {
var m Message

pri, err := parsePRI(input)
if err != nil {
return m, err
}

timestamp, err := parseTimestamp(input)
if err != nil {
return m, err
}

hostname, err := parseHostname(input)
if err != nil {
return m, err
}

tag, content := parseMessage(input)

return Message{
PRI: PRI{pri},
Timestamp: timestamp,
Hostname: hostname,
Tag: tag,
Content: content,
}, nil
}

// parsePRI parses the PRI part of a syslog message.
func parsePRI(input io.ByteScanner) (byte, error) {
b, err := input.ReadByte()
if err != nil || b != '<' {
return 0, ErrInvalidPRI
}

PRI := byte(0)
for i := 0; i < 4; i++ {
b, err = input.ReadByte()
if err != nil {
return 0, ErrInvalidPRI
}
if b == '>' {
if PRI > 191 {
return 0, ErrInvalidPRI
}
return PRI, nil
}
if b < '0' || b > '9' {
return 0, ErrInvalidPRI
}
PRI = PRI*10 + (b - '0')
}

return 0, ErrInvalidPRI
}

func parseTimestamp(input io.ByteScanner) (time.Time, error) {
b, err := input.ReadByte()
if err != nil {
return time.Time{}, ErrInvalidTimestamp
}
if b == ' ' {
return time.Time{}, nil
}

builder := strings.Builder{}
builder.WriteByte(b)
for i := 0; i < 14; i++ {
b, err := input.ReadByte()
if err != nil {
return time.Time{}, ErrInvalidTimestamp
}
builder.WriteByte(b)
}

space, err := input.ReadByte()
if err != nil || space != ' ' {
return time.Time{}, ErrInvalidTimestamp
}

timestamp, err := time.Parse(time.Stamp, builder.String())
if err != nil {
return time.Time{}, ErrInvalidTimestamp
}
return timestamp, nil
}

func parseHostname(input io.ByteScanner) (string, error) {
builder := strings.Builder{}
for {
b, err := input.ReadByte()
if err != nil {
return "", ErrInvalidHostname
}
if b == ' ' {
break
}
builder.WriteByte(b)
}
return builder.String(), nil
}

func parseMessage(input io.ByteScanner) (tag string, content string) {
builder := strings.Builder{}
tagFound := false
for {
b, err := input.ReadByte()
if err != nil {
break
}
if (b == '[' || b == ']' || b == ':') && !tagFound {
tagFound = true
tag = builder.String()
builder.Reset()
}
builder.WriteByte(b)
}
content = builder.String()
return
}
Loading

0 comments on commit d345210

Please sign in to comment.