feat(logging): implement configurable belief state logging

- Add LoggingConfig model and logging field to GlobalSettings
- Implement belief_scope context manager for structured logging
- Add configure_logger for dynamic level and file rotation settings
- Add logging configuration UI to Settings page
- Update ConfigManager to apply logging settings on initialization and updates
This commit is contained in:
2025-12-27 05:39:33 +03:00
parent d197303b9f
commit 83e34e1799
10 changed files with 224 additions and 24 deletions

3
.gitignore vendored
View File

@@ -60,4 +60,5 @@ keyring passwords.py
*github* *github*
*git* *git*
*tech_spec* *tech_spec*
dashboards dashboards
backend/mappings.db

Binary file not shown.

View File

@@ -15,6 +15,7 @@ from backend.src.dependencies import get_config_manager
from backend.src.core.superset_client import SupersetClient from backend.src.core.superset_client import SupersetClient
from superset_tool.models import SupersetConfig from superset_tool.models import SupersetConfig
from pydantic import BaseModel from pydantic import BaseModel
from backend.src.core.logger import logger
# [/SECTION] # [/SECTION]
router = APIRouter(prefix="/api/environments", tags=["environments"]) router = APIRouter(prefix="/api/environments", tags=["environments"])
@@ -38,7 +39,9 @@ class DatabaseResponse(BaseModel):
# @RETURN: List[EnvironmentResponse] # @RETURN: List[EnvironmentResponse]
@router.get("", response_model=List[EnvironmentResponse]) @router.get("", response_model=List[EnvironmentResponse])
async def get_environments(config_manager=Depends(get_config_manager)): async def get_environments(config_manager=Depends(get_config_manager)):
logger.info(f"[get_environments][Debug] Config path: {config_manager.config_path}")
envs = config_manager.get_environments() envs = config_manager.get_environments()
logger.info(f"[get_environments][Debug] Found {len(envs)} environments")
return [EnvironmentResponse(id=e.id, name=e.name, url=e.url) for e in envs] return [EnvironmentResponse(id=e.id, name=e.name, url=e.url) for e in envs]
# [/DEF:get_environments] # [/DEF:get_environments]

View File

@@ -16,7 +16,7 @@ import os
from pathlib import Path from pathlib import Path
from typing import Optional, List from typing import Optional, List
from .config_models import AppConfig, Environment, GlobalSettings from .config_models import AppConfig, Environment, GlobalSettings
from .logger import logger from .logger import logger, configure_logger
# [/SECTION] # [/SECTION]
# [DEF:ConfigManager:Class] # [DEF:ConfigManager:Class]
@@ -38,10 +38,13 @@ class ConfigManager:
# 2. Logic implementation # 2. Logic implementation
self.config_path = Path(config_path) self.config_path = Path(config_path)
self.config: AppConfig = self._load_config() self.config: AppConfig = self._load_config()
# Configure logger with loaded settings
configure_logger(self.config.settings.logging)
# 3. Runtime check of @POST # 3. Runtime check of @POST
assert isinstance(self.config, AppConfig), "self.config must be an instance of AppConfig" assert isinstance(self.config, AppConfig), "self.config must be an instance of AppConfig"
logger.info(f"[ConfigManager][Exit] Initialized") logger.info(f"[ConfigManager][Exit] Initialized")
# [/DEF:__init__] # [/DEF:__init__]
@@ -120,7 +123,10 @@ class ConfigManager:
# 2. Logic implementation # 2. Logic implementation
self.config.settings = settings self.config.settings = settings
self.save() self.save()
# Reconfigure logger with new settings
configure_logger(settings.logging)
logger.info(f"[update_global_settings][Exit] Settings updated") logger.info(f"[update_global_settings][Exit] Settings updated")
# [/DEF:update_global_settings] # [/DEF:update_global_settings]

View File

@@ -19,11 +19,22 @@ class Environment(BaseModel):
is_default: bool = False is_default: bool = False
# [/DEF:Environment] # [/DEF:Environment]
# [DEF:LoggingConfig:DataClass]
# @PURPOSE: Defines the configuration for the application's logging system.
class LoggingConfig(BaseModel):
level: str = "INFO"
file_path: Optional[str] = "logs/app.log"
max_bytes: int = 10 * 1024 * 1024
backup_count: int = 5
enable_belief_state: bool = True
# [/DEF:LoggingConfig]
# [DEF:GlobalSettings:DataClass] # [DEF:GlobalSettings:DataClass]
# @PURPOSE: Represents global application settings. # @PURPOSE: Represents global application settings.
class GlobalSettings(BaseModel): class GlobalSettings(BaseModel):
backup_path: str backup_path: str
default_environment_id: Optional[str] = None default_environment_id: Optional[str] = None
logging: LoggingConfig = Field(default_factory=LoggingConfig)
# [/DEF:GlobalSettings] # [/DEF:GlobalSettings]
# [DEF:AppConfig:DataClass] # [DEF:AppConfig:DataClass]

View File

@@ -4,12 +4,32 @@
# @LAYER: Core # @LAYER: Core
# @RELATION: Used by the main application and other modules to log events. The WebSocketLogHandler is used by the WebSocket endpoint in app.py. # @RELATION: Used by the main application and other modules to log events. The WebSocketLogHandler is used by the WebSocket endpoint in app.py.
import logging import logging
import threading
from datetime import datetime from datetime import datetime
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from collections import deque from collections import deque
from contextlib import contextmanager
from logging.handlers import RotatingFileHandler
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
# Thread-local storage for belief state
_belief_state = threading.local()
# Global flag for belief state logging
_enable_belief_state = True
# [DEF:BeliefFormatter:Class]
# @PURPOSE: Custom logging formatter that adds belief state prefixes to log messages.
class BeliefFormatter(logging.Formatter):
def format(self, record):
msg = super().format(record)
anchor_id = getattr(_belief_state, 'anchor_id', None)
if anchor_id:
msg = f"[{anchor_id}][Action] {msg}"
return msg
# [/DEF:BeliefFormatter]
# Re-using LogEntry from task_manager for consistency # Re-using LogEntry from task_manager for consistency
# [DEF:LogEntry:Class] # [DEF:LogEntry:Class]
# @SEMANTICS: log, entry, record, pydantic # @SEMANTICS: log, entry, record, pydantic
@@ -22,6 +42,81 @@ class LogEntry(BaseModel):
# [/DEF] # [/DEF]
# [DEF:BeliefScope:Function]
# @PURPOSE: Context manager for structured Belief State logging.
@contextmanager
def belief_scope(anchor_id: str, message: str = ""):
# Log Entry if enabled
if _enable_belief_state:
entry_msg = f"[{anchor_id}][Entry]"
if message:
entry_msg += f" {message}"
logger.info(entry_msg)
# Set thread-local anchor_id
old_anchor = getattr(_belief_state, 'anchor_id', None)
_belief_state.anchor_id = anchor_id
try:
yield
# Log Coherence OK and Exit
logger.info(f"[{anchor_id}][Coherence:OK]")
if _enable_belief_state:
logger.info(f"[{anchor_id}][Exit]")
except Exception as e:
# Log Coherence Failed
logger.info(f"[{anchor_id}][Coherence:Failed] {str(e)}")
raise
finally:
# Restore old anchor
_belief_state.anchor_id = old_anchor
# [/DEF:BeliefScope]
# [DEF:ConfigureLogger:Function]
# @PURPOSE: Configures the logger with the provided logging settings.
# @PRE: config is a valid LoggingConfig instance.
# @POST: Logger level, handlers, and belief state flag are updated.
# @PARAM: config (LoggingConfig) - The logging configuration.
def configure_logger(config):
global _enable_belief_state
_enable_belief_state = config.enable_belief_state
# Set logger level
level = getattr(logging, config.level.upper(), logging.INFO)
logger.setLevel(level)
# Remove existing file handlers
handlers_to_remove = [h for h in logger.handlers if isinstance(h, RotatingFileHandler)]
for h in handlers_to_remove:
logger.removeHandler(h)
h.close()
# Add file handler if file_path is set
if config.file_path:
import os
from pathlib import Path
log_file = Path(config.file_path)
log_file.parent.mkdir(parents=True, exist_ok=True)
file_handler = RotatingFileHandler(
config.file_path,
maxBytes=config.max_bytes,
backupCount=config.backup_count
)
file_handler.setFormatter(BeliefFormatter(
'[%(asctime)s][%(levelname)s][%(name)s] %(message)s'
))
logger.addHandler(file_handler)
# Update existing handlers' formatters to BeliefFormatter
for handler in logger.handlers:
if not isinstance(handler, RotatingFileHandler):
handler.setFormatter(BeliefFormatter(
'[%(asctime)s][%(levelname)s][%(name)s] %(message)s'
))
# [/DEF:ConfigureLogger]
# [DEF:WebSocketLogHandler:Class] # [DEF:WebSocketLogHandler:Class]
# @SEMANTICS: logging, handler, websocket, buffer # @SEMANTICS: logging, handler, websocket, buffer
# @PURPOSE: A custom logging handler that captures log records into a buffer. It is designed to be extended for real-time log streaming over WebSockets. # @PURPOSE: A custom logging handler that captures log records into a buffer. It is designed to be extended for real-time log streaming over WebSockets.
@@ -72,7 +167,7 @@ logger = logging.getLogger("superset_tools_app")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
# Create a formatter # Create a formatter
formatter = logging.Formatter( formatter = BeliefFormatter(
'[%(asctime)s][%(levelname)s][%(name)s] %(message)s' '[%(asctime)s][%(levelname)s][%(name)s] %(message)s'
) )

View File

@@ -0,0 +1,44 @@
import pytest
from backend.src.core.logger import belief_scope, logger
def test_belief_scope_logs_entry_action_exit(caplog):
"""Test that belief_scope generates [ID][Entry], [ID][Action], and [ID][Exit] logs."""
caplog.set_level("INFO")
with belief_scope("TestFunction"):
logger.info("Doing something important")
# Check that the logs contain the expected patterns
log_messages = [record.message for record in caplog.records]
assert any("[TestFunction][Entry]" in msg for msg in log_messages), "Entry log not found"
assert any("[TestFunction][Action] Doing something important" in msg for msg in log_messages), "Action log not found"
assert any("[TestFunction][Exit]" in msg for msg in log_messages), "Exit log not found"
def test_belief_scope_error_handling(caplog):
"""Test that belief_scope logs Coherence:Failed on exception."""
caplog.set_level("INFO")
with pytest.raises(ValueError):
with belief_scope("FailingFunction"):
raise ValueError("Something went wrong")
log_messages = [record.message for record in caplog.records]
assert any("[FailingFunction][Entry]" in msg for msg in log_messages), "Entry log not found"
assert any("[FailingFunction][Coherence:Failed]" in msg for msg in log_messages), "Failed coherence log not found"
# Exit should not be logged on failure
def test_belief_scope_success_coherence(caplog):
"""Test that belief_scope logs Coherence:OK on success."""
caplog.set_level("INFO")
with belief_scope("SuccessFunction"):
pass
log_messages = [record.message for record in caplog.records]
assert any("[SuccessFunction][Coherence:OK]" in msg for msg in log_messages), "Success coherence log not found"

View File

@@ -24,7 +24,7 @@ export const options = {
app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n", app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n" error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
}, },
version_hash: "uq8l2w" version_hash: "n7gbte"
}; };
export async function get_hooks() { export async function get_hooks() {

View File

@@ -21,7 +21,14 @@
environments: [], environments: [],
settings: { settings: {
backup_path: '', backup_path: '',
default_environment_id: null default_environment_id: null,
logging: {
level: 'INFO',
file_path: 'logs/app.log',
max_bytes: 10485760,
backup_count: 5,
enable_belief_state: true
}
} }
}; };
@@ -180,10 +187,43 @@
<label for="backup_path" class="block text-sm font-medium text-gray-700">Backup Storage Path</label> <label for="backup_path" class="block text-sm font-medium text-gray-700">Backup Storage Path</label>
<input type="text" id="backup_path" bind:value={settings.settings.backup_path} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" /> <input type="text" id="backup_path" bind:value={settings.settings.backup_path} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div> </div>
<button on:click={handleSaveGlobal} class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 w-max">
Save Global Settings
</button>
</div> </div>
<h3 class="text-lg font-medium mb-4 mt-6">Logging Configuration</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="log_level" class="block text-sm font-medium text-gray-700">Log Level</label>
<select id="log_level" bind:value={settings.settings.logging.level} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2">
<option value="DEBUG">DEBUG</option>
<option value="INFO">INFO</option>
<option value="WARNING">WARNING</option>
<option value="ERROR">ERROR</option>
<option value="CRITICAL">CRITICAL</option>
</select>
</div>
<div>
<label for="log_file_path" class="block text-sm font-medium text-gray-700">Log File Path</label>
<input type="text" id="log_file_path" bind:value={settings.settings.logging.file_path} placeholder="logs/app.log" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div>
<div>
<label for="log_max_bytes" class="block text-sm font-medium text-gray-700">Max File Size (MB)</label>
<input type="number" id="log_max_bytes" bind:value={settings.settings.logging.max_bytes} min="1" step="1" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div>
<div>
<label for="log_backup_count" class="block text-sm font-medium text-gray-700">Backup Count</label>
<input type="number" id="log_backup_count" bind:value={settings.settings.logging.backup_count} min="1" step="1" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div>
<div class="md:col-span-2">
<label class="flex items-center">
<input type="checkbox" id="enable_belief_state" bind:checked={settings.settings.logging.enable_belief_state} class="h-4 w-4 text-blue-600 border-gray-300 rounded" />
<span class="ml-2 block text-sm text-gray-900">Enable Belief State Logging</span>
</label>
</div>
</div>
<button on:click={handleSaveGlobal} class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 w-max mt-4">
Save Global Settings
</button>
</section> </section>
<section class="mb-8 bg-white p-6 rounded shadow"> <section class="mb-8 bg-white p-6 rounded shadow">

View File

@@ -2,19 +2,19 @@
**Spec**: `specs/006-configurable-belief-logs/spec.md` **Spec**: `specs/006-configurable-belief-logs/spec.md`
**Plan**: `specs/006-configurable-belief-logs/plan.md` **Plan**: `specs/006-configurable-belief-logs/plan.md`
**Status**: Pending **Status**: Completed
## Phase 1: Setup ## Phase 1: Setup
*Goal: Initialize project structure for logging.* *Goal: Initialize project structure for logging.*
- [ ] T001 Ensure logs directory exists at `logs/` (relative to project root) - [x] T001 Ensure logs directory exists at `logs/` (relative to project root)
## Phase 2: Foundational ## Phase 2: Foundational
*Goal: Define data models and infrastructure required for logging configuration.* *Goal: Define data models and infrastructure required for logging configuration.*
- [ ] T002 Define `LoggingConfig` model in `backend/src/core/config_models.py` - [x] T002 Define `LoggingConfig` model in `backend/src/core/config_models.py`
- [ ] T003 Update `GlobalSettings` model to include `logging` field in `backend/src/core/config_models.py` - [x] T003 Update `GlobalSettings` model to include `logging` field in `backend/src/core/config_models.py`
- [ ] T004 Update `ConfigManager` to handle logging configuration persistence in `backend/src/core/config_manager.py` - [x] T004 Update `ConfigManager` to handle logging configuration persistence in `backend/src/core/config_manager.py`
## Phase 3: User Story 1 - Structured Belief State Logging ## Phase 3: User Story 1 - Structured Belief State Logging
*Goal: Implement the core "Belief State" logging logic with context managers.* *Goal: Implement the core "Belief State" logging logic with context managers.*
@@ -22,9 +22,9 @@
**Independent Test**: Run `pytest backend/tests/test_logger.py` and verify `belief_scope` generates `[ID][Entry]`, `[ID][Action]`, and `[ID][Exit]` logs. **Independent Test**: Run `pytest backend/tests/test_logger.py` and verify `belief_scope` generates `[ID][Entry]`, `[ID][Action]`, and `[ID][Exit]` logs.
- [ ] T005 [US1] Create unit tests for belief state logging in `backend/tests/test_logger.py` - [x] T005 [US1] Create unit tests for belief state logging in `backend/tests/test_logger.py`
- [ ] T006 [US1] Implement `belief_scope` context manager in `backend/src/core/logger.py` - [x] T006 [US1] Implement `belief_scope` context manager in `backend/src/core/logger.py`
- [ ] T007 [US1] Implement log formatting and smart filtering (suppress Entry/Exit if disabled) in `backend/src/core/logger.py` - [x] T007 [US1] Implement log formatting and smart filtering (suppress Entry/Exit if disabled) in `backend/src/core/logger.py`
## Phase 4: User Story 2 - Configurable Logging Settings ## Phase 4: User Story 2 - Configurable Logging Settings
*Goal: Expose logging configuration to the user via API and UI.* *Goal: Expose logging configuration to the user via API and UI.*
@@ -32,15 +32,15 @@
**Independent Test**: Update settings via API/UI and verify log level changes (e.g., DEBUG logs appear) and file rotation is active. **Independent Test**: Update settings via API/UI and verify log level changes (e.g., DEBUG logs appear) and file rotation is active.
- [ ] T008 [US2] Implement `configure_logger` function to apply settings (level, file, rotation) in `backend/src/core/logger.py` - [x] T008 [US2] Implement `configure_logger` function to apply settings (level, file, rotation) in `backend/src/core/logger.py`
- [ ] T009 [US2] Update settings API endpoint to handle `logging` updates in `backend/src/api/routes/settings.py` - [x] T009 [US2] Update settings API endpoint to handle `logging` updates in `backend/src/api/routes/settings.py`
- [ ] T010 [P] [US2] Add Logging configuration section (Level, File Path, Enable Belief State) to `frontend/src/pages/Settings.svelte` - [x] T010 [P] [US2] Add Logging configuration section (Level, File Path, Enable Belief State) to `frontend/src/pages/Settings.svelte`
## Final Phase: Polish & Cross-Cutting Concerns ## Final Phase: Polish & Cross-Cutting Concerns
*Goal: Verify system stability and cleanup.* *Goal: Verify system stability and cleanup.*
- [ ] T011 Verify log rotation works by generating dummy logs (manual verification) - [x] T011 Verify log rotation works by generating dummy logs (manual verification)
- [ ] T012 Ensure default configuration provides a sensible out-of-the-box experience - [x] T012 Ensure default configuration provides a sensible out-of-the-box experience
## Dependencies ## Dependencies