1. Sources de logs #
Lecture de fichiers, journald et récepteurs réseau.
D'où viennent les logs ? #
Disponible dans : ✓ OSS ✓ Enterprise ✓ Cloud
| Composant | Source |
|---|---|
loki.source.file | Fichiers, avec suivi de la rotation |
loki.source.journal | Journal systemd |
loki.source.docker | Conteneurs Docker |
loki.source.kubernetes | Pods, par l’API Kubernetes |
loki.source.syslog | Messages syslog reçus sur le réseau |
loki.source.api | Réception en HTTP, au format de Loki |
otelcol.receiver.otlp | Logs 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'utilisateuralloydoit pouvoir lire les fichiers. Sur Debian, ajoutez-le au groupeadmpour/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| Ligne | Dans Loki |
|---|---|
error, paiement | Texte 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, panier | Texte article ajouté, étiquettes level=info, service=panier |
ligne non json | Conservée telle quelle, sans level ni service |
Les étapes les plus utiles #
| Étape | Rôle |
|---|---|
stage.json, stage.regex, stage.logfmt | Extraire des valeurs |
stage.labels | Faire d’une valeur extraite une étiquette |
stage.static_labels | Ajouter une étiquette à valeur fixe |
stage.structured_metadata | Garder une valeur variée, sans créer de flux |
stage.timestamp | Utiliser l’heure du log |
stage.drop | Écarter des lignes |
stage.output | Remplacer le texte de la ligne |
stage.multiline | Regrouper plusieurs lignes |
Ne confondez passtage.labelsetstage.static_labels. Dansstage.labels { values = { env = "production" } },productionest le nom d'une valeur extraite, pas un texte.alloy validatel'accepte, mais nous avons vérifié que l'étiquette n'apparaît jamais. Utilisezstage.static_labels.
Attention à la cardinalité : chaque combinaison d'étiquettes crée un flux dans Loki.trace_idou un identifiant utilisateur en étiquette dégrade vite les performances : c'est le rôle destage.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églage | Rôle | Défaut |
|---|---|---|
batch_size | Taille d’un lot | 1MiB |
batch_wait | Attente maximale avant l’envoi | 1s |
min_backoff_period | Premier délai avant une nouvelle tentative | 500ms |
max_backoff_period | Délai maximal entre deux tentatives | 5m |
max_backoff_retries | Nombre de tentatives ; 0 pour toujours réessayer | 10 |
tenant_id | Locataire, 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,leveletservicecouvrent la plupart des besoins ; le reste se cherche dans le texte, au moment de la requête.
