Add gelf serializer & graylog output filter. (#1167)
* add gelf serializer. * change url. * handle fields in correct format. * add graylog. * handle host field of graylog. * 1: Add go-gelf entry to Godeps to fix ci. 2: switch to github.com/Graylog2/go-gelf. * implement Close(). * Deprecated gelf serializer, and back to graylog-golang. * Update graylog-golang's hash. * move gelf related function to graylog.go. * 1: remove uneeded deps on Godeps_windows. 2: add README.md 3: add unittest. * Fix unittest on 'go test -race'
This commit is contained in:
parent
a7dfbce3d3
commit
eeeab5192b
|
@ -7,6 +7,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/datadog"
|
_ "github.com/influxdata/telegraf/plugins/outputs/datadog"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/file"
|
_ "github.com/influxdata/telegraf/plugins/outputs/file"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/graphite"
|
_ "github.com/influxdata/telegraf/plugins/outputs/graphite"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/outputs/graylog"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/influxdb"
|
_ "github.com/influxdata/telegraf/plugins/outputs/influxdb"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/instrumental"
|
_ "github.com/influxdata/telegraf/plugins/outputs/instrumental"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/kafka"
|
_ "github.com/influxdata/telegraf/plugins/outputs/kafka"
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Graylog Output Plugin
|
||||||
|
|
||||||
|
This plugin writes to a Graylog instance using the "gelf" format.
|
||||||
|
|
||||||
|
It requires a `servers` name.
|
|
@ -0,0 +1,247 @@
|
||||||
|
package graylog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
ejson "encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/outputs"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultGraylogEndpoint = "127.0.0.1:12201"
|
||||||
|
defaultConnection = "wan"
|
||||||
|
defaultMaxChunkSizeWan = 1420
|
||||||
|
defaultMaxChunkSizeLan = 8154
|
||||||
|
)
|
||||||
|
|
||||||
|
type GelfConfig struct {
|
||||||
|
GraylogEndpoint string
|
||||||
|
Connection string
|
||||||
|
MaxChunkSizeWan int
|
||||||
|
MaxChunkSizeLan int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Gelf struct {
|
||||||
|
GelfConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGelfWriter(config GelfConfig) *Gelf {
|
||||||
|
if config.GraylogEndpoint == "" {
|
||||||
|
config.GraylogEndpoint = defaultGraylogEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Connection == "" {
|
||||||
|
config.Connection = defaultConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.MaxChunkSizeWan == 0 {
|
||||||
|
config.MaxChunkSizeWan = defaultMaxChunkSizeWan
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.MaxChunkSizeLan == 0 {
|
||||||
|
config.MaxChunkSizeLan = defaultMaxChunkSizeLan
|
||||||
|
}
|
||||||
|
|
||||||
|
g := &Gelf{GelfConfig: config}
|
||||||
|
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gelf) Write(message []byte) (n int, err error) {
|
||||||
|
compressed := g.compress(message)
|
||||||
|
|
||||||
|
chunksize := g.GelfConfig.MaxChunkSizeWan
|
||||||
|
length := compressed.Len()
|
||||||
|
|
||||||
|
if length > chunksize {
|
||||||
|
|
||||||
|
chunkCountInt := int(math.Ceil(float64(length) / float64(chunksize)))
|
||||||
|
|
||||||
|
id := make([]byte, 8)
|
||||||
|
rand.Read(id)
|
||||||
|
|
||||||
|
for i, index := 0, 0; i < length; i, index = i+chunksize, index+1 {
|
||||||
|
packet := g.createChunkedMessage(index, chunkCountInt, id, &compressed)
|
||||||
|
_, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gelf) createChunkedMessage(index int, chunkCountInt int, id []byte, compressed *bytes.Buffer) bytes.Buffer {
|
||||||
|
var packet bytes.Buffer
|
||||||
|
|
||||||
|
chunksize := g.getChunksize()
|
||||||
|
|
||||||
|
packet.Write(g.intToBytes(30))
|
||||||
|
packet.Write(g.intToBytes(15))
|
||||||
|
packet.Write(id)
|
||||||
|
|
||||||
|
packet.Write(g.intToBytes(index))
|
||||||
|
packet.Write(g.intToBytes(chunkCountInt))
|
||||||
|
|
||||||
|
packet.Write(compressed.Next(chunksize))
|
||||||
|
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gelf) 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 (g *Gelf) intToBytes(i int) []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
binary.Write(buf, binary.LittleEndian, int8(i))
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gelf) compress(b []byte) bytes.Buffer {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
comp := zlib.NewWriter(&buf)
|
||||||
|
|
||||||
|
comp.Write(b)
|
||||||
|
comp.Close()
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gelf) send(b []byte) (n int, err error) {
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp", g.GelfConfig.GraylogEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.DialUDP("udp", nil, udpAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = conn.Write(b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Graylog struct {
|
||||||
|
Servers []string
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleConfig = `
|
||||||
|
## Udp endpoint for your graylog instance.
|
||||||
|
servers = ["127.0.0.1:12201", "192.168.1.1:12201"]
|
||||||
|
`
|
||||||
|
|
||||||
|
func (g *Graylog) Connect() error {
|
||||||
|
writers := []io.Writer{}
|
||||||
|
|
||||||
|
if len(g.Servers) == 0 {
|
||||||
|
g.Servers = append(g.Servers, "localhost:12201")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range g.Servers {
|
||||||
|
w := NewGelfWriter(GelfConfig{GraylogEndpoint: server})
|
||||||
|
writers = append(writers, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.writer = io.MultiWriter(writers...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graylog) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graylog) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graylog) Description() string {
|
||||||
|
return "Send telegraf metrics to graylog(s)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graylog) Write(metrics []telegraf.Metric) error {
|
||||||
|
if len(metrics) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, metric := range metrics {
|
||||||
|
values, err := serialize(metric)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range values {
|
||||||
|
_, err := g.writer.Write([]byte(value))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("FAILED to write message: %s, %s", value, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialize(metric telegraf.Metric) ([]string, error) {
|
||||||
|
out := []string{}
|
||||||
|
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
m["version"] = "1.1"
|
||||||
|
m["timestamp"] = metric.UnixNano() / 1000000000
|
||||||
|
m["short_message"] = " "
|
||||||
|
m["name"] = metric.Name()
|
||||||
|
|
||||||
|
if host, ok := metric.Tags()["host"]; ok {
|
||||||
|
m["host"] = host
|
||||||
|
} else {
|
||||||
|
host, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
m["host"] = host
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range metric.Fields() {
|
||||||
|
nkey := fmt.Sprintf("_%s", key)
|
||||||
|
m[nkey] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized, err := ejson.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
out = append(out, string(serialized))
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
outputs.Add("graylog", func() telegraf.Output {
|
||||||
|
return &Graylog{}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package graylog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWrite(t *testing.T) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go UDPServer(t, &wg)
|
||||||
|
|
||||||
|
i := Graylog{
|
||||||
|
Servers: []string{"127.0.0.1:12201"},
|
||||||
|
}
|
||||||
|
i.Connect()
|
||||||
|
|
||||||
|
metrics := testutil.MockMetrics()
|
||||||
|
metrics = append(metrics, testutil.TestMetric(int64(1234567890)))
|
||||||
|
|
||||||
|
i.Write(metrics)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
i.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type GelfObject map[string]interface{}
|
||||||
|
|
||||||
|
func UDPServer(t *testing.T, wg *sync.WaitGroup) {
|
||||||
|
serverAddr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:12201")
|
||||||
|
udpServer, _ := net.ListenUDP("udp", serverAddr)
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
bufR := make([]byte, 1024)
|
||||||
|
n, _, _ := udpServer.ReadFromUDP(bufR)
|
||||||
|
|
||||||
|
b := bytes.NewReader(bufR[0:n])
|
||||||
|
r, _ := zlib.NewReader(b)
|
||||||
|
|
||||||
|
bufW := bytes.NewBuffer(nil)
|
||||||
|
io.Copy(bufW, r)
|
||||||
|
r.Close()
|
||||||
|
|
||||||
|
var obj GelfObject
|
||||||
|
json.Unmarshal(bufW.Bytes(), &obj)
|
||||||
|
assert.Equal(t, obj["_value"], float64(1))
|
||||||
|
}
|
Loading…
Reference in New Issue