diff --git a/HACKING.rst b/HACKING.rst index cbdee68822f5..1fa7dab6de4f 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -33,6 +33,7 @@ Nova Specific Commandments - [N319] Validate that debug level logs are not translated. - [N320] Setting CONF.* attributes directly in tests is forbidden. Use self.flags(option=value) instead. +- [N321] Validate that LOG messages, except debug ones, have translations Creating Unit Tests ------------------- diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 1c8de65e3044..1dad7bf23090 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -123,7 +123,7 @@ class RequestLogging(wsgi.Middleware): request.user_agent, request.content_type, response.content_type, - context=ctxt) + context=ctxt) # noqa class Lockout(wsgi.Middleware): diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index a047b06a630b..cac785241d03 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -292,7 +292,7 @@ class APIRouterV3(base_wsgi.Router): return False if not CONF.osapi_v3.enabled: - LOG.info("V3 API has been disabled by configuration") + LOG.info(_("V3 API has been disabled by configuration")) return self.init_only = init_only diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 58f620f5fdfd..e004c1ba5ce5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -2024,7 +2024,8 @@ class ComputeManager(manager.Manager): except Exception: # Because this allocation is async any failures are likely to occur # when the driver accesses network_info during spawn(). - LOG.exception('Failed to allocate network(s)', instance=instance) + LOG.exception(_('Failed to allocate network(s)'), + instance=instance) msg = _('Failed to allocate the network(s), not rescheduling.') raise exception.BuildAbortException(instance_uuid=instance.uuid, reason=msg) diff --git a/nova/console/websocketproxy.py b/nova/console/websocketproxy.py index e5bfb9ef1630..c51078dcff77 100644 --- a/nova/console/websocketproxy.py +++ b/nova/console/websocketproxy.py @@ -52,7 +52,7 @@ class NovaWebSocketProxy(websockify.WebSocketProxy): connect_info = rpcapi.check_token(ctxt, token=token) if not connect_info: - LOG.audit("Invalid Token: %s", token) + LOG.audit(_("Invalid Token: %s"), token) raise Exception(_("Invalid Token")) host = connect_info['host'] @@ -60,7 +60,8 @@ class NovaWebSocketProxy(websockify.WebSocketProxy): # Connect to the target self.msg("connecting to: %s:%s" % (host, port)) - LOG.audit("connecting to: %s:%s" % (host, port)) + LOG.audit(_("connecting to: %(host)s:%(port)s"), + {'host': host, 'port': port}) tsock = self.socket(host, port, connect=True) # Handshake as necessary @@ -71,7 +72,7 @@ class NovaWebSocketProxy(websockify.WebSocketProxy): data = tsock.recv(4096, socket.MSG_PEEK) if data.find("\r\n\r\n") != -1: if not data.split("\r\n")[0].find("200"): - LOG.audit("Invalid Connection Info %s", token) + LOG.audit(_("Invalid Connection Info %s"), token) raise Exception(_("Invalid Connection Info")) tsock.recv(len(data)) break @@ -87,5 +88,6 @@ class NovaWebSocketProxy(websockify.WebSocketProxy): tsock.shutdown(socket.SHUT_RDWR) tsock.close() self.vmsg("%s:%s: Target closed" % (host, port)) - LOG.audit("%s:%s: Target closed" % (host, port)) + LOG.audit(_("%(host)s:%(port)s: Target closed"), + {'host': host, 'port': port}) raise diff --git a/nova/exception.py b/nova/exception.py index a0c5cae87968..efa5545afb69 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -123,7 +123,7 @@ class NovaException(Exception): # log the issue and the kwargs LOG.exception(_('Exception in string format operation')) for name, value in kwargs.iteritems(): - LOG.error("%s: %s" % (name, value)) + LOG.error("%s: %s" % (name, value)) # noqa if CONF.fatal_exception_format_errors: raise exc_info[0], exc_info[1], exc_info[2] diff --git a/nova/hacking/checks.py b/nova/hacking/checks.py index c35b18a76b4e..f062155570a6 100644 --- a/nova/hacking/checks.py +++ b/nova/hacking/checks.py @@ -15,6 +15,8 @@ import re +import pep8 + """ Guidelines for writing new hacking checks @@ -50,6 +52,8 @@ asse_equal_end_with_none_re = re.compile( asse_equal_start_with_none_re = re.compile( r"(.)*assertEqual\(None, (\w|\.|\'|\"|\[|\])+\)") conf_attribute_set_re = re.compile(r"CONF\.[a-z0-9_.]+\s*=\s*\w") +log_translation = re.compile( + r"(.)*LOG\.(audit|error|info|warn|warning|critical|exception)\(\s*('|\")") def import_no_db_in_virt(logical_line, filename): @@ -260,6 +264,19 @@ def no_setting_conf_directly_in_tests(logical_line, filename): "forbidden. Use self.flags(option=value) instead") +def validate_log_translations(logical_line, physical_line, filename): + # Translations are not required in the test directory + # and the Xen utilities + if ("nova/tests" in filename or + "plugins/xenserver/xenapi/etc/xapi.d" in filename): + return + if pep8.noqa(physical_line): + return + msg = "N321: Log messages require translations!" + if log_translation.match(logical_line): + yield (0, msg) + + def factory(register): register(import_no_db_in_virt) register(no_db_session_in_public_api) @@ -274,3 +291,4 @@ def factory(register): register(assert_equal_none) register(no_translate_debug_logs) register(no_setting_conf_directly_in_tests) + register(validate_log_translations) diff --git a/nova/tests/test_hacking.py b/nova/tests/test_hacking.py index d4b689d8ab6a..843fb164237a 100644 --- a/nova/tests/test_hacking.py +++ b/nova/tests/test_hacking.py @@ -144,3 +144,34 @@ class HackingTestCase(test.NoDBTestCase): # Shouldn't fail since not in nova/tests/ self.assertEqual(len(list(checks.no_setting_conf_directly_in_tests( "CONF.option = 1", "nova/compute/foo.py"))), 0) + + def test_log_translations(self): + logs = ['audit', 'error', 'info', 'warn', 'warning', 'critical', + 'exception'] + levels = ['_LI', '_LW', '_LE', '_LC'] + debug = "LOG.debug('OK')" + self.assertEqual(0, + len(list( + checks.validate_log_translations(debug, debug, 'f')))) + for log in logs: + bad = 'LOG.%s("Bad")' % log + self.assertEqual(1, + len(list( + checks.validate_log_translations(bad, bad, 'f')))) + ok = "LOG.%s(_('OK'))" % log + self.assertEqual(0, + len(list( + checks.validate_log_translations(ok, ok, 'f')))) + ok = "LOG.%s('OK') # noqa" % log + self.assertEqual(0, + len(list( + checks.validate_log_translations(ok, ok, 'f')))) + ok = "LOG.%s(variable)" % log + self.assertEqual(0, + len(list( + checks.validate_log_translations(ok, ok, 'f')))) + for level in levels: + ok = "LOG.%s(%s('OK'))" % (log, level) + self.assertEqual(0, + len(list( + checks.validate_log_translations(ok, ok, 'f')))) diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 263866c38d82..6965d79ac042 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -817,8 +817,8 @@ class LibvirtGenericVIFDriver(LibvirtBaseVIFDriver): class _LibvirtDeprecatedDriver(LibvirtGenericVIFDriver): def __init__(self, *args, **kwargs): - LOG.warn('VIF driver \"%s\" is marked as deprecated and will be ' - 'removed in the Juno release.', + LOG.warn(_('VIF driver \"%s\" is marked as deprecated and will be ' + 'removed in the Juno release.'), self.__class__.__name__) super(_LibvirtDeprecatedDriver, self).__init__(*args, **kwargs)