Python Service Guidelines
Logging
CRITICAL RULE: All Python services MUST implement comprehensive logging with structured error information. Logging is essential for debugging production issues and understanding service behavior.
Logger Configuration
Location: python-service/main.py
Setup:
- Configure logging at application startup with appropriate level (DEBUG in development, INFO in production)
- Use Python's standard
loggingmodule - Configure format:
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
Example:
import logging
# Configure logging
logging.basicConfig(
level=logging.DEBUG if settings.DEBUG else logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
Logger Usage in Services
CRITICAL RULE: Always use module-level loggers - Create a logger at the top of each module using logging.getLogger(__name__).
Example:
import logging
logger = logging.getLogger(__name__)
class MyService:
async def my_method(self):
logger.info("Processing request...")
Log Levels
Use appropriate log levels:
-
logger.debug(): Detailed diagnostic information (development only)- Function entry/exit
- Parameter values
- Intermediate computation steps
- Cache hits/misses
-
logger.info(): General informational messages- Successful operations
- Service initialization
- Important state changes
- Request processing start/completion
-
logger.warning(): Warning messages for potentially problematic situations- Deprecated feature usage
- Configuration issues
- Performance concerns
- Recoverable errors
-
logger.error(): Error messages for failures- Failed operations
- External service errors
- Data validation failures
- MUST include full traceback (see Error Logging section)
Error Logging
CRITICAL RULE: All error logs MUST include complete traceback information. Use traceback.format_exc() to capture full stack traces.
Required Information in Error Logs:
- Error message: Clear description of what failed
- Full traceback: Complete stack trace using
traceback.format_exc() - Context data: Relevant request parameters, IDs, sizes, etc.
- Operation details: What operation was being performed
Example - Router Error Logging:
import logging
import traceback
logger = logging.getLogger(__name__)
@router.post("/html-to-pdf", response_class=Response)
async def html_to_pdf(request: HtmlToPdfRequest):
try:
pdf_service = get_pdf_service()
pdf_bytes = await pdf_service.html_to_pdf(request.html, request.css)
# ... return response
except Exception as e:
error_traceback = traceback.format_exc()
logger.error(
f"Failed to generate PDF from HTML: {e}\n"
f"HTML length: {len(request.html)} chars\n"
f"CSS provided: {bool(request.css)}\n"
f"Traceback:\n{error_traceback}"
)
raise HTTPException(
status_code=500,
detail=f"PDF generation failed: {str(e)}",
)
Example - Service Error Logging:
import logging
import traceback
logger = logging.getLogger(__name__)
class PdfService:
async def html_to_pdf(
self,
html: str,
css: Optional[str] = None,
base_url: Optional[str] = None,
landscape: bool = False,
) -> bytes:
try:
logger.debug(f"Creating HTML document from string (length: {len(html)} chars)")
html_doc = HTML(string=html, base_url=base_url)
# ... processing
logger.info(f"Generated PDF from HTML: {len(pdf_bytes)} bytes | Landscape: {landscape}")
return pdf_bytes
except Exception as e:
error_traceback = traceback.format_exc()
logger.error(
f"Failed to generate PDF from HTML: {e}\n"
f"HTML length: {len(html)} chars\n"
f"HTML preview (first 500 chars): {html[:500]}\n"
f"CSS provided: {bool(css)}\n"
f"Base URL: {base_url}\n"
f"Landscape: {landscape}\n"
f"Traceback:\n{error_traceback}"
)
raise
Contextual Information
CRITICAL RULE: Always include relevant context in logs to help debug issues:
For Requests:
- Request parameters (IDs, sizes, types)
- User context (if available)
- Operation type
- Resource identifiers
For Operations:
- Input sizes (HTML length, file sizes, etc.)
- Configuration values (base URLs, flags)
- Intermediate results (when relevant)
- Performance metrics (durations, sizes)
Example:
logger.info(
f"Processing document conversion → "
f"DocumentID: {document_id} | "
f"Format: {format} | "
f"Size: {len(content)} bytes"
)
Debug Logging
When to use logger.debug():
- Function entry/exit points
- Parameter values (non-sensitive)
- Intermediate computation steps
- Cache operations (hits/misses)
- External API call details (request/response sizes)
Example:
logger.debug(f"Creating HTML document from string (length: {len(html)} chars)")
logger.debug(f"Adding custom CSS (length: {len(css)} chars)")
logger.debug(f"Generating PDF with {len(stylesheets)} stylesheet(s)")
Service Initialization Logging
CRITICAL RULE: Log service initialization with version information for debugging compatibility issues.
Example:
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
logger.info("🚀 Starting Python Services...")
logger.info("📦 Loading models...")
logger.info("📄 PDF generation: WeasyPrint")
# Log versions for debugging
try:
import weasyprint
import pydyf
logger.info(f"WeasyPrint version: {weasyprint.__version__}")
logger.info(f"pydyf version: {pydyf.__version__}")
except ImportError as e:
logger.warning(f"Could not import WeasyPrint/pydyf to check versions: {e}")
yield
logger.info("👋 Shutting down Python Services...")
Best Practices
- Always log errors with full context: Include traceback, parameters, and operation details
- Use appropriate log levels: Don't use ERROR for warnings, don't use INFO for debug information
- Include relevant metrics: Sizes, durations, counts help identify performance issues
- Log service versions: Helps debug compatibility issues (e.g., WeasyPrint/pydyf versions)
- Structured logging: Use consistent format across all logs
- Don't log sensitive data: Never log passwords, tokens, or personal information
- Log at operation boundaries: Log at the start and completion of important operations
- Include request identifiers: When available, include request IDs or user IDs for tracing
Error Response Format
CRITICAL RULE: Error responses MUST include detailed error messages while keeping HTTP responses user-friendly.
Pattern:
- Log: Full error details with traceback (for debugging)
- HTTP Response: User-friendly error message (without sensitive details)
Example:
except Exception as e:
error_traceback = traceback.format_exc()
# Log full details for debugging
logger.error(
f"Failed to generate PDF: {e}\n"
f"Traceback:\n{error_traceback}"
)
# Return user-friendly message
raise HTTPException(
status_code=500,
detail=f"PDF generation failed: {str(e)}",
)
Logging Checklist
When adding logging to a new service or endpoint:
- Logger created at module level:
logger = logging.getLogger(__name__) - All exceptions caught and logged with
traceback.format_exc() - Context information included (IDs, sizes, parameters)
- Appropriate log levels used (debug/info/warning/error)
- Service initialization logged with version info
- Operation start/completion logged
- No sensitive data in logs
- Error responses are user-friendly (detailed info only in logs)
Error Handling
CRITICAL RULE: All exceptions MUST be caught, logged with full context, and re-raised or converted to HTTP exceptions.
Exception Handling Pattern
In Routers:
@router.post("/endpoint")
async def my_endpoint(request: MyRequest):
try:
result = await service.process(request)
return result
except ValueError as e:
logger.warning(f"Validation error: {e}")
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
error_traceback = traceback.format_exc()
logger.error(
f"Unexpected error in my_endpoint: {e}\n"
f"Request: {request.model_dump()}\n"
f"Traceback:\n{error_traceback}"
)
raise HTTPException(
status_code=500,
detail="Internal server error",
)
In Services:
async def process(self, request: MyRequest) -> Result:
try:
# Processing logic
return result
except SpecificError as e:
logger.error(f"Specific error occurred: {e}")
raise # Re-raise to let router handle HTTP response
except Exception as e:
error_traceback = traceback.format_exc()
logger.error(
f"Unexpected error in process: {e}\n"
f"Request data: {request}\n"
f"Traceback:\n{error_traceback}"
)
raise
Code Style
- Follow PEP 8 style guide
- Use type hints for all function parameters and return types
- Use async/await for all I/O operations
- Keep functions focused and small (< 100 lines when possible)
- Use descriptive variable and function names
- Add docstrings for all public functions and classes
Dependencies
CRITICAL RULE: Always pin dependency versions in requirements.txt to ensure reproducible builds and avoid compatibility issues.
Example:
weasyprint==62.3 # PDF generation (CSS3 support)
pydyf==0.11.0 # PDF low-level library (required by WeasyPrint 62.3, 0.12.1 has bug)
When adding new dependencies:
- Pin exact version:
package==1.2.3 - Add comment explaining why the dependency is needed
- Document any version constraints or known issues
- Test compatibility with existing dependencies
FastAPI Best Practices
- Use Pydantic models for request/response validation
- Use dependency injection for services
- Return appropriate HTTP status codes
- Use response models for type safety
- Document endpoints with docstrings
Testing
- Write unit tests for all service methods
- Test error cases, not just happy paths
- Mock external dependencies
- Use pytest for testing framework
- Aim for >80% code coverage