updates #207
| @@ -72,9 +72,9 @@ type LoadOption func(o *LoadOptions) | |||||||
| // LoadOptions struct | // LoadOptions struct | ||||||
| type LoadOptions struct { | type LoadOptions struct { | ||||||
| 	Struct   interface{} | 	Struct   interface{} | ||||||
|  | 	Context  context.Context | ||||||
| 	Override bool | 	Override bool | ||||||
| 	Append   bool | 	Append   bool | ||||||
| 	Context  context.Context |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewLoadOptions create LoadOptions struct with provided opts | // NewLoadOptions create LoadOptions struct with provided opts | ||||||
|   | |||||||
| @@ -1,230 +0,0 @@ | |||||||
| package logger |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"os" |  | ||||||
| 	"runtime" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type defaultLogger struct { |  | ||||||
| 	enc  *json.Encoder |  | ||||||
| 	opts Options |  | ||||||
| 	sync.RWMutex |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Init(opts...) should only overwrite provided options |  | ||||||
| func (l *defaultLogger) Init(opts ...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 ...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 ...Option) Logger { |  | ||||||
| 	l := &defaultLogger{ |  | ||||||
| 		opts: NewOptions(opts...), |  | ||||||
| 	} |  | ||||||
| 	l.enc = json.NewEncoder(l.opts.Out) |  | ||||||
| 	return l |  | ||||||
| } |  | ||||||
							
								
								
									
										90
									
								
								logger/noop.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								logger/noop.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | package logger | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type noopLogger struct { | ||||||
|  | 	opts Options | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewLogger(opts ...Option) Logger { | ||||||
|  | 	options := NewOptions(opts...) | ||||||
|  | 	return &noopLogger{opts: options} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) V(lvl Level) bool { | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Level(lvl Level) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Init(opts ...Option) error { | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&l.opts) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Clone(opts ...Option) Logger { | ||||||
|  | 	nl := &noopLogger{opts: l.opts} | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&nl.opts) | ||||||
|  | 	} | ||||||
|  | 	return nl | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Fields(attrs ...interface{}) Logger { | ||||||
|  | 	return l | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Options() Options { | ||||||
|  | 	return l.opts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) String() string { | ||||||
|  | 	return "noop" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Log(ctx context.Context, lvl Level, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Info(ctx context.Context, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Debug(ctx context.Context, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Error(ctx context.Context, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Trace(ctx context.Context, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Warn(ctx context.Context, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Fatal(ctx context.Context, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Logf(ctx context.Context, lvl Level, msg string, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Infof(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Debugf(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Errorf(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Tracef(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Warnf(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *noopLogger) Fatalf(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								logger/slog/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								logger/slog/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | package slog | ||||||
|  |  | ||||||
|  | import "go.unistack.org/micro/v3/logger" | ||||||
|  |  | ||||||
|  | type sourceKey struct{} | ||||||
|  |  | ||||||
|  | func WithSourceKey(v string) logger.Option { | ||||||
|  | 	return logger.SetOption(sourceKey{}, v) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type timeKey struct{} | ||||||
|  |  | ||||||
|  | func WithTimeKey(v string) logger.Option { | ||||||
|  | 	return logger.SetOption(timeKey{}, v) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type messageKey struct{} | ||||||
|  |  | ||||||
|  | func WithMessageKey(v string) logger.Option { | ||||||
|  | 	return logger.SetOption(messageKey{}, v) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type levelKey struct{} | ||||||
|  |  | ||||||
|  | func WithLevelKey(v string) logger.Option { | ||||||
|  | 	return logger.SetOption(levelKey{}, v) | ||||||
|  | } | ||||||
							
								
								
									
										419
									
								
								logger/slog/slog.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										419
									
								
								logger/slog/slog.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,419 @@ | |||||||
|  | package slog | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"runtime" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"go.unistack.org/micro/v3/logger" | ||||||
|  | 	"go.unistack.org/micro/v3/tracer" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	DefaultSourceKey  string = slog.SourceKey | ||||||
|  | 	DefaultTimeKey    string = slog.TimeKey | ||||||
|  | 	DefaultMessageKey string = slog.MessageKey | ||||||
|  | 	DefaultLevelKey   string = slog.LevelKey | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	traceValue = slog.StringValue("trace") | ||||||
|  | 	debugValue = slog.StringValue("debug") | ||||||
|  | 	infoValue  = slog.StringValue("info") | ||||||
|  | 	warnValue  = slog.StringValue("warn") | ||||||
|  | 	errorValue = slog.StringValue("error") | ||||||
|  | 	fatalValue = slog.StringValue("fatal") | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr { | ||||||
|  | 	switch a.Key { | ||||||
|  | 	case slog.SourceKey: | ||||||
|  | 		source := a.Value.Any().(*slog.Source) | ||||||
|  | 		a.Value = slog.StringValue(source.File + ":" + strconv.Itoa(source.Line)) | ||||||
|  | 		a.Key = s.sourceKey | ||||||
|  | 	case slog.TimeKey: | ||||||
|  | 		a.Key = s.timeKey | ||||||
|  | 	case slog.MessageKey: | ||||||
|  | 		a.Key = s.messageKey | ||||||
|  | 	case slog.LevelKey: | ||||||
|  | 		level := a.Value.Any().(slog.Level) | ||||||
|  | 		lvl := slogToLoggerLevel(level) | ||||||
|  | 		a.Key = s.levelKey | ||||||
|  | 		switch { | ||||||
|  | 		case lvl < logger.DebugLevel: | ||||||
|  | 			a.Value = traceValue | ||||||
|  | 		case lvl < logger.InfoLevel: | ||||||
|  | 			a.Value = debugValue | ||||||
|  | 		case lvl < logger.WarnLevel: | ||||||
|  | 			a.Value = infoValue | ||||||
|  | 		case lvl < logger.ErrorLevel: | ||||||
|  | 			a.Value = warnValue | ||||||
|  | 		case lvl < logger.FatalLevel: | ||||||
|  | 			a.Value = errorValue | ||||||
|  | 		case lvl >= logger.FatalLevel: | ||||||
|  | 			a.Value = fatalValue | ||||||
|  | 		default: | ||||||
|  | 			a.Value = infoValue | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return a | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type slogLogger struct { | ||||||
|  | 	slog       *slog.Logger | ||||||
|  | 	leveler    *slog.LevelVar | ||||||
|  | 	levelKey   string | ||||||
|  | 	messageKey string | ||||||
|  | 	sourceKey  string | ||||||
|  | 	timeKey    string | ||||||
|  | 	opts       logger.Options | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger { | ||||||
|  | 	options := s.opts | ||||||
|  |  | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	l := &slogLogger{ | ||||||
|  | 		opts:       options, | ||||||
|  | 		levelKey:   s.levelKey, | ||||||
|  | 		messageKey: s.messageKey, | ||||||
|  | 		sourceKey:  s.sourceKey, | ||||||
|  | 		timeKey:    s.timeKey, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v, ok := l.opts.Context.Value(levelKey{}).(string); ok && v != "" { | ||||||
|  | 		l.levelKey = v | ||||||
|  | 	} | ||||||
|  | 	if v, ok := l.opts.Context.Value(messageKey{}).(string); ok && v != "" { | ||||||
|  | 		l.messageKey = v | ||||||
|  | 	} | ||||||
|  | 	if v, ok := l.opts.Context.Value(sourceKey{}).(string); ok && v != "" { | ||||||
|  | 		l.sourceKey = v | ||||||
|  | 	} | ||||||
|  | 	if v, ok := l.opts.Context.Value(timeKey{}).(string); ok && v != "" { | ||||||
|  | 		l.timeKey = v | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	l.leveler = new(slog.LevelVar) | ||||||
|  | 	handleOpt := &slog.HandlerOptions{ | ||||||
|  | 		ReplaceAttr: s.renameAttr, | ||||||
|  | 		Level:       l.leveler, | ||||||
|  | 		AddSource:   true, | ||||||
|  | 	} | ||||||
|  | 	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 logger.Level) bool { | ||||||
|  | 	return s.opts.Level.Enabled(level) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Level(level logger.Level) { | ||||||
|  | 	s.leveler.Set(loggerToSlogLevel(level)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Options() logger.Options { | ||||||
|  | 	return s.opts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger { | ||||||
|  | 	nl := &slogLogger{ | ||||||
|  | 		opts:       s.opts, | ||||||
|  | 		levelKey:   s.levelKey, | ||||||
|  | 		messageKey: s.messageKey, | ||||||
|  | 		sourceKey:  s.sourceKey, | ||||||
|  | 		timeKey:    s.timeKey, | ||||||
|  | 	} | ||||||
|  | 	nl.leveler = new(slog.LevelVar) | ||||||
|  | 	nl.leveler.Set(s.leveler.Level()) | ||||||
|  |  | ||||||
|  | 	handleOpt := &slog.HandlerOptions{ | ||||||
|  | 		ReplaceAttr: nl.renameAttr, | ||||||
|  | 		Level:       s.leveler, | ||||||
|  | 		AddSource:   true, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	handler := slog.NewJSONHandler(s.opts.Out, handleOpt) | ||||||
|  | 	nl.slog = slog.New(handler).With(attrs...) | ||||||
|  |  | ||||||
|  | 	return nl | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Init(opts ...logger.Option) error { | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&s.opts) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v, ok := s.opts.Context.Value(levelKey{}).(string); ok && v != "" { | ||||||
|  | 		s.levelKey = v | ||||||
|  | 	} | ||||||
|  | 	if v, ok := s.opts.Context.Value(messageKey{}).(string); ok && v != "" { | ||||||
|  | 		s.messageKey = v | ||||||
|  | 	} | ||||||
|  | 	if v, ok := s.opts.Context.Value(sourceKey{}).(string); ok && v != "" { | ||||||
|  | 		s.sourceKey = v | ||||||
|  | 	} | ||||||
|  | 	if v, ok := s.opts.Context.Value(timeKey{}).(string); ok && v != "" { | ||||||
|  | 		s.timeKey = v | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.leveler = new(slog.LevelVar) | ||||||
|  | 	handleOpt := &slog.HandlerOptions{ | ||||||
|  | 		ReplaceAttr: s.renameAttr, | ||||||
|  | 		Level:       s.leveler, | ||||||
|  | 		AddSource:   true, | ||||||
|  | 	} | ||||||
|  | 	s.leveler.Set(loggerToSlogLevel(s.opts.Level)) | ||||||
|  | 	handler := slog.NewJSONHandler(s.opts.Out, handleOpt) | ||||||
|  | 	s.slog = slog.New(handler).With(s.opts.Fields...) | ||||||
|  |  | ||||||
|  | 	slog.SetDefault(s.slog) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, attrs ...interface{}) { | ||||||
|  | 	if !s.V(lvl) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||||
|  | 	r.Add(attrs[1:]...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Logf(ctx context.Context, lvl logger.Level, msg string, attrs ...interface{}) { | ||||||
|  | 	if !s.V(lvl) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), msg, pcs[0]) | ||||||
|  | 	r.Add(attrs...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Info(ctx context.Context, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.InfoLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||||
|  | 	r.Add(attrs[1:]...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Infof(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.InfoLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelInfo, msg, pcs[0]) | ||||||
|  | 	r.Add(attrs...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Debug(ctx context.Context, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.DebugLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||||
|  | 	r.Add(attrs[1:]...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Debugf(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.DebugLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelDebug, msg, pcs[0]) | ||||||
|  | 	r.Add(attrs...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Trace(ctx context.Context, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.TraceLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelDebug-1, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||||
|  | 	r.Add(attrs[1:]...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Tracef(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.TraceLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	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.Add(attrs...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Error(ctx context.Context, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.ErrorLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||||
|  | 	r.Add(attrs[1:]...) | ||||||
|  | 	r.Attrs(func(a slog.Attr) bool { | ||||||
|  | 		if a.Key == "error" { | ||||||
|  | 			if span, ok := tracer.SpanFromContext(ctx); ok { | ||||||
|  | 				span.SetStatus(tracer.SpanStatusError, a.Value.String()) | ||||||
|  | 				return false | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return true | ||||||
|  | 	}) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Errorf(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.ErrorLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelError, msg, pcs[0]) | ||||||
|  | 	r.Add(attrs...) | ||||||
|  | 	r.Attrs(func(a slog.Attr) bool { | ||||||
|  | 		if a.Key == "error" { | ||||||
|  | 			if span, ok := tracer.SpanFromContext(ctx); ok { | ||||||
|  | 				span.SetStatus(tracer.SpanStatusError, a.Value.String()) | ||||||
|  | 				return false | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return true | ||||||
|  | 	}) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Fatal(ctx context.Context, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.FatalLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelError+1, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||||
|  | 	r.Add(attrs[1:]...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | 	os.Exit(1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Fatalf(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.FatalLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	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.Add(attrs...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | 	os.Exit(1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Warn(ctx context.Context, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.WarnLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||||
|  | 	r.Add(attrs[1:]...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) Warnf(ctx context.Context, msg string, attrs ...interface{}) { | ||||||
|  | 	if !s.V(logger.WarnLevel) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||||
|  | 	r := slog.NewRecord(time.Now(), slog.LevelWarn, msg, pcs[0]) | ||||||
|  | 	r.Add(attrs...) | ||||||
|  | 	_ = s.slog.Handler().Handle(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *slogLogger) String() string { | ||||||
|  | 	return "slog" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewLogger(opts ...logger.Option) logger.Logger { | ||||||
|  | 	s := &slogLogger{ | ||||||
|  | 		opts:       logger.NewOptions(opts...), | ||||||
|  | 		sourceKey:  DefaultSourceKey, | ||||||
|  | 		timeKey:    DefaultTimeKey, | ||||||
|  | 		messageKey: DefaultMessageKey, | ||||||
|  | 		levelKey:   DefaultLevelKey, | ||||||
|  | 	} | ||||||
|  | 	if v, ok := s.opts.Context.Value(levelKey{}).(string); ok && v != "" { | ||||||
|  | 		s.levelKey = v | ||||||
|  | 	} | ||||||
|  | 	if v, ok := s.opts.Context.Value(messageKey{}).(string); ok && v != "" { | ||||||
|  | 		s.messageKey = v | ||||||
|  | 	} | ||||||
|  | 	if v, ok := s.opts.Context.Value(sourceKey{}).(string); ok && v != "" { | ||||||
|  | 		s.sourceKey = v | ||||||
|  | 	} | ||||||
|  | 	if v, ok := s.opts.Context.Value(timeKey{}).(string); ok && v != "" { | ||||||
|  | 		s.timeKey = v | ||||||
|  | 	} | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func loggerToSlogLevel(level logger.Level) slog.Level { | ||||||
|  | 	switch level { | ||||||
|  | 	case logger.DebugLevel: | ||||||
|  | 		return slog.LevelDebug | ||||||
|  | 	case logger.WarnLevel: | ||||||
|  | 		return slog.LevelWarn | ||||||
|  | 	case logger.ErrorLevel: | ||||||
|  | 		return slog.LevelError | ||||||
|  | 	case logger.TraceLevel: | ||||||
|  | 		return slog.LevelDebug - 1 | ||||||
|  | 	case logger.FatalLevel: | ||||||
|  | 		return slog.LevelError + 1 | ||||||
|  | 	default: | ||||||
|  | 		return slog.LevelInfo | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func slogToLoggerLevel(level slog.Level) logger.Level { | ||||||
|  | 	switch level { | ||||||
|  | 	case slog.LevelDebug: | ||||||
|  | 		return logger.DebugLevel | ||||||
|  | 	case slog.LevelWarn: | ||||||
|  | 		return logger.WarnLevel | ||||||
|  | 	case slog.LevelError: | ||||||
|  | 		return logger.ErrorLevel | ||||||
|  | 	case slog.LevelDebug - 1: | ||||||
|  | 		return logger.TraceLevel | ||||||
|  | 	case slog.LevelError + 1: | ||||||
|  | 		return logger.FatalLevel | ||||||
|  | 	default: | ||||||
|  | 		return logger.InfoLevel | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										141
									
								
								logger/slog/slog_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								logger/slog/slog_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | |||||||
|  | package slog | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"log" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.unistack.org/micro/v3/logger" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestContext(t *testing.T) { | ||||||
|  | 	ctx := context.TODO() | ||||||
|  | 	buf := bytes.NewBuffer(nil) | ||||||
|  | 	l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf)) | ||||||
|  | 	if err := l.Init(); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	nl, ok := logger.FromContext(logger.NewContext(ctx, l.Fields("key", "val"))) | ||||||
|  | 	if !ok { | ||||||
|  | 		t.Fatal("context without logger") | ||||||
|  | 	} | ||||||
|  | 	nl.Info(ctx, "message") | ||||||
|  | 	if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) { | ||||||
|  | 		t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestFields(t *testing.T) { | ||||||
|  | 	ctx := context.TODO() | ||||||
|  | 	buf := bytes.NewBuffer(nil) | ||||||
|  | 	l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf)) | ||||||
|  | 	if err := l.Init(); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	nl := l.Fields("key", "val") | ||||||
|  |  | ||||||
|  | 	nl.Info(ctx, "message") | ||||||
|  | 	if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) { | ||||||
|  | 		t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestFromContextWithFields(t *testing.T) { | ||||||
|  | 	ctx := context.TODO() | ||||||
|  | 	buf := bytes.NewBuffer(nil) | ||||||
|  | 	var ok bool | ||||||
|  | 	l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf)) | ||||||
|  | 	if err := l.Init(); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	nl := l.Fields("key", "val") | ||||||
|  |  | ||||||
|  | 	ctx = logger.NewContext(ctx, nl) | ||||||
|  |  | ||||||
|  | 	l, ok = logger.FromContext(ctx) | ||||||
|  | 	if !ok { | ||||||
|  | 		t.Fatalf("context does not have logger") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	l.Info(ctx, "message") | ||||||
|  | 	if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) { | ||||||
|  | 		t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestClone(t *testing.T) { | ||||||
|  | 	ctx := context.TODO() | ||||||
|  | 	buf := bytes.NewBuffer(nil) | ||||||
|  | 	l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf)) | ||||||
|  | 	if err := l.Init(); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	nl := l.Clone(logger.WithLevel(logger.ErrorLevel)) | ||||||
|  | 	if err := nl.Init(); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	nl.Info(ctx, "info message") | ||||||
|  | 	if len(buf.Bytes()) != 0 { | ||||||
|  | 		t.Fatal("message must not be logged") | ||||||
|  | 	} | ||||||
|  | 	l.Info(ctx, "info message") | ||||||
|  | 	if len(buf.Bytes()) == 0 { | ||||||
|  | 		t.Fatal("message must be logged") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRedirectStdLogger(t *testing.T) { | ||||||
|  | 	buf := bytes.NewBuffer(nil) | ||||||
|  | 	l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf)) | ||||||
|  | 	if err := l.Init(); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	fn := logger.RedirectStdLogger(l, logger.ErrorLevel) | ||||||
|  | 	defer fn() | ||||||
|  | 	log.Print("test") | ||||||
|  | 	if !(bytes.Contains(buf.Bytes(), []byte(`"level":"error"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"test"`))) { | ||||||
|  | 		t.Fatalf("logger error, buf %s", buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStdLogger(t *testing.T) { | ||||||
|  | 	buf := bytes.NewBuffer(nil) | ||||||
|  | 	l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf)) | ||||||
|  | 	if err := l.Init(); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	lg := logger.NewStdLogger(l, logger.ErrorLevel) | ||||||
|  | 	lg.Print("test") | ||||||
|  | 	if !(bytes.Contains(buf.Bytes(), []byte(`"level":"error"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"test"`))) { | ||||||
|  | 		t.Fatalf("logger error, buf %s", buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestLogger(t *testing.T) { | ||||||
|  | 	ctx := context.TODO() | ||||||
|  | 	buf := bytes.NewBuffer(nil) | ||||||
|  | 	l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf)) | ||||||
|  | 	if err := l.Init(); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	l.Trace(ctx, "trace_msg1") | ||||||
|  | 	l.Warn(ctx, "warn_msg1") | ||||||
|  | 	l.Fields("error", "test").Info(ctx, "error message") | ||||||
|  | 	l.Warn(ctx, "first second") | ||||||
|  |  | ||||||
|  | 	if !(bytes.Contains(buf.Bytes(), []byte(`"level":"trace"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"trace_msg1"`))) { | ||||||
|  | 		t.Fatalf("logger tracer, buf %s", buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | 	if !(bytes.Contains(buf.Bytes(), []byte(`"level":"warn"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"warn_msg1"`))) { | ||||||
|  | 		t.Fatalf("logger warn, buf %s", buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | 	if !(bytes.Contains(buf.Bytes(), []byte(`"level":"info"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"error message","error":"test"`))) { | ||||||
|  | 		t.Fatalf("logger info, buf %s", buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | 	if !(bytes.Contains(buf.Bytes(), []byte(`"level":"warn"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"first second"`))) { | ||||||
|  | 		t.Fatalf("logger warn, buf %s", buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user