123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- /*
- Copyright 2021 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package serialize
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "strconv"
- "github.com/go-logr/logr"
- )
- type textWriter interface {
- WriteText(*bytes.Buffer)
- }
- // WithValues implements LogSink.WithValues. The old key/value pairs are
- // assumed to be well-formed, the new ones are checked and padded if
- // necessary. It returns a new slice.
- func WithValues(oldKV, newKV []interface{}) []interface{} {
- if len(newKV) == 0 {
- return oldKV
- }
- newLen := len(oldKV) + len(newKV)
- hasMissingValue := newLen%2 != 0
- if hasMissingValue {
- newLen++
- }
- // The new LogSink must have its own slice.
- kv := make([]interface{}, 0, newLen)
- kv = append(kv, oldKV...)
- kv = append(kv, newKV...)
- if hasMissingValue {
- kv = append(kv, missingValue)
- }
- return kv
- }
- // MergeKVs deduplicates elements provided in two key/value slices.
- //
- // Keys in each slice are expected to be unique, so duplicates can only occur
- // when the first and second slice contain the same key. When that happens, the
- // key/value pair from the second slice is used. The first slice must be well-formed
- // (= even key/value pairs). The second one may have a missing value, in which
- // case the special "missing value" is added to the result.
- func MergeKVs(first, second []interface{}) []interface{} {
- maxLength := len(first) + (len(second)+1)/2*2
- if maxLength == 0 {
- // Nothing to do at all.
- return nil
- }
- if len(first) == 0 && len(second)%2 == 0 {
- // Nothing to be overridden, second slice is well-formed
- // and can be used directly.
- return second
- }
- // Determine which keys are in the second slice so that we can skip
- // them when iterating over the first one. The code intentionally
- // favors performance over completeness: we assume that keys are string
- // constants and thus compare equal when the string values are equal. A
- // string constant being overridden by, for example, a fmt.Stringer is
- // not handled.
- overrides := map[interface{}]bool{}
- for i := 0; i < len(second); i += 2 {
- overrides[second[i]] = true
- }
- merged := make([]interface{}, 0, maxLength)
- for i := 0; i+1 < len(first); i += 2 {
- key := first[i]
- if overrides[key] {
- continue
- }
- merged = append(merged, key, first[i+1])
- }
- merged = append(merged, second...)
- if len(merged)%2 != 0 {
- merged = append(merged, missingValue)
- }
- return merged
- }
- type Formatter struct {
- AnyToStringHook AnyToStringFunc
- }
- type AnyToStringFunc func(v interface{}) string
- // MergeKVsInto is a variant of MergeKVs which directly formats the key/value
- // pairs into a buffer.
- func (f Formatter) MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) {
- if len(first) == 0 && len(second) == 0 {
- // Nothing to do at all.
- return
- }
- if len(first) == 0 && len(second)%2 == 0 {
- // Nothing to be overridden, second slice is well-formed
- // and can be used directly.
- for i := 0; i < len(second); i += 2 {
- f.KVFormat(b, second[i], second[i+1])
- }
- return
- }
- // Determine which keys are in the second slice so that we can skip
- // them when iterating over the first one. The code intentionally
- // favors performance over completeness: we assume that keys are string
- // constants and thus compare equal when the string values are equal. A
- // string constant being overridden by, for example, a fmt.Stringer is
- // not handled.
- overrides := map[interface{}]bool{}
- for i := 0; i < len(second); i += 2 {
- overrides[second[i]] = true
- }
- for i := 0; i < len(first); i += 2 {
- key := first[i]
- if overrides[key] {
- continue
- }
- f.KVFormat(b, key, first[i+1])
- }
- // Round down.
- l := len(second)
- l = l / 2 * 2
- for i := 1; i < l; i += 2 {
- f.KVFormat(b, second[i-1], second[i])
- }
- if len(second)%2 == 1 {
- f.KVFormat(b, second[len(second)-1], missingValue)
- }
- }
- func MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) {
- Formatter{}.MergeAndFormatKVs(b, first, second)
- }
- const missingValue = "(MISSING)"
- // KVListFormat serializes all key/value pairs into the provided buffer.
- // A space gets inserted before the first pair and between each pair.
- func (f Formatter) KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
- for i := 0; i < len(keysAndValues); i += 2 {
- var v interface{}
- k := keysAndValues[i]
- if i+1 < len(keysAndValues) {
- v = keysAndValues[i+1]
- } else {
- v = missingValue
- }
- f.KVFormat(b, k, v)
- }
- }
- func KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
- Formatter{}.KVListFormat(b, keysAndValues...)
- }
- // KVFormat serializes one key/value pair into the provided buffer.
- // A space gets inserted before the pair.
- func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
- b.WriteByte(' ')
- // Keys are assumed to be well-formed according to
- // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments
- // for the sake of performance. Keys with spaces,
- // special characters, etc. will break parsing.
- if sK, ok := k.(string); ok {
- // Avoid one allocation when the key is a string, which
- // normally it should be.
- b.WriteString(sK)
- } else {
- b.WriteString(fmt.Sprintf("%s", k))
- }
- // The type checks are sorted so that more frequently used ones
- // come first because that is then faster in the common
- // cases. In Kubernetes, ObjectRef (a Stringer) is more common
- // than plain strings
- // (https://github.com/kubernetes/kubernetes/pull/106594#issuecomment-975526235).
- switch v := v.(type) {
- case textWriter:
- writeTextWriterValue(b, v)
- case fmt.Stringer:
- writeStringValue(b, StringerToString(v))
- case string:
- writeStringValue(b, v)
- case error:
- writeStringValue(b, ErrorToString(v))
- case logr.Marshaler:
- value := MarshalerToValue(v)
- // A marshaler that returns a string is useful for
- // delayed formatting of complex values. We treat this
- // case like a normal string. This is useful for
- // multi-line support.
- //
- // We could do this by recursively formatting a value,
- // but that comes with the risk of infinite recursion
- // if a marshaler returns itself. Instead we call it
- // only once and rely on it returning the intended
- // value directly.
- switch value := value.(type) {
- case string:
- writeStringValue(b, value)
- default:
- f.formatAny(b, value)
- }
- case []byte:
- // In https://github.com/kubernetes/klog/pull/237 it was decided
- // to format byte slices with "%+q". The advantages of that are:
- // - readable output if the bytes happen to be printable
- // - non-printable bytes get represented as unicode escape
- // sequences (\uxxxx)
- //
- // The downsides are that we cannot use the faster
- // strconv.Quote here and that multi-line output is not
- // supported. If developers know that a byte array is
- // printable and they want multi-line output, they can
- // convert the value to string before logging it.
- b.WriteByte('=')
- b.WriteString(fmt.Sprintf("%+q", v))
- default:
- f.formatAny(b, v)
- }
- }
- func KVFormat(b *bytes.Buffer, k, v interface{}) {
- Formatter{}.KVFormat(b, k, v)
- }
- // formatAny is the fallback formatter for a value. It supports a hook (for
- // example, for YAML encoding) and itself uses JSON encoding.
- func (f Formatter) formatAny(b *bytes.Buffer, v interface{}) {
- b.WriteRune('=')
- if f.AnyToStringHook != nil {
- b.WriteString(f.AnyToStringHook(v))
- return
- }
- encoder := json.NewEncoder(b)
- l := b.Len()
- if err := encoder.Encode(v); err != nil {
- // This shouldn't happen. We discard whatever the encoder
- // wrote and instead dump an error string.
- b.Truncate(l)
- b.WriteString(fmt.Sprintf(`"<internal error: %v>"`, err))
- return
- }
- // Remove trailing newline.
- b.Truncate(b.Len() - 1)
- }
- // StringerToString converts a Stringer to a string,
- // handling panics if they occur.
- func StringerToString(s fmt.Stringer) (ret string) {
- defer func() {
- if err := recover(); err != nil {
- ret = fmt.Sprintf("<panic: %s>", err)
- }
- }()
- ret = s.String()
- return
- }
- // MarshalerToValue invokes a marshaler and catches
- // panics.
- func MarshalerToValue(m logr.Marshaler) (ret interface{}) {
- defer func() {
- if err := recover(); err != nil {
- ret = fmt.Sprintf("<panic: %s>", err)
- }
- }()
- ret = m.MarshalLog()
- return
- }
- // ErrorToString converts an error to a string,
- // handling panics if they occur.
- func ErrorToString(err error) (ret string) {
- defer func() {
- if err := recover(); err != nil {
- ret = fmt.Sprintf("<panic: %s>", err)
- }
- }()
- ret = err.Error()
- return
- }
- func writeTextWriterValue(b *bytes.Buffer, v textWriter) {
- b.WriteByte('=')
- defer func() {
- if err := recover(); err != nil {
- fmt.Fprintf(b, `"<panic: %s>"`, err)
- }
- }()
- v.WriteText(b)
- }
- func writeStringValue(b *bytes.Buffer, v string) {
- data := []byte(v)
- index := bytes.IndexByte(data, '\n')
- if index == -1 {
- b.WriteByte('=')
- // Simple string, quote quotation marks and non-printable characters.
- b.WriteString(strconv.Quote(v))
- return
- }
- // Complex multi-line string, show as-is with indention like this:
- // I... "hello world" key=<
- // <tab>line 1
- // <tab>line 2
- // >
- //
- // Tabs indent the lines of the value while the end of string delimiter
- // is indented with a space. That has two purposes:
- // - visual difference between the two for a human reader because indention
- // will be different
- // - no ambiguity when some value line starts with the end delimiter
- //
- // One downside is that the output cannot distinguish between strings that
- // end with a line break and those that don't because the end delimiter
- // will always be on the next line.
- b.WriteString("=<\n")
- for index != -1 {
- b.WriteByte('\t')
- b.Write(data[0 : index+1])
- data = data[index+1:]
- index = bytes.IndexByte(data, '\n')
- }
- if len(data) == 0 {
- // String ended with line break, don't add another.
- b.WriteString(" >")
- } else {
- // No line break at end of last line, write rest of string and
- // add one.
- b.WriteByte('\t')
- b.Write(data)
- b.WriteString("\n >")
- }
- }
|