Skip to content

Commit 9650638

Browse files
committed
add tracing as well for better error debugging
1 parent a84f368 commit 9650638

File tree

3 files changed

+38
-19
lines changed

3 files changed

+38
-19
lines changed

cli/errors.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ func (RootCmd) errorExample() *clibase.Cmd {
7070
{
7171
Use: "multi-error",
7272
Handler: func(inv *clibase.Invocation) error {
73-
return xerrors.Errorf("wrapped %w", errors.Join(
73+
return xerrors.Errorf("wrapped: %w", errors.Join(
7474
xerrors.Errorf("first error: %w", errorWithStackTrace()),
7575
xerrors.Errorf("second error: %w", errorWithStackTrace()),
76-
apiErrorNoHelper,
76+
xerrors.Errorf("wrapped api error: %w", apiErrorNoHelper),
7777
))
7878
},
7979
},

cli/root.go

+34-15
Original file line numberDiff line numberDiff line change
@@ -1016,7 +1016,7 @@ type prettyErrorFormatter struct {
10161016
// format formats the error to the console. This error should be human
10171017
// readable.
10181018
func (p *prettyErrorFormatter) format(err error) {
1019-
output, _ := cliHumanFormatError(err, &formatOpts{
1019+
output, _ := cliHumanFormatError("", err, &formatOpts{
10201020
Verbose: p.verbose,
10211021
})
10221022
// always trail with a newline
@@ -1041,7 +1041,7 @@ const indent = " "
10411041
// go run main.go exp example-error cmd
10421042
// go run main.go exp example-error multi-error
10431043
// go run main.go exp example-error validation
1044-
func cliHumanFormatError(err error, opts *formatOpts) (string, bool) {
1044+
func cliHumanFormatError(from string, err error, opts *formatOpts) (string, bool) {
10451045
if opts == nil {
10461046
opts = &formatOpts{}
10471047
}
@@ -1053,28 +1053,26 @@ func cliHumanFormatError(err error, opts *formatOpts) (string, bool) {
10531053
multiErrors := multi.Unwrap()
10541054
if len(multiErrors) == 1 {
10551055
// Format as a single error
1056-
return cliHumanFormatError(multiErrors[0], opts)
1056+
return cliHumanFormatError(from, multiErrors[0], opts)
10571057
}
1058-
return formatMultiError(multiErrors, opts), true
1058+
return formatMultiError(from, multiErrors, opts), true
10591059
}
10601060

10611061
// First check for sentinel errors that we want to handle specially.
10621062
// Order does matter! We want to check for the most specific errors first.
1063-
//var sdkError *codersdk.Error
1064-
//if errors.As(err, &sdkError) {
10651063
if sdkError, ok := err.(*codersdk.Error); ok {
1066-
return formatCoderSDKError(sdkError, opts), true
1064+
return formatCoderSDKError(from, sdkError, opts), true
10671065
}
10681066

1069-
//var cmdErr *clibase.RunCommandError
1070-
//if errors.As(err, &cmdErr) {
10711067
if cmdErr, ok := err.(*clibase.RunCommandError); ok {
1068+
// no need to pass the "from" context to this since it is always
1069+
// top level. We care about what is below this.
10721070
return formatRunCommandError(cmdErr, opts), true
10731071
}
10741072

10751073
uw, ok := err.(interface{ Unwrap() error })
10761074
if ok {
1077-
msg, special := cliHumanFormatError(uw.Unwrap(), opts)
1075+
msg, special := cliHumanFormatError(from+traceError(err), uw.Unwrap(), opts)
10781076
if special {
10791077
return msg, special
10801078
}
@@ -1100,16 +1098,20 @@ func cliHumanFormatError(err error, opts *formatOpts) (string, bool) {
11001098
// <verbose error message>
11011099
// 2. <heading error message>
11021100
// <verbose error message>
1103-
func formatMultiError(multi []error, opts *formatOpts) string {
1101+
func formatMultiError(from string, multi []error, opts *formatOpts) string {
11041102
var errorStrings []string
11051103
for _, err := range multi {
1106-
msg, _ := cliHumanFormatError(err, opts)
1104+
msg, _ := cliHumanFormatError("", err, opts)
11071105
errorStrings = append(errorStrings, msg)
11081106
}
11091107

11101108
// Write errors out
11111109
var str strings.Builder
1112-
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("%d errors encountered:", len(multi))))
1110+
var traceMsg string
1111+
if opts.Verbose {
1112+
traceMsg = fmt.Sprintf("Trace=[%s])", from)
1113+
}
1114+
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("%d errors encountered: %s", len(multi), traceMsg)))
11131115
for i, errStr := range errorStrings {
11141116
// Indent each error
11151117
errStr = strings.ReplaceAll(errStr, "\n", "\n"+indent)
@@ -1138,7 +1140,7 @@ func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) strin
11381140
var str strings.Builder
11391141
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("Encountered an error running %q", err.Cmd.FullName())))
11401142

1141-
msgString, special := cliHumanFormatError(err.Err, opts)
1143+
msgString, special := cliHumanFormatError("", err.Err, opts)
11421144
_, _ = str.WriteString("\n")
11431145
if special {
11441146
_, _ = str.WriteString(msgString)
@@ -1151,11 +1153,15 @@ func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) strin
11511153

11521154
// formatCoderSDKError come from API requests. In verbose mode, add the
11531155
// request debug information.
1154-
func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string {
1156+
func formatCoderSDKError(from string, err *codersdk.Error, opts *formatOpts) string {
11551157
var str strings.Builder
11561158
if opts.Verbose {
11571159
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("API request error to \"%s:%s\". Status code %d", err.Method(), err.URL(), err.StatusCode())))
11581160
_, _ = str.WriteString("\n")
1161+
if from != "" {
1162+
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("Trace=[%s]", from)))
1163+
_, _ = str.WriteString("\n")
1164+
}
11591165
}
11601166

11611167
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), err.Message))
@@ -1171,6 +1177,19 @@ func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string {
11711177
return str.String()
11721178
}
11731179

1180+
// traceError is a helper function that aides developers debugging failed cli
1181+
// commands. When we pretty print errors, we lose the context in which they came.
1182+
// This function adds the context back. Unfortunately there is no easy way to get
1183+
// the prefix to: "error string: %w", so we do a bit of string manipulation.
1184+
func traceError(err error) string {
1185+
if uw, ok := err.(interface{ Unwrap() error }); ok {
1186+
a, b := err.Error(), uw.Unwrap().Error()
1187+
c := strings.TrimSuffix(a, b)
1188+
return c
1189+
}
1190+
return err.Error()
1191+
}
1192+
11741193
// These styles are arbitrary.
11751194
func headLineStyle() pretty.Style {
11761195
return cliui.DefaultStyles.Error

enterprise/wsproxy/wsproxy.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,10 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
158158
// TODO: Probably do some version checking here
159159
info, err := client.SDKClient.BuildInfo(ctx)
160160
if err != nil {
161-
return nil, errors.Join(
161+
return nil, fmt.Errorf("buildinfo: %w", errors.Join(
162162
fmt.Errorf("unable to fetch build info from primary coderd. Are you sure %q is a coderd instance?", opts.DashboardURL),
163163
err,
164-
)
164+
))
165165
}
166166
if info.WorkspaceProxy {
167167
return nil, xerrors.Errorf("%q is a workspace proxy, not a primary coderd instance", opts.DashboardURL)

0 commit comments

Comments
 (0)