Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/1363: Additional configuration for JSON parser #4351

Merged
merged 21 commits into from
Aug 23, 2018

Conversation

maxunt
Copy link
Contributor

@maxunt maxunt commented Jun 27, 2018

closes: #2986

Required for all PRs:

  • Signed CLA.
  • Associated README.md updated.
  • Has appropriate unit tests.

@@ -129,11 +131,13 @@ func NewParser(config *Config) (Parser, error) {
func NewJSONParser(
metricName string,
tagKeys []string,
fieldKeys []string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally we want to avoid changing exported function signatures, as it destabilizes packages. Perhaps what we could do is add a newJSONParser that takes different (or functional) parameters. We could then update those things that call NewJSONParser directly to call NewParser, which will in turn call newJSONParser.

@maxunt maxunt closed this Jul 2, 2018
@danielnelson
Copy link
Contributor

@maxunt As we discussed, let's have this be string_keys and list flattened paths to values that should be added as string fields.

@danielnelson danielnelson reopened this Jul 10, 2018
nTime := time.Now().UTC()
if p.JSONTimeKey != "" {
if p.JSONTimeFormat == "" {
err := fmt.Errorf("E! If 'json_time_key' is specified, there must be a 'json_time_format'")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is an error and not a log message, don't include the E!. As a nitpick, it would probably be good to try to come up with a shorter error string, perhaps: use of 'json_time_key' requires 'json_time_format'?

Remember to do this for the other errors, here is some more info on how "standard" Go error strings should look: https://github.com/golang/go/wiki/CodeReviewComments#error-strings

@@ -114,6 +137,7 @@ func (p *JSONParser) switchFieldToTag(tags map[string]string, fields map[string]

func (p *JSONParser) Parse(buf []byte) ([]telegraf.Metric, error) {

//if json_query is specified
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this comment, it doesn't provide any new information on why and the what can be determined by looking at the code.

## List of field names to extract from JSON and add as string fields
# string_fields = []

## gjson query path to specify a specific chunk of JSON to be parsed with the above configuration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap comments before 78 chars for readability.

return nil, err
}

nTime, err = time.Parse(p.JSONTimeFormat, f.Fields[p.JSONTimeKey].(string))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to test the type assertion since it comes from input and could be any type.

}

_, err := parser.Parse([]byte(testString))
assert.Error(t, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use require instead of assert (almost) everywhere. Doesn't really matter here but this will prevent confusing chained errors.

metrics, err := parser.Parse([]byte(testString))
assert.NoError(t, err)
assert.Equal(t, metrics[0].Fields()["last"], "Murphy")
assert.Equal(t, 3, len(metrics))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the length before indexing, this will make the test easier to understand if it fails.

}
metrics, err := parser.Parse([]byte(testString))
assert.NoError(t, err)
assert.Equal(t, false, metrics[0].Time() == metrics[1].Time())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check len(metrics) first, add some more tests to check error cases.


The JSON data format also supports extracting time values through the config "json_time_key" and "json_time_format".
If "json_time_key" is set, "json_time_format" must be specified. The "json_time_key" describes the name of the field containing time information. The "json_time_format" must be a recognized Go time format.
More info on time formats can be found here: https://golang.org/src/time/format.go
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use https://golang.org/pkg/time/#Parse as the link. This other link is still not great but the audience of this documentation is not Go developers.

Try to wrap around 78 chars

@danielnelson danielnelson added this to the 1.8.0 milestone Jul 16, 2018
@danielnelson danielnelson added the feat Improvement on an existing feature such as adding a new setting/mode to an existing plugin label Jul 16, 2018
@maxunt maxunt mentioned this pull request Jul 17, 2018

## gjson query path to specify a specific chunk of JSON to be parsed with
## the above configuration. If not specified, the whole file will be parsed.
## gjson query paths are described here:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add link to where gjson query paths are described

// MetricName applies to JSON & value. This will be the name of the measurement.
MetricName string

//holds a gjson path for json parser
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding comments, add a space before starting comment text (// This is my comment)

timeKey string,
timeFormat string,
defaultTags map[string]string,
) (Parser, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning an error isn't useful here. Where it's an unexported function not matching any interface, I'd remove it, or remove the function altogether and just instantiate a JSONParser on line 120

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No error is ever returned by the new_Parser functions, but i thought i'd include it here to match the format of the rest of the parsers

@@ -126,7 +177,7 @@ func (p *JSONParser) ParseLine(line string) (telegraf.Metric, error) {
}

if len(metrics) < 1 {
return nil, fmt.Errorf("Can not parse the line: %s, for data format: influx ", line)
return nil, fmt.Errorf("can not parse the line: %s, for data format: influx ", line)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the copypasta, s/influx/json

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you clarify what you mean here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace "influx" with "json", it appears to be copy paste code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i see. good catch thanks

json_time_key = "b_time"

## holds the format of timestamp to be parsed
json_time_format = "02 Jan 06 15:04 MST"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting example time format because there it doesn't include a year. This results in a pared time that is in year 0. I think after parsing the time we should do something similar to what we did in logparser and use the current year. While this would be pretty annoying if you actually want to store in a metric from year 0, I think it is much more likely to be what is wanted:

if ts.Year() == 0 {
	ts = ts.AddDate(timestamp.Year(), 0, 0)
}

@danielnelson danielnelson merged commit 2729378 into master Aug 23, 2018
@danielnelson danielnelson deleted the feature/1363 branch August 23, 2018 02:26
rgitzel pushed a commit to rgitzel/telegraf that referenced this pull request Oct 17, 2018
otherpirate pushed a commit to otherpirate/telegraf that referenced this pull request Mar 15, 2019
otherpirate pushed a commit to otherpirate/telegraf that referenced this pull request Mar 15, 2019
dupondje pushed a commit to dupondje/telegraf that referenced this pull request Apr 22, 2019
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat Improvement on an existing feature such as adding a new setting/mode to an existing plugin
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make timestamp configurable for JSON parser
3 participants