package log_watcher import ( "bufio" "encoding/json" "log" "os" "github.com/Vomitblood/cspj-application/server/internal/telegram" "github.com/fsnotify/fsnotify" tg "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) const ( // TODO: add config for user to specify log location modsecLogFile = "/home/vomitblood/build/cspj-application/docker/chungus/logs/host-fs-auditlog.log" ) var lastReadPosition int64 = 0 type LogEntry struct { AuditData struct { Messages []string `json:"messages"` } `json:"audit_data"` } func watchLogFile(bot *tg.BotAPI) { 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) { 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) } }