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
Conversation
api/server/call_logs.go
Outdated
|
||
c.JSON(http.StatusOK, callLogResponse{"Successfully loaded log", &callObj}) | ||
c.Header("Content-Type", "text/plain") | ||
c.JSON(http.StatusOK, b.String()) |
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.
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?
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.
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
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 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
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 hate they default to approve -.-
return err | ||
} | ||
|
||
return m.ds.InsertLog(ctx, appName, callID, newCallLog) |
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 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
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.
metric ds is kind of the wrong place to put this, too.
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.
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)
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 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.
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.
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)
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.
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.
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'd like to deal with gzip thing in first place, only then plaintext api thing.
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 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
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.
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).
8ce792d
to
9893c56
Compare
api/server/call_logs.go
Outdated
AppName: appName, | ||
Log: b.String(), | ||
switch c.Request.Header.Get("Accept") { | ||
case "application/json": |
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 only gets the first value from Accept, which may be a list, and we likely want to prefer plaintext
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 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.
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.
ok, for the old endpoint that makes sense. do the updated swagger clients request text/plain
properly for this endpoint?
Okay, the swagger doc is updated. Will push new client(s) once swagger doc will be at the master. |
api/server/call_logs.go
Outdated
for _, mimeType := range mimeTypes { | ||
switch mimeType { | ||
case "text/plain": | ||
c.String(http.StatusOK, b.String()) |
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 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.
aside from fixing the buffer usage, would be great to know 2 things before approving:
in addition to that, an api test that covers both the |
Answering your questions:
Regarding API test, okay, that's possible. |
961b33a
to
54a2b44
Compare
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 |
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. |
54a2b44
to
6fed1d5
Compare
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. |
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. |
it seems fine to default to json, I guess users may expect this if they don't provide a content type. even with |
api/server/call_logs.go
Outdated
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) |
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.
the status code is already written at this point, can either remove this or move it before io.Copy
, either works.
api/server/call_logs.go
Outdated
Log: b.String(), | ||
mimeTypes, _ := c.Request.Header["Accept"] | ||
|
||
for _, mimeType := range mimeTypes { |
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 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.
api/server/call_logs.go
Outdated
"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 |
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 don't think this is really true, we should probably support json perpetually if somebody asks for it
6fed1d5
to
b499026
Compare
api/server/call_logs.go
Outdated
} | ||
} | ||
// if we've reached this point it means that Fn didn't recognize Accepted content type | ||
c.Status(http.StatusNotAcceptable) |
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 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
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.
please add a body to this response, as well
previous version was producing the following "couldn't find a swagger spec"
8818917
to
da7e2f8
Compare
api/server/call_logs.go
Outdated
} | ||
} | ||
// if we've reached this point it means that Fn didn't recognize Accepted content type | ||
WriteError(c, c.Writer, http.StatusNotAcceptable, |
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.
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.
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.
thanks!
Fn API changed.
Swagger updated.
Certain API tests were disabled to make CI happy.