PyLg/pylg/pylg.py

613 lines
22 KiB
Python
Raw Normal View History

2019-07-31 20:08:05 +02:00
"""PyLg: facilitate and automate the process of writing runtime logs."""
2017-03-06 00:15:39 +01:00
from datetime import datetime
from functools import partial
2019-07-31 20:08:05 +02:00
from typing import Optional
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
2019-07-31 20:08:05 +02:00
from pylg.settings import _pylg_check_bool
import pylg.settings
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
class ClassNameStack:
"""Stack for the class names of the currently executing functions.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
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.
2017-03-06 00:15:39 +01:00
"""
stack = []
2019-07-31 20:08:05 +02:00
@classmethod
def disable(cls) -> None:
"""Disable the stack.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
This is achieved by rendering all functions no-ops.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
WARNING: This is an irreversible operation.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
"""
cls.insert = lambda _classname: None
cls.pop = lambda: None
cls.get = lambda: None
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
@classmethod
def insert(cls, classname: str) -> None:
"""Insert an entry.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
Parameters
----------
classname : str
The class name to insert.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
"""
cls.stack.append(classname)
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
@classmethod
def pop(cls) -> str:
"""str: Return top-most entry and remove it."""
return cls.stack.pop() if cls.stack else None
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
@classmethod
def peek(cls) -> str:
"""str: Return the top-most entry without removing it."""
return ClassNameStack.stack[-1] if cls.stack else None
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
class PyLg:
"""Class to handle the log file."""
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
wfile = None
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
@classmethod
def configure(cls, user_settings_path: Optional[str] = None) -> None:
"""PyLg initialisation.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
Parameters
----------
user_settings_path : Optional[str]
Path to the user settings file.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
"""
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
# ---------------------------------------------------------------------
# Load the settings.
# ---------------------------------------------------------------------
settings = pylg.settings.load(user_settings_path)
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
# ---------------------------------------------------------------------
# Local variables for settings to avoid dictionary access which is only
# O(1) on average. Admittedly the size of the settings dict is not
# large, but these accesses will be frequent.
# ---------------------------------------------------------------------
cls.pylg_enable = settings["pylg_enable"]
cls.pylg_file = settings["pylg_file"]
cls.default_exception_warning = settings["default_exception_warning"]
cls.default_exception_tb_file = settings["default_exception_tb_file"]
cls.default_exception_tb_stderr = \
settings["default_exception_tb_stderr"]
cls.default_exception_exit = settings["default_exception_exit"]
cls.trace_time = settings["trace_time"]
cls.time_format = settings["time_format"]
cls.trace_filename = settings["trace_filename"]
cls.filename_column_width = settings["filename_column_width"]
cls.trace_lineno = settings["trace_lineno"]
cls.lineno_width = settings["lineno_width"]
cls.trace_function = settings["trace_function"]
cls.function_column_width = settings["function_column_width"]
cls.class_name_resolution = settings["class_name_resolution"]
cls.trace_message = settings["trace_message"]
cls.message_width = settings["message_width"]
cls.message_wrap = settings["message_wrap"]
cls.message_mark_truncation = settings["message_mark_truncation"]
cls.trace_self = settings["trace_self"]
cls.collapse_lists = settings["collapse_lists"]
cls.collapse_dicts = settings["collapse_dicts"]
cls.default_trace_args = settings["default_trace_args"]
cls.default_trace_rv = settings["default_trace_rv"]
cls.default_trace_rv_type = settings["default_trace_rv_type"]
if not cls.class_name_resolution:
ClassNameStack.disable()
cls.wfile = open(cls.pylg_file, "w")
cls.wfile.write(
"=== Log initialised at {} ===\n\n".format(datetime.now())
)
@classmethod
def write(cls, string: str):
"""Write to the log file.
A new log file is opened and initialised if it has not been opened yet.
Parameters
----------
string : str
The string to be written to the log file.
2017-03-06 00:15:39 +01:00
"""
2019-07-31 20:08:05 +02:00
cls.wfile.write(string)
cls.wfile.flush()
@classmethod
def close(cls):
"""Close the log file."""
if cls.wfile is not None:
cls.wfile.close()
cls.wfile = None
2017-03-06 00:15:39 +01:00
else:
warnings.warn("PyLg wfile is not open - nothing to close")
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
# pylint: disable=too-many-instance-attributes
class TraceFunction:
"""Decorator to trace entry and exit from functions.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
Used by appending @TraceFunction on top of the definition of the function
to trace.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
"""
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
# pylint: disable=too-few-public-methods
class TraceFunctionStruct:
"""Internal object to handle traced function properties."""
2017-03-06 00:15:39 +01:00
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):
2019-07-31 20:08:05 +02:00
"""Support for instance functions."""
2017-03-06 00:15:39 +01:00
return partial(self.__call__, obj)
def __init__(self, *args, **kwargs):
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:
2019-07-31 20:08:05 +02:00
self._exception_warning = PyLg.default_exception_warning
self._exception_tb_file = PyLg.default_exception_tb_file
self._exception_tb_stderr = PyLg.default_exception_tb_stderr
self._exception_exit = PyLg.default_exception_exit
2017-03-17 00:17:34 +01:00
2019-07-31 20:08:05 +02:00
self._trace_args = PyLg.default_trace_args
self._trace_rv = PyLg.default_trace_rv
self._trace_rv_type = PyLg.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:
2019-07-31 20:08:05 +02:00
# -----------------------------------------------------------------
# If kwargs is non-empty, args should be empty.
# -----------------------------------------------------------------
assert not args
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
2019-07-31 20:08:05 +02:00
kwopts = {}
for option, default in [
(exception_warning_str, PyLg.default_exception_warning),
(exception_tb_file_str, PyLg.default_exception_tb_file),
(exception_tb_stderr_str,
PyLg.default_exception_tb_stderr),
(exception_exit_str, PyLg.default_exception_exit),
(trace_args_str, PyLg.default_trace_args),
(trace_rv_str, PyLg.default_trace_rv),
(trace_rv_type_str, PyLg.default_trace_rv_type),
]:
kwopts[option] = kwargs.get(option, default)
if not _pylg_check_bool(kwopts[option]):
raise ValueError(
"Invalid type for {} - should be bool, is type {}"
.format(option, type(kwopts[option]).__name__)
)
self._exception_warning = kwopts[exception_warning_str]
self._exception_tb_file = kwopts[exception_tb_file_str]
self._exception_tb_stderr = kwopts[exception_tb_stderr_str]
self._exception_exit = kwopts[exception_exit_str]
self._trace_args = kwopts[trace_args_str]
self._trace_rv = kwopts[trace_rv_str]
self._trace_rv_type = kwopts[trace_rv_type_str]
2017-03-17 00:17:34 +01:00
2017-03-06 00:15:39 +01:00
self.function = None
def __call__(self, *args, **kwargs):
2019-07-31 20:08:05 +02:00
"""Wrapper that is called when a call to a decorated function is made.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
It also handles extra initialisation when parameters are passed to
TraceFunction.
2017-03-06 00:15:39 +01:00
"""
2017-03-09 01:10:28 +01:00
# ---------------------------------------------------------------------
2019-07-31 20:08:05 +02:00
# __call__ has to behave differently depending on whether the decorator
# has been given any parameters. The reason for this is as follows:
2017-03-06 00:15:39 +01:00
#
# @TraceFunction
# decorated_function
#
# translates to TraceFunction(decorated_function)
#
# @TraceFunction(*args, **kwargs)
# decorated_function
#
# translates to TraceFunction(*args, **kwargs)(decorated_function)
#
2019-07-31 20:08:05 +02:00
# 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
# -----------------------------------------------------------------
2019-07-31 20:08:05 +02: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)
2019-07-31 20:08:05 +02:00
except Exception as exc:
self.trace_exception(exc)
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
if self._exception_exit:
2017-03-17 00:17:34 +01:00
warnings.warn("Exit forced by EXCEPTION_EXIT")
2019-07-31 20:08:05 +02:00
# pylint: disable=protected-access
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):
2019-07-31 20:08:05 +02:00
"""Initialise the TraceFunctionStruct kept by the decorator."""
2017-03-06 00:15:39 +01:00
2017-03-09 01:10:28 +01:00
# ---------------------------------------------------------------------
2019-07-31 20:08:05 +02: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
# ---------------------------------------------------------------------
2019-07-31 20:08:05 +02: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):
2019-07-31 20:08:05 +02:00
"""Handle function entry.
This function collects all the function arguments and constructs a
message to pass to trace.
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 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)
2019-07-31 20:08:05 +02:00
if self._trace_args:
2017-03-06 00:15:39 +01:00
for arg in range(n_args):
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
if not PyLg.trace_self and \
2017-03-09 01:10:28 +01:00
self.function.varnames[arg] == "self":
continue
2019-07-31 20:08:05 +02:00
msg += "{} = {}, ".format(
self.function.varnames[arg],
self.get_value_string(args[arg])
)
2017-03-06 00:15:39 +01:00
for name in self.function.varnames[n_args:]:
2019-07-31 20:08:05 +02:00
msg += "{} = {}, ".format(
name, kwargs.get(name, self.function.defaults[name])
)
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):
2019-07-31 20:08:05 +02:00
"""Handle function exit.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
Log the fact that a function has finished executing.
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 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 += ": "
2019-07-31 20:08:05 +02:00
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 += "---"
2019-07-31 20:08:05 +02:00
if self._trace_rv_type:
msg += " (type: {})".format(type(rv).__name__)
2017-03-17 00:17:34 +01:00
2017-03-09 01:10:28 +01:00
trace(msg, function=self.function)
2017-03-06 00:15:39 +01:00
def trace_exception(self, exception):
2019-07-31 20:08:05 +02:00
"""Called when a function terminated due to an exception.
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 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
2019-07-31 20:08:05 +02:00
if str(exception) != "":
2017-03-06 00:15:39 +01:00
msg += " - " + str(exception)
2019-07-31 20:08:05 +02:00
if self._exception_warning:
2017-03-06 00:15:39 +01:00
warnings.warn(core_msg, RuntimeWarning)
2019-07-31 20:08:05 +02:00
if self._exception_tb_file:
2017-03-17 00:17:34 +01:00
msg += "\n--- EXCEPTION ---\n"
msg += traceback.format_exc()
msg += "-----------------"
2019-07-31 20:08:05 +02:00
if self._exception_tb_stderr:
2017-03-17 00:17:34 +01:00
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)
2019-07-31 20:08:05 +02:00
@staticmethod
def get_value_string(value):
"""Convert value to a string for the log."""
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
if isinstance(value, list) and PyLg.collapse_lists:
return "[ len={} ]".format(len(value))
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
if isinstance(value, dict) and PyLg.collapse_dicts:
return "{{ len={} }}".format(len(value))
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
return "{}".format(value)
2017-03-09 01:10:28 +01:00
def trace(message, function=None):
2019-07-31 20:08:05 +02:00
"""Write message to the log file.
It will also log the time, filename, line number and function name.
2017-03-06 00:15:39 +01:00
2019-07-31 20:08:05 +02:00
Parameters
----------
message : str
The log message.
function : Optional[TraceFunctionStruct]
A TraceFunctionStruct object if called from within TraceFunction.
2017-03-06 00:15:39 +01:00
"""
if function is None:
2017-03-09 01:10:28 +01:00
# ---------------------------------------------------------------------
2019-07-31 20:08:05 +02: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
# -------------------------------------------------------------------------
2019-07-31 20:08:05 +02: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>":
2019-07-31 20:08:05 +02:00
functionname = "{}.{}".format(classname, functionname)
2017-03-06 00:15:39 +01:00
2017-03-09 01:10:28 +01:00
# -------------------------------------------------------------------------
# Generate the string based on the settings.
# -------------------------------------------------------------------------
msg = ""
2019-07-31 20:08:05 +02:00
if PyLg.trace_time:
msg += datetime.now().strftime(PyLg.time_format) + " "
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
if PyLg.trace_filename:
msg += "{filename:{w}.{w}} ".format(
filename=filename,
w=PyLg.filename_column_width
)
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
if PyLg.trace_lineno:
msg += "{lineno:0{w}}: ".format(lineno=lineno, w=PyLg.lineno_width)
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
if PyLg.trace_function:
msg += "{function:{w}.{w}} ".format(
function=functionname,
w=PyLg.function_column_width
)
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
if PyLg.trace_message:
2017-03-09 01:10:28 +01:00
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.
# -----------------------------------------------------------------
2019-07-31 20:08:05 +02:00
wrapped = textwrap.wrap(line, PyLg.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
# -----------------------------------------------------------------
2019-07-31 20:08:05 +02: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.
2017-03-17 00:17:34 +01:00
# -----------------------------------------------------------------
if idx != 0:
2019-07-31 20:08:05 +02:00
msg += "{:{w}}".format("", w=premsglen)
2017-03-17 00:17:34 +01:00
2019-07-31 20:08:05 +02:00
if PyLg.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
# -------------------------------------------------------------
2019-07-31 20:08:05 +02:00
# Print the remaining lines. Append whitespace to align it with
# the first line.
2017-03-14 23:32:19 +01:00
# -------------------------------------------------------------
2017-03-17 00:17:34 +01:00
for wrline in wrapped[1:]:
2019-07-31 20:08:05 +02:00
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
2019-07-31 20:08:05 +02:00
if PyLg.message_mark_truncation and wrapped[1:]:
2017-03-14 23:32:19 +01:00
# ---------------------------------------------------------
2019-07-31 20:08:05 +02:00
# 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-14 23:32:19 +01:00
# ---------------------------------------------------------
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
if PyLg.message_width > 1:
wrapped = textwrap.wrap(
wrapped[0],
PyLg.message_width - 1
)
2017-03-14 23:32:19 +01:00
assert wrapped
2017-03-09 01:10:28 +01:00
2019-07-31 20:08:05 +02:00
msg += "{m:{w}}\\".format(
m=wrapped[0],
w=PyLg.message_width - 1,
)
2017-03-09 01:10:28 +01:00
else:
2019-07-31 20:08:05 +02:00
assert PyLg.message_width == 1
2017-03-14 23:32:19 +01:00
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)