Nouvelles annonces sur les technologies sans serveur et réseau ainsi que sur le RUM (Real-User Monitoring) dévoilées à la conférence Dash ! Nouvelles annonces dévoilées à la conférence Dash !

Collecte de logs Java

Les logs Java sont assez complexes à gérer, principalement à cause des traces de pile. Ces dernières sont divisées en plusieurs lignes, ce qui les rend difficiles à associer à l’événement de log d’origine :

//4 événements générés alors qu'un seul événement est attendu !
Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

En demandant à votre bibliothèque de journalisation de créer des logs au format JSON :

  • vous garantissez qu’un paramètre stack_trace est correctement associé au bon LogEvent ;
  • vous vous assurez que tous les attributs d’un événement de log sont correctement extraits (sévérité, nom de l’enregistreur, nom du thread, etc.) ;
  • vous permettez l’accès au MDC, qui consiste en des attributs à associer à n’importe quel événement de log.

Pour envoyer vos logs à Datadog, nous vous recommandons d’activer la journalisation au sein d’un fichier et de le suivre avec l’Agent Datadog.

Il est fortement conseillé de configurer vos bibliothèques de journalisation afin de générer vos logs au format JSON et d’éviter de créer des règles de parsing personnalisées.

Vous trouverez ci-dessous des exemples de configuration pour les bibliothèques de journalisation log4j, slf4j et log4j2 :

Configurer votre enregistreur

Format brut

Ajoutez un nouvel appender de fichier à log4j.xml :

<appender name="fileAppender" class="org.apache.log4j.FileAppender">
  <param name="File" value="/logs/log4j.log" />
  <param name="Append" value="true" />
  <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
  </layout>
</appender>

Ajouter des identifiants de trace à vos logs

Si l’APM est activé pour cette application et que vous souhaitez améliorer la corrélation entre les traces et les logs d’application, suivez ces instructions pour définir les MDC (contextes de diagnostic mappés) afin d’ajouter automatiquement par la suite un identifiant de trace et de span à vos logs.

Une fois cette opération effectuée, le ConversionPattern à utiliser devient :

<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %X{dd.trace_id} %X{dd.span_id} - %m%n" />

Modifiez votre fichier log4j2.xml :

 <File name="MonFichier" fileName="logs/app.log" immediateFlush="true">
        <PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
 </File>
 <Loggers>
        <Root level="debug">
        <AppenderRef ref="MonFichier" />
        </Root>
</Loggers>

Ajouter des identifiants de trace à vos logs

Si l’APM est activé pour cette application et que vous souhaitez améliorer la corrélation entre les traces et les logs d’application, suivez ces instructions pour définir les MDC (contextes de diagnostic mappés) afin d’ajouter automatiquement par la suite un identifiant de trace et de span à vos logs.

Une fois cette opération terminée, le PatternLayout à utiliser devient :

<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %X{dd.trace_id} %X{dd.span_id} - %m%n" />

Modifiez votre fichier logback.xml :

<configuration>
(....)
   <timestamp key="byDay" datePattern="yyyyMMdd'T'HHmmss"/>

   <appender name="Fichier" class="ch.qos.logback.core.FileAppender">
      <file> ~/logs/log-${byDay}.log </file>
      <append>true</append>
      <encoder>
          <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
        </encoder>
   </appender>
(....)
    <root level="debug">
        <appender-ref ref="Fichier" />
    </root>
</configuration>

Ajouter des identifiants de trace à vos logs

Si l’APM est activé pour cette application et que vous souhaitez améliorer la corrélation entre les traces et les logs d’application, suivez ces instructions pour définir les MDC (contextes de diagnostic mappés) afin d’ajouter automatiquement par la suite un identifiant de trace et de span à vos logs.

Une fois cette opération terminée, le Pattern à utiliser devient :

<Pattern>"%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %X{dd.trace_id} %X{dd.span_id} - %m%n"</Pattern>

Format JSON

La création de logs au format JSON avec log4j peut s’avérer complexe. C’est pour cette raison que nous vous conseillons d’utiliser un transmetteur slf4j avec un module du nom de log4j-over-slf4j, puis d’utiliser Logback pour le format JSON.

Pour utiliser log4j-over-slf4j dans votre propre application, la première étape consister à rechercher log4j.jar, puis à le remplacer par log4j-over-slf4j.jar. Veuillez noter qu’il vous faudra néanmoins une liaison slf4j ainsi que ses dépendances pour que log4j-over-slf4j fonctionne correctement.

Dans la plupart des situations, il vous suffit de remplacer le fichier jar pour migrer de log4j à SLF4J. Modifiez votre fichier pom.xml :

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>log4j-over-slf4j</artifactId>
  <version>1.7.13</version>
</dependency>

<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>4.5.1</version>
</dependency>

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.1.3</version>
</dependency>

Modifiez ensuite votre fichier logback.xml comme décrit dans la section Slf4j ci-dessous.

Ajouter des identifiants de trace à vos logs

Si l’APM est activé pour cette application et que vous souhaitez améliorer la corrélation entre les traces et les logs d’application, suivez ces instructions pour définir les identifiants de trace et de span avec les MDC (contextes de diagnostic mappés). Ils seront ensuite automatiquement ajoutés aux logs JSON.

Il existe une disposition JSON log4j2 qui peut être utilisée comme dans cet exemple.

Ajouter des identifiants de trace à vos logs

Si l’APM est activé pour cette application et que vous souhaitez améliorer la corrélation entre les traces et les logs d’application, suivez ces instructions pour définir les identifiants de trace et de span avec les MDC (contextes de diagnostic mappés). Ils seront ensuite automatiquement ajoutés aux logs JSON.

Nous vous recommandons d’utiliser la bibliothèque logstash-logback-encoder pour Logback. Son principal avantage réside dans le fait qu’elle est placée dans le référentiel Maven principal.

Pour l’ajouter à votre classpath, ajoutez simplement la dépendance suivante (version 4.5.1 dans l’exemple) à votre fichier pom.xml :

<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>4.5.1</version>
</dependency>

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.1.3</version>
</dependency>

Modifiez ensuite votre fichier logback.xml et mettez à jour l’encodeur :

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>logs/app.log</file>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <customFields>{"env":"prod"}</customFields>
        </encoder>
    </appender>

Ajouter des identifiants de trace à vos logs

Si l’APM est activé pour cette application et que vous souhaitez améliorer la corrélation entre les traces et les logs d’application, suivez ces instructions pour définir les identifiants de trace et de span avec les MDC (contextes de diagnostic mappés). Ils seront ensuite automatiquement ajoutés aux logs JSON.

Configurer l’Agent Datadog

Créez un fichier java.yaml dans le répertoire conf.d/ de l’Agent avec le contenu suivant :

##Section Log
logs:

    ## - type (obligatoire) : type de fichier de la source d'entrée de log (tcp/udp/file).
    ##   port / path (obligatoire) : définit le type tcp ou udp du port. Choisit le chemin si le type est défini sur file.
    ##   service (obligatoire) : nom du service propriétaire du log.
    ##   source (obligatoire) : attribut qui définit l'intégration qui envoie les logs.
    ##   sourcecategory (facultatif) : attribut à valeur multiple. Il peut être utilisé pour préciser l'attribut source.
    ##   tags (facultatif) : ajoute des tags à chaque log recueilli.

  - type: fichier
    path: /chemin/vers/votre/log/java.log
    service: java
    source: java
    sourcecategory: sourcecode
    # Pour les logs multiligne, s'ils commencent par la date au format aaaa-mm-jj, supprimez la mise en commentaire de la règle de traitement suivante.
    #log_processing_rules:
    #  - type: multi_line
    #    name: nouveau_début_log_avec_date
    #    pattern: \d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])

Journalisation sans agent

Il est possible de transmettre des logs depuis votre application vers Datadog ou directement vers l’Agent Datadog. Il ne s’agit pas de la configuration recommandée, car la gestion des problèmes de connexion ne doit pas se faire directement dans votre application, mais il peut arriver qu’il soit impossible d’enregistrer un log dans un fichier lorsque votre application est utilisée sur une machine hors d’accès.

Vous devez suivre deux étapes pour configurer l’application Java afin de diffuser des logs directement à Datadog :

  1. Ajoutez la bibliothèque de journalisation Logback à votre code (ou créez un pont entre votre enregistreur actuel et la bibliothèque).
  2. Configurez-la pour qu’elle envoie les logs à Datadog.

Créer un pont entre les bibliothèques de journalisation Java et Logback

  • Nous vous conseillons d’utiliser la bibliothèque de journalisation Logback logstash-logback-encoder pour diffuser directement vos logs.

Cette bibliothèque de journalisation peut être liée à partir des bibliothèques Java les plus couramment utilisées :

La journalisation vers un serveur à distance au format JSON avec Log4j peut s’avérer complexe. Il est recommandé d’utiliser un transmetteur SLF4J avec un module du nom de log4j-over-slf4j, puis d’utiliser Logback pour le format JSON.

Pour utiliser log4j-over-slf4j dans votre propre application, la première étape consiste à rechercher log4j.jar, puis à le remplacer par log4j-over-slf4j.jar. Dans la plupart des situations, il vous suffit de remplacer le fichier jar pour migrerde Log4j à SLF4J.

Modifiez ensuite le fichier pom.xml avec le contenu suivant :

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.13</version>
</dependency>

<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>4.5.1</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.3</version>
</dependency>

Remarque : suite à cette migration, les fichiers de configuration Log4j ne seront plus recueillis. Migrez votre fichier log4j.properties vers logback.xml avec le convertisseur Log4j.

Log4j2 permet la journalisation sur un host à distance, mais n’offre pas la possibilité d’ajouter une clé d’API en préfixe avant les logs. C’est pour cette raison qu’il est recommandé d’utiliser un transmetteur SLF4J avec un module du nom de log4j-over-slf4j, puis d’utiliser Logback pour le format JSON.

Pour utiliser log4j-over-slf4j dans votre propre application, la première étape consiste à rechercher log4j.jar, puis à le remplacer par log4j-to-slf4j-2.11.jar.

Modifiez ensuite le fichier pom.xml avec le contenu suivant :

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-to-slf4j</artifactId>
    <version>2.11.0</version>
</dependency>

<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>4.5.1</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.3</version>
</dependency>

Remarques :

Pour ajouter Logback logstash-logback-encoder à votre classpath, ajoutez la dépendance suivante (version 4.5.1 dans l’exemple) à votre fichier pom.xml :

<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>4.5.1</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.3</version>
</dependency>

Configuration de Logback

Configurez l’enregistreur Logback de façon à diffuser les logs directement à Datadog en ajoutant le code suivant à votre fichier logback.xml :

<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>

<appender name="JSON_TCP" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
    <remoteHost>intake.logs.datadoghq.com</remoteHost>
  <port>10514</port>
  <keepAliveDuration>1 minute</keepAliveDuration>
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
      <prefix class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="ch.qos.logback.classic.PatternLayout">
          <pattern><CLÉAPI> %mdc{cléNonExistante}</pattern>
        </layout>
      </prefix>    
    </encoder>
</appender>

<root level="debug">
    <appender-ref ref="JSON_TCP" />
    <appender-ref ref="JSON" />
</root>

Remarques :

Découvrez plus de détails sur le paramètre de préfixe dans la documentation Logback (en anglais).

Concepts avancés

Enrichissez vos événements de log avec des attributs contextuels.

Utilisation du parser key/value

Le parser key/value extrait n’importe quelle expression <key>=<value> identifiée dans un événement de log.

Pour enrichir vos événements de log dans Java, vous pouvez réécrire vos messages dans votre code et ajouter des séquences <key>=<value>.

Par exemple, si vous avez :

logger.info("A généré 1 001 messages lors des 93 dernières secondes pour customer scope prod30");

Vous pouvez le remplacer par :

logger.info("A généré quantity=1001 messages lors des dernières durationInMs=93180 ms pour le client scope=prod30");

Lorsque le parser key/value est activé, Datadog extrait automatiquement chaque paire de votre document JSON final :

{
    //...
    "message" : "A généré quantity=1001 messages lors des dernières durationInMs=93180 ms pour le client scope=prod30",
    "scope" : "prod30",
    "durationInMs" : 93180,
    "quantity" : 1001
    //...
}

Vous pouvez donc exploiter scope en tant que champ, et durationInMs et quantity en tant que mesures de log.

MDC (contextes de diagnostics mappés)

Pour enrichir vos logs, vous pouvez également utiliser des MDC (contextes de diagnostics mappés) dans Java.

Si vous utilisez Logback, utilisez le code Java suivant :

...
MDC.put("scope", "prod30");
logger.info("A généré 1 001 messages lors des dernières 93 secondes");
...

Pour générer ce document JSON final :

{
    "message" : "A généré 1 001 messages lors des dernières 93 secondes",
    "scope" : "prod30",
}

**Les MDC sont très utiles, mais pour une raison inconnue, seules les chaînes de caractères sont autorisées. Par conséquent, nous vous déconseillons de fournir des valeurs numériques aux métriques dotées d’un MDC.

Pour aller plus loin