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 = date('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( 77 $ID, 78 ['do' => 'admin', 'page' => 'logviewer', 'date' => $this->date, 'facility' => $facility] 79 ); 80 echo '<a href="' . $link . '">' . hsc($facility) . '</a>'; 81 } 82 echo '</li>'; 83 } 84 echo '</ul>'; 85 } 86 87 /** 88 * Read and output the logfile contents 89 */ 90 protected function displayLog() 91 { 92 $logfile = Logger::getInstance($this->facility)->getLogfile($this->date); 93 if (!file_exists($logfile)) { 94 echo $this->locale_xhtml('nolog'); 95 return; 96 } 97 98 try { 99 $lines = $this->getLogLines($logfile); 100 $this->printLogLines($lines); 101 } catch (Exception $e) { 102 msg($e->getMessage(), -1); 103 } 104 } 105 106 /** 107 * Get the available logging facilities 108 * 109 * @return array 110 */ 111 protected function getFacilities() 112 { 113 global $conf; 114 115 // default facilities first 116 $facilities = [ 117 Logger::LOG_ERROR, 118 Logger::LOG_DEPRECATED, 119 Logger::LOG_DEBUG, 120 ]; 121 122 // add all other dirs 123 $dirs = glob($conf['logdir'] . '/*', GLOB_ONLYDIR); 124 foreach ($dirs as $dir) { 125 $facilities[] = basename($dir); 126 } 127 $facilities = array_unique($facilities); 128 129 return $facilities; 130 } 131 132 /** 133 * Read the lines of the logfile and return them as array 134 * 135 * @param string $logfilePath 136 * @return array 137 * @throws Exception when reading fails 138 */ 139 protected function getLogLines($logfilePath) 140 { 141 global $lang; 142 $size = filesize($logfilePath); 143 $fp = fopen($logfilePath, 'r'); 144 145 if (!$fp) throw new Exception($lang['log_file_failed_to_open']); 146 147 try { 148 if ($size < self::MAX_READ_SIZE) { 149 $toread = $size; 150 } else { 151 $toread = self::MAX_READ_SIZE; 152 fseek($fp, -$toread, SEEK_END); 153 } 154 155 $logData = fread($fp, $toread); 156 if (!$logData) throw new Exception($lang['log_file_failed_to_read']); 157 158 $lines = explode("\n", $logData); 159 unset($logData); // free memory early 160 161 if ($size >= self::MAX_READ_SIZE) { 162 array_shift($lines); // Discard the first line 163 while ($lines !== [] && str_starts_with($lines[0], ' ')) { 164 array_shift($lines); // Discard indented lines 165 } 166 167 // A message to inform users that previous lines are skipped 168 array_unshift($lines, "******\t" . "\t" . '[' . $lang['log_file_too_large'] . ']'); 169 } 170 } finally { 171 fclose($fp); 172 } 173 174 return $lines; 175 } 176 177 /** 178 * Get an array of log lines and print them using appropriate styles 179 * 180 * @param array $lines 181 */ 182 protected function printLogLines($lines) 183 { 184 $numberOfLines = count($lines); 185 186 echo "<dl>"; 187 for ($i = 0; $i < $numberOfLines; $i++) { 188 $line = $lines[$i]; 189 if (str_starts_with($line, ' ')) { 190 // lines indented by two spaces are details, aggregate them 191 echo '<dd>'; 192 while (str_starts_with($line, ' ')) { 193 echo hsc(substr($line, 2)) . '<br />'; 194 $i++; 195 $line = $lines[$i] ?? ''; 196 } 197 echo '</dd>'; 198 --$i; // rewind the counter 199 } else { 200 // other lines are actual log lines in three parts 201 [$dt, $file, $msg] = sexplode("\t", $line, 3, ''); 202 echo '<dt>'; 203 echo '<span class="datetime">' . hsc($dt) . '</span>'; 204 echo '<span class="log">'; 205 echo '<span class="msg">' . hsc($msg) . '</span>'; 206 echo '<span class="file">' . hsc($file) . '</span>'; 207 echo '</span>'; 208 echo '</dt>'; 209 } 210 } 211 echo "</dl>"; 212 } 213} 214