實現一個功能完整的實時日誌分析工具。這個工具可以實時監控日誌文件、分析日誌內容並提供統計信息。

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
)

// 日誌分析配置
type LogAnalyzerConfig struct {
	LogFile        string            `json:"log_file"`
	Keywords       []string          `json:"keywords"`
	ErrorPatterns  []string          `json:"error_patterns"`
	AlertThreshold int               `json:"alert_threshold"`
	Filters        map[string]string `json:"filters"`
}

// 日誌統計信息
type LogStats struct {
	TotalLines     int64
	ErrorCount     int64
	WarningCount   int64
	KeywordCounts  map[string]int64
	ResponseTimes  []float64
	LastUpdate     time.Time
	mu             sync.RWMutex
}

// 日誌分析器
type LogAnalyzer struct {
	config     *LogAnalyzerConfig
	stats      *LogStats
	watcher    *fsnotify.Watcher
	patterns   []*regexp.Regexp
	alerts     chan string
	done       chan bool
}

// 日誌條目
type LogEntry struct {
	Timestamp string `json:"timestamp"`
	Level     string `json:"level"`
	Message   string `json:"message"`
	Source    string `json:"source"`
}

// 新建日誌分析器
func NewLogAnalyzer(config *LogAnalyzerConfig) (*LogAnalyzer, error) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, fmt.Errorf("創建文件監視器失敗: %v", err)
	}

	// 編譯正則表達式模式
	var patterns []*regexp.Regexp
	for _, pattern := range config.ErrorPatterns {
		re, err := regexp.Compile(pattern)
		if err != nil {
			return nil, fmt.Errorf("編譯正則表達式失敗 %s: %v", pattern, err)
		}
		patterns = append(patterns, re)
	}

	return &LogAnalyzer{
		config:   config,
		stats: &LogStats{
			KeywordCounts: make(map[string]int64),
			ResponseTimes: make([]float64, 0),
		},
		watcher:  watcher,
		patterns: patterns,
		alerts:   make(chan string, 100),
		done:     make(chan bool),
	}, nil
}

// 啓動日誌分析
func (la *LogAnalyzer) Start() error {
	// 添加文件監視
	err := la.watcher.Add(la.config.LogFile)
	if err != nil {
		return fmt.Errorf("添加文件監視失敗: %v", err)
	}

	// 讀取現有日誌內容
	la.processExistingLogs()

	// 啓動監控goroutine
	go la.monitorLogFile()
	go la.processAlerts()
	go la.periodicStatsReport()

	log.Printf("開始監控日誌文件: %s", la.config.LogFile)
	return nil
}

// 處理現有日誌
func (la *LogAnalyzer) processExistingLogs() {
	file, err := os.Open(la.config.LogFile)
	if err != nil {
		log.Printf("打開日誌文件失敗: %v", err)
		return
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		la.processLine(scanner.Text())
	}
}

// 監控日誌文件變化
func (la *LogAnalyzer) monitorLogFile() {
	for {
		select {
		case event, ok := <-la.watcher.Events:
			if !ok {
				return
			}
			if event.Op&fsnotify.Write == fsnotify.Write {
				la.handleFileWrite(event.Name)
			}
		case err, ok := <-la.watcher.Errors:
			if !ok {
				return
			}
			log.Printf("文件監視錯誤: %v", err)
		case <-la.done:
			return
		}
	}
}

// 處理文件寫入事件
func (la *LogAnalyzer) handleFileWrite(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		log.Printf("打開文件失敗: %v", err)
		return
	}
	defer file.Close()

	// 移動到上次讀取的位置(簡化處理,實際應該記錄位置)
	file.Seek(0, 2) // 移動到文件末尾

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		la.processLine(scanner.Text())
	}
}

// 處理單行日誌
func (la *LogAnalyzer) processLine(line string) {
	la.stats.mu.Lock()
	defer la.stats.mu.Unlock()

	la.stats.TotalLines++
	la.stats.LastUpdate = time.Now()

	// 分析日誌級別
	if strings.Contains(strings.ToLower(line), "error") {
		la.stats.ErrorCount++
	} else if strings.Contains(strings.ToLower(line), "warning") {
		la.stats.WarningCount++
	}

	// 關鍵詞統計
	for _, keyword := range la.config.Keywords {
		if strings.Contains(line, keyword) {
			la.stats.KeywordCounts[keyword]++
		}
	}

	// 錯誤模式匹配
	for _, pattern := range la.patterns {
		if pattern.MatchString(line) {
			// 發送警報
			select {
			case la.alerts <- fmt.Sprintf("檢測到錯誤模式: %s", line):
			default:
				// 防止阻塞,如果通道滿則丟棄警報
			}
			break
		}
	}

	// 提取響應時間(假設日誌中包含響應時間)
	if strings.Contains(line, "response_time") {
		if rt := extractResponseTime(line); rt > 0 {
			la.stats.ResponseTimes = append(la.stats.ResponseTimes, rt)
			// 只保留最近100個響應時間
			if len(la.stats.ResponseTimes) > 100 {
				la.stats.ResponseTimes = la.stats.ResponseTimes[1:]
			}
		}
	}
}

// 提取響應時間
func extractResponseTime(line string) float64 {
	re := regexp.MustCompile(`response_time[:=]?\s*([0-9.]+)`)
	matches := re.FindStringSubmatch(line)
	if len(matches) > 1 {
		var rt float64
		_, err := fmt.Sscanf(matches[1], "%f", &rt)
		if err == nil {
			return rt
		}
	}
	return 0
}

// 處理警報
func (la *LogAnalyzer) processAlerts() {
	for alert := range la.alerts {
		log.Printf("🚨 警報: %s", alert)
	}
}

// 定期報告統計信息
func (la *LogAnalyzer) periodicStatsReport() {
	ticker := time.NewTicker(30 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			la.printStats()
		case <-la.done:
			return
		}
	}
}

// 打印統計信息
func (la *LogAnalyzer) printStats() {
	la.stats.mu.RLock()
	defer la.stats.mu.RUnlock()

	fmt.Printf("\n=== 日誌統計報告 (%s) ===\n", time.Now().Format("2006-01-02 15:04:05"))
	fmt.Printf("總行數: %d\n", la.stats.TotalLines)
	fmt.Printf("錯誤數: %d\n", la.stats.ErrorCount)
	fmt.Printf("警告數: %d\n", la.stats.WarningCount)
	fmt.Printf("關鍵詞統計:\n")
	for keyword, count := range la.stats.KeywordCounts {
		fmt.Printf("  %s: %d\n", keyword, count)
	}

	if len(la.stats.ResponseTimes) > 0 {
		avgTime := calculateAverage(la.stats.ResponseTimes)
		fmt.Printf("平均響應時間: %.2fms\n", avgTime)
	}
	fmt.Println("================================")
}

// 計算平均值
func calculateAverage(numbers []float64) float64 {
	if len(numbers) == 0 {
		return 0
	}
	var sum float64
	for _, num := range numbers {
		sum += num
	}
	return sum / float64(len(numbers))
}

// 獲取當前統計信息
func (la *LogAnalyzer) GetStats() LogStats {
	la.stats.mu.RLock()
	defer la.stats.mu.RUnlock()
	return *la.stats
}

// 停止分析器
func (la *LogAnalyzer) Stop() {
	close(la.done)
	la.watcher.Close()
	close(la.alerts)
}

// 創建示例配置文件
func createSampleConfig() *LogAnalyzerConfig {
	return &LogAnalyzerConfig{
		LogFile: "./app.log",
		Keywords: []string{
			"user_login",
			"database",
			"api_call",
			"cache",
		},
		ErrorPatterns: []string{
			`(?i)error:.*`,
			`(?i)failed to.*`,
			`(?i)timeout`,
			`5\d{2}\s+HTTP`,
		},
		AlertThreshold: 10,
		Filters: map[string]string{
			"level":    "ERROR|WARN",
			"source":   "app",
		},
	}
}

// 保存配置到文件
func saveConfig(config *LogAnalyzerConfig, filename string) error {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	encoder := json.NewEncoder(file)
	encoder.SetIndent("", "  ")
	return encoder.Encode(config)
}

// 從文件加載配置
func loadConfig(filename string) (*LogAnalyzerConfig, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var config LogAnalyzerConfig
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&config)
	if err != nil {
		return nil, err
	}

	return &config, nil
}

func main() {
	// 檢查命令行參數
	if len(os.Args) < 2 {
		fmt.Printf("使用方法: %s <日誌文件> [配置文件]\n", filepath.Base(os.Args[0]))
		fmt.Println("示例:")
		fmt.Printf("  %s /var/log/app.log\n", filepath.Base(os.Args[0]))
		fmt.Printf("  %s /var/log/app.log config.json\n", filepath.Base(os.Args[0]))
		
		// 創建示例配置文件
		config := createSampleConfig()
		if err := saveConfig(config, "config.example.json"); err != nil {
			log.Printf("創建示例配置文件失敗: %v", err)
		} else {
			fmt.Println("已創建示例配置文件: config.example.json")
		}
		return
	}

	logFile := os.Args[1]
	var config *LogAnalyzerConfig

	if len(os.Args) > 2 {
		// 加載配置文件
		var err error
		config, err = loadConfig(os.Args[2])
		if err != nil {
			log.Printf("加載配置文件失敗: %v,使用默認配置", err)
			config = createSampleConfig()
		}
	} else {
		config = createSampleConfig()
	}

	config.LogFile = logFile

	// 創建日誌分析器
	analyzer, err := NewLogAnalyzer(config)
	if err != nil {
		log.Fatalf("創建日誌分析器失敗: %v", err)
	}

	// 啓動分析器
	err = analyzer.Start()
	if err != nil {
		log.Fatalf("啓動日誌分析器失敗: %v", err)
	}

	// 等待中斷信號
	fmt.Println("日誌分析器運行中... 按 Ctrl+C 停止")

	// 設置信號處理
	signalCh := make(chan os.Signal, 1)
	// 在實際應用中應該使用 signal.Notify

	// 簡單的控制枱交互
	go func() {
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			text := scanner.Text()
			if text == "stats" {
				analyzer.printStats()
			} else if text == "quit" {
				break
			}
		}
		signalCh <- os.Interrupt
	}()

	<-signalCh
	analyzer.Stop()
	fmt.Println("日誌分析器已停止")
}

還需要創建 go.mod 文件來管理依賴:

module log-analyzer

go 1.19

require github.com/fsnotify/fsnotify v1.6.0

以及一個示例配置文件 config.example.json

{
  "log_file": "./app.log",
  "keywords": [
    "user_login",
    "database",
    "api_call",
    "cache"
  ],
  "error_patterns": [
    "(?i)error:.*",
    "(?i)failed to.*",
    "(?i)timeout",
    "5\\d{2}\\s+HTTP"
  ],
  "alert_threshold": 10,
  "filters": {
    "level": "ERROR|WARN",
    "source": "app"
  }
}

功能特性

  1. 實時監控:使用 fsnotify 庫監控日誌文件變化
  2. 關鍵詞統計:統計特定關鍵詞出現次數
  3. 錯誤檢測:支持正則表達式模式匹配錯誤
  4. 警報系統:檢測到錯誤時發送警報
  5. 統計報告:定期輸出統計信息
  6. 響應時間分析:提取和分析響應時間數據
  7. 配置管理:支持JSON配置文件

使用方法

# 安裝依賴
go mod tidy

# 運行分析器
go run main.go /path/to/your/logfile.log

# 使用自定義配置
go run main.go /path/to/your/logfile.log config.json

# 構建可執行文件
go build -o log-analyzer main.go

交互命令

  • 輸入 stats 查看當前統計信息
  • 輸入 quit 退出程序

這個工具可以根據你的具體需求進行擴展,比如添加Web界面、數據庫存儲、郵件通知等功能。