Merge branch 'master' into #335

# Conflicts:
#	logger/options.go
This commit is contained in:
Gorbunov Kirill Andreevich
2024-04-15 13:20:28 +03:00
53 changed files with 1481 additions and 589 deletions

View File

@@ -48,8 +48,10 @@ type Logger interface {
Fatal(ctx context.Context, msg string, attrs ...interface{})
// Log logs message with needed level
Log(ctx context.Context, level Level, msg string, attrs ...interface{})
// String returns the name of logger
// String returns the type name of logger
String() string
// String returns the name of logger
Name() string
}
// Info writes formatted msg to default logger on info level
@@ -62,8 +64,8 @@ func Error(ctx context.Context, msg string, attrs ...interface{}) {
DefaultLogger.Error(ctx, msg, attrs...)
}
// Debugf writes formatted msg to default logger on debug level
func Debugf(ctx context.Context, msg string, attrs ...interface{}) {
// Debug writes formatted msg to default logger on debug level
func Debug(ctx context.Context, msg string, attrs ...interface{}) {
DefaultLogger.Debug(ctx, msg, attrs...)
}

View File

@@ -45,6 +45,10 @@ func (l *noopLogger) Options() Options {
return l.opts
}
func (l *noopLogger) Name() string {
return l.opts.Name
}
func (l *noopLogger) String() string {
return "noop"
}

View File

@@ -6,6 +6,7 @@ import (
"log/slog"
"os"
"reflect"
"time"
"go.unistack.org/micro/v4/options"
rutil "go.unistack.org/micro/v4/util/reflect"
@@ -17,24 +18,34 @@ type Options struct {
Out io.Writer
// Context holds exernal options
Context context.Context
// Attrs holds additional attributes
Attrs []interface{}
// Name holds the logger name
Name string
// The logging level the logger should log
Level Level
// CallerSkipCount number of frmaes to skip
CallerSkipCount int
// ContextAttrFuncs contains funcs that executed before log func on context
ContextAttrFuncs []ContextAttrFunc
// TimeFunc used to obtain current time
TimeFunc func() time.Time
// TimeKey is the key used for the time of the log call
TimeKey string
// Name holds the logger name
Name string
// LevelKey is the key used for the level of the log call
LevelKey string
// MessageKey is the key used for the message of the log call
MessageKey string
// ErrorKey is the key used for the error info
ErrorKey string
// SourceKey is the key used for the source file and line of the log call
SourceKey string
// StacktraceKey is the key used for the stacktrace
StacktraceKey string
// Attrs holds additional attributes
Attrs []interface{}
// ContextAttrFuncs contains funcs that executed before log func on context
ContextAttrFuncs []ContextAttrFunc
// CallerSkipCount number of frmaes to skip
CallerSkipCount int
// The logging level the logger should log
Level Level
// AddStacktrace controls writing of stacktaces on error
AddStacktrace bool
// AddSource enabled writing source file and position in log
AddSource bool
}
// NewOptions creates new options struct
@@ -46,12 +57,14 @@ func NewOptions(opts ...options.Option) Options {
CallerSkipCount: DefaultCallerSkipCount,
Context: context.Background(),
ContextAttrFuncs: DefaultContextAttrFuncs,
AddSource: true,
TimeFunc: time.Now,
}
WithMicroKeys()(&options)
_ = WithMicroKeys()(&options)
for _, o := range opts {
o(&options)
_ = o(&options)
}
return options
}
@@ -116,6 +129,12 @@ func WithZapKeys() options.Option {
if err = options.Set(src, "caller", ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
if err = options.Set(src, "error", ".ErrorKey"); err != nil {
return err
}
return nil
}
}
@@ -135,6 +154,12 @@ func WithZerologKeys() options.Option {
if err = options.Set(src, "caller", ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
if err = options.Set(src, "error", ".ErrorKey"); err != nil {
return err
}
return nil
}
}
@@ -154,6 +179,12 @@ func WithSlogKeys() options.Option {
if err = options.Set(src, slog.SourceKey, ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
if err = options.Set(src, "error", ".ErrorKey"); err != nil {
return err
}
return nil
}
}
@@ -173,6 +204,12 @@ func WithMicroKeys() options.Option {
if err = options.Set(src, "caller", ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
if err = options.Set(src, "error", ".ErrorKey"); err != nil {
return err
}
return nil
}
}
@@ -189,3 +226,17 @@ func WithIncCallerSkipCount(n int) options.Option {
return nil
}
}
// WithAddStacktrace controls writing stacktrace on error
func WithAddStacktrace(v bool) options.Option {
return func(src interface{}) error {
return options.Set(src, v, ".AddStacktrace")
}
}
// WitAddSource controls writing source file and pos in log
func WithAddSource(v bool) options.Option {
return func(src interface{}) error {
return options.Set(src, v, ".AddSource")
}
}

View File

@@ -4,15 +4,18 @@ import (
"context"
"log/slog"
"os"
"regexp"
"runtime"
"strconv"
"time"
"sync"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/tracer"
)
var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`)
var (
traceValue = slog.StringValue("trace")
debugValue = slog.StringValue("debug")
@@ -58,38 +61,33 @@ func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr {
}
type slogLogger struct {
slog *slog.Logger
leveler *slog.LevelVar
handler slog.Handler
opts logger.Options
mu sync.RWMutex
}
func (s *slogLogger) Clone(opts ...options.Option) logger.Logger {
s.mu.RLock()
options := s.opts
s.mu.RUnlock()
for _, o := range opts {
o(&options)
_ = 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: s.renameAttr,
ReplaceAttr: l.renameAttr,
Level: l.leveler,
AddSource: true,
AddSource: l.opts.AddSource,
}
l.leveler.Set(loggerToSlogLevel(l.opts.Level))
handler := slog.NewJSONHandler(options.Out, handleOpt)
l.slog = slog.New(handler).With(options.Attrs...)
l.handler = slog.New(slog.NewJSONHandler(options.Out, handleOpt)).With(options.Attrs...).Handler()
return l
}
@@ -107,48 +105,48 @@ func (s *slogLogger) Options() logger.Options {
}
func (s *slogLogger) Attrs(attrs ...interface{}) logger.Logger {
nl := &slogLogger{opts: s.opts}
nl.leveler = new(slog.LevelVar)
nl.leveler.Set(s.leveler.Level())
s.mu.RLock()
level := s.leveler.Level()
options := s.opts
s.mu.RUnlock()
l := &slogLogger{opts: options}
l.leveler = new(slog.LevelVar)
l.leveler.Set(level)
handleOpt := &slog.HandlerOptions{
ReplaceAttr: nl.renameAttr,
Level: s.leveler,
AddSource: true,
ReplaceAttr: l.renameAttr,
Level: l.leveler,
AddSource: l.opts.AddSource,
}
handler := slog.NewJSONHandler(s.opts.Out, handleOpt)
nl.slog = slog.New(handler).With(attrs...)
l.handler = slog.New(slog.NewJSONHandler(s.opts.Out, handleOpt)).With(attrs...).Handler()
return nl
return l
}
func (s *slogLogger) Init(opts ...options.Option) error {
s.mu.Lock()
if len(s.opts.ContextAttrFuncs) == 0 {
s.opts.ContextAttrFuncs = logger.DefaultContextAttrFuncs
}
for _, o := range opts {
if err := o(&s.opts); err != nil {
return err
}
}
/*
if slog, ok := s.opts.Context.Value(loggerKey{}).(*slog.Logger); ok {
s.slog = slog
return nil
}
*/
s.leveler = new(slog.LevelVar)
handleOpt := &slog.HandlerOptions{
ReplaceAttr: s.renameAttr,
Level: s.leveler,
AddSource: true,
AddSource: s.opts.AddSource,
}
s.leveler.Set(loggerToSlogLevel(s.opts.Level))
handler := slog.NewJSONHandler(s.opts.Out, handleOpt)
s.slog = slog.New(handler).With(s.opts.Attrs...)
s.handler = slog.New(slog.NewJSONHandler(s.opts.Out, handleOpt)).With(s.opts.Attrs...).Handler()
s.mu.Unlock()
return nil
}
@@ -159,12 +157,36 @@ func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attr
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), msg, pcs[0])
r := slog.NewRecord(s.opts.TimeFunc(), loggerToSlogLevel(lvl), msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1]))
}
}
}
r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
r.Attrs(func(a slog.Attr) bool {
if a.Key == s.opts.ErrorKey {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, a.Value.String())
return false
}
}
return true
})
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
@@ -173,12 +195,18 @@ func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{})
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelInfo, msg, pcs[0])
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelInfo, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
@@ -187,12 +215,18 @@ func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelDebug, msg, pcs[0])
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelDebug, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
@@ -201,12 +235,18 @@ func (s *slogLogger) Trace(ctx context.Context, msg string, attrs ...interface{}
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelDebug-1, msg, pcs[0])
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelDebug-1, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
@@ -215,13 +255,28 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelError, msg, pcs[0])
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
if s.opts.AddStacktrace {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1]))
}
}
}
r.Add(attrs...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == "error" {
if a.Key == s.opts.ErrorKey {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, a.Value.String())
return false
@@ -229,7 +284,7 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
}
return true
})
_ = s.slog.Handler().Handle(ctx, r)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
@@ -238,12 +293,18 @@ func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelError+1, msg, pcs[0])
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError+1, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
_ = s.handler.Handle(ctx, r)
os.Exit(1)
}
@@ -253,12 +314,22 @@ func (s *slogLogger) Warn(ctx context.Context, msg string, attrs ...interface{})
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelWarn, msg, pcs[0])
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelWarn, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Name() string {
return s.opts.Name
}
func (s *slogLogger) String() string {

View File

@@ -3,12 +3,26 @@ package slog
import (
"bytes"
"context"
"fmt"
"log"
"testing"
"go.unistack.org/micro/v4/logger"
)
func TestError(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf), logger.WithAddStacktrace(true))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Error(ctx, "msg", fmt.Errorf("message"))
if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) {
t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes())
}
}
func TestContext(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)

View File

@@ -56,9 +56,9 @@ type Wrapper struct {
s fmt.State
pointers map[uintptr]int
opts *Options
takeMap map[int]bool
depth int
ignoreNextType bool
takeMap map[int]bool
protoWrapperType bool
sqlWrapperType bool
}