Skip to content

Commit

Permalink
Improved logging for POST with file-like objects
Browse files Browse the repository at this point in the history
Details:

* If the Partition.mount_iso_image() method is called with a file-like
  object as the imagefile parameter, the hmc=debug log showed the
  content length as the length of the string that indicated that a
  file-like object was passed. This change improves that by showing
  a length of -1 and the logged string now also shows the file mode.

* As an additional safety measure, changed the Unicode decoding
  in the logging function to use errors='ignore' instead of the
  default errors='strict', so that a unicode error in logging
  is not causing a secondary exception to be raised.

Signed-off-by: Andreas Maier <[email protected]>
  • Loading branch information
andy-maier committed Oct 20, 2021
1 parent 00da085 commit 32a73e1
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 6 deletions.
3 changes: 3 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ Released: not yet

**Enhancements:**

* Improved the log entries when file-like objects are passed to
'Partition.mount_iso_image()'.

**Cleanup:**

**Known issues:**
Expand Down
31 changes: 25 additions & 6 deletions zhmcclient/_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,8 @@ def _do_logoff(self):
self._job_topic = None

@staticmethod
def _log_http_request(method, url, headers=None, content=None):
def _log_http_request(method, url, headers=None, content=None,
content_len=None):
"""
Log the HTTP request of an HMC REST API call, at the debug level.
Expand All @@ -749,14 +750,18 @@ def _log_http_request(method, url, headers=None, content=None):
content (:term:`string`): HTTP body (aka content) used for the
request (byte string or unicode string)
content_len (int): Length of content in Bytes, or `None` for
determining the length from the content string
"""

content_msg = None
if content is not None:
if isinstance(content, six.binary_type):
content = content.decode('utf-8')
content = content.decode('utf-8', errors='ignore')
assert isinstance(content, six.text_type)
content_len = len(content) # may change after JSON conversion
if content_len is None:
content_len = len(content) # may change after JSON conversion
try:
content_dict = json2dict(content)
except ValueError:
Expand Down Expand Up @@ -960,7 +965,14 @@ def post(self, uri, body=None, logon_required=True,
Must not be `None`.
body (:term:`json object` or :term:`string` or file-like object):
JSON object to be used as the HTTP request body (payload).
The HTTP request body (payload).
If a JSON object (=dict) is provided, it will be serialized into
a UTF-8 encoded binary string.
If a Unicode string is provided, it will be encoded into a UTF-8
encoded binary string.
If a binary string is provided, it will be used unchanged.
If a file-like object is provided, it must return binary strings,
i.e. the file must have been opened in binary mode.
`None` means the same as an empty dictionary, namely that no HTTP
body is included in the request.
Expand Down Expand Up @@ -1040,6 +1052,7 @@ def post(self, uri, body=None, logon_required=True,
url = self.base_url + uri
headers = self.headers.copy() # Standard headers

log_len = None
if body is None:
data = None
log_data = None
Expand All @@ -1065,12 +1078,18 @@ def post(self, uri, body=None, logon_required=True,
# File-like objects, e.g. io.BufferedReader or io.TextIOWrapper
# returned from open() or io.open().
data = body
log_data = u'(file-like object)'
try:
mode = body.mode
except AttributeError:
mode = 'unknown'
log_data = u"<file-like object with mode {}>".format(mode)
log_len = -1
headers['Content-type'] = 'application/octet-stream'
else:
raise TypeError("Body has invalid type: {}".format(type(body)))

self._log_http_request('POST', url, headers=headers, content=log_data)
self._log_http_request('POST', url, headers=headers, content=log_data,
content_len=log_len)
req = self._session or requests
req_timeout = (self.retry_timeout_config.connect_timeout,
self.retry_timeout_config.read_timeout)
Expand Down

0 comments on commit 32a73e1

Please sign in to comment.