package logger import ( "os" "path/filepath" "strings" "testing" "time" "go.uber.org/zap" ) // TestLogRotation 测试日志轮转功能(T027) func TestLogRotation(t *testing.T) { // 创建临时目录 tempDir := t.TempDir() appLogFile := filepath.Join(tempDir, "app-rotation.log") // 初始化日志系统,设置较小的 MaxSize 以便测试 err := InitLoggers("info", false, LogRotationConfig{ Filename: appLogFile, MaxSize: 1, // 1MB,写入足够数据后会触发轮转 MaxBackups: 3, MaxAge: 7, Compress: false, // 不压缩以便检查 }, LogRotationConfig{ Filename: filepath.Join(tempDir, "access-rotation.log"), MaxSize: 1, MaxBackups: 3, MaxAge: 7, Compress: false, }, ) if err != nil { t.Fatalf("InitLoggers failed: %v", err) } logger := GetAppLogger() // 写入大量日志数据以触发轮转(每条约100字节,写入15000条约1.5MB) largeMessage := strings.Repeat("a", 100) for i := 0; i < 15000; i++ { logger.Info(largeMessage, zap.Int("iteration", i), zap.String("data", largeMessage), ) } // 刷新缓冲区 if err := Sync(); err != nil { t.Fatalf("Sync failed: %v", err) } // 等待一小段时间确保文件写入完成 time.Sleep(100 * time.Millisecond) // 验证主日志文件存在 if _, err := os.Stat(appLogFile); os.IsNotExist(err) { t.Error("Main log file should exist") } // 检查是否有备份文件(轮转后的文件) files, err := filepath.Glob(filepath.Join(tempDir, "app-rotation-*.log")) if err != nil { t.Fatalf("Failed to glob backup files: %v", err) } // 由于写入了超过1MB的数据,应该触发至少一次轮转 if len(files) == 0 { // 可能系统写入速度或lumberjack行为导致未立即轮转,检查主文件大小 info, err := os.Stat(appLogFile) if err != nil { t.Fatalf("Failed to stat main log file: %v", err) } if info.Size() == 0 { t.Error("Log file should have content") } // 不强制要求必须轮转,因为取决于具体实现 t.Logf("No rotation occurred, but main log file size: %d bytes", info.Size()) } else { t.Logf("Found %d rotated backup file(s)", len(files)) } } // TestMaxBackups 测试最大备份数限制(T027) func TestMaxBackups(t *testing.T) { // 创建临时目录 tempDir := t.TempDir() appLogFile := filepath.Join(tempDir, "app-backups.log") // 初始化日志系统,设置 MaxBackups=2 err := InitLoggers("info", false, LogRotationConfig{ Filename: appLogFile, MaxSize: 1, // 1MB MaxBackups: 2, // 最多保留2个备份 MaxAge: 7, Compress: false, }, LogRotationConfig{ Filename: filepath.Join(tempDir, "access-backups.log"), MaxSize: 1, MaxBackups: 2, MaxAge: 7, Compress: false, }, ) if err != nil { t.Fatalf("InitLoggers failed: %v", err) } logger := GetAppLogger() // 写入足够的数据触发多次轮转(每次1.5MB,共4.5MB应该触发3次轮转) largeMessage := strings.Repeat("b", 100) for round := 0; round < 3; round++ { for i := 0; i < 15000; i++ { logger.Info(largeMessage, zap.Int("round", round), zap.Int("iteration", i), ) } Sync() time.Sleep(100 * time.Millisecond) } // 等待轮转完成 time.Sleep(200 * time.Millisecond) // 检查备份文件数量 files, err := filepath.Glob(filepath.Join(tempDir, "app-backups-*.log")) if err != nil { t.Fatalf("Failed to glob backup files: %v", err) } // 由于 MaxBackups=2,即使触发了多次轮转,也只应保留最多2个备份文件 // (实际行为取决于 lumberjack 的实现细节,可能小于等于2) if len(files) > 2 { t.Errorf("Expected at most 2 backup files due to MaxBackups=2, got %d", len(files)) } t.Logf("Found %d backup file(s) with MaxBackups=2", len(files)) } // TestCompressionConfig 测试压缩配置(T027) func TestCompressionConfig(t *testing.T) { // 创建临时目录 tempDir := t.TempDir() tests := []struct { name string compress bool }{ { name: "compression enabled", compress: true, }, { name: "compression disabled", compress: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { logFile := filepath.Join(tempDir, "app-"+tt.name+".log") err := InitLoggers("info", false, LogRotationConfig{ Filename: logFile, MaxSize: 1, MaxBackups: 3, MaxAge: 7, Compress: tt.compress, }, LogRotationConfig{ Filename: filepath.Join(tempDir, "access-"+tt.name+".log"), MaxSize: 1, MaxBackups: 3, MaxAge: 7, Compress: tt.compress, }, ) if err != nil { t.Fatalf("InitLoggers failed: %v", err) } logger := GetAppLogger() // 写入一些日志 for i := 0; i < 1000; i++ { logger.Info("test compression", zap.Int("id", i), zap.String("data", strings.Repeat("c", 50)), ) } Sync() time.Sleep(100 * time.Millisecond) // 验证日志文件存在 if _, err := os.Stat(logFile); os.IsNotExist(err) { t.Error("Log file should exist") } }) } } // TestMaxAge 测试日志文件保留时间(T027) func TestMaxAge(t *testing.T) { // 创建临时目录 tempDir := t.TempDir() // 初始化日志系统,设置 MaxAge=1 天 err := InitLoggers("info", false, LogRotationConfig{ Filename: filepath.Join(tempDir, "app-maxage.log"), MaxSize: 10, MaxBackups: 3, MaxAge: 1, // 1天 Compress: false, }, LogRotationConfig{ Filename: filepath.Join(tempDir, "access-maxage.log"), MaxSize: 10, MaxBackups: 3, MaxAge: 1, Compress: false, }, ) if err != nil { t.Fatalf("InitLoggers failed: %v", err) } logger := GetAppLogger() // 写入日志 logger.Info("test max age", zap.String("config", "maxage=1")) Sync() // 验证配置已应用(无法在单元测试中验证实际的清理行为,因为需要等待1天) // 这里只验证初始化没有错误 if logger == nil { t.Error("Logger should be initialized with MaxAge config") } } // TestNewLumberjackLogger 测试 Lumberjack logger 创建(T027) func TestNewLumberjackLogger(t *testing.T) { // 创建临时目录 tempDir := t.TempDir() tests := []struct { name string config LogRotationConfig }{ { name: "standard config", config: LogRotationConfig{ Filename: filepath.Join(tempDir, "test1.log"), MaxSize: 10, MaxBackups: 3, MaxAge: 7, Compress: true, }, }, { name: "minimal config", config: LogRotationConfig{ Filename: filepath.Join(tempDir, "test2.log"), MaxSize: 1, MaxBackups: 1, MaxAge: 1, Compress: false, }, }, { name: "large config", config: LogRotationConfig{ Filename: filepath.Join(tempDir, "test3.log"), MaxSize: 100, MaxBackups: 10, MaxAge: 30, Compress: true, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { logger := newLumberjackLogger(tt.config) if logger == nil { t.Error("newLumberjackLogger should not return nil") } // 验证配置已正确设置 if logger.Filename != tt.config.Filename { t.Errorf("Filename = %v, want %v", logger.Filename, tt.config.Filename) } if logger.MaxSize != tt.config.MaxSize { t.Errorf("MaxSize = %v, want %v", logger.MaxSize, tt.config.MaxSize) } if logger.MaxBackups != tt.config.MaxBackups { t.Errorf("MaxBackups = %v, want %v", logger.MaxBackups, tt.config.MaxBackups) } if logger.MaxAge != tt.config.MaxAge { t.Errorf("MaxAge = %v, want %v", logger.MaxAge, tt.config.MaxAge) } if logger.Compress != tt.config.Compress { t.Errorf("Compress = %v, want %v", logger.Compress, tt.config.Compress) } if !logger.LocalTime { t.Error("LocalTime should be true") } }) } } // TestConcurrentLogging 测试并发日志写入(T027) func TestConcurrentLogging(t *testing.T) { // 创建临时目录 tempDir := t.TempDir() // 初始化日志系统 err := InitLoggers("info", false, LogRotationConfig{ Filename: filepath.Join(tempDir, "app-concurrent.log"), MaxSize: 10, MaxBackups: 3, MaxAge: 7, Compress: false, }, LogRotationConfig{ Filename: filepath.Join(tempDir, "access-concurrent.log"), MaxSize: 10, MaxBackups: 3, MaxAge: 7, Compress: false, }, ) if err != nil { t.Fatalf("InitLoggers failed: %v", err) } logger := GetAppLogger() // 启动多个 goroutine 并发写入日志 done := make(chan bool) goroutines := 10 messagesPerGoroutine := 100 for i := 0; i < goroutines; i++ { go func(id int) { for j := 0; j < messagesPerGoroutine; j++ { logger.Info("concurrent log message", zap.Int("goroutine", id), zap.Int("message", j), ) } done <- true }(i) } // 等待所有 goroutine 完成 for i := 0; i < goroutines; i++ { <-done } // 刷新缓冲区 if err := Sync(); err != nil { t.Fatalf("Sync failed: %v", err) } // 验证日志文件存在且有内容 logFile := filepath.Join(tempDir, "app-concurrent.log") info, err := os.Stat(logFile) if err != nil { t.Fatalf("Failed to stat log file: %v", err) } if info.Size() == 0 { t.Error("Log file should have content after concurrent writes") } t.Logf("Concurrent logging test completed, log file size: %d bytes", info.Size()) }