From 2fce8ac40b2745b0f3526657305df4f4da9bdd82 Mon Sep 17 00:00:00 2001 From: Anton Sergunov Date: Fri, 14 Nov 2025 12:55:34 +0600 Subject: [PATCH 1/4] Refactor logging buffer usage to improve performance Updated the logging functions to utilize cached buffers more effectively by replacing the creation of new byte slices with calls to `AvailableBuffer()`. This change reduces memory allocations and enhances performance across multiple logging components, including `klog`, `klogr_slog`, and `textlogger`. Additionally, modified the JSON serialization in `keyvalues_slog.go` and `keyvalues.go` to leverage the same buffer optimization for quoting string values. This refactor aims to streamline logging operations and improve overall efficiency. Signed-off-by: Anton Sergunov --- internal/serialize/keyvalues.go | 2 +- internal/serialize/keyvalues_slog.go | 20 ++++++++++++++++---- klog.go | 8 ++++---- klogr_slog.go | 4 ++-- textlogger/textlogger.go | 11 ++++++----- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/internal/serialize/keyvalues.go b/internal/serialize/keyvalues.go index 9c3858ec..f8096e59 100644 --- a/internal/serialize/keyvalues.go +++ b/internal/serialize/keyvalues.go @@ -262,7 +262,7 @@ func writeStringValue(b *bytes.Buffer, v string) { if index == -1 { b.WriteByte('=') // Simple string, quote quotation marks and non-printable characters. - b.WriteString(strconv.Quote(v)) + b.Write(strconv.AppendQuote(b.AvailableBuffer(), v)) return } diff --git a/internal/serialize/keyvalues_slog.go b/internal/serialize/keyvalues_slog.go index 8e008436..03e105ec 100644 --- a/internal/serialize/keyvalues_slog.go +++ b/internal/serialize/keyvalues_slog.go @@ -132,7 +132,10 @@ func generateJSON(b *bytes.Buffer, v interface{}) { if i > 0 { b.WriteByte(',') } - b.WriteString(strconv.Quote(attr.Key)) + b.Write( + strconv.AppendQuote( + b.AvailableBuffer(), + attr.Key)) b.WriteByte(':') generateJSON(b, attr.Value) } @@ -144,15 +147,24 @@ func generateJSON(b *bytes.Buffer, v interface{}) { generateJSON(b, v.Any()) } case fmt.Stringer: - b.WriteString(strconv.Quote(StringerToString(v))) + b.Write( + strconv.AppendQuote( + b.AvailableBuffer(), + StringerToString(v))) case logr.Marshaler: generateJSON(b, MarshalerToValue(v)) case slog.LogValuer: generateJSON(b, slog.AnyValue(v).Resolve().Any()) case string: - b.WriteString(strconv.Quote(v)) + b.Write( + strconv.AppendQuote( + b.AvailableBuffer(), + v)) case error: - b.WriteString(strconv.Quote(v.Error())) + b.Write( + strconv.AppendQuote( + b.AvailableBuffer(), + v.Error())) default: formatAsJSON(b, v) } diff --git a/klog.go b/klog.go index 40b1b20b..afa319da 100644 --- a/klog.go +++ b/klog.go @@ -809,14 +809,14 @@ func (l *loggingT) infoS(logger *logWriter, filter LogFilter, depth int, msg str // printS is called from infoS and errorS if logger is not specified. // set log severity by s func (l *loggingT) printS(err error, s severity.Severity, depth int, msg string, keysAndValues ...interface{}) { + // Only create a new buffer if we don't have one cached. + b := buffer.GetBuffer() + // The message is always quoted, even if it contains line breaks. // If developers want multi-line output, they should use a small, fixed // message and put the multi-line output into a value. - qMsg := make([]byte, 0, 1024) + qMsg := b.AvailableBuffer() qMsg = strconv.AppendQuote(qMsg, msg) - - // Only create a new buffer if we don't have one cached. - b := buffer.GetBuffer() b.Write(qMsg) var errKV []interface{} diff --git a/klogr_slog.go b/klogr_slog.go index 901e28dd..06b9058a 100644 --- a/klogr_slog.go +++ b/klogr_slog.go @@ -63,10 +63,10 @@ func slogOutput(file string, line int, now time.Time, err error, s severity.Seve } // See printS. - qMsg := make([]byte, 0, 1024) + b := buffer.GetBuffer() + qMsg := b.AvailableBuffer() qMsg = strconv.AppendQuote(qMsg, msg) - b := buffer.GetBuffer() b.Write(qMsg) var errKV []interface{} diff --git a/textlogger/textlogger.go b/textlogger/textlogger.go index 6b9aab86..a5f52435 100644 --- a/textlogger/textlogger.go +++ b/textlogger/textlogger.go @@ -114,16 +114,17 @@ func runtimeBacktrace(skip int) (string, int) { } func (l *tlogger) printWithInfos(file string, line int, now time.Time, err error, s severity.Severity, msg string, kvList []interface{}) { - // The message is always quoted, even if it contains line breaks. - // If developers want multi-line output, they should use a small, fixed - // message and put the multi-line output into a value. - qMsg := make([]byte, 0, 1024) - qMsg = strconv.AppendQuote(qMsg, msg) // Only create a new buffer if we don't have one cached. b := buffer.GetBuffer() defer buffer.PutBuffer(b) + // The message is always quoted, even if it contains line breaks. + // If developers want multi-line output, they should use a small, fixed + // message and put the multi-line output into a value. + qMsg := b.AvailableBuffer() + qMsg = strconv.AppendQuote(qMsg, msg) + // Format header. if l.config.co.fixedTime != nil { now = *l.config.co.fixedTime From 0f1b22b313560f992029a5ae447d332eef67cfb9 Mon Sep 17 00:00:00 2001 From: Anton Sergunov Date: Fri, 14 Nov 2025 13:51:31 +0600 Subject: [PATCH 2/4] Fix tests Signed-off-by: Anton Sergunov --- klog.go | 7 ++++--- klogr_slog.go | 8 ++++---- textlogger/textlogger.go | 14 +++++++------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/klog.go b/klog.go index afa319da..4b92d43a 100644 --- a/klog.go +++ b/klog.go @@ -815,9 +815,10 @@ func (l *loggingT) printS(err error, s severity.Severity, depth int, msg string, // The message is always quoted, even if it contains line breaks. // If developers want multi-line output, they should use a small, fixed // message and put the multi-line output into a value. - qMsg := b.AvailableBuffer() - qMsg = strconv.AppendQuote(qMsg, msg) - b.Write(qMsg) + b.Write( + strconv.AppendQuote( + b.AvailableBuffer(), + msg)) var errKV []interface{} if err != nil { diff --git a/klogr_slog.go b/klogr_slog.go index 06b9058a..7ac627b3 100644 --- a/klogr_slog.go +++ b/klogr_slog.go @@ -64,10 +64,10 @@ func slogOutput(file string, line int, now time.Time, err error, s severity.Seve // See printS. b := buffer.GetBuffer() - qMsg := b.AvailableBuffer() - qMsg = strconv.AppendQuote(qMsg, msg) - - b.Write(qMsg) + b.Write( + strconv.AppendQuote( + b.AvailableBuffer(), + msg)) var errKV []interface{} if err != nil { diff --git a/textlogger/textlogger.go b/textlogger/textlogger.go index a5f52435..a9977a22 100644 --- a/textlogger/textlogger.go +++ b/textlogger/textlogger.go @@ -119,19 +119,19 @@ func (l *tlogger) printWithInfos(file string, line int, now time.Time, err error b := buffer.GetBuffer() defer buffer.PutBuffer(b) - // The message is always quoted, even if it contains line breaks. - // If developers want multi-line output, they should use a small, fixed - // message and put the multi-line output into a value. - qMsg := b.AvailableBuffer() - qMsg = strconv.AppendQuote(qMsg, msg) - // Format header. if l.config.co.fixedTime != nil { now = *l.config.co.fixedTime } b.FormatHeader(s, file, line, now) - b.Write(qMsg) + // The message is always quoted, even if it contains line breaks. + // If developers want multi-line output, they should use a small, fixed + // message and put the multi-line output into a value. + b.Write( + strconv.AppendQuote( + b.AvailableBuffer(), + msg)) var errKV []interface{} if err != nil { From 6db40fd05bd936f23b62790ced1cad1027b12741 Mon Sep 17 00:00:00 2001 From: Anton Sergunov Date: Fri, 14 Nov 2025 15:48:13 +0600 Subject: [PATCH 3/4] Get constant allocations back Signed-off-by: Anton Sergunov --- klog.go | 2 +- klogr_slog.go | 2 +- textlogger/textlogger.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/klog.go b/klog.go index 4b92d43a..4e9c29fc 100644 --- a/klog.go +++ b/klog.go @@ -817,7 +817,7 @@ func (l *loggingT) printS(err error, s severity.Severity, depth int, msg string, // message and put the multi-line output into a value. b.Write( strconv.AppendQuote( - b.AvailableBuffer(), + make([]byte, 0, 1024), msg)) var errKV []interface{} diff --git a/klogr_slog.go b/klogr_slog.go index 7ac627b3..1b2640c4 100644 --- a/klogr_slog.go +++ b/klogr_slog.go @@ -66,7 +66,7 @@ func slogOutput(file string, line int, now time.Time, err error, s severity.Seve b := buffer.GetBuffer() b.Write( strconv.AppendQuote( - b.AvailableBuffer(), + make([]byte, 0, 1024), msg)) var errKV []interface{} diff --git a/textlogger/textlogger.go b/textlogger/textlogger.go index a9977a22..ee6f0f1a 100644 --- a/textlogger/textlogger.go +++ b/textlogger/textlogger.go @@ -130,7 +130,7 @@ func (l *tlogger) printWithInfos(file string, line int, now time.Time, err error // message and put the multi-line output into a value. b.Write( strconv.AppendQuote( - b.AvailableBuffer(), + make([]byte, 0, 1024), msg)) var errKV []interface{} From 5025e083b0c56ccb1c9a96091f6fabaf12fd2fa6 Mon Sep 17 00:00:00 2001 From: Anton Sergunov Date: Fri, 14 Nov 2025 15:59:19 +0600 Subject: [PATCH 4/4] diff cleanup Signed-off-by: Anton Sergunov --- klog.go | 13 ++++++------- klogr_slog.go | 8 ++++---- textlogger/textlogger.go | 13 ++++++------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/klog.go b/klog.go index 4e9c29fc..40b1b20b 100644 --- a/klog.go +++ b/klog.go @@ -809,16 +809,15 @@ func (l *loggingT) infoS(logger *logWriter, filter LogFilter, depth int, msg str // printS is called from infoS and errorS if logger is not specified. // set log severity by s func (l *loggingT) printS(err error, s severity.Severity, depth int, msg string, keysAndValues ...interface{}) { - // Only create a new buffer if we don't have one cached. - b := buffer.GetBuffer() - // The message is always quoted, even if it contains line breaks. // If developers want multi-line output, they should use a small, fixed // message and put the multi-line output into a value. - b.Write( - strconv.AppendQuote( - make([]byte, 0, 1024), - msg)) + qMsg := make([]byte, 0, 1024) + qMsg = strconv.AppendQuote(qMsg, msg) + + // Only create a new buffer if we don't have one cached. + b := buffer.GetBuffer() + b.Write(qMsg) var errKV []interface{} if err != nil { diff --git a/klogr_slog.go b/klogr_slog.go index 1b2640c4..901e28dd 100644 --- a/klogr_slog.go +++ b/klogr_slog.go @@ -63,11 +63,11 @@ func slogOutput(file string, line int, now time.Time, err error, s severity.Seve } // See printS. + qMsg := make([]byte, 0, 1024) + qMsg = strconv.AppendQuote(qMsg, msg) + b := buffer.GetBuffer() - b.Write( - strconv.AppendQuote( - make([]byte, 0, 1024), - msg)) + b.Write(qMsg) var errKV []interface{} if err != nil { diff --git a/textlogger/textlogger.go b/textlogger/textlogger.go index ee6f0f1a..6b9aab86 100644 --- a/textlogger/textlogger.go +++ b/textlogger/textlogger.go @@ -114,6 +114,11 @@ func runtimeBacktrace(skip int) (string, int) { } func (l *tlogger) printWithInfos(file string, line int, now time.Time, err error, s severity.Severity, msg string, kvList []interface{}) { + // The message is always quoted, even if it contains line breaks. + // If developers want multi-line output, they should use a small, fixed + // message and put the multi-line output into a value. + qMsg := make([]byte, 0, 1024) + qMsg = strconv.AppendQuote(qMsg, msg) // Only create a new buffer if we don't have one cached. b := buffer.GetBuffer() @@ -125,13 +130,7 @@ func (l *tlogger) printWithInfos(file string, line int, now time.Time, err error } b.FormatHeader(s, file, line, now) - // The message is always quoted, even if it contains line breaks. - // If developers want multi-line output, they should use a small, fixed - // message and put the multi-line output into a value. - b.Write( - strconv.AppendQuote( - make([]byte, 0, 1024), - msg)) + b.Write(qMsg) var errKV []interface{} if err != nil {