Browse Source

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
tags/9.0.0.0b2
Takashi NATSUME 3 years ago
parent
commit
5cef3f726e

+ 1
- 0
HACKING.rst View File

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

+ 1
- 1
neutron/agent/l3/agent.py View File

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

+ 1
- 1
neutron/agent/linux/pd.py View File

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

+ 2
- 2
neutron/api/v2/resource_helper.py View File

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

+ 9
- 9
neutron/db/dns_db.py View File

@@ -287,11 +287,11 @@ class DNSDbMixin(object):
287 287
             LOG.exception(_LE("Error deleting Floating IP data from external "
288 288
                               "DNS service. Name: '%(name)s'. Domain: "
289 289
                               "'%(domain)s'. IP addresses '%(ips)s'. DNS "
290
-                              "service driver message '%(message)s'")
291
-                          % {"name": dns_name,
292
-                             "domain": dns_domain,
293
-                             "message": e.msg,
294
-                             "ips": ', '.join(records)})
290
+                              "service driver message '%(message)s'"),
291
+                          {"name": dns_name,
292
+                           "domain": dns_domain,
293
+                           "message": e.msg,
294
+                           "ips": ', '.join(records)})
295 295
 
296 296
     def _get_requested_state_for_external_dns_service_create(self, context,
297 297
                                                              floatingip_data,
@@ -318,7 +318,7 @@ class DNSDbMixin(object):
318 318
             LOG.exception(_LE("Error publishing floating IP data in external "
319 319
                               "DNS service. Name: '%(name)s'. Domain: "
320 320
                               "'%(domain)s'. DNS service driver message "
321
-                              "'%(message)s'")
322
-                          % {"name": dns_name,
323
-                             "domain": dns_domain,
324
-                             "message": e.msg})
321
+                              "'%(message)s'"),
322
+                          {"name": dns_name,
323
+                           "domain": dns_domain,
324
+                           "message": e.msg})

+ 1
- 1
neutron/db/quota/driver.py View File

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

+ 26
- 0
neutron/hacking/checks.py View File

@@ -64,6 +64,9 @@ def _regex_for_level(level, hint):
64 64
     }
65 65
 
66 66
 
67
+log_string_interpolation = re.compile(r".*LOG\.(?:error|warn|warning|info"
68
+                                      r"|critical|exception|debug)"
69
+                                      r"\([^,]*%[^,]*[,)]")
67 70
 log_translation_hint = re.compile(
68 71
     '|'.join('(?:%s)' % _regex_for_level(level, hint)
69 72
              for level, hint in six.iteritems(_all_log_levels)))
@@ -356,6 +359,28 @@ def check_unittest_imports(logical_line):
356 359
         yield (0, msg)
357 360
 
358 361
 
362
+@flake8ext
363
+def check_delayed_string_interpolation(logical_line, filename, noqa):
364
+    """N342 String interpolation should be delayed at logging calls.
365
+
366
+    N342: LOG.debug('Example: %s' % 'bad')
367
+    Okay: LOG.debug('Example: %s', 'good')
368
+    """
369
+    msg = ("N342 String interpolation should be delayed to be "
370
+           "handled by the logging code, rather than being done "
371
+           "at the point of the logging call. "
372
+           "Use ',' instead of '%'.")
373
+
374
+    if noqa:
375
+        return
376
+
377
+    if 'neutron/tests/' in filename:
378
+        return
379
+
380
+    if log_string_interpolation.match(logical_line):
381
+        yield(0, msg)
382
+
383
+
359 384
 def factory(register):
360 385
     register(validate_log_translations)
361 386
     register(use_jsonutils)
@@ -374,3 +399,4 @@ def factory(register):
374 399
     register(check_oslo_i18n_wrapper)
375 400
     register(check_builtins_gettext)
376 401
     register(check_unittest_imports)
402
+    register(check_delayed_string_interpolation)

+ 2
- 2
neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py View File

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

+ 9
- 9
neutron/plugins/ml2/extensions/dns_integration.py View File

@@ -248,10 +248,10 @@ def _send_data_to_external_dns_service(context, dns_driver, dns_domain,
248 248
     except (dns.DNSDomainNotFound, dns.DuplicateRecordSet) as e:
249 249
         LOG.exception(_LE("Error publishing port data in external DNS "
250 250
                           "service. Name: '%(name)s'. Domain: '%(domain)s'. "
251
-                          "DNS service driver message '%(message)s'")
252
-                      % {"name": dns_name,
253
-                         "domain": dns_domain,
254
-                         "message": e.msg})
251
+                          "DNS service driver message '%(message)s'"),
252
+                      {"name": dns_name,
253
+                       "domain": dns_domain,
254
+                       "message": e.msg})
255 255
 
256 256
 
257 257
 def _remove_data_from_external_dns_service(context, dns_driver, dns_domain,
@@ -262,11 +262,11 @@ def _remove_data_from_external_dns_service(context, dns_driver, dns_domain,
262 262
         LOG.exception(_LE("Error deleting port data from external DNS "
263 263
                           "service. Name: '%(name)s'. Domain: '%(domain)s'. "
264 264
                           "IP addresses '%(ips)s'. DNS service driver message "
265
-                          "'%(message)s'")
266
-                      % {"name": dns_name,
267
-                         "domain": dns_domain,
268
-                         "message": e.msg,
269
-                         "ips": ', '.join(records)})
265
+                          "'%(message)s'"),
266
+                      {"name": dns_name,
267
+                       "domain": dns_domain,
268
+                       "message": e.msg,
269
+                       "ips": ', '.join(records)})
270 270
 
271 271
 
272 272
 def _update_port_in_external_dns_service(resource, event, trigger, **kwargs):

+ 35
- 0
neutron/tests/unit/hacking/test_checks.py View File

@@ -24,6 +24,9 @@ from neutron.hacking import checks
24 24
 from neutron.tests import base
25 25
 
26 26
 
27
+CREATE_DUMMY_MATCH_OBJECT = re.compile('a')
28
+
29
+
27 30
 class HackingTestCase(base.BaseTestCase):
28 31
 
29 32
     def assertLinePasses(self, func, line):
@@ -298,6 +301,38 @@ class HackingTestCase(base.BaseTestCase):
298 301
         self.assertLineFails(f, 'from unittest.TestSuite')
299 302
         self.assertLineFails(f, 'import unittest')
300 303
 
304
+    def test_check_delayed_string_interpolation(self):
305
+        dummy_noqa = CREATE_DUMMY_MATCH_OBJECT.search('a')
306
+
307
+        # In 'logical_line', Contents of strings replaced with
308
+        # "xxx" of same length.
309
+        fail_code1 = 'LOG.error(_LE("xxxxxxxxxxxxxxx") % value)'
310
+        fail_code2 = "LOG.warning(msg % 'xxxxx')"
311
+
312
+        self.assertEqual(
313
+            1, len(list(checks.check_delayed_string_interpolation(fail_code1,
314
+                                        "neutron/common/rpc.py", None))))
315
+        self.assertEqual(
316
+            1, len(list(checks.check_delayed_string_interpolation(fail_code2,
317
+                                        "neutron/common/rpc.py", None))))
318
+
319
+        pass_code1 = 'LOG.error(_LE("xxxxxxxxxxxxxxxxxx"), value)'
320
+        pass_code2 = "LOG.warning(msg, 'xxxxx')"
321
+        self.assertEqual(
322
+            0, len(list(checks.check_delayed_string_interpolation(pass_code1,
323
+                                        "neutron/common/rpc.py", None))))
324
+        self.assertEqual(
325
+            0, len(list(checks.check_delayed_string_interpolation(pass_code2,
326
+                                        "neutron/common/rpc.py", None))))
327
+        # check a file in neutron/tests
328
+        self.assertEqual(
329
+            0, len(list(checks.check_delayed_string_interpolation(fail_code1,
330
+                                        "neutron/tests/test_assert.py",
331
+                                        None))))
332
+        # check code including 'noqa'
333
+        self.assertEqual(
334
+            0, len(list(checks.check_delayed_string_interpolation(fail_code1,
335
+                                        "neutron/common/rpc.py", dummy_noqa))))
301 336
 
302 337
 # The following is borrowed from hacking/tests/test_doctest.py.
303 338
 # Tests defined in docstring is easier to understand

Loading…
Cancel
Save