fix(parsers.xpath): Ensure precedence of explicitly defined tags and fields (#13662)
This commit is contained in:
parent
64f6c97d13
commit
da28cfdb43
|
|
@ -280,7 +280,8 @@ in the 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
|
It is also possible to specify a mixture of the two alternative ways of
|
||||||
specifying fields.
|
specifying fields. In this case _explicitly_ defined tags and fields take
|
||||||
|
_precedence_ over the batch instances if both use the same tag/field name.
|
||||||
|
|
||||||
### metric_selection (optional)
|
### metric_selection (optional)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -279,25 +279,6 @@ func (p *Parser) parseQuery(starttime time.Time, doc, selected dataNode, config
|
||||||
|
|
||||||
// Query tags and add default ones
|
// Query tags and add default ones
|
||||||
tags := make(map[string]string)
|
tags := make(map[string]string)
|
||||||
for name, query := range config.Tags {
|
|
||||||
// Execute the query and cast the returned values into strings
|
|
||||||
v, err := p.executeQuery(doc, selected, query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query tag %q: %w", name, err)
|
|
||||||
}
|
|
||||||
switch v := v.(type) {
|
|
||||||
case string:
|
|
||||||
tags[name] = v
|
|
||||||
case bool:
|
|
||||||
tags[name] = strconv.FormatBool(v)
|
|
||||||
case float64:
|
|
||||||
tags[name] = strconv.FormatFloat(v, 'G', -1, 64)
|
|
||||||
case nil:
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown format '%T' for tag %q", v, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the tag batch definitions if any.
|
// Handle the tag batch definitions if any.
|
||||||
if len(config.TagSelection) > 0 {
|
if len(config.TagSelection) > 0 {
|
||||||
|
|
@ -356,53 +337,34 @@ func (p *Parser) parseQuery(starttime time.Time, doc, selected dataNode, config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle explicitly defined tags
|
||||||
|
for name, query := range config.Tags {
|
||||||
|
// Execute the query and cast the returned values into strings
|
||||||
|
v, err := p.executeQuery(doc, selected, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query tag %q: %w", name, err)
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
tags[name] = v
|
||||||
|
case bool:
|
||||||
|
tags[name] = strconv.FormatBool(v)
|
||||||
|
case float64:
|
||||||
|
tags[name] = strconv.FormatFloat(v, 'G', -1, 64)
|
||||||
|
case nil:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown format '%T' for tag %q", v, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add default tags
|
||||||
for name, v := range p.DefaultTags {
|
for name, v := range p.DefaultTags {
|
||||||
tags[name] = v
|
tags[name] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query fields
|
// Query fields
|
||||||
fields := make(map[string]interface{})
|
fields := make(map[string]interface{})
|
||||||
for name, query := range config.FieldsInt {
|
|
||||||
// Execute the query and cast the returned values into integers
|
|
||||||
v, err := p.executeQuery(doc, selected, query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query field (int) %q: %w", name, err)
|
|
||||||
}
|
|
||||||
switch v := v.(type) {
|
|
||||||
case string:
|
|
||||||
fields[name], err = strconv.ParseInt(v, 10, 54)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse field (int) %q: %w", name, err)
|
|
||||||
}
|
|
||||||
case bool:
|
|
||||||
fields[name] = int64(0)
|
|
||||||
if v {
|
|
||||||
fields[name] = int64(1)
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
fields[name] = int64(v)
|
|
||||||
case nil:
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown format '%T' for field (int) %q", v, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, query := range config.Fields {
|
|
||||||
// Execute the query and store the result in fields
|
|
||||||
v, err := p.executeQuery(doc, selected, query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query field %q: %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.FieldsHexFilter != nil && config.FieldsHexFilter.Match(name) {
|
|
||||||
if b, ok := v.([]byte); ok {
|
|
||||||
v = hex.EncodeToString(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fields[name] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the field batch definitions if any.
|
// Handle the field batch definitions if any.
|
||||||
if len(config.FieldSelection) > 0 {
|
if len(config.FieldSelection) > 0 {
|
||||||
|
|
@ -471,6 +433,59 @@ func (p *Parser) parseQuery(starttime time.Time, doc, selected dataNode, config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle explicitly defined fields
|
||||||
|
for name, query := range config.FieldsInt {
|
||||||
|
// Execute the query and cast the returned values into integers
|
||||||
|
v, err := p.executeQuery(doc, selected, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query field (int) %q: %w", name, err)
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
fields[name], err = strconv.ParseInt(v, 10, 54)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse field (int) %q: %w", name, err)
|
||||||
|
}
|
||||||
|
case bool:
|
||||||
|
fields[name] = int64(0)
|
||||||
|
if v {
|
||||||
|
fields[name] = int64(1)
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
fields[name] = int64(v)
|
||||||
|
case nil:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown format '%T' for field (int) %q", v, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, query := range config.Fields {
|
||||||
|
// Execute the query and store the result in fields
|
||||||
|
v, err := p.executeQuery(doc, selected, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query field %q: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle complex types which would be dropped otherwise for
|
||||||
|
// native type handling
|
||||||
|
fmt.Printf("explicit field %q: %v (%T)\n", name, v, v)
|
||||||
|
if v != nil {
|
||||||
|
switch reflect.TypeOf(v).Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Map:
|
||||||
|
if b, ok := v.([]byte); ok {
|
||||||
|
if config.FieldsHexFilter != nil && config.FieldsHexFilter.Match(name) {
|
||||||
|
v = hex.EncodeToString(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[name] = v
|
||||||
|
}
|
||||||
|
|
||||||
return metric.New(metricname, tags, fields, timestamp), nil
|
return metric.New(metricname, tags, fields, timestamp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
foo a="a string",b=3.1415,c=true,d="{\"d1\":1,\"d2\":\"foo\",\"d3\":true,\"d4\":null}",e="[\"master\",42,true]",timestamp=1690193829 1690193829000000000
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
[[inputs.file]]
|
||||||
|
files = ["./testcases/json_string_representation/test.json"]
|
||||||
|
data_format = "xpath_json"
|
||||||
|
|
||||||
|
xpath_native_types = true
|
||||||
|
|
||||||
|
[[inputs.file.xpath]]
|
||||||
|
metric_name = "'foo'"
|
||||||
|
field_selection = "*"
|
||||||
|
timestamp = "timestamp"
|
||||||
|
timestamp_format = "unix"
|
||||||
|
|
||||||
|
[inputs.file.xpath.fields]
|
||||||
|
d = "string(d)"
|
||||||
|
e = "string(e)"
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"a": "a string",
|
||||||
|
"b": 3.1415,
|
||||||
|
"c": true,
|
||||||
|
"d": {
|
||||||
|
"d1": 1,
|
||||||
|
"d2": "foo",
|
||||||
|
"d3": true,
|
||||||
|
"d4": null
|
||||||
|
},
|
||||||
|
"e": ["master", 42, true],
|
||||||
|
"timestamp": 1690193829
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue