mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-22 06:38:13 +00:00
Extract helper libraries from private repo of telegram-bot.app
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
package libpack_monitoring
|
||||
|
||||
func (ms *MetricsSetup) RegisterDefaultMetrics() {
|
||||
ms.RegisterMetricsCounter(MetricsSucceeded, nil)
|
||||
ms.RegisterMetricsCounter(MetricsFailed, nil)
|
||||
ms.RegisterMetricsCounter(MetricsSkipped, nil)
|
||||
ms.RegisterMetricsHistogram(MetricsDuration, nil)
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterGoMetrics() {
|
||||
// TODO: metrics.WriteProcessMetrics(ms.metrics_set)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package libpack_monitoring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
|
||||
)
|
||||
|
||||
func (ms *MetricsSetup) get_metrics_name(name string, labels map[string]string) (complete_name string) {
|
||||
if labels == nil {
|
||||
labels = make(map[string]string)
|
||||
}
|
||||
labels["microservice"] = libpack_config.PKG_NAME
|
||||
|
||||
if ms.metrics_prefix != "" {
|
||||
complete_name = ms.metrics_prefix + "_" + name
|
||||
} else {
|
||||
complete_name = name
|
||||
}
|
||||
if labels != nil {
|
||||
complete_name += "{"
|
||||
for k, v := range labels {
|
||||
complete_name += k + "=\"" + v + "\","
|
||||
}
|
||||
complete_name = strings.TrimSuffix(complete_name, ",")
|
||||
complete_name += "}"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// validate_metrics_name validates the name of the metric to adhere to the Prometheus naming conventions
|
||||
// https://prometheus.io/docs/practices/naming/
|
||||
func validate_metrics_name(name string) error {
|
||||
// replace all spaces with underscores and remove all other non-alphanumeric characters
|
||||
name_new := strings.ReplaceAll(name, " ", "_")
|
||||
name_new = strings.Map(func(r rune) rune {
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}, name_new)
|
||||
name_new = strings.ReplaceAll(name_new, "__", "_")
|
||||
name_new = strings.Trim(name_new, "_")
|
||||
if name_new != name {
|
||||
return fmt.Errorf("Invalid metric name: %s, expected %s", name, name_new)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compile_metrics_with_labels(name string, labels map[string]string) string {
|
||||
metric_name := name
|
||||
for k, v := range labels {
|
||||
metric_name += "_" + k + "_" + v
|
||||
}
|
||||
return metric_name
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package libpack_monitoring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func (suite *MonitoringTestSuite) Test_validate_metrics_name() {
|
||||
type args struct {
|
||||
name string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test_validate_metrics_name - valid",
|
||||
args: args{
|
||||
name: "test_metrics_name",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test_validate_metrics_name - invalid",
|
||||
args: args{
|
||||
name: "test metrics name",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test_validate_metrics_name - invalid - special chars",
|
||||
args: args{
|
||||
name: "test_metrics_name!",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
suite.T().Run(tt.name, func(t *testing.T) {
|
||||
err := validate_metrics_name(tt.args.name)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *MonitoringTestSuite) TestValidateMetricsName() {
|
||||
tests := []struct {
|
||||
name string
|
||||
expected error
|
||||
}{
|
||||
{
|
||||
name: "valid_name",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "name with spaces",
|
||||
expected: fmt.Errorf("Invalid metric name: %s, expected %s", "name with spaces", "name_with_spaces"),
|
||||
},
|
||||
{
|
||||
name: "name with non-alphanumeric characters",
|
||||
expected: fmt.Errorf("Invalid metric name: %s, expected %s", "name with non-alphanumeric characters", "name_with_nonalphanumeric_characters"),
|
||||
},
|
||||
{
|
||||
name: "name__with__consecutive__underscores",
|
||||
expected: fmt.Errorf("Invalid metric name: %s, expected %s", "name__with__consecutive__underscores", "name_with_consecutive_underscores"),
|
||||
},
|
||||
{
|
||||
name: "_name_with_leading_or_trailing_underscores_",
|
||||
expected: fmt.Errorf("Invalid metric name: %s, expected %s", "_name_with_leading_or_trailing_underscores_", "name_with_leading_or_trailing_underscores"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
suite.T().Run(tt.name, func(t *testing.T) {
|
||||
err := validate_metrics_name(tt.name)
|
||||
assert.Equal(t, tt.expected, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Package `libpack_monitoring` provides and easy way to add prometheus metrics to your application.
|
||||
// It also provides a way to add custom metrics to the already started prometheus registry.
|
||||
|
||||
package libpack_monitoring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gookit/goutil/envutil"
|
||||
logging "github.com/telegram-bot-app/lib-logging"
|
||||
)
|
||||
|
||||
type MetricsSetup struct {
|
||||
metrics_prefix string
|
||||
metrics_set *metrics.Set
|
||||
}
|
||||
|
||||
var (
|
||||
log *logging.LogConfig
|
||||
)
|
||||
|
||||
func NewMonitoring() *MetricsSetup {
|
||||
log = logging.NewLogger()
|
||||
ms := &MetricsSetup{}
|
||||
ms.metrics_set = metrics.NewSet()
|
||||
go ms.startPrometheusEndpoint()
|
||||
return ms
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) startPrometheusEndpoint() {
|
||||
app := fiber.New()
|
||||
app.Get("/metrics", ms.metricsEndpoint)
|
||||
err := app.Listen(fmt.Sprintf(":%d", envutil.GetInt("MONITORING_PORT", 9393)))
|
||||
if err != nil {
|
||||
fmt.Println("Can't start the service: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) metricsEndpoint(c *fiber.Ctx) error {
|
||||
ms.metrics_set.WritePrometheus(c.Response().BodyWriter())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) AddMetricsPrefix(prefix string) {
|
||||
ms.metrics_prefix = prefix
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) ListActiveMetrics() []string {
|
||||
return ms.metrics_set.ListMetricNames()
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterMetricsGauge(metric_name string, labels map[string]string, val float64) *metrics.Gauge {
|
||||
if validate_metrics_name(metric_name) != nil {
|
||||
log.Critical("RegisterMetricsGauge() error", map[string]interface{}{"_error": "Invalid metric name", "_metric_name": metric_name})
|
||||
return nil
|
||||
}
|
||||
return ms.metrics_set.GetOrCreateGauge(ms.get_metrics_name(metric_name, labels), func() float64 {
|
||||
// get current value of the gauge and add val to it
|
||||
return val
|
||||
})
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterMetricsCounter(metric_name string, labels map[string]string) *metrics.Counter {
|
||||
if validate_metrics_name(metric_name) != nil {
|
||||
log.Critical("RegisterMetricsCounter() error", map[string]interface{}{"_error": "Invalid metric name", "_metric_name": metric_name})
|
||||
return nil
|
||||
}
|
||||
return ms.metrics_set.GetOrCreateCounter(ms.get_metrics_name(metric_name, labels))
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterFloatCounter(metric_name string, labels map[string]string) *metrics.FloatCounter {
|
||||
if validate_metrics_name(metric_name) != nil {
|
||||
log.Critical("RegisterFloatCounter() error", map[string]interface{}{"_error": "Invalid metric name", "_metric_name": metric_name})
|
||||
return nil
|
||||
}
|
||||
return ms.metrics_set.GetOrCreateFloatCounter(ms.get_metrics_name(metric_name, labels))
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterMetricsSummary(metric_name string, labels map[string]string) *metrics.Summary {
|
||||
if validate_metrics_name(metric_name) != nil {
|
||||
log.Critical("RegisterMetricsSummary() error", map[string]interface{}{"_error": "Invalid metric name", "_metric_name": metric_name})
|
||||
return nil
|
||||
}
|
||||
return ms.metrics_set.GetOrCreateSummary(ms.get_metrics_name(metric_name, labels))
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterMetricsHistogram(metric_name string, labels map[string]string) *metrics.Histogram {
|
||||
if validate_metrics_name(metric_name) != nil {
|
||||
log.Critical("RegisterMetricsHistogram() error", map[string]interface{}{"_error": "Invalid metric name", "_metric_name": metric_name})
|
||||
return nil
|
||||
}
|
||||
return ms.metrics_set.GetOrCreateHistogram(ms.get_metrics_name(metric_name, labels))
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) Increment(metric_name string, labels map[string]string) {
|
||||
ms.RegisterMetricsCounter(metric_name, labels).Inc()
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) IncrementFloat(metric_name string, labels map[string]string, value float64) {
|
||||
ms.RegisterFloatCounter(metric_name, labels).Add(value)
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) Set(metric_name string, labels map[string]string, value uint64) {
|
||||
ms.RegisterMetricsCounter(metric_name, labels).Set(value)
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) Update(metric_name string, labels map[string]string, value float64) {
|
||||
ms.RegisterMetricsHistogram(metric_name, labels).Update(value)
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) UpdateDuration(metric_name string, labels map[string]string, value time.Time) {
|
||||
ms.RegisterMetricsHistogram(metric_name, labels).UpdateDuration(value)
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) UpdateSummary(metric_name string, labels map[string]string, value float64) {
|
||||
ms.RegisterMetricsSummary(metric_name, labels).Update(value)
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RemoveMetrics(metric_name string, labels map[string]string) {
|
||||
ms.metrics_set.UnregisterMetric(ms.get_metrics_name(metric_name, labels))
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) PurgeMetrics() {
|
||||
ms.metrics_set.UnregisterAllMetrics()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package libpack_monitoring
|
||||
|
||||
const (
|
||||
MetricsSucceeded = "requests_succesful"
|
||||
MetricsFailed = "requests_failed"
|
||||
MetricsDuration = "requests_duration"
|
||||
MetricsSkipped = "requests_skipped"
|
||||
)
|
||||
@@ -0,0 +1,140 @@
|
||||
package libpack_monitoring
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "io"
|
||||
// "net/http"
|
||||
// "testing"
|
||||
// "time"
|
||||
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// "github.com/stretchr/testify/suite"
|
||||
// libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
|
||||
// )
|
||||
|
||||
type MonitoringTestSuite struct {
|
||||
suite.Suite
|
||||
metrics_endpoint string
|
||||
}
|
||||
|
||||
func (suite *MonitoringTestSuite) SetupTest() {
|
||||
suite.metrics_endpoint = "http://localhost:9393/metrics"
|
||||
}
|
||||
|
||||
func (suite *MonitoringTestSuite) TearDownTest() {
|
||||
}
|
||||
|
||||
func TestMonitoringSuite(t *testing.T) {
|
||||
suite.Run(t, new(MonitoringTestSuite))
|
||||
}
|
||||
|
||||
func (suite *MonitoringTestSuite) testing_call_metrics_endpoint() *http.Response {
|
||||
resp, err := http.Get(suite.metrics_endpoint)
|
||||
if err != nil {
|
||||
suite.T().Error("Can't call metrics endpoint", err)
|
||||
suite.T().FailNow()
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func (suite *MonitoringTestSuite) testing_get_body_as_text(resp *http.Response) string {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
suite.T().Error("Can't read response body", err)
|
||||
suite.T().FailNow()
|
||||
}
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func (suite *MonitoringTestSuite) TestNewMonitoring() {
|
||||
metrics_prefix := "within_test"
|
||||
|
||||
suite.T().Run("TestWholeEndpoint", func(t *testing.T) {
|
||||
mon := NewMonitoring()
|
||||
mon.AddMetricsPrefix(metrics_prefix)
|
||||
mon.RegisterDefaultMetrics()
|
||||
|
||||
resp := suite.testing_call_metrics_endpoint()
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
assert.Contains(t, suite.testing_get_body_as_text(resp), "within_test_requests_succesful", "Metrics endpoint should contain metrics with prefix %s", metrics_prefix)
|
||||
|
||||
mon.RegisterMetricsGauge("test_gauge_metrics", nil, 14)
|
||||
resp = suite.testing_call_metrics_endpoint()
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
assert.Contains(t, suite.testing_get_body_as_text(resp), "within_test_test_gauge_metrics", "Metrics endpoint should contain metrics with prefix %s", metrics_prefix)
|
||||
|
||||
// Triggering it again will increase the gauge by 1
|
||||
mon.RegisterMetricsGauge("test_gauge_metrics", nil, 7)
|
||||
time.Sleep(1 * time.Second)
|
||||
resp = suite.testing_call_metrics_endpoint()
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
assert.Contains(t, suite.testing_get_body_as_text(resp), "within_test_test_gauge_metrics 7", "Metrics endpoint should contain incremented () gauge metrics with prefix %s", metrics_prefix)
|
||||
|
||||
mon.RegisterMetricsCounter("test_counter_metrics", nil)
|
||||
mon.Increment("test_counter_metrics", nil)
|
||||
time.Sleep(1 * time.Second)
|
||||
resp = suite.testing_call_metrics_endpoint()
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
assert.Contains(t, suite.testing_get_body_as_text(resp), "within_test_test_counter_metrics", "Metrics endpoint should contain metrics with prefix %s", metrics_prefix)
|
||||
mon.Increment("test_counter_metrics", nil)
|
||||
time.Sleep(1 * time.Second)
|
||||
resp = suite.testing_call_metrics_endpoint()
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
assert.Contains(t, suite.testing_get_body_as_text(resp), "within_test_test_counter_metrics 2", "Metrics endpoint should contain metrics with prefix %s", metrics_prefix)
|
||||
|
||||
// mon.AddCustomMetrics(&CustomMetrics{
|
||||
// Name: "test_custom_metrics",
|
||||
// Help: "test custom metrics",
|
||||
// Type: TypeHistogram,
|
||||
// }, libpack_config.PKG_NAME)
|
||||
// resp = suite.testing_call_metrics_endpoint()
|
||||
// assert.Equal(t, 200, resp.StatusCode)
|
||||
// assert.Contains(t, suite.testing_get_body_as_text(resp), "within_test_test_custom_metrics", "Metrics endpoint should contain metrics with prefix %s", metrics_prefix)
|
||||
// fmt.Println(suite.testing_get_body_as_text(resp))
|
||||
|
||||
// assert.Containsf(t, mon.ListActiveMetrics(), "test_custom_metrics", "ListActiveMetrics() should contain metrics with prefix %s", metrics_prefix)
|
||||
|
||||
// mon.AddCustomMetrics(&CustomMetrics{
|
||||
// Name: "test_gauge_metrics",
|
||||
// Help: "test gauge metrics",
|
||||
// Type: TypeGauge,
|
||||
// }, libpack_config.PKG_NAME)
|
||||
// mon.Increment("test_gauge_metrics")
|
||||
// time.Sleep(2 * time.Second)
|
||||
// resp = suite.testing_call_metrics_endpoint()
|
||||
// assert.Equal(t, 200, resp.StatusCode)
|
||||
// assert.Contains(t, suite.testing_get_body_as_text(resp), "test_gauge_metrics{microservice=\""+libpack_config.PKG_NAME+"\"} 1", "Metrics endpoint should contain incremented (1) gauge metrics with prefix %s", metrics_prefix)
|
||||
|
||||
// mon.Increment("test_gauge_metrics")
|
||||
// time.Sleep(2 * time.Second)
|
||||
// resp = suite.testing_call_metrics_endpoint()
|
||||
// assert.Equal(t, 200, resp.StatusCode)
|
||||
// assert.Contains(t, suite.testing_get_body_as_text(resp), "test_gauge_metrics{microservice=\""+libpack_config.PKG_NAME+"\"} 2", "Metrics endpoint should contain incremented (2) gauge metrics with prefix %s", metrics_prefix)
|
||||
|
||||
// mon.AddCustomMetrics(&CustomMetrics{
|
||||
// Name: "test_counter_metrics",
|
||||
// Help: "test counter metrics",
|
||||
// Type: TypeCounter,
|
||||
// }, libpack_config.PKG_NAME)
|
||||
// mon.Set("test_counter_metrics", 7)
|
||||
// time.Sleep(2 * time.Second)
|
||||
// resp = suite.testing_call_metrics_endpoint()
|
||||
// assert.Equal(t, 200, resp.StatusCode)
|
||||
// assert.Contains(t, suite.testing_get_body_as_text(resp), "test_counter_metrics{microservice=\""+libpack_config.PKG_NAME+"\"} 7", "Metrics endpoint should contain metrics with prefix %s", metrics_prefix)
|
||||
|
||||
resp = suite.testing_call_metrics_endpoint()
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
assert.Contains(t, suite.testing_get_body_as_text(resp), "go_goroutines", "Metrics endpoint should contain metrics with prefix %s", metrics_prefix)
|
||||
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user