--- name: push_loki_stream kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func PushLokiStream(endpoint string, user string, pass string, labels map[string]string, timestampsNs []int64, lines []string) error" description: "Envia lineas de log a un servidor Grafana Loki via su push API. Construye el cuerpo JSON {\"streams\":[{\"stream\":{labels},\"values\":[[\"\",\"\"],...]}]} y lo POSTea al endpoint. Soporta Basic Auth opcional, valida que timestamps y lineas tengan igual longitud, es no-op si no hay lineas, y exige status 2xx. Solo stdlib, TLS verificado." tags: [loki, grafana, logs, push, metrics, http, json, stdlib, infra, fleet-metrics] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: ["bytes", "encoding/json", "fmt", "io", "net/http", "strconv", "time"] params: - name: endpoint desc: "URL completa del push API de Loki (ej https://logs-xxxx.organic-machine.com/loki/api/v1/push)" - name: user desc: "usuario para Basic Auth; si es cadena vacia no se envia Authorization" - name: pass desc: "password para Basic Auth; solo se usa cuando user != ''" - name: labels desc: "labels del stream Loki (ej {instance:lucas, job:journald, unit:ssh.service}); van tal cual en el campo stream" - name: timestampsNs desc: "timestamps en nanosegundos desde epoch, uno por linea; debe tener la misma longitud que lines" - name: lines desc: "lineas de log a enviar, alineadas posicionalmente con timestampsNs; si esta vacio la funcion es no-op" output: "error si la peticion falla, las longitudes no coinciden o el status no es 2xx; nil en exito (incluido el no-op de 0 lineas)" tested: true tests: - "JSON enviado tiene estructura streams/stream/values correcta" - "longitudes desiguales dan error antes del POST" - "len lines cero es no-op sin peticion" - "Basic Auth presente cuando user no vacio" - "status 500 produce error" test_file_path: "functions/infra/push_loki_stream_test.go" file_path: "functions/infra/push_loki_stream.go" --- ## Ejemplo ```go labels := map[string]string{ "instance": "lucas", "job": "journald", "unit": "ssh.service", } nowNs := time.Now().UnixNano() ts := []int64{nowNs, nowNs + 1} lines := []string{ "Accepted publickey for lucas from 10.0.0.2", "session opened for user lucas", } err := PushLokiStream( "https://logs-abcd.organic-machine.com/loki/api/v1/push", "tenant1", // user para Basic Auth (vacio = sin auth) "s3cr3t", // pass labels, ts, lines, ) if err != nil { return err } ``` ## Cuando usarla Cuando necesites enviar lineas de log a Grafana Loki desde un agente o servicio Go (ej. reenviar journald, eventos de una app, o lineas de un tailer) sin arrastrar el cliente oficial de Loki. Util para alimentar dashboards de la flota (`fleet-metrics`) con logs etiquetados por instancia/job/unit. Pasa los logs ya batcheados: un solo stream por llamada con sus labels. ## Gotchas - `timestampsNs` y `lines` deben tener exactamente la misma longitud; si no, retorna error ANTES de hacer la peticion (no envia nada). - `len(lines)==0` es un no-op deliberado: retorna `nil` sin tocar la red. Comprueba el caso vacio en el caller si necesitas distinguir "no habia logs" de "envio ok". - Loki exige timestamps en NANOSEGUNDOS. Pasar segundos o milisegundos hace que las lineas caigan fuera de la ventana de retencion y Loki las rechace silenciosamente. - Dentro de un mismo stream las entradas deberian ir en orden creciente de timestamp; Loki puede rechazar entradas fuera de orden segun su config. - Exito = status 2xx (Loki normalmente devuelve 204 No Content). Un 4xx/5xx produce error con el codigo + primeros 200 bytes del cuerpo de respuesta para diagnostico. - TLS verificado (sin InsecureSkipVerify) y `http.Client` con timeout de 10s fijo. Para endpoints con certificado interno hace falta CA confiable en el sistema. - Secretos (`user`/`pass`): nunca hardcodear — resolver desde `pass`/vault en el caller.