#3523: Standardize all failed email logger error messages - [RH] (#3937)

For failed email logger messages to be errors and have a standardized output
This commit is contained in:
Slim 2025-07-10 08:50:27 -07:00 committed by GitHub
parent 973b392923
commit 508bd30434
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 175 additions and 78 deletions

View file

@ -9,7 +9,7 @@ from django.core.management import BaseCommand
from django.conf import settings
from registrar.utility import csv_export
from io import StringIO
from ...utility.email import send_templated_email
from ...utility.email import send_templated_email, EmailSendingError
logger = logging.getLogger(__name__)
@ -46,9 +46,11 @@ class Command(BaseCommand):
logger.info("Generating report...")
try:
self.email_current_metadata_report(zip_filename, email_to)
success = self.email_current_metadata_report(zip_filename, email_to)
if not success:
# TODO - #1317: Notify operations when auto report generation fails
raise EmailSendingError("Report was generated but failed to send via email.")
except Exception as err:
# TODO - #1317: Notify operations when auto report generation fails
raise err
else:
logger.info(f"Success! Created {zip_filename} and successfully sent out an email!")
@ -78,13 +80,24 @@ class Command(BaseCommand):
encrypted_zip_in_bytes = self.get_encrypted_zip(zip_filename, reports, password)
# Send the metadata file that is zipped
send_templated_email(
template_name="emails/metadata_body.txt",
subject_template_name="emails/metadata_subject.txt",
to_addresses=email_to,
context={"current_date_str": datetime.now().strftime("%Y-%m-%d")},
attachment_file=encrypted_zip_in_bytes,
)
try:
send_templated_email(
template_name="emails/metadata_body.txt",
subject_template_name="emails/metadata_subject.txt",
to_addresses=email_to,
context={"current_date_str": datetime.now().strftime("%Y-%m-%d")},
attachment_file=encrypted_zip_in_bytes,
)
return True
except EmailSendingError as err:
logger.error(
"Failed to send metadata email:\n"
f" Subject: metadata_subject.txt\n"
f" To: {email_to}\n"
f" Error: {err}",
exc_info=True,
)
return False
def get_encrypted_zip(self, zip_filename, reports, password):
"""Helper function for encrypting the attachment file"""

View file

@ -136,9 +136,12 @@ class Command(BaseCommand):
)
except EmailSendingError as err:
logger.error(
f"email did not send successfully to {email_data['email']} "
f"for {[domain for domain in email_data['domains']]}"
f": {err}"
"Failed to send transition domain invitation email:\n"
f" Subjec template: transition_domain_invitation_subject.txt\n"
f" To: {email_data['email']}\n"
f" Domains: {', '.join(email_data['domains'])}\n"
f" Error: {err}",
exc_info=True,
)
# if email failed to send, set error in domains_with_errors for each
# domain in the email so that transition domain email_sent is not set

View file

@ -97,9 +97,17 @@ class Command(BaseCommand):
context=context,
)
logger.info(f"Sent email for domain {domain.name} to managers and CCd org admins")
except EmailSendingError as e:
except EmailSendingError as err:
if not dryrun:
logger.warning(f"Failed to send email for domain {domain.name}. Reason: {e}")
logger.error(
"Failed to send expiring soon email(s):\n"
f" Subject template: {subject_template}\n"
f" To: {', '.join(domain_manager_emails)}\n"
f" CC: {', '.join(portfolio_admin_emails)}\n"
f" Domain: {domain.name}\n"
f" Error: {err}",
exc_info=True,
)
all_emails_sent = False
if all_emails_sent:

View file

@ -1032,8 +1032,17 @@ class DomainRequest(TimeStampedModel):
wrap_email=wrap_email,
)
logger.info(f"The {new_status} email sent to: {recipient.email}")
except EmailSendingError:
logger.warning("Failed to send confirmation email", exc_info=True)
except EmailSendingError as err:
logger.error(
"Failed to send status update to creator email:\n"
f" Type: {new_status}\n"
f" Subject template: {email_template_subject}\n"
f" To: {recipient.email}\n"
f" CC: {', '.join(cc_addresses)}\n"
f" BCC: {bcc_address}"
f" Error: {err}",
exc_info=True,
)
def investigator_exists_and_is_staff(self):
"""Checks if the current investigator is in a valid state for a state transition"""

View file

@ -940,18 +940,22 @@ class TestSendPortfolioMemberPermissionUpdateEmail(unittest.TestCase):
permissions.user.email = "user@example.com"
permissions.portfolio.organization_name = "Test Portfolio"
mock_get_requestor_email.return_value = "requestor@example.com"
mock_get_requestor_email.return_value = MagicMock(name="mock.email")
# Call function
result = send_portfolio_member_permission_update_email(requestor, permissions)
# Assertions
mock_logger.warning.assert_called_once_with(
"Could not send email organization member update notification to %s for portfolio: %s",
permissions.user.email,
permissions.portfolio.organization_name,
exc_info=True,
expected_message = (
"Failed to send organization member update notification email:\n"
f" Requestor Email: {mock_get_requestor_email.return_value}\n"
f" Subject template: portfolio_update_subject.txt\n"
f" To: {permissions.user.email}\n"
f" Portfolio: {permissions.portfolio}\n"
f" Error: Email failed"
)
mock_logger.error.assert_called_once_with(expected_message, exc_info=True)
self.assertFalse(result)
@patch("registrar.utility.email_invitations._get_requestor_email", side_effect=Exception("Unexpected error"))
@ -1013,18 +1017,22 @@ class TestSendPortfolioMemberPermissionRemoveEmail(unittest.TestCase):
permissions.user.email = "user@example.com"
permissions.portfolio.organization_name = "Test Portfolio"
mock_get_requestor_email.return_value = "requestor@example.com"
mock_get_requestor_email.return_value = MagicMock(name="mock.email")
# Call function
result = send_portfolio_member_permission_remove_email(requestor, permissions)
# Assertions
mock_logger.warning.assert_called_once_with(
"Could not send email organization member removal notification to %s for portfolio: %s",
permissions.user.email,
permissions.portfolio.organization_name,
exc_info=True,
expected_message = (
"Failed to send portfolio member removal email:\n"
f" Requestor Email: {mock_get_requestor_email.return_value}\n"
f" Subject template: portfolio_removal_subject.txt\n"
f" To: {permissions.user.email}\n"
f" Portfolio: {permissions.portfolio}\n"
f" Error: Email failed"
)
mock_logger.error.assert_called_once_with(expected_message, exc_info=True)
self.assertFalse(result)
@patch("registrar.utility.email_invitations._get_requestor_email", side_effect=Exception("Unexpected error"))
@ -1092,10 +1100,12 @@ class TestSendPortfolioInvitationRemoveEmail(unittest.TestCase):
result = send_portfolio_invitation_remove_email(requestor, invitation)
# Assertions
mock_logger.warning.assert_called_once_with(
"Could not send email organization member removal notification to %s for portfolio: %s",
invitation.email,
invitation.portfolio.organization_name,
mock_logger.error.assert_called_once_with(
"Failed to send portfolio invitation removal email:\n"
f" Subject template: portfolio_removal_subject.txt\n"
f" To: {invitation.email}\n"
f" Portfolio: {invitation.portfolio.organization_name}\n"
f" Error: Email failed",
exc_info=True,
)
self.assertFalse(result)

View file

@ -98,6 +98,15 @@ def _send_domain_invitation_email(email, requestor_email, domains, requested_use
)
except EmailSendingError as err:
domain_names = ", ".join([domain.name for domain in domains])
logger.error(
"Failed to send domain invitation email:\n"
f" Requestor Email: {requestor_email}\n"
f" Subject template: domain_invitation_subject.txt\n"
f" To: {email}\n"
f" Domains: {domain_names}\n"
f" Error: {err}",
exc_info=True,
)
raise EmailSendingError(f"Could not send email invitation to {email} for domains: {domain_names}") from err
@ -173,9 +182,15 @@ def _send_domain_invitation_update_emails_to_domain_managers(
"date": date.today(),
},
)
except EmailSendingError:
logger.warning(
f"Could not send email manager notification to {user.email} for domain: {domain.name}", exc_info=True
except EmailSendingError as err:
logger.error(
"Failed to send domain manager update notification email:\n"
f" Requestor Email: {requestor_email}\n"
f" Subject: domain_manager_notification_subject.txt\n"
f" To: {user.email}\n"
f" Domain: {domain.name}\n"
f" Error: {err}",
exc_info=True,
)
all_emails_sent = False
return all_emails_sent
@ -220,11 +235,15 @@ def send_domain_manager_removal_emails_to_domain_managers(
"date": date.today(),
},
)
except EmailSendingError:
logger.warning(
"Could not send notification email to %s for domain %s",
user.email,
domain.name,
except EmailSendingError as err:
logger.error(
"Failed to send domain manager deleted notification email:\n"
f" User that did the removing: {removed_by_user}\n"
f" Domain manager removed: {manager_removed_email}\n"
f" Subject template: domain_manager_deleted_notification_subject.txt\n"
f" To: {user.email}\n"
f" Domain: {domain.name}\n"
f" Error: {err}",
exc_info=True,
)
all_emails_sent = False
@ -265,6 +284,15 @@ def send_portfolio_invitation_email(email: str, requestor, portfolio, is_admin_i
},
)
except EmailSendingError as err:
logger.error(
"Failed to send portfolio invitation email:\n"
f" Requestor Email: {requestor_email}\n"
f" Subject template: portfolio_invitation_subject.txt\n"
f" To: {email}\n"
f" Portfolio: {portfolio}\n"
f" Error: {err}",
exc_info=True,
)
raise EmailSendingError(
f"Could not sent email invitation to {email} for portfolio {portfolio}. Portfolio invitation not saved."
) from err
@ -319,11 +347,14 @@ def send_portfolio_update_emails_to_portfolio_admins(editor, portfolio, updated_
"updated_info": updated_page,
},
)
except EmailSendingError:
logger.warning(
"Could not send email organization admin notification to %s " "for portfolio: %s",
user.email,
portfolio,
except EmailSendingError as err:
logger.error(
"Failed to send portfolio org update notification email:\n"
f" Requested User: {user}\n"
f" Subject template: portfolio_org_update_notification_subject.txt\n"
f" To: {user.email}\n"
f" Portfolio: {portfolio}\n"
f" Error: {err}",
exc_info=True,
)
all_emails_sent = False
@ -362,11 +393,14 @@ def send_portfolio_member_permission_update_email(requestor, permissions: UserPo
"date": date.today(),
},
)
except EmailSendingError:
logger.warning(
"Could not send email organization member update notification to %s " "for portfolio: %s",
permissions.user.email,
permissions.portfolio.organization_name,
except EmailSendingError as err:
logger.error(
"Failed to send organization member update notification email:\n"
f" Requestor Email: {requestor_email}\n"
f" Subject template: portfolio_update_subject.txt\n"
f" To: {permissions.user.email}\n"
f" Portfolio: {permissions.portfolio}\n"
f" Error: {err}",
exc_info=True,
)
return False
@ -403,11 +437,14 @@ def send_portfolio_member_permission_remove_email(requestor, permissions: UserPo
"requestor_email": requestor_email,
},
)
except EmailSendingError:
logger.warning(
"Could not send email organization member removal notification to %s " "for portfolio: %s",
permissions.user.email,
permissions.portfolio.organization_name,
except EmailSendingError as err:
logger.error(
"Failed to send portfolio member removal email:\n"
f" Requestor Email: {requestor_email}\n"
f" Subject template: portfolio_removal_subject.txt\n"
f" To: {permissions.user.email}\n"
f" Portfolio: {permissions.portfolio}\n"
f" Error: {err}",
exc_info=True,
)
return False
@ -444,11 +481,13 @@ def send_portfolio_invitation_remove_email(requestor, invitation: PortfolioInvit
"requestor_email": requestor_email,
},
)
except EmailSendingError:
logger.warning(
"Could not send email organization member removal notification to %s " "for portfolio: %s",
invitation.email,
invitation.portfolio.organization_name,
except EmailSendingError as err:
logger.error(
"Failed to send portfolio invitation removal email:\n"
f" Subject template: portfolio_removal_subject.txt\n"
f" To: {invitation.email}\n"
f" Portfolio: {invitation.portfolio.organization_name}\n"
f" Error: {err}",
exc_info=True,
)
return False
@ -497,11 +536,15 @@ def _send_portfolio_admin_addition_emails_to_portfolio_admins(email: str, reques
"date": date.today(),
},
)
except EmailSendingError:
logger.warning(
"Could not send email organization admin notification to %s " "for portfolio: %s",
user.email,
portfolio.organization_name,
except EmailSendingError as err:
logger.error(
"Failed to send portfolio admin addition notification email:\n"
f" Requestor Email: {requestor_email}\n"
f" Subject template: portfolio_admin_addition_notification_subject.txt\n"
f" To: {user.email}\n"
f" Portfolio: {portfolio}\n"
f" Portfolio Admin: {user}\n"
f" Error: {err}",
exc_info=True,
)
all_emails_sent = False
@ -550,11 +593,14 @@ def _send_portfolio_admin_removal_emails_to_portfolio_admins(email: str, request
"date": date.today(),
},
)
except EmailSendingError:
logger.warning(
"Could not send email organization admin notification to %s " "for portfolio: %s",
user.email,
portfolio.organization_name,
except EmailSendingError as err:
logger.error(
"Failed to send portfolio admin removal notification email:\n"
f" Requestor Email: {requestor_email}\n"
f" Subject template: portfolio_admin_removal_notification_subject.txt\n"
f" To: {user.email}\n"
f" Portfolio: {portfolio.organization_name}\n"
f" Error: {err}",
exc_info=True,
)
all_emails_sent = False

View file

@ -375,11 +375,13 @@ class DomainFormBaseView(DomainBaseView, FormMixin):
context["recipient"] = manager
try:
send_templated_email(template, subject_template, to_addresses=[manager.email], context=context)
except EmailSendingError:
logger.warning(
"Could not send notification email to %s for domain %s",
manager.email,
domain.name,
except EmailSendingError as err:
logger.error(
"Failed to send notification email:\n"
f" Subject template: {subject_template}\n"
f" To: {manager.email}\n"
f" Domain: {domain.name}\n"
f" Error: {err}",
exc_info=True,
)

View file

@ -1018,8 +1018,14 @@ class Review(DomainRequestWizard):
context=context,
)
logger.info("A submission confirmation email was sent to ombdotgov@omb.eop.gov")
except EmailSendingError:
logger.warning("Failed to send confirmation email", exc_info=True)
except EmailSendingError as err:
logger.error(
"Failed to send OMB submission confirmation email:\n"
f" Subject template: omb_submission_confirmation_subject.txt\n"
f" To: ombdotgov@omb.eop.gov\n"
f" Error: {err}",
exc_info=True,
)
class Finished(DomainRequestWizard):