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