實現一個功能完整的實時日誌分析工具。這個工具可以實時監控日誌文件、分析日誌內容並提供統計信息。
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"
}
}
功能特性
- 實時監控:使用 fsnotify 庫監控日誌文件變化
- 關鍵詞統計:統計特定關鍵詞出現次數
- 錯誤檢測:支持正則表達式模式匹配錯誤
- 警報系統:檢測到錯誤時發送警報
- 統計報告:定期輸出統計信息
- 響應時間分析:提取和分析響應時間數據
- 配置管理:支持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界面、數據庫存儲、郵件通知等功能。