Кеннет Рейтц – Автостопом по Python (страница 26)
Для того чтобы еще лучше понять Diamond, запустим его. Нам нужен модифицированный файл конфигурации, который мы можем поместить в созданный нами каталог Diamond/tmp. Находясь в нем, введите следующий код:
(venv)$ mkdir tmp
(venv)$ cp conf/diamond.conf.example tmp/diamond.conf
Далее отредактируйте файл tmp/diamond.conf, чтобы он выглядел так:
Из этого примера конфигурации видно следующее.
Далее запустите Diamond, указав, что журнал будет сохраняться по адресу /dev/stdout (будет использована стандартная конфигурация форматирования), что приложение не должно работать в фоновом режиме, что нужно опустить запись в файл PID и использовать новые файлы конфигурации:
(venv)$ diamond — l — f — skip-pidfile — configfile=tmp/diamond.conf
Для того чтобы завершить процесс, нажимайте Ctrl+C до тех пор, пока снова не появится командная строка. Журнал показывает, что делают сборщики и обработчики: сборщики собирают разные метрики (вроде объема общей, свободной памяти и памяти подкачки от MemoryCollector), которые обработчики форматируют и отправляют в разные точки назначения вроде Graphite, MySQL (в нашем тестовом случае — как сообщения журнала в /dev/stdout).
Для чтения более крупных проектов лучше использовать IDE — с их помощью вы можете быстро обнаружить оригинальное определение функций и классов исходного кода (при наличии определения можете найти все места в проекте, где задействованы функция или класс). Для использования этой функциональности укажите интерпретатору Python вашей IDE применять одну из виртуальных сред[57].
Вместо того чтобы разбирать каждую функцию, как мы сделали это для HowDoI, изучим рис. 5.2 (показаны операторы импорта). Схема демонстрирует, какие модули Diamond импортируют другие модули. Подобные рисунки помогают понять более крупные проекты. Можно начать с исполняемого файла diamond в левом верхнем углу и следовать всем операторам импорта в проекте Diamond. Помимо исполняемого файла diamond, в каждом квадрате указывается файл (модуль) или папка (пакет) в каталоге src/diamond.
Хорошо организованные и удачно названные модули Diamond позволяют понять идею кода, просто взглянув на схему: модуль diamond получает версию из util, затем настраивает журналирование с помощью utils.log и запускает экземпляр сервера с помощью server. Сервер импортирует почти все вспомогательные модули, используя классы utils.classes, чтобы получить доступ к обработчикам в handler и сборщикам, config — для чтения файла конфигурации и получения настроек для сборщиков (дополнительные пути для сборщиков, определенных пользователем), scheduler и signals — для установки интервала опроса для сборщиков, чтобы подсчитать их метрики, а также для настройки обработчиков и указания им приступать к обработке очереди метрик, которые нужно отправить.
Схема не включает в себя вспомогательные модули convertor.py и gmetric.py, используемые определенными сборщиками, а также более 20 реализаций обработчиков, определенных в подпакете handler, и более 100 реализаций сборщиков, определенных в каталоге проекта Diamond/src/collectors/ (которые находятся в другом месте, если процесс установки Diamond отличается от того, который мы выполнили при чтении, то есть использовали дистрибутивы пакетов PyPI или Linux вместо исходного кода). Они импортируются с помощью функции diamond.classes.load_dynamic_class(), которая затем вызывает функцию diamond.util.load_class_from_name() для загрузки классов на основе имен, представленных в строках конфигурационного файла, поэтому операторы импорта могут не вызывать их явно.
Рис. 5.2. Структура импортированных модулей в Diamond
Чтобы понять, для чего в проекте присутствуют пакет utils и модуль util, нужно открыть код: модуль util предоставляет функции, связанные с упаковкой Diamond (а не с его работой): функцию для получения номера версии на основе version.__VERSION__ и две функции, которые анализируют строки, позволяющие определить модули или классы и импортировать их.
Функция diamond.utils.log.setup_logging(), которая находится в файле src/diamond/utils/log.py, вызывается из функции main() исполняемого файла diamond при запуске демона:
····# Инициализация журналирования
····log = setup_logging(options.configfile, options.log_stdout)
Если значение options.log_stdout равно True, функция setup_logging() настроит средство ведения журнала со стандартным форматированием так, чтобы оно отправляло записи в стандартный поток выхода на уровне DEBUG. Это делается в следующем фрагменте кода:
##~~… Пропускаем все остальное…
def setup_logging(configfile, stdout=False):
····log = logging.getLogger('diamond')
····if stdout:
········log.setLevel(logging.DEBUG)
········streamHandler = logging.StreamHandler(sys.stdout)
········streamHandler.setFormatter(DebugFormatter())
········streamHandler.setLevel(logging.DEBUG)
········log.addHandler(streamHandler)
····else:
········##~~… Пропускаем это…
В противном случае он анализирует файл конфигурации с помощью функции logging.config.file.fileConfig() из стандартной библиотеки Python. Перед вами вызов функции — он выделен отступами, поскольку находится внутри предшествующего оператора if/else и блока try/except:
········logging.config.fileConfig(configfile,
························disable_existing_loggers=False)
Конфигурация журналирования игнорирует ключевые слова в конфигурационном файле, которые не связаны с журналированием. Так Diamond может использовать один и тот же конфигурационный файл как для своей конфигурации, так и для конфигурации журналирования. В примере конфигурационного файла, который располагается по адресу Diamond/conf/diamond.conf.example, среди прочих обработчиков определяется и обработчик журналирования:
### Настройки обработчиков
[handlers]
# обработчик(и) для журналирования
keys = rotated_file
Далее в файле конфигурации под заголовком «Настройки для журналирования» определяются примеры средств ведения журнала. Для получения более подробной информации смотрите документацию к конфигурационным файлам для журналирования (http://bit.ly/config-file-format).
Примеры из структуры Diamond
Diamond — это больше чем исполняемое приложение. Он также является библиотекой, которая предоставляет пользователям возможность создавать и применять собственные сборщики.
Мы продемонстрируем те элементы структуры пакета, которые нам нравятся, а затем изучим, как именно Diamond позволяет приложению импортировать и использовать определенные извне сборщики.
На схеме рис. 5.2 показан модуль сервера, взаимодействующий с тремя другими модулями проекта: diamond.handler, diamond.collector и diamond.utils.
В подпакете utils можно было бы разместить все классы и функции в одном большом модуле util.py, однако можно подключить пространства имен для того, чтобы разбить код на отдельные файлы, — и команда разработчиков ею воспользовалась. Отличный выбор!
Все реализации обработчиков содержатся в каталоге diamond/handler (это логично), но структура для сборщиков отличается. Для них не предусмотрен каталог — только модуль diamond/collector.py, в котором определяются базовые классы Collector и ProcessCollector. Все реализации подклассов класса Collector определены в каталоге Diamond/src/collectors/, в виртуальной среде они будут установлены по адресу venv/share/diamond/collectors, если вы устанавливали Diamond с помощью PyPI (рекомендованный способ), а не с помощью GitHub (как это сделали мы). Это помогает пользователю создать новые реализации сборщиков: размещение всех сборщиков в одном месте упрощает их поиск для приложения, а также создание аналогичных сборщиков. Наконец, каждая реализация сборщика в Diamond/src/collectors находится в своем каталоге (а не в отдельном файле), что позволяет разделить тесты для каждой реализации класса Collector. Также отлично придумано!
Добавить новую реализацию класса Collector нетрудно: нужно создать подкласс абстрактного базового класса diamond.collector.Collector[58], реализовать метод Collector.collect() и поместить реализацию в отдельный каталог по адресу venv/src/collectors/.
Сама по себе реализация сложна, но пользователь этого не знает. В данном разделе рассматриваются простая часть API сборщиков, которая видна пользователю, и сложный код, благодаря которому появился подобный интерфейс.
Сложное против запутанного. Мы можем сравнить работу со сложным кодом со швейцарскими часами — они просто работают, но внутри находится множество маленьких деталей, взаимодействующих с высокой точностью, чтобы упростить работу с API. Использование запутанного кода похоже на управление самолетом — вы наверняка должны знать, что делать, чтобы не разбиться и не сгореть[59]. Мы не хотим жить в мире без самолетов, но при этом желаем пользоваться часами, не вникая в тонкости их работы. Везде, где это возможно, создавайте менее сложные пользовательские интерфейсы.