-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Conversation
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? |
3816fdc
to
672d0f7
Compare
@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 |
There was a problem hiding this comment.
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 @@ | |||
{ | |||
[{ |
There was a problem hiding this comment.
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.
metricbeat/module/http/json/json.go
Outdated
return nil, err | ||
} | ||
|
||
switch body := jsonBody.(type) { |
There was a problem hiding this comment.
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.
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. |
metricbeat/module/http/json/json.go
Outdated
defer response.Body.Close() | ||
|
||
var jsonBody interface{} | ||
var event []common.MapStr |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
672d0f7
to
bdf6e18
Compare
fmt.Fprint(w, `[{"hello1":"world1"}, {"hello2": "world2"}]`) | ||
} | ||
|
||
func serveJsonObj(w http.ResponseWriter, r *http.Request) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
dad33c5
to
9936bde
Compare
@ruflin This PR LGTM. Let me know if any changes are required. |
} else { | ||
event = jsonBody | ||
event = jsonBody.(common.MapStr) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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? :)
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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")) |
There was a problem hiding this comment.
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{} { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
9936bde
to
9183920
Compare
Build failed due to fetch (thus build) error. Re-trigger should fix it. |
There was a problem hiding this 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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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{} { |
There was a problem hiding this comment.
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.
c8c1cc2
to
1bf2901
Compare
Sorry about the merge commit. Please squash before merging into |
@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 <[email protected]>
1bf2901
to
d89f6eb
Compare
jenkins, test it |
@jaipradeesh Thanks a lot for your contribution. |
…#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.
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
. Ifsomeone 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 [email protected]
Ref: #6472