По коду разбросаны if DEBUG: print() для вывода разной полезной информации. Некрасиво. А как еще?

нужно как-то выводить отладочную информацию.

Евгений

Перфекционист с дедлайнами

Предположим, наша программа должна выводить в консоль отладочную информацию: сколько времени ушло на скачивание страницы из Интернета, каков статус ответа и прочее. Вариантов действия у нас несколько.

Первый вариант, он же самый простой, — это раскидать по коду отладочные print, а перед очередным коммитом не забыть их удалить. Вполне рабочая схема, у неё даже название свое есть - "отладка принтами". Сложности начинаются когда:

  • раз за разом приходится дописывать одни и те же вызовы print;
  • пользователю тоже иногда нужен вывод дополнительной информации в консоль.

Есть второй вариант — по коду раскидать конструкции вида:

if app_cfg['LOG_MOD']:
  print('Data fetching took {}'.format(timeout_in_secs))

У такого кода есть несколько проблем:

  • кода получается много, куча конструкций if;
  • повсюду приходится таскать за собой app_cfg, чтобы иметь возможность динамической настройки программы средствами argparse.

Еще один способ — использовать стандартную библиотеку logging. Она решает все эти проблемы. Вот пример использования:

import logging, requests

def fetch_article(slug):
    response = requests.get('http://example.com/article/{}'.format(slug))
    logging.info('HTTP STATUS {}'.format(response.status_code))
    logging.debug(response.text)
    ...

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

# lets output INFO, WARNING, ERROR and CRITICAL messages
logging.basicConfig(level=logging.INFO)

Если мы хотим вдобавок к info вывести все debug сообщения:

# lets output totally all messages: DEBUG, INFO, WARNING, ERROR and CRITICAL
logging.basicConfig(level=logging.DEBUG)

В конечном счете все это позволяет легко настраивать логгирование через аргументы скрипта:

VERBOSITY_TO_LOGGING_LEVELS = {
    0: logging.WARNING,
    1: logging.INFO,
    2: logging.DEBUG,
}

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--verbose', '-v', action='count', default=0)
    args = parser.parse_args()
    logging_level = VERBOSITY_TO_LOGGING_LEVELS[args.verbose]

    logging.basicConfig(level=logging_level)
    ...

При вызове python myscript.py -v в консоли появятся info сообщения. При вызове python myscript.py -vv — все сообщения включая debug.

Все сказанное выше справедливо для вывода вспомогательной информации. Для основной функциональности есть старый добрый print().

Модуль logging еще много чего умеет, и все это описано в документации и в туториале. Особенно часто используется возможность module-level logger. А вот логирование в файл, чему так много уделено внимания в документации, используют редко. Дело в том что stdout и stderr легко поддаются перенаправлению в файл. Кроме того, в systemd встроен логгер journald с продвинутыми механизмами фильтрации, поиска по логу, ротации и прочим. И этот journald считывает лог программ из stdout и stderr.