2013-06-05 2 views
15

У меня есть модуль log.py, который используется, по меньшей мере, в двух других модулях (server.py и device.py).Протокол Python из нескольких потоков

Она имеет следующие глобал:

fileLogger = logging.getLogger() 
fileLogger.setLevel(logging.DEBUG) 
consoleLogger = logging.getLogger() 
consoleLogger.setLevel(logging.DEBUG) 

file_logging_level_switch = { 
    'debug': fileLogger.debug, 
    'info':  fileLogger.info, 
    'warning': fileLogger.warning, 
    'error': fileLogger.error, 
    'critical': fileLogger.critical 
} 

console_logging_level_switch = { 
    'debug': consoleLogger.debug, 
    'info':  consoleLogger.info, 
    'warning': consoleLogger.warning, 
    'error': consoleLogger.error, 
    'critical': consoleLogger.critical 
} 

Он имеет две функции:

def LoggingInit(logPath, logFile, html=True): 
    global fileLogger 
    global consoleLogger 

    logFormatStr = "[%(asctime)s %(threadName)s, %(levelname)s] %(message)s" 
    consoleFormatStr = "[%(threadName)s, %(levelname)s] %(message)s" 

    if html: 
     logFormatStr = "<p>" + logFormatStr + "</p>" 

    # File Handler for log file 
    logFormatter = logging.Formatter(logFormatStr) 
    fileHandler = logging.FileHandler( 
     "{0}{1}.html".format(logPath, logFile)) 
    fileHandler.setFormatter(logFormatter) 
    fileLogger.addHandler(fileHandler) 

    # Stream Handler for stdout, stderr 
    consoleFormatter = logging.Formatter(consoleFormatStr) 
    consoleHandler = logging.StreamHandler() 
    consoleHandler.setFormatter(consoleFormatter) 
    consoleLogger.addHandler(consoleHandler) 

И:

def WriteLog(string, print_screen=True, remove_newlines=True, 
     level='debug'): 

    if remove_newlines: 
     string = string.replace('\r', '').replace('\n', ' ') 

    if print_screen: 
     console_logging_level_switch[level](string) 

    file_logging_level_switch[level](string) 

Я называю LoggingInit из server.py, который инициализирует регистратор файлов и консолей , Затем я звоню WriteLog, поэтому доступно несколько потоков fileLogger и consoleLogger.

Нужна ли мне дополнительная защита для моего файла журнала? В документации указано, что обработчики потоков обрабатываются обработчиком.

+0

Если вы не ставите блокировку, когда хотите писать, ваши записи в журнале могут смешиваться и делать нечитаемый журнал. – AliBZ

+0

@AliBZ - какие записи журнала? на уровне ведения журнала? не покрываются ли они службой каротажа python? http://stackoverflow.com/questions/2973900/is-pythons-logging-module-thread-safe –

+0

, когда вы используете «fileLogger.info ('1 2 3 4')» в двух разных потоках, ваш последний журнал может быть их смесь, что-то вроде этого «1 2 1 2 3 3 4 4» – AliBZ

ответ

38

Хорошей новостью является то, что вам не нужно ничего делать для обеспечения безопасности потоков, и вам не нужно ничего лишнего или что-то почти тривиальное для чистого выключения. Я расскажу подробности позже.

Плохая новость заключается в том, что у вашего кода есть серьезная проблема, даже до того, как вы доберетесь до этого момента: fileLogger и consoleLogger - это тот же объект. От the documentation for 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 реализации этих методов, она сильно подразумевает его (the text you mentioned in the question говорит «Модуль регистрации предназначен для поточно-без каких-либо специальных работ которые должны выполняться его клиентами »и т. д.). И вы можете посмотреть источник для своей реализации (например, 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 и т. Д., Но вы можете просто выполнить один поиск вместо того, чтобы делать это один раз для каждого регистратора (плюс, если ваши имена - это просто стандартные имена с разными литыми, вы можете обмануть).

Все это хорошо описано в разделе «Multiple handlers and formatters» и остальной части кулинарной книги по лесозаготовкам.

Единственная проблема со стандартным способом сделать это состоит в том, что вы не можете легко отключить ведение журнала консоли на основе сообщений за сообщением. Это потому, что это не нормально. Обычно вы просто регистрируетесь по уровням и устанавливаете уровень журнала выше в журнале файлов.

Но, если вы хотите большего контроля, вы можете использовать фильтры. Например, дайте FileHandler фильтр, который принимает все, и ваш ConsoleHandler фильтр, который требует что-то, начиная с console, а затем воспользуйтесь фильтром 'console' if print_screen else ''. Это уменьшает WriteLog почти до одного лайнера.

Вам по-прежнему нужны дополнительные две строки для удаления новых строк, но вы можете даже сделать , что в фильтре или через адаптер, если хотите. (Опять же, см. Поваренную книгу.) И затем WriteLog действительно is однострочный.

+0

Спасибо за дополнительные советы. Означает ли это, что все, что мне нужно сделать, это указать имена в 'getLogger()' и будет работать так, как предполагалось? – nckturner

+0

и @abarnert, если я хочу продолжить вход в два разных места (указав имена в 'getLogger()', мне все равно нужны глобальные переменные, правильно? – nckturner

+0

@ Raskol: Ну, это зависит от того, что означает «предназначенный» , но это скорее всего не то, что вы действительно хотите.Идея «getLogger» заключается в том, что вы должны использовать ее для какого-то иерархического структурирования журналов; обратите внимание на то, что документация для «Объектов Logger» говорит о «рекомендуемой конструкции logging.getLogger (__ name __)», чтобы ваша иерархия ведения журнала соответствовала вашей иерархии модулей. Вы можете отклонить это, чтобы иметь два регистратора бок о бок, но это странная вещь – abarnert

2

каротаж Python потокобезопасен:

Так у вас нет проблемы в (библиотека) код Python.

Процедура, которую вы вызываете из нескольких потоков (WriteLog), не записывается в какое-либо общее состояние. Таким образом, у вас нет проблем с кодом.

Итак, вы в порядке.

Смежные вопросы