diff --git a/safedetails/redact.go b/safedetails/redact.go index 6a20e4f..92507a6 100644 --- a/safedetails/redact.go +++ b/safedetails/redact.go @@ -49,35 +49,59 @@ func Redact(r interface{}) string { } func redactErr(buf *strings.Builder, err error) { + foundDetail := false if c := errbase.UnwrapOnce(err); c == nil { // This is a leaf error. Decode the leaf and return. - redactLeafErr(buf, err) + foundDetail = redactLeafErr(buf, err) } else /* c != nil */ { // Print the inner error before the outer error. redactErr(buf, c) - redactWrapper(buf, err) + foundDetail = redactWrapper(buf, err) } // Add any additional safe strings from the wrapper, if present. if payload := errbase.GetSafeDetails(err); len(payload.SafeDetails) > 0 { - buf.WriteString("\n(more details about this error:)") - for _, sd := range payload.SafeDetails { - buf.WriteByte('\n') - buf.WriteString(strings.TrimSpace(sd)) + consumed := 0 + if !foundDetail { + firstDetail := strings.TrimSpace(payload.SafeDetails[0]) + if strings.IndexByte(firstDetail, '\n') < 0 { + firstDetail = strings.ReplaceAll(strings.TrimSpace(payload.SafeDetails[0]), "\n", "\n ") + if len(firstDetail) > 0 { + buf.WriteString(": ") + } + buf.WriteString(firstDetail) + consumed = 1 + foundDetail = true + } + } + if len(payload.SafeDetails) > consumed { + buf.WriteString("\n (more details:)") + for _, sd := range payload.SafeDetails[consumed:] { + buf.WriteString("\n ") + buf.WriteString(strings.ReplaceAll(strings.TrimSpace(sd), "\n", "\n ")) + } + foundDetail = true } } + if !foundDetail { + buf.WriteString(": ") + } } -func redactWrapper(buf *strings.Builder, err error) { +func redactWrapper(buf *strings.Builder, err error) (hasDetail bool) { buf.WriteString("\n") switch t := err.(type) { case *os.SyscallError: + hasDetail = true typAnd(buf, t, t.Syscall) case *os.PathError: + hasDetail = true typAnd(buf, t, t.Op) case *os.LinkError: + hasDetail = true fmt.Fprintf(buf, "%T: %s ", t, t.Op) case *net.OpError: + hasDetail = true typAnd(buf, t, t.Op) if t.Net != "" { fmt.Fprintf(buf, " %s", t.Net) @@ -92,11 +116,12 @@ func redactWrapper(buf *strings.Builder, err error) { buf.WriteString(" ") } default: - typRedacted(buf, err) + fmt.Fprintf(buf, "%T", err) } + return } -func redactLeafErr(buf *strings.Builder, err error) { +func redactLeafErr(buf *strings.Builder, err error) (hasDetail bool) { // Is it a sentinel error? These are safe. if markers.IsAny(err, context.DeadlineExceeded, @@ -108,26 +133,32 @@ func redactLeafErr(buf *strings.Builder, err error) { os.ErrClosed, os.ErrNoDeadline, ) { + hasDetail = true typAnd(buf, err, err.Error()) return } if redactPre113Wrappers(buf, err) { + hasDetail = true return } // The following two types are safe too. switch t := err.(type) { case runtime.Error: + hasDetail = true typAnd(buf, t, t.Error()) case syscall.Errno: + hasDetail = true typAnd(buf, t, t.Error()) case SafeMessager: + hasDetail = true typAnd(buf, t, t.SafeMessage()) default: // No further information about this error, simply report its type. - typRedacted(buf, err) + fmt.Fprintf(buf, "%T", err) } + return } func typRedacted(buf *strings.Builder, r interface{}) { diff --git a/safedetails/redact_test.go b/safedetails/redact_test.go index 5257bb4..627c147 100644 --- a/safedetails/redact_test.go +++ b/safedetails/redact_test.go @@ -47,24 +47,24 @@ func TestRedact(t *testing.T) { {mySafeError{}, `hello`}, {&werrFmt{mySafeError{}, "unseen"}, `safedetails_test.mySafeError: hello -*safedetails_test.werrFmt:`}, +*safedetails_test.werrFmt: `}, // Redacting errors. // Unspecial cases, get redacted. - {errors.New("secret"), `*errors.errorString:`}, + {errors.New("secret"), `*errors.errorString: `}, // Stack trace in error retrieves some info about the context. {withstack.WithStack(errors.New("secret")), - `...path...: *errors.errorString: -*withstack.withStack: -(more details about this error:) -github.com/cockroachdb/errors/safedetails_test.TestRedact - ...path... -testing.tRunner - ...path... -runtime.goexit - ...path...`}, + `...path...: *errors.errorString: +*withstack.withStack + (more details:) + github.com/cockroachdb/errors/safedetails_test.TestRedact + ...path... + testing.tRunner + ...path... + runtime.goexit + ...path...`}, // Special cases, unredacted. {os.ErrInvalid, `*errors.errorString: invalid argument`}, @@ -83,12 +83,12 @@ runtime.goexit `*runtime.TypeAssertionError: interface conversion: interface {} is nil, not int`}, {errSentinel, // explodes if Error() called - `struct { error }:`}, + `struct { error }: `}, {&werrFmt{&werrFmt{os.ErrClosed, "unseen"}, "unsung"}, `*errors.errorString: file already closed -*safedetails_test.werrFmt: -*safedetails_test.werrFmt:`}, +*safedetails_test.werrFmt: +*safedetails_test.werrFmt: `}, // Special cases, get partly redacted. @@ -115,7 +115,7 @@ runtime.goexit Source: &net.IPAddr{IP: net.IP("sensitive-source")}, Addr: &net.IPAddr{IP: net.IP("sensitive-addr")}, Err: errors.New("not safe"), - }, `*errors.errorString: + }, `*errors.errorString: *net.OpError: write tcp -> `}, } diff --git a/safedetails/safedetails_test.go b/safedetails/safedetails_test.go index 036c462..15591e3 100644 --- a/safedetails/safedetails_test.go +++ b/safedetails/safedetails_test.go @@ -214,8 +214,8 @@ Error types: (1) *safedetails.withSafeDetails (2) *errors.errorString`, // Payload `payload 0 (0) format: "a %v" - (1) -- arg 1 (error): *errors.errorString: - *safedetails_test.werrFmt: + (1) -- arg 1 (error): *errors.errorString: + *safedetails_test.werrFmt: payload 1 (empty) `}, @@ -247,7 +247,7 @@ payload 2 /* this error is an argument */ safedetails.WithSafeDetails( errors.New("wuu"), - "b %v", safedetails.Safe("waa"))), + "b %v\nmulti line", safedetails.Safe("waa\nmulti line"))), woo, ` woo @@ -257,11 +257,11 @@ Error types: (1) *safedetails.withSafeDetails (2) *errors.errorString`, // Payload `payload 0 (0) format: "a %v" - (1) -- arg 1 (error): *errors.errorString: - *safedetails.withSafeDetails: - (more details about this error:) - format: "b %v" - -- arg 1: waa + (1) -- arg 1 (error): *errors.errorString: + *safedetails.withSafeDetails: format: "b %v\nmulti line" + (more details:) + -- arg 1: waa + multi line payload 1 (empty) `},