2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# PyLg: module to facilitate and automate the process of writing runtime logs.
|
2018-01-17 21:45:06 +01:00
|
|
|
# Copyright (C) 2017 Wojciech Kozlowski <wk@wojciechkozlowski.eu>
|
2017-03-06 00:15:39 +01:00
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
from __future__ import print_function
|
2017-03-06 00:15:39 +01:00
|
|
|
from datetime import datetime
|
|
|
|
from functools import partial
|
2017-03-09 01:10:28 +01:00
|
|
|
import traceback
|
2017-03-06 00:15:39 +01:00
|
|
|
import warnings
|
2017-03-09 01:10:28 +01:00
|
|
|
import textwrap
|
2017-03-06 00:15:39 +01:00
|
|
|
import inspect
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
# Load settings.
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
from .loadSettings import *
|
2017-03-06 00:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
class ClassNameStack(object):
|
|
|
|
|
|
|
|
""" A class to keep a global stack of the class names of the
|
|
|
|
functions that are currently executing. The class name of the
|
|
|
|
last traced function that was called will be on top of the
|
|
|
|
stack. It is removed after it finishes executing.
|
|
|
|
"""
|
|
|
|
|
|
|
|
stack = []
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def insert(classname):
|
|
|
|
if CLASS_NAME_RESOLUTION:
|
|
|
|
ClassNameStack.stack.append(classname)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def pop():
|
|
|
|
if CLASS_NAME_RESOLUTION and ClassNameStack.stack:
|
|
|
|
ClassNameStack.stack.pop()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get():
|
|
|
|
if CLASS_NAME_RESOLUTION and ClassNameStack.stack:
|
|
|
|
return ClassNameStack.stack[-1]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-06 00:15:39 +01:00
|
|
|
class PyLg(object):
|
|
|
|
|
|
|
|
""" Class to handle the log file.
|
|
|
|
"""
|
|
|
|
|
|
|
|
wfile = None
|
2017-03-09 01:10:28 +01:00
|
|
|
filename = PYLG_FILE
|
2017-03-06 00:15:39 +01:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def set_filename(new_filename):
|
|
|
|
|
|
|
|
""" Change the file name of the log file. The change will be
|
|
|
|
rejected if the log file is already open.
|
|
|
|
|
|
|
|
:param str new_filename: The new file name for the log file.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if PyLg.wfile is None:
|
|
|
|
PyLg.filename = new_filename
|
|
|
|
else:
|
|
|
|
warnings.warn("PyLg wfile is open - cannot change filename")
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def write(string):
|
|
|
|
|
|
|
|
""" Write to the log file. A new log file is opened and
|
|
|
|
initialised if it has not been opened yet.
|
|
|
|
|
|
|
|
:param str string: The string to be written to the log file.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if PyLg.wfile is None:
|
|
|
|
PyLg.wfile = open(PyLg.filename, "w")
|
|
|
|
PyLg.wfile.write("=== Log initialised at " +
|
|
|
|
str(datetime.now()) + " ===\n\n")
|
|
|
|
|
|
|
|
PyLg.wfile.write(string)
|
2017-03-09 01:10:28 +01:00
|
|
|
PyLg.wfile.flush()
|
2017-03-06 00:15:39 +01:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def close():
|
|
|
|
|
|
|
|
""" Close the log file.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if PyLg.wfile is not None:
|
|
|
|
PyLg.wfile.close()
|
|
|
|
PyLg.wfile = None
|
|
|
|
else:
|
|
|
|
warnings.warn("PyLg wfile is not open - nothing to close")
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-06 00:15:39 +01:00
|
|
|
class TraceFunction(object):
|
|
|
|
|
|
|
|
""" Class that serves as a decorator to trace entry and exit from
|
|
|
|
functions. Used by appending @TraceFunction on top of the
|
|
|
|
definition of the function to trace.
|
|
|
|
"""
|
|
|
|
|
|
|
|
class TraceFunctionStruct(object):
|
|
|
|
|
|
|
|
""" Internal object to handle traced function properties.
|
|
|
|
"""
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
function = None
|
|
|
|
varnames = None
|
|
|
|
defaults = None
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
filename = None
|
|
|
|
lineno = None
|
|
|
|
classname = None
|
2017-03-06 00:15:39 +01:00
|
|
|
functionname = None
|
|
|
|
|
|
|
|
def __get__(self, obj, objtype):
|
|
|
|
|
|
|
|
""" Support for instance functions.
|
|
|
|
"""
|
|
|
|
|
|
|
|
return partial(self.__call__, obj)
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
|
|
|
""" Constructor for TraceFunction. Note that the behaviour is
|
|
|
|
different depending on whether TraceFunction is passed any
|
|
|
|
parameters. For details see __call__ in this class.
|
|
|
|
"""
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# Make sure this decorator is never called with no arguments.
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
assert args or kwargs
|
|
|
|
|
|
|
|
if args:
|
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
self.exception_warning = DEFAULT_EXCEPTION_WARNING
|
|
|
|
self.exception_tb_file = DEFAULT_EXCEPTION_TB_FILE
|
|
|
|
self.exception_tb_stderr = DEFAULT_EXCEPTION_TB_STDERR
|
|
|
|
self.exception_exit = DEFAULT_EXCEPTION_EXIT
|
|
|
|
|
2017-03-06 00:15:39 +01:00
|
|
|
self.trace_args = DEFAULT_TRACE_ARGS
|
2017-03-09 01:10:28 +01:00
|
|
|
self.trace_rv = DEFAULT_TRACE_RV
|
2017-03-17 00:17:34 +01:00
|
|
|
self.trace_rv_type = DEFAULT_TRACE_RV_TYPE
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# The function init_function will verify the input.
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
self.init_function(*args, **kwargs)
|
|
|
|
|
|
|
|
if kwargs:
|
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
exception_warning_str = 'exception_warning'
|
|
|
|
exception_tb_file_str = 'exception_tb_file'
|
|
|
|
exception_tb_stderr_str = 'exception_tb_stderr'
|
|
|
|
exception_exit_str = 'exception_exit'
|
|
|
|
|
2017-03-06 00:15:39 +01:00
|
|
|
trace_args_str = 'trace_args'
|
2017-03-09 01:10:28 +01:00
|
|
|
trace_rv_str = 'trace_rv'
|
2017-03-17 00:17:34 +01:00
|
|
|
trace_rv_type_str = 'trace_rv_type'
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------
|
2017-03-17 00:17:34 +01:00
|
|
|
# If kwargs is non-empty, args should be empty.
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
assert not args
|
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
try:
|
|
|
|
self.exception_warning = kwargs[exception_warning_str]
|
|
|
|
pylg_check_bool(self.exception_warning, "exception_warning")
|
|
|
|
except (KeyError, ImportError):
|
|
|
|
self.exception_warning = DEFAULT_EXCEPTION_WARNING
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.exception_tb_file = kwargs[exception_tb_file_str]
|
|
|
|
pylg_check_bool(self.exception_tb_file, "exception_tb_file")
|
|
|
|
except (KeyError, ImportError):
|
|
|
|
self.exception_tb_file = DEFAULT_EXCEPTION_TB_FILE
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.exception_tb_stderr = kwargs[exception_tb_stderr_str]
|
|
|
|
pylg_check_bool(self.exception_tb_stderr,
|
|
|
|
"exception_tb_stderr")
|
|
|
|
except (KeyError, ImportError):
|
|
|
|
self.exception_tb_stderr = DEFAULT_EXCEPTION_TB_STDERR
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.exception_exit = kwargs[exception_exit_str]
|
|
|
|
pylg_check_bool(self.exception_exit, "exception_exit")
|
|
|
|
except (KeyError, ImportError):
|
|
|
|
self.exception_exit = DEFAULT_EXCEPTION_EXIT
|
|
|
|
|
|
|
|
try:
|
2017-03-06 00:15:39 +01:00
|
|
|
self.trace_args = kwargs[trace_args_str]
|
2017-03-17 00:17:34 +01:00
|
|
|
pylg_check_bool(self.trace_args, "trace_args")
|
|
|
|
except (KeyError, ImportError):
|
2017-03-06 00:15:39 +01:00
|
|
|
self.trace_args = DEFAULT_TRACE_ARGS
|
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
try:
|
2017-03-06 00:15:39 +01:00
|
|
|
self.trace_rv = kwargs[trace_rv_str]
|
2017-03-17 00:17:34 +01:00
|
|
|
pylg_check_bool(self.trace_rv, "trace_rv")
|
|
|
|
except (KeyError, ImportError):
|
2017-03-06 00:15:39 +01:00
|
|
|
self.trace_rv = DEFAULT_TRACE_RV
|
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
try:
|
|
|
|
self.trace_rv_type = kwargs[trace_rv_type_str]
|
|
|
|
pylg_check_bool(self.trace_rv_type, "trace_rv_type")
|
|
|
|
except (KeyError, ImportError):
|
|
|
|
self.trace_rv_type = DEFAULT_TRACE_RV_TYPE
|
|
|
|
|
2017-03-06 00:15:39 +01:00
|
|
|
self.function = None
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
|
|
|
|
""" The actual wrapper that is called when a call to a
|
|
|
|
decorated function is made. It also handles extra
|
|
|
|
initialisation when parameters are passed to
|
|
|
|
TraceFunction.
|
|
|
|
|
|
|
|
:return: The return value of the decorated function.
|
|
|
|
"""
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# __call__ has to behave differently depending on whether the
|
|
|
|
# decorator has been given any parameters. The reason for this
|
|
|
|
# is as follows:
|
|
|
|
#
|
|
|
|
# @TraceFunction
|
|
|
|
# decorated_function
|
|
|
|
#
|
|
|
|
# translates to TraceFunction(decorated_function)
|
|
|
|
#
|
|
|
|
# @TraceFunction(*args, **kwargs)
|
|
|
|
# decorated_function
|
|
|
|
#
|
|
|
|
# translates to TraceFunction(*args, **kwargs)(decorated_function)
|
|
|
|
#
|
|
|
|
# In both cases, the result should be a callable object which
|
|
|
|
# will be called whenever the decorated function is called. In
|
|
|
|
# the first case, the callable object is an instance of
|
|
|
|
# TraceFunction, in the latter case the return value of
|
|
|
|
# TraceFunction.__call__ is the callable object.
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
|
|
|
|
if self.function is None:
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# If the decorator has been passed a parameter, __init__
|
|
|
|
# will not define self.function and __call__ will be
|
|
|
|
# called immediately after __init__ with the decorated
|
|
|
|
# function as the only parameter. Therefore, this __call__
|
|
|
|
# function has to return the callable object that is meant
|
|
|
|
# to be called every time the decorated function is
|
|
|
|
# called. Here, self is returned in order to return the
|
|
|
|
# object as the callable handle for the decorated
|
|
|
|
# function. This if block should be hit only once at most
|
|
|
|
# and only during initialisation.
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
self.init_function(*args, **kwargs)
|
|
|
|
return self
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# The actual decorating.
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
ClassNameStack.insert(self.function.classname)
|
|
|
|
self.trace_entry(*args, **kwargs)
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
try:
|
|
|
|
rv = self.function.function(*args, **kwargs)
|
|
|
|
except Exception as e:
|
|
|
|
self.trace_exception(e)
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
if self.exception_exit:
|
|
|
|
warnings.warn("Exit forced by EXCEPTION_EXIT")
|
2017-03-09 01:10:28 +01:00
|
|
|
os._exit(1)
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
raise
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
self.trace_exit(rv)
|
|
|
|
ClassNameStack.pop()
|
2017-03-06 00:15:39 +01:00
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
def init_function(self, *args, **kwargs):
|
|
|
|
|
|
|
|
""" Function to initialise the TraceFunctionStruct kept by the
|
|
|
|
decorator.
|
|
|
|
"""
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# This function should only ever be called with one parameter
|
|
|
|
# - the function to be decorated. These checks are done here,
|
|
|
|
# rather than by the caller, as anything that calls this
|
|
|
|
# function should also have been called with the decorated
|
|
|
|
# function as its only parameter.
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
assert not kwargs
|
|
|
|
assert len(args) == 1
|
|
|
|
assert callable(args[0])
|
|
|
|
|
|
|
|
self.function = TraceFunction.TraceFunctionStruct()
|
|
|
|
|
|
|
|
self.function.function = args[0]
|
|
|
|
|
|
|
|
argspec = inspect.getargspec(self.function.function)
|
|
|
|
|
|
|
|
self.function.varnames = argspec.args
|
|
|
|
if argspec.defaults is not None:
|
|
|
|
self.function.defaults = dict(
|
|
|
|
zip(argspec.args[-len(argspec.defaults):],
|
|
|
|
argspec.defaults))
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# init_function is called from either __init__ or __call__ and
|
|
|
|
# we want the frame before that.
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
frames_back = 2
|
2017-03-09 01:10:28 +01:00
|
|
|
caller_frame = inspect.stack()[frames_back]
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
self.function.filename = os.path.basename(caller_frame[1])
|
|
|
|
self.function.lineno = caller_frame[2]
|
|
|
|
self.function.classname = caller_frame[3]
|
2017-03-06 00:15:39 +01:00
|
|
|
self.function.functionname = self.function.function.__name__
|
|
|
|
|
|
|
|
def trace_entry(self, *args, **kwargs):
|
|
|
|
|
|
|
|
""" Called on function entry. This function collects all the
|
|
|
|
function arguments and constructs a message to pass to
|
|
|
|
trace.
|
|
|
|
"""
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# The ENTRY message.
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
msg = "-> ENTRY"
|
|
|
|
if args or kwargs:
|
|
|
|
msg += ": "
|
|
|
|
|
|
|
|
n_args = len(args)
|
|
|
|
if self.trace_args:
|
|
|
|
for arg in range(n_args):
|
2017-03-09 01:10:28 +01:00
|
|
|
|
|
|
|
if not TRACE_SELF and \
|
|
|
|
self.function.varnames[arg] == "self":
|
|
|
|
continue
|
|
|
|
|
2017-03-06 00:15:39 +01:00
|
|
|
msg += (self.function.varnames[arg] + " = " +
|
2017-03-09 01:10:28 +01:00
|
|
|
self.get_value_string(args[arg]) + ", ")
|
2017-03-06 00:15:39 +01:00
|
|
|
|
|
|
|
for name in self.function.varnames[n_args:]:
|
|
|
|
msg += name + " = "
|
|
|
|
if name in kwargs:
|
2017-03-09 01:10:28 +01:00
|
|
|
value = kwargs[name]
|
2017-03-06 00:15:39 +01:00
|
|
|
else:
|
2017-03-09 01:10:28 +01:00
|
|
|
value = self.function.defaults[name]
|
2017-03-14 23:12:24 +01:00
|
|
|
msg += self.get_value_string(value) + ", "
|
2017-03-06 00:15:39 +01:00
|
|
|
|
|
|
|
msg = msg[:-2]
|
|
|
|
|
|
|
|
else:
|
|
|
|
msg += "---"
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
trace(msg, function=self.function)
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
def trace_exit(self, rv=None):
|
2017-03-06 00:15:39 +01:00
|
|
|
|
|
|
|
""" Called on function exit to log the fact that a function has
|
|
|
|
finished executing.
|
|
|
|
|
|
|
|
:param rv: The return value of the traced function.
|
|
|
|
"""
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# The EXIT message.
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
msg = "<- EXIT "
|
|
|
|
if rv is not None:
|
|
|
|
msg += ": "
|
|
|
|
if self.trace_rv:
|
2017-03-09 01:10:28 +01:00
|
|
|
msg += self.get_value_string(rv)
|
2017-03-06 00:15:39 +01:00
|
|
|
else:
|
|
|
|
msg += "---"
|
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
if self.trace_rv_type:
|
|
|
|
msg += " (type: " + type(rv).__name__ + ")"
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
trace(msg, function=self.function)
|
2017-03-06 00:15:39 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
def trace_exception(self, exception):
|
|
|
|
|
|
|
|
""" Called when a function terminated due to an exception.
|
|
|
|
|
|
|
|
:param exception: The raised exception.
|
|
|
|
"""
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# The EXIT message.
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
core_msg = type(exception).__name__ + " RAISED"
|
|
|
|
msg = "<- EXIT : " + core_msg
|
|
|
|
|
|
|
|
if str(exception) is not "":
|
|
|
|
msg += " - " + str(exception)
|
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
if self.exception_warning:
|
2017-03-06 00:15:39 +01:00
|
|
|
warnings.warn(core_msg, RuntimeWarning)
|
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
if self.exception_tb_file:
|
|
|
|
msg += "\n--- EXCEPTION ---\n"
|
|
|
|
msg += traceback.format_exc()
|
|
|
|
msg += "-----------------"
|
|
|
|
|
|
|
|
if self.exception_tb_stderr:
|
|
|
|
print("--- EXCEPTION ---", file=sys.stderr)
|
|
|
|
traceback.print_exc(file=sys.stderr)
|
|
|
|
print("-----------------", file=sys.stderr)
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
trace(msg, function=self.function)
|
2017-03-06 00:15:39 +01:00
|
|
|
return
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
def get_value_string(self, value):
|
|
|
|
|
|
|
|
""" Convert value to a string for the log.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if isinstance(value, list) and COLLAPSE_LISTS:
|
|
|
|
return self.collapse_list(value)
|
|
|
|
elif isinstance(value, dict) and COLLAPSE_DICTS:
|
|
|
|
return self.collapse_dict(value)
|
|
|
|
else:
|
|
|
|
return str(value)
|
|
|
|
|
|
|
|
def collapse_list(self, ll):
|
|
|
|
return "[ len=" + str(len(ll)) + " ]"
|
|
|
|
|
|
|
|
def collapse_dict(self, dd):
|
|
|
|
return "{ len=" + str(len(dd)) + " }"
|
|
|
|
|
|
|
|
|
|
|
|
def trace(message, function=None):
|
2017-03-06 00:15:39 +01:00
|
|
|
|
|
|
|
""" Writes message to the log file. It will also log the time,
|
|
|
|
filename, line number and function name.
|
|
|
|
|
|
|
|
:param str message: The log message.
|
|
|
|
:param function: A TraceFunctionStruct object if called from within
|
|
|
|
TraceFunction.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if function is None:
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# If there is no function object, we need to work out
|
|
|
|
# where the trace call was made from.
|
2017-03-09 01:10:28 +01:00
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
frames_back = 1
|
|
|
|
caller_frame = inspect.stack()[frames_back]
|
2017-03-06 00:15:39 +01:00
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
filename = os.path.basename(caller_frame[1])
|
|
|
|
lineno = caller_frame[2]
|
2017-03-06 00:15:39 +01:00
|
|
|
functionname = caller_frame[3]
|
|
|
|
|
|
|
|
else:
|
2017-03-09 01:10:28 +01:00
|
|
|
filename = function.filename
|
|
|
|
lineno = function.lineno
|
2017-03-06 00:15:39 +01:00
|
|
|
functionname = function.functionname
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# -------------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# If CLASS_NAME_RESOLUTION is enabled, the top element of the
|
|
|
|
# stack should be the class name of the function from which this
|
|
|
|
# trace call is made. This cannot be policed so the user must make
|
|
|
|
# sure this is the case by ensuring that trace is only called
|
|
|
|
# outside of any function or from within functions that have the
|
|
|
|
# @TraceFunction decorator.
|
2017-03-09 01:10:28 +01:00
|
|
|
# -------------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
classname = ClassNameStack.get()
|
|
|
|
if classname is not None and classname != "<module>":
|
|
|
|
functionname = classname + "." + functionname
|
|
|
|
|
2017-03-09 01:10:28 +01:00
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Generate the string based on the settings.
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
msg = ""
|
|
|
|
|
|
|
|
if TRACE_TIME:
|
|
|
|
msg += datetime.now().strftime(TIME_FORMAT) + " "
|
|
|
|
|
|
|
|
if TRACE_FILENAME:
|
|
|
|
msg += '{filename:{w}.{w}} '.format(filename=filename,
|
|
|
|
w=FILENAME_COLUMN_WIDTH)
|
|
|
|
|
|
|
|
if TRACE_LINENO:
|
|
|
|
msg += '{lineno:0{w}}: '.format(lineno=lineno, w=LINENO_WIDTH)
|
|
|
|
|
|
|
|
if TRACE_FUNCTION:
|
|
|
|
msg += '{function:{w}.{w}} '.format(function=functionname,
|
|
|
|
w=FUNCTION_COLUMN_WIDTH)
|
|
|
|
|
|
|
|
if TRACE_MESSAGE:
|
|
|
|
|
|
|
|
message = str(message)
|
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Get the length of the trace line so far
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
premsglen = len(msg)
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Split into lines which will be handled separately.
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
lines = message.splitlines()
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
if not lines:
|
|
|
|
lines = [""]
|
|
|
|
|
|
|
|
for idx, line in enumerate(lines):
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------
|
|
|
|
# Wrap the text.
|
|
|
|
# -----------------------------------------------------------------
|
2017-03-17 00:17:34 +01:00
|
|
|
wrapped = textwrap.wrap(line, MESSAGE_WIDTH)
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-14 23:32:19 +01:00
|
|
|
if not wrapped:
|
|
|
|
wrapped = [""]
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-17 00:17:34 +01:00
|
|
|
# -----------------------------------------------------------------
|
|
|
|
# If this is the first line of the whole trace message, it
|
|
|
|
# gets special treatment as it doesn't need whitespace in
|
|
|
|
# front of it. Otherwise, align it with the previous line.
|
|
|
|
# -----------------------------------------------------------------
|
|
|
|
if idx != 0:
|
|
|
|
msg += '{:{w}}'.format('', w=premsglen)
|
|
|
|
|
2017-03-14 23:32:19 +01:00
|
|
|
if MESSAGE_WRAP:
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-14 23:32:19 +01:00
|
|
|
# -------------------------------------------------------------
|
2017-03-17 00:17:34 +01:00
|
|
|
# The first wrapped line gets special treatment as any
|
|
|
|
# whitespace should already be prepended.
|
2017-03-14 23:32:19 +01:00
|
|
|
# -------------------------------------------------------------
|
|
|
|
msg += wrapped[0]
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-14 23:32:19 +01:00
|
|
|
# -------------------------------------------------------------
|
|
|
|
# Print the remaining lines. Append whitespace to
|
|
|
|
# align it with the first line.
|
|
|
|
# -------------------------------------------------------------
|
2017-03-17 00:17:34 +01:00
|
|
|
for wrline in wrapped[1:]:
|
|
|
|
msg += '\n' + '{:{w}}'.format('', w=premsglen) + wrline
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-14 23:32:19 +01:00
|
|
|
else:
|
|
|
|
# -------------------------------------------------------------
|
|
|
|
# The message is not being wrapped.
|
|
|
|
# -------------------------------------------------------------
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-14 23:32:19 +01:00
|
|
|
if MESSAGE_MARK_TRUNCATION and wrapped[1:]:
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
# We want to mark truncated lines so we need to
|
|
|
|
# determine if the line is being truncated. If it
|
|
|
|
# is we replace the last character with '\'.
|
|
|
|
# ---------------------------------------------------------
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-14 23:32:19 +01:00
|
|
|
if MESSAGE_WIDTH > 1:
|
2017-03-17 00:17:34 +01:00
|
|
|
wrapped = textwrap.wrap(wrapped[0], MESSAGE_WIDTH - 1)
|
2017-03-14 23:32:19 +01:00
|
|
|
assert wrapped
|
2017-03-09 01:10:28 +01:00
|
|
|
|
2017-03-14 23:32:19 +01:00
|
|
|
msg += ('{m:{w}}'.format(m=wrapped[0],
|
|
|
|
w=MESSAGE_WIDTH - 1) +
|
|
|
|
'\\')
|
2017-03-09 01:10:28 +01:00
|
|
|
|
|
|
|
else:
|
2017-03-14 23:32:19 +01:00
|
|
|
assert MESSAGE_WIDTH == 1
|
|
|
|
msg += '\\'
|
|
|
|
|
|
|
|
else:
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
# Either the message is not being truncated or
|
|
|
|
# MESSAGE_MARK_TRUNCATION is False.
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
msg += wrapped[0]
|
2017-03-09 01:10:28 +01:00
|
|
|
|
|
|
|
# -----------------------------------------------------------------
|
2017-03-17 00:17:34 +01:00
|
|
|
# Terminate with a newline.
|
2017-03-09 01:10:28 +01:00
|
|
|
# -----------------------------------------------------------------
|
2017-03-17 00:17:34 +01:00
|
|
|
msg += "\n"
|
2017-03-09 01:10:28 +01:00
|
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
2017-03-06 00:15:39 +01:00
|
|
|
# Write the data to the log file.
|
2017-03-09 01:10:28 +01:00
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
PyLg.write(msg)
|