Add a hacking rule for string interpolation at logging

String interpolation should be delayed to be handled
by the logging code, rather than being done
at the point of the logging call.
So add a hacking rule for it.

See the oslo i18n guideline.

* http://docs.openstack.org/developer/oslo.i18n/guidelines.html

Change-Id: I91e8d59d508c594256d5f74514e62f8f928d1df5
Closes-Bug: #1596829
This commit is contained in:
Takashi NATSUME
2016-07-07 22:19:33 +09:00
parent 6087f1d121
commit 5cef3f726e
10 changed files with 87 additions and 25 deletions

View File

@@ -28,6 +28,7 @@ Neutron Specific Commandments
- [N334] Use unittest2 uniformly across Neutron. - [N334] Use unittest2 uniformly across Neutron.
- [N340] Check usage of <module>.i18n (and neutron.i18n) - [N340] Check usage of <module>.i18n (and neutron.i18n)
- [N341] Check usage of _ from python builtins - [N341] Check usage of _ from python builtins
- [N342] String interpolation should be delayed at logging calls.
Creating Unit Tests Creating Unit Tests
------------------- -------------------

View File

@@ -219,7 +219,7 @@ class L3NATAgent(ha.AgentMixin,
'to retrieve service plugins enabled. ' 'to retrieve service plugins enabled. '
'Check connectivity to neutron server. ' 'Check connectivity to neutron server. '
'Retrying... ' 'Retrying... '
'Detailed message: %(msg)s.') % {'msg': e}) 'Detailed message: %(msg)s.'), {'msg': e})
continue continue
break break

View File

@@ -230,7 +230,7 @@ class PrefixDelegation(object):
def _lla_available(self, gw_ifname, ns_name, lla_with_mask): def _lla_available(self, gw_ifname, ns_name, lla_with_mask):
llas = self._get_llas(gw_ifname, ns_name) llas = self._get_llas(gw_ifname, ns_name)
if self._is_lla_active(lla_with_mask, llas): if self._is_lla_active(lla_with_mask, llas):
LOG.debug("LLA %s is active now" % lla_with_mask) LOG.debug("LLA %s is active now", lla_with_mask)
self.pd_update_cb() self.pd_update_cb()
return True return True

View File

@@ -78,8 +78,8 @@ def build_resource_info(plural_mappings, resource_map, which_service,
else: else:
plugin = manager.NeutronManager.get_plugin() plugin = manager.NeutronManager.get_plugin()
path_prefix = getattr(plugin, "path_prefix", "") path_prefix = getattr(plugin, "path_prefix", "")
LOG.debug('Service %(service)s assigned prefix: %(prefix)s' LOG.debug('Service %(service)s assigned prefix: %(prefix)s',
% {'service': which_service, 'prefix': path_prefix}) {'service': which_service, 'prefix': path_prefix})
for collection_name in resource_map: for collection_name in resource_map:
resource_name = plural_mappings[collection_name] resource_name = plural_mappings[collection_name]
params = resource_map.get(collection_name, {}) params = resource_map.get(collection_name, {})

View File

@@ -287,8 +287,8 @@ class DNSDbMixin(object):
LOG.exception(_LE("Error deleting Floating IP data from external " LOG.exception(_LE("Error deleting Floating IP data from external "
"DNS service. Name: '%(name)s'. Domain: " "DNS service. Name: '%(name)s'. Domain: "
"'%(domain)s'. IP addresses '%(ips)s'. DNS " "'%(domain)s'. IP addresses '%(ips)s'. DNS "
"service driver message '%(message)s'") "service driver message '%(message)s'"),
% {"name": dns_name, {"name": dns_name,
"domain": dns_domain, "domain": dns_domain,
"message": e.msg, "message": e.msg,
"ips": ', '.join(records)}) "ips": ', '.join(records)})
@@ -318,7 +318,7 @@ class DNSDbMixin(object):
LOG.exception(_LE("Error publishing floating IP data in external " LOG.exception(_LE("Error publishing floating IP data in external "
"DNS service. Name: '%(name)s'. Domain: " "DNS service. Name: '%(name)s'. Domain: "
"'%(domain)s'. DNS service driver message " "'%(domain)s'. DNS service driver message "
"'%(message)s'") "'%(message)s'"),
% {"name": dns_name, {"name": dns_name,
"domain": dns_domain, "domain": dns_domain,
"message": e.msg}) "message": e.msg})

View File

@@ -151,7 +151,7 @@ class DbQuotaDriver(object):
return dict((k, v) for k, v in quotas.items()) return dict((k, v) for k, v in quotas.items())
def _handle_expired_reservations(self, context, tenant_id): def _handle_expired_reservations(self, context, tenant_id):
LOG.debug("Deleting expired reservations for tenant:%s" % tenant_id) LOG.debug("Deleting expired reservations for tenant:%s", tenant_id)
# Delete expired reservations (we don't want them to accrue # Delete expired reservations (we don't want them to accrue
# in the database) # in the database)
quota_api.remove_expired_reservations( quota_api.remove_expired_reservations(

View File

@@ -64,6 +64,9 @@ def _regex_for_level(level, hint):
} }
log_string_interpolation = re.compile(r".*LOG\.(?:error|warn|warning|info"
r"|critical|exception|debug)"
r"\([^,]*%[^,]*[,)]")
log_translation_hint = re.compile( log_translation_hint = re.compile(
'|'.join('(?:%s)' % _regex_for_level(level, hint) '|'.join('(?:%s)' % _regex_for_level(level, hint)
for level, hint in six.iteritems(_all_log_levels))) for level, hint in six.iteritems(_all_log_levels)))
@@ -356,6 +359,28 @@ def check_unittest_imports(logical_line):
yield (0, msg) yield (0, msg)
@flake8ext
def check_delayed_string_interpolation(logical_line, filename, noqa):
"""N342 String interpolation should be delayed at logging calls.
N342: LOG.debug('Example: %s' % 'bad')
Okay: LOG.debug('Example: %s', 'good')
"""
msg = ("N342 String interpolation should be delayed to be "
"handled by the logging code, rather than being done "
"at the point of the logging call. "
"Use ',' instead of '%'.")
if noqa:
return
if 'neutron/tests/' in filename:
return
if log_string_interpolation.match(logical_line):
yield(0, msg)
def factory(register): def factory(register):
register(validate_log_translations) register(validate_log_translations)
register(use_jsonutils) register(use_jsonutils)
@@ -374,3 +399,4 @@ def factory(register):
register(check_oslo_i18n_wrapper) register(check_oslo_i18n_wrapper)
register(check_builtins_gettext) register(check_builtins_gettext)
register(check_unittest_imports) register(check_unittest_imports)
register(check_delayed_string_interpolation)

View File

@@ -570,8 +570,8 @@ class OVSDVRNeutronAgent(object):
if local_vlan_map.network_type not in (constants.TUNNEL_NETWORK_TYPES if local_vlan_map.network_type not in (constants.TUNNEL_NETWORK_TYPES
+ [p_const.TYPE_VLAN]): + [p_const.TYPE_VLAN]):
LOG.debug("DVR: Port %s is with network_type %s not supported" LOG.debug("DVR: Port %s is with network_type %s not supported"
" for dvr plumbing" % (port.vif_id, " for dvr plumbing", port.vif_id,
local_vlan_map.network_type)) local_vlan_map.network_type)
return return
if (port.vif_id in self.local_ports and if (port.vif_id in self.local_ports and

View File

@@ -248,8 +248,8 @@ def _send_data_to_external_dns_service(context, dns_driver, dns_domain,
except (dns.DNSDomainNotFound, dns.DuplicateRecordSet) as e: except (dns.DNSDomainNotFound, dns.DuplicateRecordSet) as e:
LOG.exception(_LE("Error publishing port data in external DNS " LOG.exception(_LE("Error publishing port data in external DNS "
"service. Name: '%(name)s'. Domain: '%(domain)s'. " "service. Name: '%(name)s'. Domain: '%(domain)s'. "
"DNS service driver message '%(message)s'") "DNS service driver message '%(message)s'"),
% {"name": dns_name, {"name": dns_name,
"domain": dns_domain, "domain": dns_domain,
"message": e.msg}) "message": e.msg})
@@ -262,8 +262,8 @@ def _remove_data_from_external_dns_service(context, dns_driver, dns_domain,
LOG.exception(_LE("Error deleting port data from external DNS " LOG.exception(_LE("Error deleting port data from external DNS "
"service. Name: '%(name)s'. Domain: '%(domain)s'. " "service. Name: '%(name)s'. Domain: '%(domain)s'. "
"IP addresses '%(ips)s'. DNS service driver message " "IP addresses '%(ips)s'. DNS service driver message "
"'%(message)s'") "'%(message)s'"),
% {"name": dns_name, {"name": dns_name,
"domain": dns_domain, "domain": dns_domain,
"message": e.msg, "message": e.msg,
"ips": ', '.join(records)}) "ips": ', '.join(records)})

View File

@@ -24,6 +24,9 @@ from neutron.hacking import checks
from neutron.tests import base from neutron.tests import base
CREATE_DUMMY_MATCH_OBJECT = re.compile('a')
class HackingTestCase(base.BaseTestCase): class HackingTestCase(base.BaseTestCase):
def assertLinePasses(self, func, line): def assertLinePasses(self, func, line):
@@ -298,6 +301,38 @@ class HackingTestCase(base.BaseTestCase):
self.assertLineFails(f, 'from unittest.TestSuite') self.assertLineFails(f, 'from unittest.TestSuite')
self.assertLineFails(f, 'import unittest') self.assertLineFails(f, 'import unittest')
def test_check_delayed_string_interpolation(self):
dummy_noqa = CREATE_DUMMY_MATCH_OBJECT.search('a')
# In 'logical_line', Contents of strings replaced with
# "xxx" of same length.
fail_code1 = 'LOG.error(_LE("xxxxxxxxxxxxxxx") % value)'
fail_code2 = "LOG.warning(msg % 'xxxxx')"
self.assertEqual(
1, len(list(checks.check_delayed_string_interpolation(fail_code1,
"neutron/common/rpc.py", None))))
self.assertEqual(
1, len(list(checks.check_delayed_string_interpolation(fail_code2,
"neutron/common/rpc.py", None))))
pass_code1 = 'LOG.error(_LE("xxxxxxxxxxxxxxxxxx"), value)'
pass_code2 = "LOG.warning(msg, 'xxxxx')"
self.assertEqual(
0, len(list(checks.check_delayed_string_interpolation(pass_code1,
"neutron/common/rpc.py", None))))
self.assertEqual(
0, len(list(checks.check_delayed_string_interpolation(pass_code2,
"neutron/common/rpc.py", None))))
# check a file in neutron/tests
self.assertEqual(
0, len(list(checks.check_delayed_string_interpolation(fail_code1,
"neutron/tests/test_assert.py",
None))))
# check code including 'noqa'
self.assertEqual(
0, len(list(checks.check_delayed_string_interpolation(fail_code1,
"neutron/common/rpc.py", dummy_noqa))))
# The following is borrowed from hacking/tests/test_doctest.py. # The following is borrowed from hacking/tests/test_doctest.py.
# Tests defined in docstring is easier to understand # Tests defined in docstring is easier to understand