From 7858d179655dec2f70d070a293e162a04f83fae5 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Tue, 29 Oct 2024 12:08:39 +0300 Subject: [PATCH] optimize output Signed-off-by: Pavel Okhlopkov --- pkg/unilogger/handler.go | 2 +- pkg/unilogger/output.go | 149 +++++++++++++++++++++++++++++---------- 2 files changed, 113 insertions(+), 38 deletions(-) diff --git a/pkg/unilogger/handler.go b/pkg/unilogger/handler.go index 13c7b256..cadef281 100644 --- a/pkg/unilogger/handler.go +++ b/pkg/unilogger/handler.go @@ -110,7 +110,7 @@ func (h *SlogHandler) Handle(ctx context.Context, r slog.Record) error { return err } - logOutput.Fields = b + logOutput.FieldsJSON = b[1 : len(b)-1] } buf := bytes.NewBuffer([]byte{}) diff --git a/pkg/unilogger/output.go b/pkg/unilogger/output.go index ee4b1699..9af19009 100644 --- a/pkg/unilogger/output.go +++ b/pkg/unilogger/output.go @@ -1,64 +1,139 @@ package unilogger -import ( - "bytes" - "encoding/json" -) - type LogOutput struct { - Level string - Name string - Message string - Source string - Fields []byte - Stacktrace string - Time string + Level string `json:"level"` + Name string `json:"logger"` + Message string `json:"msg"` + Source string `json:"source"` + FieldsJSON []byte `json:"-"` + Stacktrace string `json:"stacktrace"` + Time string `json:"time"` } func (a *LogOutput) MarshalJSON() ([]byte, error) { - fields := [][]byte{ - formJSONKeyValue("level", a.Level), - } + render := Render{} + render.buf = append(render.buf, '{') + + render.JSONKeyValue("level", a.Level) + + render.buf = append(render.buf, ',') if a.Name != "" { - fields = append(fields, formJSONKeyValue("logger", a.Name)) + render.JSONKeyValue("logger", a.Name) + render.buf = append(render.buf, ',') } - fields = append(fields, formJSONKeyValue("msg", a.Message)) + render.JSONKeyValue("msg", a.Message) + render.buf = append(render.buf, ',') if a.Source != "" { - fields = append(fields, formJSONKeyValue("source", a.Source)) + render.JSONKeyValue("source", a.Source) + render.buf = append(render.buf, ',') } - if len(a.Fields) > 0 { - fields = append(fields, a.Fields[1:len(a.Fields)-1]) + if len(a.FieldsJSON) > 0 { + render.buf = append(render.buf, a.FieldsJSON...) + render.buf = append(render.buf, ',') } if a.Stacktrace != "" { - fields = append(fields, formJSONKeyValue("stacktrace", a.Stacktrace)) + render.JSONKeyValue("stacktrace", a.Stacktrace) + render.buf = append(render.buf, ',') } - fields = append(fields, formJSONKeyValue("time", a.Time)) + render.JSONKeyValue("time", a.Time) - b := []byte{} - b = append(b, '{') - b = append(b, bytes.Join(fields, []byte{','})...) - b = append(b, '}') + render.buf = append(render.buf, '}') - return b, nil + return render.buf, nil } -func formJSONKeyValue(key, value string) []byte { - b := bytes.NewBuffer([]byte{}) +type Render struct { + buf []byte +} - if len(key) == 0 && len(value) == 0 { - return nil - } +var escapes = [256]bool{ + '"': true, + '<': true, + // do not escape ' character + // '\'': true, + '\\': true, + '\b': true, + '\f': true, + '\n': true, + '\r': true, + '\t': true, +} - _ = json.NewEncoder(b).Encode(map[string]string{ - key: value, - }) +func (r *Render) JSONKeyValue(key, value string) { + r.buf = append(r.buf, '"') + r.string(key) + r.buf = append(r.buf, '"', ':', '"') + r.string(value) + r.buf = append(r.buf, '"') +} + +func (e *Render) string(s string) { + for _, c := range []byte(s) { + if escapes[c] { + e.escapes(s) + return + } + } + e.buf = append(e.buf, s...) +} - // remove {} and \n - return b.Bytes()[1 : b.Len()-2] +func (e *Render) escapes(s string) { + n := len(s) + j := 0 + if n > 0 { + // Hint the compiler to remove bounds checks in the loop below. + _ = s[n-1] + } + for i := 0; i < n; i++ { + switch s[i] { + case '"': + e.buf = append(e.buf, s[j:i]...) + e.buf = append(e.buf, '\\', '"') + j = i + 1 + case '\\': + e.buf = append(e.buf, s[j:i]...) + e.buf = append(e.buf, '\\', '\\') + j = i + 1 + case '\n': + e.buf = append(e.buf, s[j:i]...) + e.buf = append(e.buf, '\\', 'n') + j = i + 1 + case '\r': + e.buf = append(e.buf, s[j:i]...) + e.buf = append(e.buf, '\\', 'r') + j = i + 1 + case '\t': + e.buf = append(e.buf, s[j:i]...) + e.buf = append(e.buf, '\\', 't') + j = i + 1 + case '\f': + e.buf = append(e.buf, s[j:i]...) + e.buf = append(e.buf, '\\', 'u', '0', '0', '0', 'c') + j = i + 1 + case '\b': + e.buf = append(e.buf, s[j:i]...) + e.buf = append(e.buf, '\\', 'u', '0', '0', '0', '8') + j = i + 1 + case '<': + e.buf = append(e.buf, s[j:i]...) + e.buf = append(e.buf, '\\', 'u', '0', '0', '3', 'c') + j = i + 1 + // do not escape ' character + // case '\'': + // e.buf = append(e.buf, s[j:i]...) + // e.buf = append(e.buf, '\\', 'u', '0', '0', '2', '7') + // j = i + 1 + case 0: + e.buf = append(e.buf, s[j:i]...) + e.buf = append(e.buf, '\\', 'u', '0', '0', '0', '0') + j = i + 1 + } + } + e.buf = append(e.buf, s[j:]...) }