#
# Copyright 2021-2022 VMware, Inc.  All rights reserved. -- VMware Confidential
# Description: Provides helper class UtilLog & children.
# Disabled: True
#
#  util_log.py
#

import datetime
import inspect
import logging
import os
import time
import traceback

logger = logging.getLogger(__name__)


class UtilLog(object):
    """
    This class provides utility routines, static now, and classes to aid in
    logging errors and such.
    """

    @staticmethod
    def logException(e, logLevel=logging.ERROR):
        """
        Write the supplied exception's error message and backtrace
        to the log, at the specified log level.

        Parameters
        ----------
        e : Exception
            Any instance based on class Exception.  If this is null we
            are silent.
        logLevel : int
            What level logging must be set to in order to record the
            exception info which we write. (It is otherwise silently
            dropped in the normal logging way.)
        """
        if e:
            logger.log(logLevel, "Exception: %s" % str(e))

            tb_strings = traceback.format_tb(e.__traceback__)
            # (each tb string may itself contain newlines)
            for tb_str in tb_strings:
                for line in tb_str.splitlines():
                    logger.log(logLevel, "  bt: %s" % line)

        return

    @staticmethod
    def setupLogging(fileDir, filePrefix, logLevel, fileSuffix=None,
                     consoleShowLevel=False, consoleLogLevel=None):
        """
        Configure logging so that it writes to a file and optionally also to
        the console. When console output is enabled it has a simpler format
        to improve readability.

        Parameters
        ----------
        fileDir : str
            Directory where logfile should be written. Directory is created
            if it doesn't already exist.
        filePrefix : str
            First part of formed filename which is used for logfile. We
            append a timestamp to this prefix to form the full filename.
        logLevel : logging level constant
            One of logging.DEBUG, logging.INFO, logging.WARNING, etc.
        fileSuffix : str
            Second, optional part of formed filename used for logfile. If this
            value is supplied, it goes between the filePrefix and the
            timestamp. Note that this is _not_ a file extension; we always
            use the extension ".log".
        consoleShowLevel : bool
            If True, include log level in console output formatter. If False,
            display only the log message, rather like print().
        consoleLogLevel : logging level constant
            Same values as logLevel. If consoleLogLevel is specified then this
            value is used for console logging, otherwise console logging uses
            the same logLevel setting used for file logging.

        Returns
        -------
        str
            Full pathname of formed (and already-opened) logfile.
        """

        if not consoleLogLevel:
            consoleLogLevel = logLevel

        if fileDir is None:
            # default to dir containing our caller's module file
            caller_abs_path = os.path.abspath((inspect.stack()[1])[1])
            fileDir = os.path.dirname(caller_abs_path)

        os.makedirs(fileDir, exist_ok=True)

        now = datetime.datetime.now()
        isotime = now.replace(microsecond=0).isoformat()

        if fileSuffix:
            filenameStem = "%s_%s" % (filePrefix, fileSuffix)
        else:
            filenameStem = filePrefix

        logFile = "%s/%s_%s.log" % (fileDir, filenameStem, isotime)

        # We add our handlers to the root logger, so they are available to
        # logging instances allocated in any python file / scope. Each handler
        # can set its own log level, but we must also set level globally here.

        rootLogger = logging.getLogger(name=None)
        rootLogger.setLevel(logLevel)

        # Define a Handler which writes self.logLevel or higher messages
        # to self.logFile

        fileHandler = logging.FileHandler(logFile, mode='w')
        fileHandler.setLevel(logLevel)

        # following format string is per
        #   https://gitlab.eng.vmware.com/core-build/lanier-goat/blob/development/framework/log.py  # noqa
        fileHandler.setFormatter(UtilLog.VmwLogFormatter(
            '%(asctime)s [%(levelname)s %(filename)s::'
            '%(funcName)s::%(lineno)s::%(threadName)s] %(message)s'))

        rootLogger.addHandler(fileHandler)

        # define a Handler which writes self.logLevel messages or higher
        # to sys.stderr

        # set a format which is simpler for console use
        if consoleShowLevel:
            formatter = logging.Formatter('%(levelname)-6s %(message)s')
        else:
            formatter = logging.Formatter('%(message)s')

        console = logging.StreamHandler()
        console.setLevel(consoleLogLevel)
        console.setFormatter(formatter)

        rootLogger.addHandler(console)

        return logFile

    class VmwLogFormatter(logging.Formatter):
        """
        Formatter class for use with file logging handlers. Overloads time
        format while otherwise using standard logging.Formatter methods.

        Uses ISO 8601 date/time format in UTC, augmented with milliseconds,
        a format which is uniform with how almost all VMware components do
        date/time logging (such as the vmkernel, vmx, vmm, hostd, vpxd,
        etc.)

        The qe.* Java test-vpx tests are already outputting in this
        format, this covers the python side of things.

        Without this, logging.Formatter claims to use ISO 8601, but it
        isn't really: it's only ISO 8601 inspired, but looks nothing like
        it (eg, 2014-04-17 17:23:00,123).

        This code copied directly from
        https://gitlab.eng.vmware.com/core-build/lanier-goat/blob/development/framework/log.py  # noqa
        """

        def formatTime(self, record, datefmt=None):
            return '%s.%03dZ' % (time.strftime('%Y-%m-%dT%H:%M:%S',
                                               time.gmtime(record.created)),
                                 record.msecs)
