Apply a heuristic for product name if a user_agent is not provided
Apply a heuristic when the caller of keystoneauth Session does not specify a user_agent. The approach is simple: - use the name of the calling program aka sys.argv[0] - if that name is in the ignore list, look at the call stack to determine who imported this module. Again, honoring a ignore list. - return None if all else fails This ensures that all of the OpenStack services are logged properly in Keystone's http access logs. Change-Id: I942fbac97ad830639cf4c45f6e8fb253838f54e5
This commit is contained in:
parent
29aaac32e1
commit
2a8133c8ab
@ -15,8 +15,10 @@ import functools
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
|
||||
@ -93,6 +95,72 @@ class _StringFormatter(object):
|
||||
return value
|
||||
|
||||
|
||||
def _determine_calling_package():
|
||||
"""Walk the call frames trying to identify what is using this module."""
|
||||
# Create a lookup table mapping file name to module name. The ``inspect``
|
||||
# module does this but is far less efficient. Same story with the
|
||||
# frame walking below. One could use ``inspect.stack()`` but it
|
||||
# has far more overhead.
|
||||
mod_lookup = dict((m.__file__, n) for n, m in sys.modules.items()
|
||||
if hasattr(m, '__file__'))
|
||||
|
||||
# NOTE(shaleh): these are not useful because they hide the real
|
||||
# user of the code. debtcollector did not import keystoneauth but
|
||||
# it will show up in the call stack. Similarly we do not want to
|
||||
# report ourselves or keystone client as the user agent. The real
|
||||
# user is the code importing them.
|
||||
ignored = ('debtcollector', 'keystoneauth1', 'keystoneclient')
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
i += 1
|
||||
|
||||
try:
|
||||
# NOTE(shaleh): this is safe in CPython but could break in
|
||||
# other implementations of Python. Yes, the `inspect`
|
||||
# module could be used instead. But it does a lot more
|
||||
# work so it has worse performance.
|
||||
f = sys._getframe(i)
|
||||
try:
|
||||
name = mod_lookup[f.f_code.co_filename]
|
||||
# finds the full name module.foo.bar but all we need
|
||||
# is the module name.
|
||||
name, _, _ = name.partition('.')
|
||||
if name not in ignored:
|
||||
return name
|
||||
except KeyError:
|
||||
pass # builtin or the like
|
||||
except ValueError:
|
||||
# hit the bottom of the frame stack
|
||||
break
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _determine_user_agent():
|
||||
"""Attempt to programatically generate a user agent string.
|
||||
|
||||
First, look at the name of the process. Return this unless it is in
|
||||
the `ignored` list. Otherwise, look at the function call stack and
|
||||
try to find the name of the code that invoked this module.
|
||||
"""
|
||||
# NOTE(shaleh): mod_wsgi is not any more useful than just
|
||||
# reporting "keystoneauth". Ignore it and perform the package name
|
||||
# heuristic.
|
||||
ignored = ('mod_wsgi', )
|
||||
|
||||
try:
|
||||
name = sys.argv[0]
|
||||
except IndexError:
|
||||
# sys.argv is empty, usually the Python interpreter prevents this.
|
||||
return None
|
||||
|
||||
name = os.path.basename(name)
|
||||
if name in ignored:
|
||||
name = _determine_calling_package()
|
||||
return name
|
||||
|
||||
|
||||
class Session(object):
|
||||
"""Maintains client communication state and common functionality.
|
||||
|
||||
@ -156,12 +224,16 @@ class Session(object):
|
||||
if timeout is not None:
|
||||
self.timeout = float(timeout)
|
||||
|
||||
# don't override the class variable if none provided
|
||||
# Per RFC 7231 Section 5.5.3, identifiers in a user-agent should be
|
||||
# ordered by decreasing significance. If a user sets their product
|
||||
# that value will be used. Otherwise we attempt to derive a useful
|
||||
# product value. The value will be prepended it to the KSA version,
|
||||
# requests version, and then the Python version.
|
||||
|
||||
if user_agent is None:
|
||||
user_agent = _determine_user_agent()
|
||||
|
||||
if user_agent is not None:
|
||||
# Per RFC 7231 Section 5.5.3, identifiers in a user-agent
|
||||
# should be ordered by decreasing significance.
|
||||
# If a user sets their product, we prepend it to the KSA
|
||||
# version, requests version, and then the Python version.
|
||||
self.user_agent = "%s %s" % (user_agent, DEFAULT_USER_AGENT)
|
||||
|
||||
self._json = _JSONEncoder()
|
||||
|
@ -90,6 +90,15 @@ class SessionTests(utils.TestCase):
|
||||
self.assertRequestBodyIs(json={'hello': 'world'})
|
||||
|
||||
def test_user_agent(self):
|
||||
session = client_session.Session()
|
||||
self.stub_url('GET', text='response')
|
||||
resp = session.get(self.TEST_URL)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(
|
||||
'User-Agent',
|
||||
'%s %s' % ("run.py", client_session.DEFAULT_USER_AGENT))
|
||||
|
||||
custom_agent = 'custom-agent/1.0'
|
||||
session = client_session.Session(user_agent=custom_agent)
|
||||
self.stub_url('GET', text='response')
|
||||
|
Loading…
x
Reference in New Issue
Block a user