Files
folium/logx/log.go
Snowykami b0d224dc64 feat: add logging and collector implementations
- Introduced `hertzx` package with `NewHertz` function for server initialization.
- Implemented `logx` package with various log collectors: Console, Loki, Elasticsearch, and Prometheus.
- Added `Logger` struct to manage logging levels and collectors.
- Created environment variable loading functionality in `osx` package to support configuration.
- Enhanced logging capabilities with structured log entries and asynchronous collection.
2025-11-01 13:20:02 +08:00

443 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package logx
import (
"context"
"strings"
"sync"
"time"
"git.liteyuki.org/LiteyukiStudio/folium/osx"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// 支持的日志等级
type Level string
const (
LevelTrace Level = "trace"
LevelDebug Level = "debug"
LevelInfo Level = "info"
LevelWarn Level = "warn"
LevelError Level = "error"
LevelFatal Level = "fatal"
LevelPanic Level = "panic"
)
// Entry 表示一条日志的可传输结构
type Entry struct {
Level Level `json:"level"`
Time time.Time `json:"time"`
Msg string `json:"msg"`
Fields map[string]interface{} `json:"fields,omitempty"`
}
// Collector 是外部收集器接口(用户可实现)
type Collector interface {
Collect(ctx context.Context, e Entry) error
Close() error
}
// Logger 支持本地打印并把日志发送到已注册的 collectors
type Logger struct {
zap *zap.Logger
collectors []Collector
mu sync.RWMutex
wg sync.WaitGroup
minLevel Level // 低于此级别的日志将被忽略
}
// 全局单例
var (
Default *Logger
defaultOnce sync.Once
)
// buildLoggerFromEnv 基于环境构造 Logger不设置全局变量
func buildLoggerFromEnv() *Logger {
// 输出格式
output := strings.ToLower(osx.GetEnv("LOG_OUTPUT", "console"))
cfg := zap.NewProductionConfig()
if output == "json" {
cfg.Encoding = "json"
} else {
cfg.Encoding = "console"
}
cfg.EncoderConfig.TimeKey = "ts"
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 让 zap 本身也服从 LOG_LEVEL
levelStr := strings.ToLower(osx.GetEnv("LOG_LEVEL", osx.GetEnv("LOG_MIN_LEVEL", "info")))
var zapLevel zapcore.Level
switch levelStr {
case "trace", "debug":
zapLevel = zapcore.DebugLevel
case "info":
zapLevel = zapcore.InfoLevel
case "warn", "warning":
zapLevel = zapcore.WarnLevel
case "error":
zapLevel = zapcore.ErrorLevel
case "fatal":
zapLevel = zapcore.FatalLevel
case "panic":
zapLevel = zapcore.PanicLevel
default:
zapLevel = zapcore.InfoLevel
}
cfg.Level = zap.NewAtomicLevelAt(zapLevel)
// 可通过环境关闭 stacktrace 输出
if strings.ToLower(osx.GetEnv("LOG_DISABLE_STACKTRACE", "false")) == "true" {
cfg.DisableStacktrace = true
}
zl, _ := cfg.Build()
l := &Logger{
zap: zl,
collectors: nil,
minLevel: LevelInfo,
}
// 最低日志级别(同步 env
switch levelStr {
case "trace":
l.minLevel = LevelTrace
case "debug":
l.minLevel = LevelDebug
case "info":
l.minLevel = LevelInfo
case "warn", "warning":
l.minLevel = LevelWarn
case "error":
l.minLevel = LevelError
case "fatal":
l.minLevel = LevelFatal
case "panic":
l.minLevel = LevelPanic
default:
l.minLevel = LevelInfo
}
// 从环境加载并注册 collectors实现位于 collector.go
cols := LoadCollectorsFromEnv()
for _, c := range cols {
l.RegisterCollector(c)
}
return l
}
// InitLogger 初始化全局单例(只第一次生效),返回单例
func InitLogger() *Logger {
defaultOnce.Do(func() {
Default = buildLoggerFromEnv()
})
return Default
}
// GetLogger 返回全局单例,如果尚未初始化则会自动 InitLogger
func GetLogger() *Logger {
return InitLogger()
}
// 删除旧的 New / NewDefault保留 New(...) 风格的局部 logger 创建函数改名为 NewLocal可选
func NewLocal(zl *zap.Logger) *Logger {
if zl == nil {
// 使用一个轻量 zap 实例,不触碰全局单例
cfg := zap.NewProductionConfig()
cfg.Encoding = "console"
cfg.EncoderConfig.TimeKey = "ts"
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
zl2, _ := cfg.Build()
return &Logger{
zap: zl2,
minLevel: LevelDebug,
collectors: nil,
}
}
return &Logger{
zap: zl,
minLevel: LevelDebug,
collectors: nil,
}
}
// RegisterCollector 在运行时注册一个 Collector线程安全
func (l *Logger) RegisterCollector(c Collector) {
if c == nil {
return
}
l.mu.Lock()
defer l.mu.Unlock()
l.collectors = append(l.collectors, c)
}
// UnregisterCollector 注销已注册的 Collector按指针相等匹配
func (l *Logger) UnregisterCollector(c Collector) {
if c == nil {
return
}
l.mu.Lock()
defer l.mu.Unlock()
for i, col := range l.collectors {
if col == c {
l.collectors = append(l.collectors[:i], l.collectors[i+1:]...)
return
}
}
}
// ListCollectors 返回当前已注册的 collectors副本线程安全
func (l *Logger) ListCollectors() []Collector {
l.mu.RLock()
defer l.mu.RUnlock()
cp := make([]Collector, len(l.collectors))
copy(cp, l.collectors)
return cp
}
// Close 关闭所有 collectors阻塞直到异步发送结束
func (l *Logger) Close() error {
l.mu.RLock()
collectors := append([]Collector(nil), l.collectors...)
l.mu.RUnlock()
// 等待 async sending 完成
l.wg.Wait()
var firstErr error
for _, c := range collectors {
if err := c.Close(); err != nil && firstErr == nil {
firstErr = err
}
}
_ = l.zap.Sync()
return firstErr
}
// SetLevel 设置最低日志级别,低于该级别的日志将被忽略
func (l *Logger) SetLevel(level Level) {
l.mu.Lock()
defer l.mu.Unlock()
l.minLevel = level
}
// level 排序辅助
var levelOrder = map[Level]int{
LevelTrace: 0,
LevelDebug: 1,
LevelInfo: 2,
LevelWarn: 3,
LevelError: 4,
LevelFatal: 5,
LevelPanic: 6,
}
func (l *Logger) log(ctx context.Context, level Level, msg string, fields map[string]interface{}) {
// 级别过滤
l.mu.RLock()
min := l.minLevel
l.mu.RUnlock()
if levelOrder[level] < levelOrder[min] {
return
}
// 本地打印(保持原有行为),并尽量保持 trace/level 字段
switch level {
case LevelTrace:
l.zap.Debug(msg, zap.String("level", string(LevelTrace)), zap.Any("fields", fields))
case LevelDebug:
l.zap.Debug(msg, zap.Any("fields", fields))
case LevelInfo:
l.zap.Info(msg, zap.Any("fields", fields))
case LevelWarn:
l.zap.Warn(msg, zap.Any("fields", fields))
case LevelError:
l.zap.Error(msg, zap.Any("fields", fields))
case LevelFatal:
// Fatal 会调用 os.Exit(1)
l.zap.Fatal(msg, zap.Any("fields", fields))
case LevelPanic:
// Panic 会 panic
l.zap.Panic(msg, zap.Any("fields", fields))
default:
l.zap.Info(msg, zap.Any("level", level), zap.Any("fields", fields))
}
// 向 collectors 异步发送
l.mu.RLock()
collectors := append([]Collector(nil), l.collectors...)
l.mu.RUnlock()
if len(collectors) == 0 {
return
}
e := Entry{
Level: level,
Time: time.Now(),
Msg: msg,
Fields: fields,
}
for _, c := range collectors {
c := c
l.wg.Add(1)
go func() {
defer l.wg.Done()
ctx2, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := c.Collect(ctx2, e); err != nil {
// collectors 的错误不影响主流程,记录到本地日志
l.zap.Warn("collector collect error", zap.Error(err))
}
}()
}
}
// 便捷方法(增加 Trace, Fatal, Panic
// 保留 Logger 的方法签名:需要 ctx
func (l *Logger) Trace(ctx context.Context, msg string, fields map[string]interface{}) {
l.log(ctx, LevelTrace, msg, fields)
}
func (l *Logger) Debug(ctx context.Context, msg string, fields map[string]interface{}) {
l.log(ctx, LevelDebug, msg, fields)
}
func (l *Logger) Info(ctx context.Context, msg string, fields map[string]interface{}) {
l.log(ctx, LevelInfo, msg, fields)
}
func (l *Logger) Warn(ctx context.Context, msg string, fields map[string]interface{}) {
l.log(ctx, LevelWarn, msg, fields)
}
func (l *Logger) Error(ctx context.Context, msg string, fields map[string]interface{}) {
l.log(ctx, LevelError, msg, fields)
}
func (l *Logger) Fatal(ctx context.Context, msg string, fields map[string]interface{}) {
l.log(ctx, LevelFatal, msg, fields)
}
func (l *Logger) Panic(ctx context.Context, msg string, fields map[string]interface{}) {
l.log(ctx, LevelPanic, msg, fields)
}
// SimpleLogger 提供不带 ctx 的简洁 API可选 fields
type SimpleLogger struct {
parent *Logger
}
func (s *SimpleLogger) Trace(msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
s.parent.Trace(context.Background(), msg, f)
}
func (s *SimpleLogger) Debug(msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
s.parent.Debug(context.Background(), msg, f)
}
func (s *SimpleLogger) Info(msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
s.parent.Info(context.Background(), msg, f)
}
func (s *SimpleLogger) Warn(msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
s.parent.Warn(context.Background(), msg, f)
}
func (s *SimpleLogger) Error(msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
s.parent.Error(context.Background(), msg, f)
}
func (s *SimpleLogger) Fatal(msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
s.parent.Fatal(context.Background(), msg, f)
}
func (s *SimpleLogger) Panic(msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
s.parent.Panic(context.Background(), msg, f)
}
// Simple 返回一个绑定到当前 Logger 的 SimpleLogger不创建新实例
func (l *Logger) Simple() *SimpleLogger {
return &SimpleLogger{parent: l}
}
// 包级简洁 API无需 ctx自动使用 background ctx调用者更推荐显式使用 Ctx* 以保留 trace
func Trace(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Trace(msg, fields...) }
func Debug(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Debug(msg, fields...) }
func Info(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Info(msg, fields...) }
func Warn(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Warn(msg, fields...) }
func Error(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Error(msg, fields...) }
func Fatal(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Fatal(msg, fields...) }
func Panic(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Panic(msg, fields...) }
// 包级带 ctx 的 API当你有请求上下文包含 trace时使用
func CtxTrace(ctx context.Context, msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
GetLogger().Trace(ctx, msg, f)
}
func CtxDebug(ctx context.Context, msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
GetLogger().Debug(ctx, msg, f)
}
func CtxInfo(ctx context.Context, msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
GetLogger().Info(ctx, msg, f)
}
func CtxWarn(ctx context.Context, msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
GetLogger().Warn(ctx, msg, f)
}
func CtxError(ctx context.Context, msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
GetLogger().Error(ctx, msg, f)
}
func CtxFatal(ctx context.Context, msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
GetLogger().Fatal(ctx, msg, f)
}
func CtxPanic(ctx context.Context, msg string, fields ...map[string]interface{}) {
var f map[string]interface{}
if len(fields) > 0 {
f = fields[0]
}
GetLogger().Panic(ctx, msg, f)
}