summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-05-29 14:50:39 -0300
committerTomás Touceda <chiiph@leap.se>2013-05-29 14:50:39 -0300
commit7780685a8beb8fbba54d5c366905d9ece5569008 (patch)
tree265a8340cfad6a92a1c2696003221956c81cdfc4
parentb24fd83c64f2b166a192e9cf7835e5f35a15c1e5 (diff)
parentc928400ff02adf6c23c5341c2139e95f2c49976d (diff)
Merge remote-tracking branch 'ivan/feature/logger_window_rev4' into develop
-rw-r--r--LICENSE6
-rw-r--r--Makefile4
-rw-r--r--changes/feature_2566-logger_window1
-rw-r--r--data/images/oxygen-icons/dialog-error.pngbin0 -> 1527 bytes
-rw-r--r--data/images/oxygen-icons/dialog-information.pngbin0 -> 1636 bytes
-rw-r--r--data/images/oxygen-icons/dialog-warning.pngbin0 -> 1354 bytes
-rw-r--r--data/images/oxygen-icons/document-save-as.pngbin0 -> 2152 bytes
-rw-r--r--data/images/oxygen-icons/edit-bomb.pngbin0 -> 1635 bytes
-rw-r--r--data/images/oxygen-icons/script-error.pngbin0 -> 1709 bytes
-rw-r--r--data/resources/loggerwindow.qrc11
-rw-r--r--src/leap/app.py15
-rw-r--r--src/leap/gui/loggerwindow.py124
-rw-r--r--src/leap/gui/mainwindow.py37
-rw-r--r--src/leap/gui/ui/loggerwindow.ui155
-rw-r--r--src/leap/gui/ui/mainwindow.ui10
-rw-r--r--src/leap/util/leap_log_handler.py99
16 files changed, 454 insertions, 8 deletions
diff --git a/LICENSE b/LICENSE
index bb3ae4ab..5f7cfbae 100644
--- a/LICENSE
+++ b/LICENSE
@@ -691,4 +691,8 @@ data/images/Globe.png
Author: Everaldo Coelho
License: LGPL - http://www.gnu.org/licenses/lgpl.html
WebSite: http://www.everaldo.com/
---- \ No newline at end of file
+---
+data/images/oxygen-icons/
+
+License: LGPL - http://www.gnu.org/licenses/lgpl.html
+Website: http://www.oxygen-icons.org/
diff --git a/Makefile b/Makefile
index 9e67505f..bb4b5854 100644
--- a/Makefile
+++ b/Makefile
@@ -20,9 +20,9 @@ TRANSLAT_DIR = data/translations
PROJFILE = data/leap_client.pro
#UI files to compile
-UI_FILES = mainwindow.ui wizard.ui
+UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui
#Qt resource files to compile
-RESOURCES = mainwindow.qrc locale.qrc
+RESOURCES = locale.qrc loggerwindow.qrc mainwindow.qrc
#pyuic4 and pyrcc4 binaries
PYUIC = pyside-uic
diff --git a/changes/feature_2566-logger_window b/changes/feature_2566-logger_window
new file mode 100644
index 00000000..c236aa82
--- /dev/null
+++ b/changes/feature_2566-logger_window
@@ -0,0 +1 @@
+ o Add centraliced logging facility, log history in a window, closes issue #2566
diff --git a/data/images/oxygen-icons/dialog-error.png b/data/images/oxygen-icons/dialog-error.png
new file mode 100644
index 00000000..01bc922d
--- /dev/null
+++ b/data/images/oxygen-icons/dialog-error.png
Binary files differ
diff --git a/data/images/oxygen-icons/dialog-information.png b/data/images/oxygen-icons/dialog-information.png
new file mode 100644
index 00000000..ee59e170
--- /dev/null
+++ b/data/images/oxygen-icons/dialog-information.png
Binary files differ
diff --git a/data/images/oxygen-icons/dialog-warning.png b/data/images/oxygen-icons/dialog-warning.png
new file mode 100644
index 00000000..8e913378
--- /dev/null
+++ b/data/images/oxygen-icons/dialog-warning.png
Binary files differ
diff --git a/data/images/oxygen-icons/document-save-as.png b/data/images/oxygen-icons/document-save-as.png
new file mode 100644
index 00000000..9695a564
--- /dev/null
+++ b/data/images/oxygen-icons/document-save-as.png
Binary files differ
diff --git a/data/images/oxygen-icons/edit-bomb.png b/data/images/oxygen-icons/edit-bomb.png
new file mode 100644
index 00000000..2b36224c
--- /dev/null
+++ b/data/images/oxygen-icons/edit-bomb.png
Binary files differ
diff --git a/data/images/oxygen-icons/script-error.png b/data/images/oxygen-icons/script-error.png
new file mode 100644
index 00000000..c7ace707
--- /dev/null
+++ b/data/images/oxygen-icons/script-error.png
Binary files differ
diff --git a/data/resources/loggerwindow.qrc b/data/resources/loggerwindow.qrc
new file mode 100644
index 00000000..847ca9a1
--- /dev/null
+++ b/data/resources/loggerwindow.qrc
@@ -0,0 +1,11 @@
+<RCC>
+ <qresource prefix="/">
+ <file>../images/oxygen-icons/edit-bomb.png</file>
+ <file>../images/oxygen-icons/document-save-as.png</file>
+ <file>../images/oxygen-icons/dialog-information.png</file>
+ <file>../images/oxygen-icons/script-error.png</file>
+ <file>../images/oxygen-icons/dialog-error.png</file>
+ <file>../images/oxygen-icons/dialog-information.png</file>
+ <file>../images/oxygen-icons/dialog-warning.png</file>
+ </qresource>
+</RCC>
diff --git a/src/leap/app.py b/src/leap/app.py
index 797cea8a..03552edb 100644
--- a/src/leap/app.py
+++ b/src/leap/app.py
@@ -26,6 +26,7 @@ from PySide import QtCore, QtGui
from leap.common.events import server as event_server
from leap.util import __version__ as VERSION
from leap.util import leap_argparse
+from leap.util.leap_log_handler import LeapLogHandler
from leap.gui import locale_rc
from leap.gui import twisted_main
from leap.gui.mainwindow import MainWindow
@@ -33,6 +34,7 @@ from leap.platform_init import IS_MAC
from leap.platform_init.locks import we_are_the_one_and_only
from leap.services.tx import leap_services
+
import codecs
codecs.register(lambda name: codecs.lookup('utf-8')
if name == 'cp65001' else None)
@@ -75,16 +77,23 @@ def main():
else:
level = logging.WARNING
+ # Console logger
logger = logging.getLogger(name='leap')
logger.setLevel(level)
console = logging.StreamHandler()
console.setLevel(level)
- formatter = logging.Formatter(
- '%(asctime)s '
- '- %(name)s - %(levelname)s - %(message)s')
+ log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+ formatter = logging.Formatter(log_format)
console.setFormatter(formatter)
logger.addHandler(console)
+ # LEAP custom handler
+ leap_handler = LeapLogHandler()
+ leap_handler.setLevel(level)
+ logger.addHandler(leap_handler)
+
+ logger.debug('Leap handler plugged!')
+
if not we_are_the_one_and_only():
# leap-client is already running
logger.warning("Tried to launch more than one instance "
diff --git a/src/leap/gui/loggerwindow.py b/src/leap/gui/loggerwindow.py
new file mode 100644
index 00000000..dd724ac7
--- /dev/null
+++ b/src/leap/gui/loggerwindow.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+# loggerwindow.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+History log window
+"""
+import logging
+
+from PySide import QtGui
+from ui_loggerwindow import Ui_LoggerWindow
+from leap.common.check import leap_assert, leap_assert_type
+from leap.util.leap_log_handler import LeapLogHandler
+
+logger = logging.getLogger(__name__)
+
+
+class LoggerWindow(QtGui.QWidget):
+ """
+ Window that displays a history of the logged messages in the app.
+ """
+ def __init__(self, handler):
+ """
+ Initialize the widget with the custom handler.
+
+ :param handler: Custom handler that supports history and signal.
+ :type handler: LeapLogHandler.
+ """
+ QtGui.QWidget.__init__(self)
+ leap_assert(handler, "We need a handler for the logger window")
+ leap_assert_type(handler, LeapLogHandler)
+
+ # Load UI
+ self.ui = Ui_LoggerWindow()
+ self.ui.setupUi(self)
+
+ # Make connections
+ self.ui.btnSave.clicked.connect(self._save_log_to_file)
+ self.ui.btnDebug.toggled.connect(self._load_history),
+ self.ui.btnInfo.toggled.connect(self._load_history),
+ self.ui.btnWarning.toggled.connect(self._load_history),
+ self.ui.btnError.toggled.connect(self._load_history),
+ self.ui.btnCritical.toggled.connect(self._load_history)
+
+ # Load logging history and connect logger with the widget
+ self._logging_handler = handler
+ self._connect_to_handler()
+ self._load_history()
+
+ def _connect_to_handler(self):
+ """
+ This method connects the loggerwindow with the handler through a
+ signal communicate the logger events.
+ """
+ self._logging_handler.new_log.connect(self._add_log_line)
+
+ def _add_log_line(self, log):
+ """
+ Adds a line to the history, only if it's in the desired levels to show.
+
+ :param log: a log record to be inserted in the widget
+ :type log: a dict with RECORD_KEY and MESSAGE_KEY.
+ the record contains the LogRecord of the logging module,
+ the message contains the formatted message for the log.
+ """
+ level = log[LeapLogHandler.RECORD_KEY].levelname
+ message = log[LeapLogHandler.MESSAGE_KEY]
+
+ if self._logs_to_display[level]:
+ self.ui.txtLogHistory.append(message)
+
+ def _load_history(self):
+ """
+ Load the previous logged messages in the widget.
+ They are stored in the custom handler.
+ """
+ self._set_logs_to_display()
+ self.ui.txtLogHistory.clear()
+ history = self._logging_handler.log_history
+ for line in history:
+ self._add_log_line(line)
+
+ def _set_logs_to_display(self):
+ """
+ Sets the logs_to_display dict getting the toggled options from the ui
+ """
+ self._logs_to_display = {
+ 'DEBUG': self.ui.btnDebug.isChecked(),
+ 'INFO': self.ui.btnInfo.isChecked(),
+ 'WARNING': self.ui.btnWarning.isChecked(),
+ 'ERROR': self.ui.btnError.isChecked(),
+ 'CRITICAL': self.ui.btnCritical.isChecked()
+ }
+
+ def _save_log_to_file(self):
+ """
+ Lets the user save the current log to a file
+ """
+ fileName, filtr = QtGui.QFileDialog.getSaveFileName(
+ self, self.tr("Save As"))
+
+ if fileName:
+ try:
+ with open(fileName, 'w') as output:
+ output.write(self.ui.txtLogHistory.toPlainText())
+ output.write('\n')
+ logger.debug('Log saved in %s' % (fileName, ))
+ except IOError, e:
+ logger.error("Error saving log file: %r" % (e, ))
+ else:
+ logger.debug('Log not saved!')
diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py
index a8aa1b1c..12187f51 100644
--- a/src/leap/gui/mainwindow.py
+++ b/src/leap/gui/mainwindow.py
@@ -35,6 +35,7 @@ from leap.common.events import events_pb2 as proto
from leap.config.leapsettings import LeapSettings
from leap.config.providerconfig import ProviderConfig
from leap.crypto.srpauth import SRPAuth
+from leap.gui.loggerwindow import LoggerWindow
from leap.gui.wizard import Wizard
from leap.services.eip.eipbootstrapper import EIPBootstrapper
from leap.services.eip.eipconfig import EIPConfig
@@ -208,6 +209,7 @@ class MainWindow(QtGui.QMainWindow):
self.ui.action_about_leap.triggered.connect(self._about)
self.ui.action_quit.triggered.connect(self.quit)
self.ui.action_wizard.triggered.connect(self._launch_wizard)
+ self.ui.action_show_logs.triggered.connect(self._show_logger_window)
self.raise_window.connect(self._do_raise_mainwindow)
# Used to differentiate between real quits and close to tray
@@ -249,6 +251,8 @@ class MainWindow(QtGui.QMainWindow):
self._wizard = None
self._wizard_firstrun = False
+ self._logger_window = None
+
self._bypass_checks = bypass_checks
self._soledad = None
@@ -282,6 +286,35 @@ class MainWindow(QtGui.QMainWindow):
self._wizard.exec_()
self._wizard = None
+ def _get_leap_logging_handler(self):
+ """
+ Gets the leap handler from the top level logger
+
+ :return: a logging handler or None
+ :rtype: LeapLogHandler or None
+ """
+ from leap.util.leap_log_handler import LeapLogHandler
+ leap_logger = logging.getLogger('leap')
+ for h in leap_logger.handlers:
+ if isinstance(h, LeapLogHandler):
+ return h
+ return None
+
+ def _show_logger_window(self):
+ """
+ Displays the window with the history of messages logged until now
+ and displays the new ones on arrival.
+ """
+ if self._logger_window is None:
+ leap_log_handler = self._get_leap_logging_handler()
+ if leap_log_handler is None:
+ logger.error('Leap logger handler not found')
+ else:
+ self._logger_window = LoggerWindow(handler=leap_log_handler)
+ self._logger_window.show()
+ else:
+ self._logger_window.show()
+
def _remember_state_changed(self, state):
enable = True if state == QtCore.Qt.Checked else False
self.ui.chkAutoLogin.setEnabled(enable)
@@ -1101,6 +1134,10 @@ class MainWindow(QtGui.QMainWindow):
self._really_quit = True
if self._wizard:
self._wizard.close()
+
+ if self._logger_window:
+ self._logger_window.close()
+
self.close()
if self._quit_callback:
diff --git a/src/leap/gui/ui/loggerwindow.ui b/src/leap/gui/ui/loggerwindow.ui
new file mode 100644
index 00000000..28325cdf
--- /dev/null
+++ b/src/leap/gui/ui/loggerwindow.ui
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LoggerWindow</class>
+ <widget class="QWidget" name="LoggerWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>648</width>
+ <height>469</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Logs</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../../../data/resources/mainwindow.qrc">
+ <normaloff>:/images/leap-color-big.png</normaloff>:/images/leap-color-big.png</iconset>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="0" colspan="2">
+ <widget class="QTextBrowser" name="txtLogHistory"/>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="btnDebug">
+ <property name="text">
+ <string>Debug</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/script-error.png</normaloff>:/images/oxygen-icons/script-error.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnInfo">
+ <property name="text">
+ <string>Info</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/dialog-information.png</normaloff>:/images/oxygen-icons/dialog-information.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnWarning">
+ <property name="text">
+ <string>Warning</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/dialog-warning.png</normaloff>:/images/oxygen-icons/dialog-warning.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnError">
+ <property name="text">
+ <string>Error</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/dialog-error.png</normaloff>:/images/oxygen-icons/dialog-error.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnCritical">
+ <property name="text">
+ <string>Critical</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/edit-bomb.png</normaloff>:/images/oxygen-icons/edit-bomb.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnSave">
+ <property name="text">
+ <string>Save to file</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/document-save-as.png</normaloff>:/images/oxygen-icons/document-save-as.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>btnDebug</tabstop>
+ <tabstop>btnInfo</tabstop>
+ <tabstop>btnWarning</tabstop>
+ <tabstop>btnError</tabstop>
+ <tabstop>btnCritical</tabstop>
+ <tabstop>btnSave</tabstop>
+ <tabstop>txtLogHistory</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../../../../data/resources/loggerwindow.qrc"/>
+ <include location="../../../../data/resources/mainwindow.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui
index b42ff180..fdf5c167 100644
--- a/src/leap/gui/ui/mainwindow.ui
+++ b/src/leap/gui/ui/mainwindow.ui
@@ -396,7 +396,7 @@
<x>0</x>
<y>0</y>
<width>415</width>
- <height>25</height>
+ <height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuSession">
@@ -417,9 +417,10 @@
</widget>
<widget class="QMenu" name="menuSettings">
<property name="title">
- <string>S&amp;ettings</string>
+ <string>&amp;Utils</string>
</property>
<addaction name="action_wizard"/>
+ <addaction name="action_show_logs"/>
</widget>
<addaction name="menuSession"/>
<addaction name="menuSettings"/>
@@ -451,6 +452,11 @@
<string>&amp;Wizard</string>
</property>
</action>
+ <action name="action_show_logs">
+ <property name="text">
+ <string>Show &amp;logs</string>
+ </property>
+ </action>
</widget>
<tabstops>
<tabstop>lnUser</tabstop>
diff --git a/src/leap/util/leap_log_handler.py b/src/leap/util/leap_log_handler.py
new file mode 100644
index 00000000..0e598032
--- /dev/null
+++ b/src/leap/util/leap_log_handler.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+# leap_log_handler.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Custom handler for the logger window.
+"""
+import logging
+
+from PySide import QtCore
+
+
+class LeapLogHandler(logging.Handler, QtCore.QObject):
+ """
+ Custom logging handler. It emits Qt signals so it can be plugged to a gui.
+ Also stores an history of logs that can be fetched after connect to a gui.
+ """
+ # All dicts returned are of the form
+ # {'record': LogRecord, 'message': str}
+ new_log = QtCore.Signal(dict)
+
+ MESSAGE_KEY = 'message'
+ RECORD_KEY = 'record'
+
+ def __init__(self):
+ logging.Handler.__init__(self)
+ QtCore.QObject.__init__(self)
+
+ self._log_history = []
+
+ def _set_format(self, logging_level):
+ """
+ Sets the log format depending on the parameter.
+ It uses html and css to set the colors for the logs.
+
+ :param logging_level: the debug level to define the color.
+ :type logging_level: str.
+ """
+ html_style = {
+ 'DEBUG': "color: blue",
+ 'INFO': "color: black",
+ 'WARNING': "color: black; background: yellow;",
+ 'ERROR': "color: red",
+ 'CRITICAL': "color: red; font-weight: bold;"
+ }
+
+ style_open = "<span style='" + html_style[logging_level] + "'>"
+ style_close = "</span>"
+ time = "%(asctime)s"
+ name = style_open + "%(name)s"
+ level = "%(levelname)s"
+ message = "%(message)s" + style_close
+ format_attrs = [time, name, level, message]
+ log_format = ' - '.join(format_attrs)
+ formatter = logging.Formatter(log_format)
+ self.setFormatter(formatter)
+
+ def emit(self, logRecord):
+ """
+ This method is fired every time that a record is logged by the
+ logging module.
+ This method reimplements logging.Handler.emit that is fired
+ in every logged message.
+ QObject.emit gets in the way on the PySide signal model but we
+ workarouded that issue.
+
+ :param logRecord: the record emitted by the logging module.
+ :type logRecord: logging.LogRecord.
+ """
+ self._set_format(logRecord.levelname)
+ log = self.format(logRecord)
+ log_item = {self.RECORD_KEY: logRecord, self.MESSAGE_KEY: log}
+ self._log_history.append(log_item)
+
+ # WARNING: the new-style connection does NOT work because PySide
+ # translates the emit method to self.emit, and that collides with
+ # the emit method for logging.Handler
+ # self.new_log.emit(log_item)
+ QtCore.QObject.emit(self, QtCore.SIGNAL('new_log(PyObject)'), log_item)
+
+ @property
+ def log_history(self):
+ """
+ Returns the history of the logged messages.
+ """
+ return self._log_history