owpengram-server/cmd/telesrv/log_encoder.go
2026-07-25 07:31:27 +03:00

177 lines
5.7 KiB
Go

package main
import (
"fmt"
"strings"
"time"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)
var compactBufferPool = buffer.NewPool()
// compactEncoder renders one readable line per entry: short time, level,
// logger, message, then "key=value" pairs in call order — instead of zap's
// stock console encoder, which appends fields as a raw JSON blob and makes
// high-volume RPC logs unreadable at a glance. It writes fields straight
// into a pooled buffer.Buffer (no map/interface{} boxing on the hot path),
// so it stays close to the JSON/console encoders' cost.
type compactEncoder struct {
cfg zapcore.EncoderConfig
buf *buffer.Buffer // accumulated fields from Logger.With(...)
}
func newCompactEncoder(cfg zapcore.EncoderConfig) *compactEncoder {
return &compactEncoder{cfg: cfg, buf: compactBufferPool.Get()}
}
func (e *compactEncoder) Clone() zapcore.Encoder {
clone := &compactEncoder{cfg: e.cfg, buf: compactBufferPool.Get()}
clone.buf.AppendString(e.buf.String())
return clone
}
func (e *compactEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
line := compactBufferPool.Get()
line.AppendString(ent.Time.Format("15:04:05.000"))
line.AppendByte(' ')
lvl := ent.Level.CapitalString()
line.AppendString(lvl)
for i := len(lvl); i < 5; i++ {
line.AppendByte(' ')
}
line.AppendByte(' ')
if ent.LoggerName != "" {
line.AppendByte('[')
line.AppendString(ent.LoggerName)
line.AppendString("] ")
}
line.AppendString(ent.Message)
if e.buf.Len() > 0 {
line.AppendByte(' ')
line.AppendString(e.buf.String())
}
if len(fields) > 0 {
tmp := &compactEncoder{cfg: e.cfg, buf: compactBufferPool.Get()}
for _, f := range fields {
f.AddTo(tmp)
}
if tmp.buf.Len() > 0 {
line.AppendByte(' ')
line.AppendString(tmp.buf.String())
}
tmp.buf.Free()
}
if ent.Caller.Defined {
line.AppendString(" (")
line.AppendString(ent.Caller.TrimmedPath())
line.AppendByte(')')
}
if ent.Stack != "" {
line.AppendByte('\n')
line.AppendString(ent.Stack)
}
line.AppendByte('\n')
return line, nil
}
func (e *compactEncoder) addKey(key string) {
if e.buf.Len() > 0 {
e.buf.AppendByte(' ')
}
e.buf.AppendString(key)
e.buf.AppendByte('=')
}
// appendSafeString keeps "key=value value2" pairs unambiguous: values with
// whitespace, '=' or '"' get quoted, everything else (the vast majority —
// method names, hex ids, enum-ish strings) is written bare.
func (e *compactEncoder) appendSafeString(s string) {
if s == "" || strings.ContainsAny(s, " \t\n=\"") {
e.buf.AppendByte('"')
e.buf.AppendString(strings.ReplaceAll(s, `"`, `\"`))
e.buf.AppendByte('"')
return
}
e.buf.AppendString(s)
}
func (e *compactEncoder) AddBool(key string, v bool) { e.addKey(key); e.buf.AppendBool(v) }
func (e *compactEncoder) AddInt64(key string, v int64) { e.addKey(key); e.buf.AppendInt(v) }
func (e *compactEncoder) AddInt(key string, v int) { e.AddInt64(key, int64(v)) }
func (e *compactEncoder) AddInt32(key string, v int32) { e.AddInt64(key, int64(v)) }
func (e *compactEncoder) AddInt16(key string, v int16) { e.AddInt64(key, int64(v)) }
func (e *compactEncoder) AddInt8(key string, v int8) { e.AddInt64(key, int64(v)) }
func (e *compactEncoder) AddUint64(key string, v uint64) { e.addKey(key); e.buf.AppendUint(v) }
func (e *compactEncoder) AddUint(key string, v uint) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddUint32(key string, v uint32) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddUint16(key string, v uint16) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddUint8(key string, v uint8) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddUintptr(key string, v uintptr) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddFloat64(key string, v float64) { e.addKey(key); e.buf.AppendFloat(v, 64) }
func (e *compactEncoder) AddFloat32(key string, v float32) {
e.addKey(key)
e.buf.AppendFloat(float64(v), 32)
}
func (e *compactEncoder) AddDuration(key string, v time.Duration) {
e.addKey(key)
e.buf.AppendString(v.String())
}
func (e *compactEncoder) AddTime(key string, v time.Time) {
e.addKey(key)
e.buf.AppendTime(v, "15:04:05.000")
}
func (e *compactEncoder) AddString(key, v string) { e.addKey(key); e.appendSafeString(v) }
func (e *compactEncoder) AddByteString(key string, v []byte) {
e.addKey(key)
e.appendSafeString(string(v))
}
func (e *compactEncoder) AddBinary(key string, v []byte) { e.addKey(key); fmt.Fprintf(e.buf, "%x", v) }
func (e *compactEncoder) AddComplex128(key string, v complex128) {
e.addKey(key)
fmt.Fprintf(e.buf, "%v", v)
}
func (e *compactEncoder) AddComplex64(key string, v complex64) { e.AddComplex128(key, complex128(v)) }
func (e *compactEncoder) AddReflected(key string, v interface{}) error {
e.addKey(key)
fmt.Fprintf(e.buf, "%v", v)
return nil
}
// OpenNamespace isn't used by any log call in this codebase today; nested
// fields would just render as flat top-level keys, which is an acceptable
// degradation for a format whose whole point is staying flat and scannable.
func (e *compactEncoder) OpenNamespace(string) {}
func (e *compactEncoder) AddArray(key string, m zapcore.ArrayMarshaler) error {
tmp := zapcore.NewMapObjectEncoder()
if err := tmp.AddArray("v", m); err != nil {
e.addKey(key)
e.buf.AppendString("!ERROR")
return err
}
e.addKey(key)
fmt.Fprintf(e.buf, "%v", tmp.Fields["v"])
return nil
}
func (e *compactEncoder) AddObject(key string, m zapcore.ObjectMarshaler) error {
tmp := zapcore.NewMapObjectEncoder()
if err := tmp.AddObject("v", m); err != nil {
e.addKey(key)
e.buf.AppendString("!ERROR")
return err
}
e.addKey(key)
fmt.Fprintf(e.buf, "%v", tmp.Fields["v"])
return nil
}