How to configure log duplication in Python logging for a Django project?
In my Django project, I have basic logging configured in views.py:
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__)
#### BEGIN VIEWS
def view_info_app(request,*args, **kwargs):
"""
everything output here is very important!
"""
logger.info("APP: view_info_app")
info_objects = ORM_Model.objects.get(id=123)
logger.info("Very important information - %s",info_objects)
return render("templates/index.html",{})
def view_index_app(request,*args, **kwargs):
"""
this is not important
"""
logger.info("APP: view_index_app")
info_objects = ORM_Model.objects.filter()
logger.info("Unnecessary information - %s",info_objects)
return render("templates/index.html",{})
I now need to save some logs to a database in addition to console output. How can I implement this only for specific views?
Main Problem of Log Duplication in Django
The main problem with log duplication in Django arises from a conflict between basicConfig() and Django’s built-in logging system. To avoid duplication and save logs to the database only for specific views, you should use configuration through LOGGING in settings.py.
Contents
- Main Causes of Log Duplication
- Preventing Duplication Configuration
- Database Logging
- Selective Logging for Specific Views
- Complete Configuration Example
- Optimization Recommendations
Main Causes of Log Duplication
Log duplication in Django projects is usually caused by the following factors:
-
Conflict between
basicConfig()and Django LOGGING
Your currentbasicConfig()setup conflicts with Django’s logging system, which is already configured by default. -
Logger inheritance (propagate=True)
When using multiple loggers withpropagate=True, logs may be duplicated as they are processed by multiple handlers. -
Reconfiguration of the root logger
If there’s arootlogger configuration in Django settings, this can lead to duplication when usingbasicConfig().
Preventing Duplication Configuration
To avoid duplication, completely remove basicConfig() from your views.py and configure logging through settings.py:
# DELETE THESE LINES from views.py:
# logging.basicConfig(...)
# logger = logging.getLogger(__name__)
Instead, use in views.py:
import logging
logger = logging.getLogger(__name__) # Only this line is needed
Database Logging
Option 1: Using django-logdb
Add to requirements.txt:
django-logdb
In settings.py:
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,
},
},
}
Option 2: Custom Handler
Create a file logger/handlers.py:
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'
Selective Logging for Specific Views
Method 1: Different Loggers for Different Views
# 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("Very important information - %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("Unnecessary information - %s", info_objects)
return render(request, "templates/index.html", {})
Method 2: Using Filters
# 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': 'Very important information|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,
},
},
}
Method 3: Decorators for Selective Logging
# 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("Very important information - %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("Unnecessary information - %s", info_objects)
return render(request, "templates/index.html", {})
Complete Configuration Example
# 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,
},
},
}
# 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):
"""
Everything logged here is very important!
"""
logger_important.info("APP: view_info_app")
info_objects = ORM_Model.objects.get(id=123)
logger_important.info("Very important information - %s", info_objects)
logger.info("Regular log for view_info_app")
return render(request, "templates/index.html", {})
def view_index_app(request, *args, **kwargs):
"""
This is not important
"""
logger.info("APP: view_index_app")
info_objects = ORM_Model.objects.filter()
logger.info("Unnecessary information - %s", info_objects)
return render(request, "templates/index.html", {})
Optimization Recommendations
-
Use different logger names for different parts of the application
pythonlogger = logging.getLogger(__name__) # Automatically creates hierarchy -
Set
propagate=Falseto avoid duplicationpython'myapp': { 'handlers': ['database'], 'level': 'INFO', 'propagate': False, # Important! }, -
Use logging levels for filtering
python'handlers': { 'important_only': { 'level': 'INFO', # Only INFO and above 'class': 'django_logdb.handlers.DatabaseHandler', }, }, -
For large projects, consider using specialized packages:
- django-logdb for simple database logging
- django-automated-logging for automatic model and view logging
- django-db-logger for advanced features
-
Test configuration in different environments
python# Example environment LOGGING['loggers']['django']['level'] = os.getenv('DJANGO_LOG_LEVEL', 'INFO')
Sources
- Django Documentation - Logging
- Stack Overflow - Django duplicate log output
- django-logdb Package
- SigNoz - Django Logging Guide
- GitHub - django-db-logger
- Medium - Comprehensive Django Logging Guide
- James Lin - Custom Django Log Handler
Conclusion
- Remove
basicConfig()from your code and use onlyLOGGINGinsettings.py - Configure different loggers with different handlers to avoid duplication
- Use
propagate=Falseto prevent log inheritance - For database logging, use specialized handlers or packages
- For selective logging, apply different logger names, filters, or decorators
These solutions will allow you to effectively manage logging in your Django project, avoid duplication, and save important logs only for specific views to the database.