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
59
plugins/parsers/form_urlencoded/README.md
Normal file
59
plugins/parsers/form_urlencoded/README.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Form Urlencoded Parser Plugin
|
||||
|
||||
The `form-urlencoded` data format parses `application/x-www-form-urlencoded`
|
||||
data, such as commonly used in the [query string][].
|
||||
|
||||
A common use case is to pair it with [http_listener_v2][] input plugin to parse
|
||||
request body or query params.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
[[inputs.http_listener_v2]]
|
||||
## Address and port to host HTTP listener on
|
||||
service_address = ":8080"
|
||||
|
||||
## Part of the request to consume. Available options are "body" and
|
||||
## "query".
|
||||
data_source = "body"
|
||||
|
||||
## Data format to consume.
|
||||
## Each data format has its own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
data_format = "form_urlencoded"
|
||||
|
||||
## Array of key names which should be collected as tags.
|
||||
## By default, keys with string value are ignored if not marked as tags.
|
||||
form_urlencoded_tag_keys = ["tag1"]
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic parsing
|
||||
|
||||
Config:
|
||||
|
||||
```toml
|
||||
[[inputs.http_listener_v2]]
|
||||
name_override = "mymetric"
|
||||
service_address = ":8080"
|
||||
data_source = "query"
|
||||
data_format = "form_urlencoded"
|
||||
form_urlencoded_tag_keys = ["tag1"]
|
||||
```
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -i -XGET 'http://localhost:8080/telegraf?tag1=foo&field1=0.42&field2=42'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
mymetric,tag1=foo field1=0.42,field2=42
|
||||
```
|
||||
|
||||
[query string]: https://en.wikipedia.org/wiki/Query_string
|
||||
[http_listener_v2]: /plugins/inputs/http_listener_v2
|
107
plugins/parsers/form_urlencoded/parser.go
Normal file
107
plugins/parsers/form_urlencoded/parser.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package form_urlencoded
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
"github.com/influxdata/telegraf/plugins/parsers"
|
||||
)
|
||||
|
||||
var ErrNoMetric = errors.New("no metric in line")
|
||||
|
||||
// Parser decodes "application/x-www-form-urlencoded" data into metrics
|
||||
type Parser struct {
|
||||
MetricName string `toml:"-"`
|
||||
TagKeys []string `toml:"form_urlencoded_tag_keys"`
|
||||
DefaultTags map[string]string `toml:"-"`
|
||||
}
|
||||
|
||||
// Parse converts a slice of bytes in "application/x-www-form-urlencoded" format into metrics
|
||||
func (p Parser) Parse(buf []byte) ([]telegraf.Metric, error) {
|
||||
buf = bytes.TrimSpace(buf)
|
||||
if len(buf) == 0 {
|
||||
return make([]telegraf.Metric, 0), nil
|
||||
}
|
||||
|
||||
values, err := url.ParseQuery(string(buf))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags := p.extractTags(values)
|
||||
fields := parseFields(values)
|
||||
|
||||
for key, value := range p.DefaultTags {
|
||||
tags[key] = value
|
||||
}
|
||||
|
||||
m := metric.New(p.MetricName, tags, fields, time.Now().UTC())
|
||||
|
||||
return []telegraf.Metric{m}, nil
|
||||
}
|
||||
|
||||
// ParseLine delegates a single line of text to the Parse function
|
||||
func (p Parser) ParseLine(line string) (telegraf.Metric, error) {
|
||||
metrics, err := p.Parse([]byte(line))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(metrics) < 1 {
|
||||
return nil, ErrNoMetric
|
||||
}
|
||||
|
||||
return metrics[0], nil
|
||||
}
|
||||
|
||||
// SetDefaultTags sets the default tags for every metric
|
||||
func (p *Parser) SetDefaultTags(tags map[string]string) {
|
||||
p.DefaultTags = tags
|
||||
}
|
||||
|
||||
func (p Parser) extractTags(values url.Values) map[string]string {
|
||||
tags := make(map[string]string)
|
||||
for _, key := range p.TagKeys {
|
||||
value, exists := values[key]
|
||||
|
||||
if !exists || len(key) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
tags[key] = value[0]
|
||||
delete(values, key)
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
func parseFields(values url.Values) map[string]interface{} {
|
||||
fields := make(map[string]interface{})
|
||||
|
||||
for key, value := range values {
|
||||
if len(key) == 0 || len(value) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
field, err := strconv.ParseFloat(value[0], 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fields[key] = field
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func init() {
|
||||
parsers.Add("form_urlencoded",
|
||||
func(defaultMetricName string) telegraf.Parser {
|
||||
return &Parser{MetricName: defaultMetricName}
|
||||
})
|
||||
}
|
217
plugins/parsers/form_urlencoded/parser_test.go
Normal file
217
plugins/parsers/form_urlencoded/parser_test.go
Normal file
|
@ -0,0 +1,217 @@
|
|||
package form_urlencoded
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
const (
|
||||
validFormData = "tag1=foo&tag2=bar&tag3=baz&field1=42&field2=69"
|
||||
encodedFormData = "tag1=%24%24%24&field1=1e%2B3"
|
||||
notEscapedProperlyFormData = "invalid=%Y5"
|
||||
blankKeyFormData = "=42&field2=69"
|
||||
emptyFormData = ""
|
||||
)
|
||||
|
||||
func TestParseValidFormData(t *testing.T) {
|
||||
parser := Parser{
|
||||
MetricName: "form_urlencoded_test",
|
||||
}
|
||||
|
||||
metrics, err := parser.Parse([]byte(validFormData))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, metrics, 1)
|
||||
require.Equal(t, "form_urlencoded_test", metrics[0].Name())
|
||||
require.Equal(t, map[string]string{}, metrics[0].Tags())
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"field1": float64(42),
|
||||
"field2": float64(69),
|
||||
}, metrics[0].Fields())
|
||||
}
|
||||
|
||||
func TestParseLineValidFormData(t *testing.T) {
|
||||
parser := Parser{
|
||||
MetricName: "form_urlencoded_test",
|
||||
}
|
||||
|
||||
metrics, err := parser.ParseLine(validFormData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "form_urlencoded_test", metrics.Name())
|
||||
require.Equal(t, map[string]string{}, metrics.Tags())
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"field1": float64(42),
|
||||
"field2": float64(69),
|
||||
}, metrics.Fields())
|
||||
}
|
||||
|
||||
func TestParseValidFormDataWithTags(t *testing.T) {
|
||||
parser := Parser{
|
||||
MetricName: "form_urlencoded_test",
|
||||
TagKeys: []string{"tag1", "tag2"},
|
||||
}
|
||||
|
||||
metrics, err := parser.Parse([]byte(validFormData))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, metrics, 1)
|
||||
require.Equal(t, "form_urlencoded_test", metrics[0].Name())
|
||||
require.Equal(t, map[string]string{
|
||||
"tag1": "foo",
|
||||
"tag2": "bar",
|
||||
}, metrics[0].Tags())
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"field1": float64(42),
|
||||
"field2": float64(69),
|
||||
}, metrics[0].Fields())
|
||||
}
|
||||
|
||||
func TestParseValidFormDataDefaultTags(t *testing.T) {
|
||||
parser := Parser{
|
||||
MetricName: "form_urlencoded_test",
|
||||
TagKeys: []string{"tag1", "tag2"},
|
||||
DefaultTags: map[string]string{"tag4": "default"},
|
||||
}
|
||||
|
||||
metrics, err := parser.Parse([]byte(validFormData))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, metrics, 1)
|
||||
require.Equal(t, "form_urlencoded_test", metrics[0].Name())
|
||||
require.Equal(t, map[string]string{
|
||||
"tag1": "foo",
|
||||
"tag2": "bar",
|
||||
"tag4": "default",
|
||||
}, metrics[0].Tags())
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"field1": float64(42),
|
||||
"field2": float64(69),
|
||||
}, metrics[0].Fields())
|
||||
}
|
||||
|
||||
func TestParseValidFormDataDefaultTagsOverride(t *testing.T) {
|
||||
parser := Parser{
|
||||
MetricName: "form_urlencoded_test",
|
||||
TagKeys: []string{"tag1", "tag2"},
|
||||
DefaultTags: map[string]string{"tag1": "default"},
|
||||
}
|
||||
|
||||
metrics, err := parser.Parse([]byte(validFormData))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, metrics, 1)
|
||||
require.Equal(t, "form_urlencoded_test", metrics[0].Name())
|
||||
require.Equal(t, map[string]string{
|
||||
"tag1": "default",
|
||||
"tag2": "bar",
|
||||
}, metrics[0].Tags())
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"field1": float64(42),
|
||||
"field2": float64(69),
|
||||
}, metrics[0].Fields())
|
||||
}
|
||||
|
||||
func TestParseEncodedFormData(t *testing.T) {
|
||||
parser := Parser{
|
||||
MetricName: "form_urlencoded_test",
|
||||
TagKeys: []string{"tag1"},
|
||||
}
|
||||
|
||||
metrics, err := parser.Parse([]byte(encodedFormData))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, metrics, 1)
|
||||
require.Equal(t, "form_urlencoded_test", metrics[0].Name())
|
||||
require.Equal(t, map[string]string{
|
||||
"tag1": "$$$",
|
||||
}, metrics[0].Tags())
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"field1": float64(1000),
|
||||
}, metrics[0].Fields())
|
||||
}
|
||||
|
||||
func TestParseInvalidFormDataError(t *testing.T) {
|
||||
parser := Parser{
|
||||
MetricName: "form_urlencoded_test",
|
||||
}
|
||||
|
||||
metrics, err := parser.Parse([]byte(notEscapedProperlyFormData))
|
||||
require.Error(t, err)
|
||||
require.Empty(t, metrics)
|
||||
}
|
||||
|
||||
func TestParseInvalidFormDataEmptyKey(t *testing.T) {
|
||||
parser := Parser{
|
||||
MetricName: "form_urlencoded_test",
|
||||
}
|
||||
|
||||
// Empty key for field
|
||||
metrics, err := parser.Parse([]byte(blankKeyFormData))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, metrics, 1)
|
||||
require.Equal(t, map[string]string{}, metrics[0].Tags())
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"field2": float64(69),
|
||||
}, metrics[0].Fields())
|
||||
|
||||
// Empty key for tag
|
||||
parser.TagKeys = []string{""}
|
||||
metrics, err = parser.Parse([]byte(blankKeyFormData))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, metrics, 1)
|
||||
require.Equal(t, map[string]string{}, metrics[0].Tags())
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"field2": float64(69),
|
||||
}, metrics[0].Fields())
|
||||
}
|
||||
|
||||
func TestParseInvalidFormDataEmptyString(t *testing.T) {
|
||||
parser := Parser{
|
||||
MetricName: "form_urlencoded_test",
|
||||
}
|
||||
|
||||
metrics, err := parser.Parse([]byte(emptyFormData))
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, metrics)
|
||||
}
|
||||
|
||||
const benchmarkData = `tags_host=myhost&tags_platform=python&tags_sdkver=3.11.5&value=5`
|
||||
|
||||
func TestBenchmarkData(t *testing.T) {
|
||||
plugin := &Parser{
|
||||
MetricName: "benchmark",
|
||||
TagKeys: []string{"tags_host", "tags_platform", "tags_sdkver"},
|
||||
}
|
||||
|
||||
expected := []telegraf.Metric{
|
||||
metric.New(
|
||||
"benchmark",
|
||||
map[string]string{
|
||||
"tags_host": "myhost",
|
||||
"tags_platform": "python",
|
||||
"tags_sdkver": "3.11.5",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": 5.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
}
|
||||
|
||||
actual, err := plugin.Parse([]byte(benchmarkData))
|
||||
require.NoError(t, err)
|
||||
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime(), testutil.SortMetrics())
|
||||
}
|
||||
|
||||
func BenchmarkParsing(b *testing.B) {
|
||||
plugin := &Parser{
|
||||
MetricName: "benchmark",
|
||||
TagKeys: []string{"source", "tags_platform", "tags_sdkver"},
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
//nolint:errcheck // Benchmarking so skip the error check to avoid the unnecessary operations
|
||||
plugin.Parse([]byte(benchmarkData))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue