88import threading
99import typing_extensions
1010import re
11+ import io
12+ import os
13+ import shutil
1114
1215FORMAT_STR_DETAILED = '%(asctime)s - {%(name)s:%(lineno)s} - {%(levelname)s:%(threadName)s} - %(message)s'
1316FORMAT_STR_SHORT = '%(asctime)s - %(message)s'
1417RAMDISK_PATH = str (Path (__file__ ).resolve ().parents [2 ]) + '/ramdisk/'
1518PERSISTENT_LOG_PATH = str (Path (__file__ ).resolve ().parents [2 ]) + '/data/log/'
19+ NUMBER_OF_LOGFILES = 3
1620
1721KNOWN_SENSITIVE_FIELDS = [
1822 'password' , 'secret' , 'token' , 'apikey' , 'access_token' ,
@@ -65,6 +69,7 @@ class RedactingFilter(logging.Filter):
6569 Args:
6670 name (str): The name of the filter.
6771 """
72+
6873 def __init__ (self , name : str = '' ):
6974 super ().__init__ (name )
7075
@@ -105,17 +110,114 @@ def filter_pos(name: str, record) -> bool:
105110 return False
106111
107112
113+ class InMemoryLogHandler (logging .Handler ):
114+ def __init__ (self , base_handler = None ):
115+ super ().__init__ ()
116+ self .base_handler = base_handler
117+ self .log_stream = io .StringIO ()
118+ self .has_warning_or_error = False
119+
120+ def emit (self , record ):
121+ if self .base_handler is None or self .base_handler .filter (record ):
122+ msg = self .format (record )
123+ self .log_stream .write (msg + '\n ' )
124+ if record .levelno >= logging .WARNING :
125+ self .has_warning_or_error = True
126+
127+ def get_logs (self ):
128+ return self .log_stream .getvalue ()
129+
130+ def clear (self ):
131+ self .log_stream = io .StringIO ()
132+ self .has_warning_or_error = False
133+
134+
135+ def clear_in_memory_log_handler (logger_name : str = None ) -> None :
136+ global in_memory_log_handlers
137+ if logger_name is None :
138+ # Clear all in-memory log handlers
139+ for handler in in_memory_log_handlers .values ():
140+ handler .clear ()
141+ else :
142+ # Clear specified in-memory log handler
143+ if logger_name in in_memory_log_handlers :
144+ in_memory_log_handlers [logger_name ].clear ()
145+
146+
147+ def write_logs_to_file (logger_name : str = None ) -> None :
148+ global in_memory_log_handlers
149+
150+ def rotate_logs (base_path : str , name : str ):
151+ # Rotate the log files
152+ for i in range (NUMBER_OF_LOGFILES - 1 , 0 , - 1 ):
153+ src = os .path .join (base_path , f'{ name } .previous{ i } .log' )
154+ dst = os .path .join (base_path , f'{ name } .previous{ i + 1 } .log' )
155+ if os .path .exists (src ):
156+ shutil .move (src , dst )
157+ # Move the current log to previous1
158+ current_log = os .path .join (base_path , f'{ name } .current.log' )
159+ if os .path .exists (current_log ):
160+ shutil .move (current_log , os .path .join (base_path , f'{ name } .previous1.log' ))
161+
162+ def combine_logs (base_path : str , name : str ):
163+ latest_log_path = os .path .join (base_path , f'{ name } .latest.log' )
164+ with open (latest_log_path , 'w' ) as latest_log :
165+ for i in range (NUMBER_OF_LOGFILES - 1 , - 1 , - 1 ):
166+ log_file = os .path .join (
167+ base_path , f'{ name } .previous{ i } .log' ) if i > 0 else os .path .join (base_path , f'{ name } .current.log' )
168+ if os .path .exists (log_file ):
169+ with open (log_file , 'r' ) as f :
170+ latest_log .write (f .read ())
171+
172+ if logger_name is None :
173+ # Write logs for all in-memory log handlers
174+ for name , handler in in_memory_log_handlers .items ():
175+ logs = handler .get_logs ()
176+ if logs :
177+ rotate_logs (RAMDISK_PATH , name )
178+ with open (os .path .join (RAMDISK_PATH , f'{ name } .current.log' ), 'w' ) as f :
179+ f .write (logs )
180+ combine_logs (RAMDISK_PATH , name )
181+
182+ # If any warning or error messages were logged, create a -warning copy
183+ if handler .has_warning_or_error :
184+ with open (os .path .join (RAMDISK_PATH , f'{ name } .latest-warning.log' ), 'w' ) as f :
185+ f .write (logs )
186+
187+ else :
188+ # Write logs for specified in-memory log handler
189+ if logger_name in in_memory_log_handlers :
190+ handler = in_memory_log_handlers [logger_name ]
191+ logs = handler .get_logs ()
192+ if logs :
193+ rotate_logs (RAMDISK_PATH , logger_name )
194+ with open (os .path .join (RAMDISK_PATH , f'{ logger_name } .current.log' ), 'w' ) as f :
195+ f .write (logs )
196+ combine_logs (RAMDISK_PATH , logger_name )
197+
198+ # If any warning or error messages were logged, create a -warning copy
199+ if handler .has_warning_or_error :
200+ with open (os .path .join (RAMDISK_PATH , f'{ logger_name } .latest-warning.log' ), 'w' ) as f :
201+ f .write (logs )
202+
203+
108204def setup_logging () -> None :
109205 def mb_to_bytes (megabytes : int ) -> int :
110206 return megabytes * 1000000
111207
208+ global in_memory_log_handlers
209+ in_memory_log_handlers = {name : InMemoryLogHandler () for name in ["main" , "internal_chargepoint" ]}
210+ # to do: add smarthome and soc to in_memory_log_handlers, needs updates in individual thread calls
211+
112212 # Main logger
113213 log_queue = queue .Queue ()
114214 queue_handler = logging .handlers .QueueHandler (log_queue )
115215 main_file_handler = RotatingFileHandler (RAMDISK_PATH + 'main.log' , maxBytes = mb_to_bytes (5.5 ), backupCount = 4 )
116216 main_file_handler .setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
117217 main_file_handler .addFilter (RedactingFilter ())
118- logging .basicConfig (level = logging .DEBUG , handlers = [queue_handler ])
218+ in_memory_log_handlers ["main" ] = InMemoryLogHandler (main_file_handler )
219+ in_memory_log_handlers ["main" ].setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
220+ logging .basicConfig (level = logging .DEBUG , handlers = [queue_handler , in_memory_log_handlers ["main" ]])
119221 logging .getLogger ().handlers [0 ].addFilter (functools .partial (filter_neg , "soc" ))
120222 logging .getLogger ().handlers [0 ].addFilter (functools .partial (filter_neg , "Internal Chargepoint" ))
121223 logging .getLogger ().handlers [0 ].addFilter (functools .partial (filter_neg , "smarthome" ))
@@ -191,7 +293,10 @@ def mb_to_bytes(megabytes: int) -> int:
191293 soc_log_handler .setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
192294 soc_log_handler .addFilter (functools .partial (filter_pos , "soc" ))
193295 soc_log_handler .addFilter (RedactingFilter ())
296+ in_memory_log_handlers ["soc" ] = InMemoryLogHandler (soc_log_handler )
297+ in_memory_log_handlers ["soc" ].setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
194298 logging .getLogger ().addHandler (soc_queue_handler )
299+ logging .getLogger ().addHandler (in_memory_log_handlers ["soc" ])
195300 soc_listener = logging .handlers .QueueListener (soc_queue , soc_log_handler )
196301 soc_listener .start ()
197302
@@ -204,7 +309,10 @@ def mb_to_bytes(megabytes: int) -> int:
204309 internal_chargepoint_log_handler .setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
205310 internal_chargepoint_log_handler .addFilter (functools .partial (filter_pos , "Internal Chargepoint" ))
206311 internal_chargepoint_log_handler .addFilter (RedactingFilter ())
312+ in_memory_log_handlers ["internal_chargepoint" ] = InMemoryLogHandler (internal_chargepoint_log_handler )
313+ in_memory_log_handlers ["internal_chargepoint" ].setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
207314 logging .getLogger ().addHandler (internal_chargepoint_queue_handler )
315+ logging .getLogger ().addHandler (in_memory_log_handlers ["internal_chargepoint" ])
208316 internal_chargepoint_listener = logging .handlers .QueueListener (internal_chargepoint_queue ,
209317 internal_chargepoint_log_handler )
210318 internal_chargepoint_listener .start ()
0 commit comments