Use canonical FCC server log path

This commit is contained in:
Alishahryar1
2026-05-16 11:51:45 -07:00
parent a728994e29
commit ac2c37f613
9 changed files with 58 additions and 23 deletions
-8
View File
@@ -413,14 +413,6 @@ FIELDS: tuple[ConfigFieldSpec, ...] = (
default="8082",
restart_required=True,
),
ConfigFieldSpec(
"LOG_FILE",
"Log File",
"runtime",
settings_attr="log_file",
default="logs/server.log",
restart_required=True,
),
ConfigFieldSpec(
"MESSAGING_PLATFORM",
"Messaging Platform",
+2 -1
View File
@@ -12,6 +12,7 @@ from loguru import logger
from starlette.types import Receive, Scope, Send
from config.logging_config import configure_logging
from config.paths import server_log_path
from config.settings import get_settings
from core.trace import extract_claude_session_id_from_headers, trace_event
from providers.exceptions import ProviderError
@@ -85,7 +86,7 @@ def create_app(*, lifespan_enabled: bool = True) -> FastAPI:
"""Create and configure the FastAPI application."""
settings = get_settings()
configure_logging(
settings.log_file, verbose_third_party=settings.log_raw_api_payloads
server_log_path(), verbose_third_party=settings.log_raw_api_payloads
)
app_kwargs: dict[str, Any] = {
+1 -1
View File
@@ -105,7 +105,7 @@ class InterceptHandler(logging.Handler):
def configure_logging(
log_file: str, *, force: bool = False, verbose_third_party: bool = False
log_file: str | Path, *, force: bool = False, verbose_third_party: bool = False
) -> None:
"""Configure loguru with JSON output to log_file and intercept stdlib logging.
+8
View File
@@ -5,6 +5,8 @@ from pathlib import Path
FCC_CONFIG_DIRNAME = ".fcc"
FCC_ENV_FILENAME = ".env"
CLAUDE_WORKSPACE_DIRNAME = "agent_workspace"
FCC_LOGS_DIRNAME = "logs"
SERVER_LOG_FILENAME = "server.log"
def config_dir_path() -> Path:
@@ -23,3 +25,9 @@ def default_claude_workspace_path() -> Path:
"""Return the default Claude workspace path."""
return config_dir_path() / CLAUDE_WORKSPACE_DIRNAME
def server_log_path() -> Path:
"""Return the canonical server log path."""
return config_dir_path() / FCC_LOGS_DIRNAME / SERVER_LOG_FILENAME
-1
View File
@@ -310,7 +310,6 @@ class Settings(BaseSettings):
# ==================== Server ====================
host: str = "0.0.0.0"
port: int = 8082
log_file: str = "logs/server.log"
# Optional server API key to protect endpoints (Anthropic-style)
# Set via env `ANTHROPIC_AUTH_TOKEN`. When empty, no auth is required.
anthropic_auth_token: str = Field(
+1
View File
@@ -57,6 +57,7 @@ def test_admin_config_masks_secrets_and_exposes_manifest(monkeypatch, tmp_path):
keys = {field["key"] for field in body["fields"]}
assert "ANTHROPIC_AUTH_TOKEN" in keys
assert "OPENROUTER_API_KEY" in keys
assert "LOG_FILE" not in keys
auth_field = next(
field for field in body["fields"] if field["key"] == "ANTHROPIC_AUTH_TOKEN"
)
+26 -11
View File
@@ -85,7 +85,6 @@ def test_create_app_provider_error_handler_returns_anthropic_format():
claude_workspace="./agent_workspace",
host="127.0.0.1",
port=8082,
log_file="server.log",
)
with (
patch.object(api_app_mod, "get_settings", return_value=settings),
@@ -122,7 +121,6 @@ def test_create_app_provider_error_default_logs_exclude_provider_message():
claude_workspace="./agent_workspace",
host="127.0.0.1",
port=8082,
log_file="server.log",
log_api_error_tracebacks=False,
)
with (
@@ -160,7 +158,6 @@ def test_create_app_general_exception_handler_returns_500():
claude_workspace="./agent_workspace",
host="127.0.0.1",
port=8082,
log_file="server.log",
)
with (
patch.object(api_app_mod, "get_settings", return_value=settings),
@@ -197,7 +194,6 @@ def test_create_app_general_exception_default_logs_exclude_exception_message():
claude_workspace="./agent_workspace",
host="127.0.0.1",
port=8082,
log_file="server.log",
log_api_error_tracebacks=False,
)
with (
@@ -236,7 +232,6 @@ def test_app_lifespan_sets_state_and_cleans_up(tmp_path, messaging_enabled):
claude_workspace=str(tmp_path / "data"),
host="127.0.0.1",
port=8082,
log_file=str(tmp_path / "server.log"),
)
fake_platform = MagicMock()
@@ -315,7 +310,6 @@ def test_app_lifespan_cleanup_continues_if_platform_stop_raises(tmp_path):
claude_workspace=str(tmp_path / "data"),
host="127.0.0.1",
port=8082,
log_file=str(tmp_path / "server.log"),
)
fake_platform = MagicMock()
@@ -366,7 +360,6 @@ async def test_runtime_startup_validation_failure_does_not_block_server(tmp_path
claude_workspace=str(tmp_path / "data"),
host="127.0.0.1",
port=8082,
log_file=str(tmp_path / "server.log"),
)
app = FastAPI()
runtime = api_runtime_mod.AppRuntime(
@@ -414,7 +407,6 @@ async def test_graceful_asgi_lifespan_model_validation_failure_starts(tmp_path):
claude_workspace=str(tmp_path / "data"),
host="127.0.0.1",
port=8082,
log_file=str(tmp_path / "server.log"),
)
app = api_app_mod.GracefulLifespanApp(FastAPI())
sent: list[MutableMapping[str, Any]] = []
@@ -460,7 +452,6 @@ def test_app_lifespan_messaging_import_error_no_crash(tmp_path, caplog):
claude_workspace=str(tmp_path / "data"),
host="127.0.0.1",
port=8082,
log_file=str(tmp_path / "server.log"),
)
api_app_mod = importlib.import_module("api.app")
@@ -496,7 +487,6 @@ def test_app_lifespan_platform_start_exception_cleanup_still_runs(tmp_path):
claude_workspace=str(tmp_path / "data"),
host="127.0.0.1",
port=8082,
log_file=str(tmp_path / "server.log"),
)
fake_platform = MagicMock()
@@ -547,7 +537,6 @@ def test_app_lifespan_flush_pending_save_exception_warning_only(tmp_path):
claude_workspace=str(tmp_path / "data"),
host="127.0.0.1",
port=8082,
log_file=str(tmp_path / "server.log"),
)
fake_platform = MagicMock()
@@ -582,3 +571,29 @@ def test_app_lifespan_flush_pending_save_exception_warning_only(tmp_path):
session_store.flush_pending_save.assert_called_once()
registry_cleanup.assert_awaited_once()
def test_create_app_writes_server_log_under_fcc_home(monkeypatch, tmp_path):
"""App logging uses ~/.fcc/logs/server.log regardless of cwd."""
from loguru import logger
import config.logging_config as logging_config_mod
from api.app import create_app
from config.paths import server_log_path
run_dir = tmp_path / "run"
run_dir.mkdir()
monkeypatch.chdir(run_dir)
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("USERPROFILE", str(tmp_path))
monkeypatch.setattr(logging_config_mod, "_configured", False)
create_app(lifespan_enabled=False)
logger.info("canonical log path test")
logger.complete()
canonical_log = server_log_path()
assert canonical_log == tmp_path / ".fcc" / "logs" / "server.log"
assert canonical_log.is_file()
assert "canonical log path test" in canonical_log.read_text(encoding="utf-8")
assert not (run_dir / "logs" / "server.log").exists()
-1
View File
@@ -22,7 +22,6 @@ async def test_messaging_start_failure_default_logs_exclude_traceback(caplog):
claude_workspace="./agent_workspace",
host="127.0.0.1",
port=8082,
log_file="server.log",
log_api_error_tracebacks=False,
)
runtime = api_runtime_mod.AppRuntime(app=MagicMock(), settings=settings)
+20
View File
@@ -57,6 +57,26 @@ class TestSettings:
assert settings.claude_workspace == str(tmp_path / ".fcc" / "agent_workspace")
def test_server_log_path_uses_fcc_home(self, monkeypatch, tmp_path):
"""The server log location is fixed under ~/.fcc."""
from config.paths import server_log_path
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("USERPROFILE", str(tmp_path))
assert server_log_path() == tmp_path / ".fcc" / "logs" / "server.log"
def test_removed_log_file_env_is_ignored(self, monkeypatch):
"""Legacy LOG_FILE values do not affect Settings or block startup."""
from config.settings import Settings
monkeypatch.setenv("LOG_FILE", "custom/server.log")
monkeypatch.setitem(Settings.model_config, "env_file", ())
settings = Settings()
assert not hasattr(settings, "log_file")
def test_blank_claude_workspace_uses_fcc_home(self, monkeypatch, tmp_path):
"""An explicit blank env value keeps the default workspace path."""
from config.settings import Settings