Deprecate LogAdapter.set_statsd_prefix

Previously, the set_statsd_prefix method was used to mutate a logger's
StatsdClient tail prefix after a logger was instantiated. This pattern
had led to unexpected mutations (see Related-Change). The tail_prefix
can now be passed as an argument to get_logger(), and is then
forwarded to the StatsdClient constructor, for a more explicit
assignment pattern.

The set_statsd_prefix method is left in place for backwards
compatibility. A DeprecationWarning will be raised if it is used
to mutate the StatsdClient tail prefix.

Change-Id: I7692860e3b741e1bc10626e26bb7b27399c325ab
Related-Change: I0522b1953722ca96021a0002cf93432b973ce626
This commit is contained in:
Alistair Coles 2022-02-04 10:58:46 +00:00
parent 2a60c91f6f
commit eda7d5fe3c
9 changed files with 177 additions and 36 deletions

View File

@ -145,14 +145,6 @@ require accuracy (``sample_rate=1``) while others may not.
Then the LogAdapter object returned by ``get_logger()``, usually stored Then the LogAdapter object returned by ``get_logger()``, usually stored
in ``self.logger``, has these new methods: in ``self.logger``, has these new methods:
- ``set_statsd_prefix(self, prefix)`` Sets the client library stat
prefix value which gets prefixed to every meter. The default prefix
is the ``name`` of the logger such as ``object-server``,
``container-auditor``, and so on. This is currently used to turn
``proxy-server`` into one of ``proxy-server.Account``,
``proxy-server.Container``, or ``proxy-server.Object`` as soon as the
Controller object is determined and instantiated for the request.
- ``update_stats(self, metric, amount, sample_rate=1)`` Increments - ``update_stats(self, metric, amount, sample_rate=1)`` Increments
the supplied meter by the given amount. This is used when you need the supplied meter by the given amount. This is used when you need
to add or subtract more that one from a counter, like incrementing to add or subtract more that one from a counter, like incrementing

View File

@ -133,8 +133,8 @@ class ProxyLoggingMiddleware(object):
access_log_conf[key] = value access_log_conf[key] = value
self.access_logger = logger or get_logger( self.access_logger = logger or get_logger(
access_log_conf, access_log_conf,
log_route=conf.get('access_log_route', 'proxy-access')) log_route=conf.get('access_log_route', 'proxy-access'),
self.access_logger.set_statsd_prefix('proxy-server') statsd_tail_prefix='proxy-server')
self.reveal_sensitive_prefix = int( self.reveal_sensitive_prefix = int(
conf.get('reveal_sensitive_prefix', 16)) conf.get('reveal_sensitive_prefix', 16))
self.check_log_msg_template_validity() self.check_log_msg_template_validity()

View File

@ -209,13 +209,14 @@ class TempAuth(object):
def __init__(self, app, conf): def __init__(self, app, conf):
self.app = app self.app = app
self.conf = conf self.conf = conf
self.logger = get_logger(conf, log_route='tempauth')
self.log_headers = config_true_value(conf.get('log_headers', 'f'))
self.reseller_prefixes, self.account_rules = \ self.reseller_prefixes, self.account_rules = \
config_read_reseller_options(conf, dict(require_group='')) config_read_reseller_options(conf, dict(require_group=''))
self.reseller_prefix = self.reseller_prefixes[0] self.reseller_prefix = self.reseller_prefixes[0]
self.logger.set_statsd_prefix('tempauth.%s' % ( statsd_tail_prefix = 'tempauth.%s' % (
self.reseller_prefix if self.reseller_prefix else 'NONE',)) self.reseller_prefix if self.reseller_prefix else 'NONE',)
self.logger = get_logger(conf, log_route='tempauth',
statsd_tail_prefix=statsd_tail_prefix)
self.log_headers = config_true_value(conf.get('log_headers', 'f'))
self.auth_prefix = conf.get('auth_prefix', '/auth/') self.auth_prefix = conf.get('auth_prefix', '/auth/')
if not self.auth_prefix or not self.auth_prefix.strip('/'): if not self.auth_prefix or not self.auth_prefix.strip('/'):
self.logger.warning('Rewriting invalid auth prefix "%s" to ' self.logger.warning('Rewriting invalid auth prefix "%s" to '

View File

@ -1880,7 +1880,7 @@ class StatsdClient(object):
self._host = host self._host = host
self._port = port self._port = port
self._base_prefix = base_prefix self._base_prefix = base_prefix
self.set_prefix(tail_prefix) self._set_prefix(tail_prefix)
self._default_sample_rate = default_sample_rate self._default_sample_rate = default_sample_rate
self._sample_rate_factor = sample_rate_factor self._sample_rate_factor = sample_rate_factor
self.random = random self.random = random
@ -1921,16 +1921,45 @@ class StatsdClient(object):
else: else:
self._target = (host, port) self._target = (host, port)
def set_prefix(self, new_prefix): def _set_prefix(self, tail_prefix):
if new_prefix and self._base_prefix: """
self._prefix = '.'.join([self._base_prefix, new_prefix, '']) Modifies the prefix that is added to metric names. The resulting prefix
elif new_prefix: is the concatenation of the component parts `base_prefix` and
self._prefix = new_prefix + '.' `tail_prefix`. Only truthy components are included. Each included
component is followed by a period, e.g.::
<base_prefix>.<tail_prefix>.
<tail_prefix>.
<base_prefix>.
<the empty string>
Note: this method is expected to be called from the constructor only,
but exists to provide backwards compatible functionality for the
deprecated set_prefix() method.
:param tail_prefix: The new value of tail_prefix
"""
if tail_prefix and self._base_prefix:
self._prefix = '.'.join([self._base_prefix, tail_prefix, ''])
elif tail_prefix:
self._prefix = tail_prefix + '.'
elif self._base_prefix: elif self._base_prefix:
self._prefix = self._base_prefix + '.' self._prefix = self._base_prefix + '.'
else: else:
self._prefix = '' self._prefix = ''
def set_prefix(self, tail_prefix):
"""
This method is deprecated; use the ``tail_prefix`` argument of the
constructor when instantiating the class instead.
"""
warnings.warn(
'set_prefix() is deprecated; use the ``tail_prefix`` argument of '
'the constructor when instantiating the class instead.',
DeprecationWarning
)
self._set_prefix(tail_prefix)
def _send(self, m_name, m_value, m_type, sample_rate): def _send(self, m_name, m_value, m_type, sample_rate):
if sample_rate is None: if sample_rate is None:
sample_rate = self._default_sample_rate sample_rate = self._default_sample_rate
@ -2015,7 +2044,7 @@ def timing_stats(**dec_kwargs):
class SwiftLoggerAdapter(logging.LoggerAdapter): class SwiftLoggerAdapter(logging.LoggerAdapter):
""" """
A logging.LoggerAdapter subclass that also passes through StatsD method A logging.LoggerAdapter subclass that also passes through StatsD method
calls. Adds an optional metric_prefix to statsd metric names. calls.
Like logging.LoggerAdapter, you have to subclass this and override the Like logging.LoggerAdapter, you have to subclass this and override the
process() method to accomplish anything useful. process() method to accomplish anything useful.
@ -2229,6 +2258,10 @@ class LogAdapter(logging.LoggerAdapter, object):
def set_statsd_prefix(self, prefix): def set_statsd_prefix(self, prefix):
""" """
This method is deprecated. Callers should use the
``statsd_tail_prefix`` argument of ``get_logger`` when instantiating a
logger.
The StatsD client prefix defaults to the "name" of the logger. This The StatsD client prefix defaults to the "name" of the logger. This
method may override that default with a specific value. Currently used method may override that default with a specific value. Currently used
in the proxy-server to differentiate the Account, Container, and Object in the proxy-server to differentiate the Account, Container, and Object
@ -2334,7 +2367,7 @@ class LogLevelFilter(object):
def get_logger(conf, name=None, log_to_console=False, log_route=None, def get_logger(conf, name=None, log_to_console=False, log_route=None,
fmt="%(server)s: %(message)s"): fmt="%(server)s: %(message)s", statsd_tail_prefix=None):
""" """
Get the current system logger using config settings. Get the current system logger using config settings.
@ -2365,6 +2398,8 @@ def get_logger(conf, name=None, log_to_console=False, log_route=None,
is used as the name attribute of the is used as the name attribute of the
``logging.LogAdapter`` that is returned. ``logging.LogAdapter`` that is returned.
:param fmt: Override log format :param fmt: Override log format
:param statsd_tail_prefix: tail prefix to pass to statsd client; if None
then the tail prefix defaults to the value of ``name``.
:return: an instance of ``LogAdapter`` :return: an instance of ``LogAdapter``
""" """
# note: log_name is typically specified in conf (i.e. defined by # note: log_name is typically specified in conf (i.e. defined by
@ -2443,8 +2478,10 @@ def get_logger(conf, name=None, log_to_console=False, log_route=None,
'log_statsd_default_sample_rate', 1)) 'log_statsd_default_sample_rate', 1))
sample_rate_factor = float(conf.get( sample_rate_factor = float(conf.get(
'log_statsd_sample_rate_factor', 1)) 'log_statsd_sample_rate_factor', 1))
if statsd_tail_prefix is None:
statsd_tail_prefix = name
statsd_client = StatsdClient(statsd_host, statsd_port, base_prefix, statsd_client = StatsdClient(statsd_host, statsd_port, base_prefix,
name, default_sample_rate, statsd_tail_prefix, default_sample_rate,
sample_rate_factor, logger=logger) sample_rate_factor, logger=logger)
logger.statsd_client = statsd_client logger.statsd_client = statsd_client
else: else:

View File

@ -196,10 +196,10 @@ class Application(object):
if conf is None: if conf is None:
conf = {} conf = {}
if logger is None: if logger is None:
self.logger = get_logger(conf, log_route='proxy-server') self.logger = get_logger(conf, log_route='proxy-server',
statsd_tail_prefix='proxy-server')
else: else:
self.logger = logger self.logger = logger
self.logger.set_statsd_prefix('proxy-server')
self._error_limiting = {} self._error_limiting = {}
swift_dir = conf.get('swift_dir', '/etc/swift') swift_dir = conf.get('swift_dir', '/etc/swift')

View File

@ -21,7 +21,7 @@ from logging.handlers import SysLogHandler
import six import six
from six.moves.urllib.parse import unquote from six.moves.urllib.parse import unquote
from swift.common.utils import get_logger, split_path from swift.common.utils import get_logger, split_path, StatsdClient
from swift.common.middleware import proxy_logging from swift.common.middleware import proxy_logging
from swift.common.swob import Request, Response from swift.common.swob import Request, Response
from swift.common import constraints from swift.common import constraints
@ -149,6 +149,26 @@ class TestProxyLogging(unittest.TestCase):
self.assertEqual(got_metrics_values_and_kwargs, self.assertEqual(got_metrics_values_and_kwargs,
exp_metrics_values_and_kwargs) exp_metrics_values_and_kwargs)
def test_logger_statsd_prefix(self):
app = proxy_logging.ProxyLoggingMiddleware(
FakeApp(), {'log_statsd_host': 'example.com'})
self.assertIsNotNone(app.access_logger.logger.statsd_client)
self.assertIsInstance(app.access_logger.logger.statsd_client,
StatsdClient)
self.assertEqual('proxy-server.',
app.access_logger.logger.statsd_client._prefix)
app = proxy_logging.ProxyLoggingMiddleware(
FakeApp(), {'log_statsd_metric_prefix': 'foo', # set base prefix
'access_log_name': 'bar', # not used as tail prefix
'log_name': 'baz', # not used as tail prefix
'log_statsd_host': 'example.com'})
self.assertIsNotNone(app.access_logger.logger.statsd_client)
self.assertIsInstance(app.access_logger.logger.statsd_client,
StatsdClient)
self.assertEqual('foo.proxy-server.',
app.access_logger.logger.statsd_client._prefix)
def test_log_request_statsd_invalid_stats_types(self): def test_log_request_statsd_invalid_stats_types(self):
app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {}) app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {})
app.access_logger = debug_logger() app.access_logger = debug_logger()

View File

@ -24,7 +24,7 @@ from six.moves.urllib.parse import quote, urlparse
from swift.common.middleware import tempauth as auth from swift.common.middleware import tempauth as auth
from swift.common.middleware.acl import format_acl from swift.common.middleware.acl import format_acl
from swift.common.swob import Request, Response from swift.common.swob import Request, Response
from swift.common.utils import split_path from swift.common.utils import split_path, StatsdClient
from test.unit import FakeMemcache from test.unit import FakeMemcache
NO_CONTENT_RESP = (('204 No Content', {}, ''),) # mock server response NO_CONTENT_RESP = (('204 No Content', {}, ''),) # mock server response
@ -97,6 +97,34 @@ class TestAuth(unittest.TestCase):
req.environ['swift.cache'] = FakeMemcache() req.environ['swift.cache'] = FakeMemcache()
return req return req
def test_statsd_prefix(self):
app = FakeApp()
ath = auth.filter_factory({'log_statsd_host': 'example.com'})(app)
self.assertIsNotNone(ath.logger.logger.statsd_client)
self.assertIsInstance(ath.logger.logger.statsd_client,
StatsdClient)
self.assertEqual('tempauth.AUTH_.',
ath.logger.logger.statsd_client._prefix)
ath = auth.filter_factory({'log_statsd_metric_prefix': 'foo',
'log_name': 'bar',
'log_statsd_host': 'example.com'})(app)
self.assertIsNotNone(ath.logger.logger.statsd_client)
self.assertIsInstance(ath.logger.logger.statsd_client,
StatsdClient)
self.assertEqual('foo.tempauth.AUTH_.',
ath.logger.logger.statsd_client._prefix)
ath = auth.filter_factory({'log_statsd_metric_prefix': 'foo',
'log_name': 'bar',
'log_statsd_host': 'example.com',
'reseller_prefix': 'TEST'})(app)
self.assertIsNotNone(ath.logger.logger.statsd_client)
self.assertIsInstance(ath.logger.logger.statsd_client,
StatsdClient)
self.assertEqual('foo.tempauth.TEST_.',
ath.logger.logger.statsd_client._prefix)
def test_reseller_prefix_init(self): def test_reseller_prefix_init(self):
app = FakeApp() app = FakeApp()
ath = auth.filter_factory({})(app) ath = auth.filter_factory({})(app)

View File

@ -5344,6 +5344,16 @@ class TestStatsdLogging(unittest.TestCase):
self.assertEqual(logger.logger.statsd_client._prefix, 'some-name.') self.assertEqual(logger.logger.statsd_client._prefix, 'some-name.')
self.assertEqual(logger.logger.statsd_client._default_sample_rate, 1) self.assertEqual(logger.logger.statsd_client._default_sample_rate, 1)
logger2 = utils.get_logger(
{'log_statsd_host': 'some.host.com'},
'other-name', log_route='some-route',
statsd_tail_prefix='some-name.more-specific')
self.assertEqual(logger.logger.statsd_client._prefix,
'some-name.more-specific.')
self.assertEqual(logger2.logger.statsd_client._prefix,
'some-name.more-specific.')
# note: set_statsd_prefix is deprecated
logger2 = utils.get_logger({'log_statsd_host': 'some.host.com'}, logger2 = utils.get_logger({'log_statsd_host': 'some.host.com'},
'other-name', log_route='some-route') 'other-name', log_route='some-route')
logger.set_statsd_prefix('some-name.more-specific') logger.set_statsd_prefix('some-name.more-specific')
@ -5356,15 +5366,23 @@ class TestStatsdLogging(unittest.TestCase):
self.assertEqual(logger2.logger.statsd_client._prefix, '') self.assertEqual(logger2.logger.statsd_client._prefix, '')
def test_get_logger_statsd_client_non_defaults(self): def test_get_logger_statsd_client_non_defaults(self):
logger = utils.get_logger({ conf = {
'log_statsd_host': 'another.host.com', 'log_statsd_host': 'another.host.com',
'log_statsd_port': '9876', 'log_statsd_port': '9876',
'log_statsd_default_sample_rate': '0.75', 'log_statsd_default_sample_rate': '0.75',
'log_statsd_sample_rate_factor': '0.81', 'log_statsd_sample_rate_factor': '0.81',
'log_statsd_metric_prefix': 'tomato.sauce', 'log_statsd_metric_prefix': 'tomato.sauce',
}, 'some-name', log_route='some-route') }
logger = utils.get_logger(conf, 'some-name', log_route='some-route')
self.assertEqual(logger.logger.statsd_client._prefix, self.assertEqual(logger.logger.statsd_client._prefix,
'tomato.sauce.some-name.') 'tomato.sauce.some-name.')
logger = utils.get_logger(conf, 'other-name', log_route='some-route',
statsd_tail_prefix='some-name.more-specific')
self.assertEqual(logger.logger.statsd_client._prefix,
'tomato.sauce.some-name.more-specific.')
# note: set_statsd_prefix is deprecated
logger.set_statsd_prefix('some-name.more-specific') logger.set_statsd_prefix('some-name.more-specific')
self.assertEqual(logger.logger.statsd_client._prefix, self.assertEqual(logger.logger.statsd_client._prefix,
'tomato.sauce.some-name.more-specific.') 'tomato.sauce.some-name.more-specific.')
@ -5377,6 +5395,37 @@ class TestStatsdLogging(unittest.TestCase):
self.assertEqual(logger.logger.statsd_client._sample_rate_factor, self.assertEqual(logger.logger.statsd_client._sample_rate_factor,
0.81) 0.81)
def test_statsd_set_prefix_deprecation(self):
conf = {'log_statsd_host': 'another.host.com'}
with warnings.catch_warnings(record=True) as cm:
warnings.resetwarnings()
warnings.simplefilter('always', DeprecationWarning)
logger = utils.get_logger(
conf, 'some-name', log_route='some-route')
logger.logger.statsd_client.set_prefix('some-name.more-specific')
msgs = [str(warning.message)
for warning in cm
if str(warning.message).startswith('set_prefix')]
self.assertEqual(
['set_prefix() is deprecated; use the ``tail_prefix`` argument of '
'the constructor when instantiating the class instead.'],
msgs)
with warnings.catch_warnings(record=True) as cm:
warnings.resetwarnings()
warnings.simplefilter('always', DeprecationWarning)
logger = utils.get_logger(
conf, 'some-name', log_route='some-route')
logger.set_statsd_prefix('some-name.more-specific')
msgs = [str(warning.message)
for warning in cm
if str(warning.message).startswith('set_prefix')]
self.assertEqual(
['set_prefix() is deprecated; use the ``tail_prefix`` argument of '
'the constructor when instantiating the class instead.'],
msgs)
def test_ipv4_or_ipv6_hostname_defaults_to_ipv4(self): def test_ipv4_or_ipv6_hostname_defaults_to_ipv4(self):
def stub_getaddrinfo_both_ipv4_and_ipv6(host, port, family, *rest): def stub_getaddrinfo_both_ipv4_and_ipv6(host, port, family, *rest):
if family == socket.AF_INET: if family == socket.AF_INET:

View File

@ -69,7 +69,7 @@ from swift.common.exceptions import ChunkReadTimeout, DiskFileNotExist, \
APIVersionError, ChunkReadError APIVersionError, ChunkReadError
from swift.common import utils, constraints, registry from swift.common import utils, constraints, registry
from swift.common.utils import hash_path, storage_directory, \ from swift.common.utils import hash_path, storage_directory, \
parse_content_type, parse_mime_headers, \ parse_content_type, parse_mime_headers, StatsdClient, \
iter_multipart_mime_documents, public, mkdirs, NullLogger, md5 iter_multipart_mime_documents, public, mkdirs, NullLogger, md5
from swift.common.wsgi import loadapp, ConfigString, SwiftHttpProtocol from swift.common.wsgi import loadapp, ConfigString, SwiftHttpProtocol
from swift.proxy.controllers import base as proxy_base from swift.proxy.controllers import base as proxy_base
@ -1336,11 +1336,25 @@ class TestProxyServer(unittest.TestCase):
path_parts.get('disallowed_sections')) path_parts.get('disallowed_sections'))
def test_statsd_prefix(self): def test_statsd_prefix(self):
app = proxy_server.Application({}, logger=debug_logger(), app = proxy_server.Application({'log_statsd_host': 'example.com'},
account_ring=FakeRing(), account_ring=FakeRing(),
container_ring=FakeRing()) container_ring=FakeRing())
self.assertEqual([(('proxy-server',), {})], self.assertIsNotNone(app.logger.logger.statsd_client)
app.logger.log_dict['set_statsd_prefix']) self.assertIsInstance(app.logger.logger.statsd_client,
StatsdClient)
self.assertEqual('proxy-server.',
app.logger.logger.statsd_client._prefix)
app = proxy_server.Application({'log_statsd_metric_prefix': 'foo',
'log_name': 'bar',
'log_statsd_host': 'example.com'},
account_ring=FakeRing(),
container_ring=FakeRing())
self.assertIsNotNone(app.logger.logger.statsd_client)
self.assertIsInstance(app.logger.logger.statsd_client,
StatsdClient)
self.assertEqual('foo.proxy-server.',
app.logger.logger.statsd_client._prefix)
@patch_policies([ @patch_policies([
@ -2147,7 +2161,7 @@ class TestProxyServerConfigLoading(unittest.TestCase):
self.assertEqual('proxy-server', app.logger.name) self.assertEqual('proxy-server', app.logger.name)
self.assertEqual('swift', app.logger.server) self.assertEqual('swift', app.logger.server)
mock_statsd.assert_called_once_with( mock_statsd.assert_called_once_with(
'example.com', 8125, '', 'swift', 1.0, 1.0, 'example.com', 8125, '', 'proxy-server', 1.0, 1.0,
logger=app.logger.logger) logger=app.logger.logger)
conf_sections = """ conf_sections = """
@ -2170,9 +2184,9 @@ class TestProxyServerConfigLoading(unittest.TestCase):
self.assertEqual('proxy-server', app.logger.name) self.assertEqual('proxy-server', app.logger.name)
# server is defined by log_name option # server is defined by log_name option
self.assertEqual('test-name', app.logger.server) self.assertEqual('test-name', app.logger.server)
# statsd prefix is defined by log_name option # statsd tail prefix is hard-wired 'proxy-server'
mock_statsd.assert_called_once_with( mock_statsd.assert_called_once_with(
'example.com', 8125, '', 'test-name', 1.0, 1.0, 'example.com', 8125, '', 'proxy-server', 1.0, 1.0,
logger=app.logger.logger) logger=app.logger.logger)