Plugin Logging Guide
This guide explains how to use AgentUp's integrated logging system in your plugins. AgentUp provides structured logging with automatic plugin context annotation, making it easy to debug and monitor plugin behavior in production.
Overview
AgentUp uses structlog for structured logging, which provides:
- Automatic plugin identification: All plugin logs are automatically tagged with plugin metadata
- Structured data: Log entries can include key-value pairs for better searchability
- Multiple output formats: Text for development, JSON for production
- Integration with server logs: Plugin logs appear seamlessly in AgentUp server logs
Quick Start
Every plugin automatically gets a pre-configured logger accessible via self.logger:
from agent.plugins.base import Plugin
from agent.plugins.decorators import capability
from agent.plugins.models import CapabilityContext, CapabilityResult
class WeatherPlugin(Plugin):
def __init__(self):
super().__init__()
self.name = "Weather Plugin"
self.version = "1.0.0"
@capability(id="get_weather", name="Get Weather")
async def get_weather(self, context: CapabilityContext) -> CapabilityResult:
# Plugin logs automatically include plugin context
self.logger.info("Weather request started", location="New York")
try:
# Your plugin logic here
result = await self._fetch_weather("New York")
self.logger.info("Weather request completed",
location="New York",
temperature=result.temperature,
condition=result.condition)
return CapabilityResult(
content=f"Weather in New York: {result.temperature}°F, {result.condition}",
success=True
)
except Exception as e:
self.logger.error("Weather request failed",
location="New York",
error=str(e),
exc_info=True)
return CapabilityResult(
content="Failed to fetch weather data",
success=False,
error=str(e)
)
Log Levels
AgentUp supports standard Python logging levels:
# Debug: Detailed information for diagnosing problems
self.logger.debug("Processing user input", input_length=len(user_input))
# Info: General information about plugin operation
self.logger.info("API call successful", endpoint="/weather", status_code=200)
# Warning: Something unexpected happened but plugin continues
self.logger.warning("Rate limit approaching", requests_remaining=5)
# Error: Plugin encountered an error
self.logger.error("API request failed", endpoint="/weather", error="timeout")
# Critical: Serious error that may cause plugin to stop working
self.logger.critical("Configuration invalid", config_file="weather.yml")
Structured Logging
The power of structlog comes from structured data. Instead of string formatting, use key-value pairs:
# ❌ Don't do this - hard to search and parse
self.logger.info(f"Processing request for user {user_id} with {len(items)} items")
# ✅ Do this - structured and searchable
self.logger.info("Processing request",
user_id=user_id,
item_count=len(items),
processing_mode="batch")
Automatic Plugin Context
Every log entry from your plugin automatically includes:
plugin_id: Your plugin's unique identifierplugin_name: Human-readable plugin nameplugin_version: Plugin version (if specified)logger: Logger name (e.g.,agent.plugins.weather)
Example log output:
{
"timestamp": "2025-08-01T10:30:45.123Z",
"level": "INFO",
"logger": "agent.plugins.weather",
"event": "Weather request completed",
"plugin_id": "weather",
"plugin_name": "Weather Plugin",
"plugin_version": "1.0.0",
"location": "New York",
"temperature": 72,
"condition": "sunny"
}
Error Logging
When logging errors, include the exc_info=True parameter to capture full stack traces:
try:
result = await self._risky_operation()
except ValueError as e:
self.logger.error("Invalid input provided",
input_value=user_input,
error=str(e),
exc_info=True) # Captures full stack trace
except Exception as e:
self.logger.critical("Unexpected error in plugin",
error=str(e),
error_type=type(e).__name__,
exc_info=True)
Performance Logging
Log performance metrics to help with optimization:
import time
async def expensive_operation(self, data):
start_time = time.time()
self.logger.debug("Starting expensive operation",
data_size=len(data),
operation="data_processing")
try:
result = await self._process_data(data)
duration = time.time() - start_time
self.logger.info("Operation completed",
operation="data_processing",
duration_seconds=round(duration, 3),
data_size=len(data),
result_size=len(result))
return result
except Exception as e:
duration = time.time() - start_time
self.logger.error("Operation failed",
operation="data_processing",
duration_seconds=round(duration, 3),
error=str(e),
exc_info=True)
raise
Configuration and External Service Logging
Log configuration changes and external API calls:
def configure(self, config: dict[str, Any]) -> None:
"""Configure the plugin with settings."""
super().configure(config)
self.logger.info("Plugin configured",
api_endpoint=config.get("api_endpoint"),
timeout=config.get("timeout", 30),
retry_count=config.get("retries", 3))
async def _call_external_api(self, endpoint: str, params: dict):
self.logger.debug("Calling external API",
endpoint=endpoint,
params=params)
try:
response = await self.http_client.get(endpoint, params=params)
self.logger.info("API call successful",
endpoint=endpoint,
status_code=response.status_code,
response_size=len(response.text))
return response.json()
except Exception as e:
self.logger.error("API call failed",
endpoint=endpoint,
error=str(e),
exc_info=True)
raise
Security Considerations
Be careful not to log sensitive information:
# ❌ Don't log sensitive data
self.logger.info("User authenticated",
password=user_password, # Never log passwords!
api_key=api_key) # Never log API keys!
# ✅ Log safely
self.logger.info("User authenticated",
user_id=user_id,
auth_method="api_key",
api_key_prefix=api_key[:8] + "...", # Only log prefix
timestamp=datetime.now().isoformat())
Development vs Production
AgentUp automatically configures logging based on the environment:
- Development: Text format with colors for easy reading
- Production: JSON format for structured log aggregation
You don't need to change your logging code - just use structured logging and AgentUp handles the formatting.
Filtering and Searching Logs
In production, you can filter logs by plugin:
# Filter by plugin ID
grep '"plugin_id":"weather"' agentup.log
# Filter by plugin and log level
grep '"plugin_id":"weather"' agentup.log | grep '"level":"ERROR"'
# Using jq for JSON logs
cat agentup.log | jq 'select(.plugin_id == "weather" and .level == "ERROR")'
Best Practices
1. Use Descriptive Event Names
# ❌ Vague
self.logger.info("Done")
# ✅ Descriptive
self.logger.info("Weather data processing completed")
2. Include Relevant Context
# ❌ Missing context
self.logger.error("Request failed")
# ✅ Rich context
self.logger.error("Weather API request failed",
endpoint="api.weather.com",
status_code=429,
retry_attempt=3,
user_location="New York")
3. Use Consistent Field Names
# Use consistent naming across your plugin
self.logger.info("Request started", request_id=req_id, user_id=user_id)
self.logger.info("Request completed", request_id=req_id, user_id=user_id, duration=0.5)
4. Log State Changes
self.logger.info("Plugin state changed",
previous_state="idle",
new_state="processing",
trigger="user_request")
5. Don't Over-Log
# ❌ Too verbose - will spam logs
for item in items:
self.logger.debug("Processing item", item=item)
# ✅ Aggregate logging
self.logger.info("Processing batch",
batch_size=len(items),
batch_id=batch_id)
Troubleshooting
Logger Not Available
If self.logger is not available, ensure you're calling super().__init__() in your plugin's __init__ method:
class MyPlugin(Plugin):
def __init__(self):
super().__init__() # This creates self.logger
# Your initialization code here
Logs Not Appearing
- Check that AgentUp logging is properly configured
- Verify your log level - DEBUG messages won't appear if log level is INFO
- Ensure your plugin is properly registered with AgentUp
Missing Plugin Context
If logs don't show plugin information, verify:
1. You're using self.logger (not print or standard logging)
2. Your plugin inherits from the Plugin base class
3. You're calling super().__init__() in your plugin's constructor
Advanced Usage
Custom Logger Creation
If you need additional loggers for specific components:
from agent.config.logging import get_plugin_logger
class MyPlugin(Plugin):
def __init__(self):
super().__init__()
# Additional logger for database operations
self.db_logger = get_plugin_logger(
plugin_id=f"{self.plugin_id}.database",
plugin_name=f"{self.name} - Database",
plugin_version=self.version
)
async def _database_operation(self):
self.db_logger.info("Database query started", table="users", operation="select")
Correlation IDs
AgentUp automatically includes correlation IDs for request tracking. Your plugin logs will include these automatically when processing requests.