php monolog stdout not full issue
Comments
prolic commented Nov 17, 2018
composer require prooph/event-store-client dev-master
Then execute this script:
The text was updated successfully, but these errors were encountered:
We are unable to convert the task to an issue at this time. Please try again.
The issue was successfully created but we are unable to update the comment at this time.
bwoebi commented Nov 21, 2018 •
First: this stacktrace is within the shutdown sequence, when objects are being destroyed.
kelunik commented Nov 22, 2018
Silently discarding the writes just hides the issue, but doesn’t fix it. We should probably do blocking writes in that situation instead?
bwoebi commented Nov 22, 2018
Doing a blocking write still hides a problem: the write will be non-blocking if we are not destroyed yet. And may not write everything if the output buffer gets full.
And forcing a blocking write of the remaining buffer is. well. blocking on streams happening to have full buffers.
So that’s not a viable option.
I think I however could be fine with «attempt to write as much as possible non-blockingly», which would be consistent with current behavior after loop, before destruction. (So to speak: «undestroy» the object and happily continue if methods are called after__destruct() [this only ever should happen in shutdown, so it’s safe].) And I guess that will be enough for most cases outside of an event loop, in shutdown sequence.
enumag commented Apr 20, 2020
bump, it would be great to have this fixed. until then the usability of this library is kinda questionable
enumag commented Apr 21, 2020
@kelunik @trowski Can you give me some tip how to implement it correctly?
kelunik commented Apr 21, 2020
@enumag Do you have a simple reproduction script without external dependencies?
kelunik commented Jul 28, 2020
@enumag @prolic Any news here? We’ve fixed amphp/byte-stream#58 since this issue has been opened, is that related?
prolic commented Aug 21, 2020
Sorry it took me so long. I tested this and cannot reproduce the issue anymore. Seems to be fixed. Thank you!
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Magento 2. Monolog или как писать логи
Оглавление
Monolog
Начнем с самого главного вопроса — Что такое Monolog и причем он здесь.
Monolog — Это библиотека, которая реализует PSR-3 стандарт для логирования данных. Именно Monolog используется в Magento 2 для записи логов.
PSR-3 — это, в свою очередь, стандарт, описывающий общий подход для логирования данных и рекомендации по реализации логгеров, предоставляющих общий интерфейс.
1. Логгер (объект) обязан реализовывать \Psr\Log\LoggerInterface интерфейс.
2. Имеем следующие уровни ошибок (указаны в порядке приоритета от большего к меньшему):
EMERGENCY — System is unusable.
ALERT — Action must be taken immediately. Example: Entire website down, database unavailable, etc.
CRITICAL — Critical conditions. Example: Application component unavailable, unexpected exception.
ERROR — Runtime errors that do not require immediate action but should typically monitored.
WARNING — Exceptional occurrences that are not errors.Example: Use of deprecated APIs.
NOTICE — Normal but significant events.
INFO — Interesting events. Example: User logs in, SQL logs.
DEBUG — Detailed debug information.
Monolog довольно прост в использовании. Давайте рассмотрим следующий пример.
Основные моменты работы Monolog, которые надо иметь в виду:
Чтобы замена работала вам необходимо подключить дополнительный процессор — \Monolog\Processor\PsrLogMessageProcessor.
Стоит сказать, что Monolog обладает огромным числом Formatter, Processor, Handler из коробки. Вы можете как использовать их, так и написать свои собственные.
Особенности применения в Magento 2
На оффициальном сайте Magento вы можете найти общий пример как использовать логгер. К несчастью представленный пример не раскрывает всех деталей и увы не отвечает на вопрос «как писать логи в свой собственный файл». Поэтому давайте разберемся во всем по подробнее.
Во времена Magento 1, наверное, все рано или поздно использовали Mage::log метод, который был доступен везде в коде и запись самого простого лога выглядела как Mage::log(‘ALARM!’, null, ‘api.log’). В результате у нас была запись следующего вида в файле var/log/api.log
Формат по умолчанию: %timestamp% %priorityName% (%priority%): %message%.
В результате такого вызова мы получим следующую запись в файле var/log/system.log
Формат по умолчанию: [%datetime%] %channel%.%level_name%: %message% %context% %extra%
В отличии от Magento 1, здесь куда больше нюансов.
1. Параметры для записи лога.
Рассмотрим общий вызов метода записи
Присутствует в: Block, Helper, Model, Collection, etc.
Отсутствует в: ResourceModel, Controller, Comand, Setup, etc.
В ResourceModel имеется свойство _logger, но оно не заполняется в конструкторе. Оно заполняется только при помощи приватного метода getLogger в \Magento\Framework\Model\ResourceModel\AbstractResource. Метод вызывается только в случае ошибки во время записи в бд (в блоке catch) внутри метода commit(). До тех пор, логгера у ресурс моделей не будет.
У Collection логгер имеется с самого начала. Он присваивается в конструкторе \Magento\Framework\Data\Collection\AbstractDb и позже наследуется.
3. Дефолтный логгер и список хендлеров.
В глобальном di.xml (app/etc/di.xml) вы можете найти, что \Psr\Log\LoggerInterface имплементится \Magento\Framework\Logger\Monolog классом, который в свою очередь наследуется от \Monolog\Logger. Имя логгера main. Там же определены несколько хендлеров
Некоторые классы отличаются от перечисленных выше (так как переопределены в модуле Magento\Developer):
1) Magento\Framework\Logger\Handler\System (слушает INFO)
2) Magento\Developer\Model\Logger\Handler\Debug (слушает DEBUG)
3) Magento\Developer\Model\Logger\Handler\Syslog (слушает DEBUG)
В указанных классах (Debug и Syslog) добавлена возможность отключать запись логов (dev/debug/debug_logging и dev/syslog/syslog_logging соответственно).
Обратите внимание, что в списке хендлеров нет exception хендлера, который производит запись в exception.log. Он вызывается в System хендлере.
В Magento 2 до 2.2 была проблема связанная с тем, что логгер не мог перепрыгивать на другой хендлер после первого найденного. Данная проблема была вызвана тем, что Monolog рассчитывал что к нему все хендлеры приходят в массиве с цифровыми ключами, а приходили с буквенными ([‘system’=>,’debug’=>. ]). Разработчики Magento позже исправили ситуацию — они преобразуют хеш в обычный массив с цифровыми ключами прежде чем передать его в Monolog. Monolog сейчас так же изменил алгоритм перебора хендлеров и использует метод next().
4. Внедрение своего хендлера в список уже существующих.
Мы подходим к самому интересному, что немного портит впечатление от реализации в Magento 2. Вы не сможете добавить кастомный хендлер к списку уже существующих при помощи di.xml без …”дополнительных телодвижений”. Это связано с принципом мерджа конфигов.
Существует несколько Config Scope:
1) Initial (app/etc/di.xml)
2) Global (
3) Area-specific (
Внутри 1 уровня конфиги объединяются, но следующий уровень переопределяет их при мердже (если они там тоже объявлены). Это приводит к невозможности добавлять хендлеры в своих модулях к существующему списку, так как он объявлен в initial scope.
Возможно в будущем мы увидим реализацию, в которой добавление хендлеров будет вынесено из initial scope в какой-то другой модуль, тем самым перенесено в global scope.
Реализация
Давайте рассмотрим основные способы записи логов, которые могут пригодится нам при реализации задач.
1. Запись лога при помощи стандартного логгера
Данный способ позволяет нам достаточно легко писать логи в 1 из стандартных логов (debug.log, system.log или exception.log).
Все становится еще проще, если в нашем классе уже присутствует унаследованная зависимость логгера.
2. Запись лога при помощи стандартного логгера с кастомным каналом
Данный способ отличается от предыдущего тем, что создается клон логгера и ему назначается другой канал (имя). Что упростит поиск внутри файла логов.
Для поиска нужных логов теперь достаточно воспользоваться поиском по «api» (дефолтный логгер в Magento 2 называется main) в уже существующих файлах system.log, debug.log, exception.log. Можно использовать
3. Запись в кастомный файл при помощи собственного хендлера
Создадим простой хендлер, который логирует все ошибки уровня Critical и выше в отдельный файл var/log/critical.log. Добавим также возможность, чтобы он блокировал все остальные хендлеры для данного уровня ошибки и выше. Это позволит уйти от дублирования данных в debug.log и system.log файлах.
В Magento 2 2.2+ в конструкторе \Magento\Framework\Logger\Handler\Base изменился способ обработки пути до файла с логом
К примеру выше стоит дать небольшое пояснение. Так как Base не позволяет установить bubble свойство через параметры конструктора, то нам бы пришлось либо повторять часть кода из конструктора Base, чтобы передать корректно входной параметр в родителя класса Base (который, к слову сказать, обладает входным параметром для установки этого свойства) либо воспользоваться таким подходом. Я выбрал 2ой вариант.
Данный способ добавления хендлера, не идеальный, но позволяет уйти от Config Scope проблемы, которая потребует от нас дублировать все логгеры у себя в di.xml. Если же цель заменить все логгеры своим, то гораздо лучше использовать virtualType подход, который мы рассмотрим дальше.
4. Запись в кастомный файл при помощи virtualType
В di.xml нашего модуля добавляем следующее
Добавляем в Oxis\Log\Model\A класс логгер.
Теперь абсолютно все логи, которые будут писаться в нашем классе будут обрабатываться нашей версией логгера, который будет писать логи при помощи нашего хендлера в файл var/log/api.log.
5. Быстрое логирование данных
Бывают случаи, когда нам необходимо быстро добавить логирование. Чаще всего это может понадобиться либо на продакшен сервере либо для быстрого тестирования.
Плюсы данного подхода: пишет дату, имеется контекст (массив), автоматически добавляет \n в конец
В примере выше специально применен \Monolog\Logger, а не \Magento\Framework\Logger\Monolog который его расширяет. Дело в том, что при таком использовании разницы никакой нет, а писать меньше (и запомнить проще).
\Monolog\Handler\StreamHandler в свою очередь применен вместо \Magento\Framework\Logger\Handler\Base так как Base использовать в качестве сниппета не очень удобно по причине дополнительных зависимостей от сторонних классов.
Другой подход, о котором нельзя не сказать — это старый добрый file_put_contents.
Плюсы данного подхода: относительно быстро писать и не нужно запоминать классы.
В обоих случаях главную роль выполняет константа BP. Она всегда указывает на папку с маджентой (на 1 уровень выше pub), что удобно и всегда помогает нам писать логи в нужное место.
PHP Profi
Квест → Как хакнуть форму
Как связать Monolog и E.L.K. Перевод
E.L.K. — это отличный стек для хранения, управления и мониторинга логов. Monolog — это отличная PHP-библиотека для логирования. Давайте заставим их работать вместе.
Что такое Monolog
В двух словах, Monolog предоставляет вам логер, в который вы посылаете ваши лог-записи. Этот логер имеет несколько обработчиков, которые рассылают эти записи туда, где они вам потребуются. Монолог имеет множество реализованных разных обработчиков, которые позволяют вам легко отправлять логи в разные места, например, в файлы, на электронную почту, в slack, в logstash и т. д..
Полный список обработчиков можно посмотреть в документации Monolog-а. Вы можете использовать Monolog как есть, но есть готовые интеграции Monolog-а в различные фреймворки, что очень упрощает жизнь.
Что такое стек ELK
Стек ELK (сейчас известный как Elastic-стек) состоит из Elasticsearch, Logstash и Kibana. Эти три технологии используются для мощного манипулирования логами.
Logstash используется для управления журналами. Он собирает, анализирует и сохраняет их. Имеет много реализованных входящих потоков(«приёмников» – как/откуда принимать), фильтров и исходящих потоков (куда отправлять).
Elasticsearch — это мощная распределенная NoSQL база данных с REST API. В Elastic-стеке, Elasticsearch используется как постоянное хранилище для наших журналов.
Ну а Kibana визуализирует логи из Elasticsearch и даёт возможность создавать многочисленные удобные визуализации и даш-борды, которые позволяют вам видеть все важные показатели в одном месте в реальном времени.
Интеграция Monolog и ELK Stack
Как я уже упоминал, Monolog имеет много обработчиков, которые могут отправлять логи. Logstash имеет много различных «приёмников», так что существует не один способ, чтобы соединить их, а мы можем выбрать из нескольких вариантов.
Прямой вывод логов в Elasticseach
Самый простой вариант – обойти Logstash и отправлять логи напрямую в Elasticsearch с помощью ElasticSearchHandler. Это самый простой подход, очень прост в настройке, но имеет некоторые недостатки, когда ваша инфраструктура становится более сложной. Если ваше приложение работает на другом сервере, чем ваш Elasticsearch, вам ещё нужно повозиться с аутентификацией и безопасностью Elasticsearch.
И, очевидно, вы не можете использовать сам Logstash, но это не особо и проблема, поскольку одна из основных фич Logstash – это форматирование логов и предварительная обработка, а Monolog тоже всё это умеет, но в PHP, который является более удобным, чем конфиг Logstash-а.
Другим вариантом является использование Gelf. Gelf отправляет логи по UDP протоколу, который является супербыстрым, но это довольно трудно отлаживать, когда ваши логи не доходят до их получателя. Кроме того, использование UDP имеет очевидный недостаток, потому что это не гарантирует доставку логов, что может быть очень неприятным.
Monolog предоставляет GelfHandler. Самое большое возможное преимущество использования Gelf – это GelfMessageFormatter, который добавляет много полезной информации в ваши журналы.
Вот как использовать его в Monolog в нативном PHP при тестировании на localhost:
В Symfony, вместо этого, вы можете указать его в config.yml :
Когда вы экспериментируете с Logstash, удобно настроить его на стандартный вывод, чтобы мы сразу могли видеть все входящие логи:
После этого мы должны перезагрузить ELK, чтобы Logstash подтянул новую конфигурацию.
Теперь мы можем проверить, получает ли Logstash логи извне в нужном нам виде:
Этот bash-скрипт отправляет сообщение в формате json по протоколу UDP на 127.0.0.1 / порт 12201. Если мы видим это сообщение в выводе logstash-а, он работает правильно.
Давайте теперь проверим отправляет ли наше PHP-приложение логи в Logstash, просто отправив некое сообщение в наш Monolog-логер:
Опять же, если мы видим short message в выводе Logstash-а, значит, всё ок.
Если вы используете symfony/console, вы можете создать команду для отправки сообщений в логи. Я использую эту команду для тестирования отправки и доставки сообщений:
Например, php bin/console app:generate:logs info 10 отправляет 10 информационных сообщений.
Некоторые заметки для отладки: мы можем просто отправлять сообщение в Logstash по udp из bash-скрипта, т. о. мы можем легко видеть было ли сообщение получено.
Но тестирование отправляет ли наше приложение сообщение по UDP во внешний мир немного сложнее.
На Linux, вы можете использовать tcpdump для этого:
будет захватывать пакеты, которые отправляет ваше приложение, т. о. вы можете увидеть, если оно работает.
Для Windows, вы можете скачать RawCap здесь и затем использовать его выполнив:
Использование RabbitMQ
RabbitMQ является наиболее широко известной реализацией протокола AMQP.
Основная задача, которую выполняет, – он забирает сообщения на одной стороне и отдаёт их на другой стороне. RabbitMQ очень мощный инструмент, но мы будем использовать только его основные черты.
Логи – по сути, просто сообщения, так что мы можем использовать RabbitMQ в качестве посредника между Monolog-ом и Logstash-ем.
Такой вариант имеет преимущество, потому что RabbitMQ может находиться на той же машине, что и веб-приложение. Это означает, что Monolog отправляет логи только локально и наше приложение не имеет сетевых задержек. А уже потом RabbitMQ отправляет логи в Logstash.
В RabbitMQ есть две важных части — обмен и очередь. Обмен — это как точка входа для сообщений. Очередь — это (на удивление) очередь, она держит наши логи до тех пор, пока их не обработает какой-нибудь получатель (Logstash). Мы можем привязать одну или несколько очередей к одному обмену.
Как я уже говорил, нам нужны только базовые настройки, так что у нас будет один обмен и одна очередь.
/var/lib/rabbitmq/mnesia – где хранятся данные.
/etc/rabbitmq/rabbit.json – тут задаём настройки наших обменов и очередей.
/etc/rabbitmq/rabbitmq.config должен выглядеть так:
В rabbitmq_management мы указываем где хранится наш файл с настройками очередей.
/etc/rabbitmq/rabbitmq.config должен выглядеть так:
Теперь у нас есть настроенный RabbitMQ, который готов для подключения к Monolog-у и Logstash-у.
Нам нужно изменить наш logstash.conf и настроить входной поток:
Для Monolog-а, настройка немного дольше.
Этот шаг отличается при использовании в Symfony и нативном PHP:
Для Symfony: мы регистрируем необходимые сервисы в services.yml :
Затем, мы добавляем AmqpHandler в config.yml :
Вот теперь мы закончили, и можем радостно отправлять логи с помощью Monolog-а в ELK.
Примечание
Итого
Теперь наше приложение отправляет логи в ELK, а соответственно мы можем в полной мере использовать всю информацию из логов, т. к. сейчас она выглядит намного нагляднее и удобнее, ну и можно понастроить разных графиков.
Linux: PHP-FPM, Docker, STDOUT и STDERR — нет логов приложения
Имеется Docker-образ, в который включены NGINX и PHP-FPM, плюс Supervisor для их запуска.
Проблема заключается в том, что при выполнении kubectl logs — в выводе этих данных нет.
Где проблема? В Docker? Kubernetes? Linux в контейнере?
Linux — stdout/stderr
Вспомним вообще — как работает stdout/stderr в Linux.
Подключаемся в под:
Во второй консоли — открываем его логи:
Сейчас тут есть сообщения от PHP-FPM демона — но нет сообщения от приложения.
Пробуем написать в /dev/stderr :
В логах по-прежнему пусто.
Проверяем — куда направлены файлы:
/proc/self/fd/ 1 и 2 — казалось бы, всё правильно?
Значит, при подключении к поду с tty — все каналы направляются в него:
Docker STDOUT and STDERR
А что у нас происходит при запуске процесса в контейнере самим Docker?
The official httpd driver changes the httpd application’s configuration to write its normal output directly to /proc/self/fd/1 (which is STDOUT ) and its errors to /proc/self/fd/2 (which is STDERR ). See the Dockerfile.
Проверим дексрипторы процессов в нашем контейнере.
PID 1, supervisor который запускает NGINX и PHP-FPM:
pipe:[21223650] шлёт на хост-ноду, где мы видим логи в kubectl logs (или docker logs ).
Находим процессы NGINX и PHP-FPM:
Проверяем все процессы — попробуем писать в их дескрипторы.
STDOUT/ERR для Supervisor:
supervisor-stdout, supervisor-stderr — есть аутпут.
Php monolog stdout not full issue
Monolog sends your logs to files, sockets, inboxes, databases and various web services. See the complete list of handlers below. Special handlers allow you to build advanced logging strategies.
This library implements the PSR-3 interface that you can type-hint against in your own libraries to keep a maximum of interoperability. You can also use it in your applications to make sure you can always use another compatible logger at a later time. As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. Internally Monolog still uses its own level scheme since it predates PSR-3.
Install the latest version with
Support Monolog Financially
Get supported Monolog and help fund the project with the Tidelift Subscription or via GitHub sponsorship.
Tidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use.
Third Party Packages
Third party handlers, formatters and processors are listed in the wiki. You can also add your own there if you publish one.
Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 where possible to benefit from all the latest features and fixes.
Submitting bugs and feature requests
Bugs and feature request are tracked on GitHub
This library is heavily inspired by Python’s Logbook library, although most concepts have been adjusted to fit to the PHP world.
About
Sends your logs to files, sockets, inboxes, databases and various web services