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