AI Agent Logging Best Practices: Complete Guide
How to implement logging that actually helps debugging and compliance.
Good logging is the difference between spending 5 minutes or 5 hours debugging a production issue. For AI agents, logging is even more critical β and more complex. This guide covers everything you need.
Why AI Agent Logging Is Different
Traditional logging: "Request received, processing, response sent."
AI agent logging needs: "Why did the agent choose that tool? What context was provided? What was the model's reasoning? How much did it cost? Was the output correct?"
What to Log
The Complete Logging Schema
interface AgentLog {
// Identification
conversationId: string;
messageId: string;
userId: string;
sessionId: string;
// Request
timestamp: string;
input: {
userMessage: string;
contextProvided: any;
toolsAvailable: string[];
};
// Model
model: {
name: string;
version: string;
temperature: number;
maxTokens: number;
};
// Processing
processing: {
durationMs: number;
tokensUsed: {
input: number;
output: number;
total: number;
};
cost: {
input: number;
output: number;
total: number;
currency: string;
};
toolCalls: Array<{
tool: string;
input: any;
output: any;
durationMs: number;
success: boolean;
}>;
reasoningTrace?: string;
};
// Output
output: {
response: string;
finishReason: string; // stop, length, tool_call
safetyFlags: string[];
};
// Quality
quality: {
userFeedback?: "positive" | "negative";
autoScore?: number;
flagged?: boolean;
};
// Metadata
metadata: {
source: "api" | "web" | "integration";
version: string;
environment: "production" | "staging";
};
}
Implementation
Structured Logger Setup
import structlog
import json
# Configure structlog
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.processors.JSONRenderer(),
],
wrapper_class=structlog.stdlib.BoundLogger,
logger_factory=structlog.stdlib.LoggerFactory(),
)
logger = structlog.get_logger("ai_agent")
Logging the Full Request Lifecycle
class AgentLogger:
def __init__(self):
self.logger = structlog.get_logger("agent")
async def log_request(self, conversation_id, user_message, agent_response):
"""Log a complete request-response cycle."""
self.logger.info(
"agent_request_completed",
conversation_id=conversation_id,
# Input
input_message=user_message[:500], # Truncate for log size
input_length=len(user_message),
# Output
output_length=len(agent_response.text),
output_preview=agent_response.text[:200],
# Performance
duration_ms=agent_response.duration_ms,
# Cost
tokens_input=agent_response.usage.input_tokens,
tokens_output=agent_response.usage.output_tokens,
cost_eur=agent_response.usage.cost,
# Tools
tools_called=[t.name for t in agent_response.tool_calls],
tool_results=[{
"tool": t.name,
"success": t.success,
"duration_ms": t.duration,
} for t in agent_response.tool_calls],
# Model
model=agent_response.model,
# Quality
safety_flags=agent_response.safety_flags,
)
async def log_error(self, conversation_id, error, context):
"""Log errors with full context for debugging."""
self.logger.error(
"agent_request_failed",
conversation_id=conversation_id,
error_type=type(error).__name__,
error_message=str(error),
# Context for debugging
user_message=context.get("user_message", "")[:200],
model=context.get("model"),
tools_available=context.get("tools", []),
# Stack trace
exc_info=True,
)
Log Levels for AI Agents
| Level | When to Use | Example |
|---|---|---|
| DEBUG | Detailed tracing for development | "Tool options considered: [search, calculate, translate]" |
| INFO | Normal operations | "Request processed in 2.3s, cost β¬0.003" |
| WARNING | Potential issues | "Token usage high: 4500/8000" |
| ERROR | Failures requiring attention | "LLM API timeout after 30s" |
| CRITICAL | System-level failures | "Budget exhausted, disabling agent" |
Log Storage Strategy
Hot Tier (7-30 days)
- Full logs with all details
- Stored in Elasticsearch/Loki
- Used for real-time debugging
Warm Tier (30-90 days)
- Summarized logs (key fields only)
- Stored in PostgreSQL
- Used for trend analysis
Cold Tier (90+ days)
- Aggregated metrics only
- Stored in S3/Archive
- Used for compliance audits
class LogRetention:
SCHEDULE = {
"full_logs": {"retention": "30d", "storage": "elasticsearch"},
"summarized": {"retention": "90d", "storage": "postgresql"},
"aggregated": {"retention": "2y", "storage": "s3"},
}
async def archive_old_logs(self):
"""Move logs through retention tiers."""
# Move full logs to summarized after 30 days
old_logs = await self.es.query(
index="agent_logs",
filter={"range": {"timestamp": {"lt": "now-30d"}}},
)
for log in old_logs:
summary = self.summarize(log)
await self.postgres.insert("log_summaries", summary)
await self.es.delete(
index="agent_logs",
filter={"range": {"timestamp": {"lt": "now-30d"}}},
)
Querying Logs for Debugging
Find Issues Quickly
class LogQuery:
# Find slow requests
async def find_slow_requests(self, threshold_ms=10000):
return await self.logs.query("""
SELECT conversation_id, duration_ms, model, tokens_output
FROM agent_logs
WHERE duration_ms > %s
ORDER BY duration_ms DESC
LIMIT 50
""", threshold_ms)
# Find expensive conversations
async def find_expensive_conversations(self, min_cost=1.0):
return await self.logs.query("""
SELECT conversation_id,
SUM(cost_eur) as total_cost,
COUNT(*) as messages
FROM agent_logs
WHERE timestamp > NOW() - INTERVAL '24 hours'
GROUP BY conversation_id
HAVING SUM(cost_eur) > %s
ORDER BY total_cost DESC
""", min_cost)
# Find failed tool calls
async def find_tool_failures(self):
return await self.logs.query("""
SELECT * FROM agent_logs
WHERE tool_results @> '[{"success": false}]'
AND timestamp > NOW() - INTERVAL '24 hours'
""")
# Find quality issues
async def find_negative_feedback(self):
return await self.logs.query("""
SELECT * FROM agent_logs
WHERE user_feedback = 'negative'
ORDER BY timestamp DESC
LIMIT 20
""")
Compliance Logging
GDPR Audit Trail
class GDPRLogger:
async def log_data_processing(self, event):
"""Log for GDPR Article 22 (automated decision-making)."""
await self.db.insert("gdpr_audit", {
"timestamp": datetime.utcnow(),
"user_id": event.user_id,
"processing_type": "automated_decision",
"decision": event.decision,
"logic_involved": event.reasoning,
"data_used": event.data_sources,
"model": event.model,
"human_review_available": True,
"retention_until": datetime.utcnow() + timedelta(days=365),
})
Common Logging Mistakes
- Logging too much β Don't log every token; log meaningful events
- Not logging enough β Missing context makes debugging impossible
- No correlation ID β Can't trace a request across services
- Logging PII β Never log passwords, API keys, or personal data
- No structure β Unstructured logs are unsearchable
- Synchronous logging β Blocks the event loop; use async
- No retention policy β Logs grow forever, costing money
Conclusion
Effective logging is the foundation of production AI agent operations. By implementing structured logging with the right fields, retention policies, and query capabilities, you can debug issues in minutes instead of hours.
Learn More
- AI Agent Observability
- AI Agent Monitoring Tools
- AI Agent Debugging Tools
- AI Agent Security Best Practices
Explore logging tools on SkillExchange.