This commit is contained in:
Patrick Ohly 2026-02-18 13:53:47 -08:00 committed by GitHub
commit 2c54d8bee7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 77 additions and 0 deletions

View file

@ -17,14 +17,17 @@ limitations under the License.
package runtime
import (
"bytes"
"context"
"fmt"
"net/http"
"runtime"
"strings"
"sync"
"time"
"k8s.io/klog/v2"
"k8s.io/klog/v2/textlogger"
)
var (
@ -155,8 +158,46 @@ var ErrorHandlers = []ErrorHandler{
backoffError(1 * time.Millisecond),
}
// ErrorHandler is called indirectly through [HandleError], [HandleErrorWithContext] or [HandleErrorWithLogger].
// It is passed the same parameters that a structured logging backend needs to log a problem.
// It follows the semantic described for [HandleErrorWithContext] and [logr.Logger.Error]:
// - err is optional and may be nil
// - msg is string that describes the problem
// - keysAndValues contains additional information that varies between different occurrences of the problem
//
// [ErrorToString] can be used to convert these parameters into a single string, using the klog text output.
type ErrorHandler func(ctx context.Context, err error, msg string, keysAndValues ...interface{})
// ErrorToString takes the parameters passed to [ErrorHandler] and
// formats them as a string using the klog text output.
//
// If any of the values is a multi-line string, then the resulting
// string also uses line breaks and indention for the sake of readability.
// Does not include a trailing newline.
//
// Use errors.New if an error instead of a string is needed.
func ErrorToString(err error, msg string, keysAndValues ...interface{}) string {
var buffer bytes.Buffer
// There's no API for suppressing the header, so we let it fill with constant
// information and then just strip it. Will become simpler should
// https://github.com/kubernetes/klog/issues/429 get implemented.
config := textlogger.NewConfig(
textlogger.Output(&buffer),
textlogger.Backtrace(func(int) (string, int) { return "", 0 }),
textlogger.FixedTime(time.Time{}),
)
logger := textlogger.NewLogger(config)
logger.Error(err, msg, keysAndValues...)
result := buffer.String()
result = strings.TrimSpace(result)
index := strings.Index(result, "] ")
if index > 0 {
return result[index+2:]
}
return result
}
// HandlerError is a method to invoke when a non-user facing piece of code cannot
// return an error and needs to indicate it has been ignored. Invoking this method
// is preferable to logging the error - the default behavior is to log but the

View file

@ -311,3 +311,39 @@ func TestHandleError(t *testing.T) {
})
}
}
func TestErrorToString(t *testing.T) {
for name, tt := range map[string]struct {
err error
msg string
kvs []any
expectString string
}{
"simple": {
errors.New("some error"),
"Unhandled error",
nil,
`"Unhandled error" err="some error"`,
},
"nil-error": {
nil,
"Some problem occurred",
nil,
`"Some problem occurred"`,
},
"keys-and-values": {
errors.New("some error"),
"Some error occurred",
[]any{"str", "foobar", "int", 1, "multiLine", "line 1\nline 2"},
`"Some error occurred" err="some error" str="foobar" int=1 multiLine=<
line 1
line 2
>`,
},
} {
t.Run(name, func(t *testing.T) {
actualString := ErrorToString(tt.err, tt.msg, tt.kvs...)
assert.Equal(t, tt.expectString, actualString)
})
}
}