package monitor import ( "fmt" "io" "log" "net/http" "strings" "time" "gitea.dooplex.hu/admin/felhom-controller/internal/config" ) // Pinger sends health check pings to a Healthchecks.io-compatible server. type Pinger struct { baseURL string httpClient *http.Client logger *log.Logger enabled bool debug bool } // NewPinger creates a new Pinger from monitoring config. func NewPinger(cfg *config.MonitoringConfig, logger *log.Logger) *Pinger { return &Pinger{ baseURL: strings.TrimRight(cfg.HealthchecksBase, "/"), httpClient: &http.Client{ Timeout: 10 * time.Second, }, logger: logger, enabled: cfg.Enabled, } } // SetDebug enables or disables debug logging for the pinger. func (p *Pinger) SetDebug(debug bool) { p.debug = debug } // Ping sends a success signal with optional diagnostic body. func (p *Pinger) Ping(uuid string, body string) error { if p.debug { p.logger.Printf("[DEBUG] [pinger] Ping uuid=%s body_len=%d", uuid, len(body)) } return p.send(uuid, "", body) } // Fail sends a failure signal with diagnostic body. func (p *Pinger) Fail(uuid string, body string) error { if p.debug { p.logger.Printf("[DEBUG] [pinger] Fail uuid=%s body=%q", uuid, body) } return p.send(uuid, "/fail", body) } // Start sends a "job started" signal (for duration tracking). func (p *Pinger) Start(uuid string) error { if p.debug { p.logger.Printf("[DEBUG] [pinger] Start uuid=%s", uuid) } return p.send(uuid, "/start", "") } func (p *Pinger) send(uuid, suffix, body string) error { if !p.enabled { return nil } if uuid == "" || strings.HasPrefix(uuid, "CHANGEME") { return nil } url := fmt.Sprintf("%s/ping/%s%s", p.baseURL, uuid, suffix) if p.debug { p.logger.Printf("[DEBUG] [pinger] send url=%s", url) } var lastErr error for attempt := 0; attempt < 3; attempt++ { if attempt > 0 { if p.debug { p.logger.Printf("[DEBUG] [pinger] retry attempt=%d uuid=%s", attempt+1, uuid) } time.Sleep(2 * time.Second) } var bodyReader io.Reader if body != "" { bodyReader = strings.NewReader(body) } req, err := http.NewRequest(http.MethodPost, url, bodyReader) if err != nil { lastErr = err continue } resp, err := p.httpClient.Do(req) if err != nil { lastErr = err continue } resp.Body.Close() if p.debug { p.logger.Printf("[DEBUG] [pinger] response status=%d uuid=%s", resp.StatusCode, uuid) } if resp.StatusCode >= 200 && resp.StatusCode < 300 { if p.debug { p.logger.Printf("[DEBUG] [pinger] success uuid=%s", uuid) } return nil } lastErr = fmt.Errorf("HTTP %d", resp.StatusCode) } p.logger.Printf("[WARN] [monitor] Health ping failed after 3 attempts (%s): %v", uuid, lastErr) return nil // Never let ping failures affect the caller }