# -*- coding: utf-8 -*- # # Copyright © Spyder Project Contributors # # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """ Debug utilities that are independent of Spyder code. See spyder.config.base for other helpers. """ from __future__ import print_function import inspect import traceback import time from spyder.py3compat import PY2 def log_time(fd): timestr = "Logging time: %s" % time.ctime(time.time()) print("="*len(timestr), file=fd) print(timestr, file=fd) print("="*len(timestr), file=fd) print("", file=fd) def log_last_error(fname, context=None): """Log last error in filename *fname* -- *context*: string (optional)""" fd = open(fname, 'a') log_time(fd) if context: print("Context", file=fd) print("-------", file=fd) print("", file=fd) if PY2: print(u' '.join(context).encode('utf-8').strip(), file=fd) else: print(context, file=fd) print("", file=fd) print("Traceback", file=fd) print("---------", file=fd) print("", file=fd) traceback.print_exc(file=fd) print("", file=fd) print("", file=fd) def log_dt(fname, context, t0): fd = open(fname, 'a') log_time(fd) print("%s: %d ms" % (context, 10*round(1e2*(time.time()-t0))), file=fd) print("", file=fd) print("", file=fd) def caller_name(skip=2): """ Get name of a caller in the format module.class.method `skip` specifies how many levels of call stack to skip for caller's name. skip=1 means "who calls me", skip=2 "who calls my caller" etc. An empty string is returned if skipped levels exceed stack height """ stack = inspect.stack() start = 0 + skip if len(stack) < start + 1: return '' parentframe = stack[start][0] name = [] module = inspect.getmodule(parentframe) # `modname` can be None when frame is executed directly in console # TODO(techtonik): consider using __main__ if module: name.append(module.__name__) # detect classname if 'self' in parentframe.f_locals: # I don't know any way to detect call from the object method # XXX: there seems to be no way to detect static method call - it will # be just a function call name.append(parentframe.f_locals['self'].__class__.__name__) codename = parentframe.f_code.co_name if codename != '': # top level usually name.append( codename ) # function or a method del parentframe return ".".join(name) def get_class_that_defined(method): for cls in inspect.getmro(method.im_class): if method.__name__ in cls.__dict__: return cls.__name__ def log_methods_calls(fname, some_class, prefix=None): """ Hack `some_class` to log all method calls into `fname` file. If `prefix` format is not set, each log entry is prefixed with: --[ asked / called / defined ] -- asked - name of `some_class` called - name of class for which a method is called defined - name of class where method is defined Must be used carefully, because it monkeypatches __getattribute__ call. Example: log_methods_calls('log.log', ShellBaseWidget) """ # test if file is writable open(fname, 'a').close() FILENAME = fname CLASS = some_class PREFIX = "--[ %(asked)s / %(called)s / %(defined)s ]--" if prefix != None: PREFIX = prefix MAXWIDTH = {'o_O': 10} # hack with editable closure dict, to align names def format_prefix(method, methodobj): """ --[ ShellBase / Internal / BaseEdit ]------- get_position """ classnames = { 'asked': CLASS.__name__, 'called': methodobj.__class__.__name__, 'defined': get_class_that_defined(method) } line = PREFIX % classnames MAXWIDTH['o_O'] = max(len(line), MAXWIDTH['o_O']) return line.ljust(MAXWIDTH['o_O'], '-') import types def __getattribute__(self, name): attr = object.__getattribute__(self, name) if type(attr) is not types.MethodType: return attr else: def newfunc(*args, **kwargs): log = open(FILENAME, 'a') prefix = format_prefix(attr, self) log.write('%s %s\n' % (prefix, name)) log.close() result = attr(*args, **kwargs) return result return newfunc some_class.__getattribute__ = __getattribute__