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