Merge "Log request_id for each api call"
This commit is contained in:
commit
0e9456aede
novaclient
releasenotes/notes
@ -73,6 +73,18 @@ class _ClientConnectionPool(object):
|
|||||||
return self._adapters[url]
|
return self._adapters[url]
|
||||||
|
|
||||||
|
|
||||||
|
def _log_request_id(logger, resp, service_name):
|
||||||
|
request_id = (resp.headers.get('x-openstack-request-id') or
|
||||||
|
resp.headers.get('x-compute-request-id'))
|
||||||
|
if request_id:
|
||||||
|
logger.debug('%(method)s call to %(service_name)s for %(url)s '
|
||||||
|
'used request id %(response_request_id)s',
|
||||||
|
{'method': resp.request.method,
|
||||||
|
'service_name': service_name,
|
||||||
|
'url': resp.url,
|
||||||
|
'response_request_id': request_id})
|
||||||
|
|
||||||
|
|
||||||
class SessionClient(adapter.LegacyJsonAdapter):
|
class SessionClient(adapter.LegacyJsonAdapter):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -93,6 +105,11 @@ class SessionClient(adapter.LegacyJsonAdapter):
|
|||||||
method,
|
method,
|
||||||
raise_exc=False,
|
raise_exc=False,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
|
# if service name is None then use service_type for logging
|
||||||
|
service = self.service_name or self.service_type
|
||||||
|
_log_request_id(self.logger, resp, service)
|
||||||
|
|
||||||
# TODO(andreykurilin): uncomment this line, when we will be able to
|
# TODO(andreykurilin): uncomment this line, when we will be able to
|
||||||
# check only nova-related calls
|
# check only nova-related calls
|
||||||
# api_versions.check_headers(resp, self.api_version)
|
# api_versions.check_headers(resp, self.api_version)
|
||||||
@ -142,7 +159,8 @@ class HTTPClient(object):
|
|||||||
http_log_debug=False, auth_system='keystone',
|
http_log_debug=False, auth_system='keystone',
|
||||||
auth_plugin=None, auth_token=None,
|
auth_plugin=None, auth_token=None,
|
||||||
cacert=None, tenant_id=None, user_id=None,
|
cacert=None, tenant_id=None, user_id=None,
|
||||||
connection_pool=False, api_version=None):
|
connection_pool=False, api_version=None,
|
||||||
|
logger=None):
|
||||||
self.user = user
|
self.user = user
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
self.password = password
|
self.password = password
|
||||||
@ -203,9 +221,10 @@ class HTTPClient(object):
|
|||||||
self.auth_plugin = auth_plugin
|
self.auth_plugin = auth_plugin
|
||||||
self._session = None
|
self._session = None
|
||||||
self._current_url = None
|
self._current_url = None
|
||||||
self._logger = logging.getLogger(__name__)
|
self._logger = logger or logging.getLogger(__name__)
|
||||||
|
|
||||||
if self.http_log_debug and not self._logger.handlers:
|
if (self.http_log_debug and logger is None and
|
||||||
|
not self._logger.handlers):
|
||||||
# Logging level is already set on the root logger
|
# Logging level is already set on the root logger
|
||||||
ch = logging.StreamHandler()
|
ch = logging.StreamHandler()
|
||||||
self._logger.addHandler(ch)
|
self._logger.addHandler(ch)
|
||||||
@ -320,6 +339,10 @@ class HTTPClient(object):
|
|||||||
'headers': resp.headers,
|
'headers': resp.headers,
|
||||||
'text': json.dumps(body)})
|
'text': json.dumps(body)})
|
||||||
|
|
||||||
|
# if service name is None then use service_type for logging
|
||||||
|
service = self.service_name or self.service_type
|
||||||
|
_log_request_id(self._logger, resp, service)
|
||||||
|
|
||||||
def open_session(self):
|
def open_session(self):
|
||||||
if not self._connection_pool:
|
if not self._connection_pool:
|
||||||
self._session = requests.Session()
|
self._session = requests.Session()
|
||||||
@ -706,6 +729,7 @@ def _construct_http_client(username=None, password=None, project_id=None,
|
|||||||
else:
|
else:
|
||||||
# FIXME(jamielennox): username and password are now optional. Need
|
# FIXME(jamielennox): username and password are now optional. Need
|
||||||
# to test that they were provided in this mode.
|
# to test that they were provided in this mode.
|
||||||
|
logger = kwargs.get('logger')
|
||||||
return HTTPClient(username,
|
return HTTPClient(username,
|
||||||
password,
|
password,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
@ -730,7 +754,8 @@ def _construct_http_client(username=None, password=None, project_id=None,
|
|||||||
http_log_debug=http_log_debug,
|
http_log_debug=http_log_debug,
|
||||||
cacert=cacert,
|
cacert=cacert,
|
||||||
connection_pool=connection_pool,
|
connection_pool=connection_pool,
|
||||||
api_version=api_version)
|
api_version=api_version,
|
||||||
|
logger=logger)
|
||||||
|
|
||||||
|
|
||||||
def discover_extensions(version, only_contrib=False):
|
def discover_extensions(version, only_contrib=False):
|
||||||
|
@ -380,6 +380,9 @@ class NovaClientArgumentParser(argparse.ArgumentParser):
|
|||||||
class OpenStackComputeShell(object):
|
class OpenStackComputeShell(object):
|
||||||
times = []
|
times = []
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.client_logger = None
|
||||||
|
|
||||||
def _append_global_identity_args(self, parser, argv):
|
def _append_global_identity_args(self, parser, argv):
|
||||||
# Register the CLI arguments that have moved to the session object.
|
# Register the CLI arguments that have moved to the session object.
|
||||||
loading.register_session_argparse_arguments(parser)
|
loading.register_session_argparse_arguments(parser)
|
||||||
@ -698,6 +701,11 @@ class OpenStackComputeShell(object):
|
|||||||
format=streamformat)
|
format=streamformat)
|
||||||
logging.getLogger('iso8601').setLevel(logging.WARNING)
|
logging.getLogger('iso8601').setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
self.client_logger = logging.getLogger(client.__name__)
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
self.client_logger.setLevel(logging.DEBUG)
|
||||||
|
self.client_logger.addHandler(ch)
|
||||||
|
|
||||||
def main(self, argv):
|
def main(self, argv):
|
||||||
# Parse args once to find version and debug settings
|
# Parse args once to find version and debug settings
|
||||||
parser = self.get_base_parser(argv)
|
parser = self.get_base_parser(argv)
|
||||||
@ -892,7 +900,8 @@ class OpenStackComputeShell(object):
|
|||||||
timings=args.timings, bypass_url=bypass_url,
|
timings=args.timings, bypass_url=bypass_url,
|
||||||
os_cache=os_cache, http_log_debug=args.debug,
|
os_cache=os_cache, http_log_debug=args.debug,
|
||||||
cacert=cacert, timeout=timeout,
|
cacert=cacert, timeout=timeout,
|
||||||
session=keystone_session, auth=keystone_auth)
|
session=keystone_session, auth=keystone_auth,
|
||||||
|
logger=self.client_logger)
|
||||||
|
|
||||||
if not skip_auth:
|
if not skip_auth:
|
||||||
if not api_version.is_latest():
|
if not api_version.is_latest():
|
||||||
|
@ -443,7 +443,8 @@ class ClientTest(utils.TestCase):
|
|||||||
class SessionClientTest(utils.TestCase):
|
class SessionClientTest(utils.TestCase):
|
||||||
|
|
||||||
@mock.patch.object(adapter.LegacyJsonAdapter, 'request')
|
@mock.patch.object(adapter.LegacyJsonAdapter, 'request')
|
||||||
def test_timings(self, m_request):
|
@mock.patch.object(novaclient.client, '_log_request_id')
|
||||||
|
def test_timings(self, mock_log_request_id, m_request):
|
||||||
m_request.return_value = (mock.MagicMock(status_code=200), None)
|
m_request.return_value = (mock.MagicMock(status_code=200), None)
|
||||||
|
|
||||||
client = novaclient.client.SessionClient(session=mock.MagicMock())
|
client = novaclient.client.SessionClient(session=mock.MagicMock())
|
||||||
@ -456,6 +457,17 @@ class SessionClientTest(utils.TestCase):
|
|||||||
self.assertEqual(1, len(client.times))
|
self.assertEqual(1, len(client.times))
|
||||||
self.assertEqual('GET http://no.where', client.times[0][0])
|
self.assertEqual('GET http://no.where', client.times[0][0])
|
||||||
|
|
||||||
|
@mock.patch.object(adapter.LegacyJsonAdapter, 'request')
|
||||||
|
@mock.patch.object(novaclient.client, '_log_request_id')
|
||||||
|
def test_log_request_id(self, mock_log_request_id, mock_request):
|
||||||
|
response = mock.MagicMock(status_code=200)
|
||||||
|
mock_request.return_value = (response, None)
|
||||||
|
client = novaclient.client.SessionClient(session=mock.MagicMock(),
|
||||||
|
service_name='compute')
|
||||||
|
client.request("http://no.where", 'GET')
|
||||||
|
mock_log_request_id.assert_called_once_with(client.logger, response,
|
||||||
|
'compute')
|
||||||
|
|
||||||
|
|
||||||
class DiscoverExtensionTest(utils.TestCase):
|
class DiscoverExtensionTest(utils.TestCase):
|
||||||
|
|
||||||
|
@ -71,15 +71,16 @@ retry_after_non_supporting_mock_request = mock.Mock(
|
|||||||
return_value=retry_after_non_supporting_response)
|
return_value=retry_after_non_supporting_response)
|
||||||
|
|
||||||
|
|
||||||
def get_client():
|
def get_client(**kwargs):
|
||||||
cl = client.HTTPClient("username", "password",
|
cl = client.HTTPClient("username", "password",
|
||||||
"project_id",
|
"project_id",
|
||||||
utils.AUTH_URL_V2)
|
utils.AUTH_URL_V2,
|
||||||
|
**kwargs)
|
||||||
return cl
|
return cl
|
||||||
|
|
||||||
|
|
||||||
def get_authed_client():
|
def get_authed_client(**kwargs):
|
||||||
cl = get_client()
|
cl = get_client(**kwargs)
|
||||||
cl.management_url = "http://example.com"
|
cl.management_url = "http://example.com"
|
||||||
cl.auth_token = "token"
|
cl.auth_token = "token"
|
||||||
cl.get_service_url = mock.Mock(return_value="http://example.com")
|
cl.get_service_url = mock.Mock(return_value="http://example.com")
|
||||||
@ -164,14 +165,16 @@ class ClientTest(utils.TestCase):
|
|||||||
|
|
||||||
test_refused_call()
|
test_refused_call()
|
||||||
|
|
||||||
def test_client_logger(self):
|
@mock.patch.object(requests, "request", mock_request)
|
||||||
cl1 = client.HTTPClient("username", "password", "project_id",
|
@mock.patch.object(client, '_log_request_id')
|
||||||
"auth_test", http_log_debug=True)
|
@mock.patch.object(client.HTTPClient, 'http_log_req')
|
||||||
self.assertEqual(1, len(cl1._logger.handlers))
|
def test_client_logger(self, mock_http_log_req, mock_log_request_id):
|
||||||
|
cl = get_authed_client(service_name='compute', http_log_debug=True)
|
||||||
|
self.assertIsNotNone(cl._logger)
|
||||||
|
|
||||||
cl2 = client.HTTPClient("username", "password", "project_id",
|
cl.post("/hi", body=[1, 2, 3])
|
||||||
"auth_test", http_log_debug=True)
|
mock_log_request_id.assert_called_once_with(cl._logger, fake_response,
|
||||||
self.assertEqual(1, len(cl2._logger.handlers))
|
'compute')
|
||||||
|
|
||||||
@mock.patch.object(requests, 'request', unknown_error_mock_request)
|
@mock.patch.object(requests, 'request', unknown_error_mock_request)
|
||||||
def test_unknown_server_error(self):
|
def test_unknown_server_error(self):
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from novaclient import api_versions
|
from novaclient import api_versions
|
||||||
from novaclient import client
|
from novaclient import client
|
||||||
from novaclient.i18n import _LW
|
from novaclient.i18n import _LW
|
||||||
@ -67,7 +69,7 @@ class Client(object):
|
|||||||
auth_system='keystone', auth_plugin=None, auth_token=None,
|
auth_system='keystone', auth_plugin=None, auth_token=None,
|
||||||
cacert=None, tenant_id=None, user_id=None,
|
cacert=None, tenant_id=None, user_id=None,
|
||||||
connection_pool=False, session=None, auth=None,
|
connection_pool=False, session=None, auth=None,
|
||||||
api_version=None, direct_use=True, **kwargs):
|
api_version=None, direct_use=True, logger=None, **kwargs):
|
||||||
"""Initialization of Client object.
|
"""Initialization of Client object.
|
||||||
|
|
||||||
:param str username: Username
|
:param str username: Username
|
||||||
@ -99,6 +101,8 @@ class Client(object):
|
|||||||
:param str session: Session
|
:param str session: Session
|
||||||
:param str auth: Auth
|
:param str auth: Auth
|
||||||
:param api_version: Compute API version
|
:param api_version: Compute API version
|
||||||
|
:param direct_use: Direct use
|
||||||
|
:param logger: Logger
|
||||||
:type api_version: novaclient.api_versions.APIVersion
|
:type api_version: novaclient.api_versions.APIVersion
|
||||||
"""
|
"""
|
||||||
if direct_use:
|
if direct_use:
|
||||||
@ -174,6 +178,9 @@ class Client(object):
|
|||||||
setattr(self, extension.name,
|
setattr(self, extension.name,
|
||||||
extension.manager_class(self))
|
extension.manager_class(self))
|
||||||
|
|
||||||
|
if not logger:
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
self.client = client._construct_http_client(
|
self.client = client._construct_http_client(
|
||||||
username=username,
|
username=username,
|
||||||
password=password,
|
password=password,
|
||||||
@ -202,6 +209,7 @@ class Client(object):
|
|||||||
session=session,
|
session=session,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
api_version=api_version,
|
api_version=api_version,
|
||||||
|
logger=logger,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
@client._original_only
|
@client._original_only
|
||||||
|
11
releasenotes/notes/log-request-id-ce106497e0520fad.yaml
Normal file
11
releasenotes/notes/log-request-id-ce106497e0520fad.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
- Log 'x-openstack-request-id' or 'x-compute-request-id'
|
||||||
|
in each API call. If the caller (e.g. heat) uses oslo.log,
|
||||||
|
the caller's request id in oslo.context and the callee's
|
||||||
|
request id can be output in the same log message (same line).
|
||||||
|
features:
|
||||||
|
- Log 'x-openstack-request-id' or 'x-compute-request-id'
|
||||||
|
in each API call. If the caller (e.g. heat) uses oslo.log,
|
||||||
|
the caller's request id in oslo.context and the callee's
|
||||||
|
request id can be output in the same log message (same line).
|
Loading…
x
Reference in New Issue
Block a user