Add support for non-transparent framing of syslog messages (#5148)

This commit is contained in:
Leonardo Di Donato 2018-12-18 19:54:38 +01:00 committed by Daniel Nelson
parent 1334919224
commit 697381d4b5
10 changed files with 601 additions and 120 deletions

25
Gopkg.lock generated
View File

@ -583,15 +583,17 @@
version = "v0.8.1" version = "v0.8.1"
[[projects]] [[projects]]
digest = "1:a39ef049cdeee03a57b132e7d60e32711b9d949c78458da78e702d9864c54369" digest = "1:824c4cd143ee15735f1c75d9072aad46e51dd27a4ef8bf6ce723a138265b7fb3"
name = "github.com/influxdata/go-syslog" name = "github.com/influxdata/go-syslog"
packages = [ packages = [
".",
"nontransparent",
"octetcounting",
"rfc5424", "rfc5424",
"rfc5425",
] ]
pruneopts = "" pruneopts = ""
revision = "eecd51df3ad85464a2bab9b7d3a45bc1e299059e" revision = "0cd00a9f0a5e5607d5ef9a294c260f77a74e3b5a"
version = "v1.0.1" version = "v2.0.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -689,6 +691,17 @@
pruneopts = "" pruneopts = ""
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
[[projects]]
branch = "develop"
digest = "1:3e66a61a57bbbe832c338edb3a623be0deb3dec650c2f3515149658898287e37"
name = "github.com/leodido/ragel-machinery"
packages = [
".",
"parser",
]
pruneopts = ""
revision = "299bdde78165d4ca4bc7d064d8d6a4f39ac6de8c"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:7e9956922e349af0190afa0b6621befcd201072679d8e51a9047ff149f2afe93" digest = "1:7e9956922e349af0190afa0b6621befcd201072679d8e51a9047ff149f2afe93"
@ -1478,8 +1491,10 @@
"github.com/google/go-cmp/cmp", "github.com/google/go-cmp/cmp",
"github.com/gorilla/mux", "github.com/gorilla/mux",
"github.com/hashicorp/consul/api", "github.com/hashicorp/consul/api",
"github.com/influxdata/go-syslog",
"github.com/influxdata/go-syslog/nontransparent",
"github.com/influxdata/go-syslog/octetcounting",
"github.com/influxdata/go-syslog/rfc5424", "github.com/influxdata/go-syslog/rfc5424",
"github.com/influxdata/go-syslog/rfc5425",
"github.com/influxdata/tail", "github.com/influxdata/tail",
"github.com/influxdata/toml", "github.com/influxdata/toml",
"github.com/influxdata/toml/ast", "github.com/influxdata/toml/ast",

View File

@ -72,7 +72,7 @@
[[constraint]] [[constraint]]
name = "github.com/influxdata/go-syslog" name = "github.com/influxdata/go-syslog"
version = "1.0.1" version = "2.0.0"
[[constraint]] [[constraint]]
name = "github.com/influxdata/tail" name = "github.com/influxdata/tail"

View File

@ -2,7 +2,8 @@
The syslog plugin listens for syslog messages transmitted over The syslog plugin listens for syslog messages transmitted over
[UDP](https://tools.ietf.org/html/rfc5426) or [UDP](https://tools.ietf.org/html/rfc5426) or
[TCP](https://tools.ietf.org/html/rfc5425). [TCP](https://tools.ietf.org/html/rfc6587) or
[TLS](https://tools.ietf.org/html/rfc5425), with or without the octet counting framing.
Syslog messages should be formatted according to Syslog messages should be formatted according to
[RFC 5424](https://tools.ietf.org/html/rfc5424). [RFC 5424](https://tools.ietf.org/html/rfc5424).
@ -37,6 +38,16 @@ Syslog messages should be formatted according to
## 0 means unlimited. ## 0 means unlimited.
# read_timeout = "5s" # read_timeout = "5s"
## The framing technique with which it is expected that messages are transported (default = "octet-counting").
## Whether the messages come using the octect-counting (RFC5425#section-4.3.1, RFC6587#section-3.4.1),
## or the non-transparent framing technique (RFC6587#section-3.4.2).
## Must be one of "octect-counting", "non-transparent".
# framing = "octet-counting"
## The trailer to be expected in case of non-trasparent framing (default = "LF").
## Must be one of "LF", or "NUL".
# trailer = "LF"
## Whether to parse in best effort mode or not (default = false). ## Whether to parse in best effort mode or not (default = false).
## By default best effort parsing is off. ## By default best effort parsing is off.
# best_effort = false # best_effort = false
@ -49,11 +60,18 @@ Syslog messages should be formatted according to
# sdparam_separator = "_" # sdparam_separator = "_"
``` ```
#### Best Effort #### Message transport
The `framing` option only applies to streams. It governs the way we expect to receive messages within the stream.
Namely, with the [`"octet counting"`](https://tools.ietf.org/html/rfc5425#section-4.3) technique (default) or with the [`"non-transparent"`](https://tools.ietf.org/html/rfc6587#section-3.4.2) framing.
The `trailer` option only applies when `framing` option is `"non-transparent"`. It must have one of the following values: `"LF"` (default), or `"NUL"`.
#### Best effort
The [`best_effort`](https://github.com/influxdata/go-syslog#best-effort-mode) The [`best_effort`](https://github.com/influxdata/go-syslog#best-effort-mode)
option instructs the parser to extract partial but valid info from syslog option instructs the parser to extract partial but valid info from syslog
messages. If unset only full messages will be collected. messages. If unset only full messages will be collected.
#### Rsyslog Integration #### Rsyslog Integration

View File

@ -0,0 +1,62 @@
package syslog
import (
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/testutil"
"time"
)
var (
pki = testutil.NewPKI("../../../testutil/pki")
)
type testCasePacket struct {
name string
data []byte
wantBestEffort *testutil.Metric
wantStrict *testutil.Metric
werr bool
}
type testCaseStream struct {
name string
data []byte
wantBestEffort []testutil.Metric
wantStrict []testutil.Metric
werr int // how many errors we expect in the strict mode?
}
func newUDPSyslogReceiver(address string, bestEffort bool) *Syslog {
return &Syslog{
Address: address,
now: func() time.Time {
return defaultTime
},
BestEffort: bestEffort,
Separator: "_",
}
}
func newTCPSyslogReceiver(address string, keepAlive *internal.Duration, maxConn int, bestEffort bool, f Framing) *Syslog {
d := &internal.Duration{
Duration: defaultReadTimeout,
}
s := &Syslog{
Address: address,
now: func() time.Time {
return defaultTime
},
Framing: f,
ReadTimeout: d,
BestEffort: bestEffort,
Separator: "_",
}
if keepAlive != nil {
s.KeepAlivePeriod = keepAlive
}
if maxConn > 0 {
s.MaxConnections = maxConn
}
return s
}

View File

@ -0,0 +1,64 @@
package syslog
import (
"fmt"
"strings"
)
// Framing represents the framing technique we expect the messages to come.
type Framing int
const (
// OctetCounting indicates the transparent framing technique for syslog transport.
OctetCounting Framing = iota
// NonTransparent indicates the non-transparent framing technique for syslog transport.
NonTransparent
)
func (f Framing) String() string {
switch f {
case OctetCounting:
return "OCTET-COUNTING"
case NonTransparent:
return "NON-TRANSPARENT"
}
return ""
}
// UnmarshalTOML implements ability to unmarshal framing from TOML files.
func (f *Framing) UnmarshalTOML(data []byte) (err error) {
return f.UnmarshalText(data)
}
// UnmarshalText implements encoding.TextUnmarshaler
func (f *Framing) UnmarshalText(data []byte) (err error) {
s := string(data)
switch strings.ToUpper(s) {
case `OCTET-COUNTING`:
fallthrough
case `"OCTET-COUNTING"`:
fallthrough
case `'OCTET-COUNTING'`:
*f = OctetCounting
return
case `NON-TRANSPARENT`:
fallthrough
case `"NON-TRANSPARENT"`:
fallthrough
case `'NON-TRANSPARENT'`:
*f = NonTransparent
return
}
*f = -1
return fmt.Errorf("unknown framing")
}
// MarshalText implements encoding.TextMarshaler
func (f Framing) MarshalText() ([]byte, error) {
s := f.String()
if s != "" {
return []byte(s), nil
}
return nil, fmt.Errorf("unknown framing")
}

View File

@ -0,0 +1,37 @@
package syslog
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestFraming(t *testing.T) {
var f1 Framing
f1.UnmarshalTOML([]byte(`"non-transparent"`))
assert.Equal(t, NonTransparent, f1)
var f2 Framing
f2.UnmarshalTOML([]byte(`non-transparent`))
assert.Equal(t, NonTransparent, f2)
var f3 Framing
f3.UnmarshalTOML([]byte(`'non-transparent'`))
assert.Equal(t, NonTransparent, f3)
var f4 Framing
f4.UnmarshalTOML([]byte(`"octet-counting"`))
assert.Equal(t, OctetCounting, f4)
var f5 Framing
f5.UnmarshalTOML([]byte(`octet-counting`))
assert.Equal(t, OctetCounting, f5)
var f6 Framing
f6.UnmarshalTOML([]byte(`'octet-counting'`))
assert.Equal(t, OctetCounting, f6)
var f7 Framing
err := f7.UnmarshalTOML([]byte(`nope`))
assert.Equal(t, Framing(-1), f7)
assert.Error(t, err)
}

View File

@ -0,0 +1,308 @@
package syslog
import (
"crypto/tls"
"io/ioutil"
"net"
"os"
"path/filepath"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
func getTestCasesForNonTransparent() []testCaseStream {
testCases := []testCaseStream{
{
name: "1st/avg/ok",
data: []byte(`<29>1 2016-02-21T04:32:57+00:00 web1 someservice 2341 2 [origin][meta sequence="14125553" service="someservice"] "GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`),
wantStrict: []testutil.Metric{
{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"timestamp": time.Unix(1456029177, 0).UnixNano(),
"procid": "2341",
"msgid": "2",
"message": `"GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`,
"origin": true,
"meta_sequence": "14125553",
"meta_service": "someservice",
"severity_code": 5,
"facility_code": 3,
},
Tags: map[string]string{
"severity": "notice",
"facility": "daemon",
"hostname": "web1",
"appname": "someservice",
},
Time: defaultTime,
},
},
wantBestEffort: []testutil.Metric{
{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"timestamp": time.Unix(1456029177, 0).UnixNano(),
"procid": "2341",
"msgid": "2",
"message": `"GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`,
"origin": true,
"meta_sequence": "14125553",
"meta_service": "someservice",
"severity_code": 5,
"facility_code": 3,
},
Tags: map[string]string{
"severity": "notice",
"facility": "daemon",
"hostname": "web1",
"appname": "someservice",
},
Time: defaultTime,
},
},
werr: 1,
},
{
name: "1st/min/ok//2nd/min/ok",
data: []byte("<1>2 - - - - - -\n<4>11 - - - - - -\n"),
wantStrict: []testutil.Metric{
{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(2),
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(11),
"severity_code": 4,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "warning",
"facility": "kern",
},
Time: defaultTime.Add(time.Nanosecond),
},
},
wantBestEffort: []testutil.Metric{
{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(2),
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(11),
"severity_code": 4,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "warning",
"facility": "kern",
},
Time: defaultTime.Add(time.Nanosecond),
},
},
},
}
return testCases
}
func testStrictNonTransparent(t *testing.T, protocol string, address string, wantTLS bool, keepAlive *internal.Duration) {
for _, tc := range getTestCasesForNonTransparent() {
t.Run(tc.name, func(t *testing.T) {
// Creation of a strict mode receiver
receiver := newTCPSyslogReceiver(protocol+"://"+address, keepAlive, 0, false, NonTransparent)
require.NotNil(t, receiver)
if wantTLS {
receiver.ServerConfig = *pki.TLSServerConfig()
}
require.Equal(t, receiver.KeepAlivePeriod, keepAlive)
acc := &testutil.Accumulator{}
require.NoError(t, receiver.Start(acc))
defer receiver.Stop()
// Connect
var conn net.Conn
var err error
if wantTLS {
config, e := pki.TLSClientConfig().TLSConfig()
require.NoError(t, e)
config.ServerName = "localhost"
conn, err = tls.Dial(protocol, address, config)
} else {
conn, err = net.Dial(protocol, address)
defer conn.Close()
}
require.NotNil(t, conn)
require.NoError(t, err)
// Clear
acc.ClearMetrics()
acc.Errors = make([]error, 0)
// Write
_, err = conn.Write(tc.data)
conn.Close()
require.NoError(t, err)
// Wait that the the number of data points is accumulated
// Since the receiver is running concurrently
if tc.wantStrict != nil {
acc.Wait(len(tc.wantStrict))
}
// Wait the parsing error
acc.WaitError(tc.werr)
// Verify
if len(acc.Errors) != tc.werr {
t.Fatalf("Got unexpected errors. want error = %v, errors = %v\n", tc.werr, acc.Errors)
}
var got []testutil.Metric
for _, metric := range acc.Metrics {
got = append(got, *metric)
}
if !cmp.Equal(tc.wantStrict, got) {
t.Fatalf("Got (+) / Want (-)\n %s", cmp.Diff(tc.wantStrict, got))
}
})
}
}
func testBestEffortNonTransparent(t *testing.T, protocol string, address string, wantTLS bool, keepAlive *internal.Duration) {
for _, tc := range getTestCasesForNonTransparent() {
t.Run(tc.name, func(t *testing.T) {
// Creation of a best effort mode receiver
receiver := newTCPSyslogReceiver(protocol+"://"+address, keepAlive, 0, true, NonTransparent)
require.NotNil(t, receiver)
if wantTLS {
receiver.ServerConfig = *pki.TLSServerConfig()
}
require.Equal(t, receiver.KeepAlivePeriod, keepAlive)
acc := &testutil.Accumulator{}
require.NoError(t, receiver.Start(acc))
defer receiver.Stop()
// Connect
var conn net.Conn
var err error
if wantTLS {
config, e := pki.TLSClientConfig().TLSConfig()
require.NoError(t, e)
config.ServerName = "localhost"
conn, err = tls.Dial(protocol, address, config)
} else {
conn, err = net.Dial(protocol, address)
}
require.NotNil(t, conn)
require.NoError(t, err)
// Clear
acc.ClearMetrics()
acc.Errors = make([]error, 0)
// Write
_, err = conn.Write(tc.data)
require.NoError(t, err)
conn.Close()
// Wait that the the number of data points is accumulated
// Since the receiver is running concurrently
if tc.wantBestEffort != nil {
acc.Wait(len(tc.wantBestEffort))
}
// Verify
var got []testutil.Metric
for _, metric := range acc.Metrics {
got = append(got, *metric)
}
if !cmp.Equal(tc.wantBestEffort, got) {
t.Fatalf("Got (+) / Want (-)\n %s", cmp.Diff(tc.wantBestEffort, got))
}
})
}
}
func TestNonTransparentStrict_tcp(t *testing.T) {
testStrictNonTransparent(t, "tcp", address, false, nil)
}
func TestNonTransparentBestEffort_tcp(t *testing.T) {
testBestEffortNonTransparent(t, "tcp", address, false, nil)
}
func TestNonTransparentStrict_tcp_tls(t *testing.T) {
testStrictNonTransparent(t, "tcp", address, true, nil)
}
func TestNonTransparentBestEffort_tcp_tls(t *testing.T) {
testBestEffortNonTransparent(t, "tcp", address, true, nil)
}
func TestNonTransparentStrictWithKeepAlive_tcp_tls(t *testing.T) {
testStrictNonTransparent(t, "tcp", address, true, &internal.Duration{Duration: time.Minute})
}
func TestNonTransparentStrictWithZeroKeepAlive_tcp_tls(t *testing.T) {
testStrictNonTransparent(t, "tcp", address, true, &internal.Duration{Duration: 0})
}
func TestNonTransparentStrict_unix(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestStrict_unix.sock")
testStrictNonTransparent(t, "unix", sock, false, nil)
}
func TestNonTransparentBestEffort_unix(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestBestEffort_unix.sock")
testBestEffortNonTransparent(t, "unix", sock, false, nil)
}
func TestNonTransparentStrict_unix_tls(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestStrict_unix_tls.sock")
testStrictNonTransparent(t, "unix", sock, true, nil)
}
func TestNonTransparentBestEffort_unix_tls(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestBestEffort_unix_tls.sock")
testBestEffortNonTransparent(t, "unix", sock, true, nil)
}

View File

@ -16,20 +16,8 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var ( func getTestCasesForOctetCounting() []testCaseStream {
pki = testutil.NewPKI("../../../testutil/pki") testCases := []testCaseStream{
)
type testCase5425 struct {
name string
data []byte
wantBestEffort []testutil.Metric
wantStrict []testutil.Metric
werr int // how many errors we expect in the strict mode?
}
func getTestCasesForRFC5425() []testCase5425 {
testCases := []testCase5425{
{ {
name: "1st/avg/ok", name: "1st/avg/ok",
data: []byte(`188 <29>1 2016-02-21T04:32:57+00:00 web1 someservice 2341 2 [origin][meta sequence="14125553" service="someservice"] "GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`), data: []byte(`188 <29>1 2016-02-21T04:32:57+00:00 web1 someservice 2341 2 [origin][meta sequence="14125553" service="someservice"] "GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`),
@ -346,34 +334,11 @@ func getTestCasesForRFC5425() []testCase5425 {
return testCases return testCases
} }
func newTCPSyslogReceiver(address string, keepAlive *internal.Duration, maxConn int, bestEffort bool) *Syslog { func testStrictOctetCounting(t *testing.T, protocol string, address string, wantTLS bool, keepAlive *internal.Duration) {
d := &internal.Duration{ for _, tc := range getTestCasesForOctetCounting() {
Duration: defaultReadTimeout,
}
s := &Syslog{
Address: address,
now: func() time.Time {
return defaultTime
},
ReadTimeout: d,
BestEffort: bestEffort,
Separator: "_",
}
if keepAlive != nil {
s.KeepAlivePeriod = keepAlive
}
if maxConn > 0 {
s.MaxConnections = maxConn
}
return s
}
func testStrictRFC5425(t *testing.T, protocol string, address string, wantTLS bool, keepAlive *internal.Duration) {
for _, tc := range getTestCasesForRFC5425() {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// Creation of a strict mode receiver // Creation of a strict mode receiver
receiver := newTCPSyslogReceiver(protocol+"://"+address, keepAlive, 0, false) receiver := newTCPSyslogReceiver(protocol+"://"+address, keepAlive, 0, false, OctetCounting)
require.NotNil(t, receiver) require.NotNil(t, receiver)
if wantTLS { if wantTLS {
receiver.ServerConfig = *pki.TLSServerConfig() receiver.ServerConfig = *pki.TLSServerConfig()
@ -431,11 +396,11 @@ func testStrictRFC5425(t *testing.T, protocol string, address string, wantTLS bo
} }
} }
func testBestEffortRFC5425(t *testing.T, protocol string, address string, wantTLS bool, keepAlive *internal.Duration) { func testBestEffortOctetCounting(t *testing.T, protocol string, address string, wantTLS bool, keepAlive *internal.Duration) {
for _, tc := range getTestCasesForRFC5425() { for _, tc := range getTestCasesForOctetCounting() {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// Creation of a best effort mode receiver // Creation of a best effort mode receiver
receiver := newTCPSyslogReceiver(protocol+"://"+address, keepAlive, 0, true) receiver := newTCPSyslogReceiver(protocol+"://"+address, keepAlive, 0, true, OctetCounting)
require.NotNil(t, receiver) require.NotNil(t, receiver)
if wantTLS { if wantTLS {
receiver.ServerConfig = *pki.TLSServerConfig() receiver.ServerConfig = *pki.TLSServerConfig()
@ -486,58 +451,58 @@ func testBestEffortRFC5425(t *testing.T, protocol string, address string, wantTL
} }
} }
func TestStrict_tcp(t *testing.T) { func TestOctetCountingStrict_tcp(t *testing.T) {
testStrictRFC5425(t, "tcp", address, false, nil) testStrictOctetCounting(t, "tcp", address, false, nil)
} }
func TestBestEffort_tcp(t *testing.T) { func TestOctetCountingBestEffort_tcp(t *testing.T) {
testBestEffortRFC5425(t, "tcp", address, false, nil) testBestEffortOctetCounting(t, "tcp", address, false, nil)
} }
func TestStrict_tcp_tls(t *testing.T) { func TestOctetCountingStrict_tcp_tls(t *testing.T) {
testStrictRFC5425(t, "tcp", address, true, nil) testStrictOctetCounting(t, "tcp", address, true, nil)
} }
func TestBestEffort_tcp_tls(t *testing.T) { func TestOctetCountingBestEffort_tcp_tls(t *testing.T) {
testBestEffortRFC5425(t, "tcp", address, true, nil) testBestEffortOctetCounting(t, "tcp", address, true, nil)
} }
func TestStrictWithKeepAlive_tcp_tls(t *testing.T) { func TestOctetCountingStrictWithKeepAlive_tcp_tls(t *testing.T) {
testStrictRFC5425(t, "tcp", address, true, &internal.Duration{Duration: time.Minute}) testStrictOctetCounting(t, "tcp", address, true, &internal.Duration{Duration: time.Minute})
} }
func TestStrictWithZeroKeepAlive_tcp_tls(t *testing.T) { func TestOctetCountingStrictWithZeroKeepAlive_tcp_tls(t *testing.T) {
testStrictRFC5425(t, "tcp", address, true, &internal.Duration{Duration: 0}) testStrictOctetCounting(t, "tcp", address, true, &internal.Duration{Duration: 0})
} }
func TestStrict_unix(t *testing.T) { func TestOctetCountingStrict_unix(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf") tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestStrict_unix.sock") sock := filepath.Join(tmpdir, "syslog.TestStrict_unix.sock")
testStrictRFC5425(t, "unix", sock, false, nil) testStrictOctetCounting(t, "unix", sock, false, nil)
} }
func TestBestEffort_unix(t *testing.T) { func TestOctetCountingBestEffort_unix(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf") tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestBestEffort_unix.sock") sock := filepath.Join(tmpdir, "syslog.TestBestEffort_unix.sock")
testBestEffortRFC5425(t, "unix", sock, false, nil) testBestEffortOctetCounting(t, "unix", sock, false, nil)
} }
func TestStrict_unix_tls(t *testing.T) { func TestOctetCountingStrict_unix_tls(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf") tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestStrict_unix_tls.sock") sock := filepath.Join(tmpdir, "syslog.TestStrict_unix_tls.sock")
testStrictRFC5425(t, "unix", sock, true, nil) testStrictOctetCounting(t, "unix", sock, true, nil)
} }
func TestBestEffort_unix_tls(t *testing.T) { func TestOctetCountingBestEffort_unix_tls(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf") tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestBestEffort_unix_tls.sock") sock := filepath.Join(tmpdir, "syslog.TestBestEffort_unix_tls.sock")
testBestEffortRFC5425(t, "unix", sock, true, nil) testBestEffortOctetCounting(t, "unix", sock, true, nil)
} }

View File

@ -15,16 +15,8 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type testCase5426 struct { func getTestCasesForRFC5426() []testCasePacket {
name string testCases := []testCasePacket{
data []byte
wantBestEffort *testutil.Metric
wantStrict *testutil.Metric
werr bool
}
func getTestCasesForRFC5426() []testCase5426 {
testCases := []testCase5426{
{ {
name: "empty", name: "empty",
data: []byte(""), data: []byte(""),
@ -239,17 +231,6 @@ func getTestCasesForRFC5426() []testCase5426 {
return testCases return testCases
} }
func newUDPSyslogReceiver(address string, bestEffort bool) *Syslog {
return &Syslog{
Address: address,
now: func() time.Time {
return defaultTime
},
BestEffort: bestEffort,
Separator: "_",
}
}
func testRFC5426(t *testing.T, protocol string, address string, bestEffort bool) { func testRFC5426(t *testing.T, protocol string, address string, bestEffort bool) {
for _, tc := range getTestCasesForRFC5426() { for _, tc := range getTestCasesForRFC5426() {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {

View File

@ -12,8 +12,10 @@ import (
"time" "time"
"unicode" "unicode"
"github.com/influxdata/go-syslog"
"github.com/influxdata/go-syslog/nontransparent"
"github.com/influxdata/go-syslog/octetcounting"
"github.com/influxdata/go-syslog/rfc5424" "github.com/influxdata/go-syslog/rfc5424"
"github.com/influxdata/go-syslog/rfc5425"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/internal"
tlsConfig "github.com/influxdata/telegraf/internal/tls" tlsConfig "github.com/influxdata/telegraf/internal/tls"
@ -28,8 +30,10 @@ type Syslog struct {
tlsConfig.ServerConfig tlsConfig.ServerConfig
Address string `toml:"server"` Address string `toml:"server"`
KeepAlivePeriod *internal.Duration KeepAlivePeriod *internal.Duration
ReadTimeout *internal.Duration
MaxConnections int MaxConnections int
ReadTimeout *internal.Duration
Framing Framing
Trailer nontransparent.TrailerType
BestEffort bool BestEffort bool
Separator string `toml:"sdparam_separator"` Separator string `toml:"sdparam_separator"`
@ -76,6 +80,16 @@ var sampleConfig = `
## 0 means unlimited. ## 0 means unlimited.
# read_timeout = "5s" # read_timeout = "5s"
## The framing technique with which it is expected that messages are transported (default = "octet-counting").
## Whether the messages come using the octect-counting (RFC5425#section-4.3.1, RFC6587#section-3.4.1),
## or the non-transparent framing technique (RFC6587#section-3.4.2).
## Must be one of "octect-counting", "non-transparent".
# framing = "octet-counting"
## The trailer to be expected in case of non-trasparent framing (default = "LF").
## Must be one of "LF", or "NUL".
# trailer = "LF"
## Whether to parse in best effort mode or not (default = false). ## Whether to parse in best effort mode or not (default = false).
## By default best effort parsing is off. ## By default best effort parsing is off.
# best_effort = false # best_effort = false
@ -95,7 +109,7 @@ func (s *Syslog) SampleConfig() string {
// Description returns the plugin description // Description returns the plugin description
func (s *Syslog) Description() string { func (s *Syslog) Description() string {
return "Accepts syslog messages per RFC5425" return "Accepts syslog messages following RFC5424 format with transports as per RFC5426, RFC5425, or RFC6587"
} }
// Gather ... // Gather ...
@ -203,7 +217,12 @@ func getAddressParts(a string) (string, string, error) {
func (s *Syslog) listenPacket(acc telegraf.Accumulator) { func (s *Syslog) listenPacket(acc telegraf.Accumulator) {
defer s.wg.Done() defer s.wg.Done()
b := make([]byte, ipMaxPacketSize) b := make([]byte, ipMaxPacketSize)
p := rfc5424.NewParser() var p syslog.Machine
if s.BestEffort {
p = rfc5424.NewParser(rfc5424.WithBestEffort())
} else {
p = rfc5424.NewParser()
}
for { for {
n, _, err := s.udpListener.ReadFrom(b) n, _, err := s.udpListener.ReadFrom(b)
if err != nil { if err != nil {
@ -213,9 +232,9 @@ func (s *Syslog) listenPacket(acc telegraf.Accumulator) {
break break
} }
message, err := p.Parse(b[:n], &s.BestEffort) message, err := p.Parse(b[:n])
if message != nil { if message != nil {
acc.AddFields("syslog", fields(*message, s), tags(*message), s.time()) acc.AddFields("syslog", fields(message, s), tags(message), s.time())
} }
if err != nil { if err != nil {
acc.AddError(err) acc.AddError(err)
@ -276,24 +295,38 @@ func (s *Syslog) handle(conn net.Conn, acc telegraf.Accumulator) {
conn.Close() conn.Close()
}() }()
var p *rfc5425.Parser var p syslog.Parser
if s.BestEffort { emit := func(r *syslog.Result) {
p = rfc5425.NewParser(conn, rfc5425.WithBestEffort())
} else {
p = rfc5425.NewParser(conn)
}
if s.ReadTimeout != nil && s.ReadTimeout.Duration > 0 {
conn.SetReadDeadline(time.Now().Add(s.ReadTimeout.Duration))
}
p.ParseExecuting(func(r *rfc5425.Result) {
s.store(*r, acc) s.store(*r, acc)
if s.ReadTimeout != nil && s.ReadTimeout.Duration > 0 { if s.ReadTimeout != nil && s.ReadTimeout.Duration > 0 {
conn.SetReadDeadline(time.Now().Add(s.ReadTimeout.Duration)) conn.SetReadDeadline(time.Now().Add(s.ReadTimeout.Duration))
} }
}) }
// Create parser options
opts := []syslog.ParserOption{
syslog.WithListener(emit),
}
if s.BestEffort {
opts = append(opts, syslog.WithBestEffort())
}
// Select the parser to use depeding on transport framing
if s.Framing == OctetCounting {
// Octet counting transparent framing
p = octetcounting.NewParser(opts...)
} else {
// Non-transparent framing
opts = append(opts, nontransparent.WithTrailer(s.Trailer))
p = nontransparent.NewParser(opts...)
}
p.Parse(conn)
if s.ReadTimeout != nil && s.ReadTimeout.Duration > 0 {
conn.SetReadDeadline(time.Now().Add(s.ReadTimeout.Duration))
}
} }
func (s *Syslog) setKeepAlive(c *net.TCPConn) error { func (s *Syslog) setKeepAlive(c *net.TCPConn) error {
@ -310,20 +343,16 @@ func (s *Syslog) setKeepAlive(c *net.TCPConn) error {
return c.SetKeepAlivePeriod(s.KeepAlivePeriod.Duration) return c.SetKeepAlivePeriod(s.KeepAlivePeriod.Duration)
} }
func (s *Syslog) store(res rfc5425.Result, acc telegraf.Accumulator) { func (s *Syslog) store(res syslog.Result, acc telegraf.Accumulator) {
if res.Error != nil { if res.Error != nil {
acc.AddError(res.Error) acc.AddError(res.Error)
} }
if res.MessageError != nil {
acc.AddError(res.MessageError)
}
if res.Message != nil { if res.Message != nil {
msg := *res.Message acc.AddFields("syslog", fields(res.Message, s), tags(res.Message), s.time())
acc.AddFields("syslog", fields(msg, s), tags(msg), s.time())
} }
} }
func tags(msg rfc5424.SyslogMessage) map[string]string { func tags(msg syslog.Message) map[string]string {
ts := map[string]string{} ts := map[string]string{}
// Not checking assuming a minimally valid message // Not checking assuming a minimally valid message
@ -341,7 +370,7 @@ func tags(msg rfc5424.SyslogMessage) map[string]string {
return ts return ts
} }
func fields(msg rfc5424.SyslogMessage, s *Syslog) map[string]interface{} { func fields(msg syslog.Message, s *Syslog) map[string]interface{} {
// Not checking assuming a minimally valid message // Not checking assuming a minimally valid message
flds := map[string]interface{}{ flds := map[string]interface{}{
"version": msg.Version(), "version": msg.Version(),
@ -415,6 +444,8 @@ func init() {
ReadTimeout: &internal.Duration{ ReadTimeout: &internal.Duration{
Duration: defaultReadTimeout, Duration: defaultReadTimeout,
}, },
Framing: OctetCounting,
Trailer: nontransparent.LF,
Separator: "_", Separator: "_",
} }