From 00da464af483a029cbd29903327afb5c0158b358 Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Wed, 21 Aug 2024 16:02:17 +0330 Subject: [PATCH 1/2] :zap::hammer::sparkles: feat: add dataclass in default_settings Added DefaultLoggingSettings using dataclass ---------- Updated checks file to apply defaults(default_settings) Updated utils to get configs using get_conf and handle some exceptions --- django_logging/constants/__init__.py | 13 +--- django_logging/constants/default_settings.py | 62 -------------------- django_logging/constants/defaults.py | 48 +++++++++++++++ django_logging/constants/settings_types.py | 2 +- django_logging/handlers/email_handler.py | 26 ++++---- django_logging/settings/checks.py | 46 +++++++-------- django_logging/settings/conf.py | 5 +- django_logging/utils/get_config.py | 44 +++++++------- django_logging/utils/log_and_notify.py | 35 ++++++----- 9 files changed, 125 insertions(+), 156 deletions(-) delete mode 100644 django_logging/constants/default_settings.py create mode 100644 django_logging/constants/defaults.py diff --git a/django_logging/constants/__init__.py b/django_logging/constants/__init__.py index ceb08cd..a88a79d 100644 --- a/django_logging/constants/__init__.py +++ b/django_logging/constants/__init__.py @@ -1,14 +1,3 @@ from .format_options import FORMAT_OPTIONS -from .default_settings import ( - DEFAULT_AUTO_INITIALIZATION_ENABLE, - DEFAULT_INITIALIZATION_MESSAGE_ENABLE, - DEFAULT_LOG_FILE_FORMATS, - DEFAULT_LOG_CONSOLE_COLORIZE, - DEFAULT_LOG_CONSOLE_FORMAT, - DEFAULT_LOG_CONSOLE_LEVEL, - DEFAULT_LOG_DATE_FORMAT, - DEFAULT_LOG_DIR, - DEFAULT_LOG_EMAIL_NOTIFIER, - DEFAULT_LOG_FILE_LEVELS, -) +from .defaults import DefaultLoggingSettings from .format_specifiers import LOG_FORMAT_SPECIFIERS diff --git a/django_logging/constants/default_settings.py b/django_logging/constants/default_settings.py deleted file mode 100644 index 1fa8c51..0000000 --- a/django_logging/constants/default_settings.py +++ /dev/null @@ -1,62 +0,0 @@ -import os - -from django_logging.constants.settings_types import ( - LogFileFormatsType, - LOG_DIR_TYPE, - LOG_FILE_LEVELS_TYPE, - LOG_CONSOLE_FORMAT_TYPE, - LOG_CONSOLE_LEVEL_TYPE, - LOG_CONSOLE_COLORIZE_TYPE, - LOG_DATE_FORMAT_TYPE, - INITIALIZATION_MESSAGE_ENABLE_TYPE, - LogEmailNotifierType, -) - -# Default directory for logs -DEFAULT_LOG_DIR: LOG_DIR_TYPE = os.path.join(os.getcwd(), "logs") - -# Default LogLevels in File Handlers -DEFAULT_LOG_FILE_LEVELS: LOG_FILE_LEVELS_TYPE = [ - "DEBUG", - "INFO", - "WARNING", - "ERROR", - "CRITICAL", -] - - -# Default log date format -DEFAULT_LOG_DATE_FORMAT: LOG_DATE_FORMAT_TYPE = "%Y-%m-%d %H:%M:%S" - -# Default Auto initialization flag -DEFAULT_AUTO_INITIALIZATION_ENABLE: INITIALIZATION_MESSAGE_ENABLE_TYPE = True - -# Default initialization message flag -DEFAULT_INITIALIZATION_MESSAGE_ENABLE: INITIALIZATION_MESSAGE_ENABLE_TYPE = True - -# Default log formats in log files for each LogLevel -DEFAULT_LOG_FILE_FORMATS: LogFileFormatsType = { - "DEBUG": 1, - "INFO": 1, - "WARNING": 1, - "ERROR": 1, - "CRITICAL": 1, -} - -# Default LogLevel for console output -DEFAULT_LOG_CONSOLE_LEVEL: LOG_CONSOLE_LEVEL_TYPE = "DEBUG" - -# Default log format for console output -DEFAULT_LOG_CONSOLE_FORMAT: LOG_CONSOLE_FORMAT_TYPE = 1 - -# Default colorize logs flag for console output -DEFAULT_LOG_CONSOLE_COLORIZE: LOG_CONSOLE_COLORIZE_TYPE = True - -# Default Log Email Notifier Configs -DEFAULT_LOG_EMAIL_NOTIFIER: LogEmailNotifierType = { - "ENABLE": False, - "NOTIFY_ERROR": False, - "NOTIFY_CRITICAL": False, - "LOG_FORMAT": 1, - "USE_TEMPLATE": True, -} diff --git a/django_logging/constants/defaults.py b/django_logging/constants/defaults.py new file mode 100644 index 0000000..7c3d9ba --- /dev/null +++ b/django_logging/constants/defaults.py @@ -0,0 +1,48 @@ +import os +from dataclasses import dataclass, field + +from django_logging.constants.settings_types import ( + LogFileFormatsType, + LOG_DIR_TYPE, + LOG_LEVELS_TYPE, + LOG_CONSOLE_FORMAT_TYPE, + LOG_CONSOLE_LEVEL_TYPE, + LOG_CONSOLE_COLORIZE_TYPE, + LOG_DATE_FORMAT_TYPE, + INITIALIZATION_MESSAGE_ENABLE_TYPE, + LogEmailNotifierType, +) + + +@dataclass(frozen=True) +class DefaultLoggingSettings: + log_dir: LOG_DIR_TYPE = field( + default_factory=lambda: os.path.join(os.getcwd(), "logs") + ) + log_levels: LOG_LEVELS_TYPE = field( + default_factory=lambda: ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + ) + log_date_format: LOG_DATE_FORMAT_TYPE = "%Y-%m-%d %H:%M:%S" + auto_initialization_enable: INITIALIZATION_MESSAGE_ENABLE_TYPE = True + initialization_message_enable: INITIALIZATION_MESSAGE_ENABLE_TYPE = True + log_file_formats: LogFileFormatsType = field( + default_factory=lambda: { + "DEBUG": 1, + "INFO": 1, + "WARNING": 1, + "ERROR": 1, + "CRITICAL": 1, + } + ) + log_console_level: LOG_CONSOLE_LEVEL_TYPE = "DEBUG" + log_console_format: LOG_CONSOLE_FORMAT_TYPE = 1 + log_console_colorize: LOG_CONSOLE_COLORIZE_TYPE = True + log_email_notifier: LogEmailNotifierType = field( + default_factory=lambda: { + "ENABLE": False, + "NOTIFY_ERROR": False, + "NOTIFY_CRITICAL": False, + "LOG_FORMAT": 1, + "USE_TEMPLATE": True, + } + ) diff --git a/django_logging/constants/settings_types.py b/django_logging/constants/settings_types.py index a68a4ee..9f4d44d 100644 --- a/django_logging/constants/settings_types.py +++ b/django_logging/constants/settings_types.py @@ -24,7 +24,7 @@ class LogFileFormatsType(TypedDict, total=False): # Type Aliases for other configurations LOG_DIR_TYPE = str -LOG_FILE_LEVELS_TYPE = List[str] +LOG_LEVELS_TYPE = List[str] LOG_DATE_FORMAT_TYPE = str AUTO_INITIALIZATION_ENABLE_TYPE = bool INITIALIZATION_MESSAGE_ENABLE_TYPE = bool diff --git a/django_logging/handlers/email_handler.py b/django_logging/handlers/email_handler.py index 9c5696d..5ffaa24 100644 --- a/django_logging/handlers/email_handler.py +++ b/django_logging/handlers/email_handler.py @@ -9,19 +9,9 @@ class EmailHandler(logging.Handler): - def __init__(self, include_html=True, *args, **kwargs): + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - - self.include_html = include_html - - try: - # Attempt to retrieve the logging settings from the Django settings - logging_settings = settings.DJANGO_LOGGING - - self.include_html = use_email_notifier_template() - - except Exception as e: - logging.error(f"An unexpected error occurred while initializing EmailHandler: {e}") + self.include_html = use_email_notifier_template() def emit(self, record): try: @@ -40,13 +30,19 @@ def emit(self, record): self.handleError(record) @staticmethod - def render_template(log_entry, request=None, template_path="email_notifier_template.html"): + def render_template( + log_entry, request=None, template_path="email_notifier_template.html" + ): django_engine = engines["django"] template = django_engine.get_template(template_path) # Fetch IP address and user agent using middleware methods - ip_address = RequestLogMiddleware.get_ip_address(request) if request else "Unknown" - user_agent = RequestLogMiddleware.get_user_agent(request) if request else "Unknown" + ip_address = ( + RequestLogMiddleware.get_ip_address(request) if request else "Unknown" + ) + user_agent = ( + RequestLogMiddleware.get_user_agent(request) if request else "Unknown" + ) context = { "message": log_entry, diff --git a/django_logging/settings/checks.py b/django_logging/settings/checks.py index 81eaca5..40d2e43 100644 --- a/django_logging/settings/checks.py +++ b/django_logging/settings/checks.py @@ -2,18 +2,7 @@ from django.core.checks import Error, register from typing import Dict, Any, List -from django_logging.constants import ( - DEFAULT_LOG_DIR, - DEFAULT_LOG_FILE_LEVELS, - DEFAULT_LOG_DATE_FORMAT, - DEFAULT_LOG_EMAIL_NOTIFIER, - DEFAULT_LOG_CONSOLE_LEVEL, - DEFAULT_LOG_CONSOLE_FORMAT, - DEFAULT_LOG_CONSOLE_COLORIZE, - DEFAULT_LOG_FILE_FORMATS, - DEFAULT_AUTO_INITIALIZATION_ENABLE, - DEFAULT_INITIALIZATION_MESSAGE_ENABLE, -) +from django_logging.constants import DefaultLoggingSettings from django_logging.validators.config_validators import ( validate_directory, @@ -31,26 +20,29 @@ def check_logging_settings(app_configs: Dict[str, Any], **kwargs: Any) -> List[E errors: List[Error] = [] log_settings = getattr(settings, "DJANGO_LOGGING", {}) + defaults = DefaultLoggingSettings() # Validate LOG_DIR - log_dir = log_settings.get("LOG_DIR", DEFAULT_LOG_DIR) + log_dir = log_settings.get("LOG_DIR", defaults.log_dir) errors.extend(validate_directory(log_dir, "LOG_DIR")) # Validate LOG_FILE_LEVELS - log_file_levels = log_settings.get("LOG_FILE_LEVELS", DEFAULT_LOG_FILE_LEVELS) + log_file_levels = log_settings.get("LOG_FILE_LEVELS", defaults.log_levels) errors.extend( - validate_log_levels(log_file_levels, "LOG_FILE_LEVELS", DEFAULT_LOG_FILE_LEVELS) + validate_log_levels( + log_file_levels, "LOG_FILE_LEVELS", defaults.log_levels + ) ) # Validate LOG_FILE_FORMATS - log_file_formats = log_settings.get("LOG_FILE_FORMATS", DEFAULT_LOG_FILE_FORMATS) + log_file_formats = log_settings.get("LOG_FILE_FORMATS", defaults.log_file_formats) if isinstance(log_file_formats, dict): for level, format_option in log_file_formats.items(): - if level not in DEFAULT_LOG_FILE_LEVELS: + if level not in defaults.log_levels: errors.append( Error( f"Invalid log level '{level}' in LOG_FILE_FORMATS.", - hint=f"Valid log levels are: {DEFAULT_LOG_FILE_LEVELS}.", + hint=f"Valid log levels are: {defaults.log_levels}.", id="django_logging.E019_LOG_FILE_FORMATS", ) ) @@ -68,33 +60,35 @@ def check_logging_settings(app_configs: Dict[str, Any], **kwargs: Any) -> List[E # Validate LOG_CONSOLE_FORMAT log_console_format = log_settings.get( - "LOG_CONSOLE_FORMAT", DEFAULT_LOG_CONSOLE_FORMAT + "LOG_CONSOLE_FORMAT", defaults.log_console_format ) errors.extend(validate_format_option(log_console_format, "LOG_CONSOLE_FORMAT")) # Validate LOG_CONSOLE_LEVEL - log_console_level = log_settings.get("LOG_CONSOLE_LEVEL", DEFAULT_LOG_CONSOLE_LEVEL) + log_console_level = log_settings.get( + "LOG_CONSOLE_LEVEL", defaults.log_console_level + ) errors.extend( validate_log_levels( - [log_console_level], "LOG_CONSOLE_LEVEL", DEFAULT_LOG_FILE_LEVELS + [log_console_level], "LOG_CONSOLE_LEVEL", defaults.log_levels ) ) # Validate LOG_CONSOLE_COLORIZE log_console_colorize = log_settings.get( - "LOG_CONSOLE_COLORIZE", DEFAULT_LOG_CONSOLE_COLORIZE + "LOG_CONSOLE_COLORIZE", defaults.log_console_colorize ) errors.extend( validate_boolean_setting(log_console_colorize, "LOG_CONSOLE_COLORIZE") ) # Validate LOG_DATE_FORMAT - log_date_format = log_settings.get("LOG_DATE_FORMAT", DEFAULT_LOG_DATE_FORMAT) + log_date_format = log_settings.get("LOG_DATE_FORMAT", defaults.log_date_format) errors.extend(validate_date_format(log_date_format, "LOG_DATE_FORMAT")) # Validate AUTO_INITIALIZATION_ENABLE auto_initialization_enable = log_settings.get( - "AUTO_INITIALIZATION_ENABLE", DEFAULT_AUTO_INITIALIZATION_ENABLE + "AUTO_INITIALIZATION_ENABLE", defaults.auto_initialization_enable ) errors.extend( validate_boolean_setting( @@ -104,7 +98,7 @@ def check_logging_settings(app_configs: Dict[str, Any], **kwargs: Any) -> List[E # Validate INITIALIZATION_MESSAGE_ENABLE initialization_message_enable = log_settings.get( - "INITIALIZATION_MESSAGE_ENABLE", DEFAULT_INITIALIZATION_MESSAGE_ENABLE + "INITIALIZATION_MESSAGE_ENABLE", defaults.initialization_message_enable ) errors.extend( validate_boolean_setting( @@ -114,7 +108,7 @@ def check_logging_settings(app_configs: Dict[str, Any], **kwargs: Any) -> List[E # Validate LOG_EMAIL_NOTIFIER log_email_notifier = log_settings.get( - "LOG_EMAIL_NOTIFIER", DEFAULT_LOG_EMAIL_NOTIFIER + "LOG_EMAIL_NOTIFIER", defaults.log_email_notifier ) errors.extend(validate_email_notifier(log_email_notifier)) diff --git a/django_logging/settings/conf.py b/django_logging/settings/conf.py index 07a5e3c..906a6ee 100644 --- a/django_logging/settings/conf.py +++ b/django_logging/settings/conf.py @@ -3,7 +3,7 @@ import os from typing import List, Dict, Optional, Union -from django_logging.constants import FORMAT_OPTIONS, DEFAULT_LOG_FILE_LEVELS +from django_logging.constants import FORMAT_OPTIONS, DefaultLoggingSettings from django_logging.filters.level_filter import LoggingLevelFilter @@ -132,6 +132,7 @@ def get_log_file(self, log_level: str) -> Optional[str]: def set_conf(self) -> None: """Sets the logging configuration using the generated log files.""" + defaults = DefaultLoggingSettings() handlers = { level.lower(): { "class": "logging.FileHandler", @@ -165,7 +166,7 @@ def set_conf(self) -> None: "()": LoggingLevelFilter, "logging_level": getattr(logging, level), } - for level in DEFAULT_LOG_FILE_LEVELS + for level in defaults.log_levels } formatters = { diff --git a/django_logging/utils/get_config.py b/django_logging/utils/get_config.py index d55dd7e..ba4c767 100644 --- a/django_logging/utils/get_config.py +++ b/django_logging/utils/get_config.py @@ -2,18 +2,7 @@ from django.conf import settings from typing import List -from django_logging.constants import ( - DEFAULT_LOG_DIR, - DEFAULT_LOG_FILE_LEVELS, - DEFAULT_LOG_DATE_FORMAT, - DEFAULT_LOG_EMAIL_NOTIFIER, - DEFAULT_LOG_CONSOLE_LEVEL, - DEFAULT_LOG_CONSOLE_FORMAT, - DEFAULT_LOG_CONSOLE_COLORIZE, - DEFAULT_LOG_FILE_FORMATS, - DEFAULT_INITIALIZATION_MESSAGE_ENABLE, - DEFAULT_AUTO_INITIALIZATION_ENABLE, -) +from django_logging.constants import DefaultLoggingSettings def get_conf() -> List: @@ -24,19 +13,20 @@ def get_conf() -> List: A tuple containing all necessary configurations for logging. """ log_settings = getattr(settings, "DJANGO_LOGGING", {}) + defaults = DefaultLoggingSettings() - log_levels = log_settings.get("LOG_FILE_LEVELS", DEFAULT_LOG_FILE_LEVELS) - log_dir = log_settings.get("LOG_DIR", os.path.join(os.getcwd(), DEFAULT_LOG_DIR)) - log_file_formats = log_settings.get("LOG_FILE_FORMATS", DEFAULT_LOG_FILE_FORMATS) - console_level = log_settings.get("LOG_CONSOLE_LEVEL", DEFAULT_LOG_CONSOLE_LEVEL) - console_format = log_settings.get("LOG_CONSOLE_FORMAT", DEFAULT_LOG_CONSOLE_FORMAT) + log_levels = log_settings.get("LOG_FILE_LEVELS", defaults.log_levels) + log_dir = log_settings.get("LOG_DIR", os.path.join(os.getcwd(), defaults.log_dir)) + log_file_formats = log_settings.get("LOG_FILE_FORMATS", defaults.log_file_formats) + console_level = log_settings.get("LOG_CONSOLE_LEVEL", defaults.log_console_level) + console_format = log_settings.get("LOG_CONSOLE_FORMAT", defaults.log_console_format) colorize_console = log_settings.get( - "LOG_CONSOLE_COLORIZE", DEFAULT_LOG_CONSOLE_COLORIZE + "LOG_CONSOLE_COLORIZE", defaults.log_console_colorize ) - log_date_format = log_settings.get("LOG_DATE_FORMAT", DEFAULT_LOG_DATE_FORMAT) + log_date_format = log_settings.get("LOG_DATE_FORMAT", defaults.log_date_format) log_email_notifier = log_settings.get( - "LOG_EMAIL_NOTIFIER", DEFAULT_LOG_EMAIL_NOTIFIER + "LOG_EMAIL_NOTIFIER", defaults.log_email_notifier ) log_email_notifier_enable = log_email_notifier.get("ENABLE") log_email_notifier_log_levels = [ @@ -68,10 +58,12 @@ def use_email_notifier_template() -> bool: bool: True if the email notifier should use a template, False otherwise. """ log_settings = getattr(settings, "DJANGO_LOGGING", {}) + defaults = DefaultLoggingSettings() + log_email_notifier = log_settings.get( - "LOG_EMAIL_NOTIFIER", DEFAULT_LOG_EMAIL_NOTIFIER + "LOG_EMAIL_NOTIFIER", defaults.log_email_notifier ) - return log_email_notifier.get("USE_TEMPLATE", False) + return log_email_notifier.get("USE_TEMPLATE", True) def is_auto_initialization_enabled() -> bool: @@ -83,8 +75,10 @@ def is_auto_initialization_enabled() -> bool: Defaults to True if not specified. """ log_settings = getattr(settings, "DJANGO_LOGGING", {}) + defaults = DefaultLoggingSettings() + return log_settings.get( - "AUTO_INITIALIZATION_ENABLE", DEFAULT_AUTO_INITIALIZATION_ENABLE + "AUTO_INITIALIZATION_ENABLE", defaults.auto_initialization_enable ) @@ -97,6 +91,8 @@ def is_initialization_message_enabled() -> bool: Defaults to True if not specified. """ log_settings = getattr(settings, "DJANGO_LOGGING", {}) + defaults = DefaultLoggingSettings() + return log_settings.get( - "INITIALIZATION_MESSAGE_ENABLE", DEFAULT_INITIALIZATION_MESSAGE_ENABLE + "INITIALIZATION_MESSAGE_ENABLE", defaults.initialization_message_enable ) diff --git a/django_logging/utils/log_and_notify.py b/django_logging/utils/log_and_notify.py index 7ef4011..d879af3 100644 --- a/django_logging/utils/log_and_notify.py +++ b/django_logging/utils/log_and_notify.py @@ -6,29 +6,30 @@ from django_logging.constants.format_options import FORMAT_OPTIONS from django_logging.utils.email_notifier import send_email_async +from django_logging.utils.get_config import get_conf from django_logging.handlers import EmailHandler from django_logging.settings.conf import LogConfig -def log_and_notify(logger, level: int, message: str, extra: Optional[Dict] = None) -> None: +def log_and_notify( + logger, level: int, message: str, extra: Optional[Dict] = None +) -> None: # Get the caller's frame to capture the correct module, file, and line number frame = inspect.currentframe().f_back + logging_settings = get_conf() + email_notifier_enable = getattr( + logging_settings, "log_email_notifier_enable", False + ) - if not hasattr(settings, "DJANGO_LOGGING"): + if not email_notifier_enable: raise ValueError( - "Email notifier is disabled. Please add DJANGO_LOGGING to your settings file and set " - "the 'ENABLE' option to True in the 'LOG_EMAIL_NOTIFIER' settings to activate email notifications." + "Email notifier is disabled. Please set the 'ENABLE' option to True in the 'LOG_EMAIL_NOTIFIER'" + " in DJANGO_LOGGING in your settings to activate email notifications." ) - email_notifier_conf = settings.DJANGO_LOGGING.get("LOG_EMAIL_NOTIFIER") - notifier_enable = email_notifier_conf.get("ENABLE", False) - if not notifier_enable: - raise ValueError( - "Email notifier is disabled. Please set the 'ENABLE' option to True in the 'LOG_EMAIL_NOTIFIER' " - "settings to activate email notifications." - ) - - _format = email_notifier_conf.get("LOG_FORMAT", FORMAT_OPTIONS[1]) + _format = getattr( + logging_settings, "log_email_notifier_log_format", FORMAT_OPTIONS[1] + ) try: # create a LogRecord @@ -62,4 +63,10 @@ def log_and_notify(logger, level: int, message: str, extra: Optional[Dict] = Non email_body = EmailHandler.render_template(formatted_message, request) subject = f"New Log Record: {logging.getLevelName(level)}" - send_email_async(subject, email_body, [settings.ADMIN_EMAIL]) + admin_email = getattr(settings, "ADMIN_EMAIL") + if not admin_email: + raise ValueError( + "'ADMIN EMAIL' not provided, please provide 'ADMIN_EMAIL' in your settings" + ) + + send_email_async(subject, email_body, [admin_email]) From 9e221bbef6335f24cf225806db9062cde801b63d Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Wed, 21 Aug 2024 18:20:10 +0330 Subject: [PATCH 2/2] :zap: Update(validators) LOG_LEVEL import in config_validators --- django_logging/validators/config_validators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django_logging/validators/config_validators.py b/django_logging/validators/config_validators.py index ad51b28..d3781f6 100644 --- a/django_logging/validators/config_validators.py +++ b/django_logging/validators/config_validators.py @@ -9,7 +9,7 @@ from django_logging.constants import LOG_FORMAT_SPECIFIERS, FORMAT_OPTIONS from django_logging.constants.settings_types import ( FormatOption, - LOG_FILE_LEVELS_TYPE, + LOG_LEVELS_TYPE, LogEmailNotifierType, ) @@ -44,9 +44,9 @@ def validate_directory(path: str, config_name: str) -> List[Error]: def validate_log_levels( - log_levels: LOG_FILE_LEVELS_TYPE, + log_levels: LOG_LEVELS_TYPE, config_name: str, - valid_levels: LOG_FILE_LEVELS_TYPE, + valid_levels: LOG_LEVELS_TYPE, ) -> List[Error]: errors = [] if not isinstance(log_levels, list):