refactor app

main
SavelyG 2025-02-04 11:16:36 +00:00
parent 909c1151d4
commit 90a3848c72
5 changed files with 127 additions and 58 deletions

View File

@ -1,10 +1,7 @@
root = "." # Корень проекта root = "."
tmp_dir = "tmp" tmp_dir = "tmp"
[build] [build]
binary = "app" # Имя исполняемого файла cmd = "go build -o ./tmp/app ."
cmd = "go build -o app main.go" bin = "./tmp/app"
include_ext = ["go", "tpl", "tmpl", "html"] include_ext = ["go"]
[run]
cmd = "./app"

View File

@ -3,9 +3,10 @@ services:
image: cosmtrek/air:v1.61.7 image: cosmtrek/air:v1.61.7
volumes: volumes:
- .:/app - .:/app
working_dir: /app
ports: ports:
- "9101:9101" - "9101:9101"
command: air -c .air.toml command: ["air", "-c", ".air.toml"]
prometheus: prometheus:
image: prom/prometheus:v3.1.0 image: prom/prometheus:v3.1.0

2
go.mod
View File

@ -1,4 +1,4 @@
module prometheus_exporter module exporter
go 1.23.5 go 1.23.5

167
main.go
View File

@ -4,7 +4,9 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"os"
"sync" "sync"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -16,31 +18,35 @@ var customRegistry = prometheus.NewRegistry()
// Структура JSON // Структура JSON
type MetricRequest struct { type MetricRequest struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
URL string `json:"url"` URL string `json:"url"`
Method string `json:"method"` Method string `json:"method"`
Type string `json:"type"` // "gauge", "counter" и т.д. Type string `json:"type"`
Metrics map[string]any `json:"metrics"` Metrics map[string]float64 `json:"metrics"`
} }
// Экспортёр метрик // Экспортёр метрик
type MetricsExporter struct { type MetricsExporter struct {
gaugeMetrics map[string]*prometheus.GaugeVec gaugeMetrics map[string]*prometheus.GaugeVec
counterMetrics map[string]*prometheus.CounterVec counterMetrics map[string]*prometheus.CounterVec
mu sync.Mutex // Защита от одновременного доступа histogramMetrics map[string]*prometheus.HistogramVec
summaryMetrics map[string]*prometheus.SummaryVec
mu sync.Mutex // Защита от одновременного доступа
} }
// Создаём новый экспортёр // Создаём новый экспортёр
func NewMetricsExporter() *MetricsExporter { func NewMetricsExporter() *MetricsExporter {
return &MetricsExporter{ return &MetricsExporter{
gaugeMetrics: make(map[string]*prometheus.GaugeVec), gaugeMetrics: make(map[string]*prometheus.GaugeVec),
counterMetrics: make(map[string]*prometheus.CounterVec), 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() me.mu.Lock()
defer me.mu.Unlock() defer me.mu.Unlock()
@ -52,44 +58,100 @@ func (me *MetricsExporter) UpdateMetric(name string, value interface{}) {
labels := []string{"name", "url", "method"} labels := []string{"name", "url", "method"}
labelValues := []string{request.Name, request.URL, request.Method} labelValues := []string{request.Name, request.URL, request.Method}
if request.Type == "gauge" { // Обработка метрик в зависимости от типа
// Регистрируем Gauge, если он ещё не существует switch request.Type {
if _, exists := me.gaugeMetrics[fullMetricName]; !exists { case "gauge":
gaugeVec := prometheus.NewGaugeVec( me.updateGauge(fullMetricName, labels, labelValues, value)
prometheus.GaugeOpts{ case "counter":
Name: fullMetricName, me.updateCounter(fullMetricName, labels, labelValues, value)
Help: fmt.Sprintf("Metric %s from %s", metricName, request.ID), case "histogram":
}, me.updateHistogram(fullMetricName, labels, labelValues, value)
labels, case "summary":
) me.updateSummary(fullMetricName, labels, labelValues, value)
customRegistry.MustRegister(gaugeVec) default:
me.gaugeMetrics[fullMetricName] = gaugeVec log.Printf("Неподдерживаемый тип метрики: %s\n", request.Type)
}
// Обновляем значение 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)
} }
} }
} }
// Обновление `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 // Обработчик для приёма JSON
func (me *MetricsExporter) JSONHandler(w http.ResponseWriter, r *http.Request) { func (me *MetricsExporter) JSONHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
log.Printf("Неверный метод: %s (ожидался POST)", r.Method)
http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
return return
} }
@ -97,26 +159,33 @@ func (me *MetricsExporter) JSONHandler(w http.ResponseWriter, r *http.Request) {
// Читаем тело запроса // Читаем тело запроса
body, err := io.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
log.Printf("Ошибка чтения тела запроса: %s\n", err)
http.Error(w, "Failed to read request body", http.StatusInternalServerError) http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return return
} }
defer r.Body.Close() defer r.Body.Close()
// Парсим JSON // Парсим JSON
var data MetricRequest var request MetricRequest
if err := json.Unmarshal(body, &data); err != nil { 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) http.Error(w, "Invalid JSON format", http.StatusBadRequest)
return return
} }
// Обновляем метрики // Обновляем метрики
me.UpdateMetric(name, value) log.Printf("Обновление метрик для запроса ID: %s\n", request.ID)
me.UpdateMetric(request)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write([]byte("Metrics updated")) w.Write([]byte("Metrics updated"))
log.Printf("Метрики обновлены успешно для ID: %s\n", request.ID)
} }
func main() { func main() {
log.SetOutput(os.Stdout) // Логируем в стандартный вывод
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
exporter := NewMetricsExporter() exporter := NewMetricsExporter()
// Используем кастомный реестр в обработчике /metrics // Используем кастомный реестр в обработчике /metrics
@ -124,8 +193,10 @@ func main() {
http.HandleFunc("/update", exporter.JSONHandler) // Обработчик для приёма JSON http.HandleFunc("/update", exporter.JSONHandler) // Обработчик для приёма JSON
port := ":9101" 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 { if err := http.ListenAndServe(port, nil); err != nil {
fmt.Println("Error starting server:", err) log.Fatalf("Error starting server: %s\n", err)
} }
} }
//TODO: сделать переменные окружения, настроить канал, сделать тесты

View File

@ -6,6 +6,6 @@ curl -X POST -H "Content-Type: application/json" -d '{
"type": "gauge", "type": "gauge",
"metrics": { "metrics": {
"response_time": 120.5, "response_time": 120.5,
"status_code": 200, "status_code": 200
} }
}' http://localhost:9101/update }' http://localhost:9101/update