diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | inc/Logger.php | 31 | ||||
-rw-r--r-- | inc/Ui/Admin.php | 2 | ||||
-rw-r--r-- | lib/plugins/extension/helper/extension.php | 2 | ||||
-rw-r--r-- | lib/plugins/logviewer/admin.php | 152 | ||||
-rw-r--r-- | lib/plugins/logviewer/admin.svg | 1 | ||||
-rw-r--r-- | lib/plugins/logviewer/lang/en/intro.txt | 8 | ||||
-rw-r--r-- | lib/plugins/logviewer/lang/en/lang.php | 9 | ||||
-rw-r--r-- | lib/plugins/logviewer/lang/en/nolog.txt | 1 | ||||
-rw-r--r-- | lib/plugins/logviewer/plugin.info.txt | 7 | ||||
-rw-r--r-- | lib/plugins/logviewer/script.js | 8 | ||||
-rw-r--r-- | lib/plugins/logviewer/style.less | 41 | ||||
-rw-r--r-- | lib/scripts/behaviour.js | 9 |
13 files changed, 261 insertions, 11 deletions
diff --git a/.gitignore b/.gitignore index a2e2107b1..de576886a 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ !/lib/plugins/styling !/lib/plugins/testing !/lib/plugins/usermanager +!/lib/plugins/logviewer !/lib/plugins/action.php !/lib/plugins/admin.php !/lib/plugins/auth.php diff --git a/inc/Logger.php b/inc/Logger.php index 3ff1d59b9..0ada6d63e 100644 --- a/inc/Logger.php +++ b/inc/Logger.php @@ -50,8 +50,10 @@ class Logger public function log($message, $details = null, $file = '', $line = 0) { // details are logged indented - if ($details && !is_string($details)) { - $details = json_encode($details, JSON_PRETTY_PRINT); + if ($details) { + if (!is_string($details)) { + $details = json_encode($details, JSON_PRETTY_PRINT); + } $details = explode("\n", $details); $loglines = array_map(function ($line) { return ' ' . $line; @@ -62,17 +64,35 @@ class Logger $loglines = []; } - $logline = gmdate('c') . "\t" . $message; + // datetime, fileline, message + $logline = gmdate('Y-m-d H:i:s') . "\t"; if ($file) { - $logline .= "\t$file"; + $logline .= $file; if ($line) $logline .= "($line)"; } + $logline .= "\t" . $message; array_unshift($loglines, $logline); return $this->writeLogLines($loglines); } /** + * Construct the log file for the given day + * + * @param false|string|int $date Date to access, false for today + * @return string + */ + public function getLogfile($date = false) + { + global $conf; + + if ($date !== null) $date = strtotime($date); + if (!$date) $date = time(); + + return $conf['logdir'] . '/' . $this->facility . '/' . date('Y-m-d', $date) . '.log'; + } + + /** * Write the given lines to today's facility log * * @param string[] $lines the raw lines to append to the log @@ -80,8 +100,7 @@ class Logger */ protected function writeLogLines($lines) { - global $conf; - $logfile = $conf['logdir'] . '/' . $this->facility . '/' . gmdate('Y-m-d') . '.log'; + $logfile = $this->getLogfile(); return io_saveFile($logfile, join("\n", $lines) . "\n", true); } } diff --git a/inc/Ui/Admin.php b/inc/Ui/Admin.php index fe319d414..04d8da5ec 100644 --- a/inc/Ui/Admin.php +++ b/inc/Ui/Admin.php @@ -12,7 +12,7 @@ namespace dokuwiki\Ui; */ class Admin extends Ui { - protected $forAdmins = array('usermanager', 'acl', 'extension', 'config', 'styling'); + protected $forAdmins = array('usermanager', 'acl', 'extension', 'config', 'logviewer', 'styling'); protected $forManagers = array('revert', 'popularity'); /** @var array[] */ protected $menu; diff --git a/lib/plugins/extension/helper/extension.php b/lib/plugins/extension/helper/extension.php index 5ddf3323e..eabcd3ac1 100644 --- a/lib/plugins/extension/helper/extension.php +++ b/lib/plugins/extension/helper/extension.php @@ -128,7 +128,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin array( 'authad', 'authldap', 'authpdo', 'authplain', 'acl', 'config', 'extension', 'info', 'popularity', 'revert', - 'safefnrecode', 'styling', 'testing', 'usermanager', + 'safefnrecode', 'styling', 'testing', 'usermanager', 'logviewer', 'template:dokuwiki', ) ); diff --git a/lib/plugins/logviewer/admin.php b/lib/plugins/logviewer/admin.php new file mode 100644 index 000000000..7a28e208b --- /dev/null +++ b/lib/plugins/logviewer/admin.php @@ -0,0 +1,152 @@ +<?php + +use dokuwiki\Logger; + +/** + * DokuWiki Plugin logviewer (Admin Component) + * + * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html + * @author Andreas Gohr <andi@splitbrain.org> + */ +class admin_plugin_logviewer extends DokuWiki_Admin_Plugin +{ + + protected $facilities; + protected $facility; + protected $date; + + /** @inheritDoc */ + public function forAdminOnly() + { + return true; + } + + /** @inheritDoc */ + public function handle() + { + global $INPUT; + + $this->facilities = $this->getFacilities(); + $this->facility = $INPUT->str('facility'); + if (!in_array($this->facility, $this->facilities)) { + $this->facility = $this->facilities[0]; + } + + $this->date = $INPUT->str('date'); + if (!preg_match('/^\d\d\d\d-\d\d-\d\d$/', $this->date)) { + $this->date = gmdate('Y-m-d'); + } + } + + /** @inheritDoc */ + public function html() + { + echo '<div id="plugin__logviewer">'; + echo $this->locale_xhtml('intro'); + $this->displayTabs(); + $this->displayLog(); + echo '</div>'; + } + + /** + * Show the navigational tabs and date picker + */ + protected function displayTabs() + { + global $ID; + + $form = new dokuwiki\Form\Form(['method'=>'GET']); + $form->setHiddenField('do', 'admin'); + $form->setHiddenField('page', 'logviewer'); + $form->setHiddenField('facility', $this->facility); + $form->addTextInput('date','Date*')->attr('type','date')->val($this->date)->addClass('quickselect'); + $form->addButton('submit','>')->attr('type','submit'); + echo $form->toHTML(); + + echo '<ul class="tabs">'; + foreach ($this->facilities as $facility) { + echo '<li>'; + if ($facility == $this->facility) { + echo '<strong>' . hsc($facility) . '</strong>'; + } else { + $link = wl($ID, + ['do' => 'admin', 'page' => 'logviewer', 'date' => $this->date, 'facility' => $facility]); + echo '<a href="' . $link . '">' . hsc($facility) . '</a>'; + } + echo '</li>'; + } + echo '</ul>'; + + } + + /** + * Output the logfile contents + */ + protected function displayLog() + { + $logfile = Logger::getInstance($this->facility)->getLogfile($this->date); + if (!file_exists($logfile)) { + echo $this->locale_xhtml('nolog'); + return; + } + + // loop through the file an print it + echo '<dl>'; + $lines = file($logfile); + $cnt = count($lines); + for ($i = 0; $i < $cnt; $i++) { + $line = $lines[$i]; + + if ($line[0] === ' ' && $line[1] === ' ') { + // lines indented by two spaces are details, aggregate them + echo '<dd>'; + while ($line[0] === ' ' && $line[1] === ' ') { + echo hsc(substr($line, 2)) . '<br />'; + $line = $lines[$i++]; + } + echo '</dd>'; + $i -= 2; // rewind the counter + } else { + // other lines are actual log lines in three parts + list($dt, $file, $msg) = explode("\t", $line, 3); + echo '<dt>'; + echo '<span class="datetime">' . hsc($dt) . '</span>'; + echo '<span class="log">'; + echo '<span class="msg">' . hsc($msg) . '</span>'; + echo '<span class="file">' . hsc($file) . '</span>'; + echo '</span>'; + echo '</dt>'; + } + } + echo '</dl>'; + } + + /** + * Get the available logging facilities + * + * @return array + */ + protected function getFacilities() + { + global $conf; + $conf['logdir']; + + // default facilities first + $facilities = [ + Logger::LOG_ERROR, + Logger::LOG_DEPRECATED, + Logger::LOG_DEBUG, + ]; + + // add all other dirs + $dirs = glob($conf['logdir'] . '/*', GLOB_ONLYDIR); + foreach ($dirs as $dir) { + $facilities[] = basename($dir); + } + $facilities = array_unique($facilities); + + return $facilities; + } + +} + diff --git a/lib/plugins/logviewer/admin.svg b/lib/plugins/logviewer/admin.svg new file mode 100644 index 000000000..3eb481759 --- /dev/null +++ b/lib/plugins/logviewer/admin.svg @@ -0,0 +1 @@ +<svg viewBox="0 0 24 24"><path d="M15 20a1 1 0 0 0 1-1V4H8a1 1 0 0 0-1 1v11H5V5a3 3 0 0 1 3-3h11a3 3 0 0 1 3 3v1h-2V5a1 1 0 0 0-1-1 1 1 0 0 0-1 1v14a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3v-1h11a2 2 0 0 0 2 2M9 6h5v2H9V6m0 4h5v2H9v-2m0 4h5v2H9v-2z"/></svg>
\ No newline at end of file diff --git a/lib/plugins/logviewer/lang/en/intro.txt b/lib/plugins/logviewer/lang/en/intro.txt new file mode 100644 index 000000000..f3b488ec4 --- /dev/null +++ b/lib/plugins/logviewer/lang/en/intro.txt @@ -0,0 +1,8 @@ +====== View Logs ====== + +This interface allows you to view the various logs that are written by DokuWiki. By default, there shouldn't be logged +much here (it depends on your log settings). However if something goes wrong, chances are high, you'll find useful info +here. All times are UTC! + +Please be aware **log files can contain sensitive information** like passwords, paths or other secrets. +Be sure to redact the logs appropriately when posting them on the forum or in bug reports! diff --git a/lib/plugins/logviewer/lang/en/lang.php b/lib/plugins/logviewer/lang/en/lang.php new file mode 100644 index 000000000..3f32e0bed --- /dev/null +++ b/lib/plugins/logviewer/lang/en/lang.php @@ -0,0 +1,9 @@ +<?php +/** + * English language file for logviewer plugin + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + +$lang['menu'] = 'View Logs'; + diff --git a/lib/plugins/logviewer/lang/en/nolog.txt b/lib/plugins/logviewer/lang/en/nolog.txt new file mode 100644 index 000000000..2bdefff0c --- /dev/null +++ b/lib/plugins/logviewer/lang/en/nolog.txt @@ -0,0 +1 @@ +There are no log entries for the selected day and log facility. diff --git a/lib/plugins/logviewer/plugin.info.txt b/lib/plugins/logviewer/plugin.info.txt new file mode 100644 index 000000000..d18a82c21 --- /dev/null +++ b/lib/plugins/logviewer/plugin.info.txt @@ -0,0 +1,7 @@ +base logviewer +author Andreas Gohr +email andi@splitbrain.org +date 2020-08-13 +name logviewer plugin +desc View DokuWiki logs +url https://www.dokuwiki.org/plugin:logviewer diff --git a/lib/plugins/logviewer/script.js b/lib/plugins/logviewer/script.js new file mode 100644 index 000000000..5a5303c84 --- /dev/null +++ b/lib/plugins/logviewer/script.js @@ -0,0 +1,8 @@ +/** + * Scroll to the end of the log on load + */ +jQuery(function () { + var $dl = jQuery('#plugin__logviewer').find('dl'); + if(!$dl.length) return; + $dl.animate({ scrollTop: $dl.prop("scrollHeight")}, 500); +}); diff --git a/lib/plugins/logviewer/style.less b/lib/plugins/logviewer/style.less new file mode 100644 index 000000000..f9c8b1d89 --- /dev/null +++ b/lib/plugins/logviewer/style.less @@ -0,0 +1,41 @@ +#plugin__logviewer { + form { + float: right; + } + + .tabs { + margin-bottom: 2em; + } + + dl { + max-height: 80vh; + overflow: auto; + + dt { + display: flex; + + .datetime { + flex: 0 0 auto; + margin-right: 1em; + } + + .log { + flex: 1 1 auto; + + span { + display: block; + } + + span.file { + font-family: monospace; + } + } + } + + dd { + font-size: 80%; + white-space: nowrap; + font-family: monospace; + } + } +} diff --git a/lib/scripts/behaviour.js b/lib/scripts/behaviour.js index f9aad3d02..968d2dd5f 100644 --- a/lib/scripts/behaviour.js +++ b/lib/scripts/behaviour.js @@ -102,15 +102,18 @@ var dw_behaviour = { /** * Autosubmit quick select forms * - * When a <select> tag has the class "quickselect", this script will + * When a <select> or <input> tag has the class "quickselect", this script will * automatically submit its parent form when the select value changes. * It also hides the submit button of the form. * + * This includes a workaround a weird behaviour when the submit button has a name + * + * @link https://trackjs.com/blog/when-form-submit-is-not-a-function/ * @author Andreas Gohr <andi@splitbrain.org> */ quickSelect: function(){ - jQuery('select.quickselect') - .on('change', function(e){ e.target.form.submit(); }) + jQuery('.quickselect') + .change(function(e){ HTMLFormElement.prototype.submit.call(e.target.form); }) .closest('form').find(':button').not('.show').hide(); }, |