За последние 24 часа нас посетили 18658 программистов и 1563 робота. Сейчас ищут 1093 программиста ...

Как правильно логировать

Тема в разделе "Решения, алгоритмы", создана пользователем supermike, 4 мар 2021.

  1. supermike

    supermike Активный пользователь

    С нами с:
    4 фев 2015
    Сообщения:
    8
    Симпатии:
    0
    Есть некий код выгрузки. Выгружает в несколько этапов товары со склада. Операция долгая и может что-то отвалиться, придется потом разбираться что пошло не так и как с этим жить. Нужно пошаговое логирование, какие данные получили, какие получились после обработки, и т.п.

    Выдуманный пример:
    PHP:
    1. class Export {
    2.   function exportByManager($data) {
    3.       log('начало операции в 12:00');
    4.       log('данные: '.$data);
    5.       $this->step1();
    6.       log('шаг 1 завершен в 12:01');
    7.       $this->step2();
    8.       log('шаг 2 завершен в 12:01');
    9.       ....
    10.       Helper::transformProductCodes($positions);
    11.       ....
    12.       log('Выгрузка завершена');
    13.     }
    14. }
    И вот вопрос. Как правильно логировать внутри функций (step1, transformProductCodes)? Если у нас общий класс, тут все просто. В конструкторе или другим путем создаем объект логгера и дальше везде его используем через $this->log(). Но если у нас местами (как в примере Helper::transformProductCodes()) используются внешний код, как в них продолжить это логирование?
    Рассматривал такие варианты:
    1) Можно передать логгер аргументом внутрь функции. Мне не нравится это тем, что мы как бы засоряем функцию аргументами, которые к основной деятельности функции не имеют прямого отношения. И еще мы навязываем свой класс логирования.
    2) Можно внутри этих методов вести свое логирование в свой файл. Но тогда будет очень сложно увидеть всю картину главного процесса выгрузки, придется бегать от файла к файлу и собирать все необходимые данные. А хочется открыть один файл, exportByManager_2021_03_04.log и четко по шагам все проверить.
    3) Можно логировать внутри функции в какую-нибудь переменную, а потом возвращать массив, где будет и результат работы функции и логи ее работы. Тогда во внешней системе мы сможем эти логи сохранить как мы хотим. Но как это совсем неправильно, т.к. мы получим на выходе из функции массивы какой бы результат не ожидался, в общем совсем не вариант.

    В идеале хотелось бы что-то вроде try catch блока, когда ты поверх вызова функции можешь отлавливать логи, если они тебе нужны. Если не нужны, то ничего не ловишь. Типа такого:
    PHP:
    1. try {
    2. Helper::transformProductCodes($positions);
    3. } catch(\log $e) {
    4.     log($e->getLogs())
    5. }

    Буду рад если расскажите как это реализовано в ваших проектах или о какие практиках слышали.
     
    #1 supermike, 4 мар 2021
    Последнее редактирование: 4 мар 2021
  2. Drunkenmunky

    Drunkenmunky Активный пользователь

    С нами с:
    12 авг 2020
    Сообщения:
    1.484
    Симпатии:
    281
    Из базы?
     
  3. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.788
    Симпатии:
    1.328
    Адрес:
    Лень
    PHP:
    1. <?php
    2.  
    3. // Логер
    4. final class Logger
    5. {
    6.     private array $lines;
    7.    
    8.     private string $file;
    9.    
    10.     public int $time;
    11.    
    12.     public function __construct ( string $filelog, int $time = null, bool $save = true )
    13.     {
    14.         $this -> file = $filelog;
    15.        
    16.         $this -> time = $time ?? time ();
    17.        
    18.         if ( $save )
    19.         {
    20.             register_shutdown_function ( [ $this, 'save' ] );
    21.         }
    22.     }
    23.    
    24.     public function set( ...$val ): \Logger
    25.     {
    26.         if ( func_num_args () > 1 )
    27.         {
    28.             $this -> lines[] = sprintf ( ...$val );
    29.            
    30.             return $this;
    31.         }
    32.        
    33.         $this -> lines[] = $val[0];
    34.        
    35.         return $this;
    36.     }
    37.    
    38.     public function save(): void
    39.     {
    40.         if ( ! empty ( $this -> lines ) )
    41.         {
    42.             $start = date ( 'Y-m-d H:i:s - ', $this -> time );
    43.            
    44.             file_put_contents ( $this -> file, $start . implode ( PHP_EOL . $start, $this -> lines ) . PHP_EOL, FILE_APPEND );
    45.            
    46.             $this -> lines = [];
    47.         }
    48.     }
    49. }
    PHP:
    1. <?php
    2.  
    3. $file_log = __DIR__ . '/file.log';
    4.  
    5. $logger = new Logger( $file_log );
    6.  
    7. $logger -> set( 'Hello' );
    8.  
    9. $logger -> set( 'Hi' );
    10.  
    11. $logger -> set( $file_log );
    12.  
    13. $logger -> set( 'afk' );
    --- Добавлено ---
    Код (Text):
    1. 2021-03-03 23:34:02 - Hello
    2. 2021-03-03 23:34:02 - Hi
    3. 2021-03-03 23:34:02 - .../file.log
    4. 2021-03-03 23:34:02 - afk
     
  4. supermike

    supermike Активный пользователь

    С нами с:
    4 фев 2015
    Сообщения:
    8
    Симпатии:
    0
    нет, он обращается к внешней системе. Скажем так, шаги: 1) обращение к внешней системе и просьба подготовить выгрузку 2) я получил оповещение что выгрузка подготовлена и я запускаю скачивание данных 3) данные скачаны, я заношу их в свою систему 4) я занес данные в систему и теперь могу запустить последующие процессы для проведения полученного товара. Каждый шаг это сложный процесс который дергает различные сервисы.
     
  5. Drunkenmunky

    Drunkenmunky Активный пользователь

    С нами с:
    12 авг 2020
    Сообщения:
    1.484
    Симпатии:
    281
    Ну так и записывайте в базу, после каждого шага.
    Уникальный ID цепочки "шагов", id шага.
    Потом элементарный подсчет шагов в цепочках поможет выявить незавершенные.
     
  6. supermike

    supermike Активный пользователь

    С нами с:
    4 фев 2015
    Сообщения:
    8
    Симпатии:
    0
    @MouseZver спасибо за код, нечто похожее у меня сейчас и используется. Но вопрос был не в коде, а в подходе к логированию. Чтобы ваш логгер записывал все в нужный файл, нужно передавать его экземпляр через аргумент в функцию, например, step1($logger). И внутри этой функции использовать именно его для логирования шагов. У меня сейчас это так и сделано и я хотел бы от этого уйти, чтобы пробрасывать во все методы новый аргумент. Потом в этих функциях появится новый аргумент и будет каша, где-то логгер передается вторым, где-то третьим аргументом, где-то не передается. Вот не хочу передавать логгер туда.
     
  7. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.788
    Симпатии:
    1.328
    Адрес:
    Лень
    Переделай в статический класс
    PHP:
    1. Logger :: init( ... );
    2.  
    3. Logger :: set( ... );
    4. Logger :: set( ... );
    5. Logger :: set( ... );
     
    Максим Матвийчина нравится это.