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 }