1<?php 2 3use dokuwiki\Form\Form; 4use dokuwiki\Logger; 5 6/** 7 * DokuWiki Plugin logviewer (Admin Component) 8 * 9 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 10 * @author Andreas Gohr <andi@splitbrain.org> 11 */ 12class admin_plugin_logviewer extends DokuWiki_Admin_Plugin 13{ 14 protected const MAX_READ_SIZE = 1_048_576; // 1 MB 15 16 protected $facilities; 17 protected $facility; 18 protected $date; 19 20 /** @inheritDoc */ 21 public function forAdminOnly() 22 { 23 return true; 24 } 25 26 /** @inheritDoc */ 27 public function handle() 28 { 29 global $INPUT; 30 31 $this->facilities = $this->getFacilities(); 32 $this->facility = $INPUT->str('facility'); 33 if (!in_array($this->facility, $this->facilities)) { 34 $this->facility = $this->facilities[0]; 35 } 36 37 $this->date = $INPUT->str('date'); 38 if (!preg_match('/^\d\d\d\d-\d\d-\d\d$/', $this->date)) { 39 $this->date = gmdate('Y-m-d'); 40 } 41 } 42 43 /** @inheritDoc */ 44 public function html() 45 { 46 echo '<div id="plugin__logviewer">'; 47 echo $this->locale_xhtml('intro'); 48 $this->displayTabs(); 49 $this->displayLog(); 50 echo '</div>'; 51 } 52 53 /** 54 * Show the navigational tabs and date picker 55 */ 56 protected function displayTabs() 57 { 58 global $ID; 59 60 $form = new Form(['method' => 'GET']); 61 $form->setHiddenField('do', 'admin'); 62 $form->setHiddenField('page', 'logviewer'); 63 $form->setHiddenField('facility', $this->facility); 64 $form->addTextInput('date', $this->getLang('date')) 65 ->attr('type', 'date')->val($this->date)->addClass('quickselect'); 66 $form->addButton('submit', '>')->attr('type', 'submit'); 67 echo $form->toHTML(); 68 69 echo '<ul class="tabs">'; 70 foreach ($this->facilities as $facility) { 71 echo '<li>'; 72 if ($facility == $this->facility) { 73 echo '<strong>' . hsc($facility) . '</strong>'; 74 } else { 75 $link = wl($ID, 76 ['do' => 'admin', 'page' => 'logviewer', 'date' => $this->date, 'facility' => $facility]); 77 echo '<a href="' . $link . '">' . hsc($facility) . '</a>'; 78 } 79 echo '</li>'; 80 } 81 echo '</ul>'; 82 } 83 84 /** 85 * Read and output the logfile contents 86 */ 87 protected function displayLog() 88 { 89 $logfile = Logger::getInstance($this->facility)->getLogfile($this->date); 90 if (!file_exists($logfile)) { 91 echo $this->locale_xhtml('nolog'); 92 return; 93 } 94 95 try { 96 $lines = $this->getLogLines($logfile); 97 $this->printLogLines($lines); 98 } catch (Exception $e) { 99 msg($e->getMessage(), -1); 100 } 101 } 102 103 /** 104 * Get the available logging facilities 105 * 106 * @return array 107 */ 108 protected function getFacilities() 109 { 110 global $conf; 111 112 // default facilities first 113 $facilities = [ 114 Logger::LOG_ERROR, 115 Logger::LOG_DEPRECATED, 116 Logger::LOG_DEBUG, 117 ]; 118 119 // add all other dirs 120 $dirs = glob($conf['logdir'] . '/*', GLOB_ONLYDIR); 121 foreach ($dirs as $dir) { 122 $facilities[] = basename($dir); 123 } 124 $facilities = array_unique($facilities); 125 126 return $facilities; 127 } 128 129 /** 130 * Read the lines of the logfile and return them as array 131 * 132 * @param string $logfilePath 133 * @return array 134 * @throws Exception when reading fails 135 */ 136 protected function getLogLines($logfilePath) 137 { 138 global $lang; 139 $size = filesize($logfilePath); 140 $fp = fopen($logfilePath, 'r'); 141 142 if (!$fp) throw new Exception($lang['log_file_failed_to_open']); 143 144 try { 145 if ($size < self::MAX_READ_SIZE) { 146 $toread = $size; 147 } else { 148 $toread = self::MAX_READ_SIZE; 149 fseek($fp, -$toread, SEEK_END); 150 } 151 152 $logData = fread($fp, $toread); 153 if (!$logData) throw new Exception($lang['log_file_failed_to_read']); 154 155 $lines = explode("\n", $logData); 156 unset($logData); // free memory early 157 158 if ($size >= self::MAX_READ_SIZE) { 159 array_shift($lines); // Discard the first line 160 while ($lines !== [] && (substr($lines[0], 0, 2) === ' ')) { 161 array_shift($lines); // Discard indented lines 162 } 163 164 // A message to inform users that previous lines are skipped 165 array_unshift($lines, "******\t" . "\t" . '[' . $lang['log_file_too_large'] . ']'); 166 } 167 } finally { 168 fclose($fp); 169 } 170 171 return $lines; 172 } 173 174 /** 175 * Get an array of log lines and print them using appropriate styles 176 * 177 * @param array $lines 178 */ 179 protected function printLogLines($lines) 180 { 181 $numberOfLines = count($lines); 182 183 echo "<dl>"; 184 for ($i = 0; $i < $numberOfLines; $i++) { 185 $line = $lines[$i]; 186 if (substr($line, 0, 2) === ' ') { 187 // lines indented by two spaces are details, aggregate them 188 echo '<dd>'; 189 while (substr($line, 0, 2) === ' ') { 190 echo hsc(substr($line, 2)) . '<br />'; 191 $i++; 192 $line = $lines[$i] ?? ''; 193 } 194 echo '</dd>'; 195 --$i; // rewind the counter 196 } else { 197 // other lines are actual log lines in three parts 198 [$dt, $file, $msg] = sexplode("\t", $line, 3, ''); 199 echo '<dt>'; 200 echo '<span class="datetime">' . hsc($dt) . '</span>'; 201 echo '<span class="log">'; 202 echo '<span class="msg">' . hsc($msg) . '</span>'; 203 echo '<span class="file">' . hsc($file) . '</span>'; 204 echo '</span>'; 205 echo '</dt>'; 206 } 207 } 208 echo "</dl>"; 209 } 210} 211