From 0553a1fc53deabc8dbd619f0c4599caa887f911b Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Sun, 1 Mar 2026 13:38:15 +0100 Subject: [PATCH] refactor(logging): switch to parameterized logging for improved performance and clarity - Replaced f-strings with parameterized logging calls across routes, services, and repositories to optimize log message evaluation. - Improved exception handling by using `logger.exception` where appropriate for automatic traceback logging. --- backend/app/api/routes/admin.py | 84 ++++++++++--------- backend/app/api/routes/auth.py | 78 +++++++++-------- backend/app/api/routes/oauth.py | 28 ++++--- backend/app/api/routes/oauth_provider.py | 12 +-- backend/app/api/routes/organizations.py | 10 +-- backend/app/api/routes/sessions.py | 31 ++++--- backend/app/api/routes/users.py | 36 ++++---- backend/app/core/database.py | 4 +- backend/app/core/exceptions.py | 25 ++++-- backend/app/init_db.py | 24 +++--- backend/app/main.py | 2 +- backend/app/repositories/base.py | 77 +++++++++-------- backend/app/repositories/oauth_account.py | 36 +++++--- backend/app/repositories/oauth_client.py | 24 +++--- backend/app/repositories/oauth_state.py | 18 ++-- backend/app/repositories/organization.py | 36 ++++---- backend/app/repositories/session.py | 48 ++++++----- backend/app/repositories/user.py | 26 +++--- backend/app/services/auth_service.py | 14 ++-- backend/app/services/email_service.py | 8 +- .../app/services/oauth_provider_service.py | 25 +++--- backend/app/services/oauth_service.py | 38 +++++---- backend/app/services/session_cleanup.py | 8 +- backend/pyproject.toml | 2 - 24 files changed, 375 insertions(+), 319 deletions(-) diff --git a/backend/app/api/routes/admin.py b/backend/app/api/routes/admin.py index ce7f122..181c773 100755 --- a/backend/app/api/routes/admin.py +++ b/backend/app/api/routes/admin.py @@ -243,7 +243,7 @@ async def admin_get_stats( # 4. User Status - Active vs Inactive logger.info( - f"User status counts - Active: {active_count}, Inactive: {inactive_count}" + "User status counts - Active: %s, Inactive: %s", active_count, inactive_count ) user_status = [ @@ -312,7 +312,7 @@ async def admin_list_users( return PaginatedResponse(data=users, pagination=pagination_meta) except Exception as e: - logger.error(f"Error listing users (admin): {e!s}", exc_info=True) + logger.exception("Error listing users (admin): %s", e) raise @@ -336,13 +336,13 @@ async def admin_create_user( """ try: user = await user_service.create_user(db, user_in) - logger.info(f"Admin {admin.email} created user {user.email}") + logger.info("Admin %s created user %s", admin.email, user.email) return user except DuplicateEntryError as e: - logger.warning(f"Failed to create user: {e!s}") + logger.warning("Failed to create user: %s", e) raise DuplicateError(message=str(e), error_code=ErrorCode.USER_ALREADY_EXISTS) except Exception as e: - logger.error(f"Error creating user (admin): {e!s}", exc_info=True) + logger.exception("Error creating user (admin): %s", e) raise @@ -380,11 +380,11 @@ async def admin_update_user( try: user = await user_service.get_user(db, str(user_id)) updated_user = await user_service.update_user(db, user=user, obj_in=user_in) - logger.info(f"Admin {admin.email} updated user {updated_user.email}") + logger.info("Admin %s updated user %s", admin.email, updated_user.email) return updated_user except Exception as e: - logger.error(f"Error updating user (admin): {e!s}", exc_info=True) + logger.exception("Error updating user (admin): %s", e) raise @@ -413,14 +413,14 @@ async def admin_delete_user( ) await user_service.soft_delete_user(db, str(user_id)) - logger.info(f"Admin {admin.email} deleted user {user.email}") + logger.info("Admin %s deleted user %s", admin.email, user.email) return MessageResponse( success=True, message=f"User {user.email} has been deleted" ) except Exception as e: - logger.error(f"Error deleting user (admin): {e!s}", exc_info=True) + logger.exception("Error deleting user (admin): %s", e) raise @@ -440,14 +440,14 @@ async def admin_activate_user( try: user = await user_service.get_user(db, str(user_id)) await user_service.update_user(db, user=user, obj_in={"is_active": True}) - logger.info(f"Admin {admin.email} activated user {user.email}") + logger.info("Admin %s activated user %s", admin.email, user.email) return MessageResponse( success=True, message=f"User {user.email} has been activated" ) except Exception as e: - logger.error(f"Error activating user (admin): {e!s}", exc_info=True) + logger.exception("Error activating user (admin): %s", e) raise @@ -476,14 +476,14 @@ async def admin_deactivate_user( ) await user_service.update_user(db, user=user, obj_in={"is_active": False}) - logger.info(f"Admin {admin.email} deactivated user {user.email}") + logger.info("Admin %s deactivated user %s", admin.email, user.email) return MessageResponse( success=True, message=f"User {user.email} has been deactivated" ) except Exception as e: - logger.error(f"Error deactivating user (admin): {e!s}", exc_info=True) + logger.exception("Error deactivating user (admin): %s", e) raise @@ -528,8 +528,11 @@ async def admin_bulk_user_action( failed_count = requested_count - affected_count logger.info( - f"Admin {admin.email} performed bulk {bulk_action.action.value} " - f"on {affected_count} users ({failed_count} skipped/failed)" + "Admin %s performed bulk %s on %s users (%s skipped/failed)", + admin.email, + bulk_action.action.value, + affected_count, + failed_count, ) return BulkActionResult( @@ -541,7 +544,7 @@ async def admin_bulk_user_action( ) except Exception as e: # pragma: no cover - logger.error(f"Error in bulk user action: {e!s}", exc_info=True) + logger.exception("Error in bulk user action: %s", e) raise @@ -602,7 +605,7 @@ async def admin_list_organizations( return PaginatedResponse(data=orgs_with_count, pagination=pagination_meta) except Exception as e: - logger.error(f"Error listing organizations (admin): {e!s}", exc_info=True) + logger.exception("Error listing organizations (admin): %s", e) raise @@ -622,7 +625,7 @@ async def admin_create_organization( """Create a new organization.""" try: org = await organization_service.create_organization(db, obj_in=org_in) - logger.info(f"Admin {admin.email} created organization {org.name}") + logger.info("Admin %s created organization %s", admin.email, org.name) # Add member count org_dict = { @@ -639,10 +642,10 @@ async def admin_create_organization( return OrganizationResponse(**org_dict) except DuplicateEntryError as e: - logger.warning(f"Failed to create organization: {e!s}") + logger.warning("Failed to create organization: %s", e) raise DuplicateError(message=str(e), error_code=ErrorCode.ALREADY_EXISTS) except Exception as e: - logger.error(f"Error creating organization (admin): {e!s}", exc_info=True) + logger.exception("Error creating organization (admin): %s", e) raise @@ -695,7 +698,7 @@ async def admin_update_organization( updated_org = await organization_service.update_organization( db, org=org, obj_in=org_in ) - logger.info(f"Admin {admin.email} updated organization {updated_org.name}") + logger.info("Admin %s updated organization %s", admin.email, updated_org.name) org_dict = { "id": updated_org.id, @@ -713,7 +716,7 @@ async def admin_update_organization( return OrganizationResponse(**org_dict) except Exception as e: - logger.error(f"Error updating organization (admin): {e!s}", exc_info=True) + logger.exception("Error updating organization (admin): %s", e) raise @@ -733,14 +736,14 @@ async def admin_delete_organization( try: org = await organization_service.get_organization(db, str(org_id)) await organization_service.remove_organization(db, str(org_id)) - logger.info(f"Admin {admin.email} deleted organization {org.name}") + logger.info("Admin %s deleted organization %s", admin.email, org.name) return MessageResponse( success=True, message=f"Organization {org.name} has been deleted" ) except Exception as e: - logger.error(f"Error deleting organization (admin): {e!s}", exc_info=True) + logger.exception("Error deleting organization (admin): %s", e) raise @@ -784,9 +787,7 @@ async def admin_list_organization_members( except NotFoundError: raise except Exception as e: - logger.error( - f"Error listing organization members (admin): {e!s}", exc_info=True - ) + logger.exception("Error listing organization members (admin): %s", e) raise @@ -822,8 +823,11 @@ async def admin_add_organization_member( ) logger.info( - f"Admin {admin.email} added user {user.email} to organization {org.name} " - f"with role {request.role.value}" + "Admin %s added user %s to organization %s with role %s", + admin.email, + user.email, + org.name, + request.role.value, ) return MessageResponse( @@ -831,14 +835,12 @@ async def admin_add_organization_member( ) except DuplicateEntryError as e: - logger.warning(f"Failed to add user to organization: {e!s}") + logger.warning("Failed to add user to organization: %s", e) raise DuplicateError( message=str(e), error_code=ErrorCode.USER_ALREADY_EXISTS, field="user_id" ) except Exception as e: - logger.error( - f"Error adding member to organization (admin): {e!s}", exc_info=True - ) + logger.exception("Error adding member to organization (admin): %s", e) raise @@ -871,7 +873,10 @@ async def admin_remove_organization_member( ) logger.info( - f"Admin {admin.email} removed user {user.email} from organization {org.name}" + "Admin %s removed user %s from organization %s", + admin.email, + user.email, + org.name, ) return MessageResponse( @@ -882,9 +887,7 @@ async def admin_remove_organization_member( except NotFoundError: raise except Exception as e: # pragma: no cover - logger.error( - f"Error removing member from organization (admin): {e!s}", exc_info=True - ) + logger.exception("Error removing member from organization (admin): %s", e) raise @@ -953,7 +956,10 @@ async def admin_list_sessions( session_responses.append(session_response) logger.info( - f"Admin {admin.email} listed {len(session_responses)} sessions (total: {total})" + "Admin %s listed %s sessions (total: %s)", + admin.email, + len(session_responses), + total, ) pagination_meta = create_pagination_meta( @@ -966,5 +972,5 @@ async def admin_list_sessions( return PaginatedResponse(data=session_responses, pagination=pagination_meta) except Exception as e: # pragma: no cover - logger.error(f"Error listing sessions (admin): {e!s}", exc_info=True) + logger.exception("Error listing sessions (admin): %s", e) raise diff --git a/backend/app/api/routes/auth.py b/backend/app/api/routes/auth.py index 7eca77c..aaf7cf5 100755 --- a/backend/app/api/routes/auth.py +++ b/backend/app/api/routes/auth.py @@ -94,14 +94,15 @@ async def _create_login_session( await session_service.create_session(db, obj_in=session_data) logger.info( - f"{login_type.capitalize()} successful: {user.email} from {device_info.device_name} " - f"(IP: {device_info.ip_address})" + "%s successful: %s from %s (IP: %s)", + login_type.capitalize(), + user.email, + device_info.device_name, + device_info.ip_address, ) except Exception as session_err: # Log but don't fail login if session creation fails - logger.error( - f"Failed to create session for {user.email}: {session_err!s}", exc_info=True - ) + logger.exception("Failed to create session for %s: %s", user.email, session_err) @router.post( @@ -125,19 +126,19 @@ async def register_user( return user except DuplicateError: # SECURITY: Don't reveal if email exists - generic error message - logger.warning(f"Registration failed: duplicate email {user_data.email}") + logger.warning("Registration failed: duplicate email %s", user_data.email) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Registration failed. Please check your information and try again.", ) except AuthError as e: - logger.warning(f"Registration failed: {e!s}") + logger.warning("Registration failed: %s", e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Registration failed. Please check your information and try again.", ) except Exception as e: - logger.error(f"Unexpected error during registration: {e!s}", exc_info=True) + logger.exception("Unexpected error during registration: %s", e) raise DatabaseError( message="An unexpected error occurred. Please try again later.", error_code=ErrorCode.INTERNAL_ERROR, @@ -165,7 +166,7 @@ async def login( # Explicitly check for None result and raise correct exception if user is None: - logger.warning(f"Invalid login attempt for: {login_data.email}") + logger.warning("Invalid login attempt for: %s", login_data.email) raise AuthError( message="Invalid email or password", error_code=ErrorCode.INVALID_CREDENTIALS, @@ -181,11 +182,11 @@ async def login( except AuthenticationError as e: # Handle specific authentication errors like inactive accounts - logger.warning(f"Authentication failed: {e!s}") + logger.warning("Authentication failed: %s", e) raise AuthError(message=str(e), error_code=ErrorCode.INVALID_CREDENTIALS) except Exception as e: # Handle unexpected errors - logger.error(f"Unexpected error during login: {e!s}", exc_info=True) + logger.exception("Unexpected error during login: %s", e) raise DatabaseError( message="An unexpected error occurred. Please try again later.", error_code=ErrorCode.INTERNAL_ERROR, @@ -227,10 +228,10 @@ async def login_oauth( # Return full token response with user data return tokens except AuthenticationError as e: - logger.warning(f"OAuth authentication failed: {e!s}") + logger.warning("OAuth authentication failed: %s", e) raise AuthError(message=str(e), error_code=ErrorCode.INVALID_CREDENTIALS) except Exception as e: - logger.error(f"Unexpected error during OAuth login: {e!s}", exc_info=True) + logger.exception("Unexpected error during OAuth login: %s", e) raise DatabaseError( message="An unexpected error occurred. Please try again later.", error_code=ErrorCode.INTERNAL_ERROR, @@ -263,7 +264,8 @@ async def refresh_token( if not session: logger.warning( - f"Refresh token used for inactive or non-existent session: {refresh_payload.jti}" + "Refresh token used for inactive or non-existent session: %s", + refresh_payload.jti, ) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -286,9 +288,7 @@ async def refresh_token( new_expires_at=datetime.fromtimestamp(new_refresh_payload.exp, tz=UTC), ) except Exception as session_err: - logger.error( - f"Failed to update session {session.id}: {session_err!s}", exc_info=True - ) + logger.exception("Failed to update session %s: %s", session.id, session_err) # Continue anyway - tokens are already issued return tokens @@ -311,7 +311,7 @@ async def refresh_token( # Re-raise HTTP exceptions (like session revoked) raise except Exception as e: - logger.error(f"Unexpected error during token refresh: {e!s}") + logger.error("Unexpected error during token refresh: %s", e) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An unexpected error occurred. Please try again later.", @@ -358,11 +358,12 @@ async def request_password_reset( await email_service.send_password_reset_email( to_email=user.email, reset_token=reset_token, user_name=user.first_name ) - logger.info(f"Password reset requested for {user.email}") + logger.info("Password reset requested for %s", user.email) else: # Log attempt but don't reveal if email exists logger.warning( - f"Password reset requested for non-existent or inactive email: {reset_request.email}" + "Password reset requested for non-existent or inactive email: %s", + reset_request.email, ) # Always return success to prevent email enumeration @@ -371,7 +372,7 @@ async def request_password_reset( message="If your email is registered, you will receive a password reset link shortly", ) except Exception as e: - logger.error(f"Error processing password reset request: {e!s}", exc_info=True) + logger.exception("Error processing password reset request: %s", e) # Still return success to prevent information leakage return MessageResponse( success=True, @@ -432,12 +433,14 @@ async def confirm_password_reset( db, user_id=str(user.id) ) logger.info( - f"Password reset successful for {user.email}, invalidated {deactivated_count} sessions" + "Password reset successful for %s, invalidated %s sessions", + user.email, + deactivated_count, ) except Exception as session_error: # Log but don't fail password reset if session invalidation fails logger.error( - f"Failed to invalidate sessions after password reset: {session_error!s}" + "Failed to invalidate sessions after password reset: %s", session_error ) return MessageResponse( @@ -448,7 +451,7 @@ async def confirm_password_reset( except HTTPException: raise except Exception as e: - logger.error(f"Error confirming password reset: {e!s}", exc_info=True) + logger.exception("Error confirming password reset: %s", e) await db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -498,7 +501,7 @@ async def logout( ) except (TokenExpiredError, TokenInvalidError) as e: # Even if token is expired/invalid, try to deactivate session - logger.warning(f"Logout with invalid/expired token: {e!s}") + logger.warning("Logout with invalid/expired token: %s", e) # Don't fail - return success anyway return MessageResponse(success=True, message="Logged out successfully") @@ -509,8 +512,10 @@ async def logout( # Verify session belongs to current user (security check) if str(session.user_id) != str(current_user.id): logger.warning( - f"User {current_user.id} attempted to logout session {session.id} " - f"belonging to user {session.user_id}" + "User %s attempted to logout session %s belonging to user %s", + current_user.id, + session.id, + session.user_id, ) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -521,14 +526,17 @@ async def logout( await session_service.deactivate(db, session_id=str(session.id)) logger.info( - f"User {current_user.id} logged out from {session.device_name} " - f"(session {session.id})" + "User %s logged out from %s (session %s)", + current_user.id, + session.device_name, + session.id, ) else: # Session not found - maybe already deleted or never existed # Return success anyway (idempotent) logger.info( - f"Logout requested for non-existent session (JTI: {refresh_payload.jti})" + "Logout requested for non-existent session (JTI: %s)", + refresh_payload.jti, ) return MessageResponse(success=True, message="Logged out successfully") @@ -536,9 +544,7 @@ async def logout( except HTTPException: raise except Exception as e: - logger.error( - f"Error during logout for user {current_user.id}: {e!s}", exc_info=True - ) + logger.exception("Error during logout for user %s: %s", current_user.id, e) # Don't expose error details return MessageResponse(success=True, message="Logged out successfully") @@ -581,7 +587,7 @@ async def logout_all( ) logger.info( - f"User {current_user.id} logged out from all devices ({count} sessions)" + "User %s logged out from all devices (%s sessions)", current_user.id, count ) return MessageResponse( @@ -590,9 +596,7 @@ async def logout_all( ) except Exception as e: - logger.error( - f"Error during logout-all for user {current_user.id}: {e!s}", exc_info=True - ) + logger.exception("Error during logout-all for user %s: %s", current_user.id, e) await db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, diff --git a/backend/app/api/routes/oauth.py b/backend/app/api/routes/oauth.py index eb28c07..91f937f 100644 --- a/backend/app/api/routes/oauth.py +++ b/backend/app/api/routes/oauth.py @@ -84,14 +84,16 @@ async def _create_oauth_login_session( await session_service.create_session(db, obj_in=session_data) logger.info( - f"OAuth login successful: {user.email} via {provider} " - f"from {device_info.device_name} (IP: {device_info.ip_address})" + "OAuth login successful: %s via %s from %s (IP: %s)", + user.email, + provider, + device_info.device_name, + device_info.ip_address, ) except Exception as session_err: # Log but don't fail login if session creation fails - logger.error( - f"Failed to create session for OAuth login {user.email}: {session_err!s}", - exc_info=True, + logger.exception( + "Failed to create session for OAuth login %s: %s", user.email, session_err ) @@ -176,13 +178,13 @@ async def get_authorization_url( } except AuthError as e: - logger.warning(f"OAuth authorization failed: {e!s}") + logger.warning("OAuth authorization failed: %s", e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception as e: - logger.error(f"OAuth authorization error: {e!s}", exc_info=True) + logger.exception("OAuth authorization error: %s", e) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create authorization URL", @@ -250,13 +252,13 @@ async def handle_callback( return result except AuthError as e: - logger.warning(f"OAuth callback failed: {e!s}") + logger.warning("OAuth callback failed: %s", e) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e), ) except Exception as e: - logger.error(f"OAuth callback error: {e!s}", exc_info=True) + logger.exception("OAuth callback error: %s", e) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="OAuth authentication failed", @@ -337,13 +339,13 @@ async def unlink_account( ) except AuthError as e: - logger.warning(f"OAuth unlink failed for {current_user.email}: {e!s}") + logger.warning("OAuth unlink failed for %s: %s", current_user.email, e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception as e: - logger.error(f"OAuth unlink error: {e!s}", exc_info=True) + logger.exception("OAuth unlink error: %s", e) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to unlink OAuth account", @@ -419,13 +421,13 @@ async def start_link( } except AuthError as e: - logger.warning(f"OAuth link authorization failed: {e!s}") + logger.warning("OAuth link authorization failed: %s", e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception as e: - logger.error(f"OAuth link error: {e!s}", exc_info=True) + logger.exception("OAuth link error: %s", e) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create authorization URL", diff --git a/backend/app/api/routes/oauth_provider.py b/backend/app/api/routes/oauth_provider.py index dd61d27..203961c 100644 --- a/backend/app/api/routes/oauth_provider.py +++ b/backend/app/api/routes/oauth_provider.py @@ -452,7 +452,7 @@ async def token( except Exception as e: # Log malformed Basic auth for security monitoring logger.warning( - f"Malformed Basic auth header in token request: {type(e).__name__}" + "Malformed Basic auth header in token request: %s", type(e).__name__ ) # Fall back to form body @@ -563,7 +563,8 @@ async def revoke( except Exception as e: # Log malformed Basic auth for security monitoring logger.warning( - f"Malformed Basic auth header in revoke request: {type(e).__name__}" + "Malformed Basic auth header in revoke request: %s", + type(e).__name__, ) # Fall back to form body @@ -585,7 +586,7 @@ async def revoke( ) except Exception as e: # Log but don't expose errors per RFC 7009 - logger.warning(f"Token revocation error: {e}") + logger.warning("Token revocation error: %s", e) # Always return 200 OK per RFC 7009 return {"status": "ok"} @@ -634,7 +635,8 @@ async def introspect( except Exception as e: # Log malformed Basic auth for security monitoring logger.warning( - f"Malformed Basic auth header in introspect request: {type(e).__name__}" + "Malformed Basic auth header in introspect request: %s", + type(e).__name__, ) # Fall back to form body @@ -654,7 +656,7 @@ async def introspect( headers={"WWW-Authenticate": "Basic"}, ) except Exception as e: - logger.warning(f"Token introspection error: {e}") + logger.warning("Token introspection error: %s", e) return OAuthTokenIntrospectionResponse(active=False) # pyright: ignore[reportCallIssue] diff --git a/backend/app/api/routes/organizations.py b/backend/app/api/routes/organizations.py index 90c7411..2264ef7 100755 --- a/backend/app/api/routes/organizations.py +++ b/backend/app/api/routes/organizations.py @@ -77,7 +77,7 @@ async def get_my_organizations( return orgs_with_data except Exception as e: - logger.error(f"Error getting user organizations: {e!s}", exc_info=True) + logger.exception("Error getting user organizations: %s", e) raise @@ -116,7 +116,7 @@ async def get_organization( return OrganizationResponse(**org_dict) except Exception as e: - logger.error(f"Error getting organization: {e!s}", exc_info=True) + logger.exception("Error getting organization: %s", e) raise @@ -160,7 +160,7 @@ async def get_organization_members( return PaginatedResponse(data=member_responses, pagination=pagination_meta) except Exception as e: - logger.error(f"Error getting organization members: {e!s}", exc_info=True) + logger.exception("Error getting organization members: %s", e) raise @@ -188,7 +188,7 @@ async def update_organization( db, org=org, obj_in=org_in ) logger.info( - f"User {current_user.email} updated organization {updated_org.name}" + "User %s updated organization %s", current_user.email, updated_org.name ) org_dict = { @@ -207,5 +207,5 @@ async def update_organization( return OrganizationResponse(**org_dict) except Exception as e: - logger.error(f"Error updating organization: {e!s}", exc_info=True) + logger.exception("Error updating organization: %s", e) raise diff --git a/backend/app/api/routes/sessions.py b/backend/app/api/routes/sessions.py index b17270f..1b66bd8 100755 --- a/backend/app/api/routes/sessions.py +++ b/backend/app/api/routes/sessions.py @@ -74,9 +74,7 @@ async def list_my_sessions( # For now, we'll mark current based on most recent activity except Exception as e: # Optional token parsing - silently ignore failures - logger.debug( - f"Failed to decode access token for session marking: {e!s}" - ) + logger.debug("Failed to decode access token for session marking: %s", e) # Convert to response format session_responses = [] @@ -98,7 +96,7 @@ async def list_my_sessions( session_responses.append(session_response) logger.info( - f"User {current_user.id} listed {len(session_responses)} active sessions" + "User %s listed %s active sessions", current_user.id, len(session_responses) ) return SessionListResponse( @@ -106,9 +104,7 @@ async def list_my_sessions( ) except Exception as e: - logger.error( - f"Error listing sessions for user {current_user.id}: {e!s}", exc_info=True - ) + logger.exception("Error listing sessions for user %s: %s", current_user.id, e) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve sessions", @@ -161,8 +157,10 @@ async def revoke_session( # Verify session belongs to current user if str(session.user_id) != str(current_user.id): logger.warning( - f"User {current_user.id} attempted to revoke session {session_id} " - f"belonging to user {session.user_id}" + "User %s attempted to revoke session %s belonging to user %s", + current_user.id, + session_id, + session.user_id, ) raise AuthorizationError( message="You can only revoke your own sessions", @@ -173,8 +171,10 @@ async def revoke_session( await session_service.deactivate(db, session_id=str(session_id)) logger.info( - f"User {current_user.id} revoked session {session_id} " - f"({session.device_name})" + "User %s revoked session %s (%s)", + current_user.id, + session_id, + session.device_name, ) return MessageResponse( @@ -185,7 +185,7 @@ async def revoke_session( except (NotFoundError, AuthorizationError): raise except Exception as e: - logger.error(f"Error revoking session {session_id}: {e!s}", exc_info=True) + logger.exception("Error revoking session %s: %s", session_id, e) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to revoke session", @@ -229,7 +229,7 @@ async def cleanup_expired_sessions( ) logger.info( - f"User {current_user.id} cleaned up {deleted_count} expired sessions" + "User %s cleaned up %s expired sessions", current_user.id, deleted_count ) return MessageResponse( @@ -237,9 +237,8 @@ async def cleanup_expired_sessions( ) except Exception as e: - logger.error( - f"Error cleaning up sessions for user {current_user.id}: {e!s}", - exc_info=True, + logger.exception( + "Error cleaning up sessions for user %s: %s", current_user.id, e ) await db.rollback() raise HTTPException( diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 694b18b..8e87760 100755 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -90,7 +90,7 @@ async def list_users( return PaginatedResponse(data=users, pagination=pagination_meta) except Exception as e: - logger.error(f"Error listing users: {e!s}", exc_info=True) + logger.exception("Error listing users: %s", e) raise @@ -143,15 +143,13 @@ async def update_current_user( updated_user = await user_service.update_user( db, user=current_user, obj_in=user_update ) - logger.info(f"User {current_user.id} updated their profile") + logger.info("User %s updated their profile", current_user.id) return updated_user except ValueError as e: - logger.error(f"Error updating user {current_user.id}: {e!s}") + logger.error("Error updating user %s: %s", current_user.id, e) raise except Exception as e: - logger.error( - f"Unexpected error updating user {current_user.id}: {e!s}", exc_info=True - ) + logger.exception("Unexpected error updating user %s: %s", current_user.id, e) raise @@ -184,7 +182,9 @@ async def get_user_by_id( # Check permissions if str(user_id) != str(current_user.id) and not current_user.is_superuser: logger.warning( - f"User {current_user.id} attempted to access user {user_id} without permission" + "User %s attempted to access user %s without permission", + current_user.id, + user_id, ) raise AuthorizationError( message="Not enough permissions to view this user", @@ -229,7 +229,9 @@ async def update_user( if not is_own_profile and not current_user.is_superuser: logger.warning( - f"User {current_user.id} attempted to update user {user_id} without permission" + "User %s attempted to update user %s without permission", + current_user.id, + user_id, ) raise AuthorizationError( message="Not enough permissions to update this user", @@ -241,13 +243,13 @@ async def update_user( try: updated_user = await user_service.update_user(db, user=user, obj_in=user_update) - logger.info(f"User {user_id} updated by {current_user.id}") + logger.info("User %s updated by %s", user_id, current_user.id) return updated_user except ValueError as e: - logger.error(f"Error updating user {user_id}: {e!s}") + logger.error("Error updating user %s: %s", user_id, e) raise except Exception as e: - logger.error(f"Unexpected error updating user {user_id}: {e!s}", exc_info=True) + logger.exception("Unexpected error updating user %s: %s", user_id, e) raise @@ -287,19 +289,19 @@ async def change_current_user_password( ) if success: - logger.info(f"User {current_user.id} changed their password") + logger.info("User %s changed their password", current_user.id) return MessageResponse( success=True, message="Password changed successfully" ) except AuthenticationError as e: logger.warning( - f"Failed password change attempt for user {current_user.id}: {e!s}" + "Failed password change attempt for user %s: %s", current_user.id, e ) raise AuthorizationError( message=str(e), error_code=ErrorCode.INVALID_CREDENTIALS ) except Exception as e: - logger.error(f"Error changing password for user {current_user.id}: {e!s}") + logger.error("Error changing password for user %s: %s", current_user.id, e) raise @@ -343,13 +345,13 @@ async def delete_user( try: # Use soft delete instead of hard delete await user_service.soft_delete_user(db, str(user_id)) - logger.info(f"User {user_id} soft-deleted by {current_user.id}") + logger.info("User %s soft-deleted by %s", user_id, current_user.id) return MessageResponse( success=True, message=f"User {user_id} deleted successfully" ) except ValueError as e: - logger.error(f"Error deleting user {user_id}: {e!s}") + logger.error("Error deleting user %s: %s", user_id, e) raise except Exception as e: - logger.error(f"Unexpected error deleting user {user_id}: {e!s}", exc_info=True) + logger.exception("Unexpected error deleting user %s: %s", user_id, e) raise diff --git a/backend/app/core/database.py b/backend/app/core/database.py index 5be0fbe..fcd6fa3 100755 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -139,7 +139,7 @@ async def async_transaction_scope() -> AsyncGenerator[AsyncSession, None]: logger.debug("Async transaction committed successfully") except Exception as e: await session.rollback() - logger.error(f"Async transaction failed, rolling back: {e!s}") + logger.error("Async transaction failed, rolling back: %s", e) raise finally: await session.close() @@ -155,7 +155,7 @@ async def check_async_database_health() -> bool: await db.execute(text("SELECT 1")) return True except Exception as e: - logger.error(f"Async database health check failed: {e!s}") + logger.error("Async database health check failed: %s", e) return False diff --git a/backend/app/core/exceptions.py b/backend/app/core/exceptions.py index 8e2bc61..133dcbb 100644 --- a/backend/app/core/exceptions.py +++ b/backend/app/core/exceptions.py @@ -143,8 +143,11 @@ async def api_exception_handler(request: Request, exc: APIException) -> JSONResp Returns a standardized error response with error code and message. """ logger.warning( - f"API exception: {exc.error_code} - {exc.message} " - f"(status: {exc.status_code}, path: {request.url.path})" + "API exception: %s - %s (status: %s, path: %s)", + exc.error_code, + exc.message, + exc.status_code, + request.url.path, ) error_response = ErrorResponse( @@ -186,7 +189,9 @@ async def validation_exception_handler( ) ) - logger.warning(f"Validation error: {len(errors)} errors (path: {request.url.path})") + logger.warning( + "Validation error: %s errors (path: %s)", len(errors), request.url.path + ) error_response = ErrorResponse(errors=errors) @@ -218,7 +223,10 @@ async def http_exception_handler(request: Request, exc: HTTPException) -> JSONRe ) logger.warning( - f"HTTP exception: {exc.status_code} - {exc.detail} (path: {request.url.path})" + "HTTP exception: %s - %s (path: %s)", + exc.status_code, + exc.detail, + request.url.path, ) error_response = ErrorResponse( @@ -239,10 +247,11 @@ async def unhandled_exception_handler(request: Request, exc: Exception) -> JSONR Logs the full exception and returns a generic error response to avoid leaking sensitive information in production. """ - logger.error( - f"Unhandled exception: {type(exc).__name__} - {exc!s} " - f"(path: {request.url.path})", - exc_info=True, + logger.exception( + "Unhandled exception: %s - %s (path: %s)", + type(exc).__name__, + exc, + request.url.path, ) # In production, don't expose internal error details diff --git a/backend/app/init_db.py b/backend/app/init_db.py index ae79f6a..ac4a272 100644 --- a/backend/app/init_db.py +++ b/backend/app/init_db.py @@ -44,7 +44,8 @@ async def init_db() -> User | None: if not settings.FIRST_SUPERUSER_EMAIL or not settings.FIRST_SUPERUSER_PASSWORD: logger.warning( "First superuser credentials not configured in settings. " - f"Using defaults: {superuser_email}" + "Using defaults: %s", + superuser_email, ) async with SessionLocal() as session: @@ -53,7 +54,7 @@ async def init_db() -> User | None: existing_user = await user_crud.get_by_email(session, email=superuser_email) if existing_user: - logger.info(f"Superuser already exists: {existing_user.email}") + logger.info("Superuser already exists: %s", existing_user.email) return existing_user # Create superuser if doesn't exist @@ -69,7 +70,7 @@ async def init_db() -> User | None: await session.commit() await session.refresh(user) - logger.info(f"Created first superuser: {user.email}") + logger.info("Created first superuser: %s", user.email) # Create demo data if in demo mode if settings.DEMO_MODE: @@ -79,7 +80,7 @@ async def init_db() -> User | None: except Exception as e: await session.rollback() - logger.error(f"Error initializing database: {e}") + logger.error("Error initializing database: %s", e) raise @@ -92,7 +93,7 @@ async def load_demo_data(session): """Load demo data from JSON file.""" demo_data_path = Path(__file__).parent / "core" / "demo_data.json" if not demo_data_path.exists(): - logger.warning(f"Demo data file not found: {demo_data_path}") + logger.warning("Demo data file not found: %s", demo_data_path) return try: @@ -119,7 +120,7 @@ async def load_demo_data(session): session.add(org) await session.flush() # Flush to get ID org_map[org.slug] = org - logger.info(f"Created demo organization: {org.name}") + logger.info("Created demo organization: %s", org.name) else: # We can't easily get the ORM object from raw SQL result for map without querying again or mapping # So let's just query it properly if we need it for relationships @@ -174,7 +175,10 @@ async def load_demo_data(session): ) logger.info( - f"Created demo user: {user.email} (created {days_ago} days ago, active={user_data.get('is_active', True)})" + "Created demo user: %s (created %s days ago, active=%s)", + user.email, + days_ago, + user_data.get("is_active", True), ) # Add to organization if specified @@ -187,15 +191,15 @@ async def load_demo_data(session): user_id=user.id, organization_id=org.id, role=role ) session.add(member) - logger.info(f"Added {user.email} to {org.name} as {role}") + logger.info("Added %s to %s as %s", user.email, org.name, role) else: - logger.info(f"Demo user already exists: {existing_user.email}") + logger.info("Demo user already exists: %s", existing_user.email) await session.commit() logger.info("Demo data loaded successfully") except Exception as e: - logger.error(f"Error loading demo data: {e}") + logger.error("Error loading demo data: %s", e) raise diff --git a/backend/app/main.py b/backend/app/main.py index 27c6578..9976d1d 100755 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -320,7 +320,7 @@ async def health_check() -> JSONResponse: "message": f"Database connection failed: {e!s}", } response_status = status.HTTP_503_SERVICE_UNAVAILABLE - logger.error(f"Health check failed - database error: {e}") + logger.error("Health check failed - database error: %s", e) return JSONResponse(status_code=response_status, content=health_status) diff --git a/backend/app/repositories/base.py b/backend/app/repositories/base.py index 09bb166..23c8a5f 100644 --- a/backend/app/repositories/base.py +++ b/backend/app/repositories/base.py @@ -68,7 +68,7 @@ class BaseRepository[ else: uuid_obj = uuid.UUID(str(id)) except (ValueError, AttributeError, TypeError) as e: - logger.warning(f"Invalid UUID format: {id} - {e!s}") + logger.warning("Invalid UUID format: %s - %s", id, e) return None try: @@ -81,7 +81,9 @@ class BaseRepository[ result = await db.execute(query) return result.scalar_one_or_none() except Exception as e: - logger.error(f"Error retrieving {self.model.__name__} with id {id}: {e!s}") + logger.error( + "Error retrieving %s with id %s: %s", self.model.__name__, id, e + ) raise async def get_multi( @@ -113,7 +115,7 @@ class BaseRepository[ return list(result.scalars().all()) except Exception as e: logger.error( - f"Error retrieving multiple {self.model.__name__} records: {e!s}" + "Error retrieving multiple %s records: %s", self.model.__name__, e ) raise @@ -138,22 +140,24 @@ class BaseRepository[ error_msg = str(e.orig) if hasattr(e, "orig") else str(e) if "unique" in error_msg.lower() or "duplicate" in error_msg.lower(): logger.warning( - f"Duplicate entry attempted for {self.model.__name__}: {error_msg}" + "Duplicate entry attempted for %s: %s", + self.model.__name__, + error_msg, ) raise DuplicateEntryError( f"A {self.model.__name__} with this data already exists" ) - logger.error(f"Integrity error creating {self.model.__name__}: {error_msg}") + logger.error( + "Integrity error creating %s: %s", self.model.__name__, error_msg + ) raise IntegrityConstraintError(f"Database integrity error: {error_msg}") except (OperationalError, DataError) as e: # pragma: no cover await db.rollback() - logger.error(f"Database error creating {self.model.__name__}: {e!s}") + logger.error("Database error creating %s: %s", self.model.__name__, e) raise IntegrityConstraintError(f"Database operation failed: {e!s}") except Exception as e: # pragma: no cover await db.rollback() - logger.error( - f"Unexpected error creating {self.model.__name__}: {e!s}", exc_info=True - ) + logger.exception("Unexpected error creating %s: %s", self.model.__name__, e) raise async def update( @@ -184,22 +188,24 @@ class BaseRepository[ error_msg = str(e.orig) if hasattr(e, "orig") else str(e) if "unique" in error_msg.lower() or "duplicate" in error_msg.lower(): logger.warning( - f"Duplicate entry attempted for {self.model.__name__}: {error_msg}" + "Duplicate entry attempted for %s: %s", + self.model.__name__, + error_msg, ) raise DuplicateEntryError( f"A {self.model.__name__} with this data already exists" ) - logger.error(f"Integrity error updating {self.model.__name__}: {error_msg}") + logger.error( + "Integrity error updating %s: %s", self.model.__name__, error_msg + ) raise IntegrityConstraintError(f"Database integrity error: {error_msg}") except (OperationalError, DataError) as e: await db.rollback() - logger.error(f"Database error updating {self.model.__name__}: {e!s}") + logger.error("Database error updating %s: %s", self.model.__name__, e) raise IntegrityConstraintError(f"Database operation failed: {e!s}") except Exception as e: await db.rollback() - logger.error( - f"Unexpected error updating {self.model.__name__}: {e!s}", exc_info=True - ) + logger.exception("Unexpected error updating %s: %s", self.model.__name__, e) raise async def remove(self, db: AsyncSession, *, id: str) -> ModelType | None: @@ -210,7 +216,7 @@ class BaseRepository[ else: uuid_obj = uuid.UUID(str(id)) except (ValueError, AttributeError, TypeError) as e: - logger.warning(f"Invalid UUID format for deletion: {id} - {e!s}") + logger.warning("Invalid UUID format for deletion: %s - %s", id, e) return None try: @@ -221,7 +227,7 @@ class BaseRepository[ if obj is None: logger.warning( - f"{self.model.__name__} with id {id} not found for deletion" + "%s with id %s not found for deletion", self.model.__name__, id ) return None @@ -231,15 +237,16 @@ class BaseRepository[ except IntegrityError as e: await db.rollback() error_msg = str(e.orig) if hasattr(e, "orig") else str(e) - logger.error(f"Integrity error deleting {self.model.__name__}: {error_msg}") + logger.error( + "Integrity error deleting %s: %s", self.model.__name__, error_msg + ) raise IntegrityConstraintError( f"Cannot delete {self.model.__name__}: referenced by other records" ) except Exception as e: await db.rollback() - logger.error( - f"Error deleting {self.model.__name__} with id {id}: {e!s}", - exc_info=True, + logger.exception( + "Error deleting %s with id %s: %s", self.model.__name__, id, e ) raise @@ -298,7 +305,7 @@ class BaseRepository[ return items, total except Exception as e: # pragma: no cover logger.error( - f"Error retrieving paginated {self.model.__name__} records: {e!s}" + "Error retrieving paginated %s records: %s", self.model.__name__, e ) raise @@ -308,7 +315,7 @@ class BaseRepository[ result = await db.execute(select(func.count(self.model.id))) return result.scalar_one() except Exception as e: - logger.error(f"Error counting {self.model.__name__} records: {e!s}") + logger.error("Error counting %s records: %s", self.model.__name__, e) raise async def exists(self, db: AsyncSession, id: str) -> bool: @@ -330,7 +337,7 @@ class BaseRepository[ else: uuid_obj = uuid.UUID(str(id)) except (ValueError, AttributeError, TypeError) as e: - logger.warning(f"Invalid UUID format for soft deletion: {id} - {e!s}") + logger.warning("Invalid UUID format for soft deletion: %s - %s", id, e) return None try: @@ -341,12 +348,12 @@ class BaseRepository[ if obj is None: logger.warning( - f"{self.model.__name__} with id {id} not found for soft deletion" + "%s with id %s not found for soft deletion", self.model.__name__, id ) return None if not hasattr(self.model, "deleted_at"): - logger.error(f"{self.model.__name__} does not support soft deletes") + logger.error("%s does not support soft deletes", self.model.__name__) raise InvalidInputError( f"{self.model.__name__} does not have a deleted_at column" ) @@ -358,9 +365,8 @@ class BaseRepository[ return obj except Exception as e: await db.rollback() - logger.error( - f"Error soft deleting {self.model.__name__} with id {id}: {e!s}", - exc_info=True, + logger.exception( + "Error soft deleting %s with id %s: %s", self.model.__name__, id, e ) raise @@ -376,7 +382,7 @@ class BaseRepository[ else: uuid_obj = uuid.UUID(str(id)) except (ValueError, AttributeError, TypeError) as e: - logger.warning(f"Invalid UUID format for restoration: {id} - {e!s}") + logger.warning("Invalid UUID format for restoration: %s - %s", id, e) return None try: @@ -388,14 +394,16 @@ class BaseRepository[ ) obj = result.scalar_one_or_none() else: - logger.error(f"{self.model.__name__} does not support soft deletes") + logger.error("%s does not support soft deletes", self.model.__name__) raise InvalidInputError( f"{self.model.__name__} does not have a deleted_at column" ) if obj is None: logger.warning( - f"Soft-deleted {self.model.__name__} with id {id} not found for restoration" + "Soft-deleted %s with id %s not found for restoration", + self.model.__name__, + id, ) return None @@ -406,8 +414,7 @@ class BaseRepository[ return obj except Exception as e: await db.rollback() - logger.error( - f"Error restoring {self.model.__name__} with id {id}: {e!s}", - exc_info=True, + logger.exception( + "Error restoring %s with id %s: %s", self.model.__name__, id, e ) raise diff --git a/backend/app/repositories/oauth_account.py b/backend/app/repositories/oauth_account.py index 0cc4683..948d4fa 100644 --- a/backend/app/repositories/oauth_account.py +++ b/backend/app/repositories/oauth_account.py @@ -50,7 +50,10 @@ class OAuthAccountRepository( return result.scalar_one_or_none() except Exception as e: # pragma: no cover logger.error( - f"Error getting OAuth account for {provider}:{provider_user_id}: {e!s}" + "Error getting OAuth account for %s:%s: %s", + provider, + provider_user_id, + e, ) raise @@ -76,7 +79,7 @@ class OAuthAccountRepository( return result.scalar_one_or_none() except Exception as e: # pragma: no cover logger.error( - f"Error getting OAuth account for {provider} email {email}: {e!s}" + "Error getting OAuth account for %s email %s: %s", provider, email, e ) raise @@ -97,7 +100,7 @@ class OAuthAccountRepository( ) return list(result.scalars().all()) except Exception as e: # pragma: no cover - logger.error(f"Error getting OAuth accounts for user {user_id}: {e!s}") + logger.error("Error getting OAuth accounts for user %s: %s", user_id, e) raise async def get_user_account_by_provider( @@ -122,7 +125,10 @@ class OAuthAccountRepository( return result.scalar_one_or_none() except Exception as e: # pragma: no cover logger.error( - f"Error getting OAuth account for user {user_id}, provider {provider}: {e!s}" + "Error getting OAuth account for user %s, provider %s: %s", + user_id, + provider, + e, ) raise @@ -145,7 +151,9 @@ class OAuthAccountRepository( await db.refresh(db_obj) logger.info( - f"OAuth account created: {obj_in.provider} linked to user {obj_in.user_id}" + "OAuth account created: %s linked to user %s", + obj_in.provider, + obj_in.user_id, ) return db_obj except IntegrityError as e: # pragma: no cover @@ -153,16 +161,18 @@ class OAuthAccountRepository( error_msg = str(e.orig) if hasattr(e, "orig") else str(e) if "uq_oauth_provider_user" in error_msg.lower(): logger.warning( - f"OAuth account already exists: {obj_in.provider}:{obj_in.provider_user_id}" + "OAuth account already exists: %s:%s", + obj_in.provider, + obj_in.provider_user_id, ) raise DuplicateEntryError( f"This {obj_in.provider} account is already linked to another user" ) - logger.error(f"Integrity error creating OAuth account: {error_msg}") + logger.error("Integrity error creating OAuth account: %s", error_msg) raise DuplicateEntryError(f"Failed to create OAuth account: {error_msg}") except Exception as e: # pragma: no cover await db.rollback() - logger.error(f"Error creating OAuth account: {e!s}", exc_info=True) + logger.exception("Error creating OAuth account: %s", e) raise async def delete_account( @@ -189,18 +199,20 @@ class OAuthAccountRepository( deleted = result.rowcount > 0 if deleted: logger.info( - f"OAuth account deleted: {provider} unlinked from user {user_id}" + "OAuth account deleted: %s unlinked from user %s", provider, user_id ) else: logger.warning( - f"OAuth account not found for deletion: {provider} for user {user_id}" + "OAuth account not found for deletion: %s for user %s", + provider, + user_id, ) return deleted except Exception as e: # pragma: no cover await db.rollback() logger.error( - f"Error deleting OAuth account {provider} for user {user_id}: {e!s}" + "Error deleting OAuth account %s for user %s: %s", provider, user_id, e ) raise @@ -229,7 +241,7 @@ class OAuthAccountRepository( return account except Exception as e: # pragma: no cover await db.rollback() - logger.error(f"Error updating OAuth tokens: {e!s}") + logger.error("Error updating OAuth tokens: %s", e) raise diff --git a/backend/app/repositories/oauth_client.py b/backend/app/repositories/oauth_client.py index f38858b..4d67a8a 100644 --- a/backend/app/repositories/oauth_client.py +++ b/backend/app/repositories/oauth_client.py @@ -42,7 +42,7 @@ class OAuthClientRepository( ) return result.scalar_one_or_none() except Exception as e: # pragma: no cover - logger.error(f"Error getting OAuth client {client_id}: {e!s}") + logger.error("Error getting OAuth client %s: %s", client_id, e) raise async def create_client( @@ -80,17 +80,17 @@ class OAuthClientRepository( await db.refresh(db_obj) logger.info( - f"OAuth client created: {obj_in.client_name} ({client_id[:8]}...)" + "OAuth client created: %s (%s...)", obj_in.client_name, client_id[:8] ) return db_obj, client_secret except IntegrityError as e: # pragma: no cover await db.rollback() error_msg = str(e.orig) if hasattr(e, "orig") else str(e) - logger.error(f"Error creating OAuth client: {error_msg}") + logger.error("Error creating OAuth client: %s", error_msg) raise DuplicateEntryError(f"Failed to create OAuth client: {error_msg}") except Exception as e: # pragma: no cover await db.rollback() - logger.error(f"Error creating OAuth client: {e!s}", exc_info=True) + logger.exception("Error creating OAuth client: %s", e) raise async def deactivate_client( @@ -107,11 +107,11 @@ class OAuthClientRepository( await db.commit() await db.refresh(client) - logger.info(f"OAuth client deactivated: {client.client_name}") + logger.info("OAuth client deactivated: %s", client.client_name) return client except Exception as e: # pragma: no cover await db.rollback() - logger.error(f"Error deactivating OAuth client {client_id}: {e!s}") + logger.error("Error deactivating OAuth client %s: %s", client_id, e) raise async def validate_redirect_uri( @@ -125,7 +125,7 @@ class OAuthClientRepository( return redirect_uri in (client.redirect_uris or []) except Exception as e: # pragma: no cover - logger.error(f"Error validating redirect URI: {e!s}") + logger.error("Error validating redirect URI: %s", e) return False async def verify_client_secret( @@ -158,7 +158,7 @@ class OAuthClientRepository( secret_hash = hashlib.sha256(client_secret.encode()).hexdigest() return secrets.compare_digest(stored_hash, secret_hash) except Exception as e: # pragma: no cover - logger.error(f"Error verifying client secret: {e!s}") + logger.error("Error verifying client secret: %s", e) return False async def get_all_clients( @@ -173,7 +173,7 @@ class OAuthClientRepository( result = await db.execute(query) return list(result.scalars().all()) except Exception as e: # pragma: no cover - logger.error(f"Error getting all OAuth clients: {e!s}") + logger.error("Error getting all OAuth clients: %s", e) raise async def delete_client(self, db: AsyncSession, *, client_id: str) -> bool: @@ -186,14 +186,14 @@ class OAuthClientRepository( deleted = result.rowcount > 0 if deleted: - logger.info(f"OAuth client deleted: {client_id}") + logger.info("OAuth client deleted: %s", client_id) else: - logger.warning(f"OAuth client not found for deletion: {client_id}") + logger.warning("OAuth client not found for deletion: %s", client_id) return deleted except Exception as e: # pragma: no cover await db.rollback() - logger.error(f"Error deleting OAuth client {client_id}: {e!s}") + logger.error("Error deleting OAuth client %s: %s", client_id, e) raise diff --git a/backend/app/repositories/oauth_state.py b/backend/app/repositories/oauth_state.py index 77a0ac0..6b62f47 100644 --- a/backend/app/repositories/oauth_state.py +++ b/backend/app/repositories/oauth_state.py @@ -42,16 +42,16 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch await db.commit() await db.refresh(db_obj) - logger.debug(f"OAuth state created for {obj_in.provider}") + logger.debug("OAuth state created for %s", obj_in.provider) return db_obj except IntegrityError as e: # pragma: no cover await db.rollback() error_msg = str(e.orig) if hasattr(e, "orig") else str(e) - logger.error(f"OAuth state collision: {error_msg}") + logger.error("OAuth state collision: %s", error_msg) raise DuplicateEntryError("Failed to create OAuth state, please retry") except Exception as e: # pragma: no cover await db.rollback() - logger.error(f"Error creating OAuth state: {e!s}", exc_info=True) + logger.exception("Error creating OAuth state: %s", e) raise async def get_and_consume_state( @@ -65,7 +65,7 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch db_obj = result.scalar_one_or_none() if db_obj is None: - logger.warning(f"OAuth state not found: {state[:8]}...") + logger.warning("OAuth state not found: %s...", state[:8]) return None now = datetime.now(UTC) @@ -74,7 +74,7 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch expires_at = expires_at.replace(tzinfo=UTC) if expires_at < now: - logger.warning(f"OAuth state expired: {state[:8]}...") + logger.warning("OAuth state expired: %s...", state[:8]) await db.delete(db_obj) await db.commit() return None @@ -82,11 +82,11 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch await db.delete(db_obj) await db.commit() - logger.debug(f"OAuth state consumed: {state[:8]}...") + logger.debug("OAuth state consumed: %s...", state[:8]) return db_obj except Exception as e: # pragma: no cover await db.rollback() - logger.error(f"Error consuming OAuth state: {e!s}") + logger.error("Error consuming OAuth state: %s", e) raise async def cleanup_expired(self, db: AsyncSession) -> int: @@ -100,12 +100,12 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch count = result.rowcount if count > 0: - logger.info(f"Cleaned up {count} expired OAuth states") + logger.info("Cleaned up %s expired OAuth states", count) return count except Exception as e: # pragma: no cover await db.rollback() - logger.error(f"Error cleaning up expired OAuth states: {e!s}") + logger.error("Error cleaning up expired OAuth states: %s", e) raise diff --git a/backend/app/repositories/organization.py b/backend/app/repositories/organization.py index 827a47f..3272250 100644 --- a/backend/app/repositories/organization.py +++ b/backend/app/repositories/organization.py @@ -35,7 +35,7 @@ class OrganizationRepository( ) return result.scalar_one_or_none() except Exception as e: - logger.error(f"Error getting organization by slug {slug}: {e!s}") + logger.error("Error getting organization by slug %s: %s", slug, e) raise async def create( @@ -62,17 +62,15 @@ class OrganizationRepository( or "unique" in error_msg.lower() or "duplicate" in error_msg.lower() ): - logger.warning(f"Duplicate slug attempted: {obj_in.slug}") + logger.warning("Duplicate slug attempted: %s", obj_in.slug) raise DuplicateEntryError( f"Organization with slug '{obj_in.slug}' already exists" ) - logger.error(f"Integrity error creating organization: {error_msg}") + logger.error("Integrity error creating organization: %s", error_msg) raise IntegrityConstraintError(f"Database integrity error: {error_msg}") except Exception as e: await db.rollback() - logger.error( - f"Unexpected error creating organization: {e!s}", exc_info=True - ) + logger.exception("Unexpected error creating organization: %s", e) raise async def get_multi_with_filters( @@ -117,7 +115,7 @@ class OrganizationRepository( return organizations, total except Exception as e: - logger.error(f"Error getting organizations with filters: {e!s}") + logger.error("Error getting organizations with filters: %s", e) raise async def get_member_count(self, db: AsyncSession, *, organization_id: UUID) -> int: @@ -134,7 +132,7 @@ class OrganizationRepository( return result.scalar_one() or 0 except Exception as e: logger.error( - f"Error getting member count for organization {organization_id}: {e!s}" + "Error getting member count for organization %s: %s", organization_id, e ) raise @@ -207,9 +205,7 @@ class OrganizationRepository( return orgs_with_counts, total except Exception as e: - logger.error( - f"Error getting organizations with member counts: {e!s}", exc_info=True - ) + logger.exception("Error getting organizations with member counts: %s", e) raise async def add_user( @@ -259,11 +255,11 @@ class OrganizationRepository( return user_org except IntegrityError as e: await db.rollback() - logger.error(f"Integrity error adding user to organization: {e!s}") + logger.error("Integrity error adding user to organization: %s", e) raise IntegrityConstraintError("Failed to add user to organization") except Exception as e: await db.rollback() - logger.error(f"Error adding user to organization: {e!s}", exc_info=True) + logger.exception("Error adding user to organization: %s", e) raise async def remove_user( @@ -289,7 +285,7 @@ class OrganizationRepository( return True except Exception as e: await db.rollback() - logger.error(f"Error removing user from organization: {e!s}", exc_info=True) + logger.exception("Error removing user from organization: %s", e) raise async def update_user_role( @@ -324,7 +320,7 @@ class OrganizationRepository( return user_org except Exception as e: await db.rollback() - logger.error(f"Error updating user role: {e!s}", exc_info=True) + logger.exception("Error updating user role: %s", e) raise async def get_organization_members( @@ -384,7 +380,7 @@ class OrganizationRepository( return members, total except Exception as e: - logger.error(f"Error getting organization members: {e!s}") + logger.error("Error getting organization members: %s", e) raise async def get_user_organizations( @@ -407,7 +403,7 @@ class OrganizationRepository( result = await db.execute(query) return list(result.scalars().all()) except Exception as e: - logger.error(f"Error getting user organizations: {e!s}") + logger.error("Error getting user organizations: %s", e) raise async def get_user_organizations_with_details( @@ -456,9 +452,7 @@ class OrganizationRepository( ] except Exception as e: - logger.error( - f"Error getting user organizations with details: {e!s}", exc_info=True - ) + logger.exception("Error getting user organizations with details: %s", e) raise async def get_user_role_in_org( @@ -479,7 +473,7 @@ class OrganizationRepository( return user_org.role if user_org else None # pyright: ignore[reportReturnType] except Exception as e: - logger.error(f"Error getting user role in org: {e!s}") + logger.error("Error getting user role in org: %s", e) raise async def is_user_org_owner( diff --git a/backend/app/repositories/session.py b/backend/app/repositories/session.py index 3a22bfb..e454528 100644 --- a/backend/app/repositories/session.py +++ b/backend/app/repositories/session.py @@ -29,7 +29,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate ) return result.scalar_one_or_none() except Exception as e: - logger.error(f"Error getting session by JTI {jti}: {e!s}") + logger.error("Error getting session by JTI %s: %s", jti, e) raise async def get_active_by_jti( @@ -47,7 +47,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate ) return result.scalar_one_or_none() except Exception as e: - logger.error(f"Error getting active session by JTI {jti}: {e!s}") + logger.error("Error getting active session by JTI %s: %s", jti, e) raise async def get_user_sessions( @@ -74,7 +74,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate result = await db.execute(query) return list(result.scalars().all()) except Exception as e: - logger.error(f"Error getting sessions for user {user_id}: {e!s}") + logger.error("Error getting sessions for user %s: %s", user_id, e) raise async def create_session( @@ -100,14 +100,16 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate await db.refresh(db_obj) logger.info( - f"Session created for user {obj_in.user_id} from {obj_in.device_name} " - f"(IP: {obj_in.ip_address})" + "Session created for user %s from %s (IP: %s)", + obj_in.user_id, + obj_in.device_name, + obj_in.ip_address, ) return db_obj except Exception as e: await db.rollback() - logger.error(f"Error creating session: {e!s}", exc_info=True) + logger.exception("Error creating session: %s", e) raise IntegrityConstraintError(f"Failed to create session: {e!s}") async def deactivate( @@ -117,7 +119,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate try: session = await self.get(db, id=session_id) if not session: - logger.warning(f"Session {session_id} not found for deactivation") + logger.warning("Session %s not found for deactivation", session_id) return None session.is_active = False @@ -126,14 +128,16 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate await db.refresh(session) logger.info( - f"Session {session_id} deactivated for user {session.user_id} " - f"({session.device_name})" + "Session %s deactivated for user %s (%s)", + session_id, + session.user_id, + session.device_name, ) return session except Exception as e: await db.rollback() - logger.error(f"Error deactivating session {session_id}: {e!s}") + logger.error("Error deactivating session %s: %s", session_id, e) raise async def deactivate_all_user_sessions( @@ -154,12 +158,12 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate count = result.rowcount - logger.info(f"Deactivated {count} sessions for user {user_id}") + logger.info("Deactivated %s sessions for user %s", count, user_id) return count except Exception as e: await db.rollback() - logger.error(f"Error deactivating all sessions for user {user_id}: {e!s}") + logger.error("Error deactivating all sessions for user %s: %s", user_id, e) raise async def update_last_used( @@ -174,7 +178,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate return session except Exception as e: await db.rollback() - logger.error(f"Error updating last_used for session {session.id}: {e!s}") + logger.error("Error updating last_used for session %s: %s", session.id, e) raise async def update_refresh_token( @@ -197,7 +201,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate except Exception as e: await db.rollback() logger.error( - f"Error updating refresh token for session {session.id}: {e!s}" + "Error updating refresh token for session %s: %s", session.id, e ) raise @@ -221,12 +225,12 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate count = result.rowcount if count > 0: - logger.info(f"Cleaned up {count} expired sessions using bulk DELETE") + logger.info("Cleaned up %s expired sessions using bulk DELETE", count) return count except Exception as e: await db.rollback() - logger.error(f"Error cleaning up expired sessions: {e!s}") + logger.error("Error cleaning up expired sessions: %s", e) raise async def cleanup_expired_for_user(self, db: AsyncSession, *, user_id: str) -> int: @@ -235,7 +239,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate try: uuid_obj = uuid.UUID(user_id) except (ValueError, AttributeError): - logger.error(f"Invalid UUID format: {user_id}") + logger.error("Invalid UUID format: %s", user_id) raise InvalidInputError(f"Invalid user ID format: {user_id}") now = datetime.now(UTC) @@ -255,14 +259,16 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate if count > 0: logger.info( - f"Cleaned up {count} expired sessions for user {user_id} using bulk DELETE" + "Cleaned up %s expired sessions for user %s using bulk DELETE", + count, + user_id, ) return count except Exception as e: await db.rollback() logger.error( - f"Error cleaning up expired sessions for user {user_id}: {e!s}" + "Error cleaning up expired sessions for user %s: %s", user_id, e ) raise @@ -278,7 +284,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate ) return result.scalar_one() except Exception as e: - logger.error(f"Error counting sessions for user {user_id}: {e!s}") + logger.error("Error counting sessions for user %s: %s", user_id, e) raise async def get_all_sessions( @@ -319,7 +325,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate return sessions, total except Exception as e: - logger.error(f"Error getting all sessions: {e!s}", exc_info=True) + logger.exception("Error getting all sessions: %s", e) raise diff --git a/backend/app/repositories/user.py b/backend/app/repositories/user.py index 850cf0d..24a27fb 100644 --- a/backend/app/repositories/user.py +++ b/backend/app/repositories/user.py @@ -28,7 +28,7 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]): result = await db.execute(select(User).where(User.email == email)) return result.scalar_one_or_none() except Exception as e: - logger.error(f"Error getting user by email {email}: {e!s}") + logger.error("Error getting user by email %s: %s", email, e) raise async def create(self, db: AsyncSession, *, obj_in: UserCreate) -> User: @@ -57,15 +57,15 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]): await db.rollback() error_msg = str(e.orig) if hasattr(e, "orig") else str(e) if "email" in error_msg.lower(): - logger.warning(f"Duplicate email attempted: {obj_in.email}") + logger.warning("Duplicate email attempted: %s", obj_in.email) raise DuplicateEntryError( f"User with email {obj_in.email} already exists" ) - logger.error(f"Integrity error creating user: {error_msg}") + logger.error("Integrity error creating user: %s", error_msg) raise DuplicateEntryError(f"Database integrity error: {error_msg}") except Exception as e: await db.rollback() - logger.error(f"Unexpected error creating user: {e!s}", exc_info=True) + logger.exception("Unexpected error creating user: %s", e) raise async def create_oauth_user( @@ -93,13 +93,13 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]): await db.rollback() error_msg = str(e.orig) if hasattr(e, "orig") else str(e) if "email" in error_msg.lower(): - logger.warning(f"Duplicate email attempted: {email}") + logger.warning("Duplicate email attempted: %s", email) raise DuplicateEntryError(f"User with email {email} already exists") - logger.error(f"Integrity error creating OAuth user: {error_msg}") + logger.error("Integrity error creating OAuth user: %s", error_msg) raise DuplicateEntryError(f"Database integrity error: {error_msg}") except Exception as e: await db.rollback() - logger.error(f"Unexpected error creating OAuth user: {e!s}", exc_info=True) + logger.exception("Unexpected error creating OAuth user: %s", e) raise async def update( @@ -184,7 +184,7 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]): return users, total except Exception as e: - logger.error(f"Error retrieving paginated users: {e!s}") + logger.error("Error retrieving paginated users: %s", e) raise async def bulk_update_status( @@ -206,12 +206,14 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]): await db.commit() updated_count = result.rowcount - logger.info(f"Bulk updated {updated_count} users to is_active={is_active}") + logger.info( + "Bulk updated %s users to is_active=%s", updated_count, is_active + ) return updated_count except Exception as e: await db.rollback() - logger.error(f"Error bulk updating user status: {e!s}", exc_info=True) + logger.exception("Error bulk updating user status: %s", e) raise async def bulk_soft_delete( @@ -246,12 +248,12 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]): await db.commit() deleted_count = result.rowcount - logger.info(f"Bulk soft deleted {deleted_count} users") + logger.info("Bulk soft deleted %s users", deleted_count) return deleted_count except Exception as e: await db.rollback() - logger.error(f"Error bulk deleting users: {e!s}", exc_info=True) + logger.exception("Error bulk deleting users: %s", e) raise def is_active(self, user: User) -> bool: diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py index 8f4b686..a6a9e67 100755 --- a/backend/app/services/auth_service.py +++ b/backend/app/services/auth_service.py @@ -85,7 +85,7 @@ class AuthService: # Delegate creation (hashing + commit) to the repository user = await user_repo.create(db, obj_in=user_data) - logger.info(f"User created successfully: {user.email}") + logger.info("User created successfully: %s", user.email) return user except (AuthenticationError, DuplicateError): @@ -94,7 +94,7 @@ class AuthService: except DuplicateEntryError as e: raise DuplicateError(str(e)) except Exception as e: - logger.error(f"Error creating user: {e!s}", exc_info=True) + logger.exception("Error creating user: %s", e) raise AuthenticationError(f"Failed to create user: {e!s}") @staticmethod @@ -166,7 +166,7 @@ class AuthService: return AuthService.create_tokens(user) except (TokenExpiredError, TokenInvalidError) as e: - logger.warning(f"Token refresh failed: {e!s}") + logger.warning("Token refresh failed: %s", e) raise @staticmethod @@ -201,7 +201,7 @@ class AuthService: new_hash = await get_password_hash_async(new_password) await user_repo.update_password(db, user=user, password_hash=new_hash) - logger.info(f"Password changed successfully for user {user_id}") + logger.info("Password changed successfully for user %s", user_id) return True except AuthenticationError: @@ -210,9 +210,7 @@ class AuthService: except Exception as e: # Rollback on any database errors await db.rollback() - logger.error( - f"Error changing password for user {user_id}: {e!s}", exc_info=True - ) + logger.exception("Error changing password for user %s: %s", user_id, e) raise AuthenticationError(f"Failed to change password: {e!s}") @staticmethod @@ -241,5 +239,5 @@ class AuthService: new_hash = await get_password_hash_async(new_password) user = await user_repo.update_password(db, user=user, password_hash=new_hash) - logger.info(f"Password reset successfully for {email}") + logger.info("Password reset successfully for %s", email) return user diff --git a/backend/app/services/email_service.py b/backend/app/services/email_service.py index ed41e8a..874758b 100644 --- a/backend/app/services/email_service.py +++ b/backend/app/services/email_service.py @@ -58,8 +58,8 @@ class ConsoleEmailBackend(EmailBackend): logger.info("=" * 80) logger.info("EMAIL SENT (Console Backend)") logger.info("=" * 80) - logger.info(f"To: {', '.join(to)}") - logger.info(f"Subject: {subject}") + logger.info("To: %s", ", ".join(to)) + logger.info("Subject: %s", subject) logger.info("-" * 80) if text_content: logger.info("Plain Text Content:") @@ -199,7 +199,7 @@ The {settings.PROJECT_NAME} Team text_content=text_content, ) except Exception as e: - logger.error(f"Failed to send password reset email to {to_email}: {e!s}") + logger.error("Failed to send password reset email to %s: %s", to_email, e) return False async def send_email_verification( @@ -287,7 +287,7 @@ The {settings.PROJECT_NAME} Team text_content=text_content, ) except Exception as e: - logger.error(f"Failed to send verification email to {to_email}: {e!s}") + logger.error("Failed to send verification email to %s: %s", to_email, e) return False diff --git a/backend/app/services/oauth_provider_service.py b/backend/app/services/oauth_provider_service.py index 933634a..fbffabc 100755 --- a/backend/app/services/oauth_provider_service.py +++ b/backend/app/services/oauth_provider_service.py @@ -139,7 +139,7 @@ def verify_pkce(code_verifier: str, code_challenge: str, method: str) -> bool: if method != "S256": # SECURITY: Reject any method other than S256 # 'plain' method provides no security against code interception attacks - logger.warning(f"PKCE verification rejected for unsupported method: {method}") + logger.warning("PKCE verification rejected for unsupported method: %s", method) return False # SHA-256 hash, then base64url encode (RFC 7636 Section 4.2) @@ -257,7 +257,9 @@ def validate_scopes(client: OAuthClient, requested_scopes: list[str]) -> list[st # Warn if some scopes were filtered out invalid = requested - allowed if invalid: - logger.warning(f"Client {client.client_id} requested invalid scopes: {invalid}") + logger.warning( + "Client %s requested invalid scopes: %s", client.client_id, invalid + ) return list(valid) @@ -320,7 +322,9 @@ async def create_authorization_code( ) logger.info( - f"Created authorization code for user {user.id} and client {client.client_id}" + "Created authorization code for user %s and client %s", + user.id, + client.client_id, ) return code @@ -369,7 +373,8 @@ async def exchange_authorization_code( if existing_code and existing_code.used: # Code reuse is a security incident - revoke all tokens for this grant logger.warning( - f"Authorization code reuse detected for client {existing_code.client_id}" + "Authorization code reuse detected for client %s", + existing_code.client_id, ) await revoke_tokens_for_user_client( db, UUID(str(existing_code.user_id)), str(existing_code.client_id) @@ -527,7 +532,7 @@ async def create_tokens( ip_address=ip_address, ) - logger.info(f"Issued tokens for user {user.id} to client {client.client_id}") + logger.info("Issued tokens for user %s to client %s", user.id, client.client_id) return { "access_token": access_token, @@ -580,7 +585,7 @@ async def refresh_tokens( if token_record.revoked: # Token reuse after revocation - security incident logger.warning( - f"Revoked refresh token reuse detected for client {token_record.client_id}" + "Revoked refresh token reuse detected for client %s", token_record.client_id ) raise InvalidGrantError("Refresh token has been revoked") @@ -672,7 +677,7 @@ async def revoke_token( raise InvalidClientError("Token was not issued to this client") await oauth_provider_token_repo.revoke(db, token=refresh_record) - logger.info(f"Revoked refresh token {refresh_record.jti[:8]}...") + logger.info("Revoked refresh token %s...", refresh_record.jti[:8]) return True # Try as access token (JWT) @@ -696,7 +701,7 @@ async def revoke_token( raise InvalidClientError("Token was not issued to this client") await oauth_provider_token_repo.revoke(db, token=refresh_record) logger.info( - f"Revoked refresh token via access token JTI {jti[:8]}..." + "Revoked refresh token via access token JTI %s...", jti[:8] ) return True except JWTError: @@ -731,7 +736,7 @@ async def revoke_tokens_for_user_client( if count > 0: logger.warning( - f"Revoked {count} tokens for user {user_id} and client {client_id}" + "Revoked %s tokens for user %s and client %s", count, user_id, client_id ) return count @@ -753,7 +758,7 @@ async def revoke_all_user_tokens(db: AsyncSession, user_id: UUID) -> int: count = await oauth_provider_token_repo.revoke_all_for_user(db, user_id=user_id) if count > 0: - logger.info(f"Revoked {count} OAuth provider tokens for user {user_id}") + logger.info("Revoked %s OAuth provider tokens for user %s", count, user_id) return count diff --git a/backend/app/services/oauth_service.py b/backend/app/services/oauth_service.py index da83aed..0834828 100644 --- a/backend/app/services/oauth_service.py +++ b/backend/app/services/oauth_service.py @@ -219,7 +219,7 @@ class OAuthService: **auth_params, ) - logger.info(f"OAuth authorization URL created for {provider}") + logger.info("OAuth authorization URL created for %s", provider) return url, state @staticmethod @@ -254,8 +254,9 @@ class OAuthService: # This prevents authorization code injection attacks (RFC 6749 Section 10.6) if state_record.redirect_uri != redirect_uri: logger.warning( - f"OAuth redirect_uri mismatch: expected {state_record.redirect_uri}, " - f"got {redirect_uri}" + "OAuth redirect_uri mismatch: expected %s, got %s", + state_record.redirect_uri, + redirect_uri, ) raise AuthenticationError("Redirect URI mismatch") @@ -299,7 +300,7 @@ class OAuthService: except AuthenticationError: raise except Exception as e: - logger.error(f"OAuth token exchange failed: {e!s}") + logger.error("OAuth token exchange failed: %s", e) raise AuthenticationError("Failed to exchange authorization code") # Get user info from provider @@ -312,7 +313,7 @@ class OAuthService: client, provider, config, access_token ) except Exception as e: - logger.error(f"Failed to get user info: {e!s}") + logger.error("Failed to get user info: %s", e) raise AuthenticationError( "Failed to get user information from provider" ) @@ -353,7 +354,7 @@ class OAuthService: + timedelta(seconds=token.get("expires_in", 3600)), ) - logger.info(f"OAuth login successful for {user.email} via {provider}") + logger.info("OAuth login successful for %s via %s", user.email, provider) elif state_record.user_id: # Account linking flow (user is already logged in) @@ -387,7 +388,7 @@ class OAuthService: ) await oauth_account.create_account(db, obj_in=oauth_create) - logger.info(f"OAuth account linked: {provider} -> {user.email}") + logger.info("OAuth account linked: %s -> %s", provider, user.email) else: # New OAuth login - check for existing user by email @@ -409,7 +410,9 @@ class OAuthService: if existing_provider: # This shouldn't happen if we got here, but safety check logger.warning( - f"OAuth account already linked (race condition?): {provider} -> {user.email}" + "OAuth account already linked (race condition?): %s -> %s", + provider, + user.email, ) else: # Create OAuth account link @@ -427,7 +430,9 @@ class OAuthService: ) await oauth_account.create_account(db, obj_in=oauth_create) - logger.info(f"OAuth auto-linked by email: {provider} -> {user.email}") + logger.info( + "OAuth auto-linked by email: %s -> %s", provider, user.email + ) else: # Create new user @@ -447,7 +452,7 @@ class OAuthService: ) is_new_user = True - logger.info(f"New user created via OAuth: {user.email} ({provider})") + logger.info("New user created via OAuth: %s (%s)", user.email, provider) # Generate JWT tokens claims = { @@ -583,8 +588,9 @@ class OAuthService: token_nonce = payload.get("nonce") if token_nonce != expected_nonce: logger.warning( - f"OAuth ID token nonce mismatch: expected {expected_nonce}, " - f"got {token_nonce}" + "OAuth ID token nonce mismatch: expected %s, got %s", + expected_nonce, + token_nonce, ) raise AuthenticationError("Invalid ID token nonce") @@ -592,14 +598,14 @@ class OAuthService: return payload except JWTError as e: - logger.warning(f"Google ID token verification failed: {e}") + logger.warning("Google ID token verification failed: %s", e) raise AuthenticationError("Invalid ID token signature") except httpx.HTTPError as e: - logger.error(f"Failed to fetch Google JWKS: {e}") + logger.error("Failed to fetch Google JWKS: %s", e) # If we can't verify the ID token, fail closed for security raise AuthenticationError("Failed to verify ID token") except Exception as e: - logger.error(f"Unexpected error verifying Google ID token: {e}") + logger.error("Unexpected error verifying Google ID token: %s", e) raise AuthenticationError("ID token verification error") @staticmethod @@ -701,7 +707,7 @@ class OAuthService: if not deleted: raise AuthenticationError(f"No {provider} account found to unlink") - logger.info(f"OAuth provider unlinked: {provider} from {user.email}") + logger.info("OAuth provider unlinked: %s from %s", provider, user.email) return True @staticmethod diff --git a/backend/app/services/session_cleanup.py b/backend/app/services/session_cleanup.py index 888166e..20a6f21 100755 --- a/backend/app/services/session_cleanup.py +++ b/backend/app/services/session_cleanup.py @@ -35,12 +35,12 @@ async def cleanup_expired_sessions(keep_days: int = 30) -> int: # Use CRUD method to cleanup count = await session_crud.cleanup_expired(db, keep_days=keep_days) - logger.info(f"Session cleanup complete: {count} sessions deleted") + logger.info("Session cleanup complete: %s sessions deleted", count) return count except Exception as e: - logger.error(f"Error during session cleanup: {e!s}", exc_info=True) + logger.exception("Error during session cleanup: %s", e) return 0 @@ -79,10 +79,10 @@ async def get_session_statistics() -> dict: "expired": expired_sessions, } - logger.info(f"Session statistics: {stats}") + logger.info("Session statistics: %s", stats) return stats except Exception as e: - logger.error(f"Error getting session statistics: {e!s}", exc_info=True) + logger.exception("Error getting session statistics: %s", e) return {} diff --git a/backend/pyproject.toml b/backend/pyproject.toml index fa08891..fd2ccab 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -148,8 +148,6 @@ ignore = [ "S607", # Starting a process with a partial path (safe usage) "B008", # FastAPI Depends() in function defaults (required by framework) "B904", # Exception chaining (overly strict for FastAPI error handlers) - "G004", # f-string logging (TODO: migrate existing 300+ occurrences to lazy %) - "G201", # .exception() vs .error(exc_info=True) (TODO: migrate existing 64 occurrences) ] # Allow autofix for all enabled rules