You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

214 lines
6.0 KiB

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