logger: slog logger #266

Merged
vtolstov merged 5 commits from slog into master 2023-10-14 18:05:47 +03:00
8 changed files with 251 additions and 428 deletions
Showing only changes of commit 0df97183f8 - Show all commits

9
go.mod
View File

@ -9,13 +9,14 @@ require (
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/sync v0.1.0 golang.org/x/sync v0.3.0
golang.org/x/sys v0.12.0 golang.org/x/sys v0.12.0
google.golang.org/grpc v1.54.0 google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.30.0 google.golang.org/protobuf v1.31.0
) )
require ( require (
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
) )

20
go.sum
View File

@ -15,21 +15,21 @@ github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 h1:G/FZtUu7a6NTWl3KUHMV9
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,232 +0,0 @@
package logger
import (
"context"
"encoding/json"
"fmt"
"os"
"runtime"
"strings"
"sync"
"time"
"go.unistack.org/micro/v4/options"
)
type defaultLogger struct {
enc *json.Encoder
opts Options
sync.RWMutex
}
// Init(opts...) should only overwrite provided options
func (l *defaultLogger) Init(opts ...options.Option) error {
l.Lock()
for _, o := range opts {
o(&l.opts)
}
l.enc = json.NewEncoder(l.opts.Out)
// wrap the Log func
l.Unlock()
return nil
}
func (l *defaultLogger) String() string {
return "micro"
}
func (l *defaultLogger) Clone(opts ...options.Option) Logger {
newopts := NewOptions(opts...)
oldopts := l.opts
for _, o := range opts {
o(&newopts)
o(&oldopts)
}
l.Lock()
cl := &defaultLogger{opts: oldopts, enc: json.NewEncoder(l.opts.Out)}
l.Unlock()
return cl
}
func (l *defaultLogger) V(level Level) bool {
l.RLock()
ok := l.opts.Level.Enabled(level)
l.RUnlock()
return ok
}
func (l *defaultLogger) Level(level Level) {
l.Lock()
l.opts.Level = level
l.Unlock()
}
func (l *defaultLogger) Fields(fields ...interface{}) Logger {
l.RLock()
nl := &defaultLogger{opts: l.opts, enc: l.enc}
if len(fields) == 0 {
l.RUnlock()
return nl
} else if len(fields)%2 != 0 {
fields = fields[:len(fields)-1]
}
nl.opts.Fields = copyFields(l.opts.Fields)
nl.opts.Fields = append(nl.opts.Fields, fields...)
l.RUnlock()
return nl
}
func copyFields(src []interface{}) []interface{} {
dst := make([]interface{}, len(src))
copy(dst, src)
return dst
}
// logCallerfilePath returns a package/file:line description of the caller,
// preserving only the leaf directory name and file name.
func logCallerfilePath(loggingFilePath string) string {
// To make sure we trim the path correctly on Windows too, we
// counter-intuitively need to use '/' and *not* os.PathSeparator here,
// because the path given originates from Go stdlib, specifically
// runtime.Caller() which (as of Mar/17) returns forward slashes even on
// Windows.
//
// See https://github.com/golang/go/issues/3335
// and https://github.com/golang/go/issues/18151
//
// for discussion on the issue on Go side.
idx := strings.LastIndexByte(loggingFilePath, '/')
if idx == -1 {
return loggingFilePath
}
idx = strings.LastIndexByte(loggingFilePath[:idx], '/')
if idx == -1 {
return loggingFilePath
}
return loggingFilePath[idx+1:]
}
func (l *defaultLogger) Info(ctx context.Context, args ...interface{}) {
l.Log(ctx, InfoLevel, args...)
}
func (l *defaultLogger) Error(ctx context.Context, args ...interface{}) {
l.Log(ctx, ErrorLevel, args...)
}
func (l *defaultLogger) Debug(ctx context.Context, args ...interface{}) {
l.Log(ctx, DebugLevel, args...)
}
func (l *defaultLogger) Warn(ctx context.Context, args ...interface{}) {
l.Log(ctx, WarnLevel, args...)
}
func (l *defaultLogger) Trace(ctx context.Context, args ...interface{}) {
l.Log(ctx, TraceLevel, args...)
}
func (l *defaultLogger) Fatal(ctx context.Context, args ...interface{}) {
l.Log(ctx, FatalLevel, args...)
os.Exit(1)
}
func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, InfoLevel, msg, args...)
}
func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, ErrorLevel, msg, args...)
}
func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, DebugLevel, msg, args...)
}
func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, WarnLevel, msg, args...)
}
func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, TraceLevel, msg, args...)
}
func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, FatalLevel, msg, args...)
os.Exit(1)
}
func (l *defaultLogger) Log(ctx context.Context, level Level, args ...interface{}) {
if !l.V(level) {
return
}
l.RLock()
fields := copyFields(l.opts.Fields)
l.RUnlock()
fields = append(fields, "level", level.String())
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line))
}
fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05"))
if len(args) > 0 {
fields = append(fields, "msg", fmt.Sprint(args...))
}
out := make(map[string]interface{}, len(fields)/2)
for i := 0; i < len(fields); i += 2 {
out[fields[i].(string)] = fields[i+1]
}
l.RLock()
_ = l.enc.Encode(out)
l.RUnlock()
}
func (l *defaultLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
if !l.V(level) {
return
}
l.RLock()
fields := copyFields(l.opts.Fields)
l.RUnlock()
fields = append(fields, "level", level.String())
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line))
}
fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05"))
if len(args) > 0 {
fields = append(fields, "msg", fmt.Sprintf(msg, args...))
} else if msg != "" {
fields = append(fields, "msg", msg)
}
out := make(map[string]interface{}, len(fields)/2)
for i := 0; i < len(fields); i += 2 {
out[fields[i].(string)] = fields[i+1]
}
l.RLock()
_ = l.enc.Encode(out)
l.RUnlock()
}
func (l *defaultLogger) Options() Options {
return l.opts
}
// NewLogger builds a new logger based on options
func NewLogger(opts ...options.Option) Logger {
l := &defaultLogger{
opts: NewOptions(opts...),
}
l.enc = json.NewEncoder(l.opts.Out)
return l
}

View File

@ -87,14 +87,14 @@ func TestClone(t *testing.T) {
func TestRedirectStdLogger(t *testing.T) { func TestRedirectStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf)) l := NewLogger(WithLevel(ErrorLevel), WithOutput(buf))
if err := l.Init(); err != nil { if err := l.Init(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
fn := RedirectStdLogger(l, ErrorLevel) fn := RedirectStdLogger(l, ErrorLevel)
defer fn() defer fn()
log.Print("test") log.Print("test")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) { if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes()) t.Fatalf("logger error, buf %s", buf.Bytes())
} }
} }
@ -107,7 +107,7 @@ func TestStdLogger(t *testing.T) {
} }
lg := NewStdLogger(l, ErrorLevel) lg := NewStdLogger(l, ErrorLevel)
lg.Print("test") lg.Print("test")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) { if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes()) t.Fatalf("logger error, buf %s", buf.Bytes())
} }
} }
@ -120,19 +120,21 @@ func TestLogger(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
l.Trace(ctx, "trace_msg1") l.Trace(ctx, "trace_msg1")
l.Warn(ctx, "warn_msg1") // l.Warn(ctx, "warn_msg1")
l.Fields("error", "test").Info(ctx, "error message") // l.Fields("error", "test").Info(ctx, "error message")
l.Warn(ctx, "first", " ", "second") // l.Warn(ctx, "first second")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) { if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes()) t.Fatalf("logger tracer, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"warn","msg":"warn_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"test","level":"info","msg":"error message"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"level":"warn","msg":"first second"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
} }
/*
if !bytes.Contains(buf.Bytes(), []byte(`"warn","msg":"warn_msg1"`)) {
t.Fatalf("logger warn, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"test","level":"info","msg":"error message"`)) {
t.Fatalf("logger info, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"level":"warn","msg":"first second"`)) {
t.Fatalf("logger warn, buf %s", buf.Bytes())
}
*/
} }

View File

@ -3,65 +3,110 @@ package logger
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"go.unistack.org/micro/v4/options" "go.unistack.org/micro/v4/options"
"log"
"sync"
"golang.org/x/exp/slog" "golang.org/x/exp/slog"
) )
const ( var (
slogName = "slog" traceValue = slog.StringValue("trace")
debugValue = slog.StringValue("debug")
infoValue = slog.StringValue("info")
warnValue = slog.StringValue("warn")
errorValue = slog.StringValue("error")
fatalValue = slog.StringValue("fatal")
) )
type slogLogger struct { var renameAttr = func(_ []string, a slog.Attr) slog.Attr {
slog *slog.Logger switch a.Key {
fields map[string]interface{} case slog.TimeKey:
opts Options a.Key = "timestamp"
case slog.LevelKey:
level := a.Value.Any().(slog.Level)
lvl := slogToLoggerLevel(level)
switch {
case lvl < DebugLevel:
a.Value = traceValue
case lvl < InfoLevel:
a.Value = debugValue
case lvl < WarnLevel:
a.Value = infoValue
case lvl < ErrorLevel:
a.Value = warnValue
case lvl < FatalLevel:
a.Value = errorValue
case lvl >= FatalLevel:
a.Value = fatalValue
default:
a.Value = infoValue
}
}
sync.RWMutex return a
} }
//TODO:!!!! type slogLogger struct {
slog *slog.Logger
leveler *slog.LevelVar
opts Options
}
func (s *slogLogger) Clone(opts ...options.Option) Logger { func (s *slogLogger) Clone(opts ...options.Option) Logger {
//TODO implement me options := s.opts
panic("implement me")
for _, o := range opts {
o(&options)
}
l := &slogLogger{
opts: options,
}
if slog, ok := s.opts.Context.Value(loggerKey{}).(*slog.Logger); ok {
l.slog = slog
return nil
}
l.leveler = new(slog.LevelVar)
handleOpt := &slog.HandlerOptions{
ReplaceAttr: renameAttr,
Level: l.leveler,
}
l.leveler.Set(loggerToSlogLevel(l.opts.Level))
handler := slog.NewJSONHandler(options.Out, handleOpt)
l.slog = slog.New(handler).With(options.Fields...)
return l
} }
func (s *slogLogger) V(level Level) bool { func (s *slogLogger) V(level Level) bool {
//TODO implement me return s.opts.Level.Enabled(level)
panic("implement me")
} }
func (s *slogLogger) Level(level Level) { func (s *slogLogger) Level(level Level) {
//TODO implement me s.leveler.Set(loggerToSlogLevel(level))
panic("implement me")
} }
func (s *slogLogger) Options() Options { func (s *slogLogger) Options() Options {
//TODO implement me return s.opts
panic("implement me")
} }
func (s *slogLogger) Fields(fields ...interface{}) Logger { func (s *slogLogger) Fields(fields ...interface{}) Logger {
//TODO implement me nl := &slogLogger{opts: s.opts}
panic("implement me") nl.leveler = new(slog.LevelVar)
} nl.leveler.Set(s.leveler.Level())
func (s *slogLogger) Trace(ctx context.Context, args ...interface{}) { handleOpt := &slog.HandlerOptions{
//TODO implement me ReplaceAttr: renameAttr,
panic("implement me") Level: s.leveler,
} }
func (s *slogLogger) Tracef(ctx context.Context, msg string, args ...interface{}) { handler := slog.NewJSONHandler(s.opts.Out, handleOpt)
//TODO implement me nl.slog = slog.New(handler).With(fields...)
panic("implement me")
}
func (s *slogLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) { return nl
//TODO implement me
panic("implement me")
} }
func (s *slogLogger) Init(opts ...options.Option) error { func (s *slogLogger) Init(opts ...options.Option) error {
@ -76,50 +121,33 @@ func (s *slogLogger) Init(opts ...options.Option) error {
return nil return nil
} }
s.leveler = new(slog.LevelVar)
handleOpt := &slog.HandlerOptions{ handleOpt := &slog.HandlerOptions{
ReplaceAttr: renameTime, ReplaceAttr: renameAttr,
Level: loggerToSlogLevel(s.opts.Level), Level: s.leveler,
} }
s.leveler.Set(loggerToSlogLevel(s.opts.Level))
attr := fieldsToAttr(s.fields) handler := slog.NewJSONHandler(s.opts.Out, handleOpt)
s.slog = slog.New(handler).With(s.opts.Fields...)
handler := slog.NewJSONHandler(s.opts.Out, handleOpt).WithAttrs(attr)
s.slog = slog.New(handler)
return nil return nil
} }
func (s *slogLogger) Log(ctx context.Context, lvl Level, args ...any) { func (s *slogLogger) Log(ctx context.Context, lvl Level, args ...any) {
slvl := loggerToSlogLevel(lvl) if !s.V(lvl) {
return
s.RLock()
attr := fieldsToAttr(s.fields)
s.RUnlock()
msg := fmt.Sprint(args...)
if lvl == FatalLevel {
log.Fatalln(msg, attr)
} }
slvl := loggerToSlogLevel(lvl)
s.slog.LogAttrs(ctx, slvl, msg, attr...) msg := fmt.Sprint(args...)
s.slog.Log(ctx, slvl, msg)
} }
func (s *slogLogger) Logf(ctx context.Context, lvl Level, format string, args ...any) { func (s *slogLogger) Logf(ctx context.Context, lvl Level, format string, args ...any) {
slvl := loggerToSlogLevel(lvl) if !s.V(lvl) {
return
s.RLock()
attr := fieldsToAttr(s.fields)
s.RUnlock()
msg := fmt.Sprintf(format, args...)
if lvl == FatalLevel {
log.Fatalln(msg, attr)
} }
slvl := loggerToSlogLevel(lvl)
s.slog.LogAttrs(ctx, slvl, msg, attr...) s.slog.Log(ctx, slvl, format, args...)
} }
func (s *slogLogger) Info(ctx context.Context, args ...any) { func (s *slogLogger) Info(ctx context.Context, args ...any) {
@ -142,6 +170,14 @@ func (s *slogLogger) Error(ctx context.Context, args ...any) {
s.Log(ctx, ErrorLevel, args...) s.Log(ctx, ErrorLevel, args...)
} }
func (s *slogLogger) Trace(ctx context.Context, args ...interface{}) {
s.Log(ctx, TraceLevel, args...)
}
func (s *slogLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
s.Logf(ctx, TraceLevel, msg, args...)
}
func (s *slogLogger) Errorf(ctx context.Context, format string, args ...any) { func (s *slogLogger) Errorf(ctx context.Context, format string, args ...any) {
s.Logf(ctx, ErrorLevel, format, args...) s.Logf(ctx, ErrorLevel, format, args...)
} }
@ -150,6 +186,11 @@ func (s *slogLogger) Fatal(ctx context.Context, args ...any) {
s.Log(ctx, FatalLevel, args...) s.Log(ctx, FatalLevel, args...)
} }
func (s *slogLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
s.Logf(ctx, FatalLevel, msg, args...)
os.Exit(1)
}
func (s *slogLogger) Warn(ctx context.Context, args ...any) { func (s *slogLogger) Warn(ctx context.Context, args ...any) {
s.Log(ctx, WarnLevel, args...) s.Log(ctx, WarnLevel, args...)
} }
@ -159,86 +200,46 @@ func (s *slogLogger) Warnf(ctx context.Context, format string, args ...any) {
} }
func (s *slogLogger) String() string { func (s *slogLogger) String() string {
return slogName return "slog"
} }
/* func NewLogger(opts ...options.Option) Logger {
func (s *slogLogger) Fields(fields map[string]interface{}) Logger {
nfields := make(map[string]interface{}, len(s.fields))
s.Lock()
for k, v := range s.fields {
nfields[k] = v
}
s.Unlock()
for k, v := range fields {
nfields[k] = v
}
keys := make([]string, 0, len(nfields))
for k := range nfields {
keys = append(keys, k)
}
sort.Strings(keys)
attr := make([]slog.Attr, 0, len(nfields))
for _, k := range keys {
attr = append(attr, slog.Any(k, fields[k]))
}
handleOpt := &slog.HandlerOptions{
ReplaceAttr: renameTime,
Level: loggerToSlogLevel(s.opts.Level),
}
handler := slog.NewJSONHandler(s.opts.Out, handleOpt).WithAttrs(attr)
zl := &slogLogger{
slog: slog.New(handler),
opts: s.opts,
fields: make(map[string]interface{}),
}
return zl
}
*/
func NewSlogLogger(opts ...options.Option) (Logger, error) {
l := &slogLogger{ l := &slogLogger{
opts: NewOptions(opts...), opts: NewOptions(opts...),
} }
err := l.Init() return l
return l, err
}
func renameTime(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
a.Key = "@timestamp"
}
return a
} }
func loggerToSlogLevel(level Level) slog.Level { func loggerToSlogLevel(level Level) slog.Level {
switch level { switch level {
case TraceLevel, DebugLevel: case DebugLevel:
return slog.LevelDebug return slog.LevelDebug
case WarnLevel: case WarnLevel:
return slog.LevelWarn return slog.LevelWarn
case ErrorLevel, FatalLevel: case ErrorLevel:
return slog.LevelError return slog.LevelError
case TraceLevel:
return slog.LevelDebug - 1
case FatalLevel:
return slog.LevelError + 1
default: default:
return slog.LevelInfo return slog.LevelInfo
} }
} }
func fieldsToAttr(m map[string]any) []slog.Attr { func slogToLoggerLevel(level slog.Level) Level {
data := make([]slog.Attr, 0, len(m)) switch level {
for k, v := range m { case slog.LevelDebug:
data = append(data, slog.Any(k, v)) return DebugLevel
case slog.LevelWarn:
return WarnLevel
case slog.LevelError:
return ErrorLevel
case slog.LevelDebug - 1:
return TraceLevel
case slog.LevelError + 1:
return FatalLevel
default:
return InfoLevel
} }
return data
} }

View File

@ -19,6 +19,8 @@ var (
HeaderTimeout = "Micro-Timeout" HeaderTimeout = "Micro-Timeout"
// HeaderAuthorization specifies Authorization header // HeaderAuthorization specifies Authorization header
HeaderAuthorization = "Authorization" HeaderAuthorization = "Authorization"
// HeaderXRequestID specifies request id
HeaderXRequestID = "X-Request-Id"
) )
// Metadata is our way of representing request headers internally. // Metadata is our way of representing request headers internally.

View File

@ -91,7 +91,9 @@ type SpanOptions struct {
} }
// EventOptions contains event options // EventOptions contains event options
type EventOptions struct{} type EventOptions struct {
Labels []interface{}
}
func WithSpanLabels(ls ...interface{}) options.Option { func WithSpanLabels(ls ...interface{}) options.Option {
return func(src interface{}) error { return func(src interface{}) error {
@ -110,6 +112,26 @@ func WithSpanLabels(ls ...interface{}) options.Option {
} }
} }
// EventOption func signature
type EventOption func(o *EventOptions)
func WithEventLabels(ls ...interface{}) options.Option {
return func(src interface{}) error {
v, err := options.Get(src, ".Labels")
if err != nil {
return err
} else if rutil.IsZero(v) {
v = reflect.MakeSlice(reflect.TypeOf(v), 0, len(ls)).Interface()
}
cv := reflect.ValueOf(v)
for _, l := range ls {
reflect.Append(cv, reflect.ValueOf(l))
}
err = options.Set(src, cv, ".Labels")
return err
}
}
func WithSpanKind(k SpanKind) options.Option { func WithSpanKind(k SpanKind) options.Option {
return func(src interface{}) error { return func(src interface{}) error {
return options.Set(src, k, ".Kind") return options.Set(src, k, ".Kind")

View File

@ -7,77 +7,70 @@ import (
"go.unistack.org/micro/v4/client" "go.unistack.org/micro/v4/client"
"go.unistack.org/micro/v4/metadata" "go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/server" "go.unistack.org/micro/v4/server"
"go.unistack.org/micro/v4/tracer" "go.unistack.org/micro/v4/tracer"
) )
var DefaultHeadersExctract = []string{metadata.HeaderTopic, metadata.HeaderEndpoint, metadata.HeaderService, metadata.HeaderXRequestID}
func extractLabels(md metadata.Metadata) []string {
labels := make([]string, 0, 5)
for _, k := range DefaultHeadersExctract {
if v, ok := md.Get(k); ok {
labels = append(labels, k, v)
}
}
return labels
}
var ( var (
DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, sp tracer.Span, err error) { DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []options.Option, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("Call %s.%s", req.Service(), req.Method())) sp.SetName(fmt.Sprintf("Call %s.%s", req.Service(), req.Method()))
var labels []interface{} var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok { if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]interface{}, 0, len(md)+1) labels = append(labels, extractLabels(md))
for k, v := range md {
labels = append(labels, k, v)
}
} }
if err != nil { if err != nil {
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error()) sp.SetStatus(tracer.SpanStatusError, err.Error())
} }
labels = append(labels, "kind", sp.Kind()) sp.AddLabels(labels...)
sp.SetLabels(labels...)
} }
DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, sp tracer.Span, err error) { DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []options.Option, stream client.Stream, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("Stream %s.%s", req.Service(), req.Method())) sp.SetName(fmt.Sprintf("Stream %s.%s", req.Service(), req.Method()))
var labels []interface{} var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok { if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]interface{}, 0, len(md)) labels = append(labels, extractLabels(md))
for k, v := range md {
labels = append(labels, k, v)
}
} }
if err != nil { if err != nil {
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error()) sp.SetStatus(tracer.SpanStatusError, err.Error())
} }
labels = append(labels, "kind", sp.Kind()) sp.AddLabels(labels...)
sp.SetLabels(labels...)
} }
DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, sp tracer.Span, err error) { DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("Handler %s.%s", req.Service(), req.Method())) sp.SetName(fmt.Sprintf("Handler %s.%s", req.Service(), req.Method()))
var labels []interface{} var labels []interface{}
if md, ok := metadata.FromIncomingContext(ctx); ok { if md, ok := metadata.FromIncomingContext(ctx); ok {
labels = make([]interface{}, 0, len(md)) labels = append(labels, extractLabels(md))
for k, v := range md {
labels = append(labels, k, v)
}
} }
if err != nil { if err != nil {
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error()) sp.SetStatus(tracer.SpanStatusError, err.Error())
} }
labels = append(labels, "kind", sp.Kind()) sp.AddLabels(labels...)
sp.SetLabels(labels...)
} }
DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, sp tracer.Span, err error) { DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("Call %s.%s", req.Service(), req.Method())) sp.SetName(fmt.Sprintf("Call %s.%s", req.Service(), req.Method()))
var labels []interface{} var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok { if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]interface{}, 0, len(md)) labels = append(labels, extractLabels(md))
for k, v := range md {
labels = append(labels, k, v)
}
} }
if err != nil { if err != nil {
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error()) sp.SetStatus(tracer.SpanStatusError, err.Error())
} }
labels = append(labels, "kind", sp.Kind()) sp.AddLabels(labels...)
sp.SetLabels(labels...)
} }
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"} DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
@ -91,8 +84,8 @@ type tWrapper struct {
} }
type ( type (
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, tracer.Span, error) ClientCallObserver func(context.Context, client.Request, interface{}, []options.Option, tracer.Span, error)
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, tracer.Span, error) ClientStreamObserver func(context.Context, client.Request, []options.Option, client.Stream, tracer.Span, error)
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, tracer.Span, error) ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, tracer.Span, error)
ServerHandlerObserver func(context.Context, server.Request, interface{}, tracer.Span, error) ServerHandlerObserver func(context.Context, server.Request, interface{}, tracer.Span, error)
) )
@ -176,7 +169,7 @@ func WithServerHandlerObservers(ob ...ServerHandlerObserver) Option {
} }
} }
func (ot *tWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { func (ot *tWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...options.Option) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()) endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range ot.opts.SkipEndpoints { for _, ep := range ot.opts.SkipEndpoints {
if ep == endpoint { if ep == endpoint {
@ -186,7 +179,14 @@ func (ot *tWrapper) Call(ctx context.Context, req client.Request, rsp interface{
sp, ok := tracer.SpanFromContext(ctx) sp, ok := tracer.SpanFromContext(ctx)
if !ok { if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindClient)) ctx, sp = ot.opts.Tracer.Start(ctx, "rpc-client",
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels(
"rpc.flavor", "rpc",
"rpc.call", "/"+req.Service()+"/"+req.Endpoint(),
"rpc.call_type", "unary",
),
)
} }
defer sp.Finish() defer sp.Finish()
@ -199,7 +199,7 @@ func (ot *tWrapper) Call(ctx context.Context, req client.Request, rsp interface{
return err return err
} }
func (ot *tWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { func (ot *tWrapper) Stream(ctx context.Context, req client.Request, opts ...options.Option) (client.Stream, error) {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()) endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range ot.opts.SkipEndpoints { for _, ep := range ot.opts.SkipEndpoints {
if ep == endpoint { if ep == endpoint {
@ -209,7 +209,14 @@ func (ot *tWrapper) Stream(ctx context.Context, req client.Request, opts ...clie
sp, ok := tracer.SpanFromContext(ctx) sp, ok := tracer.SpanFromContext(ctx)
if !ok { if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindClient)) ctx, sp = ot.opts.Tracer.Start(ctx, "rpc-client",
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels(
"rpc.flavor", "rpc",
"rpc.call", "/"+req.Service()+"/"+req.Endpoint(),
"rpc.call_type", "stream",
),
)
} }
defer sp.Finish() defer sp.Finish()
@ -230,10 +237,23 @@ func (ot *tWrapper) ServerHandler(ctx context.Context, req server.Request, rsp i
} }
} }
callType := "unary"
if req.Stream() {
callType = "stream"
}
sp, ok := tracer.SpanFromContext(ctx) sp, ok := tracer.SpanFromContext(ctx)
if !ok { if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindServer)) ctx, sp = ot.opts.Tracer.Start(ctx, "rpc-server",
tracer.WithSpanKind(tracer.SpanKindServer),
tracer.WithSpanLabels(
"rpc.flavor", "rpc",
"rpc.call", "/"+req.Service()+"/"+req.Endpoint(),
"rpc.call_type", callType,
),
)
} }
defer sp.Finish() defer sp.Finish()
err := ot.serverHandler(ctx, req, rsp) err := ot.serverHandler(ctx, req, rsp)
@ -279,7 +299,14 @@ func (ot *tWrapper) ClientCallFunc(ctx context.Context, addr string, req client.
sp, ok := tracer.SpanFromContext(ctx) sp, ok := tracer.SpanFromContext(ctx)
if !ok { if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindClient)) ctx, sp = ot.opts.Tracer.Start(ctx, "rpc-client",
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels(
"rpc.flavor", "rpc",
"rpc.call", "/"+req.Service()+"/"+req.Endpoint(),
"rpc.call_type", "unary",
),
)
} }
defer sp.Finish() defer sp.Finish()