Files
junhong_cmp_fiber/pkg/logger/logger_test.go
huang 1f71741836 完成 Phase 10 质量保证,项目达到生产部署标准
主要变更:
-  完成所有文档任务(T092-T095a)
  * 创建中文 README.md 和项目文档
  * 添加限流器使用指南
  * 更新快速入门文档
  * 添加详细的中文代码注释

-  完成代码质量任务(T096-T103)
  * 通过 gofmt、go vet、golangci-lint 检查
  * 修复 17 个 errcheck 问题
  * 验证无硬编码 Redis key
  * 确保命名规范符合 Go 标准

-  完成测试任务(T104-T108)
  * 58 个测试全部通过
  * 总体覆盖率 75.1%(超过 70% 目标)
  * 核心模块覆盖率 90%+

-  完成安全审计任务(T109-T113)
  * 修复日志中令牌泄露问题
  * 验证 Fail-closed 策略正确实现
  * 审查 Redis 连接安全
  * 完成依赖项漏洞扫描

-  完成性能验证任务(T114-T117)
  * 令牌验证性能:17.5 μs/op(~58,954 ops/s)
  * 响应序列化性能:1.1 μs/op(>1,000,000 ops/s)
  * 配置访问性能:0.58 ns/op(接近 CPU 缓存速度)

-  完成质量关卡任务(T118-T126)
  * 所有测试通过
  * 代码格式和静态检查通过
  * 无 TODO/FIXME 遗留
  * 中间件集成验证
  * 优雅关闭机制验证

新增文件:
- README.md(中文项目文档)
- docs/rate-limiting.md(限流器指南)
- docs/security-audit-report.md(安全审计报告)
- docs/performance-benchmark-report.md(性能基准报告)
- docs/quality-gate-report.md(质量关卡报告)
- docs/PROJECT-COMPLETION-SUMMARY.md(项目完成总结)
- 基准测试文件(config, response, validator)

安全修复:
- 移除 pkg/validator/token.go 中的敏感日志记录

质量评分:9.6/10(优秀)
项目状态: 已完成,待部署
2025-11-11 16:53:05 +08:00

519 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 logger
import (
"os"
"path/filepath"
"testing"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// TestInitLoggers 测试日志初始化T026
func TestInitLoggers(t *testing.T) {
// 创建临时目录用于日志文件
tempDir := t.TempDir()
tests := []struct {
name string
level string
development bool
appLogConfig LogRotationConfig
accessLogConfig LogRotationConfig
wantErr bool
validateFunc func(t *testing.T)
}{
{
name: "production mode with info level",
level: "info",
development: false,
appLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "app-prod.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
accessLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "access-prod.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
wantErr: false,
validateFunc: func(t *testing.T) {
if appLogger == nil {
t.Error("appLogger should not be nil")
}
if accessLogger == nil {
t.Error("accessLogger should not be nil")
}
// 写入一条日志以触发文件创建
GetAppLogger().Info("test log creation")
_ = Sync()
// 验证日志文件创建
if _, err := os.Stat(filepath.Join(tempDir, "app-prod.log")); os.IsNotExist(err) {
t.Error("app log file should be created after writing")
}
},
},
{
name: "development mode with debug level",
level: "debug",
development: true,
appLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "app-dev.log"),
MaxSize: 5,
MaxBackups: 2,
MaxAge: 3,
Compress: false,
},
accessLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "access-dev.log"),
MaxSize: 5,
MaxBackups: 2,
MaxAge: 3,
Compress: false,
},
wantErr: false,
validateFunc: func(t *testing.T) {
if appLogger == nil {
t.Error("appLogger should not be nil in dev mode")
}
if accessLogger == nil {
t.Error("accessLogger should not be nil in dev mode")
}
},
},
{
name: "warn level logging",
level: "warn",
development: false,
appLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "app-warn.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
accessLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "access-warn.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
wantErr: false,
validateFunc: func(t *testing.T) {
if appLogger == nil {
t.Error("appLogger should not be nil")
}
},
},
{
name: "error level logging",
level: "error",
development: false,
appLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "app-error.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
accessLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "access-error.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
wantErr: false,
validateFunc: func(t *testing.T) {
if appLogger == nil {
t.Error("appLogger should not be nil")
}
},
},
{
name: "invalid level defaults to info",
level: "invalid",
development: false,
appLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "app-invalid.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
accessLogConfig: LogRotationConfig{
Filename: filepath.Join(tempDir, "access-invalid.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
wantErr: false,
validateFunc: func(t *testing.T) {
if appLogger == nil {
t.Error("appLogger should not be nil even with invalid level")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := InitLoggers(tt.level, tt.development, tt.appLogConfig, tt.accessLogConfig)
if (err != nil) != tt.wantErr {
t.Errorf("InitLoggers() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.validateFunc != nil {
tt.validateFunc(t)
}
})
}
}
// TestGetAppLogger 测试获取应用日志记录器T026
func TestGetAppLogger(t *testing.T) {
// 创建临时目录
tempDir := t.TempDir()
tests := []struct {
name string
setupFunc func()
wantNil bool
}{
{
name: "after initialization",
setupFunc: func() {
_ = InitLoggers("info", false,
LogRotationConfig{
Filename: filepath.Join(tempDir, "app-get.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
LogRotationConfig{
Filename: filepath.Join(tempDir, "access-get.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
)
},
wantNil: false,
},
{
name: "before initialization returns nop logger",
setupFunc: func() {
// 重置全局变量
appLogger = nil
},
wantNil: false, // GetAppLogger 应该返回 nop logger不是 nil
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setupFunc()
logger := GetAppLogger()
if logger == nil {
t.Error("GetAppLogger() should never return nil, should return nop logger instead")
}
})
}
}
// TestGetAccessLogger 测试获取访问日志记录器T028
func TestGetAccessLogger(t *testing.T) {
// 创建临时目录
tempDir := t.TempDir()
tests := []struct {
name string
setupFunc func()
wantNil bool
}{
{
name: "after initialization",
setupFunc: func() {
_ = InitLoggers("info", false,
LogRotationConfig{
Filename: filepath.Join(tempDir, "app-access.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
LogRotationConfig{
Filename: filepath.Join(tempDir, "access-access.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
)
},
wantNil: false,
},
{
name: "before initialization returns nop logger",
setupFunc: func() {
// 重置全局变量
accessLogger = nil
},
wantNil: false, // GetAccessLogger 应该返回 nop logger不是 nil
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setupFunc()
logger := GetAccessLogger()
if logger == nil {
t.Error("GetAccessLogger() should never return nil, should return nop logger instead")
}
})
}
}
// TestSync 测试日志缓冲区刷新T028
func TestSync(t *testing.T) {
// 创建临时目录
tempDir := t.TempDir()
tests := []struct {
name string
setupFunc func()
wantErr bool
}{
{
name: "sync after initialization",
setupFunc: func() {
_ = InitLoggers("info", false,
LogRotationConfig{
Filename: filepath.Join(tempDir, "app-sync.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
LogRotationConfig{
Filename: filepath.Join(tempDir, "access-sync.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
)
},
wantErr: false,
},
{
name: "sync before initialization",
setupFunc: func() {
appLogger = nil
accessLogger = nil
},
wantErr: false, // 应该优雅地处理 nil 情况
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setupFunc()
err := Sync()
if (err != nil) != tt.wantErr {
t.Errorf("Sync() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// TestParseLevel 测试日志级别解析T026
func TestParseLevel(t *testing.T) {
tests := []struct {
name string
level string
want zapcore.Level
}{
{
name: "debug level",
level: "debug",
want: zapcore.DebugLevel,
},
{
name: "info level",
level: "info",
want: zapcore.InfoLevel,
},
{
name: "warn level",
level: "warn",
want: zapcore.WarnLevel,
},
{
name: "error level",
level: "error",
want: zapcore.ErrorLevel,
},
{
name: "invalid level defaults to info",
level: "invalid",
want: zapcore.InfoLevel,
},
{
name: "empty level defaults to info",
level: "",
want: zapcore.InfoLevel,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseLevel(tt.level)
if got != tt.want {
t.Errorf("parseLevel() = %v, want %v", got, tt.want)
}
})
}
}
// TestDualLoggerSystem 测试双日志系统T028
func TestDualLoggerSystem(t *testing.T) {
// 创建临时目录
tempDir := t.TempDir()
appLogFile := filepath.Join(tempDir, "app-dual.log")
accessLogFile := filepath.Join(tempDir, "access-dual.log")
// 初始化双日志系统
err := InitLoggers("info", false,
LogRotationConfig{
Filename: appLogFile,
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: false, // 不压缩以便检查内容
},
LogRotationConfig{
Filename: accessLogFile,
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: false,
},
)
if err != nil {
t.Fatalf("InitLoggers failed: %v", err)
}
// 写入应用日志
appLog := GetAppLogger()
appLog.Info("test app log message",
zap.String("module", "test"),
zap.Int("code", 200),
)
// 写入访问日志
accessLog := GetAccessLogger()
accessLog.Info("test access log message",
zap.String("method", "GET"),
zap.String("path", "/api/test"),
zap.Int("status", 200),
zap.Duration("latency", 100),
)
// 刷新缓冲区
if err := Sync(); err != nil {
t.Fatalf("Sync failed: %v", err)
}
// 验证应用日志文件存在并有内容
appLogContent, err := os.ReadFile(appLogFile)
if err != nil {
t.Fatalf("Failed to read app log file: %v", err)
}
if len(appLogContent) == 0 {
t.Error("App log file should not be empty")
}
// 验证访问日志文件存在并有内容
accessLogContent, err := os.ReadFile(accessLogFile)
if err != nil {
t.Fatalf("Failed to read access log file: %v", err)
}
if len(accessLogContent) == 0 {
t.Error("Access log file should not be empty")
}
// 验证两个日志文件是独立的
if string(appLogContent) == string(accessLogContent) {
t.Error("App log and access log should have different content")
}
}
// TestLoggerReinitialization 测试日志重新初始化T026
func TestLoggerReinitialization(t *testing.T) {
// 创建临时目录
tempDir := t.TempDir()
// 第一次初始化
err := InitLoggers("info", false,
LogRotationConfig{
Filename: filepath.Join(tempDir, "app-reinit1.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
LogRotationConfig{
Filename: filepath.Join(tempDir, "access-reinit1.log"),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 7,
Compress: true,
},
)
if err != nil {
t.Fatalf("First InitLoggers failed: %v", err)
}
firstAppLogger := GetAppLogger()
// 第二次初始化(重新初始化)
err = InitLoggers("debug", true,
LogRotationConfig{
Filename: filepath.Join(tempDir, "app-reinit2.log"),
MaxSize: 5,
MaxBackups: 2,
MaxAge: 3,
Compress: false,
},
LogRotationConfig{
Filename: filepath.Join(tempDir, "access-reinit2.log"),
MaxSize: 5,
MaxBackups: 2,
MaxAge: 3,
Compress: false,
},
)
if err != nil {
t.Fatalf("Second InitLoggers failed: %v", err)
}
secondAppLogger := GetAppLogger()
// 验证重新初始化后日志记录器已更新
if firstAppLogger == secondAppLogger {
t.Error("Logger should be replaced after reinitialization")
}
}