170cc2cbfSAndreas Gohr<?php 270cc2cbfSAndreas Gohr 3*8553d24dSAndreas Gohruse dokuwiki\Extension\AdminPlugin; 454cc7aa4SAndreas Gohruse dokuwiki\Form\Form; 570cc2cbfSAndreas Gohruse dokuwiki\Logger; 670cc2cbfSAndreas Gohr 770cc2cbfSAndreas Gohr/** 870cc2cbfSAndreas Gohr * DokuWiki Plugin logviewer (Admin Component) 970cc2cbfSAndreas Gohr * 1070cc2cbfSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 1170cc2cbfSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 1270cc2cbfSAndreas Gohr */ 13*8553d24dSAndreas Gohrclass admin_plugin_logviewer extends AdminPlugin 1470cc2cbfSAndreas Gohr{ 15bf9be0e3SAndreas Gohr protected const MAX_READ_SIZE = 1_048_576; // 1 MB 1670cc2cbfSAndreas Gohr 1770cc2cbfSAndreas Gohr protected $facilities; 1870cc2cbfSAndreas Gohr protected $facility; 1970cc2cbfSAndreas Gohr protected $date; 2070cc2cbfSAndreas Gohr 2170cc2cbfSAndreas Gohr /** @inheritDoc */ 2270cc2cbfSAndreas Gohr public function forAdminOnly() 2370cc2cbfSAndreas Gohr { 2470cc2cbfSAndreas Gohr return true; 2570cc2cbfSAndreas Gohr } 2670cc2cbfSAndreas Gohr 2770cc2cbfSAndreas Gohr /** @inheritDoc */ 2870cc2cbfSAndreas Gohr public function handle() 2970cc2cbfSAndreas Gohr { 3070cc2cbfSAndreas Gohr global $INPUT; 3170cc2cbfSAndreas Gohr 3270cc2cbfSAndreas Gohr $this->facilities = $this->getFacilities(); 3370cc2cbfSAndreas Gohr $this->facility = $INPUT->str('facility'); 3470cc2cbfSAndreas Gohr if (!in_array($this->facility, $this->facilities)) { 3570cc2cbfSAndreas Gohr $this->facility = $this->facilities[0]; 3670cc2cbfSAndreas Gohr } 3770cc2cbfSAndreas Gohr 3870cc2cbfSAndreas Gohr $this->date = $INPUT->str('date'); 3970cc2cbfSAndreas Gohr if (!preg_match('/^\d\d\d\d-\d\d-\d\d$/', $this->date)) { 4070cc2cbfSAndreas Gohr $this->date = gmdate('Y-m-d'); 4170cc2cbfSAndreas Gohr } 4270cc2cbfSAndreas Gohr } 4370cc2cbfSAndreas Gohr 4470cc2cbfSAndreas Gohr /** @inheritDoc */ 4570cc2cbfSAndreas Gohr public function html() 4670cc2cbfSAndreas Gohr { 4770cc2cbfSAndreas Gohr echo '<div id="plugin__logviewer">'; 4870cc2cbfSAndreas Gohr echo $this->locale_xhtml('intro'); 4970cc2cbfSAndreas Gohr $this->displayTabs(); 5070cc2cbfSAndreas Gohr $this->displayLog(); 5170cc2cbfSAndreas Gohr echo '</div>'; 5270cc2cbfSAndreas Gohr } 5370cc2cbfSAndreas Gohr 5470cc2cbfSAndreas Gohr /** 5570cc2cbfSAndreas Gohr * Show the navigational tabs and date picker 5670cc2cbfSAndreas Gohr */ 5770cc2cbfSAndreas Gohr protected function displayTabs() 5870cc2cbfSAndreas Gohr { 5970cc2cbfSAndreas Gohr global $ID; 6070cc2cbfSAndreas Gohr 6154cc7aa4SAndreas Gohr $form = new Form(['method' => 'GET']); 6270cc2cbfSAndreas Gohr $form->setHiddenField('do', 'admin'); 6370cc2cbfSAndreas Gohr $form->setHiddenField('page', 'logviewer'); 6470cc2cbfSAndreas Gohr $form->setHiddenField('facility', $this->facility); 65bc45a28eSAndreas Gohr $form->addTextInput('date', $this->getLang('date')) 66bc45a28eSAndreas Gohr ->attr('type', 'date')->val($this->date)->addClass('quickselect'); 6770cc2cbfSAndreas Gohr $form->addButton('submit', '>')->attr('type', 'submit'); 6870cc2cbfSAndreas Gohr echo $form->toHTML(); 6970cc2cbfSAndreas Gohr 7070cc2cbfSAndreas Gohr echo '<ul class="tabs">'; 7170cc2cbfSAndreas Gohr foreach ($this->facilities as $facility) { 7270cc2cbfSAndreas Gohr echo '<li>'; 7370cc2cbfSAndreas Gohr if ($facility == $this->facility) { 7470cc2cbfSAndreas Gohr echo '<strong>' . hsc($facility) . '</strong>'; 7570cc2cbfSAndreas Gohr } else { 7670cc2cbfSAndreas Gohr $link = wl($ID, 7770cc2cbfSAndreas Gohr ['do' => 'admin', 'page' => 'logviewer', 'date' => $this->date, 'facility' => $facility]); 7870cc2cbfSAndreas Gohr echo '<a href="' . $link . '">' . hsc($facility) . '</a>'; 7970cc2cbfSAndreas Gohr } 8070cc2cbfSAndreas Gohr echo '</li>'; 8170cc2cbfSAndreas Gohr } 8270cc2cbfSAndreas Gohr echo '</ul>'; 8370cc2cbfSAndreas Gohr } 8470cc2cbfSAndreas Gohr 8570cc2cbfSAndreas Gohr /** 86775003a7SHamid * Read and output the logfile contents 8770cc2cbfSAndreas Gohr */ 8870cc2cbfSAndreas Gohr protected function displayLog() 8970cc2cbfSAndreas Gohr { 9070cc2cbfSAndreas Gohr $logfile = Logger::getInstance($this->facility)->getLogfile($this->date); 9170cc2cbfSAndreas Gohr if (!file_exists($logfile)) { 9270cc2cbfSAndreas Gohr echo $this->locale_xhtml('nolog'); 9370cc2cbfSAndreas Gohr return; 9470cc2cbfSAndreas Gohr } 9570cc2cbfSAndreas Gohr 96775003a7SHamid try { 97775003a7SHamid $lines = $this->getLogLines($logfile); 98775003a7SHamid $this->printLogLines($lines); 99775003a7SHamid } catch (Exception $e) { 100775003a7SHamid msg($e->getMessage(), -1); 101775003a7SHamid } 102775003a7SHamid } 10370cc2cbfSAndreas Gohr 104775003a7SHamid /** 105775003a7SHamid * Get the available logging facilities 106775003a7SHamid * 107775003a7SHamid * @return array 108775003a7SHamid */ 109775003a7SHamid protected function getFacilities() 110775003a7SHamid { 111775003a7SHamid global $conf; 112775003a7SHamid 113775003a7SHamid // default facilities first 114775003a7SHamid $facilities = [ 115775003a7SHamid Logger::LOG_ERROR, 116775003a7SHamid Logger::LOG_DEPRECATED, 117775003a7SHamid Logger::LOG_DEBUG, 118775003a7SHamid ]; 119775003a7SHamid 120775003a7SHamid // add all other dirs 121775003a7SHamid $dirs = glob($conf['logdir'] . '/*', GLOB_ONLYDIR); 122775003a7SHamid foreach ($dirs as $dir) { 123775003a7SHamid $facilities[] = basename($dir); 124775003a7SHamid } 125775003a7SHamid $facilities = array_unique($facilities); 126775003a7SHamid 127775003a7SHamid return $facilities; 128775003a7SHamid } 129775003a7SHamid 130775003a7SHamid /** 131775003a7SHamid * Read the lines of the logfile and return them as array 132775003a7SHamid * 133775003a7SHamid * @param string $logfilePath 134775003a7SHamid * @return array 135775003a7SHamid * @throws Exception when reading fails 136775003a7SHamid */ 137775003a7SHamid protected function getLogLines($logfilePath) 138775003a7SHamid { 139775003a7SHamid global $lang; 140775003a7SHamid $size = filesize($logfilePath); 141775003a7SHamid $fp = fopen($logfilePath, 'r'); 142775003a7SHamid 143775003a7SHamid if (!$fp) throw new Exception($lang['log_file_failed_to_open']); 144775003a7SHamid 1459c697479Sfiwswe try { 146775003a7SHamid if ($size < self::MAX_READ_SIZE) { 147775003a7SHamid $toread = $size; 148775003a7SHamid } else { 149775003a7SHamid $toread = self::MAX_READ_SIZE; 150775003a7SHamid fseek($fp, -$toread, SEEK_END); 151775003a7SHamid } 152775003a7SHamid 153775003a7SHamid $logData = fread($fp, $toread); 154775003a7SHamid if (!$logData) throw new Exception($lang['log_file_failed_to_read']); 155775003a7SHamid 156775003a7SHamid $lines = explode("\n", $logData); 157775003a7SHamid unset($logData); // free memory early 158775003a7SHamid 1591eb39399Sfiwswe if ($size >= self::MAX_READ_SIZE) { 160775003a7SHamid array_shift($lines); // Discard the first line 16154cc7aa4SAndreas Gohr while ($lines !== [] && (substr($lines[0], 0, 2) === ' ')) { 162775003a7SHamid array_shift($lines); // Discard indented lines 163775003a7SHamid } 164775003a7SHamid 165775003a7SHamid // A message to inform users that previous lines are skipped 166775003a7SHamid array_unshift($lines, "******\t" . "\t" . '[' . $lang['log_file_too_large'] . ']'); 167775003a7SHamid } 1689c697479Sfiwswe } finally { 169775003a7SHamid fclose($fp); 1709c697479Sfiwswe } 1719c697479Sfiwswe 172775003a7SHamid return $lines; 173775003a7SHamid } 174775003a7SHamid 175775003a7SHamid /** 176775003a7SHamid * Get an array of log lines and print them using appropriate styles 177775003a7SHamid * 178775003a7SHamid * @param array $lines 179775003a7SHamid */ 180775003a7SHamid protected function printLogLines($lines) 181775003a7SHamid { 182775003a7SHamid $numberOfLines = count($lines); 183775003a7SHamid 184775003a7SHamid echo "<dl>"; 185775003a7SHamid for ($i = 0; $i < $numberOfLines; $i++) { 186775003a7SHamid $line = $lines[$i]; 187428df306Sfiwswe if (substr($line, 0, 2) === ' ') { 18870cc2cbfSAndreas Gohr // lines indented by two spaces are details, aggregate them 18970cc2cbfSAndreas Gohr echo '<dd>'; 190428df306Sfiwswe while (substr($line, 0, 2) === ' ') { 19170cc2cbfSAndreas Gohr echo hsc(substr($line, 2)) . '<br />'; 192586feb6eSAndreas Gohr $i++; 193586feb6eSAndreas Gohr $line = $lines[$i] ?? ''; 19470cc2cbfSAndreas Gohr } 19570cc2cbfSAndreas Gohr echo '</dd>'; 19654cc7aa4SAndreas Gohr --$i; // rewind the counter 19770cc2cbfSAndreas Gohr } else { 19870cc2cbfSAndreas Gohr // other lines are actual log lines in three parts 19954cc7aa4SAndreas Gohr [$dt, $file, $msg] = sexplode("\t", $line, 3, ''); 20070cc2cbfSAndreas Gohr echo '<dt>'; 20170cc2cbfSAndreas Gohr echo '<span class="datetime">' . hsc($dt) . '</span>'; 20270cc2cbfSAndreas Gohr echo '<span class="log">'; 20370cc2cbfSAndreas Gohr echo '<span class="msg">' . hsc($msg) . '</span>'; 20470cc2cbfSAndreas Gohr echo '<span class="file">' . hsc($file) . '</span>'; 20570cc2cbfSAndreas Gohr echo '</span>'; 20670cc2cbfSAndreas Gohr echo '</dt>'; 20770cc2cbfSAndreas Gohr } 20870cc2cbfSAndreas Gohr } 209775003a7SHamid echo "</dl>"; 21070cc2cbfSAndreas Gohr } 21170cc2cbfSAndreas Gohr} 212