package logsys import ( "fmt" "sync" "time" ) // Options 构造一个 Logger。构造完成后不要修改切片和映射。 type Options struct { Service string Module string Level Level ModuleLevels map[string]Level Sinks []Sink Redact func(string) string Fields map[string]any Now func() time.Time } // Logger 是并发安全的日志器。With / Module 返回副本,不改变原对象。 type Logger struct { service string module string min Level moduleLevels map[string]Level fields map[string]any traceID string sinks []Sink redact func(string) string now func() time.Time nop bool } // New 创建日志器。没有 sink 时写入被丢弃(与 Nop 相同,但保留级别与字段)。 func New(opt Options) *Logger { now := opt.Now if now == nil { now = func() time.Time { return time.Now().UTC() } } levels := make(map[string]Level, len(opt.ModuleLevels)) for k, v := range opt.ModuleLevels { levels[k] = v } return &Logger{ service: opt.Service, module: opt.Module, min: opt.Level, moduleLevels: levels, fields: cloneFields(opt.Fields), sinks: append([]Sink(nil), opt.Sinks...), redact: opt.Redact, now: now, } } // Nop 丢弃全部日志。 func Nop() *Logger { return &Logger{nop: true, now: time.Now} } // Module 返回绑定模块名的子 logger,用于匹配模块级别。 func (l *Logger) Module(name string) *Logger { if l == nil { return Nop() } n := l.clone() n.module = name return n } // With 追加字段。键必须是非空字符串,且与值成对;否则 panic。 func (l *Logger) With(kv ...any) *Logger { if l == nil { return Nop() } n := l.clone() n.fields = mergeFields(n.fields, fieldsFrom(kv)) return n } // WithTraceID 固定本 logger 的追踪标识。 func (l *Logger) WithTraceID(id string) *Logger { if l == nil { return Nop() } n := l.clone() n.traceID = id return n } // Enabled 报告该级别是否会写出去。 func (l *Logger) Enabled(level Level) bool { if l == nil || l.nop { return false } return l.enabled(level) } // Trace 记录 trace 级别日志。 func (l *Logger) Trace(msg string, kv ...any) { l.log(LevelTrace, msg, nil, kv...) } // Debug 记录 debug 级别日志。 func (l *Logger) Debug(msg string, kv ...any) { l.log(LevelDebug, msg, nil, kv...) } // Info 记录 info 级别日志。 func (l *Logger) Info(msg string, kv ...any) { l.log(LevelInfo, msg, nil, kv...) } // Warn 记录 warn 级别日志。 func (l *Logger) Warn(msg string, kv ...any) { l.log(LevelWarn, msg, nil, kv...) } // Error 记录 error 级别日志。err 可为 nil;若实现 ErrorCode/ErrorFields,会展开进条目。 func (l *Logger) Error(msg string, err error, kv ...any) { l.log(LevelError, msg, err, kv...) } // Fatal 记录 fatal 级别日志,不退出进程。 func (l *Logger) Fatal(msg string, err error, kv ...any) { l.log(LevelFatal, msg, err, kv...) } // Sync 刷出全部 sink。 func (l *Logger) Sync() error { if l == nil || l.nop { return nil } var errs []error for _, s := range l.sinks { if s == nil { continue } if err := s.Sync(); err != nil { errs = append(errs, err) } } return joinErrs(errs) } // Close 关闭全部 sink。 func (l *Logger) Close() error { if l == nil || l.nop { return nil } var errs []error for _, s := range l.sinks { if s == nil { continue } if err := s.Close(); err != nil { errs = append(errs, err) } } return joinErrs(errs) } func (l *Logger) log(level Level, msg string, err error, kv ...any) { extra := fieldsFrom(kv) if l == nil || l.nop || !l.enabled(level) { return } fields := mergeFields(l.fields, extra) entry := Entry{ Time: l.now(), Level: level.String(), Service: l.service, Module: l.module, Message: msg, TraceID: l.traceID, Fields: fields, } applyError(&entry, err) if l.redact != nil { entry = redactEntry(entry, l.redact) } for _, s := range l.sinks { if s == nil { continue } _ = s.Write(entry) } } func (l *Logger) enabled(level Level) bool { min := l.min if l.module != "" { if ml, ok := l.moduleLevels[l.module]; ok { min = ml } } return level >= min } func (l *Logger) clone() *Logger { n := *l n.fields = cloneFields(l.fields) n.moduleLevels = l.moduleLevels n.sinks = l.sinks return &n } func applyError(entry *Entry, err error) { if err == nil { return } entry.Error = err.Error() if coded, ok := err.(codedError); ok { entry.Code = coded.ErrorCode() for k, v := range coded.ErrorFields() { if entry.Fields == nil { entry.Fields = map[string]any{} } entry.Fields[k] = v } } } func redactEntry(entry Entry, redact func(string) string) Entry { entry.Message = redact(entry.Message) entry.Error = redact(entry.Error) entry.Fields = redactFields(entry.Fields, redact) return entry } func redactFields(in map[string]any, redact func(string) string) map[string]any { if len(in) == 0 { return nil } out := make(map[string]any, len(in)) for k, v := range in { out[redact(k)] = redactValue(v, redact) } return out } func redactValue(v any, redact func(string) string) any { switch t := v.(type) { case string: return redact(t) case error: if t == nil { return nil } return redact(t.Error()) case map[string]any: return redactFields(t, redact) default: return v } } func fieldsFrom(kv []any) map[string]any { if len(kv) == 0 { return nil } if len(kv)%2 != 0 { panic("logsys: odd number of key-value arguments") } out := make(map[string]any, len(kv)/2) for i := 0; i < len(kv); i += 2 { key, ok := kv[i].(string) if !ok || key == "" { panic(fmt.Sprintf("logsys: field key must be a non-empty string, got %T", kv[i])) } out[key] = kv[i+1] } return out } func cloneFields(in map[string]any) map[string]any { if len(in) == 0 { return nil } out := make(map[string]any, len(in)) for k, v := range in { out[k] = v } return out } func mergeFields(base, extra map[string]any) map[string]any { if len(base) == 0 && len(extra) == 0 { return nil } out := cloneFields(base) if out == nil { out = map[string]any{} } for k, v := range extra { out[k] = v } return out } func joinErrs(errs []error) error { if len(errs) == 0 { return nil } if len(errs) == 1 { return errs[0] } return &multiErr{errs: errs} } type multiErr struct{ errs []error } func (m *multiErr) Error() string { var b bytesTrim for i, err := range m.errs { if i > 0 { b.WriteString("; ") } b.WriteString(err.Error()) } return b.String() } func (m *multiErr) Unwrap() []error { return m.errs } // bytesTrim 避免为拼接错误再引入 bytes 与 strings 的混用。 type bytesTrim struct{ b []byte } func (b *bytesTrim) WriteString(s string) { b.b = append(b.b, s...) } func (b *bytesTrim) String() string { return string(b.b) } var ( defaultMu sync.RWMutex defaultL = Nop() ) // SetDefault 设置进程默认日志器。nil 等价于 Nop。 func SetDefault(l *Logger) { if l == nil { l = Nop() } defaultMu.Lock() defaultL = l defaultMu.Unlock() } // Default 返回进程默认日志器。未设置时为 Nop。 func Default() *Logger { defaultMu.RLock() defer defaultMu.RUnlock() return defaultL } // Sync 刷出默认日志器。 func Sync() error { return Default().Sync() }