[Libreoffice-commits] online.git: Branch 'distro/collabora/co-4-2-4' - loleaflet/admin loleaflet/Makefile.am wsd/Admin.cpp wsd/Admin.hpp wsd/FileServer.cpp wsd/protocol.txt

gokaysatir (via logerrit) logerrit at kemper.freedesktop.org
Mon Jun 8 08:29:02 UTC 2020


 loleaflet/Makefile.am                 |    3 
 loleaflet/admin/admin.html            |    1 
 loleaflet/admin/admin.strings.js      |    3 
 loleaflet/admin/adminAnalytics.html   |    1 
 loleaflet/admin/adminHistory.html     |    1 
 loleaflet/admin/adminLog.html         |  105 +++++++++++++++++++++++++++++
 loleaflet/admin/adminSettings.html    |    1 
 loleaflet/admin/src/AdminSocketLog.js |  121 ++++++++++++++++++++++++++++++++++
 wsd/Admin.cpp                         |  106 +++++++++++++++++++++++++++++
 wsd/Admin.hpp                         |   12 ++-
 wsd/FileServer.cpp                    |    3 
 wsd/protocol.txt                      |    9 ++
 12 files changed, 360 insertions(+), 6 deletions(-)

New commits:
commit 1af9cd06ef8f30b96cbccedbe77d83bc854261e4
Author:     gokaysatir <gokaysatir at gmail.com>
AuthorDate: Wed May 13 16:40:05 2020 +0300
Commit:     Andras Timar <andras.timar at collabora.com>
CommitDate: Mon Jun 8 10:28:44 2020 +0200

    tdf#114982 - AdminConsole: control logging
    
    Change-Id: I53cf4b0ff81ea9d6bf4ad595077f7365e7e34c00
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/94130
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Tested-by: Jenkins
    Reviewed-by: Michael Meeks <michael.meeks at collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/95705
    Reviewed-by: Andras Timar <andras.timar at collabora.com>

diff --git a/loleaflet/Makefile.am b/loleaflet/Makefile.am
index 1959b1204..90e72ef39 100644
--- a/loleaflet/Makefile.am
+++ b/loleaflet/Makefile.am
@@ -89,7 +89,8 @@ LOLEAFLET_ADMIN_JS =\
 	admin/src/AdminSocketOverview.js \
 	admin/src/AdminSocketAnalytics.js \
 	admin/src/AdminSocketSettings.js \
-	admin/src/AdminSocketHistory.js
+	admin/src/AdminSocketHistory.js \
+	admin/src/AdminSocketLog.js
 
 NODE_MODULES_SRC =\
 	autolinker at 1.4.3 \
diff --git a/loleaflet/admin/admin.html b/loleaflet/admin/admin.html
index acd114098..2b60f767b 100644
--- a/loleaflet/admin/admin.html
+++ b/loleaflet/admin/admin.html
@@ -59,6 +59,7 @@
             <li class="active"><a href="#"><script>document.write(l10nstrings.strOverview)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li>
             <li><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script></a></li>
             <li><a href="adminHistory.html"><script>document.write(l10nstrings.strHistory)</script></a></li>
+            <li><a href="adminLog.html"><script>document.write(l10nstrings.strLog)</script></a></li>
           </ul>
         </div>
         <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
diff --git a/loleaflet/admin/admin.strings.js b/loleaflet/admin/admin.strings.js
index 7dea1cdd3..91d90b4f1 100644
--- a/loleaflet/admin/admin.strings.js
+++ b/loleaflet/admin/admin.strings.js
@@ -9,6 +9,7 @@ l10nstrings.strOverview = _('Overview');
 l10nstrings.strCurrent = _('(current)');
 l10nstrings.strAnalytics = _('Analytics');
 l10nstrings.strHistory = _('History');
+l10nstrings.strLog = _('Log');
 l10nstrings.strDashboard = _('Dashboard');
 l10nstrings.strUsersOnline = _('Users online');
 l10nstrings.strUserName = _('User Name');
@@ -40,7 +41,7 @@ l10nstrings.strDocuments = _('Documents:');
 l10nstrings.strExpired = _('Expired:');
 l10nstrings.strRefresh = _('Refresh');
 l10nstrings.strShutdown = _('Shutdown Server');
-l10nstrings.strServerUptime = _('Server uptime')
+l10nstrings.strServerUptime = _('Server uptime');
 
 if (module) {
 	module.exports = l10nstrings;
diff --git a/loleaflet/admin/adminAnalytics.html b/loleaflet/admin/adminAnalytics.html
index 7667c67df..af3b6eb31 100644
--- a/loleaflet/admin/adminAnalytics.html
+++ b/loleaflet/admin/adminAnalytics.html
@@ -59,6 +59,7 @@
             <li><a href="admin.html"><script>document.write(l10nstrings.strOverview)</script></a></li>
             <li class="active"><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li>
             <li><a href="adminHistory.html"><script>document.write(l10nstrings.strHistory)</script></a></li>
+            <li><a href="adminLog.html"><script>document.write(l10nstrings.strLog)</script></a></li>
           </ul>
         </div>
         <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
diff --git a/loleaflet/admin/adminHistory.html b/loleaflet/admin/adminHistory.html
index 60ad593d3..ec652011e 100644
--- a/loleaflet/admin/adminHistory.html
+++ b/loleaflet/admin/adminHistory.html
@@ -59,6 +59,7 @@
             <li><a href="admin.html"><script>document.write(l10nstrings.strOverview)</script></a></li>
             <li><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script></a></li>
             <li class="active"><a href="adminHistory.html"><script>document.write(l10nstrings.strHistory)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li>
+            <li><a href="adminLog.html"><script>document.write(l10nstrings.strLog)</script></a></li>
           </ul>
         </div>
 
diff --git a/loleaflet/admin/adminLog.html b/loleaflet/admin/adminLog.html
new file mode 100644
index 000000000..73036828e
--- /dev/null
+++ b/loleaflet/admin/adminLog.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8"/>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1"/>
+    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
+    <meta name="description" content=""/>
+    <meta name="author" content=""/>
+
+    <title>LibreOffice Online - Admin console</title>
+
+    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
+    <!--[if lt IE 9]>
+      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+    <![endif]-->
+    <link rel="localizations" href="%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/admin-localizations.json" type="application/vnd.oftn.l10n+json"/>
+  </head>
+  <body>
+    <script src="%SERVICE_ROOT%/loleaflet/%VERSION%/admin-bundle.js"></script>
+    <!--%BRANDING_JS%-->
+    <script>if (typeof brandProductName !== 'undefined') {l10nstrings.strProductName = brandProductName}</script>
+    <script>document.title = l10nstrings.strProductName + ' - ' + l10nstrings.strAdminConsole</script>
+    <script>
+      if (window.location.protocol == "https:") {
+          var host = 'wss://' + window.location.host + '%SERVICE_ROOT%/lool/adminws/';
+      }
+      else {
+          host = 'ws://' + window.location.host + '%SERVICE_ROOT%/lool/adminws/';
+      }
+
+      Admin.Log(host)
+    </script>
+
+    <nav class="navbar navbar-inverse navbar-fixed-top">
+      <div class="container-fluid">
+        <div class="navbar-header">
+          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+            <span class="sr-only"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+          </button>
+          <a class="navbar-brand" href="#"><script>document.write(l10nstrings.strProductName + ' - ' + l10nstrings.strAdminConsole)</script></a>
+        </div>
+        <div id="navbar" class="navbar-collapse collapse">
+          <ul class="nav navbar-nav navbar-right">
+            <li><a href="adminSettings.html"><script>document.write(l10nstrings.strSettings)</script></a></li>
+          </ul>
+        </div>
+      </div>
+    </nav>
+
+    <div class="container-fluid">
+      <div class="row">
+        <div class="col-sm-3 col-md-2 sidebar">
+          <ul class="nav nav-sidebar">
+            <li><a href="admin.html"><script>document.write(l10nstrings.strOverview)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li>
+            <li><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script></a></li>
+            <li><a href="adminHistory.html"><script>document.write(l10nstrings.strHistory)</script></a></li>
+            <li class="active"><a href="adminLog.html"><script>document.write(l10nstrings.strLog)</script></a></li>
+          </ul>
+        </div>
+        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
+          <h1 class="page-header"><script>document.write(l10nstrings.strLog)</script></h1>
+          <div class="form-group text-right" style="width:100%">
+            <button type="button" class="btn btn-primary float-right" id="refresh-log" style="width:120px;">Refresh Log</button>
+            <button type="button" id="button-open-log-list-modal" class="btn btn-primary float-right" data-toggle="modal" style="width:120px;" data-target="#channel-list-modal">Set Log Levels</button>
+          </div>
+          <div class="form-group">
+            <textarea id="log-lines" style="width:100%;height:100%;min-height: calc(100vh - 250px);resize:vertical;" readonly="true"></textarea>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- Modal -->
+    <div class="modal fade" id="channel-list-modal" tabindex="-1" role="dialog" aria-labelledby="log-level-list-label" aria-hidden="true">
+      <div class="modal-dialog" role="document">
+        <div class="modal-content">
+          <div class="modal-header">
+            <h5 class="modal-title" id="log-level-list-label">
+              <i class="fa fa-spinner fa-spin"></i>
+              Log Levels
+            </h5>
+            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+              <span aria-hidden="true">×</span>
+            </button>
+          </div>
+          <div class="modal-body">
+            <form class="form-horizontal" id="form-channel-list" ></form>
+          </div>
+          <div class="modal-footer">
+            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+            <button type="button" form="form-channel-list" id="update-log-levels" class="btn btn-primary">Update Log Levels</button>
+          </div>
+        </div>
+      </div>
+    </div>
+
+
+<!--%FOOTER%-->
+  </body>
+</html>
diff --git a/loleaflet/admin/adminSettings.html b/loleaflet/admin/adminSettings.html
index 0d669beab..00fb85c53 100644
--- a/loleaflet/admin/adminSettings.html
+++ b/loleaflet/admin/adminSettings.html
@@ -59,6 +59,7 @@
             <li><a href="admin.html"><script>document.write(l10nstrings.strOverview)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li>
             <li><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script></a></li>
             <li><a href="adminHistory.html"><script>document.write(l10nstrings.strHistory)</script></a></li>
+            <li><a href="adminLog.html"><script>document.write(l10nstrings.strLog)</script></a></li>
           </ul>
           <hr />
           <div style="position:absolute; bottom:0px">
diff --git a/loleaflet/admin/src/AdminSocketLog.js b/loleaflet/admin/src/AdminSocketLog.js
new file mode 100644
index 000000000..598087558
--- /dev/null
+++ b/loleaflet/admin/src/AdminSocketLog.js
@@ -0,0 +1,121 @@
+/* -*- js-indent-level: 8 -*- */
+/*
+	Socket to be intialized on opening the log page in Admin console
+*/
+/* global Admin $ AdminSocketBase */
+var AdminSocketLog = AdminSocketBase.extend({
+	constructor: function(host) {
+		this.base(host);
+		// There is a "$" is never used error. Let's get rid of this. This is vanilla script and has not more lines than the one with JQuery.
+		$('#form-channel-list').id;
+	},
+
+	refreshLog: function() {
+		this.socket.send('log_lines');
+	},
+
+	pullChannelList: function() {
+		this.socket.send('channel_list');
+	},
+
+	sendChannelListLogLevels: function(e) {
+		e.stopPropagation();
+
+		// We change the colour of the button when we send the data and change it back when the task is done (in function applyChannelList). But it is happening too fast.
+		document.getElementById('update-log-levels').classList.add('btn-warning');
+		document.getElementById('update-log-levels').classList.remove('btn-primary');
+
+		// Get the form.
+		var form = document.getElementById('form-channel-list');
+
+		// Get channel select elements.
+		var selectList = form.querySelectorAll('select');
+
+		// Prepare the statement.
+		var textToSend = 'update-log-levels';
+		for (var i = 0; i < selectList.length; i++) {
+			textToSend += ' ' + selectList[i].getAttribute('name').replace('channel-', '') + '=' + selectList[i].value;
+		}
+
+		this.socket.send(textToSend);
+	},
+
+	onSocketOpen: function() {
+		// Base class' onSocketOpen handles authentication
+		this.base.call(this);
+
+		document.getElementById('refresh-log').onclick = this.refreshLog.bind(this);
+		document.getElementById('update-log-levels').onclick = this.sendChannelListLogLevels.bind(this);
+
+		this.pullChannelList();
+		this.refreshLog();
+	},
+
+	applyChannelList: function(channelListStr) {
+		var channelListArr = channelListStr.split(' '); // Every item holds: channel name + = + log level.
+
+		// Here we have the log channel list and their respective log levels.
+		// We will create items for them. User will be able to set the log level for each channel.
+		var channelForm = document.getElementById('form-channel-list');
+		channelForm.innerHTML = ''; // Clear and refill it.
+		var optionList = Array('none', 'fatal', 'critical', 'error', 'warning', 'notice', 'information', 'debug', 'trace');
+		var innerHTML = ''; // Of select elements.
+		for (var i = 0; i < optionList.length; i++) {
+			innerHTML += '<option value="' + optionList[i] + '">' + optionList[i] + '</option>';
+		}
+
+		for (i = 0; i < channelListArr.length; i++) {
+			if (channelListArr[i].split('=').length === 2) {
+				var channelName = channelListArr[i].split('=')[0];
+				var channelLogLevel = channelListArr[i].split('=')[1];
+
+				var newDiv = document.createElement('div');
+				newDiv.className = 'form-group';
+
+				var newLabel = document.createElement('label');
+				newLabel.className = 'control-label col-sm-6';
+				newLabel.setAttribute('for', 'channel-' + channelName);
+				newLabel.innerText = channelName;
+
+				var newSubDivision = document.createElement('div');
+				newSubDivision.className = 'col-sm-6';
+
+				var newSelectElement = document.createElement('select');
+				newSelectElement.name = 'channel-' + channelName;
+				newSelectElement.id = 'channel-' + channelName;
+				newSelectElement.innerHTML = innerHTML;
+				newSelectElement.value = channelLogLevel;
+				newSelectElement.style.width = '120px';
+				newSelectElement.className = 'form-control';
+
+				channelForm.appendChild(newDiv);
+				newDiv.appendChild(newLabel);
+				newDiv.appendChild(newSubDivision);
+				newSubDivision.appendChild(newSelectElement);
+			}
+		}
+
+		document.getElementById('update-log-levels').classList.remove('btn-warning');
+		document.getElementById('update-log-levels').classList.add('btn-primary');
+	},
+
+	onSocketMessage: function(e) {
+		if (e.data.startsWith('log_lines')) {
+			var result = e.data;
+			result = result.substring(10, result.length);
+			document.getElementById('log-lines').value = result;
+		}
+		else if (e.data.startsWith('channel_list')) {
+			var channelListStr = e.data.substring(13, e.data.length);
+			this.applyChannelList(channelListStr);
+		}
+	},
+
+	onSocketClose: function() {
+
+	}
+});
+
+Admin.Log = function(host) {
+	return new AdminSocketLog(host);
+};
diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp
index 92fe30e8a..5e6668326 100644
--- a/wsd/Admin.cpp
+++ b/wsd/Admin.cpp
@@ -151,6 +151,9 @@ void AdminSocketHandler::handleMessage(const std::vector<char> &payload)
     else if (tokens.equals(0, "uptime"))
         sendTextFrame("uptime " + std::to_string(model.getServerUptime()));
 
+    else if (tokens.equals(0, "log_lines"))
+        sendTextFrame("log_lines " + _admin->getLogLines());
+
     else if (tokens.equals(0, "kill") && tokens.size() == 2)
     {
         try
@@ -197,6 +200,10 @@ void AdminSocketHandler::handleMessage(const std::vector<char> &payload)
 
         sendTextFrame(oss.str());
     }
+    else if (tokens.equals(0, "channel_list"))
+    {
+        sendTextFrame("channel_list " + _admin->getChannelLogLevels());
+    }
     else if (tokens.equals(0, "shutdown"))
     {
         LOG_INF("Shutdown requested by admin.");
@@ -272,6 +279,18 @@ void AdminSocketHandler::handleMessage(const std::vector<char> &payload)
             }
         }
     }
+    else if (tokens.equals(0, "update-log-levels") && tokens.size() > 1) {
+        for (size_t i = 1; i < tokens.size(); i++)
+        {
+            StringVector _channel(LOOLProtocol::tokenize(tokens[i], '='));
+            if (_channel.size() == 2)
+            {
+                _admin->setChannelLogLevel((_channel[0] != "?" ? _channel[0]: ""), _channel[1]);
+            }
+        }
+        // Let's send back the current log levels in return. So the user can be sure of the values.
+        sendTextFrame("channel_list " + _admin->getChannelLogLevels());
+    }
 }
 
 AdminSocketHandler::AdminSocketHandler(Admin* adminManager,
@@ -550,19 +569,106 @@ size_t Admin::getTotalCpuUsage()
 
 unsigned Admin::getMemStatsInterval()
 {
+    assertCorrectThread();
     return _memStatsTaskIntervalMs;
 }
 
 unsigned Admin::getCpuStatsInterval()
 {
+    assertCorrectThread();
     return _cpuStatsTaskIntervalMs;
 }
 
 unsigned Admin::getNetStatsInterval()
 {
+    assertCorrectThread();
     return _netStatsTaskIntervalMs;
 }
 
+std::string Admin::getChannelLogLevels()
+{
+    std::string result = "";
+    // Get the list of channels..
+    std::vector<std::string> nameList;
+    Log::logger().names(nameList);
+
+    std::string levelList[9] = {"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace"};
+
+    for (size_t i = 0; i < nameList.size(); i++)
+    {
+        result += (nameList[i] != "" ? nameList[i]: "?") + "=" + levelList[Log::logger().get(nameList[i]).getLevel()] + (i != nameList.size() - 1 ? " ": "");
+    }
+
+    return result;
+}
+
+void Admin::setChannelLogLevel(std::string _channelName, std::string _level)
+{
+    assertCorrectThread();
+
+    std::string levelList[9] = {"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace"};
+    if (std::find(std::begin(levelList), std::end(levelList), _level) == std::end(levelList))
+    {
+        _level = "trace";
+    }
+
+    // Get the list of channels..
+    std::vector<std::string> nameList;
+    Log::logger().names(nameList);
+
+    for (size_t i = 0; i < nameList.size(); i++)
+    {
+        if (nameList[i] == _channelName)
+        {
+            Log::logger().get(nameList[i]).setLevel(_level);
+            break;
+        }
+    }
+}
+
+std::string Admin::getLogLines()
+{
+    assertCorrectThread();
+
+    try {
+        int lineCount = 500;
+        std::string fName = LOOLWSD::getPathFromConfig("logging.file.property[0]");
+        std::ifstream infile(fName);
+
+        std::string line;
+        std::deque<std::string> lines;
+
+        while (std::getline(infile, line))
+        {
+            std::istringstream iss(line);
+            lines.push_back(line);
+            if (lines.size() > (size_t)lineCount)
+            {
+                lines.pop_front();
+            }
+        }
+
+        infile.close();
+
+        if (lines.size() < (size_t)lineCount)
+        {
+            lineCount = (int)lines.size();
+        }
+
+        line = ""; // Use the same variable to include result.
+        // Newest will be on top.
+        for (int i = lineCount - 1; i >= 0; i--)
+        {
+            line += "\n" + lines[i];
+        }
+
+        return line;
+    }
+    catch (const std::exception& e) {
+        return "Could not read the log file.";
+    }
+}
+
 AdminModel& Admin::getModel()
 {
     return _model;
diff --git a/wsd/Admin.hpp b/wsd/Admin.hpp
index 1e7ebe9a6..ae3967e06 100644
--- a/wsd/Admin.hpp
+++ b/wsd/Admin.hpp
@@ -105,6 +105,12 @@ public:
 
     unsigned getNetStatsInterval();
 
+    std::string getChannelLogLevels();
+
+    void setChannelLogLevel(std::string _channelName, std::string _level);
+
+    std::string getLogLines();
+
     void rescheduleMemTimer(unsigned interval);
 
     void rescheduleCpuTimer(unsigned interval);
@@ -180,9 +186,9 @@ private:
     };
     std::vector<MonitorConnectRecord> _pendingConnects;
 
-    std::atomic<int> _cpuStatsTaskIntervalMs;
-    std::atomic<int> _memStatsTaskIntervalMs;
-    std::atomic<int> _netStatsTaskIntervalMs;
+    int _cpuStatsTaskIntervalMs;
+    int _memStatsTaskIntervalMs;
+    int _netStatsTaskIntervalMs;
     DocProcSettings _defDocProcSettings;
 
     // Don't update any more frequently than this since it's excessive.
diff --git a/wsd/FileServer.cpp b/wsd/FileServer.cpp
index 8e34c357e..b7cd523a3 100644
--- a/wsd/FileServer.cpp
+++ b/wsd/FileServer.cpp
@@ -363,7 +363,8 @@ void FileServerRequestHandler::handleRequest(const HTTPRequest& request,
             if (endPoint == "admin.html" ||
                 endPoint == "adminSettings.html" ||
                 endPoint == "adminHistory.html" ||
-                endPoint == "adminAnalytics.html")
+                endPoint == "adminAnalytics.html" ||
+                endPoint == "adminLog.html")
             {
                 preprocessAdminFile(request, requestDetails, socket);
                 return;
diff --git a/wsd/protocol.txt b/wsd/protocol.txt
index 9b1f43e03..927348bff 100644
--- a/wsd/protocol.txt
+++ b/wsd/protocol.txt
@@ -736,6 +736,15 @@ settings
 
     Queries the server for configurable settings from admin console.
 
+log_lines
+    Gets last n lines from "loolwsd.log" file.
+
+channel_list
+    Gets the log channels list with their respective log levels.
+
+update-log-levels
+    Updates the log channels' log levels with the given log levels. Format is: "update-log-levels channel1name=loglevel channel2name=loglevel".
+
 set <setting1=value1> <setting2=value2> ...
 
     Sets a particular setting (must be one returned as response to


More information about the Libreoffice-commits mailing list