- Add @logged_command decorator in utils/decorators.py to eliminate try/catch/finally boilerplate - Migrate all Discord commands to use new decorator pattern: * commands/league/info.py - /league command * commands/players/info.py - /player command * commands/teams/info.py - /team and /teams commands * commands/teams/roster.py - /roster command - Fix PyLance type issues by making model IDs required for database entities - Update Player and Team models to require id field since they come from database - Fix test cases to provide required id values - Add comprehensive test coverage for decorator functionality - Add migration guide for applying decorator to additional commands - Reduce codebase by ~100 lines of repetitive logging boilerplate 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
11 KiB
Discord Bot v2.0 - Logging Decorator Migration Guide
This guide documents the process for migrating existing Discord commands to use the new @logged_command decorator, which eliminates boilerplate logging code and standardizes command logging patterns.
Overview
The @logged_command decorator automatically handles:
- Discord context setting with interaction details
- Operation timing and trace ID generation
- Command start/completion/failure logging
- Exception handling and logging
- Parameter logging with exclusion options
What Was Changed
Before (Manual Logging Pattern)
@discord.app_commands.command(name="roster", description="Display team roster")
async def team_roster(self, interaction: discord.Interaction, abbrev: str):
set_discord_context(interaction=interaction, command="/roster")
trace_id = logger.start_operation("team_roster_command")
try:
logger.info("Team roster command started")
# Business logic here
logger.info("Team roster command completed successfully")
except Exception as e:
logger.error("Team roster command failed", error=e)
# Error handling
finally:
logger.end_operation(trace_id)
After (With Decorator)
@discord.app_commands.command(name="roster", description="Display team roster")
@logged_command("/roster")
async def team_roster(self, interaction: discord.Interaction, abbrev: str):
# Business logic only - no logging boilerplate needed
# All try/catch/finally logging is handled automatically
Step-by-Step Migration Process
1. Update Imports
Add the decorator import:
from utils.decorators import logged_command
Remove unused logging imports (if no longer needed):
# Remove if not used elsewhere in the file:
from utils.logging import set_discord_context # Usually can be removed
2. Ensure Class Has Logger
Before migration, ensure the command class has a logger:
class YourCommandCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.logger = get_contextual_logger(f'{__name__}.YourCommandCog') # Add this line
3. Apply the Decorator
Add the decorator above the command method:
@discord.app_commands.command(name="your-command", description="...")
@logged_command("/your-command") # Add this line
async def your_command_method(self, interaction, ...):
4. Remove Manual Logging Boilerplate
Remove these patterns:
set_discord_context(interaction=interaction, command="...")trace_id = logger.start_operation("...")try:/except:/finally:blocks used only for logginglogger.info("Command started")andlogger.info("Command completed")logger.error("Command failed", error=e)in catch blockslogger.end_operation(trace_id)
Keep these:
- Business logic logging (e.g.,
logger.info("Team found", team_id=123)) - Specific error handling (user-facing error messages)
- All business logic and Discord interaction code
5. Test the Migration
Run the tests to ensure the migration works:
python -m pytest tests/test_utils_decorators.py -v
python -m pytest # Run all tests to ensure no regressions
Example: Complete Migration
commands/teams/roster.py (BEFORE)
"""Team roster commands for Discord Bot v2.0"""
import logging
from typing import Optional, Dict, Any, List
import discord
from discord.ext import commands
from utils.logging import get_contextual_logger, set_discord_context
class TeamRosterCommands(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
# Missing: self.logger = get_contextual_logger(...)
@discord.app_commands.command(name="roster", description="Display team roster")
async def team_roster(self, interaction: discord.Interaction, abbrev: str):
set_discord_context(interaction=interaction, command="/roster")
trace_id = logger.start_operation("team_roster_command")
try:
await interaction.response.defer()
logger.info("Team roster command requested", team_abbrev=abbrev)
# Business logic
team = await team_service.get_team_by_abbrev(abbrev)
# ... more business logic ...
logger.info("Team roster displayed successfully")
except BotException as e:
logger.error("Bot error in team roster command", error=str(e))
# Error handling
except Exception as e:
logger.error("Unexpected error in team roster command", error=str(e))
# Error handling
finally:
logger.end_operation(trace_id)
commands/teams/roster.py (AFTER)
"""Team roster commands for Discord Bot v2.0"""
import logging
from typing import Optional, Dict, Any, List
import discord
from discord.ext import commands
from utils.logging import get_contextual_logger
from utils.decorators import logged_command # Added
class TeamRosterCommands(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.logger = get_contextual_logger(f'{__name__}.TeamRosterCommands') # Added
@discord.app_commands.command(name="roster", description="Display team roster")
@logged_command("/roster") # Added
async def team_roster(self, interaction: discord.Interaction, abbrev: str):
await interaction.response.defer()
# Business logic only - all boilerplate logging removed
team = await team_service.get_team_by_abbrev(abbrev)
if team is None:
self.logger.info("Team not found", team_abbrev=abbrev) # Business logic logging
# ... handle not found ...
return
# ... rest of business logic ...
self.logger.info("Team roster displayed successfully", # Business logic logging
team_id=team.id, team_abbrev=team.abbrev)
Migration Checklist for Each Command
- Add
from utils.decorators import logged_commandimport - Ensure class has
self.logger = get_contextual_logger(...)in__init__ - Add
@logged_command("/command-name")decorator - Remove
set_discord_context()call - Remove
trace_id = logger.start_operation()call - Remove
try:block (if only used for logging) - Remove
logger.info("Command started")andlogger.info("Command completed") - Remove generic
except Exception as e:blocks (if only used for logging) - Remove
logger.error("Command failed")calls - Remove
finally:block andlogger.end_operation()call - Keep business logic logging (specific info/debug/warning messages)
- Keep error handling that sends user-facing messages
- Test the command works correctly
Decorator Options
Basic Usage
@logged_command("/command-name")
async def my_command(self, interaction, param1: str):
# Implementation
Auto-Detect Command Name
@logged_command() # Will use "/my-command" based on function name
async def my_command(self, interaction, param1: str):
# Implementation
Exclude Sensitive Parameters
@logged_command("/login", exclude_params=["password", "token"])
async def login_command(self, interaction, username: str, password: str):
# password won't appear in logs
Disable Parameter Logging
@logged_command("/sensitive-command", log_params=False)
async def sensitive_command(self, interaction, sensitive_data: str):
# No parameters will be logged
Expected Benefits
Lines of Code Reduction
- Before: ~25-35 lines per command (including try/catch/finally)
- After: ~10-15 lines per command
- Reduction: ~15-20 lines of boilerplate per command
Consistency Improvements
- Standardized logging format across all commands
- Consistent error handling patterns
- Automatic trace ID generation and correlation
- Reduced chance of logging bugs (forgotten
end_operation, etc.)
Maintainability
- Single point of change for logging behavior
- Easier to add new logging features (e.g., performance metrics)
- Less code duplication
- Clearer separation of business logic and infrastructure
Files to Migrate
Based on the current codebase structure, these files likely need migration:
commands/
├── league/
│ └── info.py
├── players/
│ └── info.py
└── teams/
├── info.py
└── roster.py # ✅ Already migrated (example)
Testing Migration
1. Unit Tests
# Test the decorator itself
python -m pytest tests/test_utils_decorators.py -v
# Test migrated commands still work
python -m pytest tests/ -v
2. Integration Testing
# Verify command registration still works
python -c "
import discord
from commands.teams.roster import TeamRosterCommands
from discord.ext import commands
intents = discord.Intents.default()
bot = commands.Bot(command_prefix='!', intents=intents)
cog = TeamRosterCommands(bot)
print('✅ Command loads successfully')
"
3. Log Output Verification
After migration, verify that log entries still contain:
- Correct trace IDs for request correlation
- Command start/completion messages
- Error logging with exceptions
- Business logic messages
- Discord context (user_id, guild_id, etc.)
Troubleshooting
Common Issues
Issue: AttributeError: 'YourCog' object has no attribute 'logger'
Solution: Add self.logger = get_contextual_logger(...) to the cog's __init__ method
Issue: Parameters not appearing in logs
Solution: Check if parameters are in the exclude_params list or if log_params=False
Issue: Command not registering with Discord
Solution: Ensure @logged_command() is placed AFTER @discord.app_commands.command()
Issue: Signature errors during command registration Solution: The decorator preserves signatures automatically; if issues persist, check Discord.py version compatibility
Debugging Steps
- Check that all imports are correct
- Verify logger exists on the cog instance
- Run unit tests to ensure decorator functionality
- Check log files for expected trace IDs and messages
- Test command execution in a development environment
Migration Timeline
Recommended approach: Migrate one command at a time and test thoroughly before moving to the next.
- Phase 1: Migrate simple commands (no complex error handling)
- Phase 2: Migrate commands with custom error handling
- Phase 3: Migrate complex commands with multiple operations
- Phase 4: Update documentation and add any additional decorator features
This approach ensures that any issues can be isolated and resolved before affecting multiple commands.