package services import ( "crypto/tls" "log" "net/http" "strings" "time" "uptime/backend/database" "uptime/backend/models" ) var DeletedMonitorIDs = make(chan uint, 100) func StartMonitoring() { log.Printf("[INFO] Monitoring service started at %v", time.Now()) ticker := time.NewTicker(10 * time.Second) // Check for new monitors every 10 seconds monitorTimers := make(map[uint]*time.Timer) for { select { case <-ticker.C: var monitors []models.Monitor database.DB.Find(&monitors) for i := range monitors { monitor := &monitors[i] // Skip if timer already exists for this monitor if _, exists := monitorTimers[monitor.ID]; exists { continue } // Create individual timer for each monitor based on its interval interval := time.Duration(monitor.Interval) * time.Second log.Printf("[INFO] Creating timer for monitor ID=%d Name=%s Interval=%v", monitor.ID, monitor.Name, interval) monitorTimers[monitor.ID] = time.AfterFunc(0, func() { scheduleMonitorCheck(monitor, monitorTimers, interval) }) } case id := <-DeletedMonitorIDs: if timer, exists := monitorTimers[id]; exists { timer.Stop() delete(monitorTimers, id) log.Printf("[INFO] Stopped and removed timer for monitor ID=%d", id) } } } } func scheduleMonitorCheck(monitor *models.Monitor, timers map[uint]*time.Timer, interval time.Duration) { // Add a delay before the check if specified if monitor.Delay > 0 { delay := time.Duration(monitor.Delay) * time.Second log.Printf("[INFO] Monitor ID=%d Name=%s: Delaying check by %v", monitor.ID, monitor.Name, delay) time.Sleep(delay) } checkMonitorWithRetries(monitor) // Schedule next check timers[monitor.ID] = time.AfterFunc(interval, func() { scheduleMonitorCheck(monitor, timers, interval) }) } func checkMonitorWithRetries(monitor *models.Monitor) { maxRetries := int(monitor.Retries) retryInterval := time.Duration(monitor.RetryInterval) * time.Second for attempt := 0; attempt <= maxRetries; attempt++ { success := checkMonitor(monitor) if success || attempt == maxRetries { break } if attempt < maxRetries { log.Printf("[WARN] Monitor ID=%d Name=%s failed (attempt %d/%d), retrying in %v", monitor.ID, monitor.Name, attempt+1, maxRetries+1, retryInterval) time.Sleep(retryInterval) } } } func checkMonitor(monitor *models.Monitor) bool { client := &http.Client{ Timeout: time.Duration(monitor.Timeout) * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: monitor.IgnoreTLS}, }, } req, err := http.NewRequest(monitor.HttpMethod, monitor.URL, strings.NewReader(monitor.HttpBody)) if err != nil { log.Printf("[ERROR] Monitor ID=%d Name=%s: Error creating request: %s", monitor.ID, monitor.Name, err.Error()) updateMonitorStatus(monitor, "down") return false } // Add headers // For simplicity, assuming headers are in "Key:Value\nKey2:Value2" format if monitor.HttpHeaders != "" { headers := strings.Split(monitor.HttpHeaders, "\n") for _, header := range headers { parts := strings.SplitN(header, ":", 2) if len(parts) == 2 { req.Header.Set(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) } } } // Add basic auth if monitor.AuthMethod == "basic" && monitor.AuthUsername != "" { req.SetBasicAuth(monitor.AuthUsername, monitor.AuthPassword) } start := time.Now() resp, err := client.Do(req) latency := time.Since(start).Milliseconds() if err != nil { log.Printf("[ERROR] Monitor ID=%d Name=%s: HTTP request failed: %s", monitor.ID, monitor.Name, err.Error()) updateMonitorStatus(monitor, "down") return false } defer resp.Body.Close() // Store latency latencyRecord := models.Latency{MonitorID: monitor.ID, Latency: uint(latency)} database.DB.Create(&latencyRecord) if resp.StatusCode >= 200 && resp.StatusCode < 300 { log.Printf("[INFO] Monitor ID=%d Name=%s: Status UP (HTTP %d), Latency: %dms", monitor.ID, monitor.Name, resp.StatusCode, latency) updateMonitorStatus(monitor, "up") return true } else { log.Printf("[WARN] Monitor ID=%d Name=%s: Status DOWN (HTTP %d), Latency: %dms", monitor.ID, monitor.Name, resp.StatusCode, latency) updateMonitorStatus(monitor, "down") return false } } func updateMonitorStatus(monitor *models.Monitor, status string) { oldStatus := monitor.Status monitor.Status = status database.DB.Save(monitor) log.Printf("[INFO] Monitor ID=%d Name=%s: Status changed from %s to %s", monitor.ID, monitor.Name, oldStatus, status) }