Ротация логов в Python
Недавно возникла задача сделать ротацию логов в веб-сервисе на Tornado. До начала работы казалось, что вопрос должен быть давно изучен и решение будет очевидным.
Но, как это часто случается, если требования несколько отличаются от стандартных, то приходится искать решение самостоятельно. Или читать документацию.
TL;DR
Если необходимо реализовать ротацию логов таким образом, чтобы файлы создавались ежедневно и имя файла соотвествовало формату %Y%m%d.%i.log
, где %Y
- год, %m
- месяц, %d
- день, %i
- индекс текущего файла лога, то необходимо использовать TimedRotatingFileHandler
и настраивать формат имени файла переопределяя свойства namer
и suffix
этого класса:
def get_filename(filename):
# Получаем директорию, где расположены логи
log_directory = os.path.split(filename)[0]
# Расширением (с точкой) файла является значение suffix (у нас - %Y%m%d) (например .20181231).
# Но точка нам не нужна, т.к. файл будет называться suffix.log (20181231.log)
date = os.path.splitext(filename)[1][1:]
# Сформировали имя нового лог-файла
filename = os.path.join(log_directory, date)
if not os.path.exists('{}.log'.format(filename)):
return '{}.log'.format(filename)
# Найдём минимальный индекс файла на текущий момент.
index = 0
f = '{}.{}.log'.format(filename, index)
while os.path.exists(f):
index += 1
f = '{}.{}.log'.format(filename, index)
return f
rotation_logging_handler = TimedRotatingFileHandler('./logs/log.log', when='m', interval=1, backupCount=5)
rotation_logging_handler.suffix = '%Y%m%d'
rotation_logging_handler.namer = get_filename
logger.addHandler(rotation_logging_handler)
Чуть более подробно
Постановка задачи
Задача была следующая: необходимо каждый день создавать новый файл журнала. При этом файл должен иметь название годмесяцдень.log
и, если приложение перезапускается, то текущий файл не должен удаляться, а должен создаваться новый файл. Например, с именем годмесяцдень.0.log
, где 0
обозначал номер лог-файла за указанную дату: если сервис запущен 26 сентября 2018 года и два раза за этот день рестартовал, то в папке с логами должно быть три файла: 20180926.log
, 20180926.0.log
, 20180926.1.log
.
Задача ротации логов в Python решается довольно просто: есть модуль logging
и различные Handler
ы, которые позволют реализовать ротацию. Например, RotatingFileHandler
и TimedRotatingFileHandler
. Первый создаёт новый файл при достижении некоторого заданного размера, а второй — по прошествии определённого времени. Первый под заданные требования не подходит и остаётся только TimedRotatingFileHandler
.
TimedRotatingFileHandler
Первый подход
Задача казалась максимально простой и всё должно работать “из коробки”:
import logging as logging
from logging.handlers import TimedRotatingFileHandler
from time import sleep
# Комбинация параметров when и interval задаёт период для создания нового файла логов.
# В примере новый файл логов будет создаваться каждую (interval=1) минуту (when='m').
rotation_logging_handler = TimedRotatingFileHandler('./logs/log.log', when='m', interval=1, backupCount=5)
logger = logging.getLogger()
logger.addHandler(rotation_logging_handler)
for i in range(121):
sleep(1)
print('current iteration: {}'.format(i))
logger.error('current iteration: {}'.format(i))
Запускаем, наблюдаем. Видим, что в папке logs
создалось три файла: log.log
и два файла log.log.дата-время
. В простейшем случае этого достаточно, но перфекционизм не даёт мне права оставить это в таком виде. Хочу, чтобы имена файлов были в формате %Y%m%d.%i.log
.
Вторая попытка
Окей, попробовали просто - получили не тот результат, которых хотелось. Будем усложнять пример. Судя по документации, у logger
есть свойство suffix
. Модифицируем приложение, добавив suffix
с требуемым форматом %Y%m%d
:
import logging as logging
from logging.handlers import TimedRotatingFileHandler
from time import sleep
# Комбинация параметров when и interval задаёт период для создания нового файла логов.
# В примере новый файл логов будет создаваться каждую (interval=1) минуту (when='m').
rotation_logging_handler = TimedRotatingFileHandler('./logs/log.log', when='m', interval=1, backupCount=5)
rotation_logging_handler.suffix = '%Y%m%d'
logger = logging.getLogger()
logger.addHandler(rotation_logging_handler)
for i in range(121):
sleep(1)
print('current iteration: {}'.format(i))
logger.error('current iteration: {}'.format(i))
Следим за ~руками~ логами: в папке logs
появился новый файл с именем log.log.дата
. Должно было быть два: по истечению первой и второй минут. Похоже, это не работает.
Здесь я ~психанул и~ решил забраться в исходники logger
-а и посмотреть как формируется имя файла для лога. Нашёл интересное свойство namer
. namer
- это функция, которая может определить как именно должен называться файл лога. На самом деле в документации можно было найти эту же информацию.
Третья попытка
Доработаем пример с учётом новых знаний:
import logging as logging
from logging.handlers import TimedRotatingFileHandler
from time import sleep
def get_filename(filename):
# Получаем директорию, где расположены логи
log_directory = os.path.split(filename)[0]
# suffix - это расширение (с точкой) файла.
# У нас - %Y%m%d. Например .20181231.
# Точка нам не нужна, т.к. файл будет называться suffix.log (20181231.log)
date = os.path.splitext(filename)[1][1:]
# Сформировали имя нового лог-файла
filename = os.path.join(log_directory, date)
if not os.path.exists('{}.log'.format(filename)):
return '{}.log'.format(filename)
# Найдём минимальный индекс файла на текущий момент.
index = 0
f = '{}.{}.log'.format(filename, index)
while os.path.exists(f):
index += 1
f = '{}.{}.log'.format(filename, index)
return f
rotation_logging_handler = TimedRotatingFileHandler(path, when=when_mode[when_mode_config], interval=1, backupCount=5)
rotation_logging_handler.suffix = '%Y%m%d'
rotation_logging_handler.namer = get_filename
logger = logging.getLogger()
logger.addHandler(rotation_logging_handler)
for i in range(121):
sleep(1)
print('current iteration: {}'.format(i))
logger.error('current iteration: {}'.format(i))
Если выполнить последний пример, то можно будет наблюдать именно то поведение, что требовалось в самом начале: сценарий выполняется 2 минуты и каждую минуту создаётся новый файл с логами с именем годмесяцдень.индекс.log
.