Affichage des journaux Pattern et JSON avec Logback dans Spring Boot

Affichage des journaux Pattern et JSON avec Logback dans Spring Boot

17 juin 2024

image

Lorsque vous gérez un serveur API, il est courant de laisser des journaux pour le suivi des opérations ou le débogage.

Traditionnellement, dans Spring, même si un format existe pour laisser des logs, ils sont souvent laissés sous forme de texte brut. Personnellement, je pense que cela offre une meilleure visibilité que de laisser des logs au format JSON.

Cependant, rechercher et filtrer des journaux est une autre histoire. Dans des plateformes comme AWS CloudWatch qui permettent de rechercher des logs, enregistrer des logs sous forme de texte brut rend difficile le filtrage pour savoir quelle fonction a produit quelles données.

Cela devient particulièrement compliqué lorsqu’il faut rechercher sous plusieurs conditions composites.

Bien sûr, en ajoutant des mots-clés spécifiques aux logs pour les rechercher ensemble, le problème serait résolu, mais enregistrer les logs au format JSON rend la recherche formelle plus facile. (Je ne connais pas les détails de l’implémentation, mais je suppose qu’il pourrait y avoir un avantage si des parties comme l’indexation étaient également configurées.)

Pour atteindre cet objectif, apprenons comment séparer l’enregistrement des logs localement au format texte brut pour plus de visibilité et les enregistrer au format JSON dans l’environnement de déploiement.

Logback

Logback est une implémentation de SLF4J, qui est la bibliothèque de journalisation par défaut utilisée dans Spring Boot.

En général, en ajoutant simplement la dépendance spring-boot-starter-web, Logback est automatiquement ajouté, donc aucune dépendance supplémentaire n’est nécessaire.

Slf4j

Slf4j, acronyme pour Simple Logging Facade for Java, est une interface qui abstrait les bibliothèques de journalisation en Java.

En tant qu’interface, il vous permet d’utiliser diverses bibliothèques de journalisation telles que Logback, Log4j, Log4j2, JUL, etc.

La raison de cette abstraction semble être que si une vulnérabilité est découverte dans une certaine bibliothèque de journalisation et qu’il devient nécessaire de changer de bibliothèque, seul l’implémentation doit être modifiée.

Configuration de Logback

La configuration de Logback par défaut peut être définie en créant un fichier logback-spring.xml.

Cette configuration est principalement composée de Appender, Logger et Encoder.

Appender

L’Appender détermine où les logs seront affichés.

Il existe divers Appender comme ConsoleAppender, FileAppender, RollingFileAppender, SyslogAppender, etc.

Logger

Le Logger détermine pour quelle cible les logs seront laissés.

Le Logger a un nom et laisse des logs uniquement pour le Logger portant ce nom.

Encoder

L’Encoder détermine le format dans lequel les logs seront affichés.

En utilisant PatternLayoutEncoder, vous pouvez laisser des logs sous forme de texte brut, et en utilisant JsonEncoder, vous pouvez laisser des logs au format JSON.

Je vais proposer d’utiliser PatternLayoutEncoder localement et JsonLayout dans l’environnement de déploiement pour enregistrer les logs.

JsonLayout

JsonLayout est un Layout fourni par Logback, permettant d’afficher les logs au format JSON.

Pour utiliser JsonLayout, vous devez ajouter les dépendances logback-json-classic et logback-jackson.

dependencies {
    implementation 'ch.qos.logback.contrib:logback-json-classic:0.1.5'
    implementation 'ch.qos.logback.contrib:logback-jackson:0.1.5'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
}

logback-spring.xml

Ensuite, créez un fichier logback-spring.xml dans le répertoire resources et configurez-le comme suit.

Configuration de l’Appender

D’abord, configurez l’Appender pour afficher les logs au format JSON.

Les dépendances que nous avons obtenues sont utilisées dans la partie layout.

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <appender class="ch.qos.logback.core.ConsoleAppender" name="CONSOLE_JSON">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
                <appendLineSeparator>true</appendLineSeparator>
                <jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter"/>
                <timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</timestampFormat>
                <timestampFormatTimezoneId>Etc/Utc</timestampFormatTimezoneId>
            </layout>
        </encoder>
    </appender>
    <appender class="ch.qos.logback.core.ConsoleAppender" name="CONSOLE_STDOUT">
        <encoder>
            <pattern>[%thread] %highlight([%-5level]) %cyan(%logger{15}) - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- réduit -->

</configuration>

Pour l’instant, seuls les appender ont été configurés mais la partie qui utilise réellement les logs n’est pas encore définie.

Le pattern est défini pour afficher uniquement le Logger et le Message.

CONSOLE_JSON Appender

L’Appender CONSOLE_JSON est configuré pour afficher les logs au format JSON. Les réglages supplémentaires sont les suivants:

  • timestampFormat: définit le format de date. (Je l’ai défini au format RFC3339.)
  • timestampFormatTimezoneId: définit le fuseau horaire.

CONSOLE_STDOUT Appender

L’Appender CONSOLE_STDOUT est configuré pour enregistrer des logs sous forme de texte brut.

Configuration du Logger

Configurez ensuite l’Appender qui affichera les logs en fonction du profil.

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">

    <!-- réduit -->
    
    <springProfile name="qa, dev">
        <root level="INFO">
            <appender-ref ref="CONSOLE_JSON"/>
        </root>
    </springProfile>
    <springProfile name="local">
        <root level="INFO">
            <appender-ref ref="CONSOLE_STDOUT"/>
        </root>
    </springProfile>
</configuration>

Dans cette configuration, springProfile est utilisé pour configurer l’Appender affichant les logs selon le profil.

  • Dans les profils qa, dev, il est configuré pour utiliser l’Appender CONSOLE_JSON.
  • Dans le profil local, il est configuré pour utiliser l’Appender CONSOLE_STDOUT.

Exemple de code

package com.example.demo.product.service;

import com.example.demo.product.domain.dto.ProductDto;
import com.example.demo.product.repository.ProductRepository;
import com.example.demo.product.util.EventNumberPicker;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class ProductService {

    private final ProductRepository productRepository;

    public List<ProductDto> listProducts() {
        log.info("Hello World");
        
        return productRepository
            .findAll()
            .stream()
            .map(product -> new ProductDto(product, EventNumberPicker.pick(1, 1000)))
            .toList();
    }
}

Résultat

En divisant ainsi la journalisation selon l’environnement, vous pouvez conserver la visibilité localement tout en facilitant la recherche dans les environnements de déploiement.

CONSOLE_STDOUT

image

CONSOLE_JSON

image

Bien que je le trouve personnellement désordonné, la recherche sera simplifiée.

Références