One update to the accepted answer from @vvvvv - the Python formatter caches the record object, so if one handler modifies it, the next handler will process the modified record rather than the original one. That caused some side effects depending on the order of the handlers. So, I passed in copies of the record object rather than the actual record to the formatter and that fixed the issue.
Here is what I've implemented. I also added a custom formatter that removed the stack trace messages but left one line showing any exception types that were raised.
class NoStackTraceFormatter(logging.Formatter):
"""Custom formatter to remove stack trace from log messages."""
def format(self, record):
"""Removes all exception stack trace information from log messages."""
temp_record = logging.LogRecord(record.name,
record.levelno,
record.pathname,
record.lineno,
record.msg,
record.args,
record.exc_info,
record.funcName)
temp_record.exc_info = None
temp_record.exc_text = None
temp_record.stack_info = None
return logging.Formatter.format(self, temp_record)
class SimpleStackTraceFormatter(logging.Formatter):
def format(self, record):
"""Remove the full stack trace from log messages but leave the lines
in the stack trace that explicitly list the exception type raised.
"""
temp_record = logging.LogRecord(record.name,
record.levelno,
record.pathname,
record.lineno,
record.msg,
record.args,
record.exc_info,
record.funcName)
if record.exc_info:
# get rid of the stack trace except for lines that explicitly list the exception type raised
if not record.exc_text:
temp_record.exc_text = self.formatException(record.exc_info)
if temp_record.exc_text:
temp_record.exc_text = '\n'.join(
[f" {line}" for line in temp_record.exc_text.splitlines()
if '.exceptions.' in line])
temp_record.stack_info = None
return logging.Formatter.format(self, temp_record)
An example of the output from the SimpleStackTraceFormatter is as follows:
2025-03-24 20:07:31,477 INFO driver.py:439 Attempting to connect to the server
2025-03-24 20:07:34,513 ERROR rest.py:921 Request timed out, will retry
urllib3.exceptions.ConnectTimeoutError: (<urllib3.connection.HTTPSConnection object at 0x000002989D18DC90>, 'Connection to x.x.x.x timed out. (connect timeout=3)')
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='x.x.x.x', port=xxxx): Max retries exceeded with url: /path/ (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x000002989D18DC90>, 'Connection to x.x.x.x timed out. (connect timeout=3)'))
requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host='x.x.x.x', port=xxxx): Max retries exceeded with url: /path/ (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x000002989D18DC90>, 'Connection to x.x.x.x timed out. (connect timeout=3)'))