#!/usr/bin/env python2 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import functools import inspect import logging import traceback def log_dbus_method(return_logger=logging.debug, raise_logger=logging.warning, return_cb_arg=None, raise_cb_arg=None): """ Factory method for decorator to log dbus responses / errors. This method should be used to decorate the most concerete implementation of a dbus method. @param return_logger: A function that accepts a string argument to log the response from the decorated function. @param raise_logger: A function accepts a string argument to log the exception raised by the decorated function. @param return_cb_arg: str name of the async callback argument for the return value, if the function takes one. @param raise_cb_arg: str name of the async callback argument for the error return value, if the function takes one. """ def wrapper(func): """ The decorator returned by this factory. @param func: The function to be decorated. """ @functools.wraps(func) def wrapped_func(*args, **kwargs): """The modified function for the decorated function.""" modified_args = list(args) modified_kwargs = kwargs return_cb_index = getattr(wrapped_func, '_logging_return_cb_index') if return_cb_index > -1: if len(args) > return_cb_index: modified_args[return_cb_index] = _wrap_async_return( args[return_cb_index], func.__name__, return_logger) elif return_cb_arg in kwargs: modified_kwargs[return_cb_arg] = _wrap_async_return( kwargs[return_cb_arg], func.__name__, return_logger) else: logging.debug('Not logging default return_cb') raise_cb_index = getattr(wrapped_func, '_logging_raise_cb_index') if raise_cb_index > -1: if len(args) > raise_cb_index: modified_args[raise_cb_index] = _wrap_async_raise( args[raise_cb_index], func.__name__, raise_logger) elif raise_cb_arg in kwargs: modified_kwargs[raise_cb_arg] = _wrap_async_raise( kwargs[raise_cb_arg], func.__name__, raise_logger) else: logging.debug('Not logging default raise_cb') try: retval = func(*modified_args, **modified_kwargs) # No |return_cb_arg| ==> return value is the DBus response, so # it needs to be logged. if return_cb_index == -1: return_logger('Response[%s] OK: |%s|' % (func.__name__, repr(retval))) except Exception as e: raise_logger('Response[%s] ERROR: |%s|' % (func.__name__, repr(e))) raise_logger(traceback.format_exc()) raise return retval args, _, _, defaults = inspect.getargspec(func) wrapped_func._logging_return_cb_index = -1 wrapped_func._logging_raise_cb_index = -1 if return_cb_arg: if return_cb_arg not in args: logging.warning( 'Did not find expected argument %s in argument list ' 'of %s', return_cb_arg, func.__name__) wrapped_func._logging_return_cb_index = args.index(return_cb_arg) if raise_cb_arg: if raise_cb_arg not in args: logging.warning( 'Did not find expected argument %s in argument list ' 'of %s', raise_cb_arg, func.__name__) wrapped_func._logging_raise_cb_index = args.index(raise_cb_arg) return wrapped_func return wrapper def _wrap_async_return(return_cb, fname, logger): """ Wrap return_cb to log the return value. @param return_cb: The function to be wrapped. @param fname: Name of the DBus function called. @param logger: The logger to use for logging. @returns: Wrapped |return_cb| that additionally logs its arguments. """ @functools.wraps(return_cb) def wrapped_return_cb(*args, **kwargs): """ Log arguments before calling return_cb. """ logger('AsyncResponse[%s] OK: |%s|' % (fname, str((args, kwargs)))) return_cb(*args, **kwargs) return wrapped_return_cb def _wrap_async_raise(raise_cb, fname, logger): """ Wrap raise_cb to log the raised error. @param raise_cb: The function to be wrapped. @param fname: Name of the DBus function called. @param logger: The logger to use for logging. @returns: Wrapped |raise_cb| that additionally logs its arguments. """ @functools.wraps(raise_cb) def wrapped_raise_cb(*args, **kwargs): """ Log arguments before calling raise_cb. """ logger('AsyncResponse[%s] ERROR: |%s|' % (fname, str((args, kwargs)))) logger(traceback.format_exc()) raise_cb(*args, **kwargs) return wrapped_raise_cb