diff --git a/alerting/alert/alert.go b/alerting/alert/alert.go index 7a627df0e..338725fcd 100644 --- a/alerting/alert/alert.go +++ b/alerting/alert/alert.go @@ -4,11 +4,11 @@ import ( "crypto/sha256" "encoding/hex" "errors" + "log/slog" "strconv" "strings" "time" - "github.com/TwiN/logr" "gopkg.in/yaml.v3" ) @@ -131,7 +131,7 @@ func (alert *Alert) Checksum() string { func (alert *Alert) ProviderOverrideAsBytes() []byte { yamlBytes, err := yaml.Marshal(alert.ProviderOverride) if err != nil { - logr.Warnf("[alert.ProviderOverrideAsBytes] Failed to marshal alert override of type=%s as bytes: %v", alert.Type, err) + slog.Warn("Failed to marshal alert override as bytes", "type", alert.Type, "error", err) } return yamlBytes } diff --git a/alerting/config.go b/alerting/config.go index 65150396b..425d1bf4d 100644 --- a/alerting/config.go +++ b/alerting/config.go @@ -1,6 +1,7 @@ package alerting import ( + "log/slog" "reflect" "strings" @@ -46,7 +47,6 @@ import ( "github.com/TwiN/gatus/v5/alerting/provider/webex" "github.com/TwiN/gatus/v5/alerting/provider/zapier" "github.com/TwiN/gatus/v5/alerting/provider/zulip" - "github.com/TwiN/logr" ) // Config is the configuration for alerting providers @@ -186,7 +186,7 @@ func (config *Config) GetAlertingProviderByAlertType(alertType alert.Type) provi return fieldValue.Interface().(provider.AlertProvider) } } - logr.Infof("[alerting.GetAlertingProviderByAlertType] No alerting provider found for alert type %s", alertType) + slog.Info("No alerting provider found for alert type", "type", alertType) return nil } diff --git a/alerting/provider/incidentio/incidentio.go b/alerting/provider/incidentio/incidentio.go index 6783f571c..c13741a64 100644 --- a/alerting/provider/incidentio/incidentio.go +++ b/alerting/provider/incidentio/incidentio.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "strconv" "strings" @@ -13,7 +14,6 @@ import ( "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" "github.com/TwiN/gatus/v5/config/endpoint" - "github.com/TwiN/logr" "gopkg.in/yaml.v3" ) @@ -117,7 +117,7 @@ func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, r err = json.NewDecoder(response.Body).Decode(&incidentioResponse) if err != nil { // Silently fail. We don't want to create tons of alerts just because we failed to parse the body. - logr.Errorf("[incidentio.Send] Ran into error decoding pagerduty response: %s", err.Error()) + slog.Error("Error decoding incident.io response", "error", err) } alert.ResolveKey = incidentioResponse.DeduplicationKey return err diff --git a/alerting/provider/pagerduty/pagerduty.go b/alerting/provider/pagerduty/pagerduty.go index 3a563f937..470a25941 100644 --- a/alerting/provider/pagerduty/pagerduty.go +++ b/alerting/provider/pagerduty/pagerduty.go @@ -6,12 +6,12 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "github.com/TwiN/gatus/v5/alerting/alert" "github.com/TwiN/gatus/v5/client" "github.com/TwiN/gatus/v5/config/endpoint" - "github.com/TwiN/logr" "gopkg.in/yaml.v3" ) @@ -105,7 +105,7 @@ func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, r var payload pagerDutyResponsePayload if err = json.NewDecoder(response.Body).Decode(&payload); err != nil { // Silently fail. We don't want to create tons of alerts just because we failed to parse the body. - logr.Errorf("[pagerduty.Send] Ran into error decoding pagerduty response: %s", err.Error()) + slog.Error("Ran into error decoding pagerduty response", "error", err.Error()) } else { alert.ResolveKey = payload.DedupKey } diff --git a/api/api.go b/api/api.go index ebab376ac..b68130494 100644 --- a/api/api.go +++ b/api/api.go @@ -2,6 +2,7 @@ package api import ( "io/fs" + "log/slog" "net/http" "os" @@ -10,7 +11,6 @@ import ( "github.com/TwiN/gatus/v5/config/web" static "github.com/TwiN/gatus/v5/web" "github.com/TwiN/health" - "github.com/TwiN/logr" fiber "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/adaptor" "github.com/gofiber/fiber/v2/middleware/compress" @@ -29,11 +29,11 @@ type API struct { func New(cfg *config.Config) *API { api := &API{} if cfg.Web == nil { - logr.Warnf("[api.New] nil web config passed as parameter. This should only happen in tests. Using default web configuration") + slog.Warn("nil web config passed as parameter. This should only happen in tests. Using default web configuration") cfg.Web = web.GetDefaultConfig() } if cfg.UI == nil { - logr.Warnf("[api.New] nil ui config passed as parameter. This should only happen in tests. Using default ui configuration") + slog.Warn("nil ui config passed as parameter. This should only happen in tests. Using default ui configuration") cfg.UI = ui.GetDefaultConfig() } api.router = api.createRouter(cfg) @@ -47,7 +47,7 @@ func (a *API) Router() *fiber.App { func (a *API) createRouter(cfg *config.Config) *fiber.App { app := fiber.New(fiber.Config{ ErrorHandler: func(c *fiber.Ctx, err error) error { - logr.Errorf("[api.ErrorHandler] %s", err.Error()) + slog.Error("API error", slog.String("error", err.Error())) return fiber.DefaultErrorHandler(c, err) }, ReadBufferSize: cfg.Web.ReadBufferSize, diff --git a/api/chart.go b/api/chart.go index 17ae43d52..ee53e5cec 100644 --- a/api/chart.go +++ b/api/chart.go @@ -2,6 +2,7 @@ package api import ( "errors" + "log/slog" "math" "net/http" "net/url" @@ -10,7 +11,6 @@ import ( "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/storage/store/common" - "github.com/TwiN/logr" "github.com/gofiber/fiber/v2" "github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2/drawing" @@ -121,7 +121,7 @@ func ResponseTimeChart(c *fiber.Ctx) error { c.Set("Expires", "0") c.Status(http.StatusOK) if err := graph.Render(chart.SVG, c); err != nil { - logr.Errorf("[api.ResponseTimeChart] Failed to render response time chart: %s", err.Error()) + slog.Error("Failed to render response time chart", "error", err) return c.Status(500).SendString(err.Error()) } return nil diff --git a/api/endpoint_status.go b/api/endpoint_status.go index 4be1ad935..43b26dfda 100644 --- a/api/endpoint_status.go +++ b/api/endpoint_status.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/url" "github.com/TwiN/gatus/v5/client" @@ -13,7 +14,6 @@ import ( "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" - "github.com/TwiN/logr" "github.com/gofiber/fiber/v2" ) @@ -27,19 +27,19 @@ func EndpointStatuses(cfg *config.Config) fiber.Handler { if !exists { endpointStatuses, err := store.Get().GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(page, pageSize)) if err != nil { - logr.Errorf("[api.EndpointStatuses] Failed to retrieve endpoint statuses: %s", err.Error()) + slog.Error("Failed to retrieve endpoint statuses", "error", err) return c.Status(500).SendString(err.Error()) } // ALPHA: Retrieve endpoint statuses from remote instances if endpointStatusesFromRemote, err := getEndpointStatusesFromRemoteInstances(cfg.Remote); err != nil { - logr.Errorf("[handler.EndpointStatuses] Silently failed to retrieve endpoint statuses from remote: %s", err.Error()) + slog.Error("Silently failed to retrieve endpoint statuses from remote", "error", err) } else if endpointStatusesFromRemote != nil { endpointStatuses = append(endpointStatuses, endpointStatusesFromRemote...) } // Marshal endpoint statuses to JSON data, err = json.Marshal(endpointStatuses) if err != nil { - logr.Errorf("[api.EndpointStatuses] Unable to marshal object to JSON: %s", err.Error()) + slog.Error("Unable to marshal endpoint statuses to JSON", "error", err) return c.Status(500).SendString("unable to marshal object to JSON") } cache.SetWithTTL(fmt.Sprintf("endpoint-status-%d-%d", page, pageSize), data, cacheTTL) @@ -61,13 +61,13 @@ func getEndpointStatusesFromRemoteInstances(remoteConfig *remote.Config) ([]*end response, err := httpClient.Get(instance.URL) if err != nil { // Log the error but continue with other instances - logr.Errorf("[api.getEndpointStatusesFromRemoteInstances] Failed to retrieve endpoint statuses from %s: %s", instance.URL, err.Error()) + slog.Error("Failed to retrieve endpoint statuses from remote instance", "url", instance.URL, "error", err) continue } var endpointStatuses []*endpoint.Status if err = json.NewDecoder(response.Body).Decode(&endpointStatuses); err != nil { _ = response.Body.Close() - logr.Errorf("[api.getEndpointStatusesFromRemoteInstances] Failed to decode endpoint statuses from %s: %s", instance.URL, err.Error()) + slog.Error("Failed to decode endpoint statuses from remote instance", "url", instance.URL, "error", err) continue } _ = response.Body.Close() @@ -89,7 +89,7 @@ func EndpointStatus(cfg *config.Config) fiber.Handler { page, pageSize := extractPageAndPageSizeFromRequest(c, cfg.Storage.MaximumNumberOfResults) key, err := url.QueryUnescape(c.Params("key")) if err != nil { - logr.Errorf("[api.EndpointStatus] Failed to decode key: %s", err.Error()) + slog.Error("Failed to percent-decode query key", "error", err) return c.Status(400).SendString("invalid key encoding") } endpointStatus, err := store.Get().GetEndpointStatusByKey(key, paging.NewEndpointStatusParams().WithResults(page, pageSize).WithEvents(1, cfg.Storage.MaximumNumberOfEvents)) @@ -97,16 +97,16 @@ func EndpointStatus(cfg *config.Config) fiber.Handler { if errors.Is(err, common.ErrEndpointNotFound) { return c.Status(404).SendString(err.Error()) } - logr.Errorf("[api.EndpointStatus] Failed to retrieve endpoint status: %s", err.Error()) + slog.Error("Failed to retrieve endpoint status", "error", err) return c.Status(500).SendString(err.Error()) } if endpointStatus == nil { // XXX: is this check necessary? - logr.Errorf("[api.EndpointStatus] Endpoint with key=%s not found", key) + slog.Error("Endpoint status not found", "key", key) return c.Status(404).SendString("not found") } output, err := json.Marshal(endpointStatus) if err != nil { - logr.Errorf("[api.EndpointStatus] Unable to marshal object to JSON: %s", err.Error()) + slog.Error("Unable to marshal endpoint status to JSON", "error", err) return c.Status(500).SendString("unable to marshal object to JSON") } c.Set("Content-Type", "application/json") diff --git a/api/external_endpoint.go b/api/external_endpoint.go index a1a8ee919..36e50e20f 100644 --- a/api/external_endpoint.go +++ b/api/external_endpoint.go @@ -2,6 +2,7 @@ package api import ( "errors" + "log/slog" "strings" "time" @@ -11,7 +12,6 @@ import ( "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/watchdog" - "github.com/TwiN/logr" "github.com/gofiber/fiber/v2" ) @@ -35,11 +35,11 @@ func CreateExternalEndpointResult(cfg *config.Config) fiber.Handler { key := c.Params("key") externalEndpoint := cfg.GetExternalEndpointByKey(key) if externalEndpoint == nil { - logr.Errorf("[api.CreateExternalEndpointResult] External endpoint with key=%s not found", key) + slog.Error("External endpoint not found", "key", key) return c.Status(404).SendString("not found") } if externalEndpoint.Token != token { - logr.Errorf("[api.CreateExternalEndpointResult] Invalid token for external endpoint with key=%s", key) + slog.Error("Invalid token for external endpoint", "key", key) return c.Status(401).SendString("invalid token") } // Persist the result in the storage @@ -51,7 +51,7 @@ func CreateExternalEndpointResult(cfg *config.Config) fiber.Handler { if len(c.Query("duration")) > 0 { parsedDuration, err := time.ParseDuration(c.Query("duration")) if err != nil { - logr.Errorf("[api.CreateExternalEndpointResult] Invalid duration from string=%s with error: %s", c.Query("duration"), err.Error()) + slog.Error("Invalid duration", "duration", c.Query("duration"), "error", err.Error()) return c.Status(400).SendString("invalid duration: " + err.Error()) } result.Duration = parsedDuration @@ -64,14 +64,14 @@ func CreateExternalEndpointResult(cfg *config.Config) fiber.Handler { if errors.Is(err, common.ErrEndpointNotFound) { return c.Status(404).SendString(err.Error()) } - logr.Errorf("[api.CreateExternalEndpointResult] Failed to insert result in storage: %s", err.Error()) + slog.Error("Failed to insert endpoint result", "error", err.Error()) return c.Status(500).SendString(err.Error()) } - logr.Infof("[api.CreateExternalEndpointResult] Successfully inserted result for external endpoint with key=%s and success=%s", c.Params("key"), success) + slog.Info("Successfully inserted result for external endpoint", slog.Group("result", "key", c.Params("key"), "success", success)) inEndpointMaintenanceWindow := false for _, maintenanceWindow := range externalEndpoint.MaintenanceWindows { if maintenanceWindow.IsUnderMaintenance() { - logr.Debug("[api.CreateExternalEndpointResult] Under endpoint maintenance window") + slog.Debug("External endpoint under maintenance window", "key", externalEndpoint.Key) inEndpointMaintenanceWindow = true } } @@ -81,7 +81,7 @@ func CreateExternalEndpointResult(cfg *config.Config) fiber.Handler { externalEndpoint.NumberOfSuccessesInARow = convertedEndpoint.NumberOfSuccessesInARow externalEndpoint.NumberOfFailuresInARow = convertedEndpoint.NumberOfFailuresInARow } else { - logr.Debug("[api.CreateExternalEndpointResult] Not handling alerting because currently in the maintenance window") + slog.Debug("Not handling alerting because currently in the maintenance window", "key", externalEndpoint.Key) } if cfg.Metrics { metrics.PublishMetricsForEndpoint(convertedEndpoint, result, extraLabels) diff --git a/api/spa.go b/api/spa.go index 9e996fa50..447ec92ac 100644 --- a/api/spa.go +++ b/api/spa.go @@ -3,10 +3,10 @@ package api import ( _ "embed" "html/template" + "log/slog" "github.com/TwiN/gatus/v5/config/ui" static "github.com/TwiN/gatus/v5/web" - "github.com/TwiN/logr" "github.com/gofiber/fiber/v2" ) @@ -26,14 +26,14 @@ func SinglePageApplication(uiConfig *ui.Config) fiber.Handler { t, err := template.ParseFS(static.FileSystem, static.IndexPath) if err != nil { // This should never happen, because ui.ValidateAndSetDefaults validates that the template works. - logr.Errorf("[api.SinglePageApplication] Failed to parse template. This should never happen, because the template is validated on start. Error: %s", err.Error()) + slog.Error("Failed to parse template. This should never happen, because the template is validated on start.", "error", err) return c.Status(500).SendString("Failed to parse template. This should never happen, because the template is validated on start.") } c.Set("Content-Type", "text/html") err = t.Execute(c, vd) if err != nil { // This should never happen, because ui.ValidateAndSetDefaults validates that the template works. - logr.Errorf("[api.SinglePageApplication] Failed to execute template. This should never happen, because the template is validated on start. Error: %s", err.Error()) + slog.Error("Failed to parse template. This should never happen, because the template is validated on start.", "error", err) return c.Status(500).SendString("Failed to parse template. This should never happen, because the template is validated on start.") } return c.SendStatus(200) diff --git a/client/client.go b/client/client.go index 825cab421..f915d549e 100644 --- a/client/client.go +++ b/client/client.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net" "net/http" "net/smtp" @@ -19,7 +20,6 @@ import ( "time" "github.com/TwiN/gocache/v2" - "github.com/TwiN/logr" "github.com/TwiN/whois" "github.com/gorilla/websocket" "github.com/ishidawataru/sctp" @@ -163,7 +163,7 @@ func CanPerformStartTLS(address string, config *Config) (connected bool, certifi if err != nil { // We're ignoring the error, because it should have been validated on startup ValidateAndSetDefaults. // It shouldn't happen, but if it does, we'll log it... Better safe than sorry ;) - logr.Errorf("[client.getHTTPClient] THIS SHOULD NOT HAPPEN. Silently ignoring invalid DNS resolver due to error: %s", err.Error()) + slog.Error("Should never happen: Silently ignoring invalid DNS resolver", "error", err) } else { dialer := &net.Dialer{ Resolver: &net.Resolver{ @@ -464,7 +464,7 @@ func QueryDNS(queryType, queryName, url string) (connected bool, dnsRcode string m.SetQuestion(queryName, queryTypeAsUint16) r, _, err := c.Exchange(m, url) if err != nil { - logr.Infof("[client.QueryDNS] Error exchanging DNS message: %v", err) + slog.Info("Error exchanging DNS message", "error", err) return false, "", nil, err } connected = true diff --git a/client/config.go b/client/config.go index 7cc174cff..f2b994c9e 100644 --- a/client/config.go +++ b/client/config.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "errors" + "log/slog" "net" "net/http" "net/url" @@ -12,7 +13,6 @@ import ( "time" "github.com/TwiN/gatus/v5/config/tunneling/sshtunnel" - "github.com/TwiN/logr" "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" "google.golang.org/api/idtoken" @@ -239,7 +239,7 @@ func (c *Config) getHTTPClient() *http.Client { if c.ProxyURL != "" { proxyURL, err := url.Parse(c.ProxyURL) if err != nil { - logr.Errorf("[client.getHTTPClient] THIS SHOULD NOT HAPPEN. Silently ignoring custom proxy due to error: %s", err.Error()) + slog.Error("Should never happen: Silently ignoring custom proxy", "error", err.Error()) } else { c.httpClient.Transport.(*http.Transport).Proxy = http.ProxyURL(proxyURL) } @@ -249,7 +249,7 @@ func (c *Config) getHTTPClient() *http.Client { if err != nil { // We're ignoring the error, because it should have been validated on startup ValidateAndSetDefaults. // It shouldn't happen, but if it does, we'll log it... Better safe than sorry ;) - logr.Errorf("[client.getHTTPClient] THIS SHOULD NOT HAPPEN. Silently ignoring invalid DNS resolver due to error: %s", err.Error()) + slog.Error("Should never happen: Silently ignoring invalid DNS resolver", "error", err.Error()) } else { dialer := &net.Dialer{ Resolver: &net.Resolver{ @@ -266,7 +266,7 @@ func (c *Config) getHTTPClient() *http.Client { } } if c.HasOAuth2Config() && c.HasIAPConfig() { - logr.Errorf("[client.getHTTPClient] Error: Both Identity-Aware-Proxy and Oauth2 configuration are present.") + slog.Error("Both Identity-Aware-Proxy and Oauth2 configuration are present. Only one can be present at a time") } else if c.HasOAuth2Config() { c.httpClient = configureOAuth2(c.httpClient, *c.OAuth2Config) } else if c.HasIAPConfig() { @@ -289,17 +289,17 @@ func (c *Config) getHTTPClient() *http.Client { func validateIAPToken(ctx context.Context, c IAPConfig) bool { ts, err := idtoken.NewTokenSource(ctx, c.Audience) if err != nil { - logr.Errorf("[client.ValidateIAPToken] Claiming Identity token failed: %s", err.Error()) + slog.Error("Claiming Identity token failed", "error", err.Error()) return false } tok, err := ts.Token() if err != nil { - logr.Errorf("[client.ValidateIAPToken] Get Identity-Aware-Proxy token failed: %s", err.Error()) + slog.Error("Getting Identity-Aware-Proxy token failed", "error", err.Error()) return false } _, err = idtoken.Validate(ctx, tok.AccessToken, c.Audience) if err != nil { - logr.Errorf("[client.ValidateIAPToken] Token Validation failed: %s", err.Error()) + slog.Error("Token Validation failed", "error", err.Error()) return false } return true @@ -312,7 +312,7 @@ func configureIAP(httpClient *http.Client, c IAPConfig) *http.Client { if validateIAPToken(ctx, c) { ts, err := idtoken.NewTokenSource(ctx, c.Audience) if err != nil { - logr.Errorf("[client.configureIAP] Claiming Token Source failed: %s", err.Error()) + slog.Error("Claiming Token Source failed", "error", err.Error()) return httpClient } client := oauth2.NewClient(ctx, ts) @@ -341,7 +341,7 @@ func configureOAuth2(httpClient *http.Client, c OAuth2Config) *http.Client { func configureTLS(tlsConfig *tls.Config, c TLSConfig) *tls.Config { clientTLSCert, err := tls.LoadX509KeyPair(c.CertificateFile, c.PrivateKeyFile) if err != nil { - logr.Errorf("[client.configureTLS] Failed to load certificate: %s", err.Error()) + slog.Error("Failed to load client TLS certificate", "error", err.Error()) return nil } tlsConfig.Certificates = []tls.Certificate{clientTLSCert} diff --git a/client/grpc.go b/client/grpc.go index 0d2be6ea2..155b0168d 100644 --- a/client/grpc.go +++ b/client/grpc.go @@ -3,10 +3,10 @@ package client import ( "context" "crypto/tls" + "log/slog" "net" "time" - "github.com/TwiN/logr" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" @@ -42,7 +42,7 @@ func PerformGRPCHealthCheck(address string, useTLS bool, cfg *Config) (bool, str resolverCfg, err := cfg.parseDNSResolver() if err != nil { // Shouldn't happen because already validated; log and fall back - logr.Errorf("[client.PerformGRPCHealthCheck] invalid DNS resolver: %v", err) + slog.Error("Invalid DNS resolver", "error", err) } else { d := &net.Dialer{Resolver: &net.Resolver{PreferGo: true, Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { d := net.Dialer{} diff --git a/config/config.go b/config/config.go index 4d9724b54..3c9b67a5a 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "os" "path/filepath" "sort" @@ -27,7 +28,6 @@ import ( "github.com/TwiN/gatus/v5/config/web" "github.com/TwiN/gatus/v5/security" "github.com/TwiN/gatus/v5/storage" - "github.com/TwiN/logr" "gopkg.in/yaml.v3" ) @@ -222,10 +222,10 @@ func LoadConfiguration(configPath string) (*Config, error) { return fmt.Errorf("error walking path %s: %w", path, err) } if strings.Contains(path, "..") { - logr.Warnf("[config.LoadConfiguration] Ignoring configuration from %s", path) + slog.Warn("Ignoring configuration", "path", path, "reason", "path contains '..'") return nil } - logr.Infof("[config.LoadConfiguration] Reading configuration from %s", path) + slog.Info("Reading configuration", "path", path) data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("error reading configuration from file %s: %w", path, err) @@ -237,7 +237,7 @@ func LoadConfiguration(configPath string) (*Config, error) { return nil, fmt.Errorf("error reading configuration from directory %s: %w", usedConfigPath, err) } } else { - logr.Infof("[config.LoadConfiguration] Reading configuration from configFile=%s", usedConfigPath) + slog.Info("Reading configuration", "path", usedConfigPath) if data, err := os.ReadFile(usedConfigPath); err != nil { return nil, fmt.Errorf("error reading configuration from directory %s: %w", usedConfigPath, err) } else { @@ -296,8 +296,8 @@ func parseAndValidateConfigBytes(yamlBytes []byte) (config *Config, err error) { } else { // XXX: Remove this in v6.0.0 if config.Debug { - logr.Warn("WARNING: The 'debug' configuration has been deprecated and will be removed in v6.0.0") - logr.Warn("WARNING: Please use the GATUS_LOG_LEVEL environment variable instead") + slog.Warn("WARNING: The 'debug' configuration has been deprecated and will be removed in v6.0.0") + slog.Warn("WARNING: Please use the GATUS_LOG_LEVEL environment variable instead") } // XXX: End of v6.0.0 removals ValidateAlertingConfig(config.Alerting, config.Endpoints, config.ExternalEndpoints) @@ -474,7 +474,7 @@ func ValidateEndpointsConfig(config *Config) error { duplicateValidationMap := make(map[string]bool) // Validate endpoints for _, ep := range config.Endpoints { - logr.Debugf("[config.ValidateEndpointsConfig] Validating endpoint with key %s", ep.Key()) + slog.Debug("Validating endpoint", "key", ep.Key()) if endpointKey := ep.Key(); duplicateValidationMap[endpointKey] { return fmt.Errorf("invalid endpoint %s: name and group combination must be unique", ep.Key()) } else { @@ -484,10 +484,10 @@ func ValidateEndpointsConfig(config *Config) error { return fmt.Errorf("invalid endpoint %s: %w", ep.Key(), err) } } - logr.Infof("[config.ValidateEndpointsConfig] Validated %d endpoints", len(config.Endpoints)) + slog.Info("Validated endpoints", "count", len(config.Endpoints)) // Validate external endpoints for _, ee := range config.ExternalEndpoints { - logr.Debugf("[config.ValidateEndpointsConfig] Validating external endpoint '%s'", ee.Key()) + slog.Debug("Validating external endpoint", "key", ee.Key()) if endpointKey := ee.Key(); duplicateValidationMap[endpointKey] { return fmt.Errorf("invalid external endpoint %s: name and group combination must be unique", ee.Key()) } else { @@ -497,13 +497,13 @@ func ValidateEndpointsConfig(config *Config) error { return fmt.Errorf("invalid external endpoint %s: %w", ee.Key(), err) } } - logr.Infof("[config.ValidateEndpointsConfig] Validated %d external endpoints", len(config.ExternalEndpoints)) + slog.Info("Validated external endpoints", "count", len(config.ExternalEndpoints)) return nil } func ValidateSuitesConfig(config *Config) error { if config.Suites == nil || len(config.Suites) == 0 { - logr.Info("[config.ValidateSuitesConfig] No suites configured") + slog.Info("No suites configured") return nil } suiteNames := make(map[string]bool) @@ -532,7 +532,7 @@ func ValidateSuitesConfig(config *Config) error { } } } - logr.Infof("[config.ValidateSuitesConfig] Validated %d suite(s)", len(config.Suites)) + slog.Info("Validated suites", "count", len(config.Suites)) return nil } @@ -576,7 +576,7 @@ func ValidateUniqueKeys(config *Config) error { func ValidateSecurityConfig(config *Config) error { if config.Security != nil { if !config.Security.ValidateAndSetDefaults() { - logr.Debug("[config.ValidateSecurityConfig] Basic security configuration has been validated") + slog.Debug("Basic security configuration has been validated") return ErrInvalidSecurityConfig } } @@ -589,7 +589,7 @@ func ValidateSecurityConfig(config *Config) error { // sets the default alert values when none are set. func ValidateAlertingConfig(alertingConfig *alerting.Config, endpoints []*endpoint.Endpoint, externalEndpoints []*endpoint.ExternalEndpoint) { if alertingConfig == nil { - logr.Info("[config.ValidateAlertingConfig] Alerting is not configured") + slog.Info("Alerting is not configured") return } alertTypes := []alert.Type{ @@ -644,12 +644,12 @@ func ValidateAlertingConfig(alertingConfig *alerting.Config, endpoints []*endpoi for _, ep := range endpoints { for alertIndex, endpointAlert := range ep.Alerts { if alertType == endpointAlert.Type { - logr.Debugf("[config.ValidateAlertingConfig] Parsing alert %d with default alert for provider=%s in endpoint with key=%s", alertIndex, alertType, ep.Key()) + slog.Debug("Parsing alert with default alert", "alert_index", alertIndex, "provider", alertType, "endpoint_key", ep.Key()) provider.MergeProviderDefaultAlertIntoEndpointAlert(alertProvider.GetDefaultAlert(), endpointAlert) // Validate the endpoint alert's overrides, if applicable if len(endpointAlert.ProviderOverride) > 0 { if err = alertProvider.ValidateOverrides(ep.Group, endpointAlert); err != nil { - logr.Warnf("[config.ValidateAlertingConfig] endpoint with key=%s has invalid overrides for provider=%s: %s", ep.Key(), alertType, err.Error()) + slog.Warn("Endpoint has invalid alert overrides", "key", ep.Key(), "provider", alertType, "error", err.Error()) } } } @@ -658,12 +658,12 @@ func ValidateAlertingConfig(alertingConfig *alerting.Config, endpoints []*endpoi for _, ee := range externalEndpoints { for alertIndex, endpointAlert := range ee.Alerts { if alertType == endpointAlert.Type { - logr.Debugf("[config.ValidateAlertingConfig] Parsing alert %d with default alert for provider=%s in endpoint with key=%s", alertIndex, alertType, ee.Key()) + slog.Debug("Parsing alert with default alert", "alert_index", alertIndex, "provider", alertType, "endpoint_key", ee.Key()) provider.MergeProviderDefaultAlertIntoEndpointAlert(alertProvider.GetDefaultAlert(), endpointAlert) // Validate the endpoint alert's overrides, if applicable if len(endpointAlert.ProviderOverride) > 0 { if err = alertProvider.ValidateOverrides(ee.Group, endpointAlert); err != nil { - logr.Warnf("[config.ValidateAlertingConfig] endpoint with key=%s has invalid overrides for provider=%s: %s", ee.Key(), alertType, err.Error()) + slog.Warn("External endpoint has invalid alert overrides", "key", ee.Key(), "provider", alertType, "error", err.Error()) } } } @@ -672,7 +672,7 @@ func ValidateAlertingConfig(alertingConfig *alerting.Config, endpoints []*endpoi } validProviders = append(validProviders, alertType) } else { - logr.Warnf("[config.ValidateAlertingConfig] Ignoring provider=%s due to error=%s", alertType, err.Error()) + slog.Warn("Ignoring alerting provider", "provider", alertType, "error", err.Error()) invalidProviders = append(invalidProviders, alertType) alertingConfig.SetAlertingProviderToNil(alertProvider) } @@ -680,19 +680,19 @@ func ValidateAlertingConfig(alertingConfig *alerting.Config, endpoints []*endpoi invalidProviders = append(invalidProviders, alertType) } } - logr.Infof("[config.ValidateAlertingConfig] configuredProviders=%s; ignoredProviders=%s", validProviders, invalidProviders) + slog.Info("Configured providers", "enabled", validProviders, "invalid", invalidProviders) } func ValidateAndSetConcurrencyDefaults(config *Config) { if config.DisableMonitoringLock { config.Concurrency = 0 - logr.Warn("WARNING: The 'disable-monitoring-lock' configuration has been deprecated and will be removed in v6.0.0") - logr.Warn("WARNING: Please set 'concurrency: 0' instead") - logr.Debug("[config.ValidateAndSetConcurrencyDefaults] DisableMonitoringLock is true, setting unlimited (0) concurrency") + slog.Warn("WARNING: The 'disable-monitoring-lock' configuration has been deprecated and will be removed in v6.0.0") + slog.Warn("WARNING: Please set 'concurrency: 0' instead") + slog.Debug("DisableMonitoringLock is true, setting unlimited (0) concurrency") } else if config.Concurrency <= 0 && !config.DisableMonitoringLock { config.Concurrency = DefaultConcurrency - logr.Debugf("[config.ValidateAndSetConcurrencyDefaults] Setting default concurrency to %d", config.Concurrency) + slog.Debug("Setting default concurrency", "value", config.Concurrency) } else { - logr.Debugf("[config.ValidateAndSetConcurrencyDefaults] Using configured concurrency of %d", config.Concurrency) + slog.Debug("Using configured concurrency", "value", config.Concurrency) } } diff --git a/config/endpoint/condition.go b/config/endpoint/condition.go index 02feb575b..8f5eb9adb 100644 --- a/config/endpoint/condition.go +++ b/config/endpoint/condition.go @@ -79,7 +79,7 @@ func (c Condition) evaluate(result *Result, dontResolveFailedConditions bool, co return false } if !success { - //logr.Debugf("[Condition.evaluate] Condition '%s' did not succeed because '%s' is false", condition, condition) + //slog.Debug("Condition did not succeed because it is false", "condition", condition) } result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: conditionToDisplay, Success: success}) return success diff --git a/config/endpoint/endpoint.go b/config/endpoint/endpoint.go index 2014e5098..c2b239672 100644 --- a/config/endpoint/endpoint.go +++ b/config/endpoint/endpoint.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log/slog" "math/rand" "net" "net/http" @@ -151,6 +152,16 @@ type Endpoint struct { AlwaysRun bool `yaml:"always-run,omitempty"` } +func (e *Endpoint) GetLogAttribute() slog.Attr { + return slog.Attr{ + Key: "endpoint", + Value: slog.GroupValue( + slog.String("group", e.Group), + slog.String("name", e.Name), + ), + } +} + // IsEnabled returns whether the endpoint is enabled or not func (e *Endpoint) IsEnabled() bool { if e.Enabled == nil { diff --git a/config/endpoint/external_endpoint.go b/config/endpoint/external_endpoint.go index 6db95611b..6a4771a1a 100644 --- a/config/endpoint/external_endpoint.go +++ b/config/endpoint/external_endpoint.go @@ -2,6 +2,7 @@ package endpoint import ( "errors" + "log/slog" "time" "github.com/TwiN/gatus/v5/alerting/alert" @@ -50,6 +51,16 @@ type ExternalEndpoint struct { NumberOfSuccessesInARow int `yaml:"-"` } +func (ee *ExternalEndpoint) GetLogAttribute() slog.Attr { + return slog.Attr{ + Key: "external-endpoint", + Value: slog.GroupValue( + slog.String("group", ee.Group), + slog.String("name", ee.Name), + ), + } +} + // ValidateAndSetDefaults validates the ExternalEndpoint and sets the default values func (externalEndpoint *ExternalEndpoint) ValidateAndSetDefaults() error { if err := validateEndpointNameGroupAndAlerts(externalEndpoint.Name, externalEndpoint.Group, externalEndpoint.Alerts); err != nil { diff --git a/config/endpoint/result.go b/config/endpoint/result.go index e2561f0e4..8f9d3a857 100644 --- a/config/endpoint/result.go +++ b/config/endpoint/result.go @@ -1,6 +1,7 @@ package endpoint import ( + "log/slog" "time" ) @@ -63,6 +64,17 @@ type Result struct { Name string `json:"name,omitempty"` } +func (r *Result) GetLogAttribute() slog.Attr { + return slog.Attr{ + Key: "result", + Value: slog.GroupValue( + slog.Bool("success", r.Success), + slog.Duration("duration", r.Duration), + slog.Int("error_count", len(r.Errors)), + ), + } +} + // AddError adds an error to the result's list of errors. // It also ensures that there are no duplicates. func (r *Result) AddError(error string) { diff --git a/config/remote/remote.go b/config/remote/remote.go index d5bdbc477..447e0ad44 100644 --- a/config/remote/remote.go +++ b/config/remote/remote.go @@ -1,8 +1,9 @@ package remote import ( + "log/slog" + "github.com/TwiN/gatus/v5/client" - "github.com/TwiN/logr" ) // NOTICE: This is an experimental alpha feature and may be updated/removed in future versions. @@ -30,8 +31,8 @@ func (c *Config) ValidateAndSetDefaults() error { } } if len(c.Instances) > 0 { - logr.Warn("WARNING: Your configuration is using 'remote', which is in alpha and may be updated/removed in future versions.") - logr.Warn("WARNING: See https://github.com/TwiN/gatus/issues/64 for more information") + slog.Warn("WARNING: Your configuration is using 'remote', which is in alpha and may be updated/removed in future versions.") + slog.Warn("WARNING: See https://github.com/TwiN/gatus/issues/64 for more information") } return nil } diff --git a/config/suite/result.go b/config/suite/result.go index 23b2045f8..1ede33351 100644 --- a/config/suite/result.go +++ b/config/suite/result.go @@ -1,6 +1,7 @@ package suite import ( + "log/slog" "time" "github.com/TwiN/gatus/v5/config/endpoint" @@ -33,6 +34,18 @@ type Result struct { Errors []string `json:"errors,omitempty"` } +func (r *Result) GetLogAttribute() slog.Attr { + return slog.Attr{ + Key: "result", + Value: slog.GroupValue( + slog.Bool("success", r.Success), + slog.Duration("duration", r.Duration), + slog.Int("endpoints", len(r.EndpointResults)), + slog.Int("error_count", len(r.Errors)), + ), + } +} + // AddError adds an error to the suite result func (r *Result) AddError(err string) { r.Errors = append(r.Errors, err) diff --git a/config/suite/suite.go b/config/suite/suite.go index 186fd9a91..74a0fcd8c 100644 --- a/config/suite/suite.go +++ b/config/suite/suite.go @@ -3,6 +3,7 @@ package suite import ( "errors" "fmt" + "log/slog" "strconv" "strings" "time" @@ -56,6 +57,16 @@ type Suite struct { Endpoints []*endpoint.Endpoint `yaml:"endpoints"` } +func (s *Suite) GetLogAttribute() slog.Attr { + return slog.Attr{ + Key: "suite", + Value: slog.GroupValue( + slog.String("group", s.Group), + slog.String("name", s.Name), + ), + } +} + // IsEnabled returns whether the suite is enabled func (s *Suite) IsEnabled() bool { if s.Enabled == nil { diff --git a/controller/controller.go b/controller/controller.go index 47645a4c1..26fe04f80 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -1,12 +1,12 @@ package controller import ( + "log/slog" "os" "time" "github.com/TwiN/gatus/v5/api" "github.com/TwiN/gatus/v5/config" - "github.com/TwiN/logr" "github.com/gofiber/fiber/v2" ) @@ -25,19 +25,19 @@ func Handle(cfg *config.Config) { if os.Getenv("ROUTER_TEST") == "true" { return } - logr.Info("[controller.Handle] Listening on " + cfg.Web.SocketAddress()) + slog.Info("Server listening", "address", cfg.Web.SocketAddress()) if cfg.Web.HasTLS() { err := app.ListenTLS(cfg.Web.SocketAddress(), cfg.Web.TLS.CertificateFile, cfg.Web.TLS.PrivateKeyFile) if err != nil { - logr.Fatalf("[controller.Handle] %s", err.Error()) + slog.Error("Failed to start server with TLS", "error", err.Error()) } } else { err := app.Listen(cfg.Web.SocketAddress()) if err != nil { - logr.Fatalf("[controller.Handle] %s", err.Error()) + slog.Error("Failed to start server", "error", err.Error()) } } - logr.Info("[controller.Handle] Server has shut down successfully") + slog.Info("Server has shut down successfully") } // Shutdown stops the server diff --git a/go.mod b/go.mod index d3f47d3d4..74d617998 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/TwiN/g8/v2 v2.0.0 github.com/TwiN/gocache/v2 v2.4.0 github.com/TwiN/health v1.6.0 - github.com/TwiN/logr v0.3.1 github.com/TwiN/whois v1.2.0 github.com/aws/aws-sdk-go-v2 v1.40.0 github.com/aws/aws-sdk-go-v2/config v1.32.2 diff --git a/go.sum b/go.sum index 4b12ee5af..3a3c1c799 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,6 @@ github.com/TwiN/gocache/v2 v2.4.0 h1:BZ/TqvhipDQE23MFFTjC0MiI1qZ7GEVtSdOFVVXyr18 github.com/TwiN/gocache/v2 v2.4.0/go.mod h1:Cl1c0qNlQlXzJhTpAARVqpQDSuGDM5RhtzPYAM1x17g= github.com/TwiN/health v1.6.0 h1:L2ks575JhRgQqWWOfKjw9B0ec172hx7GdToqkYUycQM= github.com/TwiN/health v1.6.0/go.mod h1:Z6TszwQPMvtSiVx1QMidVRgvVr4KZGfiwqcD7/Z+3iw= -github.com/TwiN/logr v0.3.1 h1:CfTKA83jUmsAoxqrr3p4JxEkqXOBnEE9/f35L5MODy4= -github.com/TwiN/logr v0.3.1/go.mod h1:BZgZFYq6fQdU3KtR8qYato3zUEw53yQDaIuujHb55Jw= github.com/TwiN/whois v1.2.0 h1:/Z22SrS3Z0FQgMl1+4bKSu9UmEQTfGx9i9J4Hn18eQk= github.com/TwiN/whois v1.2.0/go.mod h1:TjipCMpJRAJYKmtz/rXQBU6UGxMh6bk8SHazu7OMnQE= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= diff --git a/logging/logging.go b/logging/logging.go new file mode 100644 index 000000000..d2e863f78 --- /dev/null +++ b/logging/logging.go @@ -0,0 +1,83 @@ +package logging + +import ( + "errors" + "log/slog" + "os" +) + +const ( + GatusLogSourceEnvVar = "GATUS_LOG_SOURCE" + GatusConfigLogTypeEnvVar = "GATUS_LOG_TYPE" + GatusLogLevelEnvVar = "GATUS_LOG_LEVEL" + + DefaultLogType = "TEXT" + DefaultLogLevel = "INFO" +) + +var ( + ErrInvalidLevelString = errors.New("invalid log level string, must be one of: DEBUG, INFO, WARN, ERROR, FATAL") + logLevels = map[string]slog.Level{ + "DEBUG": slog.LevelDebug, + "INFO": slog.LevelInfo, + "WARN": slog.LevelWarn, + "ERROR": slog.LevelError, + "FATAL": slog.LevelError, // TODO in v6.0.0: Remove FATAL level support + } + + logLevel slog.Level +) + +func Level() slog.Level { + return logLevel +} + +func levelFromString(level string) (slog.Level, error) { + if slogLevel, exists := logLevels[level]; exists { + return slogLevel, nil + } + return logLevel, ErrInvalidLevelString +} + +func getConfiguredLogLevel() slog.Level { + levelAsString := os.Getenv(GatusLogLevelEnvVar) + if len(levelAsString) == 0 { + return logLevels[DefaultLogLevel] + } else if level, err := levelFromString(levelAsString); err != nil { + slog.Warn("Invalid log level, using default", "provided", level, "default", DefaultLogLevel) + return logLevels[DefaultLogLevel] + } else { + if levelAsString == "FATAL" { + slog.Warn("WARNING: FATAL log level has been deprecated and will be removed in v6.0.0") + slog.Warn("WARNING: Please use the ERROR log level instead") + } + return level + } +} + +func getConfiguredLogSource() bool { + logSourceAsString := os.Getenv(GatusLogSourceEnvVar) + if len(logSourceAsString) == 0 { + return false + } else if logSourceAsString != "TRUE" && logSourceAsString != "FALSE" { + slog.Warn("Invalid log source", "provided", logSourceAsString, "default", "FALSE") + return false + } + return logSourceAsString == "TRUE" +} + +func Configure() { + logTypeAsString := os.Getenv(GatusConfigLogTypeEnvVar) + switch logTypeAsString { + case "", "TEXT": + break + case "JSON": + logSource := getConfiguredLogSource() + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: logSource}))) + default: + slog.Warn("Invalid log type", "provided", logTypeAsString, "default", DefaultLogType) + } + + logLevel = getConfiguredLogLevel() + slog.SetLogLoggerLevel(logLevel) +} diff --git a/main.go b/main.go index 13788fd10..d5b92fdfa 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "log/slog" "os" "os/signal" "strconv" @@ -9,24 +10,23 @@ import ( "github.com/TwiN/gatus/v5/config" "github.com/TwiN/gatus/v5/controller" + "github.com/TwiN/gatus/v5/logging" "github.com/TwiN/gatus/v5/metrics" "github.com/TwiN/gatus/v5/storage/store" "github.com/TwiN/gatus/v5/watchdog" - "github.com/TwiN/logr" ) const ( GatusConfigPathEnvVar = "GATUS_CONFIG_PATH" GatusConfigFileEnvVar = "GATUS_CONFIG_FILE" // Deprecated in favor of GatusConfigPathEnvVar - GatusLogLevelEnvVar = "GATUS_LOG_LEVEL" ) func main() { if delayInSeconds, _ := strconv.Atoi(os.Getenv("GATUS_DELAY_START_SECONDS")); delayInSeconds > 0 { - logr.Infof("Delaying start by %d seconds", delayInSeconds) + slog.Info("Delaying start", "seconds", delayInSeconds) time.Sleep(time.Duration(delayInSeconds) * time.Second) } - configureLogging() + logging.Configure() cfg, err := loadConfiguration() if err != nil { panic(err) @@ -39,13 +39,13 @@ func main() { signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) go func() { <-signalChannel - logr.Info("Received termination signal, attempting to gracefully shut down") + slog.Info("Received termination signal, attempting to gracefully shut down") stop(cfg) save() done <- true }() <-done - logr.Info("Shutting down") + slog.Info("Shutting down") } func start(cfg *config.Config) { @@ -64,22 +64,7 @@ func stop(cfg *config.Config) { func save() { if err := store.Get().Save(); err != nil { - logr.Errorf("Failed to save storage provider: %s", err.Error()) - } -} - -func configureLogging() { - logLevelAsString := os.Getenv(GatusLogLevelEnvVar) - if logLevel, err := logr.LevelFromString(logLevelAsString); err != nil { - logr.SetThreshold(logr.LevelInfo) - if len(logLevelAsString) == 0 { - logr.Infof("[main.configureLogging] Defaulting log level to %s", logr.LevelInfo) - } else { - logr.Warnf("[main.configureLogging] Invalid log level '%s', defaulting to %s", logLevelAsString, logr.LevelInfo) - } - } else { - logr.SetThreshold(logLevel) - logr.Infof("[main.configureLogging] Log Level is set to %s", logr.GetThreshold()) + slog.Error("Failed to save storage provider", "error", err) } } @@ -88,7 +73,7 @@ func loadConfiguration() (*config.Config, error) { // Backwards compatibility if len(configPath) == 0 { if configPath = os.Getenv(GatusConfigFileEnvVar); len(configPath) > 0 { - logr.Warnf("WARNING: %s is deprecated. Please use %s instead.", GatusConfigFileEnvVar, GatusConfigPathEnvVar) + slog.Warn("WARNING: Deprecated environment variable used", "old", GatusConfigFileEnvVar, "preferred", GatusConfigPathEnvVar) } } return config.LoadConfiguration(configPath) @@ -111,7 +96,7 @@ func initializeStorage(cfg *config.Config) { } numberOfSuiteStatusesDeleted := store.Get().DeleteAllSuiteStatusesNotInKeys(suiteKeys) if numberOfSuiteStatusesDeleted > 0 { - logr.Infof("[main.initializeStorage] Deleted %d suite statuses because their matching suites no longer existed", numberOfSuiteStatusesDeleted) + slog.Info("Deleted statuses for non-existing suites", "count", numberOfSuiteStatusesDeleted) } // Remove all EndpointStatus that represent endpoints which no longer exist in the configuration var keys []string @@ -127,10 +112,10 @@ func initializeStorage(cfg *config.Config) { keys = append(keys, ep.Key()) } } - logr.Infof("[main.initializeStorage] Total endpoint keys to preserve: %d", len(keys)) + slog.Info("Total endpoint keys to preserve", "count", len(keys)) numberOfEndpointStatusesDeleted := store.Get().DeleteAllEndpointStatusesNotInKeys(keys) if numberOfEndpointStatusesDeleted > 0 { - logr.Infof("[main.initializeStorage] Deleted %d endpoint statuses because their matching endpoints no longer existed", numberOfEndpointStatusesDeleted) + slog.Info("Deleted statuses for non-existing endpoints", "count", numberOfEndpointStatusesDeleted) } // Clean up the triggered alerts from the storage provider and load valid triggered endpoint alerts numberOfPersistedTriggeredAlertsLoaded := 0 @@ -143,12 +128,12 @@ func initializeStorage(cfg *config.Config) { } numberOfTriggeredAlertsDeleted := store.Get().DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(ep, checksums) if numberOfTriggeredAlertsDeleted > 0 { - logr.Debugf("[main.initializeStorage] Deleted %d triggered alerts for endpoint with key=%s because their configurations have been changed or deleted", numberOfTriggeredAlertsDeleted, ep.Key()) + slog.Debug("Deleted triggered alerts for endpoint", "count", numberOfTriggeredAlertsDeleted, "endpoint_key", ep.Key()) } for _, alert := range ep.Alerts { exists, resolveKey, numberOfSuccessesInARow, err := store.Get().GetTriggeredEndpointAlert(ep, alert) if err != nil { - logr.Errorf("[main.initializeStorage] Failed to get triggered alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to get triggered alert for endpoint", "key", ep.Key(), "error", err) continue } if exists { @@ -168,12 +153,12 @@ func initializeStorage(cfg *config.Config) { convertedEndpoint := ee.ToEndpoint() numberOfTriggeredAlertsDeleted := store.Get().DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(convertedEndpoint, checksums) if numberOfTriggeredAlertsDeleted > 0 { - logr.Debugf("[main.initializeStorage] Deleted %d triggered alerts for endpoint with key=%s because their configurations have been changed or deleted", numberOfTriggeredAlertsDeleted, ee.Key()) + slog.Debug("Deleted triggered alerts for external endpoint due to configuration change or deletion", "count", numberOfTriggeredAlertsDeleted, "endpoint_key", ee.Key()) } for _, alert := range ee.Alerts { exists, resolveKey, numberOfSuccessesInARow, err := store.Get().GetTriggeredEndpointAlert(convertedEndpoint, alert) if err != nil { - logr.Errorf("[main.initializeStorage] Failed to get triggered alert for endpoint with key=%s: %s", ee.Key(), err.Error()) + slog.Error("Failed to get triggered alert for external endpoint", "key", ee.Key(), "error", err) continue } if exists { @@ -194,12 +179,12 @@ func initializeStorage(cfg *config.Config) { } numberOfTriggeredAlertsDeleted := store.Get().DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(ep, checksums) if numberOfTriggeredAlertsDeleted > 0 { - logr.Debugf("[main.initializeStorage] Deleted %d triggered alerts for suite endpoint with key=%s because their configurations have been changed or deleted", numberOfTriggeredAlertsDeleted, ep.Key()) + slog.Debug("Deleted triggered alerts for suite endpoint due to configuration change or deletion", "count", numberOfTriggeredAlertsDeleted, "endpoint_key", ep.Key()) } for _, alert := range ep.Alerts { exists, resolveKey, numberOfSuccessesInARow, err := store.Get().GetTriggeredEndpointAlert(ep, alert) if err != nil { - logr.Errorf("[main.initializeStorage] Failed to get triggered alert for suite endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to get triggered alert for suite endpoint", "endpoint_key", ep.Key(), "error", err) continue } if exists { @@ -211,14 +196,14 @@ func initializeStorage(cfg *config.Config) { } } if numberOfPersistedTriggeredAlertsLoaded > 0 { - logr.Infof("[main.initializeStorage] Loaded %d persisted triggered alerts", numberOfPersistedTriggeredAlertsLoaded) + slog.Info("Loaded persisted triggered alerts", "count", numberOfPersistedTriggeredAlertsLoaded) } } func closeTunnels(cfg *config.Config) { if cfg.Tunneling != nil { if err := cfg.Tunneling.Close(); err != nil { - logr.Errorf("[main.closeTunnels] Error closing SSH tunnels: %v", err) + slog.Error("Error closing SSH tunnels", "error", err) } } } @@ -227,15 +212,15 @@ func listenToConfigurationFileChanges(cfg *config.Config) { for { time.Sleep(30 * time.Second) if cfg.HasLoadedConfigurationBeenModified() { - logr.Info("[main.listenToConfigurationFileChanges] Configuration file has been modified") + slog.Info("Configuration file has been modified, reloading") stop(cfg) time.Sleep(time.Second) // Wait a bit to make sure everything is done. save() updatedConfig, err := loadConfiguration() if err != nil { if cfg.SkipInvalidConfigUpdate { - logr.Errorf("[main.listenToConfigurationFileChanges] Failed to load new configuration: %s", err.Error()) - logr.Error("[main.listenToConfigurationFileChanges] The configuration file was updated, but it is not valid. The old configuration will continue being used.") + slog.Error("Failed to load new configuration", "error", err) + slog.Error("The configuration file was updated, but it is not valid. The old configuration will continue being used.") // Update the last file modification time to avoid trying to process the same invalid configuration again cfg.UpdateLastFileModTime() continue diff --git a/security/config.go b/security/config.go index 4622edc00..118daea6f 100644 --- a/security/config.go +++ b/security/config.go @@ -2,10 +2,10 @@ package security import ( "encoding/base64" + "log/slog" "net/http" g8 "github.com/TwiN/g8/v2" - "github.com/TwiN/logr" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/adaptor" "github.com/gofiber/fiber/v2/middleware/basicauth" @@ -99,7 +99,7 @@ func (c *Config) IsAuthenticated(ctx *fiber.Ctx) bool { // TODO: Update g8 to support fasthttp natively? (see g8's fasthttp branch) request, err := adaptor.ConvertRequest(ctx, false) if err != nil { - logr.Errorf("[security.IsAuthenticated] Unexpected error converting request: %v", err) + slog.Error("Undexpected error converting request", "error", err) return false } token := c.gate.ExtractTokenFromRequest(request) diff --git a/security/oidc.go b/security/oidc.go index 821f22104..c12e23b8b 100644 --- a/security/oidc.go +++ b/security/oidc.go @@ -2,11 +2,11 @@ package security import ( "context" + "log/slog" "net/http" "strings" "time" - "github.com/TwiN/logr" "github.com/coreos/go-oidc/v3/oidc" "github.com/gofiber/fiber/v2" "github.com/google/uuid" @@ -132,7 +132,7 @@ func (c *OIDCConfig) callbackHandler(w http.ResponseWriter, r *http.Request) { / return } } - logr.Debugf("[security.callbackHandler] Subject %s is not in the list of allowed subjects", idToken.Subject) + slog.Debug("Subject not in allow-list", "subject", idToken.Subject) http.Redirect(w, r, "/?error=access_denied", http.StatusFound) } diff --git a/storage/store/memory/memory.go b/storage/store/memory/memory.go index 79f4e505a..ac40f33e0 100644 --- a/storage/store/memory/memory.go +++ b/storage/store/memory/memory.go @@ -1,6 +1,7 @@ package memory import ( + "log/slog" "sort" "sync" "time" @@ -12,7 +13,6 @@ import ( "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" "github.com/TwiN/gocache/v2" - "github.com/TwiN/logr" ) // Store that leverages gocache @@ -219,7 +219,7 @@ func (s *Store) InsertSuiteResult(su *suite.Suite, result *suite.Result) error { Key: su.Key(), Results: []*suite.Result{}, } - logr.Debugf("[memory.InsertSuiteResult] Created new suite status for suiteKey=%s", suiteKey) + slog.Debug("Created new suite status", "key", suiteKey) } status := suiteStatus.(*suite.Status) // Add the new result at the end (append like endpoint implementation) @@ -229,7 +229,7 @@ func (s *Store) InsertSuiteResult(su *suite.Suite, result *suite.Result) error { status.Results = status.Results[len(status.Results)-s.maximumNumberOfResults:] } s.suiteCache.Set(suiteKey, status) - logr.Debugf("[memory.InsertSuiteResult] Stored suite result for suiteKey=%s, total results=%d", suiteKey, len(status.Results)) + slog.Debug("Stored suite result", "key", suiteKey, "total_results", len(status.Results)) return nil } diff --git a/storage/store/sql/sql.go b/storage/store/sql/sql.go index 32e2aff81..f6ba6a10c 100644 --- a/storage/store/sql/sql.go +++ b/storage/store/sql/sql.go @@ -4,6 +4,7 @@ import ( "database/sql" "errors" "fmt" + "log/slog" "strconv" "strings" "time" @@ -15,7 +16,6 @@ import ( "github.com/TwiN/gatus/v5/storage/store/common" "github.com/TwiN/gatus/v5/storage/store/common/paging" "github.com/TwiN/gocache/v2" - "github.com/TwiN/logr" _ "github.com/lib/pq" _ "modernc.org/sqlite" ) @@ -247,12 +247,12 @@ func (s *Store) InsertEndpointResult(ep *endpoint.Endpoint, result *endpoint.Res // Endpoint doesn't exist in the database, insert it if endpointID, err = s.insertEndpoint(tx, ep); err != nil { _ = tx.Rollback() - logr.Errorf("[sql.InsertEndpointResult] Failed to create endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to create endpoint", "key", ep.Key(), "error", err.Error()) return err } } else { _ = tx.Rollback() - logr.Errorf("[sql.InsertEndpointResult] Failed to retrieve id of endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to retrieve endpoint ID", "key", ep.Key(), "error", err.Error()) return err } } @@ -268,7 +268,7 @@ func (s *Store) InsertEndpointResult(ep *endpoint.Endpoint, result *endpoint.Res numberOfEvents, err := s.getNumberOfEventsByEndpointID(tx, endpointID) if err != nil { // Silently fail - logr.Errorf("[sql.InsertEndpointResult] Failed to retrieve total number of events for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to retrieve total number of events", "key", ep.Key(), "error", err.Error()) } if numberOfEvents == 0 { // There's no events yet, which means we need to add the EventStart and the first healthy/unhealthy event @@ -278,19 +278,19 @@ func (s *Store) InsertEndpointResult(ep *endpoint.Endpoint, result *endpoint.Res }) if err != nil { // Silently fail - logr.Errorf("[sql.InsertEndpointResult] Failed to insert event=%s for endpoint with key=%s: %s", endpoint.EventStart, ep.Key(), err.Error()) + slog.Error("Failed to insert start events", "endpoint", ep.Key(), "event", endpoint.EventStart, "error", err.Error()) } event := endpoint.NewEventFromResult(result) if err = s.insertEndpointEvent(tx, endpointID, event); err != nil { // Silently fail - logr.Errorf("[sql.InsertEndpointResult] Failed to insert event=%s for endpoint with key=%s: %s", event.Type, ep.Key(), err.Error()) + slog.Error("Failed to insert first health event", "endpoint", ep.Key(), "event", event.Type, "error", err.Error()) } } else { // Get the success value of the previous result var lastResultSuccess bool if lastResultSuccess, err = s.getLastEndpointResultSuccessValue(tx, endpointID); err != nil { // Silently fail - logr.Errorf("[sql.InsertEndpointResult] Failed to retrieve outcome of previous result for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to retrieve outcome of previous result", "endpoint", ep.Key(), "error", err.Error()) } else { // If we managed to retrieve the outcome of the previous result, we'll compare it with the new result. // If the final outcome (success or failure) of the previous and the new result aren't the same, it means @@ -300,7 +300,7 @@ func (s *Store) InsertEndpointResult(ep *endpoint.Endpoint, result *endpoint.Res event := endpoint.NewEventFromResult(result) if err = s.insertEndpointEvent(tx, endpointID, event); err != nil { // Silently fail - logr.Errorf("[sql.InsertEndpointResult] Failed to insert event=%s for endpoint with key=%s: %s", event.Type, ep.Key(), err.Error()) + slog.Error("Failed to insert health event", "endpoint", ep.Key(), "event", event.Type, "error", err.Error()) } } } @@ -309,42 +309,42 @@ func (s *Store) InsertEndpointResult(ep *endpoint.Endpoint, result *endpoint.Res // (since we're only deleting MaximumNumberOfEvents at a time instead of 1) if numberOfEvents > int64(s.maximumNumberOfEvents+eventsAboveMaximumCleanUpThreshold) { if err = s.deleteOldEndpointEvents(tx, endpointID); err != nil { - logr.Errorf("[sql.InsertEndpointResult] Failed to delete old events for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to delete old events", "endpoint", ep.Key(), "error", err.Error()) } } } // Second, we need to insert the result. if err = s.insertEndpointResult(tx, endpointID, result); err != nil { - logr.Errorf("[sql.InsertEndpointResult] Failed to insert result for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to insert result", "endpoint", ep.Key(), "error", err.Error()) _ = tx.Rollback() // If we can't insert the result, we'll rollback now since there's no point continuing return err } // Clean up old results numberOfResults, err := s.getNumberOfResultsByEndpointID(tx, endpointID) if err != nil { - logr.Errorf("[sql.InsertEndpointResult] Failed to retrieve total number of results for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to retrieve total number of results", "endpoint", ep.Key(), "error", err.Error()) } else { if numberOfResults > int64(s.maximumNumberOfResults+resultsAboveMaximumCleanUpThreshold) { if err = s.deleteOldEndpointResults(tx, endpointID); err != nil { - logr.Errorf("[sql.InsertEndpointResult] Failed to delete old results for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to delete old results", "endpoint", ep.Key(), "error", err.Error()) } } } // Finally, we need to insert the uptime data. // Because the uptime data significantly outlives the results, we can't rely on the results for determining the uptime if err = s.updateEndpointUptime(tx, endpointID, result); err != nil { - logr.Errorf("[sql.InsertEndpointResult] Failed to update uptime for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to update uptime", "endpoint", ep.Key(), "error", err.Error()) } // Merge hourly uptime entries that can be merged into daily entries and clean up old uptime entries numberOfUptimeEntries, err := s.getNumberOfUptimeEntriesByEndpointID(tx, endpointID) if err != nil { - logr.Errorf("[sql.InsertEndpointResult] Failed to retrieve total number of uptime entries for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to retrieve total number of uptime entries", "endpoint", ep.Key(), "error", err.Error()) } else { // Merge older hourly uptime entries into daily uptime entries if we have more than uptimeTotalEntriesMergeThreshold if numberOfUptimeEntries >= uptimeTotalEntriesMergeThreshold { - logr.Infof("[sql.InsertEndpointResult] Merging hourly uptime entries for endpoint with key=%s; This is a lot of work, it shouldn't happen too often", ep.Key()) + slog.Warn("Merging hourly uptime entries; This is a lot of work, it shouldn't happen too often", "endpoint", ep.Key()) if err = s.mergeHourlyUptimeEntriesOlderThanMergeThresholdIntoDailyUptimeEntries(tx, endpointID); err != nil { - logr.Errorf("[sql.InsertEndpointResult] Failed to merge hourly uptime entries for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to merge hourly uptime entries", "endpoint", ep.Key(), "error", err.Error()) } } } @@ -353,11 +353,11 @@ func (s *Store) InsertEndpointResult(ep *endpoint.Endpoint, result *endpoint.Res // but if Gatus was temporarily shut down, we might have some old entries that need to be cleaned up ageOfOldestUptimeEntry, err := s.getAgeOfOldestEndpointUptimeEntry(tx, endpointID) if err != nil { - logr.Errorf("[sql.InsertEndpointResult] Failed to retrieve oldest endpoint uptime entry for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to retrieve oldest uptime entry", "endpoint", ep.Key(), "error", err.Error()) } else { if ageOfOldestUptimeEntry > uptimeAgeCleanUpThreshold { if err = s.deleteOldUptimeEntries(tx, endpointID, time.Now().Add(-(uptimeRetention + time.Hour))); err != nil { - logr.Errorf("[sql.InsertEndpointResult] Failed to delete old uptime entries for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to delete old uptime entries", "endpoint", ep.Key(), "error", err.Error()) } } } @@ -367,7 +367,7 @@ func (s *Store) InsertEndpointResult(ep *endpoint.Endpoint, result *endpoint.Res s.writeThroughCache.Delete(cacheKey) endpointKey, params, err := extractKeyAndParamsFromCacheKey(cacheKey) if err != nil { - logr.Errorf("[sql.InsertEndpointResult] Silently deleting cache key %s instead of refreshing due to error: %s", cacheKey, err.Error()) + slog.Error("Silently deleting cache key instead of refreshing", "cacheKey", cacheKey, "error", err.Error()) continue } // Retrieve the endpoint status by key, which will in turn refresh the cache @@ -382,12 +382,12 @@ func (s *Store) InsertEndpointResult(ep *endpoint.Endpoint, result *endpoint.Res // DeleteAllEndpointStatusesNotInKeys removes all rows owned by an endpoint whose key is not within the keys provided func (s *Store) DeleteAllEndpointStatusesNotInKeys(keys []string) int { - logr.Debugf("[sql.DeleteAllEndpointStatusesNotInKeys] Called with %d keys", len(keys)) + slog.Debug("Delete all endpoint statuses not in keys start", "key_count", len(keys)) var err error var result sql.Result if len(keys) == 0 { // Delete everything - logr.Debugf("[sql.DeleteAllEndpointStatusesNotInKeys] No keys provided, deleting all endpoints") + slog.Debug("No keys provided, deleting all endpoints") result, err = s.db.Exec("DELETE FROM endpoints") } else { // First check what we're about to delete @@ -410,9 +410,9 @@ func (s *Store) DeleteAllEndpointStatusesNotInKeys(keys []string) int { } } if len(deletedKeys) > 0 { - logr.Infof("[sql.DeleteAllEndpointStatusesNotInKeys] Deleting endpoints with keys: %v", deletedKeys) + slog.Info("Deleting endpoints", "keys", deletedKeys) } else { - logr.Debugf("[sql.DeleteAllEndpointStatusesNotInKeys] No endpoints to delete") + slog.Debug("No endpoints to delete") } } @@ -424,7 +424,7 @@ func (s *Store) DeleteAllEndpointStatusesNotInKeys(keys []string) int { result, err = s.db.Exec(query, args...) } if err != nil { - logr.Errorf("[sql.DeleteAllEndpointStatusesNotInKeys] Failed to delete rows that do not belong to any of keys=%v: %s", keys, err.Error()) + slog.Error("Failed to delete endpoints not in keys", "keys", keys, "error", err.Error()) return 0 } if s.writeThroughCache != nil { @@ -440,7 +440,7 @@ func (s *Store) DeleteAllEndpointStatusesNotInKeys(keys []string) int { // GetTriggeredEndpointAlert returns whether the triggered alert for the specified endpoint as well as the necessary information to resolve it func (s *Store) GetTriggeredEndpointAlert(ep *endpoint.Endpoint, alert *alert.Alert) (exists bool, resolveKey string, numberOfSuccessesInARow int, err error) { - //logr.Debugf("[sql.GetTriggeredEndpointAlert] Getting triggered alert with checksum=%s for endpoint with key=%s", alert.Checksum(), ep.Key()) + //slog.Debug("Getting triggered endpoint alert", "endpoint_key", ep.Key(), "alert_checksum", alert.Checksum()) err = s.db.QueryRow( "SELECT resolve_key, number_of_successes_in_a_row FROM endpoint_alerts_triggered WHERE endpoint_id = (SELECT endpoint_id FROM endpoints WHERE endpoint_key = $1 LIMIT 1) AND configuration_checksum = $2", ep.Key(), @@ -458,7 +458,7 @@ func (s *Store) GetTriggeredEndpointAlert(ep *endpoint.Endpoint, alert *alert.Al // UpsertTriggeredEndpointAlert inserts/updates a triggered alert for an endpoint // Used for persistence of triggered alerts across application restarts func (s *Store) UpsertTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAlert *alert.Alert) error { - //logr.Debugf("[sql.UpsertTriggeredEndpointAlert] Upserting triggered alert with checksum=%s for endpoint with key=%s", triggeredAlert.Checksum(), ep.Key()) + //slog.Debug("Upserting triggered endpoint alert", "endpoint_key", ep.Key(), "alert_checksum", triggeredAlert.Checksum()) tx, err := s.db.Begin() if err != nil { return err @@ -470,12 +470,12 @@ func (s *Store) UpsertTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAle // This shouldn't happen, but we'll handle it anyway if endpointID, err = s.insertEndpoint(tx, ep); err != nil { _ = tx.Rollback() - logr.Errorf("[sql.UpsertTriggeredEndpointAlert] Failed to create endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to create endpoint", "key", ep.Key(), "error", err.Error()) return err } } else { _ = tx.Rollback() - logr.Errorf("[sql.UpsertTriggeredEndpointAlert] Failed to retrieve id of endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to retrieve endpoint ID", "key", ep.Key(), "error", err.Error()) return err } } @@ -494,7 +494,7 @@ func (s *Store) UpsertTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAle ) if err != nil { _ = tx.Rollback() - logr.Errorf("[sql.UpsertTriggeredEndpointAlert] Failed to persist triggered alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to persist triggered alert", "endpoint", ep.Key(), "error", err.Error()) return err } if err = tx.Commit(); err != nil { @@ -505,7 +505,7 @@ func (s *Store) UpsertTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAle // DeleteTriggeredEndpointAlert deletes a triggered alert for an endpoint func (s *Store) DeleteTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAlert *alert.Alert) error { - //logr.Debugf("[sql.DeleteTriggeredEndpointAlert] Deleting triggered alert with checksum=%s for endpoint with key=%s", triggeredAlert.Checksum(), ep.Key()) + //slog.Debug("Deleting triggered endpoint alert", "endpoint_key", ep.Key(), "alert_checksum", triggeredAlert.Checksum()) _, err := s.db.Exec("DELETE FROM endpoint_alerts_triggered WHERE configuration_checksum = $1 AND endpoint_id = (SELECT endpoint_id FROM endpoints WHERE endpoint_key = $2 LIMIT 1)", triggeredAlert.Checksum(), ep.Key()) return err } @@ -514,7 +514,7 @@ func (s *Store) DeleteTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAle // configurations are not provided in the checksums list. // This prevents triggered alerts that have been removed or modified from lingering in the database. func (s *Store) DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(ep *endpoint.Endpoint, checksums []string) int { - //logr.Debugf("[sql.DeleteAllTriggeredAlertsNotInChecksumsByEndpoint] Deleting triggered alerts for endpoint with key=%s that do not belong to any of checksums=%v", ep.Key(), checksums) + //slog.Debug("Deleting triggered alerts not in checksums", "endpoint_key", ep.Key(), "checksums", checksums) var err error var result sql.Result if len(checksums) == 0 { @@ -535,7 +535,7 @@ func (s *Store) DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(ep *endpoint.En result, err = s.db.Exec(query, args...) } if err != nil { - logr.Errorf("[sql.DeleteAllTriggeredAlertsNotInChecksumsByEndpoint] Failed to delete rows for endpoint with key=%s that do not belong to any of checksums=%v: %s", ep.Key(), checksums, err.Error()) + slog.Error("Failed to delete triggered alerts not in checksums", "endpoint_key", ep.Key(), "checksums", checksums, "error", err.Error()) return 0 } // Return number of rows deleted @@ -585,7 +585,7 @@ func (s *Store) Close() { // insertEndpoint inserts an endpoint in the store and returns the generated id of said endpoint func (s *Store) insertEndpoint(tx *sql.Tx, ep *endpoint.Endpoint) (int64, error) { - //logr.Debugf("[sql.insertEndpoint] Inserting endpoint with group=%s and name=%s", ep.Group, ep.Name) + //slog.Debug("Inserting endpoint", "key", ep.Key(), "name", ep.Name, "group", ep.Group) var id int64 err := tx.QueryRow( "INSERT INTO endpoints (endpoint_key, endpoint_name, endpoint_group) VALUES ($1, $2, $3) RETURNING endpoint_id", @@ -725,12 +725,12 @@ func (s *Store) getEndpointStatusByKey(tx *sql.Tx, key string, parameters *pagin endpointStatus := endpoint.NewStatus(group, endpointName) if parameters.EventsPageSize > 0 { if endpointStatus.Events, err = s.getEndpointEventsByEndpointID(tx, endpointID, parameters.EventsPage, parameters.EventsPageSize); err != nil { - logr.Errorf("[sql.getEndpointStatusByKey] Failed to retrieve events for key=%s: %s", key, err.Error()) + slog.Error("Failed to retrieve events for endpoint", "key", key, "error", err.Error()) } } if parameters.ResultsPageSize > 0 { if endpointStatus.Results, err = s.getEndpointResultsByEndpointID(tx, endpointID, parameters.ResultsPage, parameters.ResultsPageSize); err != nil { - logr.Errorf("[sql.getEndpointStatusByKey] Failed to retrieve results for key=%s: %s", key, err.Error()) + slog.Error("Failed to retrieve results for endpoint", "key", key, "error", err.Error()) } } if s.writeThroughCache != nil { @@ -811,7 +811,7 @@ func (s *Store) getEndpointResultsByEndpointID(tx *sql.Tx, endpointID int64, pag var joinedErrors string err = rows.Scan(&id, &result.Success, &joinedErrors, &result.Connected, &result.HTTPStatus, &result.DNSRCode, &result.CertificateExpiration, &result.DomainExpiration, &result.Hostname, &result.IP, &result.Duration, &result.Timestamp) if err != nil { - logr.Errorf("[sql.getEndpointResultsByEndpointID] Silently failed to retrieve endpoint result for endpointID=%d: %s", endpointID, err.Error()) + slog.Error("Silently failed to retrieve endpoint result", "endpoint_id", endpointID, "error", err.Error()) err = nil } if len(joinedErrors) != 0 { @@ -1207,7 +1207,7 @@ func (s *Store) GetAllSuiteStatuses(params *paging.SuiteStatusParams) ([]*suite. status.Results, err = s.getSuiteResults(tx, suiteID, page, pageSize) if err != nil { - logr.Errorf("[sql.GetAllSuiteStatuses] Failed to retrieve results for suite_id=%d: %s", suiteID, err.Error()) + slog.Error("Failed to retrieve suite results", "suite_id", suiteID, "error", err.Error()) } // Populate Name and Group fields on each result for _, result := range status.Results { @@ -1267,7 +1267,7 @@ func (s *Store) GetSuiteStatusByKey(key string, params *paging.SuiteStatusParams status.Results, err = s.getSuiteResults(tx, suiteID, page, pageSize) if err != nil { - logr.Errorf("[sql.GetSuiteStatusByKey] Failed to retrieve results for suite_id=%d: %s", suiteID, err.Error()) + slog.Error("Failed to retrieve suite results", "suite_id", suiteID, "error", err.Error()) } // Populate Name and Group fields on each result for _, result := range status.Results { @@ -1295,11 +1295,11 @@ func (s *Store) InsertSuiteResult(su *suite.Suite, result *suite.Result) error { if errors.Is(err, common.ErrSuiteNotFound) { // Suite doesn't exist in the database, insert it if suiteID, err = s.insertSuite(tx, su); err != nil { - logr.Errorf("[sql.InsertSuiteResult] Failed to create suite with key=%s: %s", su.Key(), err.Error()) + slog.Error("Failed to insert suite", "key", su.Key(), "error", err.Error()) return err } } else { - logr.Errorf("[sql.InsertSuiteResult] Failed to retrieve id of suite with key=%s: %s", su.Key(), err.Error()) + slog.Error("Failed to retrieve suite ID", "key", su.Key(), "error", err.Error()) return err } } @@ -1332,28 +1332,28 @@ func (s *Store) InsertSuiteResult(su *suite.Suite, result *suite.Result) error { if errors.Is(err, common.ErrEndpointNotFound) { // Endpoint doesn't exist, create it if epID, err = s.insertEndpoint(tx, ep); err != nil { - logr.Errorf("[sql.InsertSuiteResult] Failed to create endpoint %s: %s", epResult.Name, err.Error()) + slog.Error("Failed to create endpoint", "name", epResult.Name, "error", err.Error()) continue } } else { - logr.Errorf("[sql.InsertSuiteResult] Failed to get endpoint %s: %s", epResult.Name, err.Error()) + slog.Error("Failed to get endpoint", "name", epResult.Name, "error", err.Error()) continue } } // InsertEndpointResult the endpoint result with suite linkage err = s.insertEndpointResultWithSuiteID(tx, epID, epResult, &suiteResultID) if err != nil { - logr.Errorf("[sql.InsertSuiteResult] Failed to insert endpoint result for %s: %s", epResult.Name, err.Error()) + slog.Error("Failed to insert endpoint result for suite", "endpoint_name", epResult.Name, "error", err.Error()) } } // Clean up old suite results numberOfResults, err := s.getNumberOfSuiteResultsByID(tx, suiteID) if err != nil { - logr.Errorf("[sql.InsertSuiteResult] Failed to retrieve total number of results for suite with key=%s: %s", su.Key(), err.Error()) + slog.Error("Failed to retrieve total number of results for suite", "key", su.Key(), "error", err.Error()) } else { if numberOfResults > int64(s.maximumNumberOfResults+resultsAboveMaximumCleanUpThreshold) { if err = s.deleteOldSuiteResults(tx, suiteID); err != nil { - logr.Errorf("[sql.InsertSuiteResult] Failed to delete old results for suite with key=%s: %s", su.Key(), err.Error()) + slog.Error("Failed to delete old results for suite", "key", su.Key(), "error", err.Error()) } } } @@ -1365,13 +1365,13 @@ func (s *Store) InsertSuiteResult(su *suite.Suite, result *suite.Result) error { // DeleteAllSuiteStatusesNotInKeys removes all suite statuses that are not within the keys provided func (s *Store) DeleteAllSuiteStatusesNotInKeys(keys []string) int { - logr.Debugf("[sql.DeleteAllSuiteStatusesNotInKeys] Called with %d keys", len(keys)) + slog.Debug("Delete all suite statuses not in keys started", "keys_count", len(keys)) if len(keys) == 0 { // Delete all suites - logr.Debugf("[sql.DeleteAllSuiteStatusesNotInKeys] No keys provided, deleting all suites") + slog.Debug("No keys provided, deleting all suites") result, err := s.db.Exec("DELETE FROM suites") if err != nil { - logr.Errorf("[sql.DeleteAllSuiteStatusesNotInKeys] Failed to delete all suites: %s", err.Error()) + slog.Error("Failed to delete all suites", "error", err.Error()) return 0 } rowsAffected, _ := result.RowsAffected() @@ -1407,12 +1407,12 @@ func (s *Store) DeleteAllSuiteStatusesNotInKeys(keys []string) int { } } if len(deletedKeys) > 0 { - logr.Infof("[sql.DeleteAllSuiteStatusesNotInKeys] Deleting suites with keys: %v", deletedKeys) + slog.Info("Deleting suites with keys", "keys", deletedKeys) } } result, err := s.db.Exec(query, args...) if err != nil { - logr.Errorf("[sql.DeleteAllSuiteStatusesNotInKeys] Failed to delete suites: %s", err.Error()) + slog.Error("Failed to delete suites", "error", err.Error()) return 0 } rowsAffected, _ := result.RowsAffected() @@ -1463,7 +1463,7 @@ func (s *Store) getSuiteResults(tx *sql.Tx, suiteID int64, page, pageSize int) ( (page-1)*pageSize, ) if err != nil { - logr.Errorf("[sql.getSuiteResults] Query failed: %v", err) + slog.Error("Failed to retrieve suite results", "suite_id", suiteID, "error", err.Error()) return nil, err } defer rows.Close() @@ -1481,7 +1481,7 @@ func (s *Store) getSuiteResults(tx *sql.Tx, suiteID int64, page, pageSize int) ( var nanoseconds int64 err = rows.Scan(&suiteResultID, &result.Success, &joinedErrors, &nanoseconds, &result.Timestamp) if err != nil { - logr.Errorf("[sql.getSuiteResults] Failed to scan suite result: %s", err.Error()) + slog.Error("Failed to scan suite result", "suite_id", suiteID, "error", err.Error()) continue } result.Duration = time.Duration(nanoseconds) @@ -1519,7 +1519,7 @@ func (s *Store) getSuiteResults(tx *sql.Tx, suiteID int64, page, pageSize int) ( ORDER BY er.endpoint_result_id `, resultID) if err != nil { - logr.Errorf("[sql.getSuiteResults] Failed to get endpoint results for suite_result_id=%d: %s", resultID, err.Error()) + slog.Error("Failed to retrieve endpoint results for suite result", "suite_result_id", resultID, "error", err.Error()) continue } // Map to store endpoint results by their ID for condition lookup @@ -1535,7 +1535,7 @@ func (s *Store) getSuiteResults(tx *sql.Tx, suiteID int64, page, pageSize int) ( var timestamp time.Time err = epRows.Scan(&epResultID, &name, &success, &joinedErrors, &duration, ×tamp) if err != nil { - logr.Errorf("[sql.getSuiteResults] Failed to scan endpoint result: %s", err.Error()) + slog.Error("Failed to scan endpoint result", "suite_result_id", resultID, "error", err.Error()) continue } epResult := &endpoint.Result{ @@ -1568,7 +1568,7 @@ func (s *Store) getSuiteResults(tx *sql.Tx, suiteID int64, page, pageSize int) ( condRows, err := tx.Query(condQuery, args...) if err != nil { - logr.Errorf("[sql.getSuiteResults] Failed to get condition results for suite_result_id=%d: %s", resultID, err.Error()) + slog.Error("Failed to retrieve condition results for suite result", "suite_result_id", resultID, "error", err.Error()) } else { condCount := 0 for condRows.Next() { @@ -1576,7 +1576,7 @@ func (s *Store) getSuiteResults(tx *sql.Tx, suiteID int64, page, pageSize int) ( conditionResult := &endpoint.ConditionResult{} var epResultID int64 if err = condRows.Scan(&epResultID, &conditionResult.Condition, &conditionResult.Success); err != nil { - logr.Errorf("[sql.getSuiteResults] Failed to scan condition result: %s", err.Error()) + slog.Error("Failed to scan condition result", "suite_result_id", resultID, "error", err.Error()) continue } if epResult, exists := epResultMap[epResultID]; exists { @@ -1585,12 +1585,12 @@ func (s *Store) getSuiteResults(tx *sql.Tx, suiteID int64, page, pageSize int) ( } condRows.Close() if condCount > 0 { - logr.Debugf("[sql.getSuiteResults] Found %d condition results for suite_result_id=%d", condCount, resultID) + slog.Debug("Found condition results for suite result", "suite_result_id", resultID, "count", condCount) } } } if epCount > 0 { - logr.Debugf("[sql.getSuiteResults] Found %d endpoint results for suite_result_id=%d", epCount, resultID) + slog.Debug("Found endpoint results for suite result", "suite_result_id", resultID, "count", epCount) } } // Extract just the results for return diff --git a/storage/store/store.go b/storage/store/store.go index 693faa627..c612e4faf 100644 --- a/storage/store/store.go +++ b/storage/store/store.go @@ -2,6 +2,7 @@ package store import ( "context" + "log/slog" "time" "github.com/TwiN/gatus/v5/alerting/alert" @@ -11,7 +12,6 @@ import ( "github.com/TwiN/gatus/v5/storage/store/common/paging" "github.com/TwiN/gatus/v5/storage/store/memory" "github.com/TwiN/gatus/v5/storage/store/sql" - "github.com/TwiN/logr" ) // Store is the interface that each store should implement @@ -107,7 +107,7 @@ var ( func Get() Store { if !initialized { // This only happens in tests - logr.Info("[store.Get] Provider requested before it was initialized, automatically initializing") + slog.Warn("Provider requested before it was initialized, automatically initializing") err := Initialize(nil) if err != nil { panic("failed to automatically initialize store: " + err.Error()) @@ -126,14 +126,14 @@ func Initialize(cfg *storage.Config) error { } if cfg == nil { // This only happens in tests - logr.Warn("[store.Initialize] nil storage config passed as parameter. This should only happen in tests. Defaulting to an empty config.") + slog.Warn("nil storage config passed as parameter. This should only happen in tests. Defaulting to an empty config.") cfg = &storage.Config{ MaximumNumberOfResults: storage.DefaultMaximumNumberOfResults, MaximumNumberOfEvents: storage.DefaultMaximumNumberOfEvents, } } if len(cfg.Path) == 0 && cfg.Type != storage.TypePostgres { - logr.Infof("[store.Initialize] Creating storage provider of type=%s", cfg.Type) + slog.Info("Creating storage provider", "type", cfg.Type) } ctx, cancelFunc = context.WithCancel(context.Background()) switch cfg.Type { @@ -157,12 +157,12 @@ func autoSave(ctx context.Context, store Store, interval time.Duration) { for { select { case <-ctx.Done(): - logr.Info("[store.autoSave] Stopping active job") + slog.Info("Stopping active store auto-save task") return case <-ticker.C: - logr.Info("[store.autoSave] Saving") + slog.Info("Auto-saving store data") if err := store.Save(); err != nil { - logr.Errorf("[store.autoSave] Save failed: %s", err.Error()) + slog.Error("Failed to auto-save store data", "error", err) } } } diff --git a/watchdog/alerting.go b/watchdog/alerting.go index c71ac3334..f52330508 100644 --- a/watchdog/alerting.go +++ b/watchdog/alerting.go @@ -2,14 +2,13 @@ package watchdog import ( "errors" - "log" + "log/slog" "os" "time" "github.com/TwiN/gatus/v5/alerting" "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/storage/store" - "github.com/TwiN/logr" ) // HandleAlerting takes care of alerts to resolve and alerts to trigger based on result success or failure @@ -43,18 +42,18 @@ func handleAlertsToTrigger(ep *endpoint.Endpoint, result *endpoint.Result, alert sendReminder := endpointAlert.Triggered && endpointAlert.MinimumReminderInterval > 0 && time.Since(lastReminderSent) >= endpointAlert.MinimumReminderInterval // If neither initial alert nor reminder needs to be sent, skip to the next alert if !sendInitialAlert && !sendReminder { - logr.Debugf("[watchdog.handleAlertsToTrigger] Alert for endpoint=%s with description='%s' is not due for triggering or reminding, skipping", ep.Name, endpointAlert.GetDescription()) + slog.Debug("Alert not due for triggering or reminding", "endpoint", ep.Name, "description", endpointAlert.GetDescription()) continue } alertProvider := alertingConfig.GetAlertingProviderByAlertType(endpointAlert.Type) if alertProvider != nil { - logr.Infof("[watchdog.handleAlertsToTrigger] Sending %s alert because alert for endpoint with key=%s with description='%s' has been TRIGGERED", endpointAlert.Type, ep.Key(), endpointAlert.GetDescription()) + slog.Info("Sending alert", "type", endpointAlert.Type, "endpoint", ep.Name, "description", endpointAlert.GetDescription()) var err error alertType := "reminder" if sendInitialAlert { alertType = "initial" } - log.Printf("[watchdog.handleAlertsToTrigger] Sending %s %s alert because alert for endpoint=%s with description='%s' has been TRIGGERED", alertType, endpointAlert.Type, ep.Name, endpointAlert.GetDescription()) + slog.Info("Sending alert", "type", endpointAlert.Type, "alert_type", alertType, "endpoint", ep.Name, "description", endpointAlert.GetDescription()) if os.Getenv("MOCK_ALERT_PROVIDER") == "true" { if os.Getenv("MOCK_ALERT_PROVIDER_ERROR") == "true" { err = errors.New("error") @@ -63,7 +62,7 @@ func handleAlertsToTrigger(ep *endpoint.Endpoint, result *endpoint.Result, alert err = alertProvider.Send(ep, endpointAlert, result, false) } if err != nil { - logr.Errorf("[watchdog.handleAlertsToTrigger] Failed to send an alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to send alert", "type", endpointAlert.Type, "endpoint", ep.Name, "description", endpointAlert.GetDescription(), "error", err.Error()) } else { // Mark initial alert as triggered and update last reminder time if sendInitialAlert { @@ -71,11 +70,11 @@ func handleAlertsToTrigger(ep *endpoint.Endpoint, result *endpoint.Result, alert } ep.LastReminderSent = time.Now() if err := store.Get().UpsertTriggeredEndpointAlert(ep, endpointAlert); err != nil { - logr.Errorf("[watchdog.handleAlertsToTrigger] Failed to persist triggered endpoint alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to persist triggered endpoint alert", "endpoint", ep.Name, "description", endpointAlert.GetDescription(), "error", err.Error()) } } } else { - logr.Warnf("[watchdog.handleAlertsToTrigger] Not sending alert of type=%s endpoint with key=%s despite being TRIGGERED, because the provider wasn't configured properly", endpointAlert.Type, ep.Key()) + slog.Warn("Not sending alert because provider is not configured", "type", endpointAlert.Type, "endpoint", ep.Name, "description", endpointAlert.GetDescription()) } } } @@ -87,7 +86,7 @@ func handleAlertsToResolve(ep *endpoint.Endpoint, result *endpoint.Result, alert if isStillBelowSuccessThreshold && endpointAlert.IsEnabled() && endpointAlert.Triggered { // Persist NumberOfSuccessesInARow if err := store.Get().UpsertTriggeredEndpointAlert(ep, endpointAlert); err != nil { - logr.Errorf("[watchdog.handleAlertsToResolve] Failed to update triggered endpoint alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to update triggered endpoint alert", "endpoint", ep.Name, "description", endpointAlert.GetDescription(), "error", err.Error()) } } if !endpointAlert.IsEnabled() || !endpointAlert.Triggered || isStillBelowSuccessThreshold { @@ -97,21 +96,21 @@ func handleAlertsToResolve(ep *endpoint.Endpoint, result *endpoint.Result, alert // Further explanation can be found on Alert's Triggered field. endpointAlert.Triggered = false if err := store.Get().DeleteTriggeredEndpointAlert(ep, endpointAlert); err != nil { - logr.Errorf("[watchdog.handleAlertsToResolve] Failed to delete persisted triggered endpoint alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to delete persisted triggered endpoint alert", "endpoint", ep.Name, "description", endpointAlert.GetDescription(), "error", err.Error()) } if !endpointAlert.IsSendingOnResolved() { - logr.Debugf("[watchdog.handleAlertsToResolve] Not sending request to provider of alert with type=%s for endpoint with key=%s despite being RESOLVED, because send-on-resolved is set to false", endpointAlert.Type, ep.Key()) + slog.Debug("Not sending alert on resolved because send-on-resolved is false", "type", endpointAlert.Type, "endpoint", ep.Name) continue } alertProvider := alertingConfig.GetAlertingProviderByAlertType(endpointAlert.Type) if alertProvider != nil { - logr.Infof("[watchdog.handleAlertsToResolve] Sending %s alert because alert for endpoint with key=%s with description='%s' has been RESOLVED", endpointAlert.Type, ep.Key(), endpointAlert.GetDescription()) + slog.Info("Sending resolved alert", "type", endpointAlert.Type, "endpoint", ep.Name, "description", endpointAlert.GetDescription()) err := alertProvider.Send(ep, endpointAlert, result, true) if err != nil { - logr.Errorf("[watchdog.handleAlertsToResolve] Failed to send an alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + slog.Error("Failed to send resolved alert", "type", endpointAlert.Type, "endpoint", ep.Name, "description", endpointAlert.GetDescription(), "error", err.Error()) } } else { - logr.Warnf("[watchdog.handleAlertsToResolve] Not sending alert of type=%s for endpoint with key=%s despite being RESOLVED, because the provider wasn't configured properly", endpointAlert.Type, ep.Key()) + slog.Warn("Not sending resolved alert because provider is not configured", "type", endpointAlert.Type, "endpoint", ep.Name) } } ep.NumberOfFailuresInARow = 0 diff --git a/watchdog/endpoint.go b/watchdog/endpoint.go index 9c89bafae..42aeeed8a 100644 --- a/watchdog/endpoint.go +++ b/watchdog/endpoint.go @@ -2,13 +2,14 @@ package watchdog import ( "context" + "log/slog" "time" "github.com/TwiN/gatus/v5/config" "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/logging" "github.com/TwiN/gatus/v5/metrics" "github.com/TwiN/gatus/v5/storage/store" - "github.com/TwiN/logr" ) // monitorEndpoint a single endpoint in a loop @@ -21,7 +22,7 @@ func monitorEndpoint(ep *endpoint.Endpoint, cfg *config.Config, extraLabels []st for { select { case <-ctx.Done(): - logr.Warnf("[watchdog.monitorEndpoint] Canceling current execution of group=%s; endpoint=%s; key=%s", ep.Group, ep.Name, ep.Key()) + slog.Warn("Canceling current execution", ep.GetLogAttribute()) return case <-ticker.C: executeEndpoint(ep, cfg, extraLabels) @@ -33,47 +34,50 @@ func monitorEndpoint(ep *endpoint.Endpoint, cfg *config.Config, extraLabels []st } func executeEndpoint(ep *endpoint.Endpoint, cfg *config.Config, extraLabels []string) { + logger := slog.With(ep.GetLogAttribute()) + // Acquire semaphore to limit concurrent endpoint monitoring if err := monitoringSemaphore.Acquire(ctx, 1); err != nil { // Only fails if context is cancelled (during shutdown) - logr.Debugf("[watchdog.executeEndpoint] Context cancelled, skipping execution: %s", err.Error()) + logger.Debug("Context cancelled; skipping execution", "error", err.Error()) return } defer monitoringSemaphore.Release(1) // If there's a connectivity checker configured, check if Gatus has internet connectivity if cfg.Connectivity != nil && cfg.Connectivity.Checker != nil && !cfg.Connectivity.Checker.IsConnected() { - logr.Infof("[watchdog.executeEndpoint] No connectivity; skipping execution") + logger.Info("No connectivity, skipping execution") return } - logr.Debugf("[watchdog.executeEndpoint] Monitoring group=%s; endpoint=%s; key=%s", ep.Group, ep.Name, ep.Key()) + + logger.Debug("Monitoring start") result := ep.EvaluateHealth() if cfg.Metrics { metrics.PublishMetricsForEndpoint(ep, result, extraLabels) } UpdateEndpointStatus(ep, result) - if logr.GetThreshold() == logr.LevelDebug && !result.Success { - logr.Debugf("[watchdog.executeEndpoint] Monitored group=%s; endpoint=%s; key=%s; success=%v; errors=%d; duration=%s; body=%s", ep.Group, ep.Name, ep.Key(), result.Success, len(result.Errors), result.Duration.Round(time.Millisecond), result.Body) + if logging.Level() <= slog.LevelDebug && !result.Success { + logger.Debug("Monitoring done with errors", result.GetLogAttribute(), "body", result.Body) } else { - logr.Infof("[watchdog.executeEndpoint] Monitored group=%s; endpoint=%s; key=%s; success=%v; errors=%d; duration=%s", ep.Group, ep.Name, ep.Key(), result.Success, len(result.Errors), result.Duration.Round(time.Millisecond)) + logger.Info("Monitoring done", result.GetLogAttribute()) } inEndpointMaintenanceWindow := false for _, maintenanceWindow := range ep.MaintenanceWindows { if maintenanceWindow.IsUnderMaintenance() { - logr.Debug("[watchdog.executeEndpoint] Under endpoint maintenance window") + logger.Debug("Under endpoint maintenance window") inEndpointMaintenanceWindow = true } } if !cfg.Maintenance.IsUnderMaintenance() && !inEndpointMaintenanceWindow { HandleAlerting(ep, result, cfg.Alerting) } else { - logr.Debug("[watchdog.executeEndpoint] Not handling alerting because currently in the maintenance window") + logger.Debug("Not handling alerting due to maintenance window") } - logr.Debugf("[watchdog.executeEndpoint] Waiting for interval=%s before monitoring group=%s endpoint=%s (key=%s) again", ep.Interval, ep.Group, ep.Name, ep.Key()) + logger.Debug("Wait for next monitoring", "interval", ep.Interval) } // UpdateEndpointStatus persists the endpoint result in the storage func UpdateEndpointStatus(ep *endpoint.Endpoint, result *endpoint.Result) { if err := store.Get().InsertEndpointResult(ep, result); err != nil { - logr.Errorf("[watchdog.UpdateEndpointStatus] Failed to insert result in storage: %s", err.Error()) + slog.Error("Failed to insert result in storage", "error", err.Error()) } } diff --git a/watchdog/external_endpoint.go b/watchdog/external_endpoint.go index 3f8409b69..862082d78 100644 --- a/watchdog/external_endpoint.go +++ b/watchdog/external_endpoint.go @@ -2,13 +2,13 @@ package watchdog import ( "context" + "log/slog" "time" "github.com/TwiN/gatus/v5/config" "github.com/TwiN/gatus/v5/config/endpoint" "github.com/TwiN/gatus/v5/metrics" "github.com/TwiN/gatus/v5/storage/store" - "github.com/TwiN/logr" ) func monitorExternalEndpointHeartbeat(ee *endpoint.ExternalEndpoint, cfg *config.Config, extraLabels []string, ctx context.Context) { @@ -17,7 +17,7 @@ func monitorExternalEndpointHeartbeat(ee *endpoint.ExternalEndpoint, cfg *config for { select { case <-ctx.Done(): - logr.Warnf("[watchdog.monitorExternalEndpointHeartbeat] Canceling current execution of group=%s; endpoint=%s; key=%s", ee.Group, ee.Name, ee.Key()) + slog.Warn("Canceling current external endpoint execution", "group", ee.Group, "name", ee.Name, "key", ee.Key()) return case <-ticker.C: executeExternalEndpointHeartbeat(ee, cfg, extraLabels) @@ -26,23 +26,26 @@ func monitorExternalEndpointHeartbeat(ee *endpoint.ExternalEndpoint, cfg *config } func executeExternalEndpointHeartbeat(ee *endpoint.ExternalEndpoint, cfg *config.Config, extraLabels []string) { + logger := slog.With(ee.GetLogAttribute()) + // Acquire semaphore to limit concurrent external endpoint monitoring if err := monitoringSemaphore.Acquire(ctx, 1); err != nil { // Only fails if context is cancelled (during shutdown) - logr.Debugf("[watchdog.executeExternalEndpointHeartbeat] Context cancelled, skipping execution: %s", err.Error()) + logger.Debug("Context cancelled; skipping execution", "error", err.Error()) return } defer monitoringSemaphore.Release(1) // If there's a connectivity checker configured, check if Gatus has internet connectivity if cfg.Connectivity != nil && cfg.Connectivity.Checker != nil && !cfg.Connectivity.Checker.IsConnected() { - logr.Infof("[watchdog.monitorExternalEndpointHeartbeat] No connectivity; skipping execution") + logger.Info("No connectivity, skipping execution") return } - logr.Debugf("[watchdog.monitorExternalEndpointHeartbeat] Checking heartbeat for group=%s; endpoint=%s; key=%s", ee.Group, ee.Name, ee.Key()) + logger.Debug("Monitoring start") convertedEndpoint := ee.ToEndpoint() hasReceivedResultWithinHeartbeatInterval, err := store.Get().HasEndpointStatusNewerThan(ee.Key(), time.Now().Add(-ee.Heartbeat.Interval)) if err != nil { - logr.Errorf("[watchdog.monitorExternalEndpointHeartbeat] Failed to check if endpoint has received a result within the heartbeat interval: %s", err.Error()) + slog.Error("Failed to check if external endpoint has received a result within the heartbeat interval", "group", ee.Group, "name", ee.Name, "key", ee.Key(), "error", err.Error()) + logger.Error("Monitoring error", "error", err.Error()) return } if hasReceivedResultWithinHeartbeatInterval { @@ -50,7 +53,7 @@ func executeExternalEndpointHeartbeat(ee *endpoint.ExternalEndpoint, cfg *config // skip the rest. We don't have to worry about alerting or metrics, because if the previous heartbeat failed // while this one succeeds, it implies that there was a new result pushed, and that result being pushed // should've resolved the alert. - logr.Infof("[watchdog.monitorExternalEndpointHeartbeat] Checked heartbeat for group=%s; endpoint=%s; key=%s; success=%v; errors=%d", ee.Group, ee.Name, ee.Key(), hasReceivedResultWithinHeartbeatInterval, 0) + logger.Info("Monitoring success, heartbeat received within interval", "success", hasReceivedResultWithinHeartbeatInterval, "errors", 0) return } // All code after this point assumes the heartbeat failed @@ -63,11 +66,11 @@ func executeExternalEndpointHeartbeat(ee *endpoint.ExternalEndpoint, cfg *config metrics.PublishMetricsForEndpoint(convertedEndpoint, result, extraLabels) } UpdateEndpointStatus(convertedEndpoint, result) - logr.Infof("[watchdog.monitorExternalEndpointHeartbeat] Checked heartbeat for group=%s; endpoint=%s; key=%s; success=%v; errors=%d; duration=%s", ee.Group, ee.Name, ee.Key(), result.Success, len(result.Errors), result.Duration.Round(time.Millisecond)) + logger.Info("Monitoring done", "success", result.Success, "errors", len(result.Errors), "duration", result.Duration.Round(time.Millisecond)) inEndpointMaintenanceWindow := false for _, maintenanceWindow := range ee.MaintenanceWindows { if maintenanceWindow.IsUnderMaintenance() { - logr.Debug("[watchdog.monitorExternalEndpointHeartbeat] Under endpoint maintenance window") + slog.Debug("Under external endpoint maintenance window") inEndpointMaintenanceWindow = true } } @@ -77,7 +80,6 @@ func executeExternalEndpointHeartbeat(ee *endpoint.ExternalEndpoint, cfg *config ee.NumberOfSuccessesInARow = convertedEndpoint.NumberOfSuccessesInARow ee.NumberOfFailuresInARow = convertedEndpoint.NumberOfFailuresInARow } else { - logr.Debug("[watchdog.monitorExternalEndpointHeartbeat] Not handling alerting because currently in the maintenance window") + logger.Debug("Not handling alerting due to maintenance window") } - logr.Debugf("[watchdog.monitorExternalEndpointHeartbeat] Waiting for interval=%s before checking heartbeat for group=%s endpoint=%s (key=%s) again", ee.Heartbeat.Interval, ee.Group, ee.Name, ee.Key()) } diff --git a/watchdog/suite.go b/watchdog/suite.go index 39d2f0c6e..43780a79e 100644 --- a/watchdog/suite.go +++ b/watchdog/suite.go @@ -2,13 +2,13 @@ package watchdog import ( "context" + "log/slog" "time" "github.com/TwiN/gatus/v5/config" "github.com/TwiN/gatus/v5/config/suite" "github.com/TwiN/gatus/v5/metrics" "github.com/TwiN/gatus/v5/storage/store" - "github.com/TwiN/logr" ) // monitorSuite monitors a suite by executing it at regular intervals @@ -21,7 +21,7 @@ func monitorSuite(s *suite.Suite, cfg *config.Config, extraLabels []string, ctx for { select { case <-ctx.Done(): - logr.Warnf("[watchdog.monitorSuite] Canceling monitoring for suite=%s", s.Name) + slog.Warn("Canceling current execution", s.GetLogAttribute()) return case <-ticker.C: executeSuite(s, cfg, extraLabels) @@ -31,19 +31,21 @@ func monitorSuite(s *suite.Suite, cfg *config.Config, extraLabels []string, ctx // executeSuite executes a suite with proper concurrency control func executeSuite(s *suite.Suite, cfg *config.Config, extraLabels []string) { + logger := slog.With(s.GetLogAttribute()) + // Acquire semaphore to limit concurrent suite monitoring if err := monitoringSemaphore.Acquire(ctx, 1); err != nil { // Only fails if context is cancelled (during shutdown) - logr.Debugf("[watchdog.executeSuite] Context cancelled, skipping execution: %s", err.Error()) + logger.Debug("Context cancelled; skipping execution", "error", err.Error()) return } defer monitoringSemaphore.Release(1) // Check connectivity if configured if cfg.Connectivity != nil && cfg.Connectivity.Checker != nil && !cfg.Connectivity.Checker.IsConnected() { - logr.Infof("[watchdog.executeSuite] No connectivity; skipping suite=%s", s.Name) + logger.Info("No connectivity, skipping execution") return } - logr.Debugf("[watchdog.executeSuite] Monitoring group=%s; suite=%s; key=%s", s.Group, s.Name, s.Key()) + logger.Debug("Monitoring start") // Execute the suite using its Execute method result := s.Execute() // Publish metrics for the suite execution @@ -62,7 +64,7 @@ func executeSuite(s *suite.Suite, cfg *config.Config, extraLabels []string) { inEndpointMaintenanceWindow := false for _, maintenanceWindow := range ep.MaintenanceWindows { if maintenanceWindow.IsUnderMaintenance() { - logr.Debug("[watchdog.executeSuite] Endpoint under maintenance window") + logger.Debug("Under endpoint maintenance window", ep.GetLogAttribute()) inEndpointMaintenanceWindow = true break } @@ -73,12 +75,12 @@ func executeSuite(s *suite.Suite, cfg *config.Config, extraLabels []string) { } } } - logr.Infof("[watchdog.executeSuite] Completed suite=%s; success=%v; errors=%d; duration=%v; endpoints_executed=%d/%d", s.Name, result.Success, len(result.Errors), result.Duration, len(result.EndpointResults), len(s.Endpoints)) + logger.Info("Monitoring done", result.GetLogAttribute()) } // UpdateSuiteStatus persists the suite result in the database func UpdateSuiteStatus(s *suite.Suite, result *suite.Result) { if err := store.Get().InsertSuiteResult(s, result); err != nil { - logr.Errorf("[watchdog.executeSuite] Failed to insert suite result for suite=%s: %v", s.Name, err) + slog.Error("Failed to insert suite result", "suite", s.Name, "error", err.Error()) } }