À quoi ça sert

Quand tu as plus d'un service qui tourne — une API FastAPI, des workers Celery, des conteneurs Docker — tes logs sont éparpillés. SSH sur chaque machine, tail -f sur chaque fichier… ça ne tient pas. Loki règle ça :

  • Tous les logs au même endroit, requêtables depuis Grafana.
  • Corrélation avec les métriques Prometheus (même timeline, mêmes labels).
  • Alertes sur les logs (« plus de 5 erreurs en 1 min ») via les mêmes règles que Prometheus.
  • Coût bas : Loki indexe seulement les labels, pas le contenu — beaucoup moins de stockage qu'Elasticsearch.
Loki vs Elasticsearch / ELK

L'approche ELK (Elasticsearch + Logstash + Kibana) indexe chaque mot de chaque log : très rapide à requêter, mais demande beaucoup de RAM et de disque. Loki indexe uniquement les labels (job, host, level) et stocke le reste compressé. Tu paies en vitesse de recherche full-text, mais tu divises souvent le coût par 5 ou 10. Pour des projets perso / MLOps moyenne taille, c'est nettement plus adapté.

L'architecture en 3 briques

  • L'agent (Promtail, Grafana Alloy, Fluent Bit…) — tourne à côté de tes applis, lit leurs fichiers de logs ou les sorties Docker, ajoute des labels (job, host, container), et expédie le tout à Loki via HTTP.
  • Loki — le serveur : reçoit les logs, stocke les labels dans un index, compresse les blocs de logs, et répond aux requêtes LogQL.
  • Grafana — la UI. On y ajoute Loki comme « datasource » et on requête les logs avec LogQL dans l'explorateur ou les dashboards.
Promtail, Alloy, ou Fluent Bit ?

Promtail est l'agent historique de Grafana, simple et spécialisé pour Loki. Grafana Alloy est le successeur (multi-usage : logs, métriques, traces) et remplace progressivement Promtail. Fluent Bit est un agent universel qui parle à Loki et Elasticsearch et CloudWatch — utile si tu veux rester portable. Pour démarrer : Promtail.

Un exemple d'usage

Un stack Loki + Promtail + Grafana en local avec Docker Compose, qui lit les logs de tous les conteneurs Docker de la machine :

yaml (docker-compose.yml)
services:
  loki:
    image: grafana/loki:latest
    ports: ["3100:3100"]
    command: -config.file=/etc/loki/local-config.yaml

  promtail:
    image: grafana/promtail:latest
    volumes:
      - /var/log:/var/log
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - ./promtail-config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml

  grafana:
    image: grafana/grafana:latest
    ports: ["3000:3000"]

Dans Grafana (localhost:3000) tu ajoutes une datasource Loki pointant http://loki:3100, et tu peux requêter en LogQL.

How-to : envoyer ses logs Python à Loki

  1. Écrire les logs au format JSON

    Loki préfère des logs structurés : Promtail saura extraire les champs et en faire des labels ou des fields requêtables. Avec Loguru c'est trivial :

    python
    from loguru import logger
    logger.add("logs/app.jsonl", serialize=True)
    logger.bind(user="olivier").info("action")

    Une ligne par log, format JSON, prête à être ingérée.

  2. Configurer Promtail pour lire ce fichier

    yaml (promtail-config.yml)
    server:
      http_listen_port: 9080
    
    clients:
      - url: http://loki:3100/loki/api/v1/push
    
    scrape_configs:
      - job_name: app
        static_configs:
          - targets: [localhost]
            labels:
              job: app
              __path__: /var/log/app/*.jsonl

    Le label job=app identifiera tous les logs dans LogQL. On évite de mettre trop de labels (chaque combinaison de labels crée un « stream » indexé séparément — c'est la principale source de problèmes de perf).

  3. Alternative : driver Docker direct

    Si tes applis tournent en Docker, tu peux te passer de Promtail et utiliser le driver de logs Loki de Docker directement :

    bash
    docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
    yaml
    services:
      mon-api:
        image: monimage
        logging:
          driver: loki
          options:
            loki-url: "http://localhost:3100/loki/api/v1/push"
            loki-batch-size: "400"

    Tous les stdout du conteneur partent vers Loki, sans fichier intermédiaire ni agent.

  4. Requêter avec LogQL dans Grafana

    LogQL ressemble à PromQL : on commence par un sélecteur de labels entre {}, puis on enchaîne des filtres.

    logql
    # Tous les logs du job "app"
    {job="app"}
    
    # Ceux qui contiennent "ERROR"
    {job="app"} |= "ERROR"
    
    # Exclure une chaîne
    {job="app"} |= "ERROR" != "healthcheck"
    
    # Parser le JSON et filtrer sur un champ
    {job="app"} | json | level="ERROR"
    
    # Compter les erreurs par minute (métrique tirée des logs !)
    sum(rate({job="app"} |= "ERROR" [1m]))
  5. Définir une alerte sur les logs

    Loki s'intègre à Alertmanager (celui de Prometheus). On peut déclencher une alerte si une requête LogQL renvoie un résultat — par exemple plus de 10 erreurs en 5 min :

    yaml (rules.yml)
    groups:
      - name: app-errors
        rules:
          - alert: TooManyErrors
            expr: |
              sum(rate({job="app"} |= "ERROR" [5m])) > 0.1
            for: 2m
            annotations:
              summary: "Beaucoup d'erreurs sur app"

Aide-mémoire

logql (filtres ligne)
{job="app"}                       # sélecteur de base
{job="app", level="error"}        # plusieurs labels
{job="app"} |= "texte"             # contient
{job="app"} != "texte"             # ne contient pas
{job="app"} |~ "err.*"             # regex
logql (parsers)
{job="app"} | json                  # extrait champs JSON
{job="app"} | logfmt                # format key=value
{job="app"} | json | level="ERROR"
logql (métriques)
rate({job="app"}[5m])              # lignes/sec
count_over_time({job="app"}[5m])
sum by(level) (rate({job="app"} | json [5m]))

Loki et le reste de l'écosystème

  • Grafana — l'interface unique pour explorer Loki. Mode « Explore » pour les recherches ad-hoc, panels « Logs » dans les dashboards à côté des métriques.
  • Prometheus — Loki et Prometheus partagent les mêmes labels (job, instance) : on passe d'un graphe de latence à ses logs correspondants en un clic dans Grafana.
  • Loguru / logging — sources naturelles des logs Python à pousser vers Loki, idéalement en format JSON.
  • Docker — le driver loki de Docker peut envoyer les logs des conteneurs directement sans Promtail.
  • Kubernetes — Promtail ou Grafana Alloy se déploient en DaemonSet : un agent par nœud, lit les logs de tous les pods avec les labels Kubernetes injectés automatiquement.
Le piège des labels à haute cardinalité

Ne jamais mettre en label une valeur qui change souvent (ID utilisateur, request_id, timestamp précis). Chaque combinaison unique de labels crée un stream indexé : si tu as 1 million d'IDs, tu auras 1 million de streams et Loki s'écroule. Les champs JSON dans le message sont parfaits pour ce genre d'info : on les requête, on ne les indexe pas.

Pour aller plus loin