logs: migrate to molecular CoT JSON protocol, fix regressions

- Replace BeliefFormatter with CotJsonFormatter (single-line JSON with
  ts, level, trace_id, src, marker, intent, payload, error, span_id)
- Migrate belief_scope to use cot_log() for structured JSON output
- Update monkey-patched reason/reflect/explore to pass extra= dict
- Strip 200+ inline [REASON]/[REFLECT]/[EXPLORE] markers from 50+ files
- Remove belief_scope from hot-path utilities (_parse_datetime,
  _json_load_if_needed) to eliminate startup log noise
- Register TraceContextMiddleware in app.py (was implemented but unused)
- Seed trace_id in startup_event/shutdown_event for background tasks
- Fix broken relative imports in translate routes (3 dots → 4 dots)
- Migrate 43 translate_routes log calls to logger.reason/explore
- Fix SyntaxError (positional arg after extra=) in _repo_operations_routes
- Fix IndentationError in rbac_permission_catalog except-block
- Add smoke test tests/test_smoke_app.py (catches import errors before run)
This commit is contained in:
2026-05-13 09:44:50 +03:00
parent b6f46fe9f5
commit 83b8f4d999
55 changed files with 644 additions and 539 deletions

View File

@@ -310,9 +310,9 @@ async def list_permissions(
db=db, declared_permissions=declared_permissions
)
if inserted_count > 0:
logger.info(
"[api.admin.list_permissions][REASON] Synchronized %s missing RBAC permissions into auth catalog",
inserted_count,
logger.reason(
f"Synchronized {inserted_count} missing RBAC permissions into auth catalog",
extra={"src": "api.admin.list_permissions"},
)
repo = AuthRepository(db)

View File

@@ -141,7 +141,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: {profile_error}",
extra={"src": "get_dashboards"},
)
try:
@@ -183,9 +184,9 @@ async def get_dashboards(
total = page_payload["total"]
total_pages = page_payload["total_pages"]
except Exception as page_error:
logger.warning(
"[get_dashboards][REASON] Page-based fetch failed; using compatibility fallback: %s",
page_error,
logger.reason(
f"Page-based fetch failed; using compatibility fallback: {page_error}",
extra={"src": "get_dashboards"},
)
if can_apply_slug_filter:
dashboards = await resource_service.get_dashboards_with_status(
@@ -241,9 +242,8 @@ async def get_dashboards(
if not actor_aliases:
actor_aliases = [bound_username]
logger.reason(
"[REASON] Applying profile actor filter "
f"(env={env_id}, bound_username={bound_username}, actor_aliases={actor_aliases!r}, "
f"dashboards_before={len(dashboards)})"
"Applying profile actor filter",
extra={"src": "get_dashboards", "payload": {"env": env_id, "bound_username": bound_username, "actor_aliases": actor_aliases, "dashboards_before": len(dashboards)}},
)
filtered_dashboards: List[Dict[str, Any]] = []
max_actor_samples = 15
@@ -259,19 +259,15 @@ async def get_dashboards(
)
if index < max_actor_samples:
logger.reflect(
"[REFLECT] 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}, "
f"modified_by={modified_by_value!r}, matches={matches_actor})"
"Profile actor filter sample",
extra={"src": "get_dashboards", "payload": {"env": env_id, "dashboard_id": dashboard.get('id'), "bound_username": bound_username, "actor_aliases": actor_aliases, "owners": owners_value, "created_by": created_by_value, "modified_by": modified_by_value, "matches": matches_actor}},
)
if matches_actor:
filtered_dashboards.append(dashboard)
logger.reflect(
"[REFLECT] Profile actor filter summary "
f"(env={env_id}, bound_username={bound_username!r}, "
f"dashboards_before={len(dashboards)}, dashboards_after={len(filtered_dashboards)})"
"Profile actor filter summary",
extra={"src": "get_dashboards", "payload": {"env": env_id, "bound_username": bound_username, "dashboards_before": len(dashboards), "dashboards_after": len(filtered_dashboards)}},
)
dashboards = filtered_dashboards

View File

@@ -182,14 +182,13 @@ def _resolve_profile_actor_aliases(env: Any, bound_username: str) -> List[str]:
if display_alias and display_alias not in aliases:
aliases.append(display_alias)
logger.reflect(
"[REFLECT] Resolved profile actor aliases "
f"(env={getattr(env, 'id', None)}, bound_username={normalized_bound!r}, "
f"lookup_items={len(lookup_items)}, aliases={aliases!r})"
"Resolved profile actor aliases",
extra={"src": "_resolve_profile_actor_aliases", "payload": {"env": getattr(env, 'id', None), "bound_username": normalized_bound, "lookup_items": len(lookup_items), "aliases": aliases}},
)
except Exception as alias_error:
logger.explore(
"[EXPLORE] Failed to resolve profile actor aliases via Superset users lookup "
f"(env={getattr(env, 'id', None)}, bound_username={normalized_bound!r}): {alias_error}"
"Failed to resolve profile actor aliases via Superset users lookup",
extra={"src": "_resolve_profile_actor_aliases", "payload": {"env": getattr(env, 'id', None), "bound_username": normalized_bound}, "error": str(alias_error)},
)
return aliases

View File

@@ -61,8 +61,9 @@ 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][REASON] Repository is not initialized for dashboard {dashboard_id}"
logger.reason(
f"Repository is not initialized for dashboard {dashboard_id}",
extra={"src": "get_repository_status"},
)
return _build_no_repo_status_payload()
@@ -70,8 +71,9 @@ 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][REASON] Repository is not initialized for dashboard {dashboard_id}"
logger.reason(
f"Repository is not initialized for dashboard {dashboard_id}",
extra={"src": "get_repository_status"},
)
return _build_no_repo_status_payload()
raise
@@ -287,10 +289,9 @@ def _resolve_current_user_git_identity(
.first()
)
except Exception as resolve_error:
logger.warning(
"[_resolve_current_user_git_identity][REASON] Failed to load profile preference for user %s: %s",
user_id,
resolve_error,
logger.explore(
"Failed to load profile preference for resolving git identity",
extra={"src": "_resolve_current_user_git_identity", "payload": {"user_id": user_id}, "error": str(resolve_error)},
)
return None

View File

@@ -128,23 +128,17 @@ async def pull_changes(
config_url = config_row.url
config_provider = config_row.provider
except Exception as diagnostics_error:
logger.warning(
"[pull_changes][REASON] Failed to load repository binding diagnostics for dashboard %s: %s",
dashboard_id,
diagnostics_error,
logger.explore(
"Failed to load repository binding diagnostics",
extra={"src": "pull_changes", "payload": {"dashboard_id": dashboard_id}, "error": str(diagnostics_error)},
)
logger.info(
"[pull_changes][REASON] 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,
logger.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}",
extra={"src": "pull_changes"},
)
_apply_git_identity_from_profile(dashboard_id, db, current_user)
_gs.pull_changes(dashboard_id)
@@ -190,10 +184,9 @@ 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][REASON] Batch size %s exceeds limit %s. Truncating request.",
len(dashboard_ids),
MAX_REPOSITORY_STATUS_BATCH,
logger.reason(
f"Batch size {len(dashboard_ids)} exceeds limit {MAX_REPOSITORY_STATUS_BATCH}. Truncating request.",
extra={"src": "get_repository_status_batch"},
)
dashboard_ids = dashboard_ids[:MAX_REPOSITORY_STATUS_BATCH]

View File

@@ -61,8 +61,9 @@ async def init_repository(
raise HTTPException(status_code=404, detail="Git configuration not found")
try:
logger.info(
f"[init_repository][REASON] Initializing repo for dashboard {dashboard_id}"
logger.reason(
f"Initializing repo for dashboard {dashboard_id}",
extra={"src": "init_repository"},
)
_gs.init_repo(
dashboard_id,

View File

@@ -52,8 +52,9 @@ async def get_providers(
"""
Get all LLM provider configurations.
"""
logger.info(
f"[llm_routes][get_providers][REASON] Fetching providers for user: {current_user.username}"
logger.reason(
f"Fetching providers for user: {current_user.username}",
extra={"src": "llm_routes.get_providers"},
)
service = LLMProviderService(db)
providers = service.get_all_providers()
@@ -248,8 +249,9 @@ async def test_connection(
current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
logger.info(
f"[llm_routes][test_connection][REASON] Testing connection for provider_id: {provider_id}"
logger.reason(
f"Testing connection for provider_id: {provider_id}",
extra={"src": "llm_routes.test_connection"},
)
"""
Test connection to an LLM provider.
@@ -305,8 +307,9 @@ async def test_provider_config(
"""
from ...plugins.llm_analysis.service import LLMClient
logger.info(
f"[llm_routes][test_provider_config][REASON] Testing config for {config.name}"
logger.reason(
f"Testing config for {config.name}",
extra={"src": "llm_routes.test_provider_config"},
)
# Check if API key is provided

View File

@@ -357,8 +357,9 @@ 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][REASON] Created environment row for {env.id}"
logger.reason(
f"Created environment row for {env.id}",
extra={"src": "trigger_sync_now"},
)
else:
existing.name = env.name
@@ -373,7 +374,7 @@ 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][REASON] Synced environment {env.id}")
logger.reason(f"Synced environment {env.id}", extra={"src": "trigger_sync_now"})
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}")

View File

@@ -69,7 +69,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")
logger.reason("Resolving current user preference")
service = _get_profile_service(db, config_manager, plugin_loader)
return service.get_my_preference(current_user)
# #endregion get_preferences
@@ -91,13 +91,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")
logger.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")
logger.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")
logger.explore("Cross-user mutation guard blocked request")
raise HTTPException(status_code=403, detail=str(exc)) from exc
# #endregion update_preferences
@@ -134,13 +134,13 @@ async def lookup_superset_accounts(
sort_order=sort_order,
)
try:
logger.reason("[REASON] Executing Superset account lookup")
logger.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")
logger.explore("Lookup request references unknown environment")
raise HTTPException(status_code=404, detail=str(exc)) from exc
# #endregion lookup_superset_accounts

View File

@@ -88,7 +88,7 @@ async def get_settings(
_=Depends(has_permission("admin:settings", "READ")),
):
with belief_scope("get_settings"):
logger.info("[get_settings][REASON] Fetching all settings")
logger.reason("Fetching all settings", extra={"src": "get_settings"})
config = config_manager.get_config().copy(deep=True)
config.settings.llm = normalize_llm_settings(config.settings.llm)
# Mask passwords
@@ -127,7 +127,7 @@ async def update_global_settings(
_=Depends(has_permission("admin:settings", "WRITE")),
):
with belief_scope("update_global_settings"):
logger.info("[update_global_settings][REASON] Updating global settings")
logger.reason("Updating global settings", extra={"src": "update_global_settings"})
config_manager.update_global_settings(settings)
return settings
@@ -183,7 +183,7 @@ async def get_environments(
_=Depends(has_permission("admin:settings", "READ")),
):
with belief_scope("get_environments"):
logger.info("[get_environments][REASON] Fetching environments")
logger.reason("Fetching environments", extra={"src": "get_environments"})
environments = config_manager.get_environments()
return [
env.copy(update={"url": _normalize_superset_env_url(env.url)})
@@ -205,7 +205,7 @@ async def add_environment(
_=Depends(has_permission("admin:settings", "WRITE")),
):
with belief_scope("add_environment"):
logger.info(f"[add_environment][REASON] Adding environment {env.id}")
logger.reason(f"Adding environment {env.id}", extra={"src": "add_environment"})
env = env.copy(update={"url": _normalize_superset_env_url(env.url)})
# Validate connection before adding (fast path)
@@ -237,7 +237,7 @@ async def update_environment(
config_manager: ConfigManager = Depends(get_config_manager),
):
with belief_scope("update_environment"):
logger.info(f"[update_environment][REASON] Updating environment {id}")
logger.reason(f"Updating environment {id}", extra={"src": "update_environment"})
env = env.copy(update={"url": _normalize_superset_env_url(env.url)})
@@ -278,7 +278,7 @@ async def delete_environment(
id: str, config_manager: ConfigManager = Depends(get_config_manager)
):
with belief_scope("delete_environment"):
logger.info(f"[delete_environment][REASON] Deleting environment {id}")
logger.reason(f"Deleting environment {id}", extra={"src": "delete_environment"})
config_manager.delete_environment(id)
return {"message": f"Environment {id} deleted"}
@@ -295,7 +295,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][REASON] Testing environment {id}")
logger.reason(f"Testing environment {id}", extra={"src": "test_environment_connection"})
# Find environment
env = next((e for e in config_manager.get_environments() if e.id == id), None)
@@ -351,8 +351,8 @@ async def update_logging_config(
_=Depends(has_permission("admin:settings", "WRITE")),
):
with belief_scope("update_logging_config"):
logger.info(
f"[update_logging_config][REASON] Updating logging config: level={config.level}, task_log_level={config.task_log_level}"
logger.reason(
f"Updating logging config: level={config.level}, task_log_level={config.task_log_level}", extra={"src": "update_logging_config"}
)
# Get current settings and update logging config
@@ -475,8 +475,8 @@ async def update_consolidated_settings(
_=Depends(has_permission("admin:settings", "WRITE")),
):
with belief_scope("update_consolidated_settings"):
logger.info(
"[update_consolidated_settings][REASON] Applying consolidated settings patch"
logger.reason(
"Applying consolidated settings patch", extra={"src": "update_consolidated_settings"}
)
current_config = config_manager.get_config()

View File

@@ -36,7 +36,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}")
logger.reason(f"submit_correction User: {current_user.username}", extra={"src": "translate_routes"})
if not payload.dictionary_id:
raise HTTPException(status_code=422, detail="dictionary_id is required")
try:
@@ -68,7 +68,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}")
logger.reason(f"submit_bulk_corrections User: {current_user.username}", extra={"src": "translate_routes"})
try:
corr_list = []
for c in payload.corrections:

View File

@@ -67,7 +67,7 @@ async def get_dictionary(
logger.reason(f"Dict: {dictionary_id}, User: {current_user.username}")
try:
d = DictionaryManager.get_dictionary(db, dictionary_id)
from ...models.translate import DictionaryEntry
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})
return _dict_to_response(d, entry_count)
@@ -129,7 +129,7 @@ async def update_dictionary(
target_dialect=payload.target_dialect,
is_active=payload.is_active,
)
from ...models.translate import DictionaryEntry
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})
return _dict_to_response(d, entry_count)

View File

@@ -57,7 +57,7 @@ def _dict_to_response(d: Any, entry_count: int = 0) -> dict:
# @BRIEF Get entry counts for a list of dictionary IDs.
def _get_dictionary_entry_counts(db: Session, dict_ids: List[str]) -> Dict[str, int]:
from sqlalchemy import func
from ...models.translate import DictionaryEntry
from ....models.translate import DictionaryEntry
counts = (
db.query(DictionaryEntry.dictionary_id, func.count(DictionaryEntry.id))
.filter(DictionaryEntry.dictionary_id.in_(dict_ids))

View File

@@ -46,7 +46,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}")
logger.reason(f"list_jobs User: {current_user.username}", extra={"src": "translate_routes"})
try:
service = TranslateJobService(db, config_manager, current_user.username)
total, jobs = service.list_jobs(
@@ -77,7 +77,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}")
logger.reason(f"get_job Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
service = TranslateJobService(db, config_manager, current_user.username)
job = service.get_job(job_id)
@@ -102,7 +102,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}")
logger.reason(f"create_job User: {current_user.username}", extra={"src": "translate_routes"})
try:
service = TranslateJobService(db, config_manager, current_user.username)
job = service.create_job(payload)
@@ -128,7 +128,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}")
logger.reason(f"update_job Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
service = TranslateJobService(db, config_manager, current_user.username)
job = service.update_job(job_id, payload)
@@ -152,7 +152,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}")
logger.reason(f"delete_job Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
service = TranslateJobService(db, config_manager, current_user.username)
service.delete_job(job_id)
@@ -174,7 +174,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}")
logger.reason(f"duplicate_job Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
service = TranslateJobService(db, config_manager, current_user.username)
new_job = service.duplicate_job(job_id)
@@ -212,7 +212,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}")
logger.explore(f"list_datasources failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(e))
@router.get("/datasources/{datasource_id}/columns", response_model=DatasourceColumnsResponse)
@@ -224,9 +224,9 @@ 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}"
logger.reason(
f"get_datasource_columns — Datasource: {datasource_id}, Env: {env_id}, User: {current_user.username}",
extra={"src": "translate_routes"}
)
try:
result = fetch_datasource_columns_service(
@@ -238,7 +238,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}")
logger.explore(f"get_datasource_columns failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"Failed to fetch datasource columns from Superset: {e}",

View File

@@ -31,7 +31,7 @@ async def get_metrics(
db: Session = Depends(get_db),
):
"""Get translation metrics."""
logger.info(f"[translate_routes][get_metrics] User: {current_user.username}")
logger.reason(f"get_metrics User: {current_user.username}", extra={"src": "translate_routes"})
try:
metrics_svc = TranslationMetrics(db)
if job_id:
@@ -54,7 +54,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}")
logger.reason(f"get_job_metrics Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
metrics_svc = TranslationMetrics(db)
return metrics_svc.get_job_metrics(job_id)

View File

@@ -41,7 +41,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}")
logger.reason(f"preview_translation Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
if payload is None:
payload = PreviewRequest()
try:
@@ -56,7 +56,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}")
logger.explore(f"preview_translation failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Preview failed: {e}")
# #endregion preview_translation
@@ -76,7 +76,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}")
logger.reason(f"update_preview_row Job: {job_id}, Row: {row_key}, Action: {payload.action}", extra={"src": "translate_routes"})
try:
preview_service = TranslationPreview(db, config_manager, current_user.username)
result = preview_service.update_preview_row(
@@ -105,7 +105,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}")
logger.reason(f"accept_preview_session Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
preview_service = TranslationPreview(db, config_manager, current_user.username)
result = preview_service.accept_preview_session(job_id=job_id)
@@ -128,9 +128,9 @@ 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}")
logger.reason(f"apply_preview Session: {session_id}, User: {current_user.username}", extra={"src": "translate_routes"})
# Find job_id from session
from ...models.translate import TranslationPreviewSession
from ....models.translate import TranslationPreviewSession
session = db.query(TranslationPreviewSession).filter(TranslationPreviewSession.id == session_id).first()
if not session:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Preview session '{session_id}' not found")
@@ -159,9 +159,9 @@ 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}")
logger.reason(f"get_preview_records Session: {session_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
from ...models.translate import TranslationPreviewSession, TranslationPreviewRecord
from ....models.translate import TranslationPreviewSession, TranslationPreviewRecord
session = db.query(TranslationPreviewSession).filter(TranslationPreviewSession.id == session_id).first()
if not session:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Preview session '{session_id}' not found")
@@ -186,7 +186,7 @@ async def get_preview_records(
except HTTPException:
raise
except Exception as e:
logger.error(f"[translate_routes][get_preview_records] Error: {e}")
logger.explore(f"get_preview_records failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion get_preview_records

View File

@@ -40,9 +40,9 @@ 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}")
logger.reason(f"list_runs User: {current_user.username}", extra={"src": "translate_routes"})
try:
from ...models.translate import TranslationRun
from ....models.translate import TranslationRun
query = db.query(TranslationRun)
if job_id:
@@ -100,7 +100,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}")
logger.explore(f"list_runs failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion list_runs
@@ -122,7 +122,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}")
logger.reason(f"get_run_detail Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
status_info = orch.get_run_status(run_id)
@@ -131,7 +131,7 @@ async def get_run_detail(
event_summary = TranslationEventLog(db).get_run_event_summary(run_id)
# Get config snapshot from run
from ...models.translate import TranslationRun
from ....models.translate import TranslationRun
run = db.query(TranslationRun).filter(TranslationRun.id == run_id).first()
config_snapshot = run.config_snapshot if run else None
@@ -164,9 +164,9 @@ 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}")
logger.reason(f"download_skipped_csv Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
from ...models.translate import TranslationRecord
from ....models.translate import TranslationRecord
from fastapi.responses import StreamingResponse
import csv
import io
@@ -197,7 +197,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}")
logger.explore(f"download_skipped_csv failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion download_skipped_csv

View File

@@ -35,7 +35,7 @@ async def run_translation(
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Execute a translation job (trigger a run)."""
logger.info(f"[translate_routes][run_translation] Job: {job_id}, User: {current_user.username}")
logger.reason(f"run_translation Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
run = orch.start_run(job_id=job_id, is_scheduled=False)
@@ -50,7 +50,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}")
logger.explore(f"run_translation failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Run failed: {e}")
# #endregion run_translation
@@ -68,7 +68,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}")
logger.reason(f"retry_run Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
run = orch.retry_failed_batches(run_id)
@@ -76,7 +76,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}")
logger.explore(f"retry_run failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Retry failed: {e}")
# #endregion retry_run
@@ -94,7 +94,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}")
logger.reason(f"retry_insert Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
run = orch.retry_insert(run_id)
@@ -102,7 +102,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}")
logger.explore(f"retry_insert failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Retry insert failed: {e}")
# #endregion retry_insert
@@ -120,7 +120,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}")
logger.reason(f"cancel_run Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
run = orch.cancel_run(run_id)
@@ -145,7 +145,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}")
logger.reason(f"get_run_history Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
total, runs = orch.get_run_history(job_id, page=page, page_size=page_size)
@@ -168,7 +168,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}")
logger.reason(f"get_run_status Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
orch = TranslationOrchestrator(db, config_manager, current_user.username)
return orch.get_run_status(run_id)
@@ -193,7 +193,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}")
logger.reason(f"get_run_records Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
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)
@@ -218,9 +218,9 @@ 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}")
logger.reason(f"get_batches Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
from ...models.translate import TranslationBatch
from ....models.translate import TranslationBatch
batches = (
db.query(TranslationBatch)
.filter(TranslationBatch.run_id == run_id)
@@ -243,7 +243,7 @@ async def get_batches(
for b in batches
]
except Exception as e:
logger.error(f"[translate_routes][get_batches] Error: {e}")
logger.explore(f"get_batches failed", extra={"src": "translate_routes", "error": str(e)})
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion get_batches

View File

@@ -34,7 +34,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}")
logger.reason(f"get_schedule Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
schedule = scheduler.get_schedule(job_id)
@@ -69,11 +69,11 @@ 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}")
logger.reason(f"set_schedule Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
# Check if schedule already exists
from ...models.translate import TranslationSchedule
from ....models.translate import TranslationSchedule
existing = db.query(TranslationSchedule).filter(
TranslationSchedule.job_id == job_id
).first()
@@ -129,7 +129,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}")
logger.reason(f"enable_schedule Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
schedule = scheduler.set_schedule_active(job_id, is_active=True)
@@ -160,7 +160,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}")
logger.reason(f"disable_schedule Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
scheduler.set_schedule_active(job_id, is_active=False)
@@ -186,7 +186,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}")
logger.reason(f"delete_schedule Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
scheduler.delete_schedule(job_id)
@@ -213,7 +213,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}")
logger.reason(f"get_next_executions Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
schedule = scheduler.get_schedule(job_id)