diff --git a/backend/app/api/routes/credentials.py b/backend/app/api/routes/credentials.py index ae267e88..b6bb8977 100644 --- a/backend/app/api/routes/credentials.py +++ b/backend/app/api/routes/credentials.py @@ -1,3 +1,5 @@ +import logging + from fastapi import APIRouter, Depends from app.api.deps import SessionDep, get_current_user_org_project @@ -14,6 +16,7 @@ from app.core.providers import validate_provider from app.core.exception_handlers import HTTPException +logger = logging.getLogger(__name__) router = APIRouter(prefix="/credentials", tags=["credentials"]) @@ -40,6 +43,9 @@ def create_new_credential( project_id=_current_user.project_id, ) if existing_cred: + logger.warning( + f"[create_new_credential] Credentials for provider already exist | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}, provider: {provider}" + ) raise HTTPException( status_code=400, detail=( @@ -56,7 +62,10 @@ def create_new_credential( project_id=_current_user.project_id, ) if not created_creds: - raise Exception(status_code=500, detail="Failed to create credentials") + logger.error( + f"[create_new_credential] Failed to create credentials | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}" + ) + raise HTTPException(status_code=500, detail="Failed to create credentials") return APIResponse.success_response([cred.to_public() for cred in created_creds]) @@ -78,6 +87,9 @@ def read_credential( project_id=_current_user.project_id, ) if not creds: + logger.error( + f"[read_credential] No credentials found | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}" + ) raise HTTPException(status_code=404, detail="Credentials not found") return APIResponse.success_response([cred.to_public() for cred in creds]) @@ -121,6 +133,9 @@ def update_credential( _current_user: UserProjectOrg = Depends(get_current_user_org_project), ): if not creds_in or not creds_in.provider or not creds_in.credential: + logger.error( + f"[update_credential] Invalid input | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}" + ) raise HTTPException( status_code=400, detail="Provider and credential must be provided" ) @@ -157,6 +172,9 @@ def delete_provider_credential( project_id=_current_user.project_id, ) if provider_creds is None: + logger.error( + f"[delete_provider_credential] Provider credentials not found | organization_id: {_current_user.organization_id}, provider: {provider}, project_id: {_current_user.project_id}" + ) raise HTTPException(status_code=404, detail="Provider credentials not found") remove_provider_credential( @@ -188,6 +206,9 @@ def delete_all_credentials( project_id=_current_user.project_id, ) if not creds: + logger.error( + f"[delete_all_credentials] Credentials not found | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}" + ) raise HTTPException( status_code=404, detail="Credentials for organization/project not found" ) diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 12761875..5dd83fa3 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -1,4 +1,4 @@ -import uuid +import logging from typing import Any from fastapi import APIRouter, Depends @@ -26,6 +26,7 @@ from app.utils import generate_new_account_email, send_email from app.core.exception_handlers import HTTPException +logger = logging.getLogger(__name__) router = APIRouter(prefix="/users", tags=["users"]) @@ -49,6 +50,9 @@ def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: ) def create_user_endpoint(*, session: SessionDep, user_in: UserCreate) -> Any: if get_user_by_email(session=session, email=user_in.email): + logger.error( + f"[create_user_endpoint] Attempting to create user with existing email | email: {user_in.email}" + ) raise HTTPException( status_code=400, detail="The user with this email already exists in the system.", @@ -75,6 +79,9 @@ def update_user_me( if user_in.email: existing_user = get_user_by_email(session=session, email=user_in.email) if existing_user and existing_user.id != current_user.id: + logger.error( + f"[update_user_me] Attempting to update user with existing email | email: {user_in.email}, user_id: {current_user.id}" + ) raise HTTPException( status_code=409, detail="User with this email already exists" ) @@ -83,6 +90,7 @@ def update_user_me( session.add(current_user) session.commit() session.refresh(current_user) + logger.info(f"[update_user_me] User updated | user_id: {current_user.id}") return current_user @@ -102,6 +110,7 @@ def update_password_me( current_user.hashed_password = get_password_hash(body.new_password) session.add(current_user) session.commit() + logger.info(f"[update_password_me] Password updated | user_id: {current_user.id}") return Message(message="Password updated successfully") @@ -113,11 +122,15 @@ def read_user_me(current_user: CurrentUser) -> Any: @router.delete("/me", response_model=Message) def delete_user_me(session: SessionDep, current_user: CurrentUser) -> Any: if current_user.is_superuser: + logger.error( + f"[delete_user_me] Attempting to delete superuser account by itself | user_id: {current_user.id}" + ) raise HTTPException( status_code=403, detail="Super users are not allowed to delete themselves" ) session.delete(current_user) session.commit() + logger.info(f"[delete_user_me] User deleted | user_id: {current_user.id}") return Message(message="User deleted successfully") @@ -131,6 +144,9 @@ def register_user(session: SessionDep, user_in: UserRegister) -> Any: This endpoint allows the registration of a new user and is accessible only by a superuser. """ if get_user_by_email(session=session, email=user_in.email): + logger.error( + f"[register_user] Attempting to create user with existing email | email: {user_in.email}" + ) raise HTTPException( status_code=400, detail="The user with this email already exists in the system", @@ -171,6 +187,7 @@ def update_user_endpoint( ) -> Any: db_user = session.get(User, user_id) if not db_user: + logger.error(f"[update_user_endpoint] User not found | user_id: {user_id}") raise HTTPException( status_code=404, detail="The user with this id does not exist in the system", @@ -179,6 +196,9 @@ def update_user_endpoint( if user_in.email: existing_user = get_user_by_email(session=session, email=user_in.email) if existing_user and existing_user.id != user_id: + logger.error( + f"[update_user_endpoint] Attempting to update user with existing email | email: {user_in.email}, user_id: {user_id}" + ) raise HTTPException( status_code=409, detail="User with this email already exists" ) @@ -196,13 +216,18 @@ def delete_user( ) -> Message: user = session.get(User, user_id) if not user: + logger.error(f"[delete_user] User not found | user_id: {user_id}") raise HTTPException(status_code=404, detail="User not found") if user == current_user: + logger.error( + f"[delete_user] Attempting to delete self by superuser | user_id: {current_user.id}" + ) raise HTTPException( status_code=403, detail="Super users are not allowed to delete themselves" ) session.delete(user) session.commit() + logger.info(f"[delete_user] User deleted | user_id: {user.id}") return Message(message="User deleted successfully") diff --git a/backend/app/core/providers.py b/backend/app/core/providers.py index f703d2e6..dfaae233 100644 --- a/backend/app/core/providers.py +++ b/backend/app/core/providers.py @@ -1,7 +1,10 @@ +import logging from typing import Dict, List, Optional from enum import Enum from dataclasses import dataclass +logger = logging.getLogger(__name__) + class Provider(str, Enum): """Enumeration of supported credential providers.""" @@ -46,6 +49,9 @@ def validate_provider(provider: str) -> Provider: return Provider(provider.lower()) except ValueError: supported = ", ".join(p.value for p in Provider) + logger.error( + f"[validate_provider] Unsupported provider | provider: {provider}, supported_providers: {supported}" + ) raise ValueError( f"Unsupported provider: {provider}. Supported providers are: {supported}" ) @@ -67,6 +73,9 @@ def validate_provider_credentials(provider: str, credentials: Dict[str, str]) -> if missing_fields := [ field for field in required_fields if field not in credentials ]: + logger.error( + f"[validate_provider_credentials] Missing required fields | provider: {provider}, missing_fields: {', '.join(missing_fields)}" + ) raise ValueError( f"Missing required fields for {provider}: {', '.join(missing_fields)}" ) diff --git a/backend/app/core/util.py b/backend/app/core/util.py index c3cd4c93..2ac9107d 100644 --- a/backend/app/core/util.py +++ b/backend/app/core/util.py @@ -9,13 +9,15 @@ from langfuse.decorators import langfuse_context from openai import OpenAI +logger = logging.getLogger(__name__) + def now(): return datetime.now(timezone.utc).replace(tzinfo=None) def raise_from_unknown(error: Exception, status_code=500): - warnings.warn( + logger.warning( 'Unexpected exception "{}": {}'.format( type(error).__name__, error, @@ -31,7 +33,7 @@ def post_callback(url: HttpUrl, payload: BaseModel): try: response.raise_for_status() except RequestException as err: - warnings.warn(f"Callback failure: {err}") + logger.warning(f"Callback failure: {err}") errno += 1 return not errno @@ -67,7 +69,7 @@ def configure_langfuse(credentials: dict) -> tuple[Langfuse, bool]: return langfuse, True except Exception as e: - warnings.warn(f"Failed to configure Langfuse: {str(e)}") + logger.error(f"Failed to configure Langfuse: {str(e)}") return None, False @@ -89,5 +91,5 @@ def configure_openai(credentials: dict) -> tuple[OpenAI, bool]: client = OpenAI(api_key=credentials["api_key"]) return client, True except Exception as e: - warnings.warn(f"Failed to configure OpenAI client: {str(e)}") + logger.error(f"Failed to configure OpenAI client: {str(e)}") return None, False diff --git a/backend/app/crud/credentials.py b/backend/app/crud/credentials.py index d23a6164..d515aae5 100644 --- a/backend/app/crud/credentials.py +++ b/backend/app/crud/credentials.py @@ -1,18 +1,19 @@ +import logging from typing import Optional, Dict, Any, List, Union from sqlmodel import Session, select from sqlalchemy.exc import IntegrityError -from datetime import datetime, timezone from app.models import Credential, CredsCreate, CredsUpdate from app.core.providers import ( validate_provider, validate_provider_credentials, - get_supported_providers, ) from app.core.security import encrypt_credentials, decrypt_credentials from app.core.util import now from app.core.exception_handlers import HTTPException +logger = logging.getLogger(__name__) + def set_creds_for_org( *, session: Session, creds_add: CredsCreate, organization_id: int, project_id: int @@ -21,6 +22,9 @@ def set_creds_for_org( created_credentials = [] if not creds_add.credential: + logger.error( + f"[set_creds_for_org] No credentials provided | project_id: {project_id}" + ) raise HTTPException(400, "No credentials provided") for provider, credentials in creds_add.credential.items(): @@ -47,10 +51,16 @@ def set_creds_for_org( created_credentials.append(credential) except IntegrityError as e: session.rollback() + logger.error( + f"[set_creds_for_org] Integrity error while adding credentials | organization_id {organization_id}, project_id {project_id}, provider {provider}: {str(e)}", + exc_info=True, + ) raise ValueError( f"Error while adding credentials for provider {provider}: {str(e)}" ) - + logger.info( + f"[set_creds_for_org] Successfully created credentials | organization_id {organization_id}, project_id {project_id}" + ) return created_credentials @@ -153,6 +163,9 @@ def update_creds_for_org( ) creds = session.exec(statement).first() if creds is None: + logger.error( + f"[update_creds_for_org] Credentials not found | organization {org_id}, provider {creds_in.provider}, project_id {project_id}" + ) raise HTTPException( status_code=404, detail="Credentials not found for this provider" ) @@ -162,7 +175,9 @@ def update_creds_for_org( session.add(creds) session.commit() session.refresh(creds) - + logger.info( + f"[update_creds_for_org] Successfully updated credentials | organization_id {org_id}, provider {creds_in.provider}, project_id {project_id}" + ) return [creds] @@ -186,7 +201,9 @@ def remove_provider_credential( session.add(creds) session.commit() session.refresh(creds) - + logger.info( + f"[remove_provider_credential] Successfully removed credentials for provider | organization_id {org_id}, provider {provider}, project_id {project_id}" + ) return creds @@ -207,4 +224,7 @@ def remove_creds_for_org( session.add(cred) session.commit() + logger.info( + f"[remove_creds_for_org] Successfully removed all the credentials | organization_id {org_id}, project_id {project_id}" + ) return creds diff --git a/backend/app/crud/user.py b/backend/app/crud/user.py index a30cc7e6..cdf500e9 100644 --- a/backend/app/crud/user.py +++ b/backend/app/crud/user.py @@ -1,11 +1,14 @@ -import uuid +import logging from typing import Any from sqlmodel import Session, select from app.core.security import get_password_hash, verify_password + from app.models import User, UserCreate, UserUpdate +logger = logging.getLogger(__name__) + def create_user(*, session: Session, user_create: UserCreate) -> User: db_obj = User.model_validate( @@ -14,6 +17,7 @@ def create_user(*, session: Session, user_create: UserCreate) -> User: session.add(db_obj) session.commit() session.refresh(db_obj) + logger.info(f"[create_user] User created | user_id: {db_obj.id}") return db_obj @@ -28,6 +32,9 @@ def update_user(*, session: Session, db_user: User, user_in: UserUpdate) -> Any: session.add(db_user) session.commit() session.refresh(db_user) + logger.info( + f"[update_user] User updated | user_id: {db_user.id}, updated_fields: {list(user_data.keys())}" + ) return db_user diff --git a/backend/app/utils.py b/backend/app/utils.py index 80ef1a2c..1c03839a 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -179,7 +179,7 @@ def get_openai_client(session: Session, org_id: int, project_id: int) -> OpenAI: ) if not credentials or "api_key" not in credentials: - logger.warning( + logger.error( f"[get_openai_client] OpenAI credentials not found. | project_id: {project_id}" ) raise HTTPException(