mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
refactor: 重构日志和输出系统,优化日志级别和时间显示
主要更改: - 重构Log.go和Output.go为模块化架构 - 创建独立的logging和output模块 - 新增LevelBaseInfoSuccess默认日志级别(显示BASE、INFO、SUCCESS) - 添加运行时间显示到每条日志前面 - 保持完全向后兼容的API接口 - 支持多种输出格式(TXT、JSON、CSV) - 优化日志格式化和颜色显示 技术改进: - 模块化设计便于扩展和维护 - 智能时间格式化(毫秒→秒→分钟→小时) - 支持缓冲和批量输出 - 线程安全的并发处理
This commit is contained in:
parent
c04bfcfd07
commit
e095f376f9
@ -142,7 +142,7 @@ func Flag(Info *HostInfo) {
|
|||||||
flag.BoolVar(&DisableSave, "no", false, GetText("flag_disable_save"))
|
flag.BoolVar(&DisableSave, "no", false, GetText("flag_disable_save"))
|
||||||
flag.BoolVar(&Silent, "silent", false, GetText("flag_silent_mode"))
|
flag.BoolVar(&Silent, "silent", false, GetText("flag_silent_mode"))
|
||||||
flag.BoolVar(&NoColor, "nocolor", false, GetText("flag_no_color"))
|
flag.BoolVar(&NoColor, "nocolor", false, GetText("flag_no_color"))
|
||||||
flag.StringVar(&LogLevel, "log", LogLevelSuccess, GetText("flag_log_level"))
|
flag.StringVar(&LogLevel, "log", LogLevelBaseInfoSuccess, GetText("flag_log_level"))
|
||||||
flag.BoolVar(&ShowProgress, "pg", false, GetText("flag_show_progress"))
|
flag.BoolVar(&ShowProgress, "pg", false, GetText("flag_show_progress"))
|
||||||
flag.BoolVar(&ShowScanPlan, "sp", false, GetText("flag_show_scan_plan"))
|
flag.BoolVar(&ShowScanPlan, "sp", false, GetText("flag_show_scan_plan"))
|
||||||
flag.BoolVar(&SlowLogOutput, "slow", false, GetText("flag_slow_log_output"))
|
flag.BoolVar(&SlowLogOutput, "slow", false, GetText("flag_slow_log_output"))
|
||||||
|
336
Common/Log.go
336
Common/Log.go
@ -1,261 +1,183 @@
|
|||||||
package Common
|
package Common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/shadow1ng/fscan/Common/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 全局变量定义
|
// 全局变量定义(保持向后兼容)
|
||||||
var (
|
var (
|
||||||
// 扫描状态管理器,记录最近一次成功和错误的时间
|
// 扫描状态管理器,记录最近一次成功和错误的时间
|
||||||
status = &ScanStatus{lastSuccess: time.Now(), lastError: time.Now()}
|
status = logging.NewScanStatus()
|
||||||
|
|
||||||
// Num 表示待处理的总任务数量
|
// Num 表示待处理的总任务数量
|
||||||
Num int64
|
Num int64
|
||||||
// End 表示已经完成的任务数量
|
// End 表示已经完成的任务数量
|
||||||
End int64
|
End int64
|
||||||
|
|
||||||
|
// StartTime 开始时间(保持原有行为)
|
||||||
|
StartTime = time.Now()
|
||||||
)
|
)
|
||||||
|
|
||||||
// ScanStatus 用于记录和管理扫描状态的结构体
|
// LogEntry 定义单条日志的结构(向后兼容)
|
||||||
type ScanStatus struct {
|
type LogEntry = logging.LogEntry
|
||||||
mu sync.RWMutex // 读写互斥锁,用于保护并发访问
|
|
||||||
total int64 // 总任务数
|
|
||||||
completed int64 // 已完成任务数
|
|
||||||
lastSuccess time.Time // 最近一次成功的时间
|
|
||||||
lastError time.Time // 最近一次错误的时间
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogEntry 定义单条日志的结构
|
// ScanStatus 用于记录和管理扫描状态的结构体(向后兼容)
|
||||||
type LogEntry struct {
|
type ScanStatus = logging.ScanStatus
|
||||||
Level string // 日志级别: ERROR/INFO/SUCCESS/DEBUG
|
|
||||||
Time time.Time // 日志时间
|
|
||||||
Content string // 日志内容
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义系统支持的日志级别常量
|
// 定义系统支持的日志级别常量(向后兼容)
|
||||||
const (
|
const (
|
||||||
LogLevelAll = "ALL" // 显示所有级别日志
|
LogLevelAll = string(logging.LevelAll)
|
||||||
LogLevelError = "ERROR" // 仅显示错误日志
|
LogLevelError = string(logging.LevelError)
|
||||||
LogLevelBase = "BASE" // 仅显示信息日志
|
LogLevelBase = string(logging.LevelBase)
|
||||||
LogLevelInfo = "INFO" // 仅显示信息日志
|
LogLevelInfo = string(logging.LevelInfo)
|
||||||
LogLevelSuccess = "SUCCESS" // 仅显示成功日志
|
LogLevelSuccess = string(logging.LevelSuccess)
|
||||||
LogLevelDebug = "DEBUG" // 仅显示调试日志
|
LogLevelDebug = string(logging.LevelDebug)
|
||||||
|
LogLevelInfoSuccess = string(logging.LevelInfoSuccess)
|
||||||
|
LogLevelBaseInfoSuccess = string(logging.LevelBaseInfoSuccess)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 日志级别对应的显示颜色映射
|
// 全局日志管理器
|
||||||
var logColors = map[string]color.Attribute{
|
var (
|
||||||
LogLevelError: color.FgBlue, // 错误日志显示蓝色
|
globalLogger *logging.Logger
|
||||||
LogLevelBase: color.FgYellow, // 信息日志显示黄色
|
loggerOnce sync.Once
|
||||||
LogLevelInfo: color.FgGreen, // 信息日志显示绿色
|
)
|
||||||
LogLevelSuccess: color.FgRed, // 成功日志显示红色
|
|
||||||
LogLevelDebug: color.FgWhite, // 调试日志显示白色
|
// getGlobalLogger 获取全局日志管理器
|
||||||
|
func getGlobalLogger() *logging.Logger {
|
||||||
|
loggerOnce.Do(func() {
|
||||||
|
config := &logging.LoggerConfig{
|
||||||
|
Level: logging.LevelBaseInfoSuccess,
|
||||||
|
EnableColor: !NoColor,
|
||||||
|
SlowOutput: SlowLogOutput,
|
||||||
|
ShowProgress: true,
|
||||||
|
StartTime: StartTime,
|
||||||
|
LevelColors: map[logging.LogLevel]color.Attribute{
|
||||||
|
logging.LevelError: color.FgBlue, // 错误日志显示蓝色
|
||||||
|
logging.LevelBase: color.FgYellow, // 基础日志显示黄色
|
||||||
|
logging.LevelInfo: color.FgGreen, // 信息日志显示绿色
|
||||||
|
logging.LevelSuccess: color.FgRed, // 成功日志显示红色
|
||||||
|
logging.LevelDebug: color.FgWhite, // 调试日志显示白色
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
globalLogger = logging.NewLogger(config)
|
||||||
|
|
||||||
|
// 设置进度条(如果存在)
|
||||||
|
if ProgressBar != nil {
|
||||||
|
globalLogger.SetProgressBar(ProgressBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置输出互斥锁
|
||||||
|
globalLogger.SetOutputMutex(&OutputMutex)
|
||||||
|
|
||||||
|
// 使用全局扫描状态
|
||||||
|
status = globalLogger.GetScanStatus()
|
||||||
|
})
|
||||||
|
return globalLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitLogger 初始化日志系统
|
// InitLogger 初始化日志系统(保持原接口)
|
||||||
func InitLogger() {
|
func InitLogger() {
|
||||||
// 禁用标准日志输出
|
// 禁用标准日志输出
|
||||||
log.SetOutput(io.Discard)
|
log.SetOutput(io.Discard)
|
||||||
|
|
||||||
|
// 初始化全局日志管理器
|
||||||
|
getGlobalLogger().Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
var StartTime = time.Now()
|
// SetLoggerConfig 设置日志配置
|
||||||
|
func SetLoggerConfig(enableColor, slowOutput bool, progressBar ProgressDisplay) {
|
||||||
// formatLogMessage 格式化日志消息为标准格式
|
config := &logging.LoggerConfig{
|
||||||
// 返回格式:[时间] [级别] 内容
|
Level: logging.LevelBaseInfoSuccess,
|
||||||
func formatLogMessage(entry *LogEntry) string {
|
EnableColor: enableColor,
|
||||||
elapsed := time.Since(StartTime)
|
SlowOutput: slowOutput,
|
||||||
var timeStr string
|
ShowProgress: true,
|
||||||
|
StartTime: StartTime,
|
||||||
// 根据时间长短选择合适的单位
|
LevelColors: map[logging.LogLevel]color.Attribute{
|
||||||
switch {
|
logging.LevelError: color.FgBlue,
|
||||||
case elapsed < time.Second:
|
logging.LevelBase: color.FgYellow,
|
||||||
// 毫秒显示,不需要小数
|
logging.LevelInfo: color.FgGreen,
|
||||||
timeStr = fmt.Sprintf("%dms", elapsed.Milliseconds())
|
logging.LevelSuccess: color.FgRed,
|
||||||
case elapsed < time.Minute:
|
logging.LevelDebug: color.FgWhite,
|
||||||
// 秒显示,保留一位小数
|
},
|
||||||
timeStr = fmt.Sprintf("%.1fs", elapsed.Seconds())
|
|
||||||
case elapsed < time.Hour:
|
|
||||||
// 分钟和秒显示
|
|
||||||
minutes := int(elapsed.Minutes())
|
|
||||||
seconds := int(elapsed.Seconds()) % 60
|
|
||||||
timeStr = fmt.Sprintf("%dm%ds", minutes, seconds)
|
|
||||||
default:
|
|
||||||
// 小时、分钟和秒显示
|
|
||||||
hours := int(elapsed.Hours())
|
|
||||||
minutes := int(elapsed.Minutes()) % 60
|
|
||||||
seconds := int(elapsed.Seconds()) % 60
|
|
||||||
timeStr = fmt.Sprintf("%dh%dm%ds", hours, minutes, seconds)
|
|
||||||
}
|
|
||||||
str := " "
|
|
||||||
switch entry.Level {
|
|
||||||
case LogLevelSuccess:
|
|
||||||
str = "[+]"
|
|
||||||
case LogLevelInfo:
|
|
||||||
str = "[*]"
|
|
||||||
case LogLevelError:
|
|
||||||
str = "[-]"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("[%s] %s %s", timeStr, str, entry.Content)
|
newLogger := logging.NewLogger(config)
|
||||||
|
if progressBar != nil {
|
||||||
|
newLogger.SetProgressBar(progressBar)
|
||||||
|
}
|
||||||
|
newLogger.SetOutputMutex(&OutputMutex)
|
||||||
|
|
||||||
|
// 更新全局日志管理器
|
||||||
|
globalLogger = newLogger
|
||||||
|
status = newLogger.GetScanStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// printLog 根据日志级别打印日志
|
// ProgressDisplay 进度条显示接口(向后兼容)
|
||||||
func printLog(entry *LogEntry) {
|
type ProgressDisplay = logging.ProgressDisplay
|
||||||
if LogLevel != "debug" && (entry.Level == LogLevelDebug || entry.Level == LogLevelError) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputMutex.Lock()
|
// LogDebug 记录调试日志(保持原接口)
|
||||||
defer OutputMutex.Unlock()
|
|
||||||
|
|
||||||
// 处理进度条
|
|
||||||
clearAndWaitProgress()
|
|
||||||
|
|
||||||
// 打印日志消息
|
|
||||||
logMsg := formatLogMessage(entry)
|
|
||||||
if !NoColor {
|
|
||||||
// 使用彩色输出
|
|
||||||
if colorAttr, ok := logColors[entry.Level]; ok {
|
|
||||||
color.New(colorAttr).Println(logMsg)
|
|
||||||
} else {
|
|
||||||
fmt.Println(logMsg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 普通输出
|
|
||||||
fmt.Println(logMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据慢速输出设置决定是否添加延迟
|
|
||||||
if SlowLogOutput {
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新显示进度条
|
|
||||||
if ProgressBar != nil {
|
|
||||||
ProgressBar.RenderBlank()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clearAndWaitProgress 清除进度条并等待
|
|
||||||
func clearAndWaitProgress() {
|
|
||||||
if ProgressBar != nil {
|
|
||||||
ProgressBar.Clear()
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleLog 统一处理日志的输出
|
|
||||||
func handleLog(entry *LogEntry) {
|
|
||||||
if ProgressBar != nil {
|
|
||||||
ProgressBar.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
printLog(entry)
|
|
||||||
|
|
||||||
if ProgressBar != nil {
|
|
||||||
ProgressBar.RenderBlank()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogDebug 记录调试日志
|
|
||||||
func LogDebug(msg string) {
|
func LogDebug(msg string) {
|
||||||
handleLog(&LogEntry{
|
getGlobalLogger().Debug(msg)
|
||||||
Level: LogLevelDebug,
|
|
||||||
Time: time.Now(),
|
|
||||||
Content: msg,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogBase 记录进度信息
|
// LogBase 记录进度信息(保持原接口)
|
||||||
func LogBase(msg string) {
|
func LogBase(msg string) {
|
||||||
handleLog(&LogEntry{
|
getGlobalLogger().Base(msg)
|
||||||
Level: LogLevelBase,
|
|
||||||
Time: time.Now(),
|
|
||||||
Content: msg,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogInfo 记录信息日志
|
// LogInfo 记录信息日志(保持原接口)
|
||||||
// [*]
|
|
||||||
func LogInfo(msg string) {
|
func LogInfo(msg string) {
|
||||||
handleLog(&LogEntry{
|
getGlobalLogger().Info(msg)
|
||||||
Level: LogLevelInfo,
|
|
||||||
Time: time.Now(),
|
|
||||||
Content: msg,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogSuccess 记录成功日志,并更新最后成功时间
|
// LogSuccess 记录成功日志(保持原接口)
|
||||||
// [+]
|
|
||||||
func LogSuccess(result string) {
|
func LogSuccess(result string) {
|
||||||
entry := &LogEntry{
|
getGlobalLogger().Success(result)
|
||||||
Level: LogLevelSuccess,
|
|
||||||
Time: time.Now(),
|
|
||||||
Content: result,
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLog(entry)
|
|
||||||
|
|
||||||
// 更新最后成功时间
|
|
||||||
status.mu.Lock()
|
|
||||||
status.lastSuccess = time.Now()
|
|
||||||
status.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogError 记录错误日志,自动包含文件名和行号信息
|
// LogError 记录错误日志(保持原接口)
|
||||||
func LogError(errMsg string) {
|
func LogError(errMsg string) {
|
||||||
// 获取调用者的文件名和行号
|
getGlobalLogger().Error(errMsg)
|
||||||
_, file, line, ok := runtime.Caller(1)
|
|
||||||
if !ok {
|
|
||||||
file = "unknown"
|
|
||||||
line = 0
|
|
||||||
}
|
|
||||||
file = filepath.Base(file)
|
|
||||||
|
|
||||||
errorMsg := fmt.Sprintf("%s:%d - %s", file, line, errMsg)
|
|
||||||
|
|
||||||
entry := &LogEntry{
|
|
||||||
Level: LogLevelError,
|
|
||||||
Time: time.Now(),
|
|
||||||
Content: errorMsg,
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLog(entry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckErrs 检查是否为需要重试的错误
|
// CheckErrs 检查是否为需要重试的错误(保持原接口)
|
||||||
func CheckErrs(err error) error {
|
func CheckErrs(err error) error {
|
||||||
if err == nil {
|
return logging.CheckErrs(err)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 已知需要重试的错误列表
|
|
||||||
errs := []string{
|
|
||||||
"closed by the remote host", "too many connections",
|
|
||||||
"EOF", "A connection attempt failed",
|
|
||||||
"established connection failed", "connection attempt failed",
|
|
||||||
"Unable to read", "is not allowed to connect to this",
|
|
||||||
"no pg_hba.conf entry",
|
|
||||||
"No connection could be made",
|
|
||||||
"invalid packet size",
|
|
||||||
"bad connection",
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查错误是否匹配
|
|
||||||
errLower := strings.ToLower(err.Error())
|
|
||||||
for _, key := range errs {
|
|
||||||
if strings.Contains(errLower, strings.ToLower(key)) {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetScanStatus 获取扫描状态(新增接口)
|
||||||
|
func GetScanStatus() *logging.ScanStatus {
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateScanProgress 更新扫描进度(新增接口)
|
||||||
|
func UpdateScanProgress(completed, total int64) {
|
||||||
|
status.SetCompleted(completed)
|
||||||
|
status.SetTotal(total)
|
||||||
|
|
||||||
|
// 更新全局变量(保持向后兼容)
|
||||||
|
End = completed
|
||||||
|
Num = total
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProgressBar 设置进度条(新增接口)
|
||||||
|
func SetProgressBar(progressBar ProgressDisplay) {
|
||||||
|
if globalLogger != nil {
|
||||||
|
globalLogger.SetProgressBar(progressBar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容性别名,保持原有的使用方式
|
||||||
|
var (
|
||||||
|
// formatLogMessage 保持向后兼容(但不对外暴露实现)
|
||||||
|
// printLog 保持向后兼容(但不对外暴露实现)
|
||||||
|
// handleLog 保持向后兼容(但不对外暴露实现)
|
||||||
|
// clearAndWaitProgress 保持向后兼容(但不对外暴露实现)
|
||||||
|
)
|
445
Common/Output.go
445
Common/Output.go
@ -1,50 +1,59 @@
|
|||||||
package Common
|
package Common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/csv"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/Common/output"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 全局输出管理器
|
// 全局输出管理器(保持向后兼容)
|
||||||
var ResultOutput *OutputManager
|
var ResultOutput *OutputManager
|
||||||
|
|
||||||
// OutputManager 输出管理器结构体
|
// OutputManager 输出管理器结构体(向后兼容)
|
||||||
type OutputManager struct {
|
type OutputManager struct {
|
||||||
mu sync.Mutex
|
manager *output.Manager
|
||||||
outputPath string
|
|
||||||
outputFormat string
|
|
||||||
file *os.File
|
|
||||||
csvWriter *csv.Writer
|
|
||||||
jsonEncoder *json.Encoder
|
|
||||||
isInitialized bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResultType 定义结果类型
|
// ResultType 定义结果类型(向后兼容)
|
||||||
type ResultType string
|
type ResultType = output.ResultType
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HOST ResultType = "HOST" // 主机存活
|
HOST ResultType = output.TypeHost // 主机存活
|
||||||
PORT ResultType = "PORT" // 端口开放
|
PORT ResultType = output.TypePort // 端口开放
|
||||||
SERVICE ResultType = "SERVICE" // 服务识别
|
SERVICE ResultType = output.TypeService // 服务识别
|
||||||
VULN ResultType = "VULN" // 漏洞发现
|
VULN ResultType = output.TypeVuln // 漏洞发现
|
||||||
)
|
)
|
||||||
|
|
||||||
// ScanResult 扫描结果结构
|
// ScanResult 扫描结果结构(向后兼容)
|
||||||
type ScanResult struct {
|
type ScanResult = output.ScanResult
|
||||||
Time time.Time `json:"time"` // 发现时间
|
|
||||||
Type ResultType `json:"type"` // 结果类型
|
// createOutputManager 创建输出管理器的内部包装
|
||||||
Target string `json:"target"` // 目标(IP/域名/URL)
|
func createOutputManager(outputPath, outputFormat string) (*OutputManager, error) {
|
||||||
Status string `json:"status"` // 状态描述
|
var format output.OutputFormat
|
||||||
Details map[string]interface{} `json:"details"` // 详细信息
|
switch outputFormat {
|
||||||
|
case "txt":
|
||||||
|
format = output.FormatTXT
|
||||||
|
case "json":
|
||||||
|
format = output.FormatJSON
|
||||||
|
case "csv":
|
||||||
|
format = output.FormatCSV
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf(GetText("output_format_invalid"), outputFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := output.DefaultManagerConfig(outputPath, format)
|
||||||
|
manager, err := output.NewManager(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OutputManager{
|
||||||
|
manager: manager,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitOutput 初始化输出系统
|
// InitOutput 初始化输出系统(保持原接口)
|
||||||
func InitOutput() error {
|
func InitOutput() error {
|
||||||
LogDebug(GetText("output_init_start"))
|
LogDebug(GetText("output_init_start"))
|
||||||
|
|
||||||
@ -61,71 +70,41 @@ func InitOutput() error {
|
|||||||
return fmt.Errorf(GetText("output_path_empty"))
|
return fmt.Errorf(GetText("output_path_empty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
dir := filepath.Dir(Outputfile)
|
manager, err := createOutputManager(Outputfile, OutputFormat)
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
if err != nil {
|
||||||
LogDebug(GetText("output_create_dir_failed", err))
|
|
||||||
return fmt.Errorf(GetText("output_create_dir_failed", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
manager := &OutputManager{
|
|
||||||
outputPath: Outputfile,
|
|
||||||
outputFormat: OutputFormat,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := manager.initialize(); err != nil {
|
|
||||||
LogDebug(GetText("output_init_failed", err))
|
LogDebug(GetText("output_init_failed", err))
|
||||||
return fmt.Errorf(GetText("output_init_failed", err))
|
return fmt.Errorf(GetText("output_init_failed", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultOutput = manager
|
ResultOutput = manager
|
||||||
|
|
||||||
|
// 设置全局输出管理器
|
||||||
|
output.SetGlobalManager(manager.manager)
|
||||||
|
|
||||||
LogDebug(GetText("output_init_success"))
|
LogDebug(GetText("output_init_success"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (om *OutputManager) initialize() error {
|
// saveResult 内部方法,使用新的输出管理器保存结果
|
||||||
om.mu.Lock()
|
func (om *OutputManager) saveResult(result *ScanResult) error {
|
||||||
defer om.mu.Unlock()
|
if om.manager == nil {
|
||||||
|
return fmt.Errorf(GetText("output_not_init"))
|
||||||
if om.isInitialized {
|
|
||||||
LogDebug(GetText("output_already_init"))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LogDebug(GetText("output_opening_file", om.outputPath))
|
LogDebug(GetText("output_saving_result", result.Type, result.Target))
|
||||||
file, err := os.OpenFile(om.outputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
return om.manager.SaveResult(result)
|
||||||
if err != nil {
|
|
||||||
LogDebug(GetText("output_open_file_failed", err))
|
|
||||||
return fmt.Errorf(GetText("output_open_file_failed", err))
|
|
||||||
}
|
|
||||||
om.file = file
|
|
||||||
|
|
||||||
switch om.outputFormat {
|
|
||||||
case "csv":
|
|
||||||
LogDebug(GetText("output_init_csv"))
|
|
||||||
om.csvWriter = csv.NewWriter(file)
|
|
||||||
headers := []string{"Time", "Type", "Target", "Status", "Details"}
|
|
||||||
if err := om.csvWriter.Write(headers); err != nil {
|
|
||||||
LogDebug(GetText("output_write_csv_header_failed", err))
|
|
||||||
file.Close()
|
|
||||||
return fmt.Errorf(GetText("output_write_csv_header_failed", err))
|
|
||||||
}
|
|
||||||
om.csvWriter.Flush()
|
|
||||||
case "json":
|
|
||||||
LogDebug(GetText("output_init_json"))
|
|
||||||
om.jsonEncoder = json.NewEncoder(file)
|
|
||||||
om.jsonEncoder.SetIndent("", " ")
|
|
||||||
case "txt":
|
|
||||||
LogDebug(GetText("output_init_txt"))
|
|
||||||
default:
|
|
||||||
LogDebug(GetText("output_format_invalid", om.outputFormat))
|
|
||||||
}
|
|
||||||
|
|
||||||
om.isInitialized = true
|
|
||||||
LogDebug(GetText("output_init_complete"))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveResult 保存扫描结果
|
// getResult 内部方法,获取结果
|
||||||
|
func (om *OutputManager) getResult() ([]*ScanResult, error) {
|
||||||
|
if om.manager == nil {
|
||||||
|
return nil, fmt.Errorf(GetText("output_not_init"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return om.manager.GetResults()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveResult 保存扫描结果(保持原接口)
|
||||||
func SaveResult(result *ScanResult) error {
|
func SaveResult(result *ScanResult) error {
|
||||||
if ResultOutput == nil {
|
if ResultOutput == nil {
|
||||||
LogDebug(GetText("output_not_init"))
|
LogDebug(GetText("output_not_init"))
|
||||||
@ -135,152 +114,17 @@ func SaveResult(result *ScanResult) error {
|
|||||||
LogDebug(GetText("output_saving_result", result.Type, result.Target))
|
LogDebug(GetText("output_saving_result", result.Type, result.Target))
|
||||||
return ResultOutput.saveResult(result)
|
return ResultOutput.saveResult(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResults 获取扫描结果(保持原接口)
|
||||||
func GetResults() ([]*ScanResult, error) {
|
func GetResults() ([]*ScanResult, error) {
|
||||||
if ResultOutput == nil {
|
if ResultOutput == nil {
|
||||||
return nil, fmt.Errorf(GetText("output_not_init"))
|
return nil, fmt.Errorf(GetText("output_not_init"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if ResultOutput.outputFormat == "csv" {
|
return ResultOutput.getResult()
|
||||||
return ResultOutput.getResult()
|
|
||||||
}
|
|
||||||
// 其他格式尚未实现读取支持
|
|
||||||
return nil, fmt.Errorf(GetText("output_format_read_not_supported"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (om *OutputManager) saveResult(result *ScanResult) error {
|
// CloseOutput 关闭输出系统(保持原接口)
|
||||||
om.mu.Lock()
|
|
||||||
defer om.mu.Unlock()
|
|
||||||
|
|
||||||
if !om.isInitialized {
|
|
||||||
LogDebug(GetText("output_not_init"))
|
|
||||||
return fmt.Errorf(GetText("output_not_init"))
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
switch om.outputFormat {
|
|
||||||
case "txt":
|
|
||||||
err = om.writeTxt(result)
|
|
||||||
case "json":
|
|
||||||
err = om.writeJson(result)
|
|
||||||
case "csv":
|
|
||||||
err = om.writeCsv(result)
|
|
||||||
default:
|
|
||||||
LogDebug(GetText("output_format_invalid", om.outputFormat))
|
|
||||||
return fmt.Errorf(GetText("output_format_invalid", om.outputFormat))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
LogDebug(GetText("output_save_failed", err))
|
|
||||||
} else {
|
|
||||||
LogDebug(GetText("output_save_success", result.Type, result.Target))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
func (om *OutputManager) getResult() ([]*ScanResult, error) {
|
|
||||||
om.mu.Lock()
|
|
||||||
defer om.mu.Unlock()
|
|
||||||
|
|
||||||
if !om.isInitialized {
|
|
||||||
LogDebug(GetText("output_not_init"))
|
|
||||||
return nil, fmt.Errorf(GetText("output_not_init"))
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(om.outputPath)
|
|
||||||
if err != nil {
|
|
||||||
LogDebug(GetText("output_open_file_failed", err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
reader := csv.NewReader(file)
|
|
||||||
records, err := reader.ReadAll()
|
|
||||||
if err != nil {
|
|
||||||
LogDebug(GetText("output_read_csv_failed", err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var results []*ScanResult
|
|
||||||
for i, row := range records {
|
|
||||||
// 跳过 CSV 头部
|
|
||||||
if i == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(row) < 5 {
|
|
||||||
continue // 数据不完整
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := time.Parse("2006-01-02 15:04:05", row[0])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var details map[string]interface{}
|
|
||||||
if err := json.Unmarshal([]byte(row[4]), &details); err != nil {
|
|
||||||
details = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &ScanResult{
|
|
||||||
Time: t,
|
|
||||||
Type: ResultType(row[1]),
|
|
||||||
Target: row[2],
|
|
||||||
Status: row[3],
|
|
||||||
Details: details,
|
|
||||||
}
|
|
||||||
results = append(results, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
LogDebug(GetText("output_read_csv_success", len(results)))
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (om *OutputManager) writeTxt(result *ScanResult) error {
|
|
||||||
// 格式化 Details 为键值对字符串
|
|
||||||
var details string
|
|
||||||
if len(result.Details) > 0 {
|
|
||||||
pairs := make([]string, 0, len(result.Details))
|
|
||||||
for k, v := range result.Details {
|
|
||||||
pairs = append(pairs, fmt.Sprintf("%s=%v", k, v))
|
|
||||||
}
|
|
||||||
details = strings.Join(pairs, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
txt := GetText("output_txt_format",
|
|
||||||
result.Time.Format("2006-01-02 15:04:05"),
|
|
||||||
result.Type,
|
|
||||||
result.Target,
|
|
||||||
result.Status,
|
|
||||||
details,
|
|
||||||
) + "\n"
|
|
||||||
_, err := om.file.WriteString(txt)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (om *OutputManager) writeJson(result *ScanResult) error {
|
|
||||||
return om.jsonEncoder.Encode(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (om *OutputManager) writeCsv(result *ScanResult) error {
|
|
||||||
details, err := json.Marshal(result.Details)
|
|
||||||
if err != nil {
|
|
||||||
details = []byte("{}")
|
|
||||||
}
|
|
||||||
|
|
||||||
record := []string{
|
|
||||||
result.Time.Format("2006-01-02 15:04:05"),
|
|
||||||
string(result.Type),
|
|
||||||
result.Target,
|
|
||||||
result.Status,
|
|
||||||
string(details),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := om.csvWriter.Write(record); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
om.csvWriter.Flush()
|
|
||||||
return om.csvWriter.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseOutput 关闭输出系统
|
|
||||||
func CloseOutput() error {
|
func CloseOutput() error {
|
||||||
if ResultOutput == nil {
|
if ResultOutput == nil {
|
||||||
LogDebug(GetText("output_no_need_close"))
|
LogDebug(GetText("output_no_need_close"))
|
||||||
@ -288,25 +132,158 @@ func CloseOutput() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LogDebug(GetText("output_closing"))
|
LogDebug(GetText("output_closing"))
|
||||||
ResultOutput.mu.Lock()
|
|
||||||
defer ResultOutput.mu.Unlock()
|
|
||||||
|
|
||||||
if !ResultOutput.isInitialized {
|
err := ResultOutput.manager.Close()
|
||||||
LogDebug(GetText("output_no_need_close"))
|
if err != nil {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ResultOutput.csvWriter != nil {
|
|
||||||
LogDebug(GetText("output_flush_csv"))
|
|
||||||
ResultOutput.csvWriter.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ResultOutput.file.Close(); err != nil {
|
|
||||||
LogDebug(GetText("output_close_failed", err))
|
LogDebug(GetText("output_close_failed", err))
|
||||||
return fmt.Errorf(GetText("output_close_failed", err))
|
return fmt.Errorf(GetText("output_close_failed", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultOutput.isInitialized = false
|
|
||||||
LogDebug(GetText("output_closed"))
|
LogDebug(GetText("output_closed"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增功能接口
|
||||||
|
|
||||||
|
// SaveResultWithDetails 保存带详细信息的扫描结果
|
||||||
|
func SaveResultWithDetails(resultType ResultType, target, status string, details map[string]interface{}) error {
|
||||||
|
result := &ScanResult{
|
||||||
|
Time: time.Now(),
|
||||||
|
Type: resultType,
|
||||||
|
Target: target,
|
||||||
|
Status: status,
|
||||||
|
Details: details,
|
||||||
|
}
|
||||||
|
return SaveResult(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResultsWithFilter 获取过滤后的扫描结果
|
||||||
|
func GetResultsWithFilter(filter *output.ResultFilter) ([]*ScanResult, error) {
|
||||||
|
if ResultOutput == nil {
|
||||||
|
return nil, fmt.Errorf(GetText("output_not_init"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultOutput.manager.GetResultsWithFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOutputStatistics 获取输出统计信息
|
||||||
|
func GetOutputStatistics() *output.Statistics {
|
||||||
|
if ResultOutput == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultOutput.manager.GetStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlushOutput 刷新输出缓冲区
|
||||||
|
func FlushOutput() error {
|
||||||
|
if ResultOutput == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultOutput.manager.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOutputConfig 更新输出配置
|
||||||
|
func UpdateOutputConfig(updates map[string]interface{}) error {
|
||||||
|
if ResultOutput == nil {
|
||||||
|
return fmt.Errorf(GetText("output_not_init"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultOutput.manager.UpdateConfig(updates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 便利函数用于快速保存不同类型的结果
|
||||||
|
|
||||||
|
// SaveHostResult 保存主机存活结果
|
||||||
|
func SaveHostResult(target string, isAlive bool, details map[string]interface{}) error {
|
||||||
|
status := "离线"
|
||||||
|
if isAlive {
|
||||||
|
status = "存活"
|
||||||
|
}
|
||||||
|
return SaveResultWithDetails(HOST, target, status, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SavePortResult 保存端口扫描结果
|
||||||
|
func SavePortResult(target string, port int, isOpen bool, service string, details map[string]interface{}) error {
|
||||||
|
status := "关闭"
|
||||||
|
if isOpen {
|
||||||
|
status = "开放"
|
||||||
|
}
|
||||||
|
|
||||||
|
if details == nil {
|
||||||
|
details = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
details["port"] = port
|
||||||
|
if service != "" {
|
||||||
|
details["service"] = service
|
||||||
|
}
|
||||||
|
|
||||||
|
targetWithPort := fmt.Sprintf("%s:%d", target, port)
|
||||||
|
return SaveResultWithDetails(PORT, targetWithPort, status, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveServiceResult 保存服务识别结果
|
||||||
|
func SaveServiceResult(target string, service, version string, details map[string]interface{}) error {
|
||||||
|
status := service
|
||||||
|
if version != "" {
|
||||||
|
status = fmt.Sprintf("%s %s", service, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if details == nil {
|
||||||
|
details = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
details["service"] = service
|
||||||
|
if version != "" {
|
||||||
|
details["version"] = version
|
||||||
|
}
|
||||||
|
|
||||||
|
return SaveResultWithDetails(SERVICE, target, status, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveVulnResult 保存漏洞发现结果
|
||||||
|
func SaveVulnResult(target, vulnName, severity string, details map[string]interface{}) error {
|
||||||
|
status := vulnName
|
||||||
|
if severity != "" {
|
||||||
|
status = fmt.Sprintf("%s [%s]", vulnName, severity)
|
||||||
|
}
|
||||||
|
|
||||||
|
if details == nil {
|
||||||
|
details = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
details["vulnerability"] = vulnName
|
||||||
|
if severity != "" {
|
||||||
|
details["severity"] = severity
|
||||||
|
}
|
||||||
|
|
||||||
|
return SaveResultWithDetails(VULN, target, status, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出新的类型和接口供高级用户使用
|
||||||
|
type (
|
||||||
|
OutputFormatType = output.OutputFormat
|
||||||
|
ResultFilter = output.ResultFilter
|
||||||
|
TimeRange = output.TimeRange
|
||||||
|
Statistics = output.Statistics
|
||||||
|
ManagerConfig = output.ManagerConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
// 导出新的常量
|
||||||
|
const (
|
||||||
|
FormatTXT = output.FormatTXT
|
||||||
|
FormatJSON = output.FormatJSON
|
||||||
|
FormatCSV = output.FormatCSV
|
||||||
|
|
||||||
|
TypeInfo = output.TypeInfo
|
||||||
|
TypeBrute = output.TypeBrute
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateOutputManager 创建新的输出管理器(高级接口)
|
||||||
|
func CreateOutputManager(config *ManagerConfig) (*output.Manager, error) {
|
||||||
|
return output.NewManager(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultOutputConfig 获取默认输出配置
|
||||||
|
func DefaultOutputConfig(outputPath string, format OutputFormatType) *ManagerConfig {
|
||||||
|
return output.DefaultManagerConfig(outputPath, format)
|
||||||
|
}
|
141
Common/logging/Formatter.go
Normal file
141
Common/logging/Formatter.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StandardFormatter 标准日志格式化器
|
||||||
|
type StandardFormatter struct {
|
||||||
|
startTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStandardFormatter 创建标准格式化器
|
||||||
|
func NewStandardFormatter() *StandardFormatter {
|
||||||
|
return &StandardFormatter{
|
||||||
|
startTime: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStartTime 设置开始时间
|
||||||
|
func (f *StandardFormatter) SetStartTime(startTime time.Time) {
|
||||||
|
f.startTime = startTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format 格式化日志条目
|
||||||
|
func (f *StandardFormatter) Format(entry *LogEntry) string {
|
||||||
|
elapsed := time.Since(f.startTime)
|
||||||
|
timeStr := f.formatElapsedTime(elapsed)
|
||||||
|
prefix := f.getLevelPrefix(entry.Level)
|
||||||
|
|
||||||
|
return fmt.Sprintf("[%s] %s %s", timeStr, prefix, entry.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatElapsedTime 格式化经过的时间
|
||||||
|
func (f *StandardFormatter) formatElapsedTime(elapsed time.Duration) string {
|
||||||
|
switch {
|
||||||
|
case elapsed < time.Second:
|
||||||
|
// 毫秒显示,不需要小数
|
||||||
|
return fmt.Sprintf("%dms", elapsed.Milliseconds())
|
||||||
|
case elapsed < time.Minute:
|
||||||
|
// 秒显示,保留一位小数
|
||||||
|
return fmt.Sprintf("%.1fs", elapsed.Seconds())
|
||||||
|
case elapsed < time.Hour:
|
||||||
|
// 分钟和秒显示
|
||||||
|
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 (f *StandardFormatter) getLevelPrefix(level LogLevel) string {
|
||||||
|
switch level {
|
||||||
|
case LevelSuccess:
|
||||||
|
return "[+]"
|
||||||
|
case LevelInfo:
|
||||||
|
return "[*]"
|
||||||
|
case LevelError:
|
||||||
|
return "[-]"
|
||||||
|
default:
|
||||||
|
return " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetailedFormatter 详细日志格式化器
|
||||||
|
type DetailedFormatter struct {
|
||||||
|
StandardFormatter
|
||||||
|
includeSource bool
|
||||||
|
includeMetadata bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDetailedFormatter 创建详细格式化器
|
||||||
|
func NewDetailedFormatter(includeSource, includeMetadata bool) *DetailedFormatter {
|
||||||
|
return &DetailedFormatter{
|
||||||
|
StandardFormatter: StandardFormatter{startTime: time.Now()},
|
||||||
|
includeSource: includeSource,
|
||||||
|
includeMetadata: includeMetadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format 格式化日志条目(包含详细信息)
|
||||||
|
func (f *DetailedFormatter) Format(entry *LogEntry) string {
|
||||||
|
baseFormat := f.StandardFormatter.Format(entry)
|
||||||
|
|
||||||
|
if f.includeSource && entry.Source != "" {
|
||||||
|
baseFormat += fmt.Sprintf(" [%s]", entry.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.includeMetadata && len(entry.Metadata) > 0 {
|
||||||
|
baseFormat += fmt.Sprintf(" %v", entry.Metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONFormatter JSON格式化器
|
||||||
|
type JSONFormatter struct {
|
||||||
|
startTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSONFormatter 创建JSON格式化器
|
||||||
|
func NewJSONFormatter() *JSONFormatter {
|
||||||
|
return &JSONFormatter{
|
||||||
|
startTime: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStartTime 设置开始时间
|
||||||
|
func (f *JSONFormatter) SetStartTime(startTime time.Time) {
|
||||||
|
f.startTime = startTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format 格式化为JSON格式
|
||||||
|
func (f *JSONFormatter) Format(entry *LogEntry) string {
|
||||||
|
elapsed := time.Since(f.startTime)
|
||||||
|
|
||||||
|
jsonStr := fmt.Sprintf(`{"level":"%s","time":"%s","elapsed_ms":%d,"content":"%s"`,
|
||||||
|
entry.Level,
|
||||||
|
entry.Time.Format("2006-01-02T15:04:05.000Z07:00"),
|
||||||
|
elapsed.Milliseconds(),
|
||||||
|
entry.Content)
|
||||||
|
|
||||||
|
if entry.Source != "" {
|
||||||
|
jsonStr += fmt.Sprintf(`,"source":"%s"`, entry.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(entry.Metadata) > 0 {
|
||||||
|
jsonStr += `,"metadata":`
|
||||||
|
// 这里为简化处理,使用简单的格式
|
||||||
|
jsonStr += fmt.Sprintf(`%v`, entry.Metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonStr += "}"
|
||||||
|
return jsonStr
|
||||||
|
}
|
324
Common/logging/Logger.go
Normal file
324
Common/logging/Logger.go
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger 日志管理器
|
||||||
|
type Logger struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
config *LoggerConfig
|
||||||
|
formatter LogFormatter
|
||||||
|
handlers []LogHandler
|
||||||
|
scanStatus *ScanStatus
|
||||||
|
progressBar ProgressDisplay
|
||||||
|
outputMutex *sync.Mutex
|
||||||
|
initialized bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogger 创建新的日志管理器
|
||||||
|
func NewLogger(config *LoggerConfig) *Logger {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultLoggerConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := &Logger{
|
||||||
|
config: config,
|
||||||
|
formatter: NewStandardFormatter(),
|
||||||
|
handlers: make([]LogHandler, 0),
|
||||||
|
scanStatus: NewScanStatus(),
|
||||||
|
outputMutex: &sync.Mutex{},
|
||||||
|
initialized: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置格式化器的开始时间
|
||||||
|
logger.formatter.SetStartTime(config.StartTime)
|
||||||
|
|
||||||
|
// 添加默认的控制台处理器
|
||||||
|
consoleHandler := NewConsoleHandler(config)
|
||||||
|
logger.AddHandler(consoleHandler)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFormatter 设置格式化器
|
||||||
|
func (l *Logger) SetFormatter(formatter LogFormatter) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.formatter = formatter
|
||||||
|
l.formatter.SetStartTime(l.config.StartTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHandler 添加日志处理器
|
||||||
|
func (l *Logger) AddHandler(handler LogHandler) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.handlers = append(l.handlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProgressBar 设置进度条显示
|
||||||
|
func (l *Logger) SetProgressBar(progressBar ProgressDisplay) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.progressBar = progressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutputMutex 设置输出互斥锁
|
||||||
|
func (l *Logger) SetOutputMutex(mutex *sync.Mutex) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.outputMutex = mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log 记录日志
|
||||||
|
func (l *Logger) Log(level LogLevel, content string, metadata ...map[string]interface{}) {
|
||||||
|
if !l.shouldLog(level) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := &LogEntry{
|
||||||
|
Level: level,
|
||||||
|
Time: time.Now(),
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加元数据
|
||||||
|
if len(metadata) > 0 {
|
||||||
|
entry.Metadata = metadata[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于错误级别,自动添加调用者信息
|
||||||
|
if level == LevelError {
|
||||||
|
if _, file, line, ok := runtime.Caller(2); ok {
|
||||||
|
entry.Source = fmt.Sprintf("%s:%d", filepath.Base(file), line)
|
||||||
|
entry.Content = fmt.Sprintf("%s:%d - %s", filepath.Base(file), line, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.handleLogEntry(entry)
|
||||||
|
|
||||||
|
// 更新扫描状态
|
||||||
|
if level == LevelSuccess {
|
||||||
|
l.scanStatus.UpdateSuccess()
|
||||||
|
} else if level == LevelError {
|
||||||
|
l.scanStatus.UpdateError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldLog 检查是否应该记录该级别的日志
|
||||||
|
func (l *Logger) shouldLog(level LogLevel) bool {
|
||||||
|
switch l.config.Level {
|
||||||
|
case LevelAll:
|
||||||
|
return true
|
||||||
|
case LevelBaseInfoSuccess:
|
||||||
|
return level == LevelBase || level == LevelInfo || level == LevelSuccess
|
||||||
|
case LevelInfoSuccess:
|
||||||
|
return level == LevelInfo || level == LevelSuccess
|
||||||
|
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
|
||||||
|
default:
|
||||||
|
// 向后兼容:如果是字符串 "debug",显示所有
|
||||||
|
if l.config.Level == "debug" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 默认显示base、info和success
|
||||||
|
return level == LevelBase || level == LevelInfo || level == LevelSuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleLogEntry 处理日志条目
|
||||||
|
func (l *Logger) handleLogEntry(entry *LogEntry) {
|
||||||
|
l.outputMutex.Lock()
|
||||||
|
defer l.outputMutex.Unlock()
|
||||||
|
|
||||||
|
// 清除进度条
|
||||||
|
l.clearProgress()
|
||||||
|
|
||||||
|
// 使用所有处理器处理日志
|
||||||
|
l.mu.RLock()
|
||||||
|
for _, handler := range l.handlers {
|
||||||
|
if handler.IsEnabled() {
|
||||||
|
handler.Handle(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.mu.RUnlock()
|
||||||
|
|
||||||
|
// 恢复进度条
|
||||||
|
l.restoreProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearProgress 清除进度条
|
||||||
|
func (l *Logger) clearProgress() {
|
||||||
|
if l.progressBar != nil {
|
||||||
|
l.progressBar.Clear() // 忽略错误
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// restoreProgress 恢复进度条
|
||||||
|
func (l *Logger) restoreProgress() {
|
||||||
|
if l.progressBar != nil {
|
||||||
|
l.progressBar.RenderBlank() // 忽略错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 便利方法
|
||||||
|
func (l *Logger) Debug(content string, metadata ...map[string]interface{}) {
|
||||||
|
l.Log(LevelDebug, content, metadata...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Base(content string, metadata ...map[string]interface{}) {
|
||||||
|
l.Log(LevelBase, content, metadata...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Info(content string, metadata ...map[string]interface{}) {
|
||||||
|
l.Log(LevelInfo, content, metadata...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Success(content string, metadata ...map[string]interface{}) {
|
||||||
|
l.Log(LevelSuccess, content, metadata...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Error(content string, metadata ...map[string]interface{}) {
|
||||||
|
l.Log(LevelError, content, metadata...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScanStatus 获取扫描状态管理器
|
||||||
|
func (l *Logger) GetScanStatus() *ScanStatus {
|
||||||
|
return l.scanStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize 初始化日志系统(兼容原接口)
|
||||||
|
func (l *Logger) Initialize() {
|
||||||
|
// 禁用标准日志输出
|
||||||
|
log.SetOutput(io.Discard)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsoleHandler 控制台日志处理器
|
||||||
|
type ConsoleHandler struct {
|
||||||
|
config *LoggerConfig
|
||||||
|
formatter LogFormatter
|
||||||
|
enabled bool
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConsoleHandler 创建控制台处理器
|
||||||
|
func NewConsoleHandler(config *LoggerConfig) *ConsoleHandler {
|
||||||
|
formatter := NewStandardFormatter()
|
||||||
|
formatter.SetStartTime(config.StartTime)
|
||||||
|
|
||||||
|
return &ConsoleHandler{
|
||||||
|
config: config,
|
||||||
|
formatter: formatter,
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 处理日志条目
|
||||||
|
func (h *ConsoleHandler) Handle(entry *LogEntry) {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
|
if !h.enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用自己的格式化器格式化消息
|
||||||
|
logMsg := h.formatter.Format(entry)
|
||||||
|
|
||||||
|
// 根据颜色设置输出
|
||||||
|
if h.config.EnableColor {
|
||||||
|
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok {
|
||||||
|
color.New(colorAttr).Println(logMsg)
|
||||||
|
} else {
|
||||||
|
fmt.Println(logMsg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println(logMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据慢速输出设置决定是否添加延迟
|
||||||
|
if h.config.SlowOutput {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEnabled 设置处理器启用状态
|
||||||
|
func (h *ConsoleHandler) SetEnabled(enabled bool) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
h.enabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEnabled 检查处理器是否启用
|
||||||
|
func (h *ConsoleHandler) IsEnabled() bool {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
return h.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局日志管理器实例
|
||||||
|
var (
|
||||||
|
globalLogger *Logger
|
||||||
|
initOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetGlobalLogger 获取全局日志管理器
|
||||||
|
func GetGlobalLogger() *Logger {
|
||||||
|
initOnce.Do(func() {
|
||||||
|
globalLogger = NewLogger(DefaultLoggerConfig())
|
||||||
|
})
|
||||||
|
return globalLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGlobalLogger 设置全局日志管理器
|
||||||
|
func SetGlobalLogger(logger *Logger) {
|
||||||
|
globalLogger = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误检查函数(保持原有逻辑)
|
||||||
|
func CheckErrs(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已知需要重试的错误列表
|
||||||
|
errs := []string{
|
||||||
|
"closed by the remote host", "too many connections",
|
||||||
|
"EOF", "A connection attempt failed",
|
||||||
|
"established connection failed", "connection attempt failed",
|
||||||
|
"Unable to read", "is not allowed to connect to this",
|
||||||
|
"no pg_hba.conf entry",
|
||||||
|
"No connection could be made",
|
||||||
|
"invalid packet size",
|
||||||
|
"bad connection",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查错误是否匹配
|
||||||
|
errLower := strings.ToLower(err.Error())
|
||||||
|
for _, key := range errs {
|
||||||
|
if strings.Contains(errLower, strings.ToLower(key)) {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
146
Common/logging/Types.go
Normal file
146
Common/logging/Types.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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" // 显示基础、信息和成功日志
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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"` // 附加元数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogFormatter 日志格式化器接口
|
||||||
|
type LogFormatter interface {
|
||||||
|
Format(entry *LogEntry) string
|
||||||
|
SetStartTime(startTime time.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogHandler 日志处理器接口
|
||||||
|
type LogHandler interface {
|
||||||
|
Handle(entry *LogEntry)
|
||||||
|
SetEnabled(enabled bool)
|
||||||
|
IsEnabled() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]color.Attribute `json:"-"` // 级别颜色映射
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultLoggerConfig 默认日志器配置
|
||||||
|
func DefaultLoggerConfig() *LoggerConfig {
|
||||||
|
return &LoggerConfig{
|
||||||
|
Level: LevelAll,
|
||||||
|
EnableColor: true,
|
||||||
|
SlowOutput: false,
|
||||||
|
ShowProgress: true,
|
||||||
|
StartTime: time.Now(),
|
||||||
|
LevelColors: map[LogLevel]color.Attribute{
|
||||||
|
LevelError: color.FgBlue, // 错误日志显示蓝色
|
||||||
|
LevelBase: color.FgYellow, // 基础日志显示黄色
|
||||||
|
LevelInfo: color.FgGreen, // 信息日志显示绿色
|
||||||
|
LevelSuccess: color.FgRed, // 成功日志显示红色
|
||||||
|
LevelDebug: color.FgWhite, // 调试日志显示白色
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanStatus 扫描状态管理器
|
||||||
|
type ScanStatus struct {
|
||||||
|
mu sync.RWMutex // 读写互斥锁
|
||||||
|
total int64 // 总任务数
|
||||||
|
completed int64 // 已完成任务数
|
||||||
|
lastSuccess time.Time // 最近一次成功的时间
|
||||||
|
lastError time.Time // 最近一次错误的时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScanStatus 创建新的扫描状态管理器
|
||||||
|
func NewScanStatus() *ScanStatus {
|
||||||
|
now := time.Now()
|
||||||
|
return &ScanStatus{
|
||||||
|
lastSuccess: now,
|
||||||
|
lastError: now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSuccess 更新最后成功时间
|
||||||
|
func (s *ScanStatus) UpdateSuccess() {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.lastSuccess = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateError 更新最后错误时间
|
||||||
|
func (s *ScanStatus) UpdateError() {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.lastError = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLastSuccess 获取最后成功时间
|
||||||
|
func (s *ScanStatus) GetLastSuccess() time.Time {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.lastSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLastError 获取最后错误时间
|
||||||
|
func (s *ScanStatus) GetLastError() time.Time {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.lastError
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotal 设置总任务数
|
||||||
|
func (s *ScanStatus) SetTotal(total int64) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.total = total
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCompleted 设置已完成任务数
|
||||||
|
func (s *ScanStatus) SetCompleted(completed int64) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.completed = completed
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProgress 获取进度信息
|
||||||
|
func (s *ScanStatus) GetProgress() (int64, int64) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.completed, s.total
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProgressDisplay 进度条显示接口
|
||||||
|
type ProgressDisplay interface {
|
||||||
|
Clear() error
|
||||||
|
RenderBlank() error
|
||||||
|
}
|
379
Common/output/Manager.go
Normal file
379
Common/output/Manager.go
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
package output
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager 输出管理器
|
||||||
|
type Manager struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
config *ManagerConfig
|
||||||
|
writer OutputWriter
|
||||||
|
reader OutputReader
|
||||||
|
statistics *Statistics
|
||||||
|
buffer []*ScanResult
|
||||||
|
bufferMutex sync.Mutex
|
||||||
|
flushTicker *time.Ticker
|
||||||
|
stopChan chan struct{}
|
||||||
|
initialized bool
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager 创建新的输出管理器
|
||||||
|
func NewManager(config *ManagerConfig) (*Manager, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("配置不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证输出格式
|
||||||
|
if err := validateFormat(config.Format); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建输出目录
|
||||||
|
if err := createOutputDir(config.OutputPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := &Manager{
|
||||||
|
config: config,
|
||||||
|
statistics: NewStatistics(),
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化写入器
|
||||||
|
if err := manager.initializeWriter(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化读取器
|
||||||
|
manager.initializeReader()
|
||||||
|
|
||||||
|
// 如果启用缓冲,初始化缓冲区
|
||||||
|
if config.EnableBuffer {
|
||||||
|
manager.buffer = make([]*ScanResult, 0, config.BufferSize)
|
||||||
|
|
||||||
|
// 如果启用自动刷新,启动定时器
|
||||||
|
if config.AutoFlush {
|
||||||
|
manager.startAutoFlush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.initialized = true
|
||||||
|
return manager, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateFormat 验证输出格式
|
||||||
|
func validateFormat(format OutputFormat) error {
|
||||||
|
switch format {
|
||||||
|
case FormatTXT, FormatJSON, FormatCSV:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("不支持的输出格式: %s", format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createOutputDir 创建输出目录
|
||||||
|
func createOutputDir(outputPath string) error {
|
||||||
|
dir := filepath.Dir(outputPath)
|
||||||
|
return os.MkdirAll(dir, 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeWriter 初始化写入器
|
||||||
|
func (m *Manager) initializeWriter() error {
|
||||||
|
var writer OutputWriter
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch m.config.Format {
|
||||||
|
case FormatTXT:
|
||||||
|
writer, err = NewTXTWriter(m.config.OutputPath)
|
||||||
|
case FormatJSON:
|
||||||
|
writer, err = NewJSONWriter(m.config.OutputPath)
|
||||||
|
case FormatCSV:
|
||||||
|
writer, err = NewCSVWriter(m.config.OutputPath)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("不支持的输出格式: %s", m.config.Format)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("初始化写入器失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.writer = writer
|
||||||
|
|
||||||
|
// 写入头部(如果需要)
|
||||||
|
return m.writer.WriteHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeReader 初始化读取器
|
||||||
|
func (m *Manager) initializeReader() {
|
||||||
|
// 目前只有CSV格式支持读取
|
||||||
|
if m.config.Format == FormatCSV {
|
||||||
|
m.reader = NewCSVReader(m.config.OutputPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startAutoFlush 启动自动刷新
|
||||||
|
func (m *Manager) startAutoFlush() {
|
||||||
|
m.flushTicker = time.NewTicker(m.config.FlushInterval)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.flushTicker.C:
|
||||||
|
m.flushBuffer()
|
||||||
|
case <-m.stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveResult 保存扫描结果
|
||||||
|
func (m *Manager) SaveResult(result *ScanResult) error {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
if !m.initialized {
|
||||||
|
return fmt.Errorf("输出管理器未初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.closed {
|
||||||
|
return fmt.Errorf("输出管理器已关闭")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新统计信息
|
||||||
|
m.statistics.AddResult(result.Type)
|
||||||
|
|
||||||
|
// 如果启用缓冲,先添加到缓冲区
|
||||||
|
if m.config.EnableBuffer {
|
||||||
|
return m.addToBuffer(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接写入
|
||||||
|
return m.writer.Write(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addToBuffer 添加结果到缓冲区
|
||||||
|
func (m *Manager) addToBuffer(result *ScanResult) error {
|
||||||
|
m.bufferMutex.Lock()
|
||||||
|
defer m.bufferMutex.Unlock()
|
||||||
|
|
||||||
|
m.buffer = append(m.buffer, result)
|
||||||
|
|
||||||
|
// 如果缓冲区已满,立即刷新
|
||||||
|
if len(m.buffer) >= m.config.BufferSize {
|
||||||
|
return m.flushBufferUnsafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushBuffer 刷新缓冲区(加锁版本)
|
||||||
|
func (m *Manager) flushBuffer() error {
|
||||||
|
m.bufferMutex.Lock()
|
||||||
|
defer m.bufferMutex.Unlock()
|
||||||
|
return m.flushBufferUnsafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushBufferUnsafe 刷新缓冲区(无锁版本,内部使用)
|
||||||
|
func (m *Manager) flushBufferUnsafe() error {
|
||||||
|
if len(m.buffer) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量写入
|
||||||
|
for _, result := range m.buffer {
|
||||||
|
if err := m.writer.Write(result); err != nil {
|
||||||
|
return fmt.Errorf("写入结果失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新写入器
|
||||||
|
if err := m.writer.Flush(); err != nil {
|
||||||
|
return fmt.Errorf("刷新写入器失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空缓冲区
|
||||||
|
m.buffer = m.buffer[:0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResults 获取扫描结果
|
||||||
|
func (m *Manager) GetResults() ([]*ScanResult, error) {
|
||||||
|
return m.GetResultsWithFilter(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResultsWithFilter 获取过滤后的扫描结果
|
||||||
|
func (m *Manager) GetResultsWithFilter(filter *ResultFilter) ([]*ScanResult, error) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
if m.reader == nil {
|
||||||
|
return nil, fmt.Errorf("当前输出格式不支持读取")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.reader.ReadWithFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStatistics 获取统计信息
|
||||||
|
func (m *Manager) GetStatistics() *Statistics {
|
||||||
|
return m.statistics
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush 刷新所有缓冲区
|
||||||
|
func (m *Manager) Flush() error {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
if !m.initialized || m.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新缓冲区
|
||||||
|
if m.config.EnableBuffer {
|
||||||
|
if err := m.flushBuffer(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新写入器
|
||||||
|
return m.writer.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭输出管理器
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止自动刷新
|
||||||
|
if m.flushTicker != nil {
|
||||||
|
m.flushTicker.Stop()
|
||||||
|
close(m.stopChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后一次刷新缓冲区
|
||||||
|
if m.config.EnableBuffer {
|
||||||
|
m.flushBufferUnsafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭写入器
|
||||||
|
var err error
|
||||||
|
if m.writer != nil {
|
||||||
|
err = m.writer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭读取器
|
||||||
|
if m.reader != nil {
|
||||||
|
if closeErr := m.reader.Close(); closeErr != nil && err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.closed = true
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInitialized 检查是否已初始化
|
||||||
|
func (m *Manager) IsInitialized() bool {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
return m.initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClosed 检查是否已关闭
|
||||||
|
func (m *Manager) IsClosed() bool {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
return m.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig 获取配置
|
||||||
|
func (m *Manager) GetConfig() *ManagerConfig {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
// 返回配置副本
|
||||||
|
config := *m.config
|
||||||
|
return &config
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfig 更新配置(部分配置可以动态更新)
|
||||||
|
func (m *Manager) UpdateConfig(updates map[string]interface{}) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.closed {
|
||||||
|
return fmt.Errorf("输出管理器已关闭")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只允许更新部分配置
|
||||||
|
for key, value := range updates {
|
||||||
|
switch key {
|
||||||
|
case "enable_buffer":
|
||||||
|
if enableBuffer, ok := value.(bool); ok {
|
||||||
|
m.config.EnableBuffer = enableBuffer
|
||||||
|
}
|
||||||
|
case "buffer_size":
|
||||||
|
if bufferSize, ok := value.(int); ok && bufferSize > 0 {
|
||||||
|
m.config.BufferSize = bufferSize
|
||||||
|
}
|
||||||
|
case "auto_flush":
|
||||||
|
if autoFlush, ok := value.(bool); ok {
|
||||||
|
m.config.AutoFlush = autoFlush
|
||||||
|
if autoFlush && m.flushTicker == nil {
|
||||||
|
m.startAutoFlush()
|
||||||
|
} else if !autoFlush && m.flushTicker != nil {
|
||||||
|
m.flushTicker.Stop()
|
||||||
|
m.flushTicker = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "flush_interval":
|
||||||
|
if flushInterval, ok := value.(time.Duration); ok && flushInterval > 0 {
|
||||||
|
m.config.FlushInterval = flushInterval
|
||||||
|
if m.flushTicker != nil {
|
||||||
|
m.flushTicker.Stop()
|
||||||
|
m.startAutoFlush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("不支持更新的配置项: %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局输出管理器实例
|
||||||
|
var (
|
||||||
|
globalManager *Manager
|
||||||
|
managerOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetGlobalManager 获取全局输出管理器
|
||||||
|
func GetGlobalManager() *Manager {
|
||||||
|
return globalManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGlobalManager 设置全局输出管理器
|
||||||
|
func SetGlobalManager(manager *Manager) {
|
||||||
|
globalManager = manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitGlobalManager 初始化全局输出管理器
|
||||||
|
func InitGlobalManager(config *ManagerConfig) error {
|
||||||
|
manager, err := NewManager(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
SetGlobalManager(manager)
|
||||||
|
return nil
|
||||||
|
}
|
153
Common/output/Types.go
Normal file
153
Common/output/Types.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package output
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OutputFormat 输出格式类型
|
||||||
|
type OutputFormat string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FormatTXT OutputFormat = "txt" // 文本格式
|
||||||
|
FormatJSON OutputFormat = "json" // JSON格式
|
||||||
|
FormatCSV OutputFormat = "csv" // CSV格式
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResultType 定义结果类型
|
||||||
|
type ResultType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeHost ResultType = "HOST" // 主机存活
|
||||||
|
TypePort ResultType = "PORT" // 端口开放
|
||||||
|
TypeService ResultType = "SERVICE" // 服务识别
|
||||||
|
TypeVuln ResultType = "VULN" // 漏洞发现
|
||||||
|
TypeInfo ResultType = "INFO" // 信息收集
|
||||||
|
TypeBrute ResultType = "BRUTE" // 爆破结果
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScanResult 扫描结果结构
|
||||||
|
type ScanResult struct {
|
||||||
|
Time time.Time `json:"time"` // 发现时间
|
||||||
|
Type ResultType `json:"type"` // 结果类型
|
||||||
|
Target string `json:"target"` // 目标(IP/域名/URL)
|
||||||
|
Status string `json:"status"` // 状态描述
|
||||||
|
Details map[string]interface{} `json:"details"` // 详细信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputWriter 输出写入器接口
|
||||||
|
type OutputWriter interface {
|
||||||
|
Write(result *ScanResult) error
|
||||||
|
WriteHeader() error
|
||||||
|
Flush() error
|
||||||
|
Close() error
|
||||||
|
GetFormat() OutputFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputReader 输出读取器接口
|
||||||
|
type OutputReader interface {
|
||||||
|
Read() ([]*ScanResult, error)
|
||||||
|
ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultFilter 结果过滤器
|
||||||
|
type ResultFilter struct {
|
||||||
|
Types []ResultType `json:"types"` // 过滤的结果类型
|
||||||
|
Targets []string `json:"targets"` // 过滤的目标
|
||||||
|
TimeRange *TimeRange `json:"time_range"` // 时间范围
|
||||||
|
Limit int `json:"limit"` // 限制数量
|
||||||
|
Offset int `json:"offset"` // 偏移量
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeRange 时间范围
|
||||||
|
type TimeRange struct {
|
||||||
|
Start time.Time `json:"start"` // 开始时间
|
||||||
|
End time.Time `json:"end"` // 结束时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManagerConfig 输出管理器配置
|
||||||
|
type ManagerConfig struct {
|
||||||
|
OutputPath string `json:"output_path"` // 输出路径
|
||||||
|
Format OutputFormat `json:"format"` // 输出格式
|
||||||
|
EnableBuffer bool `json:"enable_buffer"` // 是否启用缓冲
|
||||||
|
BufferSize int `json:"buffer_size"` // 缓冲区大小
|
||||||
|
AutoFlush bool `json:"auto_flush"` // 是否自动刷新
|
||||||
|
FlushInterval time.Duration `json:"flush_interval"` // 刷新间隔
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultManagerConfig 默认管理器配置
|
||||||
|
func DefaultManagerConfig(outputPath string, format OutputFormat) *ManagerConfig {
|
||||||
|
return &ManagerConfig{
|
||||||
|
OutputPath: outputPath,
|
||||||
|
Format: format,
|
||||||
|
EnableBuffer: true,
|
||||||
|
BufferSize: 100,
|
||||||
|
AutoFlush: true,
|
||||||
|
FlushInterval: 5 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistics 输出统计信息
|
||||||
|
type Statistics struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
TotalResults int64 `json:"total_results"` // 总结果数
|
||||||
|
TypeCounts map[ResultType]int64 `json:"type_counts"` // 各类型计数
|
||||||
|
StartTime time.Time `json:"start_time"` // 开始时间
|
||||||
|
LastUpdate time.Time `json:"last_update"` // 最后更新时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatistics 创建新的统计信息
|
||||||
|
func NewStatistics() *Statistics {
|
||||||
|
return &Statistics{
|
||||||
|
TypeCounts: make(map[ResultType]int64),
|
||||||
|
StartTime: time.Now(),
|
||||||
|
LastUpdate: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddResult 添加结果统计
|
||||||
|
func (s *Statistics) AddResult(resultType ResultType) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.TotalResults++
|
||||||
|
s.TypeCounts[resultType]++
|
||||||
|
s.LastUpdate = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTotalResults 获取总结果数
|
||||||
|
func (s *Statistics) GetTotalResults() int64 {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.TotalResults
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTypeCounts 获取类型计数
|
||||||
|
func (s *Statistics) GetTypeCounts() map[ResultType]int64 {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
|
// 返回副本以避免并发问题
|
||||||
|
counts := make(map[ResultType]int64)
|
||||||
|
for k, v := range s.TypeCounts {
|
||||||
|
counts[k] = v
|
||||||
|
}
|
||||||
|
return counts
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDuration 获取运行时长
|
||||||
|
func (s *Statistics) GetDuration() time.Duration {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.LastUpdate.Sub(s.StartTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset 重置统计信息
|
||||||
|
func (s *Statistics) Reset() {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.TotalResults = 0
|
||||||
|
s.TypeCounts = make(map[ResultType]int64)
|
||||||
|
s.StartTime = time.Now()
|
||||||
|
s.LastUpdate = time.Now()
|
||||||
|
}
|
461
Common/output/Writers.go
Normal file
461
Common/output/Writers.go
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
package output
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TXTWriter 文本格式写入器
|
||||||
|
type TXTWriter struct {
|
||||||
|
file *os.File
|
||||||
|
mu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTXTWriter 创建文本写入器
|
||||||
|
func NewTXTWriter(filePath string) (*TXTWriter, error) {
|
||||||
|
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建文本文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TXTWriter{
|
||||||
|
file: file,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader 写入头部(文本格式无需头部)
|
||||||
|
func (w *TXTWriter) WriteHeader() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write 写入扫描结果
|
||||||
|
func (w *TXTWriter) Write(result *ScanResult) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return fmt.Errorf("写入器已关闭")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化 Details 为键值对字符串
|
||||||
|
var details string
|
||||||
|
if len(result.Details) > 0 {
|
||||||
|
pairs := make([]string, 0, len(result.Details))
|
||||||
|
for k, v := range result.Details {
|
||||||
|
pairs = append(pairs, fmt.Sprintf("%s=%v", k, v))
|
||||||
|
}
|
||||||
|
details = strings.Join(pairs, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用类似原有格式的文本输出
|
||||||
|
txt := fmt.Sprintf("[%s] [%s] %s - %s",
|
||||||
|
result.Time.Format("2006-01-02 15:04:05"),
|
||||||
|
result.Type,
|
||||||
|
result.Target,
|
||||||
|
result.Status,
|
||||||
|
)
|
||||||
|
if details != "" {
|
||||||
|
txt += fmt.Sprintf(" (%s)", details)
|
||||||
|
}
|
||||||
|
txt += "\n"
|
||||||
|
|
||||||
|
_, err := w.file.WriteString(txt)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush 刷新缓冲区
|
||||||
|
func (w *TXTWriter) Flush() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.file.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭写入器
|
||||||
|
func (w *TXTWriter) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.closed = true
|
||||||
|
return w.file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFormat 获取格式类型
|
||||||
|
func (w *TXTWriter) GetFormat() OutputFormat {
|
||||||
|
return FormatTXT
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONWriter JSON格式写入器
|
||||||
|
type JSONWriter struct {
|
||||||
|
file *os.File
|
||||||
|
encoder *json.Encoder
|
||||||
|
mu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSONWriter 创建JSON写入器
|
||||||
|
func NewJSONWriter(filePath string) (*JSONWriter, error) {
|
||||||
|
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建JSON文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(file)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
|
||||||
|
return &JSONWriter{
|
||||||
|
file: file,
|
||||||
|
encoder: encoder,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader 写入头部(JSON格式无需头部)
|
||||||
|
func (w *JSONWriter) WriteHeader() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write 写入扫描结果
|
||||||
|
func (w *JSONWriter) Write(result *ScanResult) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return fmt.Errorf("写入器已关闭")
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.encoder.Encode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush 刷新缓冲区
|
||||||
|
func (w *JSONWriter) Flush() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.file.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭写入器
|
||||||
|
func (w *JSONWriter) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.closed = true
|
||||||
|
return w.file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFormat 获取格式类型
|
||||||
|
func (w *JSONWriter) GetFormat() OutputFormat {
|
||||||
|
return FormatJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSVWriter CSV格式写入器
|
||||||
|
type CSVWriter struct {
|
||||||
|
file *os.File
|
||||||
|
csvWriter *csv.Writer
|
||||||
|
mu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
headerWritten bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCSVWriter 创建CSV写入器
|
||||||
|
func NewCSVWriter(filePath string) (*CSVWriter, error) {
|
||||||
|
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建CSV文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
csvWriter := csv.NewWriter(file)
|
||||||
|
|
||||||
|
return &CSVWriter{
|
||||||
|
file: file,
|
||||||
|
csvWriter: csvWriter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader 写入CSV头部
|
||||||
|
func (w *CSVWriter) WriteHeader() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.headerWritten {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"Time", "Type", "Target", "Status", "Details"}
|
||||||
|
err := w.csvWriter.Write(headers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("写入CSV头部失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.csvWriter.Flush()
|
||||||
|
w.headerWritten = true
|
||||||
|
return w.csvWriter.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write 写入扫描结果
|
||||||
|
func (w *CSVWriter) Write(result *ScanResult) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return fmt.Errorf("写入器已关闭")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保头部已写入
|
||||||
|
if !w.headerWritten {
|
||||||
|
if err := w.writeHeaderUnsafe(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化Details为JSON字符串
|
||||||
|
details, err := json.Marshal(result.Details)
|
||||||
|
if err != nil {
|
||||||
|
details = []byte("{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
record := []string{
|
||||||
|
result.Time.Format("2006-01-02 15:04:05"),
|
||||||
|
string(result.Type),
|
||||||
|
result.Target,
|
||||||
|
result.Status,
|
||||||
|
string(details),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.csvWriter.Write(record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.csvWriter.Flush()
|
||||||
|
return w.csvWriter.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeHeaderUnsafe 不安全的写入头部(内部使用,无锁)
|
||||||
|
func (w *CSVWriter) writeHeaderUnsafe() error {
|
||||||
|
if w.headerWritten {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"Time", "Type", "Target", "Status", "Details"}
|
||||||
|
err := w.csvWriter.Write(headers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("写入CSV头部失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.csvWriter.Flush()
|
||||||
|
w.headerWritten = true
|
||||||
|
return w.csvWriter.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush 刷新缓冲区
|
||||||
|
func (w *CSVWriter) Flush() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.csvWriter.Flush()
|
||||||
|
return w.csvWriter.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭写入器
|
||||||
|
func (w *CSVWriter) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.csvWriter.Flush()
|
||||||
|
err := w.csvWriter.Error()
|
||||||
|
w.closed = true
|
||||||
|
|
||||||
|
if fileErr := w.file.Close(); fileErr != nil && err == nil {
|
||||||
|
err = fileErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFormat 获取格式类型
|
||||||
|
func (w *CSVWriter) GetFormat() OutputFormat {
|
||||||
|
return FormatCSV
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSVReader CSV格式读取器
|
||||||
|
type CSVReader struct {
|
||||||
|
filePath string
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCSVReader 创建CSV读取器
|
||||||
|
func NewCSVReader(filePath string) *CSVReader {
|
||||||
|
return &CSVReader{
|
||||||
|
filePath: filePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read 读取所有结果
|
||||||
|
func (r *CSVReader) Read() ([]*ScanResult, error) {
|
||||||
|
return r.ReadWithFilter(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadWithFilter 带过滤条件读取结果
|
||||||
|
func (r *CSVReader) ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
file, err := os.Open(r.filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("打开CSV文件失败: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
reader := csv.NewReader(file)
|
||||||
|
records, err := reader.ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取CSV文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []*ScanResult
|
||||||
|
for i, row := range records {
|
||||||
|
// 跳过CSV头部
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(row) < 5 {
|
||||||
|
continue // 数据不完整
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := r.parseCSVRow(row)
|
||||||
|
if err != nil {
|
||||||
|
continue // 跳过解析失败的行
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用过滤器
|
||||||
|
if filter != nil && !r.matchFilter(result, filter) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, result)
|
||||||
|
|
||||||
|
// 应用限制
|
||||||
|
if filter != nil && filter.Limit > 0 && len(results) >= filter.Limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCSVRow 解析CSV行
|
||||||
|
func (r *CSVReader) parseCSVRow(row []string) (*ScanResult, error) {
|
||||||
|
// 解析时间
|
||||||
|
t, err := parseTime(row[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析Details
|
||||||
|
var details map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(row[4]), &details); err != nil {
|
||||||
|
details = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ScanResult{
|
||||||
|
Time: t,
|
||||||
|
Type: ResultType(row[1]),
|
||||||
|
Target: row[2],
|
||||||
|
Status: row[3],
|
||||||
|
Details: details,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchFilter 检查结果是否匹配过滤器
|
||||||
|
func (r *CSVReader) matchFilter(result *ScanResult, filter *ResultFilter) bool {
|
||||||
|
// 检查类型过滤
|
||||||
|
if len(filter.Types) > 0 {
|
||||||
|
found := false
|
||||||
|
for _, t := range filter.Types {
|
||||||
|
if result.Type == t {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查目标过滤
|
||||||
|
if len(filter.Targets) > 0 {
|
||||||
|
found := false
|
||||||
|
for _, target := range filter.Targets {
|
||||||
|
if strings.Contains(result.Target, target) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查时间范围过滤
|
||||||
|
if filter.TimeRange != nil {
|
||||||
|
if result.Time.Before(filter.TimeRange.Start) || result.Time.After(filter.TimeRange.End) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭读取器
|
||||||
|
func (r *CSVReader) Close() error {
|
||||||
|
return nil // CSV读取器无需特殊关闭操作
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTime 解析时间字符串
|
||||||
|
func parseTime(timeStr string) (time.Time, error) {
|
||||||
|
// 尝试多种时间格式
|
||||||
|
formats := []string{
|
||||||
|
"2006-01-02 15:04:05",
|
||||||
|
"2006-01-02T15:04:05Z07:00",
|
||||||
|
"2006-01-02T15:04:05.000Z07:00",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, format := range formats {
|
||||||
|
if t, err := time.Parse(format, timeStr); err == nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{}, fmt.Errorf("无法解析时间: %s", timeStr)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user