Skip to content

Commit

Permalink
✨⚡🔨🚨feat(settings): Add SettingsManager class to manage Django loggin…
Browse files Browse the repository at this point in the history
…g configurations

- Added SettingsManager class to handle and organize all DJANGO_LOGGING configurations.
  - The class initializes all logging configurations at once and provides easy access through attributes.
  - Default values are used if specific settings are not defined in Django settings.

- Refactored get_config method:
  - Updated to retrieve logging configurations from the SettingsManager.
  - This refactor improves efficiency and code readability by allowing direct access to individual settings.

- Updated checks to fetch logging settings directly from the SettingsManager class.

- Refactored related tests:
  - Adjusted test cases to reflect the new settings access method via SettingsManager.
  - Ensured that the tests validate the correct functionality of the new settings manager and its configurations.

This update improves the overall maintainability and performance of the logging configuration management in the project.

Closes #124
  • Loading branch information
MEHRSHAD-MIRSHEKARY committed Oct 15, 2024
1 parent 43ef34d commit 3c9e9a5
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 320 deletions.
8 changes: 2 additions & 6 deletions django_logging/management/commands/logs_size_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
from django.conf import settings
from django.core.management.base import BaseCommand

from django_logging.constants import DefaultLoggingSettings
from django_logging.constants.config_types import LogDir
from django_logging.handlers import EmailHandler
from django_logging.management.commands.send_logs import Command as cmd
from django_logging.settings import settings_manager
from django_logging.utils.get_conf import (
get_log_dir_size_limit,
use_email_notifier_template,
Expand Down Expand Up @@ -40,10 +39,7 @@ def handle(self, *args: Tuple[Any], **kwargs: Dict[str, Any]) -> None:
**kwargs: Keyword arguments passed to the command.
"""
default_settings = DefaultLoggingSettings()
log_dir: LogDir = settings.DJANGO_LOGGING.get(
"LOG_DIR", os.path.join(os.getcwd(), default_settings.log_dir)
)
log_dir = settings_manager.log_dir

# Check if log directory exists
if not os.path.exists(log_dir):
Expand Down
1 change: 1 addition & 0 deletions django_logging/settings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .manager import settings_manager
60 changes: 27 additions & 33 deletions django_logging/settings/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,7 @@
from django.core.checks import Error, register

from django_logging.constants import ALLOWED_FILE_FORMAT_TYPES, DefaultLoggingSettings
from django_logging.utils.get_conf import (
get_config,
get_log_dir_size_limit,
include_log_iboard,
is_auto_initialization_enabled,
is_initialization_message_enabled,
is_log_sql_queries_enabled,
)
from django_logging.settings import settings_manager
from django_logging.validators.config_validators import (
validate_boolean_setting,
validate_date_format,
Expand Down Expand Up @@ -75,25 +68,21 @@ def check_logging_settings(app_configs: Dict[str, Any], **kwargs: Any) -> List[E
"""
errors: List[Error] = []

log_settings = get_config(extra_info=True)
logging_defaults = DefaultLoggingSettings()

# Validate LOG_DIR
errors.extend(validate_directory(log_settings.get("log_dir"), "LOG_DIR")) # type: ignore
errors.extend(validate_directory(settings_manager.log_dir, "LOG_DIR"))

# Validate LOG_FILE_LEVELS
log_file_levels = log_settings.get("log_levels")
log_file_levels = settings_manager.log_levels
errors.extend(
validate_log_levels(
log_file_levels, "LOG_FILE_LEVELS", logging_defaults.log_levels # type: ignore
)
)

# Validate LOG_FILE_FORMATS
log_file_formats = log_settings.get(
"log_file_formats", logging_defaults.log_file_formats
)
log_file_formats = settings_manager.log_file_formats
if isinstance(log_file_formats, dict):
for level, format_option in log_file_formats.items():
if level not in logging_defaults.log_levels:
Expand All @@ -117,75 +106,80 @@ def check_logging_settings(app_configs: Dict[str, Any], **kwargs: Any) -> List[E
)

# Validate LOG_FILE_FORMAT_TYPES
log_file_format_types = log_settings.get("log_file_format_types", {})
errors.extend(
validate_log_file_format_types(
log_file_format_types,
settings_manager.log_file_format_types,
"LOG_FILE_FORMAT_TYPES",
logging_defaults.log_levels,
ALLOWED_FILE_FORMAT_TYPES + ["NORMAL"],
)
)

# Validate EXTRA_LOG_FILES
extra_log_files = log_settings.get("extra_log_files", {})
errors.extend(
validate_extra_log_files(
extra_log_files, "EXTRA_LOG_FILES", logging_defaults.log_levels
settings_manager.extra_log_files,
"EXTRA_LOG_FILES",
logging_defaults.log_levels,
)
)

# Validate LOG_CONSOLE_FORMAT
log_console_format = log_settings.get("console_format")
errors.extend(validate_format_option(log_console_format, "LOG_CONSOLE_FORMAT")) # type: ignore
errors.extend(validate_format_option(settings_manager.console_format, "LOG_CONSOLE_FORMAT")) # type: ignore

# Validate LOG_CONSOLE_LEVEL
log_console_level = log_settings.get("console_level")
errors.extend(
validate_log_levels(
[log_console_level], "LOG_CONSOLE_LEVEL", logging_defaults.log_levels # type: ignore
[settings_manager.console_level], "LOG_CONSOLE_LEVEL", logging_defaults.log_levels # type: ignore
)
)

# Validate LOG_CONSOLE_COLORIZE
log_console_colorize = log_settings.get("colorize_console")
errors.extend(
validate_boolean_setting(log_console_colorize, "LOG_CONSOLE_COLORIZE") # type: ignore
validate_boolean_setting(settings_manager.colorize_console, "LOG_CONSOLE_COLORIZE") # type: ignore
)

# Validate LOG_DATE_FORMAT
log_date_format = log_settings.get("log_date_format")
errors.extend(validate_date_format(log_date_format, "LOG_DATE_FORMAT")) # type: ignore
errors.extend(validate_date_format(settings_manager.log_date_format, "LOG_DATE_FORMAT")) # type: ignore

# Validate INCLUDE_LOG_iBOARD
errors.extend(validate_boolean_setting(include_log_iboard(), "INCLUDE_LOG_iBOARD"))
errors.extend(
validate_boolean_setting(
settings_manager.include_log_iboard, "INCLUDE_LOG_iBOARD"
)
)

# Validate AUTO_INITIALIZATION_ENABLE
errors.extend(
validate_boolean_setting(
is_auto_initialization_enabled(), "AUTO_INITIALIZATION_ENABLE"
settings_manager.auto_initialization_enabled, "AUTO_INITIALIZATION_ENABLE"
)
)

# Validate INITIALIZATION_MESSAGE_ENABLE
errors.extend(
validate_boolean_setting(
is_initialization_message_enabled(), "INITIALIZATION_MESSAGE_ENABLE"
settings_manager.initialization_message_enabled,
"INITIALIZATION_MESSAGE_ENABLE",
)
)

# Validate LOG_SQL_QUERIES_ENABLE
errors.extend(
validate_boolean_setting(is_log_sql_queries_enabled(), "LOG_SQL_QUERIES_ENABLE")
validate_boolean_setting(
settings_manager.log_sql_queries_enabled, "LOG_SQL_QUERIES_ENABLE"
)
)

# Validate LOG_DIR_SIZE_LIMIT
errors.extend(
validate_integer_setting(get_log_dir_size_limit(), "LOG_DIR_SIZE_LIMIT")
validate_integer_setting(
settings_manager.log_dir_size_limit, "LOG_DIR_SIZE_LIMIT"
)
)

# Validate LOG_EMAIL_NOTIFIER
log_email_notifier = log_settings.get("log_email_notifier")
log_email_notifier = settings_manager.email_notifier
errors.extend(validate_email_notifier(log_email_notifier)) # type: ignore

if log_email_notifier.get("ENABLE", False): # type: ignore
Expand Down
134 changes: 134 additions & 0 deletions django_logging/settings/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import os
from typing import Any, Dict, List, Union

from django.conf import settings

from django_logging.constants import DefaultConsoleSettings, DefaultLoggingSettings
from django_logging.constants.config_types import (
ExtraLogFiles,
FormatOption,
LogDateFormat,
LogDir,
LogEmailNotifier,
LogFileFormats,
LogFileFormatTypes,
LogLevels,
NotifierLogLevels,
)


# pylint: disable=too-many-instance-attributes
class SettingsManager:
"""Manages DJANGO_LOGGING settings for the Django Logging application. All
configurations are initialized at once and accessible via attributes.
Attributes:
log_dir (str): The directory where log files are stored.
log_levels (List[str]): List of logging levels (e.g., DEBUG, INFO).
log_file_formats (Dict[str, Union[int, str]]): Log file formats, which can be integers or strings.
log_file_format_types (Dict[str, str]): Format types (e.g., 'json', 'xml', 'flat', 'normal') for each logging level.
extra_log_files (Dict[str, bool]): Boolean values indicating whether separate files (JSON, XML) should be used for each logging level.
console_level (str): The logging level for console output.
console_format (Union[int, str]): The format for console logs, either an integer or a string.
colorize_console (bool): Whether to colorize console logs.
log_date_format (str): The format used for timestamps in logs.
email_notifier (Dict[str, Any]): Configuration for email notifications.
email_notifier_enabled (bool): Whether email notifications are enabled.
email_notifier_log_levels (List[str]): Levels that trigger email notifications.
email_notifier_log_format (Union[int, str]): Log format used in email notifications, either an integer or a format string.
auto_initialization_enabled (bool): Whether auto initialization of logging is enabled.
initialization_message_enabled (bool): Whether to display an initialization message for logging.
log_sql_queries_enabled (bool): Whether to log SQL queries in each request.
log_dir_size_limit (int): The maximum size (in MB) allowed for the log directory.
include_log_iboard (bool): Whether the Logiboard feature is included.
use_email_notifier_template (bool): Whether to use a template for email notifications.
"""

def __init__(self) -> None:
"""Initializes all settings from the Django settings, falling back to
default values defined in DefaultLoggingSettings and
DefaultConsoleSettings."""
self.log_settings = getattr(settings, "DJANGO_LOGGING", {})
if not isinstance(self.log_settings, dict):
raise ValueError("DJANGO_LOGGING must be a dictionary with configs as keys")

self.default_logging = DefaultLoggingSettings()
self.default_console = DefaultConsoleSettings()

# Initialize all configuration settings
self.log_dir: LogDir = self.get(
"LOG_DIR", os.path.join(os.getcwd(), self.default_logging.log_dir)
)
self.log_levels: LogLevels = self.get(
"LOG_FILE_LEVELS", self.default_logging.log_levels
)
self.log_file_formats: LogFileFormats = self.get(
"LOG_FILE_FORMATS", self.default_logging.log_file_formats
)
self.log_file_format_types: LogFileFormatTypes = self.get(
"LOG_FILE_FORMAT_TYPES", self.default_logging.log_file_format_types
)
self.extra_log_files: ExtraLogFiles = self.get(
"EXTRA_LOG_FILES", self.default_logging.extra_log_files
)
self.console_level: str = self.get(
"LOG_CONSOLE_LEVEL", self.default_console.log_console_level
)
self.console_format: FormatOption = self.get(
"LOG_CONSOLE_FORMAT", self.default_console.log_console_format
)
self.colorize_console: bool = self.get(
"LOG_CONSOLE_COLORIZE", self.default_console.log_console_colorize
)
self.log_date_format: LogDateFormat = self.get(
"LOG_DATE_FORMAT", self.default_logging.log_date_format
)
self.email_notifier: LogEmailNotifier = self.get(
"LOG_EMAIL_NOTIFIER", self.default_logging.log_email_notifier
)
self.email_notifier_enabled: bool = self.email_notifier.get("ENABLE", False)
self.email_notifier_log_levels: NotifierLogLevels = [
"ERROR" if self.email_notifier.get("NOTIFY_ERROR", False) else None,
("CRITICAL" if self.email_notifier.get("NOTIFY_CRITICAL", False) else None),
]
self.email_notifier_log_format: FormatOption = self.email_notifier.get(
"LOG_FORMAT", 1
)
self.auto_initialization_enabled: bool = self.get(
"AUTO_INITIALIZATION_ENABLE",
self.default_logging.auto_initialization_enable,
)
self.initialization_message_enabled: bool = self.get(
"INITIALIZATION_MESSAGE_ENABLE",
self.default_logging.initialization_message_enable,
)
self.log_sql_queries_enabled: bool = self.get(
"LOG_SQL_QUERIES_ENABLE", self.default_logging.log_sql_queries_enable
)
self.log_dir_size_limit: int = self.get(
"LOG_DIR_SIZE_LIMIT", self.default_logging.log_dir_size_limit
)
self.include_log_iboard: bool = self.get(
"INCLUDE_LOG_iBOARD", self.default_logging.include_log_iboard
)
self.use_email_notifier_template: bool = self.email_notifier.get(
"USE_TEMPLATE", True
)

def get(self, key: str, default_value: Any) -> Any:
"""Retrieves a logging-related setting from the Django settings. If the
setting is not present, returns the provided default value.
Args:
key (str): The key to look up in the logging settings.
default_value (Any): The default value to return if the key is not found.
Returns:
Any: The value of the setting or the default value.
"""
return self.log_settings.get(key, default_value)


settings_manager: SettingsManager = SettingsManager()
2 changes: 1 addition & 1 deletion django_logging/tests/commands/test_logs_size_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_command_log_directory_size_exceeds_limit(
with patch("os.path.getsize", side_effect=[60 * 1024 * 1024, 50 * 1024 * 1024]):
out = StringIO()
with patch("django.conf.settings.ADMIN_EMAIL", "[email protected]"):
with patch("django.conf.settings.DJANGO_LOGGING", {"LOG_DIR_SIZE_LIMIT": 100}):
with patch("django_logging.management.commands.logs_size_audit.settings_manager.log_dir_size_limit", 100):
call_command("logs_size_audit", stdout=out)

# Verify that the warning email was sent
Expand Down
11 changes: 7 additions & 4 deletions django_logging/tests/fixtures/settings_fixture.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from typing import Dict, Generator
from typing import Generator
from unittest.mock import patch

import pytest
from django.conf import settings

from django_logging.settings.manager import SettingsManager


@pytest.fixture
def mock_settings() -> Generator[Dict, None, None]:
def mock_settings() -> SettingsManager:
"""
Fixture to mock Django settings.
Expand Down Expand Up @@ -46,8 +47,10 @@ def mock_settings() -> Generator[Dict, None, None]:
},
}
}

with patch.object(settings, "DJANGO_LOGGING", mock_settings["DJANGO_LOGGING"]):
yield mock_settings
# Initialize SettingsManager after patching the settings
return SettingsManager()


@pytest.fixture
Expand Down
2 changes: 1 addition & 1 deletion django_logging/tests/fixtures/views_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


@pytest.fixture
def setup_users(db) -> Dict[str, User]:
def setup_users() -> Dict[str, User]:
"""
Fixture to create a superuser and a normal user for testing purposes.
Returns a dictionary with `superuser` and `non_superuser` keys.
Expand Down
Loading

0 comments on commit 3c9e9a5

Please sign in to comment.