О логах в Python
Многие программисты используют print для лечения багов, но в больших, серьёзных проектах так не получится:
- Во-первых, они обычно запущены на сервере, из-за чего до вывода print будет не добраться. А если программу перезапустить, то все сообщения пропадут и вовсе.
- Во-вторых, чтобы сообщения были полезны, нужно выводить много всего: время, строку в коде, что случилось… Это всё очень засорит код ненужным мусором.
Просто не выводить такие сообщения — тоже плохо. Представьте, вы написали программу, запустили на сервере. Через год вам пишут, что ваша программа сломалась. И что же делать, где ошибка?
Помогут логи — журнал действий программы. По сути, это те же сообщения через print , только за вас заранее реализовано много всего классного:
- Можно сортировать по степени важности, времени и т. д.
- Можно выводить не только в терминал. За вас уже реализован вывод в файл, например.
- Легко понять где, когда и что произошло.
Как пользоваться
Для ведения логов в Python есть библиотека logging :
import logging logging.debug('Сообщение для дебагинга') logging.info('Произошло какое-то событие. Всё идёт по плану.') logging.warning('Предупреждение, что-то могло сломаться') logging.error('Ошибка, что-то сломалось') logging.critical('МЫ В ОГНЕ ЧТО ДЕЛАТЬ. ')
Здесь мы создаём записи в логах на разных уровнях важности (от debug до critical). При таком использовании будет выглядеть почти как принты:
WARNING:root:Предупреждение, что-то могло сломаться ERROR:root:Ошибка, что-то сломалось CRITICAL:root:МЫ В ОГНЕ ЧТО ДЕЛАТЬ.
Вопрос, куда делись первые 2 сообщения? Дело в том, что logging автоматически фильтрует для вас логи по степени важности DEBUG , INFO , WARNING , ERROR и CRITICAL .
По умолчанию logging фиксирует только логи уровня WARNING и выше (т.е. ещё ERROR и CRITICAL), а все логи уровнями ниже — игнорирует (DEBUG и INFO). Это можно изменить в настройках логов:
import logging logging.basicConfig(level=logging.DEBUG) logging.debug('Сообщение уровня DEBUG')
Теперь вы увидите все логи, на всех уровнях.
Уровни логирования
Рассмотрим каждый вариант отдельно.
DEBUG
DEBUG — это сообщения для отладки, которые вы оставили для себя. Содержимое переменных и всё такое. Самые не важные. Обычные пользователи их не читают, только программисты и системные администраторы.
INFO
INFO — это сообщения о происходящих событиях, не требующих реакции пользователя. Например: отправлено письмо, зарегистрирован пользователь.
WARNING
WARNING — предупреждения о том, что вскоре может привести к неожиданному поведению программы или же к ошибкам в работе модулей. Например: Файл настроек не найден, использую стандартные.
ERROR
ERROR — это такие ошибки, из-за которых приложение вынуждено завершить свою работу или теряет часть функциональности. Например, если чат-бот не может ответить одному из пользователей из-за ошибки, он может его проигнорировать и общаться с остальными.
CRITICAL
CRITICAL — самые серьезные ситуации, когда программа повреждена и простой перезапуск дело уже не исправит. Требуется немедленное вмешательство программиста или системного администратора. Пример такой ситуации — это повреждение базы данных в результате экстренного выключения сервера.
Что читать дальше?
Обращайтесь к нашим статьям:
- Форматирование логов
- Обработчики логов
- Логирование ошибок
К статьям с других сайтов:
- Логирование в Python
- Logging in Python (en)
Или к документации logging
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.
Эффективное логирование в Python
В Python существует встроенный модуль logging, который позволяет журналировать этапы выполнения программы. Логирование полезно когда, например, нужно оставить большой скрипт сбора / обработки данных на длительное время, а в случае возникновения непредвиденных ошибок выяснить, с чем они могут быть связаны. Анализ логов позволяет быстро и эффективно выявлять проблемные места в коде, но для удобного использования модуля следует написать несколько функций по взаимодействию с ним и вынести их в отдельный файл — сегодня мы этим и займёмся.
Пишем логгер
Создадим файл loggers.py. Для начала импортируем модули и задаём пару значений по умолчанию — директорию для файла с логом и наименование конфигурационного файла, содержащего шаблоны логирования. Его мы опишем следом.
import os import json import logging import logging.config FOLDER_LOG = "log" LOGGING_CONFIG_FILE = 'loggers.json'
Опишем функцию для создания папки с логом: она принимает наименование для папки, но по умолчанию будет называть её «log». Директорию создаём при помощи модуля os и только в том случае, если такой директории ещё не существует.
def create_log_folder(folder=FOLDER_LOG): if not os.path.exists(folder): os.mkdir(folder)
Теперь опишем функцию создания нового логгера по заданному шаблону. Функция должна создать директорию для логирования, открыть конфигурационный файл и достать нужный шаблон. Затем по шаблону при помощи модуля logging создаём новый логгер:
def get_logger(name, template='default'): create_log_folder() with open(LOGGING_CONFIG_FILE, "r") as f: dict_config = json.load(f) dict_config["loggers"][name] = dict_config["loggers"][template] logging.config.dictConfig(dict_config) return logging.getLogger(name)
Для удобства опишем ещё одну функцию — получение стандартного лога. Она ничего не принимает и нужна только для инициализации лога с шаблоном default:
def get_default_logger(): create_log_folder() with open(LOGGING_CONFIG_FILE, "r") as f: logging.config.dictConfig(json.load(f)) return logging.getLogger("default")
Описываем конфигурационный файл
Создадим по соседству файл loggers.json — он будет содержать настройки логгера. Внутри указываем такие настройки, как версию логгера, форматы логирования для разных уровней, наименование выходного файла и его максимальный размер:
< "version": 1, "disable_existing_loggers": false, "formatters": < "default": < "format": "%(asctime)s - %(processName)-10s - %(name)-10s - %(levelname)-8s - %(message)s" >>, "handlers": < "console": < "class": "logging.StreamHandler", "level": "INFO", "formatter": "default" >, "rotating_file": < "class": "logging.handlers.RotatingFileHandler", "level": "DEBUG", "formatter": "default", "filename": "log/main.log", "maxBytes": 10485760, "backupCount": 20 >>, "loggers": < "default": < "handlers": ["console", "rotating_file"], "level": "DEBUG" >> >
Использование логгера
Теперь давайте представим, что вы выгружаете данные по API и складываете их в базу данных на примере нашего материала про транзакции в SQLAlchemy. Рассмотрим заключительную часть кода: добавим строку с инициализацией стандартного логгера и изменим код так, чтобы сначала в лог выводился offset, затем в случае успеха предложение «Successfully inserted data», а в случае ошибки выводилась сама ошибка и предложение: «Error: tried to insert data but got an error».
logger = get_logger('main') offset = 0 subs_count = get_subs_count(group_id) while offset < subs_count: with engine.connect() as conn: transaction = conn.begin() try: logger.info(f"/ ") df = get_subs_info(group_id, offset) df.to_sql('subscribers', con=conn, if_exists='append', index=False) if offset == 10: raise(ValueError("This is a test errror")) transaction.commit() logger.info(f"Successfully inserted data") except Exception as E: transaction.rollback() logger.error(f"Error: tried to insert but got an error: ") time.sleep(1) offset += 10
Теперь во время работы программы будет отображаться такой вывод, который также будет записан в файл main.log папки log в директории проекта. После завершения работы программы можно исследовать логи, посмотреть, на каких offset возникли проблемы, какие данные не удалось вставить и прочитать текст ошибки:
Логирование в Python
Логирование — это процесс записи потока кода при его выполнении наряду с записью любых других событий. Запись происходит в файлы, которые впоследствии можно использовать для анализа и устранения возникающих неполадок.
- В отладке кода для определения потока исходного кода во время разработки и после развертывания.
- Оповещении об исключительном событии, вызванном кодом. Например, нехватке памяти и т. д.
- Поиске пользователей или систем, обращающихся к коду.
Что должно записываться в логи?
Логи должны содержать сообщение, в котором указывается:
- Информация о доступе : пользователи и устройства, имеющие доступ к коду.
- Версия кода : текущая редакция приложения.
- Отметка времени : отметки времени всех ключевых событий.
- Результаты : результаты переменных, вычисленных в коде.
- Исключения , возникающие вместе с трассировкой стека.
- Поток кода : различные классы и функции, вызываемые во время выполнения кода.
Как реализовать логирование в Python?
Python предоставляет библиотеку «logging» для записи сообщений в файл или любой другой поток вывода.
Различные уровни логирования в порядке их сложности :
- DEBUG : используется только при диагностике проблем.
- INFO : только для информации, используемой для понимания потока кода при диагностике проблемы.
- WARNING : когда возникает непредвиденная ситуация, но код все еще выполняется.
- ERROR : когда код не может выполнять какую-либо функцию.
- CRITICAL : серьезная ошибка, когда программа не может продолжить работ.
Простой код реализации логирования различных вариантов сложности:
import logging logging.debug('This is a debug message') logging.info('This is an info message') logging.warning('This is a warning message') logging.error('This is an error message') logging.critical('This is a critical message')
Вывод в консоль
Мы логируем структуру сообщений уровня WARNING, за которым следует корневой модуль логирования по умолчанию и сообщение. П ри этом отладочные и информационные сообщения не отображаются. Модуль регистрации по умолчанию отображает только сообщения с уровнем сложности WARNING и выше.
Как вывести сообщения для уровней сложности Debug и Info?
Мы используем метод basicConfig(), чтобы установить базовую конфигурацию для системы логирования.
import logginglogging.basicConfig(level=logging.debug)logging.debug('This is a debug message') logging.info('This is an info message') logging.warning('This is a warning message') logging.error('This is an error message') logging.critical('This is a critical message')
Вывод в консоль
Метод basicConfig() не будет учитывать новые настройки, если корневой регистратор уже настроен. Мне пришлось перезапустить ядро, чтобы приведенный выше код работал правильно. Так как при первом вызове любой из функций она настраивает корневой регистратор внутри системы.
Параметры, принимаемые basicConfig()
filename : имя файла, в который нужно записать сообщение.
filemode : режим, в котором файл должен быть открыт. Например, «w» для записи, «a» - для добавления. Режим файла по умолчанию - «а».
format : строка формата, доступную в качестве атрибутов в LogRecord.
datefmt : формат даты, отображаемой в сообщениях лога. Формат должен быть передан time.strftime().
level : уровень сложности для корневого регистратора.
Запись сообщения лога в файл log.txt в режиме добавления с уровнем сложности DEBUG
import logging logging.basicConfig(filename='log.txt', filemode='a', level=logging.DEBUG)logging.debug('This is a debug message') logging.info('This is an info message') logging.warning('This is a warning message') logging.error('This is an error message') logging.critical('This is a critical message')
Файл log.txt с сообщениями лога
Запись отформатированного лога в файл log.txt в режиме добавления с уровнем сложности DEBUG
import logging logging.basicConfig(filename='log.txt', filemode='a', format='%(asctime)s %(msecs)d- %(process)d -%(levelname)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S %p' , level=logging.DEBUG)logging.debug('This is a debug message') logging.info('This is an info message') logging.warning('This is a warning message') logging.error('This is an error message') logging.critical('This is a critical message')
Файл лога log.txt с отформатированными сообщениями лога
Регистрация сообщений в классах и функциях
Приведенный ниже фрагмент кода демонстрирует логирование в классах и функциях.
Мы создаем класс TestLog с divide(). Он принимает два параметра и возвращает результат деления. В случае ошибки в делении мы хотим иметь трассировку стека в файле лога.
import logging class TestLog: def __init__(self): logging.info('init method') def divide(self, x, y): try: logging.info(" Dividing. ") return x/y except Exception as e: logging.error(" Error in divide", exc_info=True)
Создание экземпляра класса TestLog и вызов divide().
import logginglogging.basicConfig(filename='app.txt', filemode='a',level=logging.DEBUG, format='%(asctime)s %(msecs)d- %(process)d-%(levelname)s - %(message)s')logging.info('Started') x=10 y=2 t= TestLog() num_1= t.divide(x,y)logging.info(" Result of dividing %d by %d is %d", x, y,num_1)
Сообщение лога в app.txt
Логирование трассировки стека
Для отображения трассировки стека нужно установить для exc_info значение true в блоке except обработки исключений.
logging.info('Started') x=10 y=0 t= TestLog() num_1= t.divide(x,y) logging.info(" Result of dividing %d by %d is %d", x, y,num_1)
Заключение
Логирование помогает в устранении неполадок приложения во время разработки или после развертывания. Логирование может быть реализовано через запись в файлы, стандартного вывода или записи трассировки стека.
Введение в logging на Python
22 Ноя. 2020 , Python, 63936 просмотров, The Ultimate Guide To Python Logging
В стандартной библиотеке Python есть замечательный пакет для логирования — logging. В сети бытует мнение, что он сложный и настраивать его сплошная боль. В этой статье я попробую убедить вас в обратном. Мы разберём что из себя представляет этот пакет, изучим основные компоненты и закрепим материал практическим примером.
Зачем нужны логи?
Логи это рентген снимок выполнения вашей программы. Чем детальнее лог, тем проще разобраться в нестандартных ситуациях, которые могут приключиться с вашим скриптом. Наиболее популярным примером логов служат access логи веб-сервера, например, Apache httpd или nginx. Пример куска access лога моего блога:
92.63.107.227 - - [04/Nov/2020:06:30:48 +0000] "GET /ru/hosted-open-vpn-server/ HTTP/1.1" 301 169 "-" "python-requests/2.11.1" "-" 92.63.107.227 - - [04/Nov/2020:06:30:49 +0000] "GET /ru/data-engineering-course/ HTTP/1.1" 301 169 "-" "python-requests/2.11.1" "-" 213.180.203.50 - - [04/Nov/2020:06:36:07 +0000] "GET / HTTP/1.1" 301 169 "-" "Mozilla/5.0 (compatible; YandexMetrika/2.0; +http://yandex.com/bots yabs01)" "-" 114.119.160.75 - - [04/Nov/2020:06:36:41 +0000] "GET /robots.txt HTTP/1.1" 301 169 "-" "(compatible;PetalBot;+https://aspiegel.com/petalbot)" "10.179.80.67" 90.180.35.207 - - [04/Nov/2020:06:47:11 +0000] "GET / HTTP/1.0" 301 169 "-" "-" "-" 46.246.122.77 - - [04/Nov/2020:06:53:22 +0000] "GET / HTTP/1.1" 301 169 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36" "-" 66.249.76.16 - - [04/Nov/2020:06:53:30 +0000] "GET / HTTP/1.1" 301 169 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" "-" 66.102.9.118 - - [04/Nov/2020:07:11:19 +0000] "GET / HTTP/1.1" 301 169 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36 Google Favicon" "46.159.204.234" 71.6.167.142 - - [04/Nov/2020:07:11:55 +0000] "GET / HTTP/1.1" 301 169 "-" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36" "-"
Помимо access логов веб-сервер также пишет error лог, там хранится информация обо всех ошибках при обработке HTTP запросов. Также и в ваших скриптах, логи могут делиться на информационные — вывод текущего состояния выполнения, отладочной информации, и на логи с ошибками — вывод исключений, ошибок с дополнительной информацией для отладки, содержащей контекст).
logging и Python
Точкой входа в работу с логированием в Python является библиотека logging. На первый взгляд может показаться, что библиотека сложная и запутанная, но потратив некоторое время на её изучение, можно убедиться в обратном. Для меня logging это классический пример дизайна ООП, где композиция преобладает над наследованием, поэтому в исходном коде библиотеки можно встретить множество функциональных классов. Цель этого туториала разобрать по косточкам каждый класс и воссоединить их в единый механизм логирования в Python. Начнём-с.
Logger
Чтобы начать работу с logging необходимо в импортировать библиотеку logging и вызвать функцию getLogger, передав ей имя будущего логера. Функция вернёт инстанс объекта Logger. Логер это рычаг за который мы дёргаем каждый раз, когда нам нужно записать информацию в лог.
import logging logger = logging.getLogger('logger')
Заметьте, что функция getLogger принимает на вход параметр — имя логера. Можно назначать любое имя или __name__ . Вызов getLogger с одинаковым названием вернёт один и тот же инстанс логера.
Я рекомендую использовать в качестве аргумента __name__ , в этом случае не нужно беспокоиться, что разные модули могут ссылаться на один и тот же логер.
Класс Logger предоставляет наружу несколько методов для записи сообщений разного уровня. Уровни необходимы для понимания контекста сообщения/лога, который мы пишем. В logging существует несколько уровней:
- DEBUG — уровень отладочной информации, зачастую помогает при разработке приложения на машине программиста.
- INFO — уровень вспомогательной информации о ходе работы приложения/скрипта.
- WARNING — уровень предупреждения. Например, мы можем предупреждать о том, что та или иная функция будет удалена в будущих версиях вашего приложения.
- ERROR — с таким уровнем разработчики пишут логи с ошибками, например, о том, что внешний сервис недоступен.
- CRITICAL — уровень сообщений после которых работа приложения продолжаться не может.
По умолчанию в logging задан уровень WARNING, это означает, что сообщения уровня DEBUG и INFOбудут игнорироваться при записи в лог. Разработчик может самостоятельно задать необходимый ему уровень логирования через метод setLevel у инстанса Logger:
logger.setLevel(logging.DEBUG)
Методы для записи сообщений с разными уровнями именуются по тому же принципу:
logger.debug('debug info') logger.info('info') logger.warning('warning') logger.error('debug info') logger.critical('debug info')
Также есть ещё один метод — exception . Его желательно вызывать в блоке except при обработке исключения. В это случае он сможет уловить контекст исключения и записать его в лог:
try: 1/0 except : logger.exception('exception')
Handler
Задача класса Handler и его потомков обрабатывать запись сообщений/логов. Т.е. Handler отвечает за то куда будут записаны сообщения. В базовом наборе logging предоставляет ряд готовых классов-обработчиков:
- SteamHandler — запись в поток, например, stdout или stderr.
- FileHandler — запись в файл, класс имеет множество производных классов с различной функциональностью (ротация файлов логов по размеру, времени и т.д.)
- SocketHandler — запись сообщений в сокет по TCP
- DatagramHandler — запись сообщений в сокет по UDP
- SysLogHandler — запись в syslog
- HTTPHandler — запись по HTTP
Это далеко не полный список. Чтобы посмотреть все, перейдите по ссылке выше. Для указания Handler, необходимо у инстанса Logger вызвать метод addHandler и передать туда инстанс класса Handler. У одного Logger инстанса может быть множество обработчиков.
Пример записи лога в stdout:
import sys import logging from logging import StreamHandler logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) handler = StreamHandler(stream=sys.stdout) logger.addHandler(handler) logger.debug('debug information')
Если запустить этот скрипт, то можно увидеть сообщение:
debug information
Мы видим сообщение уровня DEBUG потому что задачи этот уровень в настройках. Если поменять его на INFO, то сообщение пропадёт:
logger.setLevel(logging.INFO)
Наверняка вы обратили внимание, что лог содержит всего лишь переданную строку. Как сделать так, чтобы в логе была информация об уровне лога, времени записи?
Formatter
Formatter это ёщё один класс в семействе logging, отвечающий за отображение лога. Если класс Handler ответственный за то куда будет происходить запись, то класс Formatter отвечает на вопрос как будет записано сообщение. По умолчанию в лог пишется голая строка, которую мы передаём через методы debug , info и т.д. Давайте обогатим наш лог дополнительной метаинформация, например, о времени записи и уровне сообщения. Formatter передаётся инстансу Handler через метод .setFormatter
import sys import logging from logging import StreamHandler, Formatter logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) handler = StreamHandler(stream=sys.stdout) handler.setFormatter(Formatter(fmt='[%(asctime)s: %(levelname)s] %(message)s')) logger.addHandler(handler) logger.debug('debug information')
Запуск скрипта выведет на экран следующее сообщение:
[2020-11-22 13:00:08,751: DEBUG] debug information
Обратите внимание на строку, которую я передал при инициализации инстанса Formatter :
[%(asctime)s: %(levelname)s] %(message)s
Это шаблон, который будет заполнен при записи сообщения в лог. Набор таких готовых шаблонов можно посмотреть в разделе LogRecord attributes.
Filter
Задача класса фильтровать сообщения по заданной разработчиком логике. Предположим, что я хочу записывать в лог сообщения, содержащие слово python . Чтобы задать фильтр необходимо вызвать метод addFilter у инстанса Logger. Передать можно либо инстанс класса, реализующий метод filter либо callable объект (например, функцию). На вход прилетит инстанс класса LogRecord, это и есть 1 сообщение лога:
import sys import logging from logging import StreamHandler, Formatter, LogRecord def filter_python(record: LogRecord) -> bool: return record.getMessage().find('python') != -1 logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) handler = StreamHandler(stream=sys.stdout) handler.setFormatter(Formatter(fmt='[%(asctime)s: %(levelname)s] %(message)s')) logger.addHandler(handler) logger.addFilter(filter_python) logger.debug('python debug information')
Наглядно и понятно, разве logging может быть сложным?
LoggerAdapter
Адаптер нужен для передачи дополнительной контекстной информации при каждой записи лога через Logger. Например, вы написали веб-приложение и вам необходимо в логи дополнительно передавать username пользователя:
class CustomLoggerAdapter(LoggerAdapter): def process(self, msg, kwargs): return f' from ', kwargs logger2 = logging.getLogger('adapter') logger2.setLevel(logging.DEBUG) handler = StreamHandler(stream=sys.stdout) handler.setFormatter(Formatter(fmt='[%(asctime)s: %(levelname)s] %(message)s')) adapter = CustomLoggerAdapter(logger2, ) logger2.addHandler(handler) adapter.error('failed to save')
extra и не только
Строки в логах это хорошо, а что если я хочу помимо строки дополнительно передавать ответ от веб-сервера? Для этого можно использовать аргумент extra при вызове методов debug , info и т.д. Давайте напишем пример вывода
logger.debug('debug info', extra=)
Теперь вывод значения ключа response можно указать через Formatter (при условии, что response передаётся всегда):
Formatter(fmt='[%(asctime)s: %(levelname)s] %(message)s, response: %(response)s')
Аргумент extra удобен при написании своих кастомных обработчиков логов (например, отсылка логов в телеграм). Далее я покажу пример кастомного Handler класса для отправки логов в Telegram через бота.
Конфигурация logging
Официальная документация рекомендует конфигурировать logging через python-словарь. Для этого необходимо вызвать функцию logging.config.dictConfig и передать ей специальный словарь. Схема словаря описана здесь. Я лишь вкратце пробегусь по основным ключам:
- version — ключ указывает версию конфига, рекомендуется наличие этого ключа со значением 1, нужно для обратной совместимости в случае, если в будущем появятся новые версии конфигов.
- disable_existing_loggers — запрещает или разрешает настройки для существующих логеров (на момент запуска), по умолчанию равен True
- formatters — настройки форматов логов
- handlers — настройки для обработчиков логов
- loggers — настройки существующих логеров
Ранее все настройки я задавал кодом через вызов методов. Это может быть удобно, если у вас 1 модуль, но когда таких модулей становится множество, то в каждом из них задавать общие настройки кажется излишним занятием. Давайте попробуем все настройки задать в одном месте:
import logging.config LOGGING_CONFIG = < 'version': 1, 'disable_existing_loggers': False, 'formatters': < 'default_formatter': < 'format': '[%(levelname)s:%(asctime)s] %(message)s' >, >, 'handlers': < 'stream_handler': < 'class': 'logging.StreamHandler', 'formatter': 'default_formatter', >, >, 'loggers': < 'my_logger': < 'handlers': ['stream_handler'], 'level': 'DEBUG', 'propagate': True >> > logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger('my_logger') logger.debug('debug log')
Неправда ли удобно? В реальных приложениях настройки выносят в отдельный модуль, который обязательно импортируется на старте, например, модуль в settings.py как в Django. Именно в нём задаются глобальные настройки для всех логеров приложения.
Наследование в logging
Ещё одним удобным механизмом в logging является "наследование" настроек корневого логера его потомками. Наследование задаётся через символ . в названии логера. То есть логер с названием my_package.logger1 унаследует все настройки, заданные для my_package . Давайте обновим пример выше, добавив в LOGGING_CONFIG настройку для my_package
LOGGING_CONFIG['loggers'].update( < 'my_package': < 'handlers': ['stream_handler'], 'level': 'DEBUG', 'propagate': False >>)
Если у вас есть настройка для конкретного логера и вы не хотите, чтобы он был дополнительно обработан родительскими Handler классами, то ключу propagate нужно присвоить значение False . В этом случае передача управления "вверх" до родителя будет запрещена.
Отправляем логи в Telegram
А теперь давайте напишем свой кастомный Handler для отправки логов через бота в телеграм. Если вы никогда не работали с телеграм-ботами, то почитайте мою статью про создание телеграм-ботов. Я предполагаю, что вы уже создали бота, получили его токен и знаете свой user-id/chat-id, чтобы бот смог посылать сообщения лично вам. Для работы с телеграмом я использую библиотеку pyTelegramBotAPI.
Чтобы создать свой обработчик, необходимо наследоваться от класса Handler и перезаписать метод emit :
import telebot from logging import Handler, LogRecord class TelegramBotHandler(Handler): def __init__(self, token: str, chat_id: str): super().__init__() self.token = token self.chat_id = chat_id def emit(self, record: LogRecord): bot = telebot.TeleBot(self.token) bot.send_message( self.chat_id, self.format(record) )
При инициализации инстанса класса TelegramBotHandler ему необходимо будет передать токен бота и chat_id . Замечу, что эти настройки можно задать через конфигурирование:
'handlers': < 'telegram_handler': < 'class': 'handlers.TelegramBotHandler', 'chat_id': '', 'token': '', 'formatter': 'default_formatter', > >,
Чтобы обработчик начал свою работу, достаточно в настройках вашего логера прописать новый обработчик:
LOGGING_CONFIG['loggers'].update( < 'my_package': < 'handlers': ['stream_handler', 'telegram_handler'], 'level': 'DEBUG', 'propagate': False >>)
Заключение
В этой статье я постарался вкратце рассказать и показать основные сущности библиотеки logging, а также продемонстрировать гибкий механизм логирования в python. Надеюсь мне это удалось, и статья оказалась для вас полезной.
Интересные записи:
- Pyenv: удобный менеджер версий python
- Что нового появилось в Django Channels?
- Обзор Python 3.9
- Руководство по работе с HTTP в Python. Библиотека requests
- Работа с MySQL в Python
- Django Channels: работа с WebSocket и не только
- Celery: начинаем правильно
- Почему Python?
- FastAPI, asyncio и multiprocessing
- Авторизация через Telegram в Django и Python
- Разворачиваем Django приложение в production на примере Telegram бота
- Python-RQ: очередь задач на базе Redis
- Работа с PostgreSQL в Python
- Как написать Telegram бота: практическое руководство
- Введение в pandas: анализ данных на Python
- Django, RQ и FakeRedis
- Итоги первой встречи Python программистов в Алматы
- Обзор Python 3.8
- Участие в подкасте TalkPython
- Интеграция Trix editor в Django
- Строим Data Pipeline на Python и Luigi
- Видео презентации ETL на Python
- Авторизация через Telegram в Django приложении