package main import ( "encoding/json" "fmt" "io" "log" "net/http" "os" "sync" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) // Создаём кастомный реестр 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"` Metrics map[string]float64 `json:"metrics"` } // Экспортёр метрик type MetricsExporter struct { 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), histogramMetrics: make(map[string]*prometheus.HistogramVec), summaryMetrics: make(map[string]*prometheus.SummaryVec), } } // Обновление или создание метрики func (me *MetricsExporter) UpdateMetric(request MetricRequest) { me.mu.Lock() defer me.mu.Unlock() for metricName, value := range request.Metrics { // Уникальное имя метрики fullMetricName := fmt.Sprintf("vks_%s_%s", request.ID, metricName) // Лейблы labels := []string{"name", "url", "method"} labelValues := []string{request.Name, request.URL, request.Method} // Обработка метрик в зависимости от типа 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 } // Читаем тело запроса 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 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 } // Обновляем метрики 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 http.Handle("/metrics", promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})) http.HandleFunc("/update", exporter.JSONHandler) // Обработчик для приёма JSON port := ":9101" log.Printf("Starting server on port %s\n", port) if err := http.ListenAndServe(port, nil); err != nil { log.Fatalf("Error starting server: %s\n", err) } } //TODO: сделать переменные окружения, настроить канал, дописать тесты