mirror of
https://github.com/airlabspl/uptimemonitor.git
synced 2025-08-15 04:39:15 +02:00
initial commit
This commit is contained in:
258
service/check_service.go
Normal file
258
service/check_service.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/store"
|
||||
)
|
||||
|
||||
type CheckService struct {
|
||||
Store *store.Store
|
||||
}
|
||||
|
||||
func (s *CheckService) StartCheck() chan uptimemonitor.Monitor {
|
||||
ch := make(chan uptimemonitor.Monitor, 10)
|
||||
|
||||
go func() {
|
||||
for m := range ch {
|
||||
s.handleCheck(m)
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (s *CheckService) RunCheck(ctx context.Context, ch chan uptimemonitor.Monitor) error {
|
||||
monitors, err := s.Store.ListMonitors(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, m := range monitors {
|
||||
ch <- m
|
||||
}
|
||||
|
||||
go func() {
|
||||
s.Cleanup()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CheckService) handleCheck(m uptimemonitor.Monitor) {
|
||||
c, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
start := time.Now()
|
||||
var customBody io.Reader
|
||||
|
||||
if m.HttpBody != "" {
|
||||
customBody = strings.NewReader(m.HttpBody)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(
|
||||
m.HttpMethod,
|
||||
m.Url,
|
||||
customBody,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if m.HttpHeaders != "" {
|
||||
customHeaders := map[string]string{}
|
||||
err = json.Unmarshal([]byte(m.HttpHeaders), &customHeaders)
|
||||
if err == nil {
|
||||
for k, v := range customHeaders {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: add timeout
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
check, err := s.Store.CreateCheck(c, uptimemonitor.Check{
|
||||
MonitorID: m.ID,
|
||||
Monitor: m,
|
||||
StatusCode: http.StatusNotFound,
|
||||
ResponseTimeMs: elapsed.Milliseconds(),
|
||||
})
|
||||
|
||||
if err != nil || check.ID == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.createIncident(m, check, elapsed.Milliseconds(), http.StatusNotFound, "", ""); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
statusCode := res.StatusCode
|
||||
if res.Request.Response != nil {
|
||||
statusCode = res.Request.Response.StatusCode
|
||||
}
|
||||
|
||||
check, err := s.Store.CreateCheck(c, uptimemonitor.Check{
|
||||
MonitorID: m.ID,
|
||||
Monitor: m,
|
||||
StatusCode: statusCode,
|
||||
ResponseTimeMs: elapsed.Milliseconds(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if statusCode < 300 {
|
||||
open, err := s.Store.ListMonitorOpenIncidents(c, m.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, i := range open {
|
||||
s.Store.ResolveIncident(c, i)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var body string
|
||||
var headers string
|
||||
|
||||
if res.Request.Response != nil {
|
||||
var resHeaders string
|
||||
for k, v := range res.Request.Response.Header {
|
||||
resHeaders += fmt.Sprintf("%s: %s\n", k, v)
|
||||
}
|
||||
|
||||
resBody, _ := io.ReadAll(res.Request.Response.Body)
|
||||
defer res.Request.Response.Body.Close()
|
||||
body = string(resBody)
|
||||
headers = resHeaders
|
||||
} else {
|
||||
var resHeaders string
|
||||
for k, v := range res.Header {
|
||||
resHeaders += fmt.Sprintf("%s: %s\n", k, v)
|
||||
}
|
||||
|
||||
resBody, _ := io.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
body = string(resBody)
|
||||
headers = resHeaders
|
||||
}
|
||||
|
||||
s.createIncident(m, check, elapsed.Milliseconds(), statusCode, string(body), headers)
|
||||
}
|
||||
|
||||
func (s *CheckService) createIncident(m uptimemonitor.Monitor, check uptimemonitor.Check, responseTimeMs int64, statusCode int, body string, headers string) error {
|
||||
reqURL := m.Url
|
||||
reqMethod := m.HttpMethod
|
||||
reqHeaders := m.HttpHeaders
|
||||
reqBody := m.HttpBody
|
||||
|
||||
if exists, latest := s.incidentAlreadyExists(context.Background(), m, statusCode); exists {
|
||||
return s.Store.UpdateIncidentBodyAndHeaders(context.Background(), latest, body, headers, reqMethod, reqURL, reqHeaders, reqBody)
|
||||
}
|
||||
|
||||
s.Store.ResolveMonitorIncidents(context.Background(), m)
|
||||
|
||||
incident := uptimemonitor.Incident{
|
||||
MonitorID: m.ID,
|
||||
StatusCode: check.StatusCode,
|
||||
ResponseTimeMs: responseTimeMs,
|
||||
Body: body,
|
||||
Headers: headers,
|
||||
ReqUrl: reqURL,
|
||||
ReqHeaders: reqHeaders,
|
||||
ReqBody: reqBody,
|
||||
ReqMethod: reqMethod,
|
||||
}
|
||||
|
||||
saved, err := s.Store.CreateIncident(context.Background(), incident)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create incident for monitor %d: %w", m.ID, err)
|
||||
}
|
||||
|
||||
if m.WebhookUrl != "" {
|
||||
s.callWebhook(m, saved)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CheckService) incidentAlreadyExists(ctx context.Context, m uptimemonitor.Monitor, statusCode int) (bool, uptimemonitor.Incident) {
|
||||
latest, err := s.Store.LastIncidentByStatusCode(ctx, m.ID, uptimemonitor.IncidentStatusOpen, statusCode)
|
||||
if err != nil {
|
||||
return false, uptimemonitor.Incident{}
|
||||
}
|
||||
|
||||
return latest.StatusCode == statusCode, latest
|
||||
}
|
||||
|
||||
func (s *CheckService) callWebhook(m uptimemonitor.Monitor, i uptimemonitor.Incident) {
|
||||
var customBody io.Reader
|
||||
|
||||
// todo: parse url
|
||||
if m.WebhookBody != "" {
|
||||
t, err := template.New("webhook").Parse(m.WebhookBody)
|
||||
if err != nil {
|
||||
customBody = strings.NewReader(m.WebhookBody)
|
||||
} else {
|
||||
var buf bytes.Buffer
|
||||
err = t.Execute(&buf, struct {
|
||||
Url string
|
||||
StatusCode int
|
||||
}{
|
||||
Url: m.Url,
|
||||
StatusCode: i.StatusCode,
|
||||
})
|
||||
if err != nil {
|
||||
customBody = strings.NewReader(m.WebhookBody)
|
||||
} else {
|
||||
customBody = strings.NewReader(buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(
|
||||
m.WebhookMethod,
|
||||
m.WebhookUrl,
|
||||
customBody,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if m.WebhookHeaders != "" {
|
||||
customHeaders := map[string]string{}
|
||||
err = json.Unmarshal([]byte(m.WebhookHeaders), &customHeaders)
|
||||
if err == nil {
|
||||
for k, v := range customHeaders {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
func (s *CheckService) Cleanup() {
|
||||
s.Store.DeleteOldChecks(context.Background())
|
||||
s.Store.DeleteOldIncidents(context.Background())
|
||||
}
|
Reference in New Issue
Block a user