use environment variable to control file location probing

If OSLO_CONFIG_SHOW_CODE_LOCATIONS is set to any non-empty string it
will turn on the ability to see which file has the definition of an
Opt or where set_default() was invoked.

Change-Id: Ie705014dcf331e3c6b3367d2fefbfb9acc091799
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
Doug Hellmann 2018-03-28 17:26:26 -04:00
parent b6cfa29055
commit 7db6a2604d
3 changed files with 48 additions and 26 deletions

View File

@ -6,3 +6,20 @@ Helper Functions
.. autofunction:: find_config_files .. autofunction:: find_config_files
.. autofunction:: set_defaults .. autofunction:: set_defaults
Showing detailed locations for configuration settings
-----------------------------------------------------
``oslo.config`` can track the location in application and library code
where an option is defined, defaults are set, or values are
overridden. This feature is disabled by default because it is
expensive and incurs a significant performance penalty, but it can be
useful for developers tracing down issues with configuration option
definitions.
To turn on detailed location tracking, set the environment variable
``OSLO_CONFIG_SHOW_CODE_LOCATIONS`` to any non-empty value (for
example, ``"1"`` or ``"yes, please"``) before starting the
application, test suite, or script. Then use
:func:`ConfigOpts.get_location` to access the location data for the
option.

View File

@ -489,7 +489,7 @@ import copy
import errno import errno
import functools import functools
import glob import glob
# import inspect import inspect
import itertools import itertools
import logging import logging
import os import os
@ -804,6 +804,10 @@ def _is_opt_registered(opts, opt):
return False return False
_show_caller_details = bool(os.environ.get(
'OSLO_CONFIG_SHOW_CODE_LOCATIONS'))
def _get_caller_detail(n=2): def _get_caller_detail(n=2):
"""Return a string describing where this is being called from. """Return a string describing where this is being called from.
@ -811,23 +815,22 @@ def _get_caller_detail(n=2):
:type n: int :type n: int
:returns: str :returns: str
""" """
if not _show_caller_details:
return None return None
# FIXME(dhellmann): We need to look at the performance issues with s = inspect.stack()[:n + 1]
# doing this for every Opt instance. try:
# s = inspect.stack()[:n + 1] frame = s[n]
# try: try:
# frame = s[n] return frame[1]
# try: # WARNING(dhellmann): Using frame.lineno to include the
# return frame[1] # line number in the return value causes some sort of
# # WARNING(dhellmann): Using frame.lineno to include the # memory or stack corruption that manifests in values not
# # line number in the return value causes some sort of # being cleaned up in the cfgfilter tests.
# # memory or stack corruption that manifests in values not # return '%s:%s' % (frame[1], frame[2])
# # being cleaned up in the cfgfilter tests. finally:
# # return '%s:%s' % (frame[1], frame[2]) del frame
# finally: finally:
# del frame del s
# finally:
# del s
def set_defaults(opts, **kwargs): def set_defaults(opts, **kwargs):

View File

@ -51,6 +51,12 @@ class GetLocationTestCase(base.BaseTestCase):
def setUp(self): def setUp(self):
super(GetLocationTestCase, self).setUp() super(GetLocationTestCase, self).setUp()
def _clear():
cfg._show_caller_details = False
self.addCleanup(_clear)
cfg._show_caller_details = True
self.conf = TestConfigOpts() self.conf = TestConfigOpts()
self.normal_opt = cfg.StrOpt( self.normal_opt = cfg.StrOpt(
'normal_opt', 'normal_opt',
@ -70,8 +76,7 @@ class GetLocationTestCase(base.BaseTestCase):
cfg.Locations.opt_default, cfg.Locations.opt_default,
loc.location, loc.location,
) )
self.assertIsNone(loc.detail) self.assertIn('test_get_location.py', loc.detail)
# self.assertIn('test_get_location.py', loc.detail)
def test_set_default_on_config_opt(self): def test_set_default_on_config_opt(self):
self.conf.set_default('normal_opt', self.id()) self.conf.set_default('normal_opt', self.id())
@ -81,8 +86,7 @@ class GetLocationTestCase(base.BaseTestCase):
cfg.Locations.set_default, cfg.Locations.set_default,
loc.location, loc.location,
) )
self.assertIsNone(loc.detail) self.assertIn('test_get_location.py', loc.detail)
# self.assertIn('test_get_location.py', loc.detail)
def test_set_defaults_func(self): def test_set_defaults_func(self):
cfg.set_defaults([self.normal_opt], normal_opt=self.id()) cfg.set_defaults([self.normal_opt], normal_opt=self.id())
@ -92,8 +96,7 @@ class GetLocationTestCase(base.BaseTestCase):
cfg.Locations.set_default, cfg.Locations.set_default,
loc.location, loc.location,
) )
self.assertIsNone(loc.detail) self.assertIn('test_get_location.py', loc.detail)
# self.assertIn('test_get_location.py', loc.detail)
def test_set_override(self): def test_set_override(self):
self.conf.set_override('normal_opt', self.id()) self.conf.set_override('normal_opt', self.id())
@ -103,8 +106,7 @@ class GetLocationTestCase(base.BaseTestCase):
cfg.Locations.set_override, cfg.Locations.set_override,
loc.location, loc.location,
) )
self.assertIsNone(loc.detail) self.assertIn('test_get_location.py', loc.detail)
# self.assertIn('test_get_location.py', loc.detail)
def test_user_cli(self): def test_user_cli(self):
filename = self._write_opt_to_tmp_file( filename = self._write_opt_to_tmp_file(