✨feat(mail_reader): add moving analyzed mails
add moving analyzed mails feature, upgrade reports by adding date of generation
This commit is contained in:
@@ -12,6 +12,7 @@ from logwatcher.mail_reader import (
|
|||||||
connect_to_mailbox,
|
connect_to_mailbox,
|
||||||
extract_logs_from_mails,
|
extract_logs_from_mails,
|
||||||
fetch_log_messages,
|
fetch_log_messages,
|
||||||
|
move_analyzed_mails,
|
||||||
)
|
)
|
||||||
from logwatcher.models import LogEntry
|
from logwatcher.models import LogEntry
|
||||||
from logwatcher.parser import parse_file, parse_lines
|
from logwatcher.parser import parse_file, parse_lines
|
||||||
@@ -90,6 +91,7 @@ def from_mail(
|
|||||||
write_log_report(
|
write_log_report(
|
||||||
SourceType.MAIL, relevant, irrelevant, nb_files, output_dir=output_dir
|
SourceType.MAIL, relevant, irrelevant, nb_files, output_dir=output_dir
|
||||||
)
|
)
|
||||||
|
move_analyzed_mails(account, messages)
|
||||||
logger.info("============ logwatcher mail ended ============")
|
logger.info("============ logwatcher mail ended ============")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from exchangelib import (
|
|||||||
Configuration,
|
Configuration,
|
||||||
Credentials,
|
Credentials,
|
||||||
FileAttachment,
|
FileAttachment,
|
||||||
|
Folder,
|
||||||
Message,
|
Message,
|
||||||
Version,
|
Version,
|
||||||
)
|
)
|
||||||
@@ -17,7 +18,8 @@ from exchangelib.errors import UnauthorizedError
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
MAILBOX_DIR = "Logs"
|
LOG_DIR = "Logs"
|
||||||
|
ANALYZED_LOG_DIR = "Analyzed"
|
||||||
LOG_IN_ATTACHMENT_PATTERN = "Le compte-rendu contient plus de 100 lignes."
|
LOG_IN_ATTACHMENT_PATTERN = "Le compte-rendu contient plus de 100 lignes."
|
||||||
|
|
||||||
|
|
||||||
@@ -138,7 +140,7 @@ def fetch_log_messages(account: Account) -> list[Message]:
|
|||||||
Returns:
|
Returns:
|
||||||
queryset of log-related mails
|
queryset of log-related mails
|
||||||
"""
|
"""
|
||||||
logs_folder = account.msg_folder_root / MAILBOX_DIR
|
logs_folder = account.msg_folder_root / LOG_DIR
|
||||||
return list(logs_folder.all())
|
return list(logs_folder.all())
|
||||||
|
|
||||||
|
|
||||||
@@ -178,3 +180,57 @@ def extract_logs_from_mails(messages: list[Message]) -> list[str]:
|
|||||||
content = _get_mail_body(message)
|
content = _get_mail_body(message)
|
||||||
logs.extend(extract_log_lines(content))
|
logs.extend(extract_log_lines(content))
|
||||||
return logs
|
return logs
|
||||||
|
|
||||||
|
|
||||||
|
def _get_or_create_analyzed_folder(account: Account) -> Folder:
|
||||||
|
"""
|
||||||
|
Return the 'Analyzed' folder, creating it if it doesn't exist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account: The mailbox account owning the Logs folder.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The existing or newly created 'Analyzed' folder.
|
||||||
|
|
||||||
|
"""
|
||||||
|
log_folder = account.msg_folder_root / LOG_DIR
|
||||||
|
matches = list(log_folder.glob(ANALYZED_LOG_DIR))
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
logger.info(f"\tFolder '{ANALYZED_LOG_DIR}' found.")
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
logger.info(f"\tCreating '{ANALYZED_LOG_DIR}' folder.")
|
||||||
|
analyzed_folder = Folder(parent=log_folder, name=ANALYZED_LOG_DIR)
|
||||||
|
analyzed_folder.save()
|
||||||
|
return analyzed_folder
|
||||||
|
|
||||||
|
|
||||||
|
def move_analyzed_mails(account: Account, messages: list[Message]) -> None:
|
||||||
|
"""
|
||||||
|
Move processed mails from the Logs folder to the Analyzed subfolder.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
account: The mailbox account owning the Logs folder.
|
||||||
|
messages: The processed mails to move.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Partial failure is possible: if a move fails mid-batch, some mails
|
||||||
|
may remain in Logs and be retried on the next run (at-least-once).
|
||||||
|
"""
|
||||||
|
if not messages:
|
||||||
|
logger.info("No mails to move.")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Moving {len(messages)} mails from '{LOG_DIR}' directory to '{ANALYZED_LOG_DIR}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
analyzed_folder = _get_or_create_analyzed_folder(account)
|
||||||
|
|
||||||
|
ids = [(m.id, m.changekey) for m in messages]
|
||||||
|
account.bulk_move(ids=ids, to_folder=analyzed_folder)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Mails were successfully moved from '{LOG_DIR}' to '{ANALYZED_LOG_DIR}'."
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from datetime import datetime
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from string import Template
|
from string import Template
|
||||||
|
|
||||||
from logwatcher.config import DATETIME_FORMAT, RESULT_PATH, SourceType
|
from logwatcher.config import DATETIME_FORMAT, FRENCH_TIMEZONE, RESULT_PATH, SourceType
|
||||||
from logwatcher.models import LogEntry
|
from logwatcher.models import LogEntry
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -12,6 +12,7 @@ logger = logging.getLogger(__name__)
|
|||||||
BASE_TEMPLATE = Template("""
|
BASE_TEMPLATE = Template("""
|
||||||
RAPPORT D'ANALYSE DE LOGS
|
RAPPORT D'ANALYSE DE LOGS
|
||||||
=========================
|
=========================
|
||||||
|
Généré le\t : $today
|
||||||
Période\t : $start_date -> $end_date
|
Période\t : $start_date -> $end_date
|
||||||
$source_type(s) lu(s)\t : $nb_files
|
$source_type(s) lu(s)\t : $nb_files
|
||||||
Nombre total d'erreur(s)\t: $nb_errors
|
Nombre total d'erreur(s)\t: $nb_errors
|
||||||
@@ -126,10 +127,12 @@ def build_reports(
|
|||||||
logger.info("\t\t\tn2 and other reports were generated.")
|
logger.info("\t\t\tn2 and other reports were generated.")
|
||||||
logger.info("\t\tbuilding reports job completed.")
|
logger.info("\t\tbuilding reports job completed.")
|
||||||
|
|
||||||
|
today = datetime.now(FRENCH_TIMEZONE).strftime(DATETIME_FORMAT)
|
||||||
source_value = source_type.value.capitalize()
|
source_value = source_type.value.capitalize()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"n2": BASE_TEMPLATE.substitute(
|
"n2": BASE_TEMPLATE.substitute(
|
||||||
|
today=today,
|
||||||
source_type=source_value,
|
source_type=source_value,
|
||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
@@ -138,6 +141,7 @@ def build_reports(
|
|||||||
sub_content=relevant_report,
|
sub_content=relevant_report,
|
||||||
),
|
),
|
||||||
"other": BASE_TEMPLATE.substitute(
|
"other": BASE_TEMPLATE.substitute(
|
||||||
|
today=today,
|
||||||
source_type=source_value,
|
source_type=source_value,
|
||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
@@ -146,6 +150,7 @@ def build_reports(
|
|||||||
sub_content=irrelevant_report,
|
sub_content=irrelevant_report,
|
||||||
),
|
),
|
||||||
"all": BASE_TEMPLATE.substitute(
|
"all": BASE_TEMPLATE.substitute(
|
||||||
|
today=today,
|
||||||
source_type=source_value,
|
source_type=source_value,
|
||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
|
|||||||
Reference in New Issue
Block a user