From 0256f75bfb6aae7550b5f4daa5ddc80e78df6ecb Mon Sep 17 00:00:00 2001 From: Tigor Hutasuhut Date: Fri, 23 Aug 2024 12:29:47 +0700 Subject: [PATCH] zlog: update implementations --- core/zcaller/zcaller.go | 40 ++++++++++++++++++ core/zlog/buffer.go | 8 ++-- core/zlog/caller.go | 52 +++++++++++++++++++++++ core/zlog/null.go | 13 ++++++ core/zlog/zlog.go | 92 ++++++++++++++++++++++++++++------------- go.mod | 1 + go.sum | 2 + 7 files changed, 175 insertions(+), 33 deletions(-) create mode 100644 core/zcaller/zcaller.go diff --git a/core/zcaller/zcaller.go b/core/zcaller/zcaller.go new file mode 100644 index 0000000..5fe2656 --- /dev/null +++ b/core/zcaller/zcaller.go @@ -0,0 +1,40 @@ +package zcaller + +import ( + "runtime" +) + +// Current returns the ProgramCounter of the caller. +// +// It gives information about the function that called +// this. +// +// Returns 0 if caller information is not available. +func Current() uintptr { + return Get(3) +} + +// Get works just like Current, but it allows you to +// specify the number of frames to skip. +// +// For reference, current is equivalent to Get(3). +// (use Get(2) because Current wraps this function and has to add +1 to the skip value) +// +// Returns 0 if caller information is not available. +func Get(skip int) uintptr { + pcs := make([]uintptr, 1) + n := runtime.Callers(skip, pcs) + if n == 0 { + return 0 + } + return pcs[0] +} + +// Frame creates a runtime.Frame from a ProgramCounter. +// +// Return zero value if the frame is not available. +func Frame(pc uintptr) runtime.Frame { + frames := runtime.CallersFrames([]uintptr{pc}) + frame, _ := frames.Next() + return frame +} diff --git a/core/zlog/buffer.go b/core/zlog/buffer.go index 546770b..0f904b0 100644 --- a/core/zlog/buffer.go +++ b/core/zlog/buffer.go @@ -16,7 +16,7 @@ func newJsonHandlerPool(handlerOption *slog.HandlerOptions) *jsonHandlerPool { New: func() any { buf := new(bytes.Buffer) return &jsonHandler{ - Buffer: buf, + buf: buf, pool: handlerPool, JSONHandler: slog.NewJSONHandler(buf, handlerOption), } @@ -34,15 +34,15 @@ func (jhp *jsonHandlerPool) Get() *jsonHandler { } type jsonHandler struct { - *bytes.Buffer + buf *bytes.Buffer pool *jsonHandlerPool *slog.JSONHandler } func (j *jsonHandler) Put() { // Only put back to pool if buffer is smaller than 4MB in RAM size. - if j.Buffer.Cap() < 1024*1024*4 { - j.Buffer.Reset() + if j.buf.Cap() < 1024*1024*4 { + j.buf.Reset() j.pool.Put(j) } } diff --git a/core/zlog/caller.go b/core/zlog/caller.go index d3e6811..f2851c5 100644 --- a/core/zlog/caller.go +++ b/core/zlog/caller.go @@ -1 +1,53 @@ package zlog + +import ( + "log/slog" + "os" + "runtime" + "strings" + + "gitlab.bareksa.com/backend/zen/core/zcaller" +) + +// Caller represents the caller of a function. +// +// It gives helper methods to get the caller's information. +type Caller uintptr + +func (ca Caller) LogValue() slog.Value { + if ca == 0 { + return slog.AnyValue(nil) + } + + f := frame{ca.Frame()} + + return slog.GroupValue( + slog.String("file", f.ShortFile()), + slog.Int("line", f.Line), + slog.String("function", f.ShortFunction()), + ) +} + +func (ca Caller) Frame() runtime.Frame { + return zcaller.Frame(uintptr(ca)) +} + +type frame struct { + runtime.Frame +} + +func (f frame) ShortFile() string { + wd, err := os.Getwd() + if err != nil { + return f.File + } + if after, found := strings.CutPrefix(f.File, wd); found { + return strings.TrimPrefix(after, string(os.PathSeparator)) + } + return f.File +} + +func (f frame) ShortFunction() string { + split := strings.Split(f.Function, string(os.PathSeparator)) + return split[len(split)-1] +} diff --git a/core/zlog/null.go b/core/zlog/null.go index d3e6811..6b960f3 100644 --- a/core/zlog/null.go +++ b/core/zlog/null.go @@ -1 +1,14 @@ package zlog + +import ( + "context" + "log/slog" +) + +// Null is a no-op log handler. +type Null struct{} + +func (Null) Enabled(context.Context, slog.Level) bool { return false } +func (Null) Handle(context.Context, slog.Record) error { return nil } +func (Null) WithAttrs([]slog.Attr) slog.Handler { return Null{} } +func (nu Null) WithGroup(string) slog.Handler { return Null{} } diff --git a/core/zlog/zlog.go b/core/zlog/zlog.go index aaabadb..2333861 100644 --- a/core/zlog/zlog.go +++ b/core/zlog/zlog.go @@ -5,8 +5,10 @@ import ( "io" "log/slog" "sync" + "time" "github.com/fatih/color" + "github.com/tidwall/pretty" ) type WriteLocker interface { @@ -27,15 +29,15 @@ type writeLocker struct { } type Log struct { - level slog.Level - bufPool *bufferPool - jsonPool *jsonHandlerPool - withAttrs []slog.Attr - withGroup []string - handlerOption *slog.HandlerOptions - pretty bool - color bool - writer WriteLocker + level slog.Level + bufPool *bufferPool + jsonPool *jsonHandlerPool + withAttrs []slog.Attr + withGroup []string + opts *slog.HandlerOptions + pretty bool + color bool + writer WriteLocker } // Clone clones the Logger, replacing the output writer with w. @@ -53,14 +55,14 @@ func (log *Log) Clone(w io.Writer) *Log { withGroup := make([]string, len(log.withGroup)) copy(withGroup, log.withGroup) return &Log{ - level: log.level, - withAttrs: withAttrs, - withGroup: withGroup, - handlerOption: log.handlerOption, - pretty: log.pretty, - bufPool: newBufferPool(), - jsonPool: newJsonHandlerPool(log.handlerOption), - writer: output, + level: log.level, + withAttrs: withAttrs, + withGroup: withGroup, + opts: log.opts, + pretty: log.pretty, + bufPool: newBufferPool(), + jsonPool: newJsonHandlerPool(log.opts), + writer: output, } } @@ -94,19 +96,51 @@ func (lo *Log) Handle(ctx context.Context, record slog.Record) error { buf := lo.bufPool.Get() defer lo.bufPool.Put(buf) - panic("not implemented") + if record.PC != 0 && lo.opts.AddSource { + f := frame{Caller(record.PC).Frame()} + levelColor.Fprint(buf, f.ShortFile()) + levelColor.Fprint(buf, ":") + levelColor.Fprint(buf, f.Line) + levelColor.Fprint(buf, " -- ") + levelColor.Fprint(buf, f.ShortFunction()) + buf.WriteByte('\n') + } - // if record.PC != 0 && pr.opts.AddSource { - // frame := caller.From(record.PC).Frame - // levelColor.Fprint(buf, frame.File) - // levelColor.Fprint(buf, ":") - // levelColor.Fprint(buf, frame.Line) - // levelColor.Fprint(buf, " -- ") - // split := strings.Split(frame.Function, string(os.PathSeparator)) - // fnName := split[len(split)-1] - // levelColor.Fprint(buf, fnName) - // buf.WriteByte('\n') - // } + if !record.Time.IsZero() { + const format = `[` + time.DateTime + `] ` + b := record.Time.AppendFormat(nil, format) + buf.Write(b) + } + + buf.WriteByte('[') + levelColor.Add(color.Bold).Fprint(buf, record.Level.String()) + buf.WriteString("] ") + + if record.Message != "" { + buf.WriteString(record.Message) + } + + buf.WriteByte('\n') + + jHandler := lo.jsonPool.Get() + defer jHandler.Put() + + _ = jHandler.Handle(ctx, record) + if jHandler.buf.Len() > 3 { // Skip empty objects like "{}\n" + jsonData := jHandler.buf.Bytes() + jsonData = pretty.Pretty(jsonData) + if lo.color { + jsonData = pretty.Color(jsonData, nil) + } + buf.Write(jsonData) + } + + buf.WriteByte('\n') + + lo.writer.Lock() + defer lo.writer.Unlock() + _, err := buf.WriteTo(lo.writer) + return err } // WithAttrs implements the slog.Handler interface. diff --git a/go.mod b/go.mod index 69708bd..45f5bd9 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( connectrpc.com/connect v1.16.2 connectrpc.com/grpcreflect v1.2.0 github.com/fatih/color v1.17.0 + github.com/tidwall/pretty v1.2.1 golang.org/x/net v0.23.0 google.golang.org/protobuf v1.34.2 ) diff --git a/go.sum b/go.sum index 0605658..3ab36a1 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=