]> git.mxchange.org Git - friendica.git/commitdiff
Add search and filter to log view
authorfabrixxm <fabrix.xm@gmail.com>
Mon, 24 May 2021 19:47:10 +0000 (21:47 +0200)
committerfabrixxm <fabrix.xm@gmail.com>
Thu, 19 Aug 2021 12:55:33 +0000 (14:55 +0200)
src/Model/Log/ParsedLogIterator.php
src/Module/Admin/Logs/View.php
src/Object/Log/ParsedLog.php
src/Util/ReversedFileReader.php
view/templates/admin/logs/view.tpl

index 621381ac55089f19be9c22294ac1130dc2d2c3c4..15714454e7ce667a9631c0c7e477824ae5ed98d2 100644 (file)
@@ -28,36 +28,113 @@ use \Friendica\Object\Log\ParsedLog;
  * An iterator which returns `\Friendica\Objec\Log\ParsedLog` instances
  *
  * Uses `\Friendica\Util\ReversedFileReader` to fetch log lines
- * from newest to oldest
+ * from newest to oldest.
  */
 class ParsedLogIterator implements \Iterator
 {
-       public function __construct(string $filename, int $limit=0)
+       /** @var \Iterator */
+       private $reader;
+
+       /** @var ParsedLog current iterator value*/
+       private $value; 
+
+       /** @var int max number of lines to read */
+       private $limit;
+
+       /** @var array filters per column */
+       private $filters;
+
+       /** @var string search term */
+       private $search;
+
+
+       /**
+        * @param string $filename      File to open
+        * @param int $limit            Max num of lines to read
+        * @param array $filter         filters per column
+        * @param string $search        string to search to filter lines
+        */
+       public function __construct(string $filename, int $limit=0, array $filters=[], string $search="")
        {
                $this->reader = new ReversedFileReader($filename);
-               $this->_value = null;
-               $this->_limit = $limit;
+               $this->value = null;
+               $this->limit = $limit;
+               $this->filters = $filters;
+               $this->search = $search;
        }
 
-       public function next()
+       /**
+        * Check if parsed log line match filters.
+        * Always match if no filters are set.
+        * 
+        * @param ParsedLog $parsedlog
+        * @return bool
+        */
+       private function filter($parsedlog)
        {
-               $this->reader->next();
-               if ($this->_limit > 0 && $this->reader->key() > $this->_limit) {
-                       $this->_value = null;
-                       return;
+               $match = true;
+               foreach ($this->filters as $filter => $filtervalue) {
+                       switch($filter) {
+                               case "level":
+                                       $match = $match && ($parsedlog->level == strtoupper($filtervalue));
+                                       break;
+                               case "context":
+                                       $match = $match && ($parsedlog->context == $filtervalue);
+                                       break;
+                       }
+               }
+               return $match;
+       }
+
+       /**
+        * Check if parsed log line match search.
+        * Always match if no search query is set.
+        * 
+        * @param ParsedLog $parsedlog
+        * @return bool
+        */
+       private function search($parsedlog)
+       {
+               if ($this->search != "") {
+                       return strstr($parsedlog->logline, $this->search) !== false;
                }
-               if ($this->reader->valid()) {
-                       $line = $this->reader->current();
-                       $this->_value = new ParsedLog($this->reader->key(), $line);
-               } else {
-                       $this->_value = null;
+               return True;
+       }
+
+       /**
+        * Read a line from reader and parse.
+        * Returns null if limit is reached or the reader is invalid.
+        * 
+        * @param ParsedLog $parsedlog
+        * @return ?ParsedLog
+        */
+       private function read()
+       {
+               $this->reader->next();
+               if ($this->limit > 0 && $this->reader->key() > $this->limit  || !$this->reader->valid()) {
+                       return null;
                }
+
+               $line = $this->reader->current();
+               return new ParsedLog($this->reader->key(), $line);
        }
 
+       public function next()
+       {
+               $parsed = $this->read();
+
+               // if read() has not retuned none and
+               // the line don't match filters or search
+               //      read the next line
+               while(is_null($parsed) == false && !($this->filter($parsed)  && $this->search($parsed))) {
+                       $parsed = $this->read();
+               }
+               $this->value = $parsed;
+       }
 
        public function rewind()
        {
-               $this->_value = null;
+               $this->value = null;
                $this->reader->rewind();
                $this->next();
        }
@@ -69,12 +146,12 @@ class ParsedLogIterator implements \Iterator
 
        public function current()
        {
-               return $this->_value;
+               return $this->value;
        }
 
        public function valid()
        {
-               return ! is_null($this->_value);
+               return ! is_null($this->value);
        }
 
 }
index 339a28b6a5b2dde40cd766222a17237769bb6eb1..a512507278f76eaa72640d3f4e2424a87591c974 100644 (file)
@@ -26,6 +26,7 @@ use Friendica\Core\Renderer;
 use Friendica\Core\Theme;
 use Friendica\Module\BaseAdmin;
 use Friendica\Model\Log\ParsedLogIterator;
+use Psr\Log\LogLevel;
 
 class View extends BaseAdmin
 {
@@ -43,11 +44,34 @@ class View extends BaseAdmin
                $error = null;
 
 
+               $search = $_GET['q'] ?? '';
+               $filters_valid_values = [
+                       'level' => [
+                               '',
+                               LogLevel::CRITICAL,
+                               LogLevel::ERROR,
+                               LogLevel::WARNING,
+                               LogLevel::NOTICE,
+                               LogLevel::INFO,
+                               LogLevel::DEBUG,
+                       ],
+                       'context' => ['', 'index', 'worker'],
+               ];
+               $filters = [
+                       'level' => $_GET['level'] ?? '',
+                       'context' => $_GET['context'] ?? '',
+               ];
+               foreach($filters as $k=>$v) {
+                       if ($v == '' || !in_array($v, $filters_valid_values[$k])) {
+                               unset($filters[$k]);
+                       }
+               }
+
                if (!file_exists($f)) {
                        $error = DI::l10n()->t('Error trying to open <strong>%1$s</strong> log file.\r\n<br/>Check to see if file %1$s exist and is readable.', $f);
                } else {
                        try {
-                               $data = new ParsedLogIterator($f, self::LIMIT);
+                               $data = new ParsedLogIterator($f, self::LIMIT, $filters, $search);
                        } catch (Exception $e) {
                                $error = DI::l10n()->t('Couldn\'t open <strong>%1$s</strong> log file.\r\n<br/>Check to see if file %1$s is readable.', $f);
                        }
@@ -56,8 +80,11 @@ class View extends BaseAdmin
                        '$title' => DI::l10n()->t('Administration'),
                        '$page' => DI::l10n()->t('View Logs'),
                        '$data' => $data,
+                       '$q' => $search,
+                       '$filters' => $filters,
+                       '$filtersvalues' => $filters_valid_values,
                        '$error' => $error,
-                       '$logname' => DI::config()->get('system', 'logfile')
+                       '$logname' => DI::config()->get('system', 'logfile'),
                ]);
        }
 }
index 21bd538765dbec0a907f2cb6ecb431db3f89eb46..f48ce400e7678fe940c065bff225eb6ffe171f5c 100644 (file)
@@ -27,22 +27,38 @@ class ParsedLog
 {
        const REGEXP = '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^ ]*) (\w+) \[(\w*)\]: (.*)/';
 
+       /** @var int */
        public $id = 0;
+
+       /** @var string */
        public $date = null;
+
+       /** @var string */
        public $context = null;
+
+       /** @var string */
        public $level = null;
+
+       /** @var string */
        public $message = null;
+
+       /** @var string */
        public $data = null;
+
+       /** @var string */
        public $source = null;
 
+       /** @var string */
+       public $logline;
+
        /**
+        * @param int line id
         * @param string $logline Source log line to parse
         */
        public function __construct(int $id, string $logline)
        {
                $this->id = $id;
                $this->parse($logline);
-               $this->stop = false;
        }
 
        private function parse($logline)
@@ -60,30 +76,34 @@ class ParsedLog
                $this->message = $matches[4];
                $this->data = $jsondata;
                $this->source = $jsonsource;
-               $this->try_fix_json('data');
+               $this->try_fix_json();
+
+               $this->logline = $logline;
        }
 
        /**
+        * Fix message / data split
+        * 
         * In log boundary between message and json data is not specified.
         * If message  contains '{' the parser thinks there starts the json data.
         * This method try to parse the found json and if it fails, search for next '{'
         * in json data and retry
         */
-       private function try_fix_json(string $key)
+       private function try_fix_json()
        {
-               if (is_null($this->$key) || $this->$key == "") {
+               if (is_null($this->data) || $this->data == "") {
                        return;
                }
                try {
-                       $d = json_decode($this->$key, true, 512, JSON_THROW_ON_ERROR);
+                       $d = json_decode($this->data, true, 512, JSON_THROW_ON_ERROR);
                } catch (\JsonException $e) {
                        // try to find next { in $str and move string before to 'message'
 
-                       $pos = strpos($this->$key, '{', 1);
+                       $pos = strpos($this->data, '{', 1);
 
-                       $this->message .= substr($this->$key, 0, $pos);
-                       $this->$key = substr($this->key, $pos);
-                       $this->try_fix_json($key);
+                       $this->message .= substr($this->data, 0, $pos);
+                       $this->data = substr($this->data, $pos);
+                       $this->try_fix_json();
                }
        }
 
index 8a3083f7d8d14a31489d3cd2263d65af3a29cbce..eeedc1a5ccba435fe6ecac8f6055677924eb9101 100644 (file)
@@ -32,6 +32,21 @@ class ReversedFileReader implements \Iterator
     const BUFFER_SIZE = 4096;
     const SEPARATOR = "\n";
 
+    /** @var int */
+    private $filesize;
+
+    /** @var int */
+    private $pos;
+
+    /** @var array */
+    private $buffer;
+
+    /** @var int */
+    private $key;
+
+    /** @var string */
+    private $value;
+
     public function __construct($filename)
     {
         $this->_fh = fopen($filename, 'r');
@@ -39,25 +54,25 @@ class ReversedFileReader implements \Iterator
                        // this should use a custom exception.
                        throw \Exception("Unable to open $filename");
                }
-        $this->_filesize = filesize($filename);
-        $this->_pos = -1;
-        $this->_buffer = null;
-        $this->_key = -1;
-        $this->_value = null;
+        $this->filesize = filesize($filename);
+        $this->pos = -1;
+        $this->buffer = null;
+        $this->key = -1;
+        $this->value = null;
     }
 
     public function _read($size)
     {
-        $this->_pos -= $size;
-        fseek($this->_fh, $this->_pos);
+        $this->pos -= $size;
+        fseek($this->_fh, $this->pos);
         return fread($this->_fh, $size);
     }
 
     public function _readline()
     {
-        $buffer =& $this->_buffer;
+        $buffer =& $this->buffer;
         while (true) {
-            if ($this->_pos == 0) {
+            if ($this->pos == 0) {
                 return array_pop($buffer);
             }
             if (count($buffer) > 1) {
@@ -69,33 +84,33 @@ class ReversedFileReader implements \Iterator
 
     public function next()
     {
-        ++$this->_key;
-        $this->_value = $this->_readline();
+        ++$this->key;
+        $this->value = $this->_readline();
     }
 
     public function rewind()
     {
-        if ($this->_filesize > 0) {
-            $this->_pos = $this->_filesize;
-            $this->_value = null;
-            $this->_key = -1;
-            $this->_buffer = explode(self::SEPARATOR, $this->_read($this->_filesize % self::BUFFER_SIZE ?: self::BUFFER_SIZE));
+        if ($this->filesize > 0) {
+            $this->pos = $this->filesize;
+            $this->value = null;
+            $this->key = -1;
+            $this->buffer = explode(self::SEPARATOR, $this->_read($this->filesize % self::BUFFER_SIZE ?: self::BUFFER_SIZE));
             $this->next();
         }
     }
 
     public function key()
        {
-               return $this->_key;
+               return $this->key;
        }
 
     public function current()
        {
-               return $this->_value;
+               return $this->value;
        }
 
     public function valid()
        {
-               return ! is_null($this->_value);
+               return ! is_null($this->value);
        }
 }
index 166dea0ba98dc6f3b429b0c721e69ae690340e48..523cb6a2014c5a8845a42820e8699e04d19f128a 100644 (file)
@@ -7,40 +7,68 @@
                        <p>{{$error nofilter}}</p>
                </div>
        {{else}}
-               <table>
-                       <thead>
-                               <tr>
-                                       <th>Date</th>
-                                       <th>Level</th>
-                                       <th>Context</th>
-                                       <th>Message</th>
-                               </tr>
-                       </thead>
-                       <tbody>
-                               {{foreach $data as $row}}
-                               <tr id="ev-{{$row->id}}" onClick="log_show_details('ev-{{$row->id}}')">
-                                       <td>{{$row->date}}</td>
-                                       <td>{{$row->level}}</td>
-                                       <td>{{$row->context}}</td>
-                                       <td>{{$row->message}}</td>
-                               </tr>
-                               {{foreach $row->get_data() as $k=>$v}}
-                                       <tr class="hidden" data-id="ev-{{$row->id}}">
-                                               <th>{{$k}}</th>
-                                               <td colspan="3">
-                                                       <pre>{{$v nofilter}}</pre>
-                                               </td>
+               <form>
+                       <p>
+                               <input type="search" name="q" value="{{$q}}" placeholder="search"></input>
+                               <input type="Submit" value="search">
+                               <a href="/admin/logs/view">clear</a>
+                       </p>
+
+
+                       <table>
+                               <thead>
+                                       <tr>
+                                               <th>Date</th>
+                                               <th>
+                                                       <select name="level" onchange="this.form.submit()">
+                                                               {{foreach $filtersvalues.level as $v }}
+                                                               <option {{if $filters.level == $v}}selected{{/if}} value="{{$v}}">
+                                                                       {{if $v == ""}}Level{{/if}}
+                                                                       {{$v}}
+                                                               </option>
+                                                               {{/foreach}}
+                                                       </select>
+                                               </th>
+                                               <th>
+                                                       <select name="context" onchange="this.form.submit()">
+                                                               {{foreach $filtersvalues.context as $v }}
+                                                               <option {{if $filters.context == $v}}selected{{/if}} value="{{$v}}">
+                                                                       {{if $v == ""}}Context{{/if}}
+                                                                       {{$v}}
+                                                               </option>
+                                                               {{/foreach}}
+                                                       </select>
+                                               </th>
+                                               <th>Message</th>
                                        </tr>
-                                       {{/foreach}}
-                                       <tr class="hidden" data-id="ev-{{$row->id}}"><th colspan="4">Source</th></tr>
-                                       {{foreach $row->get_source() as $k=>$v}}
-                                               <tr class="hidden" data-id="ev-{{$row->id}}">
-                                                       <th>{{$k}}</th>
-                                                       <td colspan="3">{{$v}}</td>
+                               </thead>
+                               <tbody>
+                                       {{foreach $data as $row}}
+                                               <tr id="ev-{{$row->id}}" onClick="log_show_details('ev-{{$row->id}}')">
+                                                       <td>{{$row->date}}</td>
+                                                       <td>{{$row->level}}</td>
+                                                       <td>{{$row->context}}</td>
+                                                       <td>{{$row->message}}</td>
                                                </tr>
+                                               <tr class="hidden" data-id="ev-{{$row->id}}"><th colspan="4">Data</th></tr>
+                                               {{foreach $row->get_data() as $k=>$v}}
+                                                       <tr class="hidden" data-id="ev-{{$row->id}}">
+                                                               <th>{{$k}}</th>
+                                                               <td colspan="3">
+                                                                       <pre>{{$v nofilter}}</pre>
+                                                               </td>
+                                                       </tr>
+                                               {{/foreach}}
+                                               <tr class="hidden" data-id="ev-{{$row->id}}"><th colspan="4">Source</th></tr>
+                                               {{foreach $row->get_source() as $k=>$v}}
+                                                       <tr class="hidden" data-id="ev-{{$row->id}}">
+                                                               <th>{{$k}}</th>
+                                                               <td colspan="3">{{$v}}</td>
+                                                       </tr>
+                                               {{/foreach}}
                                        {{/foreach}}
-                               {{/foreach}}
-                       </tbody>
-               </table>
+                               </tbody>
+                       </table>
+               </form>
        {{/if}}
 </div>