From 9eb11ce052f3fa5b3043226ee27d2053ce8e1a6d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 14 Sep 2023 10:14:27 -0500 Subject: [PATCH 1/2] chore: add debug information to wsproxy errors --- enterprise/coderd/proxyhealth/proxyhealth.go | 28 +++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/enterprise/coderd/proxyhealth/proxyhealth.go b/enterprise/coderd/proxyhealth/proxyhealth.go index 4b9e3f13982f8..d6110a1de60af 100644 --- a/enterprise/coderd/proxyhealth/proxyhealth.go +++ b/enterprise/coderd/proxyhealth/proxyhealth.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "net/url" "strings" @@ -289,7 +290,32 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID case err == nil && resp.StatusCode != http.StatusOK: // Unhealthy as we did reach the proxy but it got an unexpected response. status.Status = Unhealthy - status.Report.Errors = []string{fmt.Sprintf("unexpected status code %d", resp.StatusCode)} + var builder strings.Builder + builder.WriteString(fmt.Sprintf("unexpected status code %d. ", resp.StatusCode)) + contentType := resp.Header.Get("content-type") + body, _ := io.ReadAll(resp.Body) + + switch { + case strings.Contains(contentType, "html"): + // Showing html payloads is useful, but we display this error message + // on our FE. An html payload is quite large and would be hard to + // format decently. Just tell the user to make the request themselves + // to observe the response. + builder.WriteString(fmt.Sprintf("\nThe response was html, which is unexpected. "+ + "Send a request to %s from the Coderd environment to debug this issue.", reqURL)) + case strings.Contains(contentType, "json"): + builder.WriteString(fmt.Sprintf("\nJSON response payload: %s", string(body))) + default: + // Just set an arbitrary cutoff point. The user can always debug with a manual request. + if len(body) < 1000 { + builder.WriteString(fmt.Sprintf("\n%q response payload: %s", contentType, string(body))) + } else { + builder.WriteString(fmt.Sprintf("\nThe response content type was %q, which is unexpected. "+ + "Send a request to %s from the Coderd environment to debug this issue.", contentType, reqURL)) + } + } + + status.Report.Errors = []string{builder.String()} case err != nil: // Request failed, mark the proxy as unreachable. status.Status = Unreachable From bad257b8eb0741208bdc0ccb926f04fa0d726b47 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 20 Sep 2023 09:27:19 -0500 Subject: [PATCH 2/2] Use codersdk.ReadBodyAsError --- enterprise/coderd/proxyhealth/proxyhealth.go | 34 ++++++++------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/enterprise/coderd/proxyhealth/proxyhealth.go b/enterprise/coderd/proxyhealth/proxyhealth.go index d6110a1de60af..9f3abdac849f2 100644 --- a/enterprise/coderd/proxyhealth/proxyhealth.go +++ b/enterprise/coderd/proxyhealth/proxyhealth.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" "net/url" "strings" @@ -291,27 +290,22 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID // Unhealthy as we did reach the proxy but it got an unexpected response. status.Status = Unhealthy var builder strings.Builder + // This string is shown on the UI where newlines are respected. + // This error message is not ever decoded programmatically, so keep it human- + // readable. builder.WriteString(fmt.Sprintf("unexpected status code %d. ", resp.StatusCode)) - contentType := resp.Header.Get("content-type") - body, _ := io.ReadAll(resp.Body) - - switch { - case strings.Contains(contentType, "html"): - // Showing html payloads is useful, but we display this error message - // on our FE. An html payload is quite large and would be hard to - // format decently. Just tell the user to make the request themselves - // to observe the response. - builder.WriteString(fmt.Sprintf("\nThe response was html, which is unexpected. "+ - "Send a request to %s from the Coderd environment to debug this issue.", reqURL)) - case strings.Contains(contentType, "json"): - builder.WriteString(fmt.Sprintf("\nJSON response payload: %s", string(body))) - default: - // Just set an arbitrary cutoff point. The user can always debug with a manual request. - if len(body) < 1000 { - builder.WriteString(fmt.Sprintf("\n%q response payload: %s", contentType, string(body))) + builder.WriteString(fmt.Sprintf("\nEncountered error, send a request to %q from the Coderd environment to debug this issue.", reqURL)) + err := codersdk.ReadBodyAsError(resp) + if err != nil { + var apiErr *codersdk.Error + if xerrors.As(err, &apiErr) { + builder.WriteString(fmt.Sprintf("\nError Message: %s\nError Detail: %s", apiErr.Message, apiErr.Detail)) + for _, v := range apiErr.Validations { + // Pretty sure this is not possible from the called endpoint, but just in case. + builder.WriteString(fmt.Sprintf("\n\tValidation: %s=%s", v.Field, v.Detail)) + } } else { - builder.WriteString(fmt.Sprintf("\nThe response content type was %q, which is unexpected. "+ - "Send a request to %s from the Coderd environment to debug this issue.", contentType, reqURL)) + builder.WriteString(fmt.Sprintf("\nError: %s", err.Error())) } }