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

Metricbeat/HTTP: Support array in http/json metricset #6480

Merged
merged 1 commit into from
Mar 13, 2018

Conversation

dolftax
Copy link
Contributor

@dolftax dolftax commented Feb 27, 2018

Currently (before this commit) the http/json metricset in Metricbeat
only can query information from http endpoints which expose
map[string]interface{}. For endpoints which expose an array on the
root level, the json metricset does not work.

A config option is added response.isarray | bool. If
someone configures array but a non array json object
is returned, an error is logged.

Event response is unified to []map[string]interface{} even
if the response is map[string]interface{}.

Signed-off-by: Jaipradeesh jaipradeesh@gmail.com

Ref: #6472

@elasticmachine
Copy link
Collaborator

Since this is a community submitted pull request, a Jenkins build has not been kicked off automatically. Can an Elastic organization member please verify the contents of this patch and then kick off a build manually?

@dolftax
Copy link
Contributor Author

dolftax commented Feb 27, 2018

@ruflin WIP. Yet to check thoroughly. Any thoughts on the approach?

@@ -38,6 +38,7 @@ metricbeat.modules:
#request.enabled: false
#response.enabled: false
#dedot.enabled: false
#response.isarray: false
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking if we should make this json.is_array or json.response.is_array. Now that we have multiple metricsets in the http module not all settings apply to all metricsets I think.

I'm aware that previous configs do not follow the convention :-(

@@ -1,4 +1,4 @@
{
[{
Copy link
Member

Choose a reason for hiding this comment

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

This part is not going to change. In case of an array, each document will be sent as a single vent in the previous format.

return nil, err
}

switch body := jsonBody.(type) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's separate the processing and not only do a "dynamic" switch to figure out if there is an error. The way I imagine is that we have jsonBody and jsonBodyArray (or similar) and directly decode in what we want in the end. Like this I would assume we don't have to do any type checks and directly get the errors.

@ruflin
Copy link
Member

ruflin commented Feb 27, 2018

It would be great if for the tests we could spin up a small mock http server in Golang as some other modules do. And then have some example json files with both variants to test against.

defer response.Body.Close()

var jsonBody interface{}
var event []common.MapStr
Copy link
Contributor

@christiangalsterer christiangalsterer Feb 27, 2018

Choose a reason for hiding this comment

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

event should be named events as this now an array of events and eventObj can then be named event again.

Copy link
Member

Choose a reason for hiding this comment

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

@christiangalsterer glad you stumbled over the PR.

fmt.Fprint(w, `[{"hello1":"world1"}, {"hello2": "world2"}]`)
}

func serveJsonObj(w http.ResponseWriter, r *http.Request) {

Choose a reason for hiding this comment

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

func serveJsonObj should be serveJSONObj

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack

err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

func serve(w http.ResponseWriter, r *http.Request) {
func serveJsonArr(w http.ResponseWriter, r *http.Request) {

Choose a reason for hiding this comment

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

func serveJsonArr should be serveJSONArr

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack

@dolftax dolftax force-pushed the metricbeat-http-json-arrays branch 5 times, most recently from dad33c5 to 9936bde Compare February 28, 2018 14:33
@dolftax
Copy link
Contributor Author

dolftax commented Feb 28, 2018

➜  metricbeat git:(metricbeat-http-json-arrays) go test --tags=integration github.com/elastic/beats/metricbeat/module/http/json/... -v                   

=== RUN   TestFetch
--- PASS: TestFetch (1.14s)
	json_integration_test.go:24: http/json event: [{"_namespace":"testnamespace","hello":"world"}]
	json_integration_test.go:32: http/json event: [{"_namespace":"testnamespace","hello1":"world1"} {"_namespace":"testnamespace","hello2":"world2"}]
=== RUN   TestData
--- SKIP: TestData (1.18s)
	data_generator.go:43: skip data generation tests
PASS
ok  	github.com/elastic/beats/metricbeat/module/http/json	2.330s

@ruflin This PR LGTM. Let me know if any changes are required.

} else {
event = jsonBody
event = jsonBody.(common.MapStr)
Copy link
Member

Choose a reason for hiding this comment

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

We probably should do some type conversion checks on both lines?

Copy link
Contributor Author

@dolftax dolftax Mar 4, 2018

Choose a reason for hiding this comment

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

Sorry, I don't understand what you mean by 'both lines'. Elaborate please? :)

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I meant on line 106 and 108 as both to a type converstion to common.MapStr.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did assertion because common.DeDotJSON accepts and emits interface{}. Type conversion would fail.

If types are mismatched, line 160 and 170 should error out.

Copy link
Member

Choose a reason for hiding this comment

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

One more miscommunication on my end. I meant type assertions. I was looking for:

event, ok = jsonBody.(common.MapStr)
if !ok { ... }

If for whatever reason the assertion does not work, we would have a panic otherwise. At the same time I see it should not happen here.

event, err := f.Fetch()
if !assert.NoError(t, err) {
t.FailNow()
}

t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event)

f = mbtest.NewEventsFetcher(t, getConfig("array"))
Copy link
Member

Choose a reason for hiding this comment

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

Can we split this up in 2 tests? Like TestFetchObject and TestFetchArray? This will make it eaiser to spot what failed if something fails

}

func getConfig() map[string]interface{} {
func getConfig(jsonType string) map[string]interface{} {
Copy link
Member

Choose a reason for hiding this comment

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

you could make this a bool called isArray. But that also works. Do you expect other types here in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If confirming to JSON standards, yes. A string, number, object, array, true, false and null could be valid JSON. I would say to leave it this way than adding a boolean between array or not.

Copy link
Member

Choose a reason for hiding this comment

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

Interesting point about the other types. I wonder when a request for that will pop up :-) Lets go with your version.

@@ -37,6 +37,7 @@ metricbeat.modules:
#method: "GET"
#request.enabled: false
#response.enabled: false
#response.is_array: false
Copy link
Member

Choose a reason for hiding this comment

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

I was thinking again about the naming and I think we should go here with json.is_array. It's the json metricset so a prefix is needed, and as it's always about processing of the body / response anyways, we don't need this in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

json.is_array makes sense to me. Will amend the change.

@dolftax dolftax force-pushed the metricbeat-http-json-arrays branch from 9936bde to 9183920 Compare March 4, 2018 09:38
@dolftax
Copy link
Contributor Author

dolftax commented Mar 9, 2018

Build failed due to fetch (thus build) error. Re-trigger should fix it.

Copy link
Member

@ruflin ruflin left a comment

Choose a reason for hiding this comment

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

Sorry for the slow response. PR looks almost complete. Left a few minor last comments.

} else {
event = jsonBody
event = jsonBody.(common.MapStr)
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I meant on line 106 and 108 as both to a type converstion to common.MapStr.

}
defer response.Body.Close()

var jsonBody common.MapStr
Copy link
Member

Choose a reason for hiding this comment

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

As jsonBody and jsonBodyArray are "local" to the if / else parts, these could be defined inside the if clause.

}

func getConfig() map[string]interface{} {
func getConfig(jsonType string) map[string]interface{} {
Copy link
Member

Choose a reason for hiding this comment

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

Interesting point about the other types. I wonder when a request for that will pop up :-) Lets go with your version.

@dolftax dolftax force-pushed the metricbeat-http-json-arrays branch from c8c1cc2 to 1bf2901 Compare March 12, 2018 08:29
@dolftax
Copy link
Contributor Author

dolftax commented Mar 12, 2018

Sorry about the merge commit. Please squash before merging into master :)

@ruflin
Copy link
Member

ruflin commented Mar 12, 2018

@jaipradeesh This seems to conflict with the master branch. Could you rebase on master and push again?

I'm good with the changes. @christiangalsterer could you also have a look again?

Currently (before this commit) the http/json metricset in Metricbeat
only can query information from http endpoints which expose
map[string]interface{}. For endpoints which expose an array on the
root level, the json metricset does not work.

A config option is added `response.isarray | bool`. If
someone configures array but a non array json object
is returned, an error is logged.

Event response is unified to []map[string]interface{} even
if the response is map[string]interface{}.

Signed-off-by: Jaipradeesh <jaipradeesh@gmail.com>
@dolftax dolftax force-pushed the metricbeat-http-json-arrays branch from 1bf2901 to d89f6eb Compare March 12, 2018 14:25
@ruflin
Copy link
Member

ruflin commented Mar 13, 2018

jenkins, test it

@ruflin ruflin merged commit 4e9f33b into elastic:master Mar 13, 2018
@ruflin
Copy link
Member

ruflin commented Mar 13, 2018

@jaipradeesh Thanks a lot for your contribution.

@dolftax dolftax deleted the metricbeat-http-json-arrays branch March 13, 2018 10:34
ycombinator added a commit that referenced this pull request Nov 12, 2018
…#8974)

Cherry-pick of PR #8951 to 6.x branch. Original message: 

Motivated by https://discuss.elastic.co/t/how-to-query-json-url-with-http-module/155572 and follow up doc PR to #6480.

This PR documents the `json.is_array` setting supported by the `http/json` metricset. The setting was introduced in #6480 but not documented at the time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants