From fb0d83251fdd8366d0565dd7c93dcde63d5836b9 Mon Sep 17 00:00:00 2001 From: wonder Date: Mon, 25 May 2026 14:30:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(log):=20=E6=96=B0=E5=BB=BA=20logger=20?= =?UTF-8?q?=E5=8C=85=EF=BC=8C=E5=9F=BA=E4=BA=8E=20log/slog=20=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E7=BB=93=E6=9E=84=E5=8C=96=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 internal/logger/logger.go,提供 Init/FromCtx/WithRequestID 方法 - Config 新增 LogConfig(level/format),支持 GEN2D_LOG_LEVEL/GEN2D_LOG_FORMAT 环境变量 - config.yml 和 .env.example 添加日志配置示例 --- backend/.env.example | 4 ++ backend/internal/config/config.go | 13 ++++++ backend/internal/config/config.yml | 4 ++ backend/internal/logger/logger.go | 71 ++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 backend/internal/logger/logger.go diff --git a/backend/.env.example b/backend/.env.example index 3e656b5..402d14b 100755 --- a/backend/.env.example +++ b/backend/.env.example @@ -5,6 +5,10 @@ GEN2D_PORT=8080 GEN2D_MODE=debug +# 日志配置 +GEN2D_LOG_LEVEL=info # debug / info / warn / error +GEN2D_LOG_FORMAT=text # text / json + # SQLite 数据库路径 GEN2D_DSN=data/gen2d.db diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 31d545f..000f62e 100755 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -12,11 +12,18 @@ type Config struct { Server ServerConfig `mapstructure:"server"` Database DatabaseConfig `mapstructure:"database"` JWT JWTConfig `mapstructure:"jwt"` + Log LogConfig `mapstructure:"log"` LLM LLMConfig `mapstructure:"llm"` ImageGen ImageGenConfig `mapstructure:"image_gen"` Qiniu QiniuConfig `mapstructure:"qiniu"` } +// LogConfig 日志配置。 +type LogConfig struct { + Level string `mapstructure:"level"` // debug / info / warn / error + Format string `mapstructure:"format"` // text / json +} + // ServerConfig HTTP 服务配置。 type ServerConfig struct { Port int `mapstructure:"port"` @@ -103,6 +110,9 @@ func setDefaults(v *viper.Viper) { v.SetDefault("server.mode", "debug") v.SetDefault("server.max_file_size", int64(10<<20)) + v.SetDefault("log.level", "info") + v.SetDefault("log.format", "text") + v.SetDefault("database.dsn", "data/gen2d.db") v.SetDefault("jwt.secret", "gen2d-dev-secret") @@ -139,6 +149,9 @@ func bindEnvVars(v *viper.Viper) { v.BindEnv("server.mode", "GEN2D_MODE") v.BindEnv("server.max_file_size", "GEN2D_MAX_FILE_SIZE") v.BindEnv("database.dsn", "GEN2D_DSN") + + v.BindEnv("log.level", "GEN2D_LOG_LEVEL") + v.BindEnv("log.format", "GEN2D_LOG_FORMAT") v.BindEnv("jwt.secret", "GEN2D_JWT_SECRET") v.BindEnv("jwt.expire", "GEN2D_JWT_EXPIRE") diff --git a/backend/internal/config/config.yml b/backend/internal/config/config.yml index aa29e74..7f1a7b2 100755 --- a/backend/internal/config/config.yml +++ b/backend/internal/config/config.yml @@ -6,6 +6,10 @@ server: mode: debug max_file_size: 10485760 # 10MB +log: + level: info # debug / info / warn / error + format: text # text / json + database: dsn: "data/gen2d.db" diff --git a/backend/internal/logger/logger.go b/backend/internal/logger/logger.go new file mode 100644 index 0000000..01650cf --- /dev/null +++ b/backend/internal/logger/logger.go @@ -0,0 +1,71 @@ +package logger + +import ( + "context" + "log/slog" + "os" + "strings" +) + +type ctxKey struct{} + +// L 全局 logger,Init 之前使用默认 text 输出。 +var L *slog.Logger + +func init() { + L = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) +} + +// Init 初始化全局 logger。 +// level: debug / info / warn / error +// format: text / json +func Init(level, format string) { + var lvl slog.Level + switch strings.ToLower(level) { + case "debug": + lvl = slog.LevelDebug + case "warn": + lvl = slog.LevelWarn + case "error": + lvl = slog.LevelError + default: + lvl = slog.LevelInfo + } + + opts := &slog.HandlerOptions{Level: lvl} + + var h slog.Handler + switch strings.ToLower(format) { + case "json": + h = slog.NewJSONHandler(os.Stdout, opts) + default: + h = slog.NewTextHandler(os.Stdout, opts) + } + + L = slog.New(h) + slog.SetDefault(L) +} + +// With 创建带附加字段的 logger。 +func With(args ...any) *slog.Logger { + return L.With(args...) +} + +// IntoCtx 将 logger 存入 context。 +func IntoCtx(ctx context.Context, l *slog.Logger) context.Context { + return context.WithValue(ctx, ctxKey{}, l) +} + +// FromCtx 从 context 取出 logger,不存在时返回全局 L。 +func FromCtx(ctx context.Context) *slog.Logger { + if l, ok := ctx.Value(ctxKey{}).(*slog.Logger); ok { + return l + } + return L +} + +// WithRequestID 创建带 request_id 的 logger 并存入 context。 +func WithRequestID(ctx context.Context, requestID string) (context.Context, *slog.Logger) { + l := L.With("request_id", requestID) + return IntoCtx(ctx, l), l +}