#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
**reporter.py**
**Platform:**
Windows, Linux, Mac Os X.
**Description:**
Defines the :class:`Reporter` class and various others exceptions handling related objects.
**Others:**
"""
#**********************************************************************************************************************
#*** Future imports.
#**********************************************************************************************************************
from __future__ import unicode_literals
#**********************************************************************************************************************
#*** External imports.
#**********************************************************************************************************************
import functools
import inspect
import os
import platform
import re
import sys
if sys.version_info[:2] <= (2, 6):
from ordereddict import OrderedDict
else:
from collections import OrderedDict
import time
import traceback
from PyQt4.QtGui import QApplication
#**********************************************************************************************************************
#*** Internal imports.
#**********************************************************************************************************************
import foundations.core
import foundations.exceptions
import foundations.io
import foundations.verbose
import foundations.strings
import foundations.ui.common
import umbra.ui.common
from umbra.globals.constants import Constants
from umbra.globals.uiConstants import UiConstants
from umbra.globals.runtimeGlobals import RuntimeGlobals
#**********************************************************************************************************************
#*** Module attributes.
#**********************************************************************************************************************
__author__ = "Thomas Mansencal"
__copyright__ = "Copyright (C) 2008 - 2014 - Thomas Mansencal"
__license__ = "GPL V3.0 - http://www.gnu.org/licenses/"
__maintainer__ = "Thomas Mansencal"
__email__ = "[email protected]"
__status__ = "Production"
__all__ = ["LOGGER",
"UI_FILE",
"Reporter",
"baseExceptionHandler",
"systemExitExceptionHandler"
"criticalExceptionHandler",
"installExceptionReporter",
"uninstallExceptionReporter",
"enableExceptionReporter",
"disableExceptionReporter"]
LOGGER = foundations.verbose.installLogger()
UI_FILE = umbra.ui.common.getResourcePath(UiConstants.reporterUiFile)
#**********************************************************************************************************************
#*** Module classes and definitions.
#**********************************************************************************************************************
[docs]class Reporter(foundations.ui.common.QWidgetFactory(uiFile=UI_FILE)):
"""
Defines an exception reporting Widget.
"""
__instance = None
"""
:param __instance: Class instance.
:type __instance: Reporter
"""
def __new__(cls, *args, **kwargs):
"""
Constructor of the class.
:param \*args: Arguments.
:type \*args: \*
:param \*\*kwargs: Keywords arguments.
:type \*\*kwargs: \*\*
:return: Class instance.
:rtype: Library
"""
if not cls._Reporter__instance:
cls._Reporter__instance = super(Reporter, cls).__new__(cls, *args, **kwargs)
return cls._Reporter__instance
def __init__(self, parent=None, report=True, enabled=True, *args, **kwargs):
"""
Initializes the class.
:param parent: Object parent.
:type parent: QObject
:param report: Report to Crittercism.
:type report: bool
:param enabled: Is reporter enabled.
:type enabled: bool
:param \*args: Arguments.
:type \*args: \*
:param \*\*kwargs: Keywords arguments.
:type \*\*kwargs: \*\*
"""
if hasattr(self, "_Reporter__initialized"):
return
LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
super(Reporter, self).__init__(parent, *args, **kwargs)
# --- Setting class attributes. ---
self.__initialized = True
self.__report = None
self.report = report
self.__enabled = None
self.enabled = enabled
self.__jqueryJavascriptPath = umbra.ui.common.getResourcePath(os.path.join("javascripts", "jquery.js"))
self.__crittercismJavascriptPath = umbra.ui.common.getResourcePath(os.path.join("javascripts", "crittercism.js"))
self.__reporterJavascriptPath = umbra.ui.common.getResourcePath(os.path.join("javascripts", "reporter.js"))
self.__jqueryJavascript = foundations.io.File(self.__jqueryJavascriptPath).read()
self.__crittercismJavascript = foundations.io.File(self.__crittercismJavascriptPath).read()
self.__reporterJavascript = foundations.io.File(self.__reporterJavascriptPath).read().format(
UiConstants.crittercismId, Constants.version)
self.__style = """* {
margin: 0;
padding: 0;
}
::-webkit-scrollbar {
height: 12px;
width: 12px;
}
::-webkit-scrollbar-track-piece {
background-color: rgb(48, 48, 48);
}
::-webkit-scrollbar-thumb:horizontal, ::-webkit-scrollbar-thumb:vertical {
background-color: rgb(96, 96, 96);
}
::-webkit-scrollbar-thumb:horizontal:hover, ::-webkit-scrollbar-thumb:vertical:hover {
background-color: rgb(128, 128, 128);
}
::-webkit-scrollbar-thumb:horizontal {
width: 50px;
}
::-webkit-scrollbar-thumb:vertical {
height: 50px;
}
body {
background-color: rgb(32, 32, 32);
color: rgb(192, 192, 192);
font-size: 12pt;
margin: 16px;
overflow-y: scroll;
}
A:link {
color: rgb(160, 96, 64);
text-decoration: none;
}
A:visited {
text-decoration: none;
color: rgb(160, 96, 64);
}
A:active {
text-decoration: none;
color: rgb(160, 96, 64);
}
A:hover {
text-decoration: underline;
color: rgb(160, 96, 64);
}
.floatRight {
float: right;
}
.textAlignRight {
text-align: right;
}
div {
overflow:hidden;
margin: auto;
text-overflow: ellipsis;
word-wrap: break-word;
}
div.header {
background-color: rgb(210, 64, 32);
color: rgb(32, 32, 32);
padding: 24px;
}
div.traceback {
background-color: rgb(210, 64, 32);
color: rgb(32, 32, 32);
font-size: 16px;
padding: 16px;
}
div.content {
padding: 16px;
}
div.stack {
}
div.location {
background-color: rgb(48, 48, 48);
font-size: 16px;
padding: 8px;
}
div.context {
background-color: rgb(48, 48, 48);
color: rgb(160, 160, 160);
font-family: "Courier New";
font-size: 14px;
padding: 32px;
}
span.highlight {
background-color: rgb(160, 96, 64);
color: rgb(32, 32, 32);
display: block;
font-weight: bold;
}
div.exception {
background-color: rgb(210, 64, 32);
color: rgb(32, 32, 32);
font-size: 16px;
padding: 16px;
}
div.debug {
padding: 16px;
}
div.frame {
background-color: rgb(48, 48, 48);
padding: 8px;
}
div.type {
font-size: 16px;
}
div.locals {
background-color: rgb(48, 48, 48);
color: rgb(160, 160, 160);
line-height: 150%;
padding: 32px;
}"""
self.__html = None
self.__onlineText = "An <b>unhandled</b> exception occured, \
this report has been sent to <b>HDRLabs</b> development team!"
self.__offlineText = "An <b>unhandled</b> exception occured, \
mailing this report to <b>{0}</b> would help improving <b>{1}</b>!".format(__email__, Constants.applicationName)
self.__footerText = \
"The severity of this exception is not critical and <b>{0}</b> will resume!".format(Constants.applicationName)
self.__initializeUi()
#******************************************************************************************************************
#*** Attributes properties.
#******************************************************************************************************************
@property
def report(self):
"""
Property for **self.__report** attribute.
:return: self.__report.
:rtype: bool
"""
return self.__report
@report.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(AssertionError)
def report(self, value):
"""
Setter for **self.__report** attribute.
:param value: Attribute value.
:type value: bool
"""
if value is not None:
assert type(value) is bool, "'{0}' attribute: '{1}' type is not 'bool'!".format("report", value)
self.__report = value
@report.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def report(self):
"""
Deleter for **self.__report** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "report"))
@property
def enabled(self):
"""
Property for **self.__enabled** attribute.
:return: self.__enabled.
:rtype: bool
"""
return self.__enabled
@enabled.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(AssertionError)
def enabled(self, value):
"""
Setter for **self.__enabled** attribute.
:param value: Attribute value.
:type value: bool
"""
if value is not None:
assert type(value) is bool, "'{0}' attribute: '{1}' type is not 'bool'!".format("enabled", value)
self.__enabled = value
@enabled.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def enabled(self):
"""
Deleter for **self.__enabled** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "enabled"))
#******************************************************************************************************************
#*** Class methods.
#******************************************************************************************************************
def __call__(self, *args):
"""
Caller of the class.
:param \*args: Arguments.
:type \*args: \*
:param \*\*kwargs: Keywords arguments.
:type \*\*kwargs: \*\*
:return: Class instance.
:rtype: Library
"""
self.handleException(*args)
[docs] def show(self):
"""
Reimplements the :meth:`QWidget.show` method.
"""
RuntimeGlobals.splashscreen and RuntimeGlobals.splashscreen.hide()
super(Reporter, self).show()
self.raise_()
def __initializeUi(self):
"""
Initializes the Widget ui.
"""
LOGGER.debug("> Initializing '{0}' Widget ui.".format(self.__class__.__name__))
self.__view = self.Reporter_webView
self.setWindowTitle("{0} - Reporter".format(Constants.applicationName))
self.Footer_label.setText(self.__footerText)
self.__initializeContextUi()
self.__setHtml()
# Signals / Slots.
self.Copy_Report_pushButton.clicked.connect(self.__Copy_Report_pushButton__clicked)
self.Disable_Reporter_pushButton.clicked.connect(self.__Disable_Reporter_pushButton__clicked)
def __initializeContextUi(self):
"""
Sets the context Widget ui.
"""
if foundations.common.isInternetAvailable():
text = self.__onlineText
else:
text = self.__offlineText
self.Header_label.setText(text)
def __Copy_Report_pushButton__clicked(self, checked):
"""
Defines the slot triggered by **Copy_Report_pushButton** Widget when clicked.
:param checked: Checked state.
:type checked: bool
"""
clipboard = QApplication.clipboard()
clipboard.setText(self.__view.page().mainFrame().toPlainText())
def __Disable_Reporter_pushButton__clicked(self, checked):
"""
Defines the slot triggered by **Disable_Reporter_pushButton** Widget when clicked.
:param checked: Checked state.
:type checked: bool
"""
uninstallExceptionReporter()
self.__enabled = False
def __getHtml(self, body=None):
"""
Returns the html content with given body tag content.
:param body: Body tag content.
:type body: unicode
:return: Html.
:rtype: unicode
"""
output = []
output.append("<html>")
output.append("<head>")
for javascript in (self.__jqueryJavascript,
self.__crittercismJavascript,
self.__reporterJavascript):
output.append("<script type=\"text/javascript\">")
output.append(javascript)
output.append("</script>")
output.append("<style type=\"text/css\">")
output.append(self.__style)
output.append("</style>")
output.append("</head>")
if body is not None:
output.append(body)
else:
output.append("<body>")
output.append("<div id=\"report\">")
output.append("</div>")
output.append("</body>")
output.append("</html>")
return "\n".join(output)
def __setHtml(self, html=None):
"""
Sets the html content in the View using given body.
:param html: Html content.
:type html: unicode
"""
self.__html = self.__getHtml(html)
self.__view.setHtml(self.__html)
def __updateHtml(self, html):
"""
Updates the View with given html content.
:param html: Html content.
:type html: unicode
"""
if platform.system() in ("Windows", "Microsoft"):
html = re.sub(r"((?:[a-zA-Z]\:|\\\\[\w\.]+\\[\w.$]+)\\(?:[\w]+\\)*\w([\w.])+)",
lambda x: foundations.strings.toForwardSlashes(x.group(1)),
html)
html = foundations.strings.replace(html, OrderedDict([('"', '\\"'), ("\n", "")]))
self.__evaluateJavascript("$(\"#report\").html(\"{0}\");".format(html))
def __evaluateJavascript(self, javascript):
"""
Evaluates given javascript content in the View.
:param javascript: Javascript.
:type javascript: unicode
"""
self.__view.page().mainFrame().evaluateJavaScript(javascript)
[docs] def handleException(self, *args):
"""
Handles given exception.
:param \*args: Arguments.
:type \*args: \*
"""
if not self.__enabled:
return
cls, instance, trcback = foundations.exceptions.extractException(*args)
LOGGER.info("{0} | Handling '{1}' exception!".format(self.__class__.__name__, foundations.strings.toString(cls)))
self.__initializeContextUi()
self.__updateHtml(self.formatHtmlException(cls, instance, trcback))
self.show()
self.__report and self.reportExceptionToCrittercism(cls, instance, trcback)
foundations.exceptions.baseExceptionHandler(cls, instance, trcback)
self.exec_()
# Oncilla: Statement commented by auto-documentation process:
# Oncilla: Statement commented by auto-documentation process: @staticmethod
[docs] def formatTextException(*args):
"""
Formats given exception as a text.
:param \*args: Arguments.
:type \*args: \*
:return: Exception text.
:rtype: unicode
"""
format = lambda x: re.sub(r"^(\s+)", lambda y: "{0} ".format("." * len(y.group(0))), x.rstrip().expandtabs(4))
verbose = 10
cls, instance, trcback = args
stack = foundations.exceptions.extractStack(foundations.exceptions.getInnerMostFrame(trcback), verbose)
text = []
text.append(foundations.strings.toString(cls))
text.append("")
for line in foundations.exceptions.formatException(cls, instance, trcback):
text.append(format("{0}".format(format(line))))
text.append("")
text.append("An unhandled exception occured in {0} {1}!".format(Constants.applicationName,
Constants.version))
text.append("Sequence of calls leading up to the exception, in their occurring order:")
text.append("")
for frame, fileName, lineNumber, name, context, index in stack:
location = "{0}{1}".format(name if name != "<module>" else "",
inspect.formatargvalues(*inspect.getargvalues(frame)))
text.append("File \"{0}\", line {1}, in {2}".format(fileName, lineNumber, location))
for i, line in enumerate(context):
if i == index:
text.append(format("\t{0} {1} <===".format(lineNumber - index + i, format(format(line)))))
else:
text.append(format("\t{0} {1}".format(lineNumber - index + i, format(format(line)))))
text.append("")
for line in traceback.format_exception_only(cls, instance):
text.append("{0}".format(format(line)))
text.append("")
text.append("Frames locals by stack ordering, innermost last:")
text.append("")
for frame, locals in foundations.exceptions.extractLocals(trcback):
name, fileName, lineNumber = frame
text.append("Frame \"{0}\" in \"{1}\" file, line {2}:".format(name, fileName, lineNumber))
arguments, namelessArgs, keywordArgs, locals = locals
hasArguments, hasLocals = any((arguments, namelessArgs, keywordArgs)), any(locals)
hasArguments and text.append(format("\tArguments:"))
for key, value in arguments.iteritems():
text.append(format("\t\t{0} = {1}".format(key, value)))
for value in namelessArgs:
text.append(format("\t\t{0}".format(value)))
for key, value in sorted(keywordArgs.iteritems()):
text.append(format("\\tt{0} = {1}".format(key, value)))
hasLocals and text.append(format("\tLocals:"))
for key, value in sorted(locals.iteritems()):
text.append(format("\t\t{0} = {1}".format(key, value)))
text.append("")
return text
[docs] def reportExceptionToCrittercism(self, *args):
"""
Reports given exception to Crittercism.
:param \*args: Arguments.
:type \*args: \*
:return: Method success.
:rtype: bool
"""
if foundations.common.isInternetAvailable():
cls, instance, trcback = args
title = re.escape("".join(map(lambda x: x.strip(), traceback.format_exception_only(cls, instance))))
file = trcback.tb_frame.f_code.co_filename
lineNumber = trcback.tb_lineno
stack = repr(map(str, self.formatTextException(cls, instance, trcback)))
javascript = "Crittercism.logExternalException(\"{0}\", \"{1}\", {2}, {3});".format(
title, file, lineNumber, stack)
self.__evaluateJavascript(javascript)
LOGGER.info("{0} | Exception report sent to Crittercism!".format(self.__class__.__name__))
return True
else:
LOGGER.warning("!> {0} | Failed sending exception report to Crittercism!".format(self.__class__.__name__))
return False
[docs]def baseExceptionHandler(*args):
"""
Provides a base exception handler.
:param \*args: Arguments.
:type \*args: \*
:return: Definition success.
:rtype: bool
"""
Reporter().handleException(*args)
return True
[docs]def systemExitExceptionHandler(*args):
"""
Provides a system exit exception handler.
:param \*args: Arguments.
:type \*args: \*
:return: Definition success.
:rtype: bool
"""
reporter = Reporter()
reporter.Footer_label.setText(
"The severity of this exception is critical, <b>{0}</b> cannot continue and will now close!".format(
Constants.applicationName))
baseExceptionHandler(*args)
foundations.core.exit(1)
return True
[docs]def criticalExceptionHandler(object):
"""
Marks an object that would system exit in case of critical exception.
:param object: Object to decorate.
:type object: object
:return: Object.
:rtype: object
"""
# Oncilla: Statement commented by auto-documentation process:
# Oncilla: Statement commented by auto-documentation process: @functools.wraps(object)
def criticalExceptionHandlerWrapper(*args, **kwargs):
"""
Marks an object that would system exit in case of critical exception.
:param \*args: Arguments.
:type \*args: \*
:param \*\*kwargs: Keywords arguments.
:type \*\*kwargs: \*\*
"""
_exceptions__frame__ = True
try:
return object(*args, **kwargs)
except Exception as error:
systemExitExceptionHandler(error)
return criticalExceptionHandlerWrapper
[docs]def installExceptionReporter(report=True):
"""
Installs the exceptions reporter.
:param report: Report to Crittercism.
:type report: bool
:return: Reporter instance.
:rtype: Reporter
"""
reporter = Reporter(report=report)
sys.excepthook = reporter
return reporter
[docs]def uninstallExceptionReporter():
"""
Uninstalls the exceptions reporter.
:return: Definition success.
:rtype: bool
"""
return foundations.exceptions.installExceptionHandler()
[docs]def enableExceptionReporter():
"""
Enables the exceptions reporter.
:return: Definition success.
:rtype: bool
"""
reporter = Reporter().enabled = True
return True
[docs]def disableExceptionReporter():
"""
Disables the exceptions reporter.
:return: Definition success.
:rtype: bool
"""
reporter = Reporter().enabled = False
return True
# Oncilla: Statement commented by auto-documentation process:
# Oncilla: Statement commented by auto-documentation process: if __name__ == "__main__":
# Oncilla: Statement commented by auto-documentation process: foundations.verbose.getLoggingConsoleHandler()
# Oncilla: Statement commented by auto-documentation process: foundations.verbose.setVerbosityLevel(3)
# Oncilla: Statement commented by auto-documentation process:
# Oncilla: Statement commented by auto-documentation process: application = umbra.ui.common.getApplicationInstance()
# Oncilla: Statement commented by auto-documentation process:
# Oncilla: Statement commented by auto-documentation process: installExceptionReporter()
# Oncilla: Statement commented by auto-documentation process:
# Oncilla: Statement commented by auto-documentation process: def _testReporter(bar=1, nemo="captain", *args, **kwargs):
# Oncilla: Statement commented by auto-documentation process: 1 / 0
# Oncilla: Statement commented by auto-documentation process:
# Oncilla: Statement commented by auto-documentation process: _testReporter(luke="skywalker")
# Oncilla: Statement commented by auto-documentation process: