fix: Prevent TUI corruption from logging and improve sync feedback #1

Merged
cal merged 1 commits from fix/tui-logging-errors into main 2026-02-10 22:20:36 +00:00
4 changed files with 116 additions and 9 deletions

3
.gitignore vendored
View File

@ -13,3 +13,6 @@ wheels/
__pycache__/
*.pyc
.venv/
# Data directory (contains user settings, logs, and database)
data/

63
TROUBLESHOOTING.md Normal file
View File

@ -0,0 +1,63 @@
# Troubleshooting Guide
## Sync Errors
### API Key Configuration
The app requires a valid API key to sync data from the SBA Major Domo API.
**Option 1: Settings Screen (Recommended)**
1. Launch the app: `sba-scout`
2. Press `x` to open Settings
3. Enter your API key
4. Click "Save Settings"
**Option 2: Edit settings.yaml directly**
1. Edit `data/settings.yaml`
2. Set `api.api_key` to your actual API key
3. Save the file
4. Restart the app
### Viewing Logs
All errors are now logged to `data/logs/sba_scout.log` instead of the terminal.
To view recent errors:
```bash
tail -f data/logs/sba_scout.log
```
To search for specific errors:
```bash
grep "ERROR" data/logs/sba_scout.log
```
### Common Errors
**"API key not configured"**
- Your API key is missing or set to the placeholder value
- Fix: Configure your API key using one of the methods above
**"Sync failed: Invalid API key"** (401/403 error)
- Your API key is invalid or expired
- Fix: Get a new API key from the SBA Major Domo system
**"Sync failed: API error 500"**
- The API server is experiencing issues
- Fix: Wait a few minutes and try again
**"Request failed: Connection timeout"**
- Network connectivity issues
- Fix: Check your internet connection, or increase timeout in settings
## Getting an API Key
To get an API key for the SBA Major Domo API:
1. Visit the SBA Major Domo system
2. Go to your account settings
3. Generate a new API key
4. Copy the key and paste it into the app settings
## Need More Help?
Check the full logs at `data/logs/sba_scout.log` for detailed error messages and stack traces.

View File

@ -112,11 +112,16 @@ class LeagueAPIClient:
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
logger.error(f"API error: {e.response.status_code} - {e.response.text}")
raise LeagueAPIError(
f"API request failed: {e.response.text}",
status_code=e.response.status_code,
)
# Check if this is a Cloudflare HTML error page
response_text = e.response.text
if "cloudflare" in response_text.lower() and e.response.status_code == 403:
error_msg = "API blocked by Cloudflare (missing or invalid API key)"
logger.error(f"API error: {e.response.status_code} - Cloudflare block")
else:
error_msg = f"API request failed (HTTP {e.response.status_code})"
logger.error(f"API error: {e.response.status_code} - {response_text[:200]}")
raise LeagueAPIError(error_msg, status_code=e.response.status_code)
except httpx.RequestError as e:
logger.error(f"Request error: {e}")
raise LeagueAPIError(f"Request failed: {str(e)}")

View File

@ -23,10 +23,20 @@ from .screens.matchup import MatchupScreen
from .screens.roster import RosterScreen
from .screens.settings import SettingsScreen
# Configure logging
# Configure logging - write to file to avoid interfering with TUI
from pathlib import Path
log_dir = Path("data/logs")
log_dir.mkdir(parents=True, exist_ok=True)
log_file = log_dir / "sba_scout.log"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(log_file),
# Don't add StreamHandler - it corrupts the TUI
],
)
logger = logging.getLogger(__name__)
@ -100,6 +110,7 @@ class DashboardScreen(Screen):
# Status bar
with Horizontal(id="status-bar"):
yield Label("Last sync: Never", id="sync-status")
yield LoadingIndicator(id="sync-loader")
yield Button("Sync Now [s]", id="btn-sync", variant="success")
yield Button("Settings [x]", id="btn-settings", variant="default")
@ -186,28 +197,47 @@ class DashboardScreen(Screen):
async def action_sync_data(self) -> None:
"""Sync data from the league API."""
from .api.client import LeagueAPIError
from .api.sync import sync_all
sync_btn = self.query_one("#btn-sync", Button)
sync_status = self.query_one("#sync-status", Label)
sync_loader = self.query_one("#sync-loader", LoadingIndicator)
sync_btn.disabled = True
sync_loader.display = True
sync_status.update("Syncing...")
try:
settings = get_settings()
async with get_session() as session:
counts = await sync_all(session, season=13)
counts = await sync_all(session, season=settings.team.current_season)
sync_status.update(f"Synced: {counts['teams']} teams, {counts['players']} players")
# Refresh roster summary
await self.load_roster_summary()
except LeagueAPIError as e:
logger.error(f"API error during sync: {e.message}")
if "cloudflare" in e.message.lower() or e.status_code == 403:
sync_status.update("Sync disabled: API key required")
elif e.status_code == 401:
sync_status.update("Sync failed: Invalid API key")
elif e.status_code:
sync_status.update(f"Sync failed: HTTP {e.status_code}")
else:
sync_status.update(f"Sync failed: {str(e.message)[:40]}")
except Exception as e:
logger.error(f"Sync failed: {e}")
sync_status.update(f"Sync failed: {str(e)[:50]}")
logger.error(f"Unexpected error during sync: {e}", exc_info=True)
# Show a user-friendly truncated message
error_msg = str(e).split("\n")[0][:50]
sync_status.update(f"Sync failed: {error_msg}")
finally:
sync_loader.display = False
sync_btn.disabled = False
@ -313,6 +343,12 @@ class SBAScoutApp(App):
width: 1fr;
}
#sync-loader {
width: auto;
margin-right: 1;
display: none;
}
#btn-sync {
width: auto;
}