'Python - Log dictionary to console with correct indentation

Below references to questions that have helped me get this far:

I currently have this:

# got from second reference link
class MultiLineFormatter(logging.Formatter):

    def get_header_length(self, record):
        # Get the header length of a given record
        return len(super().format(logging.LogRecord(name=record.name,
                                                    level=record.levelno,
                                                    pathname=record.pathname,
                                                    lineno=record.lineno,
                                                    msg='',
                                                    args=(),
                                                    exc_info=None)))

    def format(self, record):
        # Format a record with added indentation
        indent = ' ' * self.get_header_length(record)
        head, *trailing = super().format(record).splitlines(True)

        # return
        return head + ''.join(indent + line for line in trailing)



def report_logger(log_level: logging.INFO):
    # set format for logging style
    formatter = MultiLineFormatter(fmt='%(asctime)-8s - %(levelname)-8s - %(name)-30s  : %(message)s',
                                   datefmt='%y/%b/%Y %H:%M:%S', )

    # create console logger
    console = logging.StreamHandler()
    console.setLevel(log_level)

    # apply formatter to console logger
    console.setFormatter(formatter)

    # request logger
    final_logger = logging.getLogger(__name__)
    final_logger.setLevel(log_level)

    # prevent double logs in console
    final_logger.propagate = False

    # add handler
    final_logger.addHandler(console)

    # return
    return final_logger

def print_request(request_data):
    format_headers = lambda d: '\n                '.join(f'{k}: {v}' for k, v in d.items())

    request_body = json.dumps(request_data.req_body, indent=20, sort_keys=True, default=str)
    response_text = json.dumps(request_data.resp_text, indent=20, sort_keys=True, default=str)
    response_body = json.dumps(request_data.resp_as_dict, indent=20, sort_keys=True, default=str)

    msg_print = f'---------------- Request ----------------\n' \
                f'Headers     :   {format_headers(request_data.req_headers)}\n' \
                f'URL         :   {request_data.req_url}\n' \
                f'Method      :   {request_data.req_method}\n' \
                f'Body        :   {request_body}\n' \
                f'\n' \
                f'---------------- Response ----------------\n' \
                f'Headers     :   {format_headers(request_data.resp_headers)}\n' \
                f'Status Code :   {request_data.resp_status_code}\n' \
                f'Text        :   {response_text}\n' \
                f'Response    :   {response_body}\n'

    logger.info(msg_print)

At the moment, it's pretty much on par for what I'm trying to achieve, however, I'm just trying to get the output of the response bodies a little more "nice".

In the last line above logger.info(msg_print) I get this output:

22/Feb/2022 16:11:09 - INFO     - logger_function                 : ---------------- Request ----------------
                                                                    Headers     :   User-Agent: python-requests/2.25.1
                                                                                    Accept-Encoding: gzip, deflate
                                                                                    Accept: */*
                                                                                    Connection: keep-alive
                                                                                    authorization: AuthToken
                                                                    URL         :   my_url
                                                                    Method      :   GET
                                                                    Body        :   null
                                                                    
                                                                    ---------------- Response ----------------
                                                                    Headers     :   content-type: application/json
                                                                                    Content-Length: 119
                                                                                    x-envoy-upstream-service-time: 187
                                                                                    date: today
                                                                                    server: server
                                                                                    Via: 1.1 service
                                                                                    Alt-Svc: alt_service
                                                                    Status Code :   401
                                                                    Text        :   {
                                                                                        "code": 111111,
                                                                                        "component": "ABC",
                                                                                        "errorType": "DEF",
                                                                                        "message": "",
                                                                                        "traceId": UUID4
                                                                    }
                                                                    Response    :   {
                                                                                        "code": 111111,
                                                                                        "component": "ABC",
                                                                                        "errorType": "DEF",
                                                                                        "message": "",
                                                                                        "traceId": UUID4
                                                                    }

But I'd really like to get those dictionaries to be like this:

22/Feb/2022 16:11:09 - INFO     - logger_function                 : ---------------- Response ----------------
                                                                    Text        :   {"code": 111111,
                                                                                     "component": "ABC",
                                                                                     "errorType": "DEF",
                                                                                     "message": "",
                                                                                     "traceId": UUID4}
                                                                    Response    :   {"code": 111111,
                                                                                     "component": "ABC",
                                                                                     "errorType": "DEF",
                                                                                     "message": "",
                                                                                     "traceId": UUID4}

Edit:

Sample dict with nested values:

{
    "level1": {
        "myInt": "Original",
        "level2": {
            "myInt": "Original",
            "myBool": "Original",
            "level3": {
                "myBool": "Original"
            }
        }
    },
    "level4": [
        {
            "myList": "Original"
        },
        {
            "myList": "Original"
        }
        ,
        {
            "myList": "Original"
        }
    ]
}


Solution 1:[1]

Though it seems a bit fragile, it looks like you can slightly adjust the indentation then use the re package to strip out the unwanted spacing between the leading and closing curly braces.

import json
import re

req_body = {"code": 111111, "component": "ABC", "errorType": "DEF", "message": "", "traceId": "UUID4"}
resp_text = {"code": 111111, "component": "ABC", "errorType": "DEF", "message": "", "traceId": "UUID4"}

request_body = json.dumps(req_body, indent=20, sort_keys=True, default=str)

response_text = json.dumps(resp_text, indent=17, sort_keys=True, default=str)
response_text = re.sub(r"^{\s*", "{", response_text)
response_text = re.sub(r"\s*}$", "}", response_text)

msg_print = f'---------------- Response ----------------\n' \
            f'Request     :   {request_body}\n' \
            f'Response    :   {response_text}\n'

print(msg_print)

That looks like it might give you what you are after:

---------------- Response ----------------
Request     :   {
                    "code": 111111,
                    "component": "ABC",
                    "errorType": "DEF",
                    "message": "",
                    "traceId": "UUID4"
}
Response    :   {"code": 111111,
                 "component": "ABC",
                 "errorType": "DEF",
                 "message": "",
                 "traceId": "UUID4"}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1