# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

from logging import getLogger as getSysLogger
from logging import *
# Some of the build slave environments don't see the following when doing
# 'from logging import *'
# see https://bugzilla.mozilla.org/show_bug.cgi?id=700415#c35
from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig
import json

_default_level = INFO
_LoggerClass = getLoggerClass()

# Define mozlog specific log levels
START      = _default_level + 1
END        = _default_level + 2
PASS       = _default_level + 3
KNOWN_FAIL = _default_level + 4
FAIL       = _default_level + 5
CRASH      = _default_level + 6
# Define associated text of log levels
addLevelName(START, 'TEST-START')
addLevelName(END, 'TEST-END')
addLevelName(PASS, 'TEST-PASS')
addLevelName(KNOWN_FAIL, 'TEST-KNOWN-FAIL')
addLevelName(FAIL, 'TEST-UNEXPECTED-FAIL')
addLevelName(CRASH, 'PROCESS-CRASH')

class MozLogger(_LoggerClass):
    """
    MozLogger class which adds some convenience log levels
    related to automated testing in Mozilla and ability to
    output structured log messages.
    """
    def testStart(self, message, *args, **kwargs):
        """Logs a test start message"""
        self.log(START, message, *args, **kwargs)

    def testEnd(self, message, *args, **kwargs):
        """Logs a test end message"""
        self.log(END, message, *args, **kwargs)

    def testPass(self, message, *args, **kwargs):
        """Logs a test pass message"""
        self.log(PASS, message, *args, **kwargs)

    def testFail(self, message, *args, **kwargs):
        """Logs a test fail message"""
        self.log(FAIL, message, *args, **kwargs)

    def testKnownFail(self, message, *args, **kwargs):
        """Logs a test known fail message"""
        self.log(KNOWN_FAIL, message, *args, **kwargs)

    def processCrash(self, message, *args, **kwargs):
        """Logs a process crash message"""
        self.log(CRASH, message, *args, **kwargs)

    def log_structured(self, action, params=None):
        """Logs a structured message object."""
        if params is None:
            params = {}

        level = params.get('_level', _default_level)
        if isinstance(level, int):
            params['_level'] = getLevelName(level)
        else:
            params['_level'] = level
            level = getLevelName(level.upper())

            # If the logger is fed a level number unknown to the logging
            # module, getLevelName will return a string. Unfortunately,
            # the logging module will raise a type error elsewhere if
            # the level is not an integer.
            if not isinstance(level, int):
                level = _default_level

        params['action'] = action

        # The can message be None. This is expected, and shouldn't cause
        # unstructured formatters to fail.
        message = params.get('_message')

        self.log(level, message, extra={'params': params})

class JSONFormatter(Formatter):
    """Log formatter for emitting structured JSON entries."""

    def format(self, record):
        # Default values determined by logger metadata
        output = {
            '_time': int(round(record.created * 1000, 0)),
            '_namespace': record.name,
            '_level': getLevelName(record.levelno),
        }

        # If this message was created by a call to log_structured,
        # anything specified by the caller's params should act as
        # an override.
        output.update(getattr(record, 'params', {}))

        if record.msg and output.get('_message') is None:
            # For compatibility with callers using the printf like
            # API exposed by python logging, call the default formatter.
            output['_message'] = Formatter.format(self, record)

        return json.dumps(output, indent=output.get('indent'))

class MozFormatter(Formatter):
    """
    MozFormatter class used to standardize formatting
    If a different format is desired, this can be explicitly
    overriden with the log handler's setFormatter() method
    """
    level_length = 0
    max_level_length = len('TEST-START')

    def __init__(self, include_timestamp=False):
        """
        Formatter.__init__ has fmt and datefmt parameters that won't have
        any affect on a MozFormatter instance.

        :param include_timestamp: if True, include formatted time at the
                                  beginning of the message
        """
        self.include_timestamp = include_timestamp
        Formatter.__init__(self)

    def format(self, record):
        # Handles padding so record levels align nicely
        if len(record.levelname) > self.level_length:
            pad = 0
            if len(record.levelname) <= self.max_level_length:
                self.level_length = len(record.levelname)
        else:
            pad = self.level_length - len(record.levelname) + 1
        sep = '|'.rjust(pad)
        fmt = '%(name)s %(levelname)s ' + sep + ' %(message)s'
        if self.include_timestamp:
            fmt = '%(asctime)s ' + fmt
        # this protected member is used to define the format
        # used by the base Formatter's method
        self._fmt = fmt
        return Formatter.format(self, record)

def getLogger(name, handler=None):
    """
    Returns the logger with the specified name.
    If the logger doesn't exist, it is created.
    If handler is specified, adds it to the logger. Otherwise a default handler
    that logs to standard output will be used.

    :param name: The name of the logger to retrieve
    :param handler: A handler to add to the logger. If the logger already exists,
                    and a handler is specified, an exception will be raised. To
                    add a handler to an existing logger, call that logger's
                    addHandler method.
    """
    setLoggerClass(MozLogger)

    if name in Logger.manager.loggerDict:
        if handler:
            raise ValueError('The handler parameter requires ' + \
                             'that a logger by this name does ' + \
                             'not already exist')
        return Logger.manager.loggerDict[name]

    logger = getSysLogger(name)
    logger.setLevel(_default_level)

    if handler is None:
        handler = StreamHandler()
        handler.setFormatter(MozFormatter())

    logger.addHandler(handler)
    logger.propagate = False
    return logger


[(14, 'logging.getLoggerClass', 'getLoggerClass', False, 'from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig\n'), (24, 'logging.addLevelName', 'addLevelName', False, 'from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig\n'), (25, 'logging.addLevelName', 'addLevelName', False, 'from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig\n'), (26, 'logging.addLevelName', 'addLevelName', False, 'from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig\n'), (27, 'logging.addLevelName', 'addLevelName', False, 'from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig\n'), (28, 'logging.addLevelName', 'addLevelName', False, 'from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig\n'), (29, 'logging.addLevelName', 'addLevelName', False, 'from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig\n'), (161, 'logging.setLoggerClass', 'setLoggerClass', False, 'from logging import getLoggerClass, addLevelName, setLoggerClass, shutdown, debug, info, basicConfig\n'), (170, 'logging.getLogger', 'getSysLogger', True, 'from logging import getLogger as getSysLogger\n')]