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