增加日志系统

This commit is contained in:
yml2213
2026-05-29 12:40:12 +08:00
parent eb80a3ba84
commit 3b8b65ab56
6 changed files with 181 additions and 4 deletions
+1
View File
@@ -42,6 +42,7 @@ npm run dev
## 开发态短信与实名
- 后端日志默认同时输出到控制台和 `backend/logs/app-YYYY-MM-DD.log`,按北京时间自动按天切分;可通过 `LOG_LEVEL=debug|info|warn|error` 调整级别。
- 短信验证码默认使用 mock 适配器,验证码会打印在后端日志中;生产环境可通过 `SMS_PROVIDER=aliyun` 接入阿里云短信。
- 阿里云短信需要配置 `ALIYUN_ACCESS_KEY_ID``ALIYUN_ACCESS_KEY_SECRET``ALIYUN_SMS_SIGN_NAME``ALIYUN_SMS_LOGIN_TEMPLATE_CODE`,模板变量名默认为 `code`
- 实名认证使用 mock 适配器,登录后请求 `POST /api/realname/start`,提交合法姓名和 18 位身份证号会直接通过。
+6
View File
@@ -7,6 +7,12 @@ REDIS_PASSWORD=
REDIS_DB=0
JWT_SECRET=change-me
LOG_LEVEL=info
LOG_DIR=logs
LOG_ENABLE_CONSOLE=true
LOG_ENABLE_FILE=true
STORAGE_ENDPOINT=http://localhost:9000
STORAGE_BUCKET=hfb-sys
STORAGE_ACCESS_KEY_ID=minioadmin
+2 -4
View File
@@ -11,6 +11,7 @@ import (
"hfb_sys/backend/internal/config"
"hfb_sys/backend/internal/database"
"hfb_sys/backend/internal/jobs/ordertimeout"
"hfb_sys/backend/internal/logging"
"hfb_sys/backend/internal/router"
"go.uber.org/zap"
@@ -18,10 +19,7 @@ import (
func main() {
cfg := config.Load()
logger, err := zap.NewProduction()
if cfg.AppEnv == "development" {
logger, err = zap.NewDevelopment()
}
logger, err := logging.New(cfg.Log)
if err != nil {
panic(err)
}
+26
View File
@@ -15,6 +15,7 @@ type Config struct {
JWTSecret string
Storage StorageConfig
SMS SMSConfig
Log LogConfig
}
type StorageConfig struct {
@@ -33,6 +34,13 @@ type SMSConfig struct {
AliyunLoginTemplateCode string
}
type LogConfig struct {
Level string
Dir string
EnableConsole bool
EnableFile bool
}
func Load() Config {
return Config{
AppEnv: getEnv("APP_ENV", "development"),
@@ -56,6 +64,12 @@ func Load() Config {
AliyunSignName: getEnv("ALIYUN_SMS_SIGN_NAME", ""),
AliyunLoginTemplateCode: getEnv("ALIYUN_SMS_LOGIN_TEMPLATE_CODE", ""),
},
Log: LogConfig{
Level: getEnv("LOG_LEVEL", "info"),
Dir: getEnv("LOG_DIR", "logs"),
EnableConsole: getEnvBool("LOG_ENABLE_CONSOLE", true),
EnableFile: getEnvBool("LOG_ENABLE_FILE", true),
},
}
}
@@ -78,3 +92,15 @@ func getEnvInt(key string, fallback int) int {
}
return parsed
}
func getEnvBool(key string, fallback bool) bool {
value := os.Getenv(key)
if value == "" {
return fallback
}
parsed, err := strconv.ParseBool(value)
if err != nil {
return fallback
}
return parsed
}
+71
View File
@@ -0,0 +1,71 @@
package logging
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
)
type dailyWriter struct {
mu sync.Mutex
dir string
prefix string
location *time.Location
day string
file *os.File
}
func newDailyWriter(dir string, prefix string, location *time.Location) *dailyWriter {
return &dailyWriter{
dir: dir,
prefix: prefix,
location: location,
}
}
func (w *dailyWriter) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
if err := w.rotateIfNeeded(time.Now().In(w.location)); err != nil {
return 0, err
}
return w.file.Write(p)
}
func (w *dailyWriter) Sync() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.file == nil {
return nil
}
return w.file.Sync()
}
func (w *dailyWriter) rotateIfNeeded(now time.Time) error {
day := now.Format("2006-01-02")
if w.file != nil && w.day == day {
return nil
}
if err := os.MkdirAll(w.dir, 0o755); err != nil {
return err
}
if w.file != nil {
_ = w.file.Close()
w.file = nil
}
path := filepath.Join(w.dir, fmt.Sprintf("%s-%s.log", w.prefix, day))
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil {
return err
}
w.file = file
w.day = day
return nil
}
+75
View File
@@ -0,0 +1,75 @@
package logging
import (
"os"
"strings"
"time"
"hfb_sys/backend/internal/config"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func New(cfg config.LogConfig) (*zap.Logger, error) {
location, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
location = time.FixedZone("Asia/Shanghai", 8*60*60)
}
level := zap.NewAtomicLevelAt(parseLevel(cfg.Level))
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.TimeKey = "time"
encoderConfig.LevelKey = "level"
encoderConfig.MessageKey = "message"
encoderConfig.CallerKey = "caller"
encoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.In(location).Format("2006-01-02 15:04:05.000 -0700"))
}
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
encoderConfig.EncodeDuration = zapcore.StringDurationEncoder
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
cores := make([]zapcore.Core, 0, 2)
if cfg.EnableConsole {
cores = append(cores, zapcore.NewCore(
zapcore.NewConsoleEncoder(encoderConfig),
zapcore.Lock(os.Stdout),
level,
))
}
if cfg.EnableFile {
writer := newDailyWriter(cfg.Dir, "app", location)
cores = append(cores, zapcore.NewCore(
zapcore.NewConsoleEncoder(encoderConfig),
writer,
level,
))
}
if len(cores) == 0 {
cores = append(cores, zapcore.NewCore(
zapcore.NewConsoleEncoder(encoderConfig),
zapcore.Lock(os.Stdout),
level,
))
}
return zap.New(
zapcore.NewTee(cores...),
zap.AddCaller(),
zap.AddStacktrace(zapcore.ErrorLevel),
), nil
}
func parseLevel(value string) zapcore.Level {
switch strings.ToLower(strings.TrimSpace(value)) {
case "debug":
return zapcore.DebugLevel
case "warn", "warning":
return zapcore.WarnLevel
case "error":
return zapcore.ErrorLevel
default:
return zapcore.InfoLevel
}
}