Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚡ 🔨 Update configs #100

Merged
merged 3 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions django_logging/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
from .default_settings import DefaultConsoleSettings, DefaultLoggingSettings
from .log_format_options import FORMAT_OPTIONS
from .log_format_specifiers import LOG_FORMAT_SPECIFIERS

# Used in settings.conf
ALLOWED_EXTRA_FILE_TYPES = ["JSON", "XML"]
ALLOWED_FILE_FORMAT_TYPES = ["JSON", "XML", "FLAT"]
19 changes: 19 additions & 0 deletions django_logging/constants/ansi_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class AnsiColors:
BRIGHT_MAGENTA: str = "\033[0;95m"
BRIGHT_CYAN: str = "\033[0;96m"
BRIGHT_WHITE: str = "\033[0;97m"
PINK: str = "\033[38;5;213m"
LIGHT_PURPLE = "\033[38;5;129m"
BLACK_BACKGROUND: str = "\033[40m"
RED_BACKGROUND: str = "\033[41m"
GREEN_BACKGROUND: str = "\033[42m"
Expand All @@ -30,6 +32,23 @@ class AnsiColors:
MAGENTA_BACKGROUND: str = "\033[45m"
CYAN_BACKGROUND: str = "\033[46m"
WHITE_BACKGROUND: str = "\033[47m"
BRIGHT_BLACK_BACKGROUND: str = "\033[100m"
BRIGHT_RED_BACKGROUND: str = "\033[101m"
BRIGHT_GREEN_BACKGROUND: str = "\033[102m"
BRIGHT_YELLOW_BACKGROUND: str = "\033[103m"
BRIGHT_BLUE_BACKGROUND: str = "\033[104m"
BRIGHT_MAGENTA_BACKGROUND: str = "\033[105m"
BRIGHT_CYAN_BACKGROUND: str = "\033[106m"
BRIGHT_WHITE_BACKGROUND: str = "\033[107m"

BOLD: str = "\033[1m"
DIM: str = "\033[2m"
ITALIC: str = "\033[3m"
BOLD_ITALIC: str = "\033[1;3m"
UNDERLINE: str = "\033[4m"
BLINK: str = "\033[5m"
INVERT: str = "\033[7m"
STRIKETHROUGH: str = "\033[9m"


# Mapping log levels to ANSI colors
Expand Down
32 changes: 24 additions & 8 deletions django_logging/constants/config_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,42 @@

FormatOption = Union[int, str]

# Type Aliases for configurations
LogFileFormatType = Literal["JSON", "XML", "FLAT", "LOG"]
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
LogDir = str
LogLevels = List[LogLevel]
NotifierLogLevels = List[Literal["ERROR", "CRITICAL"]]
LogDateFormat = str


class LogEmailNotifierType(TypedDict, total=False):
class LogEmailNotifier(TypedDict, total=False):
ENABLE: bool
NOTIFY_ERROR: bool
NOTIFY_CRITICAL: bool
LOG_FORMAT: FormatOption
USE_TEMPLATE: bool


class LogFileFormatsType(TypedDict, total=False):
class LogFileFormats(TypedDict, total=False):
DEBUG: FormatOption
INFO: FormatOption
WARNING: FormatOption
ERROR: FormatOption
CRITICAL: FormatOption


# Type Aliases for other configurations
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
LogDir = str
LogLevels = List[LogLevel]
NotifierLogLevels = List[Literal["ERROR", "CRITICAL"]]
LogDateFormat = str
class LogFileFormatTypes(TypedDict, total=False):
DEBUG: LogFileFormatType
INFO: LogFileFormatType
WARNING: LogFileFormatType
ERROR: LogFileFormatType
CRITICAL: LogFileFormatType


class ExtraLogFiles(TypedDict, total=False):
DEBUG: bool
INFO: bool
WARNING: bool
ERROR: bool
CRITICAL: bool
42 changes: 36 additions & 6 deletions django_logging/constants/default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,33 @@
from typing import cast

from django_logging.constants.config_types import (
ExtraLogFiles,
FormatOption,
LogDateFormat,
LogDir,
LogEmailNotifierType,
LogFileFormatsType,
LogEmailNotifier,
LogFileFormats,
LogFileFormatTypes,
LogLevel,
LogLevels,
)


# pylint: disable=too-many-instance-attributes
@dataclass(frozen=True)
class DefaultLoggingSettings:
log_dir: LogDir = field(default_factory=lambda: os.path.join(os.getcwd(), "logs"))
log_dir_size_limit: int = 1024 # MB
log_levels: LogLevels = field(
default_factory=lambda: ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
)
log_date_format: LogDateFormat = "%Y-%m-%d %H:%M:%S"
auto_initialization_enable: bool = True
initialization_message_enable: bool = True
log_file_formats: LogFileFormatsType = field(
log_sql_queries_enable: bool = False
log_file_formats: LogFileFormats = field(
default_factory=lambda: cast(
LogFileFormatsType,
LogFileFormats,
{
"DEBUG": 1,
"INFO": 1,
Expand All @@ -34,10 +39,35 @@ class DefaultLoggingSettings:
},
)
)
log_file_format_types: LogFileFormatTypes = field(
default_factory=lambda: cast(
LogFileFormatTypes,
{
"DEBUG": "normal",
"INFO": "normal",
"WARNING": "normal",
"ERROR": "normal",
"CRITICAL": "normal",
},
)
)

extra_log_files: ExtraLogFiles = field(
default_factory=lambda: cast(
ExtraLogFiles,
{
"DEBUG": False,
"INFO": False,
"WARNING": False,
"ERROR": False,
"CRITICAL": False,
},
)
)

log_email_notifier: LogEmailNotifierType = field(
log_email_notifier: LogEmailNotifier = field(
default_factory=lambda: cast(
LogEmailNotifierType,
LogEmailNotifier,
{
"ENABLE": False,
"NOTIFY_ERROR": False,
Expand Down
33 changes: 20 additions & 13 deletions django_logging/constants/log_format_options.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
FORMAT_OPTIONS = {
1: "%(levelname)s | %(asctime)s | %(module)s | %(message)s",
2: "%(levelname)s | %(asctime)s | %(message)s",
3: "%(levelname)s | %(message)s",
4: "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
5: "%(levelname)s | %(message)s | [in %(pathname)s:%(lineno)d]",
6: "%(asctime)s | %(levelname)s | %(message)s",
7: "%(levelname)s | %(asctime)s | in %(module)s: %(message)s",
8: "%(levelname)s | %(message)s | [%(filename)s:%(lineno)d]",
9: "[%(asctime)s] | %(levelname)s | in %(module)s: %(message)s",
10: "%(asctime)s | %(processName)s | %(name)s | %(levelname)s | %(message)s",
11: "%(asctime)s | %(threadName)s | %(name)s | %(levelname)s | %(message)s",
12: "%(levelname)s | [%(asctime)s] | (%(filename)s:%(lineno)d) | %(message)s",
13: "%(levelname)s | [%(asctime)s] | {%(name)s} | (%(filename)s:%(lineno)d): %(message)s",
1: "%(levelname)s | %(asctime)s | %(module)s | %(message)s | %(context)s",
2: "%(levelname)s | %(asctime)s | %(context)s | %(message)s",
3: "%(levelname)s | %(context)s | %(message)s",
4: "%(context)s | %(asctime)s - %(name)s - %(levelname)s - %(message)s",
5: "%(levelname)s | %(message)s | %(context)s | [in %(pathname)s:%(lineno)d]",
6: "%(asctime)s | %(context)s | %(levelname)s | %(message)s",
7: "%(levelname)s | %(asctime)s | %(context)s | in %(module)s: %(message)s",
8: "%(levelname)s | %(context)s | %(message)s | [%(filename)s:%(lineno)d]",
9: "[%(asctime)s] | %(levelname)s | %(context)s | in %(module)s: %(message)s",
10: "%(asctime)s | %(processName)s | %(context)s | %(name)s | %(levelname)s | %(message)s",
11: "%(asctime)s | %(context)s | %(threadName)s | %(name)s | %(levelname)s | %(message)s",
12: "%(levelname)s | [%(asctime)s] | %(context)s | (%(filename)s:%(lineno)d) | %(message)s",
13: "%(levelname)s | [%(asctime)s] | %(context)s | {%(name)s} | (%(filename)s:%(lineno)d): %(message)s",
14: "[%(asctime)s] | %(levelname)s | %(context)s | %(name)s | %(module)s | %(message)s",
15: "%(levelname)s | %(context)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s",
16: "%(levelname)s | %(context)s | %(message)s | [%(asctime)s] | %(module)s",
17: "%(levelname)s | %(context)s | [%(asctime)s] | %(process)d | %(message)s",
18: "%(levelname)s | %(context)s | %(asctime)s | %(name)s | %(message)s",
19: "%(levelname)s | %(asctime)s | %(context)s | %(module)s:%(lineno)d | %(message)s",
20: "[%(asctime)s] | %(levelname)s | %(context)s | %(thread)d | %(message)s",
}
1 change: 1 addition & 0 deletions django_logging/constants/log_format_specifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
"thread",
"threadName",
"message",
"context",
]
8 changes: 4 additions & 4 deletions django_logging/decorators/execution_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import os
import time
from functools import wraps
from typing import Callable, Optional
from typing import Any, Callable, Optional

from django.conf import settings
from django.db import connection

from django_logging.utils.time import format_elapsed_time
from django_logging.validators.config_validators import (
validate_boolean_setting,
validate_integer_setting,
Expand Down Expand Up @@ -69,7 +70,7 @@ def execution_tracker(

def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
def wrapper(*args: Any, **kwargs: Any) -> Any:
start_time = time.time()

# Check if DEBUG is True and log_queries is enabled; if not, ignore query tracking
Expand All @@ -82,15 +83,14 @@ def wrapper(*args, **kwargs):

# Calculate execution time
elapsed_time = time.time() - start_time
minutes, seconds = divmod(elapsed_time, 60)

# Get detailed function information
module_name = func.__module__
function_name = func.__qualname__
file_path = os.path.abspath(func.__code__.co_filename)
line_number = func.__code__.co_firstlineno

time_message = f"{minutes} minute(s) and {seconds:.4f} second(s)"
time_message = format_elapsed_time(elapsed_time)
log_message = (
f"Performance Metrics for Function: '{function_name}'\n"
f" Module: {module_name}\n"
Expand Down
68 changes: 53 additions & 15 deletions django_logging/settings/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
import os
from typing import Dict, List, Optional

from django_logging.constants import FORMAT_OPTIONS, DefaultLoggingSettings
from django_logging.constants import (
ALLOWED_EXTRA_FILE_TYPES,
ALLOWED_FILE_FORMAT_TYPES,
FORMAT_OPTIONS,
DefaultLoggingSettings,
)
from django_logging.constants.config_types import (
ExtraLogFiles,
FormatOption,
LogDateFormat,
LogDir,
LogFileFormatsType,
LogFileFormats,
LogFileFormatTypes,
LogLevel,
LogLevels,
NotifierLogLevels,
Expand All @@ -30,7 +37,9 @@ def __init__(
self,
log_levels: LogLevels,
log_dir: LogDir,
log_file_formats: LogFileFormatsType,
log_file_formats: LogFileFormats,
log_file_format_types: LogFileFormatTypes,
extra_log_files: ExtraLogFiles,
console_level: LogLevel,
console_format: FormatOption,
colorize_console: bool,
Expand All @@ -53,8 +62,10 @@ def __init__(
self.email_notifier_log_format = self.resolve_format(
log_email_notifier_log_format
)
self.log_file_format_types = log_file_format_types
self.extra_log_files = extra_log_files

def _resolve_file_formats(self, log_file_formats: LogFileFormatsType) -> Dict:
def _resolve_file_formats(self, log_file_formats: LogFileFormats) -> Dict:
resolved_formats = {}
for level in self.log_levels:
format_option = log_file_formats.get(level, None)
Expand Down Expand Up @@ -116,9 +127,22 @@ def __init__(self, log_config: LogConfig) -> None:
def create_log_files(self) -> None:
"""Creates log files based on the log levels in the configuration."""
for log_level in self.log_config.log_levels:
log_file_path = os.path.join(
self.log_config.log_dir, f"{log_level.lower()}.log"
)
fmt_type = self.log_config.log_file_format_types.get(log_level, "").lower()
extra_file = self.log_config.extra_log_files.get(log_level, False)

if extra_file and fmt_type.upper() in ALLOWED_EXTRA_FILE_TYPES:
# Use separate files for extra file format structure
log_file_path = os.path.join(
self.log_config.log_dir,
fmt_type,
f"{log_level.lower()}.{fmt_type}",
)
else:
# Use regular log file for normal, JSON, or XML
log_file_path = os.path.join(
self.log_config.log_dir, f"{log_level.lower()}.log"
)

os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
if not os.path.exists(log_file_path):
with open(log_file_path, "w", encoding="utf-8"):
Expand All @@ -139,28 +163,30 @@ def get_log_file(self, log_level: LogLevel) -> Optional[str]:

def set_conf(self) -> None:
"""Sets the logging configuration using the generated log files."""
formatters = {}
default_settings = DefaultLoggingSettings()
handlers = {
level.lower(): {
"class": "logging.FileHandler",
"filename": log_file,
"formatter": f"{level.lower()}",
"level": level,
"filters": [level.lower()],
"filters": [level.lower(), "context_var_filter"],
}
for level, log_file in self.log_files.items()
}
handlers["console"] = {
"class": "logging.StreamHandler",
"formatter": "console",
"level": self.log_config.console_level,
"filters": ["context_var_filter"],
}
email_handler = {
f"email_{level.lower()}": {
"class": "django_logging.handlers.EmailHandler",
"formatter": "email",
"level": level,
"filters": [level.lower()],
"filters": [level.lower(), "context_var_filter"],
}
for level in self.log_config.email_notifier_log_levels
if level
Expand All @@ -176,13 +202,25 @@ def set_conf(self) -> None:
for level in default_settings.log_levels
}

formatters = {
level.lower(): {
"format": self.log_config.log_file_formats[level],
"datefmt": self.log_config.log_date_format,
}
for level in self.log_config.log_levels
# ContextVarFilter for context variables
filters["context_var_filter"] = {
"()": "django_logging.filters.ContextVarFilter",
}

for level in self.log_config.log_levels:
formatter = {
level.lower(): {
"format": self.log_config.log_file_formats[level],
"datefmt": self.log_config.log_date_format,
}
}
fmt_type = self.log_config.log_file_format_types.get(level, "None").upper()
if fmt_type in ALLOWED_FILE_FORMAT_TYPES:
formatter[level.lower()].update(
{"()": f"django_logging.formatters.{fmt_type}Formatter"}
)
formatters.update(formatter)

formatters["console"] = {
"format": self.log_config.console_format,
"datefmt": self.log_config.log_date_format,
Expand Down
Loading