Idée en 30 secondes
Le logging d'agents répond à une question simple : qu'est-ce qui s'est exactement passé pendant le run.
Pour cela, il faut des événements structurés avec corrélation via run_id et trace_id.
Sans cela, en incident on voit souvent seulement la réponse finale, pas le chemin qui y mÚne.
ProblĂšme principal
Dans un backend classique, quelques logs par requĂȘte suffisent souvent.
Dans un systĂšme agentique, une requĂȘte peut inclure reasoning, tool calls, retries et plusieurs Ă©tapes modĂšle. Si on logge seulement le final, il devient difficile de voir oĂč le systĂšme a cassĂ©.
En production, cela ressemble souvent a :
- un utilisateur se plaint d'une mauvaise réponse ;
- les coûts ou la latency montent par vagues ;
- un log contient une erreur isolée sans contexte run.
C'est pourquoi les agents ont besoin de logs structurés sur tout le cycle de vie du run, pas de logs aléatoires.
Comment ça fonctionne
L'idée de base est simple : chaque étape importante est enregistrée comme événement structuré distinct.
Minimum par événement :
run_idettrace_idpour la corrélation ;event(ce qui s'est passé) ;timestamp;status(ok/error) quand c'est pertinent ;- champs clés de l'étape (tool, latency, stop_reason, etc.).
Quels événements logger en premier
| Evenement | Ce qu'il faut enregistrer |
|---|---|
| run_started | run_id, trace_id, request_id, user_id |
| agent_step | step_type, step_index, tool |
| tool_call | tool_name, args_hash |
| tool_result | tool_name, latency_ms, status, error_class |
| llm_result | model, token usage, latency_ms, status |
| run_finished | stop_reason, total_steps, total_latency_ms |
En production, les raw prompts et raw tool args ne sont generalement pas ecrits sans redaction. Le plus souvent, on stocke un hash ou une version anonymisee.
Quand l'utiliser
Le logging detaille n'est pas toujours necessaire.
Pour un scenario single-shot simple, des logs minimaux request -> response peuvent suffire.
Mais des qu'il y a tools, retries, plusieurs etapes ou cout eleve, sans logging structure il devient difficile de :
- debugger les incidents ;
- expliquer les couts ;
- regler les alertes de facon stable.
Exemple d'implementation
Ci-dessous, un exemple simplifie de structured logging dans runtime et tool gateway.
Dans cet exemple, les raw args ne sont pas logs : on enregistre args_hash.
agent_step trace l'etape elle-meme, alors que tool_call et tool_result detaillent separement le debut et le resultat d'appel d'outil.
import hashlib
import json
import logging
import time
import uuid
logger = logging.getLogger("agent")
def stable_hash(value):
payload = json.dumps(
value,
sort_keys=True,
ensure_ascii=False,
default=str, # pour datetime et types complexes; en systemes critiques preferer un format stable (ex. ISO 8601)
).encode("utf-8")
return hashlib.sha256(payload).hexdigest()
def log_event(event, **fields):
logger.info(event, extra={"event": event, **fields})
def run_agent(agent, task, user_id=None, request_id=None):
run_id = str(uuid.uuid4())
trace_id = str(uuid.uuid4())
started_at = time.time()
steps = 0
stop_reason = "max_steps"
run_status = "ok"
log_event(
"run_started",
run_id=run_id,
trace_id=trace_id,
user_id=user_id,
request_id=request_id,
task_hash=stable_hash(task),
)
try:
for step in agent.iter(task): # step: reasoning ou tool execution
steps += 1
step_started_at = time.time()
step_type = step.type
tool_name = getattr(step, "tool_name", None)
log_event(
"agent_step",
run_id=run_id,
trace_id=trace_id,
step_index=steps,
step_type=step_type,
tool=tool_name,
)
if step_type == "tool_call":
args = getattr(step, "args", {})
log_event(
"tool_call",
run_id=run_id,
trace_id=trace_id,
tool=tool_name,
args_hash=stable_hash(args),
)
try:
result = step.execute()
latency_ms = int((time.time() - step_started_at) * 1000)
if step_type == "tool_call":
log_event(
"tool_result",
run_id=run_id,
trace_id=trace_id,
tool=tool_name,
latency_ms=latency_ms,
status="ok",
)
else:
token_usage = getattr(result, "token_usage", None)
log_event(
"llm_result",
run_id=run_id,
trace_id=trace_id,
step_type=step_type,
model=getattr(step, "model", None),
token_usage=token_usage,
latency_ms=latency_ms,
status="ok",
)
except Exception as error:
latency_ms = int((time.time() - step_started_at) * 1000)
result_event = "tool_result" if step_type == "tool_call" else "llm_result"
log_event(
result_event,
run_id=run_id,
trace_id=trace_id,
step_type=step_type,
tool=tool_name,
model=getattr(step, "model", None),
latency_ms=latency_ms,
status="error",
error_class=type(error).__name__,
error_message=str(error),
)
run_status = "error"
stop_reason = "tool_error" if step_type == "tool_call" else "step_error"
raise
if result.is_final:
stop_reason = "completed"
break
finally:
log_event(
"run_finished",
run_id=run_id,
trace_id=trace_id,
status=run_status,
stop_reason=stop_reason,
total_steps=steps,
total_latency_ms=int((time.time() - started_at) * 1000),
)
En production, ces evenements sont generalement envoyes vers un systeme de logs centralise (par exemple ELK, Datadog ou ClickHouse), puis utilises pour dashboards et alertes.
Cet exemple suffit pour :
- trouver un tool call problematique ;
- calculer la latency par etape ;
- comprendre pourquoi le run s'est arrete.
Par exemple, une entree JSON peut ressembler a :
{
"timestamp": "2026-03-21T15:17:00Z",
"event": "tool_result",
"run_id": "run_9fd2",
"trace_id": "tr_9fd2",
"tool": "search_docs",
"latency_ms": 410,
"status": "ok"
}
Erreurs typiques
Meme quand le logging est deja en place, les incidents restent souvent difficiles a analyser a cause des erreurs ci-dessous.
Seule la reponse finale est loggee
Sans evenements intermediaires, on ne voit pas comment l'agent est arrive au resultat. Dans ce mode, meme un incident simple prend trop de temps.
Pas d'identifiants stables (run_id, trace_id)
Quand les evenements ne sont pas correles, impossible de reconstruire un run complet. En production, cela transforme souvent le debugging en recherche manuelle entre services.
Raw prompts ou raw args logs sans redaction
C'est un risque direct de fuite de donnees personnelles ou sensibles. Il est plus sur de stocker un hash, des champs rediges ou une version anonymisee.
tool_result et stop_reason non logges
Si tool_result et stop_reason manquent, il est difficile de comprendre ce qui a casse.
Ces trous masquent souvent echec d'outil ou une phase precoce de spam d'outils.
Auto-verification
Ci-dessous, une checklist courte de base pour le logging d'agents avant release.
Progression: 0/9
â L'observability de base manque
Le systÚme sera difficile à déboguer en production. Commencez par run_id, structured logs et tracing des tool calls.
FAQ
Q : Quelle difference entre logging et tracing ?
R : Le logging repond a "que s'est-il passe" et enregistre les evenements. Le tracing montre "comment exactement" via la sequence d'etapes et leurs liens.
Q : Que logger en premier si le logging est quasi absent ?
R : Commencez par la base : run_id, trace_id, run_started, tool_call, tool_result, run_finished, stop_reason. C'est deja suffisant pour un debugging de base.
Q : Peut-on logger les prompts complets ?
R : Par defaut, mieux vaut non. En production, les prompts contiennent souvent des donnees sensibles. Plus sur : hash ou version redigee.
Q : Comment savoir si le logging est suffisant ?
R : Si vous pouvez reconstruire la sequence d'un run problematique et trouver le point de panne en 5-10 minutes, votre niveau de base fonctionne.
Pages liees
Suite du sujet :
- Observability pour agents IA â modele global de traces, logs et metrics.
- Tracing d'agent â comment voir le chemin d'un run pas a pas.
- Tracing distribue d'agents â comment connecter les evenements entre services.
- Debugging des runs d'agent â analyse pratique d'incidents.
- Metriques d'agents â indicateurs necessaires pour une exploitation stable.