feat: do not error if no nodes found for current config with xpath parser (#11102)

This commit is contained in:
Thomas Casteleyn 2022-05-19 17:00:23 +02:00 committed by GitHub
parent ab04f3a1c7
commit 9a6816782b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 29 deletions

View File

@ -188,6 +188,7 @@ type Config struct {
XPathProtobufFile string `toml:"xpath_protobuf_file"`
XPathProtobufType string `toml:"xpath_protobuf_type"`
XPathProtobufImportPaths []string `toml:"xpath_protobuf_import_paths"`
XPathAllowEmptySelection bool `toml:"xpath_allow_empty_selection"`
XPathConfig []XPathConfig
// JSONPath configuration
@ -287,6 +288,7 @@ func NewParser(config *Config) (Parser, error) {
ProtobufImportPaths: config.XPathProtobufImportPaths,
PrintDocument: config.XPathPrintDocument,
DefaultTags: config.DefaultTags,
AllowEmptySelection: config.XPathAllowEmptySelection,
Configs: NewXPathParserConfigs(config.MetricName, config.XPathConfig),
}
case "json_v2":

View File

@ -86,6 +86,10 @@ In this configuration mode, you explicitly specify the field and tags you want t
## to get an idea on the expression necessary to derive fields etc.
# xpath_print_document = false
## Allow the results of one of the parsing sections to be empty.
## Useful when not all selected files have the exact same structure.
# xpath_allow_empty_selection = false
## Multiple parsing sections are allowed
[[inputs.file.xpath]]
## Optional: XPath-query to select a subset of nodes from the XML document.
@ -152,6 +156,10 @@ metric.
## to get an idea on the expression necessary to derive fields etc.
# xpath_print_document = false
## Allow the results of one of the parsing sections to be empty.
## Useful when not all selected files have the exact same structure.
# xpath_allow_empty_selection = false
## Multiple parsing sections are allowed
[[inputs.file.xpath]]
## Optional: XPath-query to select a subset of nodes from the XML document.
@ -201,7 +209,7 @@ metric.
```
*Please note*: The resulting fields are _always_ of type string!
**Please note**: The resulting fields are *always* of type string!
It is also possible to specify a mixture of the two alternative ways of specifying fields.

View File

@ -29,6 +29,7 @@ type Parser struct {
ProtobufMessageType string
ProtobufImportPaths []string
PrintDocument bool
AllowEmptySelection bool
Configs []Config
DefaultTags map[string]string
Log telegraf.Logger
@ -108,7 +109,9 @@ func (p *Parser) Parse(buf []byte) ([]telegraf.Metric, error) {
}
if len(selectedNodes) < 1 || selectedNodes[0] == nil {
p.debugEmptyQuery("metric selection", doc, config.Selection)
return nil, fmt.Errorf("cannot parse with empty selection node")
if !p.AllowEmptySelection {
return metrics, fmt.Errorf("cannot parse with empty selection node")
}
}
p.Log.Debugf("Number of selected metric nodes: %d", len(selectedNodes))
@ -126,37 +129,20 @@ func (p *Parser) Parse(buf []byte) ([]telegraf.Metric, error) {
}
func (p *Parser) ParseLine(line string) (telegraf.Metric, error) {
t := time.Now()
switch len(p.Configs) {
metrics, err := p.Parse([]byte(line))
if err != nil {
return nil, err
}
switch len(metrics) {
case 0:
return nil, nil
case 1:
config := p.Configs[0]
doc, err := p.document.Parse([]byte(line))
if err != nil {
return nil, err
}
selected := doc
if len(config.Selection) > 0 {
selectedNodes, err := p.document.QueryAll(doc, config.Selection)
if err != nil {
return nil, err
}
if len(selectedNodes) < 1 || selectedNodes[0] == nil {
p.debugEmptyQuery("metric selection", doc, config.Selection)
return nil, fmt.Errorf("cannot parse line with empty selection")
} else if len(selectedNodes) != 1 {
return nil, fmt.Errorf("cannot parse line with multiple selected nodes (%d)", len(selectedNodes))
}
selected = selectedNodes[0]
}
return p.parseQuery(t, doc, selected, config)
return metrics[0], nil
default:
return metrics[0], fmt.Errorf("cannot parse line with multiple (%d) metrics", len(metrics))
}
return nil, fmt.Errorf("cannot parse line with multiple (%d) configurations", len(p.Configs))
}
func (p *Parser) SetDefaultTags(tags map[string]string) {

View File

@ -1154,7 +1154,74 @@ func TestEmptySelection(t *testing.T) {
_, err := parser.Parse([]byte(tt.input))
require.Error(t, err)
require.Equal(t, err.Error(), "cannot parse with empty selection node")
require.Equal(t, "cannot parse with empty selection node", err.Error())
})
}
}
func TestEmptySelectionAllowed(t *testing.T) {
var tests = []struct {
name string
input string
configs []Config
}{
{
name: "empty path",
input: multipleNodesXML,
configs: []Config{
{
Selection: "/Device/NonExisting",
Fields: map[string]string{"value": "number(Value)"},
FieldsInt: map[string]string{"mode": "Value/@mode"},
Tags: map[string]string{},
},
},
},
{
name: "empty pattern",
input: multipleNodesXML,
configs: []Config{
{
Selection: "//NonExisting",
Fields: map[string]string{"value": "number(Value)"},
FieldsInt: map[string]string{"mode": "Value/@mode"},
Tags: map[string]string{},
},
},
},
{
name: "empty axis",
input: multipleNodesXML,
configs: []Config{
{
Selection: "/Device/child::NonExisting",
Fields: map[string]string{"value": "number(Value)"},
FieldsInt: map[string]string{"mode": "Value/@mode"},
Tags: map[string]string{},
},
},
},
{
name: "empty predicate",
input: multipleNodesXML,
configs: []Config{
{
Selection: "/Device[@NonExisting=true]",
Fields: map[string]string{"value": "number(Value)"},
FieldsInt: map[string]string{"mode": "Value/@mode"},
Tags: map[string]string{},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parser := &Parser{Configs: tt.configs, AllowEmptySelection: true, DefaultTags: map[string]string{}, Log: testutil.Logger{Name: "parsers.xml"}}
require.NoError(t, parser.Init())
_, err := parser.Parse([]byte(tt.input))
require.NoError(t, err)
})
}
}