Python использует Logging для записи программных журналов
Эта статья начнется с основ и на простых примерах научит вас, как использовать ведение журналов для записи журналов программ.
logging Quick start
Почему бы не использовать печать?
Ведение журнала является чрезвычайно важной частью развертывания программы в производственной среде, и logging полезно для программ тремя способами:
- Ведение журнала состояния: для каждого момента времени важно записывать состояние бизнес-операции в это время, и часто вам нужно полагаться на эту информацию для определения бизнес-проблем.
- Воспроизведение ошибок: Производственные среды не оборудованы для одноэтапной отладки, а ошибки в производственных средах чрезвычайно трудно воспроизвести, поэтому ведение журнала является единственным и наиболее эффективным средством воспроизведения ошибок.
- Установление уровней: установите разные уровни вывода для разных модулей, чтобы файлы журналов выглядели более лаконичными.
logging — это собственная библиотека ведения журналов Python, которая сочетает в себе возможности ведения журналов с гибкостью записи в файл, печати на консоль или на удаленный HTTP/FTP-сервер, независимо от того, хотите ли вы делать все это.
Примеры использования logging
Для начала вы можете увидеть официальный пример, который будет использоваться в качестве основы для остальной части объяснения, с некоторыми изменениями, чтобы упростить его выполнение.
Демо:
- main.py
- auxiliary_module.py
main.py
import logging
import auxiliary_module
# create logger with 'spam_application'
logger = logging.getLogger('spam_application')
logger.setLevel(logging.DEBUG)
# create file handler which logs even debug messages
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fh)
logger.addHandler(ch)
logger.info('creating an instance of auxiliary_module.Auxiliary')
a = auxiliary_module.Auxiliary()
logger.info('created an instance of auxiliary_module.Auxiliary')
logger.info('calling auxiliary_module.Auxiliary.do_something')
a.do_something()
logger.info('finished auxiliary_module.Auxiliary.do_something')
logger.info('calling auxiliary_module.some_function()')
auxiliary_module.some_function()
logger.info('done with auxiliary_module.some_function()')
auxiliary_module.py
import logging
# create logger
module_logger = logging.getLogger('spam_application.auxiliary')
class Auxiliary:
def __init__(self):
self.logger = logging.getLogger('spam_application.auxiliary.Auxiliary')
self.logger.info('creating an instance of Auxiliary')
def do_something(self):
self.logger.info('doing something')
a = 1 + 1
self.logger.info('done doing something')
def some_function():
module_logger.info('received a call to "some_function"')
Запустите main.py файл напрямую и получите следующий результат.
2020-07-21 00:39:24,997 - spam_application - INFO - creating an instance of auxiliary_module.Auxiliary
2020-07-21 00:39:24,997 - spam_application.auxiliary.Auxiliary - INFO - creating an instance of Auxiliary
2020-07-21 00:39:24,998 - spam_application - INFO - created an instance of auxiliary_module.Auxiliary
2020-07-21 00:39:24,998 - spam_application - INFO - calling auxiliary_module.Auxiliary.do_something
2020-07-21 00:39:24,998 - spam_application.auxiliary.Auxiliary - INFO - doing something
2020-07-21 00:39:24,998 - spam_application.auxiliary.Auxiliary - INFO - done doing something
2020-07-21 00:39:24,998 - spam_application - INFO - finished auxiliary_module.Auxiliary.do_something
2020-07-21 00:39:24,998 - spam_application - INFO - calling auxiliary_module.some_function()
2020-07-21 00:39:24,999 - spam_application.auxiliary - INFO - received a call to "some_function"
2020-07-21 00:39:24,999 - spam_application - INFO - done with auxiliary_module.some_function()
Рекомендуется более подробно ознакомиться с демонстрационным кодом, который фактически охватывает основы ведения журнала, в котором есть несколько встроенных объектов.
- Logger: объект Logger, если он не инициализирован активно, будет объектом Logger по умолчанию при вызове logging.info('hello world').
- LogRecord: регистратор получает журналы, переданные объектом Logger, и отправляет их соответствующему обработчику Handler.
- Handler: базовый процессор, который выводит сгенерированные журналы в указанное место (т. е. определяет, отправляются ли журналы в службу вывода desk/HTTP server/local).
- Filter: фильтр, обеспечивающий лучшую детализацию для управления фильтрацией некоторого определенного выходного содержимого.
- Formatter: средство форматирования, которое работает с обработчиком для реализации различных форматов вывода журнала.
Официальный пример схемы рабочего процесса ведения журнала (не волнуйтесь, если вы этого не понимаете, продолжайте читать и вернитесь к этой схеме позже, чтобы убедиться, что это несложно).
Для простоты использования нам нужно сосредоточиться только на трех частях, а именно: Logger/Handler/Formatter.
Просто нужно освоить Logger/Handler/Formatter
Logger является основным объектом ведения журнала, например, в примере кода используется logging.getLogger(‘spam_application’) для получения объекта ведения журнала с именем spam_application.
Затем, изменив некоторые свойства этого объекта ведения журнала, мы можем получить настраиваемый объект ведения журнала, который будет снова использоваться в других модулях, так что выполненная ранее настройка вступит в силу.
Затем проанализируйте каждое предложение примера кода по очереди, чтобы увидеть, что достигается.
setLevel(logging.DEBUG)
Установите этот объект ведения журнала для вывода журналов на уровне Debug и выше. Официальная документация для уровня ведения журнала приведена ниже, если установлен уровень Debug, то будут выводиться все журналы со значениями > Уровень DEBUG.
Level | Value |
CRITICAL | 50 |
FATAL | 50 |
ERROR | 40 |
WARNING | 30 |
WARN | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
fh = logging.FileHandler('spam.log')
Создается обработчик с именем FH, указывающий выходной формат цели вывода журнала DEBUG, и то же самое для объекта ch, где FileHandler указывает, что это файловый обработчик вывода, который будет выводить журнал в файл, разные обработчики имеют различные параметры, а список общих обработчиков выглядит следующим образом.
Обработчик | Описание |
StreamHandler | Вывод журнала в поток, либо sys.stderr, sys.stdout, либо в пользовательский файл. |
FileHandler | Вывод журналов в файл |
BaseRotatingHandler | Прокат журналов |
RotatingHandler | Вывод скользящего журнала, который позволяет вам контролировать количество журналов. |
TimeRotatingHandler | Поддержка прокрутки файлов журнала по времени. |
SocketHandler | Выводить журналы по протоколу socket на указанную удаленную машину |
SMTPHandler | Вывод журналов на smtp-сервер (email) |
SysLogHandler | Вывод в системный журнал |
HTTPHandler | Вывод на HTTP-сервер |
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
formatter создан. Параметры в нем настраиваемые и список параметров выглядит следующим образом.
Параметры | Описание |
%(levelno)s | Значения уровня журнала |
%(levelname)s | Имя уровня журнала |
%(pathname)s | Путь к выполняемой в данный момент программе, которая на самом деле является sys.argv[0] |
%(filename)s | Имя выполняемой в данный момент программы |
%(funcName)s | Название функции |
%(lineno)d | Номер строки кода |
%(asctime)s | Время печати |
%(thread)d | Идентификатор темы |
%(threadName)s | Название потока |
%(process)d | Идентификатор процесса |
%(processName)s | Имя процесса |
%(module)s | Имя модуля |
%(message)s | Сообщения журнала |
Оставшиеся четыре строки устанавливают соответствующий объект форматирования/объект процессора для указанного объекта для завершения вывода, и с этим пониманием ведение журнала можно использовать для базового использования без каких-либо проблем.
Если вы заинтересованы в перехвате исключений/настраиваемых файлах конфигурации, вы можете прочитать об этом в следующем разделе.
Гибкое ведение журнала
Выход исключения
Что касается вывода исключения, следующий вывод придет вам на ум при первом его написании.
import logging
try:
result = 10 / 0
except Exception as e:
logging.error('Error as %s', e)
Тот факт, что вывод e напрямую показывает информацию о стеке, не очевиден. Более рекомендуется использовать logging.error('Error as',exc_info=True)/logging.exception('Error')
для печати информации о стеке в обоих направлениях.
Пользовательская конфигурация файла журнала
logging поддерживает настраиваемые файлы конфигурации ведения журнала, просто используйте следующий код, чтобы прочитать файл конфигурации ведения журнала.
import logging.config
import yaml
with open('logging.yaml','r',encoding='utf-8') as f:
config = yaml.load(f)
logging.config.dictConfig(config)
Файлы конфигурации поддерживают широкий спектр форматов, включая config/ini/yaml/json и т. д., и все они могут использоваться в качестве файлов конфигурации ведения журнала.
Ниже приведен файл конфигурации yaml, который автоматически прокручивается по дате.
version: 1
formatters:
common:
format: "%(asctime)s - %(levelname)s- %(threadName)s - %(message)s"
datefmt: "%Y/%m/%d %H:%M:%S"
console:
format: "%(asctime)s - %(levelname)s- %(pathname)s - %(message)s"
datefmt: "%Y/%m/%d %H:%M:%S"
handlers:
# No logs greater than or equal to this level will be output.
common:
class: logging.handlers.TimedRotatingFileHandler
formatter: common
level: INFO
when: D
interval: 1
encoding: utf8
filename: "app.log"
# suffix: "%Y-%m-%d.log"
# Number of logs to keep
backupCount: 7
console:
class : logging.StreamHandler
formatter: brief
level : INFO
stream : ext://sys.stdout
loggers:
main.business:
level: INFO
handlers: [common]
# If the module does not use a configuration such as logging.getLogger('main.business') to get the corresponding configuration in loggers, the following root configuration will be used by default.
root:
level: DEBUG
handlers: [console]
Соответствующий код можно найти здесь.
Для более сложного использования рекомендуется обратиться к официальной документации.