Skip to content

Commit 3790a13

Browse files
MartinRinasLKuemmel
authored andcommitted
Keep log messages from last full run and last run with error or warnings (#2118)
* add in-memory log handler to capture latest log cycle and keep last with warnings or errors * specifcy log to be cleared and saved, add soc.log handling * skip writing to disk if there are no messages * keep last three logs, remove duplicate urllib logging * add warning/error
1 parent 98af1bb commit 3790a13

3 files changed

Lines changed: 118 additions & 1 deletion

File tree

packages/helpermodules/logger.py

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@
88
import threading
99
import typing_extensions
1010
import re
11+
import io
12+
import os
13+
import shutil
1114

1215
FORMAT_STR_DETAILED = '%(asctime)s - {%(name)s:%(lineno)s} - {%(levelname)s:%(threadName)s} - %(message)s'
1316
FORMAT_STR_SHORT = '%(asctime)s - %(message)s'
1417
RAMDISK_PATH = str(Path(__file__).resolve().parents[2]) + '/ramdisk/'
1518
PERSISTENT_LOG_PATH = str(Path(__file__).resolve().parents[2]) + '/data/log/'
19+
NUMBER_OF_LOGFILES = 3
1620

1721
KNOWN_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+
108204
def 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()

packages/main.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ def handler_with_control_interval():
154154
self.interval_counter = 1
155155
else:
156156
self.interval_counter = self.interval_counter + 1
157+
158+
# In-Memory Log-Handler zurücksetzen
159+
logger.clear_in_memory_log_handler("main")
160+
157161
log.info("# ***Start*** ")
158162
# log.debug(run_command.run_shell_command("top -b -n 1 | head -n 20"))
159163
# log.debug(f'Drosselung: {run_command.run_shell_command("if which vcgencmd >/dev/null; then vcgencmd get_throttled; else echo not found; fi")}')
@@ -162,6 +166,7 @@ def handler_with_control_interval():
162166
return
163167
try:
164168
handler_with_control_interval()
169+
logger.write_logs_to_file("main")
165170
finally:
166171
self.__release_lock("handler10Sec")
167172
except Exception:

packages/modules/update_soc.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from helpermodules.utils import joined_thread_handler
1313
from modules.common.abstract_vehicle import VehicleUpdateData
1414
from modules.utils import wait_for_module_update_completed
15+
from helpermodules.logger import clear_in_memory_log_handler, write_logs_to_file
1516

1617
log = logging.getLogger(__name__)
1718

@@ -30,14 +31,17 @@ def update(self) -> None:
3031
self.event_update_soc.clear()
3132
topic = "openWB/set/vehicle/set/vehicle_update_completed"
3233
try:
34+
clear_in_memory_log_handler("soc")
3335
threads_update, threads_store = self._get_threads()
3436
joined_thread_handler(threads_update, 300)
3537
wait_for_module_update_completed(self.event_vehicle_update_completed, topic)
3638
# threads_store = self._filter_failed_store_threads(threads_store)
3739
joined_thread_handler(threads_store, data.data.general_data.data.control_interval/3)
3840
wait_for_module_update_completed(self.event_vehicle_update_completed, topic)
41+
write_logs_to_file("soc")
3942
except Exception:
4043
log.exception("Fehler im update_soc-Modul")
44+
write_logs_to_file("soc")
4145

4246
def _get_threads(self) -> Tuple[List[Thread], List[Thread]]:
4347
threads_update, threads_store = [], []

0 commit comments

Comments
 (0)