Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
79
plugins/outputs/graylog/README.md
Normal file
79
plugins/outputs/graylog/README.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# Graylog Output Plugin
|
||||
|
||||
This plugin writes metrics to a [Graylog][graylog] instance using the
|
||||
[GELF data format][gelf].
|
||||
|
||||
⭐ Telegraf v1.0.0
|
||||
🏷️ datastore, logging
|
||||
💻 all
|
||||
|
||||
[gelf]: https://docs.graylog.org/en/3.1/pages/gelf.html#gelf-payload-specification
|
||||
[graylog]: https://graylog.org/
|
||||
|
||||
## GELF Fields
|
||||
|
||||
The [GELF spec][] spec defines a number of specific fields in a GELF payload.
|
||||
These fields may have specific requirements set by the spec and users of the
|
||||
Graylog plugin need to follow these requirements or metrics may be rejected due
|
||||
to invalid data.
|
||||
|
||||
For example, the timestamp field defined in the GELF spec, is required to be a
|
||||
UNIX timestamp. This output plugin will not modify or check the timestamp field
|
||||
if one is present and send it as-is to Graylog. If the field is absent then
|
||||
Telegraf will set the timestamp to the current time.
|
||||
|
||||
Any field not defined by the spec will have an underscore (e.g. `_`) prefixed to
|
||||
the field name.
|
||||
|
||||
[GELF spec]: https://docs.graylog.org/docs/gelf#gelf-payload-specification
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Send telegraf metrics to graylog
|
||||
[[outputs.graylog]]
|
||||
## Endpoints for your graylog instances.
|
||||
servers = ["udp://127.0.0.1:12201"]
|
||||
|
||||
## Connection timeout.
|
||||
# timeout = "5s"
|
||||
|
||||
## The field to use as the GELF short_message, if unset the static string
|
||||
## "telegraf" will be used.
|
||||
## example: short_message_field = "message"
|
||||
# short_message_field = ""
|
||||
|
||||
## According to GELF payload specification, additional fields names must be prefixed
|
||||
## with an underscore. Previous versions did not prefix custom field 'name' with underscore.
|
||||
## Set to true for backward compatibility.
|
||||
# name_field_no_prefix = false
|
||||
|
||||
## Connection retry options
|
||||
## Attempt to connect to the endpoints if the initial connection fails.
|
||||
## If 'false', Telegraf will give up after 3 connection attempt and will
|
||||
## exit with an error. If set to 'true', the plugin will retry to connect
|
||||
## to the unconnected endpoints infinitely.
|
||||
# connection_retry = false
|
||||
## Time to wait between connection retry attempts.
|
||||
# connection_retry_wait_time = "15s"
|
||||
|
||||
## Optional TLS Config
|
||||
# tls_ca = "/etc/telegraf/ca.pem"
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
```
|
||||
|
||||
Server endpoint may be specified without UDP or TCP scheme
|
||||
(eg. "127.0.0.1:12201"). In such case, UDP protocol is assumed. TLS config is
|
||||
ignored for UDP endpoints.
|
538
plugins/outputs/graylog/graylog.go
Normal file
538
plugins/outputs/graylog/graylog.go
Normal file
|
@ -0,0 +1,538 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package graylog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
_ "embed"
|
||||
"encoding/binary"
|
||||
ejson "encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
common_tls "github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/plugins/outputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
const (
|
||||
defaultEndpoint = "127.0.0.1:12201"
|
||||
defaultConnection = "wan"
|
||||
defaultMaxChunkSizeWan = 1420
|
||||
defaultMaxChunkSizeLan = 8154
|
||||
defaultScheme = "udp"
|
||||
defaultTimeout = 5 * time.Second
|
||||
defaultReconnectionTime = 15 * time.Second
|
||||
)
|
||||
|
||||
var defaultSpecFields = []string{"version", "host", "short_message", "full_message", "timestamp", "level", "facility", "line", "file"}
|
||||
|
||||
type gelfConfig struct {
|
||||
Endpoint string
|
||||
Connection string
|
||||
MaxChunkSizeWan int
|
||||
MaxChunkSizeLan int
|
||||
}
|
||||
|
||||
type gelf interface {
|
||||
io.WriteCloser
|
||||
Connect() error
|
||||
}
|
||||
|
||||
type gelfCommon struct {
|
||||
gelfConfig
|
||||
dialer *net.Dialer
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
type gelfUDP struct {
|
||||
gelfCommon
|
||||
}
|
||||
|
||||
type gelfTCP struct {
|
||||
gelfCommon
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
func newGelfWriter(cfg gelfConfig, dialer *net.Dialer, tlsConfig *tls.Config) gelf {
|
||||
if cfg.Endpoint == "" {
|
||||
cfg.Endpoint = defaultEndpoint
|
||||
}
|
||||
|
||||
if cfg.Connection == "" {
|
||||
cfg.Connection = defaultConnection
|
||||
}
|
||||
|
||||
if cfg.MaxChunkSizeWan == 0 {
|
||||
cfg.MaxChunkSizeWan = defaultMaxChunkSizeWan
|
||||
}
|
||||
|
||||
if cfg.MaxChunkSizeLan == 0 {
|
||||
cfg.MaxChunkSizeLan = defaultMaxChunkSizeLan
|
||||
}
|
||||
|
||||
scheme := defaultScheme
|
||||
parts := strings.SplitN(cfg.Endpoint, "://", 2)
|
||||
if len(parts) == 2 {
|
||||
scheme = strings.ToLower(parts[0])
|
||||
cfg.Endpoint = parts[1]
|
||||
}
|
||||
common := gelfCommon{
|
||||
gelfConfig: cfg,
|
||||
dialer: dialer,
|
||||
}
|
||||
|
||||
var g gelf
|
||||
switch scheme {
|
||||
case "tcp":
|
||||
g = &gelfTCP{gelfCommon: common, tlsConfig: tlsConfig}
|
||||
default:
|
||||
g = &gelfUDP{gelfCommon: common}
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *gelfUDP) Write(message []byte) (n int, err error) {
|
||||
compressed, err := g.compress(message)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
chunksize := g.gelfConfig.MaxChunkSizeWan
|
||||
length := compressed.Len()
|
||||
|
||||
if length > chunksize {
|
||||
chunkCountInt := int(math.Ceil(float64(length) / float64(chunksize)))
|
||||
|
||||
id := make([]byte, 8)
|
||||
_, err = rand.Read(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for i, index := 0, 0; i < length; i, index = i+chunksize, index+1 {
|
||||
packet, err := g.createChunkedMessage(index, chunkCountInt, id, &compressed)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = g.send(packet.Bytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = g.send(compressed.Bytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
n = len(message)
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (g *gelfUDP) Close() (err error) {
|
||||
if g.conn != nil {
|
||||
err = g.conn.Close()
|
||||
g.conn = nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *gelfUDP) createChunkedMessage(index, chunkCountInt int, id []byte, compressed *bytes.Buffer) (bytes.Buffer, error) {
|
||||
var packet bytes.Buffer
|
||||
|
||||
chunksize := g.getChunksize()
|
||||
|
||||
b, err := g.intToBytes(30)
|
||||
if err != nil {
|
||||
return packet, err
|
||||
}
|
||||
packet.Write(b)
|
||||
|
||||
b, err = g.intToBytes(15)
|
||||
if err != nil {
|
||||
return packet, err
|
||||
}
|
||||
packet.Write(b)
|
||||
|
||||
packet.Write(id)
|
||||
|
||||
b, err = g.intToBytes(index)
|
||||
if err != nil {
|
||||
return packet, err
|
||||
}
|
||||
packet.Write(b)
|
||||
|
||||
b, err = g.intToBytes(chunkCountInt)
|
||||
if err != nil {
|
||||
return packet, err
|
||||
}
|
||||
packet.Write(b)
|
||||
|
||||
packet.Write(compressed.Next(chunksize))
|
||||
|
||||
return packet, nil
|
||||
}
|
||||
|
||||
func (g *gelfUDP) getChunksize() int {
|
||||
if g.gelfConfig.Connection == "wan" {
|
||||
return g.gelfConfig.MaxChunkSizeWan
|
||||
}
|
||||
|
||||
if g.gelfConfig.Connection == "lan" {
|
||||
return g.gelfConfig.MaxChunkSizeLan
|
||||
}
|
||||
|
||||
return g.gelfConfig.MaxChunkSizeWan
|
||||
}
|
||||
|
||||
func (*gelfUDP) intToBytes(i int) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
err := binary.Write(buf, binary.LittleEndian, int8(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
func (*gelfUDP) compress(b []byte) (bytes.Buffer, error) {
|
||||
var buf bytes.Buffer
|
||||
comp := zlib.NewWriter(&buf)
|
||||
|
||||
if _, err := comp.Write(b); err != nil {
|
||||
return bytes.Buffer{}, err
|
||||
}
|
||||
|
||||
if err := comp.Close(); err != nil {
|
||||
return bytes.Buffer{}, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (g *gelfUDP) Connect() error {
|
||||
conn, err := g.dialer.Dial("udp", g.gelfConfig.Endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.conn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gelfUDP) send(b []byte) error {
|
||||
if g.conn == nil {
|
||||
err := g.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := g.conn.Write(b)
|
||||
if err != nil {
|
||||
_ = g.conn.Close()
|
||||
g.conn = nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *gelfTCP) Write(message []byte) (n int, err error) {
|
||||
err = g.send(message)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n = len(message)
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (g *gelfTCP) Close() (err error) {
|
||||
if g.conn != nil {
|
||||
err = g.conn.Close()
|
||||
g.conn = nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *gelfTCP) Connect() error {
|
||||
var err error
|
||||
var conn net.Conn
|
||||
if g.tlsConfig == nil {
|
||||
conn, err = g.dialer.Dial("tcp", g.gelfConfig.Endpoint)
|
||||
} else {
|
||||
conn, err = tls.DialWithDialer(g.dialer, "tcp", g.gelfConfig.Endpoint, g.tlsConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.conn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gelfTCP) send(b []byte) error {
|
||||
if g.conn == nil {
|
||||
err := g.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := g.conn.Write(b)
|
||||
if err != nil {
|
||||
_ = g.conn.Close()
|
||||
g.conn = nil
|
||||
} else {
|
||||
_, err = g.conn.Write([]byte{0}) // message delimiter
|
||||
if err != nil {
|
||||
_ = g.conn.Close()
|
||||
g.conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type Graylog struct {
|
||||
Servers []string `toml:"servers"`
|
||||
ShortMessageField string `toml:"short_message_field"`
|
||||
NameFieldNoPrefix bool `toml:"name_field_noprefix"`
|
||||
Timeout config.Duration `toml:"timeout"`
|
||||
Reconnection bool `toml:"connection_retry"`
|
||||
ReconnectionTime config.Duration `toml:"connection_retry_wait_time"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
common_tls.ClientConfig
|
||||
|
||||
writer io.Writer
|
||||
closers []io.WriteCloser
|
||||
unconnected []string
|
||||
stopRetry bool
|
||||
wg sync.WaitGroup
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (*Graylog) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (g *Graylog) Connect() error {
|
||||
if len(g.Servers) == 0 {
|
||||
g.Servers = append(g.Servers, "localhost:12201")
|
||||
}
|
||||
|
||||
tlsCfg, err := g.ClientConfig.TLSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if g.Reconnection {
|
||||
go g.connectRetry(tlsCfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
unconnected, gelfs := g.connectEndpoints(g.Servers, tlsCfg)
|
||||
if len(unconnected) > 0 {
|
||||
servers := strings.Join(unconnected, ",")
|
||||
return fmt.Errorf("connect: connection failed for %s", servers)
|
||||
}
|
||||
writers := make([]io.Writer, 0, len(gelfs))
|
||||
closers := make([]io.WriteCloser, 0, len(gelfs))
|
||||
for _, w := range gelfs {
|
||||
writers = append(writers, w)
|
||||
closers = append(closers, w)
|
||||
}
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
g.writer = io.MultiWriter(writers...)
|
||||
g.closers = closers
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graylog) connectRetry(tlsCfg *tls.Config) {
|
||||
var writers []io.Writer
|
||||
var closers []io.WriteCloser
|
||||
var attempt int64
|
||||
|
||||
g.wg.Add(1)
|
||||
|
||||
servers := make([]string, 0, len(g.Servers))
|
||||
servers = append(servers, g.Servers...)
|
||||
for {
|
||||
unconnected, gelfs := g.connectEndpoints(servers, tlsCfg)
|
||||
for _, w := range gelfs {
|
||||
writers = append(writers, w)
|
||||
closers = append(closers, w)
|
||||
}
|
||||
g.Lock()
|
||||
g.unconnected = unconnected
|
||||
stopRetry := g.stopRetry
|
||||
g.Unlock()
|
||||
if stopRetry {
|
||||
g.Log.Info("Stopping connection retries...")
|
||||
break
|
||||
}
|
||||
if len(unconnected) == 0 {
|
||||
break
|
||||
}
|
||||
attempt++
|
||||
servers := strings.Join(unconnected, ",")
|
||||
g.Log.Infof("Not connected to endpoints %s after attempt #%d...", servers, attempt)
|
||||
time.Sleep(time.Duration(g.ReconnectionTime))
|
||||
}
|
||||
g.Log.Info("Connected!")
|
||||
|
||||
g.Lock()
|
||||
g.writer = io.MultiWriter(writers...)
|
||||
g.closers = closers
|
||||
g.Unlock()
|
||||
|
||||
g.wg.Done()
|
||||
}
|
||||
|
||||
func (g *Graylog) connectEndpoints(servers []string, tlsCfg *tls.Config) ([]string, []gelf) {
|
||||
writers := make([]gelf, 0, len(servers))
|
||||
unconnected := make([]string, 0, len(servers))
|
||||
dialer := &net.Dialer{Timeout: time.Duration(g.Timeout)}
|
||||
for _, server := range servers {
|
||||
w := newGelfWriter(gelfConfig{Endpoint: server}, dialer, tlsCfg)
|
||||
if err := w.Connect(); err != nil {
|
||||
g.Log.Warnf("failed to connect to server [%s]: %v", server, err)
|
||||
unconnected = append(unconnected, server)
|
||||
continue
|
||||
}
|
||||
writers = append(writers, w)
|
||||
}
|
||||
return unconnected, writers
|
||||
}
|
||||
|
||||
func (g *Graylog) Close() error {
|
||||
g.Lock()
|
||||
g.stopRetry = true
|
||||
g.Unlock()
|
||||
g.wg.Wait()
|
||||
|
||||
for _, closer := range g.closers {
|
||||
_ = closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graylog) Write(metrics []telegraf.Metric) error {
|
||||
g.Lock()
|
||||
writer := g.writer
|
||||
g.Unlock()
|
||||
|
||||
if writer == nil {
|
||||
g.Lock()
|
||||
unconnected := strings.Join(g.unconnected, ",")
|
||||
g.Unlock()
|
||||
|
||||
return fmt.Errorf("not connected to %s", unconnected)
|
||||
}
|
||||
for _, metric := range metrics {
|
||||
values, err := g.serialize(metric)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
_, err = writer.Write([]byte(value))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing message: %q: %w", value, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graylog) serialize(metric telegraf.Metric) ([]string, error) {
|
||||
m := make(map[string]interface{})
|
||||
m["version"] = "1.1"
|
||||
m["timestamp"] = float64(metric.Time().UnixNano()) / 1_000_000_000
|
||||
m["short_message"] = "telegraf"
|
||||
if g.NameFieldNoPrefix {
|
||||
m["name"] = metric.Name()
|
||||
} else {
|
||||
m["_name"] = metric.Name()
|
||||
}
|
||||
|
||||
if host, ok := metric.GetTag("host"); ok {
|
||||
m["host"] = host
|
||||
} else {
|
||||
host, err := os.Hostname()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m["host"] = host
|
||||
}
|
||||
|
||||
for _, tag := range metric.TagList() {
|
||||
if tag.Key == "host" {
|
||||
continue
|
||||
}
|
||||
|
||||
if fieldInSpec(tag.Key) {
|
||||
m[tag.Key] = tag.Value
|
||||
} else {
|
||||
m["_"+tag.Key] = tag.Value
|
||||
}
|
||||
}
|
||||
|
||||
for _, field := range metric.FieldList() {
|
||||
if field.Key == g.ShortMessageField {
|
||||
m["short_message"] = field.Value
|
||||
} else if fieldInSpec(field.Key) {
|
||||
m[field.Key] = field.Value
|
||||
} else {
|
||||
m["_"+field.Key] = field.Value
|
||||
}
|
||||
}
|
||||
|
||||
serialized, err := ejson.Marshal(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []string{string(serialized)}, nil
|
||||
}
|
||||
|
||||
func fieldInSpec(field string) bool {
|
||||
for _, specField := range defaultSpecFields {
|
||||
if specField == field {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
outputs.Add("graylog", func() telegraf.Output {
|
||||
return &Graylog{
|
||||
Timeout: config.Duration(defaultTimeout),
|
||||
ReconnectionTime: config.Duration(defaultReconnectionTime),
|
||||
}
|
||||
})
|
||||
}
|
51
plugins/outputs/graylog/graylog_test.go
Normal file
51
plugins/outputs/graylog/graylog_test.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package graylog
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
)
|
||||
|
||||
func TestSerializer(t *testing.T) {
|
||||
m1 := metric.New("testing",
|
||||
map[string]string{
|
||||
"verb": "GET",
|
||||
"host": "hostname",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"full_message": "full",
|
||||
"short_message": "short",
|
||||
"level": "1",
|
||||
"facility": "demo",
|
||||
"line": "42",
|
||||
"file": "graylog.go",
|
||||
},
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
graylog := Graylog{}
|
||||
result, err := graylog.serialize(m1)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, r := range result {
|
||||
obj := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(r), &obj)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "1.1", obj["version"])
|
||||
require.Equal(t, "testing", obj["_name"])
|
||||
require.Equal(t, "GET", obj["_verb"])
|
||||
require.Equal(t, "hostname", obj["host"])
|
||||
require.Equal(t, "full", obj["full_message"])
|
||||
require.Equal(t, "short", obj["short_message"])
|
||||
require.Equal(t, "1", obj["level"])
|
||||
require.Equal(t, "demo", obj["facility"])
|
||||
require.Equal(t, "42", obj["line"])
|
||||
require.Equal(t, "graylog.go", obj["file"])
|
||||
}
|
||||
}
|
462
plugins/outputs/graylog/graylog_test_linux.go
Normal file
462
plugins/outputs/graylog/graylog_test_linux.go
Normal file
|
@ -0,0 +1,462 @@
|
|||
//go:build !windows && !darwin
|
||||
|
||||
package graylog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/config"
|
||||
common_tls "github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestWriteUDP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namefieldnoprefix bool
|
||||
}{
|
||||
{
|
||||
name: "default without scheme",
|
||||
},
|
||||
{
|
||||
name: "UDP",
|
||||
},
|
||||
{
|
||||
name: "UDP non-standard name field",
|
||||
namefieldnoprefix: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
address := UDPServer(t, &wg, tt.namefieldnoprefix)
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: tt.namefieldnoprefix,
|
||||
Servers: []string{"udp://" + address},
|
||||
}
|
||||
require.NoError(t, plugin.Connect())
|
||||
defer plugin.Close()
|
||||
defer wg.Wait()
|
||||
|
||||
metrics := testutil.MockMetrics()
|
||||
|
||||
// UDP scenario:
|
||||
// 4 messages are send
|
||||
require.NoError(t, plugin.Write(metrics))
|
||||
require.NoError(t, plugin.Write(metrics))
|
||||
require.NoError(t, plugin.Write(metrics))
|
||||
require.NoError(t, plugin.Write(metrics))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteTCP(t *testing.T) {
|
||||
pki := testutil.NewPKI("../../../testutil/pki")
|
||||
tlsClientConfig := pki.TLSClientConfig()
|
||||
tlsServerConfig, err := pki.TLSServerConfig().TLSConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tlsClientCfg common_tls.ClientConfig
|
||||
}{
|
||||
{
|
||||
name: "TCP",
|
||||
},
|
||||
{
|
||||
name: "TLS",
|
||||
tlsClientCfg: common_tls.ClientConfig{
|
||||
ServerName: "localhost",
|
||||
TLSCA: tlsClientConfig.TLSCA,
|
||||
TLSKey: tlsClientConfig.TLSKey,
|
||||
TLSCert: tlsClientConfig.TLSCert,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TLS no validation",
|
||||
tlsClientCfg: common_tls.ClientConfig{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "localhost",
|
||||
TLSKey: tlsClientConfig.TLSKey,
|
||||
TLSCert: tlsClientConfig.TLSCert,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
errs := make(chan error)
|
||||
address := TCPServer(t, &wg, tlsServerConfig, errs)
|
||||
|
||||
plugin := Graylog{
|
||||
ClientConfig: common_tls.ClientConfig{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "localhost",
|
||||
TLSKey: tlsClientConfig.TLSKey,
|
||||
TLSCert: tlsClientConfig.TLSCert,
|
||||
},
|
||||
Servers: []string{"tcp://" + address},
|
||||
}
|
||||
require.NoError(t, plugin.Connect())
|
||||
defer plugin.Close()
|
||||
defer wg.Wait()
|
||||
|
||||
metrics := testutil.MockMetrics()
|
||||
|
||||
// TCP scenario:
|
||||
// 4 messages are send
|
||||
// -> connection gets forcefully broken after the 2nd message (server closes connection)
|
||||
// -> the 3rd write fails with error
|
||||
// -> during the 4th write connection is restored and write is successful
|
||||
|
||||
require.NoError(t, plugin.Write(metrics))
|
||||
require.NoError(t, plugin.Write(metrics))
|
||||
require.NoError(t, <-errs)
|
||||
require.ErrorContains(t, plugin.Write(metrics), "error writing message")
|
||||
require.NoError(t, plugin.Write(metrics))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type GelfObject map[string]interface{}
|
||||
|
||||
func UDPServer(t *testing.T, wg *sync.WaitGroup, namefieldnoprefix bool) string {
|
||||
udpServer, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
recv := func() error {
|
||||
bufR := make([]byte, 1024)
|
||||
n, _, err := udpServer.ReadFrom(bufR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := bytes.NewReader(bufR[0:n])
|
||||
r, err := zlib.NewReader(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var maxDecompressionSize int64 = 500 * 1024 * 1024
|
||||
bufW := bytes.NewBuffer(nil)
|
||||
written, err := io.CopyN(bufW, r, maxDecompressionSize)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
} else if written == maxDecompressionSize {
|
||||
return fmt.Errorf("size of decoded data exceeds allowed size %d", maxDecompressionSize)
|
||||
}
|
||||
|
||||
err = r.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var obj GelfObject
|
||||
err = json.Unmarshal(bufW.Bytes(), &obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
require.Equal(t, "telegraf", obj["short_message"])
|
||||
if namefieldnoprefix {
|
||||
require.Equal(t, "test1", obj["name"])
|
||||
} else {
|
||||
require.Equal(t, "test1", obj["_name"])
|
||||
}
|
||||
require.Equal(t, "value1", obj["_tag1"])
|
||||
require.InDelta(t, float64(1), obj["_value"], testutil.DefaultDelta)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send the address with the random port to the channel for the graylog instance to use it
|
||||
address := udpServer.LocalAddr().String()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer udpServer.Close()
|
||||
defer wg.Done()
|
||||
|
||||
// in UDP scenario all 4 messages are received
|
||||
err := recv()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = recv()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = recv()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = recv()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
return address
|
||||
}
|
||||
|
||||
func TCPServer(t *testing.T, wg *sync.WaitGroup, tlsConfig *tls.Config, errs chan error) string {
|
||||
tcpServer, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send the address with the random port to the channel for the graylog instance to use it
|
||||
address := tcpServer.Addr().String()
|
||||
|
||||
accept := func() (net.Conn, error) {
|
||||
conn, err := tcpServer.Accept()
|
||||
require.NoError(t, err)
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
err = tcpConn.SetLinger(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = conn.SetDeadline(time.Now().Add(15 * time.Second))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tlsConfig != nil {
|
||||
conn = tls.Server(conn, tlsConfig)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
recv := func(conn net.Conn) error {
|
||||
bufR := make([]byte, 1)
|
||||
bufW := bytes.NewBuffer(nil)
|
||||
for {
|
||||
n, err := conn.Read(bufR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
if bufR[0] == 0 { // message delimiter found
|
||||
break
|
||||
}
|
||||
bufW.Write(bufR)
|
||||
}
|
||||
}
|
||||
|
||||
var obj GelfObject
|
||||
err = json.Unmarshal(bufW.Bytes(), &obj)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "telegraf", obj["short_message"])
|
||||
require.Equal(t, "test1", obj["_name"])
|
||||
require.Equal(t, "value1", obj["_tag1"])
|
||||
require.InDelta(t, float64(1), obj["_value"], testutil.DefaultDelta)
|
||||
return nil
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer tcpServer.Close()
|
||||
defer wg.Done()
|
||||
|
||||
fmt.Println("server: opening connection")
|
||||
conn, err := accept()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// in TCP scenario only 3 messages are received, the 3rd is lost due to simulated connection break after the 2nd
|
||||
|
||||
fmt.Println("server: receiving packet 1")
|
||||
err = recv(conn)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println("server: receiving packet 2")
|
||||
err = recv(conn)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
fmt.Println("server: closing connection")
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
errs <- err
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("server: re-opening connection")
|
||||
conn, err = accept()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Println("server: receiving packet 4")
|
||||
err = recv(conn)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
return address
|
||||
}
|
||||
|
||||
func TestWriteUDPServerDown(t *testing.T) {
|
||||
dummy, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: true,
|
||||
Servers: []string{"udp://" + dummy.LocalAddr().String()},
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
require.NoError(t, dummy.Close())
|
||||
require.NoError(t, plugin.Connect())
|
||||
}
|
||||
|
||||
func TestWriteUDPServerUnavailableOnWrite(t *testing.T) {
|
||||
dummy, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: true,
|
||||
Servers: []string{"udp://" + dummy.LocalAddr().String()},
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
require.NoError(t, plugin.Connect())
|
||||
require.NoError(t, dummy.Close())
|
||||
require.NoError(t, plugin.Write(testutil.MockMetrics()))
|
||||
}
|
||||
|
||||
func TestWriteTCPServerDown(t *testing.T) {
|
||||
dummy, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: true,
|
||||
Servers: []string{"tcp://" + dummy.Addr().String()},
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
require.NoError(t, dummy.Close())
|
||||
require.ErrorContains(t, plugin.Connect(), "connect: connection refused")
|
||||
}
|
||||
|
||||
func TestWriteTCPServerUnavailableOnWrite(t *testing.T) {
|
||||
dummy, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: true,
|
||||
Servers: []string{"tcp://" + dummy.Addr().String()},
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
require.NoError(t, plugin.Connect())
|
||||
require.NoError(t, dummy.Close())
|
||||
err = plugin.Write(testutil.MockMetrics())
|
||||
require.ErrorContains(t, err, "error writing message")
|
||||
}
|
||||
|
||||
func TestWriteUDPServerDownRetry(t *testing.T) {
|
||||
dummy, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: true,
|
||||
Servers: []string{"udp://" + dummy.LocalAddr().String()},
|
||||
Reconnection: true,
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
require.NoError(t, dummy.Close())
|
||||
require.NoError(t, plugin.Connect())
|
||||
require.NoError(t, plugin.Close())
|
||||
}
|
||||
|
||||
func TestWriteUDPServerUnavailableOnWriteRetry(t *testing.T) {
|
||||
dummy, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: true,
|
||||
Servers: []string{"udp://" + dummy.LocalAddr().String()},
|
||||
Reconnection: true,
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
require.NoError(t, plugin.Connect())
|
||||
require.NoError(t, dummy.Close())
|
||||
err = plugin.Write(testutil.MockMetrics())
|
||||
require.ErrorContains(t, err, "not connected")
|
||||
require.NoError(t, plugin.Close())
|
||||
}
|
||||
|
||||
func TestWriteTCPServerDownRetry(t *testing.T) {
|
||||
dummy, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
logger := &testutil.CaptureLogger{}
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: true,
|
||||
Servers: []string{"tcp://" + dummy.Addr().String()},
|
||||
Reconnection: true,
|
||||
ReconnectionTime: config.Duration(100 * time.Millisecond),
|
||||
Log: logger,
|
||||
}
|
||||
require.NoError(t, dummy.Close())
|
||||
require.NoError(t, plugin.Connect())
|
||||
require.Eventually(t, func() bool {
|
||||
return strings.Contains(logger.LastError(), "after attempt #5...")
|
||||
}, 5*time.Second, 100*time.Millisecond)
|
||||
require.NoError(t, plugin.Close())
|
||||
}
|
||||
|
||||
func TestWriteTCPServerUnavailableOnWriteRetry(t *testing.T) {
|
||||
dummy, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: true,
|
||||
Servers: []string{"tcp://" + dummy.Addr().String()},
|
||||
Reconnection: true,
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
require.NoError(t, plugin.Connect())
|
||||
require.NoError(t, dummy.Close())
|
||||
err = plugin.Write(testutil.MockMetrics())
|
||||
require.ErrorContains(t, err, "not connected")
|
||||
require.NoError(t, plugin.Close())
|
||||
}
|
||||
|
||||
func TestWriteTCPRetryStopping(t *testing.T) {
|
||||
dummy, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
logger := &testutil.CaptureLogger{}
|
||||
plugin := Graylog{
|
||||
NameFieldNoPrefix: true,
|
||||
Servers: []string{"tcp://" + dummy.Addr().String()},
|
||||
Reconnection: true,
|
||||
ReconnectionTime: config.Duration(10 * time.Millisecond),
|
||||
Log: logger,
|
||||
}
|
||||
require.NoError(t, dummy.Close())
|
||||
require.NoError(t, plugin.Connect())
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
require.NoError(t, plugin.Close())
|
||||
}
|
33
plugins/outputs/graylog/sample.conf
Normal file
33
plugins/outputs/graylog/sample.conf
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Send telegraf metrics to graylog
|
||||
[[outputs.graylog]]
|
||||
## Endpoints for your graylog instances.
|
||||
servers = ["udp://127.0.0.1:12201"]
|
||||
|
||||
## Connection timeout.
|
||||
# timeout = "5s"
|
||||
|
||||
## The field to use as the GELF short_message, if unset the static string
|
||||
## "telegraf" will be used.
|
||||
## example: short_message_field = "message"
|
||||
# short_message_field = ""
|
||||
|
||||
## According to GELF payload specification, additional fields names must be prefixed
|
||||
## with an underscore. Previous versions did not prefix custom field 'name' with underscore.
|
||||
## Set to true for backward compatibility.
|
||||
# name_field_no_prefix = false
|
||||
|
||||
## Connection retry options
|
||||
## Attempt to connect to the endpoints if the initial connection fails.
|
||||
## If 'false', Telegraf will give up after 3 connection attempt and will
|
||||
## exit with an error. If set to 'true', the plugin will retry to connect
|
||||
## to the unconnected endpoints infinitely.
|
||||
# connection_retry = false
|
||||
## Time to wait between connection retry attempts.
|
||||
# connection_retry_wait_time = "15s"
|
||||
|
||||
## Optional TLS Config
|
||||
# tls_ca = "/etc/telegraf/ca.pem"
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
Loading…
Add table
Add a link
Reference in a new issue