справочник по Python. мм isbn 9785932861578 9 785932 861578
Скачать 4.21 Mb.
|
Примечание Модуль io появился совсем недавно, впервые он был включен в состав стандарт- ной библиотеки в версии Python 3 и затем был перенесен в версию Python 2.6. К моменту написания этих строк модуль был еще недостаточно доработанным и имел очень низкую производительность, что особенно заметно в приложениях, Модуль logging 445 выполняющих массивные операции ввода-вывода текстовой информации. Если вы используете Python 2, использование встроенной функции open() позволит добиться лучших показателей по сравнению с использованием классов ввода- вывода, объявленных в модуле io. Если вы используете Python 3, то у вас нет дру- Python 3, то у вас нет дру- 3, то у вас нет дру- гой альтернативы. В будущих выпусках производительность модуля наверняка будет увеличена, тем не менее многоуровневая архитектура ввода-вывода в со- единении с декодированием Юникода едва ли сравнится по производительности с низкоуровневыми операциями ввода-вывода, реализованными в стандартной библиотеке языка C, которая является основой реализации механизмов ввода- вывода в Python 2. Модуль logging М одуль logging предоставляет гибкую возможность реализовать в прило- жениях журналирование событий, ошибок, предупреждений и отладочной информации. Эта информация может собираться, фильтроваться, записы- ваться в файлы, отправляться в системный журнал и даже передаваться по сети на удаленные машины. В этом разделе приводятся самые основные сведения об использовании этого модуля в наиболее типичных ситуациях. Уровни журналирования Основной задачей модуля logging является получение и обработка сообще- ний. Каждое сообщение состоит из некоторого текста и ассоциированного с ним уровня, определяющего его важность. Уровни имеют как символиче- ские, так и числовые обозначения: Уровень Значение Описание CRITICAL 50 Критические ошибки/сообщения ERROR 40 Ошибки WARNING 30 Предупреждения INFO 20 Информационные сообщения DEBUG 10 Отладочная информация NOTSET 0 Уровень не установлен Эти уровни являются основой для различных функций и методов в модуле logging . Например, существуют методы, которые различают уровни важно- сти сообщений и выполняют фильтрацию, блокируя запись сообщений, уро- вень важности которых не соответствует заданному пороговому значению. Базовая настройка Перед использованием функций из модуля logging необходимо выполнить базовую настройку специального объекта, известного как корневой регист- ратор . Корневой регистратор содержит настройки по умолчанию, включая 446 Глава 19. Службы операционной системы уровень журналирования, поток вывода, формат сообщений и другие па- раметры. Для выполнения настройки используется следующая функция: basicConfig([**kwargs]) Выполняет базовую настройку корневого регистратора. Эта функция должна вызываться перед вызовом других функций из модуля logging. Она принимает множество именованных аргументов: Именованный аргумент Описание filename Журналируемые сообщения будут добавляться в файл с указан- ным именем. filemode Определяет режим открытия файла. По умолчанию использует- ся режим ‘a’ (добавление в конец). format Строка формата для формирования сообщений. datefmt Строка формата для вывода даты и времени. level Устанавливает уровень важности корневого регистратора. Обра- батываться будут сообщения с уровнем важности, равным или выше указанного. Сообщения с более низким уровнем просто будут игнорироваться. stream Определяет объект открытого файла, куда будут записываться журналируемые сообщения. По умолчанию используется поток std.stderr . Этот аргумент не может использоваться одновременно с аргументом filename. Назначение большинства этих аргументов понятно по их названиям. Аргу- мент format используется для определения формата журналируемых сооб- щений с дополнительной контекстной информацией, такой как имена фай- лов, уровни важности, номера строк и так далее. Аргумент datefmt опреде- ляет формат вывода дат, совместимый с функцией time.strftime(). Если не определен, даты форматируются в соответствии со стандартом ISO8601. В аргументе format допускается использовать следующие символы подста- новки: Формат Описание %(name)s Имя регистратора. %(levelno)s Числовой уровень важности. %(levelname)s Символическое имя уровня важности. %(pathname)s Путь к исходному файлу, откуда была выполнена запись в журнал. %(filename)s Имя исходного файла, откуда была выполнена запись в журнал. %(funcName)s Имя функции, выполнившей запись в журнал. %(module)s Имя модуля, откуда была выполнена запись в журнал. Модуль logging 447 Формат Описание %(lineno)d Н омер строки, откуда была выполнена запись в журнал. %(created)f Время, когда была выполнена запись в журнал. Значением должно быть число, такое как возвращаемое функцией time. time() %(asctime)s Время в формате ASCII, когда была выполнена запись в журнал. %(msecs)s Миллисекунда, когда была выполнена запись в журнал. %(thread)d Числовой идентификатор потока выполнения. %(threadName)s Имя потока выполнения. %(process)d Числовой идентификатор процесса. %(message)s Текст журналируемого сообщения (определяется пользова- телем). Ниже приводится пример, иллюстрирующий настройки для случая, когда в файл журнала записываются сообщения с уровнем INFO или выше: import logging logging.basicConfig( filename = “app.log”, format = “%(levelname)-10s %(asctime)s %(message)s” level = logging.INFO ) При таких настройках вывод сообщения ‘Hello World’ с уровнем важности CRITICAL будет выглядеть в файле журнала ‘app.log’, как показано ниже. CRITICAL 2005-10-25 20:46:57,126 Hello World Объекты класса Logger Чтобы выводить сообщения в журнал, необходимо получить объект класса Logger . В этом разделе описывается процесс создания, настройки и исполь- зования этих объектов. Создание экземпляра класса Logger Создать новый объект класса Logger можно с помощью следующей функ- ции: getLogger([logname]) Возвращает экземпляр класса Logger с именем logname. Если объект с та- ким именем не существует, то создается и возвращается новый экземпляр класса Logger. В аргументе logname передается строка, определяющая имя или последовательность имен, разделенных точками (например, ‘app’ или ‘app.net’ ). При вызове без аргумента logname вернет объект Logger корневого регистратора. 448 Глава 19. Службы операционной системы Э кземпляры класса Logger создаются иначе, чем экземпляры большинства классов в других библиотечных модулях. При создании объекта Logger с помощью функции getLogger() ей всегда необходимо передавать аргумент logname . За кулисами функция getLogger() хранит кэш экземпляров класса Logger вместе с их именами. Если в какой-либо части программы будет за- прошен регистратор с тем же именем, она возвратит экземпляр, созданный ранее. Это существенно упрощает обработку журналируемых сообщений в крупных приложениях, потому что не приходится заботиться о способах передачи экземпляров класса Logger из одного модуля программы в другой. Вместо этого в каждом модуле, где возникает необходимость журналиро- вания сообщений, достаточно просто вызвать функцию getLogger(), чтобы получить ссылку на соответствующий объект Logger. Выбор имен По причинам, которые станут очевидными чуть ниже, при использовании функции getLogger() желательно всегда выбирать говорящие имена. На- пример, если приложение называется ‘app’, тогда как минимум следует ис- пользовать getLogger(‘app’) в начале каждого модуля, составляющего при- ложение. Например: import logging log = logging.getLogger(‘app’) Можно также добавить имя модуля, например getLogger(‘app.net’) или getLogger(‘app.user’) , чтобы более четко указать источник сообщений. Реа- лизовать это можно с помощью инструкций, как показано ниже: import logging log = logging.getLogger(‘app.’+__name__) Добавление имен модулей упрощает выборочное отключение или перена- стройку механизма журналирования для каждого модуля в отдельности, как будет описано ниже. Запись сообщений в журнал Если допустить, что переменная log является экземпляром класса Logger (созданного вызовом функции getLogger(), описанной в предыдущем разде- ле), то для записи сообщений с разными уровнями важности можно будет использовать следующие методы: Уровень важности Описание CRITICAL log.critical( fmt [, *args [, exc_info [, extra]]]) ERROR log.error( fmt [, *args [, exc_info [, extra]]]) WARNING log.warning( fmt [, *args [, exc_info [, extra]]]) INFO log.info( fmt [, *args [, exc_info [, extra]]]) DEBUG log.debug( fmt [, *args [, exc_info [, extra]]]) Модуль logging 449 В аргументе fmt передается строка формата, определяющая формат вывода сообщения в журнал. Все остальные аргументы в args будут служить пара- метрами спецификаторов формата в строке fmt. Для формирования оконча- тельного сообщения из этих аргументов используется оператор форматиро- вания строк %. Если передается несколько аргументов, они будут переданы оператору форматирования в виде кортежа. Если передается единствен- ный аргумент, он будет помещен сразу вслед за оператором %. То есть, если в качестве единственного аргумента передается словарь, имеется возмож- ность использовать имена ключей этого словаря в строке формата. Ниже приводится несколько примеров, иллюстрирующих эту возможность: log = logging.getLogger(“app”) # Записать сообщение, используя позиционные аргументы форматирования log.critical(“Can’t connect to %s at port %d”, host, port) ёё # Записать сообщение, используя словарь значений parms = { ‘host’ : ‘www.python.org’, ‘port’ : 80 } log.critical(“Can’t connect to %(host)s at port %(port)d”, parms) Если в именованном аргументе exc_info передается значение True, в сообще- ние добавляется информация об исключении, полученная вызовом sys. exc_info() . Если в этом аргументе передается кортеж с информацией об ис- ключении, какой возвращает sys.exc_info(), то будет использоваться эта информация. В именованном аргументе extra передается словарь с допол- нительными значениями для использования в строке формата (описывает- ся ниже). Оба аргумента, exc_info и extra, должны передаваться как имено- ванные аргументы. Выполняя вывод журналируемых сообщений, не следует использовать возможности форматирования строк при вызове функции (то есть следует избегать приемов, когда сообщение сначала форматируется, а затем пере- дается модулю logging). Например: log.critical(“Can’t connect to %s at port %d” % (host, port)) В этом примере оператор форматирования строки всегда будет выполнять- ся перед вызовом самой функции log.critical(), потому что аргументы должны передаваться функции или методу уже полностью вычисленны- ми. Однако в более раннем примере значения для спецификаторов формата просто передаются модулю logging и используются, только когда сообще- ние действительно будет выводиться. Это очень тонкое отличие, но так как в большинстве приложений задействуется механизм фильтрации сообще- ний или сообщения выводятся только в процессе отладки, первый подход обеспечивает более высокую производительность, когда журналирование отключено. В дополнение к методам, показанным выше, существует еще несколько методов экземпляра log класса Logger, позволяющих выводить сообщения в журнал. 450 Глава 19. Службы операционной системы log.exception(fmt [, *args ]) Выводит сообщение с уровнем ERROR и добавляет информацию о текущем обрабатываемом исключении. Может использоваться только внутри бло- ков except. log.log(level, fmt [, *args [, exc_info [, extra]]]) Выводит сообщение с уровнем level. Может использоваться в случаях, ког- да уровень важности определяется значением переменной или когда требу- ется использовать дополнительные уровни важности, не входящие в число пяти базовых уровней. log.findCaller() Возвращает кортеж (filename, lineno, funcname) с именем файла источни- ка сообщения, номером строки и именем функции. Эта информация может пригодиться, например, когда желательно знать, в какой точке программы было выведено сообщение. Фильтрование журналируемых сообщений Каждый объект log класса Logger имеет свой уровень и обладает внутрен- ним механизмом фильтрации, с помощью которого определяет, какие со- общения следует обрабатывать. Следующие два метода используются для выполнения простой фильтрации на основе числового значения уровня важности сообщений: log.setLevel(level) Устанавливает уровень важности в объекте log, в соответствии со значе- нием аргумента level. Обрабатываться будут только сообщения с уровнем важности равным или выше значения level. Все остальные сообщения по- просту игнорируются. По умолчанию аргумент level получает значение logging.NOTSET , при котором обрабатываются все сообщения. log.isEnabledFor(level) Возвращает True, если сообщение с уровнем level должно обрабатываться. Журналируемые сообщения могут также фильтроваться по информации, ассоциированной с самим сообщением, например, по имени файла, по но- меру строки и другим сведениям. Для этих целей используются следую- щие методы: log.addFilter(filt) Добавляет в регистратор объект filt фильтра. log.removeFilter(filt) Удаляет объект filt фильтра из регистратора. Оба метода принимают в аргументе filt экземпляр класса Filter. Filter(logname) Создает фильтр, который пропускает только сообщения из регистратора logname или его потомков. Например, если в аргументе logname передать имя ‘app’ , то фильтр будет пропускать сообщения из регистраторов с именами, Модуль logging 451 такими как ‘app’, ‘app.net’ или ‘app.user’, а сообщения из регистратора, на- пример с именем ‘spam’, пропускаться не будут. Собственные фильтры можно создавать, определяя подклассы класса Fil- ter с собственной реализацией метода filter(record), который принимает запись record с информацией о сообщении и возвращает True или False, в за- висимости от того, должно ли обрабатываться сообщение. Объект record, который передается этому методу, обычно обладает следующими атрибу- тами: Атрибут Описание record.name Имя, присвоенное объекту регистратора record.levelname Символическое имя уровня важности record.levelno Числовое значение уровня важности record.pathname Путь к модулю record.filename Имя файла модуля record.module Имя модуля record.exc_info Информация об исключении record.lineno Номер строки, где была выполнена попытка вывести сообщение record.funcName Имя функции, где была выполнена попытка вывести сообщение record.created Время вывода сообщения record.thread Числовой идентификатор потока управления record.threadName Имя потока управления record.process Числовой идентификатор текущего процесса (PID) Следующий пример демонстрирует, как создавать собственные фильтры: class FilterFunc(logging.Filter): def __init__(self,name): self.funcName = name def filter(self, record): if record.funcName == self.funcName: return False else: return True ёё log.addFilter(FilterFunc(‘foo’)) # Игнорировать все сообщения из функции foo() log.addFilter(FilterFunc(‘bar’)) # Игнорировать все сообщения из функции bar() Распространение сообщений и иерархии регистраторов В приложениях со сложной организацией процесса журналирования объ- екты класса Logger могут объединяться в иерархии. Для этого достаточно присвоить объекту класса Logger соответствующее имя, такое как ‘app.net. client’ . В данном случае фактически существует три разных объекта Log- ger с именами ‘app’, ‘app.net’ и ‘app.net.client’. Если какому-либо из реги- 452 Глава 19. Службы операционной системы страторов будет передано сообщение и оно благополучно будет пропущено фильтром этого регистратора, это сообщение продолжит распространение вверх по дереву иерархии и будет обработано всеми родительскими реги- страторами. Например, сообщение, благополучно принятое регистратором ‘app.net.client’ , будет также передано регистраторам ‘app.net’, ‘app’ и кор- невому регистратору. Распространением сообщений управляют следующие атрибуты и методы объекта log класса Logger. log.propagate Логический флаг, который определяет, должно ли сообщение передаваться родительскому регистратору. По умолчанию имеет значение True. log.getEffectiveLevel() Возвращает действующий уровень регистратора. Если уровень был уста- действующий уровень регистратора. Если уровень был уста- действующий уровень регистратора. Если уровень был уста- уровень регистратора. Если уровень был уста- уровень регистратора. Если уровень был уста- регистратора. Если уровень был уста- регистратора. Если уровень был уста- . Если уровень был уста- Если уровень был уста- новлен вызовом метода setLevel(), вернет этот уровень. Если уровень не был указан явно в вызове метода setLevel() (в этом случае по умолчанию уро- вень принимает значение logging.NOTSET), этот метод вернет действующий уровень родительского регистратора. Если ни в одном из родительских ре- гистраторов не был явно установлен уровень, возвращается действующий уровень корневого регистратора. Основное назначение иерархий регистраторов состоит в том, чтобы упро- стить фильтрование сообщений, поступающих из различных частей круп- ного приложения. Например, если потребуется предотвратить вывод со- общений, поступающих из регистратора ‘app.net.client’, достаточно будет просто добавить следующий программный код, выполняющий необходи- мые настройки: import logging logging.getLogger(‘app.net.client’).propagate = False Или сохранить возможность вывода только критических сообщений: import logging logging.getLogger(‘app.net.client’).setLevel(logging.CRITICAL) Отличительной особенностью иерархий регистраторов является то обстоя- тельство, что решение о выводе сообщения принимается самим объектом Logger , которому это сообщение было передано, исходя из его собственного уровня важности и фильтров, а не какими-либо родительскими регистра- торами. То есть, если сообщение было пропущено первым встретившимся набором фильтров, оно продолжит свое распространение и будет обрабо- тано всеми родительскими регистраторами, независимо от их фильтров и уровней важности, даже если при других обстоятельствах эти фильтры могли бы отвергнуть сообщение. На первый взгляд такой механизм работы кажется противоестественным и может даже восприниматься как ошибка. Однако установка уровня в дочернем регистраторе в более низкое значение, чем у его родителя, является одним из способов преодолеть ограничения, накладываемые родителем, и обеспечить возможность вывода сообщений с более низким уровнем важности. Например: |