Pourquoi l’APM transforme le debugging en production
Les métriques te disent que quelque chose ne va pas — un pic de latence, un taux d’erreur qui grimpe. Mais elles ne te disent pas où ni pourquoi. Quand ton API met 3 secondes à répondre, c’est la trace APM qui révèle que 2.8 secondes sont passées dans une requête SQL non optimisée, sur un endpoint spécifique, pour un type de client précis.
L’APM (Application Performance Monitoring) de Datadog décompose chaque requête en traces (le parcours complet à travers tes services) et spans (chaque opération individuelle : appel HTTP, requête SQL, lecture Redis). Un trace ID unique est propagé de service en service via les headers HTTP, ce qui permet de reconstituer le chemin complet d’une requête dans une architecture microservices.
En combinant l’APM avec le Log Management, tu obtiens une corrélation bidirectionnelle : depuis une trace lente, tu accèdes directement aux logs émis pendant cette opération. Depuis un log d’erreur, tu remontes à la trace complète. C’est cette corrélation qui fait la puissance de la plateforme.
🧠 À retenir — Le trio métriques + traces + logs forme les “trois piliers de l’observabilité”. Les métriques pour détecter, les traces pour diagnostiquer, les logs pour comprendre le détail.
Instrumentation APM : du code à la trace
L’instrumentation Datadog fonctionne à deux niveaux. L’auto-instrumentation intercepte automatiquement les bibliothèques populaires (Flask, Django, Express, Gin, SQLAlchemy, Redis…) sans modification de code. L’instrumentation manuelle ajoute des spans custom pour tracer ta logique métier spécifique.
En Python, le setup est minimal. ddtrace-run wrappe ton application et instrumente automatiquement tout ce qui est supporté. Les variables d’environnement DD_SERVICE, DD_ENV et DD_VERSION sont les trois tags obligatoires du Unified Service Tagging :
# Auto-instrumentation Python — zéro code à modifier
pip install ddtrace
DD_SERVICE=payment-api \
DD_ENV=production \
DD_VERSION=1.2.3 \
DD_LOGS_INJECTION=true \
DD_PROFILING_ENABLED=true \
ddtrace-run gunicorn -w 4 app:app
Pour tracer de la logique métier qui n’est pas couverte par l’auto-instrumentation (un workflow de paiement, un calcul complexe), tu ajoutes des spans manuelles avec des tags métier qui apparaîtront dans les traces :
from ddtrace import tracer
@tracer.wrap(service="payment-api", resource="process_payment")
def process_payment(order_id, amount):
validate_order(order_id)
charge_card(amount)
send_confirmation(order_id)
# Pour plus de contrôle : context manager avec tags
def handle_checkout(request):
with tracer.trace("checkout.process", service="payment-api") as span:
span.set_tag("order.id", request.order_id)
span.set_tag("order.amount", request.total)
span.set_tag("payment.method", "stripe")
try:
result = execute_checkout(request)
span.set_tag("checkout.status", result.status)
return result
except Exception as e:
span.set_tag("error", True)
span.set_tag("error.message", str(e))
raise
En Go et Node.js, le principe est identique. L’important est de toujours propager le contexte de trace entre les appels pour maintenir la chaîne :
// Go — instrumentation avec propagation de contexte
import (
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
)
func main() {
tracer.Start(
tracer.WithService("user-api"),
tracer.WithEnv("production"),
tracer.WithServiceVersion("1.2.3"),
)
defer tracer.Stop()
mux := httptrace.NewServeMux()
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
span, ctx := tracer.StartSpanFromContext(r.Context(), "users.fetch")
defer span.Finish()
users, err := fetchUsers(ctx) // ctx propage le trace ID
if err != nil {
span.SetTag("error", true)
return
}
span.SetTag("users.count", len(users))
})
}
💡 Tip DevOps — Active DD_PROFILING_ENABLED=true en production. Le profiling continu a un overhead négligeable (~2% CPU) et te donne la flamegraph au niveau du code source — indispensable pour identifier les fonctions qui consomment le plus de CPU ou de mémoire.
Configuration APM dans Docker et Kubernetes
En environnement conteneurisé, l’agent Datadog tourne dans son propre container. Les applications communiquent avec lui via le port 8126 pour les traces et 8125 pour les métriques DogStatsD. La configuration Docker Compose type :
# docker-compose.yml
services:
datadog-agent:
image: gcr.io/datadoghq/agent:7
environment:
- DD_API_KEY=${DD_API_KEY}
- DD_SITE=datadoghq.eu
- DD_APM_ENABLED=true
- DD_APM_NON_LOCAL_TRAFFIC=true # Accepte les traces des autres containers
- DD_LOGS_ENABLED=true
- DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL=true
ports:
- "8126:8126" # APM traces
- "8125:8125" # DogStatsD métriques
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /proc/:/host/proc/:ro
api:
build: ./api
environment:
- DD_AGENT_HOST=datadog-agent
- DD_SERVICE=payment-api
- DD_ENV=production
- DD_VERSION=1.2.3
labels:
com.datadoghq.ad.logs: '[{"source":"python","service":"payment-api"}]'
depends_on:
- datadog-agent
⚠️ Attention — DD_APM_NON_LOCAL_TRAFFIC=true est obligatoire en Docker/Kubernetes. Sans ça, l’agent refuse les traces venant d’autres containers. C’est le piège numéro un lors du setup.
Log Management : collecte, parsing et indexation
Le Log Management de Datadog centralise tous tes logs et les rend cherchables. La collecte se fait soit depuis des fichiers, soit directement depuis stdout/stderr des containers Docker.
Pour activer la collecte, il suffit d’ajouter logs_enabled: true dans la config de l’agent. En Docker, les logs sont collectés automatiquement via le socket Docker. Pour des fichiers spécifiques :
# /etc/datadog-agent/conf.d/myapp.d/conf.yaml
logs:
- type: file
path: /var/log/myapp/*.log
service: payment-api
source: python
tags:
- env:production
- team:backend
Les logs structurés en JSON sont automatiquement parsés par Datadog — chaque champ devient un attribut cherchable. C’est toujours préférable aux logs texte bruts :
import logging
import json
class JSONFormatter(logging.Formatter):
def format(self, record):
log_data = {
"message": record.getMessage(),
"level": record.levelname,
"service": "payment-api",
"timestamp": self.formatTime(record),
# Tags métier cherchables dans Datadog
**{k: v for k, v in record.__dict__.items()
if k not in logging.LogRecord.__dict__}
}
return json.dumps(log_data)
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger = logging.getLogger("myapp")
logger.addHandler(handler)
# Usage
logger.info("Payment processed", extra={
"order_id": "ORD-123",
"amount": 49.99,
"method": "stripe"
})
Pour maîtriser les coûts de log (facturés au volume indexé), utilise les exclusion filters pour ne pas indexer les logs inutiles (health checks, debug verbose) et les index multiples avec des rétentions différentes :
# Exclusion filter — health checks non indexés
source:nginx status:200 @http.url:/health
# Index "errors" → rétention 30 jours (status:error seulement)
# Index "all" → rétention 7 jours (tout le reste)
# Archives → S3 pour stockage long terme à faible coût
🔥 Cas réel — Une startup SaaS indexait tous les logs en rétention 15 jours. Facture : 3 000€/mois. En excluant les health checks, les logs de debug, et en séparant les erreurs (30j) du reste (3j), la facture est tombée à 800€/mois sans perte de visibilité opérationnelle.
Corrélation traces-logs : la killer feature
La corrélation bidirectionnelle entre traces APM et logs est ce qui distingue Datadog d’une simple stack ELK. Depuis une trace lente, tu cliques sur “Logs” et tu vois exactement ce qui s’est passé. Depuis un log d’erreur, tu cliques “View Trace” et tu remontes toute la chaîne.
Pour que ça fonctionne, le trace ID doit être injecté dans chaque ligne de log. Avec DD_LOGS_INJECTION=true, c’est automatique pour les bibliothèques supportées. Les champs dd.trace_id, dd.span_id, dd.service, dd.env et dd.version apparaissent dans chaque entrée :
import logging
from ddtrace import patch_all
patch_all()
# Format incluant les champs de corrélation
FORMAT = (
'%(asctime)s %(levelname)s [%(name)s] '
'[dd.service=%(dd.service)s dd.trace_id=%(dd.trace_id)s '
'dd.span_id=%(dd.span_id)s] %(message)s'
)
logging.basicConfig(format=FORMAT, level=logging.INFO)
logger = logging.getLogger(__name__)
# Chaque log émis pendant une trace contiendra automatiquement
# le trace_id — Datadog les corrèle dans l'interface
@app.route('/api/orders/<order_id>')
def get_order(order_id):
logger.info(f"Fetching order {order_id}") # trace_id injecté
order = db.get_order(order_id)
if not order:
logger.warning(f"Order {order_id} not found") # corrélé aussi
abort(404)
return jsonify(order)
La clé de voûte de cette corrélation est le Unified Service Tagging : les trois variables DD_SERVICE, DD_ENV et DD_VERSION doivent être identiques partout — dans l’APM, les logs et les métriques custom. C’est ce qui permet à Datadog de connecter les trois piliers dans une vue unifiée par service.
💡 Tip DevOps — Configure un service.datadog.yaml à la racine de chaque repo pour alimenter le Service Catalog. Il associe à chaque service son équipe, son channel Slack d’alertes, son runbook et son repo — indispensable quand l’on-call reçoit un incident à 3h du matin.
# service.datadog.yaml — metadata du service
schema-version: v2.1
dd-service: payment-api
team: backend
contacts:
- type: slack
contact: "#backend-alerts"
links:
- name: Runbook
type: runbook
url: https://wiki.internal/runbooks/payment-api
- name: Source
type: repo
url: https://github.com/org/payment-api
Bonnes pratiques et pièges à éviter
Instrumentation :
- Commence par l’auto-instrumentation, ajoute des spans manuelles seulement pour la logique métier critique
- Tague tes spans avec des infos métier (order_id, customer_tier, payment_method) — c’est ce qui rend le debugging efficace
- Active le profiling en production — l’overhead est minimal et la valeur est immense
Logs :
- Toujours des logs structurés (JSON) plutôt que du texte libre
- Exclus les logs à faible valeur de l’indexation (health checks, debug verbose)
- Utilise des archives S3/GCS pour le stockage long terme
Pièges classiques :
- Oublier
DD_APM_NON_LOCAL_TRAFFIC=trueen Docker — les traces n’arrivent jamais à l’agent - Tags
DD_SERVICEincohérents entre APM et logs — la corrélation ne fonctionne pas - Indexer tous les logs sans exclusion ni archives — la facture explose
- Ne pas propager le contexte de trace dans les appels asynchrones (workers, queues) — les traces sont coupées
⚠️ Attention — En architecture event-driven (Kafka, RabbitMQ), le trace ID ne se propage pas automatiquement. Il faut l’injecter manuellement dans les headers du message et l’extraire côté consumer pour maintenir la chaîne de traces.
🧠 À retenir — L’APM avec le tracing distribué te donne le “pourquoi” derrière les métriques. Le Log Management centralise et structure tes logs. La corrélation traces-logs via le Unified Service Tagging (DD_SERVICE, DD_ENV, DD_VERSION) est ce qui transforme trois outils séparés en une plateforme d’observabilité cohérente. Instrumente d’abord, optimise ensuite.
Contenu réservé aux abonnés
Ce chapitre fait partie de la formation complète. Abonne-toi pour débloquer tous les contenus.
Débloquer pour 29 CHF/moisLe chapitre 1 de chaque formation est gratuit.
Série pas encore débloquée
Termine la série prérequise d'abord pour accéder à ce contenu.
Aller à la série prérequiseSérie : Datadog
3 / 4Sur cette page
Articles liés
Datadog - Introduction et premiers pas
Découvre pourquoi Datadog domine le monitoring, comprends l'architecture de l'agent, installe-le sur Ubuntu et Docker, et crée ton premier dashboard.
Datadog - Métriques custom et dashboards avancés
Apprends à envoyer des métriques custom via DogStatsD, maîtrise les widgets de dashboards, et crée des templates réutilisables.
Datadog - Alerting, SLOs, Synthetics et PagerDuty
Configure des monitors intelligents, définis des SLOs, mets en place des tests Synthetics, et intègre PagerDuty pour l'escalade d'incidents.