* feat: add dynamic labels support for Prometheus metrics - Add `toBoolPtr` function to convert a bool to a bool pointer - Add `contains` function to check if a key exists in a slice - Add `GetMetricLabels` method to `Config` struct to return unique metric labels from enabled endpoints - Change file permission notation from `0644` to `0o644` in `config_test.go` - Add `Labels` field to `Endpoint` struct for key-value pairs - Initialize Prometheus metrics with dynamic labels from configuration - Modify `PublishMetricsForEndpoint` to include dynamic labels - Add test for `GetMetricLabels` method in `config_test.go` - Update `watchdog` to pass labels to monitoring and execution functions Signed-off-by: appleboy <appleboy.tw@gmail.com> * refactor: refactor pointer conversion utility and update related tests - Rename `toBoolPtr` function to a generic `toPtr` function - Update tests to use the new `toPtr` function instead of `toBoolPtr` Signed-off-by: appleboy <appleboy.tw@gmail.com> * refactor: refactor utility functions and improve test coverage - Move `toPtr` and `contains` utility functions to a new file `util.go` Signed-off-by: appleboy <appleboy.tw@gmail.com> * missing labels parameter * refactor: reorder parameters in metrics-related functions and tests - Reorder parameters in `PublishMetricsForEndpoint` function - Update test cases to match the new parameter order in `PublishMetricsForEndpoint` - Reorder parameters in `monitor` function - Adjust `monitor` function calls to match the new parameter order - Reorder parameters in `execute` function call to `PublishMetricsForEndpoint` Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com> * Update main.go * Update config/config.go * docs: improve documentation formatting, examples, and readability - Add multiple blank lines for spacing in the README file - Fix formatting issues in markdown tables - Correct deprecated formatting for Teams alerts - Replace single quotes with double quotes in JSON examples - Add new sections and examples for various configurations and endpoints - Improve readability and consistency in the documentation - Update links and references to examples and configurations Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com> * docs: enhance custom labels support in Prometheus metrics - Add a section for custom labels in the README - Include an example configuration for custom labels in Prometheus metrics initialization Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com> * refactor: rename and refactor metric labels to use ExtraLabels - Rename the endpoint metric labels field from Labels to ExtraLabels and update its YAML tag accordingly - Update code and tests to use ExtraLabels instead of Labels for metrics - Replace GetMetricLabels with GetUniqueExtraMetricLabels and adjust usages throughout the codebase - Ensure all metric publishing and monitoring functions accept and use the new extraLabels naming and semantics - Update tests to verify correct extraction and handling of ExtraLabels for enabled endpoints Signed-off-by: appleboy <appleboy.tw@gmail.com> * refactor: refactor parameter order for monitor and execute for consistency - Change the order of parameters for monitor and execute functions to group extraLabels consistently as the last argument before context. - Update all relevant function calls and signatures to reflect the new parameter order. - Replace usage of labels with extraLabels for clarity and consistency. Signed-off-by: appleboy <appleboy.tw@gmail.com> * test: improve initialization and labeling of Prometheus metrics - Add a test to verify that Prometheus metrics initialize correctly with extra labels. - Ensure metrics variables are properly initialized and not nil. - Check that WithLabelValues accepts both default and extra labels without causing a panic. Signed-off-by: appleboy <appleboy.tw@gmail.com> * test: improve Prometheus metrics testing for extra label handling - Remove a redundant test for WithLabelValues label length. - Add a new test to verify that extraLabels are correctly included in exported Prometheus metrics. Signed-off-by: appleboy <appleboy.tw@gmail.com> * refactor: refactor metrics to support custom Prometheus registries - Refactor metrics initialization to accept a custom Prometheus registry, defaulting to the global registry when nil - Replace promauto with direct metric construction and explicit registration - Update tests to use dedicated, isolated registries instead of the default global registry Signed-off-by: appleboy <appleboy.tw@gmail.com> * Revert README.md to a previous version * docs: document support for custom metric labels in endpoints - Add documentation section explaining support for custom labels on metrics - Provide YAML configuration example illustrating the new labels field for endpoints - Update table of contents to include the custom labels section Signed-off-by: appleboy <appleboy.tw@gmail.com> --------- Signed-off-by: appleboy <appleboy.tw@gmail.com> Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com> Co-authored-by: TwiN <twin@linux.com>
84 lines
3.3 KiB
Go
84 lines
3.3 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"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/gatus/v5/storage/store/common"
|
|
"github.com/TwiN/gatus/v5/watchdog"
|
|
"github.com/TwiN/logr"
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
func CreateExternalEndpointResult(cfg *config.Config) fiber.Handler {
|
|
extraLabels := cfg.GetUniqueExtraMetricLabels()
|
|
return func(c *fiber.Ctx) error {
|
|
// Check if the success query parameter is present
|
|
success, exists := c.Queries()["success"]
|
|
if !exists || (success != "true" && success != "false") {
|
|
return c.Status(400).SendString("missing or invalid success query parameter")
|
|
}
|
|
// Check if the authorization bearer token header is correct
|
|
authorizationHeader := string(c.Request().Header.Peek("Authorization"))
|
|
if !strings.HasPrefix(authorizationHeader, "Bearer ") {
|
|
return c.Status(401).SendString("invalid Authorization header")
|
|
}
|
|
token := strings.TrimSpace(strings.TrimPrefix(authorizationHeader, "Bearer "))
|
|
if len(token) == 0 {
|
|
return c.Status(401).SendString("bearer token must not be empty")
|
|
}
|
|
key := c.Params("key")
|
|
externalEndpoint := cfg.GetExternalEndpointByKey(key)
|
|
if externalEndpoint == nil {
|
|
logr.Errorf("[api.CreateExternalEndpointResult] External endpoint with key=%s not found", 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)
|
|
return c.Status(401).SendString("invalid token")
|
|
}
|
|
// Persist the result in the storage
|
|
result := &endpoint.Result{
|
|
Timestamp: time.Now(),
|
|
Success: c.QueryBool("success"),
|
|
Errors: []string{},
|
|
}
|
|
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())
|
|
return c.Status(400).SendString("invalid duration: " + err.Error())
|
|
}
|
|
result.Duration = parsedDuration
|
|
}
|
|
if !result.Success && c.Query("error") != "" {
|
|
result.Errors = append(result.Errors, c.Query("error"))
|
|
}
|
|
convertedEndpoint := externalEndpoint.ToEndpoint()
|
|
if err := store.Get().Insert(convertedEndpoint, result); err != nil {
|
|
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())
|
|
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)
|
|
// Check if an alert should be triggered or resolved
|
|
if !cfg.Maintenance.IsUnderMaintenance() {
|
|
watchdog.HandleAlerting(convertedEndpoint, result, cfg.Alerting)
|
|
externalEndpoint.NumberOfSuccessesInARow = convertedEndpoint.NumberOfSuccessesInARow
|
|
externalEndpoint.NumberOfFailuresInARow = convertedEndpoint.NumberOfFailuresInARow
|
|
}
|
|
if cfg.Metrics {
|
|
metrics.PublishMetricsForEndpoint(convertedEndpoint, result, extraLabels)
|
|
}
|
|
// Return the result
|
|
return c.Status(200).SendString("")
|
|
}
|
|
}
|