Techletter #105 | December 28, 2024
Requirements
- The logging framework should support different log levels, such as DEBUG, INFO, WARNING, ERROR, and FATAL.
- It should allow logging messages with a timestamp, log level, and message content.
- The framework should support multiple output destinations, such as console, file, and database.
- It should provide a configuration mechanism to set the log level and output destination.
- The logging framework should be thread-safe to handle concurrent logging from multiple threads.
- It should be extensible to accommodate new log levels and output destinations in the future.

Enum (log_level.py)
from enum import Enum
class LogLevel(Enum):
DEBUG = 1
INFO = 2
WARNING = 3
ERROR = 4
FATAL = 5
log_appender.py
from abc import ABC, abstractmethod
class LogAppender(ABC):
@abstractmethod
def append(self, log_message):
pass
file_appender.py
from log_appender import LogAppender
class FileAppender(LogAppender):
def __init__(self, file_path):
self.file_path = file_path
def append(self, log_message):
with open(self.file_path, "a") as file:
file.write(str(log_message) + "\n")
console_appender.py
from log_appender import LogAppender
class ConsoleAppender(LogAppender):
def append(self, log_message):
print(log_message)
logger_config.py
class LoggerConfig:
def __init__(self, log_level, log_appender):
self.log_level = log_level
self.log_appender = log_appender
def get_log_level(self):
return self.log_level
def set_log_level(self, log_level):
self.log_level = log_level
def get_log_appender(self):
return self.log_appender
def set_log_appender(self, log_appender):
self.log_appender = log_appender
log_message.py
import time
class LogMessage:
def __init__(self, level, message):
self.level = level
self.message = message
self.timestamp = int(time.time() * 1000)
def get_level(self):
return self.level
def get_message(self):
return self.message
def get_timestamp(self):
return self.timestamp
def __str__(self):
return f"[{self.level}] {self.timestamp} - {self.message}"
main.py
from enum.log_level import LogLevel
class Logger:
_instance = None
def __init__(self):
if Logger._instance is not None:
raise Exception("This class is a singleton")
else:
Logger._instance = self
self.config = LoggerConfig(LogLevel.INFO, ConsoleAppender())
@staticmethod
def get_instance():
if Logger._instance is None:
Logger()
return Logger._instance
def set_config(self, config):
self.config = config
def log(self, level, message):
if level.value >= self.config.get_log_level().value:
log_message = LogMessage(level, message)
self.config.get_log_appender().append(log_message)
def debug(self, message):
self.log(LogLevel.DEBUG, message)
def info(self, message):
self.log(LogLevel.INFO, message)
def warning(self, message):
self.log(LogLevel.WARNING, message)
def error(self, message):
self.log(LogLevel.ERROR, message)
def fatal(self, message):
self.log(LogLevel.FATAL, message)