HOW-TO · RAG

How to Implement Logging for Agent Debugging

intermediate15 minBy Fredoline Eruo
Target environment
Ubuntu 24.04 · Ollama 0.4.x
PREREQUISITES

Agent system running, logging library (structlog, loguru), Python 3.10+

What this does

Structured logging captures agent decisions, LLM inputs/outputs, tool calls, errors, and timing — providing a searchable record for debugging incorrect agent behavior.

Steps

  • Set up structured logging with structlog.
import structlog

structlog.configure(
    processors=[
        structlog.stdlib.add_log_level,
        structlog.dev.ConsoleRenderer()
    ],
    context_class=dict,
)

log = structlog.get_logger()
  • Log agent lifecycle events. Every step of the agent loop should produce a log entry.
def agent_step(task: str, turn: int) -> str:
    log.info("agent.step.start", task=task, turn=turn)

    log.info("agent.llm.call", prompt_length=len(task))
    response = llm.invoke(task)
    log.info("agent.llm.response", finish_reason=response.finish_reason, token_count=len(response.content))

    if response.tool_calls:
        for tc in response.tool_calls:
            log.info("agent.tool.call",
                tool=tc.function.name,
                args=tc.function.arguments,
                tool_call_id=tc.id
            )
            result = execute_tool(tc.function.name, json.loads(tc.function.arguments))
            log.info("agent.tool.result", tool=tc.function.name, success="error" not in result)

    log.info("agent.step.complete", turn=turn)
    return response.content
  • Add JSON logging for production. Switch to JSON renderer for log aggregation.
structlog.configure(
    processors=[
        structlog.stdlib.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.JSONRenderer()
    ]
)
  • Log with correlation IDs. Trace requests across multiple turns.
import uuid

def run_agent(user_input: str):
    correlation_id = str(uuid.uuid4())
    log = structlog.get_logger().bind(correlation_id=correlation_id)

    log.info("agent.session.start", input=user_input)
    result = agent_loop(user_input)
    log.info("agent.session.end", result=result)
    return result
  • Configure exception logging. Capture full tracebacks for errors.
import traceback

try:
    result = execute_tool(tool_name, args)
except Exception as e:
    log.error("agent.tool.error",
        tool=tool_name,
        error=str(e),
        traceback=traceback.format_exc()
    )
  • Set log levels per module. Reduce noise from third-party libraries.
import logging

logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("openai").setLevel(logging.WARNING)
# Agent logs stay at DEBUG

Verification

python -c "
import structlog
log = structlog.get_logger()
log.info('test.message', key='value')
print('Logging works')
# Expected: 2026-05-29 ... test.message key=value  OR  Logging works (JSON output)
"

Common failures

  • Sensitive data in logs. LLM inputs may contain PII. Add a processor that redacts emails, API keys, and personal names before logging.
  • Log noise. Logging every token or every retry attempt produces gigabytes per hour. Use DEBUG level for verbose events and INFO for decisions.
  • Async context loss. Coroutine logs may lose the correlation_id binding. Use structlog.threadlocal.bind_threadlocal() or pass the logger explicitly.
  • Version mismatch - The installed package or runtime differs from the command shown; check the version first and rerun the smallest verification command.
  • Local environment drift - Another service, virtual environment, model, or path is being used; print the active binary path and configuration before changing the guide steps.

Related guides

  • How to Set Up Agent Observability and Tracing
  • How to Debug Agent Reasoning and Tool Selection