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

Ship call logs to the user as text/plain instead of JSON #615

Merged
merged 15 commits into from Jan 2, 2018

Conversation

denismakogon
Copy link
Member

Fn API changed.
Swagger updated.
Certain API tests were disabled to make CI happy.


c.JSON(http.StatusOK, callLogResponse{"Successfully loaded log", &callObj})
c.Header("Content-Type", "text/plain")
c.JSON(http.StatusOK, b.String())
Copy link
Member

Choose a reason for hiding this comment

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

From docs, it sounds like c.JSON() is going to set the content-type to json again?

https://godoc.org/github.com/gin-gonic/gin#Context.JSON

maybe c.String() or c.Data() would work better?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, but it’s funny, line above that set content type to text worked, so the response content type at client side was text/plain, but not json

rdallman
rdallman previously approved these changes Dec 21, 2017
Copy link
Contributor

@rdallman rdallman left a comment

Choose a reason for hiding this comment

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

I'm concerned about breaking this API. I don't think there's any reason we can't return both a JSON or a plaintext from this endpoint, based on Accept headers. I'd also like to start storing all our logs in gzip form and returning a gzip string from here which will be similar in nature (Accept-Encoding). I have a little patch laying around playing with the gzip stuff, I can dust it off and try to finish it or we can just do the Accept header stuff here and add gzip later, it's not too complex so you can take over if you'd like. But at this point I don't think we should be breaking the API since a bunch of people have CLI laying around now, esp when we can easily support the old API and just upgrade clients around the updated API.

let me know re: gzip

Copy link
Contributor

@rdallman rdallman left a comment

Choose a reason for hiding this comment

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

i hate they default to approve -.-

return err
}

return m.ds.InsertLog(ctx, appName, callID, newCallLog)
Copy link
Contributor

Choose a reason for hiding this comment

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

let's punt on this (didn't get a reply from you). we should discuss. we want to thread gzip through the whole system really, it's not this straightforward. then we aren't gathering un-compressed bytes (in memory/files) and then compressing them just on upload. let's just fix plain text api here

Copy link
Contributor

Choose a reason for hiding this comment

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

metric ds is kind of the wrong place to put this, too.

Copy link
Contributor

Choose a reason for hiding this comment

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

anyway i ran into complications trying that (hence my abandonment). but now i know what was wrong (there was another since fixed bug in logs)

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure i understand your first comment.

So, if that is not metrics ds, so gzip compressing should be just in every logstore implementation, which doesn't seem reasonable.

Copy link
Contributor

Choose a reason for hiding this comment

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

metric ds is not the only option. i don't think we need any implementation. i can dig up my patch and try to explain more concretely, am happy to push forward with the plaintext api meanwhile (based on Accept headers)

Copy link
Member Author

Choose a reason for hiding this comment

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

there's one problem - swagger, not sure that they support multiple kinds of responses in different formation: same response code, but the body is different.

Copy link
Member Author

Choose a reason for hiding this comment

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

i'd like to deal with gzip thing in first place, only then plaintext api thing.

Copy link
Contributor

Choose a reason for hiding this comment

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

we can change swagger to plaintext, that's fine, and new clients will use it, but we need backwards compat for old clients using json still. the gzip isn't pressing, really, and there's code that has to be written for compatibility so it's not perfectly straightforward

Copy link
Member Author

Choose a reason for hiding this comment

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

Done, API remains compatible with older clients. So, i'd like to know your thoughts and how you see gzip'ing function's logs.

As far as i understand there's nothing much code needed to do actual compressing, right? But the bigger question to me is where it happens in order to be the same for all stores (sql and minion at this moment, obviously it should happen just right before going to actual implementations).

AppName: appName,
Log: b.String(),
switch c.Request.Header.Get("Accept") {
case "application/json":
Copy link
Contributor

Choose a reason for hiding this comment

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

this only gets the first value from Accept, which may be a list, and we likely want to prefer plaintext

Copy link
Member Author

Choose a reason for hiding this comment

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

I do aware that Accept can be a list, but fn_go sends exactly one item (which is application/json). So the best option i see here is to loop over MIME types.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, for the old endpoint that makes sense. do the updated swagger clients request text/plain properly for this endpoint?

@denismakogon
Copy link
Member Author

Okay, the swagger doc is updated. Will push new client(s) once swagger doc will be at the master.

for _, mimeType := range mimeTypes {
switch mimeType {
case "text/plain":
c.String(http.StatusOK, b.String())
Copy link
Contributor

Choose a reason for hiding this comment

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

one of the nice benefits of having a plaintext api here is that we don't need to buffer and encode the log. we should only read the log into a buffer for the deprecated json path and for the new plaintext api, we can just copy the log directly to the response connection. for minio/s3 backend this will read directly from the s3 connection and copy it to the client response which means we aren't buffering it at all internally, which is pretty slick.

@rdallman
Copy link
Contributor

rdallman commented Dec 23, 2017

aside from fixing the buffer usage, would be great to know 2 things before approving:

  • this code works with the old swagger clients, for sure (pick one, cli even)
  • the plaintext path is executed with the newly generated swagger clients. if not, can we fix our swagger doc to make this possible?

in addition to that, an api test that covers both the Accept: application/json and Accept: text/plain and makes sure that the output is what we expect would be great (also wrt priorities if both are given)

@denismakogon
Copy link
Member Author

Answering your questions:

  1. This code works with old swagger clients (API tests are passing and Logs API tests are there).
  2. API works with the new client (patch for Ship logs as text cli#128).

Regarding API test, okay, that's possible.

@denismakogon denismakogon force-pushed the ship-log-as-text branch 2 times, most recently from 961b33a to 54a2b44 Compare December 23, 2017 20:02
@rdallman
Copy link
Contributor

rdallman commented Dec 26, 2017

thanks. please fix #615 (comment) -- for plaintext, the code should read:

io.Copy(c.ResponseWriter, logReader)

with no buffer in between

edit: you may also need to set the content type explicitly since go detection may detect something else from the log, and we do not want to use the gin String method, as we don't want to turn this into a string it's a wasteful [large] allocation.

@treeder
Copy link
Contributor

treeder commented Dec 27, 2017

I see that a lot of code was rolled back in this PR, but why did/do we want to change the default response to plain/text instead of json like it was? I get the reason that someone might want this, but I would find it strange that a particular endpoint returns plain text by default while the rest return JSON.

@denismakogon
Copy link
Member Author

So, there's at least one reason why we should not ship logs as JSON. It forces us to do buffering of the whole log object which can actually be pretty huge. The second reason is JSON that we ship at this moment is mostly useless because users would need just log entity, why do they need exactly the same call ID as they passed in the request (just remember we dropped app name from router response object). Without call ID current JSON response is nothing but a JSON with 1 item only. So, tell me what's the point of having a JSON response that forces us to buffer the log at the server and do JSON parsing at the client side, why not just ship log as text?

Regarding amendments, at 1st iteration, i decided to implement gzip thing for making logs smaller, but @rdallman had concerns about the implementation, so we decided to stick with 1st part of the original issue - ship log as text, and as the follow-up - use gzip to shrink logs.

@treeder
Copy link
Contributor

treeder commented Dec 28, 2017

Well, consistency for one. Every endpoint returns JSON by default except one.

And two, there's a high probability we could add more fields.

I like leaving it up to the user to pass in text/plain if they want to change to another format, but default should probably remain JSON. If it's too big of a log (which would take a lot and we probably shouldn't be storing it anyways), we could return an error and tell them to pass in text/plain to retrieve it.

@rdallman
Copy link
Contributor

rdallman commented Jan 2, 2018

it seems fine to default to json, I guess users may expect this if they don't provide a content type. even with jq magic the logs are pretty hard to parse out (via eyeball) of json since the newline chars get escaped fwiw. at least in our clients we generate and in the cli we can send in Accept: text/plain and get back a plain text log, which should cover most usage (so we get the benefits). wrt fields, at a minimum we can maintain some endpoint that just returns a plaintext log, whether it's the current one or not -- we have .../call/:id for additional call fields, for most stuff probably.

mimeTypes, _ := c.Request.Header["Accept"]

for _, mimeType := range mimeTypes {
switch mimeType {
case "text/plain":
c.String(http.StatusOK, b.String())
io.Copy(c.Writer, logReader)
c.Status(http.StatusOK)
Copy link
Contributor

Choose a reason for hiding this comment

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

the status code is already written at this point, can either remove this or move it before io.Copy, either works.

Log: b.String(),
mimeTypes, _ := c.Request.Header["Accept"]

for _, mimeType := range mimeTypes {
Copy link
Contributor

@rdallman rdallman Jan 2, 2018

Choose a reason for hiding this comment

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

this isn't the behavior we want. an example:

Accept: application/xml, text/plain will return a json blob. the user clearly can handle text/plain but it's not explicit whether they can handle json, we should return text/plain since it was specified and json was not.

One suggestion might be switch on exact Accept headers in the loop, since we do want to treat them as a prioritized list, and only default to json if we don't find any of the types we expect. the HTML spec pretty clearly defines the behavior we should expose here: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 and we also need to handle q values (which won't be switched on properly in this code); for q values, I don't think we need to worry about quality reduction [for now/ever], just to parse them properly. in the spec, if any Accept header is provided but we don't find either application/json or text/plain we're supposed to return 406. If no Accept is provided (which implies */*) it seems like we've agreed to return application/json. I like the idea of sticking to the spec (adding 406 possibility).

edit: some clarification of spec finer details.

"net/http"

"github.com/fnproject/fn/api"
"github.com/fnproject/fn/api/models"
"github.com/gin-gonic/gin"
)

// note: for backward compatibility, will go away later
Copy link
Contributor

Choose a reason for hiding this comment

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

i don't think this is really true, we should probably support json perpetually if somebody asks for it

}
}
// if we've reached this point it means that Fn didn't recognize Accepted content type
c.Status(http.StatusNotAcceptable)
Copy link
Contributor

Choose a reason for hiding this comment

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

if no accept headers are given, we also want to return a json. this is only if they provide accept headers, none of which we can offer.

p.s. extract the function for the json logic, please

Copy link
Contributor

Choose a reason for hiding this comment

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

please add a body to this response, as well

}
}
// if we've reached this point it means that Fn didn't recognize Accepted content type
WriteError(c, c.Writer, http.StatusNotAcceptable,
Copy link
Contributor

Choose a reason for hiding this comment

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

every other call in the front end uses handleErrorResponse, with an error that implements models.APIError. please do the same here, so that we might log this in handleErrorResponse, add a metric for it, whatever else we may want to do in there so that we have telemetry around this and uniformity in the front end error handling.

Copy link
Contributor

@rdallman rdallman left a comment

Choose a reason for hiding this comment

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

thanks!

@rdallman rdallman merged commit 60d2ca2 into master Jan 2, 2018
@rdallman rdallman deleted the ship-log-as-text branch January 2, 2018 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants