Skip to content

Commit

Permalink
Update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ysmilda committed Aug 21, 2024
1 parent 1684cbd commit 82421e8
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 63 deletions.
2 changes: 0 additions & 2 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ linters:
- asciicheck # Finds non-ASCII identifiers.
- bodyclose # Finds HTTP response bodies that are not closed.
- errorlint # Finds problems with error wrapping.
- exportloopref # Finds pointers to range variables.
- goconst # Finds repeated strings that could be replaced by a const.
- gocyclo # Finds funcs with high cyclomatic complexity.
- godot # Finds comments that do not end with a period.
- gofumpt # Finds files that were not gofumpt-ed.
- gosec # Finds security problems.
- lll # Finds lines that exceed the line length limit.
Expand Down
10 changes: 8 additions & 2 deletions rfc5424/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package rfc5424

import "time"

// Message represents a syslog message as defined in RFC 5424.
type Message struct {
PRI PRI
Version byte
Expand All @@ -11,23 +12,28 @@ type Message struct {
ProcID string
MsgID string
StructuredData string
StructuredDataElements *[]StructuredDataElements
StructuredDataElements *[]StructuredDataElement
Message 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
}

// 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
}

type StructuredDataElements struct {
// StructuredDataElement represents a structured data element in a syslog message.
type StructuredDataElement struct {
ID string
Parameters map[string]string
}
5 changes: 3 additions & 2 deletions rfc5424/options.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package rfc5424

type parseOption func(*RFC5424)
type parseOption func(*Parser)

// WithParseStructuredDataElements enables parsing of structured data elements into its seperate parts.
func WithParseStructuredDataElements() parseOption {
return func(r *RFC5424) {
return func(r *Parser) {
r.parseStructuredDataElements = true
}
}
107 changes: 55 additions & 52 deletions rfc5424/rfc5424.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,29 @@ import (
"github.com/ysmilda/syslog/pkg/characters"
)

const (
nilValue = '-'
)

type RFC5424 struct {
type Parser struct {
parseStructuredDataElements bool
}

func NewRFC5424(options ...parseOption) RFC5424 {
r := RFC5424{}
// NewParser creates a new Parser with the provided options.
func NewParser(options ...parseOption) Parser {
r := Parser{}
for _, option := range options {
option(&r)
}
return r
}

func (r RFC5424) Parse(input io.ByteScanner) (Message, error) {
// Parse tries to parse a syslog message from the input. If the input is not a valid syslog message, an error is returned.
func (r Parser) Parse(input io.ByteScanner) (Message, error) {
// Taken from https://datatracker.ietf.org/doc/html/rfc5424#section-6
// The syslog message has the following ABNF [RFC5234] definition:
// SYSLOG-MSG = HEADER SP STRUCTURED-DATA [SP MSG]
// HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID

var (
m Message
elements *[]StructuredDataElements
elements *[]StructuredDataElement
)

pri, err := parsePRI(input)
Expand Down Expand Up @@ -105,10 +103,10 @@ func (r RFC5424) Parse(input io.ByteScanner) (Message, error) {
}, nil
}

// parsePRI parses the PRI part of a syslog message according to the following rules.
// PRI = "<" PRIVAL ">"
// PRIVAL = 1*3DIGIT ; range 0 .. 191
func parsePRI(input io.ByteScanner) (byte, error) {
// PRI = "<" PRIVAL ">"
// PRIVAL = 1*3DIGIT ; range 0 .. 191

b, err := input.ReadByte()
if err != nil || b != '<' {
return 0, ErrInvalidPRI
Expand All @@ -135,10 +133,10 @@ func parsePRI(input io.ByteScanner) (byte, error) {
return 0, ErrInvalidPRI
}

// parseVersion parses the VERSION part of a syslog message according to the following rules.
// VERSION = NONZERO-DIGIT 0*2DIGIT
// NONZERO-DIGIT = %d49-57 ; 1-9
func parseVersion(input io.ByteScanner) (byte, error) {
// VERSION = NONZERO-DIGIT 0*2DIGIT
// NONZERO-DIGIT = %d49-57 ; 1-9

b, err := input.ReadByte()
if err != nil {
return 0, ErrInvalidVersion
Expand All @@ -154,22 +152,22 @@ func parseVersion(input io.ByteScanner) (byte, error) {
return b, nil
}

// parseTimestamp parses the TIMESTAMP part of a syslog message according to the following rules.
// The TIMESTAMP field is a formalized timestamp derived from [RFC3339]
// TIMESTAMP = NILVALUE / FULL-DATE "T" FULL-TIME
// FULL-DATE = DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY
// DATE-FULLYEAR = 4DIGIT
// DATE-MONTH = 2DIGIT ; 01-12
// DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
// FULL-TIME = PARTIAL-TIME TIME-OFFSET
// PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND [TIME-SECFRAC]
// TIME-HOUR = 2DIGIT ; 00-23
// TIME-MINUTE = 2DIGIT ; 00-59
// TIME-SECOND = 2DIGIT ; 00-59
// TIME-SECFRAC = "." 1*6DIGIT
// TIME-OFFSET = "Z" / TIME-NUMOFFSET
// TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE
func parseTimestamp(input io.ByteScanner) (time.Time, error) {
// The TIMESTAMP field is a formalized timestamp derived from [RFC3339]
// TIMESTAMP = NILVALUE / FULL-DATE "T" FULL-TIME
// FULL-DATE = DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY
// DATE-FULLYEAR = 4DIGIT
// DATE-MONTH = 2DIGIT ; 01-12
// DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
// FULL-TIME = PARTIAL-TIME TIME-OFFSET
// PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND [TIME-SECFRAC]
// TIME-HOUR = 2DIGIT ; 00-23
// TIME-MINUTE = 2DIGIT ; 00-59
// TIME-SECOND = 2DIGIT ; 00-59
// TIME-SECFRAC = "." 1*6DIGIT
// TIME-OFFSET = "Z" / TIME-NUMOFFSET
// TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE

isNil, err := checkNilValue(input)
if err != nil {
return time.Time{}, ErrInvalidTimestamp
Expand All @@ -193,34 +191,34 @@ func parseTimestamp(input io.ByteScanner) (time.Time, error) {
return time.Parse(time.RFC3339, builder.String())
}

// parseHostname parses the HOSTNAME part of a syslog message according to the following rules.
// HOSTNAME = NILVALUE / 1*255PRINTUSASCII
func parseHostname(input io.ByteScanner) (string, error) {
// HOSTNAME = NILVALUE / 1*255PRINTUSASCII

return parseString(input, 255, ErrInvalidHostname)
}

// parseAppName parses the APP-NAME part of a syslog message according to the following rules.
// APP-NAME = NILVALUE / 1*48PRINTUSASCII
func parseAppName(input io.ByteScanner) (string, error) {
// APP-NAME = NILVALUE / 1*48PRINTUSASCII

return parseString(input, 48, ErrInvalidAppName)
}

// parseProcID parses the PROCID part of a syslog message according to the following rules.
// PROCID = NILVALUE / 1*128PRINTUSASCII
func parseProcID(input io.ByteScanner) (string, error) {
// PROCID = NILVALUE / 1*128PRINTUSASCII

return parseString(input, 128, ErrInvalidProcID)
}

// parseMsgID parses the MSGID part of a syslog message according to the following rules.
// MSGID = NILVALUE / 1*32PRINTUSASCII
func parseMsgID(input io.ByteScanner) (string, error) {
// MSGID = NILVALUE / 1*32PRINTUSASCII

return parseString(input, 32, ErrInvalidMsgID)
}

// parseStructuredData parses the STRUCTURED-DATA part of a syslog message into a string according to the following rules.
// STRUCTURED-DATA = NILVALUE / 1*SD-ELEMENT
// SD-ELEMENT = "[" SD-ID *(SP SD-PARAM) "]"
func parseStructuredData(input io.ByteScanner) (string, error) {
// STRUCTURED-DATA = NILVALUE / 1*SD-ELEMENT
// SD-ELEMENT = "[" SD-ID *(SP SD-PARAM) "]"

isNil, err := checkNilValue(input)
if err != nil {
return "", ErrInvalidStructuredData
Expand Down Expand Up @@ -250,22 +248,22 @@ func parseStructuredData(input io.ByteScanner) (string, error) {
return builder.String(), nil
}

func parseStructuredDataElements(input string) (*[]StructuredDataElements, error) {
// SD-ELEMENT = "[" SD-ID *(SP SD-PARAM) "]"
// SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34
// SD-ID = SD-NAME
// PARAM-NAME = SD-NAME
// PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and ']' MUST be escaped.
// SD-NAME = 1*32PRINTUSASCII except '=', SP, ']', %d34 (")

// parseStructuredDataElements parses the STRUCTURED-DATA part of a syslog message according to the following rules.
// SD-ELEMENT = "[" SD-ID *(SP SD-PARAM) "]"
// SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34
// SD-ID = SD-NAME
// PARAM-NAME = SD-NAME
// PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and ']' MUST be escaped.
// SD-NAME = 1*32PRINTUSASCII except '=', SP, ']', %d34 (")
func parseStructuredDataElements(input string) (*[]StructuredDataElement, error) {
// If the input is empty, return nil
// The structured data is optional, and a nil value ('-') is parsed as an empty string.
if input == "" {
return nil, nil //nolint:nilnil
}
input = strings.TrimSpace(input)

elements := []StructuredDataElements{}
elements := []StructuredDataElement{}
// Split the input by the indicator of a new element: '['
for _, element := range strings.Split(input, "[") {
if element == "" {
Expand All @@ -291,14 +289,17 @@ func parseStructuredDataElements(input string) (*[]StructuredDataElements, error
value = strings.ReplaceAll(value, "\\]", "]")
params[parts[0]] = value
}
elements = append(elements, StructuredDataElements{
elements = append(elements, StructuredDataElement{
ID: id,
Parameters: params,
})
}
return &elements, nil
}

// parseString parses a string from the input with a maximum length according to the following rules.
// STRING = NILVALUE / 1*[max]PRINTUSASCII SP

func parseString(input io.ByteScanner, max int, e error) (string, error) {
isNil, err := checkNilValue(input)
if err != nil {
Expand All @@ -324,12 +325,14 @@ func parseString(input io.ByteScanner, max int, e error) (string, error) {
return builder.String(), nil
}

// checkNilValue checks if the input is a nil value ('-') according to the following rules.
// NILVALUE = "-"
func checkNilValue(input io.ByteScanner) (bool, error) {
b, err := input.ReadByte()
if err != nil {
return false, ErrInvalidNilValue
}
if b == nilValue {
if b == '-' {
space, err := input.ReadByte()
if err != nil || space != ' ' {
return false, ErrInvalidNilValue
Expand Down
10 changes: 5 additions & 5 deletions rfc5424/rfc5424_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestParse(t *testing.T) {
}

for _, tc := range testcases {
r := NewRFC5424()
r := NewParser()
msg, err := r.Parse(bytes.NewReader(tc.msg))
assert.Equal(t, tc.expectedMessage, msg, tc.name)
assert.Equal(t, tc.expectedError, err, tc.name)
Expand Down Expand Up @@ -475,7 +475,7 @@ func TestParseStructuredDataElements(t *testing.T) {
testcases := []struct {
name string
msg []byte
expectedSD *[]StructuredDataElements
expectedSD *[]StructuredDataElement
expectedError error
}{
{
Expand All @@ -486,7 +486,7 @@ func TestParseStructuredDataElements(t *testing.T) {
{
name: "valid structured-data-elements - example 1",
msg: []byte("[exampleSDID@32473 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"] "),
expectedSD: &[]StructuredDataElements{
expectedSD: &[]StructuredDataElement{
{
ID: "exampleSDID@32473",
Parameters: map[string]string{
Expand All @@ -500,7 +500,7 @@ func TestParseStructuredDataElements(t *testing.T) {
{
name: "valid structured-data-elements - example 2",
msg: []byte("[exampleSDID@32473 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"][examplePriority@32473 class=\"high\"] "),
expectedSD: &[]StructuredDataElements{
expectedSD: &[]StructuredDataElement{
{
ID: "exampleSDID@32473",
Parameters: map[string]string{
Expand Down Expand Up @@ -631,7 +631,7 @@ func TestCheckNilValue(t *testing.T) {
}

func BenchmarkParse(b *testing.B) {
r := RFC5424{}
r := Parser{}
msg := []byte("<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...")
for i := 0; i < b.N; i++ {
_, err := r.Parse(bytes.NewReader(msg))
Expand Down

0 comments on commit 82421e8

Please sign in to comment.