Merge branch 'logging'

* logging:
  added JavaScript based filter mechanism
  added logging configuration
  replaced out calls to dbglog with new Logger calls
  added convenience methods to log to our default facilities
  added logviwer admin plugin
  added log dir to git
  central logging mechanism
This commit is contained in:
Andreas Gohr 2020-12-03 20:01:57 +01:00
commit 413313a155
24 changed files with 493 additions and 58 deletions

1
.gitignore vendored
View File

@ -56,6 +56,7 @@
!/lib/plugins/styling !/lib/plugins/styling
!/lib/plugins/testing !/lib/plugins/testing
!/lib/plugins/usermanager !/lib/plugins/usermanager
!/lib/plugins/logviewer
!/lib/plugins/action.php !/lib/plugins/action.php
!/lib/plugins/admin.php !/lib/plugins/admin.php
!/lib/plugins/auth.php !/lib/plugins/auth.php

View File

@ -111,6 +111,7 @@ $conf['mailfrom'] = ''; //use this email when sending mails
$conf['mailreturnpath'] = ''; //use this email as returnpath for bounce mails $conf['mailreturnpath'] = ''; //use this email as returnpath for bounce mails
$conf['mailprefix'] = ''; //use this as prefix of outgoing mails $conf['mailprefix'] = ''; //use this as prefix of outgoing mails
$conf['htmlmail'] = 1; //send HTML multipart mails $conf['htmlmail'] = 1; //send HTML multipart mails
$conf['dontlog'] = 'debug'; //logging facilites that should be disabled
/* Syndication Settings */ /* Syndication Settings */
$conf['sitemap'] = 0; //Create a google sitemap? How often? In days. $conf['sitemap'] = 0; //Create a google sitemap? How often? In days.

1
data/log/_dummy Normal file
View File

@ -0,0 +1 @@
You can safely delete this file.

View File

@ -5,6 +5,7 @@ namespace dokuwiki\Debug;
use Doku_Event; use Doku_Event;
use dokuwiki\Extension\EventHandler; use dokuwiki\Extension\EventHandler;
use dokuwiki\Logger;
class DebugHelper class DebugHelper
{ {
@ -164,7 +165,7 @@ class DebugHelper
if ($event->data['alternative']) { if ($event->data['alternative']) {
$msg .= ' ' . $event->data['alternative'] . ' should be used instead!'; $msg .= ' ' . $event->data['alternative'] . ' should be used instead!';
} }
dbglog($msg); Logger::getInstance(Logger::LOG_DEPRECATED)->log($msg);
} }
$event->advise_after(); $event->advise_after();
} }

View File

@ -36,7 +36,7 @@ class ErrorHandler
$msg = 'An unforeseen error has occured. This is most likely a bug somewhere.'; $msg = 'An unforeseen error has occured. This is most likely a bug somewhere.';
if ($plugin) $msg .= ' It might be a problem in the ' . $plugin . ' plugin.'; if ($plugin) $msg .= ' It might be a problem in the ' . $plugin . ' plugin.';
$logged = self::logException($e) $logged = self::logException($e)
? 'More info has been written to the DokuWiki _error.log' ? 'More info has been written to the DokuWiki error log.'
: $e->getFile() . ':' . $e->getLine(); : $e->getFile() . ':' . $e->getLine();
echo <<<EOT echo <<<EOT
@ -64,7 +64,7 @@ EOT;
public static function showExceptionMsg($e, $intro = 'Error!') public static function showExceptionMsg($e, $intro = 'Error!')
{ {
$msg = hsc($intro) . '<br />' . hsc(get_class($e) . ': ' . $e->getMessage()); $msg = hsc($intro) . '<br />' . hsc(get_class($e) . ': ' . $e->getMessage());
if (self::logException($e)) $msg .= '<br />More info is available in the _error.log'; if (self::logException($e)) $msg .= '<br />More info is available in the error log.';
msg($msg, -1); msg($msg, -1);
} }
@ -100,16 +100,12 @@ EOT;
*/ */
public static function logException($e) public static function logException($e)
{ {
global $conf; return Logger::getInstance()->log(
get_class($e) . ': ' . $e->getMessage(),
$log = join("\t", [ $e->getTraceAsString(),
gmdate('c'), $e->getFile(),
get_class($e), $e->getLine()
$e->getFile() . '(' . $e->getLine() . ')', );
$e->getMessage(),
]) . "\n";
$log .= $e->getTraceAsString() . "\n";
return io_saveFile($conf['cachedir'] . '/_error.log', $log, true);
} }
/** /**

View File

@ -3,6 +3,8 @@
namespace dokuwiki\Extension; namespace dokuwiki\Extension;
use dokuwiki\Logger;
/** /**
* The Action plugin event * The Action plugin event
*/ */
@ -71,7 +73,8 @@ class Event
if ($EVENT_HANDLER !== null) { if ($EVENT_HANDLER !== null) {
$EVENT_HANDLER->process_event($this, 'BEFORE'); $EVENT_HANDLER->process_event($this, 'BEFORE');
} else { } else {
dbglog($this->name . ':BEFORE event triggered before event system was initialized'); Logger::getInstance(Logger::LOG_DEBUG)
->log($this->name . ':BEFORE event triggered before event system was initialized');
} }
return (!$enablePreventDefault || $this->runDefault); return (!$enablePreventDefault || $this->runDefault);
@ -92,7 +95,8 @@ class Event
if ($EVENT_HANDLER !== null) { if ($EVENT_HANDLER !== null) {
$EVENT_HANDLER->process_event($this, 'AFTER'); $EVENT_HANDLER->process_event($this, 'AFTER');
} else { } else {
dbglog($this->name . ':AFTER event triggered before event system was initialized'); Logger::getInstance(Logger::LOG_DEBUG)->
log($this->name . ':AFTER event triggered before event system was initialized');
} }
} }

164
inc/Logger.php Normal file
View File

@ -0,0 +1,164 @@
<?php
namespace dokuwiki;
class Logger
{
const LOG_ERROR = 'error';
const LOG_DEPRECATED = 'deprecated';
const LOG_DEBUG = 'debug';
/** @var Logger[] */
static protected $instances;
/** @var string what kind of log is this */
protected $facility;
protected $isLogging = true;
/**
* Logger constructor.
*
* @param string $facility The type of log
*/
protected function __construct($facility)
{
global $conf;
$this->facility = $facility;
// Should logging be disabled for this facility?
$dontlog = explode(',', $conf['dontlog']);
$dontlog = array_map('trim', $dontlog);
if (in_array($facility, $dontlog)) $this->isLogging = false;
}
/**
* Return a Logger instance for the given facility
*
* @param string $facility The type of log
* @return Logger
*/
static public function getInstance($facility = self::LOG_ERROR)
{
if (self::$instances[$facility] === null) {
self::$instances[$facility] = new Logger($facility);
}
return self::$instances[$facility];
}
/**
* Convenience method to directly log to the error log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
static public function error($message, $details = null, $file = '', $line = 0)
{
return self::getInstance(self::LOG_ERROR)->log(
$message, $details, $file, $line
);
}
/**
* Convenience method to directly log to the debug log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
static public function debug($message, $details = null, $file = '', $line = 0)
{
return self::getInstance(self::LOG_DEBUG)->log(
$message, $details, $file, $line
);
}
/**
* Convenience method to directly log to the deprecation log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
static public function deprecated($message, $details = null, $file = '', $line = 0)
{
return self::getInstance(self::LOG_DEPRECATED)->log(
$message, $details, $file, $line
);
}
/**
* Log a message to the facility log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
public function log($message, $details = null, $file = '', $line = 0)
{
if(!$this->isLogging) return false;
// details are logged indented
if ($details) {
if (!is_string($details)) {
$details = json_encode($details, JSON_PRETTY_PRINT);
}
$details = explode("\n", $details);
$loglines = array_map(function ($line) {
return ' ' . $line;
}, $details);
} elseif ($details) {
$loglines = [$details];
} else {
$loglines = [];
}
// datetime, fileline, message
$logline = gmdate('Y-m-d H:i:s') . "\t";
if ($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
* @return bool true if the log was written
*/
protected function writeLogLines($lines)
{
$logfile = $this->getLogfile();
return io_saveFile($logfile, join("\n", $lines) . "\n", true);
}
}

View File

@ -9,6 +9,7 @@
namespace dokuwiki\Sitemap; namespace dokuwiki\Sitemap;
use dokuwiki\HTTP\DokuHTTPClient; use dokuwiki\HTTP\DokuHTTPClient;
use dokuwiki\Logger;
/** /**
* A class for building sitemaps and pinging search engines with the sitemap URL. * A class for building sitemaps and pinging search engines with the sitemap URL.
@ -43,14 +44,14 @@ class Mapper {
if(@filesize($sitemap) && if(@filesize($sitemap) &&
@filemtime($sitemap) > (time()-($conf['sitemap']*86400))){ // 60*60*24=86400 @filemtime($sitemap) > (time()-($conf['sitemap']*86400))){ // 60*60*24=86400
dbglog('Sitemapper::generate(): Sitemap up to date'); Logger::debug('Sitemapper::generate(): Sitemap up to date');
return false; return false;
} }
dbglog("Sitemapper::generate(): using $sitemap"); Logger::debug("Sitemapper::generate(): using $sitemap");
$pages = idx_get_indexer()->getPages(); $pages = idx_get_indexer()->getPages();
dbglog('Sitemapper::generate(): creating sitemap using '.count($pages).' pages'); Logger::debug('Sitemapper::generate(): creating sitemap using '.count($pages).' pages');
$items = array(); $items = array();
// build the sitemap items // build the sitemap items
@ -150,10 +151,11 @@ class Mapper {
$event = new \dokuwiki\Extension\Event('SITEMAP_PING', $data); $event = new \dokuwiki\Extension\Event('SITEMAP_PING', $data);
if ($event->advise_before(true)) { if ($event->advise_before(true)) {
foreach ($data['ping_urls'] as $name => $url) { foreach ($data['ping_urls'] as $name => $url) {
dbglog("Sitemapper::PingSearchEngines(): pinging $name"); Logger::debug("Sitemapper::PingSearchEngines(): pinging $name");
$resp = $http->get($url); $resp = $http->get($url);
if($http->error) dbglog("Sitemapper:pingSearchengines(): $http->error"); if($http->error) {
dbglog('Sitemapper:pingSearchengines(): '.preg_replace('/[\n\r]/',' ',strip_tags($resp))); Logger::debug("Sitemapper:pingSearchengines(): $http->error", $resp);
}
} }
} }
$event->advise_after(); $event->advise_after();

View File

@ -14,7 +14,7 @@ use dokuwiki\Utf8\Sort;
*/ */
class Admin extends 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'); protected $forManagers = array('revert', 'popularity');
/** @var array[] */ /** @var array[] */
protected $menu; protected $menu;

View File

@ -7,6 +7,7 @@
*/ */
use dokuwiki\HTTP\DokuHTTPClient; use dokuwiki\HTTP\DokuHTTPClient;
use dokuwiki\Logger;
if(!defined('DOKU_MESSAGEURL')){ if(!defined('DOKU_MESSAGEURL')){
if(in_array('ssl', stream_get_transports())) { if(in_array('ssl', stream_get_transports())) {
@ -35,7 +36,7 @@ function checkUpdateMessages(){
// check if new messages needs to be fetched // check if new messages needs to be fetched
if($lm < time()-(60*60*24) || $lm < @filemtime(DOKU_INC.DOKU_SCRIPT)){ if($lm < time()-(60*60*24) || $lm < @filemtime(DOKU_INC.DOKU_SCRIPT)){
@touch($cf); @touch($cf);
dbglog("checkUpdateMessages(): downloading messages to ".$cf.($is_http?' (without SSL)':' (with SSL)')); Logger::debug("checkUpdateMessages(): downloading messages to ".$cf.($is_http?' (without SSL)':' (with SSL)'));
$http = new DokuHTTPClient(); $http = new DokuHTTPClient();
$http->timeout = 12; $http->timeout = 12;
$resp = $http->get(DOKU_MESSAGEURL.$updateVersion); $resp = $http->get(DOKU_MESSAGEURL.$updateVersion);
@ -44,10 +45,10 @@ function checkUpdateMessages(){
// or it looks like one of our messages, not WiFi login or other interposed response // or it looks like one of our messages, not WiFi login or other interposed response
io_saveFile($cf,$resp); io_saveFile($cf,$resp);
} else { } else {
dbglog("checkUpdateMessages(): unexpected HTTP response received"); Logger::debug("checkUpdateMessages(): unexpected HTTP response received", $http->error);
} }
}else{ }else{
dbglog("checkUpdateMessages(): messages up to date"); Logger::debug("checkUpdateMessages(): messages up to date");
} }
$data = io_readFile($cf); $data = io_readFile($cf);
@ -429,33 +430,25 @@ function dbg($msg,$hidden=false){
} }
/** /**
* Print info to a log file * Print info to debug log file
* *
* @author Andreas Gohr <andi@splitbrain.org> * @author Andreas Gohr <andi@splitbrain.org>
* * @deprecated 2020-08-13
* @param string $msg * @param string $msg
* @param string $header * @param string $header
*/ */
function dbglog($msg,$header=''){ function dbglog($msg,$header=''){
global $conf; dbg_deprecated('\\dokuwiki\\Logger');
/* @var Input $INPUT */
global $INPUT;
// The debug log isn't automatically cleaned thus only write it when // was the msg as single line string? use it as header
// debugging has been enabled by the user. if($header === '' && is_string($msg) && strpos($msg, "\n") === false) {
if($conf['allowdebug'] !== 1) return; $header = $msg;
if(is_object($msg) || is_array($msg)){ $msg = '';
$msg = print_r($msg,true);
} }
if($header) $msg = "$header\n$msg"; Logger::getInstance(Logger::LOG_DEBUG)->log(
$header, $msg
$file = $conf['cachedir'].'/debug.log'; );
$fh = fopen($file,'a');
if($fh){
fwrite($fh,date('H:i:s ').$INPUT->server->str('REMOTE_ADDR').': '.$msg."\n");
fclose($fh);
}
} }
/** /**

View File

@ -269,16 +269,19 @@ function init_session() {
function init_paths(){ function init_paths(){
global $conf; global $conf;
$paths = array('datadir' => 'pages', $paths = [
'olddir' => 'attic', 'datadir' => 'pages',
'mediadir' => 'media', 'olddir' => 'attic',
'mediaolddir' => 'media_attic', 'mediadir' => 'media',
'metadir' => 'meta', 'mediaolddir' => 'media_attic',
'mediametadir' => 'media_meta', 'metadir' => 'meta',
'cachedir' => 'cache', 'mediametadir' => 'media_meta',
'indexdir' => 'index', 'cachedir' => 'cache',
'lockdir' => 'locks', 'indexdir' => 'index',
'tmpdir' => 'tmp'); 'lockdir' => 'locks',
'tmpdir' => 'tmp',
'logdir' => 'log',
];
foreach($paths as $c => $p) { foreach($paths as $c => $p) {
$path = empty($conf[$c]) ? $conf['savedir'].'/'.$p : $conf[$c]; $path = empty($conf[$c]) ? $conf['savedir'].'/'.$p : $conf[$c];

View File

@ -1,6 +1,8 @@
<?php <?php
use dokuwiki\Utf8\Sort; use dokuwiki\Utf8\Sort;
use dokuwiki\Logger;
/** /**
* Active Directory authentication backend for DokuWiki * Active Directory authentication backend for DokuWiki
* *
@ -384,7 +386,7 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin
{ {
$adldap = $this->initAdLdap(null); $adldap = $this->initAdLdap(null);
if (!$adldap) { if (!$adldap) {
dbglog("authad/auth.php getUserCount(): _adldap not set."); Logger::debug("authad/auth.php getUserCount(): _adldap not set.");
return -1; return -1;
} }
if ($filter == array()) { if ($filter == array()) {

View File

@ -147,6 +147,7 @@ $lang['mailfrom'] = 'Sender email address to use for automatic mails';
$lang['mailreturnpath'] = 'Recipient email address for non delivery notifications'; $lang['mailreturnpath'] = 'Recipient email address for non delivery notifications';
$lang['mailprefix'] = 'Email subject prefix to use for automatic mails. Leave blank to use the wiki title'; $lang['mailprefix'] = 'Email subject prefix to use for automatic mails. Leave blank to use the wiki title';
$lang['htmlmail'] = 'Send better looking, but larger in size HTML multipart emails. Disable for plain text only mails.'; $lang['htmlmail'] = 'Send better looking, but larger in size HTML multipart emails. Disable for plain text only mails.';
$lang['dontlog'] = 'Disable logging for these types of logs.';
/* Syndication Settings */ /* Syndication Settings */
$lang['sitemap'] = 'Generate Google sitemap this often (in days). 0 to disable'; $lang['sitemap'] = 'Generate Google sitemap this often (in days). 0 to disable';

View File

@ -199,6 +199,14 @@ $meta['mailfrom'] = array('email', '_placeholders' => true);
$meta['mailreturnpath'] = array('email', '_placeholders' => true); $meta['mailreturnpath'] = array('email', '_placeholders' => true);
$meta['mailprefix'] = array('string'); $meta['mailprefix'] = array('string');
$meta['htmlmail'] = array('onoff'); $meta['htmlmail'] = array('onoff');
$meta['dontlog'] = array(
'disableactions',
'_choices' => array(
'error',
'debug',
'deprecated',
),
);
$meta['_syndication'] = array('fieldset'); $meta['_syndication'] = array('fieldset');
$meta['sitemap'] = array('numeric'); $meta['sitemap'] = array('numeric');

View File

@ -128,7 +128,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin
array( array(
'authad', 'authldap', 'authpdo', 'authplain', 'authad', 'authldap', 'authpdo', 'authplain',
'acl', 'config', 'extension', 'info', 'popularity', 'revert', 'acl', 'config', 'extension', 'info', 'popularity', 'revert',
'safefnrecode', 'styling', 'testing', 'usermanager', 'safefnrecode', 'styling', 'testing', 'usermanager', 'logviewer',
'template:dokuwiki', 'template:dokuwiki',
) )
); );

View File

@ -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',$this->getLang('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;
}
}

View File

@ -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>

After

Width:  |  Height:  |  Size: 246 B

View File

@ -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 [[doku>config:dontlog|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!

View File

@ -0,0 +1,10 @@
<?php
/**
* English language file for logviewer plugin
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
$lang['menu'] = 'View Logs';
$lang['date'] = 'Date';
$lang['js']['filter'] = 'Filter Loglines:';

View File

@ -0,0 +1 @@
There are no log entries for the selected day and log facility.

View File

@ -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

View File

@ -0,0 +1,25 @@
/**
* 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);
var $filter = jQuery('<input>');
$filter.on('keyup', function (e) {
var re = new RegExp($filter.val(), 'i');
$dl.find('dt').each(function (idx, elem) {
if (elem.innerText.match(re)) {
jQuery(elem).removeClass('hidden');
} else {
jQuery(elem).addClass('hidden');
}
});
});
$dl.before($filter);
$filter.wrap('<label></label>');
$filter.before(LANG.plugins.logviewer.filter + ' ');
});

View File

@ -0,0 +1,51 @@
#plugin__logviewer {
form {
float: right;
}
.tabs {
margin-bottom: 2em;
}
label {
display: block;
margin-top: -1em;
margin-bottom: 1em;
}
dl {
max-height: 80vh;
overflow: auto;
dt {
display: flex;
&.hidden {
display: none;
}
.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;
}
}
}

View File

@ -114,15 +114,18 @@ var dw_behaviour = {
/** /**
* Autosubmit quick select forms * 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. * automatically submit its parent form when the select value changes.
* It also hides the submit button of the form. * 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> * @author Andreas Gohr <andi@splitbrain.org>
*/ */
quickSelect: function(){ quickSelect: function(){
jQuery('select.quickselect') jQuery('.quickselect')
.on('change', function(e){ e.target.form.submit(); }) .change(function(e){ HTMLFormElement.prototype.submit.call(e.target.form); })
.closest('form').find(':button').not('.show').hide(); .closest('form').find(':button').not('.show').hide();
}, },