From ab3c16195c609d9bb52032826509c18b16bee068 Mon Sep 17 00:00:00 2001 From: builder Date: Fri, 17 May 2024 13:44:14 +0300 Subject: [PATCH] JsonToStdErrHandler has been covered by the tests --- readme.md | 7 +- src/Handler/JsonToStdErrHandler.php | 213 +++++++++-- .../Handler/JsonToStdErrHandlerTest.php | 350 ++++++++++++++++-- 3 files changed, 501 insertions(+), 69 deletions(-) diff --git a/readme.md b/readme.md index 22ec1d8..9ebb1c3 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ MonologExt =============== -The realy useful Monolog`s extensions. +The really useful Monolog`s extensions. ![lib tests](https://github.com/yapro/monolog-ext/actions/workflows/main.yml/badge.svg) @@ -12,7 +12,7 @@ Add as a requirement in your `composer.json` file or run composer require yapro/monolog-ext dev-master ``` -Configuration Symfony >= 2.x +Configuration of Symfony >= 2.x ------------ You can use the best way to handle your logs because it's the easiest way: ```yaml @@ -233,8 +233,7 @@ docker run --rm -v $(pwd):/app yapro/monolog-ext:latest bash -c "cd /app \ && composer install --optimize-autoloader --no-scripts --no-interaction \ && /app/vendor/bin/phpunit /app/tests" ``` --'"array:1 [\n \"context\" => array:3 [\n \"first\" => \"string\"\n \"second\" => \"the log entry is too long, so it is reduced: ююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююю\"\n \"thirs\" => \"the log entry is too long\"\n ]\n]\n"' -+'"array:1 [\n \"context\" => array:3 [\n \"first\" => \"string\"\n \"second\" => \"the log entry is too long, so it is reduced: ююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююююю\"\n \"thirs\" => \"the log entry is too long\"\n ]\n]\n"' + Dev ------------ ```sh diff --git a/src/Handler/JsonToStdErrHandler.php b/src/Handler/JsonToStdErrHandler.php index 0d19beb..2b13971 100644 --- a/src/Handler/JsonToStdErrHandler.php +++ b/src/Handler/JsonToStdErrHandler.php @@ -9,6 +9,10 @@ use Monolog\Logger; use Symfony\Component\HttpKernel\Exception\HttpException; use JsonException; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\AbstractDumper; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextualizedDumper; use YaPro\MonologExt\Processor\AddStackTraceOfCallPlaceProcessor; use YaPro\MonologExt\VarHelper; use function is_numeric; @@ -16,10 +20,7 @@ // todo покрыть методы тестами class JsonToStdErrHandler extends AbstractProcessingHandler { - // PHP дробит строки данной длинны, а инфраструктура обрабатывающая запись теряет их: https://github.com/docker-library/php/pull/725#issuecomment-443540114 - public const MAX_RECORD_LENGTH = 8192; - const THE_LOG_ENTRY_IS_TOO_LONG = 'the log entry is too long'; - const THE_LOG_ENTRY_IS_TOO_LONG_SO_IT_IS_REDUCED = self::THE_LOG_ENTRY_IS_TOO_LONG . ', so it is reduced: '; + const THE_VALUE_IS_TOO_BIG = 'too big'; /** * @var false|resource */ @@ -28,12 +29,45 @@ class JsonToStdErrHandler extends AbstractProcessingHandler // используется для игнорирования повтороного сообщения (такое бывает, когда приложение завершается с ошибкой, при // этом set_exception_handler пишет ошибку, а потом register_shutdown_function пишет ее же (еще раз) - private string $lastMessageHash = ''; + private string $lastRecordHash = ''; + private bool $devMode = false; - public function __construct() { + public const MAX_DUMP_LEVEL_DEFAULT = 5; + private int $maxDumpLevel = self::MAX_DUMP_LEVEL_DEFAULT; + /** + * Проблема масштабная: + * 1. PHP дробит строк длинной больше чем значение log_limit https://www.php.net/manual/en/install.fpm.configuration.php#log-limit + * 2. Docker дробит строки длинной больше 16 Кб https://github.com/moby/moby/issues/34855 + * 3. Инфраструктура обрабатывающая разбитые записи теряет их + * Решение: перед записью в stderr проверять длинну сообщения на значение maxRecordLength + * История - как получилось данное значение: + * 1. некий bukka указал для докер-файла размер log_limit = 1024 https://github.com/docker-library/php/pull/725#issuecomment-443540114 + * 2. затем jnoordsij установил log_limit = 8192 https://github.com/docker-library/php/blame/396ead877c1751e756f484e01ac72c93925dfaa8/8.3/alpine3.19/fpm/Dockerfile#L231 + * Важно: сокращение записей до данной длины может не помогать, когда используются UTF8-символы (где на один символ + * прходится несколько байт), в этом случае подрезанное сообщение все равно будет разбито на Х строк (docker-ом или + * PHP, который в документации не говорит какой кодировки characters он будет подсчитывать) + */ + public const MAX_RECORD_LENGTH_DEFAULT = 16000; + private int $maxRecordLength = self::MAX_RECORD_LENGTH_DEFAULT; + + private VarCloner $varCloner; + + public function __construct(int $maxRecordLength = 0) { parent::__construct(); $this->stderr = fopen('php://stderr', 'w'); $this->varHelper = new VarHelper(); + $this->devMode = isset($_ENV['ERROR_HANDLER_DEV_MODE']); + $this->maxDumpLevel = isset($_ENV['ERROR_HANDLER_MAX_DUMP_LEVEL']) && (int) $_ENV['ERROR_HANDLER_MAX_DUMP_LEVEL'] ? (int) $_ENV['ERROR_HANDLER_MAX_DUMP_LEVEL'] : self::MAX_DUMP_LEVEL_DEFAULT; + if ($maxRecordLength) { + $this->maxRecordLength = $maxRecordLength; + } else { + $limit = (int) ini_get('log_limit'); // по-умолчанию данная настройка не задана, но PHP настроен на 1024 символа + if ($limit) { + $this->maxRecordLength = $limit; + } + } + $this->varCloner = new VarCloner(); + $this->varDumper = new CliDumper(null, null, AbstractDumper::DUMP_LIGHT_ARRAY); } // Не реализуем метод isHandling т.к. он уже реализован \Monolog\Handler\AbstractHandler::isHandling(), а главное @@ -84,9 +118,9 @@ public function handle(array $record): bool /** * @throws JsonException */ - protected function write(array $record): void + public function write(array $record): void { - if (isset($_ENV['ERROR_HANDLER_DEV_MODE']) && $record['level'] > Logger::INFO) { + if ($this->devMode && $record['level'] > Logger::INFO) { $result = PHP_EOL . ':::::::::::::::::::: ' . __CLASS__ . ' informs ::::::::::::::::::' . PHP_EOL . PHP_EOL; @@ -100,29 +134,111 @@ protected function write(array $record): void $result .= 'The log entry has been wrote by ' . ($callPlace['file'] ?? '') . ':' . ($callPlace['line'] ?? '') . PHP_EOL; foreach ($record['context'] as $key => $value) { - $result .= trim($key .' : ' . $this->varHelper->dump($value)) . PHP_EOL; + $result .= trim($key .' : ' . $this->dump($value)) . PHP_EOL; } unset($record['context']); foreach ($record as $key => $value) { - $result .= trim($key .' : ' . $this->varHelper->dump($value)) . PHP_EOL; + $result .= trim($key .' : ' . $this->dump($value)) . PHP_EOL; } // $message .= json_encode($this->varHelper->dump($record), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); fwrite($this->stderr, $result . PHP_EOL); - exit; + $this->writeToStdErr($result); + exit(123); }; - $message = $this->getMessage($record); - if (sha1($message) === $this->lastMessageHash) { + $result = $this->getMessage($record); + if (sha1($result) === $this->lastRecordHash) { return; } - $this->lastMessageHash = sha1($message); + $this->lastRecordHash = sha1($result); + $this->writeToStdErr($result); + } + + public function writeToStdErr(string $message) + { // todo можно подумать над тем, чтобы сплитить запись на несколько при превышении длинны fwrite($this->stderr, $message . PHP_EOL); } - // todo вынести в либу и актуализировать в других сервисах: + + public function reduceRecord(array $record, $maxLevel, $currentLevel = 0) + { + $currentLevel++; + foreach ($record as $key => $value) { + if ($currentLevel === $maxLevel) { + $value = self::THE_VALUE_IS_TOO_BIG; // The maximum dump level has been reached. + } + if (is_iterable($value)) { + $record[$key] = $this->reduceRecord($value, $maxLevel, $currentLevel); + } else { + $record[$key] = $value; + } + } + return $record; + } + + // возвращает $record, которая на уровне $maxLevel имеет строковые значения (задампленные значения) + public function dumpRecordDataOnTheLevel(array $record, $maxLevel, $currentLevel = 1) + { + foreach ($record as $key => $value) { + if ($currentLevel === $maxLevel) { + $value = $this->dump($value); + } + if (is_iterable($value)) { + $record[$key] = $this->dumpRecordDataOnTheLevel($value, $maxLevel, $currentLevel+1); + } else { + $record[$key] = $value; + } + } + return $record; + } + + public function reduceRecordDataOnTheLevel(array &$record, $maxLevel, $currentLevel = 1, &$changeableRecord) + { + if ($currentLevel === $maxLevel) { + $reversed = array_reverse($record, true); + foreach ($reversed as $key => $value) { + $record[$key] = self::THE_VALUE_IS_TOO_BIG; + $changeableRecordAsJson = $this->getJson($record); + if ($this->isRecordShort($changeableRecordAsJson)) { + return $record; + } + } + } else { + foreach ($record as $key => $value) { + if (is_iterable($value)) { + $record[$key] = $this->reduceRecordDataOnTheLevel($value, $maxLevel, $currentLevel++, $changeableRecord); + } else { + $record[$key] = $value; + } + } + } + return $record; + } + + // Находим $maxDumpLevel требуемый для безопасного сохранения сообщения в stderr + public function findMaxDumpLevel(array $record): int + { + for ($maxDumpLevel = $this->maxDumpLevel; $maxDumpLevel > 0; $maxDumpLevel--) { + $string = $this->dump($record, $maxDumpLevel); + if ($this->isRecordShort($string)) { + break; + } + } + return $maxDumpLevel; + } + public function getMessage(array $record): string { + $maxDumpLevel = $this->findMaxDumpLevel($record); + // В данной строке мы знаем приемлемый уровень для создания строки log-записи с учетом $this->maxRecordLength, но + // попробуем не укорачивать глобально по уровню, а попробуем укоротить уменьшая даннные на уровне $maxDumpLevel+1 + // Для этого сначала задампим данные на уровне $maxDumpLevel+1, а потом будем отбрасывать значения + $dumpedRecord = $this->dumpRecordDataOnTheLevel($record, $maxDumpLevel+1); + $this->reduceRecordDataOnTheLevel($dumpedRecord, $maxDumpLevel+1, 1, $dumpedRecord); + + return $this->getJson($dumpedRecord); +/* $result = $this->getJson($record); if ($this->isMessageShort($result)) { return $result; @@ -131,10 +247,9 @@ public function getMessage(array $record): string // при нахождении ключей с большим значением, они по очереди удаляются, пока лог-запись не станет приемлемого размера if (isset($record['context'])) { $result = $this->getReducedRecord($record, 'context'); - if ($this->isMessageShort($result)) { - return $result; - } + return $result; } + // если вдруг админы решили не индексировать поле context, то просто можно начать вместо него использовать поле debugInfo: if (isset($record['debugInfo'])) { $result = $this->getReducedRecord($record, 'debugInfo'); if ($this->isMessageShort($result)) { @@ -142,25 +257,61 @@ public function getMessage(array $record): string } } // попробуем сохранить хотя бы часть сообщения: - $record['message'] = mb_substr($record['message'], 0, self::MAX_RECORD_LENGTH - mb_strlen('{"message":""}')); + $record['message'] = mb_substr($record['message'], 0, $this->maxRecordLength - mb_strlen('{"message":""}')); $record = ['message' => $record['message']]; return $this->getJson($record); + */ } - public function getReducedRecord(array &$record, $keyName): string + /** + * @param $value + * @param int $maxLevel - уровень, на котором скалярные значения остаются как есть, а другие типы превращаются в строку, например массив с двумя элементами превращается в "[ …2]" + * @return string + */ + public function dump($value, int $maxLevel = 0): string + { + if (is_null($value)) { + return 'null'; + } + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + if (is_scalar($value)) { + return (string) $value; + } + //return $this->varCloner->cloneVar($value)->dump(new CliDumper()); + //return (new CliDumper())->dump($serializebleClone, true); + // return print_r($serializebleClone, true); // var_export + $serializebleClone = $this->varCloner->cloneVar($value); + $data = $serializebleClone->withMaxDepth($maxLevel); + return trim(str_replace(PHP_EOL, ' ', $this->varDumper->dump($data, true))); + } + + public function isRecordShort(string $record): bool + { + return mb_strlen($record) < $this->maxRecordLength; + } + + public function getJson(array $record): string + { + return json_encode($record, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PRESERVE_ZERO_FRACTION | JSON_THROW_ON_ERROR); + } +/* + public function getReducedRecord(array &$changeableRecord, array &$changeableRecordValue): string { $mysteriousCharacters = 2; $explanation = self::THE_LOG_ENTRY_IS_TOO_LONG_SO_IT_IS_REDUCED; $explanationLength = mb_strlen($explanation); - $preserved = array_reverse($record[$keyName], true); - foreach ($preserved as $key => $value) { + $reversed = array_reverse($record, true); + foreach ($reversed as $key => $value) { + $result = $this->dump($value); $result = $this->getJson($record); if ($this->isMessageShort($result)) { return $result; } // находим, на сколько символов нужно уменьшить $record (лишнее количество символов): - $excessCharactersInTheRecord = mb_strlen($result) - self::MAX_RECORD_LENGTH; + $excessCharactersInTheRecord = mb_strlen($result) - $this->maxRecordLength; if ($excessCharactersInTheRecord > 0) { // находим насколько мы должны подрезать value: $valueAsString = $this->varHelper->dump($value); @@ -170,23 +321,13 @@ public function getReducedRecord(array &$record, $keyName): string } $newValueMaxLength = mb_strlen($valueAsString) - $excessCharactersInTheRecord - $explanationLength - $mysteriousCharacters; if ($newValueMaxLength > 0) {// даем пояснение + подрезаем value: - $record[$keyName][$key] = $explanation . mb_substr($valueAsString, 0, $newValueMaxLength); + $record[$key] = $explanation . mb_substr($valueAsString, 0, $newValueMaxLength); } else {// символов на подрезку не остается, увы удаляем value: - $record[$keyName][$key] = self::THE_LOG_ENTRY_IS_TOO_LONG; + $record[$key] = self::THE_LOG_ENTRY_IS_TOO_LONG; } } } return $this->getJson($record); - } - - public function isMessageShort(string $record): bool - { - return mb_strlen($record) < self::MAX_RECORD_LENGTH; - } - - public function getJson(array $record): string - { - return json_encode($this->varHelper->dump($record), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); - } + }*/ } diff --git a/tests/Unit/WhiteBox/Handler/JsonToStdErrHandlerTest.php b/tests/Unit/WhiteBox/Handler/JsonToStdErrHandlerTest.php index acf6c65..fd46cbe 100644 --- a/tests/Unit/WhiteBox/Handler/JsonToStdErrHandlerTest.php +++ b/tests/Unit/WhiteBox/Handler/JsonToStdErrHandlerTest.php @@ -3,7 +3,11 @@ namespace YaPro\MonologExt\Tests\Unit\WhiteBox\Handler; +use Closure; +use Generator; use PHPUnit\Framework\TestCase; +use stdClass; +use Symfony\Component\VarDumper\Dumper\CliDumper; use YaPro\Helper\LiberatorTrait; use YaPro\MonologExt\Handler\JsonToStdErrHandler; use YaPro\MonologExt\VarHelper; @@ -12,52 +16,340 @@ class JsonToStdErrHandlerTest extends TestCase { use LiberatorTrait; - public function testGetReducedRecord(): void + public function providerFindMaxDumpLevel(): Generator { + $object = new stdClass(); + $object->foo = new stdClass(); + $object->foo->baz = new stdClass(); + $object->bar = new stdClass(); + $object->bar->baz = new stdClass(); + $record = [ + 'first-1' => [ + 'second-1' => 'string', + 'second-2' => [ + 'third-1' => 'string', + 'third-2' => ['string'], + ], + ], + 'first-2' => [ + 'second-3' => 'привет', + 'second-4' => [ + 'third-3' => 12345, + 'third-4' => true, + 'third-5' => $object, + ], + 'second-5' => 'мир', + ], + ]; + yield [ + 'record' => $record, + 'maxRecordLength' => $this->getLength($record) + 81, // todo такая разница в количестве символов связана с тем, что CliDumper делает слишком много пробелов, поэтому нужно написать свой Dumper - JsonDumper + 'expectedMaxDumpLevel' => 2, + ]; + yield [ + 'record' => $record, + 'maxRecordLength' => $this->getLength($record) + 82, // todo такая разница в количестве символов связана с тем, что CliDumper делает слишком много пробелов, поэтому нужно написать свой Dumper - JsonDumper + 'expectedMaxDumpLevel' => 3, + ]; + } + + private function getLength(array $value): int + { + $json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE | JSON_THROW_ON_ERROR ); + return mb_strlen($json); + } + + /** + * @dataProvider providerFindMaxDumpLevel + */ + public function testFindMaxDumpLevel(array $record, int $maxRecordLength, int $expectedMaxDumpLevel): void + { + $rat = new JsonToStdErrHandler(); + $this->setClassPropertyValue($rat, 'maxRecordLength', $maxRecordLength); + + $result = $rat->findMaxDumpLevel($record); + $this->assertEquals($expectedMaxDumpLevel, $result); + } + + public function testWrite(): void + { + // проверка дубля записи (когда хендлер ошибок пишет ошикбу + хендрел шатдауна ее дублирует) $mock = $this->getMockBuilder(JsonToStdErrHandler::class) ->disableOriginalConstructor() - ->setMethodsExcept(['getReducedRecord', 'getJson', 'isMessageShort']) + ->setMethodsExcept(['write']) ->getMock(); + $mock->expects($this->exactly(1))->method('writeToStdErr'); + $record = ['any']; + $mock->write($record); + $mock->write($record); - $this->setClassPropertyValue($mock, 'varHelper', new VarHelper()); + // проверка: после дубля сообщения нормально пишутся + $mock = $this->getMockBuilder(JsonToStdErrHandler::class) + ->disableOriginalConstructor() + ->setMethodsExcept(['write']) + ->getMock(); + $mock->expects($this->exactly(2))->method('writeToStdErr'); + $mock->method('getMessage')->willReturn('{"first":"message"}', '{"first":"message"}','{"second":"message"}'); + $record = ['any']; + $mock->write($record); + $mock->write($record); + $mock->write($record); + } - // подрезка сообщения: - $keyName = 'context'; + public function providerGetMessage(): Generator + { + $object = new stdClass(); + $object->foo = new stdClass(); + $object->foo->baz = new stdClass(); + $object->bar = new stdClass(); + $object->bar->baz = new stdClass(); $record = [ - $keyName => [ - 'first' => 'string', - 'second' => str_repeat('ю', JsonToStdErrHandler::MAX_RECORD_LENGTH), - 'thirs' => 'string', + 'first-1' => [ + 'second-1' => 'string', + 'second-2' => [ + 'third-1' => 'string', + 'third-2' => ['string'], + ], + ], + 'first-2' => [ + 'second-3' => 'string', + 'second-4' => [ + 'third-3' => 12345, + 'third-4' => true, + 'third-5' => $object, + ], + 'second-5' => 'string', ], ]; - $explanationMessagesLength = 195; - $expected = [ - $keyName => [ - 'first' => 'string', - 'second' => JsonToStdErrHandler::THE_LOG_ENTRY_IS_TOO_LONG_SO_IT_IS_REDUCED . str_repeat('ю', JsonToStdErrHandler::MAX_RECORD_LENGTH - $explanationMessagesLength), - 'thirs' => JsonToStdErrHandler::THE_LOG_ENTRY_IS_TOO_LONG, + yield [ + 'record' => $record, + 'maxRecordLength' => 174, + 'expected' => '{"first-1":{"second-1":"string","second-2":"[ …2]"},"first-2":{"second-3":"string","second-4":"[ …3]","second-5":"too big"}}', + ]; + yield [ + 'record' => $record, + 'maxRecordLength' => 175, + 'expected' => '{"first-1":{"second-1":"string","second-2":{"third-1":"string","third-2":"[ …1]"}},"first-2":{"second-3":"string","second-4":{"third-3":"12345","third-4":"true","third-5":"{#274 …2}"},"second-5":"string"}}', + ]; + } + + /** + * @dataProvider providerGetMessage + */ + public function testGetMessage(array $record, int $maxRecordLength, string $expected): void + { + $rat = new JsonToStdErrHandler(); + $this->setClassPropertyValue($rat, 'maxRecordLength', $maxRecordLength); + $result = $rat->getMessage($record); + //$this->assertSame($expected, $result); + $this->assertJsonSame($expected, $result); + } + + private function assertJsonSame(string $jsonExpected, string $jsonResult) + { + $arrayExpected = json_decode($jsonExpected, true, 512, JSON_THROW_ON_ERROR); + $arrayResult = json_decode($jsonResult, true, 512, JSON_THROW_ON_ERROR); + $this->assertSame( + $arrayExpected, + $arrayResult, + ); + } + + public function providerDump(): Generator + { + // тесты на типы данных - во что они будут сериализованы + yield [ + 'value' => 'string', + 'expected' => 'string', + 'level' => 0, + ]; + yield [ + 'value' => 12345, + 'expected' => '12345', + 'level' => 0, + ]; + yield [ + 'value' => 0, + 'expected' => '0', + 'level' => 0, + ]; + yield [ + 'value' => 0.12345, + 'expected' => '0.12345', + 'level' => 0, + ]; + yield [ + 'value' => 1.2345, + 'expected' => '1.2345', + 'level' => 0, + ]; + yield [ + 'value' => true, + 'expected' => 'true', + 'level' => 0, + ]; + yield [ + 'value' => false, + 'expected' => 'false', + 'level' => 0, + ]; + yield [ + 'value' => ['string'], + 'expected' => '[ …1]', + 'level' => 0, + ]; + $object = new stdClass(); + $object->foo = new stdClass(); + $object->foo->baz = new stdClass(); + $object->bar->baz = new stdClass(); + yield [ // 8 + 'value' => $object, + 'expected' => '{#262 …2}', + 'level' => 0, + ]; + yield [ // 9 + 'value' => $object, + 'expected' => '{#262 +"foo": {#261 …1} +"bar": {#258 …1} }', + 'level' => 1, + ]; + yield [ + 'value' => $object, + 'expected' => '{#262 +"foo": {#261 +"baz": {#260} } +"bar": {#258 +"baz": {#259} } }', + 'level' => 2, + ]; + $value = [ + 'first-1' => 'string', + 'first-2' => 12345, + 'first-3' => 1.2345, + 'first-4' => true, + 'first-5' => ['string'], + 'first-6' => $object, + ]; + yield [ + 'value' => $value, + 'expected' => '[ …6]', + 'level' => 0, + ]; + yield [ + 'value' => $value, + 'expected' => '[ "first-1" => "string" "first-2" => 12345 "first-3" => 1.2345 "first-4" => true "first-5" => [ …1] "first-6" => {#262 …2} ]', + 'level' => 1, + ]; + yield [ + 'value' => $value, + 'expected' => '[ "first-1" => "string" "first-2" => 12345 "first-3" => 1.2345 "first-4" => true "first-5" => [ "string" ] "first-6" => {#262 +"foo": {#261 …1} +"bar": {#258 …1} } ]', + 'level' => 2, + ]; + // тест на глубину - во что будут сериализованы многомерные массивы: + $value = [ + 'first-1' => [ + 'second-1' => 'string', + 'second-2' => [ + 'third-1' => 'string', + 'third-2' => ['string'], + ], + ], + 'first-2' => [ + 'second-3' => 'string', + 'second-4' => [ + 'third-3' => 12345, + 'third-4' => true, + 'third-5' => $object, + ], + 'second-5' => 'string', ], ]; - $result = $mock->getReducedRecord($record, $keyName); - $this->assertEquals($mock->getJson($expected), $result); + yield [ + 'value' => $value, + 'expected' => '[ "first-1" => [ …2] "first-2" => [ …3] ]', + 'level' => 1, + ]; + yield [ + 'value' => $value, + 'expected' => '[ "first-1" => [ "second-1" => "string" "second-2" => [ …2] ] "first-2" => [ "second-3" => "string" "second-4" => [ …3] "second-5" => "string" ] ]', + 'level' => 2, + ]; + } + + /** + * @dataProvider providerDump + */ + public function testTheDump($value, string $expected, int $level = 0): void + { + $rat = new JsonToStdErrHandler(); + $result = $rat->dump($value, $level); + $this->assertSame($expected, $result); + } + + public function testDumpRecordDataOnTheLevel(): void + { + $rat = new JsonToStdErrHandler(); - // сообщение не подрезается: - $keyName = 'context'; $record = [ - $keyName => [ - 'first' => 'string', - 'second' => 'string', - 'thirs' => 'string', + 'first-1' => [ + 'second-1' => 'string', + 'second-2' => [ + 'third-1' => 'string', + 'third-2' => ['string'], + ], + ], + 'first-2' => [ + 'second-3' => 'string', + 'second-4' => [ + 'third-3' => 12345, + 'third-4' => true, + 'third-5' => new stdClass(), + ], + 'second-5' => 12345, + ], + ]; + $expected = [ + 'first-1' => [ + 'second-1' => 'string', + 'second-2' => '[ …2]', + ], + 'first-2' => [ + 'second-3' => 'string', + 'second-4' => '[ …3]', + 'second-5' => '12345', + ], + ]; + + $result = $rat->dumpRecordDataOnTheLevel($record, 2); + $this->assertSame($expected, $result); + } + + public function testReduceRecordDataOnTheLevel(): void + { + + $record = [ + 'first-1' => [ + 'second-1' => str_repeat('ю', 100), + 'second-2' => str_repeat('ю', 100), + ], + 'first-2' => [ + 'second-3' => str_repeat('ю', 100), + 'second-4' => str_repeat('ю', 100), + 'second-5' => str_repeat('ю', 100), ], ]; $expected = [ - $keyName => [ - 'first' => 'string', - 'second' => 'string', - 'thirs' => 'string', + 'first-1' => [ + 'second-1' => str_repeat('ю', 100), + 'second-2' => str_repeat('ю', 100), + ], + 'first-2' => [ + 'second-3' => str_repeat('ю', 100), + 'second-4' => str_repeat('ю', 100), + 'second-5' => JsonToStdErrHandler::THE_VALUE_IS_TOO_BIG, ], ]; - $result = $mock->getReducedRecord($record, $keyName); - $this->assertEquals($mock->getJson($expected), $result); + + $rat = new JsonToStdErrHandler(); + $this->setClassPropertyValue($rat, 'maxRecordLength', 400); + $maxLevel = 2; // мы знаем, что значения на этом уровене делают $record слишком большим для записи, поэтому будем уменьшать значения ключей на нем + + $result = $rat->reduceRecordDataOnTheLevel($record, 2, 1, $record); + $this->assertSame($expected, $result); } }