Structured Logging in StatusPro MCP Server¶
The StatusPro MCP Server uses structured logging with structlog to provide rich observability and debugging capabilities.
Features¶
- Structured JSON or text output - Choose between JSON (for log aggregation) or human-readable text format
- Contextual information - Every log includes relevant context (tool names, SKUs, IDs, etc.)
- Performance metrics - Automatic duration tracking for all tool executions
- Trace IDs - Support for request correlation across operations
- Security - Automatic redaction of sensitive data (API keys, passwords, credentials)
- Configurable levels - Control log verbosity via environment variables
Configuration¶
Environment Variables¶
Configure logging behavior with these environment variables:
-
STATUSPRO_MCP_LOG_LEVEL- Log level (default:INFO) -
Options:
DEBUG,INFO,WARNING,ERROR -
STATUSPRO_MCP_LOG_FORMAT- Output format (default:json) -
Options:
json,text
Examples¶
Development (verbose text output):
Production (structured JSON):
Log Structure¶
JSON Format Example¶
{
"event": "tool_executed",
"tool_name": "search_items",
"query": "widget",
"result_count": 15,
"duration_ms": 245.67,
"timestamp": "2025-01-05T17:08:40.123456Z",
"level": "info"
}
Text Format Example¶
2025-01-05 17:08:40 [info ] tool_executed tool_name=search_items query=widget result_count=15 duration_ms=245.67
Logged Events¶
Server Lifecycle¶
Server Initialization:
{
"event": "server_initializing",
"version": "0.4.0",
"base_url": "https://app.orderstatuspro.com/api/v1",
"level": "info"
}
Client Ready:
{
"event": "client_initialized",
"timeout": 30.0,
"max_retries": 5,
"max_pages": 100,
"level": "info"
}
Server Ready:
Tool Execution¶
Inventory Check (Success):
{
"event": "order_status_updated",
"sku": "WIDGET-001",
"product_name": "Widget Pro",
"available_stock": 100,
"committed": 30,
"duration_ms": 123.45,
"level": "info"
}
Search Items (Success):
{
"event": "item_search_completed",
"query": "widget",
"result_count": 15,
"duration_ms": 245.67,
"level": "info"
}
Create Item (Success):
{
"event": "item_create_completed",
"item_type": "product",
"item_id": 123,
"name": "Widget Pro",
"sku": "WGT-PRO-001",
"duration_ms": 567.89,
"level": "info"
}
Error Logging¶
Tool Failure:
{
"event": "item_search_failed",
"query": "invalid",
"error": "Invalid search query",
"error_type": "ValueError",
"duration_ms": 12.34,
"level": "error",
"exception": "Traceback (most recent call last)..."
}
Authentication Error:
{
"event": "authentication_failed",
"reason": "STATUSPRO_API_KEY environment variable is required",
"message": "Please set it in your .env file or environment.",
"level": "error"
}
Security Features¶
Sensitive Data Redaction¶
The logger automatically redacts sensitive information from logs:
Input:
Output (JSON):
Redacted Keys:
api_key,API_KEYpassword,PASSWORDsecret,SECRETtoken,TOKENauth,authorization,AUTHORIZATIONcredential,CREDENTIAL
Any field containing these keywords (case-insensitive) will be automatically redacted.
Performance Metrics¶
All tool executions include performance metrics:
duration_ms- Time taken to execute the tool (in milliseconds)result_count- Number of items returned (for search/list operations)threshold- Configured threshold (for low stock checks)
Example with metrics:
{
"event": "low_stock_search_completed",
"threshold": 10,
"total_count": 25,
"returned_count": 25,
"duration_ms": 678.90,
"level": "info"
}
Observability Decorators¶
The StatusPro MCP Server provides convenience decorators to automatically instrument tools and service methods with logging, timing, and error tracking.
@observe_tool¶
Automatically instruments MCP tool functions with comprehensive observability.
Features:
- Automatic logging of tool invocations with parameters
- Execution timing in milliseconds
- Success/failure tracking
- Error details with exception types
- Context parameter filtering (ctx/context excluded from logs)
Usage:
from statuspro_mcp.logging import observe_tool
from fastmcp import Context
@observe_tool
@mcp.tool()
async def my_tool(request: MyRequest, context: Context) -> MyResponse:
"""My tool implementation."""
return await do_work()
Log Events Produced:
Tool invocation (INFO level):
{
"event": "tool_invoked",
"tool_name": "my_tool",
"params": {"request": {...}},
"timestamp": "2025-01-05T17:08:40.123456Z",
"level": "info"
}
Successful completion (INFO level):
{
"event": "tool_completed",
"tool_name": "my_tool",
"duration_ms": 245.67,
"success": true,
"timestamp": "2025-01-05T17:08:40.369126Z",
"level": "info"
}
Tool failure (ERROR level):
{
"event": "tool_failed",
"tool_name": "my_tool",
"duration_ms": 12.34,
"error": "Invalid request parameters",
"error_type": "ValueError",
"success": false,
"timestamp": "2025-01-05T17:08:40.135796Z",
"level": "error"
}
Best Practices:
- Place
@observe_toolbefore@mcp.tool()decorator - Use with
@unpack_pydantic_paramsfor parameter unpacking - Context parameters (ctx, context) are automatically filtered from logs
- No need to add manual timing or error logging in tool implementation
Example with Parameter Unpacking:
@observe_tool
@unpack_pydantic_params
async def search_items(
request: Annotated[SearchItemsRequest, Unpack()], context: Context
) -> SearchItemsResponse:
"""Search for items - automatically instrumented."""
services = get_services(context)
return await services.client.orders.list(search=request.query)
@observe_service¶
Instruments service layer methods with debug-level logging.
Features:
- Service operation tracking
- Method timing
- Error logging with context
- Class name extraction
Usage:
from statuspro_mcp.logging import observe_service
class MyService:
@observe_service("get_item")
async def get(self, item_id: int) -> Item:
"""Fetch item by ID."""
return await self._fetch(item_id)
@observe_service("create_item")
async def create(self, data: dict) -> Item:
"""Create new item."""
return await self._create(data)
Log Events Produced:
Operation started (DEBUG level):
{
"event": "service_operation_started",
"service": "MyService",
"operation": "get_item",
"params": {"item_id": 123},
"timestamp": "2025-01-05T17:08:40.123456Z",
"level": "debug"
}
Operation completed (DEBUG level):
{
"event": "service_operation_completed",
"service": "MyService",
"operation": "get_item",
"duration_ms": 45.67,
"success": true,
"timestamp": "2025-01-05T17:08:40.169126Z",
"level": "debug"
}
Operation failed (ERROR level):
{
"event": "service_operation_failed",
"service": "MyService",
"operation": "get_item",
"duration_ms": 12.34,
"error": "Item not found",
"error_type": "ItemNotFoundError",
"success": false,
"timestamp": "2025-01-05T17:08:40.135796Z",
"level": "error"
}
Best Practices:
- Use descriptive operation names (e.g., "get_product", "create_order")
- Service operations log at DEBUG level (less verbose than tools)
- Use for lower-level operations called by tools
- Provides granular visibility into service layer performance
Decorator Comparison¶
| Feature | @observe_tool | @observe_service |
|---|---|---|
| Use Case | MCP tools (user-facing operations) | Service methods (internal operations) |
| Log Level | INFO | DEBUG |
| Parameter Filtering | Yes (ctx/context) | No |
| Primary Audience | Operations/users | Developers |
| Visibility | High (always logged in production) | Low (only in debug mode) |
Migration Guide¶
Before (Manual Logging):
@mcp.tool()
async def my_tool(request: MyRequest, context: Context) -> MyResponse:
logger.info("my_tool_started", request=request)
start_time = time.monotonic()
try:
result = await do_work()
duration_ms = (time.monotonic() - start_time) * 1000
logger.info("my_tool_completed", duration_ms=duration_ms)
return result
except Exception as e:
logger.error("my_tool_failed", error=str(e))
raise
After (With Decorator):
@observe_tool
@mcp.tool()
async def my_tool(request: MyRequest, context: Context) -> MyResponse:
return await do_work() # Automatic logging, timing, error tracking!
Benefits:
- 10+ lines of boilerplate eliminated per tool
- Consistent log format across all tools
- Automatic error tracking without try/except
- Performance metrics captured automatically
- No risk of forgetting to log important events
Best Practices¶
- Use INFO level in production - Provides operational visibility without noise
- Use DEBUG for troubleshooting - Includes detailed execution traces and service operations
- Use JSON format for log aggregation - Easier to parse and analyze
- Use text format for development - More human-readable
- Monitor duration_ms - Identify performance bottlenecks
- Never log sensitive data - The filter catches common patterns, but be careful with custom fields
- Use @observe_tool for all MCP tools - Automatic instrumentation with zero boilerplate
- Use @observe_service for service methods - Granular visibility into internal operations