From 359b2e9b8c3451fcb2fc55c0fd4ed547c2ed9ea0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 19:43:34 +0000 Subject: [PATCH 1/3] Initial plan From da2cf46765496cb853bafbe518cdd9bf1867bd2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 19:48:53 +0000 Subject: [PATCH 2/3] Fix logging configuration propagation to child parser processes - Add _configure_logging() helper function to set up logging in child processes - Modified cli_parse() to accept log_level and log_file parameters - Pass current logging configuration from parent to child processes - Logging warnings/errors from child processes now properly display Fixes issue where logging handlers in parent process were not inherited by child processes created via multiprocessing.Process(). Child processes now configure their own logging with the same settings as the parent. Tested with sample files and confirmed warnings from DNS exceptions in child processes are now visible. Co-authored-by: seanthegeek <44679+seanthegeek@users.noreply.github.com> --- parsedmarc/cli.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/parsedmarc/cli.py b/parsedmarc/cli.py index dcef5ec4..627a75df 100644 --- a/parsedmarc/cli.py +++ b/parsedmarc/cli.py @@ -67,6 +67,50 @@ def _str_to_list(s): return list(map(lambda i: i.lstrip(), _list)) +def _configure_logging(log_level, log_file=None): + """ + Configure logging for the current process. + This is needed for child processes to properly log messages. + + Args: + log_level: The logging level (e.g., logging.DEBUG, logging.WARNING) + log_file: Optional path to log file + """ + # Get the logger + from parsedmarc.log import logger + + # Set the log level + logger.setLevel(log_level) + + # Add StreamHandler with formatter if not already present + # Check if we already have a StreamHandler to avoid duplicates + has_stream_handler = any( + isinstance(h, logging.StreamHandler) and not isinstance(h, logging.FileHandler) + for h in logger.handlers + ) + + if not has_stream_handler: + formatter = logging.Formatter( + fmt="%(levelname)8s:%(filename)s:%(lineno)d:%(message)s", + datefmt="%Y-%m-%d:%H:%M:%S", + ) + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logger.addHandler(handler) + + # Add FileHandler if log_file is specified + if log_file: + try: + fh = logging.FileHandler(log_file, "a") + formatter = logging.Formatter( + "%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s" + ) + fh.setFormatter(formatter) + logger.addHandler(fh) + except Exception as error: + logger.warning("Unable to write to log file: {}".format(error)) + + def cli_parse( file_path, sa, @@ -79,8 +123,29 @@ def cli_parse( reverse_dns_map_url, normalize_timespan_threshold_hours, conn, + log_level=logging.ERROR, + log_file=None, ): - """Separated this function for multiprocessing""" + """Separated this function for multiprocessing + + Args: + file_path: Path to the report file + sa: Strip attachment payloads flag + nameservers: List of nameservers + dns_timeout: DNS timeout + ip_db_path: Path to IP database + offline: Offline mode flag + always_use_local_files: Always use local files flag + reverse_dns_map_path: Path to reverse DNS map + reverse_dns_map_url: URL to reverse DNS map + normalize_timespan_threshold_hours: Timespan threshold + conn: Pipe connection for IPC + log_level: Logging level for this process + log_file: Optional path to log file + """ + # Configure logging in this child process + _configure_logging(log_level, log_file) + try: file_results = parse_report_file( file_path, @@ -1461,6 +1526,10 @@ def process_reports(reports_): if n_procs < 1: n_procs = 1 + # Capture the current log level to pass to child processes + current_log_level = logger.level + current_log_file = opts.log_file + for batch_index in range((len(file_paths) + n_procs - 1) // n_procs): processes = [] connections = [] @@ -1486,6 +1555,8 @@ def process_reports(reports_): opts.reverse_dns_map_url, opts.normalize_timespan_threshold_hours, child_conn, + current_log_level, + current_log_file, ), ) processes.append(process) From 2eb91ed67df7682690077e7ecc0c0de6fba3cbd0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 19:50:26 +0000 Subject: [PATCH 3/3] Address code review feedback on logging configuration - Use exact type check (type(h) is logging.StreamHandler) instead of isinstance to avoid confusion with FileHandler subclass - Catch specific exceptions (IOError, OSError, PermissionError) instead of bare Exception when creating FileHandler - Kept logging.ERROR as default to maintain consistency with existing behavior Co-authored-by: seanthegeek <44679+seanthegeek@users.noreply.github.com> --- parsedmarc/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/parsedmarc/cli.py b/parsedmarc/cli.py index 627a75df..a6968dd0 100644 --- a/parsedmarc/cli.py +++ b/parsedmarc/cli.py @@ -84,8 +84,9 @@ def _configure_logging(log_level, log_file=None): # Add StreamHandler with formatter if not already present # Check if we already have a StreamHandler to avoid duplicates + # Use exact type check to distinguish from FileHandler subclass has_stream_handler = any( - isinstance(h, logging.StreamHandler) and not isinstance(h, logging.FileHandler) + type(h) is logging.StreamHandler for h in logger.handlers ) @@ -107,7 +108,7 @@ def _configure_logging(log_level, log_file=None): ) fh.setFormatter(formatter) logger.addHandler(fh) - except Exception as error: + except (IOError, OSError, PermissionError) as error: logger.warning("Unable to write to log file: {}".format(error))