package log_watcher import ( "bufio" "encoding/json" "log" "os" "path/filepath" "github.com/Vomitblood/cspj-application/server/internal/config" "github.com/Vomitblood/cspj-application/server/internal/telegram" "github.com/fsnotify/fsnotify" tg "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) var lastReadPosition int64 = 0 type LogEntry struct { AuditData struct { Messages []string `json:"messages"` } `json:"audit_data"` } func WatchFile(bot *tg.BotAPI) { modsecLogFile := filepath.Join(config.LogDirectory, "host-fs-auditlog.log") log.Println("Watching logfile directory:", config.LogDirectory) watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal("Failed to initialize watcher: ", err) } defer watcher.Close() // add log file to watcher err = watcher.Add(modsecLogFile) if err != nil { log.Fatal("Failed to watch log file: ", err) } log.Println("Monitoring log file for changes...") for { select { case event, ok := <-watcher.Events: if !ok { return } if event.Op&fsnotify.Write == fsnotify.Write { log.Println("Log file updated, reading new entries...") readNewLines(bot) } case err, ok := <-watcher.Errors: if !ok { return } log.Println("Watcher error: ", err) } } } func readNewLines(bot *tg.BotAPI) { modsecLogFile := filepath.Join(config.LogDirectory, "host-fs-auditlog.log") file, err := os.Open(modsecLogFile) if err != nil { log.Println("Failed to reopen log file: ", err) return } defer file.Close() // move to the last read position file.Seek(lastReadPosition, os.SEEK_SET) scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() var logEntry LogEntry // try to parse json if err := json.Unmarshal([]byte(line), &logEntry); err != nil { log.Println("⚠️ Failed to parse JSON: ", err) // skip invalid json lines // very crucial as modsecurity does not respect the json spec continue } // send index 0 element will do if len(logEntry.AuditData.Messages) > 0 { telegram.SendTelegramAlert(bot, logEntry.AuditData.Messages[0]) } } // update last read position lastReadPosition, _ = file.Seek(0, os.SEEK_CUR) if err := scanner.Err(); err != nil { log.Println("Error reading log file: ", err) } }