Coverage for nova/api/openstack/requestlog.py: 92%

43 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +0000

1# Licensed under the Apache License, Version 2.0 (the "License"); 

2# you may not use this file except in compliance with the License. 

3# You may obtain a copy of the License at 

4# 

5# http://www.apache.org/licenses/LICENSE-2.0 

6# 

7# Unless required by applicable law or agreed to in writing, software 

8# distributed under the License is distributed on an "AS IS" BASIS, 

9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 

10# implied. 

11# See the License for the specific language governing permissions and 

12# limitations under the License. 

13"""Simple middleware for request logging.""" 

14 

15import time 

16 

17from oslo_log import log as logging 

18from oslo_utils import excutils 

19import webob.dec 

20import webob.exc 

21 

22from nova.api.openstack import wsgi 

23from nova.api import wsgi as base_wsgi 

24import nova.conf 

25 

26CONF = nova.conf.CONF 

27 

28# TODO(sdague) maybe we can use a better name here for the logger 

29LOG = logging.getLogger(__name__) 

30 

31 

32class RequestLog(base_wsgi.Middleware): 

33 """WSGI Middleware to write a simple request log to. 

34 

35 Borrowed from Paste Translogger 

36 """ 

37 

38 # This matches the placement fil 

39 _log_format = ('%(REMOTE_ADDR)s "%(REQUEST_METHOD)s %(REQUEST_URI)s" ' 

40 'status: %(status)s len: %(len)s ' 

41 'microversion: %(microversion)s time: %(time).6f') 

42 

43 @staticmethod 

44 def _get_uri(environ): 

45 req_uri = (environ.get('SCRIPT_NAME', '') + 

46 environ.get('PATH_INFO', '')) 

47 if environ.get('QUERY_STRING'): 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true

48 req_uri += '?' + environ['QUERY_STRING'] 

49 return req_uri 

50 

51 @staticmethod 

52 def _should_emit(req): 

53 """Conditions under which we should skip logging. 

54 

55 If we detect we are running under eventlet wsgi processing, we 

56 already are logging things, let it go. This is broken out as a 

57 separate method so that it can be easily adjusted for testing. 

58 """ 

59 if req.environ.get('eventlet.input', None) is not None: 59 ↛ 60line 59 didn't jump to line 60 because the condition on line 59 was never true

60 return False 

61 return True 

62 

63 def _log_req(self, req, res, start): 

64 if not self._should_emit(req): 

65 return 

66 

67 # in the event that things exploded really badly deeper in the 

68 # wsgi stack, res is going to be an empty dict for the 

69 # fallback logging. So never count on it having attributes. 

70 status = getattr(res, "status", "500 Error").split(None, 1)[0] 

71 

72 remote_address = req.environ.get('REMOTE_ADDR', '-') 

73 

74 data = { 

75 'REMOTE_ADDR': remote_address, 

76 'REQUEST_METHOD': req.environ['REQUEST_METHOD'], 

77 'REQUEST_URI': self._get_uri(req.environ), 

78 'status': status, 

79 'len': getattr(res, "content_length", 0), 

80 'time': time.time() - start, 

81 'microversion': '-' 

82 } 

83 # set microversion if it exists 

84 if not req.api_version_request.is_null(): 

85 data["microversion"] = req.api_version_request.get_string() 

86 LOG.info(self._log_format, data) 

87 

88 @webob.dec.wsgify(RequestClass=wsgi.Request) 

89 def __call__(self, req): 

90 res = {} 

91 start = time.time() 

92 try: 

93 res = req.get_response(self.application) 

94 self._log_req(req, res, start) 

95 return res 

96 except Exception: 

97 with excutils.save_and_reraise_exception(): 

98 self._log_req(req, res, start)