RousselTMLEARNING

Observabilité

Grafana Alloy et les logs : collecter et envoyer vers Loki

Découvrez comment configurer Grafana Alloy pour lire des fichiers de logs, journald ou des flux réseau, traiter ces données et les expédier en toute sécurité vers Grafana Loki.

1. Sources de logs #

Lecture de fichiers, journald et récepteurs réseau.

D'où viennent les logs ? #

Disponible dans : ✓ OSS ✓ Enterprise ✓ Cloud

La chaîne des logs dans Alloy
ComposantSource
loki.source.fileFichiers, avec suivi de la rotation
loki.source.journalJournal systemd
loki.source.dockerConteneurs Docker
loki.source.kubernetesPods, par l’API Kubernetes
loki.source.syslogMessages syslog reçus sur le réseau
loki.source.apiRéception en HTTP, au format de Loki
otelcol.receiver.otlpLogs OpenTelemetry des applications

Lire des fichiers #

local.file_match trouve les fichiers, loki.source.file les lit. Les étiquettes de la cible, ici job, accompagnent chaque ligne ; __path__ désigne le fichier, et devient l'étiquette filename.

local.file_match "app" {
  path_targets = [{ "__path__" = "/var/log/app/*.log", "job" = "app" }]
}

loki.source.file "app" {
  targets    = local.file_match.app.targets
  forward_to = [loki.write.local.receiver]
}

loki.write "local" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}

Alloy mémorise sa position dans --storage.path : au redémarrage, il reprend là où il s'était arrêté. Si ce dossier n'est pas conservé, tout le fichier est relu, et Loki reçoit des doublons.

Rien n'arrive ? Vérifiez d'abord les droits : l'utilisateur alloy doit pouvoir lire les fichiers. Sur Debian, ajoutez-le au groupe adm pour /var/log.

Le journal systemd #

loki.source.journal "systeme" {
  forward_to = [loki.write.local.receiver]
  labels     = { job = "journal" }
}

loki.write "local" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}

loki.source.journal peut aussi transformer les champs internes du journal avec relabel_rules, par exemple pour conserver le nom de l'unité.

Vérifier sans Loki #

Pour observer ce que voit Alloy, branchez loki.echo dans forward_to : chaque ligne est écrite dans les journaux d'Alloy, avec ses étiquettes. Nos essais de cette formation se sont appuyés sur ce composant.

2. Pipelines de traitement #

Filtrage, parsing (JSON, Regex) et réétiquetage (Relabeling).

Traiter ligne par ligne #

loki.process applique des étapes, dans l'ordre. Elles partagent une zone de travail, la « carte extraite » : une étape y dépose des valeurs, une suivante les utilise.

loki.process "app" {
  forward_to = [loki.write.local.receiver]

  stage.json {
    expressions = { level = "", service = "", trace_id = "", ts = "", msg = "" }
  }

  stage.drop {
    source = "level"
    value  = "debug"
  }

  stage.timestamp {
    source = "ts"
    format = "RFC3339"
  }

  stage.labels {
    values = { level = "", service = "" }
  }

  stage.structured_metadata {
    values = { trace_id = "" }
  }

  stage.static_labels {
    values = { env = "production" }
  }

  stage.output {
    source = "msg"
  }
}

loki.write "local" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}

Ce que produit ce pipeline #

Testé de bout en bout avec un vrai Loki, sur ce fichier :

{"ts":"2026-11-05T10:00:00Z","level":"error","service":"paiement","trace_id":"abc123","msg":"timeout banque"}
{"ts":"2026-11-05T10:00:00Z","level":"debug","service":"paiement","trace_id":"abc124","msg":"détail inutile"}
{"ts":"2026-11-05T10:00:00Z","level":"info","service":"panier","trace_id":"abc125","msg":"article ajouté"}
ligne non json
LigneDans Loki
error, paiementTexte timeout banque, étiquettes level=error, service=paiement, env=production, métadonnée trace_id=abc123
debugÉcartée : loki_process_dropped_lines_total vaut 1
info, panierTexte article ajouté, étiquettes level=info, service=panier
ligne non jsonConservée telle quelle, sans level ni service

Les étapes les plus utiles #

ÉtapeRôle
stage.json, stage.regex, stage.logfmtExtraire des valeurs
stage.labelsFaire d’une valeur extraite une étiquette
stage.static_labelsAjouter une étiquette à valeur fixe
stage.structured_metadataGarder une valeur variée, sans créer de flux
stage.timestampUtiliser l’heure du log
stage.dropÉcarter des lignes
stage.outputRemplacer le texte de la ligne
stage.multilineRegrouper plusieurs lignes
Ne confondez pas stage.labels et stage.static_labels. Dans stage.labels { values = { env = "production" } }, production est le nom d'une valeur extraite, pas un texte. alloy validate l'accepte, mais nous avons vérifié que l'étiquette n'apparaît jamais. Utilisez stage.static_labels.
Attention à la cardinalité : chaque combinaison d'étiquettes crée un flux dans Loki. trace_id ou un identifiant utilisateur en étiquette dégrade vite les performances : c'est le rôle de stage.structured_metadata.

Les piles d'appels sur plusieurs lignes #

Sans précaution, chaque ligne d'une pile Java devient un log distinct. stage.multiline les regroupe : une nouvelle entrée commence à chaque ligne qui correspond à firstline.

loki.process "java" {
  forward_to = [loki.write.local.receiver]

  stage.multiline {
    firstline     = "^\\d{4}-\\d{2}-\\d{2}"
    max_wait_time = "3s"
  }

  stage.regex {
    expression = "^\\S+ \\S+ (?P<level>[A-Z]+) "
  }

  stage.labels {
    values = { level = "" }
  }
}

loki.write "local" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}

Testé : les 3 lignes de la pile forment une seule entrée, avec level=ERROR.

3. Expédition vers Loki #

Envoi sécurisé vers Grafana Loki.

Le composant loki.write #

loki.write "central" {
  external_labels = { cluster = "paris" }

  endpoint {
    url       = "https://loki.example.com/loki/api/v1/push"
    tenant_id = "equipe-paiement"

    batch_size = "1MiB"
    batch_wait = "1s"

    max_backoff_retries = 10
    max_backoff_period  = "5m"

    basic_auth {
      username = sys.env("LOKI_USER")
      password = sys.env("LOKI_PASSWORD")
    }
  }
}
RéglageRôleDéfaut
batch_sizeTaille d’un lot1MiB
batch_waitAttente maximale avant l’envoi1s
min_backoff_periodPremier délai avant une nouvelle tentative500ms
max_backoff_periodDélai maximal entre deux tentatives5m
max_backoff_retriesNombre de tentatives ; 0 pour toujours réessayer10
tenant_idLocataire, en mode multi-tenant–

Que se passe-t-il pendant une panne ? #

Alloy retente avec un délai croissant. Une fois les tentatives épuisées, le lot est perdu et loki_write_dropped_entries_total augmente. Pour vos logs de fichiers, la position protège ce qui n'a pas encore été lu ; pour le reste, prévoyez le risque.

# Lignes perdues après épuisement des nouvelles tentatives (à alerter)
rate(loki_write_dropped_entries_total[5m]) > 0

# Lignes envoyées, nouvelles tentatives
rate(loki_write_sent_entries_total[5m])
rate(loki_write_batch_retries_total[5m])

Plusieurs destinations #

Comme pour les métriques, forward_to accepte plusieurs récepteurs : un loki.write pour le stockage courant, un autre pour l'audit, avec une rétention plus longue côté serveur. Les external_labels s'ajoutent à l'envoi : réservez-les à ce qui identifie le site, comme cluster.

Vérifier l'arrivée #

Nous avons vérifié l'envoi directement dans Loki :

curl -s http://localhost:3100/loki/api/v1/labels
curl -s -G http://localhost:3100/loki/api/v1/query_range \
  --data-urlencode 'query={job="app"}' \
  --data-urlencode "start=$(( $(date +%s)-600 ))000000000"
Bonne pratique : gardez peu d'étiquettes, à faible nombre de valeurs. job, env, level et service couvrent la plupart des besoins ; le reste se cherche dans le texte, au moment de la requête.

Articles liés

Alloy et l'écosystème Grafana Labs : quel composant pour quel outil ?

La carte des intégrations d'Alloy avec Faro, Beyla, k6, Loki, Mimir, Tempo, Pyroscope, Database Observability…

Faro et Alloy : recevoir le RUM front-end avec faro.receiver

Configurer faro.receiver : CORS, clé d'API, limite de débit, source maps, puis envoyer erreurs, Web Vitals et…

Beyla et Alloy : métriques RED et traces sans code avec beyla.ebpf

Activer l'instrumentation eBPF de Beyla dans Alloy : découverte des services, métriques RED vers Mimir, trace…

Pour aller plus loin

Soutenir

♥ SOUTENIR

Soutenez RousselTM Learning

Les formations, articles, TP et attestations restent gratuits et sans publicité intrusive. Une contribution libre, du montant de votre choix, finance l’hébergement, les environnements de TP et l’écriture de nouveaux contenus.

Contribuer avec PayPal Montant libre · carte bancaire ou compte PayPalEn savoir plus
QR code PayPal
Ou scannez ce QR code avec votre téléphone

Merci, chaque contribution compte !