Skip to main content

Command Palette

Search for a command to run...

La gestion des logs applicatifs avec l'écosystème OpenTelemetry

Published
12 min read
La gestion des logs applicatifs avec l'écosystème OpenTelemetry

Mise en place du collecteur OpenTelemetry et instrumentation des logs sur une application Node.js

Cet article se focalise sur la mise en place d'un collecteur OpenTelemetry, et de l'instrumentation des logs applicatifs dans des applications Node.js, puis sur la mise en place de Loki et Grafana pour consulter les logs collectés par le collecteur.

Contexte

Rôle et définition

L'observabilité est la capacité de comprendre l'état d'un système ou d'une application informatique et d'y réagir (citation extraite du site de RedHat).

Les logs applicatifs sont un pilier de l'observabilité, au même titre que les traces HTTP et les métriques. Ils sont les messages générés par les applications lors de leur exécution, et contiennent des informations brutes — erreurs, états, événements — et constituent souvent la première source de diagnostic.

Pourquoi les logs sont essentiels ?

  • Ils décrivent ce qui s’est passé à un moment donné.

  • Ils aident à reconstituer une séquence d’événements.

  • Ils sont indispensables pour comprendre des erreurs fonctionnelles ou applicatives.

Objectif de l'article

Ayant plusieurs services backend utilsant Node.js, et plus précisément Express ou Nest.js, je souhaite mutualiser la collecte des logs applicatifs de ces services, et pour y parvenir, je vais utiliser les technologies suivantes:

  • l'API OpenTelemetry JavaScript / TypeScript, à intégrer directement dans le code de mes services

  • un collecteur OpenTelemetry, qui assurera la mutualisation des logs de mes services

  • des outils de monitoring: Loki et Grafana

NOTE : Alors que cet article décrit la mise en place de la mutualisation des logs, je souhaite également appliquer les mêmes principes pour les traces et les métriques, mais la description de leurs mises en place fera l'objet d'autres articles.

L'objectif en image:


Installation du collecteur OpenTelemetry

Rôle du collecteur

Le Collector agit comme un intermédiaire capable de :

  • Recevoir des données de plusieurs applications (via des receivers)

  • Les transformer ou les filtrer

  • Les exporter vers différentes plateformes (via des exporters)

Les Receivers

Les receivers ont pour rôle de recevoir les données de télémétrie et constituent le point d’entrée des traces, métriques et logs dans l’écosystème OpenTelemetry, permettant ainsi de centraliser et d’unifier la collecte des données d’observabilité.

OpenTelemetry s’appuie largement sur le protocole OTLP (pour OpenTelemetry Protocol). Les receivers peuvent également supporter d’autres protocoles reconnus, ce qui garantit une forte interopérabilité avec les outils et infrastructures existants, mais j'utilise le protocole OTLP dans ma configuration.

Le protocole OTLP consiste en une spécification de formattage de données. Le transport de ces données peut être réalisé via les protocoles HTTP ou gRPC; j'utilise le transport HTTP dans mon infrastructure.

Les Exporters

Les exporters servent à envoyer les données collectées vers des systèmes externes. Ils permettent à OpenTelemetry de rester flexible et compatible avec de nombreux outils d’observabilité. Ici aussi, j'utilise le protocole OTLP, mais, comme dans le cas des receivers, d’autres protocoles peuvent être utilisés.

Installation

Nous créons d'abord un fichier docker-compose.yml qui contient la configuration pour déployer le collecteur OpenTelemetry sous forme de conteneur Docker:

services:
  open-telemetry-collector:
    image: otel/opentelemetry-collector
    volumes:
      - ./open-telemetry-collector-config.yaml:/etc/otelcol/config.yaml
    ports:
      - 4318:4318

Ici, le collecteur va écouter sur le port spécifique 4318; les services Node.js utiliseront donc ce port pour transmettre les logs via HTTP.

Avant de lancer le conteneur, nous créons un second fichier nommé open-telemetry-collector-config.yaml dans le même dossier que le fichier docker-compose.yml. Ce fichier permet de configurer le collecteur:

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    logs:
      receivers: [otlp]
      exporters: [debug]

On note que le collecteur est configuré pour avoir:

  • un receiver qui écoute les requêtes HTTP sur le port 4318.

  • un exporter de debug qui permet de vérifier la réception des logs

  • un pipeline qui envoie à l'exporter les données reçues par le receiver

Enfin, nous lançons le conteneur:

docker compose up

Mise en place de l'API OpenTelemetry sur les services Node.js

Installation

J'installe les dépendances sur mon projet Node.js:

pnpm install --save @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions

NOTE : j'utilise la commande pnpm, mais npm ou yarn conviennent également.

Concepts

Au sens de la spécification OTLP, un log est un objet qui contient principalement un body et des attributes.

Dans l'exemple ci-dessous, nous allons créer les éléments suivants, qui ajoutent des attributes communs à tous les logs qui les implémentent :

  • une resource, qui ajoute des attributes de haut niveau (ex: nom du service, environnement, ...)

  • un scope, qui agit comme une instance du logger et qui ajoute aussi des attributes partagés (ex: section de l'application)

Le log que nous allons créer va donc utiliser une resource et un scope qui lui injecteront les attributes suivants:

nom attribute valeur attribute ajouté par
service.name mon-service-nodejs resource
service.version 1.0.0 resource
deployment.environment development resource
logger.section http-controller scope

Exemple d'implémentation

puis nous créons un fichier exemple-open-telemetry.js à la racine du projet. Cet exemple va créer un exporter OTLP qui est utilisé pour transmettre nos logs au collecteur

import { logs } from '@opentelemetry/api-logs';
import {
  LoggerProvider,
  BatchLogRecordProcessor,
} from '@opentelemetry/sdk-logs';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions';

// Création d'une `resource` (qui ajoute des attributs à chaque log)
const resource = new Resource({
  [SEMRESATTRS_SERVICE_NAME]: 'mon-service-nodejs',
  'service.version': '1.0.0',
  'deployment.environment': 'development',
});
const loggerProvider = new LoggerProvider({
  resource: resource,
});

// Création d'un exporter OTLP qui envoie les logs via HTTP sur le port 4318
const otlpExporter = new OTLPLogExporter({
  url: 'http://localhost:4318/v1/logs', // Modifier `localhost` pour le nom de domaine ou l'adresse IP du collecteur
  headers: {
    // Ajouter des headers d'authentification si nécessaire
    // 'Authorization': 'Bearer YOUR_TOKEN',
  },
});

// Utilisation de la fonctionnalité de batch:
// Plutôt qu'envoyer une requête HTTP par log,
// il est souvent préférable d'utiliser un traitement par lot 🚀
loggerProvider.addLogRecordProcessor(
  new BatchLogRecordProcessor(otlpExporter, {
    maxQueueSize: 2048,
    maxExportBatchSize: 512,
    scheduledDelayMillis: 5000,
  })
);

// Déclaration del'instance du logger
// Cette instance est considérée comme le `scope` (qui ajoute des attributs à chaque log)
logs.setGlobalLoggerProvider(loggerProvider);
const logger = logs.getLogger('logger.section', 'http-controller');

// Envoi du 1er log !! 😀
// (Ici, un log typique d'application Express ou Nest.js)
// Il implémente la structure OTLP à envoyer au collecteur
logger.emit({
  severityText: 'INFO',
  body: 'Requete entrante',
  attributes: {
    'http.method': 'GET',
    'http.url': '/api/users',
    'http.status_code': 200,
  },
});

// Extinction du logger lorsque le traitement est terminé
setTimeout(async () => {
  console.log('Fermeture...');
  await loggerProvider.shutdown();
  console.log('Les logs ont été exportés !!');
}, 2000);

Puis nous exécutons le script:

node exemple-open-telemetry.js

Vérification de fonctionnement

Nous allons vérifier que le collecteur a bien reçu la donnée OTLP de notre log applicatif.

D'abord, j'ai besoin de connaître le nom de notre conteneur Docker:

docker ps

Une liste des conteneurs actifs va être affichée; celui que nous cherchons dans cette liste utilise l'image otel/opentelemetry-collector, ce qui nous permet de trouver son nom en fin de ligne : setup-open-telemetry-collector-1.

Grâce à ce nom, nous pouvons afficher les logs du collecteur (de logs 🙄):

docker logs setup-open-telemetry-collector-1

et nous voyons bien dans les informations en retour que notre log a été reçu correctement 🎉 :

2026-03-23T10:29:01.917Z        info    ResourceLog #0
Resource SchemaURL:
Resource attributes:
     -> service.name: Str(mon-service-nodejs)
     -> telemetry.sdk.language: Str(nodejs)
     -> telemetry.sdk.name: Str(opentelemetry)
     -> telemetry.sdk.version: Str(1.23.0)
     -> service.version: Str(1.0.0)
     -> deployment.environment: Str(development)
ScopeLogs #0
ScopeLogs SchemaURL:
InstrumentationScope logger.section http-controller
LogRecord #0
ObservedTimestamp: 2026-03-23 10:28:59.901 +0000 UTC
Timestamp: 2026-03-23 10:28:59.901 +0000 UTC
SeverityText: INFO
SeverityNumber: Unspecified(0)
Body: Str(Requete entrante)
Attributes:
     -> http.method: Str(GET)
     -> http.url: Str(/api/users)
     -> http.status_code: Int(200)
Trace ID:
Span ID:
Flags: 0

Installation de Loki et Grafana

Pourquoi ces outils?

Après collecte des logs, nous avons désormais besoin d'une interface pour les visualiser, les trier, les filtrer...

Il existe plusieurs solutions sur le marché, et voici un tableau comparatif des plus célèbres d'entre elles:

Critère Grafana Loki Elastic Kibana Datadog New Relic
Rôle Stockage et gestion des logs. Analyse et visualisation des logs et métriques. Collecte, analyse et visualisation des logs, métriques, et traces. Surveillance des performances applicatives, logs et métriques.
Connectivité avec OpenTelemetry Intégration native via OpenTelemetry Collector (OTLP) Intégration via Elastic Agent ou OpenTelemetry Collector Intégration native avec OpenTelemetry. Intégration native avec OpenTelemetry.
Visualisation Visualisation via Grafana. Visualisation via Kibana (interface web). Tableau de bord personnalisé avec de nombreuses visualisations intégrées. Interface web riche avec visualisation de logs et de performances.
Coût Open source (gratuite pour usage de base), mais des coûts peuvent s'appliquer pour l'infrastructure et les plugins payants Gratuit pour l'usage de base, coût pour l'infrastructure et les fonctionnalités avancées Modèle payant basé sur l’utilisation (logs, traces, métriques) avec tarification par volume Modèle payant basé sur l’utilisation (log, métriques, traces) avec options d’abonnement par niveau.
Dépendance fournisseur Faible : Solution open source, mais peut nécessiter des outils associés pour la gestion à grande échelle. Dépendance à Elastic (en particulier pour les services managés). Haute : Solution SaaS avec dépendance à Datadog pour la gestion des logs et des métriques. Haute : Solution SaaS avec dépendance à New Relic pour la gestion des logs et des métriques.
Scalabilité Excellente scalabilité avec un stockage optimisé pour les logs (peut être plus complexe à gérer à très grande échelle). Très bonne scalabilité, mais peut devenir coûteuse pour les très grands volumes de données. Scalabilité très bonne, conçu pour des environnements de grande taille. Scalabilité très bonne, mais coûteuse à grande échelle.
Recherches et requêtes Recherche rapide et efficace via Grafana, mais limité à des recherches basées sur des logs sans analyse avancée. Recherche puissante avec support de requêtes complexes via Elasticsearch. Recherche rapide et bien intégrée, avec une prise en charge des logs et des traces. Recherche rapide, avec des filtres et des capacités de requêtes basées sur des logs et des traces.
Formats standards Compatible avec des formats comme JSON, Logfmt, et d’autres formats personnalisés. Compatible avec JSON, les logs structurés et semi-structurés. Compatible avec JSON, logfmt, et autres formats courants. Compatible avec JSON, logfmt, et autres formats courants.

Pour ma part, je souhaite une solution 100% Open Source (si possible); et en particulier éviter les dépendances à des outils tiers. Le critère du coût est prioritaire, et je veux le plus possible avoir la main sur le traitement de mes données par ces outils. Mon choix s'est donc porté sur le couple Grafana / Loki.

Par ailleurs, Loki reconnaît nativement les données OTLP que le collecteur lui transmet par requêtes HTTP; nous restons donc sur les mêmes principes de fonctionnement que ceux présentés au début de cet article.

Installation

Nous ajoutons de nouveaux services au fichier de configuration de conteneurs docker-compose.yml (et quelques paramètres au collecteur):

services:
  open-telemetry-collector:
    image: otel/opentelemetry-collector
    volumes:
      - ./open-telemetry-collector-config.yaml:/etc/otelcol/config.yaml
    ports:
      - 4318:4318
    depends_on:
      - loki
    networks:
      - loki

  loki:
    image: grafana/loki:3.4.2
    ports:
      - "3100:3100"
    volumes:
      - ./loki-config.yaml:/etc/loki/local-config.yaml
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - loki

  grafana:
    image: grafana/grafana:11.6.0
    environment:
      - GF_FEATURE_TOGGLES_ENABLE=grafanaManagedRecordingRules
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_BASIC_ENABLED=false
    ports:
      - 3000:3000/tcp
    entrypoint:
       - sh
       - -euc
       - |
         mkdir -p /etc/grafana/provisioning/datasources
         cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
         apiVersion: 1
         datasources:
         - name: Loki
           type: loki
           access: proxy
           orgId: 1
           url: 'http://loki:3100'
           basicAuth: false
           isDefault: true
           version: 1
           editable: true
         EOF
         /run.sh
    networks:
      - loki

networks:
    loki:

Avec cette configuration:

  • Loki écoute les requêtes HTTP au format OTLP sur le port 3100

  • L'interface visuelle Grafana sera disponible sur l'URL suivante: http://localhost:3000

Et nous configurons un nouvel exporter dans notre fichier de configuration du collecteur open-telemetry-collector-config.yaml :

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

exporters:
  debug:
    verbosity: detailed
  otlphttp/logs:
    endpoint: "http://loki:3100/otlp"
    tls:
      insecure: true

service:
  pipelines:
    logs:
      receivers: [otlp]
      exporters: [debug, otlphttp/logs]

Il faut noter que l'exporter est paramétré pour envoyer les données vers le nom de domaine loki sur le port 3100; ce nom de domaine est configuré dans la configuration des conteneurs, section networks.

Loki a lui aussi besoin de son fichier de configuration loki-config.yaml, à placer dans le même dossier que les 2 fichiers précédents:

auth_enabled: false

limits_config:
  allow_structured_metadata: true
  volume_enabled: true

server:
  http_listen_port: 3100

common:
  ring:
    instance_addr: 0.0.0.0
    kvstore:
      store: inmemory
  replication_factor: 1
  path_prefix: /tmp/loki

schema_config:
  configs:
  - from: 2020-05-15
    store: tsdb
    object_store: filesystem
    schema: v13
    index:
      prefix: index_
      period: 24h

storage_config:
  tsdb_shipper:
    active_index_directory: /tmp/loki/index
    cache_location: /tmp/loki/index_cache
  filesystem:
    directory: /tmp/loki/chunks

pattern_ingester:
  enabled: true

Nous relançons les conteneurs:

docker compose up

Nous exécutons à nouveau le script Node.js présenté en partie 1:

node exemple-open-telemetry.js

Lorsque les conteneurs sont lancés, Grafana est disponible sur l'URL suivante : http://localhost:3000.

Dans la section "Explore", nous allons créer une query. On note que Grafana dispose déjà d'une liste de labels créée à partir de notre simple exécution du script ci-dessus; on sélectionne donc le label "deployment_environment" et la valeur "development" pour ce label, puis on clique sur le bouton "Run query".

Nous trouvons bien notre log, et ses métadonnées dans les résultats de Grafana.


Industrialiser la solution

Pour mettre à l'échelle cette infrastructure, voici mes prochaines étapes

  • installer mes services Node.js, et instances du collecteur, de Loki et de Grafana sur des machines différentes (et donc remplacer "localhost" par des noms de domaine)

  • utiliser HTTPS pour les communications entre ces instances

  • paramétrer l'authentification sur les différents outils

  • factoriser le code d'exemple présenté en partie 1, de sorte à l'implémenter sous forme d'une classe TypeScript


Conclusion

Cet article, bien que focalisé sur la gestion des logs de leur production dans le code jusqu'à leur exploitation visuelle dans un écosystème OpenTelemetry, a également décrit l'installation du collecteur.

Ce collecteur est le fondement de cet écosystème, et j'écris d'ailleurs un article similaire à propos de l'instrumentation des traces HTTP. Ce nouvel article se basera lui aussi sur le collecteur et j'en reprendrai donc les éléments de configuration...

Opentelemetry

Part 1 of 1

Mise en place et exploitation d'Opentelemetry, une solution dédiée à l'observabilité de nos systèmes