НейроАгент

Как настроить дублирование логов в Django проекте

Полное руководство по настройке избегания дублирования логов в Django и сохранению важных логов в базу данных только для нужных вьюх.

Вопрос

Как настроить дублирование логов в Python logging для Django проекта?

В моем Django проекте в файле views.py настроено базовое логирование:

python
from django.shortcuts import render
from models import ORM_Model
logging.basicConfig(
    format="%(time)s - %(name)s %(lineno)d - %(levelname)s - %(message)s", level=logging.INFO,
    #datefmt='%H:%M:%S',
    fmt='%(asctime)s.%(msecs)03d',
)
logger = logging.getLogger(__name__)

#### НАЧАЛО ВЬЮХ 
def view_info_app(request,*args, **kwargs):
    """
    а все что тут выводится - очень важно!
    """
    logger.info("APP: view_info_app")
    info_objects = ORM_Model.objects.get(id=123)
    logger.info("Очень нужная информация - %s",info_objects)
    return render("templates/index.html",{})

def view_index_app(request,*args, **kwargs):
    """
    это не важно
    """
    logger.info("APP: view_index_app")
    info_objects = ORM_Model.objects.filter()
    logger.info("Не нужная информация - %s",info_objects)
    return render("templates/index.html",{})

Появилась необходимость некоторую часть логов помимо вывода в консоль сохранить в базу данных. Как это реализовать только для нужных вьюх?

НейроАгент

Основная проблема дублирования логов в Django возникает из-за конфликта между basicConfig() и встроенной системой логирования Django. Для избежания дублирования и сохранения логов в базу данных только для нужных вьюх следует использовать конфигурацию через LOGGING в settings.py.


Содержание


Основные причины дублирования логов

Дублирование логов в Django проекте обычно вызвано следующими факторами:

  1. Конфликт между basicConfig() и Django LOGGING
    Ваша текущая настройка basicConfig() конфликтует с Django-системой логирования, которая уже настроена по умолчанию.

  2. Наследование логгеров (propagate=True)
    При использовании нескольких логгеров с propagate=True логи могут дублироваться, так как они обрабатываются несколькими обработчиками.

  3. Повторная настройка корневого логгера
    Если в Django settings есть настройка root логгера, это может привести к дублированию при использовании basicConfig().


Настройка избежания дублирования

Чтобы избежать дублирования, полностью удалите basicConfig() из вашего views.py и настройте логирование через settings.py:

python
# УДАЛИТЕ ЭТИ СТРОКИ views.py:
# logging.basicConfig(...)
# logger = logging.getLogger(__name__)

Вместо этого используйте в views.py:

python
import logging
logger = logging.getLogger(__name__)  # Только эта строка нужна

Сохранение логов в базу данных

Вариант 1: Использование django-logdb

Добавьте в requirements.txt:

django-logdb

В settings.py:

python
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
            'level': 'INFO',
        },
        'database': {
            'class': 'django_logdb.handlers.DatabaseHandler',
            'formatter': 'verbose',
            'level': 'DEBUG',
        },
    },
    'loggers': {
        'my_app': {
            'handlers': ['console', 'database'],
            'level': 'DEBUG',
            'propagate': False,
        },
        'django': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': True,
        },
    },
}

Вариант 2: Кастомный обработчик

Создайте файл logger/handlers.py:

python
import logging
from django.db import models

class DBHandler(logging.Handler):
    def emit(self, record):
        logger_entry = LogEntry(
            level=record.levelname,
            message=record.getMessage(),
            module=record.module,
            function_name=record.funcName,
            line_number=record.lineno,
            timestamp=record.created
        )
        logger_entry.save()

class LogEntry(models.Model):
    level = models.CharField(max_length=20)
    message = models.TextField()
    module = models.CharField(max_length=100)
    function_name = models.CharField(max_length=100)
    line_number = models.IntegerField()
    timestamp = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        app_label = 'logger'

Выборочное логирование для конкретных вьюх

Метод 1: Разные логгеры для разных вьюх

python
# views.py
import logging

logger_important = logging.getLogger('important_logs')
logger_regular = logging.getLogger('regular_logs')

def view_info_app(request, *args, **kwargs):
    logger_important.info("APP: view_info_app")
    info_objects = ORM_Model.objects.get(id=123)
    logger_important.info("Очень нужная информация - %s", info_objects)
    return render(request, "templates/index.html", {})

def view_index_app(request, *args, **kwargs):
    logger_regular.info("APP: view_index_app")
    info_objects = ORM_Model.objects.filter()
    logger_regular.info("Не нужная информация - %s", info_objects)
    return render(request, "templates/index.html", {})

Метод 2: Использование фильтров

python
# settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'simple': {
            'format': '[%(asctime)s] %(levelname)s|%(name)s|%(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S',
        },
    },
    'filters': {
        'important_only': {
            '()': 'django_filters.logging.MessageFilter',
            'message': 'Очень нужная информация|APP: view_info_app',
        },
    },
    'handlers': {
        'applogfile': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(BASE_DIR, 'django_blend.log'),
            'backupCount': 10,
            'formatter': 'simple',
        },
        'database_important': {
            'level': 'INFO',
            'class': 'logger.handlers.DBHandler',
            'formatter': 'simple',
            'filters': ['important_only'],
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
    },
    'loggers': {
        'important_logs': {
            'handlers': ['database_important'],
            'level': 'INFO',
            'propagate': False,
        },
        'regular_logs': {
            'handlers': ['applogfile', 'console'],
            'level': 'DEBUG',
            'propagate': False,
        },
        'django': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': True,
        },
    },
}

Метод 3: Декораторы для выборочного логирования

python
# decorators.py
import logging
import functools

def log_to_database(view_func):
    @functools.wraps(view_func)
    def wrapper(request, *args, **kwargs):
        db_logger = logging.getLogger('database_logs')
        db_logger.info(f"VIEW: {view_func.__name__} started")
        result = view_func(request, *args, **kwargs)
        db_logger.info(f"VIEW: {view_func.__name__} completed")
        return result
    return wrapper

# views.py
@log_to_database
def view_info_app(request, *args, **kwargs):
    logger = logging.getLogger(__name__)
    logger.info("APP: view_info_app")
    info_objects = ORM_Model.objects.get(id=123)
    logger.info("Очень нужная информация - %s", info_objects)
    return render(request, "templates/index.html", {})

def view_index_app(request, *args, **kwargs):
    logger = logging.getLogger(__name__)
    logger.info("APP: view_index_app")
    info_objects = ORM_Model.objects.filter()
    logger.info("Не нужная информация - %s", info_objects)
    return render(request, "templates/index.html", {})

Полный пример конфигурации

python
# settings.py
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'simple': {
            'format': '[%(asctime)s] %(levelname)s|%(name)s|%(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S',
        },
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
        },
        'file': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(BASE_DIR, 'django.log'),
            'maxBytes': 10485760,  # 10MB
            'backupCount': 5,
            'formatter': 'simple',
        },
        'database': {
            'level': 'INFO',
            'class': 'django_logdb.handlers.DatabaseHandler',
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'myapp.views.important': {
            'handlers': ['database'],
            'level': 'INFO',
            'propagate': False,
        },
        'myapp.views': {
            'handlers': ['file', 'console'],
            'level': 'DEBUG',
            'propagate': False,
        },
        'django': {
            'handlers': ['console'],
            'level': 'WARNING',
            'propagate': True,
        },
    },
}
python
# views.py
import logging
from django.shortcuts import render
from .models import ORM_Model

logger = logging.getLogger(__name__)
logger_important = logging.getLogger('myapp.views.important')

def view_info_app(request, *args, **kwargs):
    """
    а все что тут выводится - очень важно!
    """
    logger_important.info("APP: view_info_app")
    info_objects = ORM_Model.objects.get(id=123)
    logger_important.info("Очень нужная информация - %s", info_objects)
    logger.info("Regular log for view_info_app")
    return render(request, "templates/index.html", {})

def view_index_app(request, *args, **kwargs):
    """
    это не важно
    """
    logger.info("APP: view_index_app")
    info_objects = ORM_Model.objects.filter()
    logger.info("Не нужная информация - %s", info_objects)
    return render(request, "templates/index.html", {})

Рекомендации по оптимизации

  1. Используйте разные имена логгеров для разных частей приложения

    python
    logger = logging.getLogger(__name__)  # Автоматически создает иерархию
    
  2. Настройте propagate=False для избежания дублирования

    python
    'myapp': {
        'handlers': ['database'],
        'level': 'INFO',
        'propagate': False,  # Важно!
    },
    
  3. Используйте уровни логирования для фильтрации

    python
    'handlers': {
        'important_only': {
            'level': 'INFO',  # Только INFO и выше
            'class': 'django_logdb.handlers.DatabaseHandler',
        },
    },
    
  4. Для больших проектов рассмотрите использование специализированных пакетов:

  5. Тестируйте конфигурацию в разных окружениях

    python
    # Пример окружения
    LOGGING['loggers']['django']['level'] = os.getenv('DJANGO_LOG_LEVEL', 'INFO')
    

Источники

  1. Django Documentation - Logging
  2. Stack Overflow - Django duplicate log output
  3. django-logdb Package
  4. SigNoz - Django Logging Guide
  5. GitHub - django-db-logger
  6. Medium - Comprehensive Django Logging Guide
  7. James Lin - Custom Django Log Handler

Заключение

  1. Удалите basicConfig() из вашего кода и используйте только LOGGING в settings.py
  2. Настройте разные логгеры с разными обработчиками для избежания дублирования
  3. Используйте propagate=False для предотвращения наследования логов
  4. Для логирования в базу данных используйте специализированные обработчики или пакеты
  5. Для выборочного логирования применяйте разные имена логгеров, фильтры или декораторы

Эти решения позволят вам эффективно управлять логированием в Django проекте, избегать дублирования и сохранять важные логи только для нужных вьюх в базу данных.