Commit 8593ae64 authored by Vinay Sajip's avatar Vinay Sajip

Logging: added stack_info argument.

parent b6b76c2f
...@@ -757,13 +757,31 @@ functions. ...@@ -757,13 +757,31 @@ functions.
*msg* using the string formatting operator. (Note that this means that you can *msg* using the string formatting operator. (Note that this means that you can
use keywords in the format string, together with a single dictionary argument.) use keywords in the format string, together with a single dictionary argument.)
There are two keyword arguments in *kwargs* which are inspected: *exc_info* There are three keyword arguments in *kwargs* which are inspected: *exc_info*
which, if it does not evaluate as false, causes exception information to be which, if it does not evaluate as false, causes exception information to be
added to the logging message. If an exception tuple (in the format returned by added to the logging message. If an exception tuple (in the format returned by
:func:`sys.exc_info`) is provided, it is used; otherwise, :func:`sys.exc_info` :func:`sys.exc_info`) is provided, it is used; otherwise, :func:`sys.exc_info`
is called to get the exception information. is called to get the exception information.
The other optional keyword argument is *extra* which can be used to pass a The second optional keyword argument is *stack_info*, which defaults to
False. If specified as True, stack information is added to the logging
message, including the actual logging call. Note that this is not the same
stack information as that displayed through specifying *exc_info*: The
former is stack frames from the bottom of the stack up to the logging call
in the current thread, whereas the latter is information about stack frames
which have been unwound, following an exception, while searching for
exception handlers.
You can specify *stack_info* independently of *exc_info*, e.g. to just show
how you got to a certain point in your code, even when no exceptions were
raised. The stack frames are printed following a header line which says::
Stack (most recent call last):
This mimics the `Traceback (most recent call last):` which is used when
displaying exception frames.
The third optional keyword argument is *extra* which can be used to pass a
dictionary which is used to populate the __dict__ of the LogRecord created for dictionary which is used to populate the __dict__ of the LogRecord created for
the logging event with user-defined attributes. These custom attributes can then the logging event with user-defined attributes. These custom attributes can then
be used as you like. For example, they could be incorporated into logged be used as you like. For example, they could be incorporated into logged
...@@ -796,6 +814,8 @@ functions. ...@@ -796,6 +814,8 @@ functions.
above example). In such circumstances, it is likely that specialized above example). In such circumstances, it is likely that specialized
:class:`Formatter`\ s would be used with particular :class:`Handler`\ s. :class:`Formatter`\ s would be used with particular :class:`Handler`\ s.
.. versionadded:: 3.2
The *stack_info* parameter was added.
.. function:: info(msg, *args, **kwargs) .. function:: info(msg, *args, **kwargs)
...@@ -1038,13 +1058,31 @@ instantiated directly, but always through the module-level function ...@@ -1038,13 +1058,31 @@ instantiated directly, but always through the module-level function
*msg* using the string formatting operator. (Note that this means that you can *msg* using the string formatting operator. (Note that this means that you can
use keywords in the format string, together with a single dictionary argument.) use keywords in the format string, together with a single dictionary argument.)
There are two keyword arguments in *kwargs* which are inspected: *exc_info* There are three keyword arguments in *kwargs* which are inspected: *exc_info*
which, if it does not evaluate as false, causes exception information to be which, if it does not evaluate as false, causes exception information to be
added to the logging message. If an exception tuple (in the format returned by added to the logging message. If an exception tuple (in the format returned by
:func:`sys.exc_info`) is provided, it is used; otherwise, :func:`sys.exc_info` :func:`sys.exc_info`) is provided, it is used; otherwise, :func:`sys.exc_info`
is called to get the exception information. is called to get the exception information.
The other optional keyword argument is *extra* which can be used to pass a The second optional keyword argument is *stack_info*, which defaults to
False. If specified as True, stack information is added to the logging
message, including the actual logging call. Note that this is not the same
stack information as that displayed through specifying *exc_info*: The
former is stack frames from the bottom of the stack up to the logging call
in the current thread, whereas the latter is information about stack frames
which have been unwound, following an exception, while searching for
exception handlers.
You can specify *stack_info* independently of *exc_info*, e.g. to just show
how you got to a certain point in your code, even when no exceptions were
raised. The stack frames are printed following a header line which says::
Stack (most recent call last):
This mimics the `Traceback (most recent call last):` which is used when
displaying exception frames.
The third keyword argument is *extra* which can be used to pass a
dictionary which is used to populate the __dict__ of the LogRecord created for dictionary which is used to populate the __dict__ of the LogRecord created for
the logging event with user-defined attributes. These custom attributes can then the logging event with user-defined attributes. These custom attributes can then
be used as you like. For example, they could be incorporated into logged be used as you like. For example, they could be incorporated into logged
...@@ -1078,6 +1116,9 @@ instantiated directly, but always through the module-level function ...@@ -1078,6 +1116,9 @@ instantiated directly, but always through the module-level function
above example). In such circumstances, it is likely that specialized above example). In such circumstances, it is likely that specialized
:class:`Formatter`\ s would be used with particular :class:`Handler`\ s. :class:`Formatter`\ s would be used with particular :class:`Handler`\ s.
.. versionadded:: 3.2
The *stack_info* parameter was added.
.. method:: Logger.info(msg, *args, **kwargs) .. method:: Logger.info(msg, *args, **kwargs)
...@@ -1142,10 +1183,11 @@ instantiated directly, but always through the module-level function ...@@ -1142,10 +1183,11 @@ instantiated directly, but always through the module-level function
Removes the specified handler *hdlr* from this logger. Removes the specified handler *hdlr* from this logger.
.. method:: Logger.findCaller() .. method:: Logger.findCaller(stack_info=False)
Finds the caller's source filename and line number. Returns the filename, line Finds the caller's source filename and line number. Returns the filename, line
number and function name as a 3-element tuple. number, function name and stack information as a 4-element tuple. The stack
information is returned as *None* unless *stack_info* is *True*.
.. method:: Logger.handle(record) .. method:: Logger.handle(record)
...@@ -1156,7 +1198,7 @@ instantiated directly, but always through the module-level function ...@@ -1156,7 +1198,7 @@ instantiated directly, but always through the module-level function
Logger-level filtering is applied using :meth:`~Logger.filter`. Logger-level filtering is applied using :meth:`~Logger.filter`.
.. method:: Logger.makeRecord(name, lvl, fn, lno, msg, args, exc_info, func=None, extra=None) .. method:: Logger.makeRecord(name, lvl, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None)
This is a factory method which can be overridden in subclasses to create This is a factory method which can be overridden in subclasses to create
specialized :class:`LogRecord` instances. specialized :class:`LogRecord` instances.
...@@ -3043,6 +3085,9 @@ Currently, the useful mapping keys in a :class:`LogRecord` are: ...@@ -3043,6 +3085,9 @@ Currently, the useful mapping keys in a :class:`LogRecord` are:
formatter to handle the event doesn't use the cached value but formatter to handle the event doesn't use the cached value but
recalculates it afresh. recalculates it afresh.
If stack information is available, it's appended after the exception
information, using :meth:`formatStack` to transform it if necessary.
.. method:: formatTime(record, datefmt=None) .. method:: formatTime(record, datefmt=None)
...@@ -3062,6 +3107,12 @@ Currently, the useful mapping keys in a :class:`LogRecord` are: ...@@ -3062,6 +3107,12 @@ Currently, the useful mapping keys in a :class:`LogRecord` are:
just uses :func:`traceback.print_exception`. The resulting string is just uses :func:`traceback.print_exception`. The resulting string is
returned. returned.
.. method:: formatStack(stack_info)
Formats the specified stack information (a string as returned by
:func:`traceback.print_stack`, but with the last newline removed) as a
string. This default implementation just returns the input value.
.. _filter: .. _filter:
Filter Objects Filter Objects
...@@ -3131,7 +3182,7 @@ every time something is logged, and can be created manually via ...@@ -3131,7 +3182,7 @@ every time something is logged, and can be created manually via
wire). wire).
.. class:: LogRecord(name, lvl, pathname, lineno, msg, args, exc_info, func=None) .. class:: LogRecord(name, lvl, pathname, lineno, msg, args, exc_info, func=None, sinfo=None)
Contains all the information pertinent to the event being logged. Contains all the information pertinent to the event being logged.
...@@ -3178,6 +3229,12 @@ wire). ...@@ -3178,6 +3229,12 @@ wire).
Absolute pathname of the source file of origin. Absolute pathname of the source file of origin.
.. attribute:: stack_info
Stack frame information (where available) from the bottom of the stack
in the current thread, up to and including the stack frame of the
logging call which resulted in the creation of this record.
.. method:: getMessage() .. method:: getMessage()
Returns the message for this :class:`LogRecord` instance after merging any Returns the message for this :class:`LogRecord` instance after merging any
......
...@@ -238,7 +238,7 @@ class LogRecord(object): ...@@ -238,7 +238,7 @@ class LogRecord(object):
information to be logged. information to be logged.
""" """
def __init__(self, name, level, pathname, lineno, def __init__(self, name, level, pathname, lineno,
msg, args, exc_info, func=None): msg, args, exc_info, func=None, sinfo=None):
""" """
Initialize a logging record with interesting information. Initialize a logging record with interesting information.
""" """
...@@ -272,6 +272,7 @@ class LogRecord(object): ...@@ -272,6 +272,7 @@ class LogRecord(object):
self.module = "Unknown module" self.module = "Unknown module"
self.exc_info = exc_info self.exc_info = exc_info
self.exc_text = None # used to cache the traceback text self.exc_text = None # used to cache the traceback text
self.stack_info = sinfo
self.lineno = lineno self.lineno = lineno
self.funcName = func self.funcName = func
self.created = ct self.created = ct
...@@ -515,6 +516,19 @@ class Formatter(object): ...@@ -515,6 +516,19 @@ class Formatter(object):
def formatMessage(self, record): def formatMessage(self, record):
return self._style.format(record) return self._style.format(record)
def formatStack(self, stack_info):
"""
This method is provided as an extension point for specialized
formatting of stack information.
The input data is a string as returned from a call to
:func:`traceback.print_stack`, but with the last trailing newline
removed.
The base implementation just returns the value passed in.
"""
return stack_info
def format(self, record): def format(self, record):
""" """
Format the specified record as text. Format the specified record as text.
...@@ -541,6 +555,10 @@ class Formatter(object): ...@@ -541,6 +555,10 @@ class Formatter(object):
if s[-1:] != "\n": if s[-1:] != "\n":
s = s + "\n" s = s + "\n"
s = s + record.exc_text s = s + record.exc_text
if record.stack_info:
if s[-1:] != "\n":
s = s + "\n"
s = s + self.formatStack(record.stack_info)
return s return s
# #
...@@ -1213,11 +1231,12 @@ class Logger(Filterer): ...@@ -1213,11 +1231,12 @@ class Logger(Filterer):
if self.isEnabledFor(ERROR): if self.isEnabledFor(ERROR):
self._log(ERROR, msg, args, **kwargs) self._log(ERROR, msg, args, **kwargs)
def exception(self, msg, *args): def exception(self, msg, *args, **kwargs):
""" """
Convenience method for logging an ERROR with exception information. Convenience method for logging an ERROR with exception information.
""" """
self.error(msg, exc_info=1, *args) kwargs['exc_info'] = True
self.error(msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs): def critical(self, msg, *args, **kwargs):
""" """
...@@ -1250,7 +1269,7 @@ class Logger(Filterer): ...@@ -1250,7 +1269,7 @@ class Logger(Filterer):
if self.isEnabledFor(level): if self.isEnabledFor(level):
self._log(level, msg, args, **kwargs) self._log(level, msg, args, **kwargs)
def findCaller(self): def findCaller(self, stack_info=False):
""" """
Find the stack frame of the caller so that we can note the source Find the stack frame of the caller so that we can note the source
file name, line number and function name. file name, line number and function name.
...@@ -1260,23 +1279,34 @@ class Logger(Filterer): ...@@ -1260,23 +1279,34 @@ class Logger(Filterer):
#IronPython isn't run with -X:Frames. #IronPython isn't run with -X:Frames.
if f is not None: if f is not None:
f = f.f_back f = f.f_back
rv = "(unknown file)", 0, "(unknown function)" rv = "(unknown file)", 0, "(unknown function)", None
while hasattr(f, "f_code"): while hasattr(f, "f_code"):
co = f.f_code co = f.f_code
filename = os.path.normcase(co.co_filename) filename = os.path.normcase(co.co_filename)
if filename == _srcfile: if filename == _srcfile:
f = f.f_back f = f.f_back
continue continue
rv = (co.co_filename, f.f_lineno, co.co_name) sinfo = None
if stack_info:
sio = io.StringIO()
sio.write('Stack (most recent call last):\n')
traceback.print_stack(f, file=sio)
sinfo = sio.getvalue()
if sinfo[-1] == '\n':
sinfo = sinfo[:-1]
sio.close()
rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
break break
return rv return rv
def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
func=None, extra=None, sinfo=None):
""" """
A factory method which can be overridden in subclasses to create A factory method which can be overridden in subclasses to create
specialized LogRecords. specialized LogRecords.
""" """
rv = _logRecordClass(name, level, fn, lno, msg, args, exc_info, func) rv = _logRecordClass(name, level, fn, lno, msg, args, exc_info, func,
sinfo)
if extra is not None: if extra is not None:
for key in extra: for key in extra:
if (key in ["message", "asctime"]) or (key in rv.__dict__): if (key in ["message", "asctime"]) or (key in rv.__dict__):
...@@ -1284,17 +1314,18 @@ class Logger(Filterer): ...@@ -1284,17 +1314,18 @@ class Logger(Filterer):
rv.__dict__[key] = extra[key] rv.__dict__[key] = extra[key]
return rv return rv
def _log(self, level, msg, args, exc_info=None, extra=None): def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
""" """
Low-level logging routine which creates a LogRecord and then calls Low-level logging routine which creates a LogRecord and then calls
all the handlers of this logger to handle the record. all the handlers of this logger to handle the record.
""" """
sinfo = None
if _srcfile: if _srcfile:
#IronPython doesn't track Python frames, so findCaller throws an #IronPython doesn't track Python frames, so findCaller throws an
#exception on some versions of IronPython. We trap it here so that #exception on some versions of IronPython. We trap it here so that
#IronPython can use logging. #IronPython can use logging.
try: try:
fn, lno, func = self.findCaller() fn, lno, func, sinfo = self.findCaller(stack_info)
except ValueError: except ValueError:
fn, lno, func = "(unknown file)", 0, "(unknown function)" fn, lno, func = "(unknown file)", 0, "(unknown function)"
else: else:
...@@ -1302,7 +1333,8 @@ class Logger(Filterer): ...@@ -1302,7 +1333,8 @@ class Logger(Filterer):
if exc_info: if exc_info:
if not isinstance(exc_info, tuple): if not isinstance(exc_info, tuple):
exc_info = sys.exc_info() exc_info = sys.exc_info()
record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra) record = self.makeRecord(self.name, level, fn, lno, msg, args,
exc_info, func, extra, sinfo)
self.handle(record) self.handle(record)
def handle(self, record): def handle(self, record):
...@@ -1657,12 +1689,13 @@ def error(msg, *args, **kwargs): ...@@ -1657,12 +1689,13 @@ def error(msg, *args, **kwargs):
basicConfig() basicConfig()
root.error(msg, *args, **kwargs) root.error(msg, *args, **kwargs)
def exception(msg, *args): def exception(msg, *args, **kwargs):
""" """
Log a message with severity 'ERROR' on the root logger, Log a message with severity 'ERROR' on the root logger,
with exception information. with exception information.
""" """
error(msg, exc_info=1, *args) kwargs['exc_info'] = True
error(msg, *args, **kwargs)
def warning(msg, *args, **kwargs): def warning(msg, *args, **kwargs):
""" """
......
...@@ -63,6 +63,8 @@ Core and Builtins ...@@ -63,6 +63,8 @@ Core and Builtins
Library Library
------- -------
- Issue #1553375: logging: Added stack_info kwarg to display stack information.
- Issue #5111: IPv6 Host in the Header is wrapped inside [ ]. Patch by Chandru. - Issue #5111: IPv6 Host in the Header is wrapped inside [ ]. Patch by Chandru.
- Fix Fraction.__hash__ so that Fraction.__hash__(-1) is -2. (See also issue - Fix Fraction.__hash__ so that Fraction.__hash__(-1) is -2. (See also issue
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment