log refactor

This commit is contained in:
2026-05-12 19:30:15 +03:00
parent 1d59df2233
commit b17b5333c7
84 changed files with 5827 additions and 3908 deletions

View File

@@ -30,7 +30,10 @@ from ...schemas.auth import (
)
from ...models.auth import User, Role, ADGroupMapping
from ...dependencies import has_permission, get_plugin_loader
from ...core.cot_logger import MarkerLogger
from ...core.logger import logger, belief_scope
log = MarkerLogger("AdminApi")
from ...services.rbac_permission_catalog import (
discover_declared_permissions,
sync_permission_catalog,
@@ -167,25 +170,17 @@ async def delete_user(
_=Depends(has_permission("admin:users", "WRITE")),
):
with belief_scope("api.admin.delete_user"):
logger.info(
f"[DEBUG] Attempting to delete user context={{'user_id': '{user_id}'}}"
)
log.reason(f"Attempting to delete user {user_id}")
repo = AuthRepository(db)
user = repo.get_user_by_id(user_id)
if not user:
logger.warning(
f"[DEBUG] User not found for deletion context={{'user_id': '{user_id}'}}"
)
log.explore(f"User not found for deletion: {user_id}", error="User not found")
raise HTTPException(status_code=404, detail="User not found")
logger.info(
f"[DEBUG] Found user to delete context={{'username': '{user.username}'}}"
)
log.reason(f"Found user to delete: {user.username}")
db.delete(user)
db.commit()
logger.info(
f"[DEBUG] Successfully deleted user context={{'user_id': '{user_id}'}}"
)
log.reflect(f"Successfully deleted user {user_id}")
return None
@@ -348,10 +343,7 @@ async def list_permissions(
db=db, declared_permissions=declared_permissions
)
if inserted_count > 0:
logger.info(
"[api.admin.list_permissions][Action] Synchronized %s missing RBAC permissions into auth catalog",
inserted_count,
)
log.reason(f"Synchronized {inserted_count} missing RBAC permissions into auth catalog")
repo = AuthRepository(db)
return repo.list_permissions()

View File

@@ -18,7 +18,10 @@ from src.dependencies import (
get_current_user, has_permission,
)
from src.core.database import get_db
from src.core.cot_logger import MarkerLogger
from src.core.logger import logger, belief_scope
log = MarkerLogger("DashboardListing")
from src.models.auth import User
from src.services.profile_service import ProfileService
from ._helpers import _normalize_filter_values, _dashboard_git_filter_value
@@ -73,16 +76,16 @@ async def get_dashboards(
f"page_context={page_context}, applyp={apply_profile_default}, overhide={override_show_all}",
):
if page < 1:
logger.error(f"[get_dashboards][Coherence:Failed] Invalid page: {page}")
log.explore(f"Invalid page: {page}", error="Page must be >= 1")
raise HTTPException(status_code=400, detail="Page must be >= 1")
if page_size < 1 or page_size > 100:
logger.error(f"[get_dashboards][Coherence:Failed] Invalid page_size: {page_size}")
log.explore(f"Invalid page_size: {page_size}", error="Page size must be between 1 and 100")
raise HTTPException(status_code=400, detail="Page size must be between 1 and 100")
environments = config_manager.get_environments()
env = next((e for e in environments if e.id == env_id), None)
if not env:
logger.error(f"[get_dashboards][Coherence:Failed] Environment not found: {env_id}")
log.explore(f"Environment not found: {env_id}", error="Environment not found")
raise HTTPException(status_code=404, detail="Environment not found")
bound_username: Optional[str] = None
@@ -151,7 +154,8 @@ async def get_dashboards(
)
except Exception as profile_error:
logger.explore(
f"[EXPLORE] Profile preference unavailable; continuing without profile-default filter: {profile_error}"
f"Profile preference unavailable; continuing without profile-default filter",
error=str(profile_error),
)
try:
@@ -193,10 +197,7 @@ async def get_dashboards(
total = page_payload["total"]
total_pages = page_payload["total_pages"]
except Exception as page_error:
logger.warning(
"[get_dashboards][Action] Page-based fetch failed; using compatibility fallback: %s",
page_error,
)
log.explore(f"Page-based fetch failed; using compatibility fallback", error=str(page_error))
if can_apply_slug_filter:
dashboards = await resource_service.get_dashboards_with_status(
env,
@@ -251,7 +252,7 @@ async def get_dashboards(
if not actor_aliases:
actor_aliases = [bound_username]
logger.reason(
"[REASON] Applying profile actor filter "
f"Applying profile actor filter "
f"(env={env_id}, bound_username={bound_username}, actor_aliases={actor_aliases!r}, "
f"dashboards_before={len(dashboards)})"
)
@@ -269,7 +270,7 @@ async def get_dashboards(
)
if index < max_actor_samples:
logger.reflect(
"[REFLECT] Profile actor filter sample "
f"Profile actor filter sample "
f"(env={env_id}, dashboard_id={dashboard.get('id')}, "
f"bound_username={bound_username!r}, actor_aliases={actor_aliases!r}, "
f"owners={owners_value!r}, created_by={created_by_value!r}, "
@@ -279,7 +280,7 @@ async def get_dashboards(
filtered_dashboards.append(dashboard)
logger.reflect(
"[REFLECT] Profile actor filter summary "
f"Profile actor filter summary "
f"(env={env_id}, bound_username={bound_username!r}, "
f"dashboards_before={len(dashboards)}, dashboards_after={len(filtered_dashboards)})"
)
@@ -365,10 +366,7 @@ async def get_dashboards(
end_idx = start_idx + page_size
paginated_dashboards = dashboards[start_idx:end_idx]
logger.info(
f"[get_dashboards][Coherence:OK] Returning {len(paginated_dashboards)} dashboards "
f"(page {page}/{total_pages}, total: {total}, profile_filter_applied={effective_profile_filter.applied})"
)
log.reflect(f"Returning {len(paginated_dashboards)} dashboards (page {page}/{total_pages}, total: {total}, profile_filter_applied={effective_profile_filter.applied})")
response_dashboards = _project_dashboard_response_items(
paginated_dashboards
@@ -386,9 +384,7 @@ async def get_dashboards(
except HTTPException:
raise
except Exception as e:
logger.error(
f"[get_dashboards][Coherence:Failed] Failed to fetch dashboards: {e}"
)
log.explore("Failed to fetch dashboards", error=str(e))
raise HTTPException(
status_code=503, detail=f"Failed to fetch dashboards: {str(e)}"
)

View File

@@ -13,8 +13,9 @@ from fastapi import APIRouter, Depends, Header, HTTPException, Query, Response,
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from src.core.cot_logger import MarkerLogger
from src.core.database import get_db
from src.core.logger import belief_scope, logger
from src.core.logger import belief_scope
from src.dependencies import (
get_config_manager,
get_current_user,
@@ -69,6 +70,8 @@ from src.services.dataset_review.repositories.session_repository import (
DatasetReviewSessionVersionConflictError,
)
log = MarkerLogger("DatasetReviewDeps")
# [DEF:StartSessionRequest:Class]
# @COMPLEXITY: 1
@@ -445,7 +448,7 @@ def _enforce_session_version(repository, session, expected_version):
try:
repository.require_session_version(session, expected_version)
except DatasetReviewSessionVersionConflictError as exc:
logger.explore("Dataset review optimistic-lock conflict detected", extra={"session_id": exc.session_id, "expected_version": exc.expected_version, "actual_version": exc.actual_version})
log.explore("Dataset review optimistic-lock conflict detected", payload={"session_id": exc.session_id, "expected_version": exc.expected_version, "actual_version": exc.actual_version}, error="Optimistic lock conflict")
raise _build_session_version_conflict_http_exception(exc) from exc
return session

View File

@@ -10,7 +10,8 @@ from typing import Any, List, Optional, Union, cast
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from src.core.logger import belief_scope, logger
from src.core.cot_logger import MarkerLogger
from src.core.logger import belief_scope
from src.dependencies import get_config_manager, get_current_user, has_permission
from src.models.auth import User
from src.models.dataset_review import (
@@ -89,6 +90,8 @@ from src.api.routes.dataset_review_pkg._dependencies import (
_build_session_version_conflict_http_exception,
)
log = MarkerLogger("DatasetReviewRoutes")
router = APIRouter(prefix="/api/dataset-orchestration", tags=["Dataset Orchestration"])
@@ -110,17 +113,17 @@ async def list_sessions(
current_user: User = Depends(get_current_user),
):
with belief_scope("dataset_review.list_sessions"):
logger.reason(
log.reason(
"Listing dataset review sessions",
extra={"user_id": current_user.id, "page": page, "page_size": page_size},
payload={"user_id": current_user.id, "page": page, "page_size": page_size},
)
sessions = repository.list_sessions_for_user(current_user.id)
start = (page - 1) * page_size
end = start + page_size
items = [_serialize_session_summary(s) for s in sessions[start:end]]
logger.reflect(
log.reflect(
"Session page assembled",
extra={
payload={
"user_id": current_user.id,
"returned": len(items),
"total": len(sessions),
@@ -156,9 +159,9 @@ async def start_session(
current_user: User = Depends(get_current_user),
):
with belief_scope("start_session"):
logger.reason(
log.reason(
"Starting dataset review session",
extra={
payload={
"user_id": current_user.id,
"environment_id": request.environment_id,
},
@@ -173,10 +176,7 @@ async def start_session(
)
)
except ValueError as exc:
logger.explore(
"Session start rejected",
extra={"user_id": current_user.id, "error": str(exc)},
)
log.explore("Session start rejected", error="Session start rejected by orchestrator", payload={"user_id": current_user.id, "error": str(exc)})
detail = str(exc)
sc = (
status.HTTP_404_NOT_FOUND
@@ -184,8 +184,8 @@ async def start_session(
else status.HTTP_400_BAD_REQUEST
)
raise HTTPException(status_code=sc, detail=detail) from exc
logger.reflect(
"Session started", extra={"session_id": result.session.session_id}
log.reflect(
"Session started", payload={"session_id": result.session.session_id}
)
return _serialize_session_summary(result.session)

View File

@@ -13,7 +13,9 @@ from typing import Optional
from fastapi import HTTPException
from sqlalchemy.orm import Session
from src.core.logger import logger
from src.core.cot_logger import MarkerLogger
log = MarkerLogger("GitHelpers")
from src.core.superset_client import SupersetClient
from src.dependencies import get_config_manager
from src.models.auth import User
@@ -52,7 +54,7 @@ def _build_no_repo_status_payload() -> dict:
# @PRE: `error` is a non-HTTPException instance.
# @POST: Raises HTTPException(500) with route-specific context.
def _handle_unexpected_git_route_error(route_name: str, error: Exception) -> None:
logger.error(f"[{route_name}][Coherence:Failed] {error}")
log.explore(f"{route_name} failed: {error}", error=str(error))
raise HTTPException(status_code=500, detail=f"{route_name} failed: {str(error)}")
# [/DEF:_handle_unexpected_git_route_error:Function]
@@ -66,8 +68,8 @@ def _resolve_repository_status(dashboard_id: int) -> dict:
git_service = get_git_service()
repo_path = git_service._get_repo_path(dashboard_id)
if not os.path.exists(repo_path):
logger.debug(
f"[get_repository_status][Action] Repository is not initialized for dashboard {dashboard_id}"
log.reason(
f"Repository is not initialized for dashboard {dashboard_id}"
)
return _build_no_repo_status_payload()
@@ -75,8 +77,8 @@ def _resolve_repository_status(dashboard_id: int) -> dict:
return git_service.get_status(dashboard_id)
except HTTPException as e:
if e.status_code == 404:
logger.debug(
f"[get_repository_status][Action] Repository is not initialized for dashboard {dashboard_id}"
log.reason(
f"Repository is not initialized for dashboard {dashboard_id}"
)
return _build_no_repo_status_payload()
raise
@@ -300,11 +302,7 @@ def _resolve_current_user_git_identity(
.first()
)
except Exception as resolve_error:
logger.warning(
"[_resolve_current_user_git_identity][Action] Failed to load profile preference for user %s: %s",
user_id,
resolve_error,
)
log.explore(f"Failed to load profile preference for user {user_id}: {resolve_error}", error=str(resolve_error))
return None
if not preference:

View File

@@ -10,7 +10,10 @@ from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session
from src.core.database import get_db
from src.core.logger import belief_scope, logger
from src.core.cot_logger import MarkerLogger
from src.core.logger import belief_scope
log = MarkerLogger("GitRepoOps")
from src.dependencies import get_config_manager, get_current_user, has_permission
from src.models.auth import User
from src.models.git import GitRepository, GitServerConfig
@@ -133,23 +136,13 @@ async def pull_changes(
config_url = config_row.url
config_provider = config_row.provider
except Exception as diagnostics_error:
logger.warning(
"[pull_changes][Action] Failed to load repository binding diagnostics for dashboard %s: %s",
dashboard_id,
diagnostics_error,
)
logger.info(
"[pull_changes][Action] Route diagnostics dashboard_ref=%s env_id=%s resolved_dashboard_id=%s "
"binding_exists=%s binding_local_path=%s binding_remote_url=%s binding_config_id=%s config_provider=%s config_url=%s",
dashboard_ref,
env_id,
dashboard_id,
bool(db_repo),
(db_repo.local_path if db_repo else None),
(db_repo.remote_url if db_repo else None),
(db_repo.config_id if db_repo else None),
config_provider,
config_url,
log.explore(f"Failed to load repository binding diagnostics for dashboard {dashboard_id}: {diagnostics_error}", error=str(diagnostics_error))
log.reason(
f"Route diagnostics dashboard_ref={dashboard_ref} env_id={env_id} resolved_dashboard_id={dashboard_id} "
f"binding_exists={bool(db_repo)} binding_local_path={(db_repo.local_path if db_repo else None)} "
f"binding_remote_url={(db_repo.remote_url if db_repo else None)} "
f"binding_config_id={(db_repo.config_id if db_repo else None)} "
f"config_provider={config_provider} config_url={config_url}"
)
_apply_git_identity_from_profile(dashboard_id, db, current_user)
_gs.pull_changes(dashboard_id)
@@ -197,11 +190,7 @@ async def get_repository_status_batch(
with belief_scope("get_repository_status_batch"):
dashboard_ids = list(dict.fromkeys(request.dashboard_ids))
if len(dashboard_ids) > MAX_REPOSITORY_STATUS_BATCH:
logger.warning(
"[get_repository_status_batch][Action] Batch size %s exceeds limit %s. Truncating request.",
len(dashboard_ids),
MAX_REPOSITORY_STATUS_BATCH,
)
log.explore(f"Batch size {len(dashboard_ids)} exceeds limit {MAX_REPOSITORY_STATUS_BATCH}. Truncating request.", error="Batch size exceeds limit")
dashboard_ids = dashboard_ids[:MAX_REPOSITORY_STATUS_BATCH]
statuses = {}
@@ -215,9 +204,7 @@ async def get_repository_status_batch(
"sync_status": "ERROR",
}
except Exception as e:
logger.error(
f"[get_repository_status_batch][Coherence:Failed] Failed for dashboard {dashboard_id}: {e}"
)
log.explore(f"Failed for dashboard {dashboard_id}: {e}", error=str(e))
statuses[str(dashboard_id)] = {
**_build_no_repo_status_payload(),
"sync_state": "ERROR",

View File

@@ -10,7 +10,10 @@ from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session
from src.core.database import get_db
from src.core.logger import belief_scope, logger
from src.core.cot_logger import MarkerLogger
from src.core.logger import belief_scope
log = MarkerLogger("GitRepoRoutes")
from src.dependencies import get_config_manager, get_current_user, has_permission
from src.models.auth import User
from src.models.git import GitRepository, GitServerConfig
@@ -64,9 +67,7 @@ async def init_repository(
raise HTTPException(status_code=404, detail="Git configuration not found")
try:
logger.info(
f"[init_repository][Action] Initializing repo for dashboard {dashboard_id}"
)
log.reason(f"Initializing repo for dashboard {dashboard_id}")
_gs.init_repo(
dashboard_id,
init_data.remote_url,
@@ -97,15 +98,10 @@ async def init_repository(
db_repo.current_branch = "dev"
db.commit()
logger.info(
f"[init_repository][Coherence:OK] Repository initialized for dashboard {dashboard_id}"
)
return {"status": "success", "message": "Repository initialized"}
except Exception as e:
db.rollback()
logger.error(
f"[init_repository][Coherence:Failed] Failed to init repository: {e}"
)
log.explore("Failed to init repository", error=str(e))
if isinstance(e, HTTPException):
raise
_handle_unexpected_git_route_error("init_repository", e)

View File

@@ -11,7 +11,9 @@
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List, Optional
import httpx
from ...core.logger import logger
from ...core.cot_logger import MarkerLogger
log = MarkerLogger("LlmRoutes")
from ...schemas.auth import User
from ...dependencies import get_current_user as get_current_active_user
from ...plugins.llm_analysis.models import (
@@ -80,8 +82,8 @@ async def get_providers(
"""
Get all LLM provider configurations.
"""
logger.info(
f"[llm_routes][get_providers][Action] Fetching providers for user: {current_user.username}"
log.reason(
f"Fetching providers for user: {current_user.username}"
)
service = LLMProviderService(db)
providers = service.get_all_providers()
@@ -289,8 +291,8 @@ async def fetch_models_for_provider(
3. {base_url}/api/tags (Ollama format)
Returns empty list if none respond — user can enter model name manually.
"""
logger.info(
f"[llm_routes][fetch_models] Fetching models for {request.base_url}"
log.reason(
f"Fetching models for {request.base_url}"
)
# Resolve API key: prefer direct, fall back to stored key via provider_id
@@ -300,9 +302,7 @@ async def fetch_models_for_provider(
svc = LLMProviderService(db)
api_key = svc.get_decrypted_api_key(request.provider_id)
except Exception:
logger.warning(
f"[llm_routes][fetch_models] Could not resolve API key for provider {request.provider_id}"
)
log.explore(f"Could not resolve API key for provider {request.provider_id}", error="API key resolution failed")
base = request.base_url.rstrip("/")
@@ -343,8 +343,8 @@ async def fetch_models_for_provider(
for m in raw
)
if models:
logger.info(
f"[llm_routes][fetch_models] Found {len(models)} models at {url}"
log.reason(
f"Found {len(models)} models at {url}"
)
return FetchModelsResponse(
models=models, total=len(models)
@@ -359,8 +359,8 @@ async def fetch_models_for_provider(
for m in models_list
)
if models:
logger.info(
f"[llm_routes][fetch_models] Found {len(models)} Ollama models at {url}"
log.reason(
f"Found {len(models)} Ollama models at {url}"
)
return FetchModelsResponse(
models=models, total=len(models)
@@ -374,16 +374,11 @@ async def fetch_models_for_provider(
last_error = f"Error at {url}: {str(e)[:100]}"
# All endpoints failed — return empty list with no error (user can type manually)
logger.warning(
f"[llm_routes][fetch_models] No model endpoint responded for {request.base_url}. "
f"Last error: {last_error}"
)
log.explore(f"No model endpoint responded for {request.base_url}. Last error: {last_error}", error=f"No model endpoint responded for {request.base_url}")
return FetchModelsResponse(models=[], total=0)
except Exception as e:
logger.error(
f"[llm_routes][fetch_models] Unexpected error for {request.base_url}: {e}"
)
log.explore(f"Unexpected error for {request.base_url}: {e}", error=str(e))
return FetchModelsResponse(models=[], total=0)
@@ -402,8 +397,8 @@ async def test_connection(
current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
logger.info(
f"[llm_routes][test_connection][Action] Testing connection for provider_id: {provider_id}"
log.reason(
f"Testing connection for provider_id: {provider_id}"
)
"""
Test connection to an LLM provider.
@@ -419,9 +414,7 @@ async def test_connection(
# Check if API key was successfully decrypted
if not api_key:
logger.error(
f"[llm_routes][test_connection] Failed to decrypt API key for provider {provider_id}"
)
log.explore(f"Failed to decrypt API key for provider {provider_id}", error="API key decryption failed")
raise HTTPException(
status_code=500,
detail="Failed to decrypt API key. The provider may have been encrypted with a different encryption key. Please update the provider with a new API key.",
@@ -459,8 +452,8 @@ async def test_provider_config(
"""
from ...plugins.llm_analysis.service import LLMClient
logger.info(
f"[llm_routes][test_provider_config][Action] Testing config for {config.name}"
log.reason(
f"Testing config for {config.name}"
)
# Check if API key is provided

View File

@@ -346,7 +346,6 @@ async def trigger_sync_now(
_=Depends(has_permission("plugin:migration", "EXECUTE")),
):
with belief_scope("trigger_sync_now"):
from ...core.logger import logger
from ...models.mapping import Environment as EnvironmentModel
config = config_manager.get_config()
@@ -366,8 +365,8 @@ async def trigger_sync_now(
credentials_id=env.id, # Use env.id as credentials reference
)
db.add(db_env)
logger.info(
f"[trigger_sync_now][Action] Created environment row for {env.id}"
logger.reason(
f"Created environment row for {env.id}"
)
else:
existing.name = env.name
@@ -382,10 +381,10 @@ async def trigger_sync_now(
client = SupersetClient(env)
service.sync_environment(env.id, client)
results["synced"].append(env.id)
logger.info(f"[trigger_sync_now][Action] Synced environment {env.id}")
logger.reason(f"Synced environment {env.id}")
except Exception as e:
results["failed"].append({"env_id": env.id, "error": str(e)})
logger.error(f"[trigger_sync_now][Error] Failed to sync {env.id}: {e}")
logger.explore(f"Failed to sync {env.id}", error=str(e))
return {
"status": "completed",

View File

@@ -22,7 +22,8 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from ...core.database import get_db
from ...core.logger import logger, belief_scope
from ...core.logger import belief_scope
from ...core.cot_logger import MarkerLogger
from ...dependencies import (
get_config_manager,
get_current_user,
@@ -43,6 +44,8 @@ from ...services.profile_service import (
)
# [/SECTION]
log = MarkerLogger("Profile")
router = APIRouter(prefix="/api/profile", tags=["profile"])
@@ -73,7 +76,7 @@ async def get_preferences(
plugin_loader=Depends(get_plugin_loader),
):
with belief_scope("profile.get_preferences", f"user_id={current_user.id}"):
logger.reason("[REASON] Resolving current user preference")
log.reason("Resolving current user preference")
service = _get_profile_service(db, config_manager, plugin_loader)
return service.get_my_preference(current_user)
# [/DEF:get_preferences:Function]
@@ -95,13 +98,13 @@ async def update_preferences(
with belief_scope("profile.update_preferences", f"user_id={current_user.id}"):
service = _get_profile_service(db, config_manager, plugin_loader)
try:
logger.reason("[REASON] Attempting preference save")
log.reason("Attempting preference save")
return service.update_my_preference(current_user=current_user, payload=payload)
except ProfileValidationError as exc:
logger.reflect("[REFLECT] Preference validation failed")
log.reflect("Preference validation failed")
raise HTTPException(status_code=422, detail=exc.errors) from exc
except ProfileAuthorizationError as exc:
logger.explore("[EXPLORE] Cross-user mutation guard blocked request")
log.explore("Cross-user mutation guard blocked request", error="User attempted to mutate another user's resource")
raise HTTPException(status_code=403, detail=str(exc)) from exc
# [/DEF:update_preferences:Function]
@@ -138,13 +141,13 @@ async def lookup_superset_accounts(
sort_order=sort_order,
)
try:
logger.reason("[REASON] Executing Superset account lookup")
log.reason("Executing Superset account lookup")
return service.lookup_superset_accounts(
current_user=current_user,
request=lookup_request,
)
except EnvironmentNotFoundError as exc:
logger.explore("[EXPLORE] Lookup request references unknown environment")
log.explore("Lookup request references unknown environment", error="EnvironmentNotFoundError raised during Superset account lookup")
raise HTTPException(status_code=404, detail=str(exc)) from exc
# [/DEF:lookup_superset_accounts:Function]

View File

@@ -19,7 +19,10 @@ from ...core.config_models import AppConfig, Environment, GlobalSettings, Loggin
from ...models.storage import StorageConfig
from ...dependencies import get_config_manager, has_permission
from ...core.config_manager import ConfigManager
from ...core.logger import logger, belief_scope
from ...core.logger import belief_scope
from ...core.cot_logger import MarkerLogger
log = MarkerLogger("Settings")
from ...core.superset_client import SupersetClient
from ...services.llm_prompt_templates import normalize_llm_settings
from ...models.llm import ValidationPolicy
@@ -98,7 +101,7 @@ async def get_settings(
_=Depends(has_permission("admin:settings", "READ")),
):
with belief_scope("get_settings"):
logger.info("[get_settings][Entry] Fetching all settings")
log.reason("Fetching all settings")
config = config_manager.get_config().copy(deep=True)
config.settings.llm = normalize_llm_settings(config.settings.llm)
# Mask passwords
@@ -141,7 +144,7 @@ async def update_global_settings(
_=Depends(has_permission("admin:settings", "WRITE")),
):
with belief_scope("update_global_settings"):
logger.info("[update_global_settings][Entry] Updating global settings")
log.reason("Updating global settings")
config_manager.update_global_settings(settings)
return settings
@@ -204,7 +207,7 @@ async def get_environments(
_=Depends(has_permission("admin:settings", "READ")),
):
with belief_scope("get_environments"):
logger.info("[get_environments][Entry] Fetching environments")
log.reason("Fetching environments")
environments = config_manager.get_environments()
return [
env.copy(update={"url": _normalize_superset_env_url(env.url)})
@@ -229,16 +232,14 @@ async def add_environment(
_=Depends(has_permission("admin:settings", "WRITE")),
):
with belief_scope("add_environment"):
logger.info(f"[add_environment][Entry] Adding environment {env.id}")
log.reason("Adding environment", payload={"env_id": env.id})
env = env.copy(update={"url": _normalize_superset_env_url(env.url)})
# Validate connection before adding (fast path)
try:
_validate_superset_connection_fast(env)
except Exception as e:
logger.error(
f"[add_environment][Coherence:Failed] Connection validation failed: {e}"
)
log.explore("Connection validation failed", payload={"env_id": env.id}, error=str(e))
raise HTTPException(
status_code=400, detail=f"Connection validation failed: {e}"
)
@@ -265,7 +266,7 @@ async def update_environment(
config_manager: ConfigManager = Depends(get_config_manager),
):
with belief_scope("update_environment"):
logger.info(f"[update_environment][Entry] Updating environment {id}")
log.reason("Updating environment", payload={"id": id})
env = env.copy(update={"url": _normalize_superset_env_url(env.url)})
@@ -282,9 +283,7 @@ async def update_environment(
try:
_validate_superset_connection_fast(env_to_validate)
except Exception as e:
logger.error(
f"[update_environment][Coherence:Failed] Connection validation failed: {e}"
)
log.explore("Connection validation failed", payload={"id": id}, error=str(e))
raise HTTPException(
status_code=400, detail=f"Connection validation failed: {e}"
)
@@ -308,7 +307,7 @@ async def delete_environment(
id: str, config_manager: ConfigManager = Depends(get_config_manager)
):
with belief_scope("delete_environment"):
logger.info(f"[delete_environment][Entry] Deleting environment {id}")
log.reason("Deleting environment", payload={"id": id})
config_manager.delete_environment(id)
return {"message": f"Environment {id} deleted"}
@@ -328,7 +327,7 @@ async def test_environment_connection(
id: str, config_manager: ConfigManager = Depends(get_config_manager)
):
with belief_scope("test_environment_connection"):
logger.info(f"[test_environment_connection][Entry] Testing environment {id}")
log.reason("Testing environment connection", payload={"id": id})
# Find environment
env = next((e for e in config_manager.get_environments() if e.id == id), None)
@@ -338,14 +337,10 @@ async def test_environment_connection(
try:
_validate_superset_connection_fast(env)
logger.info(
f"[test_environment_connection][Coherence:OK] Connection successful for {id}"
)
log.reflect("Connection successful", payload={"id": id})
return {"status": "success", "message": "Connection successful"}
except Exception as e:
logger.error(
f"[test_environment_connection][Coherence:Failed] Connection failed for {id}: {e}"
)
log.explore("Connection failed", payload={"id": id}, error=str(e))
return {"status": "error", "message": str(e)}
@@ -389,9 +384,7 @@ async def update_logging_config(
_=Depends(has_permission("admin:settings", "WRITE")),
):
with belief_scope("update_logging_config"):
logger.info(
f"[update_logging_config][Entry] Updating logging config: level={config.level}, task_log_level={config.task_log_level}"
)
log.reason("Updating logging config", payload={"level": config.level, "task_log_level": config.task_log_level})
# Get current settings and update logging config
settings = config_manager.get_config().settings
@@ -444,7 +437,7 @@ async def get_consolidated_settings(
_=Depends(has_permission("admin:settings", "READ")),
):
with belief_scope("get_consolidated_settings"):
logger.reason("Fetching consolidated settings payload")
log.reason("Fetching consolidated settings payload")
config = config_manager.get_config()
@@ -491,9 +484,9 @@ async def get_consolidated_settings(
notifications=notifications_payload,
features=config.settings.features.model_dump(),
)
logger.reflect(
log.reflect(
"Consolidated settings payload assembled",
extra={
payload={
"environment_count": len(response_payload.environments),
"provider_count": len(response_payload.llm_providers),
},
@@ -516,9 +509,7 @@ async def update_consolidated_settings(
_=Depends(has_permission("admin:settings", "WRITE")),
):
with belief_scope("update_consolidated_settings"):
logger.info(
"[update_consolidated_settings][Entry] Applying consolidated settings patch"
)
log.reason("Applying consolidated settings patch")
current_config = config_manager.get_config()
current_settings = current_config.settings

View File

@@ -13,7 +13,7 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from ....core.database import get_db
from ....core.logger import logger, belief_scope
from ....core.cot_logger import MarkerLogger
from ....schemas.auth import User
from ....dependencies import get_current_user, has_permission
from ....plugins.translate.dictionary import DictionaryManager
@@ -25,6 +25,7 @@ from ....schemas.translate import (
from ._router import router
log = MarkerLogger("TranslateCorrectionRoutes")
# ============================================================
# Corrections
@@ -44,7 +45,7 @@ async def submit_correction(
db: Session = Depends(get_db),
):
"""Submit a term correction from a run result review."""
logger.info(f"[translate_routes][submit_correction] User: {current_user.username}")
log.reason("Correction request", payload={"user": current_user.username})
if not payload.dictionary_id:
raise HTTPException(status_code=422, detail="dictionary_id is required")
try:
@@ -78,7 +79,7 @@ async def submit_bulk_corrections(
db: Session = Depends(get_db),
):
"""Submit multiple term corrections atomically."""
logger.info(f"[translate_routes][submit_bulk_corrections] User: {current_user.username}")
log.reason("Bulk correction request", payload={"user": current_user.username})
try:
corr_list = []
for c in payload.corrections:

View File

@@ -13,8 +13,11 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from ....core.database import get_db
from ....core.logger import logger, belief_scope
from ....core.logger import belief_scope
from ....core.cot_logger import MarkerLogger
from ....schemas.auth import User
log = MarkerLogger("TranslateDictRoutes")
from ....dependencies import get_current_user, has_permission
from ....plugins.translate.dictionary import DictionaryManager
from ....schemas.translate import (
@@ -49,12 +52,12 @@ async def list_dictionaries(
):
"""List all terminology dictionaries."""
with belief_scope("list_dictionaries"):
logger.reason(f"User: {current_user.username}")
log.reason("List dictionaries", payload={"user": current_user.username})
dicts, total = DictionaryManager.list_dictionaries(db, page=page, page_size=page_size)
dict_ids = [d.id for d in dicts]
counts = _get_dictionary_entry_counts(db, dict_ids) if dict_ids else {}
items = [_dict_to_response(d, counts.get(d.id, 0)) for d in dicts]
logger.reflect("Dictionaries listed", {"total": total, "returned": len(items)})
log.reflect("Dictionaries listed", payload={"total": total, "returned": len(items)})
return {"items": items, "total": total, "page": page, "page_size": page_size}
# #endregion list_dictionaries
@@ -74,12 +77,12 @@ async def get_dictionary(
):
"""Get a terminology dictionary by ID."""
with belief_scope("get_dictionary"):
logger.reason(f"Dict: {dictionary_id}, User: {current_user.username}")
log.reason("Get dictionary", payload={"dict_id": dictionary_id, "user": current_user.username})
try:
d = DictionaryManager.get_dictionary(db, dictionary_id)
from ....models.translate import DictionaryEntry
entry_count = db.query(DictionaryEntry.id).filter(DictionaryEntry.dictionary_id == dictionary_id).count()
logger.reflect("Dictionary found", {"id": dictionary_id})
log.reflect("Dictionary found", payload={"id": dictionary_id})
return _dict_to_response(d, entry_count)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
@@ -101,7 +104,7 @@ async def create_dictionary(
):
"""Create a new terminology dictionary."""
with belief_scope("create_dictionary"):
logger.reason(f"User: {current_user.username}")
log.reason("Create dictionary", payload={"user": current_user.username})
d = DictionaryManager.create_dictionary(
db,
name=payload.name,
@@ -111,7 +114,7 @@ async def create_dictionary(
description=payload.description,
is_active=payload.is_active,
)
logger.reflect("Dictionary created", {"id": d.id})
log.reflect("Dictionary created", payload={"id": d.id})
return _dict_to_response(d, 0)
# #endregion create_dictionary
@@ -132,7 +135,7 @@ async def update_dictionary(
):
"""Update a terminology dictionary."""
with belief_scope("update_dictionary"):
logger.reason(f"Dict: {dictionary_id}, User: {current_user.username}")
log.reason("Update dictionary", payload={"dict_id": dictionary_id, "user": current_user.username})
try:
d = DictionaryManager.update_dictionary(
db,
@@ -145,7 +148,7 @@ async def update_dictionary(
)
from ....models.translate import DictionaryEntry
entry_count = db.query(DictionaryEntry.id).filter(DictionaryEntry.dictionary_id == dictionary_id).count()
logger.reflect("Dictionary updated", {"id": dictionary_id})
log.reflect("Dictionary updated", payload={"id": dictionary_id})
return _dict_to_response(d, entry_count)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
@@ -167,10 +170,10 @@ async def delete_dictionary(
):
"""Delete a terminology dictionary."""
with belief_scope("delete_dictionary"):
logger.reason(f"Dict: {dictionary_id}, User: {current_user.username}")
log.reason("Delete dictionary", payload={"dict_id": dictionary_id, "user": current_user.username})
try:
DictionaryManager.delete_dictionary(db, dictionary_id)
logger.reflect("Dictionary deleted", {"id": dictionary_id})
log.reflect("Dictionary deleted", payload={"id": dictionary_id})
return None
except ValueError as e:
if "active/scheduled" in str(e):
@@ -200,7 +203,7 @@ async def list_dictionary_entries(
):
"""List entries for a dictionary."""
with belief_scope("list_dictionary_entries"):
logger.reason(f"Dict: {dictionary_id}, User: {current_user.username}")
log.reason("List dictionary entries", payload={"dict_id": dictionary_id, "user": current_user.username})
try:
entries, total = DictionaryManager.list_entries(db, dictionary_id, page=page, page_size=page_size)
items = [
@@ -216,7 +219,7 @@ async def list_dictionary_entries(
}
for e in entries
]
logger.reflect("Entries listed", {"dict_id": dictionary_id, "total": total})
log.reflect("Entries listed", payload={"dict_id": dictionary_id, "total": total})
return {"items": items, "total": total, "page": page, "page_size": page_size}
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
@@ -239,7 +242,7 @@ async def add_dictionary_entry(
):
"""Add a new entry to a dictionary."""
with belief_scope("add_dictionary_entry"):
logger.reason(f"Dict: {dictionary_id}, User: {current_user.username}")
log.reason("Add dictionary entry", payload={"dict_id": dictionary_id, "user": current_user.username})
try:
entry = DictionaryManager.add_entry(
db, dictionary_id,
@@ -247,7 +250,7 @@ async def add_dictionary_entry(
target_term=payload.target_term,
context_notes=payload.context_notes,
)
logger.reflect("Entry added", {"entry_id": entry.id})
log.reflect("Entry added", payload={"entry_id": entry.id})
return {
"id": entry.id,
"dictionary_id": entry.dictionary_id,
@@ -282,7 +285,7 @@ async def edit_dictionary_entry(
):
"""Update an existing dictionary entry."""
with belief_scope("edit_dictionary_entry"):
logger.reason(f"Entry: {entry_id}, User: {current_user.username}")
log.reason("Edit dictionary entry", payload={"entry_id": entry_id, "user": current_user.username})
try:
entry = DictionaryManager.edit_entry(
db, entry_id,
@@ -290,7 +293,7 @@ async def edit_dictionary_entry(
target_term=payload.target_term,
context_notes=payload.context_notes,
)
logger.reflect("Entry updated", {"entry_id": entry_id})
log.reflect("Entry updated", payload={"entry_id": entry_id})
return {
"id": entry.id,
"dictionary_id": entry.dictionary_id,
@@ -324,10 +327,10 @@ async def delete_dictionary_entry(
):
"""Delete a dictionary entry."""
with belief_scope("delete_dictionary_entry"):
logger.reason(f"Entry: {entry_id}, User: {current_user.username}")
log.reason("Delete dictionary entry", payload={"entry_id": entry_id, "user": current_user.username})
try:
DictionaryManager.delete_entry(db, entry_id)
logger.reflect("Entry deleted", {"entry_id": entry_id})
log.reflect("Entry deleted", payload={"entry_id": entry_id})
return None
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
@@ -350,7 +353,7 @@ async def import_dictionary_entries(
):
"""Import entries into a terminology dictionary from CSV/TSV content."""
with belief_scope("import_dictionary_entries"):
logger.reason(f"Dict: {dictionary_id}, User: {current_user.username}")
log.reason("Import dictionary entries", payload={"dict_id": dictionary_id, "user": current_user.username})
if payload.on_conflict not in ("overwrite", "keep_existing", "cancel"):
raise HTTPException(
@@ -367,7 +370,7 @@ async def import_dictionary_entries(
on_conflict=payload.on_conflict,
preview_only=payload.preview_only,
)
logger.reflect("Import completed", {
log.reflect("Import completed", payload={
"dict_id": dictionary_id,
"total": result["total"],
"errors": len(result["errors"]),

View File

@@ -15,8 +15,11 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from ....core.database import get_db
from ....core.logger import logger, belief_scope
from ....core.logger import belief_scope
from ....core.cot_logger import MarkerLogger
from ....schemas.auth import User
log = MarkerLogger("TranslateJobRoutes")
from ....dependencies import get_current_user, has_permission, get_config_manager
from ....core.config_manager import ConfigManager
from ....plugins.translate.service import (
@@ -53,7 +56,7 @@ async def list_jobs(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""List all translation jobs."""
logger.info(f"[translate_routes][list_jobs] User: {current_user.username}")
log.reason("Listing jobs", payload={"user": current_user.username})
try:
service = TranslateJobService(db, config_manager, current_user.username)
total, jobs = service.list_jobs(
@@ -83,7 +86,7 @@ async def get_job(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Get a translation job by ID."""
logger.info(f"[translate_routes][get_job] Job: {job_id}, User: {current_user.username}")
log.reason("Getting job", payload={"job_id": job_id, "user": current_user.username})
try:
service = TranslateJobService(db, config_manager, current_user.username)
job = service.get_job(job_id)
@@ -109,7 +112,7 @@ async def create_job(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Create a new translation job."""
logger.info(f"[translate_routes][create_job] User: {current_user.username}")
log.reason("Creating job", payload={"user": current_user.username})
try:
service = TranslateJobService(db, config_manager, current_user.username)
job = service.create_job(payload)
@@ -136,7 +139,7 @@ async def update_job(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Update a translation job."""
logger.info(f"[translate_routes][update_job] Job: {job_id}, User: {current_user.username}")
log.reason("Updating job", payload={"job_id": job_id, "user": current_user.username})
try:
service = TranslateJobService(db, config_manager, current_user.username)
job = service.update_job(job_id, payload)
@@ -162,7 +165,7 @@ async def delete_job(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Delete a translation job."""
logger.info(f"[translate_routes][delete_job] Job: {job_id}, User: {current_user.username}")
log.reason("Deleting job", payload={"job_id": job_id, "user": current_user.username})
try:
service = TranslateJobService(db, config_manager, current_user.username)
service.delete_job(job_id)
@@ -186,7 +189,7 @@ async def duplicate_job(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Duplicate a translation job."""
logger.info(f"[translate_routes][duplicate_job] Job: {job_id}, User: {current_user.username}")
log.reason("Duplicating job", payload={"job_id": job_id, "user": current_user.username})
try:
service = TranslateJobService(db, config_manager, current_user.username)
new_job = service.duplicate_job(job_id)
@@ -223,7 +226,7 @@ async def list_datasources(
datasets = service.fetch_available_datasources(env_id, search)
return datasets
except Exception as e:
logger.error(f"[translate_routes][list_datasources] Error: {e}")
log.explore("List datasources failed", error=str(e))
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(e))
# #endregion list_datasources
@@ -244,10 +247,11 @@ async def get_datasource_columns(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Get column metadata and database dialect for a Superset datasource."""
logger.info(
f"[translate_routes][get_datasource_columns] "
f"Datasource: {datasource_id}, Env: {env_id}, User: {current_user.username}"
)
log.reason("Getting datasource columns", payload={
"datasource_id": datasource_id,
"env_id": env_id,
"user": current_user.username,
})
try:
result = fetch_datasource_columns_service(
datasource_id=datasource_id,
@@ -258,7 +262,7 @@ async def get_datasource_columns(
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except Exception as e:
logger.error(f"[translate_routes][get_datasource_columns] Error: {e}")
log.explore("Get datasource columns failed", error=str(e))
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"Failed to fetch datasource columns from Superset: {e}",

View File

@@ -10,13 +10,14 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from ....core.database import get_db
from ....core.logger import logger, belief_scope
from ....core.cot_logger import MarkerLogger
from ....schemas.auth import User
from ....dependencies import get_current_user, has_permission
from ....plugins.translate.metrics import TranslationMetrics
from ._router import router
log = MarkerLogger("TranslateMetricsRoutes")
# ============================================================
# Metrics
@@ -33,7 +34,7 @@ async def get_metrics(
db: Session = Depends(get_db),
):
"""Get translation metrics."""
logger.info(f"[translate_routes][get_metrics] User: {current_user.username}")
log.reason("Metrics request", payload={"user": current_user.username})
try:
metrics_svc = TranslationMetrics(db)
if job_id:
@@ -55,7 +56,7 @@ async def get_job_metrics(
db: Session = Depends(get_db),
):
"""Get aggregated metrics for a specific job."""
logger.info(f"[translate_routes][get_job_metrics] Job: {job_id}, User: {current_user.username}")
log.reason("Job metrics request", payload={"job_id": job_id, "user": current_user.username})
try:
metrics_svc = TranslationMetrics(db)
return metrics_svc.get_job_metrics(job_id)

View File

@@ -14,8 +14,10 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from ....core.database import get_db
from ....core.logger import logger, belief_scope
from ....core.cot_logger import MarkerLogger
from ....schemas.auth import User
log = MarkerLogger("TranslatePreviewRoutes")
from ....dependencies import get_current_user, has_permission, get_config_manager
from ....core.config_manager import ConfigManager
from ....plugins.translate.preview import TranslationPreview
@@ -50,7 +52,7 @@ async def preview_translation(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Preview a translation before applying it."""
logger.info(f"[translate_routes][preview_translation] Job: {job_id}, User: {current_user.username}")
log.reason("Preview translation", payload={"job_id": job_id, "user": current_user.username})
if payload is None:
payload = PreviewRequest()
try:
@@ -65,7 +67,7 @@ async def preview_translation(
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except Exception as e:
logger.error(f"[translate_routes][preview_translation] Error: {e}")
log.explore("Preview translation error", error=str(e))
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Preview failed: {e}")
# #endregion preview_translation
@@ -87,7 +89,7 @@ async def update_preview_row(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Approve, edit, or reject a preview row."""
logger.info(f"[translate_routes][update_preview_row] Job: {job_id}, Row: {row_key}, Action: {payload.action}")
log.reason("Update preview row", payload={"job_id": job_id, "row": row_key, "action": payload.action})
try:
preview_service = TranslationPreview(db, config_manager, current_user.username)
result = preview_service.update_preview_row(
@@ -118,7 +120,7 @@ async def accept_preview_session(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Accept a preview session, enabling full translation execution."""
logger.info(f"[translate_routes][accept_preview_session] Job: {job_id}, User: {current_user.username}")
log.reason("Accept preview session", payload={"job_id": job_id, "user": current_user.username})
try:
preview_service = TranslationPreview(db, config_manager, current_user.username)
result = preview_service.accept_preview_session(job_id=job_id)
@@ -143,7 +145,7 @@ async def apply_preview(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Apply a preview session by session ID."""
logger.info(f"[translate_routes][apply_preview] Session: {session_id}, User: {current_user.username}")
log.reason("Apply preview", payload={"session_id": session_id, "user": current_user.username})
# Find job_id from session
from ....models.translate import TranslationPreviewSession
session = db.query(TranslationPreviewSession).filter(TranslationPreviewSession.id == session_id).first()
@@ -174,7 +176,7 @@ async def get_preview_records(
db: Session = Depends(get_db),
):
"""Get records for a preview session."""
logger.info(f"[translate_routes][get_preview_records] Session: {session_id}, User: {current_user.username}")
log.reason("Get preview records", payload={"session_id": session_id, "user": current_user.username})
try:
from ....models.translate import TranslationPreviewSession, TranslationPreviewRecord
session = db.query(TranslationPreviewSession).filter(TranslationPreviewSession.id == session_id).first()
@@ -201,7 +203,7 @@ async def get_preview_records(
except HTTPException:
raise
except Exception as e:
logger.error(f"[translate_routes][get_preview_records] Error: {e}")
log.explore("Get preview records error", error=str(e))
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion get_preview_records

View File

@@ -11,8 +11,10 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from ....core.database import get_db
from ....core.logger import logger, belief_scope
from ....core.cot_logger import MarkerLogger
from ....schemas.auth import User
log = MarkerLogger("TranslateRunListRoutes")
from ....dependencies import get_current_user, has_permission, get_config_manager
from ....core.config_manager import ConfigManager
from ....plugins.translate.orchestrator import TranslationOrchestrator
@@ -43,7 +45,7 @@ async def list_runs(
db: Session = Depends(get_db),
):
"""List all runs with cross-job filtering and pagination."""
logger.info(f"[translate_routes][list_runs] User: {current_user.username}")
log.reason("Listing runs", payload={"user": current_user.username})
try:
from ....models.translate import TranslationRun
query = db.query(TranslationRun)
@@ -103,7 +105,7 @@ async def list_runs(
return {"items": items, "total": total, "page": page, "page_size": page_size}
except Exception as e:
logger.error(f"[translate_routes][list_runs] Error: {e}")
log.explore("List runs error", error=str(e))
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion list_runs
@@ -126,7 +128,7 @@ async def get_run_detail(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Get detailed run info with config_snapshot, records, events."""
logger.info(f"[translate_routes][get_run_detail] Run: {run_id}, User: {current_user.username}")
log.reason("Get run detail", payload={"run_id": run_id, "user": current_user.username})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
status_info = orch.get_run_status(run_id)
@@ -167,7 +169,7 @@ async def download_skipped_csv(
db: Session = Depends(get_db),
):
"""Download skipped translation records as CSV."""
logger.info(f"[translate_routes][download_skipped_csv] Run: {run_id}, User: {current_user.username}")
log.reason("Download skipped CSV", payload={"run_id": run_id, "user": current_user.username})
try:
from ....models.translate import TranslationRecord
from fastapi.responses import StreamingResponse
@@ -200,7 +202,7 @@ async def download_skipped_csv(
headers={"Content-Disposition": f"attachment; filename=skipped_{run_id}.csv"},
)
except Exception as e:
logger.error(f"[translate_routes][download_skipped_csv] Error: {e}")
log.explore("Download skipped CSV error", error=str(e))
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion download_skipped_csv

View File

@@ -14,8 +14,10 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from ....core.database import get_db, SessionLocal
from ....core.logger import logger, belief_scope
from ....core.cot_logger import MarkerLogger
from ....schemas.auth import User
log = MarkerLogger("TranslateRunRoutes")
from ....dependencies import get_current_user, has_permission, get_config_manager
from ....core.config_manager import ConfigManager
from ....plugins.translate.orchestrator import TranslationOrchestrator
@@ -50,7 +52,7 @@ async def run_translation(
By default runs translation on preview-approved rows only.
Set full_translation=true to fetch ALL rows from the Superset source dataset.
"""
logger.info(f"[translate_routes][run_translation] Job: {job_id}, User: {current_user.username}, full={full_translation}")
log.reason("Run translation", payload={"job_id": job_id, "user": current_user.username, "full_translation": full_translation})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
run = orch.start_run(job_id=job_id, is_scheduled=False)
@@ -68,7 +70,7 @@ async def run_translation(
if thread_run:
thread_orch.execute_run(thread_run, full_translation=full_translation)
except Exception as e:
logger.error(f"[translate_routes][run_translation] Background execution error: {e}")
log.explore("Background execution error", error=str(e))
finally:
thread_db.close()
@@ -77,7 +79,7 @@ async def run_translation(
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except Exception as e:
logger.error(f"[translate_routes][run_translation] Error: {e}")
log.explore("Run translation error", error=str(e))
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Run failed: {e}")
# #endregion run_translation
@@ -97,7 +99,7 @@ async def retry_run(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Retry failed batches in a translation run."""
logger.info(f"[translate_routes][retry_run] Run: {run_id}, User: {current_user.username}")
log.reason("Retry run", payload={"run_id": run_id, "user": current_user.username})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
run = orch.retry_failed_batches(run_id)
@@ -105,7 +107,7 @@ async def retry_run(
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except Exception as e:
logger.error(f"[translate_routes][retry_run] Error: {e}")
log.explore("Retry run error", error=str(e))
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Retry failed: {e}")
# #endregion retry_run
@@ -125,7 +127,7 @@ async def retry_insert(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Retry the SQL insert phase for a completed run."""
logger.info(f"[translate_routes][retry_insert] Run: {run_id}, User: {current_user.username}")
log.reason("Retry insert", payload={"run_id": run_id, "user": current_user.username})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
run = orch.retry_insert(run_id)
@@ -133,7 +135,7 @@ async def retry_insert(
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except Exception as e:
logger.error(f"[translate_routes][retry_insert] Error: {e}")
log.explore("Retry insert error", error=str(e))
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Retry insert failed: {e}")
# #endregion retry_insert
@@ -153,7 +155,7 @@ async def cancel_run(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Cancel a running translation."""
logger.info(f"[translate_routes][cancel_run] Run: {run_id}, User: {current_user.username}")
log.reason("Cancel run", payload={"run_id": run_id, "user": current_user.username})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
run = orch.cancel_run(run_id)
@@ -177,7 +179,7 @@ async def get_run_history(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Get run history for a translation job."""
logger.info(f"[translate_routes][get_run_history] Job: {job_id}, User: {current_user.username}")
log.reason("Get run history", payload={"job_id": job_id, "user": current_user.username})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
total, runs = orch.get_run_history(job_id, page=page, page_size=page_size)
@@ -199,7 +201,7 @@ async def get_run_status(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Get status and statistics for a translation run."""
logger.info(f"[translate_routes][get_run_status] Run: {run_id}, User: {current_user.username}")
log.reason("Get run status", payload={"run_id": run_id, "user": current_user.username})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
return orch.get_run_status(run_id)
@@ -223,7 +225,7 @@ async def get_run_records(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Get paginated records for a translation run."""
logger.info(f"[translate_routes][get_run_records] Run: {run_id}, User: {current_user.username}")
log.reason("Get run records", payload={"run_id": run_id, "user": current_user.username})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
return orch.get_run_records(run_id, page=page, page_size=page_size, status_filter=status)
@@ -247,7 +249,7 @@ async def get_batches(
db: Session = Depends(get_db),
):
"""Get batches for a translation run."""
logger.info(f"[translate_routes][get_batches] Run: {run_id}, User: {current_user.username}")
log.reason("Get batches", payload={"run_id": run_id, "user": current_user.username})
try:
from ....models.translate import TranslationBatch
batches = (
@@ -272,7 +274,7 @@ async def get_batches(
for b in batches
]
except Exception as e:
logger.error(f"[translate_routes][get_batches] Error: {e}")
log.explore("Get batches error", error=str(e))
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion get_batches

View File

@@ -14,8 +14,10 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from ....core.database import get_db
from ....core.logger import logger, belief_scope
from ....core.cot_logger import MarkerLogger
from ....schemas.auth import User
log = MarkerLogger("TranslateScheduleRoutes")
from ....dependencies import get_current_user, has_permission, get_config_manager
from ....core.config_manager import ConfigManager
from ....plugins.translate.scheduler import TranslationScheduler
@@ -40,7 +42,7 @@ async def get_schedule(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Get schedule for a translation job."""
logger.info(f"[translate_routes][get_schedule] Job: {job_id}, User: {current_user.username}")
log.reason("Get schedule", payload={"job_id": job_id, "user": current_user.username})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
schedule = scheduler.get_schedule(job_id)
@@ -77,7 +79,7 @@ async def set_schedule(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Set or update schedule for a translation job."""
logger.info(f"[translate_routes][set_schedule] Job: {job_id}, User: {current_user.username}")
log.reason("Set schedule", payload={"job_id": job_id, "user": current_user.username})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
# Check if schedule already exists
@@ -139,7 +141,7 @@ async def enable_schedule(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Enable a schedule for a translation job."""
logger.info(f"[translate_routes][enable_schedule] Job: {job_id}, User: {current_user.username}")
log.reason("Enable schedule", payload={"job_id": job_id, "user": current_user.username})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
schedule = scheduler.set_schedule_active(job_id, is_active=True)
@@ -172,7 +174,7 @@ async def disable_schedule(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Disable a schedule for a translation job."""
logger.info(f"[translate_routes][disable_schedule] Job: {job_id}, User: {current_user.username}")
log.reason("Disable schedule", payload={"job_id": job_id, "user": current_user.username})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
scheduler.set_schedule_active(job_id, is_active=False)
@@ -200,7 +202,7 @@ async def delete_schedule(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Delete schedule for a translation job."""
logger.info(f"[translate_routes][delete_schedule] Job: {job_id}, User: {current_user.username}")
log.reason(f"Delete schedule", payload={"job_id": job_id, "user": current_user.username})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
scheduler.delete_schedule(job_id)
@@ -226,7 +228,7 @@ async def get_next_executions(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Preview next N executions for a job's schedule."""
logger.info(f"[translate_routes][get_next_executions] Job: {job_id}, User: {current_user.username}")
log.reason(f"Get next executions", payload={"job_id": job_id, "user": current_user.username})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
schedule = scheduler.get_schedule(job_id)