87 lines
2.2 KiB
Go
87 lines
2.2 KiB
Go
package infra
|
|
|
|
import (
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// FormatPromExposition convierte un slice de PromSample en texto con formato
|
|
// Prometheus exposition. Genera una linea por sample:
|
|
//
|
|
// name{k1="v1",k2="v2"} value timestampMs
|
|
//
|
|
// Reglas:
|
|
// - Si timestampMs <= 0, omite el campo timestamp.
|
|
// - Sin labels: "name value" (sin llaves).
|
|
// - Las labels se ordenan por clave (salida determinista).
|
|
// - En los valores de label se escapa: backslash -> \\, comilla -> \", newline -> \n.
|
|
// - El nombre de metrica se sanitiza a [a-zA-Z0-9_:] (el resto -> _).
|
|
// - El valor se formatea con strconv.FormatFloat(v, 'g', -1, 64).
|
|
//
|
|
// Es una funcion pura: no tiene efectos secundarios y la salida es deterministica
|
|
// para una entrada dada.
|
|
func FormatPromExposition(samples []PromSample, timestampMs int64) string {
|
|
var b strings.Builder
|
|
for _, s := range samples {
|
|
b.WriteString(sanitizeMetricName(s.Name))
|
|
|
|
if len(s.Labels) > 0 {
|
|
keys := make([]string, 0, len(s.Labels))
|
|
for k := range s.Labels {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
b.WriteByte('{')
|
|
for i, k := range keys {
|
|
if i > 0 {
|
|
b.WriteByte(',')
|
|
}
|
|
b.WriteString(k)
|
|
b.WriteString(`="`)
|
|
b.WriteString(escapeLabelValue(s.Labels[k]))
|
|
b.WriteByte('"')
|
|
}
|
|
b.WriteByte('}')
|
|
}
|
|
|
|
b.WriteByte(' ')
|
|
b.WriteString(strconv.FormatFloat(s.Value, 'g', -1, 64))
|
|
|
|
if timestampMs > 0 {
|
|
b.WriteByte(' ')
|
|
b.WriteString(strconv.FormatInt(timestampMs, 10))
|
|
}
|
|
|
|
b.WriteByte('\n')
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// sanitizeMetricName sustituye cualquier caracter fuera de [a-zA-Z0-9_:] por '_'.
|
|
func sanitizeMetricName(name string) string {
|
|
var b strings.Builder
|
|
b.Grow(len(name))
|
|
for _, r := range name {
|
|
if (r >= 'a' && r <= 'z') ||
|
|
(r >= 'A' && r <= 'Z') ||
|
|
(r >= '0' && r <= '9') ||
|
|
r == '_' || r == ':' {
|
|
b.WriteRune(r)
|
|
} else {
|
|
b.WriteByte('_')
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// escapeLabelValue escapa los caracteres especiales del formato exposition en
|
|
// el valor de una label: backslash, comilla doble y newline.
|
|
func escapeLabelValue(v string) string {
|
|
v = strings.ReplaceAll(v, `\`, `\\`)
|
|
v = strings.ReplaceAll(v, `"`, `\"`)
|
|
v = strings.ReplaceAll(v, "\n", `\n`)
|
|
return v
|
|
}
|