nova/nova/notifications/objects/exception.py
Stephen Finucane 125df26bf9 Use 'Exception.__traceback__' for versioned notifications
The 'inspect.trace()' function is expected to be called within the
context of an exception handler. The 'from_exc_and_traceback' class
method of the 'nova.notification.objects.exception.ExceptionPayload'
class uses this to get information about a provided exception, however,
there are cases where this is called from outside of an exception
handler. In these cases, we see an 'IndexError' since we can't get the
last frame of a non-existent stacktrace. The solution to this is to
fallback to using the traceback embedded in the exception. This is a bit
lossy when decorators are involved but for all other cases this will
give us the same information. This also allows us to avoid passing a
traceback argument to the function since we have it to hand already.

Change-Id: I404ca316b1bf2a963106cd34e927934befbd9b12
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
Closes-Bug: #1881455
2020-06-08 14:38:33 +01:00

86 lines
3.3 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import inspect
import traceback as tb
from nova.notifications.objects import base
from nova.objects import base as nova_base
from nova.objects import fields
@nova_base.NovaObjectRegistry.register_notification
class ExceptionPayload(base.NotificationPayloadBase):
# Version 1.0: Initial version
# Version 1.1: Add traceback field to ExceptionPayload
VERSION = '1.1'
fields = {
'module_name': fields.StringField(),
'function_name': fields.StringField(),
'exception': fields.StringField(),
'exception_message': fields.StringField(),
'traceback': fields.StringField()
}
def __init__(self, module_name, function_name, exception,
exception_message, traceback):
super(ExceptionPayload, self).__init__()
self.module_name = module_name
self.function_name = function_name
self.exception = exception
self.exception_message = exception_message
self.traceback = traceback
@classmethod
def from_exception(cls, fault: Exception):
traceback = fault.__traceback__
# NOTE(stephenfin): inspect.trace() will only return something if we're
# inside the scope of an exception handler. If we are not, we fallback
# to extracting information from the traceback. This is lossy, since
# the stack stops at the exception handler, not the exception raise.
# Check the inspect docs for more information.
#
# https://docs.python.org/3/library/inspect.html#types-and-members
trace = inspect.trace()
if trace:
module = inspect.getmodule(trace[-1][0])
function_name = trace[-1][3]
else:
module = inspect.getmodule(traceback)
function_name = traceback.tb_frame.f_code.co_name
module_name = module.__name__ if module else 'unknown'
# TODO(gibi): apply strutils.mask_password on exception_message and
# consider emitting the exception_message only if the safe flag is
# true in the exception like in the REST API
return cls(
function_name=function_name,
module_name=module_name,
exception=fault.__class__.__name__,
exception_message=str(fault),
# NOTE(stephenfin): the first argument to format_exception is
# ignored since Python 3.5
traceback=','.join(tb.format_exception(None, fault, traceback)),
)
@base.notification_sample('compute-exception.json')
@nova_base.NovaObjectRegistry.register_notification
class ExceptionNotification(base.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': fields.ObjectField('ExceptionPayload')
}