logging update for exceptions with special error log to console

When calling log.exception it automatically writes a short message to the console as error
This error message is tagged <=EXCEPTION> and contains only the message without the stack trace
This commit is contained in:
Clemens Schwaighofer
2025-07-15 17:54:09 +09:00
parent ea3b4f1790
commit 694712ed2e
3 changed files with 44 additions and 11 deletions

View File

@@ -73,11 +73,32 @@ class CustomConsoleFormatter(logging.Formatter):
message = super().format(record)
return f"{color}{message}{reset}"
# TODO: add custom handlers for stack_trace, if not set fill with %(filename)s:%(funcName)s:%(lineno)d
# hasattr(record, 'stack_trace')
class CustomHandlerFilter(logging.Filter):
"""
Add a custom handler for filtering
"""
def __init__(self, handler_name: str, filter_exceptions: bool = False):
super().__init__(name=handler_name)
self.handler_name = handler_name
self.filter_exceptions = filter_exceptions
def filter(self, record: logging.LogRecord) -> bool:
# if console and exception do not show
if self.handler_name == 'console' and self.filter_exceptions:
return record.levelname != "EXCEPTION"
# if cnosole entry is true and traget file filter
if hasattr(record, 'console') and getattr(record, 'console') is True and self.handler_name == 'file':
return False
return True
# def __filter_exceptions(self, record: logging.LogRecord) -> bool:
# return record.levelname != "EXCEPTION"
# MARK: Log class
class Log:
"""
@@ -142,7 +163,7 @@ class Log:
# in the file writer too, for the ones where color is set BEFORE the format
# Any is logging.StreamHandler, logging.FileHandler and all logging.handlers.*
self.handlers: dict[str, Any] = {}
self.add_handler('file_handler', self.__create_time_rotating_file_handler(
self.add_handler('file_handler', self.__create_timed_rotating_file_handler(
self.log_settings['log_level_file'], log_path)
)
if self.log_settings['console_enabled']:
@@ -211,8 +232,8 @@ class Log:
default_log_settings['log_queue'] = __setting
return default_log_settings
def __filter_exceptions(self, record: logging.LogRecord) -> bool:
return record.levelname != "EXCEPTION"
# def __filter_exceptions(self, record: logging.LogRecord) -> bool:
# return record.levelname != "EXCEPTION"
# MARK: add a handler
def add_handler(
@@ -259,16 +280,15 @@ class Log:
formatter_console = CustomConsoleFormatter(format_string, datefmt=format_date)
else:
formatter_console = logging.Formatter(format_string, datefmt=format_date)
console_handler.setLevel(log_level_console.name)
console_handler.set_name('console')
console_handler.setLevel(log_level_console.name)
# do not show exceptions logs on console
if filter_exceptions:
console_handler.addFilter(self.__filter_exceptions)
console_handler.addFilter(CustomHandlerFilter('console', filter_exceptions))
console_handler.setFormatter(formatter_console)
return console_handler
# MARK: file handler
def __create_time_rotating_file_handler(
def __create_timed_rotating_file_handler(
self, log_level_file: LoggingLevel, log_path: Path,
when: str = "D", interval: int = 1, backup_count: int = 0
) -> logging.handlers.TimedRotatingFileHandler:
@@ -304,6 +324,8 @@ class Log:
)
file_handler.set_name('file_timed_rotate')
file_handler.setLevel(log_level_file.name)
# do not show errors flagged with console (they are from exceptions)
file_handler.addFilter(CustomHandlerFilter('file'))
file_handler.setFormatter(formatter_file_handler)
return file_handler
@@ -454,7 +476,11 @@ class Log:
self.logger.log(LoggingLevel.EMERGENCY.value, msg, *args, extra=extra, stacklevel=2)
# MARK: EXCEPTION: 70
def exception(self, msg: object, *args: object, extra: MutableMapping[str, object] | None = None) -> None:
def exception(
self,
msg: object, *args: object, extra: MutableMapping[str, object] | None = None,
log_error: bool = True
) -> None:
"""
log on exceotion level, this is log.exception, but logs with a new level
@@ -462,12 +488,19 @@ class Log:
msg (object): _description_
*args (object): arguments for msg
extra: Mapping[str, object] | None: extra arguments for the formatting if needed
log_error: (bool): If set to false will not write additional error message for console (Default True)
"""
if not hasattr(self, 'logger'):
raise ValueError('Logger is not yet initialized')
if extra is None:
extra = {}
extra['stack_trace'] = traceback_call_str(start=3)
# write to console first with extra flag for filtering in file
if log_error:
self.logger.log(
LoggingLevel.ERROR.value,
f"<=EXCEPTION> {msg}", *args, extra=dict(extra) | {'console': True}, stacklevel=2
)
self.logger.log(LoggingLevel.EXCEPTION.value, msg, *args, exc_info=True, extra=extra, stacklevel=2)
# MARK: break line

View File

@@ -76,7 +76,7 @@ def main():
print(f"Divied: {__test}")
except ZeroDivisionError as e:
log.logger.critical("Divison through zero: %s", e)
log.exception("Divison through zero")
log.exception("Divison through zero: %s", e)
for handler in log.logger.handlers:
print(f"Handler (logger) {handler} -> {handler.level} -> {LoggingLevel.from_any(handler.level)}")

2
uv.lock generated
View File

@@ -44,7 +44,7 @@ wheels = [
[[package]]
name = "corelibs"
version = "0.12.6"
version = "0.13.2"
source = { editable = "." }
dependencies = [
{ name = "jmespath" },