Chapitre 10
10 - Logging
10 - Logging
Chapitre 10 : Logging
10.1 Introduction au Logging
10.1.1 Pourquoi le logging est essentiel
Le logging est le deuxieme pilier de l'observabilite. Il permet de :
- Deboguer les applications en production
- Auditer les acces et les actions
- Detecter les anomalies et les incidents de securite
- Analyser les tendances et les patterns d'utilisation
- Satisfaire aux exigences de conformite (RGPD, SOC 2)
10.1.2 Logging structure vs non-structure
Log non-structure :
2024-01-15 10:30:45 User alice logged in from 192.168.1.1
Log structure (JSON) :
{
"timestamp": "2024-01-15T10:30:45.000Z",
"level": "INFO",
"logger": "auth.service",
"message": "User logged in",
"user": "alice",
"ip": "192.168.1.1",
"duration_ms": 45,
"correlation_id": "abc-123-def"
}
Log structure (logfmt) :
level=INFO ts=2024-01-15T10:30:45Z logger=auth.service msg="User logged in" user=alice ip=192.168.1.1 duration_ms=45 correlation_id=abc-123-def
10.1.3 Niveaux de log
| Niveau | Valeur | Utilisation |
|---|---|---|
| TRACE | 0 | Debugging tres detaille |
| DEBUG | 1 | Informations de debugging |
| INFO | 2 | Evenements normaux |
| WARN | 3 | Avertissements non critiques |
| ERROR | 4 | Erreurs necessitant action |
| FATAL | 5 | Erreurs fatales, arret du service |
Bonnes pratiques :
- Ne pas logger en DEBUG en production par defaut
- Utiliser INFO pour les evenements metier importants
- WARN pour les situations anormales mais recuperables
- ERROR pour les erreurs necessitant une intervention humaine
10.1.4 Correlation ID
Le correlation ID (ou trace ID) permet de suivre une requete a travers tous les services :
Frontend API Payment
│ │ │
├─ correlation_id: abc ──> │
│ ├─ correlation_id: abc ─>
│ │ │
│ │ │
│ │ │
│ │ │
Implementation en Python :
import uuid
from flask import Flask, request
app = Flask(__name__)
@app.before_request
def ensure_correlation_id():
correlation_id = request.headers.get('X-Correlation-ID')
if not correlation_id:
correlation_id = str(uuid.uuid4())
request.correlation_id = correlation_id
@app.route('/api/users')
def get_users():
app.logger.info("Fetching users", extra={
'correlation_id': request.correlation_id
})
return {"users": ["alice", "bob"]}
10.2 ELK Stack
10.2.1 Architecture ELK
L'ELK Stack se compose de :
- Elasticsearch : Moteur de recherche et d'analyse distribue
- Logstash : Pipeline de collecte et de transformation
- Kibana : Interface de visualisation et d'exploration
Architecture :
Application ──> Filebeat ──> Logstash ──> Elasticsearch ──> Kibana
│
Filter/Transform
10.2.2 Elasticsearch
Elasticsearch est un moteur de recherche distribue base sur Apache Lucene.
Concepts cles :
- Index : Collection de documents similaires (equivalent a une table)
- Document : Unite de donnees stockee au format JSON
- Shard : Partition d'un index (distribue sur plusieurs nœuds)
- Replica : Copie d'un shard (haute disponibilite)
Exemple d'index mapping :
PUT /logs-app
{
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"level": { "type": "keyword" },
"message": { "type": "text" },
"service": { "type": "keyword" },
"correlation_id": { "type": "keyword" },
"duration_ms": { "type": "integer" },
"user": { "type": "keyword" }
}
},
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
10.2.3 Logstash
Logstash est un pipeline de traitement de donnees :
input {
beats {
port => 5044
}
}
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:message}" }
}
date {
match => ["timestamp", "ISO8601"]
target => "@timestamp"
}
mutate {
remove_field => ["timestamp"]
}
if [level] == "ERROR" {
elasticsearch {
hosts => ["localhost:9200"]
index => "logs-app-error-%{+YYYY.MM.dd}"
}
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "logs-app-%{+YYYY.MM.dd}"
}
}
10.2.4 Filebeat
Filebeat est un collecteur de logs leger :
filebeat.inputs:
- type: container
paths:
- /var/log/containers/*.log
output.logstash:
hosts: ["logstash:5044"]
logging.level: info
logging.to_files: true
logging.files:
path: /var/log/filebeat
name: filebeat.log
keepfiles: 7
10.2.5 Kibana
Kibana permet d'explorer et visualiser les donnees Elasticsearch.
Fonctionnalites cles :
- Discover : Exploration des logs en temps reel
- Visualize : Creation de graphiques et tableaux
- Dashboard : Assemblage de visualisations
- Canvas : Presentations personnalisables
- Machine Learning : Detection d'anomalies
Exemple de requete Kibana (KQL) :
level: ERROR AND service: payment AND duration_ms > 1000
10.3 Loki + Promtail
10.3.1 Architecture Loki
Loki est un systeme de logging horizontalement scalable, optimise pour Kubernetes :
Pods ──> Promtail ──> Loki ──> Grafana
│
Labels (pod, namespace, container)
Contrairement a ELK, Loki :
- N'indexe pas le contenu des logs, seulement les labels
- Est beaucoup plus leger en ressources
- S'integre nativement avec Grafana
- Utilise le meme modele de labels que Prometheus
10.3.2 Promtail
Promtail est l'agent de collecte pour Loki :
# promtail-config.yaml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
pipeline_stages:
- cri: {}
- drop:
expression: ".*healthcheck.*"
- multiline:
firstline: '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'
- labels:
namespace:
source_labels: ["__meta_kubernetes_namespace"]
pod:
source_labels: ["__meta_kubernetes_pod_name"]
container:
source_labels: ["__meta_kubernetes_container_name"]
- static_labels:
cluster: production-eu-west-1
10.3.3 Loki Configuration
# loki-config.yaml
auth_enabled: false
server:
http_listen_port: 3100
ingester:
lifecycler:
ring:
kvstore:
store: inmemory
replication_factor: 1
chunk_idle_period: 1h
chunk_retain_period: 30s
max_transfer_retries: 0
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /loki/boltdb-shipper-active
cache_location: /loki/boltdb-shipper-cache
cache_ttl: 24h
shared_store: filesystem
filesystem:
directory: /loki/chunks
limits_config:
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 168h
ingestion_rate_mb: 10
ingestion_burst_size_mb: 20
10.3.4 LogQL
LogQL est le langage de requete de Loki, inspire de PromQL :
# Tous les logs d'un namespace
{namespace="production"}
# Logs avec filtre sur le message
{namespace="production"} |= "ERROR"
# Logs avec expression reguliere
{service="api"} |~ "status=\d{3}"
# Logs avec exclusion
{app="frontend"} != "healthcheck"
# Aggregation temporelle
rate({namespace="production"} |= "ERROR" [5m])
# Top 5 des endpoints les plus lents
topk(5, max_over_time({job="api"} | json | unwrap duration_ms [1h]))
10.4 Fluentd et Fluent Bit
10.4.1 Fluentd
Fluentd est un collecteur de donnees open-source :
# fluentd-config.conf
<source>
@type tail
path /var/log/containers/*.log
pos_file /var/log/fluentd-containers.log.pos
tag kubernetes.*
format json
read_from_head true
</source>
<filter kubernetes.**>
@type kubernetes_metadata
</filter>
<match kubernetes.var.log.containers.**>
@type elasticsearch
host "#{ENV['ES_HOST']}"
port 9200
logstash_format true
logstash_prefix fluentd
flush_interval 5s
</match>
10.4.2 Fluent Bit
Fluent Bit est une version ultra-leger de Fluentd (C vs Ruby) :
# fluent-bit-config.conf
[SERVICE]
flush 1
daemon Off
log_level info
parsers_file parsers.conf
[INPUT]
Name tail
Path /var/log/containers/*.log
Parser docker
Tag kube.*
Mem_Buf_Limit 50MB
Skip_Long_Lines On
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Merge_Log On
Annotations Off
[OUTPUT]
Name es
Match *
Host ${ES_HOST}
Port 9200
Index k8s-logs
Type _doc
Logstash_Format On
Logstash_Prefix k8s
Retry_Limit 6
10.4.3 Comparaison
| Critere | Fluentd | Fluent Bit |
|---|---|---|
| Langage | Ruby + C | C |
| Memoire | ~40MB | ~650KB |
| Debit | ~10K events/s | ~100K events/s |
| Plugins | 1000+ | 100+ |
| Configuration | Ruby DSL | INI-like |
| Cas d'usage | Collecte complexe | Collecte legere |
10.5 Bonnes Pratiques
10.5.1 Rotation des logs
La rotation des logs evite de saturer le disque :
# logrotate.conf
/var/log/application/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 app app
sharedscripts
postrotate
/usr/bin/systemctl reload app > /dev/null 2>&1 || true
endscript
}
10.5.2 Centralisation des logs
Architecture de logging centralise :
┌─> ELK (Analytics)
Application ──> Kafka ─────┼─> Loki (Kubernetes)
└─> S3 (Archive)
# docker-compose logging stack
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
logstash:
image: docker.elastic.co/logstash/logstash:8.11.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
10.5.3 GDPR et conformite
Principes GDPR applicables au logging :
- Minimisation : Ne logger que les donnees necessaires
- Limitation de conservation : Definir des periodes de retention
- Pseudonymisation : Masquer les donnees personnelles
- Droit a l'effacement : P pouvoir supprimer les logs d'un utilisateur
Exemple de filtre GDPR Logstash :
filter {
# Masquer les emails
mutate {
gsub => ["message", "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", "***@***"]
}
# Masquer les IPs (pseudonymisation)
mutate {
gsub => ["message", "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", "XXX.XXX.XXX.XXX"]
}
# Masquer les numeros de carte bancaire
mutate {
gsub => ["message", "\b\d{16}\b", "****-****-****-****"]
}
}
Politique de retention :
# Politique ILM Elasticsearch
PUT _ilm/policy/logs-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_size": "50GB",
"max_age": "30d"
}
}
},
"warm": {
"min_age": "30d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 }
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
10.5.4 Correlation ID en pratique
# middleware_correlation.py
import uuid
import logging
from flask import Flask, request, g
app = Flask(__name__)
class CorrelationFilter(logging.Filter):
def filter(self, record):
record.correlation_id = getattr(g, 'correlation_id', 'N/A')
return True
@app.before_request
def set_correlation_id():
g.correlation_id = request.headers.get(
'X-Correlation-ID',
str(uuid.uuid4())
)
@app.after_request
def add_correlation_header(response):
response.headers['X-Correlation-ID'] = g.correlation_id
return response
# Configuration du logger
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter(
'{"timestamp": "%(asctime)s", "level": "%(levelname)s", '
'"correlation_id": "%(correlation_id)s", '
'"message": "%(message)s"}'
))
handler.addFilter(CorrelationFilter())
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
@app.route('/api/users')
def get_users():
app.logger.info("Fetching users list")
return {"users": ["alice", "bob"]}
Resume
- Le logging structure (JSON/logfmt) permet une analyse automatisee
- L'ELK Stack est la solution historique de centralisation de logs
- Loki offre une alternative cloud-native optimisee pour Kubernetes
- Fluentd/Fluent Bit sont des collecteurs flexibles et performants
- Les correlation IDs sont essentiels pour le debugging distribue
- La rotation et la retention des logs sont cruciales pour la production
- Le GDPR impose des regles strictes sur les donnees dans les logs