# MusicDB, a music manager with web-bases UI that focus on music.
# Copyright (C) 2017 - 2021 Ralf Stemmer <ralf.stemmer@gmx.net>
#
# 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/>.
"""
This library provides the excessive logging system of MusicDB.
For details about the file and directory structure for logging information into files see :doc:`/basics/data`.
"""
import logging
import sys
import os
import stat
from systemd import journal
[docs]class MDBLogFormatter(logging.Formatter):
"""
This class handles log messages.
The class is derived from `logging.Formatter <https://docs.python.org/3/library/logging.html#formatter-objects>`_.
It sets the *loglevel* of the modules listed in configuration under ``[log]->ignore`` to WARNING.
Furthermore this class defines how the log entries will look like.
Args:
ignorelist: List of module names that shall not appear in the logs
"""
start_fmt= "\033[1;34m LOG[\033[0;34m%(asctime)s\033[1;34m|"
file_fmt = "\033[0;35m%(filename)s\033[0;34m:\033[0;35m%(funcName)s\033[0;34m.\033[0;31m%(lineno)d\033[1;34m: "
short = "\033[0;35m%(filename)s\033[1;34m: "
#debug_format = "\033[1;34m LOG[\033[0;35mDEBUG\033[1;34m] " + file_format + "\033[1;30m%(message)s [%(module)s]"
debug_format = start_fmt + "\033[0;35mDEBUG\033[1;34m] " + file_fmt + "\033[1;30m%(message)s\033[0m"
info_format = start_fmt + "\033[0;36mINFO \033[1;34m] " + short + "\033[1;34m%(message)s\033[0m"
warning_format = start_fmt + "\033[0;33mWARN \033[1;34m] " + file_fmt + "\033[1;33m%(message)s\033[0m"
error_format = start_fmt + "\033[0;31mERROR\033[1;34m] " + file_fmt + "\033[1;31m%(message)s\033[0m"
critical_format = start_fmt + "\033[1;31mFATAL\033[1;34m] " + file_fmt + "\033[1;31m%(message)s\033[0m"
# %(asctime)s
def __init__(self, ignorelist):
logging.Formatter.__init__(self, datefmt="%Y-%m-%d %H:%M:%S")
# reduce spam from third party libraries
for entry in ignorelist:
logging.getLogger(entry).setLevel(logging.WARNING)
logging.root.setLevel(logging.DEBUG)
[docs] def format(self, record):
"""
Overloads the `format method of logging.Formatter <https://docs.python.org/3/library/logging.html#logging.Formatter.format>`_
to apply coloring and add additional information to the messages.
Source: http://stackoverflow.com/questions/1343227/can-pythons-logging-format-be-modified-depending-on-the-message-log-level
"""
# Save the original format configured by the user
# when the logger formatter was instantiated
format_orig = self._style._fmt
# Replace the original format with one customized by logging level
if record.levelno == logging.DEBUG:
self._style._fmt = MDBLogFormatter.debug_format
elif record.levelno == logging.INFO:
self._style._fmt = MDBLogFormatter.info_format
elif record.levelno == logging.WARNING:
self._style._fmt = MDBLogFormatter.warning_format
elif record.levelno == logging.ERROR:
self._style._fmt = MDBLogFormatter.error_format
elif record.levelno == logging.CRITICAL:
self._style._fmt = MDBLogFormatter.critical_format
# Call the original formatter class to do the grunt work
result = logging.Formatter.format(self, record)
# Restore the original format configured by the user
self._style._fmt = format_orig
return result
[docs]class MusicDBLogger():
"""
This class provides the logging management itself and handles the logging configuration.
"""
loglevelmap = {}
loglevelmap["DEBUG"] = logging.DEBUG
loglevelmap["INFO"] = logging.INFO
loglevelmap["WARNING"] = logging.WARNING
loglevelmap["ERROR"] = logging.ERROR
loglevelmap["CRITICAL"] = logging.CRITICAL
def __init__(self):
# create default output handler setup
self.handler = [] # list of handler. At least one: stderr. Maybe a file for more details
self.Reconfigure()
[docs] def SetFilePermissions(self, path):
"""
This method set the access permission of a file to "rw-rw----".
If the file in *path* cannot be accessed, an error gets printed to ``stderr``
and MusicDB gets exited with error code ``1``.
Args:
path (str): Absolute path to a log file
Returns:
``True`` on success. On Error MusicDB gets terminated.
"""
try:
with open(path, "a"):
pass
except Exception as e:
print("\033[1;31mFATAL ERROR: Opening log file failed with error: %s\033[0m (%s)"%(str(e), str(path)), file=sys.stderr)
exit(1)
try:
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP) # rw-rw----
except Exception as e:
pass
#print("\033[1;33mWARNING: Setting permissions of log file failed with error: %s\033[0m (%s)"%(str(e), str(path)), file=sys.stderr)
return True
[docs] def Reconfigure(self, logpath="stderr", loglevelname="DEBUG", debugpath=None, config=None):
"""
This method allows to reconfigure the setting for the MusicDB logger.
Args:
path (str): A "path" where the logs will be written. Use ``"stdout"`` or ``"stderr"`` to show them on the screen. Use ``"journal"`` to write to SystemDs journal.
loglevelname (str): Name of the lowest log level that shall be shown: ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, ``"ERROR"`` or ``"CRITICAL"``.
debugpath (str): Path to a file where everything (DEBUG) shall be logged in. ``None`` for not such a file.
config: A MusicDB Configuration object that hold the ignore list. If ``None`` the configuration will not be appied.
Returns:
*nothing*
"""
loglevelname = loglevelname.upper()
loglevel = MusicDBLogger.loglevelmap[loglevelname]
# remove old handlers
for h in self.handler:
logging.root.removeHandler(h)
h.flush()
h.close()
self.handler = []
# primary handler
if logpath != None and logpath != "/dev/null":
if logpath == "stdout":
phandler = logging.StreamHandler(sys.stdout)
elif logpath == "stderr":
phandler = logging.StreamHandler(sys.stderr)
elif logpath == "journal":
phandler = journal.JournalHandler(SYSLOG_IDENTIFIER="musicdb")
else:
self.SetFilePermissions(logpath)
phandler = logging.FileHandler(logpath)
phandler.setLevel(loglevel)
self.handler.append(phandler)
# secondary handler
if debugpath != None and debugpath != "/dev/null":
self.SetFilePermissions(debugpath)
shandler = logging.FileHandler(debugpath)
shandler.setLevel(logging.DEBUG)
self.handler.append(shandler)
# configure formatter
if config:
self.formatter = MDBLogFormatter(config.log.ignore)
else:
self.formatter = MDBLogFormatter([])
for h in self.handler:
if h:
h.setFormatter(self.formatter)
# Add handler
for h in self.handler:
if h:
logging.root.addHandler(h)
# Show the user where to find the debugging infos
if debugpath != None and debugpath != "/dev/null":
logging.debug("logging debugging info in %s", debugpath)
logging.debug("setting display-loglevel to \033[1;36m%s", loglevelname)
return
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4