# Back In Time
# Copyright (C) 2008-2022 Oprea Dan, Bart de Koning, Richard Bailey, Germar Reitze
#
# 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 2 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import syslog
import os
import sys
import atexit
import bcolors
DEBUG = False # Set to "True" when passing "--debug" as cmd arg
SYSLOG_IDENTIFIER = 'backintime'
SYSLOG_MESSAGE_PREFIX = ''
# Labels for the syslog levels
_level_names = {
syslog.LOG_INFO: 'INFO',
syslog.LOG_ERR: 'ERROR',
syslog.LOG_WARNING: 'WARNING',
syslog.LOG_DEBUG: 'DEBUG'
}
[docs]
def openlog():
"""
Initialize the BiT logger system (which uses syslog)
Esp. sets the app name as identifier for the log entries in the syslog.
Attention: Call it in each sub process that uses logging.
"""
syslog.openlog(SYSLOG_IDENTIFIER)
atexit.register(closelog)
[docs]
def changeProfile(profile_id, profile_name):
global SYSLOG_MESSAGE_PREFIX
SYSLOG_MESSAGE_PREFIX = f'{profile_name}({profile_id}) :: '
[docs]
def closelog():
syslog.closelog()
def _do_syslog(message: str, level: int) -> str:
for line in wrapLine(message):
syslog.syslog(level, '{}{}: {}'.format(
SYSLOG_MESSAGE_PREFIX, _level_names[level], line))
[docs]
def error(msg, parent=None, traceDepth=0):
if DEBUG:
msg = '%s %s' % (_debugHeader(parent, traceDepth), msg)
print('%sERROR%s: %s' % (bcolors.FAIL, bcolors.ENDC, msg), file=sys.stderr)
_do_syslog(msg, syslog.LOG_ERR)
[docs]
def warning(msg, parent=None, traceDepth=0):
if DEBUG:
msg = '%s %s' % (_debugHeader(parent, traceDepth), msg)
print('%sWARNING%s: %s' % (bcolors.WARNING, bcolors.ENDC, msg),
file=sys.stderr)
_do_syslog(msg, syslog.LOG_WARNING)
[docs]
def info(msg, parent=None, traceDepth=0):
if DEBUG:
msg = '%s %s' % (_debugHeader(parent, traceDepth), msg)
print('%sINFO%s: %s' % (bcolors.OKGREEN, bcolors.ENDC, msg),
file=sys.stderr)
_do_syslog(msg, syslog.LOG_INFO)
[docs]
def debug(msg, parent=None, traceDepth=0):
if DEBUG:
msg = '%s %s' % (_debugHeader(parent, traceDepth), msg)
# Why does this code differ from eg. "error()"
# (where the following lines are NOT part of the "if")?
print('%sDEBUG%s: %s' % (bcolors.OKBLUE, bcolors.ENDC, msg),
file=sys.stderr)
_do_syslog(msg, syslog.LOG_DEBUG)
[docs]
def deprecated(parent=None):
"""Dev note (buhtz 2023-07-23): To my knowledge this function is called
only one time in BIT. I assume it could be replace with python's own
deprecation warning system.
"""
frame = sys._getframe(1)
fdir, fname = os.path.split(frame.f_code.co_filename)
fmodule = os.path.basename(fdir)
line = frame.f_lineno
if parent:
fclass = '%s.' %parent.__class__.__name__
else:
fclass = ''
func = frame.f_code.co_name
frameCaller = sys._getframe(2)
fdirCaller, fnameCaller = os.path.split(frameCaller.f_code.co_filename)
fmoduleCaller = os.path.basename(fdirCaller)
lineCaller = frameCaller.f_lineno
msg = '%s/%s:%s %s%s called from ' %(fmodule, fname, line, fclass, func)
msgCaller = '%s/%s:%s' %(fmoduleCaller, fnameCaller, lineCaller)
print('%sDEPRECATED%s: %s%s%s%s' %(bcolors.WARNING, bcolors.ENDC, msg, bcolors.OKBLUE, msgCaller, bcolors.ENDC), file=sys.stderr)
_do_syslog('DEPRECATED: %s%s' % (msg, msgCaller), syslog.LOG_WARNING)
def _debugHeader(parent, traceDepth):
frame = sys._getframe(2 + traceDepth)
fdir, fname = os.path.split(frame.f_code.co_filename)
fmodule = os.path.basename(fdir)
line = frame.f_lineno
fclass = '%s.' % parent.__class__.__name__ if parent else ''
func = frame.f_code.co_name
return '[%s/%s:%s %s%s]' % (fmodule, fname, line, fclass, func)
# This function was moved from tools.py to here to solve a circular
# import dependency between "tools" and "logger".
[docs]
def wrapLine(msg, size=950, delimiters='\t ', new_line_indicator = 'CONTINUE: '):
"""
Wrap line ``msg`` into multiple lines with each shorter than ``size``. Try
to break the line on ``delimiters``. New lines will start with
``new_line_indicator``.
Args:
msg (str): string that should get wrapped
size (int): maximum length of returned strings
delimiters (str): try to break ``msg`` on these characters
new_line_indicator (str): start new lines with this string
Yields:
str: lines with max ``size`` length
"""
# TODO Use "textwrap.wrap" instead (https://docs.python.org/3/library/textwrap.html)
# (which may change the output formatting and may affect unit tests then)
# To avoid duplicated argument values in calls this function could
# act as a wrapper-
if len(new_line_indicator) >= size - 1:
new_line_indicator = ''
while msg:
if len(msg) <= size:
yield(msg)
break
else:
line = ''
for look in range(size-1, size//2, -1):
if msg[look] in delimiters:
line, msg = msg[:look+1], new_line_indicator + msg[look+1:]
break
if not line:
line, msg = msg[:size], new_line_indicator + msg[size:]
yield(line)