1+ r"""
2+ ================================================================================
3+ Logger Utility Module
4+ ================================================================================
5+ Author : Breno Farias da Silva
6+ Created : 2025-12-11
7+ Description :
8+ This module implements a dual-channel logging system designed to capture
9+ all console output produced by Python scripts while preserving ANSI colors
10+ in the terminal and removing them from the log file.
11+
12+ It provides consistent, color-safe logging across interactive terminals,
13+ background executions, CI pipelines, Makefile pipelines, and nohup/systemd
14+ environments.
15+
16+ Key features include:
17+ - Automatic ANSI color stripping for log files
18+ - Full compatibility with interactive and non-interactive terminals
19+ - Mirrored output: terminal (colored) + log file (clean)
20+ - Optional integration by assigning it to sys.stdout/sys.stderr
21+
22+ Usage:
23+ 1. Create a Logger instance:
24+
25+ from logger import Logger
26+ logger = Logger("./Logs/output.log", clean=True)
27+
28+ 2. (Optional) Redirect all stdout/stderr to the logger:
29+
30+ sys.stdout = logger
31+ sys.stderr = logger
32+
33+ 3. Print normally:
34+
35+ print("\x1b[92mHello World\x1b[0m")
36+
37+ Terminal shows colored output.
38+ Log file receives the same text without ANSI escape sequences.
39+
40+ Outputs:
41+ - <path>.log file with fully sanitized (color-free) log output
42+ - Real-time terminal output with correct ANSI handling
43+
44+ TODOs:
45+ - Timestamp prefixing for each log line
46+ - File rotation or size-based log splitting
47+ - CLI flag to force color on/off
48+ - Optional JSON-structured logs
49+
50+ Dependencies:
51+ - Python >= 3.8
52+ - colorama (optional but recommended for Windows)
53+
54+ Assumptions & Notes:
55+ - ANSI escape sequences follow the regex: \x1B\[[0-9;]*[a-zA-Z]
56+ - Log file always stores clean output
57+ - When stdout is not a TTY, color output is automatically disabled
58+ """
59+
60+ import os # For interacting with the filesystem
61+ import re # For stripping ANSI escape sequences
62+ import sys # For replacing stdout/stderr
63+
64+ # Regex Constants:
65+ ANSI_ESCAPE_REGEX = re .compile (r"\x1B\[[0-9;]*[a-zA-Z]" ) # Pattern to remove ANSI colors
66+
67+ # Classes Definitions:
68+
69+ class Logger :
70+ """
71+ Simple logger class that prints colored messages to the terminal and
72+ writes a cleaned (ANSI-stripped) version to a log file.
73+
74+ Usage:
75+ logger = Logger("./Logs/output.log", clean=True)
76+ logger.info("\x1b [92mHello world\x1b [0m")
77+
78+ :param logfile_path: Path to the log file.
79+ :param clean: If True, truncate the log file on init; otherwise append.
80+ """
81+
82+ def __init__ (self , logfile_path , clean = False ):
83+ """
84+ Initialize the Logger.
85+
86+ :param self: Instance of the Logger class.
87+ :param logfile_path: Path to the log file.
88+ :param clean: If True, truncate the log file on init; otherwise append.
89+ """
90+
91+ self .logfile_path = logfile_path # Store log file path
92+
93+ parent = os .path .dirname (logfile_path ) # Ensure log directory exists
94+ if parent and not os .path .exists (parent ): # Create parent directories if needed
95+ os .makedirs (parent , exist_ok = True ) # Safe creation
96+
97+ mode = "w" if clean else "a" # Choose file mode based on 'clean' flag
98+ self .logfile = open (logfile_path , mode , encoding = "utf-8" ) # Open log file
99+ self .is_tty = sys .stdout .isatty () # Verify if stdout is a TTY
100+
101+ def write (self , message ):
102+ """
103+ Internal method to write messages to both terminal and log file.
104+
105+ :param self: Instance of the Logger class.
106+ :param message: The message to log.
107+ """
108+
109+ if message is None : # Ignore None messages
110+ return # Early exit
111+
112+ out = str (message ) # Convert message to string
113+ if not out .endswith ("\n " ): # Ensure newline termination
114+ out += "\n " # Append newline if missing
115+
116+ clean_out = ANSI_ESCAPE_REGEX .sub ("" , out ) # Strip ANSI sequences for log file
117+
118+ try : # Write to log file
119+ self .logfile .write (clean_out ) # Write cleaned message
120+ self .logfile .flush () # Ensure immediate write
121+ except Exception : # Fail silently to avoid breaking user code
122+ pass # Silent fail
123+
124+ try : # Write to terminal: colored when TTY, cleaned otherwise
125+ if self .is_tty : # Terminal supports colors
126+ sys .__stdout__ .write (out ) # Write colored message
127+ sys .__stdout__ .flush () # Flush immediately
128+ else : # Terminal does not support colors
129+ sys .__stdout__ .write (clean_out ) # Write cleaned message
130+ sys .__stdout__ .flush () # Flush immediately
131+ except Exception : # Fail silently to avoid breaking user code
132+ pass # Silent fail
133+
134+ def flush (self ):
135+ """
136+ Flush the log file.
137+
138+ :param self: Instance of the Logger class.
139+ """
140+
141+ try : # Flush log file buffer
142+ self .logfile .flush () # Flush log file
143+ except Exception : # Fail silently
144+ pass # Silent fail
145+
146+ def close (self ):
147+ """
148+ Close the log file.
149+
150+ :param self: Instance of the Logger class.
151+ """
152+
153+ try : # Close log file
154+ self .logfile .close () # Close log file
155+ except Exception : # Fail silently
156+ pass # Silent fail
0 commit comments