Настройка ведения журнала третьей стороны script
У меня есть сторонняя консоль python script, источник которой я не хочу изменять.
Но я хочу настроить ведение журнала, которое выполняется script и его библиотеками. script использует стандартное ведение журнала python, но не поддерживает его конфигурацию.
script использует этот шаблон:
import logging
logger=logging.getLogger(__name__)
Варианты использования:
- Я хочу, чтобы сообщения INFO файла foo.py игнорировались.
Я хочу включить PID в сообщения об ошибках.
Как настроить ведение журнала, если я не хочу изменять источники консоли script?
script вызывается через cron
.
Как я могу настроить ведение журнала, если этот script?
Внимание!
Создание обертки script, как в этот ответ, не является для меня решением.
Иерархия процессов linux выглядит так:
Cron -> third_party_script
Должны быть какие-либо "клей", "обертывание" или "грязный взлом" script между cron и third_party_script
.
Почему навязчивый/нетпикинг?
Я хочу практиковать "разделение проблем". Я хочу иметь возможность настраивать ведение журнала один раз и в одном месте. Эта конфигурация должна использоваться всеми кодами python для virtualenv. Написание обертки было бы обходным. Я хочу решение.
Библиотека не должна настраивать ведение журнала - это до разработчика приложения. Ответ Inbar Rose не совсем прав. Если модуль, на который вы ссылаетесь, называется foo
, то ссылка на __name__
в своем вызове getLogger
будет передаваться в foo
. Поэтому в вашем конфигурационном коде вам нужно будет сделать эквивалент
logging.getLogger('foo').setLevel(logging.WARNING)
Чтобы включить PID в журналы, просто убедитесь, что вы используете соответствующую строку форматирования для вашего Форттера, т.е. тот, который включает %(process)d
. Простой пример:
logging.basicConfig(format='%(process)d %(message)s')
Обратите внимание, что вы не можете одновременно записывать один файл журнала из нескольких процессов - вам может потребоваться альтернативный подход если вы хотите это сделать.
Обновление: Разработчик приложений - это тот, кто пишет код Python, который не является библиотекой, но вызывается, например, пользователя или другого script через командную строку или другие средства создания процесса Python.
Чтобы использовать код, который я написал выше, нет необходимости обертывать или модифицировать сторонний код, если он является библиотекой. Например, в главном script, который вызывает стороннюю библиотеку:
if __name__ == '__main__':
# configure logging here
# sets the third party logger to do WARNING or greater
# replace 'foo' with whatever the top-level package name your
# third party package uses
logging.getLogger('foo').setLevel(logging.WARNING)
# set any other loggers to use INFO or greater,
# unless otherwise configured explicitly
logging.basicConfig(level=logging.INFO, format='%(process)d %(message)s')
# now call the main function (or else inline code here)
main()
Если сторонний код работает через cron, это не код библиотеки - это приложение, и вам, вероятно, не повезло.
Я задал этот вопрос несколько месяцев назад. К сожалению, я не получил ответа, который удовлетворил меня.
Для меня важно различие между использованием журнала и его настройкой.
Это мое решение: в нашем контексте мы настраиваем ведение журнала в методе, который вызывается в usercustomize.py
.
Таким образом, дополнительные плагины могут использовать регистрацию без необходимости ее настройки.
Это почти решило все мои потребности.
До сих пор я не нашел лучшего способа, чем usercustomize.py
. Моим идеальным решением я бы назвал virtualenvcustomize.py
: Некоторый код инициализации, который запускается, если интерпретатор загружает virtualenv. До сих пор я не мог найти такой крючок. Пожалуйста, дайте мне знать, если у вас есть решение.
Несколько возможностей:
Упаковочный
Если вы можете отредактировать свою таблицу cron, вы можете создать небольшой script в python, который получит регистратор lib, удалите существующий обработчик журнала и подключите к нему свой собственный обработчик:
# Assumes the lib defines a logger object
from third_party_lib import *
# Note this assumes only one handler was defined by the lib
logger.removeHandler(logger.handlers[0])
# Then we can hook our custom format handler
custom_handler = logging.StreamHandler(sys.stdout)
custom_handler.setFormatter(logging.Formatter(format = '%(asctime)s %(levelname)s %(name)s %(process)d: %(message)s', None))
logger.addHandler(custom_handler)
logger.setLevel(logging.WARNING)
Также помните об этом, предположим, что lib не повторно объявляет регистратор в пути.
Редактирование динамического кода
Если у вас нет возможности изменить вызов cron, вы можете выполнить динамическое редактирование кода, но это эквивалентно редактированию файла вручную (hacky):
- получить файл сторонних разработчиков, содержащий конфигурацию журнала
изменить и сохранить измененную версию
задание cron запускает задачу с использованием стороннего кода
после выполнения задания cron вы восстанавливаете файл в исходное состояние.
Вы можете изменить минимальный уровень журнала этого регистратора.
logging.getLogger(__name__).setLevel(logging.WARNING)
Теперь будет показано только ПРЕДУПРЕЖДЕНИЕ и выше. Нет INFO и DEBUG.
Кроме того, вы также можете изменить формат. %(process)d
является PID.
log_format = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(process)d: %(message)s', '%H:%M:%S')
logging.getLogger(__name__).setFormatter(log_format)
Все вместе:
log_format = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(process)d: %(message)s', '%H:%M:%S')
log_handle = logging.getLogger(__name__)
log_handle.setLevel(logging.WARNING)
log_handle.setFormatter(log_format)
Примечание. Вы должны заменить __name__
в вашем коде соответствующим обработчиком журнала.
ТЛ; др
Короче говоря, мы хотим внедрить код, который выполняется интерпретатором python до того, как будет выполнен наш основной код.
Лучший способ добиться этого - создать virtualenv и добавить sitecustomize.py
в пакеты сайта virtualenv.
демонстрация
Предположим, что приложение, которое мы хотим запустить, называется my_app.py
и его регистратор имеет то же имя.
$ cat my_app.py
import logging
logger = logging.getLogger("my_app")
logger.debug("A debug message")
logger.info("An info message")
logger.warning("A warning message")
logger.error("An error message")
Запуск my_app.py
должен показывать только те сообщения, уровень серьезности которых> WARNING
(это поведение по умолчанию при ведении журнала python).
$ python my_app.py
A warning message
An error message
Теперь давайте создадим виртуальную среду
python3 -m venv my_venv
И давайте добавим sitecustomize.py
к пакетам сайта virtualenv.
$ cat my_venv/lib/python3.7/site-packages/sitecustomize.py
import logging
# Setup logging for my_app
# We will only setup a console handler
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setFormatter(
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
)
logger.addHandler(ch)
Теперь давайте попробуем запустить my_app.py
используя virtualenv:
$ ./my_venv/bin/python my_app.py
2019-01-25 16:03:16,815 - my_app - DEBUG - A debug message
2019-01-25 16:03:16,815 - my_app - INFO - An info message
2019-01-25 16:03:16,815 - my_app - WARNING - A warning message
2019-01-25 16:03:16,815 - my_app - ERROR - An error message
И это было все) Мы получили правильную регистрацию без изменения my_app.py
или написания оболочки!
Теперь, если вы хотите знать, почему это оптимальный подход, продолжайте читать.
(Действительно) длинный ответ Прежде чем понять, почему использование virtualenv + sitecustomize.py
является правильным подходом к этой проблеме, необходимо сделать не очень краткое введение.
Примечание. Я предполагаю, что вы создаете виртуальные среды с venv
модуля venv
который использует stdlib site.py
Библиотека virtuaelnv
использует свой собственный site.py
и может делать что-то немного иначе. Тем не менее, прочитав этот ответ, вы сможете проверить, есть ли какие-либо различия между venv
и vitualenv
и понять, как с ними справиться.
Что такое site-packages
Короткий ответ: site-packages
- это место, где python устанавливает сторонний код (как в не -s коде tdlib). Для получения дополнительной информации прочитайте это и предоставленные ссылки.
Как ввести код?
Python позволяет вам настроить интерпретатор python во время его запуска, то есть до того, как будет выполнен наш основной код/скрипт/что угодно. Это может быть полезно, например:
- кода покрытия профайлеры и вообще вводить код, как нам нужно сделать для этого вопроса.
Способ внедрения - это создание/изменение либо sitecustomize.py
либо usercustomize.py
. Вы также можете использовать файл "путь конфигурации" (т.е. *.pth
) с оператором импорта, но я не буду здесь рассматривать этот случай, так как:
- Эти заявления об импорте кажутся ужасным взломом Я не уверен, что он дает какие-либо реальные преимущества по сравнению с
sitecustomize
/usercustomize
. Я хотел бы сделать вещи простыми В любом случае, если вам нужна дополнительная информация WRT к файлам конфигурации пути, вы можете проверить PyMOTW и, если вам нужен пример использования их с оператором импорта, проверить этот пост в блоге.
sitecustomize
& usercustomize
Итак, sitecustomize
и usercustomize
- это специальные файлы, которые по умолчанию не существуют, но если мы их создадим, python автоматически импортирует их, прежде чем начнет выполнять наш код. Мы можем создать эти файлы:
- либо в глобальных пакетах сайта (например,
/usr/lib/python3.7/site-packages/
) или на сайте пользователя-пакета (например, ~/.local/lib/python3.7/site-packages/
) sitecustomize
всегда импортируется перед usercustomize
. Если ImportError
либо из файлов отсутствует, ImportError
игнорируется.
В качестве меры безопасности, если существует несоответствие между идентификатором пользователя или группы и эффективным идентификатором, то пользовательские пакеты сайта отключаются (источник). Более того, интерпретатор python имеет аргументы CLI, которые либо полностью отключают пакеты сайта (как системные, так и пользовательские) или отключают пакеты сайта пользователя. Если предположить, что у нас нет несовпадения идентификаторов и что мы не используем какие-либо флаги CLI, то пользовательские пакеты сайтов имеют более высокий приоритет, чем системные пакеты сайтов. Так что, если у нас есть оба:
~/.local/lib/python3.7/site-packages/sitecustomize.py
/usr/lib/python3.7/site-packages/sitecustomize.py
первый - тот, который будет импортирован. На самом деле мы можем проверить приоритет sys.path, выполнив модуль site.py
:
$ python3 -msite
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/home/username/.local/lib/python3.7/site-packages', # user site-packages
'/usr/lib/python3.7/site-packages', # system site-packages
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: True
Важной информацией здесь является значение ENABLE_USER_SITE
. Если это True
то пользовательские пакеты сайта включены. Если это False
то мы можем использовать только глобальные пакеты сайта. Например, если мы используем python -s
:
$ python3 -s -msite
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: False
Обратите внимание, что в этом случае ENABLE_USER_SITE
имеет значение False
.
Просто для полноты позвольте полностью отключить пакеты сайта:
$ python3 -S -msite
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: None
эксперимент
Чтобы лучше это понять, давайте проведем эксперимент. Сначала давайте создадим модули usercustomize
sitecustomize
как в системных, так и в пользовательских пакетах сайта.
ВНИМАНИЕ: Мы будем создавать файлы в системных пакетах сайта. Это будет мешать вашему дистрибутиву Python. Будьте осторожны и ПОМНИТЕ, чтобы удалить их, когда мы закончим.
# system site packages
echo 'print(f"-> {__file__}")' | sudo tee /usr/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | sudo tee /usr/lib/python3.7/site-packages/sitecustomize.py
# user site packages
echo 'print(f"-> {__file__}")' | tee ~/.local/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | tee ~/.local/lib/python3.7/site-packages/sitecustomize.py
Позвольте также создать модуль Python:
echo 'print("Inside foo")' | tee foo.py
Теперь давайте foo.py
:
$ python3 foo.py
-> /home/username/.local/lib/python3.7/site-packages/sitecustomize.py
-> /home/username/.local/lib/python3.7/site-packages/usercustomize.py
Inside foo
Как мы можем видеть:
sitecustomize
как sitecustomize
и usercustomize
они импортируются из пользовательских пакетов сайта Что будет, если мы отключим пользовательские пакеты сайтов?
$ python3 -s foo.py
-> /usr/lib/python3.7/site-packages/sitecustomize.py
Inside foo
На этот раз мы видим, что мы:
- только
sitecustomize
импортируется. Хотя usercustomize
существует в системных пакетах сайта, python не импортирует его! Это важно! Имейте это в виду, когда мы обсуждаем virtualenvs! (СОВЕТ: это связано с ENABLE_USER_SITE
; вы помните, какое значение имеет в этом случае?) sitecustomize
импортируется из системных пакетов сайта Наконец, если мы полностью usercustomize
пакеты сайтов, очевидно, что usercustomize
и sitecustomize
будут игнорироваться:
$ python3 -S foo.py
Inside foo
Как насчет virtualenvs?
Хорошо, теперь давайте добавим в игру virtualenv. Есть два типа virtualenvs:
- нормальные созданные с помощью
--system-site-packages
. Пусть создадут virtualenvs обоих типов
python3 -mvenv venv_no_system
python3 -mvenv venv_system
Также sitecustomize.py
usercustomize.py
модули sitecustomize.py
и usercustomize.py
в site-пакеты virtualenv:
echo 'print(f"-> {__file__}")' | tee ./venv_no_system/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | tee ./venv_no_system/lib/python3.7/site-packages/sitecustomize.py
echo 'print(f"-> {__file__}")' | tee ./venv_system/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | tee ./venv_system/lib/python3.7/site-packages/sitecustomize.py
и давайте посмотрим на различия:
$ ./venv_no_system/bin/python -msite
/tmp/test/venv_no_system/lib/python3.7/site-packages/sitecustomize.py
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/tmp/test/venv_no_system/lib/python3.7/site-packages',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: False
Что мы видим здесь? На обычном virtualenv ENABLE_USER_SITE
имеет значение False
, что означает, что:
sitecustomize
импортируется! Т.е. мы не можем использовать usercustomize
для внедрения кода !!! Мы также видим, что вместо наших глобальных пакетов сайтов, virtualenv использует свой собственный (т.е. /tmp/test/venv_no_system/lib/python3.7/site-packages
).
Теперь давайте повторим это, но на этот раз с virtualenv, который использует системные пакеты сайта:
$ ./venv_system/bin/python -msite
-> /home/username/.local/lib/python3.7/site-packages/sitecustomize.py
-> /home/username/.local/lib/python3.7/site-packages/usercustomize.py
sys.path = [
'/tmp/test',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/tmp/test/venv_system/lib/python3.7/site-packages',
'/home/username/.local/lib/python3.7/site-packages',
'/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: True
В этом случае поведение отличается...
ENABLE_USER_SITE
- это True
что означает:
usercustomize
импортируется нормально. Но есть и еще одно отличие. В этом случае у нас есть 3 каталоги сайтов-пакетов. Virtualenv - это пакет с более высоким приоритетом, за которым следуют пользовательские пакеты сайтов, а системные пакеты сайта - последние.
Так что использовать?
Я думаю, что есть три варианта:
использовать системную установку Python использовать обычный virtualenv использовать virtualenv с системными сайтами-пакетамиЯ думаю, что в большинстве случаев, например, на обычных серверах/десктопах, изменения системы Python, как правило, следует избегать. По крайней мере, на * nix, слишком многое зависит от Python. Я бы очень не хотел менять свое поведение. Возможными исключениями являются эфемерные или статические "системы" (например, внутри контейнера).
Что касается виртуальных сред, если мы не знаем, что нам понадобятся системные пакеты сайтов, я думаю, что имеет смысл придерживаться текущей практики и использовать обычные. Если мы придерживаемся этого, то для внедрения кода перед выполнением нашего скрипта у нас есть только одна опция:
Добавить
sitecustomize.py
в пакеты сайта virtuaelenv.
Убираться
# remove the virtualenvs
rm -rf my_venv
rm -rf venv_system
rm -rf venv_no_system
# remove our usercustomize.py and sitecustomize.py
sudo rm /usr/lib/python3.7/site-packages/sitecustomize.py
sudo rm /usr/lib/python3.7/site-packages/usercustomize.py
rm ~/.local/lib/python3.7/site-packages/sitecustomize.py
rm ~/.local/lib/python3.7/site-packages/usercustomize.py
# remove the modules
rm foo.py
rm my_app.py