Что такое логирование в python
Перейти к содержимому

Что такое логирование в python

  • автор:

Логирование в Python

Логирование — это процесс записи потока кода при его выполнении наряду с записью любых других событий. Запись происходит в файлы, которые впоследствии можно использовать для анализа и устранения возникающих неполадок.

  • В отладке кода для определения потока исходного кода во время разработки и после развертывания.
  • Оповещении об исключительном событии, вызванном кодом. Например, нехватке памяти и т. д.
  • Поиске пользователей или систем, обращающихся к коду.

Обновлено: 2023-06-22 17:36:35 Вадим Дворников автор материала

Что должно записываться в логи?

Логи должны содержать сообщение, в котором указывается:

  • Информация о доступе : пользователи и устройства, имеющие доступ к коду.
  • Версия кода : текущая редакция приложения.
  • Отметка времени : отметки времени всех ключевых событий.
  • Результаты : результаты переменных, вычисленных в коде.
  • Исключения , возникающие вместе с трассировкой стека.
  • Поток кода : различные классы и функции, вызываемые во время выполнения кода.

Как реализовать логирование в Python?

Python предоставляет библиотеку «logging» для записи сообщений в файл или любой другой поток вывода.

Различные уровни логирования в порядке их сложности :

  1. DEBUG : используется только при диагностике проблем.
  2. INFO : только для информации, используемой для понимания потока кода при диагностике проблемы.
  3. WARNING : когда возникает непредвиденная ситуация, но код все еще выполняется.
  4. ERROR : когда код не может выполнять какую-либо функцию.
  5. 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')

Как реализовать логирование в Python?

Вывод в консоль

Мы логируем структуру сообщений уровня 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')

Как вывести сообщения для уровней сложности Debug и Info?

Вывод в консоль

Метод 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 в режиме добавления с уровнем сложности DEBUG

Файл 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 в режиме добавления с уровнем сложности DEBUG

Файл лога 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)

Логирование трассировки стека

Заключение

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

Эффективное логирование в 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-приложении с помощью стандартной библиотеки `logging` в этой информативной статье для новичков!

Python performing logging process.

Алексей Кодов
Автор статьи
23 июня 2023 в 18:49

Логирование является важной частью разработки программного обеспечения, так как оно помогает отслеживать события и действия в приложении. В этой статье мы рассмотрим, как настроить логирование в Python-приложении с использованием стандартной библиотеки logging .

Шаг 1: Импортировать модуль logging

Для начала, импортируйте модуль logging в вашем коде:

import logging

Шаг 2: Настроить базовый уровень логирования

Затем, настройте базовый уровень логирования, чтобы контролировать, какие сообщения будут записаны в журнал. Уровни логирования, начиная с самого высокого:

  • CRITICAL: 50
  • ERROR: 40
  • WARNING: 30
  • INFO: 20
  • DEBUG: 10
  • NOTSET: 0
logging.basicConfig(level=logging.DEBUG)

В приведенном выше примере уровень логирования установлен на DEBUG, что означает, что будут записаны все сообщения, начиная с DEBUG и выше.

Python-разработчик: новая работа через 9 месяцев
Получится, даже если у вас нет опыта в IT

Шаг 3: Записать сообщения в журнал

Теперь вы можете использовать различные методы, предоставляемые модулем logging , чтобы записывать сообщения разных уровней. Например:

logging.debug('Это сообщение DEBUG') logging.info('Это сообщение INFO') logging.warning('Это сообщение WARNING') logging.error('Это сообщение ERROR') logging.critical('Это сообщение CRITICAL')

�� Обратите внимание, что сообщения будут отображаться в соответствии с установленным уровнем логирования.

Шаг 4: Настроить формат сообщений

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

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)

После применения этого формата, сообщения в журнале будут выглядеть следующим образом:

2021-09-01 15:00:00,000 - root - DEBUG - Это сообщение DEBUG 2021-09-01 15:00:00,000 - root - INFO - Это сообщение INFO 

Шаг 5: Настроить вывод логов

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

logging.basicConfig(filename='app.log', filemode='w', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)

Теперь все сообщения будут записываться в файл app.log вместо вывода на консоль.

�� Надеюсь, эта статья поможет вам настроить логирование в вашем Python-приложении. Не забудьте использовать хорошие практики логирования и учесть разные уровни логов, чтобы сделать ваше приложение более информативным и удобным для отладки.

Логирование в Питоне

Логирование является важной частью разработки. То, что правильно построенные логи сильно облегчают отладку и дальнейшее сопровождение проекта — сейчас, кажется, неоспоримая истина и общее место.

В Питоне существуют несколько способов делать это. Простейший — выводить всю необходимую информацию через print. Достоинства очевидны — простота. Недостатки тоже — отсутствие гибкости в настройках.

Поэтому появилось довольно много библиотек для решения этой проблемы. Я не буду даже пытаться их перечислять, остановлюсь на стандартном пакете logging. Он весьма мощен, допускает гибкую настройку, иерархические категории, вывод логов в любой мыслимый источник, указание желаемого форматирования сообщений. И, главное, logging — это стандарт, включенный в любую поставку Питона. Архитектурно он вырос из знаменитого log4j (Apache Group), который имеет множество клонов на других языках программирования.

Я не буду описывать тривиальные шаги по настройке logging системы и выводу простейших логов. Все это можно прочитать в стандартной документации модуля logging и ее части, посвященной простому примеру использования.

Перейду к опыту применения. Первый же вопрос, который возникает — как отличать записи из разных мест. Поясню на примере.

# module sample_mod import logging def a(): logging.debug("I'm here") . def b(): logging.debug("I'm here") . 

Совершенно непонятно, кто источник сообщения. Конечно, можно настроить формат так, что будет указан полный путь к файлу и номер строки в нем, но это сильно загромождает вывод. Писать с явным указанием источника:

# module sample_mod def a(): logging.debug("sample_mod:a - I'm here") . 

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

# module sample_mod class A(object): cls_logger = logging.getLogger('sample_mod.A') def __init__(self): self.inst_logger = logging.getLogger('sample_mod.A.0x%008x'%id(self)) . def f(self): self.cls_logger.debug("class logger message") def g(self): self.inst_logger.debug("instance logger message") 

Отлично. Теперь мы можем писать что-то вроде:

>>> a = A() >>> a.f() DEBUG:sample_mod.A:class logger message >>> a.g() DEBUG:sample_mod.A.0x009fb650:class logger message 

Вроде бы все хорошо, но есть пара «но». Первое очевидно — нужно явно указывать имя логера, хотя оно собирается из имени модуля, имени класса (и адреса инстанции для второго случая). Все это можно спросить у Питона, но вот незадача — если в конструкторе все уже есть и мы можем просто написать

self.inst_logger = logging.getLogger('%s.%s.0x%008x'%(self.__class__.__module__, self.__class__.__name__,id(self)))

то при создании логера для класса спросить __class__ просто не у кого.

Есть еще одна проблема: поскольку не предусмотрено удаление логеров, то для каждого нового объекта делается новый, а старые продолжают жить. Мало того, что неограниченно растет память, так еще и создание занимает ощутимое количество времени. Я подсмотрел, как это делают в SQLAlchemy:

def _get_instance_name(instance): return instance.__class__.__module__ + "." + instance.__class__.__name__ + ".0x.." + hex(id(instance))[-2:] def instance_logger(instance): return logging.getLogger(_get_instance_name(instance)) def class_logger(cls): return logging.getLogger(cls.__module__ + "." + cls.__name__) 

Что ж, довольно элегантно. Берем последние две цифры адреса. Различать экземпляры объектов вполне можно, а память расти не будет. Вполне подойдет. Пример модифицировался так:

class A(object): def __init__(self): self.inst_logger = instance_logger(self) . A.cls_logger = class_logger(A) 

Все почти хорошо. Только мне очень не нравится модифицирование класса после создания при добавлении cls_logger. Не потому, что считаю подобные действия абсолютным злом — наоборот, очень часто пользуюсь. Причина другая — легко не заметить этот код. Да и привык я описывать переменные класса в его начале. Мысль завертелась вокруг инструментирования кода. В который раз посетовав на отсутствие декораторов класса (не ждать же Python 2.6), начал строить все на метаклассах. Но с ними тоже все не хорошо. Во-первых, пришлось бы явно указывать метакласс или навязывать свой базовый. Во-вторых и главных: как только в предках появляется два разных метакласса, Питону становится нехорошо и он впадает в ступор. И, конечно же, в моем проекте это вылезло почти сразу. Я уже начал огорченно менять иерархию наследования, добавляя ненужные костыли, как вспомнил о дескрипторах. Решение оказалось очень простым:

class class_logger(object): '''Class logger descriptor''' def __init__(self): self._logger = None def __get__(self, instance, owner): if self._logger is None: self._logger = logging.getLogger(owner.__module__ + "." + owner.__name__) return self._logger class instance_logger(object): '''Instance logger descriptor''' def __init__(self): self._logger = None def __get__(self, instance, owner): if self._logger is None: self._logger = logging.getLogger(_get_instance_name(instance)) return self._logger 

а пример использования весьма лаконичным:

class A(object): cls_logger = class_logger() inst_logger = instance_logger() def __init__(self): . 

Преимущества очевидны: логер (как для класса, так и для объекта) описывается одной строкой, никаких дополнительных трюков не требуется, конструктор класса не содержит никакого дополнительного кода. К тому же все прекрасно видно, а параметрами для class_logger и instance_logger можно передавать какие-нибудь настроечные параметры. На этом стоит поставить точку и привести получившийся код целиком (он до смешного короток):

# module logutils import sys import new import logging #copy public content of logging package for name in dir(logging): if not name.startswith('_'): globals()[name] = getattr(logging, name) def _get_instance_name(instance): return instance.__class__.__module__ + "." + instance.__class__.__name__ + ".0x.." + hex(id(instance))[-2:] class class_logger(object): '''Class logger descriptor''' def __init__(self): self._logger = None def __get__(self, instance, owner): if self._logger is None: self._logger = logging.getLogger(owner.__module__ + "." + owner.__name__) return self._logger class instance_logger(object): '''Instance logger descriptor''' def __init__(self): self._logger = None def __get__(self, instance, owner): if self._logger is None: self._logger = logging.getLogger(_get_instance_name(instance)) return self._logger 

�� Подобається Сподобалось 0

До обраного В обраному 0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *