Files
telecom-platform/apps/api-server/internal/monitoring/prometheus.go
T
nutcas3 a6ec720df3 feat: Add Prometheus service client with instant query execution, alert retrieval, and health checking
- Add PrometheusService struct with baseURL/http.Client (10s timeout) and PROMETHEUS_URL env config (default: http://prometheus:9090)
- Add Available method with /-/ready endpoint health check
- Add Query method with instant PromQL execution via /api/v1/query, URL escaping, error handling
- Add promResponse/promVectorData/PromVectorSample structs for query result parsing with metric/value fields
2026-04-20 18:10:36 +03:00

134 lines
3.5 KiB
Go

package monitoring
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
)
// PrometheusService queries a Prometheus HTTP API for real system metrics
// and alerts. Configure via PROMETHEUS_URL (e.g. http://prometheus:9090).
type PrometheusService struct {
baseURL string
http *http.Client
}
func NewPrometheusService() *PrometheusService {
base := os.Getenv("PROMETHEUS_URL")
if base == "" {
base = "http://prometheus:9090"
}
return &PrometheusService{
baseURL: strings.TrimRight(base, "/"),
http: &http.Client{Timeout: 10 * time.Second},
}
}
// Available reports whether the configured Prometheus instance is reachable.
func (p *PrometheusService) Available(ctx context.Context) bool {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.baseURL+"/-/ready", nil)
if err != nil {
return false
}
resp, err := p.http.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
return resp.StatusCode >= 200 && resp.StatusCode < 300
}
type promResponse struct {
Status string `json:"status"`
Data json.RawMessage `json:"data"`
Error string `json:"error,omitempty"`
}
type PromVectorSample struct {
Metric map[string]string `json:"metric"`
Value [2]any `json:"value"`
}
type promVectorData struct {
ResultType string `json:"resultType"`
Result []PromVectorSample `json:"result"`
}
// Query runs an instant PromQL query.
func (p *PrometheusService) Query(ctx context.Context, promQL string) ([]PromVectorSample, error) {
u := fmt.Sprintf("%s/api/v1/query?query=%s", p.baseURL, url.QueryEscape(promQL))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return nil, err
}
resp, err := p.http.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("prometheus query failed: %d %s", resp.StatusCode, string(b))
}
var out promResponse
if err := json.Unmarshal(b, &out); err != nil {
return nil, err
}
if out.Status != "success" {
return nil, fmt.Errorf("prometheus query error: %s", out.Error)
}
var data promVectorData
if err := json.Unmarshal(out.Data, &data); err != nil {
return nil, err
}
return data.Result, nil
}
// Alert represents an active Prometheus alert.
type PromAlert struct {
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
State string `json:"state"`
ActiveAt time.Time `json:"activeAt"`
Value string `json:"value"`
}
type promAlertsData struct {
Alerts []PromAlert `json:"alerts"`
}
// Alerts returns currently firing/pending alerts.
func (p *PrometheusService) Alerts(ctx context.Context) ([]PromAlert, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.baseURL+"/api/v1/alerts", nil)
if err != nil {
return nil, err
}
resp, err := p.http.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("prometheus alerts failed: %d %s", resp.StatusCode, string(b))
}
var out promResponse
if err := json.Unmarshal(b, &out); err != nil {
return nil, err
}
if out.Status != "success" {
return nil, fmt.Errorf("prometheus alerts error: %s", out.Error)
}
var data promAlertsData
if err := json.Unmarshal(out.Data, &data); err != nil {
return nil, err
}
return data.Alerts, nil
}