logger/slog: initial import #290
@@ -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