package observability import ( "fmt" "strings" ) // ExportPrometheus exports metrics in Prometheus text format. func (m *Metrics) ExportPrometheus() string { m.mu.RLock() defer m.mu.RUnlock() var sb strings.Builder // Help and type for request_total counter sb.WriteString("# HELP gateway_requests_total Total number of HTTP requests\n") sb.WriteString("# TYPE gateway_requests_total counter\n") for key, count := range m.requestTotal { parts := strings.Split(key, ":") if len(parts) == 3 { route, upstream, status := parts[0], parts[1], parts[2] sb.WriteString(fmt.Sprintf("gateway_requests_total{route=\"%s\",upstream=\"%s\",status=\"%s\"} %d\n", route, upstream, status, count)) } } sb.WriteString("\n") // Help and type for request_duration_seconds histogram sb.WriteString("# HELP gateway_request_duration_seconds Request latency in seconds\n") sb.WriteString("# TYPE gateway_request_duration_seconds histogram\n") buckets := []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10} for key := range m.requestDurationBuckets { parts := strings.Split(key, ":") if len(parts) == 2 { route, upstream := parts[0], parts[1] // Write buckets cumulativeCount := int64(0) for _, bucket := range buckets { if count, ok := m.requestDurationBuckets[key][bucket]; ok { cumulativeCount += count } sb.WriteString(fmt.Sprintf("gateway_request_duration_seconds_bucket{route=\"%s\",upstream=\"%s\",le=\"%g\"} %d\n", route, upstream, bucket, cumulativeCount)) } // Write +Inf bucket totalCount := int64(0) for _, count := range m.requestDurationBuckets[key] { totalCount += count } sb.WriteString(fmt.Sprintf("gateway_request_duration_seconds_bucket{route=\"%s\",upstream=\"%s\",le=\"+Inf\"} %d\n", route, upstream, totalCount)) // Write sum totalDuration := m.requestDuration[key] sb.WriteString(fmt.Sprintf("gateway_request_duration_seconds_sum{route=\"%s\",upstream=\"%s\"} %g\n", route, upstream, float64(totalDuration)/1000.0)) // convert ms to seconds // Write count sb.WriteString(fmt.Sprintf("gateway_request_duration_seconds_count{route=\"%s\",upstream=\"%s\"} %d\n", route, upstream, totalCount)) } } sb.WriteString("\n") // Help and type for gateway_bytes_in counter sb.WriteString("# HELP gateway_bytes_in_total Total bytes received from clients\n") sb.WriteString("# TYPE gateway_bytes_in_total counter\n") for key, count := range m.bytesIn { parts := strings.Split(key, ":") if len(parts) == 2 { route, upstream := parts[0], parts[1] sb.WriteString(fmt.Sprintf("gateway_bytes_in_total{route=\"%s\",upstream=\"%s\"} %d\n", route, upstream, count)) } } sb.WriteString("\n") // Help and type for gateway_bytes_out counter sb.WriteString("# HELP gateway_bytes_out_total Total bytes sent to clients\n") sb.WriteString("# TYPE gateway_bytes_out_total counter\n") for key, count := range m.bytesOut { parts := strings.Split(key, ":") if len(parts) == 2 { route, upstream := parts[0], parts[1] sb.WriteString(fmt.Sprintf("gateway_bytes_out_total{route=\"%s\",upstream=\"%s\"} %d\n", route, upstream, count)) } } sb.WriteString("\n") // Help and type for upstream_health gauge sb.WriteString("# HELP gateway_upstream_health Upstream health status (1=healthy, 0=unhealthy)\n") sb.WriteString("# TYPE gateway_upstream_health gauge\n") for upstream, health := range m.upstreamHealth { sb.WriteString(fmt.Sprintf("gateway_upstream_health{upstream=\"%s\"} %d\n", upstream, health)) } sb.WriteString("\n") // Help and type for streaming responses sb.WriteString("# HELP gateway_streaming_responses_total Total streaming responses\n") sb.WriteString("# TYPE gateway_streaming_responses_total counter\n") for key, count := range m.streamingResponsesTotal { parts := strings.Split(key, ":") if len(parts) == 2 { route, upstream := parts[0], parts[1] sb.WriteString(fmt.Sprintf("gateway_streaming_responses_total{route=\"%s\",upstream=\"%s\"} %d\n", route, upstream, count)) } } sb.WriteString("\n") // Help and type for streaming byte count sb.WriteString("# HELP gateway_streaming_bytes_total Total bytes in streaming responses\n") sb.WriteString("# TYPE gateway_streaming_bytes_total counter\n") for key, count := range m.streamingByteCount { parts := strings.Split(key, ":") if len(parts) == 2 { route, upstream := parts[0], parts[1] sb.WriteString(fmt.Sprintf("gateway_streaming_bytes_total{route=\"%s\",upstream=\"%s\"} %d\n", route, upstream, count)) } } return sb.String() }