File size: 4,699 Bytes
2196bfe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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)
}