j’ai achetĂ© un PC...
Grafana Stack 📈 3. Collecte des logs avec OpenTelemetry

Grafana Stack 📈 3. Collecte des logs avec OpenTelemetry

⏱ 6 mn

Dans l’article prĂ©cĂ©dent , nous avons collectĂ© les mĂ©triques de l’application Spring. Reste maintenant Ă  collecter les logs de cette application. GrĂące Ă  la configuration de Logback que l’on a mis en place dans le premier article , les logs de Spring sortent au formta JSON, ce qui va grandement simplifier les pipeline de collecte.

Les autres articles de la série :

  1. Observabilité avec Spring Boot 3
  2. Collecte des métriques avec OpenTelemetry
  3. Collecte des logs avec OpenTelemetry
  4. DĂ©ploiement d’un Grafana

Prérequis au déploiement

Loki comme stockage des logs
Loki (Tom Hiddleston) dans la série de Disney+, créée par Michael Waldron et réalisée par Kate Herron. MARVEL STUDIOS

Docker Compose

Tout comme dans l’article prĂ©cĂ©dent, il faut complĂ©ter le fichier docker-compose.yml avec un nouveau service : Loki .

Service prometheus

Loki (toujours de Grafana Labs), est un moteur de stockage de logs. Sur le mĂȘme principe que Prometheus il va permettre de conserver les logs applicatifs pour les restituer via des requĂȘtes LogQL. L’approche de Loki est diffĂ©rente de celle d’Elastic par exemple, car il ne va indexer que les meta-donnĂ©es des logs et non tout leur contenu.

Stockage optimisé Loki

Voilà la déclaration du service Loki dans le compose.

services:
  loki:
    image: grafana/loki:2.8.1
    restart: unless-stopped
    environment:
      - LOKI_RETENTION_PERIOD: 90d
    command:
      - -config.file=/etc/loki/local-config.yaml
      - -config.expand-env=true
    volumes:
      - ./.compose/loki/local-config.yaml:/etc/loki/local-config.yaml
      - loki_data:/loki
    networks:
      metrics: {}

volumes:
  loki_data: {}

networks:
  metrics: {}

Loki se configure via le fichier local-config.yaml passé en paramÚtre de la ligne de commande dans le dockerfile ci-dessus. Les paramÚtres de configuration sont détaillés dans la documentation , voilà le fichier utilisé pour notre application.

On notera dans les commandes du compose le paramùtre -config.expand-env=true qui autorise à mettre des variables d’environnement dans le fichier de configuration suivant.

---
auth_enabled: false

server:
  http_listen_port: ${LOKI_LISTEN_PORT:-3100}

common:
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

compactor:
  retention_enabled: true

limits_config:
  retention_period: ${LOKI_RETENTION_PERIOD:-30d}

schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h

ruler:
  alertmanager_url: http://localhost:9093

Il s’agit du fichier de configuration par dĂ©faut avec quelques amĂ©liorations tout de mĂȘme :

  • On active une rĂ©tention de 30 jours par dĂ©faut. Sans ça, Loki garde les logs Ad-Vitam.
  • On utilise la rĂ©solution de variables d’env pour permettre de modifier les valeurs du port et la durĂ©e de rĂ©tention.

Le stockage filesystem est largement suffisant pour le cas de notre application, mais il prĂ©sente l’inconvĂ©nient de ne pas ĂȘtre scalable, contrairement Ă  d’autres systĂšmes proposĂ©s.

Service OpenTelemetry

Nous avons dĂ©jĂ  dĂ©ployĂ© un collecteur lors de l’article prĂ©cĂ©dent , on va modifier sa configuration pour y ajouter la collecte des logs.

Alternatives

Il existe plusieurs alternatives pour collecter les logs d’une application Spring vers un serveur Loki.

Finalement, de toutes ces possibilitĂ©s, j’ai optĂ© pour la derniĂšre. Le fait de lire les logs produits sur le disque plutĂŽt que de se les faire envoyer est moins intrusif pour l’application. Moins de risque de ralentir l’application et c’est mieux dĂ©corrĂ©lĂ©.

Configuration du receiver

On a dĂ©jĂ  vu la configuration d’Open Telemetry, il suffit de rajouter un receiver pour scruter les logs. Dans le cas de notre application packagĂ©e en docker, il faut aller chercher les fichiers de log du conteneur, pour ça, il y a le plugin filelog. OTEL se configure en 4 Ă©tapes :

receivers:
  filelog/containers:
    include: ["/var/lib/docker/containers/*/*.log"]
    start_at: end
    include_file_path: false
    include_file_name: false
    operators: []

Il faudra changer le rĂ©pertoire docker si vous n’utilisez pas la configuration par dĂ©faut.

À cette configuration, il faut ajouter des opĂ©rateurs qui vont transformer les logs afin de les rendre plus exploitables dans Loki.

    operators:
      - type: json_parser
        timestamp:
          parse_from: attributes.time
          layout: '%Y-%m-%dT%H:%M:%S.%LZ'

On parse les logs json qui sortent de docker.

    operators:
      - type: filter
        expr: '(attributes?.attrs?.tag ?? "empty") == "empty"'
      - type: key_value_parser
        parse_from: attributes["attrs"]["tag"]
        parse_to: resource.container
        on_error: drop
      - type: move
        from: resource.container.id
        to: resource.container_id
      - type: move
        from: resource.container.name
        to: resource.container_name
      - type: move
        from: resource.container.image
        to: resource.container_image

On extrait les informations du conteneur, son nom et celui de son image. Si le conteneur n’est pas correctement tagger, on ne tient pas compte du log. Pour que cela fonctionne, il faudra tagger correctement les conteneurs que vous souhaitez observer.

    operators:
      - type: move
        from: attributes.log
        to: body
      - type: move
        from: attributes["attrs"]["application"]
        to: resource.application
      - type: json_parser
        timestamp:
          parse_from: attributes.timestamp
          layout: '%Y-%m-%dT%H:%M:%S.%LZ'
        severity:
          parse_from: attributes.level
          mapping:
            warn: WARN
            error: ERROR
            info: INFO
            debug: DEBUG
      - type: move
        from: attributes.message
        to: body

On dĂ©place ensuite le champ attributes.log qui a Ă©tĂ© parsĂ© du json docker vers body qui est le contenu du message. Puis on refait un parsing json, cette fois pour parser le json qui sort de Spring. On prĂ©cise que le timestamp de l’évĂšnement sera celui de Spring et on mappe les niveaux de log en majuscule pour uniformiser.

Enfin, on déplace le message issu du log de Spring dans le champ body pour en faire le nouveau contenu du log.

    operators:
      - type: remove
        field: attributes.time
      - type: remove
        field: attributes.stream
      - type: remove
        field: attributes["timestamp"]
      - type: remove
        field: attributes["level"]
      - type: remove
        field: attributes["attrs"]

Pour finir, on fait le mĂ©nage dans les champs que l’on ne souhaite pas conserver.

Configuration de l’exporter

Une fois les logs reçus et traité, il faut les renvoyer à Loki. Cela se fait via un exporter loki .

exporters:
  loki:
    endpoint: 'http://loki:3100/loki/api/v1/push'

Configuration du pipeline

Enfin, on met toutes ces configurations bout Ă  bout dans le pipeline de logs Open Telemetry

service:
  pipelines:
    logs:
      receivers: [filelog/containers]
      processors: [attributes, batch]
      exporters: [logging, loki]

Service compose application

CotĂ© Open Telemetry la configuration du service ne change pas. NĂ©anmoins, pour la configuration du service de notre application, il va ĂȘtre nĂ©cessaire de tagger correctement le conteneur.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
services:

  myapp:
    image: 'marthym/myapp:2.0.0-SNAPSHOT'
    labels:
      application: myapp
    read_only: true
    environment:
      SPRING_MAIN_BANNER-MODE: off
      SPRING_PROFILES_ACTIVE: json-logging
    logging:
      driver: json-file
      options:
        labels: 'application'
        tag: 'id={{.ID}} name={{.Name}} image={{.ImageName}}'
        max-size: 12m                               
        max-file: '5'
    ports:
      - '8081:8081'
    networks:
      - myappnet
    volumes:
      - /home/marthym:/var/lib/myapp
      - /tmp/myapp:/tmp

networks:
  myappnet: {}

Le tag dans les options de logging du service docker compose va permettre de récupérer les informations du conteneur lors du parsing fait pas Open Telemetry. On ajoute aussi un label pour indiquer le nom de notre application, cela permettra de distinguer plusieurs instances si le cas se présente.

Comme pour les configurations des articles prĂ©cĂ©dents, le mode verbose d’Open Telemetry peut s’avĂ©rer trĂšs utile pour comprendre ce qu’il se passe dans le pipeline de transformation.

Cela se fait grĂące au Logging Exporter .

exporters:
  logging:
    verbosity: detailed

L’ensemble des fichiers de configuration modifiĂ©s sont disponibles sur github .

Conclusion

À ce stade, les mĂ©triques et les logs de l’application Spring sont rĂ©coltĂ©s, traitĂ©s et stockĂ©s dans les serveurs Prometheus et Loki. Il ne reste plus qu’à mettre en place un Grafana pour visualiser tout ça. C’est ce que nous verrons dans le prochain article.


Grafana Stack 📈 3. Collecte des logs avec OpenTelemetry est paru le