Extract helper libraries from private repo of telegram-bot.app

This commit is contained in:
2023-10-10 22:16:50 +01:00
parent 0758cd5b52
commit 1a3628837f
19 changed files with 955 additions and 16 deletions
+12
View File
@@ -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)
}
+57
View File
@@ -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
}
+87
View File
@@ -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)
})
}
}
+129
View File
@@ -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()
}
+8
View File
@@ -0,0 +1,8 @@
package libpack_monitoring
const (
MetricsSucceeded = "requests_succesful"
MetricsFailed = "requests_failed"
MetricsDuration = "requests_duration"
MetricsSkipped = "requests_skipped"
)
+140
View File
@@ -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)
})
}