feat: v2.1.0 核心重构与功能增强

## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n

## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池

## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)

## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建

## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
  JSON 输出格式、Redis 检测、Context 超时等

## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
This commit is contained in:
ZacharyZcR
2026-01-11 20:16:23 +08:00
parent 6b13b2e84f
commit 71b92d4408
948 changed files with 92335 additions and 24630 deletions
+96
View File
@@ -0,0 +1,96 @@
package logging
/*
constants.go - 日志系统常量定义
统一管理common/logging包中的所有常量,便于查看和编辑。
*/
import (
"time"
"github.com/fatih/color"
)
// =============================================================================
// 日志级别常量 (从Types.go迁移)
// =============================================================================
// LogLevel 日志级别类型
type LogLevel string
// 定义系统支持的日志级别常量
const (
LevelAll LogLevel = "ALL" // 显示所有级别日志
LevelError LogLevel = "ERROR" // 仅显示错误日志
LevelBase LogLevel = "BASE" // 仅显示基础信息日志
LevelInfo LogLevel = "INFO" // 仅显示信息日志
LevelSuccess LogLevel = "SUCCESS" // 仅显示成功日志
LevelDebug LogLevel = "DEBUG" // 仅显示调试日志
LevelInfoSuccess LogLevel = "INFO_SUCCESS" // 仅显示信息和成功日志
LevelBaseInfoSuccess LogLevel = "BASE_INFO_SUCCESS" // 显示基础、信息和成功日志
)
// =============================================================================
// 时间显示常量 (从Formatter.go迁移)
// =============================================================================
const (
// MaxMillisecondDisplay 毫秒显示的最大时长
MaxMillisecondDisplay = time.Second
// MaxSecondDisplay 秒显示的最大时长
MaxSecondDisplay = time.Minute
// MaxMinuteDisplay 分钟显示的最大时长
MaxMinuteDisplay = time.Hour
// SlowOutputDelay 慢速输出延迟
SlowOutputDelay = 50 * time.Millisecond
// ProgressClearDelay 进度条清除延迟
ProgressClearDelay = 10 * time.Millisecond
)
// =============================================================================
// 日志前缀常量 (从Formatter.go迁移)
// =============================================================================
const (
// PrefixSuccess 成功日志前缀
PrefixSuccess = "[+]"
// PrefixInfo 信息日志前缀
PrefixInfo = "[*]"
// PrefixError 错误日志前缀
PrefixError = "[-]"
// PrefixDefault 默认日志前缀
PrefixDefault = " "
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
// DefaultLevel 默认日志级别
DefaultLevel = LevelAll
// DefaultEnableColor 默认启用彩色输出
DefaultEnableColor = true
// DefaultSlowOutput 默认不启用慢速输出
DefaultSlowOutput = false
// DefaultShowProgress 默认显示进度条
DefaultShowProgress = true
)
// =============================================================================
// 默认颜色映射
// =============================================================================
// GetDefaultLevelColors 获取默认的日志级别颜色映射
func GetDefaultLevelColors() map[LogLevel]interface{} {
return map[LogLevel]interface{}{
LevelError: color.FgRed, // 错误日志显示红色(危险/错误)
LevelBase: color.FgYellow, // 基础日志显示黄色(警告/提示)
LevelInfo: color.FgCyan, // 信息日志显示青色(中性信息)
LevelSuccess: color.FgGreen, // 成功日志显示绿色(成功/通过)
LevelDebug: color.FgWhite, // 调试日志显示白色(详细信息)
}
}
+213
View File
@@ -0,0 +1,213 @@
package logging
import (
"fmt"
"sync"
"time"
"github.com/fatih/color"
)
// LogEntry 日志条目
type LogEntry struct {
Level LogLevel `json:"level"`
Time time.Time `json:"time"`
Content string `json:"content"`
Source string `json:"source"`
Metadata map[string]interface{} `json:"metadata"`
}
// LoggerConfig 日志器配置
type LoggerConfig struct {
Level LogLevel `json:"level"`
EnableColor bool `json:"enable_color"`
SlowOutput bool `json:"slow_output"`
ShowProgress bool `json:"show_progress"`
StartTime time.Time `json:"start_time"`
LevelColors map[LogLevel]interface{} `json:"-"`
}
// DefaultLoggerConfig 默认日志器配置
func DefaultLoggerConfig() *LoggerConfig {
return &LoggerConfig{
Level: DefaultLevel,
EnableColor: DefaultEnableColor,
SlowOutput: DefaultSlowOutput,
ShowProgress: DefaultShowProgress,
StartTime: time.Now(),
LevelColors: GetDefaultLevelColors(),
}
}
// Logger 简化的日志管理器
type Logger struct {
mu sync.RWMutex
config *LoggerConfig
startTime time.Time
coordinatedOutput func(string)
initialized bool
}
// NewLogger 创建新的日志管理器
func NewLogger(config *LoggerConfig) *Logger {
if config == nil {
config = DefaultLoggerConfig()
}
return &Logger{
config: config,
startTime: config.StartTime,
initialized: true,
}
}
// Initialize 初始化日志器
func (l *Logger) Initialize() {
l.mu.Lock()
defer l.mu.Unlock()
l.initialized = true
}
// SetCoordinatedOutput 设置协调输出函数
func (l *Logger) SetCoordinatedOutput(outputFunc func(string)) {
l.mu.Lock()
defer l.mu.Unlock()
l.coordinatedOutput = outputFunc
}
// Debug 输出调试信息
func (l *Logger) Debug(msg string) {
l.log(LevelDebug, msg)
}
// Base 输出基础信息
func (l *Logger) Base(msg string) {
l.log(LevelBase, msg)
}
// Info 输出信息
func (l *Logger) Info(msg string) {
l.log(LevelInfo, msg)
}
// Success 输出成功信息
func (l *Logger) Success(msg string) {
l.log(LevelSuccess, msg)
}
// Error 输出错误信息
func (l *Logger) Error(msg string) {
l.log(LevelError, msg)
}
// log 内部日志处理方法
func (l *Logger) log(level LogLevel, content string) {
l.mu.Lock()
defer l.mu.Unlock()
if !l.shouldLog(level) {
return
}
// 格式化消息
timeStr := l.formatElapsedTime(time.Since(l.startTime))
prefix := l.getLevelPrefix(level)
logMsg := fmt.Sprintf("[%s] %s %s", timeStr, prefix, content)
// 输出消息
l.outputMessage(level, logMsg)
// 根据慢速输出设置决定是否添加延迟
if l.config.SlowOutput {
time.Sleep(SlowOutputDelay)
}
}
// shouldLog 检查是否应该记录该级别的日志
func (l *Logger) shouldLog(level LogLevel) bool {
switch l.config.Level {
case LevelAll:
return true
case LevelError:
return level == LevelError
case LevelBase:
return level == LevelBase
case LevelInfo:
return level == LevelInfo
case LevelSuccess:
return level == LevelSuccess
case LevelDebug:
return level == LevelDebug
case LevelInfoSuccess:
return level == LevelInfo || level == LevelSuccess
case LevelBaseInfoSuccess:
return level == LevelBase || level == LevelInfo || level == LevelSuccess
default:
// 向后兼容:字符串"debug"显示所有
if string(l.config.Level) == "debug" {
return true
}
return level == LevelInfo || level == LevelSuccess
}
}
// outputMessage 输出消息
func (l *Logger) outputMessage(level LogLevel, logMsg string) {
if l.coordinatedOutput != nil {
// 使用协调输出(与进度条配合)
if l.config.EnableColor {
if colorAttr, ok := l.config.LevelColors[level]; ok {
if attr, ok := colorAttr.(color.Attribute); ok {
coloredMsg := color.New(attr).Sprint(logMsg)
l.coordinatedOutput(coloredMsg)
return
}
}
}
l.coordinatedOutput(logMsg)
} else {
// 直接输出
if l.config.EnableColor {
if colorAttr, ok := l.config.LevelColors[level]; ok {
if attr, ok := colorAttr.(color.Attribute); ok {
_, _ = color.New(attr).Println(logMsg)
return
}
}
}
fmt.Println(logMsg)
}
}
// formatElapsedTime 格式化经过的时间
func (l *Logger) formatElapsedTime(elapsed time.Duration) string {
switch {
case elapsed < MaxMillisecondDisplay:
return fmt.Sprintf("%dms", elapsed.Milliseconds())
case elapsed < MaxSecondDisplay:
return fmt.Sprintf("%.1fs", elapsed.Seconds())
case elapsed < MaxMinuteDisplay:
minutes := int(elapsed.Minutes())
seconds := int(elapsed.Seconds()) % 60
return fmt.Sprintf("%dm%ds", minutes, seconds)
default:
hours := int(elapsed.Hours())
minutes := int(elapsed.Minutes()) % 60
seconds := int(elapsed.Seconds()) % 60
return fmt.Sprintf("%dh%dm%ds", hours, minutes, seconds)
}
}
// getLevelPrefix 获取日志级别前缀
func (l *Logger) getLevelPrefix(level LogLevel) string {
switch level {
case LevelSuccess:
return PrefixSuccess
case LevelInfo:
return PrefixInfo
case LevelError:
return PrefixError
default:
return PrefixDefault
}
}
+665
View File
@@ -0,0 +1,665 @@
package logging
import (
"fmt"
"strings"
"sync"
"testing"
"time"
)
/*
logger_test.go - 日志系统测试
测试目标:Logger核心功能
价值:日志是程序的眼睛,错误会导致:
- 关键信息丢失(用户看不到错误)
- 性能问题(并发日志混乱)
- 调试困难(时间格式错误)
"日志不是可选功能。日志丢失或错误,等于程序在撒谎。
测试必须验证:过滤正确、格式正确、并发安全。"
*/
// =============================================================================
// 测试辅助函数
// =============================================================================
// captureOutput 捕获日志输出(不污染控制台)
type captureOutput struct {
mu sync.Mutex
output []string
}
func (c *captureOutput) Write(msg string) {
c.mu.Lock()
defer c.mu.Unlock()
c.output = append(c.output, msg)
}
func (c *captureOutput) Get() []string {
c.mu.Lock()
defer c.mu.Unlock()
result := make([]string, len(c.output))
copy(result, c.output)
return result
}
func (c *captureOutput) Clear() {
c.mu.Lock()
defer c.mu.Unlock()
c.output = nil
}
// createTestLogger 创建测试用Logger(捕获输出)
func createTestLogger(level LogLevel, enableColor bool) (*Logger, *captureOutput) {
capture := &captureOutput{}
config := &LoggerConfig{
Level: level,
EnableColor: enableColor,
SlowOutput: false, // 测试时禁用慢速输出
ShowProgress: false,
StartTime: time.Now(),
LevelColors: GetDefaultLevelColors(),
}
logger := NewLogger(config)
logger.SetCoordinatedOutput(capture.Write)
return logger, capture
}
// =============================================================================
// Logger - 基础功能测试
// =============================================================================
// TestNewLogger_DefaultConfig 测试默认配置
func TestNewLogger_DefaultConfig(t *testing.T) {
// nil配置应该使用默认值
logger := NewLogger(nil)
if logger == nil {
t.Fatal("NewLogger(nil) 应该返回有效的logger")
}
if logger.config == nil {
t.Error("config不应为nil(应使用默认配置)")
}
if logger.config.Level != DefaultLevel {
t.Errorf("默认Level = %v, want %v", logger.config.Level, DefaultLevel)
}
if !logger.initialized {
t.Error("logger应该已初始化")
}
t.Logf("✓ 默认配置测试通过")
}
// TestNewLogger_CustomConfig 测试自定义配置
func TestNewLogger_CustomConfig(t *testing.T) {
config := &LoggerConfig{
Level: LevelError,
EnableColor: false,
SlowOutput: true,
ShowProgress: false,
StartTime: time.Now(),
LevelColors: GetDefaultLevelColors(),
}
logger := NewLogger(config)
if logger.config.Level != LevelError {
t.Errorf("Level = %v, want %v", logger.config.Level, LevelError)
}
if logger.config.EnableColor {
t.Error("EnableColor应该为false")
}
t.Logf("✓ 自定义配置测试通过")
}
// TestLogger_AllLevels 测试所有日志级别
//
// 验证:每个级别都能正确输出
func TestLogger_AllLevels(t *testing.T) {
logger, capture := createTestLogger(LevelAll, false)
tests := []struct {
name string
logFunc func(string)
message string
wantMsg string
wantPfx string
}{
{
name: "Debug级别",
logFunc: logger.Debug,
message: "debug message",
wantMsg: "debug message",
wantPfx: PrefixDefault,
},
{
name: "Base级别",
logFunc: logger.Base,
message: "base message",
wantMsg: "base message",
wantPfx: PrefixDefault,
},
{
name: "Info级别",
logFunc: logger.Info,
message: "info message",
wantMsg: "info message",
wantPfx: PrefixInfo,
},
{
name: "Success级别",
logFunc: logger.Success,
message: "success message",
wantMsg: "success message",
wantPfx: PrefixSuccess,
},
{
name: "Error级别",
logFunc: logger.Error,
message: "error message",
wantMsg: "error message",
wantPfx: PrefixError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
capture.Clear()
tt.logFunc(tt.message)
output := capture.Get()
if len(output) != 1 {
t.Fatalf("期望1条输出,实际%d条", len(output))
}
msg := output[0]
if !strings.Contains(msg, tt.wantMsg) {
t.Errorf("输出缺少消息: %s\n实际: %s", tt.wantMsg, msg)
}
if !strings.Contains(msg, tt.wantPfx) {
t.Errorf("输出缺少前缀: %s\n实际: %s", tt.wantPfx, msg)
}
// 验证时间格式
if !strings.HasPrefix(msg, "[") {
t.Errorf("输出应该以时间开头: %s", msg)
}
t.Logf("✓ %s 输出正确: %s", tt.name, msg)
})
}
}
// =============================================================================
// Logger - 级别过滤测试
// =============================================================================
// TestLogger_LevelFiltering 测试日志级别过滤
//
// 验证:不同级别配置下,只输出对应级别的日志
func TestLogger_LevelFiltering(t *testing.T) {
tests := []struct {
name string
configLevel LogLevel
logLevels map[string]func(*Logger, string)
wantOutput map[string]bool // true表示应该输出
}{
{
name: "LevelAll - 显示所有",
configLevel: LevelAll,
logLevels: map[string]func(*Logger, string){
"debug": (*Logger).Debug,
"base": (*Logger).Base,
"info": (*Logger).Info,
"success": (*Logger).Success,
"error": (*Logger).Error,
},
wantOutput: map[string]bool{
"debug": true, "base": true, "info": true,
"success": true, "error": true,
},
},
{
name: "LevelError - 仅错误",
configLevel: LevelError,
logLevels: map[string]func(*Logger, string){
"info": (*Logger).Info,
"error": (*Logger).Error,
},
wantOutput: map[string]bool{
"info": false, "error": true,
},
},
{
name: "LevelInfoSuccess - 信息和成功",
configLevel: LevelInfoSuccess,
logLevels: map[string]func(*Logger, string){
"base": (*Logger).Base,
"info": (*Logger).Info,
"success": (*Logger).Success,
"error": (*Logger).Error,
},
wantOutput: map[string]bool{
"base": false, "info": true,
"success": true, "error": false,
},
},
{
name: "LevelBaseInfoSuccess - 基础、信息和成功",
configLevel: LevelBaseInfoSuccess,
logLevels: map[string]func(*Logger, string){
"debug": (*Logger).Debug,
"base": (*Logger).Base,
"info": (*Logger).Info,
"success": (*Logger).Success,
},
wantOutput: map[string]bool{
"debug": false, "base": true,
"info": true, "success": true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger, capture := createTestLogger(tt.configLevel, false)
for levelName, logFunc := range tt.logLevels {
capture.Clear()
logFunc(logger, levelName+" message")
output := capture.Get()
shouldOutput := tt.wantOutput[levelName]
if shouldOutput && len(output) == 0 {
t.Errorf("%s: 应该输出但没有输出", levelName)
}
if !shouldOutput && len(output) > 0 {
t.Errorf("%s: 不应该输出但输出了: %v", levelName, output)
}
}
t.Logf("✓ %s 过滤测试通过", tt.name)
})
}
}
// =============================================================================
// Logger - 时间格式化测试
// =============================================================================
// TestLogger_TimeFormatting 测试时间格式化
//
// 验证:不同时长格式化正确(毫秒、秒、分钟、小时)
func TestLogger_TimeFormatting(t *testing.T) {
tests := []struct {
name string
elapsed time.Duration
wantStr string
}{
{
name: "0毫秒",
elapsed: 0,
wantStr: "0ms",
},
{
name: "500毫秒",
elapsed: 500 * time.Millisecond,
wantStr: "500ms",
},
{
name: "999毫秒",
elapsed: 999 * time.Millisecond,
wantStr: "999ms",
},
{
name: "1秒",
elapsed: 1 * time.Second,
wantStr: "1.0s",
},
{
name: "30秒",
elapsed: 30 * time.Second,
wantStr: "30.0s",
},
{
name: "59秒",
elapsed: 59 * time.Second,
wantStr: "59.0s",
},
{
name: "1分钟",
elapsed: 1 * time.Minute,
wantStr: "1m0s",
},
{
name: "5分30秒",
elapsed: 5*time.Minute + 30*time.Second,
wantStr: "5m30s",
},
{
name: "59分59秒",
elapsed: 59*time.Minute + 59*time.Second,
wantStr: "59m59s",
},
{
name: "1小时",
elapsed: 1 * time.Hour,
wantStr: "1h0m0s",
},
{
name: "2小时30分45秒",
elapsed: 2*time.Hour + 30*time.Minute + 45*time.Second,
wantStr: "2h30m45s",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := &LoggerConfig{
Level: LevelAll,
EnableColor: false,
StartTime: time.Now().Add(-tt.elapsed),
}
logger := NewLogger(config)
capture := &captureOutput{}
logger.SetCoordinatedOutput(capture.Write)
logger.Info("test")
output := capture.Get()
if len(output) != 1 {
t.Fatalf("期望1条输出,实际%d条", len(output))
}
if !strings.Contains(output[0], tt.wantStr) {
t.Errorf("时间格式错误\n期望包含: %s\n实际输出: %s",
tt.wantStr, output[0])
}
t.Logf("✓ %s → %s", tt.name, tt.wantStr)
})
}
}
// =============================================================================
// Logger - 并发安全测试
// =============================================================================
// TestLogger_ConcurrentLogging 测试并发日志输出
//
// 验证:多个goroutine同时写日志不会panic或丢失
func TestLogger_ConcurrentLogging(t *testing.T) {
logger, capture := createTestLogger(LevelAll, false)
numGoroutines := 100
logsPerGoroutine := 10
totalLogs := numGoroutines * logsPerGoroutine
var wg sync.WaitGroup
wg.Add(numGoroutines)
// 并发写入不同级别的日志
for i := 0; i < numGoroutines; i++ {
go func(id int) {
defer wg.Done()
for j := 0; j < logsPerGoroutine; j++ {
msg := fmt.Sprintf("goroutine-%d-log-%d", id, j)
// 随机使用不同级别
switch j % 5 {
case 0:
logger.Debug(msg)
case 1:
logger.Info(msg)
case 2:
logger.Success(msg)
case 3:
logger.Error(msg)
case 4:
logger.Base(msg)
}
}
}(i)
}
wg.Wait()
// 验证输出数量
output := capture.Get()
if len(output) != totalLogs {
t.Errorf("期望%d条日志,实际%d条(数据丢失或重复)",
totalLogs, len(output))
}
// 验证每条日志格式正确
for i, line := range output {
if !strings.HasPrefix(line, "[") {
t.Errorf("第%d条日志格式错误: %s", i+1, line)
break
}
}
t.Logf("✓ 并发日志测试通过(%d个goroutine,共%d条日志)",
numGoroutines, totalLogs)
}
// TestLogger_NoCoordinatedOutput 测试无协调输出的情况
//
// 验证:coordinatedOutput为nil时,使用fmt.Println(不会panic
func TestLogger_NoCoordinatedOutput(t *testing.T) {
config := &LoggerConfig{
Level: LevelAll,
EnableColor: false,
StartTime: time.Now(),
}
logger := NewLogger(config)
// 不设置 coordinatedOutput
// 应该不会panic(会使用fmt.Println
defer func() {
if r := recover(); r != nil {
t.Errorf("不应该panic: %v", r)
}
}()
logger.Info("test message")
t.Logf("✓ 无协调输出测试通过(使用fmt.Println")
}
// =============================================================================
// Logger - 高级功能测试(提升覆盖率)
// =============================================================================
// TestLogger_SingleLevels 测试单独级别配置
//
// 验证:每个单独级别(LevelDebug, LevelBase等)只输出对应级别
func TestLogger_SingleLevels(t *testing.T) {
tests := []struct {
name string
configLevel LogLevel
testLevels map[string]func(*Logger, string)
wantOutput map[string]bool
}{
{
name: "LevelDebug - 仅调试",
configLevel: LevelDebug,
testLevels: map[string]func(*Logger, string){
"debug": (*Logger).Debug,
"base": (*Logger).Base,
"info": (*Logger).Info,
"success": (*Logger).Success,
"error": (*Logger).Error,
},
wantOutput: map[string]bool{
"debug": true, "base": false, "info": false,
"success": false, "error": false,
},
},
{
name: "LevelBase - 仅基础",
configLevel: LevelBase,
testLevels: map[string]func(*Logger, string){
"debug": (*Logger).Debug,
"base": (*Logger).Base,
"info": (*Logger).Info,
},
wantOutput: map[string]bool{
"debug": false, "base": true, "info": false,
},
},
{
name: "LevelInfo - 仅信息",
configLevel: LevelInfo,
testLevels: map[string]func(*Logger, string){
"base": (*Logger).Base,
"info": (*Logger).Info,
},
wantOutput: map[string]bool{
"base": false, "info": true,
},
},
{
name: "LevelSuccess - 仅成功",
configLevel: LevelSuccess,
testLevels: map[string]func(*Logger, string){
"info": (*Logger).Info,
"success": (*Logger).Success,
},
wantOutput: map[string]bool{
"info": false, "success": true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger, capture := createTestLogger(tt.configLevel, false)
for levelName, logFunc := range tt.testLevels {
capture.Clear()
logFunc(logger, levelName+" message")
output := capture.Get()
shouldOutput := tt.wantOutput[levelName]
if shouldOutput && len(output) == 0 {
t.Errorf("%s: 应该输出但没有输出", levelName)
}
if !shouldOutput && len(output) > 0 {
t.Errorf("%s: 不应该输出但输出了: %v", levelName, output)
}
}
t.Logf("✓ %s 测试通过", tt.name)
})
}
}
// TestLogger_ColorOutput 测试颜色输出
//
// 验证:EnableColor开关正确控制颜色输出
func TestLogger_ColorOutput(t *testing.T) {
t.Run("禁用颜色", func(t *testing.T) {
logger, capture := createTestLogger(LevelAll, false)
logger.Info("test")
output := capture.Get()
if len(output) == 0 {
t.Fatal("应该有输出")
}
// 无颜色时,输出就是纯文本
if strings.Contains(output[0], "\033[") {
t.Error("禁用颜色时不应该包含ANSI转义序列")
}
t.Logf("✓ 禁用颜色测试通过")
})
t.Run("启用颜色", func(t *testing.T) {
logger, capture := createTestLogger(LevelAll, true)
logger.Info("test")
output := capture.Get()
if len(output) == 0 {
t.Fatal("应该有输出")
}
// 启用颜色时,输出可能包含颜色(取决于终端支持)
// 但不会panic
t.Logf("✓ 启用颜色测试通过: %s", output[0])
})
}
// TestLogger_BackwardCompatibility 测试向后兼容性
//
// 验证:字符串"debug"作为级别时的行为
func TestLogger_BackwardCompatibility(t *testing.T) {
config := &LoggerConfig{
Level: LogLevel("debug"), // 旧版本可能用字符串
EnableColor: false,
ShowProgress: false,
StartTime: time.Now(),
LevelColors: GetDefaultLevelColors(),
}
logger := NewLogger(config)
capture := &captureOutput{}
logger.SetCoordinatedOutput(capture.Write)
// 字符串"debug"应该显示所有级别
logger.Debug("debug msg")
logger.Info("info msg")
logger.Error("error msg")
output := capture.Get()
if len(output) != 3 {
t.Errorf("字符串'debug'应该显示所有级别,期望3条,实际%d条", len(output))
}
t.Logf("✓ 向后兼容测试通过(字符串'debug'显示所有级别)")
}
// TestLogger_Initialize 测试初始化标记
//
// 验证:Initialize方法正确设置initialized标志
func TestLogger_Initialize(t *testing.T) {
config := &LoggerConfig{
Level: LevelAll,
EnableColor: false,
ShowProgress: false,
StartTime: time.Now(),
LevelColors: GetDefaultLevelColors(),
}
// 手动创建logger,跳过NewLogger中的自动初始化
logger := &Logger{
config: config,
initialized: false, // 明确设置为false
}
// 验证初始状态
if logger.initialized {
t.Error("新创建的logger不应该已初始化")
}
// 调用Initialize
logger.Initialize()
// 验证已初始化
if !logger.initialized {
t.Error("调用Initialize后应该已初始化")
}
t.Logf("✓ Initialize测试通过")
}