Ответ 1
Хорошей новостью является то, что вам не нужно делать ничего лишнего для безопасности потоков, и вам не нужно ничего лишнего или что-то почти тривиальное для чистого выключения. Я расскажу подробности позже.
Плохая новость заключается в том, что ваш код имеет серьезную проблему даже до того, как вы дойдете до этой точки: fileLogger
и consoleLogger
- это один и тот же объект. Из документация для getLogger()
:
Верните регистратор с указанным именем или, если имя не указано, верните регистратор, который является корневым регистратором иерархии.
Итак, вы получаете корневой журнал и сохраняете его как fileLogger
, а затем получаете корневой журнал и сохраняете его как consoleLogger
. Итак, в LoggingInit
вы инициализируете fileLogger
, затем повторно инициализируете один и тот же объект под другим именем с разными значениями.
Вы можете добавить несколько обработчиков в один и тот же журнал - и, поскольку единственная инициализация, которую вы фактически выполняете для каждого, - это addHandler
, ваш код будет сортировать работу по назначению, но только случайно. И только вроде. Вы получите две копии каждого сообщения в обоих журналах, если вы пройдете print_screen=True
, и вы получите копии в консоли, даже если вы пройдете print_screen=False
.
На самом деле нет причин для глобальных переменных; вся точка getLogger()
заключается в том, что вы можете вызывать ее каждый раз, когда вам это нужно, и получать глобальный корневой журнал, поэтому вам не нужно его хранить где-нибудь.
Более незначительная проблема заключается в том, что вы не избегаете текста, который вы вставляете в HTML. В какой-то момент вы попытаетесь записать строку "a < b"
и попадете в проблему.
Менее серьезно, последовательность тегов <p>
, которая не находится внутри <body>
внутри <html>
, не является допустимым HTML-документом. Но множество зрителей позаботятся об этом автоматически, или вы можете отправить свои журналы тривиально перед их отображением. Но если вы действительно хотите, чтобы это было правильно, вам нужно подклассом FileHandler
и добавьте __init__
заголовок, если ему задан пустой файл, и удалите нижний колонтитул, если он есть, а затем добавьте нижний колонтитул close
.
Возвращаясь к вашему актуальному вопросу:
Вам не нужна дополнительная блокировка. Если обработчик правильно реализует createLock
, acquire
и release
(и он вызывается на платформе с потоками), система ведения журнала автоматически будет обязательно получать блокировку, когда это необходимо, чтобы каждое сообщение регистрировалось атомарно.
Насколько я знаю, в документации прямо не сказано, что StreamHandler
и FileHandler
реализуют эти методы, это сильно подразумевает (текст, который вы упомянули в вопросе, гласит: "Модуль протоколирования предназначен для потокобезопасности без какой-либо специальной работы, которую должны выполнять его клиенты" и т.д.). И вы можете посмотреть источник для своей реализации (например, CPython 3.3) и увидеть, что они оба наследуют правильно реализованные методы из logging.Handler
.
Аналогично, если обработчик корректно реализует flush
и close
, механизм регистрации будет корректно завершен при нормальном отключении.
Здесь документация объясняет, что StreamHandler.flush()
, FileHandler.flush()
и FileHandler.close()
. В основном это то, что вы ожидаете, за исключением того, что StreamHandler.close()
- это не-op, что означает, что окончательные сообщения журнала на консоли могут потеряться. Из документов:
Обратите внимание, что метод
close()
наследуется отHandler
и поэтому не выводит, поэтому может потребоваться явный вызовflush()
.
Если это имеет значение для вас, и вы хотите его исправить, вам нужно сделать что-то вроде этого:
class ClosingStreamHandler(logging.StreamHandler):
def close(self):
self.flush()
super().close()
И затем используйте ClosingStreamHandler()
вместо StreamHandler()
.
FileHandler
не имеет такой проблемы.
Обычный способ отправки журналов в два места - просто использовать корневой журнал с двумя обработчиками, каждый со своим собственным форматированием.
Кроме того, даже если вам нужны два регистратора, вам не нужны отдельные карты console_logging_level_switch
и file_logging_level_switch
; вызов Logger.debug(msg)
- это то же самое, что и вызов Logger.log(DEBUG, msg)
. Вам все равно нужно каким-то образом сопоставить имена пользовательских уровней debug
и т.д. С стандартными именами debug
и т.д., Но вы можете просто выполнить один поиск, вместо того, чтобы делать это один раз для каждого регистратора (плюс, если ваш имена - это просто стандартные имена с разными актами, вы можете обманывать).
Все это очень хорошо описано в разделе Несколько обработчиков и форматировщиков и остальной кулинарной книге для ведения журнала.
Единственная проблема со стандартным способом сделать это состоит в том, что вы не можете легко отключить ведение журнала консоли на основе сообщений за сообщением. Это потому, что это не нормальная вещь. Обычно вы просто регистрируетесь по уровням и устанавливаете уровень журнала выше в журнале файлов.
Но, если вы хотите большего контроля, вы можете использовать фильтры. Например, дайте FileHandler
фильтру, который принимает все, и ваш ConsoleHandler
фильтр, который требует чего-то, начинающегося с console
, затем используйте фильтр 'console' if print_screen else ''
. Это уменьшает WriteLog
до почти однострочного.
Вам все еще нужны дополнительные две строки для удаления новых строк, но вы можете даже сделать это в фильтре или через адаптер, если хотите. (Опять же, см. Поваренную книгу.) И тогда WriteLog
действительно является однострочным.