Files
2025-09-13 19:48:13 +08:00

167 lines
5.5 KiB
Go
Raw Permalink 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 logs_test
import (
"bytes"
"context"
"encoding/json"
"errors"
"strings"
"testing"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// captureOutput 是一个辅助函数,用于捕获 logger 的输出到内存缓冲区
func captureOutput(cfg config.LogConfig) (*logs.Logger, *bytes.Buffer) {
var buf bytes.Buffer
encoder := logs.GetEncoder(cfg.Format)
writer := zapcore.AddSync(&buf)
level := zap.NewAtomicLevel()
_ = level.UnmarshalText([]byte(cfg.Level))
core := zapcore.NewCore(encoder, writer, level)
// 匹配 logs.go 中 NewLogger 的行为,添加调用者信息
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
logger := &logs.Logger{SugaredLogger: zapLogger.Sugar()}
return logger, &buf
}
func TestNewLogger(t *testing.T) {
t.Run("日志级别应生效", func(t *testing.T) {
// 1. 创建一个级别为 WARN 的 logger
logger, buf := captureOutput(config.LogConfig{Level: "warn", Format: "console"})
// 2. 调用不同级别的日志方法
logger.Info("这条 info 日志不应被打印")
logger.Warn("这条 warn 日志应该被打印")
// 3. 断言输出
output := buf.String()
assert.NotContains(t, output, "这条 info 日志不应被打印")
assert.Contains(t, output, "这条 warn 日志应该被打印")
})
t.Run("JSON 格式应生效", func(t *testing.T) {
// 1. 创建一个格式为 JSON 的 logger
logger, buf := captureOutput(config.LogConfig{Level: "info", Format: "json"})
// 2. 打印一条日志
logger.Info("测试json输出")
// 3. 断言输出
output := buf.String()
// 验证它是否是合法的 JSON并且包含预期的键值对
var logEntry map[string]interface{}
// 注意:由于日志库可能会在行尾添加换行符,我们先 trim space
err := json.Unmarshal([]byte(strings.TrimSpace(output)), &logEntry)
assert.NoError(t, err, "日志输出应为合法的JSON")
assert.Equal(t, "INFO", logEntry["level"])
assert.Equal(t, "测试json输出", logEntry["msg"])
})
t.Run("文件日志构造函数不应 panic", func(t *testing.T) {
// 这个测试保持原样,只验证构造函数在启用文件时不会崩溃
// 注意:我们不在单元测试中实际写入文件
cfgFile := config.LogConfig{
Level: "info",
EnableFile: true,
FilePath: "test.log", // 在测试环境中,这个文件不会被真正创建
}
assert.NotPanics(t, func() { logs.NewLogger(cfgFile) })
})
}
func TestLogger_Write_ForGin(t *testing.T) {
logger, buf := captureOutput(config.LogConfig{Level: "info"})
ginLog := "[GIN-debug] Listening and serving HTTP on :8080\n"
_, err := logger.Write([]byte(ginLog))
assert.NoError(t, err)
output := buf.String()
// logger.Write 会将 gin 的日志转为 info 级别
assert.Contains(t, output, "INFO")
assert.Contains(t, output, strings.TrimSpace(ginLog))
}
func TestGormLogger(t *testing.T) {
logger, buf := captureOutput(config.LogConfig{Level: "debug"}) // 设置为 debug 以捕获所有级别
gormLogger := logs.NewGormLogger(logger)
// 模拟 GORM 的 Trace 调用参数
ctx := context.Background()
sql := "SELECT * FROM users WHERE id = 1"
rows := int64(1)
fc := func() (string, int64) {
return sql, rows
}
t.Run("慢查询应记录为警告", func(t *testing.T) {
buf.Reset()
// 模拟一个耗时超过 200ms 的查询
begin := time.Now().Add(-300 * time.Millisecond)
gormLogger.Trace(ctx, begin, fc, nil)
output := buf.String()
assert.Contains(t, output, "WARN", "应包含 WARN 级别")
assert.Contains(t, output, "[GORM] slow query", "应包含慢查询信息")
assert.Contains(t, output, "SELECT * FROM users WHERE id = 1", "应包含 SQL 语句")
})
t.Run("普通错误应记录为Error", func(t *testing.T) {
buf.Reset()
queryError := errors.New("syntax error")
gormLogger.Trace(ctx, time.Now(), fc, queryError)
output := buf.String()
assert.Contains(t, output, "ERROR")
assert.Contains(t, output, "[GORM] error: syntax error")
})
t.Run("当SkipErrRecordNotFound为true时应跳过RecordNotFound错误", func(t *testing.T) {
buf.Reset()
// 确保默认设置是 true
gormLogger.SkipErrRecordNotFound = true
// 错误必须包含 "record not found" 字符串以匹配 logs.go 中的判断逻辑
queryError := errors.New("record not found")
gormLogger.Trace(ctx, time.Now(), fc, queryError)
assert.Empty(t, buf.String(), "开启 SkipErrRecordNotFound 后record not found 错误不应产生任何日志")
})
t.Run("当SkipErrRecordNotFound为false时应记录RecordNotFound错误", func(t *testing.T) {
buf.Reset()
// 手动将 SkipErrRecordNotFound 设置为 false
gormLogger.SkipErrRecordNotFound = false
queryError := errors.New("record not found")
gormLogger.Trace(ctx, time.Now(), fc, queryError)
// 恢复设置,避免影响其他测试
gormLogger.SkipErrRecordNotFound = true
output := buf.String()
assert.NotEmpty(t, output, "关闭 SkipErrRecordNotFound 后record not found 错误应该产生日志")
assert.Contains(t, output, "ERROR")
assert.Contains(t, output, "[GORM] error: record not found")
})
t.Run("正常查询应记录为Debug", func(t *testing.T) {
buf.Reset()
// 模拟一个快速查询
gormLogger.Trace(ctx, time.Now(), fc, nil)
output := buf.String()
assert.Contains(t, output, "DEBUG") // 正常查询是 Debug 级别
assert.Contains(t, output, "[GORM] trace")
})
}