Журнал HOWTO

Автор:

Vinay Sajip <vinay_sajip at red-dove dot com>

This page contains tutorial information. For links to reference information and a logging cookbook, please see Інші ресурси.

Підручник з базового журналювання

Ведення журналу — це засіб відстеження подій, які відбуваються під час роботи певного програмного забезпечення. Розробник програмного забезпечення додає до свого коду виклики реєстрації, щоб вказати, що відбулися певні події. Подія описується описовим повідомленням, яке може додатково містити змінні дані (тобто дані, які потенційно відрізняються для кожного випадку події). Події також мають важливість, яку розробник приписує події; важливість також можна назвати рівнем або серйозністю.

Коли використовувати журналювання

You can access logging functionality by creating a logger via logger = getLogger(__name__), and then calling the logger’s debug(), info(), warning(), error() and critical() methods. To determine when to use logging, and to see which logger methods to use when, see the table below. It states, for each of a set of common tasks, the best tool to use for that task.

Завдання, яке ви хочете виконати

Найкращий інструмент для завдання

Відображення виводу консолі для звичайного використання сценарію або програми командного рядка

print()

Повідомлення про події, які відбуваються під час нормальної роботи програми (наприклад, для моніторингу стану або дослідження помилок)

A logger’s info() (or debug() method for very detailed output for diagnostic purposes)

Видавати попередження щодо певної події виконання

warnings.warn() у коді бібліотеки, якщо проблеми можна уникнути і клієнтську програму слід змінити, щоб усунути попередження

A logger’s warning() method if there is nothing the client application can do about the situation, but the event should still be noted

Повідомити про помилку щодо певної події виконання

Викликати виняток

Повідомити про придушення помилки без виклику винятку (наприклад, обробник помилок у тривалому серверному процесі)

A logger’s error(), exception() or critical() method as appropriate for the specific error and application domain

The logger methods are named after the level or severity of the events they are used to track. The standard levels and their applicability are described below (in increasing order of severity):

Рівень

Коли він використовується

НАЛАШТУВАННЯ

Детальна інформація, яка зазвичай цікава лише під час діагностики проблем.

ІНФО

Підтвердження того, що все працює належним чином.

ПОПЕРЕДЖЕННЯ

Ознака того, що трапилося щось несподіване, або вказує на певну проблему в найближчому майбутньому (наприклад, «замало місця на диску»). Програмне забезпечення все ще працює належним чином.

ПОМИЛКА

Через більш серйозну проблему програмне забезпечення не може виконувати деякі функції.

КРИТИЧНО

Серйозна помилка, яка вказує на те, що сама програма може не працювати далі.

The default level is WARNING, which means that only events of this level and above will be tracked, unless the logging package is configured to do otherwise.

Події, які відстежуються, можна обробляти різними способами. Найпростіший спосіб обробки відстежуваних подій — роздрукувати їх на консолі. Ще один поширений спосіб - записати їх у файл на диску.

Простий приклад

Дуже простий приклад:

import logging
logging.warning('Watch out!')  # will print a message to the console
logging.info('I told you so')  # will not print anything

Якщо ви введете ці рядки в сценарій і запустите його, ви побачите:

WARNING:root:Watch out!

printed out on the console. The INFO message doesn’t appear because the default level is WARNING. The printed message includes the indication of the level and the description of the event provided in the logging call, i.e. „Watch out!“. The actual output can be formatted quite flexibly if you need that; formatting options will also be explained later.

Notice that in this example, we use functions directly on the logging module, like logging.debug, rather than creating a logger and calling functions on it. These functions operation on the root logger, but can be useful as they will call basicConfig() for you if it has not been called yet, like in this example. In larger programs you’ll usually want to control the logging configuration explicitly however - so for that reason as well as others, it’s better to create loggers and call their methods.

Запис у файл

A very common situation is that of recording logging events in a file, so let’s look at that next. Be sure to try the following in a newly started Python interpreter, and don’t just continue from the session described above:

import logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logger.debug('This message should go to the log file')
logger.info('So should this')
logger.warning('And this, too')
logger.error('And non-ASCII stuff, too, like Øresund and Malmö')

Змінено в версії 3.9: Додано аргумент кодування. У попередніх версіях Python або, якщо не вказано, використовувалося кодування за замовчуванням, яке використовується open(). Хоча це не показано в наведеному вище прикладі, тепер також можна передати аргумент errors, який визначає, як обробляються помилки кодування. Доступні значення та типові значення дивіться в документації для open().

А тепер, якщо ми відкриємо файл і подивимося, що у нас є, ми повинні знайти повідомлення журналу:

DEBUG:__main__:This message should go to the log file
INFO:__main__:So should this
WARNING:__main__:And this, too
ERROR:__main__:And non-ASCII stuff, too, like Øresund and Malmö

У цьому прикладі також показано, як можна встановити рівень журналювання, який діє як порогове значення для відстеження. У цьому випадку, оскільки ми встановили порогове значення DEBUG, усі повідомлення було надруковано.

Якщо ви хочете встановити рівень журналювання за допомогою параметра командного рядка, наприклад:

--log=INFO

і у вас є значення параметра, переданого для --log у деякій змінній loglevel, ви можете використовувати:

getattr(logging, loglevel.upper())

щоб отримати значення, яке ви передасте basicConfig() через аргумент level. Можливо, ви захочете перевірити помилки будь-якого введеного користувачем значення, можливо, як у наступному прикладі:

# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
    raise ValueError('Invalid log level: %s' % loglevel)
logging.basicConfig(level=numeric_level, ...)

The call to basicConfig() should come before any calls to a logger’s methods such as debug(), info(), etc. Otherwise, that logging event may not be handled in the desired manner.

Якщо ви запустите наведений вище сценарій кілька разів, повідомлення від послідовних запусків буде додано до файлу example.log. Якщо ви хочете, щоб кожен запуск починався заново, не запам’ятовуючи повідомлення з попередніх запусків, ви можете вказати аргумент filemode, змінивши виклик у прикладі вище на:

logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)

Результат буде таким же, як і раніше, але файл журналу більше не додається, тому повідомлення від попередніх запусків буде втрачено.

Журналування змінних даних

Щоб зареєструвати змінні дані, використовуйте рядок формату для повідомлення з описом події та додайте змінні дані як аргументи. Наприклад:

import logging
logging.warning('%s before you %s', 'Look', 'leap!')

буде відображатися:

WARNING:root:Look before you leap!

Як бачите, об’єднання змінних даних у повідомлення опису події використовує старий, %-style форматування рядків. Це для зворотної сумісності: пакет журналювання передує новішим параметрам форматування, таким як str.format() і string.Template. Ці нові параметри форматування підтримуються, але їх вивчення виходить за рамки цього підручника: див. Використання певних стилів форматування у вашій програмі для отримання додаткової інформації.

Зміна формату повідомлень, що відображаються

Щоб змінити формат, який використовується для відображення повідомлень, вам потрібно вказати формат, який ви хочете використовувати:

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')

який буде друкувати:

DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too

Зверніть увагу, що «корінь», який з’явився в попередніх прикладах, зник. Щоб отримати повний набір речей, які можуть відображатися в рядках формату, ви можете звернутися до документації для Атрибути LogRecord, але для простого використання вам потрібні лише levelname (серйозність), message (подія). опис, включаючи змінні дані) і, можливо, для відображення, коли сталася подія. Це описано в наступному розділі.

Відображення дати/часу в повідомленнях

Щоб відобразити дату й час події, потрібно розмістити «%(asctime)s» у рядку формату:

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

який має надрукувати щось на зразок цього:

2010-12-12 11:41:42,612 is when this event was logged.

Формат за замовчуванням для відображення дати/часу (показаний вище) такий, як ISO8601 або RFC 3339. Якщо вам потрібен більший контроль над форматуванням дати/часу, надайте аргумент datefmt для basicConfig, як у цьому прикладі:

import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')

який відображатиме щось на зразок цього:

12/12/2010 11:46:36 AM is when this event was logged.

Формат аргументу datefmt такий самий, як підтримується time.strftime().

Наступні кроки

На цьому основний підручник завершується. Цього має бути достатньо, щоб розпочати роботу з журналюванням. Пакет журналювання пропонує багато іншого, але щоб отримати від нього найкраще, вам потрібно буде витратити трохи більше часу на читання наступних розділів. Якщо ви готові до цього, візьміть свій улюблений напій і продовжуйте.

If your logging needs are simple, then use the above examples to incorporate logging into your own scripts, and if you run into problems or don’t understand something, please post a question on the comp.lang.python Usenet group (available at https://groups.google.com/g/comp.lang.python) and you should receive help before too long.

Досі тут? Ви можете продовжувати читати кілька наступних розділів, які містять дещо більш просунутий/поглиблений підручник, ніж основний вище. Після цього ви можете переглянути «Книга рецептів» з логування.

Навчальний посібник із розширеного журналювання

Бібліотека журналювання має модульний підхід і пропонує кілька категорій компонентів: журнали, обробники, фільтри та засоби форматування.

  • Логери відкривають інтерфейс, який безпосередньо використовує код програми.

  • Обробники надсилають записи журналу (створені реєстраторами) у відповідне місце призначення.

  • Фільтри забезпечують точніші засоби для визначення того, які записи журналу виводити.

  • Засоби форматування вказують макет записів журналу в кінцевому виведенні.

Інформація про подію журналу передається між реєстраторами, обробниками, фільтрами та форматувальниками в екземплярі LogRecord.

Логування виконується шляхом виклику методів екземплярів класу Logger (надалі loggers). Кожен екземпляр має назву, і вони концептуально впорядковані в ієрархії простору імен із використанням крапок (крапок) як роздільників. Наприклад, реєстратор під назвою «scan» є батьківським для реєстраторів «scan.text», «scan.html» і «scan.pdf». Імена реєстратора можуть бути будь-якими, і вони вказують на область програми, з якої походить повідомлення, що реєструється.

Хороша угода для використання під час іменування реєстраторів полягає в тому, щоб використовувати реєстратор на рівні модуля, у кожному модулі, який використовує журналювання, названий таким чином:

logger = logging.getLogger(__name__)

Це означає, що імена реєстраторів відстежують ієрархію пакетів/модулів, і інтуїтивно зрозуміло, де події реєструються лише з імені реєстратора.

Корінь ієрархії реєстраторів називається кореневим реєстратором. Це реєстратор, який використовують функції debug(), info(), warning(), error() і critical(), які просто викликають одноіменний метод кореневого реєстратора. Функції та методи мають однакові сигнатури. Ім’я кореневого реєстратора друкується як „root“ у зареєстрованих результатах.

Звичайно, можна записувати повідомлення до різних адресатів. У пакет включено підтримку для запису повідомлень журналу у файли, розташування HTTP GET/POST, електронну пошту через SMTP, загальні сокети, черги або специфічні для ОС механізми журналювання, такі як syslog або журнал подій Windows NT. Пункти призначення обслуговуються класами handler. Ви можете створити власний клас призначення журналу, якщо у вас є особливі вимоги, яких не задовольняє жоден із вбудованих класів обробки.

За замовчуванням адресат не встановлено для будь-яких повідомлень журналу. Ви можете вказати призначення (наприклад, консоль або файл) за допомогою basicConfig(), як у прикладах підручника. Якщо ви викликаєте функції debug(), info(), warning(), error() і critical(), вони перевірять, чи не встановлено місце призначення ; і якщо його не встановлено, вони встановлять призначення консолі (sys.stderr) і формат за замовчуванням для відображеного повідомлення перед делегуванням кореневому реєстратору для фактичного виведення повідомлення.

Типовий формат, встановлений basicConfig() для повідомлень:

severity:logger name:message

Ви можете змінити це, передавши рядок формату в basicConfig() з ключовим аргументом format. Щоб дізнатися про всі варіанти створення рядка формату, перегляньте Об’єкти форматування.

Потік журналювання

Потік інформації про подію журналу в реєстраторах і обробниках показано на наступній діаграмі.

../_images/logging_flow.png

Лісоруби

Об’єкти Logger виконують потрійну роботу. По-перше, вони надають кілька методів коду програми, щоб програми могли реєструвати повідомлення під час виконання. По-друге, об’єкти реєстратора визначають, з якими повідомленнями журналу діяти, залежно від серйозності (засоби фільтрації за замовчуванням) або об’єктів фільтрації. По-третє, об’єкти журналу передають відповідні повідомлення журналу всім зацікавленим обробникам журналів.

Найбільш широко використовувані методи для об’єктів журналу діляться на дві категорії: конфігурація та надсилання повідомлень.

Ось найпоширеніші методи налаштування:

  • Logger.setLevel() визначає повідомлення журналу з найнижчим рівнем серйозності, яке обробить реєстратор, де debug — найнижчий вбудований рівень серйозності, а критичний — найвищий вбудований рівень серйозності. Наприклад, якщо рівень серйозності INFO, реєстратор оброблятиме лише повідомлення INFO, WARNING, ERROR і CRITICAL і ігноруватиме повідомлення DEBUG.

  • Logger.addHandler() і Logger.removeHandler() додають і видаляють об’єкти обробки з об’єкта реєстратора. Обробники описані більш детально в Обробники.

  • Logger.addFilter() і Logger.removeFilter() додають і видаляють об’єкти фільтрів з об’єкта реєстратора. Фільтри описані більш детально в Фільтр об’єктів.

Вам не потрібно завжди викликати ці методи в кожному створеному вами реєстраторі. Дивіться останні два абзаци цього розділу.

Якщо об’єкт журналу налаштовано, такі методи створюють повідомлення журналу:

  • Logger.debug(), Logger.info(), Logger.warning(), Logger.error() і Logger.critical() створюють записи журналу з повідомлення та рівень, що відповідає їх відповідним назвам методів. Повідомлення фактично є рядком формату, який може містити стандартний синтаксис заміни рядка %s, %d, %f і так далі. Решта аргументів — це список об’єктів, які відповідають полям підстановки в повідомленні. Щодо **kwargs, методи журналювання дбають лише про ключове слово exc_info і використовують його, щоб визначити, чи потрібно реєструвати інформацію про винятки.

  • Logger.exception() створює повідомлення журналу, подібне до Logger.error(). Різниця полягає в тому, що Logger.exception() виводить трасування стека разом із ним. Викликайте цей метод лише з обробника винятків.

  • Logger.log() приймає рівень журналу як явний аргумент. Це трохи докладніше для реєстрації повідомлень, ніж використання зручних методів на рівні журналу, перелічених вище, але це те, як реєструвати на спеціальних рівнях журналу.

getLogger() повертає посилання на екземпляр журналу з указаним іменем, якщо воно надано, або root, якщо ні. Назви є ієрархічними структурами, розділеними на періоди. Кілька викликів getLogger() з однаковою назвою повертатимуть посилання на той самий об’єкт журналу. Реєстратори, розташовані нижче в ієрархічному списку, є нащадками реєстраторів, розташованих вище в списку. Наприклад, якщо задано реєстратор із іменем foo, усі реєстратори з іменами foo.bar, foo.bar.baz і foo.bam є нащадками фу.

Лісоруби мають поняття ефективного рівня. Якщо рівень не встановлено явно в реєстраторі, рівень його батьківського елемента використовується замість цього як ефективний рівень. Якщо батьківський елемент не має явно встановленого рівня, перевіряється його батьківський елемент, і так далі - шукаються всі предки, доки не буде знайдено явно встановлений рівень. Кореневий реєстратор завжди має чітко встановлений рівень (ПОПЕРЕДЖЕННЯ за замовчуванням). При прийнятті рішення про обробку події ефективний рівень реєстратора використовується для визначення того, чи подія передається обробникам реєстратора.

Дочірні реєстратори поширюють повідомлення до обробників, пов’язаних із їхніми попередніми реєстраторами. Через це немає необхідності визначати та налаштовувати обробники для всіх реєстраторів, які використовує програма. Достатньо налаштувати обробники для реєстратора верхнього рівня та створити дочірні реєстратори за потреби. (Проте ви можете вимкнути розповсюдження, встановивши для атрибута propagate реєстратора значення False.)

Обробники

Об’єкти Handler відповідають за надсилання відповідних повідомлень журналу (залежно від серйозності повідомлень журналу) до вказаного призначення обробника. Об’єкти Logger можуть додавати до себе нуль або більше об’єктів обробки за допомогою методу addHandler(). Як приклад сценарію, програма може захотіти надіслати всі повідомлення журналу до файлу журналу, усі повідомлення журналу про помилку або вище до stdout, а всі критичні повідомлення на адресу електронної пошти. Цей сценарій потребує трьох окремих обробників, де кожен обробник відповідає за надсилання повідомлень певного рівня серйозності в певне місце.

Стандартна бібліотека включає досить багато типів обробників (див. Корисні обробники); навчальні посібники в основному використовують StreamHandler і FileHandler у своїх прикладах.

У обробнику дуже мало методів, якими можуть зайнятися розробники програм. Єдиними методами обробки, які здаються актуальними для розробників додатків, які використовують вбудовані об’єкти обробки (тобто не створюють спеціальні обробники), є такі методи конфігурації:

  • The setLevel() method, just as in logger objects, specifies the lowest severity that will be dispatched to the appropriate destination. Why are there two setLevel() methods? The level set in the logger determines which severity of messages it will pass to its handlers. The level set in each handler determines which messages that handler will send on.

  • setFormatter() вибирає об’єкт Formatter для використання цим обробником.

  • addFilter() і removeFilter() відповідно налаштовують і деконфігурують об’єкти фільтрів на обробниках.

Код програми не повинен безпосередньо створювати та використовувати екземпляри Handler. Натомість клас Handler є базовим класом, який визначає інтерфейс, який повинні мати всі обробники, і встановлює певну поведінку за замовчуванням, яку дочірні класи можуть використовувати (або замінювати).

Форматери

Об’єкти форматувальника налаштовують остаточний порядок, структуру та вміст повідомлення журналу. На відміну від базового класу logging.Handler, код програми може створювати екземпляри класів форматера, хоча ви, ймовірно, можете створити підкласи форматера, якщо ваша програма потребує особливої поведінки. Конструктор приймає три необов’язкові аргументи - рядок формату повідомлення, рядок формату дати та індикатор стилю.

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

Якщо рядок формату повідомлення відсутній, за умовчанням використовується необроблене повідомлення. Якщо рядок формату дати відсутній, стандартний формат дати:

%Y-%m-%d %H:%M:%S

with the milliseconds tacked on at the end. The style is one of '%', '{', or '$'. If one of these is not specified, then '%' will be used.

If the style is '%', the message format string uses %(<dictionary key>)s styled string substitution; the possible keys are documented in Атрибути LogRecord. If the style is '{', the message format string is assumed to be compatible with str.format() (using keyword arguments), while if the style is '$' then the message format string should conform to what is expected by string.Template.substitute().

Змінено в версії 3.2: Додано параметр style.

Наступний рядок формату повідомлення реєструватиме час у зрозумілому для людини форматі, серйозність повідомлення та вміст повідомлення в такому порядку:

'%(asctime)s - %(levelname)s - %(message)s'

Форматери використовують настроювану користувачем функцію для перетворення часу створення запису в кортеж. За замовчуванням використовується time.localtime(); щоб змінити це для певного екземпляра форматера, установіть атрибут converter екземпляра на функцію з тим самим підписом, що й time.localtime() або time.gmtime(). Щоб змінити його для всіх засобів форматування, наприклад, якщо ви хочете, щоб усі часи журналювання відображалися за GMT, установіть атрибут converter у класі Formatter (на time.gmtime для відображення GMT).

Налаштування журналювання

Програмісти можуть налаштувати журналювання трьома способами:

  1. Створення реєстраторів, обробників і форматувальників явно за допомогою коду Python, який викликає перелічені вище методи конфігурації.

  2. Створення файлу конфігурації журналу та його читання за допомогою функції fileConfig().

  3. Створення словника конфігураційної інформації та передача його функції dictConfig().

Довідкову документацію щодо останніх двох параметрів див. Configuration functions. У наступному прикладі налаштовується дуже простий реєстратор, обробник консолі та простий засіб форматування за допомогою коду Python:

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

Запуск цього модуля з командного рядка дає такий результат:

$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

Наступний модуль Python створює реєстратор, обробник і форматування, майже ідентичні тим, що в наведеному вище прикладі, з тією лише різницею, як імена об’єктів:

import logging
import logging.config

logging.config.fileConfig('logging.conf')

# create logger
logger = logging.getLogger('simpleExample')

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

Ось файл logging.conf:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

Результат майже ідентичний прикладу без конфігураційного файлу:

$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message

Ви бачите, що підхід із конфігураційним файлом має кілька переваг перед підходом до коду Python, головним чином розділення конфігурації та коду та можливість некодерів легко змінювати властивості журналювання.

Попередження

Функція fileConfig() приймає параметр за замовчуванням, disable_existing_loggers, який за умовчанням має значення True з причин зворотної сумісності. Це може бути або не те, що ви хочете, оскільки це призведе до вимкнення будь-яких некореневих реєстраторів, які існують до виклику fileConfig(), якщо вони (або предок) явно не вказані в конфігурації. Будь ласка, зверніться до довідкової документації для отримання додаткової інформації та вкажіть False для цього параметра, якщо хочете.

Словник, переданий у dictConfig(), також може вказати логічне значення з ключем disable_existing_loggers, яке, якщо не вказано явно в словнику, також за умовчанням інтерпретується як True. Це призводить до поведінки вимкнення реєстратора, описаної вище, яка може не відповідати вашим побажанням - у такому випадку вкажіть ключ явно зі значенням False.

Зауважте, що назви класів, на які посилаються у конфігураційних файлах, мають бути або відносними до модуля журналювання, або абсолютними значеннями, які можна вирішити за допомогою звичайних механізмів імпорту. Таким чином, ви можете використовувати WatchedFileHandler (щодо модуля журналювання) або mypackage.mymodule.MyHandler (для класу, визначеного в пакунку mypackage і модулі mymodule, де mypackage доступний на шляху імпорту Python).

У Python 3.2 було введено новий засіб конфігурації журналювання, використовуючи словники для зберігання конфігураційної інформації. Це забезпечує надмножину функціональних можливостей підходу на основі конфігураційного файлу, описаного вище, і є рекомендованим методом конфігурації для нових програм і розгортань. Оскільки словник Python використовується для зберігання конфігураційної інформації, і оскільки ви можете заповнювати цей словник різними засобами, у вас є більше можливостей для конфігурації. Наприклад, ви можете використовувати файл конфігурації у форматі JSON або, якщо у вас є доступ до функцій обробки YAML, файл у форматі YAML, щоб заповнити словник конфігурації. Або, звісно, ви можете побудувати словник у коді Python, отримати його в маринованому вигляді через сокет або використати будь-який підхід, який має сенс для вашої програми.

Ось приклад тієї ж конфігурації, що й вище, у форматі YAML для нового підходу на основі словника:

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: no
root:
  level: DEBUG
  handlers: [console]

Для отримання додаткової інформації про журналювання за допомогою словника див. Configuration functions.

Що станеться, якщо конфігурацію не надано

If no logging configuration is provided, it is possible to have a situation where a logging event needs to be output, but no handlers can be found to output the event.

The event is output using a „handler of last resort“, stored in lastResort. This internal handler is not associated with any logger, and acts like a StreamHandler which writes the event description message to the current value of sys.stderr (therefore respecting any redirections which may be in effect). No formatting is done on the message - just the bare event description message is printed. The handler’s level is set to WARNING, so all events at this and greater severities will be output.

Змінено в версії 3.2: Для версій Python до 3.2 поведінка така:

  • If raiseExceptions is False (production mode), the event is silently dropped.

  • If raiseExceptions is True (development mode), a message „No handlers could be found for logger X.Y.Z“ is printed once.

To obtain the pre-3.2 behaviour, lastResort can be set to None.

Налаштування журналювання для бібліотеки

Розробляючи бібліотеку, яка використовує журналювання, ви повинні подбати про документування того, як бібліотека використовує журналювання - наприклад, імена використовуваних журналів. Певну увагу також слід приділити його конфігурації журналювання. Якщо програма, що використовує, не використовує журналювання, а код бібліотеки здійснює виклики журналювання, тоді (як описано в попередньому розділі) події серйозності ПОПЕРЕДЖЕННЯ і вище будуть надруковані в sys.stderr. Це вважається найкращою поведінкою за умовчанням.

Якщо з якоїсь причини ви не хочете, щоб ці повідомлення друкувались за відсутності будь-якої конфігурації журналювання, ви можете приєднати обробник нічого не робити до реєстратора верхнього рівня для вашої бібліотеки. Це дозволяє уникнути друку повідомлення, оскільки для подій бібліотеки завжди буде знайдено обробник: він просто не створює жодних виводів. Якщо користувач бібліотеки налаштовує журналювання для використання програмою, імовірно, ця конфігурація додасть деякі обробники, і якщо рівні налаштовано відповідним чином, виклики журналювання, зроблені в коді бібліотеки, надсилатимуть вихідні дані цим обробникам, як зазвичай.

Обробник нічого не робиться включено в пакет журналювання: NullHandler (починаючи з Python 3.1). Екземпляр цього обробника можна додати до реєстратора верхнього рівня простору імен журналювання, який використовується бібліотекою (якщо ви хочете запобігти виведенню зареєстрованих у бібліотеці подій у sys.stderr за відсутності конфігурації журналювання ). Якщо все журналювання бібліотекою foo здійснюється за допомогою реєстраторів з іменами, що відповідають «foo.x», «foo.x.y» тощо, тоді код:

import logging
logging.getLogger('foo').addHandler(logging.NullHandler())

має мати бажаний ефект. Якщо організація виробляє декілька бібліотек, то ім’я реєстратора може бути «orgname.foo», а не просто «foo».

Примітка

It is strongly advised that you do not log to the root logger in your library. Instead, use a logger with a unique and easily identifiable name, such as the __name__ for your library’s top-level package or module. Logging to the root logger will make it difficult or impossible for the application developer to configure the logging verbosity or handlers of your library as they wish.

Примітка

Настійно рекомендується не додавати жодних обробників, окрім NullHandler до реєстраторів вашої бібліотеки. Це пояснюється тим, що конфігурація обробників є прерогативою розробника програми, який використовує вашу бібліотеку. Розробник програми знає свою цільову аудиторію та знає, які обробники найбільше підходять для їхньої програми: якщо ви додасте обробники «під капотом», ви цілком можете втрутитися в їх здатність виконувати модульні тести та доставляти журнали, які відповідають їхнім вимогам.

Рівні реєстрації

Числові значення рівнів журналювання наведені в наступній таблиці. Це насамперед цікаво, якщо ви бажаєте визначити власні рівні та потребуєте, щоб вони мали певні значення відносно попередньо визначених рівнів. Якщо ви визначаєте рівень з тим самим числовим значенням, він перезаписує попередньо визначене значення; попередньо визначене ім’я втрачено.

Рівень

Числове значення

КРИТИЧНО

50

ПОМИЛКА

40

ПОПЕРЕДЖЕННЯ

30

ІНФО

20

НАЛАШТУВАННЯ

10

NOTSET

0

Рівні також можуть бути пов’язані з реєстраторами, які встановлюються розробником або через завантаження збереженої конфігурації журналювання. Коли в реєстраторі викликається метод ведення журналу, реєстратор порівнює свій власний рівень із рівнем, пов’язаним із викликом методу. Якщо рівень реєстратора вищий, ніж виклик методу, повідомлення журналу фактично не генерується. Це основний механізм, який контролює докладність вихідних даних журналу.

Повідомлення журналу кодуються як екземпляри класу LogRecord. Коли реєстратор вирішує фактично зареєструвати подію, екземпляр LogRecord створюється з повідомлення журналу.

Повідомлення журналювання підлягають механізму відправлення за допомогою handlers, які є екземплярами підкласів класу Handler. Обробники відповідають за те, щоб зареєстроване повідомлення (у формі LogRecord) потрапляло в певне місце (або набір місць), яке є корисним для цільової аудиторії цього повідомлення (наприклад, кінцевих користувачів, співробітники служби підтримки, системні адміністратори, розробники). Обробники передають екземпляри LogRecord, призначені для певних місць призначення. Кожен реєстратор може мати нуль, один або більше пов’язаних з ним обробників (через метод addHandler() Logger). На додаток до будь-яких обробників, безпосередньо пов’язаних із реєстратором, усі обробники, пов’язані з усіма предками реєстратора викликаються для надсилання повідомлення (якщо прапор розповсюдження для реєстратора не має значення false, після чого передача до обробників предків зупиняється).

Як і для реєстраторів, обробники можуть мати пов’язані з ними рівні. Рівень обробника діє як фільтр так само, як і рівень реєстратора. Якщо обробник вирішує фактично відправити подію, метод emit() використовується для надсилання повідомлення до місця призначення. Більшість визначених користувачем підкласів Handler повинні замінити цей emit().

Спеціальні рівні

Визначення власних рівнів можливо, але це не обов’язково, оскільки існуючі рівні було обрано на основі практичного досвіду. Однак, якщо ви впевнені, що вам потрібні користувацькі рівні, слід бути дуже обережним, роблячи це, і це, можливо, дуже погана ідея визначати користувацькі рівні, якщо ви розробляєте бібліотеку. Це тому, що якщо кілька авторів бібліотек визначають власні власні рівні, існує ймовірність того, що вихід журналу з таких кількох бібліотек, які використовуються разом, розробнику буде важко контролювати та/або інтерпретувати, оскільки дане числове значення може означати різні речі для різних бібліотек.

Корисні обробники

Окрім базового класу Handler, надається багато корисних підкласів:

  1. Екземпляри StreamHandler надсилають повідомлення до потоків (файлоподібних об’єктів).

  2. Екземпляри FileHandler надсилають повідомлення до файлів на диску.

  3. BaseRotatingHandler — це базовий клас для обробників, які обертають файли журналів у певний момент. Він не призначений для безпосереднього створення екземпляра. Замість цього використовуйте RotatingFileHandler або TimedRotatingFileHandler.

  4. Екземпляри RotatingFileHandler надсилають повідомлення до файлів на диску з підтримкою максимального розміру файлу журналу та ротації файлів журналу.

  5. TimedRotatingFileHandler екземпляри надсилають повідомлення до дискових файлів, чергуючи файл журналу через певні проміжки часу.

  6. Екземпляри SocketHandler надсилають повідомлення до сокетів TCP/IP. Починаючи з версії 3.4, також підтримуються доменні сокети Unix.

  7. Екземпляри DatagramHandler надсилають повідомлення до UDP-сокетів. Починаючи з версії 3.4, також підтримуються доменні сокети Unix.

  8. Екземпляри SMTPHandler надсилають повідомлення на вказану електронну адресу.

  9. Екземпляри SysLogHandler надсилають повідомлення до демона системного журналу Unix, можливо, на віддаленій машині.

  10. Екземпляри NTEventLogHandler надсилають повідомлення до журналу подій Windows NT/2000/XP.

  11. Екземпляри MemoryHandler надсилають повідомлення до буфера в пам’яті, який очищається щоразу, коли виконуються певні критерії.

  12. Екземпляри HTTPHandler надсилають повідомлення на сервер HTTP, використовуючи семантику GET або POST.

  13. Екземпляри WatchedFileHandler спостерігають за файлом, до якого вони входять. Якщо файл змінюється, він закривається та знову відкривається з використанням імені файлу. Цей обробник корисний лише в Unix-подібних системах; Windows не підтримує використовуваний основний механізм.

  14. Екземпляри QueueHandler надсилають повідомлення до черги, як-от реалізовані в модулях queue або multiprocessing.

  15. NullHandler instances do nothing with error messages. They are used by library developers who want to use logging, but want to avoid the „No handlers could be found for logger XXX“ message which can be displayed if the library user has not configured logging. See Налаштування журналювання для бібліотеки for more information.

Added in version 3.1: Клас NullHandler.

Added in version 3.2: Клас QueueHandler.

Класи NullHandler, StreamHandler і FileHandler визначені в базовому пакеті журналювання. Інші обробники визначені в підмодулі logging.handlers. (Існує також інший підмодуль, logging.config, для функціональних можливостей налаштування.)

Зареєстровані повідомлення форматуються для представлення через екземпляри класу Formatter. Вони ініціалізуються рядком формату, придатним для використання з оператором % і словником.

For formatting multiple messages in a batch, instances of BufferingFormatter can be used. In addition to the format string (which is applied to each message in the batch), there is provision for header and trailer format strings.

Якщо фільтрації на основі рівня реєстратора та/або рівня обробника недостатньо, екземпляри Filter можна додати до екземплярів Logger і Handler (через їх метод addFilter()). Перш ніж вирішити продовжити обробку повідомлення, і реєстратори, і обробники звертаються до всіх своїх фільтрів для отримання дозволу. Якщо будь-який фільтр повертає хибне значення, повідомлення не обробляється далі.

Основна функція Filter дозволяє фільтрувати за певним іменем реєстратора. Якщо використовується ця функція, повідомлення, надіслані до названого реєстратора та його дочірніх елементів, пропускаються через фільтр, а всі інші відкидаються.

Винятки, які виникають під час реєстрації

Пакет журналювання призначений для ковтання винятків, які виникають під час входу в робочу систему. Це зроблено для того, щоб помилки, які виникають під час обробки подій журналювання (наприклад, неправильна конфігурація журналу, мережа чи інші подібні помилки), не призвели до передчасного завершення програми, яка використовує журналювання.

Винятки SystemExit і KeyboardInterrupt ніколи не проковтуються. Інші винятки, які виникають під час методу emit() підкласу Handler, передаються до його методу handleError().

Стандартна реалізація handleError() в Handler перевіряє, чи встановлено змінну рівня модуля, raiseExceptions. Якщо встановлено, зворотне відстеження друкується в sys.stderr. Якщо не встановлено, виняток проковтується.

Примітка

Значенням за замовчуванням raiseExceptions є True. Це пояснюється тим, що під час розробки ви зазвичай хочете отримувати сповіщення про будь-які винятки, які трапляються. Радимо встановити raiseExceptions на False для використання у робочому режимі.

Використання довільних об’єктів як повідомлень

У попередніх розділах і прикладах передбачалося, що повідомлення, передане під час реєстрації події, є рядком. Однак це не єдина можливість. Ви можете передати довільний об’єкт як повідомлення, і його метод __str__() буде викликано, коли системі журналювання потрібно перетворити його на представлення рядка. Насправді, якщо ви хочете, ви можете взагалі уникнути обчислення представлення рядка - наприклад, SocketHandler випромінює подію, вибираючи її та надсилаючи по дроту.

Оптимізація

Форматування аргументів повідомлення відкладено, доки його не можна уникнути. Однак обчислення аргументів, переданих до методу журналювання, також може бути дорогим, і ви можете уникнути цього, якщо реєстратор просто відкине вашу подію. Щоб вирішити, що робити, ви можете викликати метод isEnabledFor(), який приймає аргумент рівня та повертає значення true, якщо подію буде створено реєстратором для цього рівня виклику. Ви можете написати такий код:

if logger.isEnabledFor(logging.DEBUG):
    logger.debug('Message with %s, %s', expensive_func1(),
                                        expensive_func2())

so that if the logger’s threshold is set above DEBUG, the calls to expensive_func1 and expensive_func2 are never made.

Примітка

У деяких випадках isEnabledFor() сам по собі може бути дорожчим, ніж вам хотілося б (наприклад, для глибоко вкладених реєстраторів, де явний рівень встановлюється лише високо в ієрархії реєстратора). У таких випадках (або якщо ви хочете уникнути виклику методу в жорстких циклах), ви можете кешувати результат виклику isEnabledFor() у локальній змінній чи змінній екземпляра та використовувати це замість виклику метод кожного разу. Таке кешоване значення потрібно було б повторно обчислити лише тоді, коли конфігурація журналювання динамічно змінюється під час роботи програми (що не так вже й часто).

Існують інші оптимізації, які можна зробити для конкретних програм, які потребують більш точного контролю над тим, яка інформація журналу збирається. Ось список речей, які ви можете зробити, щоб уникнути обробки під час журналювання, яка вам не потрібна:

Те, що ви не хочете збирати

Як уникнути його збору

Інформація про те, звідки дзвонили.

Встановіть для logging._srcfile значення None. Це дозволяє уникнути виклику sys._getframe(), що може допомогти пришвидшити ваш код у таких середовищах, як PyPy (який не може прискорити код, який використовує sys._getframe()).

Інформація про потоки.

Встановіть для logging.logThreads значення False.

Ідентифікатор поточного процесу (os.getpid())

Встановіть для logging.logProcesses значення False.

Поточне ім’я процесу, якщо для керування кількома процесами використовується багатопроцесорна обробка.

Встановіть для logging.logMultiprocessing значення False.

Current asyncio.Task name when using asyncio.

Set logging.logAsyncioTasks to False.

Також зауважте, що основний модуль журналювання включає лише основні обробники. Якщо ви не імпортуєте logging.handlers і logging.config, вони не займатимуть жодної пам’яті.

Інші ресурси

Дивись також

Модуль logging

Довідник API для модуля журналювання.

Модуль logging.config

API конфігурації для модуля журналювання.

Модуль logging.handlers

Корисні обробники, включені в модуль журналювання.

Кулінарна книга журналювання