66import threading
77import typing_extensions
88import re
9+ import io
10+ import os
11+ import shutil
912
1013FORMAT_STR_DETAILED = '%(asctime)s - {%(name)s:%(lineno)s} - {%(levelname)s:%(threadName)s} - %(message)s'
1114FORMAT_STR_SHORT = '%(asctime)s - %(message)s'
1215RAMDISK_PATH = str (Path (__file__ ).resolve ().parents [2 ]) + '/ramdisk/'
1316PERSISTENT_LOG_PATH = str (Path (__file__ ).resolve ().parents [2 ]) + '/data/log/'
17+ NUMBER_OF_LOGFILES = 3
1418
1519KNOWN_SENSITIVE_FIELDS = [
1620 'password' , 'secret' , 'token' , 'apikey' , 'access_token' ,
@@ -104,18 +108,117 @@ def filter_pos(name: str, record) -> bool:
104108 return False
105109
106110
111+ class InMemoryLogHandler (logging .Handler ):
112+ def __init__ (self , base_handler = None ):
113+ super ().__init__ ()
114+ self .base_handler = base_handler
115+ self .log_stream = io .StringIO ()
116+ self .has_warning_or_error = False
117+
118+ def emit (self , record ):
119+ if self .base_handler is None or self .base_handler .filter (record ):
120+ msg = self .format (record )
121+ self .log_stream .write (msg + '\n ' )
122+ if record .levelno >= logging .WARNING :
123+ self .has_warning_or_error = True
124+
125+ def get_logs (self ):
126+ return self .log_stream .getvalue ()
127+
128+ def clear (self ):
129+ self .log_stream = io .StringIO ()
130+ self .has_warning_or_error = False
131+
132+
133+ def clear_in_memory_log_handler (logger_name : str = None ) -> None :
134+ global in_memory_log_handlers
135+ if logger_name is None :
136+ # Clear all in-memory log handlers
137+ for handler in in_memory_log_handlers .values ():
138+ handler .clear ()
139+ else :
140+ # Clear specified in-memory log handler
141+ if logger_name in in_memory_log_handlers :
142+ in_memory_log_handlers [logger_name ].clear ()
143+
144+
145+ def write_logs_to_file (logger_name : str = None ) -> None :
146+ global in_memory_log_handlers
147+
148+ def rotate_logs (base_path : str , name : str ):
149+ # Rotate the log files
150+ for i in range (NUMBER_OF_LOGFILES - 1 , 0 , - 1 ):
151+ src = os .path .join (base_path , f'{ name } .previous{ i } .log' )
152+ dst = os .path .join (base_path , f'{ name } .previous{ i + 1 } .log' )
153+ if os .path .exists (src ):
154+ shutil .move (src , dst )
155+ # Move the current log to previous1
156+ current_log = os .path .join (base_path , f'{ name } .current.log' )
157+ if os .path .exists (current_log ):
158+ shutil .move (current_log , os .path .join (base_path , f'{ name } .previous1.log' ))
159+
160+ def combine_logs (base_path : str , name : str ):
161+ latest_log_path = os .path .join (base_path , f'{ name } .latest.log' )
162+ with open (latest_log_path , 'w' ) as latest_log :
163+ for i in range (NUMBER_OF_LOGFILES - 1 , - 1 , - 1 ):
164+ log_file = os .path .join (
165+ base_path , f'{ name } .previous{ i } .log' ) if i > 0 else os .path .join (base_path , f'{ name } .current.log' )
166+ if os .path .exists (log_file ):
167+ with open (log_file , 'r' ) as f :
168+ latest_log .write (f .read ())
169+
170+ if logger_name is None :
171+ # Write logs for all in-memory log handlers
172+ for name , handler in in_memory_log_handlers .items ():
173+ logs = handler .get_logs ()
174+ if logs :
175+ rotate_logs (RAMDISK_PATH , name )
176+ with open (os .path .join (RAMDISK_PATH , f'{ name } .current.log' ), 'w' ) as f :
177+ f .write (logs )
178+ combine_logs (RAMDISK_PATH , name )
179+
180+ # If any warning or error messages were logged, create a -warning copy
181+ if handler .has_warning_or_error :
182+ with open (os .path .join (RAMDISK_PATH , f'{ name } .latest-warning.log' ), 'w' ) as f :
183+ f .write (logs )
184+
185+ else :
186+ # Write logs for specified in-memory log handler
187+ if logger_name in in_memory_log_handlers :
188+ handler = in_memory_log_handlers [logger_name ]
189+ logs = handler .get_logs ()
190+ if logs :
191+ rotate_logs (RAMDISK_PATH , logger_name )
192+ with open (os .path .join (RAMDISK_PATH , f'{ logger_name } .current.log' ), 'w' ) as f :
193+ f .write (logs )
194+ combine_logs (RAMDISK_PATH , logger_name )
195+
196+ # If any warning or error messages were logged, create a -warning copy
197+ if handler .has_warning_or_error :
198+ with open (os .path .join (RAMDISK_PATH , f'{ logger_name } .latest-warning.log' ), 'w' ) as f :
199+ f .write (logs )
200+
201+
107202def setup_logging () -> None :
108203 def mb_to_bytes (megabytes : int ) -> int :
109204 return megabytes * 1000000
110- # Mehrere kleine Dateien verwenden, damit nicht zu viel verworfen wird, wenn die Datei voll ist.
205+
206+ global in_memory_log_handlers
207+ in_memory_log_handlers = {name : InMemoryLogHandler () for name in ["main" , "internal_chargepoint" ]}
208+ # to do: add smarthome and soc to in_memory_log_handlers, needs updates in individual thread calls
209+
210+ # Main logger
111211 main_file_handler = RotatingFileHandler (RAMDISK_PATH + 'main.log' , maxBytes = mb_to_bytes (5 ), backupCount = 4 )
112212 main_file_handler .setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
113213 main_file_handler .addFilter (RedactingFilter ())
114- logging .basicConfig (level = logging .DEBUG , handlers = [main_file_handler ])
214+ in_memory_log_handlers ["main" ] = InMemoryLogHandler (main_file_handler )
215+ in_memory_log_handlers ["main" ].setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
216+ logging .basicConfig (level = logging .DEBUG , handlers = [main_file_handler , in_memory_log_handlers ["main" ]])
115217 logging .getLogger ().handlers [0 ].addFilter (functools .partial (filter_neg , "soc" ))
116218 logging .getLogger ().handlers [0 ].addFilter (functools .partial (filter_neg , "Internal Chargepoint" ))
117219 logging .getLogger ().handlers [0 ].addFilter (functools .partial (filter_neg , "smarthome" ))
118220
221+ # Chargelog logger
119222 chargelog_log = logging .getLogger ("chargelog" )
120223 chargelog_log .propagate = False
121224 chargelog_file_handler = RotatingFileHandler (
@@ -124,6 +227,7 @@ def mb_to_bytes(megabytes: int) -> int:
124227 chargelog_file_handler .addFilter (RedactingFilter ())
125228 chargelog_log .addHandler (chargelog_file_handler )
126229
230+ # Data migration logger
127231 data_migration_log = logging .getLogger ("data_migration" )
128232 data_migration_log .propagate = False
129233 data_migration_file_handler = RotatingFileHandler (
@@ -132,6 +236,7 @@ def mb_to_bytes(megabytes: int) -> int:
132236 data_migration_file_handler .addFilter (RedactingFilter ())
133237 data_migration_log .addHandler (data_migration_file_handler )
134238
239+ # MQTT logger
135240 mqtt_log = logging .getLogger ("mqtt" )
136241 mqtt_log .propagate = False
137242 mqtt_file_handler = RotatingFileHandler (RAMDISK_PATH + 'mqtt.log' , maxBytes = mb_to_bytes (3 ), backupCount = 1 )
@@ -170,19 +275,27 @@ def mb_to_bytes(megabytes: int) -> int:
170275 smarthome_log_handler .addFilter (RedactingFilter ())
171276 logging .getLogger ().addHandler (smarthome_log_handler )
172277
278+ # SoC logger
173279 soc_log_handler = RotatingFileHandler (RAMDISK_PATH + 'soc.log' , maxBytes = mb_to_bytes (2 ), backupCount = 1 )
174280 soc_log_handler .setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
175281 soc_log_handler .addFilter (functools .partial (filter_pos , "soc" ))
176282 soc_log_handler .addFilter (RedactingFilter ())
283+ in_memory_log_handlers ["soc" ] = InMemoryLogHandler (soc_log_handler )
284+ in_memory_log_handlers ["soc" ].setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
177285 logging .getLogger ().addHandler (soc_log_handler )
286+ logging .getLogger ().addHandler (in_memory_log_handlers ["soc" ])
178287
288+ # Internal chargepoint logger
179289 internal_chargepoint_log_handler = RotatingFileHandler (RAMDISK_PATH + 'internal_chargepoint.log' ,
180290 maxBytes = mb_to_bytes (1 ),
181291 backupCount = 1 )
182292 internal_chargepoint_log_handler .setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
183293 internal_chargepoint_log_handler .addFilter (functools .partial (filter_pos , "Internal Chargepoint" ))
184294 internal_chargepoint_log_handler .addFilter (RedactingFilter ())
295+ in_memory_log_handlers ["internal_chargepoint" ] = InMemoryLogHandler (internal_chargepoint_log_handler )
296+ in_memory_log_handlers ["internal_chargepoint" ].setFormatter (logging .Formatter (FORMAT_STR_DETAILED ))
185297 logging .getLogger ().addHandler (internal_chargepoint_log_handler )
298+ logging .getLogger ().addHandler (in_memory_log_handlers ["internal_chargepoint" ])
186299
187300 # urllib3 logger
188301 urllib3_log = logging .getLogger ("urllib3.connectionpool" )
0 commit comments