package service import ( "context" "errors" "fmt" "net/http" "time" "git.ostiwe.com/ostiwe-com/status/model" "git.ostiwe.com/ostiwe-com/status/modules/log" "git.ostiwe.com/ostiwe-com/status/modules/scheduler" "git.ostiwe.com/ostiwe-com/status/repository" "github.com/go-co-op/gocron/v2" "github.com/samber/lo" "github.com/sirupsen/logrus" ) var ( httpObserveFailed = errors.New("http observe fail") httpObserveMaxTries = errors.New("http observe max tries") GlobalCheckService Check ) func init() { log.Global.Put(log.CRON, logrus.New()) log.Global.Put(log.Observer, logrus.New()) } type Check interface { Observe(ctx context.Context, srv *model.Service) error RegisterStatus(ctx context.Context, serviceID uint64, status model.StatusCode, meta ResponseMeta) error RegisterTasks(ctx context.Context) error } type check struct { serviceRepository repository.Service statusRepository repository.Status appScheduler scheduler.AppScheduler } func NewCheck(appScheduler scheduler.AppScheduler) Check { return &check{ serviceRepository: repository.NewServiceRepository(), statusRepository: repository.NewStatusRepository(), appScheduler: appScheduler, } } func (c *check) RegisterTasks(ctx context.Context) error { services, err := c.serviceRepository.All(ctx, -1, 0, false) if err != nil { return err } for _, service := range services { job, jobCreateErr := c.appScheduler.NewServiceJob( service.ID, gocron.DurationJob(time.Duration(service.Config.Interval)*time.Second), gocron.NewTask(c.Observe, ctx, &service), gocron.WithName(fmt.Sprintf("task-service-%d", service.ID)), ) if jobCreateErr != nil { return jobCreateErr } log.Global.Get(log.CRON).Infof( "Registered service observe task for service with id - %d; task id - UUID: %s; Job interval: %d(s)", service.ID, job.ID().String(), service.Config.Interval, ) } return nil } func (c *check) Observe(ctx context.Context, srv *model.Service) error { // If the observation of an HTTP service failed, check the last N statuses. // If the first one has a failure status, set it as failed. // If all statuses in the last N are warning, set it as failed. // Otherwise, set the status as warning. if srv.Type == "http" { meta, err := c.ObserveHttp(ctx, srv) if err == nil { return c.RegisterStatus(ctx, srv.ID, model.StatusOK, *meta) } lastNStatuses, err := c.statusRepository.LastNStatuses(ctx, srv.ID, uint(srv.Config.MaxFails)) if err != nil { return err } if len(lastNStatuses) > 0 && lastNStatuses[0].Status == model.StatusFailed { return c.RegisterStatus(ctx, srv.ID, model.StatusFailed, *meta) } everyWarn := lo.EveryBy(lastNStatuses, func(item model.Status) bool { return item.Status == model.StatusWarn }) if everyWarn { return c.RegisterStatus(ctx, srv.ID, model.StatusFailed, *meta) } return c.RegisterStatus(ctx, srv.ID, model.StatusWarn, *meta) } // Todo: return err return nil } func (c *check) RegisterStatus(ctx context.Context, serviceID uint64, status model.StatusCode, meta ResponseMeta) error { return c.statusRepository.Add(ctx, model.Status{ ServiceID: serviceID, Status: status, ResponseTime: meta.ResponseTime, CreatedAt: time.Now(), }) } type ResponseMeta struct { ResponseTime uint64 } func (c *check) ObserveHttp(ctx context.Context, service *model.Service) (*ResponseMeta, error) { var meta = &ResponseMeta{ ResponseTime: 0, } var startTime = time.Now() defer func() { meta.ResponseTime = uint64(time.Since(startTime).Milliseconds()) }() if service.Config == nil || service.Config.HTTPConfig == nil { return meta, errors.New("service has no config for http") } logger := log.Global.Get(log.Observer) logger.Debugf("Start observe service - %d", service.ID) conf := service.Config.HTTPConfig client := &http.Client{ Timeout: time.Duration(service.Config.Timeout) * time.Second, } request, err := http.NewRequestWithContext(ctx, conf.Method, service.Host, nil) if err != nil { return meta, err } for s := range conf.Headers { request.Header.Set(s, conf.Headers[s]) } response, err := client.Do(request) if err != nil { return meta, err } if response.StatusCode != http.StatusOK { return meta, httpObserveFailed } return meta, nil }