Skip to content

Commit 7226e5d

Browse files
author
jpellerin
committed
Applied patch from issue 277: adds better filtering of captured logging
1 parent 59d6d0c commit 7226e5d

File tree

2 files changed

+79
-15
lines changed

2 files changed

+79
-15
lines changed

nose/plugins/logcapture.py

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,30 +29,57 @@
2929

3030
log = logging.getLogger(__name__)
3131

32-
32+
class FilterSet(object):
33+
def __init__(self, filter_components):
34+
self.inclusive, self.exclusive = self._partition(filter_components)
35+
36+
@staticmethod
37+
def _partition(components):
38+
inclusive, exclusive = [], []
39+
for component in components:
40+
if component.startswith('-'):
41+
exclusive.append(component[1:])
42+
else:
43+
inclusive.append(component)
44+
return inclusive, exclusive
45+
46+
def allow(self, record):
47+
"""returns whether this record should be printed"""
48+
if not self:
49+
# nothing to filter
50+
return True
51+
return self._allow(record) and not self._deny(record)
52+
53+
@staticmethod
54+
def _any_match(matchers, record):
55+
"""return the bool of whether `record` starts with
56+
any item in `matchers`"""
57+
def record_matches_key(key):
58+
return record == key or record.startswith(key + '.')
59+
return any(map(record_matches_key, matchers))
60+
61+
def _allow(self, record):
62+
if not self.inclusive:
63+
return True
64+
return self._any_match(self.inclusive, record)
65+
66+
def _deny(self, record):
67+
if not self.exclusive:
68+
return False
69+
return self._any_match(self.exclusive, record)
70+
3371
class MyMemoryHandler(BufferingHandler):
3472
def __init__(self, capacity, logformat, logdatefmt, filters):
3573
BufferingHandler.__init__(self, capacity)
3674
fmt = logging.Formatter(logformat, logdatefmt)
3775
self.setFormatter(fmt)
38-
self.filters = filters
76+
self.filterset = FilterSet(filters)
3977
def flush(self):
4078
pass # do nothing
4179
def truncate(self):
4280
self.buffer = []
4381
def filter(self, record):
44-
"""Our custom record filtering logic.
45-
46-
Built-in filtering logic (via logging.Filter) is too limiting.
47-
"""
48-
if not self.filters:
49-
return True
50-
matched = False
51-
rname = record.name # shortcut
52-
for name in self.filters:
53-
if rname == name or rname.startswith(name+'.'):
54-
matched = True
55-
return matched
82+
return self.filterset.allow(record.name)
5683
def __getstate__(self):
5784
state = self.__dict__.copy()
5885
del state['lock']
@@ -109,7 +136,9 @@ def options(self, parser, env):
109136
" verbose,\nuse this option to filter out needless output.\n"
110137
"Example: filter=foo will capture statements issued ONLY to\n"
111138
" foo or foo.what.ever.sub but not foobar or other logger.\n"
112-
"Specify multiple loggers with comma: filter=foo,bar,baz."
139+
"Specify multiple loggers with comma: filter=foo,bar,baz.\n"
140+
"If any logger name is prefixed with a minus, eg filter=-foo,\n"
141+
"it will be excluded rather than included."
113142
" [NOSE_LOGFILTER]\n")
114143
parser.add_option(
115144
"--logging-clear-handlers", action="store_true",

unit_tests/test_logcapture_plugin.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,41 @@ def test_logging_filter(self):
131131
assert records[0].startswith('foo:'), records[0]
132132
assert records[1].startswith('foo.x:'), records[1]
133133
assert records[2].startswith('bar.quux:'), records[2]
134+
135+
def test_logging_filter_exclude(self):
136+
env = {'NOSE_LOGFILTER': '-foo,-bar'}
137+
c = LogCapture()
138+
parser = OptionParser()
139+
c.addOptions(parser, env)
140+
options, args = parser.parse_args(['foo'])
141+
print options, args
142+
c.configure(options, Config())
143+
c.start()
144+
for name in ['foobar.something', 'foo', 'foo.x', 'abara', 'bar.quux']:
145+
log = logging.getLogger(name)
146+
log.info("Hello %s" % name)
147+
c.end()
148+
records = c.formatLogRecords()
149+
eq_(2, len(records))
150+
assert records[0].startswith('foobar.something:'), records[0]
151+
assert records[1].startswith('abara:'), records[1]
152+
153+
def test_logging_filter_exclude_and_include(self):
154+
env = {'NOSE_LOGFILTER': 'foo,-foo.bar'}
155+
c = LogCapture()
156+
parser = OptionParser()
157+
c.addOptions(parser, env)
158+
options, args = parser.parse_args(['foo'])
159+
print options, args
160+
c.configure(options, Config())
161+
c.start()
162+
for name in ['foo.yes', 'foo.bar', 'foo.bar.no', 'blah']:
163+
log = logging.getLogger(name)
164+
log.info("Hello %s" % name)
165+
c.end()
166+
records = c.formatLogRecords()
167+
eq_(1, len(records))
168+
assert records[0].startswith('foo.yes:'), records[0]
134169

135170
def test_unicode_messages_handled(self):
136171
msg = u'Ivan Krsti\u0107'

0 commit comments

Comments
 (0)