feat: do not error if no nodes found for current config with xpath parser (#11102)
This commit is contained in:
parent
ab04f3a1c7
commit
9a6816782b
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue