Skip to content
Snippets Groups Projects
Select Git revision
  • ca920a6dffcc823403bf02036c8c9d3080e2d659
  • master default protected
  • Legacy_Php7
3 results

logger.class.php

Blame
  • logger.class.php 14.22 KiB
    <?php
    
    /**
     * ----------------------------------------------------------------------
     * 
     * Debug logging during a client request.
     * So you can measure any action find bottlenecks in your code.
     * 
     * Licence: GNU GPL 3.0
     * Source:  https://github.com/axelhahn/ahlogger
     * Docs:    https://www.axel-hahn.de/docs/ahlogger/
     * 
     * USAGE:<br>
     * (1) Trigger a message with add() to add a marker<br>
     * (2) The render() method lists all items in a table with time since start
     *     and the delta to the last message. <br>
     * 
     * @author www.axel-hahn.de
     * 
     * ----------------------------------------------------------------------
     * 2016-02-26  init
     * 2016-11-19  add memory usage
     * (...)
     * 2022-09-25  add memory tracking, add cli renderer 
     * 2022-09-27  css updates
     * 2022-10-02  add emoji chars 
     * 2022-10-16  mark longest action with an icon 
     * 2022-12-15  make it compatible to PHP 8.2; add doc + comments
     * 2023-05-15  fix _getBar() - division by zero
     * 2024-07-12  php8 only: use variable types; update phpdocs
     * 2024-09-04  fix short array syntax
     * ----------------------------------------------------------------------
     */
    class logger
    {
    
        /**
         * @var {array} array of added messages
         */
        protected $aMessages = [];
    
        /**
         * @var {bool} flag: show debug infos? default: false
         */
        protected $bShowDebug = false;
    
        /**
         * @var {int} memory usage on start
         */
        protected $_iMemStart = false;
    
        /**
         * @var {string} dynamic prefix for used css - it is set in the cronstructor
         */
        protected $sCssPrefix = '';
    
        protected $sSourceUrl = 'https://github.com/axelhahn/ahlogger';
    
        // ----------------------------------------------------------------------
        // CONSTRUCTOR
        // ----------------------------------------------------------------------
    
        /**
         * Constuctor
         * @param  string $sInitMessage  init message
         */
        public function __construct(string $sInitMessage = "Logger was initialized.")
        {
            $this->_iMemStart = memory_get_usage();
            $this->enableDebug(true);
            $this->add($sInitMessage);
            $this->sCssPrefix = 'debug-' . md5(microtime(true));
        }
    
        // ----------------------------------------------------------------------
        // PUBLIC METHODS
        // ----------------------------------------------------------------------
    
        /**
         * Add a logging message
         * @param string $sMessage
         * @param string $sLevel
         * @return boolean
         */
        public function add(string $sMessage, string $sLevel = "info"): bool
        {
            if (!$this->bShowDebug) {
                return false;
            }
            $this->aMessages[] = [
                'time' => microtime(true),
                'message' => $sMessage,
                'level' => preg_replace('/[^a-z0-9\-\_]/', '', $sLevel),
                'memory' => memory_get_usage()
            ];
    
            return true;
        }
    
        /**
         * Enable / disable debugging
         * @param bool $bEnable
         * @return bool
         */
        public function enableDebug(bool $bEnable = true): bool
        {
            return $this->bShowDebug = !!$bEnable;
        }
    
        /**
         * Enable client debugging by a given array of allowed ip addresses
         * @param array $aIpArray list of ip addresses in a flat array
         * @return boolean
         */
        public function enableDebugByIp(array $aIpArray): bool
        {
            $this->enableDebug(false);
            if (!$_SERVER || !is_array($_SERVER) || !array_key_exists("REMOTE_ADDR", $_SERVER)) {
                return false;
            }
            if (array_search($_SERVER['REMOTE_ADDR'], $aIpArray) !== false) {
                $this->enableDebug(true);
            }
            return true;
        }
    
        /**
         * Helper function: prepare array of added massages before output
         * - detect warnings and errors
         * - detect needed time for each action
         * - detect longest action
         * - detect maximum of memory usage
         * - calculate total time
         * 
         * @return array
         */
        protected function _prepareRendering(): array
        {
            $iMem = memory_get_usage();
            $this->add('<hr>');
            $this->add('Memory on start: ' . number_format($this->_iMemStart, 0, '.', ',') . " bytes");
            $this->add('Memory on end: ' . number_format($iMem, 0, '.', ',') . " bytes");
            $this->add('Memory peak: ' . number_format(memory_get_peak_usage(), 0, '.', ',') . " bytes");
    
            $aReturn = [
                'totaltime' => false,
                'level' => false,
                'warnings' => '',
                'errors' => '',
                'maxrowid' => false,
                'maxtime' => false,
                'result' => []
            ];
            $sStarttime = $this->aMessages[0]["time"];
            $iLasttime = $sStarttime;
            $iCounter = 0;
            $sMaxRowId = false;
            $iMaxtime = -1;
            $iMaxmem = -1;
            $bHasWarning = false;
            $bHasError = false;
    
            foreach ($this->aMessages as $aLogentry) {
                $iCounter++;
    
                if ($aLogentry["level"] == "warning") {
                    $bHasWarning = true;
                }
                if ($aLogentry["level"] == "error") {
                    $bHasError = true;
                }
    
                $sTrId = $this->sCssPrefix . 'debugTableRow' . $iCounter;
                $iDelta = $aLogentry["time"] - $iLasttime;
                if ($iDelta > $iMaxtime) {
                    $iMaxtime = $iDelta;
                    $sMaxRowId = $sTrId;
                }
                $iMaxmem = max($aLogentry["memory"], $iMaxmem);
    
    
                if (($iDelta > 1) || $aLogentry["level"] == "warning") {
                    $aReturn['warnings'] .= '<a href="#' . $sTrId . '" title="' . sprintf("%01.4f", $iDelta) . ' s">' . $iCounter . '</a>&nbsp;';
                }
                if ($aLogentry["level"] == "error") {
                    $aReturn['errors'] .= '<a href="#' . $sTrId . '" title="' . sprintf("%01.4f", $iDelta) . ' s">' . $iCounter . '</a>&nbsp;';
                }
                $aReturn['entries'][] = [
                    'time' => $aLogentry["time"],
                    'level' => $aLogentry["level"],
                    'message' => $aLogentry["message"],
                    'memory' => sprintf("%01.2f", $aLogentry["memory"] / 1024 / 1024), // MB
    
                    'trid' => $sTrId,
                    'trclass' => $aLogentry["level"],
                    'counter' => $iCounter,
                    'timer' => sprintf("%01.3f", $aLogentry["time"] - $sStarttime),
                    'delta' => sprintf("%01.0f", $iDelta * 1000),
                ];
                $iLasttime = $aLogentry["time"];
            }
            $aReturn['level'] = ($bHasWarning
                ? ($bHasError ? 'error' : 'warning')
                : ''
            );
            $aReturn['maxrowid'] = $sMaxRowId;
            $aReturn['maxtime'] = sprintf("%01.3f", $iMaxtime);
            $aReturn['maxmem'] = sprintf("%01.2f", $iMaxmem / 1024 / 1024);
            $aReturn['totaltime'] = sprintf("%01.3f", $aLogentry['time'] - $aReturn['entries'][0]['time']);
            return $aReturn;
        }
    
        /**
         * Get html code for a progressbar with divs
         * @param  int|float  $iVal  value between 0..max value
         * @param  int|float  $iMax  max value
         * @return string
         */
        protected function _getBar(int|float $iVal, int|float $iMax): string
        {
            return $iMax > 0
                ? '<div class="bar"><div class="progress" style="width: ' . ($iVal / $iMax * 100) . '%;">&nbsp;</div></div>'
                : ''
            ;
        }
    
        /**
         * Render output of all logging messages
         * @return string
         */
        public function render(): string
        {
            if (!$this->bShowDebug) {
                return false;
            }
            $aData = $this->_prepareRendering();
    
            /*
            Array
            (
                [totaltime] => 0.006
                [errors] =>  
                [warnings] => 3 
                [maxrowid] => debugTableRow3
                [maxtime] => 0.005
                [result] => Array
                    (
                    )
    
                [entries] => Array
                    mit Elementen
                    Array
                        (
                            [time] => 1663959608.2566
                            [level] => info
                            [message] => Logger was initialized.
                            [memory] => 538056
                            [trid] => debugTableRow1
                            [trclass] => info
                            [trstyle] => 
                            [counter] => 1
                            [timer] => 0.000
                            [delta] => 0.000
                        )
            */
    
            $sOut = '';
            // echo '<pre>'; print_r($aData); die();
            foreach ($aData['entries'] as $aLogentry) {
                $sOut .= '<tr class="' . $this->sCssPrefix . '-level-' . $aLogentry["level"] . '' . ($aLogentry["trid"] == $aData["maxrowid"] ? ' ' . $this->sCssPrefix . '-maxrow' : '') . '" '
                    . 'id="' . $aLogentry["trid"] . '">' .
                    '<td align="right">' . $aLogentry["counter"] . '</td>' .
                    '<td>' . $aLogentry["level"] . '</td>' .
                    '<td align="right">' . $aLogentry["timer"] . '</td>' .
                    '<td align="right">' . $this->_getBar($aLogentry["delta"], $aData["maxtime"] * 1000) . ($aLogentry["delta"] == $aData['maxtime'] * 1000 ? '⏱️    ' : '') . $aLogentry["delta"] . ' ms</td>' .
                    '<td align="right">' . $this->_getBar($aLogentry["memory"], $aData["maxmem"]) . $aLogentry["memory"] . ' MB' . '</td>' .
                    '<td>' . $aLogentry["message"] . '</td>' .
                    '</tr>';
            }
            if ($sOut) {
                $sOut = '
                <style>
                    .' . $this->sCssPrefix . '-info {position: fixed; top: 6em; right: 1em; background: rgba(230,240,255, 0.8); border: 2px solid rgba(0,0,0,0.2); border-radius: 0.3em; z-index: 99999;}
                    .' . $this->sCssPrefix . '-info .loggerhead    {background: rgba(0,0,0,0.4); color: #fff;padding: 0em 0.5em 0.2em; border-radius: 0.3em 0.3em 0 0; }
                    .' . $this->sCssPrefix . '-info .loggercontent {padding: 0.5em; }
                    .' . $this->sCssPrefix . '-info .loggercontent .total {font-size: 160%; color: rgba(0,0,0,0.5); margin: 0.3em 0; display: inline-block;}
    
                    .' . $this->sCssPrefix . '-messages {margin: 5em 2em 2em;}
                    .' . $this->sCssPrefix . '-messages>h3 {font-size: 150%; margin: 0 0 0.5em 0;}
                    .' . $this->sCssPrefix . '-messages .bar      {background: rgba(0,0,0,0.03); height: 1.4em; position: absolute; width: 6em; border-right: 1px solid rgba(0,0,0,0.2);}
                    .' . $this->sCssPrefix . '-messages .progress {background: rgba(100,140,180,0.2); height: 1.4em; padding: 0; float: left;}
                    .' . $this->sCssPrefix . '-messages table{background: #fff; color: #222;table-layout:fixed; border: 2px solid rgba(0,0,0,0.2); border-radius: 0.5em;}
                    .' . $this->sCssPrefix . '-messages table th{background: none; color: #222; border-bottom: 2px solid rgba(0,0,0,0.4);}
                    .' . $this->sCssPrefix . '-messages table th.barcol{min-width: 7em; position: relative;}
                    .' . $this->sCssPrefix . '-messages table td{padding: 3px; vertical-align: top;}
                    .' . $this->sCssPrefix . '-messages table th:hover{background:#aaa !important;}
    
                    .' . $this->sCssPrefix . '-level-info{background: #f0f4f4; color:#124}
                    .' . $this->sCssPrefix . '-level-warning{background: #fcf8e3; color: #980;}
                    .' . $this->sCssPrefix . '-level-error{background: #fce0e0; color: #944;}
                    .' . $this->sCssPrefix . '-maxrow{color:#f33; font-weight: bold;}
                </style>
                <div class="' . $this->sCssPrefix . ' ' . $this->sCssPrefix . '-info ' . $this->sCssPrefix . '-level-' . $aData['level'] . '" onclick="location.href=\'#' . $this->sCssPrefix . '-messages\';">
                    <div class="loggerhead">ahLogger</div>
                    <div class="loggercontent">
                        <span class="total">⏱️ ' . $aData['totaltime'] . '&nbsp;s</span><br>
                        🪲 <a href="#' . $this->sCssPrefix . '-messages">Debug infos</a> | 🔺 <a href="#">top</a><br>
                        <span>longest&nbsp;action: ⏱️&nbsp;<a href="#' . $aData['maxrowid'] . '">' . ($aData['maxtime'] * 1000) . '&nbsp;ms</a></span>
                        ' . ($aData['errors'] ? '<br><span>‼️ Errors: ' . $aData['errors'] . '</span>' : '') . '
                        ' . ($aData['warnings'] ? '<br><span>⚠️ Warnings: ' . $aData['warnings'] . '</span>' : '') . '
                    </div>
                </div>
    
                <div id="' . $this->sCssPrefix . '-messages" class="' . $this->sCssPrefix . ' ' . $this->sCssPrefix . '-messages">
                <h3>ahLogger 🪳 Debug messages</h3>'
                    . ($aData['errors'] ? '<span>Errors: ' . $aData['errors'] . '</span><br>' : '')
                    . ($aData['warnings'] ? '<span>Warnings: ' . $aData['warnings'] . '</span><br>' : '')
                    . '<br>
                <table >
                <thead>
                <tr>
                    <th>#</th>
                    <th>level</th>
                    <th>time [s]</th>
                    <th class="barcol">delta</th>
                    <th class="barcol">memory</th>
                    <th>message</th>
                </tr></thead><tbody>
                ' . $sOut 
                . '</tbody></table>'
                . '🌐 <a href="'.$this->sSourceUrl.'" target="_blank">'.$this->sSourceUrl.'</a>'
                ;
            }
            return $sOut;
        }
    
        /**
         * Render output of all logging messages for cli output
         * @return string
         */
        public function renderCli(): string
        {
            if (!$this->bShowDebug) {
                return false;
            }
            $aData = $this->_prepareRendering();
    
            $sOut = '';
            foreach ($aData['entries'] as $aLogentry) {
                $sOut .= $aLogentry["timer"] . ' | '
                    . $aLogentry["delta"] . ' ms | '
                    . $aLogentry["level"] . ' | '
                    . (sprintf("%01.3f", $aLogentry["memory"] / 1024 / 1024)) . ' MB | '
                    . $aLogentry["message"] . ' '
                    . "\n"
                ;
            }
            $sOut .= "\nTotal time: " . $aData['totaltime'] . "\n";
            return $sOut;
        }
    }