forked from Rockachopa/Timmy-time-dashboard
fix: clean up logging colors, reduce noise, enable Tailscale access (#166)
* fix: reserve red for real errors, reduce log noise, allow Tailscale access - Add _ColorFormatter: red = ERROR/CRITICAL only, yellow = WARNING, green = INFO - Override uvicorn's default colors to use our scheme - Downgrade discord "not installed" from ERROR to WARNING (optional dep) - Downgrade DuckDuckGo unavailable from INFO to DEBUG - Stop discord token watcher retry loop when discord.py not installed - Add configurable trusted_hosts setting; dev mode allows all hosts - Exclude .claude/ from uvicorn reload watcher (worktree isolation) - Fix pre-commit hook: use tox -e unit, bump timeout to 60s Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: auto-format with black Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: pre-commit hook auto-formats with black+isort before testing Formatting should never block a commit — just fix it automatically. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Trip T <trip@local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
1191ea2f9a
commit
c41e3e1e15
@@ -1,25 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# Pre-commit hook: format + test via tox.
|
||||
# Blocks the commit if formatting, imports, or tests fail.
|
||||
# Current baseline: ~18s wall-clock. Limit set to 30s for headroom.
|
||||
# Pre-commit hook: auto-format, then test via tox.
|
||||
# Blocks the commit if tests fail. Formatting is applied automatically.
|
||||
#
|
||||
# Auto-activated by `make install` via git core.hooksPath.
|
||||
|
||||
set -e
|
||||
|
||||
MAX_SECONDS=30
|
||||
MAX_SECONDS=60
|
||||
|
||||
# Auto-format staged files so formatting never blocks a commit
|
||||
echo "Auto-formatting with black + isort..."
|
||||
tox -e format -- 2>/dev/null || tox -e format
|
||||
git add -u
|
||||
|
||||
echo "Running pre-commit gate via tox (${MAX_SECONDS}s limit)..."
|
||||
|
||||
# macOS lacks GNU timeout; use perl as a portable fallback.
|
||||
if command -v timeout &>/dev/null; then
|
||||
timeout "${MAX_SECONDS}" tox -e pre-commit
|
||||
timeout "${MAX_SECONDS}" tox -e unit
|
||||
else
|
||||
perl -e "alarm ${MAX_SECONDS}; exec @ARGV" -- tox -e pre-commit
|
||||
perl -e "alarm ${MAX_SECONDS}; exec @ARGV" -- tox -e unit
|
||||
fi
|
||||
exit_code=$?
|
||||
|
||||
# Re-stage any files that black/isort reformatted
|
||||
# Re-stage any files that were reformatted
|
||||
git add -u
|
||||
|
||||
if [ "$exit_code" -eq 142 ] || [ "$exit_code" -eq 124 ]; then
|
||||
|
||||
@@ -112,6 +112,17 @@ class Settings(BaseSettings):
|
||||
# Set CORS_ORIGINS as a comma-separated list, e.g. "http://localhost:3000,https://example.com"
|
||||
cors_origins: list[str] = ["*"]
|
||||
|
||||
# Trusted hosts for the Host header check (TrustedHostMiddleware).
|
||||
# Set TRUSTED_HOSTS as a comma-separated list. Wildcards supported (e.g. "*.ts.net").
|
||||
# Defaults include localhost + Tailscale MagicDNS. Add your Tailscale IP if needed.
|
||||
trusted_hosts: list[str] = [
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
"*.local",
|
||||
"*.ts.net",
|
||||
"testserver",
|
||||
]
|
||||
|
||||
# Environment mode: development | production
|
||||
# In production, security settings are strictly enforced.
|
||||
timmy_env: Literal["development", "production"] = "development"
|
||||
|
||||
@@ -51,6 +51,24 @@ from dashboard.routes.work_orders import router as work_orders_router
|
||||
from infrastructure.router.api import router as cascade_router
|
||||
|
||||
|
||||
class _ColorFormatter(logging.Formatter):
|
||||
"""ANSI color formatter — red is reserved for ERROR/CRITICAL only."""
|
||||
|
||||
RESET = "\033[0m"
|
||||
COLORS = {
|
||||
logging.DEBUG: "\033[37m", # white/gray
|
||||
logging.INFO: "\033[32m", # green
|
||||
logging.WARNING: "\033[33m", # yellow
|
||||
logging.ERROR: "\033[31m", # red
|
||||
logging.CRITICAL: "\033[1;31m", # bold red
|
||||
}
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
color = self.COLORS.get(record.levelno, self.RESET)
|
||||
formatted = super().format(record)
|
||||
return f"{color}{formatted}{self.RESET}"
|
||||
|
||||
|
||||
def _configure_logging() -> None:
|
||||
"""Configure logging with console and optional rotating file handler."""
|
||||
root_logger = logging.getLogger()
|
||||
@@ -59,13 +77,20 @@ def _configure_logging() -> None:
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(logging.INFO)
|
||||
console.setFormatter(
|
||||
logging.Formatter(
|
||||
_ColorFormatter(
|
||||
"%(asctime)s %(levelname)-8s %(name)s — %(message)s",
|
||||
datefmt="%H:%M:%S",
|
||||
)
|
||||
)
|
||||
root_logger.addHandler(console)
|
||||
|
||||
# Override uvicorn's default colored formatter so all console output
|
||||
# uses our color scheme (red = ERROR/CRITICAL only).
|
||||
for name in ("uvicorn", "uvicorn.error", "uvicorn.access"):
|
||||
uv_logger = logging.getLogger(name)
|
||||
uv_logger.handlers.clear()
|
||||
uv_logger.propagate = True
|
||||
|
||||
if settings.error_log_enabled:
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
@@ -175,6 +200,13 @@ async def _discord_token_watcher() -> None:
|
||||
"""Poll for DISCORD_TOKEN appearing in env or .env and auto-start Discord bot."""
|
||||
from integrations.chat_bridge.vendors.discord import discord_bot
|
||||
|
||||
# Don't poll if discord.py isn't even installed
|
||||
try:
|
||||
import discord as _discord_check # noqa: F401
|
||||
except ImportError:
|
||||
logger.debug("discord.py not installed — token watcher exiting")
|
||||
return
|
||||
|
||||
while True:
|
||||
await asyncio.sleep(30)
|
||||
|
||||
@@ -325,9 +357,11 @@ app.add_middleware(SecurityHeadersMiddleware, production=not settings.debug)
|
||||
app.add_middleware(CSRFMiddleware)
|
||||
|
||||
# 4. Standard FastAPI middleware
|
||||
# In development, allow all hosts (Tailscale IPs, MagicDNS, etc.)
|
||||
_trusted = settings.trusted_hosts if settings.timmy_env == "production" else ["*"]
|
||||
app.add_middleware(
|
||||
TrustedHostMiddleware,
|
||||
allowed_hosts=["localhost", "127.0.0.1", "*.local", "testserver"],
|
||||
allowed_hosts=_trusted,
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
|
||||
@@ -138,7 +138,9 @@ class DiscordVendor(ChatPlatform):
|
||||
try:
|
||||
import discord
|
||||
except ImportError:
|
||||
logger.error("discord.py is not installed. " 'Run: pip install ".[discord]"')
|
||||
logger.warning(
|
||||
'discord.py is not installed — skipping. Install with: pip install ".[discord]"'
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
|
||||
@@ -427,7 +427,7 @@ def create_full_toolkit(base_dir: str | Path | None = None):
|
||||
search_tools = DuckDuckGoTools()
|
||||
toolkit.register(search_tools.web_search, name="web_search")
|
||||
else:
|
||||
logger.info("DuckDuckGo tools unavailable (ddgs not installed) — skipping web_search")
|
||||
logger.debug("DuckDuckGo tools unavailable (ddgs not installed) — skipping web_search")
|
||||
|
||||
# Python execution
|
||||
python_tools = PythonTools()
|
||||
|
||||
3
tox.ini
3
tox.ini
@@ -166,7 +166,8 @@ commands =
|
||||
[testenv:dev]
|
||||
description = Start dashboard with auto-reload (local development)
|
||||
commands =
|
||||
uvicorn dashboard.app:app --reload --host 0.0.0.0 --port 8000
|
||||
uvicorn dashboard.app:app --reload --host 0.0.0.0 --port 8000 \
|
||||
--reload-exclude ".claude"
|
||||
|
||||
# ── All Tests (parallel) ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user