package main import ( "encoding/json" "fmt" "io" "net/http" "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"` // "gauge", "counter" и т.д. Metrics map[string]any `json:"metrics"` } // Экспортёр метрик type MetricsExporter struct { gaugeMetrics map[string]*prometheus.GaugeVec counterMetrics map[string]*prometheus.CounterVec mu sync.Mutex // Защита от одновременного доступа } // Создаём новый экспортёр func NewMetricsExporter() *MetricsExporter { return &MetricsExporter{ gaugeMetrics: make(map[string]*prometheus.GaugeVec), counterMetrics: make(map[string]*prometheus.CounterVec), } } // Обновление или создание метрики func (me *MetricsExporter) UpdateMetric(name string, value interface{}) { 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} 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) } } } // Обработчик для приёма JSON func (me *MetricsExporter) JSONHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) return } // Читаем тело запроса body, err := io.ReadAll(r.Body) if err != nil { 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 { http.Error(w, "Invalid JSON format", http.StatusBadRequest) return } // Обновляем метрики me.UpdateMetric(name, value) w.WriteHeader(http.StatusOK) w.Write([]byte("Metrics updated")) } func main() { exporter := NewMetricsExporter() // Используем кастомный реестр в обработчике /metrics http.Handle("/metrics", promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})) http.HandleFunc("/update", exporter.JSONHandler) // Обработчик для приёма JSON port := ":9101" fmt.Println("Starting server on port", port) if err := http.ListenAndServe(port, nil); err != nil { fmt.Println("Error starting server:", err) } }