Skip to content

Commit

Permalink
Merge pull request #16 from ARYAN-NIKNEZHAD/develop
Browse files Browse the repository at this point in the history
Develop To Main
  • Loading branch information
ARYAN-NIKNEZHAD authored Aug 18, 2024
2 parents 14d2206 + 550e0c8 commit f4eeba0
Show file tree
Hide file tree
Showing 18 changed files with 924 additions and 7 deletions.
25 changes: 24 additions & 1 deletion django_logging/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@ def ready(self):
"LOG_FILE_LEVELS", ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
)
log_dir = log_settings.get("LOG_DIR", os.path.join(os.getcwd(), "logs"))
log_file_formats = log_settings.get("LOG_FILE_FORMATS", {})
console_level = log_settings.get("LOG_CONSOLE_LEVEL", "DEBUG")
console_format = log_settings.get("LOG_CONSOLE_FORMAT")
colorize_console = log_settings.get("LOG_CONSOLE_COLORIZE", True)
log_date_format = log_settings.get("LOG_DATE_FORMAT", "%Y-%m-%d %H:%M:%S")
log_email_notifier = log_settings.get("LOG_EMAIL_NOTIFIER", {})
log_email_notifier_enable = log_email_notifier.get("ENABLE", False)
log_email_notifier_log_levels = [
"ERROR" if log_email_notifier.get("NOTIFY_ERROR", False) else None,
"CRITICAL" if log_email_notifier.get("NOTIFY_CRITICAL", False) else None,
]
log_email_notifier_log_format = log_email_notifier.get("LOG_FORMAT")

# Set the logging configuration
set_logging(log_levels, log_dir)
set_logging(
log_levels,
log_dir,
log_file_formats,
console_level,
console_format,
colorize_console,
log_date_format,
log_email_notifier_enable,
log_email_notifier_log_levels,
log_email_notifier_log_format
)
1 change: 1 addition & 0 deletions django_logging/constants/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .format_options import FORMAT_OPTIONS
38 changes: 38 additions & 0 deletions django_logging/constants/ansi_colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class AnsiColors:
BLACK = "\033[0;30m"
RED = "\033[0;31m"
RED_BACKGROUND = "\033[1;41m"
GREEN = "\033[0;32m"
YELLOW = "\033[0;33m"
BLUE = "\033[0;34m"
MAGENTA = "\033[0;35m"
CYAN = "\033[0;36m"
GRAY = "\033[0;37m"
WHITE = "\033[0;38m"
RESET = "\033[0m"
BRIGHT_BLACK = "\033[0;90m"
BRIGHT_RED = "\033[0;91m"
BRIGHT_GREEN = "\033[0;92m"
BRIGHT_YELLOW = "\033[0;93m"
BRIGHT_BLUE = "\033[0;94m"
BRIGHT_MAGENTA = "\033[0;95m"
BRIGHT_CYAN = "\033[0;96m"
BRIGHT_WHITE = "\033[0;97m"
BLACK_BACKGROUND = "\033[40m"
RED_BACKGROUND = "\033[41m"
GREEN_BACKGROUND = "\033[42m"
YELLOW_BACKGROUND = "\033[43m"
BLUE_BACKGROUND = "\033[44m"
MAGENTA_BACKGROUND = "\033[45m"
CYAN_BACKGROUND = "\033[46m"
WHITE_BACKGROUND = "\033[47m"


# Mapping log levels to ANSI colors
LOG_LEVEL_COLORS = {
"DEBUG": AnsiColors.BLUE,
"INFO": AnsiColors.GREEN,
"WARNING": AnsiColors.YELLOW,
"ERROR": AnsiColors.RED,
"CRITICAL": AnsiColors.RED_BACKGROUND,
}
15 changes: 15 additions & 0 deletions django_logging/constants/format_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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 change: 1 addition & 0 deletions django_logging/formatters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .colorized_formatter import ColorizedFormatter
20 changes: 20 additions & 0 deletions django_logging/formatters/colorized_formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logging
from django_logging.settings.conf import LogConfig
from django_logging.utils.colorizer import colorize_log_format


class ColorizedFormatter(logging.Formatter):
def format(self, record):
original_format = self._style._fmt

# checks that the format does not have any color it's self
if LogConfig.remove_ansi_escape_sequences(original_format) == original_format:
colorized_format = colorize_log_format(original_format, record.levelname)
self._style._fmt = colorized_format

formatted_output = super().format(record)

# Reset to the original format string
self._style._fmt = original_format

return formatted_output
1 change: 1 addition & 0 deletions django_logging/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .email_handler import EmailHandler
60 changes: 60 additions & 0 deletions django_logging/handlers/email_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import logging

from django.conf import settings
from django.template import engines
from django.utils.timezone import now
from django_logging.utils.email.notifier import send_email_async
from django_logging.middleware import RequestLogMiddleware


class EmailHandler(logging.Handler):
def __init__(self, include_html=False, *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 = logging_settings.get("LOG_EMAIL_NOTIFIER", {}).get("USE_TEMPLATE", include_html)

except AttributeError:
logging.warning(
f"DJANGO_LOGGING settings not found. Using default include_html value: {self.include_html}.")
except Exception as e:
logging.error(f"An unexpected error occurred while initializing EmailHandler: {e}")

def emit(self, record):
try:
request = getattr(record, "request", None)
log_entry = self.format(record)

if self.include_html:
email_body = self.render_template(log_entry, request)
else:
email_body = log_entry

subject = f"New Log Record: {record.levelname}"
send_email_async(subject, email_body, [settings.ADMIN_EMAIL])

except Exception as e:
self.handleError(record)

@staticmethod
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"

context = {
"message": log_entry,
"time": now(),
"browser_type": user_agent,
"ip_address": ip_address,
}

return template.render(context)
Empty file.
Empty file.
107 changes: 107 additions & 0 deletions django_logging/management/commands/send_logs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import os
import shutil
import tempfile
import logging
from django.core.mail import EmailMessage
from django.core.management.base import BaseCommand
from django.conf import settings

logger = logging.getLogger(__name__)


class Command(BaseCommand):
"""
A Django management command that zips the log directory and sends it to
the specified email address.
This command is used to send the log files to a specified email address.
It zips the log directory, creates an email with the zipped file as an attachment,
and sends it to the specified email address.
"""

help = "Send log folder to the specified email address"

def add_arguments(self, parser):
"""
Add custom command arguments.
Parameters:
parser (ArgumentParser): The argument parser to add arguments to.
"""
parser.add_argument(
"email", type=str, help="The email address to send the logs to"
)

def handle(self, *args, **kwargs):
"""
The main entry point for the command.
Parameters:
args (tuple): Positional arguments.
kwargs (dict): Keyword arguments.
"""
email = kwargs["email"]

log_dir = settings.DJANGO_LOGGING.get("LOG_DIR", os.path.join(os.getcwd(), "logs"))

if not os.path.exists(log_dir):
self.stdout.write(
self.style.ERROR(f'Log directory "{log_dir}" does not exist.')
)
logger.error(f'Log directory "{log_dir}" does not exist.')
return

self.check_email_settings()

# Create a temporary file to store the zipped logs
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
zip_path = f"{tmp_file.name}.zip"
tmp_file.close()

# Zip the log directory
shutil.make_archive(tmp_file.name, "zip", log_dir)

# Send the email with the zipped logs
email_subject = "Log Files"
email_body = "Please find the attached log files."
email_message = EmailMessage(
subject=email_subject,
body=email_body,
from_email=settings.DEFAULT_FROM_EMAIL,
to=[email],
)
email_message.attach_file(zip_path)

try:
email_message.send()
self.stdout.write(self.style.SUCCESS(f"Logs sent successfully to {email}."))
logger.info(f"Logs sent successfully to {email}.")
except Exception as e:
self.stdout.write(self.style.ERROR(f"Failed to send logs: {e}"))
logger.error(f"Failed to send logs: {e}")
finally:
# Clean up the temporary file
os.remove(zip_path)
logger.info("Temporary zip file cleaned up successfully.")

def check_email_settings(self):
"""
Check if all required email settings are present in the settings file.
Raises an exception if any of the required email settings are missing.
"""
required_settings = [
"EMAIL_HOST",
"EMAIL_PORT",
"EMAIL_HOST_USER",
"EMAIL_HOST_PASSWORD",
"EMAIL_USE_TLS",
"DEFAULT_FROM_EMAIL",
]

for setting in required_settings:
if not getattr(settings, setting, None):
error_message = f"Missing required email setting: {setting}"
self.stdout.write(self.style.ERROR(error_message))
logger.error(f"Missing required email setting: {setting}")
raise Exception(error_message)
Loading

0 comments on commit f4eeba0

Please sign in to comment.