---
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\":[[\"<ts_ns>\",\"<line>\"],...]}]} 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.
