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 <[email protected]> AuthorDate: Wed May 13 16:40:05 2020 +0300 Commit: Andras Timar <[email protected]> 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 <[email protected]> Tested-by: Jenkins Reviewed-by: Michael Meeks <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/online/+/95705 Reviewed-by: Andras Timar <[email protected]> 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 =\ [email protected] \ 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 _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
