22 – Log4j2 et LogForwarder (Nouveauté V4)
Version : 4.0.0 Date : 2025-12-09
1. Introduction
Le Socle V4 remplace Logback par Log4j2 pour le logging, avec un LogForwarder intégré pour la centralisation des logs.
Pourquoi Log4j2 ?
| Critère | Logback (V3) | Log4j2 (V4) |
|---|---|---|
| Async natif | AsyncAppender (wrapper) | AsyncLoggers (LMAX Disruptor) |
| Performance | Bon | 6-68x plus rapide |
| Garbage-free | Non | Oui |
| Custom Appender | Complexe | Plugin system simple |
| JSON natif | Via encoder externe | JsonTemplateLayout intégré |
2. Architecture
┌─────────────────────────────────────────────────────────────┐
│ Application │
│ │
│ Logger.info("message") │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Log4j2 AsyncLoggers │ │
│ │ (LMAX Disruptor) │ │
│ └────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌─────────────────────┐ │
│ │ Console │ │ File │ │ SocleLogForwarder │ │
│ │ Appender │ │ Appender │ │ Appender │ │
│ └───────────┘ └───────────┘ └──────────┬──────────┘ │
│ │ │
└─────────────────────────────────────────┼───────────────────┘
│
┌─────────────────────┴─────────────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ HTTP Transport│ │ NATS Transport│
│ → LogHub │ │ → JetStream │
└──────┬───────┘ └──────┬───────┘
│ │
│ (si échec) │
▼ │
┌──────────────┐ │
│ H2 Fallback │◄─────────────────────────────────┘
│ Storage │
└──────────────┘
3. Configuration
3.1 Dépendances Maven
<!-- Exclure Logback de Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- LMAX Disruptor (AsyncLoggers) -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>4.0.0</version>
</dependency>
<!-- JSON Template Layout -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-layout-template-json</artifactId>
<version>2.22.1</version>
</dependency>
3.2 log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="LOG_DIR">${env:LOG_DIR:-./logs}</Property>
<Property name="APP_NAME">${env:APP_NAME:-socle-v4}</Property>
<Property name="REGION">${env:REGION:-local}</Property>
</Properties>
<Appenders>
<!-- Console (dev) -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{ISO8601} %highlight{%-5level} [%thread] %logger{36} - %msg%n"/>
</Console>
<!-- Fichier rotatif -->
<RollingFile name="File"
fileName="${LOG_DIR}/${APP_NAME}.log"
filePattern="${LOG_DIR}/${APP_NAME}-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{ISO8601} %-5level [%thread] %logger{36} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- LogForwarder (centralisation) -->
<SocleLogForwarder name="LogForwarder"
transportMode="${env:LOG_TRANSPORT_MODE:-http}"
logHubUrl="${env:LOG_HUB_URL:-http://localhost:8080/api/ingest-logs}"
natsUrl="${env:NATS_URL:-nats://localhost:4222}"
batchSize="100"
flushIntervalMs="5000"
queueCapacity="10000"
serviceName="${APP_NAME}"
region="${REGION}">
<ThresholdFilter level="INFO"/>
</SocleLogForwarder>
</Appenders>
<Loggers>
<!-- Socle -->
<Logger name="eu.lmvi.socle" level="${env:LOG_LEVEL:-INFO}" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
<AppenderRef ref="LogForwarder"/>
</Logger>
<!-- Frameworks (moins verbeux) -->
<Logger name="org.springframework" level="WARN"/>
<Logger name="org.apache.kafka" level="WARN"/>
<Logger name="io.nats" level="WARN"/>
<!-- Root -->
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
<AppenderRef ref="LogForwarder"/>
</Root>
</Loggers>
</Configuration>
3.3 log4j2.component.properties
# Activer AsyncLoggers globalement (LMAX Disruptor)
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
# Ring buffer (puissance de 2)
AsyncLogger.RingBufferSize=262144
# Politique d'attente
AsyncLogger.WaitStrategy=Sleep
# Sécurité (Log4Shell)
log4j2.formatMsgNoLookups=true
3.4 application.yml
socle:
logging:
forwarder:
enabled: ${LOG_FORWARDER_ENABLED:false}
transport-mode: ${LOG_TRANSPORT_MODE:http}
log-hub-url: ${LOG_HUB_URL:http://localhost:8080/api/ingest-logs}
nats-url: ${NATS_URL:nats://localhost:4222}
nats-subject-prefix: ${LOG_NATS_SUBJECT:logs}
batch-size: ${LOG_BATCH_SIZE:100}
flush-interval-ms: ${LOG_FLUSH_INTERVAL_MS:5000}
queue-capacity: ${LOG_QUEUE_CAPACITY:10000}
logging:
config: classpath:log4j2.xml
4. Variables d’environnement
| Variable | Description | Défaut |
|---|---|---|
LOG_LEVEL |
Niveau de log | INFO |
LOG_DIR |
Répertoire des logs | ./logs |
LOG_FORWARDER_ENABLED |
Activer LogForwarder | false |
LOG_TRANSPORT_MODE |
Mode transport (http/nats) |
http |
LOG_HUB_URL |
URL du LogHub | – |
NATS_URL |
URL NATS | – |
LOG_BATCH_SIZE |
Taille des batches | 100 |
LOG_FLUSH_INTERVAL_MS |
Intervalle flush (ms) | 5000 |
5. Utilisation
5.1 Logging standard
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MonService {
private static final Logger log = LoggerFactory.getLogger(MonService.class);
public void process() {
log.debug("Processing started");
log.info("Processing item: {}", itemId);
log.warn("Slow processing detected");
log.error("Processing failed", exception);
}
}
5.2 MDC (Mapped Diagnostic Context)
import org.slf4j.MDC;
public class MonService {
public void process(String correlationId) {
MDC.put("correlationId", correlationId);
MDC.put("worker", "order-processor");
try {
log.info("Processing order");
// Les logs incluront correlationId et worker
} finally {
MDC.clear();
}
}
}
5.3 Structured logging
// Les logs JSON incluent automatiquement :
// - timestamp
// - level
// - logger
// - thread
// - message
// - MDC (correlationId, execId, etc.)
// - exception (si présente)
log.info("Order processed: orderId={}, amount={}", orderId, amount);
6. LogForwarder
6.1 Principe
Le LogForwarder :
- Collecte les logs dans une queue interne (non-bloquant)
- Envoie les logs en batch vers le LogHub (HTTP ou NATS)
- Stocke en H2 si le réseau est indisponible
- Rejoue automatiquement à la reconnexion
6.2 Mode HTTP
socle:
logging:
forwarder:
enabled: true
transport-mode: http
log-hub-url: https://logs.mycompany.com/api/ingest-logs
Les logs sont envoyés en POST avec JWT :
POST /api/ingest-logs
Authorization: Bearer <jwt>
Content-Type: application/json
[
{"timestamp": "...", "level": "INFO", "message": "...", ...},
{"timestamp": "...", "level": "ERROR", "message": "...", ...}
]
6.3 Mode NATS
socle:
logging:
forwarder:
enabled: true
transport-mode: nats
nats-url: nats://nats.mycompany.com:4222
nats-subject-prefix: logs
Les logs sont publiés sur logs.<region>.<service> :
logs.mtq.order-service
logs.gua.sync-agent
6.4 Fallback H2
Si le transport échoue, les logs sont stockés dans socle_log_fallback :
SELECT COUNT(*) FROM socle_log_fallback; -- Logs en attente
Ils sont automatiquement rejoués quand le transport redevient disponible.
7. Format JSON des logs
{
"timestamp": "2025-12-09T10:30:00.123Z",
"level": "INFO",
"logger": "eu.lmvi.socle.mop.MainOrchestratorProcess",
"thread": "main",
"message": "MOP démarré avec succès",
"service": "socle-v4",
"region": "MTQ",
"instanceId": "nuc-mtq-001",
"execId": "20251209-1030-abc123",
"correlationId": "MTQ-2025-12-09-000001",
"mdc": {
"worker": "http_worker",
"phase": "startup"
},
"exception": null
}
8. Profils de configuration
8.1 Développement
<!-- log4j2-dev.xml -->
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %highlight{%-5level} [%thread] %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
8.2 Production
<!-- log4j2-prod.xml -->
<Configuration status="ERROR">
<Appenders>
<Console name="ConsoleJson" target="SYSTEM_OUT">
<JsonTemplateLayout eventTemplateUri="classpath:socle-log-template.json"/>
</Console>
<SocleLogForwarder name="LogForwarder" .../>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="ConsoleJson"/>
<AppenderRef ref="LogForwarder"/>
</Root>
</Loggers>
</Configuration>
8.3 Sélection du profil
logging:
config: classpath:log4j2-${spring.profiles.active}.xml
9. Performances
AsyncLoggers vs Sync
| Mode | Throughput | Latence |
|---|---|---|
| Sync | ~1M logs/sec | Variable |
| Async (LMAX) | ~18M logs/sec | Stable |
Bonnes pratiques
// BON - Lazy evaluation
log.debug("Processing: {}", () -> expensiveToString());
// MAUVAIS - Toujours évalué
log.debug("Processing: " + expensiveToString());
10. Troubleshooting
Logs non visibles
- Vérifier
log4j2.xmldanssrc/main/resources/ - Vérifier les niveaux de log
- Vérifier
logging.configdansapplication.yml
AsyncLoggers non activés
Vérifier que log4j2.component.properties existe et contient :
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
LogForwarder queue pleine
WARN - Log queue full, storing to fallback
Solutions :
- Augmenter
queue-capacity - Réduire
batch-size - Vérifier la connectivité réseau
Logs en fallback non rejoués
-- Vérifier les logs en attente
SELECT COUNT(*) FROM socle_log_fallback;
-- Forcer le replay (via API admin)
POST /admin/logforwarder/replay
11. Sécurité
Log4Shell (CVE-2021-44228)
Log4j2 2.22.1 est protégé contre Log4Shell. De plus :
# Désactiver les lookups JNDI
log4j2.formatMsgNoLookups=true
Données sensibles
// NE PAS logger de données sensibles
log.info("User logged in: {}", user.getEmail()); // OK
log.info("Password: {}", password); // INTERDIT

Laisser un commentaire