feat: trace httpapi.{Read,Write} (#4134)

This commit is contained in:
Colin Adler
2022-09-21 17:07:00 -05:00
committed by GitHub
parent 1bf2dc0cc3
commit 5de6f86959
45 changed files with 919 additions and 774 deletions

View File

@ -15,6 +15,7 @@ import (
"github.com/go-playground/validator/v10"
"github.com/coder/coder/coderd/tracing"
"github.com/coder/coder/codersdk"
)
@ -49,16 +50,19 @@ func init() {
}
}
// Convenience error functions don't take contexts since their responses are
// static, it doesn't make much sense to trace them.
// ResourceNotFound is intentionally vague. All 404 responses should be identical
// to prevent leaking existence of resources.
func ResourceNotFound(rw http.ResponseWriter) {
Write(rw, http.StatusNotFound, codersdk.Response{
Write(context.Background(), rw, http.StatusNotFound, codersdk.Response{
Message: "Resource not found or you do not have access to this resource",
})
}
func Forbidden(rw http.ResponseWriter) {
Write(rw, http.StatusForbidden, codersdk.Response{
Write(context.Background(), rw, http.StatusForbidden, codersdk.Response{
Message: "Forbidden.",
})
}
@ -69,20 +73,29 @@ func InternalServerError(rw http.ResponseWriter, err error) {
details = err.Error()
}
Write(rw, http.StatusInternalServerError, codersdk.Response{
Write(context.Background(), rw, http.StatusInternalServerError, codersdk.Response{
Message: "An internal server error occurred.",
Detail: details,
})
}
func RouteNotFound(rw http.ResponseWriter) {
Write(rw, http.StatusNotFound, codersdk.Response{
Write(context.Background(), rw, http.StatusNotFound, codersdk.Response{
Message: "Route not found.",
})
}
// Write outputs a standardized format to an HTTP response body.
func Write(rw http.ResponseWriter, status int, response interface{}) {
// Write outputs a standardized format to an HTTP response body. ctx is used for
// tracing and can be nil for tracing to be disabled. Tracing this function is
// helpful because JSON marshaling can sometimes take a non-insignificant amount
// of time, and could help us catch outliers. Additionally, we can enrich span
// data a bit more since we have access to the actual interface{} we're
// marshaling, such as the number of elements in an array, which could help us
// spot routes that need to be paginated.
func Write(ctx context.Context, rw http.ResponseWriter, status int, response interface{}) {
_, span := tracing.StartSpan(ctx)
defer span.End()
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(true)
@ -100,12 +113,17 @@ func Write(rw http.ResponseWriter, status int, response interface{}) {
}
}
// Read decodes JSON from the HTTP request into the value provided.
// It uses go-validator to validate the incoming request body.
func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool {
// Read decodes JSON from the HTTP request into the value provided. It uses
// go-validator to validate the incoming request body. ctx is used for tracing
// and can be nil. Although tracing this function isn't likely too helpful, it
// was done to be consistent with Write.
func Read(ctx context.Context, rw http.ResponseWriter, r *http.Request, value interface{}) bool {
ctx, span := tracing.StartSpan(ctx)
defer span.End()
err := json.NewDecoder(r.Body).Decode(value)
if err != nil {
Write(rw, http.StatusBadRequest, codersdk.Response{
Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Request body must be valid JSON.",
Detail: err.Error(),
})
@ -121,14 +139,14 @@ func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool {
Detail: fmt.Sprintf("Validation failed for tag %q with value: \"%v\"", validationError.Tag(), validationError.Value()),
})
}
Write(rw, http.StatusBadRequest, codersdk.Response{
Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Validation failed.",
Validations: apiErrors,
})
return false
}
if err != nil {
Write(rw, http.StatusInternalServerError, codersdk.Response{
Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error validating request body payload.",
Detail: err.Error(),
})