Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Updated MailHandler to allow using TLS without having a keyfile/certfile #47

Closed
wants to merge 10 commits into from

2 participants

@shanx

Hey guys,

Thanks for logbook! After dealing with the pain of python's stdlib logging for years, this library is such a relief! Never want to go back ;)

Attached is a small patch to allow using TLS without explicitly defining keyfile/certfile. I didn't find any unit tests testing this behavior. Also fixed a small typo.

Cheers,
Remco

@shanx

Hmm this is of course not really backwards compatible with people using tls with smtp in the old way. Let me know what you think and I could fix backward compatibility.

@brainstorm
Collaborator

Hello @shanx, I find your patch interesting, could you update it a little bit so that it merges the current codebase and guarantee backwards compatibility?

Thanks!

@brainstorm
Collaborator

Unmantained, closing.

@brainstorm brainstorm closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 3, 2011
  1. @shanx

    Fixed typo

    shanx authored
Commits on Dec 5, 2011
  1. @shanx

    Added venv files to gitignore

    shanx authored
  2. @shanx
Commits on Dec 8, 2011
  1. @shanx
  2. @shanx
  3. @shanx
Commits on Dec 14, 2011
  1. @shanx

    Changed level of default_handler to error. IMHO we only want the defa…

    shanx authored
    …ult_handler to log to stderr when an error occurs. If I want to see more information, then I will register my own handler.
  2. @shanx

    Added python 3.2 to tox

    shanx authored
  3. @shanx
  4. @shanx
This page is out of date. Refresh to see the latest.
View
5 .gitignore
@@ -11,3 +11,8 @@ env*
.coverage
cover
build
+.DS_Store
+.Python
+/bin
+/include
+/lib
View
2  logbook/__init__.py
@@ -42,5 +42,5 @@
# install a default global handler
-default_handler = StderrHandler()
+default_handler = StderrHandler(level=ERROR)
default_handler.push_application()
View
18 logbook/base.py
@@ -152,7 +152,11 @@ def push_thread(self):
def pop_thread(self):
"""Pops the context object from the stack."""
popped = self.stack_manager.pop_thread()
- assert popped is self, 'popped unexpected object'
+ assert popped is self, \
+ 'Popped unexpected object. Expected: %s, got: %s' % (
+ repr(self),
+ repr(popped)
+ )
def push_application(self):
"""Pushes the context object to the application stack."""
@@ -161,7 +165,11 @@ def push_application(self):
def pop_application(self):
"""Pops the context object from the stack."""
popped = self.stack_manager.pop_application()
- assert popped is self, 'popped unexpected object'
+ assert popped is self, \
+ 'Popped unexpected object. Expected: %s, got: %s' % (
+ repr(self),
+ repr(popped)
+ )
class NestedSetup(StackedObject):
@@ -666,7 +674,9 @@ def log(self, level, *args, **kwargs):
def catch_exceptions(self, *args, **kwargs):
"""A context manager that catches exceptions and calls
- :meth:`exception` for exceptions caught that way. Example::
+ :meth:`exception` for exceptions caught that way.
+
+ Example::
with logger.catch_exceptions():
execute_code_that_might_fail()
@@ -848,7 +858,7 @@ class LoggerGroup(object):
def __init__(self, loggers=None, level=NOTSET, processor=None):
#: a list of all loggers on the logger group. Use the
#: :meth:`add_logger` and :meth:`remove_logger` methods to add
- #: or remove loggers from this list.
+ #: or remove loggers from this list.
self.loggers = []
if loggers is not None:
for logger in loggers:
View
3  logbook/compat.py
@@ -196,7 +196,8 @@ def emit(self, record):
def redirect_warnings():
"""Like :func:`redirected_warnings` but will redirect all warnings
- to the shutdown of the interpreter::
+ to the shutdown of the interpreter
+ ::
from logbook.compat import redirect_warnings
redirect_warnings()
View
20 logbook/handlers.py
@@ -239,7 +239,7 @@ def emit_batch(self, records, reason):
is intended to be used by other handlers which are already protected
against internal breakage.
- `reason` is a string that specifies the rason why :meth:`emit_batch`
+ `reason` is a string that specifies the reason why :meth:`emit_batch`
was called, and not :meth:`emit`. The following are valid values:
``'buffer'``
@@ -1021,10 +1021,10 @@ class MailHandler(Handler, StringFormatterHandlerMixin,
record_cache_prune = 0.333
def __init__(self, from_addr, recipients, subject=None,
- server_addr=None, credentials=None, secure=None,
- record_limit=None, record_delta=None, level=NOTSET,
- format_string=None, related_format_string=None,
- filter=None, bubble=False):
+ server_addr=None, credentials=None, use_tls=False,
+ keyfile=None, certfile=None,record_limit=None,
+ record_delta=None, level=NOTSET, format_string=None,
+ related_format_string=None, filter=None, bubble=False):
Handler.__init__(self, level, filter, bubble)
StringFormatterHandlerMixin.__init__(self, format_string)
LimitingHandlerMixin.__init__(self, record_limit, record_delta)
@@ -1035,7 +1035,9 @@ def __init__(self, from_addr, recipients, subject=None,
self.subject = subject
self.server_addr = server_addr
self.credentials = credentials
- self.secure = secure
+ self.use_tls = use_tls
+ self.keyfile=keyfile
+ self.certfile=certfile
if related_format_string is None:
related_format_string = self.default_related_format_string
self.related_format_string = related_format_string
@@ -1123,15 +1125,15 @@ def get_connection(self):
from smtplib import SMTP, SMTP_PORT, SMTP_SSL_PORT
if self.server_addr is None:
host = 'localhost'
- port = self.secure and SMTP_SSL_PORT or SMTP_PORT
+ port = self.use_tls and SMTP_SSL_PORT or SMTP_PORT
else:
host, port = self.server_addr
con = SMTP()
con.connect(host, port)
if self.credentials is not None:
- if self.secure is not None:
+ if self.use_tls:
con.ehlo()
- con.starttls(*self.secure)
+ con.starttls(self.keyfile, self.certfile)
con.ehlo()
con.login(*self.credentials)
return con
View
22 logbook/testsuite/__init__.py
@@ -9,9 +9,13 @@
:license: BSD, see LICENSE for more details.
"""
import sys
-import unittest
-import logbook
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest
+
+import logbook
_skipped_modules = []
_missing = object()
@@ -86,17 +90,3 @@ def wrapper(*args, **kwargs):
sys.modules[name] = old
return wrapper
return decorate
-
-
-def suite():
- loader = unittest.TestLoader()
- suite = LogbookTestSuite()
- suite.addTests(loader.loadTestsFromName('logbook.testsuite.test_regular'))
- if sys.version_info >= (2, 5):
- suite.addTests(loader.loadTestsFromName
- ('logbook.testsuite.test_contextmanager'))
- return suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')
View
51 logbook/testsuite/test_contextmanager.py
@@ -15,7 +15,6 @@
import sys
import pickle
import shutil
-import unittest
import tempfile
import socket
from datetime import datetime, timedelta
@@ -24,6 +23,11 @@
from contextlib import contextmanager
from cStringIO import StringIO
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest
+
import logbook
from logbook.testsuite import LogbookTestCase, make_fake_mail_handler
@@ -278,21 +282,21 @@ def test_mail_handler(self):
else:
ascii_subject = False
subject = u'\xf8nicode'
- handler = make_fake_mail_handler(subject=subject)
+ handler = make_fake_mail_handler(subject=subject, level=logbook.CRITICAL)
with capture_stderr() as fallback:
with handler:
- self.log.warn('This is not mailed')
+ self.log.error('This is not mailed')
try:
1 / 0
- except Exception:
- self.log.exception('This is unfortunate')
+ except Exception, e:
+ self.log.critical('This is unfortunate', exc_info=sys.exc_info())
self.assertEqual(len(handler.mails), 1)
sender, receivers, mail = handler.mails[0]
self.assertEqual(sender, handler.from_addr)
if not ascii_subject:
self.assert_('=?utf-8?q?=C3=B8nicode?=' in mail)
- self.assert_(re.search('Message type:\s+ERROR', mail))
+ self.assert_(re.search('Message type:\s+CRITICAL', mail))
self.assert_(re.search('Location:.*%s' % test_file, mail))
self.assert_(re.search('Module:\s+%s' % __name__, mail))
self.assert_(re.search('Function:\s+test_mail_handler', mail))
@@ -400,9 +404,9 @@ def process_record(self, record):
handler = MyTestHandler()
with capture_stderr() as captured:
with handler:
- log.warn('From my logger')
- self.log.warn('From another logger')
- self.assert_(handler.has_warning('From my logger'))
+ log.error('From my logger')
+ self.log.error('From another logger')
+ self.assert_(handler.has_error('From my logger'))
self.assert_('From another logger' in captured.getvalue())
def test_custom_handling_tester(self):
@@ -445,7 +449,7 @@ def new_heavy_init(self):
null_handler.bubble = True
with capture_stderr() as captured:
- logbook.warning('Not a blockhole')
+ logbook.error('Not a blockhole')
self.assertNotEqual(captured.getvalue(), '')
def test_calling_frame(self):
@@ -471,7 +475,7 @@ def test_nested_setups(self):
logger.error('This is also a mail')
with logger.catch_exceptions():
1 / 0
- logger.warn('And here we go straight back to stderr')
+ logger.error('And here we go straight back to stderr')
self.assert_(test_handler.has_warning('This is a warning'))
self.assert_(test_handler.has_error('This is also a mail'))
@@ -692,12 +696,12 @@ def test_error_flag(self):
with logbook.Flags(errors='silent'):
with logbook.Flags(errors='print'):
- self.log.warn('Foo {42}', 'aha')
+ self.log.error('Foo {42}', 'aha')
self.assertNotEqual(captured.getvalue(), '')
try:
with logbook.Flags(errors='raise'):
- self.log.warn('Foo {42}', 'aha')
+ self.log.error('Foo {42}', 'aha')
except Exception, e:
self.assert_('Could not format message with provided '
'arguments' in str(e))
@@ -733,10 +737,13 @@ def inject_extra(record):
class DefaultConfigurationTestCase(LogbookTestCase):
def test_default_handlers(self):
+ # Logbook should log level ERROR and up by default
+ self.assertEqual(logbook.ERROR, logbook.default_handler.level)
+
with capture_stderr() as stream:
- self.log.warn('Aha!')
+ self.log.error('Aha!')
captured = stream.getvalue()
- self.assert_('WARNING: testlogger: Aha!' in captured)
+ self.assert_('ERROR: testlogger: Aha!' in captured)
class LoggingCompatTestCase(LogbookTestCase):
@@ -754,7 +761,7 @@ def test_basic_compat(self):
logger.warn('This is from the old system')
logger.error('This is from the old system')
logger.critical('This is from the old system')
- self.assert_(('WARNING: %s: This is from the old system' % name)
+ self.assert_(('ERROR: %s: This is from the old system' % name)
in captured.getvalue())
def test_redirect_logbook(self):
@@ -829,5 +836,13 @@ def test_tagged(self):
self.assert_('cmd message' in stringio)
-if __name__ == '__main__':
- unittest.main()
+class PlatformSpecificTestCase(LogbookTestCase):
+ @unittest.skipUnless(sys.platform.startswith("win"), "Requires Windows")
+ def test_win32_logger(self):
+ from logbook import NTEventLogHandler, Logger
+
+ logger = Logger('MyLogger')
+ handler = NTEventLogHandler('My Application')
+
+ with handler.applicationbound():
+ logger.error('Testing')
View
80 logbook/testsuite/test_regular.py
@@ -17,7 +17,6 @@
import thread
import pickle
import shutil
-import unittest
import tempfile
import socket
from datetime import datetime, timedelta
@@ -25,6 +24,11 @@
from itertools import izip
from cStringIO import StringIO
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest
+
import logbook
from logbook.testsuite import LogbookTestCase, skip_if, require, missing, \
make_fake_mail_handler
@@ -147,6 +151,35 @@ def make_record():
self.assert_(re.search('Happened in file .*%s, line \d+' % test_file,
errormsg))
+ def test_context_exception(self):
+ # NOTE: Since no actual logging is done we only put this
+ # test in test_regular
+
+ # Push a certain handler onto the application stack
+ handler = logbook.TestHandler()
+ handler.push_application()
+
+ # Creating another handler and trying to pop it from that stack
+ # should raise an AssertionError
+ another_handler = logbook.StderrHandler()
+
+ # This is for compatibility reasons, we would rather use
+ # assertRaisesRegexp here.
+ expected_msg = (
+ r"Popped unexpected object. "
+ r"Expected\: \<logbook\.handlers\.StderrHandler object at .*\>, "
+ r"got\: \<logbook\.handlers\.TestHandler object at .*\>"
+ )
+ self.assertRaisesRegexp(expected_msg, another_handler.pop_application)
+
+ handler.pop_application()
+
+ # Now repeat for thread context
+ handler.push_thread()
+ self.assertRaisesRegexp(expected_msg, another_handler.pop_thread)
+ handler.pop_thread()
+
+
def test_exception_catching(self):
logger = logbook.Logger('Test')
handler = logbook.TestHandler()
@@ -372,16 +405,16 @@ def test_mail_handler(self):
else:
ascii_subject = False
subject = u'\xf8nicode'
- handler = make_fake_mail_handler(subject=subject)
+ handler = make_fake_mail_handler(subject=subject, level=logbook.CRITICAL)
fallback = capture_stderr.start()
try:
handler.push_thread()
try:
- self.log.warn('This is not mailed')
+ self.log.error('This is not mailed')
try:
1 / 0
except Exception:
- self.log.exception('This is unfortunate')
+ self.log.critical('This is unfortunate', exc_info=sys.exc_info())
finally:
handler.pop_thread()
@@ -390,7 +423,7 @@ def test_mail_handler(self):
self.assertEqual(sender, handler.from_addr)
if not ascii_subject:
self.assert_('=?utf-8?q?=C3=B8nicode?=' in mail)
- self.assert_(re.search('Message type:\s+ERROR', mail))
+ self.assert_(re.search('Message type:\s+CRITICAL', mail))
self.assert_(re.search('Location:.*%s' % test_file, mail))
self.assert_(re.search('Module:\s+%s' % __name__, mail))
self.assert_(re.search('Function:\s+test_mail_handler', mail))
@@ -580,7 +613,7 @@ def process_record(self, record):
handler.push_thread()
try:
log.warn('From my logger')
- self.log.warn('From another logger')
+ self.log.error('From another logger')
finally:
handler.pop_thread()
self.assert_(handler.has_warning('From my logger'))
@@ -649,7 +682,7 @@ def new_heavy_init(self):
null_handler.bubble = True
captured = capture_stderr.start()
try:
- logbook.warning('Not a blockhole')
+ logbook.error('Not a blockhole')
self.assertNotEqual(captured.getvalue(), '')
finally:
capture_stderr.end()
@@ -686,7 +719,7 @@ def test_nested_setups(self):
logger.exception()
finally:
handlers.pop_thread()
- logger.warn('And here we go straight back to stderr')
+ logger.error('And here we go straight back to stderr')
self.assert_(test_handler.has_warning('This is a warning'))
self.assert_(test_handler.has_error('This is also a mail'))
@@ -946,7 +979,7 @@ def test_error_flag(self):
print_flag = logbook.Flags(errors='print')
print_flag.push_thread()
try:
- self.log.warn('Foo {42}', 'aha')
+ self.log.error('Foo {42}', 'aha')
finally:
print_flag.pop_thread()
finally:
@@ -957,7 +990,7 @@ def test_error_flag(self):
raise_flag = logbook.Flags(errors='raise')
raise_flag.push_thread()
try:
- self.log.warn('Foo {42}', 'aha')
+ self.log.error('Foo {42}', 'aha')
finally:
raise_flag.pop_thread()
except Exception, e:
@@ -1008,13 +1041,16 @@ def inject_extra(record):
class DefaultConfigurationTestCase(LogbookTestCase):
def test_default_handlers(self):
+ # Logbook should log level ERROR and up by default
+ self.assertEqual(logbook.ERROR, logbook.default_handler.level)
+
stream = capture_stderr.start()
try:
- self.log.warn('Aha!')
+ self.log.error('Aha!')
captured = stream.getvalue()
finally:
capture_stderr.end()
- self.assert_('WARNING: testlogger: Aha!' in captured)
+ self.assert_('ERROR: testlogger: Aha!' in captured)
class LoggingCompatTestCase(LogbookTestCase):
@@ -1037,7 +1073,7 @@ def test_basic_compat(self):
logger.critical('This is from the old system')
finally:
redirector.end()
- self.assert_(('WARNING: %s: This is from the old system' % name)
+ self.assert_(('ERROR: %s: This is from the old system' % name)
in captured.getvalue())
finally:
capture_stderr.end()
@@ -1265,7 +1301,6 @@ def test_exception_handler_specific_level(self):
self.assert_('this is irrelevant' in test_handler.records[0].message)
-
class QueuesTestCase(LogbookTestCase):
def _get_zeromq(self):
@@ -1519,20 +1554,3 @@ def test_datehelpers(self):
self.assertEqual(v.hour, 11)
v = parse_iso8601('2000-01-01T12:00:00-01:00')
self.assertEqual(v.hour, 13)
-
-
-def suite():
- loader = unittest.TestLoader()
- suite = LogbookTestSuite()
- suite.addTests(loader.loadTestsFromName(__name__))
- try:
- suite.addTests(loader.loadTestsFromName
- ('logbook.testsuite.test_contextmanager'))
- except SyntaxError:
- # Python 2.4 does not support the 'with' statement
- pass
- return suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')
View
7 testwin32log.py
@@ -1,7 +0,0 @@
-from logbook import NTEventLogHandler, Logger
-
-logger = Logger('MyLogger')
-handler = NTEventLogHandler('My Application')
-
-with handler.applicationbound():
- logger.error('Testing')
View
39 tox.ini
@@ -1,26 +1,42 @@
[tox]
-envlist=py24,py25,py26,py27,py31,docs
+envlist=py25,py26,py27,py31,py32,docs
[testenv]
+
+[testenv:py24]
+deps=
+ SQLAlchemy
+ pysqlite
+ simplejson
+ multiprocessing
+ execnet
+ Jinja2
+ unittest2==0.5.1
+commands=unit2 discover []
+
+[testenv:py25]
deps=
SQLAlchemy
+ simplejson
+ multiprocessing
pyzmq
execnet
Jinja2
-commands=python -c __import__('unittest').main('logbook.testsuite','suite')
-changedir={toxworkdir}
+ unittest2==0.5.1
+commands=unit2 discover []
-[testenv:py24]
+[testenv:py26]
deps=
SQLAlchemy
- pysqlite
simplejson
multiprocessing
pyzmq
execnet
Jinja2
+ unittest2==0.5.1
+commands=unit2 discover []
-[testenv:py25]
+[testenv:py27]
deps=
SQLAlchemy
simplejson
@@ -28,12 +44,23 @@ deps=
pyzmq
execnet
Jinja2
+commands=python -m unittest discover []
[testenv:py31]
deps=
SQLAlchemy
execnet
Jinja2
+ discover
+# Test discovery missed python 3.1 release :(
+commands=python -m discover []
+
+[testenv:py32]
+deps=
+ SQLAlchemy
+ execnet
+ Jinja2
+commands=python -m unittest discover []
[testenv:docs]
deps=
Something went wrong with that request. Please try again.