From 90a3848c72871c686cd8fcf4264cab7723823dd7 Mon Sep 17 00:00:00 2001 From: SavelyG Date: Tue, 4 Feb 2025 11:16:36 +0000 Subject: [PATCH] refactor app --- .air.toml | 11 ++- docker-compose.yml | 3 +- go.mod | 2 +- main.go | 167 ++++++++++++++++++++++++++++++++------------- test_data.txt | 2 +- 5 files changed, 127 insertions(+), 58 deletions(-) diff --git a/.air.toml b/.air.toml index 78eb298..2c2182c 100644 --- a/.air.toml +++ b/.air.toml @@ -1,10 +1,7 @@ -root = "." # Корень проекта +root = "." tmp_dir = "tmp" [build] - binary = "app" # Имя исполняемого файла - cmd = "go build -o app main.go" - include_ext = ["go", "tpl", "tmpl", "html"] - -[run] - cmd = "./app" \ No newline at end of file + cmd = "go build -o ./tmp/app ." + bin = "./tmp/app" + include_ext = ["go"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d797c85..916e55f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,9 +3,10 @@ services: image: cosmtrek/air:v1.61.7 volumes: - .:/app + working_dir: /app ports: - "9101:9101" - command: air -c .air.toml + command: ["air", "-c", ".air.toml"] prometheus: image: prom/prometheus:v3.1.0 diff --git a/go.mod b/go.mod index d9e8950..a1a517a 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module prometheus_exporter +module exporter go 1.23.5 diff --git a/main.go b/main.go index 5319009..8f23270 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,9 @@ import ( "encoding/json" "fmt" "io" + "log" "net/http" + "os" "sync" "github.com/prometheus/client_golang/prometheus" @@ -16,31 +18,35 @@ var customRegistry = prometheus.NewRegistry() // Структура JSON type MetricRequest struct { - ID string `json:"id"` - Name string `json:"name"` - URL string `json:"url"` - Method string `json:"method"` - Type string `json:"type"` // "gauge", "counter" и т.д. - Metrics map[string]any `json:"metrics"` + ID string `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + Method string `json:"method"` + Type string `json:"type"` + Metrics map[string]float64 `json:"metrics"` } // Экспортёр метрик type MetricsExporter struct { - gaugeMetrics map[string]*prometheus.GaugeVec - counterMetrics map[string]*prometheus.CounterVec - mu sync.Mutex // Защита от одновременного доступа + gaugeMetrics map[string]*prometheus.GaugeVec + counterMetrics map[string]*prometheus.CounterVec + histogramMetrics map[string]*prometheus.HistogramVec + summaryMetrics map[string]*prometheus.SummaryVec + mu sync.Mutex // Защита от одновременного доступа } // Создаём новый экспортёр func NewMetricsExporter() *MetricsExporter { return &MetricsExporter{ - gaugeMetrics: make(map[string]*prometheus.GaugeVec), - counterMetrics: make(map[string]*prometheus.CounterVec), + gaugeMetrics: make(map[string]*prometheus.GaugeVec), + counterMetrics: make(map[string]*prometheus.CounterVec), + histogramMetrics: make(map[string]*prometheus.HistogramVec), + summaryMetrics: make(map[string]*prometheus.SummaryVec), } } // Обновление или создание метрики -func (me *MetricsExporter) UpdateMetric(name string, value interface{}) { +func (me *MetricsExporter) UpdateMetric(request MetricRequest) { me.mu.Lock() defer me.mu.Unlock() @@ -52,44 +58,100 @@ func (me *MetricsExporter) UpdateMetric(name string, value interface{}) { labels := []string{"name", "url", "method"} labelValues := []string{request.Name, request.URL, request.Method} - if request.Type == "gauge" { - // Регистрируем Gauge, если он ещё не существует - if _, exists := me.gaugeMetrics[fullMetricName]; !exists { - gaugeVec := prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: fullMetricName, - Help: fmt.Sprintf("Metric %s from %s", metricName, request.ID), - }, - labels, - ) - customRegistry.MustRegister(gaugeVec) - me.gaugeMetrics[fullMetricName] = gaugeVec - } - // Обновляем значение Gauge - me.gaugeMetrics[fullMetricName].WithLabelValues(labelValues...).Set(floatVal) - - } else if request.Type == "counter" { - // Регистрируем Counter, если он ещё не существует - if _, exists := me.counterMetrics[fullMetricName]; !exists { - counterVec := prometheus.NewCounterVec( - prometheus.CounterOpts{ - Name: fullMetricName, - Help: fmt.Sprintf("Metric %s from %s", metricName, request.ID), - }, - labels, - ) - customRegistry.MustRegister(counterVec) - me.counterMetrics[fullMetricName] = counterVec - } - // Инкрементируем Counter - me.counterMetrics[fullMetricName].WithLabelValues(labelValues...).Add(floatVal) + // Обработка метрик в зависимости от типа + switch request.Type { + case "gauge": + me.updateGauge(fullMetricName, labels, labelValues, value) + case "counter": + me.updateCounter(fullMetricName, labels, labelValues, value) + case "histogram": + me.updateHistogram(fullMetricName, labels, labelValues, value) + case "summary": + me.updateSummary(fullMetricName, labels, labelValues, value) + default: + log.Printf("Неподдерживаемый тип метрики: %s\n", request.Type) } } } +// Обновление `Gauge` метрик +func (me *MetricsExporter) updateGauge(name string, labels []string, labelValues []string, value float64) { + if _, exists := me.gaugeMetrics[name]; !exists { + gaugeVec := prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: name, + Help: fmt.Sprintf("Gauge metric %s", name), + }, + labels, + ) + customRegistry.MustRegister(gaugeVec) + me.gaugeMetrics[name] = gaugeVec + } + me.gaugeMetrics[name].WithLabelValues(labelValues...).Set(value) + log.Printf("Gauge обновлён: %s = %f\n", name, value) +} + +// Обновление `Counter` метрик +func (me *MetricsExporter) updateCounter(name string, labels []string, labelValues []string, value float64) { + if value < 0 { + log.Printf("Ошибка: Counter %s не может быть отрицательным\n", name) + return + } + if _, exists := me.counterMetrics[name]; !exists { + counterVec := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: name, + Help: fmt.Sprintf("Counter metric %s", name), + }, + labels, + ) + customRegistry.MustRegister(counterVec) + me.counterMetrics[name] = counterVec + } + me.counterMetrics[name].WithLabelValues(labelValues...).Add(value) + log.Printf("Counter обновлён: %s += %f\n", name, value) +} + +// Обновление `Histogram` метрик +func (me *MetricsExporter) updateHistogram(name string, labels []string, labelValues []string, value float64) { + if _, exists := me.histogramMetrics[name]; !exists { + histogramVec := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: name, + Help: fmt.Sprintf("Histogram metric %s", name), + Buckets: prometheus.LinearBuckets(10, 10, 10), // Пример диапазонов + }, + labels, + ) + customRegistry.MustRegister(histogramVec) + me.histogramMetrics[name] = histogramVec + } + me.histogramMetrics[name].WithLabelValues(labelValues...).Observe(value) + log.Printf("Histogram обновлён: %s = %f\n", name, value) +} + +// Обновление `Summary` метрик +func (me *MetricsExporter) updateSummary(name string, labels []string, labelValues []string, value float64) { + if _, exists := me.summaryMetrics[name]; !exists { + summaryVec := prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Name: name, + Help: fmt.Sprintf("Summary metric %s", name), + Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, // Пример целей + }, + labels, + ) + customRegistry.MustRegister(summaryVec) + me.summaryMetrics[name] = summaryVec + } + me.summaryMetrics[name].WithLabelValues(labelValues...).Observe(value) + log.Printf("Summary обновлён: %s = %f\n", name, value) +} + // Обработчик для приёма JSON func (me *MetricsExporter) JSONHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { + log.Printf("Неверный метод: %s (ожидался POST)", r.Method) http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) return } @@ -97,26 +159,33 @@ func (me *MetricsExporter) JSONHandler(w http.ResponseWriter, r *http.Request) { // Читаем тело запроса body, err := io.ReadAll(r.Body) if err != nil { + log.Printf("Ошибка чтения тела запроса: %s\n", err) http.Error(w, "Failed to read request body", http.StatusInternalServerError) return } defer r.Body.Close() // Парсим JSON - var data MetricRequest - if err := json.Unmarshal(body, &data); err != nil { + var request MetricRequest + if err := json.Unmarshal(body, &request); err != nil { + log.Printf("Ошибка парсинга JSON: %s\nТело запроса: %s\n", err, string(body)) http.Error(w, "Invalid JSON format", http.StatusBadRequest) return } // Обновляем метрики - me.UpdateMetric(name, value) + log.Printf("Обновление метрик для запроса ID: %s\n", request.ID) + me.UpdateMetric(request) w.WriteHeader(http.StatusOK) w.Write([]byte("Metrics updated")) + log.Printf("Метрики обновлены успешно для ID: %s\n", request.ID) } func main() { + log.SetOutput(os.Stdout) // Логируем в стандартный вывод + log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) + exporter := NewMetricsExporter() // Используем кастомный реестр в обработчике /metrics @@ -124,8 +193,10 @@ func main() { http.HandleFunc("/update", exporter.JSONHandler) // Обработчик для приёма JSON port := ":9101" - fmt.Println("Starting server on port", port) + log.Printf("Starting server on port %s\n", port) if err := http.ListenAndServe(port, nil); err != nil { - fmt.Println("Error starting server:", err) + log.Fatalf("Error starting server: %s\n", err) } } + +//TODO: сделать переменные окружения, настроить канал, сделать тесты diff --git a/test_data.txt b/test_data.txt index d2be418..e87b3a2 100644 --- a/test_data.txt +++ b/test_data.txt @@ -6,6 +6,6 @@ curl -X POST -H "Content-Type: application/json" -d '{ "type": "gauge", "metrics": { "response_time": 120.5, - "status_code": 200, + "status_code": 200 } }' http://localhost:9101/update