Введение в модуль журналирования

PythonPythonBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

В этом разделе кратко介绍руется модуль журналирования.

Модуль logging

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

Возвращение к исключениям

В упражнениях мы написали функцию parse(), которая выглядела примерно так:

## fileparse.py
def parse(f, types=None, names=None, delimiter=None):
    records = []
    for line in f:
        line = line.strip()
        if not line: continue
        try:
            records.append(split(line,types,names,delimiter))
        except ValueError as e:
            print("Couldn't parse :", line)
            print("Reason :", e)
    return records

Обратите внимание на инструкцию try-except. Что нужно делать в блоке except?

Следует ли выводить сообщение об ошибке?

try:
    records.append(split(line,types,names,delimiter))
except ValueError as e:
    print("Couldn't parse :", line)
    print("Reason :", e)

Или тихо игнорировать его?

try:
    records.append(split(line,types,names,delimiter))
except ValueError as e:
    pass

Ни одно из решений не является удовлетворительным, потому что часто требуются оба поведения (возможность выбора пользователем).

Использование журналирования

Модуль logging может решить эту проблему.

## fileparse.py
import logging
log = logging.getLogger(__name__)

def parse(f,types=None,names=None,delimiter=None):
 ...
    try:
        records.append(split(line,types,names,delimiter))
    except ValueError as e:
        log.warning("Couldn't parse : %s", line)
        log.debug("Reason : %s", e)

Код модифицирован для вывода сообщений об ошибках или с использованием специального объекта Logger. Создается с помощью logging.getLogger(__name__).

Основы журналирования

Создайте объект журналировщика.

log = logging.getLogger(name)   ## name - это строка

Вывод сообщений журнала.

log.critical(message [, args])
log.error(message [, args])
log.warning(message [, args])
log.info(message [, args])
log.debug(message [, args])

Каждый метод представляет собой разный уровень серьезности.

Все они создают отформатированное сообщение журнала. args используется с оператором % для создания сообщения.

logmsg = message % args ## Записывается в журнал

Конфигурация журналирования

Поведение журналирования настраивается отдельно.

## main.py

...

if __name__ == '__main__':
    import logging
    logging.basicConfig(
        filename  = 'app.log',      ## Файл для вывода логов
        level     = logging.INFO,   ## Уровень вывода
    )

обычно, это一次性配置在程序启动时进行。配置与进行日志记录调用的代码是分开的。 (注:这里“一次性配置”表述不太准确,原文“one-time configuration”更准确意思是“一次性的配置操作”,但按要求尽量贴近原文翻译了)

Комментарии

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

Упражнение 8.2: Добавление журналирования в модуль

В fileparse.py есть некоторые обработчики ошибок, связанные с исключениями, вызванными неправильными входными данными. Они выглядят так:

## fileparse.py
import csv

def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
    '''
    Parse a CSV file into a list of records with type conversion.
    '''
    if select and not has_headers:
        raise RuntimeError('select requires column headers')

    rows = csv.reader(lines, delimiter=delimiter)

    ## Read the file headers (if any)
    headers = next(rows) if has_headers else []

    ## If specific columns have been selected, make indices for filtering and set output columns
    if select:
        indices = [ headers.index(colname) for colname in select ]
        headers = select

    records = []
    for rowno, row in enumerate(rows, 1):
        if not row:     ## Skip rows with no data
            continue

        ## If specific column indices are selected, pick them out
        if select:
            row = [ row[index] for index in indices]

        ## Apply type conversion to the row
        if types:
            try:
                row = [func(val) for func, val in zip(types, row)]
            except ValueError as e:
                if not silence_errors:
                    print(f"Row {rowno}: Couldn't convert {row}")
                    print(f"Row {rowno}: Reason {e}")
                continue

        ## Make a dictionary or a tuple
        if headers:
            record = dict(zip(headers, row))
        else:
            record = tuple(row)
        records.append(record)

    return records

Обратите внимание на инструкции print, которые выводят диагностические сообщения. Замена этих print на операции журналирования относительно проста. Измените код так:

## fileparse.py
import csv
import logging
log = logging.getLogger(__name__)

def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
    '''
    Parse a CSV file into a list of records with type conversion.
    '''
    if select and not has_headers:
        raise RuntimeError('select requires column headers')

    rows = csv.reader(lines, delimiter=delimiter)

    ## Read the file headers (if any)
    headers = next(rows) if has_headers else []

    ## If specific columns have been selected, make indices for filtering and set output columns
    if select:
        indices = [ headers.index(colname) for colname in select ]
        headers = select

    records = []
    for rowno, row in enumerate(rows, 1):
        if not row:     ## Skip rows with no data
            continue

        ## If specific column indices are selected, pick them out
        if select:
            row = [ row[index] for index in indices]

        ## Apply type conversion to the row
        if types:
            try:
                row = [func(val) for func, val in zip(types, row)]
            except ValueError as e:
                if not silence_errors:
                    log.warning("Row %d: Couldn't convert %s", rowno, row)
                    log.debug("Row %d: Reason %s", rowno, e)
                continue

        ## Make a dictionary or a tuple
        if headers:
            record = dict(zip(headers, row))
        else:
            record = tuple(row)
        records.append(record)

    return records

Теперь, когда вы внесли эти изменения, попробуйте использовать часть своего кода с неправильными данными.

>>> import report
>>> a = report.read_portfolio('missing.csv')
Row 4: Bad row: ['MSFT', '', '51.23']
Row 7: Bad row: ['IBM', '', '70.44']
>>>

Если вы ничего не сделаете, вы получите только сообщения журнала уровня WARNING и выше. Вывод будет выглядеть как простые инструкции print. Однако, если вы настроите модуль журналирования, вы получите дополнительную информацию о уровнях журналирования, модуле и т.д. Введите эти шаги, чтобы увидеть это:

>>> import logging
>>> logging.basicConfig()
>>> a = report.read_portfolio('missing.csv')
WARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23']
WARNING:fileparse:Row 7: Bad row: ['IBM', '', '70.44']
>>>

Вы заметите, что не видите вывод операции log.debug(). Введите это, чтобы изменить уровень.

>>> logging.getLogger('fileparse').setLevel(logging.DEBUG)
>>> a = report.read_portfolio('missing.csv')
WARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23']
DEBUG:fileparse:Row 4: Reason: invalid literal for int() with base 10: ''
WARNING:fileparse:Row 7: Bad row: ['IBM', '', '70.44']
DEBUG:fileparse:Row 7: Reason: invalid literal for int() with base 10: ''
>>>

Отключите все, кроме наиболее критичных сообщений журнала:

>>> logging.getLogger('fileparse').setLevel(logging.CRITICAL)
>>> a = report.read_portfolio('missing.csv')
>>>

Упражнение 8.3: Добавление журналирования в программу

Для добавления журналирования в приложение вам нужно иметь какой-то механизм для инициализации модуля журналирования в главном модуле. Одним из способов сделать это является включение некоторого кода настройки, который выглядит так:

## Этот файл настраивает базовую конфигурацию модуля журналирования.
## Измените настройки здесь, чтобы настроить вывод журнала по необходимости.
import logging
logging.basicConfig(
    filename = 'app.log',            ## Имя файла журнала (оставьте пустым, чтобы использовать stderr)
    filemode = 'w',                  ## Режим файла (используйте 'a', чтобы добавлять записи)
    level    = logging.WARNING,      ## Уровень журналирования (DEBUG, INFO, WARNING, ERROR или CRITICAL)
)

Опять же, вам нужно разместить это в каком-то месте в этапах запуска вашей программы. Например, где вы бы поместили это в своей программе report.py?

✨ Проверить решение и практиковаться

Резюме

Поздравляем! Вы завершили лабораторную работу по журналированию. Вы можете практиковаться в других лабораторных работах в LabEx, чтобы улучшить свои навыки.