log.py 5.56 KB
import os
import tempfile
import subprocess
import logging
import shutil

from utils.settings_manager import SETTINGS

#tuple in LOG_FILES are (filename, isRotated)
LOG_FILES = {
    "lib videostitch": ("libvideostitch.log", True),
    "python": ("vs_server.log", True),
    "nginx error": ("nginx/nginx_error.log", False),
    "nginx error 2": ("nginx/error.log", False),
    "system/debug": ("debug", True),
    "system/messages": ("messages", False),
    "system/syslog": ("kern.log", False),
    "system/xorg": ("Xorg.0.log", False)
}
VS_LOG_FILE = os.path.join(SETTINGS.log_path, LOG_FILES["lib videostitch"][0])
PYTHON_LOG_FILE = os.path.join(SETTINGS.log_path, LOG_FILES["python"][0])

PACKAGE_FILENAME = "{}_logs.zip"
PACKAGE_PASSWORD = "ICanReadTheLogs"
PACKAGE_COMPRESSION_LEVEL = 5  # 1 (fastest) to 9 (most compressed)
PACKAGE_LOG_MAX_SIZE = 10*1024  # in kB
VERSIONS_FILENAME = "versions.txt"


ROTATE_SIZE = 3

def logrotate(filename):
    try:
        for index in range(9, ROTATE_SIZE, -1):
            new_file_name = filename + "." + str(index)
            if os.path.exists(new_file_name):
                os.remove(new_file_name)
        for index in range(ROTATE_SIZE, 1, -1):
            old_file_name = filename + "." + str(index - 1)
            new_file_name = filename + "." + str(index)
            if os.path.exists(old_file_name):
                os.rename(old_file_name, new_file_name)
        if os.path.exists(filename):
            os.rename(filename, filename + ".1")
    except:
        pass


def set_vs_log(loglevel, rotateAtRuntime=None, message = None):
    """ Sets log level for libvideostitch
    """
    import vs

    loglevels = {
        0: vs.Logger.Error,
        1: vs.Logger.Warning,
        2: vs.Logger.Info,
        3: vs.Logger.Verbose,
        4: vs.Logger.Debug
    }
    if not rotateAtRuntime:
        logrotate(VS_LOG_FILE)
    vs.Logger.setLevel(loglevels[loglevel])
    vs.Logger.setLogFile(VS_LOG_FILE, rotateAtRuntime)
    if message is not None:
        vs.Logger.log(vs.Logger.Info, message)


def set_python_log(loglevel, use_log_file=None, rotateAtRuntime=None):
    """ Redirect the python log to stdio or to a file
    """
    loglevels = {
        0: logging.ERROR,
        1: logging.WARNING,
        2: logging.INFO,
        3: logging.INFO,
        4: logging.DEBUG
    }
    if use_log_file:
        if rotateAtRuntime:
            # Checks if a log has already happened before configuration
            assert (len(logging.getLogger().handlers) == 0)
            fh = logging.handlers.RotatingFileHandler(PYTHON_LOG_FILE, maxBytes=10*1024*1024, backupCount=4)
            fh.setLevel(loglevels[loglevel])
            logging.getLogger().addHandler(fh)
        else:
            logrotate(PYTHON_LOG_FILE)
            logging.basicConfig(filename=PYTHON_LOG_FILE, level=loglevels[loglevel])
    else:
        logging.basicConfig(level=loglevels[loglevel])

    formatter = logging.Formatter("%(asctime)s %(levelname)s [%(name)s][%(threadName)s] %(message)s")
    logging.getLogger().handlers[0].setFormatter(formatter)

def logrotate_previous(base_log_filename):
    return base_log_filename + ".1"

def package_logs(log_file_prefix, versions):
    """ Package all logs for user download
    """
    logs = []
    for logInfo in LOG_FILES.itervalues():
        logs.append(logInfo[0])
        if logInfo[1]:
            logs.append(logrotate_previous(logInfo[0]))

    package_filename = PACKAGE_FILENAME.format(log_file_prefix)

    package_dir = tempfile.mkdtemp()

    # copy logs to temp dir
    copy_and_truncate_logs(logs, package_dir)

    # add a file with version info
    logs.append(create_versions_file(versions, package_dir))

    # create the package
    create_archive(os.path.join(SETTINGS.log_path, package_filename), package_dir, logs)

    return package_filename


def copy_and_truncate_logs(logs, dest_path, truncate=False):
    """
    copy files to dest_path (only last PACKAGE_LOG_MAX_SIZE kilobytes if file is bigger)
    """
    for log_filename in logs:
        file_path = os.path.join(SETTINGS.log_path, log_filename)
        # some of the files might be missing if there was no related error
        if os.path.exists(file_path):
            file_stat = os.stat(file_path)
            tmp_file = os.path.join(dest_path, os.path.basename(log_filename))
            if truncate:
                with open(file_path, 'r') as log_file, \
                        open(tmp_file, 'w') as trunc_file:
                    file_seek = max(file_stat.st_size / 1024 - PACKAGE_LOG_MAX_SIZE + 1, 0)
                    log_file.seek(file_seek * 1024)
                    trunc_file.write(log_file.read())
            else:
                shutil.copyfile(file_path, tmp_file)


def create_versions_file(versions, dest_path):
    version_path = os.path.join(dest_path, VERSIONS_FILENAME)
    with open(version_path, 'w') as version_file:
        for (version_key, version_value) in versions:
            version_file.write(version_key + " : " + version_value + "\r\n")
    return VERSIONS_FILENAME


def create_archive(archive_path, tmp_dir, files):
    # remove an eventual previous archive
    if os.path.exists(archive_path):
        os.remove(archive_path)

    command_line = ['7z', 'a', '-p{}'.format(PACKAGE_PASSWORD), '-y', archive_path]
    for file_name in files:
        file_path = os.path.join(tmp_dir, os.path.basename(file_name))
        if os.path.exists(file_path):
            command_line.append(file_path)
    try:
        subprocess.check_call(command_line)
    except:
        raise Exception("7zip command line failed")
    finally:
        # clean files
        shutil.rmtree(tmp_dir)