1. Описание проблемы2. Анализ2.1 Модуль логирования реализует откат лога2.2 Безопасный вывод мультипроцессных журналов в одну и ту же файловую схему3. Решения3.1 Использование пакета ConcurrentRotatingFileHandler3.2 пакет concurrent-log-handler3.3 Блокировка вывода журнала3.4 Переопределение класса FileHandler3.5 За регистрацию событий отвечает отдельный процесс3.6 Схема логирования.SocketHandler4. Ссылки
1. Описание проблемы
проект, использованиеRotatingFileHandler
Разделите журнал в соответствии с размером файла журнала. файл настроекMaxBytes
за1GB
,backupCount
размер 5.
После проверки обнаруживается, что размер лог-файла меньше10MB
, и время записи каждого файла журнала отката также относительно близко.
2. Анализ
Если лог-файл слишком мал, то предполагается, что проблема с кодом или содержимое файла утеряно, время записи лога близко к предположению, что это проблема одновременной записи.
После осмотра проблем с кодом нет, и эта причина исключена. Учитывайте текущее использованиеgunicorn
Большинство программ запуска с несколькими процессами вызваны одновременной записью нескольких процессов в один и тот же файл, что приводит к потере файла журнала.
logging
Модули потокобезопасны, но не процессобезопасны.
Как решить эту проблему? сначала пройти черезPython
изlogging
Конкретный метод реализации модуля обработки отката журнала.
2.1 Модуль логирования реализует откат лога
logging
серединаRotatingFileHandler
класс иTimedRotatingFileHandler
Класс реализует разделение файлов в соответствии с размером файла журнала и временем файла журнала, которые унаследованы отBaseRotatingHandler
своего рода.
BaseRotatingHandler
Запуск и выполнение сегментации файла реализовано в классе, конкретный процесс выглядит следующим образом:
1def emit(self, record):
2 """
3 Emit a record.
4 Output the record to the file, catering for rollover as described
5 in doRollover().
6 """
7 try:
8 if self.shouldRollover(record):
9 self.doRollover()
10 logging.FileHandler.emit(self, record)
11 except Exception:
12 self.handleError(record)
конкретный процесс исполненияshouldRollover(record)
иdoRollover()
функция находится вRotatingFileHandler
класс иTimedRotatingFileHandler
реализованы в классе.
отRotatingFileHandler
класс, например,doRollover()
Поток функций выглядит следующим образом:
1def doRollover(self):
2 if self.stream:
3 self.stream.close()
4 self.stream = None
5 if self.backupCount > 0:
6 for i in range(self.backupCount - 1, 0, -1): # 从backupCount,依次到1
7 sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i))
8 dfn = self.rotation_filename("%s.%d" % (self.baseFilename,
9 i + 1))
10 if os.path.exists(sfn):
11 if os.path.exists(dfn):
12 os.remove(dfn)
13 os.rename(sfn, dfn) # 实现将xx.log.i->xx.log.i+1
14 dfn = self.rotation_filename(self.baseFilename + ".1")
15 # ---------start-----------
16 if os.path.exists(dfn): # 判断如果xx.log.1存在,则删除xx.log.1
17 os.remove(dfn)
18 self.rotate(self.baseFilename, dfn) # 将xx.log->xx.log.1
19 # ----------end------------
20 if not self.delay:
21 self.stream = self._open() # 执行新的xx.log
Анализируя вышеописанный процесс, все шаги таковы:
- Обрабатываемый в данный момент файл журнала называется
self.baseFilename
, Значениеself.baseFilename = os.path.abspath(filename)
- это абсолютный путь к установленному файлу журнала, при условии, чтоbaseFilename
заerror.log
. - При откате файла
error.log.i
переименован вerror.log.i+1
. - судить
error.log.1
Существует ли он, если он существует, удалите его и сохраните текущий файл журнала.error.log
переименован вerror.log.1
. -
self.stream
перенаправить на новыйerror.log
документ.
Когда программа запускает несколько процессов, каждый процесс выполняетсяdoRollover
процесс, если более одного процесса входит в критическую секцию, это вызоветdfn
Многократное удаление и другие запутанные операции.
2.2 Безопасный вывод мультипроцессных журналов в одну и ту же файловую схему
Соответствующий обходной путь:
- Отправьте лог тому же процессу, который отвечает за вывод в файл (используя
Queue
иQueueHandler
отправить все события журнала в один процесс) - Заблокируйте вывод журнала, и каждый процесс сначала получает блокировку при выполнении вывода журнала (используя
Lock
класс для сериализации доступа к файлу процесса) - все процессы регистрируются в
SocketHandler
, а затем используйте отдельный процесс, который реализует сервер сокетов для чтения из сокета и записи в файл (Python
указано в мануале)
3. Решения
3.1 Использование пакета ConcurrentRotatingFileHandler
Этот метод относится к блокировочной схеме.
ConcurrentLogHandler
Журналы можно безопасно записывать в один и тот же файл в многопроцессорной среде, а файлы журналов можно разделять, когда файл журнала достигает определенного размера (Поддержка разделения по размеру файла). ноConcurrentLogHandler
Разделение файлов журнала по времени не поддерживается.
ConcurrentLogHandler
Модуль использует блокировку файлов, поэтому несколько процессов одновременно регистрируются в одном файле, не повреждая события журнала. Этот модуль обеспечиваетRotatingFileHandler
Аналогичная схема ротации файлов.
Этот модуль пытается сохранить записи любой ценой, что означает, что файл журнала будет больше указанного максимального размера (если у вас закончилось место на диске, придерживайтесьRotatingFileHandler
, так как он строго соблюдается максимальным размером файла), если несколько экземпляров скрипта выполняются одновременно и пишут в один и тот же файл журнала, тогда все скрипты должны использоватьConcurrentLogHandler
, не должны смешиваться и соответствовать этому классу.
одновременный доступ с помощьюблокировка файлаЧтобы справиться с этим, блокировка файла должна гарантировать, что сообщения журнала не будут удалены или повреждены. Это означает, что блокировка файла будет установлена и снята для каждого сообщения журнала, записываемого на диск. (В Windows вы также можете столкнуться с временной ситуацией, когда файл журнала необходимо открывать и закрывать для каждого сообщения журнала.) Это может повлиять на производительность. В моих тестах производительность была более чем адекватной, но если вам нужно решение с высокой емкостью или малой задержкой, я бы порекомендовал поискать в другом месте.
ConcurrentRotatingFileLogHandler
class — это стандартный обработчик журнала Python.RotatingFileHandler
прямая замена.
Этот пакет в комплектеportalocker
для обработки блокировки файлов. из-за использованияportalocker
модуль, который в настоящее время поддерживает только“nt”
и“posix”
Платформа.
Установить:
1pip install ConcurrentLogHandler
Этот модуль поддерживает
Python2.6
и более поздние версии. Текущая последняя версия0.9.1
ConcurrentLogHandler
как пользоваться и прочееhandler
класс, соответствующийRotatingFileHandler
используется таким же образом.
Функция и параметры инициализации:
1class ConcurrentRotatingFileHandler(BaseRotatingHandler):
2 """
3 Handler for logging to a set of files, which switches from one file to the
4 next when the current file reaches a certain size. Multiple processes can
5 write to the log file concurrently, but this may mean that the file will
6 exceed the given size.
7 """
8 def __init__(self, filename, mode='a', maxBytes=0, backupCount=0,
9 encoding=None, debug=True, delay=0):
Параметры имеют тот же смыслPython
встроенныйRotatingFileHandler
Класс тот же, пожалуйста, обратитесь к предыдущему сообщению в блоге для деталей. также унаследовано отBaseRotatingHandler
своего рода.
Простой пример:
1import logging
2from cloghandler import ConcurrentRotatingFileHandler
3
4logger = logging.getLogger()
5rotateHandler = ConcurrentRotatingFileHandler('./logs/my_logfile.log', "a", 1024*1024, 5)
6logger.addHandler(rotateHandler)
7logger.setLevel(logging.DEBUG)
8
9logger.info('This is a info message.')
соответствовать нетConcurrentRotatingFileHandler
В случае пакетов добавьте резервное использованиеRotatingFileHandler
код:
1try:
2 from cloghandler import ConcurrentRotatingFileHandler as RFHandler
3except ImportError:
4 from warning import warn
5 warn('ConcurrentRotatingFileHandler package not installed, Using builtin log handler')
6 from logging.handlers import RotatingFileHandler as RFHandler
После запуска можно обнаружить, что.lock
файл, безопасно записывайте файлы журнала, блокируя.
Примечания: Библиотека не обновлялась с 2013 года. Если есть какие-либо проблемы, вы можете использовать ее.
3.2
в разделеconcurrent-log-handler
упаковка.
не используется отдельноpython
При написании сценария обратите внимание на то, как вы его используете:
1# 不建议使用方式
2from cloghandler import ConcurrentRotatingFileHandler
3
4.......
5'handlers':{
6 "error_file": {
7 "class": "ConcurrentRotatingFileHandler",
8 "maxBytes": 100*1024*1024,# 日志的大小
9 "backupCount": 3,
10# 建议写完整
11import cloghandler
12'handlers':{
13 "error_file": {
14 "class": "cloghandler.ConcurrentRotatingFileHandler",
15 "maxBytes": 100*1024*1024,# 日志的大小
16 "backupCount": 3,
В противном случае появится следующая ошибка:
1Error: Unable to configure handler 'access_file': Cannot resolve 'ConcurrentRotatingFileHandler': No module named 'ConcurrentRotatingFileHandler'
3.2 пакет concurrent-log-handler
Этот модуль также предоставляет дополнительные обработчики журналов для стандартного программного обеспечения Python для ведения журналов. То есть события журнала записываются в файл журнала.Когда файл достигает определенного размера, файл журнала будет ротироваться по очереди, и несколько процессов могут безопасно записывать в один и тот же файл журнала, а также его можно сжимать (открывать) .Windows
иPOSIX
системы поддерживаются.
это можно рассматривать как старую версиюcloghandler
прямая замена основногоcloghandler
изменить наconcurrent_log_handler
.
Его характеристики и описаниеcloghandler
последовательный, конкретный3.1
подраздел.
Установить:
1pip install concurrent-log-handler
При установке из исходников выполните следующую команду:
1python setup.py install
Пример использования:
1import logging
2from concurrent_log_handler import ConcurrentRotatingFileHandler
3
4logger = logging.getLogger()
5rotateHandler = ConcurrentRotatingFileHandler('./logs/mylogfile.log', 'a', 512*1024, 5)
6logger.addHandler(rotateHandler)
7logger.setLevel(logging.DEBUG)
8
9logger.info('This is a info message.')
Точно так же, если вы хотите распространять код, не уверены, что все установленоconcurrent_log_handler
упаковать, сделатьPython
Можно легко вернуться к встроенномуRotatingFileHandler
. Вот пример:
1import logging
2try:
3 from concurrent_log_handler import ConcurrentRotatingFileHandler as RFHandler
4except ImportError:
5 # 下面两行可选
6 from warnings import warn
7 warn('concurrent_log_handler package not installed. Using builtin log handler')
8 from logging.handlers import RotatingFileHandler as RFHandler
9
10logger = logging.getLogger()
11rotateHandler = RFHandler('./logs/mylogfile.log', 'a', 1024*1024, 5)
12logger.addHandler(rotateHandler)
Точно так же рекомендуется импортировать напрямую
concurrent_log_handler
,использоватьconcurrent_log_handler.ConcurrentRotatingFileHandler
Способ.
3.3 Блокировка вывода журнала
TimedRotatingFileHandler
своего родаdoRollover
Основная часть функции выглядит следующим образом:
1def doRollover(self):
2 ....
3 dfn = self.rotation_filename(self.baseFilename + "." +
4 time.strftime(self.suffix, timeTuple))
5 # -------begin-------
6 if os.path.exists(dfn): # 判断如果存在dfn,则删除
7 os.remove(dfn)
8 self.rotate(self.baseFilename, dfn) # 将当前日志文件重命名为dfn
9 # --------end--------
10 if self.backupCount > 0:
11 for s in self.getFilesToDelete():
12 os.remove(s)
13 if not self.delay:
14 self.stream = self._open()
15 ....
Идеи модификации:
судитьdfn
Существует ли уже файл, если да, значит, он былrename
пройден; если нет, разрешен только один процессrename
, другим процессам нужно подождать.
Создайте новый класс, который наследуется отTimeRotatingFileHandler
,ИсправлятьdoRollover
функции, просто обработайте закомментированную часть кода выше. следующее:
1class MPTimeRotatingFileHandler(TimeRotatingFileHandler):
2 def doRollover(self):
3 ....
4 dfn = self.rotation_filename(self.baseFilename + "." +
5 time.strftime(self.suffix, timeTuple))
6 # ----modify start----
7 if not os.path.exists(dfn):
8 f = open(self.baseFilename, 'a')
9 fcntl.lockf(f.fileno(), fcntl.LOCK_EX)
10 if os.path.exists(self.baseFilename): # 判断baseFilename是否存在
11 self.rotate(self.baseFilename, dfn)
12 # ----modify end-----
13 if self.backupCount > 0:
14 for s in self.getFilesToDelete():
15 os.remove(s)
16 ....
3.4 Переопределение класса FileHandler
logging.handlers.py
Отношения наследования различных типов показаны на следующем рисунке:
TimeRotatingFileHandler
Класс наследуется от этого класса, вFileHandler
Добавьте некоторую обработку в класс.
Для получения дополнительной информации, пожалуйста, обратитесь к следующим сообщениям в блоге:
модуль ведения журнала python и многопроцессное ведение журнала | блог doudou0o
Многопроцессорность Python решает проблему путаницы в логах — блог qq_20690231 — блог CSDN
существует
Python
В официальном мануале предусмотрен метод логирования в один файл в нескольких процессах.
logging
является потокобезопасным и поддерживает регистрацию нескольких потоков в одном процессе в один файл. Ведение журнала из нескольких процессов в один файл не поддерживается, поскольку вPython
Стандартной схемы сериализации доступа к одному файлу для нескольких процессов не существует.
Существует несколько вариантов записи нескольких внутрипроцессных журналов в один файл:
- все процессы регистрируются в
SocketHandler
, а затем используйте отдельный процесс, который реализует сервер сокетов для чтения из сокета и записи в файл. - использовать
Queue
иQueueHandlerОтправляйте все события журнала в один процесс в многопроцессном приложении.
3.5 За регистрацию событий отвечает отдельный процесс
Один процесс-слушатель отвечает за прослушивание событий журнала других процессов и ведение журнала в соответствии со своей собственной конфигурацией.
Пример:
1import logging
2import logging.handlers
3import multiprocessing
4
5from random import choice, random
6import time
7
8def listener_configurer():
9 root = logging.getLogger()
10 h = logging.handlers.RotatingFileHandler('test.log', 'a', 300,10) # rotate file设置的很小,以便于查看结果
11 f = logging.Formatter('%(asctime)s %(processName)-10s %(name)s %(levelname)-8s %(message)s')
12 h.setFormatter(f)
13 root.addHandler(h)
14
15def listenser_process(queue, configurer):
16 configurer()
17 while True:
18 try:
19 record = queue.get()
20 if record is None:
21 break
22 logger = logging.getLogger(record.name)
23 logger.handle(record)
24 except Exception:
25 import sys, traceback
26 print('Whoops! Problem:', file=sys.stderr)
27 trackback.print_exc(file=sys.stderr)
28
29LEVELS = [logging.DEBUG, logging.INFO, logging.WARNING,
30 logging.ERROR, logging.CRITICAL]
31
32LOGGERS = ['a.b.c', 'd.e.f']
33
34MESSAGES = [
35 'Random message #1',
36 'Random message #2',
37 'Random message #3',
38]
39
40def worker_configurer(queue):
41 h = logging.handlers.QueueHandler(queue)
42 root = logging.getLogger()
43 root.addHandler(h)
44 root.setLevel(logging.DEBUG)
45
46# 该循环仅记录10个事件,这些事件具有随机的介入延迟,然后终止
47def worker_process(queue, configurer):
48 configurer(queue)
49 name = multiprocessing.current_process().name
50 print('Worker started:%s'%name)
51 for i in range(10):
52 time.sleep(random())
53 logger = logging.getLogger(choice(LOGGERS))
54 level = choice(LEVELS)
55 message = choice(MESSAGES)
56 logger.log(level, message)
57# 创建队列,创建并启动监听器,创建十个工作进程并启动它们,等待它们完成,然后将None发送到队列以通知监听器完成
58def main():
59 queue = multiprocessing.Queue(-1)
60 listener = multiprocessing.Process(target=listener_process,
61 args=(queue, listener_configurer))
62 listener.start()
63 workers = []
64 for i in range(10):
65 worker = multiprocessing.Process(target=worker_process,
66 args=(queue, listener_configurer))
67 workers.append(worker)
68 worker.start()
69 for w in workers:
70 w.join()
71 queue.put_nowait(None)
72 listener.join()
73
74if __name__ == '__main__':
75 main()
Используйте отдельный поток в основном процессе для ведения журнала
В следующем фрагменте кода показано, как использовать определенную конфигурацию ведения журнала, напримерfoo
Регистратор использует специальный обработчик, которыйfoo
Все события в подсистеме записываются в файлmplog-foo.log
. Соответствующая конфигурация будет использоваться непосредственно в механизме ведения журнала главного процесса (даже для событий журнала, генерируемых рабочими процессами).
1import logging
2import logging.config
3import logging.handlers
4from multiprocessing import Process, Queue
5import random
6import threading
7import time
8
9def logger_thread(q):
10 while True:
11 record = q.get()
12 if record is None:
13 break
14 logger = logging.getLogger(record.name)
15 logger.handle(record)
16
17def worker_process(q):
18 qh = logging.handlers.QueueHandler(q)
19 root = logging.getLogger()
20 root.setLevel(logging.DEBUG)
21 root.addHandler(qh)
22 levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR,
23 logging.CRITICAL]
24 loggers = ['foo', 'foo.bar', 'foo.bar.baz', 'spam', 'spam.ham', 'spam.ham.eggs']
25
26 for i in range(100):
27 lv1=l = random.choice(levles)
28 logger = logging.getLogger(random.choice(loggers))
29 logger.log(lvl, 'Message no. %d', i)
30
31for __name__ == '__main__':
32 q = Queue()
33 d = {
34 'version': 1,
35 'formatters': {
36 'detailed': {
37 'class': 'logging.Formatter',
38 'format': '%(asctime)s %(name)-15s %(levelname)-8s %(processName)-10s %(message)s'
39 }
40 },
41 'handlers': {
42 'console': {
43 'class': 'logging.StreamHandler',
44 'level': 'INFO',
45 },
46 'file': {
47 'class': 'logging.FileHandler',
48 'filename': 'mplog.log',
49 'mode': 'w',
50 'formatter': 'detailed',
51 },
52 'foofile': {
53 'class': 'logging.FileHandler',
54 'filename': 'mplog-foo.log',
55 'mode': 'w',
56 'formatter': 'detailed',
57 },
58 'errors': {
59 'class': 'logging.FileHandler',
60 'filename': 'mplog-errors.log',
61 'mode': 'w',
62 'level': 'ERROR',
63 'formatter': 'detailed',
64 },
65 },
66 'loggers': {
67 'foo': {
68 'handlers': ['foofile']
69 }
70 },
71 'root': {
72 'level': 'DEBUG',
73 'handlers': ['console', 'file', 'errors']
74 },
75 }
76 workers = []
77 for i in range(5):
78 wp = Process(target=worker_process, name='worker %d'%(i+1), args=(q,))
79 workers.append(wp)
80 wp.start()
81 logging.config.dictConfig(d)
82 lp = threading.Thread(target=logger_thread, args=(q,))
83 lp.start()
84
85 for wp in workers:
86 wp.join()
87 q.put(None)
88 lp.join()
3.6 Схема логирования.SocketHandler
Конкретное использование этого решения описано в следующей записи блога. Конкретную реализацию см. в следующем блоге:
В Python журналирование печатает журналы в многопроцессорной среде — VictoKu — Blog Park