Sumo Logic output plugin (#8023)
This commit is contained in:
parent
8cd0fe5e7b
commit
e9dcade0a8
|
|
@ -437,3 +437,4 @@ For documentation on the latest development code see the [documentation index][d
|
||||||
* [udp](./plugins/outputs/socket_writer)
|
* [udp](./plugins/outputs/socket_writer)
|
||||||
* [warp10](./plugins/outputs/warp10)
|
* [warp10](./plugins/outputs/warp10)
|
||||||
* [wavefront](./plugins/outputs/wavefront)
|
* [wavefront](./plugins/outputs/wavefront)
|
||||||
|
* [sumologic](./plugins/outputs/sumologic)
|
||||||
|
|
|
||||||
|
|
@ -1267,6 +1267,62 @@
|
||||||
# # location = "eu-north0"
|
# # location = "eu-north0"
|
||||||
|
|
||||||
|
|
||||||
|
# # A plugin that can transmit metrics to Sumo Logic HTTP Source
|
||||||
|
# [[outputs.sumologic]]
|
||||||
|
# ## Unique URL generated for your HTTP Metrics Source.
|
||||||
|
# ## This is the address to send metrics to.
|
||||||
|
# # url = "https://events.sumologic.net/receiver/v1/http/<UniqueHTTPCollectorCode>"
|
||||||
|
#
|
||||||
|
# ## Data format to be used for sending metrics.
|
||||||
|
# ## This will set the "Content-Type" header accordingly.
|
||||||
|
# ## Currently supported formats:
|
||||||
|
# ## * graphite - for Content-Type of application/vnd.sumologic.graphite
|
||||||
|
# ## * carbon2 - for Content-Type of application/vnd.sumologic.carbon2
|
||||||
|
# ## * prometheus - for Content-Type of application/vnd.sumologic.prometheus
|
||||||
|
# ##
|
||||||
|
# ## More information can be found at:
|
||||||
|
# ## https://help.sumologic.com/03Send-Data/Sources/02Sources-for-Hosted-Collectors/HTTP-Source/Upload-Metrics-to-an-HTTP-Source#content-type-headers-for-metrics
|
||||||
|
# ##
|
||||||
|
# ## NOTE:
|
||||||
|
# ## When unset, telegraf will by default use the influx serializer which is currently unsupported
|
||||||
|
# ## in HTTP Source.
|
||||||
|
# data_format = "carbon2"
|
||||||
|
#
|
||||||
|
# ## Timeout used for HTTP request
|
||||||
|
# # timeout = "5s"
|
||||||
|
#
|
||||||
|
# ## HTTP method, one of: "POST" or "PUT". "POST" is used by default if unset.
|
||||||
|
# # method = "POST"
|
||||||
|
#
|
||||||
|
# ## Max HTTP request body size in bytes before compression (if applied).
|
||||||
|
# ## By default 1MB is recommended.
|
||||||
|
# ## NOTE:
|
||||||
|
# ## Bear in mind that in some serializer a metric even though serialized to multiple
|
||||||
|
# ## lines cannot be split any further so setting this very low might not work
|
||||||
|
# ## as expected.
|
||||||
|
# # max_request_body_size = 1_000_000
|
||||||
|
#
|
||||||
|
# ## Additional, Sumo specific options.
|
||||||
|
# ## Full list can be found here:
|
||||||
|
# ## https://help.sumologic.com/03Send-Data/Sources/02Sources-for-Hosted-Collectors/HTTP-Source/Upload-Metrics-to-an-HTTP-Source#supported-http-headers
|
||||||
|
#
|
||||||
|
# ## Desired source name.
|
||||||
|
# ## Useful if you want to override the source name configured for the source.
|
||||||
|
# # source_name = ""
|
||||||
|
#
|
||||||
|
# ## Desired host name.
|
||||||
|
# ## Useful if you want to override the source host configured for the source.
|
||||||
|
# # source_host = ""
|
||||||
|
#
|
||||||
|
# ## Desired source category.
|
||||||
|
# ## Useful if you want to override the source category configured for the source.
|
||||||
|
# # source_category = ""
|
||||||
|
#
|
||||||
|
# ## Comma-separated key=value list of dimensions to apply to every metric.
|
||||||
|
# ## Custom dimensions will allow you to query your metrics at a more granular level.
|
||||||
|
# # dimensions = ""
|
||||||
|
|
||||||
|
|
||||||
# # Configuration for Syslog server to send metrics to
|
# # Configuration for Syslog server to send metrics to
|
||||||
# [[outputs.syslog]]
|
# [[outputs.syslog]]
|
||||||
# ## URL to connect to
|
# ## URL to connect to
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/riemann_legacy"
|
_ "github.com/influxdata/telegraf/plugins/outputs/riemann_legacy"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/socket_writer"
|
_ "github.com/influxdata/telegraf/plugins/outputs/socket_writer"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/stackdriver"
|
_ "github.com/influxdata/telegraf/plugins/outputs/stackdriver"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/outputs/sumologic"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/syslog"
|
_ "github.com/influxdata/telegraf/plugins/outputs/syslog"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/warp10"
|
_ "github.com/influxdata/telegraf/plugins/outputs/warp10"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/wavefront"
|
_ "github.com/influxdata/telegraf/plugins/outputs/wavefront"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
# Sumo Logic Output Plugin
|
||||||
|
|
||||||
|
This plugin sends metrics to [Sumo Logic HTTP Source](https://help.sumologic.com/03Send-Data/Sources/02Sources-for-Hosted-Collectors/HTTP-Source/Upload-Metrics-to-an-HTTP-Source)
|
||||||
|
in HTTP messages, encoded using one of the output data formats.
|
||||||
|
|
||||||
|
Currently metrics can be sent using one of the following data formats, supported
|
||||||
|
by Sumologic HTTP Source:
|
||||||
|
|
||||||
|
* `graphite` - for Content-Type of `application/vnd.sumologic.graphite`
|
||||||
|
* `carbon2` - for Content-Type of `application/vnd.sumologic.carbon2`
|
||||||
|
* `prometheus` - for Content-Type of `application/vnd.sumologic.prometheus`
|
||||||
|
|
||||||
|
### Configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# A plugin that can send metrics to Sumo Logic HTTP metric collector.
|
||||||
|
[[outputs.sumologic]]
|
||||||
|
## Unique URL generated for your HTTP Metrics Source.
|
||||||
|
## This is the address to send metrics to.
|
||||||
|
# url = "https://events.sumologic.net/receiver/v1/http/<UniqueHTTPCollectorCode>"
|
||||||
|
|
||||||
|
## Data format to be used for sending metrics.
|
||||||
|
## This will set the "Content-Type" header accordingly.
|
||||||
|
## Currently supported formats:
|
||||||
|
## * graphite - for Content-Type of application/vnd.sumologic.graphite
|
||||||
|
## * carbon2 - for Content-Type of application/vnd.sumologic.carbon2
|
||||||
|
## * prometheus - for Content-Type of application/vnd.sumologic.prometheus
|
||||||
|
##
|
||||||
|
## More information can be found at:
|
||||||
|
## https://help.sumologic.com/03Send-Data/Sources/02Sources-for-Hosted-Collectors/HTTP-Source/Upload-Metrics-to-an-HTTP-Source#content-type-headers-for-metrics
|
||||||
|
##
|
||||||
|
## NOTE:
|
||||||
|
## When unset, telegraf will by default use the influx serializer which is currently unsupported
|
||||||
|
## in HTTP Source.
|
||||||
|
data_format = "carbon2"
|
||||||
|
|
||||||
|
## Timeout used for HTTP request
|
||||||
|
# timeout = "5s"
|
||||||
|
|
||||||
|
## HTTP method, one of: "POST" or "PUT". "POST" is used by default if unset.
|
||||||
|
# method = "POST"
|
||||||
|
|
||||||
|
## Max HTTP request body size in bytes before compression (if applied).
|
||||||
|
## By default 1MB is recommended.
|
||||||
|
## NOTE:
|
||||||
|
## Bear in mind that in some serializer a metric even though serialized to multiple
|
||||||
|
## lines cannot be split any further so setting this very low might not work
|
||||||
|
## as expected.
|
||||||
|
# max_request_body_size = 1_000_000
|
||||||
|
|
||||||
|
## Additional, Sumo specific options.
|
||||||
|
## Full list can be found here:
|
||||||
|
## https://help.sumologic.com/03Send-Data/Sources/02Sources-for-Hosted-Collectors/HTTP-Source/Upload-Metrics-to-an-HTTP-Source#supported-http-headers
|
||||||
|
|
||||||
|
## Desired source name.
|
||||||
|
## Useful if you want to override the source name configured for the source.
|
||||||
|
# source_name = ""
|
||||||
|
|
||||||
|
## Desired host name.
|
||||||
|
## Useful if you want to override the source host configured for the source.
|
||||||
|
# source_host = ""
|
||||||
|
|
||||||
|
## Desired source category.
|
||||||
|
## Useful if you want to override the source category configured for the source.
|
||||||
|
# source_category = ""
|
||||||
|
|
||||||
|
## Comma-separated key=value list of dimensions to apply to every metric.
|
||||||
|
## Custom dimensions will allow you to query your metrics at a more granular level.
|
||||||
|
# dimensions = ""
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
package sumologic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/config"
|
||||||
|
"github.com/influxdata/telegraf/internal"
|
||||||
|
"github.com/influxdata/telegraf/plugins/outputs"
|
||||||
|
"github.com/influxdata/telegraf/plugins/serializers"
|
||||||
|
"github.com/influxdata/telegraf/plugins/serializers/carbon2"
|
||||||
|
"github.com/influxdata/telegraf/plugins/serializers/graphite"
|
||||||
|
"github.com/influxdata/telegraf/plugins/serializers/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sampleConfig = `
|
||||||
|
## Unique URL generated for your HTTP Metrics Source.
|
||||||
|
## This is the address to send metrics to.
|
||||||
|
# url = "https://events.sumologic.net/receiver/v1/http/<UniqueHTTPCollectorCode>"
|
||||||
|
|
||||||
|
## Data format to be used for sending metrics.
|
||||||
|
## This will set the "Content-Type" header accordingly.
|
||||||
|
## Currently supported formats:
|
||||||
|
## * graphite - for Content-Type of application/vnd.sumologic.graphite
|
||||||
|
## * carbon2 - for Content-Type of application/vnd.sumologic.carbon2
|
||||||
|
## * prometheus - for Content-Type of application/vnd.sumologic.prometheus
|
||||||
|
##
|
||||||
|
## More information can be found at:
|
||||||
|
## https://help.sumologic.com/03Send-Data/Sources/02Sources-for-Hosted-Collectors/HTTP-Source/Upload-Metrics-to-an-HTTP-Source#content-type-headers-for-metrics
|
||||||
|
##
|
||||||
|
## NOTE:
|
||||||
|
## When unset, telegraf will by default use the influx serializer which is currently unsupported
|
||||||
|
## in HTTP Source.
|
||||||
|
data_format = "carbon2"
|
||||||
|
|
||||||
|
## Timeout used for HTTP request
|
||||||
|
# timeout = "5s"
|
||||||
|
|
||||||
|
## HTTP method, one of: "POST" or "PUT". "POST" is used by default if unset.
|
||||||
|
# method = "POST"
|
||||||
|
|
||||||
|
## Max HTTP request body size in bytes before compression (if applied).
|
||||||
|
## By default 1MB is recommended.
|
||||||
|
## NOTE:
|
||||||
|
## Bear in mind that in some serializer a metric even though serialized to multiple
|
||||||
|
## lines cannot be split any further so setting this very low might not work
|
||||||
|
## as expected.
|
||||||
|
# max_request_body_size = 1_000_000
|
||||||
|
|
||||||
|
## Additional, Sumo specific options.
|
||||||
|
## Full list can be found here:
|
||||||
|
## https://help.sumologic.com/03Send-Data/Sources/02Sources-for-Hosted-Collectors/HTTP-Source/Upload-Metrics-to-an-HTTP-Source#supported-http-headers
|
||||||
|
|
||||||
|
## Desired source name.
|
||||||
|
## Useful if you want to override the source name configured for the source.
|
||||||
|
# source_name = ""
|
||||||
|
|
||||||
|
## Desired host name.
|
||||||
|
## Useful if you want to override the source host configured for the source.
|
||||||
|
# source_host = ""
|
||||||
|
|
||||||
|
## Desired source category.
|
||||||
|
## Useful if you want to override the source category configured for the source.
|
||||||
|
# source_category = ""
|
||||||
|
|
||||||
|
## Comma-separated key=value list of dimensions to apply to every metric.
|
||||||
|
## Custom dimensions will allow you to query your metrics at a more granular level.
|
||||||
|
# dimensions = ""
|
||||||
|
`
|
||||||
|
|
||||||
|
defaultClientTimeout = 5 * time.Second
|
||||||
|
defaultMethod = http.MethodPost
|
||||||
|
defaultMaxRequestBodySize = 1_000_000
|
||||||
|
|
||||||
|
contentTypeHeader = "Content-Type"
|
||||||
|
carbon2ContentType = "application/vnd.sumologic.carbon2"
|
||||||
|
graphiteContentType = "application/vnd.sumologic.graphite"
|
||||||
|
prometheusContentType = "application/vnd.sumologic.prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type header string
|
||||||
|
|
||||||
|
const (
|
||||||
|
sourceNameHeader header = `X-Sumo-Name`
|
||||||
|
sourceHostHeader header = `X-Sumo-Host`
|
||||||
|
sourceCategoryHeader header = `X-Sumo-Category`
|
||||||
|
dimensionsHeader header = `X-Sumo-Dimensions`
|
||||||
|
)
|
||||||
|
|
||||||
|
type SumoLogic struct {
|
||||||
|
URL string `toml:"url"`
|
||||||
|
Timeout internal.Duration `toml:"timeout"`
|
||||||
|
Method string `toml:"method"`
|
||||||
|
MaxRequstBodySize config.Size `toml:"max_request_body_size"`
|
||||||
|
|
||||||
|
SourceName string `toml:"source_name"`
|
||||||
|
SourceHost string `toml:"source_host"`
|
||||||
|
SourceCategory string `toml:"source_category"`
|
||||||
|
Dimensions string `toml:"dimensions"`
|
||||||
|
|
||||||
|
client *http.Client
|
||||||
|
serializer serializers.Serializer
|
||||||
|
|
||||||
|
err error
|
||||||
|
headers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SumoLogic) SetSerializer(serializer serializers.Serializer) {
|
||||||
|
if s.headers == nil {
|
||||||
|
s.headers = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch serializer.(type) {
|
||||||
|
case *carbon2.Serializer:
|
||||||
|
s.headers[contentTypeHeader] = carbon2ContentType
|
||||||
|
case *graphite.GraphiteSerializer:
|
||||||
|
s.headers[contentTypeHeader] = graphiteContentType
|
||||||
|
case *prometheus.Serializer:
|
||||||
|
s.headers[contentTypeHeader] = prometheusContentType
|
||||||
|
|
||||||
|
default:
|
||||||
|
s.err = errors.Errorf("unsupported serializer %T", serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.serializer = serializer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SumoLogic) createClient(ctx context.Context) (*http.Client, error) {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
},
|
||||||
|
Timeout: s.Timeout.Duration,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SumoLogic) Connect() error {
|
||||||
|
if s.err != nil {
|
||||||
|
return errors.Wrap(s.err, "sumologic: incorrect configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Method == "" {
|
||||||
|
s.Method = defaultMethod
|
||||||
|
}
|
||||||
|
s.Method = strings.ToUpper(s.Method)
|
||||||
|
if s.Method != http.MethodPost && s.Method != http.MethodPut {
|
||||||
|
return fmt.Errorf("invalid method [%s] %s", s.URL, s.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Timeout.Duration == 0 {
|
||||||
|
s.Timeout.Duration = defaultClientTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := s.createClient(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.client = client
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SumoLogic) Close() error {
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SumoLogic) Description() string {
|
||||||
|
return "A plugin that can transmit metrics to Sumo Logic HTTP Source"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SumoLogic) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SumoLogic) Write(metrics []telegraf.Metric) error {
|
||||||
|
if s.err != nil {
|
||||||
|
return errors.Wrap(s.err, "sumologic: incorrect configuration")
|
||||||
|
}
|
||||||
|
if s.serializer == nil {
|
||||||
|
return errors.New("sumologic: serializer unset")
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody, err := s.serializer.SerializeBatch(metrics)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := len(reqBody); l > int(s.MaxRequstBodySize) {
|
||||||
|
var (
|
||||||
|
// Do the rounded up integer division
|
||||||
|
numChunks = (l + int(s.MaxRequstBodySize) - 1) / int(s.MaxRequstBodySize)
|
||||||
|
chunks = make([][]byte, 0, numChunks)
|
||||||
|
numMetrics = len(metrics)
|
||||||
|
// Do the rounded up integer division
|
||||||
|
stepMetrics = (numMetrics + numChunks - 1) / numChunks
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < numMetrics; i += stepMetrics {
|
||||||
|
boundary := i + stepMetrics
|
||||||
|
if boundary > numMetrics {
|
||||||
|
boundary = numMetrics - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkBody, err := s.serializer.SerializeBatch(metrics[i:boundary])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
chunks = append(chunks, chunkBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.writeRequestChunks(chunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.write(reqBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SumoLogic) writeRequestChunks(chunks [][]byte) error {
|
||||||
|
for _, reqChunk := range chunks {
|
||||||
|
if err := s.write(reqChunk); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SumoLogic) write(reqBody []byte) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
buff bytes.Buffer
|
||||||
|
gz = gzip.NewWriter(&buff)
|
||||||
|
)
|
||||||
|
|
||||||
|
if _, err = gz.Write(reqBody); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = gz.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(s.Method, s.URL, &buff)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Encoding", "gzip")
|
||||||
|
req.Header.Set("User-Agent", internal.ProductToken())
|
||||||
|
|
||||||
|
// Set headers coming from the configuration.
|
||||||
|
for k, v := range s.headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
setHeaderIfSetInConfig(req, sourceNameHeader, s.SourceName)
|
||||||
|
setHeaderIfSetInConfig(req, sourceHostHeader, s.SourceHost)
|
||||||
|
setHeaderIfSetInConfig(req, sourceCategoryHeader, s.SourceCategory)
|
||||||
|
setHeaderIfSetInConfig(req, dimensionsHeader, s.Dimensions)
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "sumologic: failed sending request to [%s]", s.URL)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return errors.Errorf(
|
||||||
|
"sumologic: when writing to [%s] received status code: %d",
|
||||||
|
s.URL, resp.StatusCode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setHeaderIfSetInConfig(r *http.Request, h header, value string) {
|
||||||
|
if value != "" {
|
||||||
|
r.Header.Set(string(h), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Default() *SumoLogic {
|
||||||
|
return &SumoLogic{
|
||||||
|
Timeout: internal.Duration{
|
||||||
|
Duration: defaultClientTimeout,
|
||||||
|
},
|
||||||
|
Method: defaultMethod,
|
||||||
|
MaxRequstBodySize: defaultMaxRequestBodySize,
|
||||||
|
headers: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
outputs.Add("sumologic", func() telegraf.Output {
|
||||||
|
return Default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,576 @@
|
||||||
|
package sumologic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/config"
|
||||||
|
"github.com/influxdata/telegraf/internal"
|
||||||
|
"github.com/influxdata/telegraf/metric"
|
||||||
|
"github.com/influxdata/telegraf/plugins/serializers"
|
||||||
|
"github.com/influxdata/telegraf/plugins/serializers/carbon2"
|
||||||
|
"github.com/influxdata/telegraf/plugins/serializers/graphite"
|
||||||
|
"github.com/influxdata/telegraf/plugins/serializers/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getMetric(t *testing.T) telegraf.Metric {
|
||||||
|
m, err := metric.New(
|
||||||
|
"cpu",
|
||||||
|
map[string]string{},
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": 42.0,
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetrics(t *testing.T) []telegraf.Metric {
|
||||||
|
const count = 10
|
||||||
|
var metrics = make([]telegraf.Metric, count)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
m, err := metric.New(
|
||||||
|
fmt.Sprintf("cpu-%d", i),
|
||||||
|
map[string]string{
|
||||||
|
"ec2_instance": "aws-129038123",
|
||||||
|
"image": "aws-ami-1234567890",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"idle": 5876876,
|
||||||
|
"steal": 5876876,
|
||||||
|
"system": 5876876,
|
||||||
|
"user": 5876876,
|
||||||
|
"temp": 70.0,
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
metrics[i] = m
|
||||||
|
}
|
||||||
|
return metrics
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidMethod(t *testing.T) {
|
||||||
|
plugin := &SumoLogic{
|
||||||
|
URL: "",
|
||||||
|
Method: http.MethodGet,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Connect()
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethod(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.NotFoundHandler())
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
u, err := url.Parse(fmt.Sprintf("http://%s", ts.Listener.Addr().String()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
plugin func() *SumoLogic
|
||||||
|
expectedMethod string
|
||||||
|
connectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default method is POST",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
expectedMethod: http.MethodPost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "put is okay",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
s.Method = http.MethodPut
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
expectedMethod: http.MethodPut,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get is invalid",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
s.Method = http.MethodGet
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
connectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method is case insensitive",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
s.Method = "poST"
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
expectedMethod: http.MethodPost,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, tt.expectedMethod, r.Method)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
serializer, err := carbon2.NewSerializer()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
plugin := tt.plugin()
|
||||||
|
plugin.SetSerializer(serializer)
|
||||||
|
err = plugin.Connect()
|
||||||
|
if tt.connectError {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = plugin.Write([]telegraf.Metric{getMetric(t)})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusCode(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.NotFoundHandler())
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
u, err := url.Parse(fmt.Sprintf("http://%s", ts.Listener.Addr().String()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pluginFn := func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
plugin *SumoLogic
|
||||||
|
statusCode int
|
||||||
|
errFunc func(t *testing.T, err error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
plugin: pluginFn(),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
errFunc: func(t *testing.T, err error) {
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1xx status is an error",
|
||||||
|
plugin: pluginFn(),
|
||||||
|
statusCode: http.StatusSwitchingProtocols,
|
||||||
|
errFunc: func(t *testing.T, err error) {
|
||||||
|
require.Error(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3xx status is an error",
|
||||||
|
plugin: pluginFn(),
|
||||||
|
statusCode: http.StatusMultipleChoices,
|
||||||
|
errFunc: func(t *testing.T, err error) {
|
||||||
|
require.Error(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "4xx status is an error",
|
||||||
|
plugin: pluginFn(),
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
errFunc: func(t *testing.T, err error) {
|
||||||
|
require.Error(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(tt.statusCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
serializer, err := carbon2.NewSerializer()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tt.plugin.SetSerializer(serializer)
|
||||||
|
err = tt.plugin.Connect()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = tt.plugin.Write([]telegraf.Metric{getMetric(t)})
|
||||||
|
tt.errFunc(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContentType(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.NotFoundHandler())
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
u, err := url.Parse(fmt.Sprintf("http://%s", ts.Listener.Addr().String()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
carbon2Serializer, err := carbon2.NewSerializer()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
plugin func() *SumoLogic
|
||||||
|
expectedErr bool
|
||||||
|
serializer serializers.Serializer
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "carbon2 is supported",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
s.headers = map[string]string{
|
||||||
|
contentTypeHeader: carbon2ContentType,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
serializer: carbon2Serializer,
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "graphite is supported",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
s.headers = map[string]string{
|
||||||
|
contentTypeHeader: graphiteContentType,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
serializer: &graphite.GraphiteSerializer{},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prometheus is supported",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
s.headers = map[string]string{
|
||||||
|
contentTypeHeader: prometheusContentType,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
serializer: &prometheus.Serializer{},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
plugin := tt.plugin()
|
||||||
|
|
||||||
|
plugin.SetSerializer(tt.serializer)
|
||||||
|
|
||||||
|
err := plugin.Connect()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = plugin.Write([]telegraf.Metric{getMetric(t)})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContentEncodingGzip(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.NotFoundHandler())
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
u, err := url.Parse(fmt.Sprintf("http://%s", ts.Listener.Addr().String()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
plugin func() *SumoLogic
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default content_encoding=gzip works",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, "gzip", r.Header.Get("Content-Encoding"))
|
||||||
|
|
||||||
|
body, err := gzip.NewReader(r.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
payload, err := ioutil.ReadAll(body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, string(payload), "metric=cpu field=value 42 0\n")
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
serializer, err := carbon2.NewSerializer()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
plugin := tt.plugin()
|
||||||
|
|
||||||
|
plugin.SetSerializer(serializer)
|
||||||
|
err = plugin.Connect()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = plugin.Write([]telegraf.Metric{getMetric(t)})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestHandlerFunc func(t *testing.T, w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
|
func TestDefaultUserAgent(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.NotFoundHandler())
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
u, err := url.Parse(fmt.Sprintf("http://%s", ts.Listener.Addr().String()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("default-user-agent", func(t *testing.T) {
|
||||||
|
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, internal.ProductToken(), r.Header.Get("User-Agent"))
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
plugin := &SumoLogic{
|
||||||
|
URL: u.String(),
|
||||||
|
Method: defaultMethod,
|
||||||
|
MaxRequstBodySize: Default().MaxRequstBodySize,
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer, err := carbon2.NewSerializer()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
plugin.SetSerializer(serializer)
|
||||||
|
err = plugin.Connect()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = plugin.Write([]telegraf.Metric{getMetric(t)})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTOMLConfig(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
configBytes []byte
|
||||||
|
expectedError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "carbon2 content type is supported",
|
||||||
|
configBytes: []byte(`
|
||||||
|
[[outputs.sumologic]]
|
||||||
|
url = "https://localhost:3000"
|
||||||
|
data_format = "carbon2"
|
||||||
|
`),
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "graphite content type is supported",
|
||||||
|
configBytes: []byte(`
|
||||||
|
[[outputs.sumologic]]
|
||||||
|
url = "https://localhost:3000"
|
||||||
|
data_format = "graphite"
|
||||||
|
`),
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prometheus content type is supported",
|
||||||
|
configBytes: []byte(`
|
||||||
|
[[outputs.sumologic]]
|
||||||
|
url = "https://localhost:3000"
|
||||||
|
data_format = "prometheus"
|
||||||
|
`),
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "setting extra headers is not possible",
|
||||||
|
configBytes: []byte(`
|
||||||
|
[[outputs.sumologic]]
|
||||||
|
url = "https://localhost:3000"
|
||||||
|
data_format = "carbon2"
|
||||||
|
[outputs.sumologic.headers]
|
||||||
|
X-Sumo-Name = "dummy"
|
||||||
|
X-Sumo-Host = "dummy"
|
||||||
|
X-Sumo-Category = "dummy"
|
||||||
|
X-Sumo-Dimensions = "dummy"
|
||||||
|
`),
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "full example from sample config is correct",
|
||||||
|
configBytes: []byte(`
|
||||||
|
[[outputs.sumologic]]
|
||||||
|
url = "https://localhost:3000"
|
||||||
|
data_format = "carbon2"
|
||||||
|
timeout = "5s"
|
||||||
|
method = "POST"
|
||||||
|
source_name = "name"
|
||||||
|
source_host = "hosta"
|
||||||
|
source_category = "category"
|
||||||
|
dimensions = "dimensions"
|
||||||
|
`),
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key - sumo_metadata - in config fails",
|
||||||
|
configBytes: []byte(`
|
||||||
|
[[outputs.sumologic]]
|
||||||
|
url = "https://localhost:3000"
|
||||||
|
data_format = "carbon2"
|
||||||
|
timeout = "5s"
|
||||||
|
method = "POST"
|
||||||
|
source_name = "name"
|
||||||
|
sumo_metadata = "metadata"
|
||||||
|
`),
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range testcases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := config.NewConfig()
|
||||||
|
|
||||||
|
if tt.expectedError {
|
||||||
|
require.Error(t, c.LoadConfigData(tt.configBytes))
|
||||||
|
} else {
|
||||||
|
require.NoError(t, c.LoadConfigData(tt.configBytes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxRequestBodySize(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.NotFoundHandler())
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
u, err := url.Parse(fmt.Sprintf("http://%s", ts.Listener.Addr().String()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
plugin func() *SumoLogic
|
||||||
|
metrics []telegraf.Metric
|
||||||
|
expectedError bool
|
||||||
|
expectedRequestCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default max request body size is 1MB and doesn't split small enough metric slices",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
metrics: []telegraf.Metric{getMetric(t)},
|
||||||
|
expectedError: false,
|
||||||
|
expectedRequestCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default max request body size is 1MB and doesn't split small even medium sized metrics",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
metrics: getMetrics(t),
|
||||||
|
expectedError: false,
|
||||||
|
expectedRequestCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max request body size properly splits requests - max 2500",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
s.MaxRequstBodySize = 2500
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
metrics: getMetrics(t),
|
||||||
|
expectedError: false,
|
||||||
|
expectedRequestCount: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max request body size properly splits requests - max 1000",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
s.MaxRequstBodySize = 1000
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
metrics: getMetrics(t),
|
||||||
|
expectedError: false,
|
||||||
|
expectedRequestCount: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max request body size properly splits requests - max 300",
|
||||||
|
plugin: func() *SumoLogic {
|
||||||
|
s := Default()
|
||||||
|
s.URL = u.String()
|
||||||
|
s.MaxRequstBodySize = 300
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
metrics: getMetrics(t),
|
||||||
|
expectedError: false,
|
||||||
|
expectedRequestCount: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testcases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var requestCount int
|
||||||
|
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requestCount++
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
serializer, err := carbon2.NewSerializer()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
plugin := tt.plugin()
|
||||||
|
plugin.SetSerializer(serializer)
|
||||||
|
|
||||||
|
err = plugin.Connect()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = plugin.Write(tt.metrics)
|
||||||
|
if tt.expectedError {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.expectedRequestCount, requestCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,24 +3,25 @@ package carbon2
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
)
|
)
|
||||||
|
|
||||||
type serializer struct {
|
type Serializer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSerializer() (*serializer, error) {
|
func NewSerializer() (*Serializer, error) {
|
||||||
s := &serializer{}
|
s := &Serializer{}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serializer) Serialize(metric telegraf.Metric) ([]byte, error) {
|
func (s *Serializer) Serialize(metric telegraf.Metric) ([]byte, error) {
|
||||||
return s.createObject(metric), nil
|
return s.createObject(metric), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serializer) SerializeBatch(metrics []telegraf.Metric) ([]byte, error) {
|
func (s *Serializer) SerializeBatch(metrics []telegraf.Metric) ([]byte, error) {
|
||||||
var batch bytes.Buffer
|
var batch bytes.Buffer
|
||||||
for _, metric := range metrics {
|
for _, metric := range metrics {
|
||||||
batch.Write(s.createObject(metric))
|
batch.Write(s.createObject(metric))
|
||||||
|
|
@ -28,7 +29,7 @@ func (s *serializer) SerializeBatch(metrics []telegraf.Metric) ([]byte, error) {
|
||||||
return batch.Bytes(), nil
|
return batch.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serializer) createObject(metric telegraf.Metric) []byte {
|
func (s *Serializer) createObject(metric telegraf.Metric) []byte {
|
||||||
var m bytes.Buffer
|
var m bytes.Buffer
|
||||||
for fieldName, fieldValue := range metric.Fields() {
|
for fieldName, fieldValue := range metric.Fields() {
|
||||||
if isNumeric(fieldValue) {
|
if isNumeric(fieldValue) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue