Как настроить логирование Django + Celery с помощью RotatingFileHandler?
Есть сервис на Django (DRF), в котором настроена подсистема логирования. Также в сервисе есть Celery-воркер, который выполняет некоторую задачу. Я хочу настроить логирование таким образом, чтобы все логи (и Django, и Celery) сохранялись в .log-файлах, при этом чтобы файлы "прокручивались" с помощью RotatingFileHandler, чтобы не засорять дисковое пространство. Сразу скажу, что я новичок в логировании в Django.
Настройки LOGGING в файле settings.py:
# Настройки логирования.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '[{levelname}]-[{asctime}]-[{module}]-[{process:d}]-[{thread:d}]: {message}',
'style': '{',
},
},
'handlers': {
'applications_logfile': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'default',
'filename': LOGS_DIR / 'applications/applications.log',
'maxBytes': 300,
'backupCount': 5,
},
'celery_worker_logfile': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'default',
'filename': LOGS_DIR / 'celery/worker.log',
'maxBytes': 300,
'backupCount': 5,
},
},
'loggers': {
'celery': {
'level': 'INFO',
'handlers': ['celery_worker_logfile'],
},
'api.application_handler.views': {
'level': 'INFO',
'handlers': ['applications_logfile'],
},
},
}
Настройка логирования в файле celery.py:
# Настройка логирования для Celery. Используем настройки логирования проекта.
@setup_logging.connect
def config_loggers(*args, **kwargs) -> None:
from logging.config import dictConfig
from django.conf import settings
dictConfig(settings.LOGGING)
Также я отключил захват корневого регистратора для Celery:
# Настройки Celery.
CELERY_BROKER_URL = config('CELERY_BROKER_URL')
CELERYD_HIJACK_ROOT_LOGGER = False
Проблема заключается в том, что когда какой-либо прокручиваемый .log-файл достигает максимального размера и наступает момент, когда python пытается сделать прокрутку файлов, это не срабатывает и появляется следующая ошибка:
PermissionError: [WinError 32] Процесс не может получить доступ к файлу, так как этот файл занят другим процессом
. Что в воркере, что в django.
Причина. После долгих изучений данного поведения, я понял, что процесс django держит .log-файл, в который пишет celery-воркер, в celery-воркер в свою очередь держит .log-файл, в который пишет процесс django. "Прокручивание" (ротация) .log-файлов осуществляется через создании пустого .log-файла, в который пишутся логи, и переименовании старого .log-файла. Вот в этом-то и есть проблема. Ни один процесс не может переименовать файл из-за того, что он используется другим процессом.
Что я пробовал сделать. Я пробовал полностью разделить настройки логирования celery и django, убрав все настройки для celery из settings.py и прописав их в celery.py, чтобы два процесса вообще никак не трогали файлы друг друга.
settings.py:
# Настройки логирования.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '[{levelname}]-[{asctime}]-[{module}]-[{process:d}]-[{thread:d}]: {message}',
'style': '{',
},
},
'handlers': {
'applications_logfile': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'default',
'filename': LOGS_DIR / 'applications/applications.log',
'maxBytes': 300,
'backupCount': 5,
},
},
'loggers': {
'api.application_handler.views': {
'level': 'INFO',
'handlers': ['applications_logfile'],
},
},
}
celery.py
# Настройка логирования для Celery.
@setup_logging.connect
def config_loggers(*args, **kwargs) -> None:
logger = logging.getLogger('celery')
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
fmt='[{levelname}]-[{asctime}]-[{module}]-[{process:d}]-[{thread:d}]: {message}',
style='{',
)
handler = handlers.RotatingFileHandler(
filename=settings.LOGS_DIR / 'celery/worker.log',
maxBytes=300,
backupCount=5,
)
handler.setFormatter(formatter)
logger.addHandler(handler)
После этого процесс django больше не держал .log-файл, в который пишет воркер, и тот успешно вращался! Только вот почему-то celery-воркер до сих пор держит .log-файл, в который пишет процесс django, и тот не вращается и падает с ошибкой...
Я видел множество подобных проблем, но не нашел подходящего решения для себя. Информация везде разная, везде разные варианты, а задача довольно тривиальная, на мой взгляд, и наверняка должна иметь хорошее решение.
Заранее спасибо.
UPD.1:
Я подозреваю, что все-таки celery как-то загружает в себя настройки, которые прописаны в LOGGING. Когда я убрал строку os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'company.settings')
из celery.py, процесс воркера больше не держал .log-файл, в который пишет процесс django (разумеется, и задачи автоматически не находились, я это сделал просто ради проверки).
UPD.2:
Запуск сервера django: python manage.py runserver --noreload
.
Запуск celery-воркера: celery -A company worker --loglevel=info -P eventlet
(я использую win10).
Я нашел ответ на свой вопрос!
В общем, все дело в том, что когда мы прописываем строчку os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'company.settings')
в celery.py, процесс celery-воркера получает модуль logging (который является singleton'ом) со всеми настройками django и LOGGING из settings.py. Поэтому процесс django и celery-воркера имеют общие настройки logging (celery только еще своего добавляет). Из-за этого они захватывают файлы логов друга друга.
Так вот собственно решение. Все дело в том, что windows запрещает переименовывать файлы (при ротации логов), если файл занят другим процессом, а линукс - нет :)
Так что все настройки логирования celery можно спокойно прописывать в settings.py, но если запускать это дело под linux'ом.
Чем это может быть опасно
В моем случае у меня для каждого процесса свои файлы логов, что логично и правильно. Однако один процесс может удалить/переименовать файл, который использует в это время другой процесс. Другой процесс об этом не узнает и могут быть большие проблемы. Но если процессы работают только со своими файлами, не трогая другие, то проблем не будет.