Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement LoggedException in vsc.utils.exceptions module #160

Merged
merged 9 commits into from
Mar 18, 2015
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions lib/vsc/utils/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
##
# Copyright 2015-2015 Ghent University
#
# This file is part of vsc-base,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en),
# the Hercules foundation (http://www.herculesstichting.be/in_English)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# http://github.com/hpcugent/vsc-base
#
# vsc-base is free software: you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as
# published by the Free Software Foundation, either version 2 of
# the License, or (at your option) any later version.
#
# vsc-base 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 Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with vsc-base. If not, see <http://www.gnu.org/licenses/>.
##
"""
Module providing custom exceptions.

@author: Kenneth Hoste (Ghent University)
@author: Riccardo Murri (University of Zurich)
"""
import inspect
import logging
from vsc.utils import fancylogger


def get_callers_logger():
"""
Get logger defined in caller's environment
@return: logger instance (or None if none was found)
"""
logger_cls = logging.getLoggerClass()
frame = inspect.currentframe()
logger = None
try:
# frame may be None, see https://docs.python.org/2/library/inspect.html#inspect.currentframe
if frame is None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the whole try/finally blck is part of the else (or rather the if is not None)

framerecords = []
else:
framerecords = inspect.getouterframes(frame)

for frameinfo in framerecords:
bindings = inspect.getargvalues(frameinfo[0]).locals
for val in bindings.values():
if isinstance(val, logger_cls):
logger = val
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add break?

finally:
# make very sure that reference to frame object is removed, to avoid reference cycles
# see https://docs.python.org/2/library/inspect.html#the-interpreter-stack
del frame

return logger


class LoggedException(Exception):
"""Exception that logs it's message when it is created."""

def __init__(self, msg, logger=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

support *args

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I'll tackle that

"""
Constructor.
@param logger: logger to use
"""
super(LoggedException, self).__init__(msg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there anything in the __init__ that could cause the logging part below not to be reached? (i guess only a raise in the __init__ can cause that?)
to be safe, i'd move the super after the logging block (then there's always logging).
(maybe this is a bit farfetched 😄 )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt there's anything, but it can't hurt to do the logging first, so changed.


# try to use logger defined in caller's environment
if logger is None:
logger = get_callers_logger()
# search can fail, use root logger as a fallback
if logger is None:
logger = fancylogger.getLogger()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make fancylogger a class constant. if someone want to override it, it's the easiest way


logger.error(msg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also the log function should be configurable with class constant (probably needed in EB)

2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def remove_bdist_rpm_source_file():

PACKAGE = {
'name': 'vsc-base',
'version': '2.0.4',
'version': '2.1.0',
'author': [sdw, jt, ag, kh],
'maintainer': [sdw, jt, ag, kh],
'packages': ['vsc', 'vsc.utils', 'vsc.install'],
Expand Down
122 changes: 122 additions & 0 deletions test/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
#
# Copyright 2015-2015 Ghent University
#
# This file is part of vsc-base,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en),
# the Hercules foundation (http://www.herculesstichting.be/in_English)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# http://github.com/hpcugent/vsc-base
#
# vsc-base is free software: you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as
# published by the Free Software Foundation, either version 2 of
# the License, or (at your option) any later version.
#
# vsc-base 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 Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with vsc-base. If not, see <http://www.gnu.org/licenses/>.
#
"""
Unit tests for exceptions module.

@author: Kenneth Hoste (Ghent University)
"""
import logging
import os
import re
import tempfile
from unittest import TestLoader, main

from vsc.utils.exceptions import LoggedException
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test get_callers_logger itself if possible?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

from vsc.utils.fancylogger import getLogger, logToFile, logToScreen, getRootLoggerName, setLogFormat
from vsc.utils.testing import EnhancedTestCase


def raise_loggedexception(msg, logger=None):
"""Utility function: just raise a LoggedException."""
raise LoggedException(msg, logger=logger)


logToScreen(enable=False)


class ExceptionsTest(EnhancedTestCase):
"""Tests for exceptions module."""

def test_loggedexception_defaultlogger(self):
"""Test LoggedException custom exception class."""
fd, tmplog = tempfile.mkstemp()
os.close(fd)

# set log format, for each regex searching
setLogFormat("%(name)s :: %(message)s")

# if no logger is available, and no logger is specified, use default 'root' fancylogger
logToFile(tmplog, enable=True)
self.assertErrorRegex(LoggedException, 'BOOM', raise_loggedexception, 'BOOM')
logToFile(tmplog, enable=False)

log_re = re.compile("^%s :: BOOM$" % getRootLoggerName(), re.M)
logtxt = open(tmplog, 'r').read()
self.assertTrue(log_re.match(logtxt), "%s matches %s" % (log_re.pattern, logtxt))

os.remove(tmplog)

def test_loggedexception_specifiedlogger(self):
"""Test LoggedException custom exception class."""
fd, tmplog = tempfile.mkstemp()
os.close(fd)

# set log format, for each regex searching
setLogFormat("%(name)s :: %(message)s")

logger1 = getLogger('testlogger_one')
logger2 = getLogger('testlogger_two')

# if logger is specified, it should be used
logToFile(tmplog, enable=True)
self.assertErrorRegex(LoggedException, 'BOOM', raise_loggedexception, 'BOOM', logger=logger1)
logToFile(tmplog, enable=False)

log_re = re.compile("^%s.testlogger_one :: BOOM$" % getRootLoggerName())
logtxt = open(tmplog, 'r').read()
self.assertTrue(log_re.match(logtxt), "%s matches %s" % (log_re.pattern, logtxt))

os.remove(tmplog)

def test_loggedexception_callerlogger(self):
"""Test LoggedException custom exception class."""
fd, tmplog = tempfile.mkstemp()
os.close(fd)

# set log format, for each regex searching
setLogFormat("%(name)s :: %(message)s")

logger = getLogger('testlogger_local')

# if no logger is specified, logger available in calling context should be used
logToFile(tmplog, enable=True)
self.assertErrorRegex(LoggedException, 'BOOM', raise_loggedexception, 'BOOM')
logToFile(tmplog, enable=False)

log_re = re.compile("^%s.testlogger_local :: BOOM$" % getRootLoggerName())
logtxt = open(tmplog, 'r').read()
self.assertTrue(log_re.match(logtxt), "%s matches %s" % (log_re.pattern, logtxt))

os.remove(tmplog)

def suite():
""" returns all the testcases in this module """
return TestLoader().loadTestsFromTestCase(ExceptionsTest)

if __name__ == '__main__':
"""Use this __main__ block to help write and test unittests"""
main()
2 changes: 1 addition & 1 deletion test/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def test_modules_in_pkg_path(self):
"""Test modules_in_pkg_path function."""
# real example
import vsc.utils
vsc_utils_modules = ['__init__', 'affinity', 'asyncprocess', 'daemon', 'dateandtime', 'fancylogger',
vsc_utils_modules = ['__init__', 'affinity', 'asyncprocess', 'daemon', 'dateandtime', 'exceptions', 'fancylogger',
'frozendict', 'generaloption', 'mail', 'missing', 'optcomplete', 'patterns', 'rest',
'run', 'testing', 'wrapper']
self.assertEqual(sorted(modules_in_pkg_path(vsc.utils.__path__[0])), vsc_utils_modules)
Expand Down
5 changes: 3 additions & 2 deletions test/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

import test.asyncprocess as a
import test.dateandtime as td
import test.generaloption as tg
import test.exceptions as te
import test.fancylogger as tf
import test.generaloption as tg
import test.missing as tm
import test.rest as trest
import test.run as trun
Expand All @@ -20,7 +21,7 @@
from vsc.utils import fancylogger
fancylogger.logToScreen(enable=False)

suite = unittest.TestSuite([x.suite() for x in (a, td, tg, tf, tm, trest, trun, tt, topt, wrapt)])
suite = unittest.TestSuite([x.suite() for x in (a, td, tg, tf, te, tm, trest, trun, tt, topt, wrapt)])

try:
import xmlrunner
Expand Down