170cc2cbfSAndreas Gohr<?php 270cc2cbfSAndreas Gohr 38553d24dSAndreas 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 */ 138553d24dSAndreas 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)) { 40*d4059ee7Shauk92 $this->date = date('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 { 76dccd6b2bSAndreas Gohr $link = wl( 77dccd6b2bSAndreas Gohr $ID, 78dccd6b2bSAndreas Gohr ['do' => 'admin', 'page' => 'logviewer', 'date' => $this->date, 'facility' => $facility] 79dccd6b2bSAndreas Gohr ); 8070cc2cbfSAndreas Gohr echo '<a href="' . $link . '">' . hsc($facility) . '</a>'; 8170cc2cbfSAndreas Gohr } 8270cc2cbfSAndreas Gohr echo '</li>'; 8370cc2cbfSAndreas Gohr } 8470cc2cbfSAndreas Gohr echo '</ul>'; 8570cc2cbfSAndreas Gohr } 8670cc2cbfSAndreas Gohr 8770cc2cbfSAndreas Gohr /** 88775003a7SHamid * Read and output the logfile contents 8970cc2cbfSAndreas Gohr */ 9070cc2cbfSAndreas Gohr protected function displayLog() 9170cc2cbfSAndreas Gohr { 9270cc2cbfSAndreas Gohr $logfile = Logger::getInstance($this->facility)->getLogfile($this->date); 9370cc2cbfSAndreas Gohr if (!file_exists($logfile)) { 9470cc2cbfSAndreas Gohr echo $this->locale_xhtml('nolog'); 9570cc2cbfSAndreas Gohr return; 9670cc2cbfSAndreas Gohr } 9770cc2cbfSAndreas Gohr 98775003a7SHamid try { 99775003a7SHamid $lines = $this->getLogLines($logfile); 100775003a7SHamid $this->printLogLines($lines); 101775003a7SHamid } catch (Exception $e) { 102775003a7SHamid msg($e->getMessage(), -1); 103775003a7SHamid } 104775003a7SHamid } 10570cc2cbfSAndreas Gohr 106775003a7SHamid /** 107775003a7SHamid * Get the available logging facilities 108775003a7SHamid * 109775003a7SHamid * @return array 110775003a7SHamid */ 111775003a7SHamid protected function getFacilities() 112775003a7SHamid { 113775003a7SHamid global $conf; 114775003a7SHamid 115775003a7SHamid // default facilities first 116775003a7SHamid $facilities = [ 117775003a7SHamid Logger::LOG_ERROR, 118775003a7SHamid Logger::LOG_DEPRECATED, 119775003a7SHamid Logger::LOG_DEBUG, 120775003a7SHamid ]; 121775003a7SHamid 122775003a7SHamid // add all other dirs 123775003a7SHamid $dirs = glob($conf['logdir'] . '/*', GLOB_ONLYDIR); 124775003a7SHamid foreach ($dirs as $dir) { 125775003a7SHamid $facilities[] = basename($dir); 126775003a7SHamid } 127775003a7SHamid $facilities = array_unique($facilities); 128775003a7SHamid 129775003a7SHamid return $facilities; 130775003a7SHamid } 131775003a7SHamid 132775003a7SHamid /** 133775003a7SHamid * Read the lines of the logfile and return them as array 134775003a7SHamid * 135775003a7SHamid * @param string $logfilePath 136775003a7SHamid * @return array 137775003a7SHamid * @throws Exception when reading fails 138775003a7SHamid */ 139775003a7SHamid protected function getLogLines($logfilePath) 140775003a7SHamid { 141775003a7SHamid global $lang; 142775003a7SHamid $size = filesize($logfilePath); 143775003a7SHamid $fp = fopen($logfilePath, 'r'); 144775003a7SHamid 145775003a7SHamid if (!$fp) throw new Exception($lang['log_file_failed_to_open']); 146775003a7SHamid 1479c697479Sfiwswe try { 148775003a7SHamid if ($size < self::MAX_READ_SIZE) { 149775003a7SHamid $toread = $size; 150775003a7SHamid } else { 151775003a7SHamid $toread = self::MAX_READ_SIZE; 152775003a7SHamid fseek($fp, -$toread, SEEK_END); 153775003a7SHamid } 154775003a7SHamid 155775003a7SHamid $logData = fread($fp, $toread); 156775003a7SHamid if (!$logData) throw new Exception($lang['log_file_failed_to_read']); 157775003a7SHamid 158775003a7SHamid $lines = explode("\n", $logData); 159775003a7SHamid unset($logData); // free memory early 160775003a7SHamid 1611eb39399Sfiwswe if ($size >= self::MAX_READ_SIZE) { 162775003a7SHamid array_shift($lines); // Discard the first line 1631b2deed9Sfiwswe while ($lines !== [] && str_starts_with($lines[0], ' ')) { 164775003a7SHamid array_shift($lines); // Discard indented lines 165775003a7SHamid } 166775003a7SHamid 167775003a7SHamid // A message to inform users that previous lines are skipped 168775003a7SHamid array_unshift($lines, "******\t" . "\t" . '[' . $lang['log_file_too_large'] . ']'); 169775003a7SHamid } 1709c697479Sfiwswe } finally { 171775003a7SHamid fclose($fp); 1729c697479Sfiwswe } 1739c697479Sfiwswe 174775003a7SHamid return $lines; 175775003a7SHamid } 176775003a7SHamid 177775003a7SHamid /** 178775003a7SHamid * Get an array of log lines and print them using appropriate styles 179775003a7SHamid * 180775003a7SHamid * @param array $lines 181775003a7SHamid */ 182775003a7SHamid protected function printLogLines($lines) 183775003a7SHamid { 184775003a7SHamid $numberOfLines = count($lines); 185775003a7SHamid 186775003a7SHamid echo "<dl>"; 187775003a7SHamid for ($i = 0; $i < $numberOfLines; $i++) { 188775003a7SHamid $line = $lines[$i]; 1891b2deed9Sfiwswe if (str_starts_with($line, ' ')) { 19070cc2cbfSAndreas Gohr // lines indented by two spaces are details, aggregate them 19170cc2cbfSAndreas Gohr echo '<dd>'; 1921b2deed9Sfiwswe while (str_starts_with($line, ' ')) { 19370cc2cbfSAndreas Gohr echo hsc(substr($line, 2)) . '<br />'; 194586feb6eSAndreas Gohr $i++; 195586feb6eSAndreas Gohr $line = $lines[$i] ?? ''; 19670cc2cbfSAndreas Gohr } 19770cc2cbfSAndreas Gohr echo '</dd>'; 19854cc7aa4SAndreas Gohr --$i; // rewind the counter 19970cc2cbfSAndreas Gohr } else { 20070cc2cbfSAndreas Gohr // other lines are actual log lines in three parts 20154cc7aa4SAndreas Gohr [$dt, $file, $msg] = sexplode("\t", $line, 3, ''); 20270cc2cbfSAndreas Gohr echo '<dt>'; 20370cc2cbfSAndreas Gohr echo '<span class="datetime">' . hsc($dt) . '</span>'; 20470cc2cbfSAndreas Gohr echo '<span class="log">'; 20570cc2cbfSAndreas Gohr echo '<span class="msg">' . hsc($msg) . '</span>'; 20670cc2cbfSAndreas Gohr echo '<span class="file">' . hsc($file) . '</span>'; 20770cc2cbfSAndreas Gohr echo '</span>'; 20870cc2cbfSAndreas Gohr echo '</dt>'; 20970cc2cbfSAndreas Gohr } 21070cc2cbfSAndreas Gohr } 211775003a7SHamid echo "</dl>"; 21270cc2cbfSAndreas Gohr } 21370cc2cbfSAndreas Gohr} 214