Ir al contenido

Loki

El destino Loki envía a Grafana Loki, como líneas de log, la actividad de GitHub que es un evento y no un número (una estrella, una release, una ejecución fallida).

sinks:
loki:
url: http://loki:3100/loki/api/v1/push
tenant_id: ""
labels:
job: ghchronicle
max_age: 1h
batch: 1000

Parte de lo que informa GitHub es una medida y parte es un evento. “El repositorio tiene 148 estrellas” es una medida. “Alguien le dio una estrella a las 03:03, se publicó esta release, aquel workflow falló en main, se abrió esta alerta” son eventos: cada uno ocurrió una vez, en un momento conocido, y lo que uno quiere después es leerlos en orden y buscarlos, no promediarlos.

Veintidós medidas tienen una representación de evento: estrellas en ambas direcciones, forks, releases, versiones de paquete publicadas, pull requests, revisiones, hilos de revisión, issues, commits, ejecuciones de workflows, logs de jobs, actividad del repositorio, alertas de Dependabot, análisis de code scanning, el feed de eventos, las notificaciones, las discusiones, las entregas de webhook, los despliegues, las versiones de ruleset y las contribuciones externas. Todo lo demás es un gauge disfrazado y no se envía.

Una medida sin representación se descarta en silencio, lo cual está bien para un gauge y mal para un evento que nadie ha llegado a escribir: los despliegues y los hilos de revisión fueron eventos fechados sin línea de log durante meses y nada lo dijo. Por eso ahora toda medida fechada tiene que aparecer en una de las dos tablas de internal/sink/loki.go, la de representaciones o la de rechazos, y cada rechazo lleva el motivo por el que no es una línea de log. Una prueba falla ante una medida fechada que no aparezca en ninguna de las dos.

En el registro de la pasada ya no se descarta en silencio. Este destino escribe una fracción de lo que se le ofrece, así que la mayoría de sus líneas llevan filtered y points=0, que es lo que guardó y no lo que se le dio. Una entrada más vieja que max_age, o demasiado atrasada respecto a la más reciente de su propio flujo para que Loki la acepte, también se cuenta ahí y se informa en una línea de entradas descartadas.

Cada línea se lee primero como una frase y lleva detrás cada etiqueta y cada campo en logfmt, así que la misma línea es greppable en una terminal y consultable en Grafana sin mantener dos copias de los datos.

someone starred acme/telemetry full_name="acme/telemetry" user="someone" starred=1

Dos representaciones cambiaron en 2.6.0, y una de ellas otra vez en 2.6.1. Una línea que ya está en Loki conserva el texto con el que se envió:

  • Una contribución externa es una línea cuando el elemento se cierra, en el momento en que se cerró, y dice qué le pasó al elemento y no quién lo hizo, porque la fila no dice quién lo fusionó o lo cerró: una pull request dice USER's pull request OWNER/REPO#N was merged o USER's pull request OWNER/REPO#N was closed without merging, y una issue USER's issue OWNER/REPO#N was closed. Una fila cerrada cuyo estado y cuya marca de fusión no coinciden dice USER's contribution OWNER/REPO#N. Un elemento todavía abierto no es ninguna línea desde 2.6.1: su fila se sella al principio de cada día en que se ve abierto, lo que es una lectura y no algo que ocurrió, y abrirlo ya es una línea del feed de eventos, con action="opened". 2.6.0 enviaba esa fila como ... is open, y solo el día en que una pasada de outbound escribía en la primera hora del día UTC. Antes todas las líneas decían USER merged OWNER/REPO#N, issues abiertas incluidas.
  • Una release dice published release TAG of OWNER/REPO, o published prerelease TAG of OWNER/REPO, en el momento en que se publicó. Antes decía release TAG of OWNER/REPO, N downloads, así que una consulta que filtre por downloads solo encuentra las líneas antiguas.

batch es cuántas entradas van en un envío, 1000 salvo que se baje.

La etiqueta de stream es kind, que es por lo que se filtra primero.

{job="ghchronicle", kind="workflow_run"} |= "failure"
{job="ghchronicle", kind="job_log"}

max_age, y por qué la razón no es la obvia

Sección titulada «max_age, y por qué la razón no es la obvia»

Loki rechaza un envío entero cuando una entrada es anterior a reject_old_samples_max_age, una semana por omisión, y la mitad de lo que produce este colector es más viejo que eso a propósito: una estrella de 2020, un pull request de 2024.

Pero el límite que de verdad muerde es el otro. Loki también rechaza una entrada que esté más atrasada que su ventana de desorden respecto a la entrada más nueva que ya hay en ese stream, que es la mitad del max_chunk_age del ingester, una hora por omisión.

Medido contra un Loki 3 real: una vez que el stream tenía una entrada de las 19:14, una de las 00:35 del mismo día volvió como “entry too far behind”. Contra Loki 3.7.7 con sus límites por omisión, un stream con una entrada de hace cinco minutos aceptó una de hace 55 minutos y rechazó una de hace 75.

Así que el horizonte se aplica de dos formas:

  1. contra el reloj de pared,
  2. contra la entrada más nueva que se ha enviado antes a ese stream.

Dentro de un mismo envío nada va atrasado, porque el destino envía cada stream de la más vieja a la más nueva y Loki juzga cada entrada contra la más nueva anterior a ella: un envío con entradas de hace 23 horas, 12 horas y un minuto a un stream vacío se aceptó entero.

Lo que cae fuera se deja fuera y se cuenta, a nivel de depuración, en vez de costar el envío entero.

max_age vale una hora por omisión, que es la ventana por defecto de Loki. Súbelo solo si has subido a juego el max_chunk_age de Loki, ya que la ventana es la mitad.

Una release es el evento que muestra lo que cuesta el horizonte, y uno de los dos streams que miran más atrás. Su línea se genera a partir de gh_release_published, en el momento en que se publicó la release. Antes salía de gh_release, que se sella en la pasada porque sus descargas se mueven, así que cada pasada de repositorios volvía a enviar todas las releases. Medido con 2.5.1 en producción, durante 30,9 horas y 27 pasadas de repo, fueron 4.313 de las 10.467 líneas que envió el destino, un 41 por ciento, para las 2 releases publicadas en esas horas. Fechada en la publicación, una release la ve primero la pasada de repo siguiente, así que una publicada justo después de que una pasada leyera su repositorio tiene una cadencia entera cuando escribe la siguiente pasada, más lo que esa pasada se retrase: un tick que perdió, las familias más lentas que corrieron antes, un reinicio. Con repo y max_age en su hora por omisión, max_age por sí solo dejaba fuera esa release, y cada pasada posterior solo la veía más vieja.

Así que el stream de releases mira atrás la cadencia de repo más max_age: dos horas por omisión, siete con repo: 6h. Una contribución externa es el otro stream que lo hace, por la misma razón: su línea se fecha cuando se cerró el elemento, y la primera pasada que lo ve es la de outbound siguiente, así que el stream mira atrás la cadencia de outbound más max_age, dos horas por omisión. Antes de 2.6.1 tenía solo max_age, y un cierre se enviaba solo si la pasada siguiente escribía dentro de la hora, cosa que una pasada horaria no hace con un elemento cerrado en los segundos posteriores a que la anterior leyera las búsquedas, ni, cuando se retrasa, con uno cerrado en los minutos que lleva de retraso.

Cada mirada atrás tiene un tope de seis días, un día menos que la semana que permite reject_old_samples_max_age, y nunca acorta max_age: uno fijado por encima de seis días es el horizonte de estos streams igual que el de cualquier otro. Loki solo rechaza una línea vieja por ir atrasada respecto a una más nueva de su stream, que es lo que la segunda comprobación de arriba sigue haciendo. Una release publicada en la hora anterior a una pasada la envía esa pasada y otra vez la siguiente, la misma línea en el mismo instante, que Loki guarda una sola vez. Una contribución se envía dos veces del mismo modo, y su línea lleva el número de comentarios y el título del elemento, que pueden moverse entre medias: medido contra Loki 3.7.7, una línea enviada otra vez con comments pasado de 1 a 2 se guardó como una segunda línea en el mismo instante. Con -once la cadencia que cuenta es la del calendario que lanza el binario, así que pon every.families.repo y every.families.outbound a ese valor. Las dos están en el almacén de métricas en cualquier caso.

Para la historia fechada. De eso se encarga un almacén de métricas, y por eso los dos van juntos y no uno en lugar del otro. Un log responde “qué pasó recientemente, en orden”; una serie temporal responde “cuánto, en qué periodo”.

La familia joblogs, apagada hasta que every.families.joblogs le da una cadencia, recoge las últimas cuarenta líneas de cada job fallido de GitHub Actions. Es texto y no una medida, así que el destino de InfluxDB lo excluye por omisión y el exportador de Prometheus lo salta. Loki es donde le corresponde, y la consulta es:

{job="ghchronicle", kind="job_log"}

Los dashboards exportados no lo muestran, porque un dashboard atado a un datasource no puede consultar dos y quien lo importa puede no tener Loki: llevan un panel de texto, “Where failure output went”, con esa consulta. Publicar el dashboard desde el colector cambia ese panel por las líneas, las más recientes primero, filtradas por la variable de repositorio del dashboard allí donde la variable del almacén se puede leer como expresión regular. Basta con un sink de Loki cuya dirección termine en /loki/api/v1/push: se adopta un datasource de Loki que Grafana ya tenga en esa dirección, sin la ruta, y si no lo hay el datasource se crea a partir de ella. Uno que escriba en otro sitio lo dice y toma un grafana.datasource.loki_uid en su lugar, y lo mismo un Grafana que llega a Loki por otra dirección. Un token que no puede crear el datasource cuesta este panel y nada más: la ejecución avisa una vez y publica todos los dashboards con la nota.

Un stream es su job, su tipo y las etiquetas configuradas, y las etiquetas de un punto van dentro de la línea, así que una versión que mueve una etiqueta no cambia ningún stream, y -migrate no tiene nada que hacer aquí. Una línea es lo que se dijo cuando se dijo, y Loki rechaza de todos modos las entradas más antiguas que su ventana.

  • Elegir almacén compara Loki con los demás, y lleva el registro de escrituras que todos comparten.
  • Los dashboards dice cuál de los cinco se dibuja contra cada almacén, y en qué se convierte un panel que un almacén no puede responder.