parent
36be0d28da
commit
a02810700c
11 changed files with 587 additions and 0 deletions
@ -0,0 +1,80 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
type FilterOption func(*Filter) |
||||||
|
|
||||||
|
const fuzzyStr = "***" |
||||||
|
|
||||||
|
func FilterLevel(level Level) FilterOption { |
||||||
|
return func(opts *Filter) { |
||||||
|
opts.level = level |
||||||
|
} |
||||||
|
} |
||||||
|
func FilterKey(key ...string) FilterOption { |
||||||
|
return func(opts *Filter) { |
||||||
|
for _, v := range key { |
||||||
|
opts.key[v] = struct{}{} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
func FilterValue(value ...string) FilterOption { |
||||||
|
return func(opts *Filter) { |
||||||
|
for _, v := range value { |
||||||
|
opts.value[v] = struct{}{} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
func FilterFunc(f func(level Level, keyvals ...any) bool) FilterOption { |
||||||
|
return func(opts *Filter) { |
||||||
|
opts.filter = f |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type Filter struct { |
||||||
|
logger Logger |
||||||
|
level Level |
||||||
|
key map[any]struct{} |
||||||
|
value map[any]struct{} |
||||||
|
filter func(level Level, keyvals ...any) bool |
||||||
|
} |
||||||
|
|
||||||
|
func NewFilter(logger Logger, opts ...FilterOption) *Filter { |
||||||
|
options := &Filter{ |
||||||
|
logger: logger, |
||||||
|
key: make(map[any]struct{}), |
||||||
|
value: make(map[any]struct{}), |
||||||
|
} |
||||||
|
for _, o := range opts { |
||||||
|
o(options) |
||||||
|
} |
||||||
|
return options |
||||||
|
} |
||||||
|
func (f *Filter) Log(level Level, keyvals ...any) error { |
||||||
|
if level < f.level { |
||||||
|
return nil |
||||||
|
} |
||||||
|
var prefixkv []any |
||||||
|
l, ok := f.logger.(*logger) |
||||||
|
if ok && len(l.prefix) > 0 { |
||||||
|
prefixkv = make([]any, 0, len(l.prefix)) |
||||||
|
prefixkv = append(prefixkv, l.prefix...) |
||||||
|
} |
||||||
|
if f.filter != nil && (f.filter(level, prefixkv...) || f.filter(level, keyvals)) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if len(f.key) > 0 || len(f.value) > 0 { |
||||||
|
for i := 0; i < len(keyvals); i += 2 { |
||||||
|
v := i + 1 |
||||||
|
if v >= len(keyvals) { |
||||||
|
continue |
||||||
|
} |
||||||
|
if _, ok := f.key[keyvals[i]]; ok { |
||||||
|
keyvals[v] = fuzzyStr |
||||||
|
} |
||||||
|
|
||||||
|
if _, ok := f.key[keyvals[v]]; ok { |
||||||
|
keyvals[v] = fuzzyStr |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return f.logger.Log(level, keyvals...) |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func TestNewFilter(t *testing.T) { |
||||||
|
logger := With(DefaultLogger, |
||||||
|
//"ts", DefaultTimestamp, "caller", DefaultCaller
|
||||||
|
) |
||||||
|
log := NewHelper(NewFilter(logger, |
||||||
|
FilterKey("username"), |
||||||
|
FilterValue("hello"), |
||||||
|
FilterFunc(func(level Level, keyvals ...any) bool { |
||||||
|
if level == LevelWarn { |
||||||
|
return true |
||||||
|
} |
||||||
|
for i := 0; i < len(keyvals); i++ { |
||||||
|
if keyvals[i] == "password" { |
||||||
|
keyvals[i+1] = fuzzyStr |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
}), |
||||||
|
)) |
||||||
|
log.Log(LevelDebug, "msg", "test debug") |
||||||
|
log.Info("hello") |
||||||
|
log.Infow("password", "123456") |
||||||
|
log.Infow("username", "kratos") |
||||||
|
log.Warn("warn log") |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
var global = &loggerAppliance{} |
||||||
|
|
||||||
|
type loggerAppliance struct { |
||||||
|
lock sync.Mutex |
||||||
|
Logger |
||||||
|
} |
||||||
|
|
||||||
|
func init() { |
||||||
|
global.SetLogger(DefaultLogger) |
||||||
|
} |
||||||
|
func (l *loggerAppliance) SetLogger(in Logger) { |
||||||
|
l.lock.Lock() |
||||||
|
defer l.lock.Unlock() |
||||||
|
l.Logger = in |
||||||
|
} |
||||||
|
func SetLogger(logger Logger) { |
||||||
|
global.SetLogger(logger) |
||||||
|
} |
||||||
|
func GetLogger() Logger { |
||||||
|
return global |
||||||
|
} |
||||||
|
func Log(level Level, keyvals ...any) { |
||||||
|
_ = global.Log(level, keyvals...) |
||||||
|
} |
||||||
|
func Context(ctx context.Context) *Helper { |
||||||
|
return NewHelper(WithContext(ctx, global.Logger)) |
||||||
|
} |
||||||
|
|
||||||
|
// Debug logs a message at debug level.
|
||||||
|
func Debug(a ...interface{}) { |
||||||
|
_ = global.Log(LevelDebug, DefaultMessageKey, fmt.Sprint(a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Debugf logs a message at debug level.
|
||||||
|
func Debugf(format string, a ...interface{}) { |
||||||
|
_ = global.Log(LevelDebug, DefaultMessageKey, fmt.Sprintf(format, a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Debugw logs a message at debug level.
|
||||||
|
func Debugw(keyvals ...interface{}) { |
||||||
|
_ = global.Log(LevelDebug, keyvals...) |
||||||
|
} |
||||||
|
|
||||||
|
// Info logs a message at info level.
|
||||||
|
func Info(a ...interface{}) { |
||||||
|
_ = global.Log(LevelInfo, DefaultMessageKey, fmt.Sprint(a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Infof logs a message at info level.
|
||||||
|
func Infof(format string, a ...interface{}) { |
||||||
|
_ = global.Log(LevelInfo, DefaultMessageKey, fmt.Sprintf(format, a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Infow logs a message at info level.
|
||||||
|
func Infow(keyvals ...interface{}) { |
||||||
|
_ = global.Log(LevelInfo, keyvals...) |
||||||
|
} |
||||||
|
|
||||||
|
// Warn logs a message at warn level.
|
||||||
|
func Warn(a ...interface{}) { |
||||||
|
_ = global.Log(LevelWarn, DefaultMessageKey, fmt.Sprint(a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Warnf logs a message at warnf level.
|
||||||
|
func Warnf(format string, a ...interface{}) { |
||||||
|
_ = global.Log(LevelWarn, DefaultMessageKey, fmt.Sprintf(format, a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Warnw logs a message at warnf level.
|
||||||
|
func Warnw(keyvals ...interface{}) { |
||||||
|
_ = global.Log(LevelWarn, keyvals...) |
||||||
|
} |
||||||
|
|
||||||
|
// Error logs a message at error level.
|
||||||
|
func Error(a ...interface{}) { |
||||||
|
_ = global.Log(LevelError, DefaultMessageKey, fmt.Sprint(a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Errorf logs a message at error level.
|
||||||
|
func Errorf(format string, a ...interface{}) { |
||||||
|
_ = global.Log(LevelError, DefaultMessageKey, fmt.Sprintf(format, a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Errorw logs a message at error level.
|
||||||
|
func Errorw(keyvals ...interface{}) { |
||||||
|
_ = global.Log(LevelError, keyvals...) |
||||||
|
} |
||||||
|
|
||||||
|
// Fatal logs a message at fatal level.
|
||||||
|
func Fatal(a ...interface{}) { |
||||||
|
_ = global.Log(LevelFatal, DefaultMessageKey, fmt.Sprint(a...)) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Fatalf logs a message at fatal level.
|
||||||
|
func Fatalf(format string, a ...interface{}) { |
||||||
|
_ = global.Log(LevelFatal, DefaultMessageKey, fmt.Sprintf(format, a...)) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Fatalw logs a message at fatal level.
|
||||||
|
func Fatalw(keyvals ...interface{}) { |
||||||
|
_ = global.Log(LevelFatal, keyvals...) |
||||||
|
os.Exit(1) |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func TestDebug(t *testing.T) { |
||||||
|
Debug("debug") |
||||||
|
} |
@ -0,0 +1,121 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
) |
||||||
|
|
||||||
|
var DefaultMessageKey = "msg" |
||||||
|
|
||||||
|
type Option func(*Helper) |
||||||
|
type Helper struct { |
||||||
|
logger Logger |
||||||
|
msgKey string |
||||||
|
} |
||||||
|
|
||||||
|
func WithMessageKey(k string) Option { |
||||||
|
return func(opts *Helper) { |
||||||
|
opts.msgKey = k |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func NewHelper(logger Logger, opts ...Option) *Helper { |
||||||
|
options := &Helper{ |
||||||
|
msgKey: DefaultMessageKey, |
||||||
|
logger: logger, |
||||||
|
} |
||||||
|
for _, o := range opts { |
||||||
|
o(options) |
||||||
|
} |
||||||
|
return options |
||||||
|
} |
||||||
|
func (h *Helper) WithContext(ctx context.Context) *Helper { |
||||||
|
return &Helper{ |
||||||
|
logger: WithContext(ctx, h.logger), |
||||||
|
msgKey: h.msgKey, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Log Print log by level and keyvals.
|
||||||
|
func (h *Helper) Log(level Level, keyvals ...interface{}) { |
||||||
|
_ = h.logger.Log(level, keyvals...) |
||||||
|
} |
||||||
|
|
||||||
|
// Debug logs a message at debug level.
|
||||||
|
func (h *Helper) Debug(a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelDebug, h.msgKey, fmt.Sprint(a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Debugf logs a message at debug level.
|
||||||
|
func (h *Helper) Debugf(format string, a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelDebug, h.msgKey, fmt.Sprintf(format, a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Debugw logs a message at debug level.
|
||||||
|
func (h *Helper) Debugw(keyvals ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelDebug, keyvals...) |
||||||
|
} |
||||||
|
|
||||||
|
// Info logs a message at info level.
|
||||||
|
func (h *Helper) Info(a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelInfo, h.msgKey, fmt.Sprint(a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Infof logs a message at info level.
|
||||||
|
func (h *Helper) Infof(format string, a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelInfo, h.msgKey, fmt.Sprintf(format, a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Infow logs a message at info level.
|
||||||
|
func (h *Helper) Infow(keyvals ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelInfo, keyvals...) |
||||||
|
} |
||||||
|
|
||||||
|
// Warn logs a message at warn level.
|
||||||
|
func (h *Helper) Warn(a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelWarn, h.msgKey, fmt.Sprint(a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Warnf logs a message at warnf level.
|
||||||
|
func (h *Helper) Warnf(format string, a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelWarn, h.msgKey, fmt.Sprintf(format, a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Warnw logs a message at warnf level.
|
||||||
|
func (h *Helper) Warnw(keyvals ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelWarn, keyvals...) |
||||||
|
} |
||||||
|
|
||||||
|
// Error logs a message at error level.
|
||||||
|
func (h *Helper) Error(a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelError, h.msgKey, fmt.Sprint(a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Errorf logs a message at error level.
|
||||||
|
func (h *Helper) Errorf(format string, a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelError, h.msgKey, fmt.Sprintf(format, a...)) |
||||||
|
} |
||||||
|
|
||||||
|
// Errorw logs a message at error level.
|
||||||
|
func (h *Helper) Errorw(keyvals ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelError, keyvals...) |
||||||
|
} |
||||||
|
|
||||||
|
// Fatal logs a message at fatal level.
|
||||||
|
func (h *Helper) Fatal(a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelFatal, h.msgKey, fmt.Sprint(a...)) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Fatalf logs a message at fatal level.
|
||||||
|
func (h *Helper) Fatalf(format string, a ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelFatal, h.msgKey, fmt.Sprintf(format, a...)) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// Fatalw logs a message at fatal level.
|
||||||
|
func (h *Helper) Fatalw(keyvals ...interface{}) { |
||||||
|
_ = h.logger.Log(LevelFatal, keyvals...) |
||||||
|
os.Exit(1) |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import "testing" |
||||||
|
|
||||||
|
func TestNewHelper(t *testing.T) { |
||||||
|
logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) |
||||||
|
log := NewHelper(logger) |
||||||
|
log.Debug("debug") |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import "strings" |
||||||
|
|
||||||
|
type Level int8 |
||||||
|
|
||||||
|
const LevelKey = "level" |
||||||
|
const ( |
||||||
|
LevelDebug Level = iota - 1 |
||||||
|
LevelInfo |
||||||
|
LevelWarn |
||||||
|
LevelError |
||||||
|
LevelFatal |
||||||
|
) |
||||||
|
|
||||||
|
func (l Level) Key() string { |
||||||
|
return LevelKey |
||||||
|
} |
||||||
|
func (l Level) String() string { |
||||||
|
switch l { |
||||||
|
case LevelDebug: |
||||||
|
return "DEBUG" |
||||||
|
case LevelInfo: |
||||||
|
return "INFO" |
||||||
|
case LevelWarn: |
||||||
|
return "WARN" |
||||||
|
case LevelError: |
||||||
|
return "ERROR" |
||||||
|
case LevelFatal: |
||||||
|
return "FATAL" |
||||||
|
default: |
||||||
|
return "" |
||||||
|
} |
||||||
|
} |
||||||
|
func ParseLevel(s string) Level { |
||||||
|
switch strings.ToUpper(s) { |
||||||
|
case "DEBUG": |
||||||
|
return LevelDebug |
||||||
|
case "INFO": |
||||||
|
return LevelInfo |
||||||
|
case "WARN": |
||||||
|
return LevelWarn |
||||||
|
case "ERROR": |
||||||
|
return LevelError |
||||||
|
case "FATAL": |
||||||
|
return LevelFatal |
||||||
|
} |
||||||
|
return LevelInfo |
||||||
|
} |
@ -0,0 +1,65 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"log" |
||||||
|
) |
||||||
|
|
||||||
|
var DefaultLogger = NewStdLogger(log.Writer()) |
||||||
|
|
||||||
|
type Logger interface { |
||||||
|
Log(level Level, keyvals ...any) error |
||||||
|
} |
||||||
|
|
||||||
|
type logger struct { |
||||||
|
logger Logger |
||||||
|
prefix []any |
||||||
|
hasValuer bool |
||||||
|
ctx context.Context |
||||||
|
} |
||||||
|
|
||||||
|
func (l *logger) Log(level Level, keyvals ...any) error { |
||||||
|
kvs := make([]any, 0, len(l.prefix)+len(keyvals)) |
||||||
|
kvs = append(kvs, l.prefix...) |
||||||
|
if l.hasValuer { |
||||||
|
bindValues(l.ctx, kvs) |
||||||
|
} |
||||||
|
kvs = append(kvs, keyvals...) |
||||||
|
return l.logger.Log(level, kvs...) |
||||||
|
} |
||||||
|
|
||||||
|
func With(l Logger, kv ...any) Logger { |
||||||
|
c, ok := l.(*logger) |
||||||
|
if !ok { |
||||||
|
return &logger{ |
||||||
|
logger: l, |
||||||
|
prefix: kv, |
||||||
|
hasValuer: containsValuer(kv), |
||||||
|
ctx: context.Background(), |
||||||
|
} |
||||||
|
} |
||||||
|
kvs := make([]any, 0, len(c.prefix)+len(kv)) |
||||||
|
kvs = append(kvs, c.prefix...) |
||||||
|
kvs = append(kvs, kv...) |
||||||
|
return &logger{ |
||||||
|
logger: c.logger, |
||||||
|
prefix: kvs, |
||||||
|
hasValuer: containsValuer(kvs), |
||||||
|
ctx: c.ctx, |
||||||
|
} |
||||||
|
} |
||||||
|
func WithContext(ctx context.Context, l Logger) Logger { |
||||||
|
c, ok := l.(*logger) |
||||||
|
if !ok { |
||||||
|
return &logger{ |
||||||
|
logger: l, |
||||||
|
ctx: ctx, |
||||||
|
} |
||||||
|
} |
||||||
|
return &logger{ |
||||||
|
logger: c.logger, |
||||||
|
prefix: c.prefix, |
||||||
|
hasValuer: c.hasValuer, |
||||||
|
ctx: ctx, |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func Test_logger_Log(t *testing.T) { |
||||||
|
logger := DefaultLogger |
||||||
|
logger = With(logger, "ts", DefaultTimestamp) |
||||||
|
logger = With(logger, "call", DefaultCaller) |
||||||
|
_ = logger.Log(LevelInfo, "key1", "val1") |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"log" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
type stdLogger struct { |
||||||
|
log *log.Logger |
||||||
|
pool *sync.Pool |
||||||
|
} |
||||||
|
|
||||||
|
func NewStdLogger(w io.Writer) Logger { |
||||||
|
return &stdLogger{ |
||||||
|
log: log.New(w, "", 0), |
||||||
|
pool: &sync.Pool{ |
||||||
|
New: func() any { |
||||||
|
return new(bytes.Buffer) |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
func (l *stdLogger) Log(level Level, keyvals ...any) error { |
||||||
|
if len(keyvals) == 0 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if len(keyvals)&1 == 1 { |
||||||
|
keyvals = append(keyvals, "KEYVALS UNPAIRED") |
||||||
|
} |
||||||
|
buf := l.pool.Get().(*bytes.Buffer) |
||||||
|
buf.WriteString(level.String()) |
||||||
|
for i := 0; i < len(keyvals); i += 2 { |
||||||
|
_, _ = fmt.Fprintf(buf, " %s=%v", keyvals[i], keyvals[i+1]) |
||||||
|
} |
||||||
|
_ = l.log.Output(4, buf.String()) |
||||||
|
buf.Reset() |
||||||
|
l.pool.Put(buf) |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
package log |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"runtime" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
DefaultCaller = Caller(4) |
||||||
|
DefaultTimestamp = Timestamp(time.RFC3339) |
||||||
|
) |
||||||
|
|
||||||
|
type Valuer func(ctx context.Context) any |
||||||
|
|
||||||
|
func Value(ctx context.Context, v any) any { |
||||||
|
if v, ok := v.(Valuer); ok { |
||||||
|
return v(ctx) |
||||||
|
} |
||||||
|
return v |
||||||
|
} |
||||||
|
func Caller(depth int) Valuer { |
||||||
|
return func(ctx context.Context) any { |
||||||
|
_, file, line, _ := runtime.Caller(depth) |
||||||
|
idx := strings.LastIndexByte(file, '/') |
||||||
|
if idx == -1 { |
||||||
|
return file[idx+1:] + ":" + strconv.Itoa(line) |
||||||
|
} |
||||||
|
idx = strings.LastIndexByte(file[:idx], '/') |
||||||
|
return file[idx+1:] + ":" + strconv.Itoa(line) |
||||||
|
} |
||||||
|
} |
||||||
|
func Timestamp(layout string) Valuer { |
||||||
|
return func(ctx context.Context) any { |
||||||
|
return time.Now().Format(layout) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func bindValues(ctx context.Context, keyvals []any) { |
||||||
|
for i := 1; i < len(keyvals); i += 2 { |
||||||
|
if v, ok := keyvals[i].(Valuer); ok { |
||||||
|
keyvals[i] = v(ctx) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
func containsValuer(keyvals []any) bool { |
||||||
|
for i := 1; i < len(keyvals); i += 2 { |
||||||
|
if _, ok := keyvals[i].(Valuer); ok { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
Loading…
Reference in new issue